KEYCLOAK-6718 Auth Flow does not Check Client Protocol
This commit is contained in:
parent
c3fc9e9815
commit
efe6a38648
18 changed files with 386 additions and 24 deletions
|
@ -201,6 +201,16 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
|
|||
throw new ErrorPageException(session, authenticationSession, Response.Status.FORBIDDEN, Messages.BEARER_ONLY);
|
||||
}
|
||||
|
||||
String protocol = client.getProtocol();
|
||||
if (protocol == null) {
|
||||
logger.warnf("Client '%s' doesn't have protocol set. Fallback to openid-connect. Please fix client configuration", clientId);
|
||||
protocol = OIDCLoginProtocol.LOGIN_PROTOCOL;
|
||||
}
|
||||
if (!protocol.equals(OIDCLoginProtocol.LOGIN_PROTOCOL)) {
|
||||
event.error(Errors.INVALID_CLIENT);
|
||||
throw new ErrorPageException(session, authenticationSession, Response.Status.BAD_REQUEST, "Wrong client protocol.");
|
||||
}
|
||||
|
||||
session.getContext().setClient(client);
|
||||
}
|
||||
|
||||
|
|
|
@ -218,7 +218,7 @@ public class LogoutEndpoint {
|
|||
ClientModel client = AuthorizeClientUtil.authorizeClient(session, event).getClient();
|
||||
|
||||
if (client.isBearerOnly()) {
|
||||
throw new ErrorResponseException("invalid_client", "Bearer-only not allowed", Response.Status.BAD_REQUEST);
|
||||
throw new ErrorResponseException(Errors.INVALID_CLIENT, "Bearer-only not allowed", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
return client;
|
||||
|
|
|
@ -41,6 +41,7 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.services.ErrorResponseException;
|
||||
import org.keycloak.services.Urls;
|
||||
|
@ -149,6 +150,11 @@ public class UserInfoEndpoint {
|
|||
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Client not found", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (!clientModel.getProtocol().equals(OIDCLoginProtocol.LOGIN_PROTOCOL)) {
|
||||
event.error(Errors.INVALID_CLIENT);
|
||||
throw new ErrorResponseException(Errors.INVALID_CLIENT, "Wrong client protocol.", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
session.getContext().setClient(clientModel);
|
||||
|
||||
event.client(clientModel);
|
||||
|
|
|
@ -17,10 +17,12 @@
|
|||
|
||||
package org.keycloak.protocol.oidc.utils;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.authentication.AuthenticationProcessor;
|
||||
import org.keycloak.authentication.ClientAuthenticator;
|
||||
import org.keycloak.authentication.ClientAuthenticatorFactory;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.models.AuthenticationFlowModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
|
@ -40,6 +42,8 @@ import java.util.Map;
|
|||
*/
|
||||
public class AuthorizeClientUtil {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(AuthorizeClientUtil.class);
|
||||
|
||||
public static ClientAuthResult authorizeClient(KeycloakSession session, EventBuilder event) {
|
||||
AuthenticationProcessor processor = getAuthenticationProcessor(session, event);
|
||||
|
||||
|
@ -50,7 +54,18 @@ public class AuthorizeClientUtil {
|
|||
|
||||
ClientModel client = processor.getClient();
|
||||
if (client == null) {
|
||||
throw new ErrorResponseException("invalid_client", "Client authentication ended, but client is null", Response.Status.BAD_REQUEST);
|
||||
throw new ErrorResponseException(Errors.INVALID_CLIENT, "Client authentication ended, but client is null", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
String protocol = client.getProtocol();
|
||||
if (protocol == null) {
|
||||
logger.warnf("Client '%s' doesn't have protocol set. Fallback to openid-connect. Please fix client configuration", client.getClientId());
|
||||
protocol = OIDCLoginProtocol.LOGIN_PROTOCOL;
|
||||
}
|
||||
|
||||
if (!protocol.equals(OIDCLoginProtocol.LOGIN_PROTOCOL)) {
|
||||
event.error(Errors.INVALID_CLIENT);
|
||||
throw new ErrorResponseException(Errors.INVALID_CLIENT, "Wrong client protocol.", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
session.getContext().setClient(client);
|
||||
|
|
|
@ -175,6 +175,13 @@ public class SamlService extends AuthorizationEndpointBase {
|
|||
event.error(Errors.CLIENT_NOT_FOUND);
|
||||
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.CLIENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!isClientProtocolCorrect(client)) {
|
||||
event.event(EventType.LOGOUT);
|
||||
event.error(Errors.INVALID_CLIENT);
|
||||
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, "Wrong client protocol.");
|
||||
}
|
||||
|
||||
session.getContext().setClient(client);
|
||||
logger.debug("logout response");
|
||||
Response response = authManager.browserLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers);
|
||||
|
@ -225,6 +232,11 @@ public class SamlService extends AuthorizationEndpointBase {
|
|||
event.error(Errors.NOT_ALLOWED);
|
||||
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.STANDARD_FLOW_DISABLED);
|
||||
}
|
||||
if (!isClientProtocolCorrect(client)) {
|
||||
event.event(EventType.LOGIN);
|
||||
event.error(Errors.INVALID_CLIENT);
|
||||
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, "Wrong client protocol.");
|
||||
}
|
||||
|
||||
session.getContext().setClient(client);
|
||||
|
||||
|
@ -607,6 +619,14 @@ public class SamlService extends AuthorizationEndpointBase {
|
|||
key.getKid(), PemUtils.encodeCertificate(key.getCertificate()), purpose, false));
|
||||
}
|
||||
|
||||
private boolean isClientProtocolCorrect(ClientModel clientModel) {
|
||||
if (SamlProtocol.LOGIN_PROTOCOL.equals(clientModel.getProtocol())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("clients/{client}")
|
||||
@Produces(MediaType.TEXT_HTML_UTF_8)
|
||||
|
@ -631,6 +651,10 @@ public class SamlService extends AuthorizationEndpointBase {
|
|||
event.error(Errors.CLIENT_DISABLED);
|
||||
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.CLIENT_DISABLED);
|
||||
}
|
||||
if (!isClientProtocolCorrect(client)) {
|
||||
event.error(Errors.INVALID_CLIENT);
|
||||
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, "Wrong client protocol.");
|
||||
}
|
||||
if (client.getManagementUrl() == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE) == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE) == null) {
|
||||
logger.error("SAML assertion consumer url not set up");
|
||||
event.error(Errors.INVALID_REDIRECT_URI);
|
||||
|
|
|
@ -100,10 +100,9 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
|
|||
}
|
||||
}
|
||||
|
||||
public ClientRepresentation get(String clientId) {
|
||||
public ClientRepresentation get(ClientModel client) {
|
||||
event.event(EventType.CLIENT_INFO);
|
||||
|
||||
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
|
||||
auth.requireView(client);
|
||||
|
||||
ClientRepresentation rep = ModelToRepresentation.toRepresentation(client, session);
|
||||
|
|
|
@ -36,15 +36,13 @@ import org.keycloak.models.UserModel;
|
|||
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
import org.keycloak.services.ErrorResponseException;
|
||||
import org.keycloak.services.clientregistration.policy.RegistrationAuth;
|
||||
import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyException;
|
||||
import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyManager;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.clientregistration.policy.RegistrationAuth;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -62,11 +60,13 @@ public class ClientRegistrationAuth {
|
|||
private ClientInitialAccessModel initialAccessModel;
|
||||
private String kid;
|
||||
private String token;
|
||||
private String endpoint;
|
||||
|
||||
public ClientRegistrationAuth(KeycloakSession session, ClientRegistrationProvider provider, EventBuilder event) {
|
||||
public ClientRegistrationAuth(KeycloakSession session, ClientRegistrationProvider provider, EventBuilder event, String endpoint) {
|
||||
this.session = session;
|
||||
this.provider = provider;
|
||||
this.event = event;
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
|
@ -129,6 +129,8 @@ public class ClientRegistrationAuth {
|
|||
RegistrationAuth registrationAuth = RegistrationAuth.ANONYMOUS;
|
||||
|
||||
if (isBearerToken()) {
|
||||
checkClientProtocol();
|
||||
|
||||
if (hasRole(AdminRoles.MANAGE_CLIENTS, AdminRoles.CREATE_CLIENT)) {
|
||||
registrationAuth = RegistrationAuth.AUTHENTICATED;
|
||||
} else {
|
||||
|
@ -162,6 +164,8 @@ public class ClientRegistrationAuth {
|
|||
init();
|
||||
|
||||
if (isBearerToken()) {
|
||||
checkClientProtocol();
|
||||
|
||||
if (hasRole(AdminRoles.MANAGE_CLIENTS, AdminRoles.VIEW_CLIENTS)) {
|
||||
if (client == null) {
|
||||
throw notFound();
|
||||
|
@ -224,10 +228,26 @@ public class ClientRegistrationAuth {
|
|||
}
|
||||
}
|
||||
|
||||
private void checkClientProtocol() {
|
||||
ClientModel client = session.getContext().getRealm().getClientByClientId(jwt.getIssuedFor());
|
||||
|
||||
checkClientProtocol(client);
|
||||
}
|
||||
|
||||
private void checkClientProtocol(ClientModel client) {
|
||||
if (endpoint.equals("openid-connect") || endpoint.equals("saml2-entity-descriptor")) {
|
||||
if (client != null && !endpoint.contains(client.getProtocol())) {
|
||||
throw new ErrorResponseException(Errors.INVALID_CLIENT, "Wrong client protocol.", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RegistrationAuth requireUpdateAuth(ClientModel client) {
|
||||
init();
|
||||
|
||||
if (isBearerToken()) {
|
||||
checkClientProtocol();
|
||||
|
||||
if (hasRole(AdminRoles.MANAGE_CLIENTS)) {
|
||||
if (client == null) {
|
||||
throw notFound();
|
||||
|
@ -344,6 +364,8 @@ public class ClientRegistrationAuth {
|
|||
throw unauthorized("Different client authenticated");
|
||||
}
|
||||
|
||||
checkClientProtocol(authClient);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ public class ClientRegistrationService {
|
|||
}
|
||||
|
||||
provider.setEvent(event);
|
||||
provider.setAuth(new ClientRegistrationAuth(session, provider, event));
|
||||
provider.setAuth(new ClientRegistrationAuth(session, provider, event, providerId));
|
||||
return provider;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.keycloak.services.clientregistration;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
|
||||
|
@ -55,8 +56,9 @@ public class DefaultClientRegistrationProvider extends AbstractClientRegistratio
|
|||
@Path("{clientId}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response getDefault(@PathParam("clientId") String clientId) {
|
||||
ClientRepresentation client = get(clientId);
|
||||
return Response.ok(client).build();
|
||||
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
|
||||
ClientRepresentation clientRepresentation = get(client);
|
||||
return Response.ok(clientRepresentation).build();
|
||||
}
|
||||
|
||||
@PUT
|
||||
|
|
|
@ -100,8 +100,11 @@ public class OIDCClientRegistrationProvider extends AbstractClientRegistrationPr
|
|||
@Path("{clientId}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response getOIDC(@PathParam("clientId") String clientId) {
|
||||
ClientRepresentation client = get(clientId);
|
||||
OIDCClientRepresentation clientOIDC = DescriptionConverter.toExternalResponse(session, client, session.getContext().getUri().getRequestUri());
|
||||
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
|
||||
|
||||
ClientRepresentation clientRepresentation = get(client);
|
||||
|
||||
OIDCClientRepresentation clientOIDC = DescriptionConverter.toExternalResponse(session, clientRepresentation, session.getContext().getUri().getRequestUri());
|
||||
return Response.ok(clientOIDC).build();
|
||||
}
|
||||
|
||||
|
@ -175,5 +178,4 @@ public class OIDCClientRegistrationProvider extends AbstractClientRegistrationPr
|
|||
}
|
||||
rep.setProtocolMappers(mappings);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.keycloak.authentication.AuthenticationProcessor;
|
|||
import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
|
||||
import org.keycloak.authentication.authenticators.broker.util.PostBrokerLoginConstants;
|
||||
import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
|
||||
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
|
||||
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
|
@ -32,6 +33,7 @@ import org.keycloak.broker.provider.IdentityProviderFactory;
|
|||
import org.keycloak.broker.provider.IdentityProviderMapper;
|
||||
import org.keycloak.broker.provider.util.IdentityBrokerState;
|
||||
import org.keycloak.broker.saml.SAMLEndpoint;
|
||||
import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
|
||||
import org.keycloak.broker.social.SocialIdentityProvider;
|
||||
import org.keycloak.common.ClientConnection;
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
|
@ -70,6 +72,7 @@ import org.keycloak.representations.AccessToken;
|
|||
import org.keycloak.services.ErrorPage;
|
||||
import org.keycloak.services.ErrorPageException;
|
||||
import org.keycloak.services.ErrorResponse;
|
||||
import org.keycloak.services.ErrorResponseException;
|
||||
import org.keycloak.services.ServicesLogger;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.managers.AppAuthManager;
|
||||
|
@ -1240,7 +1243,6 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private static class ParsedCodeContext {
|
||||
private ClientSessionCode<AuthenticationSessionModel> clientSessionCode;
|
||||
private Response response;
|
||||
|
|
|
@ -21,10 +21,12 @@ package org.keycloak.testsuite.client;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.admin.client.resource.ClientsResource;
|
||||
import org.keycloak.client.registration.Auth;
|
||||
import org.keycloak.client.registration.ClientRegistrationException;
|
||||
import org.keycloak.client.registration.HttpErrorException;
|
||||
import org.keycloak.common.util.CollectionUtil;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.jose.jws.Algorithm;
|
||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
|
@ -33,19 +35,19 @@ import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
|
|||
import org.keycloak.representations.idm.ClientInitialAccessPresentation;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.util.KeycloakModelUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -58,8 +60,14 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
|
|||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
super.addTestRealms(testRealms);
|
||||
testRealms.get(0).setPrivateKey(PRIVATE_KEY);
|
||||
testRealms.get(0).setPublicKey(PUBLIC_KEY);
|
||||
RealmRepresentation testRealm = testRealms.get(0);
|
||||
testRealm.setPrivateKey(PRIVATE_KEY);
|
||||
testRealm.setPublicKey(PUBLIC_KEY);
|
||||
|
||||
ClientRepresentation samlApp = KeycloakModelUtils.createClient(testRealm, "saml-client");
|
||||
samlApp.setSecret("secret");
|
||||
samlApp.setServiceAccountsEnabled(true);
|
||||
samlApp.setDirectAccessGrantsEnabled(true);
|
||||
}
|
||||
|
||||
@Before
|
||||
|
@ -103,6 +111,19 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
|
|||
}
|
||||
}
|
||||
|
||||
private void assertGetFail(String clientId, int expectedStatusCode, String expectedErrorContains) {
|
||||
try {
|
||||
reg.oidc().get(clientId);
|
||||
Assert.fail("Not expected to successfully get client");
|
||||
} catch (ClientRegistrationException expected) {
|
||||
HttpErrorException httpEx = (HttpErrorException) expected.getCause();
|
||||
Assert.assertEquals(expectedStatusCode, httpEx.getStatusLine().getStatusCode());
|
||||
if (expectedErrorContains != null) {
|
||||
assertTrue("Error response doesn't contain expected text", httpEx.getErrorResponse().contains(expectedErrorContains));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// KEYCLOAK-3421
|
||||
@Test
|
||||
public void createClientWithUriFragment() {
|
||||
|
@ -262,6 +283,50 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOIDCEndpointCreateWithSamlClient() throws Exception {
|
||||
ClientsResource clientsResource = adminClient.realm(TEST).clients();
|
||||
ClientRepresentation samlClient = clientsResource.findByClientId("saml-client").get(0);
|
||||
String samlClientServiceId = clientsResource.get(samlClient.getId()).getServiceAccountUser().getId();
|
||||
|
||||
String realmManagementId = clientsResource.findByClientId("realm-management").get(0).getId();
|
||||
RoleRepresentation role = clientsResource.get(realmManagementId).roles().get("create-client").toRepresentation();
|
||||
|
||||
adminClient.realm(TEST).users().get(samlClientServiceId).roles().clientLevel(realmManagementId).add(Arrays.asList(role));
|
||||
|
||||
String accessToken = oauth.clientId("saml-client").doClientCredentialsGrantAccessTokenRequest("secret").getAccessToken();
|
||||
reg.auth(Auth.token(accessToken));
|
||||
|
||||
// change client to saml
|
||||
samlClient.setProtocol("saml");
|
||||
clientsResource.get(samlClient.getId()).update(samlClient);
|
||||
|
||||
OIDCClientRepresentation client = createRep();
|
||||
assertCreateFail(client, 400, Errors.INVALID_CLIENT);
|
||||
|
||||
// revert client
|
||||
samlClient.setProtocol("openid-connect");
|
||||
clientsResource.get(samlClient.getId()).update(samlClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOIDCEndpointGetWithSamlClient() {
|
||||
ClientsResource clientsResource = adminClient.realm(TEST).clients();
|
||||
ClientRepresentation samlClient = clientsResource.findByClientId("saml-client").get(0);
|
||||
|
||||
reg.auth(Auth.client("saml-client", "secret"));
|
||||
|
||||
// change client to saml
|
||||
samlClient.setProtocol("saml");
|
||||
clientsResource.get(samlClient.getId()).update(samlClient);
|
||||
|
||||
assertGetFail(samlClient.getClientId(), 400, Errors.INVALID_CLIENT);
|
||||
|
||||
// revert client
|
||||
samlClient.setProtocol("openid-connect");
|
||||
clientsResource.get(samlClient.getId()).update(samlClient);
|
||||
}
|
||||
|
||||
private ClientRepresentation getKeycloakClient(String clientId) {
|
||||
return ApiUtil.findClientByClientId(adminClient.realms().realm(REALM_NAME), clientId).toRepresentation();
|
||||
}
|
||||
|
|
|
@ -20,22 +20,44 @@ package org.keycloak.testsuite.client;
|
|||
import org.apache.commons.io.IOUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.ClientsResource;
|
||||
import org.keycloak.client.registration.Auth;
|
||||
import org.keycloak.client.registration.ClientRegistrationException;
|
||||
import org.keycloak.client.registration.HttpErrorException;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
|
||||
import org.keycloak.representations.idm.ClientInitialAccessPresentation;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.util.KeycloakModelUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class SAMLClientRegistrationTest extends AbstractClientRegistrationTest {
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
super.addTestRealms(testRealms);
|
||||
RealmRepresentation testRealm = testRealms.get(0);
|
||||
|
||||
ClientRepresentation samlApp = KeycloakModelUtils.createClient(testRealm, "oidc-client");
|
||||
samlApp.setSecret("secret");
|
||||
samlApp.setServiceAccountsEnabled(true);
|
||||
samlApp.setDirectAccessGrantsEnabled(true);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
super.before();
|
||||
|
@ -61,4 +83,34 @@ public class SAMLClientRegistrationTest extends AbstractClientRegistrationTest {
|
|||
assertThat(response.getAttributes().get("saml_single_logout_service_url_redirect"), is("https://LoadBalancer-9.siroe.com:3443/federation/SPSloRedirect/metaAlias/sp"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSAMLEndpointCreateWithOIDCClient() throws Exception {
|
||||
ClientsResource clientsResource = adminClient.realm(TEST).clients();
|
||||
ClientRepresentation oidcClient = clientsResource.findByClientId("oidc-client").get(0);
|
||||
String oidcClientServiceId = clientsResource.get(oidcClient.getId()).getServiceAccountUser().getId();
|
||||
|
||||
String realmManagementId = clientsResource.findByClientId("realm-management").get(0).getId();
|
||||
RoleRepresentation role = clientsResource.get(realmManagementId).roles().get("create-client").toRepresentation();
|
||||
|
||||
adminClient.realm(TEST).users().get(oidcClientServiceId).roles().clientLevel(realmManagementId).add(Arrays.asList(role));
|
||||
|
||||
String accessToken = oauth.clientId("oidc-client").doClientCredentialsGrantAccessTokenRequest("secret").getAccessToken();
|
||||
reg.auth(Auth.token(accessToken));
|
||||
|
||||
String entityDescriptor = IOUtils.toString(getClass().getResourceAsStream("/clientreg-test/saml-entity-descriptor.xml"));
|
||||
assertCreateFail(entityDescriptor, 400, Errors.INVALID_CLIENT);
|
||||
}
|
||||
|
||||
private void assertCreateFail(String entityDescriptor, int expectedStatusCode, String expectedErrorContains) {
|
||||
try {
|
||||
reg.saml().create(entityDescriptor);
|
||||
Assert.fail("Not expected to successfully register client");
|
||||
} catch (ClientRegistrationException expected) {
|
||||
HttpErrorException httpEx = (HttpErrorException) expected.getCause();
|
||||
Assert.assertEquals(expectedStatusCode, httpEx.getStatusLine().getStatusCode());
|
||||
if (expectedErrorContains != null) {
|
||||
assertTrue("Error response doesn't contain expected text", httpEx.getErrorResponse().contains(expectedErrorContains));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.junit.Test;
|
|||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
|
@ -68,6 +69,11 @@ public class TokenIntrospectionTest extends AbstractTestRealmKeycloakTest {
|
|||
ClientRepresentation pubApp = KeycloakModelUtils.createClient(testRealm, "public-cli");
|
||||
pubApp.setPublicClient(Boolean.TRUE);
|
||||
|
||||
ClientRepresentation samlApp = KeycloakModelUtils.createClient(testRealm, "saml-client");
|
||||
samlApp.setSecret("secret2");
|
||||
samlApp.setServiceAccountsEnabled(Boolean.TRUE);
|
||||
samlApp.setProtocol("saml");
|
||||
|
||||
UserRepresentation user = new UserRepresentation();
|
||||
user.setUsername("no-permissions");
|
||||
CredentialRepresentation credential = new CredentialRepresentation();
|
||||
|
@ -350,4 +356,21 @@ public class TokenIntrospectionTest extends AbstractTestRealmKeycloakTest {
|
|||
assertNull(rep.getClientId());
|
||||
assertNull(rep.getSubject());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test covers the same scenario from different endpoints like TokenEndpoint and LogoutEndpoint.
|
||||
*/
|
||||
@Test
|
||||
public void testIntrospectWithSamlClient() throws Exception {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
events.expectLogin().assertEvent();
|
||||
AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||
String tokenResponse = oauth.introspectAccessTokenWithClientCredential("saml-client", "secret2", accessTokenResponse.getAccessToken());
|
||||
TokenMetadataRepresentation rep = JsonSerialization.readValue(tokenResponse, TokenMetadataRepresentation.class);
|
||||
|
||||
assertEquals(Errors.INVALID_CLIENT, rep.getOtherClaims().get("error"));
|
||||
assertNull(rep.getSubject());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ import org.keycloak.events.EventType;
|
|||
import org.keycloak.jose.jws.Algorithm;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.testsuite.util.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
|
@ -86,11 +86,15 @@ public class UserInfoTest extends AbstractKeycloakTest {
|
|||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
|
||||
RealmRepresentation realmRepresentation = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class);
|
||||
RealmBuilder realm = RealmBuilder.edit(realmRepresentation).testEventListener();
|
||||
testRealms.add(realm.build());
|
||||
RealmRepresentation testRealm = realm.build();
|
||||
testRealms.add(testRealm);
|
||||
|
||||
ClientRepresentation samlApp = KeycloakModelUtils.createClient(testRealm, "saml-client");
|
||||
samlApp.setSecret("secret");
|
||||
samlApp.setServiceAccountsEnabled(true);
|
||||
samlApp.setDirectAccessGrantsEnabled(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -351,6 +355,35 @@ public class UserInfoTest extends AbstractKeycloakTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserInfoRequestWithSamlClient() throws Exception {
|
||||
// obtain an access token
|
||||
String accessToken = oauth.doGrantAccessTokenRequest("test", "test-user@localhost", "password", null, "saml-client", "secret").getAccessToken();
|
||||
|
||||
// change client's protocol
|
||||
ClientRepresentation samlClient = adminClient.realm("test").clients().findByClientId("saml-client").get(0);
|
||||
samlClient.setProtocol("saml");
|
||||
adminClient.realm("test").clients().get(samlClient.getId()).update(samlClient);
|
||||
|
||||
Client client = ClientBuilder.newClient();
|
||||
try {
|
||||
events.clear();
|
||||
Response response = UserInfoClientUtil.executeUserInfoRequest_getMethod(client, accessToken);
|
||||
response.close();
|
||||
|
||||
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
|
||||
events.expect(EventType.USER_INFO_REQUEST)
|
||||
.error(Errors.INVALID_CLIENT)
|
||||
.client((String) null)
|
||||
.user(Matchers.nullValue(String.class))
|
||||
.session(Matchers.nullValue(String.class))
|
||||
.detail(Details.AUTH_METHOD, Details.VALIDATE_ACCESS_TOKEN)
|
||||
.assertEvent();
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
private AccessTokenResponse executeGrantAccessTokenRequest(Client client) {
|
||||
return executeGrantAccessTokenRequest(client, false);
|
||||
}
|
||||
|
|
|
@ -16,13 +16,19 @@
|
|||
*/
|
||||
package org.keycloak.testsuite.saml;
|
||||
|
||||
import org.apache.http.client.HttpResponseException;
|
||||
import org.junit.Assert;
|
||||
import org.keycloak.admin.client.resource.ClientsResource;
|
||||
import org.keycloak.admin.client.resource.UsersResource;
|
||||
import org.keycloak.client.registration.HttpErrorException;
|
||||
import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.saml.SamlProtocol;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.representations.idm.UserSessionRepresentation;
|
||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||
import org.keycloak.testsuite.util.ClientBuilder;
|
||||
import org.keycloak.testsuite.util.Matchers;
|
||||
import org.keycloak.testsuite.util.SamlClient.Binding;
|
||||
import org.keycloak.testsuite.util.SamlClientBuilder;
|
||||
|
@ -31,10 +37,16 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.keycloak.testsuite.util.Matchers.bodyHC;
|
||||
import static org.keycloak.testsuite.util.Matchers.statusCodeIsHC;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -107,4 +119,21 @@ public class IdpInitiatedLoginTest extends AbstractSamlTest {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdpInitiatedLoginWithOIDCClient() {
|
||||
ClientRepresentation clientRep = adminClient.realm(REALM_NAME).clients().findByClientId(SAML_CLIENT_ID_SALES_POST).get(0);
|
||||
adminClient.realm(REALM_NAME).clients().get(clientRep.getId()).update(ClientBuilder.edit(clientRep)
|
||||
.protocol(OIDCLoginProtocol.LOGIN_PROTOCOL).build());
|
||||
|
||||
new SamlClientBuilder()
|
||||
.idpInitiatedLogin(getAuthServerSamlEndpoint(REALM_NAME), "sales-post").build()
|
||||
.execute(r -> {
|
||||
Assert.assertThat(r, statusCodeIsHC(Response.Status.BAD_REQUEST));
|
||||
Assert.assertThat(r, bodyHC(containsString("Wrong client protocol.")));
|
||||
});
|
||||
|
||||
|
||||
adminClient.realm(REALM_NAME).clients().get(clientRep.getId()).update(ClientBuilder.edit(clientRep)
|
||||
.protocol(SamlProtocol.LOGIN_PROTOCOL).build());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
package org.keycloak.testsuite.saml;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.protocol.HttpClientContext;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.saml.SamlProtocol;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
||||
import org.keycloak.saml.common.exceptions.ParsingException;
|
||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
||||
import org.keycloak.saml.processing.api.saml.v2.request.SAML2Request;
|
||||
import org.keycloak.testsuite.util.ClientBuilder;
|
||||
import org.keycloak.testsuite.util.SamlClient;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author mkanis
|
||||
*/
|
||||
public class SamlClientTest extends AbstractSamlTest {
|
||||
|
||||
@Test
|
||||
public void testLoginWithOIDCClient() throws ParsingException, ConfigurationException, ProcessingException, IOException {
|
||||
ClientRepresentation salesRep = adminClient.realm(REALM_NAME).clients().findByClientId(SAML_CLIENT_ID_SALES_POST).get(0);
|
||||
adminClient.realm(REALM_NAME).clients().get(salesRep.getId()).update(ClientBuilder.edit(salesRep)
|
||||
.protocol(OIDCLoginProtocol.LOGIN_PROTOCOL).build());
|
||||
|
||||
AuthnRequestType loginRep = createLoginRequestDocument(SAML_CLIENT_ID_SALES_POST, SAML_ASSERTION_CONSUMER_URL_SALES_POST, REALM_NAME);
|
||||
Document samlRequest = SAML2Request.convert(loginRep);
|
||||
|
||||
SamlClient.RedirectStrategyWithSwitchableFollowRedirect strategy = new SamlClient.RedirectStrategyWithSwitchableFollowRedirect();
|
||||
URI samlEndpoint = getAuthServerSamlEndpoint(REALM_NAME);
|
||||
|
||||
try (CloseableHttpClient client = HttpClientBuilder.create().setRedirectStrategy(strategy).build()) {
|
||||
HttpUriRequest post = SamlClient.Binding.POST.createSamlUnsignedRequest(samlEndpoint, null, samlRequest);
|
||||
CloseableHttpResponse response = sendPost(post, client);
|
||||
Assert.assertEquals(response.getStatusLine().getStatusCode(), 400);
|
||||
String s = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
|
||||
Assert.assertThat(s, Matchers.containsString("Wrong client protocol."));
|
||||
|
||||
response.close();
|
||||
}
|
||||
|
||||
adminClient.realm(REALM_NAME).clients().get(salesRep.getId()).update(ClientBuilder.edit(salesRep)
|
||||
.protocol(SamlProtocol.LOGIN_PROTOCOL).build());
|
||||
}
|
||||
|
||||
private CloseableHttpResponse sendPost(HttpUriRequest post, final CloseableHttpClient client) {
|
||||
CloseableHttpResponse response;
|
||||
try {
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
response = client.execute(post, context);
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
|
@ -23,6 +23,9 @@ import org.keycloak.representations.idm.ClientRepresentation;
|
|||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.keycloak.models.utils.KeycloakModelUtils.getDefaultClientAuthenticatorType;
|
||||
|
||||
/**
|
||||
|
@ -37,7 +40,12 @@ public class KeycloakModelUtils {
|
|||
ClientRepresentation app = new ClientRepresentation();
|
||||
app.setName(name);
|
||||
app.setClientId(name);
|
||||
realm.getClients().add(app);
|
||||
List<ClientRepresentation> clients = realm.getClients();
|
||||
if (clients != null) {
|
||||
clients.add(app);
|
||||
} else {
|
||||
realm.setClients(Arrays.asList(app));
|
||||
}
|
||||
app.setClientAuthenticatorType(getDefaultClientAuthenticatorType());
|
||||
generateSecret(app);
|
||||
app.setFullScopeAllowed(true);
|
||||
|
|
Loading…
Reference in a new issue