KEYCLOAK-15270 Account REST API doesn't verify audience

This commit is contained in:
vmuzikar 2020-09-07 10:49:50 +02:00 committed by Bruno Oliveira da Silva
parent b62d68a591
commit a9a719b88c
13 changed files with 225 additions and 53 deletions

View file

@ -15,7 +15,7 @@ public class ExampleRestResource {
public ExampleRestResource(KeycloakSession session) { public ExampleRestResource(KeycloakSession session) {
this.session = session; this.session = session;
this.auth = new AppAuthManager().authenticateBearerToken(session, session.getContext().getRealm()); this.auth = new AppAuthManager.BearerTokenAuthenticator(session).authenticate();
} }
@Path("companies") @Path("companies")

View file

@ -30,9 +30,7 @@ import org.keycloak.services.managers.AuthenticationManager.AuthResult;
public class Tokens { public class Tokens {
public static AccessToken getAccessToken(KeycloakSession keycloakSession) { public static AccessToken getAccessToken(KeycloakSession keycloakSession) {
AppAuthManager authManager = new AppAuthManager(); AuthResult authResult = new AppAuthManager.BearerTokenAuthenticator(keycloakSession).authenticate();
KeycloakContext context = keycloakSession.getContext();
AuthResult authResult = authManager.authenticateBearerToken(keycloakSession, context.getRealm(), context.getUri(), context.getConnection(), context.getRequestHeaders());
if (authResult != null) { if (authResult != null) {
return authResult.getToken(); return authResult.getToken();
@ -42,9 +40,9 @@ public class Tokens {
} }
public static AccessToken getAccessToken(String accessToken, KeycloakSession keycloakSession) { public static AccessToken getAccessToken(String accessToken, KeycloakSession keycloakSession) {
AppAuthManager authManager = new AppAuthManager(); AuthResult authResult = new AppAuthManager.BearerTokenAuthenticator(keycloakSession)
KeycloakContext context = keycloakSession.getContext(); .setTokenString(accessToken)
AuthResult authResult = authManager.authenticateBearerToken(accessToken, keycloakSession, context.getRealm(), context.getUri(), context.getConnection(), context.getRequestHeaders()); .authenticate();
if (authResult != null) { if (authResult != null) {
return authResult.getToken(); return authResult.getToken();

View file

@ -84,7 +84,6 @@ import org.keycloak.services.CorsErrorResponseException;
import org.keycloak.services.ServicesLogger; import org.keycloak.services.ServicesLogger;
import org.keycloak.services.Urls; import org.keycloak.services.Urls;
import org.keycloak.services.clientpolicy.ClientPolicyException; import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.DefaultClientPolicyManager;
import org.keycloak.services.clientpolicy.TokenRefreshContext; import org.keycloak.services.clientpolicy.TokenRefreshContext;
import org.keycloak.services.clientpolicy.TokenRequestContext; import org.keycloak.services.clientpolicy.TokenRequestContext;
import org.keycloak.services.managers.AppAuthManager; import org.keycloak.services.managers.AppAuthManager;
@ -797,7 +796,7 @@ public class TokenEndpoint {
} }
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) { if (authResult == null) {
event.detail(Details.REASON, "subject_token validation failure"); event.detail(Details.REASON, "subject_token validation failure");
event.error(Errors.INVALID_TOKEN); event.error(Errors.INVALID_TOKEN);

View file

@ -53,7 +53,7 @@ public class AppAuthManager extends AuthenticationManager {
* *
* @return the token string or {@literal null} * @return the token string or {@literal null}
*/ */
private String extractTokenStringFromAuthHeader(String authHeader) { private static String extractTokenStringFromAuthHeader(String authHeader) {
if (authHeader == null) { if (authHeader == null) {
return null; return null;
@ -83,7 +83,7 @@ public class AppAuthManager extends AuthenticationManager {
* @param headers * @param headers
* @return the token string or {@literal null} if the Authorization header is not of type Bearer, or the token string is missing. * @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); String authHeader = headers.getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
return extractTokenStringFromAuthHeader(authHeader); return extractTokenStringFromAuthHeader(authHeader);
} }
@ -95,7 +95,7 @@ public class AppAuthManager extends AuthenticationManager {
* @return the token string or {@literal null} of the Authorization header is missing * @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. * @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); String authHeader = headers.getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (authHeader == null) { if (authHeader == null) {
return null; return null;
@ -107,23 +107,65 @@ public class AppAuthManager extends AuthenticationManager {
return tokenString; return tokenString;
} }
public AuthResult authenticateBearerToken(KeycloakSession session, RealmModel realm) { public static class BearerTokenAuthenticator {
KeycloakContext ctx = session.getContext(); private KeycloakSession session;
return authenticateBearerToken(session, realm, ctx.getUri(), ctx.getConnection(), ctx.getRequestHeaders()); private RealmModel realm;
} private UriInfo uriInfo;
private ClientConnection connection;
private HttpHeaders headers;
private String tokenString;
private String audience;
public AuthResult authenticateBearerToken(KeycloakSession session) { public BearerTokenAuthenticator(KeycloakSession session) {
return authenticateBearerToken(session, session.getContext().getRealm(), session.getContext().getUri(), session.getContext().getConnection(), session.getContext().getRequestHeaders()); this.session = session;
} }
public AuthResult authenticateBearerToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) { public BearerTokenAuthenticator setSession(KeycloakSession session) {
return authenticateBearerToken(extractAuthorizationHeaderToken(headers), session, realm, uriInfo, connection, headers); this.session = session;
} return this;
}
public AuthResult authenticateBearerToken(String tokenString, KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) { public BearerTokenAuthenticator setRealm(RealmModel realm) {
if (tokenString == null) return null; this.realm = realm;
AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, true, true, false, tokenString, headers); return this;
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);
}
} }
} }

View file

@ -779,7 +779,7 @@ public class AuthenticationManager {
} }
String tokenString = cookie.getValue(); 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) { if (authResult == null) {
expireIdentityCookie(realm, session.getContext().getUri(), session.getContext().getConnection()); expireIdentityCookie(realm, session.getContext().getUri(), session.getContext().getConnection());
expireOldIdentityCookie(realm, session.getContext().getUri(), session.getContext().getConnection()); expireOldIdentityCookie(realm, session.getContext().getUri(), session.getContext().getConnection());
@ -1261,7 +1261,7 @@ public class AuthenticationManager {
} }
public static AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, boolean checkTokenType, public static AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, boolean checkTokenType,
boolean isCookie, String tokenString, HttpHeaders headers, Predicate<? super AccessToken>... additionalChecks) { String checkAudience, boolean isCookie, String tokenString, HttpHeaders headers, Predicate<? super AccessToken>... additionalChecks) {
try { try {
TokenVerifier<AccessToken> verifier = TokenVerifier.create(tokenString, AccessToken.class) TokenVerifier<AccessToken> verifier = TokenVerifier.create(tokenString, AccessToken.class)
.withDefaultChecks() .withDefaultChecks()
@ -1269,6 +1269,11 @@ public class AuthenticationManager {
.checkActive(checkActive) .checkActive(checkActive)
.checkTokenType(checkTokenType) .checkTokenType(checkTokenType)
.withChecks(additionalChecks); .withChecks(additionalChecks);
if (checkAudience != null) {
verifier.audience(checkAudience);
}
String kid = verifier.getHeader().getKeyId(); String kid = verifier.getHeader().getKeyId();
String algorithm = verifier.getHeader().getAlgorithm().name(); String algorithm = verifier.getHeader().getAlgorithm().name();

View file

@ -448,8 +448,11 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
this.event.event(EventType.IDENTITY_PROVIDER_RETRIEVE_TOKEN); this.event.event(EventType.IDENTITY_PROVIDER_RETRIEVE_TOKEN);
try { try {
AppAuthManager authManager = new AppAuthManager(); AuthenticationManager.AuthResult authResult = new AppAuthManager.BearerTokenAuthenticator(session)
AuthenticationManager.AuthResult authResult = authManager.authenticateBearerToken(this.session, this.realmModel, this.session.getContext().getUri(), this.clientConnection, this.request.getHttpHeaders()); .setRealm(realmModel)
.setConnection(clientConnection)
.setHeaders(request.getHttpHeaders())
.authenticate();
if (authResult != null) { if (authResult != null) {
AccessToken token = authResult.getToken(); AccessToken token = authResult.getToken();

View file

@ -114,7 +114,10 @@ public class AccountLoader {
} }
private AccountRestService getAccountRestService(ClientModel client, String versionStr) { 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) { if (authResult == null) {
throw new NotAuthorizedException("Bearer token required"); throw new NotAuthorizedException("Bearer token required");
} }

View file

@ -87,12 +87,10 @@ public class AdminConsole {
@Context @Context
protected Providers providers; protected Providers providers;
protected AppAuthManager authManager;
protected RealmModel realm; protected RealmModel realm;
public AdminConsole(RealmModel realm) { public AdminConsole(RealmModel realm) {
this.realm = realm; this.realm = realm;
this.authManager = new AppAuthManager();
} }
public static class WhoAmI { public static class WhoAmI {
@ -195,7 +193,12 @@ public class AdminConsole {
@NoCache @NoCache
public Response whoAmI(final @Context HttpHeaders headers) { public Response whoAmI(final @Context HttpHeaders headers) {
RealmManager realmManager = new RealmManager(session); 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) { if (authResult == null) {
return Response.status(401).build(); return Response.status(401).build();
} }

View file

@ -72,7 +72,6 @@ public class AdminRoot {
@Context @Context
protected HttpResponse response; protected HttpResponse response;
protected AppAuthManager authManager;
protected TokenManager tokenManager; protected TokenManager tokenManager;
@Context @Context
@ -80,7 +79,6 @@ public class AdminRoot {
public AdminRoot() { public AdminRoot() {
this.tokenManager = new TokenManager(); this.tokenManager = new TokenManager();
this.authManager = new AppAuthManager();
} }
public static UriBuilder adminBaseUrl(UriInfo uriInfo) { public static UriBuilder adminBaseUrl(UriInfo uriInfo) {
@ -153,7 +151,7 @@ public class AdminRoot {
protected AdminAuth authenticateRealmAdminRequest(HttpHeaders headers) { protected AdminAuth authenticateRealmAdminRequest(HttpHeaders headers) {
String tokenString = authManager.extractAuthorizationHeaderToken(headers); String tokenString = AppAuthManager.extractAuthorizationHeaderToken(headers);
if (tokenString == null) throw new NotAuthorizedException("Bearer"); if (tokenString == null) throw new NotAuthorizedException("Bearer");
AccessToken token; AccessToken token;
try { try {
@ -169,7 +167,13 @@ public class AdminRoot {
throw new NotAuthorizedException("Unknown realm in token"); throw new NotAuthorizedException("Unknown realm in token");
} }
session.getContext().setRealm(realm); 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) { if (authResult == null) {
logger.debug("Token not valid"); logger.debug("Token not valid");
throw new NotAuthorizedException("Bearer"); throw new NotAuthorizedException("Bearer");

View file

@ -32,7 +32,7 @@ public class ExampleRestResource {
public ExampleRestResource(KeycloakSession session) { public ExampleRestResource(KeycloakSession session) {
this.session = session; this.session = session;
this.auth = new AppAuthManager().authenticateBearerToken(session, session.getContext().getRealm()); this.auth = new AppAuthManager.BearerTokenAuthenticator(session).authenticate();
} }
@Path("companies") @Path("companies")

View file

@ -1203,7 +1203,7 @@ public class AccountFormServiceTest extends AbstractTestRealmKeycloakTest {
Map<String, AccountApplicationsPage.AppEntry> apps = applicationsPage.getApplications(); Map<String, AccountApplicationsPage.AppEntry> apps = applicationsPage.getApplications();
Assert.assertThat(apps.keySet(), containsInAnyOrder( 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()) rsu.add(testRealm().roles().get("user").toRepresentation())
.update(); .update();
@ -1211,7 +1211,7 @@ public class AccountFormServiceTest extends AbstractTestRealmKeycloakTest {
driver.navigate().refresh(); driver.navigate().refresh();
apps = applicationsPage.getApplications(); apps = applicationsPage.getApplications();
Assert.assertThat(apps.keySet(), containsInAnyOrder( 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 class AccountFormServiceTest extends AbstractTestRealmKeycloakTest {
Map<String, AccountApplicationsPage.AppEntry> apps = applicationsPage.getApplications(); Map<String, AccountApplicationsPage.AppEntry> apps = applicationsPage.getApplications();
Assert.assertThat(apps.keySet(), containsInAnyOrder( 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 class AccountFormServiceTest extends AbstractTestRealmKeycloakTest {
applicationsPage.assertCurrent(); applicationsPage.assertCurrent();
Map<String, AccountApplicationsPage.AppEntry> apps = applicationsPage.getApplications(); Map<String, AccountApplicationsPage.AppEntry> 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"); AccountApplicationsPage.AppEntry accountEntry = apps.get("Account");
Assert.assertThat(accountEntry.getRolesAvailable(), containsInAnyOrder( Assert.assertThat(accountEntry.getRolesAvailable(), containsInAnyOrder(

View file

@ -47,6 +47,7 @@ import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.ClientScopeRepresentation; import org.keycloak.representations.idm.ClientScopeRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.ErrorRepresentation; import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderRepresentation; import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderSimpleRepresentation; import org.keycloak.representations.idm.RequiredActionProviderSimpleRepresentation;
@ -73,6 +74,7 @@ import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.keycloak.common.Profile.Feature.ACCOUNT_API; import static org.keycloak.common.Profile.Feature.ACCOUNT_API;
@ -294,7 +296,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
null, UserModel.RequiredAction.UPDATE_PASSWORD.toString(), false, 1); null, UserModel.RequiredAction.UPDATE_PASSWORD.toString(), false, 1);
CredentialRepresentation password1 = password.getUserCredentials().get(0); CredentialRepresentation password1 = password.getUserCredentials().get(0);
Assert.assertNull(password1.getSecretData()); assertNull(password1.getSecretData());
Assert.assertNotNull(password1.getCredentialData()); Assert.assertNotNull(password1.getCredentialData());
AccountCredentialResource.CredentialContainer otp = credentials.get(1); AccountCredentialResource.CredentialContainer otp = credentials.get(1);
@ -341,7 +343,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
Assert.assertEquals(1, credentials.size()); Assert.assertEquals(1, credentials.size());
password = credentials.get(0); password = credentials.get(0);
Assert.assertEquals(PasswordCredentialModel.TYPE, password.getType()); Assert.assertEquals(PasswordCredentialModel.TYPE, password.getType());
Assert.assertNull(password.getUserCredentials()); assertNull(password.getUserCredentials());
} }
@ -452,8 +454,8 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
credentials = getCredentials(); credentials = getCredentials();
assertExpectedCredentialTypes(credentials, PasswordCredentialModel.TYPE, OTPCredentialModel.TYPE); assertExpectedCredentialTypes(credentials, PasswordCredentialModel.TYPE, OTPCredentialModel.TYPE);
AccountCredentialResource.CredentialContainer otpCredential = credentials.get(1); AccountCredentialResource.CredentialContainer otpCredential = credentials.get(1);
Assert.assertNull(otpCredential.getCreateAction()); assertNull(otpCredential.getCreateAction());
Assert.assertNull(otpCredential.getUpdateAction()); assertNull(otpCredential.getUpdateAction());
// Revert - re-enable requiredAction and remove OTP credential from the user // Revert - re-enable requiredAction and remove OTP credential from the user
setRequiredActionEnabledStatus(UserModel.RequiredAction.CONFIGURE_TOTP.name(), true); setRequiredActionEnabledStatus(UserModel.RequiredAction.CONFIGURE_TOTP.name(), true);
@ -578,7 +580,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
public void listApplications() throws Exception { public void listApplications() throws Exception {
oauth.clientId("in-use-client"); oauth.clientId("in-use-client");
OAuthClient.AccessTokenResponse tokenResponse = oauth.doGrantAccessTokenRequest("secret1", "view-applications-access", "password"); 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"); TokenUtil token = new TokenUtil("view-applications-access", "password");
List<ClientRepresentation> applications = SimpleHttp List<ClientRepresentation> applications = SimpleHttp
@ -600,7 +602,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
public void listApplicationsFiltered() throws Exception { public void listApplicationsFiltered() throws Exception {
oauth.clientId("in-use-client"); oauth.clientId("in-use-client");
OAuthClient.AccessTokenResponse tokenResponse = oauth.doGrantAccessTokenRequest("secret1", "view-applications-access", "password"); 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"); TokenUtil token = new TokenUtil("view-applications-access", "password");
List<ClientRepresentation> applications = SimpleHttp List<ClientRepresentation> applications = SimpleHttp
@ -623,7 +625,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
oauth.scope(OAuth2Constants.OFFLINE_ACCESS); oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
oauth.clientId("offline-client"); oauth.clientId("offline-client");
OAuthClient.AccessTokenResponse offlineTokenResponse = oauth.doGrantAccessTokenRequest("secret1", "view-applications-access", "password"); 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"); TokenUtil token = new TokenUtil("view-applications-access", "password");
List<ClientRepresentation> applications = SimpleHttp List<ClientRepresentation> applications = SimpleHttp
@ -687,7 +689,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
public void listApplicationsWithRootUrl() throws Exception { public void listApplicationsWithRootUrl() throws Exception {
oauth.clientId("root-url-client"); oauth.clientId("root-url-client");
OAuthClient.AccessTokenResponse tokenResponse = oauth.doGrantAccessTokenRequest("password", "view-applications-access", "password"); 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"); TokenUtil token = new TokenUtil("view-applications-access", "password");
List<ClientRepresentation> applications = SimpleHttp List<ClientRepresentation> applications = SimpleHttp
@ -1100,7 +1102,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
oauth.scope(OAuth2Constants.OFFLINE_ACCESS); oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
oauth.clientId("offline-client"); oauth.clientId("offline-client");
OAuthClient.AccessTokenResponse offlineTokenResponse = oauth.doGrantAccessTokenRequest("secret1", "view-applications-access", "password"); 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"); TokenUtil token = new TokenUtil("view-applications-access", "password");
@ -1142,4 +1144,47 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
assertEquals("API version not found", response.asJson().get("error").textValue()); assertEquals("API version not found", response.asJson().get("error").textValue());
assertEquals(404, response.getStatus()); 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
}
} }

View file

@ -414,7 +414,77 @@
"enabled": true, "enabled": true,
"directAccessGrantsEnabled": true, "directAccessGrantsEnabled": true,
"secret": "password", "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" : { "roles" : {