keycloak-aplcache
Changes
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java 38(+34 -4)
services/src/main/java/org/keycloak/services/resources/admin/ClientStorageProviderResource.java 111(+111 -0)
services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java 5(+3 -2)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/HardcodedClientStorageProvider.java 7(+3 -4)
Details
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
index 4cfea1d..c55013e 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -296,17 +296,48 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return getUserSessions(realm, client, firstResult, maxResults, false);
}
+ @Override
+ public List<UserSessionModel> getOfflineUserSessions(RealmModel realm) {
+ return getOfflineUserSessions(realm, -1, -1);
+ }
+
+ @Override
+ public List<UserSessionModel> getOfflineUserSessions(RealmModel realm, int first, int max) {
+ return getUserSessions(realm, first, max, true);
+ }
+
protected List<UserSessionModel> getUserSessions(final RealmModel realm, ClientModel client, int firstResult, int maxResults, final boolean offline) {
+ final String clientUuid = client.getId();
+ UserSessionPredicate predicate = UserSessionPredicate.create(realm.getId()).client(clientUuid);
+
+ return getUserSessionModels(realm, firstResult, maxResults, offline, predicate);
+ }
+
+ @Override
+ public List<UserSessionModel> getUserSessions(RealmModel realm) {
+ return getUserSessions(realm, -1, -1);
+ }
+
+ @Override
+ public List<UserSessionModel> getUserSessions(RealmModel realm, int firstResult, int maxResults) {
+ return getUserSessions(realm, firstResult, maxResults, false);
+ }
+
+ protected List<UserSessionModel> getUserSessions(final RealmModel realm, int firstResult, int maxResults, final boolean offline) {
+ UserSessionPredicate predicate = UserSessionPredicate.create(realm.getId());
+
+ return getUserSessionModels(realm, firstResult, maxResults, offline, predicate);
+ }
+
+ protected List<UserSessionModel> getUserSessionModels(RealmModel realm, int firstResult, int maxResults, boolean offline, UserSessionPredicate predicate) {
Cache<String, SessionEntityWrapper<UserSessionEntity>> cache = getCache(offline);
cache = CacheDecorators.skipCacheLoaders(cache);
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionCache = getClientSessionCache(offline);
Cache<UUID, SessionEntityWrapper<AuthenticatedClientSessionEntity>> clientSessionCacheDecorated = CacheDecorators.skipCacheLoaders(clientSessionCache);
- final String clientUuid = client.getId();
-
Stream<UserSessionEntity> stream = cache.entrySet().stream()
- .filter(UserSessionPredicate.create(realm.getId()).client(clientUuid))
+ .filter(predicate)
.map(Mappers.userSessionEntity())
.sorted(Comparators.userSessionLastSessionRefresh());
@@ -330,7 +361,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return sessions;
}
-
@Override
public UserSessionModel getUserSessionWithPredicate(RealmModel realm, String id, boolean offline, Predicate<UserSessionModel> predicate) {
UserSessionModel userSession = getUserSession(realm, id, offline);
diff --git a/server-spi/src/main/java/org/keycloak/models/ClientProvider.java b/server-spi/src/main/java/org/keycloak/models/ClientProvider.java
index b999485..29dc415 100644
--- a/server-spi/src/main/java/org/keycloak/models/ClientProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/ClientProvider.java
@@ -27,12 +27,12 @@ import java.util.Set;
* @version $Revision: 1 $
*/
public interface ClientProvider extends ClientLookupProvider, Provider {
+ List<ClientModel> getClients(RealmModel realm);
+
ClientModel addClient(RealmModel realm, String clientId);
ClientModel addClient(RealmModel realm, String id, String clientId);
- List<ClientModel> getClients(RealmModel realm);
-
RoleModel addClientRole(RealmModel realm, ClientModel client, String name);
RoleModel addClientRole(RealmModel realm, ClientModel client, String id, String name);
diff --git a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
index fc67d4e..00827e1 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
@@ -35,9 +35,11 @@ public interface UserSessionProvider extends Provider {
UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId);
UserSessionModel createUserSession(String id, RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId);
UserSessionModel getUserSession(RealmModel realm, String id);
+ List<UserSessionModel> getUserSessions(RealmModel realm);
List<UserSessionModel> getUserSessions(RealmModel realm, UserModel user);
List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client);
List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults);
+ List<UserSessionModel> getUserSessions(RealmModel realm, int firstResult, int maxResults);
List<UserSessionModel> getUserSessionByBrokerUserId(RealmModel realm, String brokerUserId);
UserSessionModel getUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId);
@@ -75,9 +77,11 @@ public interface UserSessionProvider extends Provider {
/** Will automatically attach newly created offline client session to the offlineUserSession **/
AuthenticatedClientSessionModel createOfflineClientSession(AuthenticatedClientSessionModel clientSession, UserSessionModel offlineUserSession);
List<UserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel user);
+ List<UserSessionModel> getOfflineUserSessions(RealmModel realm);
long getOfflineSessionsCount(RealmModel realm, ClientModel client);
List<UserSessionModel> getOfflineUserSessions(RealmModel realm, ClientModel client, int first, int max);
+ List<UserSessionModel> getOfflineUserSessions(RealmModel realm, int first, int max);
/** Triggered by persister during pre-load. It optionally imports authenticatedClientSessions too if requested. Otherwise the imported UserSession will have empty list of AuthenticationSessionModel **/
UserSessionModel importUserSession(UserSessionModel persistentUserSession, boolean offline, boolean importAuthenticatedClientSessions);
diff --git a/server-spi/src/main/java/org/keycloak/storage/client/ClientStorageProvider.java b/server-spi/src/main/java/org/keycloak/storage/client/ClientStorageProvider.java
index 6cf80d9..c0773a6 100644
--- a/server-spi/src/main/java/org/keycloak/storage/client/ClientStorageProvider.java
+++ b/server-spi/src/main/java/org/keycloak/storage/client/ClientStorageProvider.java
@@ -25,6 +25,9 @@ import org.keycloak.storage.client.ClientLookupProvider;
/**
* Base interface for components that want to provide an alternative storage mechanism for clients
*
+ * This is currently a private incomplete SPI. Please discuss on dev list if you want us to complete it or want to do the work yourself.
+ * This work is described in KEYCLOAK-6408 JIRA issue.
+ *
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
index c0ea7df..580a0bc 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
@@ -98,7 +98,7 @@ public class ClientsResource {
public List<ClientRepresentation> getClients(@QueryParam("clientId") String clientId, @QueryParam("viewableOnly") @DefaultValue("false") boolean viewableOnly) {
List<ClientRepresentation> rep = new ArrayList<>();
- if (clientId == null) {
+ if (clientId == null || clientId.trim().equals("")) {
List<ClientModel> clientModels = realm.getClients();
auth.clients().requireList();
boolean view = auth.clients().canView();
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientStorageProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientStorageProviderResource.java
new file mode 100644
index 0000000..6c8561c
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientStorageProviderResource.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.services.resources.admin;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.spi.NotFoundException;
+import org.keycloak.common.ClientConnection;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.services.ServicesLogger;
+import org.keycloak.services.managers.UserStorageSyncManager;
+import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
+import org.keycloak.storage.UserStorageProvider;
+import org.keycloak.storage.UserStorageProviderModel;
+import org.keycloak.storage.client.ClientStorageProvider;
+import org.keycloak.storage.ldap.LDAPStorageProvider;
+import org.keycloak.storage.ldap.mappers.LDAPStorageMapper;
+import org.keycloak.storage.user.SynchronizationResult;
+
+import javax.ws.rs.BadRequestException;
+import javax.ws.rs.GET;
+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.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @resource User Storage Provider
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ClientStorageProviderResource {
+ private static final Logger logger = Logger.getLogger(ClientStorageProviderResource.class);
+
+ protected RealmModel realm;
+
+ protected AdminPermissionEvaluator auth;
+
+ protected AdminEventBuilder adminEvent;
+
+ @Context
+ protected ClientConnection clientConnection;
+
+ @Context
+ protected UriInfo uriInfo;
+
+ @Context
+ protected KeycloakSession session;
+
+ @Context
+ protected HttpHeaders headers;
+
+ public ClientStorageProviderResource(RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
+ this.auth = auth;
+ this.realm = realm;
+ this.adminEvent = adminEvent;
+ }
+
+ /**
+ * Need this for admin console to display simple name of provider when displaying client detail
+ *
+ * KEYCLOAK-4328
+ *
+ * @param id
+ * @return
+ */
+ @GET
+ @Path("{id}/name")
+ @NoCache
+ @Produces(MediaType.APPLICATION_JSON)
+ public Map<String, String> getSimpleName(@PathParam("id") String id) {
+ auth.clients().requireList();
+
+ ComponentModel model = realm.getComponent(id);
+ if (model == null) {
+ throw new NotFoundException("Could not find component");
+ }
+ if (!model.getProviderType().equals(ClientStorageProvider.class.getName())) {
+ throw new NotFoundException("found, but not a ClientStorageProvider");
+ }
+
+ Map<String, String> data = new HashMap<>();
+ data.put("id", model.getId());
+ data.put("name", model.getName());
+ return data;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java
index 1c5978e..00726e4 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java
@@ -31,6 +31,7 @@ import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.ForbiddenException;
+import org.keycloak.storage.StorageId;
import java.util.Arrays;
import java.util.Collection;
@@ -634,8 +635,8 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
public Map<String, Boolean> getAccess(ClientModel client) {
Map<String, Boolean> map = new HashMap<>();
map.put("view", canView(client));
- map.put("manage", canManage(client));
- map.put("configure", canConfigure(client));
+ map.put("manage", StorageId.isLocalStorage(client) && canManage(client));
+ map.put("configure", StorageId.isLocalStorage(client) && canConfigure(client));
return map;
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 88af6f6..b5b583b 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -100,6 +100,7 @@ import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
@@ -504,17 +505,62 @@ public class RealmAdminResource {
public List<Map<String, String>> getClientSessionStats() {
auth.realm().requireViewRealm();
- List<Map<String, String>> data = new LinkedList<Map<String, String>>();
- for (ClientModel client : realm.getClients()) {
- long size = session.sessions().getActiveUserSessions(client.getRealm(), client);
- if (size == 0) continue;
- Map<String, String> map = new HashMap<>();
- map.put("id", client.getId());
- map.put("clientId", client.getClientId());
- map.put("active", size + "");
- data.add(map);
- }
- return data;
+ Map<String, Map<String, String>> data = new HashMap();
+ {
+ List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm);
+ Map<String, Long> activeCount = new HashMap<>();
+ // we have to iterate over all realm user sessions as clients coming from client storage provider might not be reachable from getClients()
+ for (UserSessionModel userSession : userSessions) {
+ for (String id : userSession.getAuthenticatedClientSessions().keySet()) {
+ Long number = activeCount.get(id);
+ if (number == null) {
+ activeCount.put(id, new Long(1));
+ } else {
+ activeCount.put(id, number + 1);
+ }
+ }
+ }
+ for (Map.Entry<String, Long> entry : activeCount.entrySet()) {
+ Map<String, String> map = new HashMap<>();
+ ClientModel client = realm.getClientById(entry.getKey());
+ map.put("id", client.getId());
+ map.put("clientId", client.getClientId());
+ map.put("active", entry.getValue().toString());
+ map.put("offline", "0");
+ data.put(client.getId(), map);
+
+ }
+ }
+ {
+ Map<String, Long> offlineCount = new HashMap<>();
+ // we have to iterate over all realm user sessions as clients coming from client storage provider might not be reachable from getClients()
+ List<UserSessionModel> offlineSessions = session.sessions().getOfflineUserSessions(realm);
+ for (UserSessionModel userSession : offlineSessions) {
+ for (String id : userSession.getAuthenticatedClientSessions().keySet()) {
+ Long number = offlineCount.get(id);
+ if (number == null) {
+ offlineCount.put(id, new Long(1));
+ } else {
+ offlineCount.put(id, number + 1);
+ }
+ }
+ }
+ for (Map.Entry<String, Long> entry : offlineCount.entrySet()) {
+ Map<String, String> map = data.get(entry.getKey());
+ if (map == null) {
+ map = new HashMap<>();
+ ClientModel client = realm.getClientById(entry.getKey());
+ map.put("id", client.getId());
+ map.put("clientId", client.getClientId());
+ map.put("active", "0");
+ data.put(client.getId(), map);
+ }
+ map.put("offline", entry.getValue().toString());
+ }
+ }
+ List<Map<String, String>> result = new LinkedList<>();
+ for (Map<String, String> item : data.values()) result.add(item);
+ return result;
}
/**
diff --git a/services/src/main/java/org/keycloak/storage/ClientStorageManager.java b/services/src/main/java/org/keycloak/storage/ClientStorageManager.java
index 53f38cc..d60d2f4 100644
--- a/services/src/main/java/org/keycloak/storage/ClientStorageManager.java
+++ b/services/src/main/java/org/keycloak/storage/ClientStorageManager.java
@@ -25,14 +25,13 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
-import org.keycloak.models.UserModel;
import org.keycloak.storage.client.ClientLookupProvider;
import org.keycloak.storage.client.ClientStorageProvider;
import org.keycloak.storage.client.ClientStorageProviderFactory;
import org.keycloak.storage.client.ClientStorageProviderModel;
-import org.keycloak.storage.user.UserLookupProvider;
import java.util.Collections;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -163,9 +162,12 @@ public class ClientStorageManager implements ClientProvider {
return session.clientLocalStorage().addClient(realm, id, clientId);
}
+
+
+
@Override
public List<ClientModel> getClients(RealmModel realm) {
- return session.clientLocalStorage().getClients(realm);
+ return session.clientLocalStorage().getClients(realm);
}
@Override
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/HardcodedClientStorageProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/HardcodedClientStorageProvider.java
index 198014f..acf0db8 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/HardcodedClientStorageProvider.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/HardcodedClientStorageProvider.java
@@ -23,7 +23,6 @@ import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.storage.StorageId;
-import org.keycloak.storage.client.AbstractClientStorageAdapter;
import org.keycloak.storage.client.AbstractReadOnlyClientStorageAdapter;
import org.keycloak.storage.client.ClientLookupProvider;
import org.keycloak.storage.client.ClientStorageProvider;
@@ -55,13 +54,13 @@ public class HardcodedClientStorageProvider implements ClientStorageProvider, Cl
public ClientModel getClientById(String id, RealmModel realm) {
StorageId storageId = new StorageId(id);
final String clientId = storageId.getExternalId();
- if (clientId.equals(clientId)) return new ClientAdapter(realm);
+ if (this.clientId.equals(clientId)) return new ClientAdapter(realm);
return null;
}
@Override
public ClientModel getClientByClientId(String clientId, RealmModel realm) {
- if (clientId.equals(clientId)) return new ClientAdapter(realm);
+ if (this.clientId.equals(clientId)) return new ClientAdapter(realm);
return null;
}
@@ -155,7 +154,7 @@ public class HardcodedClientStorageProvider implements ClientStorageProvider, Cl
@Override
public String getProtocol() {
- return null;
+ return "openid-connect";
}
@Override
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/ClientStorageTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/ClientStorageTest.java
index 0684007..a1af7fe 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/ClientStorageTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/ClientStorageTest.java
@@ -132,6 +132,7 @@ public class ClientStorageTest extends AbstractTestRealmKeycloakTest {
public void testBrowser() throws Exception {
String clientId = "hardcoded-client";
testBrowser(clientId);
+ //Thread.sleep(10000000);
}
private void testBrowser(String clientId) {
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 3595dd9..f8ff2d0 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -173,6 +173,7 @@ realm-sessions=Realm Sessions
revocation=Revocation
logout-all=Logout all
active-sessions=Active Sessions
+offline-sessions=Offline Sessions
sessions=Sessions
not-before=Not Before
not-before.tooltip=Revoke any tokens issued before this date.
@@ -1343,6 +1344,8 @@ userStorage.cachePolicy.maxLifespan.tooltip=Max lifespan of a user cache entry i
user-origin-link=Storage Origin
user-origin.tooltip=UserStorageProvider the user was loaded from
user-link.tooltip=UserStorageProvider this locally stored user was imported from.
+client-origin-link=Storage Origin
+client-origin.tooltip=Provider the client was loaded from
disable=Disable
disableable-credential-types=Disableable Types
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index 2e88f02..1625218 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -764,6 +764,15 @@ module.controller('ClientListCtrl', function($scope, realm, Client, serverInfo,
});
};
+ $scope.searchClient = function() {
+ console.log('searchQuery!!! ' + $scope.search.clientId);
+ Client.query({realm: realm.realm, viewableOnly: true, clientId: $scope.search.clientId}).$promise.then(function(clients) {
+ $scope.numberOfPages = Math.ceil(clients.length/$scope.pageSize);
+ $scope.clients = clients;
+ });
+
+ };
+
$scope.exportClient = function(client) {
var clientCopy = angular.copy(client);
delete clientCopy.id;
@@ -819,7 +828,7 @@ module.controller('ClientInstallationCtrl', function($scope, realm, client, serv
}
});
-module.controller('ClientDetailCtrl', function($scope, realm, client, templates, $route, serverInfo, Client, ClientDescriptionConverter, $location, $modal, Dialog, Notifications) {
+module.controller('ClientDetailCtrl', function($scope, realm, client, templates, $route, serverInfo, Client, ClientDescriptionConverter, Components, ClientStorageOperations, $location, $modal, Dialog, Notifications) {
@@ -889,6 +898,25 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
$scope.disableServiceAccountRolesTab = !client.serviceAccountsEnabled;
$scope.disableCredentialsTab = client.publicClient;
+ if(client.origin) {
+ if ($scope.access.viewRealm) {
+ Components.get({realm: realm.realm, componentId: client.origin}, function (link) {
+ $scope.originName = link.name;
+ //$scope.originLink = "#/realms/" + realm.realm + "/user-storage/providers/" + link.providerId + "/" + link.id;
+ })
+ }
+ else {
+ // KEYCLOAK-4328
+ ClientStorageOperations.simpleName.get({realm: realm.realm, componentId: client.origin}, function (link) {
+ $scope.originName = link.name;
+ //$scope.originLink = $location.absUrl();
+ })
+ }
+ } else {
+ console.log("origin is null");
+ }
+
+
function updateProperties() {
if (!$scope.client.attributes) {
$scope.client.attributes = {};
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/services.js b/themes/src/main/resources/theme/base/admin/resources/js/services.js
index b1b6304..5700792 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/services.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/services.js
@@ -1813,6 +1813,16 @@ module.factory('UserStorageOperations', function($resource) {
});
+module.factory('ClientStorageOperations', function($resource) {
+ var object = {}
+ object.simpleName = $resource(authUrl + '/admin/realms/:realm/client-storage/:componentId/name', {
+ realm : '@realm',
+ componentId : '@componentId'
+ });
+ return object;
+});
+
+
module.factory('ClientRegistrationPolicyProviders', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/client-registration-policy/providers', {
realm : '@realm',
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
index c00601a..cbeb321 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
@@ -37,6 +37,13 @@
</div>
<kc-tooltip>{{:: 'client.enabled.tooltip' | translate}}</kc-tooltip>
</div>
+ <div class="form-group clearfix block" data-ng-show="client.origin">
+ <label class="col-md-2 control-label">{{:: 'client-origin-link' | translate}}</label>
+ <div class="col-md-6">
+ {{originName}}
+ </div>
+ <kc-tooltip>{{:: 'client-origin.tooltip' | translate}}</kc-tooltip>
+ </div>
<div class="form-group clearfix block" data-ng-show="protocol != 'docker-v2'">
<label class="col-md-2 control-label" for="consentRequired">{{:: 'consent-required' | translate}}</label>
<div class="col-sm-6">
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html
index 03ebf5c..744e3c4 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html
@@ -11,9 +11,9 @@
<div class="form-inline">
<div class="form-group">
<div class="input-group">
- <input type="text" placeholder="{{:: 'search.placeholder' | translate}}" data-ng-model="search.clientId" class="form-control search" onkeyup="if(event.keyCode === 13){$(this).next('I').click();}">
+ <input type="text" placeholder="{{:: 'search.placeholder' | translate}}" data-ng-model="search.clientId" class="form-control search" onkeydown="if(event.keyCode === 13) document.getElementById('clientSearch').click()">
<div class="input-group-addon">
- <i class="fa fa-search" type="submit"></i>
+ <i class="fa fa-search" id="clientSearch" data-ng-click="searchClient()"></i>
</div>
</div>
</div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/session-realm.html b/themes/src/main/resources/theme/base/admin/resources/partials/session-realm.html
index e724d95..98b2284 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/session-realm.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/session-realm.html
@@ -18,12 +18,14 @@
<tr>
<th>{{:: 'client' | translate}}</th>
<th>{{:: 'active-sessions' | translate}}</th>
+ <th>{{:: 'offline-sessions' | translate}}</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="data in stats">
<td><a href="#/realms/{{realm.realm}}/clients/{{data.id}}/sessions">{{data.clientId}}</a></td>
<td>{{data.active}}</td>
+ <td>{{data.offline}}</td>
</tr>
</tbody>
</table>
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html
index 40aab54..c294692 100755
--- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html
@@ -9,11 +9,11 @@
<ul class="nav nav-tabs" data-ng-hide="create && !path[4]">
<li ng-class="{active: !path[4]}"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}">{{:: 'settings' | translate}}</a></li>
<li ng-class="{active: path[4] == 'credentials'}"
- data-ng-show="!client.publicClient && client.protocol == 'openid-connect'">
+ data-ng-show="!client.publicClient && client.protocol == 'openid-connect' && !client.origin">
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/credentials">{{:: 'credentials' | translate}}</a>
</li>
<li ng-class="{active: path[4] == 'saml'}" data-ng-show="client.protocol == 'saml' && (client.attributes['saml.client.signature'] == 'true' || client.attributes['saml.encrypt'] == 'true')"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/saml/keys">{{:: 'saml-keys' | translate}}</a></li>
- <li ng-class="{active: path[4] == 'roles'}"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/roles">{{:: 'roles' | translate}}</a></li>
+ <li ng-class="{active: path[4] == 'roles'}" data-ng-show="!client.origin"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/roles">{{:: 'roles' | translate}}</a></li>
<li ng-class="{active: path[4] == 'mappers'}" data-ng-show="!client.bearerOnly">
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/mappers">{{:: 'mappers' | translate}}</a>
<kc-tooltip>{{:: 'mappers.tooltip' | translate}}</kc-tooltip>
@@ -23,10 +23,10 @@
<kc-tooltip>{{:: 'scope.tooltip' | translate}}</kc-tooltip>
</li>
<li ng-class="{active: path[4] == 'authz'}"
- data-ng-show="serverInfo.featureEnabled('AUTHORIZATION') && !disableAuthorizationTab && client.authorizationServicesEnabled">
+ data-ng-show="serverInfo.featureEnabled('AUTHORIZATION') && !disableAuthorizationTab && client.authorizationServicesEnabled && !client.origin">
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server">{{:: 'authz-authorization' |
translate}}</a></li>
- <li ng-class="{active: path[4] == 'revocation'}" data-ng-show="client.protocol != 'docker-v2' && client.protocol != 'saml'"><a
+ <li ng-class="{active: path[4] == 'revocation'}" data-ng-show="client.protocol != 'docker-v2' && client.protocol != 'saml' && !client.origin"><a
href="#/realms/{{realm.realm}}/clients/{{client.id}}/revocation">{{:: 'revocation' | translate}}</a>
</li>
<!-- <li ng-class="{active: path[4] == 'identity-provider'}" data-ng-show="realm.identityFederationEnabled"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/identity-provider">Identity Provider</a></li> -->
@@ -40,9 +40,9 @@
<kc-tooltip>{{:: 'offline-access.tooltip' | translate}}</kc-tooltip>
</li>
- <li ng-class="{active: path[4] == 'clustering'}" data-ng-show="!client.publicClient"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/clustering">{{:: 'clustering' | translate}}</a></li>
+ <li ng-class="{active: path[4] == 'clustering'}" data-ng-show="!client.publicClient && !client.origin"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/clustering">{{:: 'clustering' | translate}}</a></li>
- <li ng-class="{active: path[4] == 'installation'}">
+ <li ng-class="{active: path[4] == 'installation'}" data-ng-show="!client.origin">
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/installation">{{:: 'installation' | translate}}</a>
<kc-tooltip>{{:: 'installation.tooltip' | translate}}</kc-tooltip>
</li>