diff --git a/examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/rest/ExampleRestResource.java b/examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/rest/ExampleRestResource.java index df642f5fbd68..3e4ceeb24c1c 100644 --- a/examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/rest/ExampleRestResource.java +++ b/examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/rest/ExampleRestResource.java @@ -15,7 +15,7 @@ public class ExampleRestResource { public ExampleRestResource(KeycloakSession session) { this.session = session; - this.auth = new AppAuthManager().authenticateBearerToken(session, session.getContext().getRealm()); + this.auth = new AppAuthManager.BearerTokenAuthenticator(session).authenticate(); } @Path("companies") diff --git a/services/src/main/java/org/keycloak/authorization/util/Tokens.java b/services/src/main/java/org/keycloak/authorization/util/Tokens.java index 5ca43f2a7d35..b17a01f710a8 100644 --- a/services/src/main/java/org/keycloak/authorization/util/Tokens.java +++ b/services/src/main/java/org/keycloak/authorization/util/Tokens.java @@ -30,9 +30,7 @@ public class Tokens { public static AccessToken getAccessToken(KeycloakSession keycloakSession) { - AppAuthManager authManager = new AppAuthManager(); - KeycloakContext context = keycloakSession.getContext(); - AuthResult authResult = authManager.authenticateBearerToken(keycloakSession, context.getRealm(), context.getUri(), context.getConnection(), context.getRequestHeaders()); + AuthResult authResult = new AppAuthManager.BearerTokenAuthenticator(keycloakSession).authenticate(); if (authResult != null) { return authResult.getToken(); @@ -42,9 +40,9 @@ public static AccessToken getAccessToken(KeycloakSession keycloakSession) { } public static AccessToken getAccessToken(String accessToken, KeycloakSession keycloakSession) { - AppAuthManager authManager = new AppAuthManager(); - KeycloakContext context = keycloakSession.getContext(); - AuthResult authResult = authManager.authenticateBearerToken(accessToken, keycloakSession, context.getRealm(), context.getUri(), context.getConnection(), context.getRequestHeaders()); + AuthResult authResult = new AppAuthManager.BearerTokenAuthenticator(keycloakSession) + .setTokenString(accessToken) + .authenticate(); if (authResult != null) { return authResult.getToken(); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java index 5eb5a13f0ddd..367537eeca90 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java @@ -84,7 +84,6 @@ import org.keycloak.services.ServicesLogger; import org.keycloak.services.Urls; import org.keycloak.services.clientpolicy.ClientPolicyException; -import org.keycloak.services.clientpolicy.DefaultClientPolicyManager; import org.keycloak.services.clientpolicy.TokenRefreshContext; import org.keycloak.services.clientpolicy.TokenRequestContext; import org.keycloak.services.managers.AppAuthManager; @@ -797,7 +796,7 @@ public Response tokenExchange() { } - AuthenticationManager.AuthResult authResult = AuthenticationManager.verifyIdentityToken(session, realm, session.getContext().getUri(), clientConnection, true, true, false, subjectToken, headers); + AuthenticationManager.AuthResult authResult = AuthenticationManager.verifyIdentityToken(session, realm, session.getContext().getUri(), clientConnection, true, true, null, false, subjectToken, headers); if (authResult == null) { event.detail(Details.REASON, "subject_token validation failure"); event.error(Errors.INVALID_TOKEN); diff --git a/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java b/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java index ab1fb5908a9a..256abd637452 100755 --- a/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java @@ -53,7 +53,7 @@ public AuthResult authenticateIdentityCookie(KeycloakSession session, RealmModel * * @return the token string or {@literal null} */ - private String extractTokenStringFromAuthHeader(String authHeader) { + private static String extractTokenStringFromAuthHeader(String authHeader) { if (authHeader == null) { return null; @@ -83,7 +83,7 @@ private String extractTokenStringFromAuthHeader(String authHeader) { * @param headers * @return the token string or {@literal null} if the Authorization header is not of type Bearer, or the token string is missing. */ - public String extractAuthorizationHeaderTokenOrReturnNull(HttpHeaders headers) { + public static String extractAuthorizationHeaderTokenOrReturnNull(HttpHeaders headers) { String authHeader = headers.getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION); return extractTokenStringFromAuthHeader(authHeader); } @@ -95,7 +95,7 @@ public String extractAuthorizationHeaderTokenOrReturnNull(HttpHeaders headers) { * @return the token string or {@literal null} of the Authorization header is missing * @throws NotAuthorizedException if the Authorization header is not of type Bearer, or the token string is missing. */ - public String extractAuthorizationHeaderToken(HttpHeaders headers) { + public static String extractAuthorizationHeaderToken(HttpHeaders headers) { String authHeader = headers.getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION); if (authHeader == null) { return null; @@ -107,23 +107,65 @@ public String extractAuthorizationHeaderToken(HttpHeaders headers) { return tokenString; } - public AuthResult authenticateBearerToken(KeycloakSession session, RealmModel realm) { - KeycloakContext ctx = session.getContext(); - return authenticateBearerToken(session, realm, ctx.getUri(), ctx.getConnection(), ctx.getRequestHeaders()); - } + public static class BearerTokenAuthenticator { + private KeycloakSession session; + private RealmModel realm; + private UriInfo uriInfo; + private ClientConnection connection; + private HttpHeaders headers; + private String tokenString; + private String audience; + + public BearerTokenAuthenticator(KeycloakSession session) { + this.session = session; + } - public AuthResult authenticateBearerToken(KeycloakSession session) { - return authenticateBearerToken(session, session.getContext().getRealm(), session.getContext().getUri(), session.getContext().getConnection(), session.getContext().getRequestHeaders()); - } + public BearerTokenAuthenticator setSession(KeycloakSession session) { + this.session = session; + return this; + } - public AuthResult authenticateBearerToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) { - return authenticateBearerToken(extractAuthorizationHeaderToken(headers), session, realm, uriInfo, connection, headers); - } + public BearerTokenAuthenticator setRealm(RealmModel realm) { + this.realm = realm; + return this; + } - public AuthResult authenticateBearerToken(String tokenString, KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) { - if (tokenString == null) return null; - AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, true, true, false, tokenString, headers); - return authResult; + public BearerTokenAuthenticator setUriInfo(UriInfo uriInfo) { + this.uriInfo = uriInfo; + return this; + } + + public BearerTokenAuthenticator setConnection(ClientConnection connection) { + this.connection = connection; + return this; + } + + public BearerTokenAuthenticator setHeaders(HttpHeaders headers) { + this.headers = headers; + return this; + } + + public BearerTokenAuthenticator setTokenString(String tokenString) { + this.tokenString = tokenString; + return this; + } + + public BearerTokenAuthenticator setAudience(String audience) { + this.audience = audience; + return this; + } + + public AuthResult authenticate() { + KeycloakContext ctx = session.getContext(); + if (realm == null) realm = ctx.getRealm(); + if (uriInfo == null) uriInfo = ctx.getUri(); + if (connection == null) connection = ctx.getConnection(); + if (headers == null) headers = ctx.getRequestHeaders(); + if (tokenString == null) tokenString = extractAuthorizationHeaderToken(headers); + // audience can be null + + return verifyIdentityToken(session, realm, uriInfo, connection, true, true, audience, false, tokenString, headers); + } } } 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 a8216806cdf2..503d51793dae 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -779,7 +779,7 @@ public static AuthResult authenticateIdentityCookie(KeycloakSession session, Rea } String tokenString = cookie.getValue(); - AuthResult authResult = verifyIdentityToken(session, realm, session.getContext().getUri(), session.getContext().getConnection(), checkActive, false, true, tokenString, session.getContext().getRequestHeaders(), VALIDATE_IDENTITY_COOKIE); + AuthResult authResult = verifyIdentityToken(session, realm, session.getContext().getUri(), session.getContext().getConnection(), checkActive, false, null, true, tokenString, session.getContext().getRequestHeaders(), VALIDATE_IDENTITY_COOKIE); if (authResult == null) { expireIdentityCookie(realm, session.getContext().getUri(), session.getContext().getConnection()); expireOldIdentityCookie(realm, session.getContext().getUri(), session.getContext().getConnection()); @@ -1261,7 +1261,7 @@ public void ignore() { } public static AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, boolean checkTokenType, - boolean isCookie, String tokenString, HttpHeaders headers, Predicate... additionalChecks) { + String checkAudience, boolean isCookie, String tokenString, HttpHeaders headers, Predicate... additionalChecks) { try { TokenVerifier verifier = TokenVerifier.create(tokenString, AccessToken.class) .withDefaultChecks() @@ -1269,6 +1269,11 @@ public static AuthResult verifyIdentityToken(KeycloakSession session, RealmModel .checkActive(checkActive) .checkTokenType(checkTokenType) .withChecks(additionalChecks); + + if (checkAudience != null) { + verifier.audience(checkAudience); + } + String kid = verifier.getHeader().getKeyId(); String algorithm = verifier.getHeader().getAlgorithm().name(); diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java index 00f2d6389c71..6bf7e0623784 100755 --- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java +++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java @@ -448,8 +448,11 @@ private Response getToken(String providerId, boolean forceRetrieval) { this.event.event(EventType.IDENTITY_PROVIDER_RETRIEVE_TOKEN); try { - AppAuthManager authManager = new AppAuthManager(); - AuthenticationManager.AuthResult authResult = authManager.authenticateBearerToken(this.session, this.realmModel, this.session.getContext().getUri(), this.clientConnection, this.request.getHttpHeaders()); + AuthenticationManager.AuthResult authResult = new AppAuthManager.BearerTokenAuthenticator(session) + .setRealm(realmModel) + .setConnection(clientConnection) + .setHeaders(request.getHttpHeaders()) + .authenticate(); if (authResult != null) { AccessToken token = authResult.getToken(); diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountLoader.java b/services/src/main/java/org/keycloak/services/resources/account/AccountLoader.java index 785e6a045305..de602d9cb289 100644 --- a/services/src/main/java/org/keycloak/services/resources/account/AccountLoader.java +++ b/services/src/main/java/org/keycloak/services/resources/account/AccountLoader.java @@ -114,7 +114,10 @@ private boolean isDeprecatedFormsAccountConsole(Theme theme) { } private AccountRestService getAccountRestService(ClientModel client, String versionStr) { - AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(session); + AuthenticationManager.AuthResult authResult = new AppAuthManager.BearerTokenAuthenticator(session) + .setAudience(client.getClientId()) + .authenticate(); + if (authResult == null) { throw new NotAuthorizedException("Bearer token required"); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java index 48ce971d4c04..9f02a13cbebc 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java @@ -87,12 +87,10 @@ public class AdminConsole { @Context protected Providers providers; - protected AppAuthManager authManager; protected RealmModel realm; public AdminConsole(RealmModel realm) { this.realm = realm; - this.authManager = new AppAuthManager(); } public static class WhoAmI { @@ -195,7 +193,12 @@ public ClientManager.InstallationAdapterConfig config() { @NoCache public Response whoAmI(final @Context HttpHeaders headers) { RealmManager realmManager = new RealmManager(session); - AuthenticationManager.AuthResult authResult = authManager.authenticateBearerToken(session, realm, session.getContext().getUri(), clientConnection, headers); + AuthenticationManager.AuthResult authResult = new AppAuthManager.BearerTokenAuthenticator(session) + .setRealm(realm) + .setConnection(clientConnection) + .setHeaders(headers) + .authenticate(); + if (authResult == null) { return Response.status(401).build(); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java index 858e91dab042..412f346e4f5e 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java @@ -72,7 +72,6 @@ public class AdminRoot { @Context protected HttpResponse response; - protected AppAuthManager authManager; protected TokenManager tokenManager; @Context @@ -80,7 +79,6 @@ public class AdminRoot { public AdminRoot() { this.tokenManager = new TokenManager(); - this.authManager = new AppAuthManager(); } public static UriBuilder adminBaseUrl(UriInfo uriInfo) { @@ -153,7 +151,7 @@ public AdminConsole getAdminConsole(final @PathParam("realm") String name) { protected AdminAuth authenticateRealmAdminRequest(HttpHeaders headers) { - String tokenString = authManager.extractAuthorizationHeaderToken(headers); + String tokenString = AppAuthManager.extractAuthorizationHeaderToken(headers); if (tokenString == null) throw new NotAuthorizedException("Bearer"); AccessToken token; try { @@ -169,7 +167,13 @@ protected AdminAuth authenticateRealmAdminRequest(HttpHeaders headers) { throw new NotAuthorizedException("Unknown realm in token"); } session.getContext().setRealm(realm); - AuthenticationManager.AuthResult authResult = authManager.authenticateBearerToken(session, realm, session.getContext().getUri(), clientConnection, headers); + + AuthenticationManager.AuthResult authResult = new AppAuthManager.BearerTokenAuthenticator(session) + .setRealm(realm) + .setConnection(clientConnection) + .setHeaders(headers) + .authenticate(); + if (authResult == null) { logger.debug("Token not valid"); throw new NotAuthorizedException("Bearer"); diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/domainextension/rest/ExampleRestResource.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/domainextension/rest/ExampleRestResource.java index 90328842ba28..03049e217b35 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/domainextension/rest/ExampleRestResource.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/domainextension/rest/ExampleRestResource.java @@ -32,7 +32,7 @@ public class ExampleRestResource { public ExampleRestResource(KeycloakSession session) { this.session = session; - this.auth = new AppAuthManager().authenticateBearerToken(session, session.getContext().getRealm()); + this.auth = new AppAuthManager.BearerTokenAuthenticator(session).authenticate(); } @Path("companies") diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java index a86c6f21abd6..9f82b0289edd 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java @@ -1203,7 +1203,7 @@ public void applicationsVisibilityNoScopesNoConsent() throws Exception { Map apps = applicationsPage.getApplications(); Assert.assertThat(apps.keySet(), containsInAnyOrder( - /* "root-url-client", */ "Account", "Account Console", "test-app", "test-app-scope", "third-party", "test-app-authz", "My Named Test App", "Test App Named - ${client_account}", "direct-grant")); + /* "root-url-client", */ "Account", "Account Console", "test-app", "test-app-scope", "third-party", "test-app-authz", "My Named Test App", "Test App Named - ${client_account}", "direct-grant", "custom-audience")); rsu.add(testRealm().roles().get("user").toRepresentation()) .update(); @@ -1211,7 +1211,7 @@ public void applicationsVisibilityNoScopesNoConsent() throws Exception { driver.navigate().refresh(); apps = applicationsPage.getApplications(); Assert.assertThat(apps.keySet(), containsInAnyOrder( - "root-url-client", "Account", "Account Console", "test-app", "test-app-scope", "third-party", "test-app-authz", "My Named Test App", "Test App Named - ${client_account}", "direct-grant")); + "root-url-client", "Account", "Account Console", "test-app", "test-app-scope", "third-party", "test-app-authz", "My Named Test App", "Test App Named - ${client_account}", "direct-grant", "custom-audience")); } } @@ -1230,7 +1230,7 @@ public void applicationsVisibilityNoScopesAndConsent() throws Exception { Map apps = applicationsPage.getApplications(); Assert.assertThat(apps.keySet(), containsInAnyOrder( - "root-url-client", "Account", "Account Console", "test-app", "test-app-scope", "third-party", "test-app-authz", "My Named Test App", "Test App Named - ${client_account}", "direct-grant")); + "root-url-client", "Account", "Account Console", "test-app", "test-app-scope", "third-party", "test-app-authz", "My Named Test App", "Test App Named - ${client_account}", "direct-grant", "custom-audience")); } } @@ -1245,7 +1245,7 @@ public void applications() { applicationsPage.assertCurrent(); Map apps = applicationsPage.getApplications(); - Assert.assertThat(apps.keySet(), containsInAnyOrder("root-url-client", "Account", "Account Console", "Broker", "test-app", "test-app-scope", "third-party", "test-app-authz", "My Named Test App", "Test App Named - ${client_account}", "direct-grant")); + Assert.assertThat(apps.keySet(), containsInAnyOrder("root-url-client", "Account", "Account Console", "Broker", "test-app", "test-app-scope", "third-party", "test-app-authz", "My Named Test App", "Test App Named - ${client_account}", "direct-grant", "custom-audience")); AccountApplicationsPage.AppEntry accountEntry = apps.get("Account"); Assert.assertThat(accountEntry.getRolesAvailable(), containsInAnyOrder( diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java index 99e0de62d46f..005b80d7aa39 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java @@ -47,6 +47,7 @@ import org.keycloak.representations.idm.ClientScopeRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.ErrorRepresentation; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RequiredActionProviderRepresentation; import org.keycloak.representations.idm.RequiredActionProviderSimpleRepresentation; @@ -73,6 +74,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.keycloak.common.Profile.Feature.ACCOUNT_API; @@ -294,7 +296,7 @@ public void testCredentialsGet() throws IOException { null, UserModel.RequiredAction.UPDATE_PASSWORD.toString(), false, 1); CredentialRepresentation password1 = password.getUserCredentials().get(0); - Assert.assertNull(password1.getSecretData()); + assertNull(password1.getSecretData()); Assert.assertNotNull(password1.getCredentialData()); AccountCredentialResource.CredentialContainer otp = credentials.get(1); @@ -341,7 +343,7 @@ public void testCredentialsGet() throws IOException { Assert.assertEquals(1, credentials.size()); password = credentials.get(0); Assert.assertEquals(PasswordCredentialModel.TYPE, password.getType()); - Assert.assertNull(password.getUserCredentials()); + assertNull(password.getUserCredentials()); } @@ -452,8 +454,8 @@ public void testCredentialsGetWithDisabledOtpRequiredAction() throws IOException credentials = getCredentials(); assertExpectedCredentialTypes(credentials, PasswordCredentialModel.TYPE, OTPCredentialModel.TYPE); AccountCredentialResource.CredentialContainer otpCredential = credentials.get(1); - Assert.assertNull(otpCredential.getCreateAction()); - Assert.assertNull(otpCredential.getUpdateAction()); + assertNull(otpCredential.getCreateAction()); + assertNull(otpCredential.getUpdateAction()); // Revert - re-enable requiredAction and remove OTP credential from the user setRequiredActionEnabledStatus(UserModel.RequiredAction.CONFIGURE_TOTP.name(), true); @@ -578,7 +580,7 @@ public void testDeleteSessions() throws IOException { public void listApplications() throws Exception { oauth.clientId("in-use-client"); OAuthClient.AccessTokenResponse tokenResponse = oauth.doGrantAccessTokenRequest("secret1", "view-applications-access", "password"); - Assert.assertNull(tokenResponse.getErrorDescription()); + assertNull(tokenResponse.getErrorDescription()); TokenUtil token = new TokenUtil("view-applications-access", "password"); List applications = SimpleHttp @@ -600,7 +602,7 @@ public void listApplications() throws Exception { public void listApplicationsFiltered() throws Exception { oauth.clientId("in-use-client"); OAuthClient.AccessTokenResponse tokenResponse = oauth.doGrantAccessTokenRequest("secret1", "view-applications-access", "password"); - Assert.assertNull(tokenResponse.getErrorDescription()); + assertNull(tokenResponse.getErrorDescription()); TokenUtil token = new TokenUtil("view-applications-access", "password"); List applications = SimpleHttp @@ -623,7 +625,7 @@ public void listApplicationsOfflineAccess() throws Exception { oauth.scope(OAuth2Constants.OFFLINE_ACCESS); oauth.clientId("offline-client"); OAuthClient.AccessTokenResponse offlineTokenResponse = oauth.doGrantAccessTokenRequest("secret1", "view-applications-access", "password"); - Assert.assertNull(offlineTokenResponse.getErrorDescription()); + assertNull(offlineTokenResponse.getErrorDescription()); TokenUtil token = new TokenUtil("view-applications-access", "password"); List applications = SimpleHttp @@ -687,7 +689,7 @@ public void listApplicationsThirdParty() throws Exception { public void listApplicationsWithRootUrl() throws Exception { oauth.clientId("root-url-client"); OAuthClient.AccessTokenResponse tokenResponse = oauth.doGrantAccessTokenRequest("password", "view-applications-access", "password"); - Assert.assertNull(tokenResponse.getErrorDescription()); + assertNull(tokenResponse.getErrorDescription()); TokenUtil token = new TokenUtil("view-applications-access", "password"); List applications = SimpleHttp @@ -1100,7 +1102,7 @@ public void revokeOfflineAccess() throws Exception { oauth.scope(OAuth2Constants.OFFLINE_ACCESS); oauth.clientId("offline-client"); OAuthClient.AccessTokenResponse offlineTokenResponse = oauth.doGrantAccessTokenRequest("secret1", "view-applications-access", "password"); - Assert.assertNull(offlineTokenResponse.getErrorDescription()); + assertNull(offlineTokenResponse.getErrorDescription()); TokenUtil token = new TokenUtil("view-applications-access", "password"); @@ -1142,4 +1144,47 @@ public void testInvalidApiVersion() throws IOException { assertEquals("API version not found", response.asJson().get("error").textValue()); assertEquals(404, response.getStatus()); } + + @Test + public void testAudience() throws Exception { + oauth.clientId("custom-audience"); + OAuthClient.AccessTokenResponse tokenResponse = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password"); + assertNull(tokenResponse.getErrorDescription()); + + SimpleHttp.Response response = SimpleHttp.doGet(getAccountUrl(null), httpClient) + .auth(tokenResponse.getAccessToken()) + .header("Accept", "application/json") + .asResponse(); + assertEquals(401, response.getStatus()); + + // update to correct audience + org.keycloak.representations.idm.ClientRepresentation clientRep = testRealm().clients().findByClientId("custom-audience").get(0); + ProtocolMapperRepresentation mapperRep = clientRep.getProtocolMappers().stream().filter(m -> m.getName().equals("aud")).findFirst().orElse(null); + assertNotNull("Audience mapper not found", mapperRep); + mapperRep.getConfig().put("included.custom.audience", "account"); + testRealm().clients().get(clientRep.getId()).getProtocolMappers().update(mapperRep.getId(), mapperRep); + + tokenResponse = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password"); + assertNull(tokenResponse.getErrorDescription()); + + response = SimpleHttp.doGet(getAccountUrl(null), httpClient) + .auth(tokenResponse.getAccessToken()) + .header("Accept", "application/json") + .asResponse(); + assertEquals(200, response.getStatus()); + + // remove audience completely + testRealm().clients().get(clientRep.getId()).getProtocolMappers().delete(mapperRep.getId()); + + tokenResponse = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password"); + assertNull(tokenResponse.getErrorDescription()); + + response = SimpleHttp.doGet(getAccountUrl(null), httpClient) + .auth(tokenResponse.getAccessToken()) + .header("Accept", "application/json") + .asResponse(); + assertEquals(401, response.getStatus()); + + // custom-audience client is used only in this test so no need to revert the changes + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json index 1c41b986d0f3..513e6769f5ef 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json @@ -414,7 +414,77 @@ "enabled": true, "directAccessGrantsEnabled": true, "secret": "password", - "webOrigins": [ "http://localtest.me:8180" ] + "webOrigins": [ "http://localtest.me:8180" ], + "protocolMappers": [ + { + "name": "aud-account", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "config": { + "included.client.audience": "account", + "id.token.claim": "true", + "access.token.claim": "true" + } + }, + { + "name": "aud-admin", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "config": { + "included.client.audience": "security-admin-console", + "id.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + + { + "clientId": "custom-audience", + "enabled": true, + "directAccessGrantsEnabled": true, + "secret": "password", + "protocolMappers": [ + { + "name": "aud", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "included.custom.audience": "foo-bar" + } + }, + { + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "profile", + "email" + ] } ], "roles" : {