KEYCLOAK-8484 Remove audience client scope template
This commit is contained in:
parent
91c4bfa81c
commit
9652748ba9
17 changed files with 76 additions and 334 deletions
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
package org.keycloak.admin.client.resource;
|
package org.keycloak.admin.client.resource;
|
||||||
|
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
|
||||||
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
|
@ -26,7 +25,6 @@ import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -47,18 +45,4 @@ public interface ClientScopesResource {
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
List<ClientScopeRepresentation> findAll();
|
List<ClientScopeRepresentation> findAll();
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate new client scope for specified service client. The "Frontend" clients, who will use this client scope, will be able to
|
|
||||||
* send their access token to authenticate against specified service client
|
|
||||||
*
|
|
||||||
* @param clientId Client ID of service client (typically bearer-only client)
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Path("generate-audience-client-scope")
|
|
||||||
@POST
|
|
||||||
@NoCache
|
|
||||||
Response generateAudienceClientScope(final @QueryParam("clientId") String clientId);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,19 +103,22 @@ public class KeycloakOIDCClientInstallation implements ClientInstallationProvide
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Check if there is audience client scope created for particular client. If yes, admin wants verifying token audience
|
|
||||||
static boolean showVerifyTokenAudience(ClientModel client) {
|
static boolean showVerifyTokenAudience(ClientModel client) {
|
||||||
String clientId = client.getClientId();
|
// We want to verify-token-audience if service client has any client roles
|
||||||
ClientScopeModel clientScope = KeycloakModelUtils.getClientScopeByName(client.getRealm(), clientId);
|
if (client.getRoles().size() > 0) {
|
||||||
if (clientScope == null) {
|
return true;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if there is client scope with audience protocol mapper created for particular client. If yes, admin wants verifying token audience
|
||||||
|
String clientId = client.getClientId();
|
||||||
|
|
||||||
|
for (ClientScopeModel clientScope : client.getRealm().getClientScopes()) {
|
||||||
for (ProtocolMapperModel protocolMapper : clientScope.getProtocolMappers()) {
|
for (ProtocolMapperModel protocolMapper : clientScope.getProtocolMappers()) {
|
||||||
if (AudienceProtocolMapper.PROVIDER_ID.equals(protocolMapper.getProtocolMapper()) && (clientId.equals(protocolMapper.getConfig().get(AudienceProtocolMapper.INCLUDED_CLIENT_AUDIENCE)))) {
|
if (AudienceProtocolMapper.PROVIDER_ID.equals(protocolMapper.getProtocolMapper()) && (clientId.equals(protocolMapper.getConfig().get(AudienceProtocolMapper.INCLUDED_CLIENT_AUDIENCE)))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,17 +22,12 @@ import org.jboss.resteasy.spi.NotFoundException;
|
||||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
import org.keycloak.events.admin.OperationType;
|
import org.keycloak.events.admin.OperationType;
|
||||||
import org.keycloak.events.admin.ResourceType;
|
import org.keycloak.events.admin.ResourceType;
|
||||||
import org.keycloak.models.ClientModel;
|
|
||||||
import org.keycloak.models.ClientScopeModel;
|
import org.keycloak.models.ClientScopeModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
import org.keycloak.models.utils.RepresentationToModel;
|
import org.keycloak.models.utils.RepresentationToModel;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
|
||||||
import org.keycloak.protocol.oidc.mappers.AudienceProtocolMapper;
|
|
||||||
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
||||||
import org.keycloak.services.ErrorResponse;
|
import org.keycloak.services.ErrorResponse;
|
||||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
||||||
|
@ -43,7 +38,6 @@ import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
@ -124,57 +118,6 @@ public class ClientScopesResource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate new client scope for specified service client. The "Frontend" clients, who will use this client scope, will be able to
|
|
||||||
* send their access token to authenticate against specified service client
|
|
||||||
*
|
|
||||||
* @param clientId Client ID of service client (typically bearer-only client)
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Path("generate-audience-client-scope")
|
|
||||||
@POST
|
|
||||||
@NoCache
|
|
||||||
public Response generateAudienceClientScope(final @QueryParam("clientId") String clientId) {
|
|
||||||
auth.clients().requireManageClientScopes();
|
|
||||||
|
|
||||||
logger.debugf("Generating audience scope for service client: " + clientId);
|
|
||||||
|
|
||||||
String clientScopeName = clientId;
|
|
||||||
try {
|
|
||||||
ClientModel serviceClient = realm.getClientByClientId(clientId);
|
|
||||||
if (serviceClient == null) {
|
|
||||||
logger.warnf("Referenced service client '%s' doesn't exists", clientId);
|
|
||||||
return ErrorResponse.exists("Referenced service client doesn't exists");
|
|
||||||
}
|
|
||||||
|
|
||||||
ClientScopeModel clientScopeModel = realm.addClientScope(clientScopeName);
|
|
||||||
clientScopeModel.setDescription("Client scope useful for frontend clients, which want to call service " + clientId);
|
|
||||||
clientScopeModel.setProtocol(serviceClient.getProtocol()==null ? OIDCLoginProtocol.LOGIN_PROTOCOL : serviceClient.getProtocol());
|
|
||||||
clientScopeModel.setDisplayOnConsentScreen(true);
|
|
||||||
|
|
||||||
String consentText = serviceClient.getName() != null ? serviceClient.getName() : serviceClient.getClientId();
|
|
||||||
consentText = consentText.substring(0, 1).toUpperCase() + consentText.substring(1);
|
|
||||||
clientScopeModel.setConsentScreenText(consentText);
|
|
||||||
|
|
||||||
clientScopeModel.setIncludeInTokenScope(true);
|
|
||||||
|
|
||||||
// Add audience protocol mapper
|
|
||||||
ProtocolMapperModel audienceMapper = AudienceProtocolMapper.createClaimMapper("Audience for " + clientId, clientId, null,true, false);
|
|
||||||
clientScopeModel.addProtocolMapper(audienceMapper);
|
|
||||||
|
|
||||||
// Add scope to client roles
|
|
||||||
for (RoleModel role : serviceClient.getRoles()) {
|
|
||||||
clientScopeModel.addScopeMapping(role);
|
|
||||||
}
|
|
||||||
|
|
||||||
adminEvent.operation(OperationType.CREATE).resourcePath(session.getContext().getUri()).success();
|
|
||||||
|
|
||||||
return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(clientScopeModel.getId()).build()).build();
|
|
||||||
} catch (ModelDuplicateException e) {
|
|
||||||
return ErrorResponse.exists("Client Scope " + clientScopeName + " already exists");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base path for managing a specific client scope.
|
* Base path for managing a specific client scope.
|
||||||
*
|
*
|
||||||
|
|
|
@ -20,7 +20,6 @@ package org.keycloak.testsuite.rest;
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.jboss.resteasy.spi.BadRequestException;
|
import org.jboss.resteasy.spi.BadRequestException;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.keycloak.common.util.MultivaluedHashMap;
|
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.events.Event;
|
import org.keycloak.events.Event;
|
||||||
|
@ -34,9 +33,11 @@ import org.keycloak.events.admin.OperationType;
|
||||||
import org.keycloak.events.admin.ResourceType;
|
import org.keycloak.events.admin.ResourceType;
|
||||||
import org.keycloak.models.AuthenticationFlowModel;
|
import org.keycloak.models.AuthenticationFlowModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.ClientScopeModel;
|
||||||
import org.keycloak.models.FederatedIdentityModel;
|
import org.keycloak.models.FederatedIdentityModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.LDAPConstants;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RealmProvider;
|
import org.keycloak.models.RealmProvider;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
@ -45,6 +46,8 @@ import org.keycloak.models.UserProvider;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.sessions.infinispan.changes.sessions.LastSessionRefreshStoreFactory;
|
import org.keycloak.models.sessions.infinispan.changes.sessions.LastSessionRefreshStoreFactory;
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
|
import org.keycloak.protocol.oidc.mappers.AudienceProtocolMapper;
|
||||||
import org.keycloak.provider.ProviderFactory;
|
import org.keycloak.provider.ProviderFactory;
|
||||||
import org.keycloak.representations.idm.AdminEventRepresentation;
|
import org.keycloak.representations.idm.AdminEventRepresentation;
|
||||||
import org.keycloak.representations.idm.AuthDetailsRepresentation;
|
import org.keycloak.representations.idm.AuthDetailsRepresentation;
|
||||||
|
@ -52,12 +55,9 @@ import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
|
||||||
import org.keycloak.representations.idm.EventRepresentation;
|
import org.keycloak.representations.idm.EventRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
|
||||||
import org.keycloak.services.resource.RealmResourceProvider;
|
import org.keycloak.services.resource.RealmResourceProvider;
|
||||||
import org.keycloak.services.scheduled.ClearExpiredUserSessions;
|
import org.keycloak.services.scheduled.ClearExpiredUserSessions;
|
||||||
import org.keycloak.storage.UserStorageProvider;
|
import org.keycloak.storage.UserStorageProvider;
|
||||||
import org.keycloak.storage.UserStorageProviderModel;
|
|
||||||
import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
|
|
||||||
import org.keycloak.testsuite.components.TestProvider;
|
import org.keycloak.testsuite.components.TestProvider;
|
||||||
import org.keycloak.testsuite.components.TestProviderFactory;
|
import org.keycloak.testsuite.components.TestProviderFactory;
|
||||||
import org.keycloak.testsuite.events.EventsListenerProvider;
|
import org.keycloak.testsuite.events.EventsListenerProvider;
|
||||||
|
@ -99,7 +99,6 @@ import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TimerTask;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -126,11 +125,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
@Path("/remove-user-session")
|
@Path("/remove-user-session")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Response removeUserSession(@QueryParam("realm") final String name, @QueryParam("session") final String sessionId) {
|
public Response removeUserSession(@QueryParam("realm") final String name, @QueryParam("session") final String sessionId) {
|
||||||
RealmManager realmManager = new RealmManager(session);
|
RealmModel realm = getRealmByName(name);
|
||||||
RealmModel realm = realmManager.getRealmByName(name);
|
|
||||||
if (realm == null) {
|
|
||||||
throw new NotFoundException("Realm not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
UserSessionModel sessionModel = session.sessions().getUserSession(realm, sessionId);
|
UserSessionModel sessionModel = session.sessions().getUserSession(realm, sessionId);
|
||||||
if (sessionModel == null) {
|
if (sessionModel == null) {
|
||||||
|
@ -145,11 +140,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
@Path("/remove-user-sessions")
|
@Path("/remove-user-sessions")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Response removeUserSessions(@QueryParam("realm") final String realmName) {
|
public Response removeUserSessions(@QueryParam("realm") final String realmName) {
|
||||||
RealmManager realmManager = new RealmManager(session);
|
RealmModel realm = getRealmByName(realmName);
|
||||||
RealmModel realm = realmManager.getRealmByName(realmName);
|
|
||||||
if (realm == null) {
|
|
||||||
throw new NotFoundException("Realm not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
session.sessions().removeUserSessions(realm);
|
session.sessions().removeUserSessions(realm);
|
||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
|
@ -159,12 +150,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
@Path("/get-last-session-refresh")
|
@Path("/get-last-session-refresh")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Integer getLastSessionRefresh(@QueryParam("realm") final String name, @QueryParam("session") final String sessionId, @QueryParam("offline") boolean offline) {
|
public Integer getLastSessionRefresh(@QueryParam("realm") final String name, @QueryParam("session") final String sessionId, @QueryParam("offline") boolean offline) {
|
||||||
|
RealmModel realm = getRealmByName(name);
|
||||||
RealmManager realmManager = new RealmManager(session);
|
|
||||||
RealmModel realm = realmManager.getRealmByName(name);
|
|
||||||
if (realm == null) {
|
|
||||||
throw new NotFoundException("Realm not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
UserSessionModel sessionModel = offline ? session.sessions().getOfflineUserSession(realm, sessionId) : session.sessions().getUserSession(realm, sessionId);
|
UserSessionModel sessionModel = offline ? session.sessions().getOfflineUserSession(realm, sessionId) : session.sessions().getUserSession(realm, sessionId);
|
||||||
if (sessionModel == null) {
|
if (sessionModel == null) {
|
||||||
|
@ -178,11 +164,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
@Path("/remove-expired")
|
@Path("/remove-expired")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Response removeExpired(@QueryParam("realm") final String name) {
|
public Response removeExpired(@QueryParam("realm") final String name) {
|
||||||
RealmManager realmManager = new RealmManager(session);
|
RealmModel realm = getRealmByName(name);
|
||||||
RealmModel realm = realmManager.getRealmByName(name);
|
|
||||||
if (realm == null) {
|
|
||||||
throw new NotFoundException("Realm not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
session.sessions().removeExpired(realm);
|
session.sessions().removeExpired(realm);
|
||||||
session.authenticationSessions().removeExpired(realm);
|
session.authenticationSessions().removeExpired(realm);
|
||||||
|
@ -196,11 +178,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Integer getClientSessionsCountInUserSession(@QueryParam("realm") final String name, @QueryParam("session") final String sessionId) {
|
public Integer getClientSessionsCountInUserSession(@QueryParam("realm") final String name, @QueryParam("session") final String sessionId) {
|
||||||
|
|
||||||
RealmManager realmManager = new RealmManager(session);
|
RealmModel realm = getRealmByName(name);
|
||||||
RealmModel realm = realmManager.getRealmByName(name);
|
|
||||||
if (realm == null) {
|
|
||||||
throw new NotFoundException("Realm not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
UserSessionModel sessionModel = session.sessions().getUserSession(realm, sessionId);
|
UserSessionModel sessionModel = session.sessions().getUserSession(realm, sessionId);
|
||||||
if (sessionModel == null) {
|
if (sessionModel == null) {
|
||||||
|
@ -753,6 +731,42 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate new client scope for specified service client. The "Frontend" clients, who will use this client scope, will be able to
|
||||||
|
* send their access token to authenticate against specified service client
|
||||||
|
*
|
||||||
|
* @param clientId Client ID of service client (typically bearer-only client)
|
||||||
|
* @return ID of the newly generated clientScope
|
||||||
|
*/
|
||||||
|
@Path("generate-audience-client-scope")
|
||||||
|
@POST
|
||||||
|
@NoCache
|
||||||
|
public String generateAudienceClientScope(@QueryParam("realm") final String realmName, final @QueryParam("clientId") String clientId) {
|
||||||
|
try {
|
||||||
|
RealmModel realm = getRealmByName(realmName);
|
||||||
|
ClientModel serviceClient = realm.getClientByClientId(clientId);
|
||||||
|
if (serviceClient == null) {
|
||||||
|
throw new NotFoundException("Referenced service client doesn't exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientScopeModel clientScopeModel = realm.addClientScope(clientId);
|
||||||
|
clientScopeModel.setProtocol(serviceClient.getProtocol()==null ? OIDCLoginProtocol.LOGIN_PROTOCOL : serviceClient.getProtocol());
|
||||||
|
clientScopeModel.setDisplayOnConsentScreen(true);
|
||||||
|
clientScopeModel.setConsentScreenText(clientId);
|
||||||
|
clientScopeModel.setIncludeInTokenScope(true);
|
||||||
|
|
||||||
|
// Add audience protocol mapper
|
||||||
|
ProtocolMapperModel audienceMapper = AudienceProtocolMapper.createClaimMapper("Audience for " + clientId, clientId, null,true, false);
|
||||||
|
clientScopeModel.addProtocolMapper(audienceMapper);
|
||||||
|
|
||||||
|
return clientScopeModel.getId();
|
||||||
|
} catch (ModelDuplicateException e) {
|
||||||
|
throw new BadRequestException("Client Scope " + clientId + " already exists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/run-on-server")
|
@Path("/run-on-server")
|
||||||
@Consumes(MediaType.TEXT_PLAIN_UTF_8)
|
@Consumes(MediaType.TEXT_PLAIN_UTF_8)
|
||||||
|
@ -810,7 +824,11 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
|
|
||||||
private RealmModel getRealmByName(String realmName) {
|
private RealmModel getRealmByName(String realmName) {
|
||||||
RealmProvider realmProvider = session.getProvider(RealmProvider.class);
|
RealmProvider realmProvider = session.getProvider(RealmProvider.class);
|
||||||
return realmProvider.getRealmByName(realmName);
|
RealmModel realm = realmProvider.getRealmByName(realmName);
|
||||||
|
if (realm == null) {
|
||||||
|
throw new NotFoundException("Realm not found");
|
||||||
|
}
|
||||||
|
return realm;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -275,6 +275,11 @@ public interface TestingResource {
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
Response restorePeriodicTasks();
|
Response restorePeriodicTasks();
|
||||||
|
|
||||||
|
@Path("generate-audience-client-scope")
|
||||||
|
@POST
|
||||||
|
@NoCache
|
||||||
|
String generateAudienceClientScope(@QueryParam("realm") final String realmName, final @QueryParam("clientId") String clientId);
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/uncaught-error")
|
@Path("/uncaught-error")
|
||||||
@Produces(MediaType.TEXT_HTML_UTF_8)
|
@Produces(MediaType.TEXT_HTML_UTF_8)
|
||||||
|
|
|
@ -33,7 +33,6 @@ import javax.security.auth.login.AppConfigurationEntry;
|
||||||
import javax.security.auth.login.Configuration;
|
import javax.security.auth.login.Configuration;
|
||||||
import javax.security.auth.login.LoginContext;
|
import javax.security.auth.login.LoginContext;
|
||||||
import javax.security.auth.login.LoginException;
|
import javax.security.auth.login.LoginException;
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
@ -68,9 +67,8 @@ public class LoginModulesTest extends AbstractKeycloakTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate audience client scope
|
// Generate audience client scope
|
||||||
Response resp = adminClient.realm("demo").clientScopes().generateAudienceClientScope("customer-db-audience-required");
|
String clientScopeId = testingClient.testing().generateAudienceClientScope("demo", "customer-db-audience-required");
|
||||||
String clientScopeId = ApiUtil.getCreatedId(resp);
|
|
||||||
resp.close();
|
|
||||||
ClientResource client = ApiUtil.findClientByClientId(adminClient.realm("demo"), "customer-portal");
|
ClientResource client = ApiUtil.findClientByClientId(adminClient.realm("demo"), "customer-portal");
|
||||||
client.addOptionalClientScope(clientScopeId);
|
client.addOptionalClientScope(clientScopeId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -831,9 +831,8 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
|
||||||
@Test
|
@Test
|
||||||
public void testVerifyTokenAudience() {
|
public void testVerifyTokenAudience() {
|
||||||
// Generate audience client scope
|
// Generate audience client scope
|
||||||
Response resp = adminClient.realm("demo").clientScopes().generateAudienceClientScope("customer-db-audience-required");
|
String clientScopeId = testingClient.testing().generateAudienceClientScope("demo", "customer-db-audience-required");
|
||||||
String clientScopeId = ApiUtil.getCreatedId(resp);
|
|
||||||
resp.close();
|
|
||||||
ClientResource client = ApiUtil.findClientByClientId(adminClient.realm("demo"), "customer-portal");
|
ClientResource client = ApiUtil.findClientByClientId(adminClient.realm("demo"), "customer-portal");
|
||||||
client.addOptionalClientScope(clientScopeId);
|
client.addOptionalClientScope(clientScopeId);
|
||||||
|
|
||||||
|
|
|
@ -17,20 +17,15 @@
|
||||||
|
|
||||||
package org.keycloak.testsuite.admin.client;
|
package org.keycloak.testsuite.admin.client;
|
||||||
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.admin.client.resource.ClientResource;
|
import org.keycloak.admin.client.resource.ClientResource;
|
||||||
import org.keycloak.events.admin.OperationType;
|
import org.keycloak.events.admin.OperationType;
|
||||||
import org.keycloak.events.admin.ResourceType;
|
import org.keycloak.events.admin.ResourceType;
|
||||||
import org.keycloak.testsuite.ProfileAssume;
|
|
||||||
import org.keycloak.testsuite.admin.ApiUtil;
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
|
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
|
||||||
import org.keycloak.testsuite.util.AdminEventPaths;
|
import org.keycloak.testsuite.util.AdminEventPaths;
|
||||||
import org.keycloak.testsuite.util.WaitUtils;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
|
@ -109,10 +104,7 @@ public class InstallationTest extends AbstractClientTest {
|
||||||
@Test
|
@Test
|
||||||
public void testOidcBearerOnlyJsonWithAudienceClientScope() {
|
public void testOidcBearerOnlyJsonWithAudienceClientScope() {
|
||||||
// Generate audience client scope
|
// Generate audience client scope
|
||||||
Response resp = testRealmResource().clientScopes().generateAudienceClientScope(OIDC_NAME_BEARER_ONLY_NAME);
|
String clientScopeId = testingClient.testing().generateAudienceClientScope("test", OIDC_NAME_BEARER_ONLY_NAME);
|
||||||
String clientScopeId = ApiUtil.getCreatedId(resp);
|
|
||||||
resp.close();
|
|
||||||
assertAdminEvents.assertEvent(getRealmId(), OperationType.CREATE, AdminEventPaths.clientScopeGenerateAudienceClientScopePath(), null, ResourceType.CLIENT_SCOPE);
|
|
||||||
|
|
||||||
String json = oidcBearerOnlyClient.getInstallationProvider("keycloak-oidc-keycloak-json");
|
String json = oidcBearerOnlyClient.getInstallationProvider("keycloak-oidc-keycloak-json");
|
||||||
assertOidcInstallationConfig(json);
|
assertOidcInstallationConfig(json);
|
||||||
|
|
|
@ -179,55 +179,6 @@ public class AudienceTest extends AbstractOIDCScopeTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAudienceClientScopeGeneration() throws Exception {
|
|
||||||
// Generate the "Audience" client scope for the "service-client" as an audience
|
|
||||||
Response resp = testRealm().clientScopes().generateAudienceClientScope("service-client");
|
|
||||||
String audienceScopeId = ApiUtil.getCreatedId(resp);
|
|
||||||
resp.close();
|
|
||||||
|
|
||||||
// Login and check audiences in the token. It's no audience for "service-client" yet and no clientRoles of "service-client" in the token
|
|
||||||
oauth.scope("openid service-client");
|
|
||||||
oauth.doLogin("john", "password");
|
|
||||||
EventRepresentation loginEvent = events.expectLogin()
|
|
||||||
.user(userId)
|
|
||||||
.assertEvent();
|
|
||||||
Tokens tokens = sendTokenRequest(loginEvent, userId,"openid profile email", "test-app");
|
|
||||||
assertAudiences(tokens.accessToken);
|
|
||||||
assertAudiences(tokens.idToken, "test-app");
|
|
||||||
Assert.assertFalse(tokens.accessToken.getResourceAccess().containsKey("service-client"));
|
|
||||||
|
|
||||||
// Logout
|
|
||||||
oauth.doLogout(tokens.refreshToken, "password");
|
|
||||||
events.expectLogout(tokens.idToken.getSessionState())
|
|
||||||
.client("test-app")
|
|
||||||
.user(userId)
|
|
||||||
.removeDetail(Details.REDIRECT_URI).assertEvent();
|
|
||||||
|
|
||||||
|
|
||||||
// Add clientScope to the test-app client
|
|
||||||
ClientResource testApp = ApiUtil.findClientByClientId(testRealm(), "test-app");
|
|
||||||
testApp.addOptionalClientScope(audienceScopeId);
|
|
||||||
|
|
||||||
// Login again and check audiences in the token. Now there is audience for "service-client" and clientRoles of "service-client" in the token
|
|
||||||
oauth.scope("openid service-client");
|
|
||||||
oauth.doLogin("john", "password");
|
|
||||||
loginEvent = events.expectLogin()
|
|
||||||
.user(userId)
|
|
||||||
.assertEvent();
|
|
||||||
tokens = sendTokenRequest(loginEvent, userId,"openid profile email service-client", "test-app");
|
|
||||||
assertAudiences(tokens.accessToken, "service-client");
|
|
||||||
assertAudiences(tokens.idToken, "test-app");
|
|
||||||
Assert.assertTrue(tokens.accessToken.getResourceAccess().containsKey("service-client"));
|
|
||||||
Assert.assertNames(tokens.accessToken.getResourceAccess().get("service-client").getRoles(), "role1");
|
|
||||||
|
|
||||||
// Revert
|
|
||||||
testApp.removeOptionalClientScope(audienceScopeId);
|
|
||||||
testRealm().clientScopes().get(audienceScopeId).remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void assertAudiences(JsonWebToken token, String... expectedAudience) {
|
private void assertAudiences(JsonWebToken token, String... expectedAudience) {
|
||||||
Collection<String> audiences = token.getAudience() == null ? Collections.emptyList() : Arrays.asList(token.getAudience());
|
Collection<String> audiences = token.getAudience() == null ? Collections.emptyList() : Arrays.asList(token.getAudience());
|
||||||
Collection<String> expectedAudiences = Arrays.asList(expectedAudience);
|
Collection<String> expectedAudiences = Arrays.asList(expectedAudience);
|
||||||
|
|
|
@ -837,12 +837,6 @@ client-storage=Client Storage
|
||||||
no-client-storage-providers-configured=No client storage providers configured
|
no-client-storage-providers-configured=No client storage providers configured
|
||||||
client-stores.tooltip=Keycloak can retrieve clients and their details from external stores.
|
client-stores.tooltip=Keycloak can retrieve clients and their details from external stores.
|
||||||
|
|
||||||
add-client-scope-step-1=Add Client Scope - Step 1
|
|
||||||
add-client-scope-step-2=Add Client Scope - Step 2
|
|
||||||
client-scope-template=Client Scope Template
|
|
||||||
client-scope-template.tooltip=Choose if you want to use some client scope template. Template allows you to easily create client scope with some pre-set options and protocol mappers. It is similar to the Archetype concept in Maven.
|
|
||||||
audience-client=Audience
|
|
||||||
audience-client.tooltip=Used audience (service client). Newly created client scope will contain audience protocol mapper and role scopes of all client roles of chosen service client. This is useful if you want to invoke the specified audience (service client) from your frontend clients.
|
|
||||||
client-scope.name.tooltip=Name of the client scope. Must be unique in the realm. Name shouldn't contain space characters as it's used as value of scope parameter
|
client-scope.name.tooltip=Name of the client scope. Must be unique in the realm. Name shouldn't contain space characters as it's used as value of scope parameter
|
||||||
client-scope.description.tooltip=Description of the client scope
|
client-scope.description.tooltip=Description of the client scope
|
||||||
client-scope.protocol.tooltip=Which SSO protocol configuration is being supplied by this client scope
|
client-scope.protocol.tooltip=Which SSO protocol configuration is being supplied by this client scope
|
||||||
|
|
|
@ -1458,19 +1458,7 @@ module.config([ '$routeProvider', function($routeProvider) {
|
||||||
},
|
},
|
||||||
controller : 'ClientDetailCtrl'
|
controller : 'ClientDetailCtrl'
|
||||||
})
|
})
|
||||||
.when('/create/client-scope/step-1/:realm', {
|
.when('/create/client-scope/:realm', {
|
||||||
templateUrl : resourceUrl + '/partials/client-scope-create-step-1.html',
|
|
||||||
resolve : {
|
|
||||||
realm : function(RealmLoader) {
|
|
||||||
return RealmLoader();
|
|
||||||
},
|
|
||||||
clients : function(ClientListLoader) {
|
|
||||||
return ClientListLoader();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
controller : 'ClientScopeCreateStep1Ctrl'
|
|
||||||
})
|
|
||||||
.when('/create/client-scope/step-2/:realm', {
|
|
||||||
templateUrl : resourceUrl + '/partials/client-scope-detail.html',
|
templateUrl : resourceUrl + '/partials/client-scope-detail.html',
|
||||||
resolve : {
|
resolve : {
|
||||||
realm : function(RealmLoader) {
|
realm : function(RealmLoader) {
|
||||||
|
|
|
@ -2626,85 +2626,6 @@ module.controller('ClientScopesRealmDefaultCtrl', function($scope, realm, Realm,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
module.controller('ClientScopeCreateStep1Ctrl', function($scope, realm, clients, $route, ClientScopeGenerateAudienceClientScope, Client, $location, $modal, Dialog, Notifications) {
|
|
||||||
console.log('ClientScopeCreateStep1Ctrl');
|
|
||||||
|
|
||||||
$scope.realm = realm;
|
|
||||||
$scope.clientScopeTemplate = "none";
|
|
||||||
|
|
||||||
$scope.serviceClients = [];
|
|
||||||
for (var i = 0; i < clients.length; i++) {
|
|
||||||
if (clients[i].bearerOnly) {
|
|
||||||
$scope.serviceClients.push(clients[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.clientScopeTemplates = [
|
|
||||||
{ name: "No template", value: "none" },
|
|
||||||
{ name: "Audience template", value: "audience" }
|
|
||||||
];
|
|
||||||
|
|
||||||
$scope.audienceClientUiSelect = {
|
|
||||||
minimumInputLength: 1,
|
|
||||||
delay: 500,
|
|
||||||
allowClear: true,
|
|
||||||
query: function (query) {
|
|
||||||
var data = {results: []};
|
|
||||||
if ('' == query.term.trim()) {
|
|
||||||
query.callback(data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Client.query({realm: $route.current.params.realm, search: query.term.trim(), max: 20}, function(response) {
|
|
||||||
for (i = 0; i < response.length; i++) {
|
|
||||||
if (response[i].clientId.indexOf(query.term) != -1) {
|
|
||||||
data.results.push(response[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
query.callback(data);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
formatResult: function(object, container, query) {
|
|
||||||
object.text = object.clientId;
|
|
||||||
return object.clientId;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.selectedAudienceClient = null;
|
|
||||||
|
|
||||||
$scope.selectAudienceClient = function(audienceClient) {
|
|
||||||
|
|
||||||
if (!audienceClient || !audienceClient.id) {
|
|
||||||
$scope.selectedAudienceClient = null;
|
|
||||||
$scope.audienceClientId = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.audienceClientId = audienceClient.clientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.next = function() {
|
|
||||||
if ($scope.clientScopeTemplate !== 'audience') {
|
|
||||||
$location.url("/create/client-scope/step-2/" + realm.realm);
|
|
||||||
} else {
|
|
||||||
if (!$scope.audienceClientId) {
|
|
||||||
Notifications.error("You must select audience (service client)");
|
|
||||||
} else {
|
|
||||||
ClientScopeGenerateAudienceClientScope.save({ realm: realm.realm, clientId : $scope.audienceClientId }, function (data, headers) {
|
|
||||||
$scope.changed = false;
|
|
||||||
var l = headers().location;
|
|
||||||
var id = l.substring(l.lastIndexOf("/") + 1);
|
|
||||||
$location.url("/realms/" + realm.realm + "/client-scopes/" + id);
|
|
||||||
Notifications.success("The client scope has been created.");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.cancel = function() {
|
|
||||||
$location.url("/realms/" + realm.realm + "/client-scopes");
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
module.controller('ClientScopeDetailCtrl', function($scope, realm, clientScope, $route, serverInfo, ClientScope, $location, $modal, Dialog, Notifications) {
|
module.controller('ClientScopeDetailCtrl', function($scope, realm, clientScope, $route, serverInfo, ClientScope, $location, $modal, Dialog, Notifications) {
|
||||||
$scope.protocols = serverInfo.listProviderIds('login-protocol');
|
$scope.protocols = serverInfo.listProviderIds('login-protocol');
|
||||||
|
|
||||||
|
|
|
@ -1210,13 +1210,6 @@ module.factory('Client', function($resource) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
module.factory('ClientScopeGenerateAudienceClientScope', function($resource) {
|
|
||||||
return $resource(authUrl + '/admin/realms/:realm/client-scopes/generate-audience-client-scope?clientId=:clientId', {
|
|
||||||
realm : '@realm',
|
|
||||||
clientId : "@clientId"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module.factory('ClientScope', function($resource) {
|
module.factory('ClientScope', function($resource) {
|
||||||
return $resource(authUrl + '/admin/realms/:realm/client-scopes/:clientScope', {
|
return $resource(authUrl + '/admin/realms/:realm/client-scopes/:clientScope', {
|
||||||
realm : '@realm',
|
realm : '@realm',
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
|
||||||
|
|
||||||
<ol class="breadcrumb">
|
|
||||||
<li><a href="#/realms/{{realm.realm}}/client-scopes">{{:: 'client-scopes' | translate}}</a></li>
|
|
||||||
<li>{{:: 'add-client-scope-step-1' | translate}}</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<h1>{{:: 'add-client-scope-step-1' | translate}}</h1>
|
|
||||||
|
|
||||||
<form class="form-horizontal" name="clientForm" novalidate kc-read-only="!access.manageClients">
|
|
||||||
<fieldset class="border-top">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="col-md-2 control-label" for="clientScopeTemplate">{{:: 'client-scope-template' | translate}}</label>
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<div>
|
|
||||||
<select class="form-control" id="clientScopeTemplate"
|
|
||||||
ng-model="clientScopeTemplate"
|
|
||||||
ng-options="temp.value as temp.name for temp in clientScopeTemplates">
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<kc-tooltip>{{:: 'client-scope-template.tooltip' | translate}}</kc-tooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group clearfix" data-ng-show="clientScopeTemplate === 'audience'">
|
|
||||||
<label class="col-md-2 control-label" for="clients">{{:: 'audience-client' | translate}}</label>
|
|
||||||
|
|
||||||
<div class="col-md-6">
|
|
||||||
<input type="hidden" ui-select2="audienceClientUiSelect" id="clients" data-ng-model="selectedAudienceClient" data-ng-change="selectAudienceClient(selectedAudienceClient);" data-placeholder="{{:: 'authz-select-client' | translate}}...">
|
|
||||||
</input>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<kc-tooltip>{{:: 'audience-client.tooltip' | translate}}</kc-tooltip>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-md-10 col-md-offset-2" data-ng-show="access.manageClients">
|
|
||||||
<button class="btn btn-primary" data-ng-click="next()">{{:: 'next' | translate}}</button>
|
|
||||||
<button kc-cancel data-ng-click="cancel()">{{:: 'cancel' | translate}}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<kc-menu></kc-menu>
|
|
|
@ -2,8 +2,7 @@
|
||||||
|
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="#/realms/{{realm.realm}}/client-scopes">{{:: 'client-scopes' | translate}}</a></li>
|
<li><a href="#/realms/{{realm.realm}}/client-scopes">{{:: 'client-scopes' | translate}}</a></li>
|
||||||
<li data-ng-show="create"><a href="#/create/client-scope/step-1/{{realm.realm}}">{{:: 'add-client-scope-step-1' | translate}}</a></li>
|
<li data-ng-show="create">{{:: 'add-client-scope' | translate}}</li>
|
||||||
<li data-ng-show="create">{{:: 'add-client-scope-step-2' | translate}}</li>
|
|
||||||
<li data-ng-hide="create">{{clientScope.name}}</li>
|
<li data-ng-hide="create">{{clientScope.name}}</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pull-right" data-ng-show="access.manageClients">
|
<div class="pull-right" data-ng-show="access.manageClients">
|
||||||
<a id="createClient" class="btn btn-default" href="#/create/client-scope/step-1/{{realm.realm}}">{{:: 'create' | translate}}</a>
|
<a id="createClient" class="btn btn-default" href="#/create/client-scope/{{realm.realm}}">{{:: 'create' | translate}}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<div data-ng-controller="ClientScopeTabCtrl">
|
<div data-ng-controller="ClientScopeTabCtrl">
|
||||||
|
|
||||||
<h1 data-ng-show="create">{{:: 'add-client-scope-step-2' | translate}}</h1>
|
<h1 data-ng-show="create">{{:: 'add-client-scope' | translate}}</h1>
|
||||||
<h1 data-ng-hide="create">
|
<h1 data-ng-hide="create">
|
||||||
{{clientScope.name|capitalize}}
|
{{clientScope.name|capitalize}}
|
||||||
<i id="removeClientScope" class="pficon pficon-delete clickable" data-ng-show="access.manageClients" data-ng-click="removeClientScope()"></i>
|
<i id="removeClientScope" class="pficon pficon-delete clickable" data-ng-show="access.manageClients" data-ng-click="removeClientScope()"></i>
|
||||||
|
|
Loading…
Reference in a new issue