Merge pull request #229 from patriot1burke/master

tokens/access/codes now uses basic auth
This commit is contained in:
Bill Burke 2014-02-20 17:20:06 -05:00
commit dfacae9beb
6 changed files with 83 additions and 25 deletions

View file

@ -0,0 +1,44 @@
package org.keycloak.util;
import net.iharder.Base64;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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;
}
}

View file

@ -10,6 +10,7 @@ import org.apache.http.message.BasicNameValuePair;
import org.keycloak.adapters.config.RealmConfiguration; import org.keycloak.adapters.config.RealmConfiguration;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.util.BasicAuthHelper;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import org.keycloak.util.KeycloakUriBuilder; import org.keycloak.util.KeycloakUriBuilder;
import org.keycloak.util.StreamUtil; import org.keycloak.util.StreamUtil;
@ -62,11 +63,16 @@ public class TokenGrantRequest {
} }
formparams.add(new BasicNameValuePair("grant_type", "authorization_code")); formparams.add(new BasicNameValuePair("grant_type", "authorization_code"));
formparams.add(new BasicNameValuePair("code", code)); formparams.add(new BasicNameValuePair("code", code));
formparams.add(new BasicNameValuePair("client_id", client_id));
formparams.add(new BasicNameValuePair("redirect_uri", redirectUri)); formparams.add(new BasicNameValuePair("redirect_uri", redirectUri));
HttpResponse response = null; HttpResponse response = null;
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8"); UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
HttpPost post = new HttpPost(codeUrl); 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); post.setEntity(form);
response = client.execute(post); response = client.execute(post);
int status = response.getStatusLine().getStatusCode(); int status = response.getStatusLine().getStatusCode();

View file

@ -111,11 +111,11 @@ var Keycloak = function (options) {
var prompt = window.oauth.prompt; var prompt = window.oauth.prompt;
if (code) { 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 url = getRealmUrl() + '/tokens/access/codes';
var req = new XMLHttpRequest(); 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.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
req.onreadystatechange = function () { req.onreadystatechange = function () {

View file

@ -245,6 +245,7 @@ public class AuthenticationManager {
} }
} }
public AuthenticationStatus authenticateForm(RealmModel realm, UserModel user, MultivaluedMap<String, String> formData) { public AuthenticationStatus authenticateForm(RealmModel realm, UserModel user, MultivaluedMap<String, String> formData) {
if (user == null) { if (user == null) {
logger.debug("Not Authenticated! Incorrect user name"); logger.debug("Not Authenticated! Incorrect user name");

View file

@ -28,9 +28,11 @@ import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.flows.Flows; import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.resources.flows.OAuthFlows; import org.keycloak.services.resources.flows.OAuthFlows;
import org.keycloak.services.validation.Validation; import org.keycloak.services.validation.Validation;
import org.keycloak.util.BasicAuthHelper;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.NotAcceptableException; import javax.ws.rs.NotAcceptableException;
import javax.ws.rs.NotAuthorizedException; import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.POST; import javax.ws.rs.POST;
@ -326,7 +328,7 @@ public class TokenService {
@Path("access/codes") @Path("access/codes")
@POST @POST
@Produces("application/json") @Produces("application/json")
public Response accessCodeToToken(final MultivaluedMap<String, String> formData) { public Response accessCodeToToken(@HeaderParam(HttpHeaders.AUTHORIZATION) String authorizationHeader, final MultivaluedMap<String, String> formData) {
logger.debug("accessRequest <---"); logger.debug("accessRequest <---");
if (!checkSsl()) { if (!checkSsl()) {
@ -337,23 +339,17 @@ public class TokenService {
throw new NotAuthorizedException("Realm not enabled"); throw new NotAuthorizedException("Realm not enabled");
} }
String code = formData.getFirst("code"); if (authorizationHeader == null) {
if (code == null) { throw new NotAuthorizedException("No Authorization header to authenticate client", "Basic realm=\"" + realm.getName() + "\"");
logger.debug("code not specified"); }
Map<String, String> error = new HashMap<String, String>();
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();
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) { String client_id = usernameSecret[0];
logger.debug("client_id not specified"); String clientSecret = usernameSecret[1];
Map<String, String> error = new HashMap<String, String>();
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();
}
UserModel client = realm.getUser(client_id); UserModel client = realm.getUser(client_id);
if (client == null) { if (client == null) {
logger.debug("Could not find user"); 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(); return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build();
} }
AuthenticationStatus status = authManager.authenticateForm(realm, client, formData); if (!realm.validateSecret(client, clientSecret)) {
if (status != AuthenticationStatus.SUCCESS) {
Map<String, String> error = new HashMap<String, String>(); Map<String, String> error = new HashMap<String, String>();
error.put("error", "unauthorized_client"); error.put("error", "unauthorized_client");
return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build(); 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<String, String> error = new HashMap<String, String>();
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); JWSInput input = new JWSInput(code);
boolean verifiedCode = false; boolean verifiedCode = false;
try { try {

View file

@ -35,6 +35,7 @@ import org.json.JSONObject;
import org.junit.Assert; import org.junit.Assert;
import org.keycloak.RSATokenVerifier; import org.keycloak.RSATokenVerifier;
import org.keycloak.VerificationException; import org.keycloak.VerificationException;
import org.keycloak.util.BasicAuthHelper;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import org.keycloak.representations.SkeletonKeyScope; import org.keycloak.representations.SkeletonKeyScope;
import org.keycloak.representations.SkeletonKeyToken; import org.keycloak.representations.SkeletonKeyToken;
@ -121,11 +122,12 @@ public class OAuthClient {
if (redirectUri != null) { if (redirectUri != null) {
parameters.add(new BasicNameValuePair("redirect_uri", redirectUri)); parameters.add(new BasicNameValuePair("redirect_uri", redirectUri));
} }
if (clientId != null) { if (clientId != null && password != null) {
parameters.add(new BasicNameValuePair("client_id", clientId)); String authorization = BasicAuthHelper.createHeader(clientId, password);
post.setHeader("Authorization", authorization);
} }
if (password != null) { else if (clientId != null) {
parameters.add(new BasicNameValuePair("secret", password)); parameters.add(new BasicNameValuePair("client_id", clientId));
} }
UrlEncodedFormEntity formEntity = null; UrlEncodedFormEntity formEntity = null;