[KEYCLOAK-11330] - Properly handling POST formdata and UriInfo
This commit is contained in:
parent
90b29b0e31
commit
e8dc10b4a1
7 changed files with 52 additions and 29 deletions
|
@ -28,6 +28,7 @@ import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
|
|
||||||
|
import javax.ws.rs.HttpMethod;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -97,6 +98,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
|
||||||
throw new AuthenticationFlowException("Execution not found", AuthenticationFlowError.INTERNAL_ERROR);
|
throw new AuthenticationFlowException("Execution not found", AuthenticationFlowError.INTERNAL_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (HttpMethod.POST.equals(processor.getRequest().getHttpMethod())) {
|
||||||
MultivaluedMap<String, String> inputData = processor.getRequest().getDecodedFormParameters();
|
MultivaluedMap<String, String> inputData = processor.getRequest().getDecodedFormParameters();
|
||||||
String authExecId = inputData.getFirst(Constants.AUTHENTICATION_EXECUTION);
|
String authExecId = inputData.getFirst(Constants.AUTHENTICATION_EXECUTION);
|
||||||
|
|
||||||
|
@ -120,7 +122,8 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
|
||||||
selectionOptions.stream()
|
selectionOptions.stream()
|
||||||
.filter(authSelectionOption -> authExecId.equals(authSelectionOption.getAuthExecId()))
|
.filter(authSelectionOption -> authExecId.equals(authSelectionOption.getAuthExecId()))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow(() -> new AuthenticationFlowException("Requested authentication execution is not allowed", AuthenticationFlowError.INTERNAL_ERROR)
|
.orElseThrow(() -> new AuthenticationFlowException("Requested authentication execution is not allowed",
|
||||||
|
AuthenticationFlowError.INTERNAL_ERROR)
|
||||||
);
|
);
|
||||||
|
|
||||||
model = processor.getRealm().getAuthenticationExecutionById(authExecId);
|
model = processor.getRealm().getAuthenticationExecutionById(authExecId);
|
||||||
|
@ -128,7 +131,9 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
|
||||||
Response response = processSingleFlowExecutionModel(model, false);
|
Response response = processSingleFlowExecutionModel(model, false);
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
return continueAuthenticationAfterSuccessfulAction(model);
|
return continueAuthenticationAfterSuccessfulAction(model);
|
||||||
} else return response;
|
} else
|
||||||
|
return response;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//handle case where execution is a flow - This can happen during user registration for example
|
//handle case where execution is a flow - This can happen during user registration for example
|
||||||
|
|
|
@ -53,7 +53,7 @@ public class X509ClientAuthenticator extends AbstractClientAuthenticator {
|
||||||
boolean hasFormData = mediaType != null && mediaType.isCompatible(MediaType.APPLICATION_FORM_URLENCODED_TYPE);
|
boolean hasFormData = mediaType != null && mediaType.isCompatible(MediaType.APPLICATION_FORM_URLENCODED_TYPE);
|
||||||
|
|
||||||
MultivaluedMap<String, String> formData = hasFormData ? context.getHttpRequest().getDecodedFormParameters() : null;
|
MultivaluedMap<String, String> formData = hasFormData ? context.getHttpRequest().getDecodedFormParameters() : null;
|
||||||
MultivaluedMap<String, String> queryParams = context.getHttpRequest().getUri().getQueryParameters();
|
MultivaluedMap<String, String> queryParams = context.getSession().getContext().getUri().getQueryParameters();
|
||||||
|
|
||||||
if (formData != null) {
|
if (formData != null) {
|
||||||
client_id = formData.getFirst(OAuth2Constants.CLIENT_ID);
|
client_id = formData.getFirst(OAuth2Constants.CLIENT_ID);
|
||||||
|
|
|
@ -91,12 +91,14 @@ import org.keycloak.sessions.RootAuthenticationSessionModel;
|
||||||
import org.keycloak.util.TokenUtil;
|
import org.keycloak.util.TokenUtil;
|
||||||
import org.keycloak.utils.ProfileHelper;
|
import org.keycloak.utils.ProfileHelper;
|
||||||
|
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.OPTIONS;
|
import javax.ws.rs.OPTIONS;
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.MultivaluedHashMap;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.Response.Status;
|
import javax.ws.rs.core.Response.Status;
|
||||||
|
@ -160,11 +162,18 @@ public class TokenEndpoint {
|
||||||
this.event = event;
|
this.event = event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
@POST
|
@POST
|
||||||
public Response processGrantRequest() {
|
public Response processGrantRequest() {
|
||||||
cors = Cors.add(request).auth().allowedMethods("POST").auth().exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS);
|
cors = Cors.add(request).auth().allowedMethods("POST").auth().exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS);
|
||||||
|
|
||||||
formParams = request.getDecodedFormParameters();
|
MultivaluedMap<String, String> formParameters = request.getDecodedFormParameters();
|
||||||
|
|
||||||
|
if (formParameters == null) {
|
||||||
|
formParameters = new MultivaluedHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
formParams = formParameters;
|
||||||
grantType = formParams.getFirst(OIDCLoginProtocol.GRANT_TYPE_PARAM);
|
grantType = formParams.getFirst(OIDCLoginProtocol.GRANT_TYPE_PARAM);
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc6749#section-5.1
|
// https://tools.ietf.org/html/rfc6749#section-5.1
|
||||||
|
|
|
@ -35,6 +35,7 @@ import javax.ws.rs.NotAuthorizedException;
|
||||||
import javax.ws.rs.NotFoundException;
|
import javax.ws.rs.NotFoundException;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -61,10 +62,11 @@ public class AccountLoader {
|
||||||
|
|
||||||
Theme theme = getTheme(session);
|
Theme theme = getTheme(session);
|
||||||
boolean deprecatedAccount = isDeprecatedFormsAccountConsole(theme);
|
boolean deprecatedAccount = isDeprecatedFormsAccountConsole(theme);
|
||||||
|
UriInfo uriInfo = session.getContext().getUri();
|
||||||
|
|
||||||
if (request.getHttpMethod().equals(HttpMethod.OPTIONS)) {
|
if (request.getHttpMethod().equals(HttpMethod.OPTIONS)) {
|
||||||
return new CorsPreflightService(request);
|
return new CorsPreflightService(request);
|
||||||
} else if ((accepts.contains(MediaType.APPLICATION_JSON_TYPE) || MediaType.APPLICATION_JSON_TYPE.equals(content)) && !request.getUri().getPath().endsWith("keycloak.json")) {
|
} else if ((accepts.contains(MediaType.APPLICATION_JSON_TYPE) || MediaType.APPLICATION_JSON_TYPE.equals(content)) && !uriInfo.getPath().endsWith("keycloak.json")) {
|
||||||
AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(session);
|
AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(session);
|
||||||
if (authResult == null) {
|
if (authResult == null) {
|
||||||
throw new NotAuthorizedException("Bearer token required");
|
throw new NotAuthorizedException("Bearer token required");
|
||||||
|
|
|
@ -35,6 +35,7 @@ import org.keycloak.authorization.store.ResourceStore;
|
||||||
import org.keycloak.authorization.store.ScopeStore;
|
import org.keycloak.authorization.store.ScopeStore;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakUriInfo;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
@ -53,6 +54,7 @@ public abstract class AbstractResourceService {
|
||||||
protected final PermissionTicketStore ticketStore;
|
protected final PermissionTicketStore ticketStore;
|
||||||
protected final ResourceStore resourceStore;
|
protected final ResourceStore resourceStore;
|
||||||
protected final ScopeStore scopeStore;
|
protected final ScopeStore scopeStore;
|
||||||
|
protected final KeycloakUriInfo uriInfo;
|
||||||
protected HttpRequest request;
|
protected HttpRequest request;
|
||||||
protected Auth auth;
|
protected Auth auth;
|
||||||
|
|
||||||
|
@ -64,6 +66,7 @@ public abstract class AbstractResourceService {
|
||||||
ticketStore = provider.getStoreFactory().getPermissionTicketStore();
|
ticketStore = provider.getStoreFactory().getPermissionTicketStore();
|
||||||
resourceStore = provider.getStoreFactory().getResourceStore();
|
resourceStore = provider.getStoreFactory().getResourceStore();
|
||||||
scopeStore = provider.getStoreFactory().getScopeStore();
|
scopeStore = provider.getStoreFactory().getScopeStore();
|
||||||
|
uriInfo = session.getContext().getUri();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Response cors(Response.ResponseBuilder response) {
|
protected Response cors(Response.ResponseBuilder response) {
|
||||||
|
|
|
@ -219,14 +219,14 @@ public class ResourcesService extends AbstractResourceService {
|
||||||
|
|
||||||
if (nextPage) {
|
if (nextPage) {
|
||||||
links.add(Link.fromUri(
|
links.add(Link.fromUri(
|
||||||
KeycloakUriBuilder.fromUri(request.getUri().getRequestUri()).replaceQuery("first={first}&max={max}")
|
KeycloakUriBuilder.fromUri(uriInfo.getRequestUri()).replaceQuery("first={first}&max={max}")
|
||||||
.build(first + max, max))
|
.build(first + max, max))
|
||||||
.rel("next").build());
|
.rel("next").build());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (first > 0) {
|
if (first > 0) {
|
||||||
links.add(Link.fromUri(
|
links.add(Link.fromUri(
|
||||||
KeycloakUriBuilder.fromUri(request.getUri().getRequestUri()).replaceQuery("first={first}&max={max}")
|
KeycloakUriBuilder.fromUri(uriInfo.getRequestUri()).replaceQuery("first={first}&max={max}")
|
||||||
.build(Math.max(first - max, 0), max))
|
.build(Math.max(first - max, 0), max))
|
||||||
.rel("prev").build());
|
.rel("prev").build());
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,9 @@ import java.util.List;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
|
@ -610,6 +613,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest extends AbstractKeycloakT
|
||||||
|
|
||||||
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
|
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
|
||||||
HttpPost post = new HttpPost(oauth.getResourceOwnerPasswordCredentialGrantUrl());
|
HttpPost post = new HttpPost(oauth.getResourceOwnerPasswordCredentialGrantUrl());
|
||||||
|
post.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
OAuthClient.AccessTokenResponse response = new OAuthClient.AccessTokenResponse(client.execute(post));
|
OAuthClient.AccessTokenResponse response = new OAuthClient.AccessTokenResponse(client.execute(post));
|
||||||
|
|
||||||
assertEquals(400, response.getStatusCode());
|
assertEquals(400, response.getStatusCode());
|
||||||
|
|
Loading…
Reference in a new issue