KEYCLOAK-8484 Remove audience client scope template

This commit is contained in:
mposolda 2018-10-25 13:17:50 +02:00 committed by Marek Posolda
parent 91c4bfa81c
commit 9652748ba9
17 changed files with 76 additions and 334 deletions

View file

@ -17,7 +17,6 @@
package org.keycloak.admin.client.resource;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.representations.idm.ClientScopeRepresentation;
import javax.ws.rs.Consumes;
@ -26,7 +25,6 @@ import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
@ -47,18 +45,4 @@ public interface ClientScopesResource {
@Produces(MediaType.APPLICATION_JSON)
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);
}

View file

@ -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) {
String clientId = client.getClientId();
ClientScopeModel clientScope = KeycloakModelUtils.getClientScopeByName(client.getRealm(), clientId);
if (clientScope == null) {
return false;
// We want to verify-token-audience if service client has any client roles
if (client.getRoles().size() > 0) {
return true;
}
// 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()) {
if (AudienceProtocolMapper.PROVIDER_ID.equals(protocolMapper.getProtocolMapper()) && (clientId.equals(protocolMapper.getConfig().get(AudienceProtocolMapper.INCLUDED_CLIENT_AUDIENCE)))) {
return true;
}
}
}
return false;
}

View file

@ -22,17 +22,12 @@ import org.jboss.resteasy.spi.NotFoundException;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.utils.ModelToRepresentation;
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.services.ErrorResponse;
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.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
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.
*

View file

@ -20,7 +20,6 @@ package org.keycloak.testsuite.rest;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.BadRequestException;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.Time;
import org.keycloak.component.ComponentModel;
import org.keycloak.events.Event;
@ -34,9 +33,11 @@ import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.FederatedIdentityModel;
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.RealmProvider;
import org.keycloak.models.UserCredentialModel;
@ -45,6 +46,8 @@ import org.keycloak.models.UserProvider;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.infinispan.changes.sessions.LastSessionRefreshStoreFactory;
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.representations.idm.AdminEventRepresentation;
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.UserRepresentation;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resource.RealmResourceProvider;
import org.keycloak.services.scheduled.ClearExpiredUserSessions;
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.TestProviderFactory;
import org.keycloak.testsuite.events.EventsListenerProvider;
@ -99,7 +99,6 @@ import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimerTask;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -126,11 +125,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
@Path("/remove-user-session")
@Produces(MediaType.APPLICATION_JSON)
public Response removeUserSession(@QueryParam("realm") final String name, @QueryParam("session") final String sessionId) {
RealmManager realmManager = new RealmManager(session);
RealmModel realm = realmManager.getRealmByName(name);
if (realm == null) {
throw new NotFoundException("Realm not found");
}
RealmModel realm = getRealmByName(name);
UserSessionModel sessionModel = session.sessions().getUserSession(realm, sessionId);
if (sessionModel == null) {
@ -145,11 +140,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
@Path("/remove-user-sessions")
@Produces(MediaType.APPLICATION_JSON)
public Response removeUserSessions(@QueryParam("realm") final String realmName) {
RealmManager realmManager = new RealmManager(session);
RealmModel realm = realmManager.getRealmByName(realmName);
if (realm == null) {
throw new NotFoundException("Realm not found");
}
RealmModel realm = getRealmByName(realmName);
session.sessions().removeUserSessions(realm);
return Response.ok().build();
@ -159,12 +150,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
@Path("/get-last-session-refresh")
@Produces(MediaType.APPLICATION_JSON)
public Integer getLastSessionRefresh(@QueryParam("realm") final String name, @QueryParam("session") final String sessionId, @QueryParam("offline") boolean offline) {
RealmManager realmManager = new RealmManager(session);
RealmModel realm = realmManager.getRealmByName(name);
if (realm == null) {
throw new NotFoundException("Realm not found");
}
RealmModel realm = getRealmByName(name);
UserSessionModel sessionModel = offline ? session.sessions().getOfflineUserSession(realm, sessionId) : session.sessions().getUserSession(realm, sessionId);
if (sessionModel == null) {
@ -178,11 +164,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
@Path("/remove-expired")
@Produces(MediaType.APPLICATION_JSON)
public Response removeExpired(@QueryParam("realm") final String name) {
RealmManager realmManager = new RealmManager(session);
RealmModel realm = realmManager.getRealmByName(name);
if (realm == null) {
throw new NotFoundException("Realm not found");
}
RealmModel realm = getRealmByName(name);
session.sessions().removeExpired(realm);
session.authenticationSessions().removeExpired(realm);
@ -196,11 +178,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
@Produces(MediaType.APPLICATION_JSON)
public Integer getClientSessionsCountInUserSession(@QueryParam("realm") final String name, @QueryParam("session") final String sessionId) {
RealmManager realmManager = new RealmManager(session);
RealmModel realm = realmManager.getRealmByName(name);
if (realm == null) {
throw new NotFoundException("Realm not found");
}
RealmModel realm = getRealmByName(name);
UserSessionModel sessionModel = session.sessions().getUserSession(realm, sessionId);
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
@Path("/run-on-server")
@Consumes(MediaType.TEXT_PLAIN_UTF_8)
@ -810,7 +824,11 @@ public class TestingResourceProvider implements RealmResourceProvider {
private RealmModel getRealmByName(String realmName) {
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;
}
}

View file

@ -275,6 +275,11 @@ public interface TestingResource {
@Produces(MediaType.APPLICATION_JSON)
Response restorePeriodicTasks();
@Path("generate-audience-client-scope")
@POST
@NoCache
String generateAudienceClientScope(@QueryParam("realm") final String realmName, final @QueryParam("clientId") String clientId);
@GET
@Path("/uncaught-error")
@Produces(MediaType.TEXT_HTML_UTF_8)

View file

@ -33,7 +33,6 @@ import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.ws.rs.core.Response;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@ -68,9 +67,8 @@ public class LoginModulesTest extends AbstractKeycloakTest {
}
// Generate audience client scope
Response resp = adminClient.realm("demo").clientScopes().generateAudienceClientScope("customer-db-audience-required");
String clientScopeId = ApiUtil.getCreatedId(resp);
resp.close();
String clientScopeId = testingClient.testing().generateAudienceClientScope("demo", "customer-db-audience-required");
ClientResource client = ApiUtil.findClientByClientId(adminClient.realm("demo"), "customer-portal");
client.addOptionalClientScope(clientScopeId);
}

View file

@ -831,9 +831,8 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest {
@Test
public void testVerifyTokenAudience() {
// Generate audience client scope
Response resp = adminClient.realm("demo").clientScopes().generateAudienceClientScope("customer-db-audience-required");
String clientScopeId = ApiUtil.getCreatedId(resp);
resp.close();
String clientScopeId = testingClient.testing().generateAudienceClientScope("demo", "customer-db-audience-required");
ClientResource client = ApiUtil.findClientByClientId(adminClient.realm("demo"), "customer-portal");
client.addOptionalClientScope(clientScopeId);

View file

@ -17,20 +17,15 @@
package org.keycloak.testsuite.admin.client;
import javax.ws.rs.core.Response;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
import org.keycloak.testsuite.util.AdminEventPaths;
import org.keycloak.testsuite.util.WaitUtils;
import static org.junit.Assert.assertThat;
import static org.hamcrest.Matchers.*;
@ -109,10 +104,7 @@ public class InstallationTest extends AbstractClientTest {
@Test
public void testOidcBearerOnlyJsonWithAudienceClientScope() {
// Generate audience client scope
Response resp = testRealmResource().clientScopes().generateAudienceClientScope(OIDC_NAME_BEARER_ONLY_NAME);
String clientScopeId = ApiUtil.getCreatedId(resp);
resp.close();
assertAdminEvents.assertEvent(getRealmId(), OperationType.CREATE, AdminEventPaths.clientScopeGenerateAudienceClientScopePath(), null, ResourceType.CLIENT_SCOPE);
String clientScopeId = testingClient.testing().generateAudienceClientScope("test", OIDC_NAME_BEARER_ONLY_NAME);
String json = oidcBearerOnlyClient.getInstallationProvider("keycloak-oidc-keycloak-json");
assertOidcInstallationConfig(json);

View file

@ -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) {
Collection<String> audiences = token.getAudience() == null ? Collections.emptyList() : Arrays.asList(token.getAudience());
Collection<String> expectedAudiences = Arrays.asList(expectedAudience);

View file

@ -837,12 +837,6 @@ client-storage=Client Storage
no-client-storage-providers-configured=No client storage providers configured
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.description.tooltip=Description of the client scope
client-scope.protocol.tooltip=Which SSO protocol configuration is being supplied by this client scope

View file

@ -1458,19 +1458,7 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'ClientDetailCtrl'
})
.when('/create/client-scope/step-1/: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', {
.when('/create/client-scope/:realm', {
templateUrl : resourceUrl + '/partials/client-scope-detail.html',
resolve : {
realm : function(RealmLoader) {

View file

@ -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) {
$scope.protocols = serverInfo.listProviderIds('login-protocol');

View file

@ -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) {
return $resource(authUrl + '/admin/realms/:realm/client-scopes/:clientScope', {
realm : '@realm',

View file

@ -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>

View file

@ -2,8 +2,7 @@
<ol class="breadcrumb">
<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-step-2' | translate}}</li>
<li data-ng-show="create">{{:: 'add-client-scope' | translate}}</li>
<li data-ng-hide="create">{{clientScope.name}}</li>
</ol>

View file

@ -29,7 +29,7 @@
</div>
<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>
</th>

View file

@ -1,6 +1,6 @@
<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">
{{clientScope.name|capitalize}}
<i id="removeClientScope" class="pficon pficon-delete clickable" data-ng-show="access.manageClients" data-ng-click="removeClientScope()"></i>