From 6794166b58a5b75bed224efb8802e0de68db5ceb Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Thu, 20 Feb 2014 17:19:51 -0500 Subject: [PATCH] tokens/access/codes now uses basic auth --- .../org/keycloak/util/BasicAuthHelper.java | 44 +++++++++++++++++++ .../keycloak/adapters/TokenGrantRequest.java | 8 +++- .../META-INF/resources/js/keycloak.js | 4 +- .../managers/AuthenticationManager.java | 1 + .../services/resources/TokenService.java | 41 +++++++++-------- .../org/keycloak/testsuite/OAuthClient.java | 10 +++-- 6 files changed, 83 insertions(+), 25 deletions(-) create mode 100755 core/src/main/java/org/keycloak/util/BasicAuthHelper.java mode change 100644 => 100755 integration/js/src/main/resources/META-INF/resources/js/keycloak.js diff --git a/core/src/main/java/org/keycloak/util/BasicAuthHelper.java b/core/src/main/java/org/keycloak/util/BasicAuthHelper.java new file mode 100755 index 0000000000..88065424b9 --- /dev/null +++ b/core/src/main/java/org/keycloak/util/BasicAuthHelper.java @@ -0,0 +1,44 @@ +package org.keycloak.util; + +import net.iharder.Base64; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class BasicAuthHelper +{ + public static String createHeader(String username, String password) + { + StringBuffer buf = new StringBuffer(username); + buf.append(':').append(password); + try + { + return "Basic " + Base64.encodeBytes(buf.toString().getBytes("UTF-8")); + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + } + + public static String[] parseHeader(String header) + { + if (header.length() < 6) return null; + String type = header.substring(0, 5); + type = type.toLowerCase(); + if (!type.equalsIgnoreCase("Basic")) return null; + String val = header.substring(6); + try { + val = new String(Base64.decode(val.getBytes())); + } catch (IOException e) { + throw new RuntimeException(e); + } + String[] split = val.split(":"); + if (split.length != 2) return null; + return split; + } +} diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/TokenGrantRequest.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/TokenGrantRequest.java index da49e8853d..4f3d2ffff6 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/TokenGrantRequest.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/TokenGrantRequest.java @@ -10,6 +10,7 @@ import org.apache.http.message.BasicNameValuePair; import org.keycloak.adapters.config.RealmConfiguration; import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.util.BasicAuthHelper; import org.keycloak.util.JsonSerialization; import org.keycloak.util.KeycloakUriBuilder; import org.keycloak.util.StreamUtil; @@ -62,11 +63,16 @@ public class TokenGrantRequest { } formparams.add(new BasicNameValuePair("grant_type", "authorization_code")); formparams.add(new BasicNameValuePair("code", code)); - formparams.add(new BasicNameValuePair("client_id", client_id)); formparams.add(new BasicNameValuePair("redirect_uri", redirectUri)); HttpResponse response = null; UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8"); HttpPost post = new HttpPost(codeUrl); + String clientSecret = credentials.get(CredentialRepresentation.SECRET); + if (clientSecret != null) { + String authorization = BasicAuthHelper.createHeader(client_id, clientSecret); + post.setHeader("Authorization", authorization); + } + post.setEntity(form); response = client.execute(post); int status = response.getStatusLine().getStatusCode(); diff --git a/integration/js/src/main/resources/META-INF/resources/js/keycloak.js b/integration/js/src/main/resources/META-INF/resources/js/keycloak.js old mode 100644 new mode 100755 index 5222953f3d..077e852f1a --- a/integration/js/src/main/resources/META-INF/resources/js/keycloak.js +++ b/integration/js/src/main/resources/META-INF/resources/js/keycloak.js @@ -111,11 +111,11 @@ var Keycloak = function (options) { var prompt = window.oauth.prompt; if (code) { - var params = 'code=' + code + '&client_id=' + encodeURIComponent(options.clientId) + '&secret=' + encodeURIComponent(options.clientSecret); + var params = 'code=' + code; var url = getRealmUrl() + '/tokens/access/codes'; var req = new XMLHttpRequest(); - req.open('POST', url, true); + req.open('POST', url, true, options.clientId, options.clientSecret); req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); req.onreadystatechange = function () { diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index 5805ba4071..08abe26844 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -245,6 +245,7 @@ public class AuthenticationManager { } } + public AuthenticationStatus authenticateForm(RealmModel realm, UserModel user, MultivaluedMap formData) { if (user == null) { logger.debug("Not Authenticated! Incorrect user name"); diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java index 32637153f9..7279f45116 100755 --- a/services/src/main/java/org/keycloak/services/resources/TokenService.java +++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java @@ -28,9 +28,11 @@ import org.keycloak.services.messages.Messages; import org.keycloak.services.resources.flows.Flows; import org.keycloak.services.resources.flows.OAuthFlows; import org.keycloak.services.validation.Validation; +import org.keycloak.util.BasicAuthHelper; import javax.ws.rs.Consumes; import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; import javax.ws.rs.NotAcceptableException; import javax.ws.rs.NotAuthorizedException; import javax.ws.rs.POST; @@ -326,7 +328,7 @@ public class TokenService { @Path("access/codes") @POST @Produces("application/json") - public Response accessCodeToToken(final MultivaluedMap formData) { + public Response accessCodeToToken(@HeaderParam(HttpHeaders.AUTHORIZATION) String authorizationHeader, final MultivaluedMap formData) { logger.debug("accessRequest <---"); if (!checkSsl()) { @@ -337,23 +339,17 @@ public class TokenService { throw new NotAuthorizedException("Realm not enabled"); } - String code = formData.getFirst("code"); - if (code == null) { - logger.debug("code not specified"); - Map error = new HashMap(); - error.put("error", "invalid_request"); - error.put("error_description", "code not specified"); - return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build(); + if (authorizationHeader == null) { + throw new NotAuthorizedException("No Authorization header to authenticate client", "Basic realm=\"" + realm.getName() + "\""); + } + String[] usernameSecret = BasicAuthHelper.parseHeader(authorizationHeader); + if (usernameSecret == null) { + throw new NotAuthorizedException("No Authorization header to authenticate client", "Basic realm=\"" + realm.getName() + "\""); } - String client_id = formData.getFirst("client_id"); - if (client_id == null) { - logger.debug("client_id not specified"); - Map error = new HashMap(); - error.put("error", "invalid_request"); - error.put("error_description", "client_id not specified"); - return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build(); - } + + String client_id = usernameSecret[0]; + String clientSecret = usernameSecret[1]; UserModel client = realm.getUser(client_id); if (client == null) { logger.debug("Could not find user"); @@ -371,13 +367,22 @@ public class TokenService { return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build(); } - AuthenticationStatus status = authManager.authenticateForm(realm, client, formData); - if (status != AuthenticationStatus.SUCCESS) { + if (!realm.validateSecret(client, clientSecret)) { Map error = new HashMap(); error.put("error", "unauthorized_client"); return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build(); } + String code = formData.getFirst("code"); + if (code == null) { + logger.debug("code not specified"); + Map error = new HashMap(); + error.put("error", "invalid_request"); + error.put("error_description", "code not specified"); + return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build(); + + } + JWSInput input = new JWSInput(code); boolean verifiedCode = false; try { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java index a68ffad59f..e92b223fde 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java @@ -35,6 +35,7 @@ import org.json.JSONObject; import org.junit.Assert; import org.keycloak.RSATokenVerifier; import org.keycloak.VerificationException; +import org.keycloak.util.BasicAuthHelper; import org.keycloak.util.JsonSerialization; import org.keycloak.representations.SkeletonKeyScope; import org.keycloak.representations.SkeletonKeyToken; @@ -121,11 +122,12 @@ public class OAuthClient { if (redirectUri != null) { parameters.add(new BasicNameValuePair("redirect_uri", redirectUri)); } - if (clientId != null) { - parameters.add(new BasicNameValuePair("client_id", clientId)); + if (clientId != null && password != null) { + String authorization = BasicAuthHelper.createHeader(clientId, password); + post.setHeader("Authorization", authorization); } - if (password != null) { - parameters.add(new BasicNameValuePair("secret", password)); + else if (clientId != null) { + parameters.add(new BasicNameValuePair("client_id", clientId)); } UrlEncodedFormEntity formEntity = null;