KEYCLOAK-15270 Account REST API doesn't verify audience
This commit is contained in:
parent
b62d68a591
commit
a9a719b88c
13 changed files with 225 additions and 53 deletions
|
@ -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")
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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" : {
|
||||||
|
|
Loading…
Reference in a new issue