Merge branch 'velias-KEYCLOAK-1542'
This commit is contained in:
commit
a1f0208767
68 changed files with 1464 additions and 427 deletions
|
@ -142,6 +142,7 @@
|
|||
<dropColumn tableName="CLIENT_SESSION" columnName="ACTION"/>
|
||||
<addColumn tableName="USER_ENTITY">
|
||||
<column name="CREATED_TIMESTAMP" type="BIGINT"/>
|
||||
<column name="SERVICE_ACCOUNT_CLIENT_LINK" type="VARCHAR(36)"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
|
|
|
@ -13,10 +13,12 @@ import javax.persistence.EntityManagerFactory;
|
|||
import javax.persistence.Persistence;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
@ -29,6 +31,8 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
|||
private volatile EntityManagerFactory emf;
|
||||
|
||||
private Config.Scope config;
|
||||
|
||||
private Map<String,String> operationalInfo;
|
||||
|
||||
@Override
|
||||
public JpaConnectionProvider create(KeycloakSession session) {
|
||||
|
@ -120,56 +124,73 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
|||
properties.put("hibernate.show_sql", config.getBoolean("showSql", false));
|
||||
properties.put("hibernate.format_sql", config.getBoolean("formatSql", true));
|
||||
|
||||
if (databaseSchema != null) {
|
||||
logger.trace("Updating database");
|
||||
connection = getConnection();
|
||||
try{
|
||||
prepareOperationalInfo(connection);
|
||||
|
||||
if (databaseSchema != null) {
|
||||
logger.trace("Updating database");
|
||||
|
||||
JpaUpdaterProvider updater = session.getProvider(JpaUpdaterProvider.class);
|
||||
if (updater == null) {
|
||||
throw new RuntimeException("Can't update database: JPA updater provider not found");
|
||||
}
|
||||
|
||||
if (databaseSchema.equals("update")) {
|
||||
String currentVersion = null;
|
||||
try {
|
||||
ResultSet resultSet = connection.createStatement().executeQuery(updater.getCurrentVersionSql(schema));
|
||||
if (resultSet.next()) {
|
||||
currentVersion = resultSet.getString(1);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
}
|
||||
|
||||
if (currentVersion == null || !JpaUpdaterProvider.LAST_VERSION.equals(currentVersion)) {
|
||||
updater.update(session, connection, schema);
|
||||
} else {
|
||||
logger.debug("Database is up to date");
|
||||
}
|
||||
} else if (databaseSchema.equals("validate")) {
|
||||
updater.validate(connection, schema);
|
||||
} else {
|
||||
throw new RuntimeException("Invalid value for databaseSchema: " + databaseSchema);
|
||||
}
|
||||
|
||||
logger.trace("Database update completed");
|
||||
}
|
||||
|
||||
logger.trace("Creating EntityManagerFactory");
|
||||
emf = Persistence.createEntityManagerFactory(unitName, properties);
|
||||
logger.trace("EntityManagerFactory created");
|
||||
|
||||
JpaUpdaterProvider updater = session.getProvider(JpaUpdaterProvider.class);
|
||||
if (updater == null) {
|
||||
throw new RuntimeException("Can't update database: JPA updater provider not found");
|
||||
}
|
||||
|
||||
connection = getConnection();
|
||||
|
||||
if (databaseSchema.equals("update")) {
|
||||
String currentVersion = null;
|
||||
try {
|
||||
ResultSet resultSet = connection.createStatement().executeQuery(updater.getCurrentVersionSql(schema));
|
||||
if (resultSet.next()) {
|
||||
currentVersion = resultSet.getString(1);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
}
|
||||
|
||||
if (currentVersion == null || !JpaUpdaterProvider.LAST_VERSION.equals(currentVersion)) {
|
||||
updater.update(session, connection, schema);
|
||||
} else {
|
||||
logger.debug("Database is up to date");
|
||||
}
|
||||
} else if (databaseSchema.equals("validate")) {
|
||||
updater.validate(connection, schema);
|
||||
} else {
|
||||
throw new RuntimeException("Invalid value for databaseSchema: " + databaseSchema);
|
||||
}
|
||||
|
||||
logger.trace("Database update completed");
|
||||
}
|
||||
|
||||
logger.trace("Creating EntityManagerFactory");
|
||||
emf = Persistence.createEntityManagerFactory(unitName, properties);
|
||||
logger.trace("EntityManagerFactory created");
|
||||
|
||||
// Close after creating EntityManagerFactory to prevent in-mem databases from closing
|
||||
if (connection != null) {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (SQLException e) {
|
||||
logger.warn(e);
|
||||
}
|
||||
} finally {
|
||||
// Close after creating EntityManagerFactory to prevent in-mem databases from closing
|
||||
if (connection != null) {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (SQLException e) {
|
||||
logger.warn(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void prepareOperationalInfo(Connection connection) {
|
||||
try {
|
||||
operationalInfo = new LinkedHashMap<>();
|
||||
DatabaseMetaData md = connection.getMetaData();
|
||||
operationalInfo.put("databaseUrl",md.getURL());
|
||||
operationalInfo.put("databaseUser", md.getUserName());
|
||||
operationalInfo.put("databaseProduct", md.getDatabaseProductName() + " " + md.getDatabaseProductVersion());
|
||||
operationalInfo.put("databaseDriver", md.getDriverName() + " " + md.getDriverVersion());
|
||||
} catch (SQLException e) {
|
||||
logger.warn("Unable to prepare operational info due database exception: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private Connection getConnection() {
|
||||
try {
|
||||
|
@ -185,5 +206,10 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
|||
throw new RuntimeException("Failed to connect to database", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String,String> getOperationalInfo() {
|
||||
return operationalInfo;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package org.keycloak.connections.jpa;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.ServerInfoAwareProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface JpaConnectionProviderFactory extends ProviderFactory<JpaConnectionProvider> {
|
||||
public interface JpaConnectionProviderFactory extends ServerInfoAwareProviderFactory<JpaConnectionProvider> {
|
||||
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import com.mongodb.MongoClient;
|
|||
import com.mongodb.MongoClientOptions;
|
||||
import com.mongodb.MongoCredential;
|
||||
import com.mongodb.ServerAddress;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.connections.mongo.api.MongoStore;
|
||||
|
@ -18,6 +19,8 @@ import javax.net.ssl.SSLSocketFactory;
|
|||
import java.lang.reflect.Method;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -57,6 +60,8 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
|
|||
private MongoStore mongoStore;
|
||||
private DB db;
|
||||
protected Config.Scope config;
|
||||
|
||||
private Map<String,String> operationalInfo;
|
||||
|
||||
@Override
|
||||
public MongoConnectionProvider create(KeycloakSession session) {
|
||||
|
@ -159,7 +164,13 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
|
|||
} else {
|
||||
client = new MongoClient(new ServerAddress(host, port), clientOptions);
|
||||
}
|
||||
|
||||
|
||||
operationalInfo = new LinkedHashMap<>();
|
||||
operationalInfo.put("mongoServerAddress", client.getAddress().toString());
|
||||
operationalInfo.put("mongoDatabaseName", dbName);
|
||||
operationalInfo.put("mongoUser", user);
|
||||
operationalInfo.put("mongoDriverVersion", client.getVersion());
|
||||
|
||||
logger.debugv("Initialized mongo model. host: %s, port: %d, db: %s", host, port, dbName);
|
||||
return client;
|
||||
}
|
||||
|
@ -206,5 +217,10 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String,String> getOperationalInfo() {
|
||||
return operationalInfo;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package org.keycloak.connections.mongo;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.ServerInfoAwareProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface MongoConnectionProviderFactory extends ProviderFactory<MongoConnectionProvider> {
|
||||
public interface MongoConnectionProviderFactory extends ServerInfoAwareProviderFactory<MongoConnectionProvider> {
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ public interface ServiceAccountConstants {
|
|||
String CLIENT_AUTH = "client_auth";
|
||||
|
||||
String SERVICE_ACCOUNT_USER_PREFIX = "service-account-";
|
||||
String SERVICE_ACCOUNT_CLIENT_ATTRIBUTE = "serviceAccountClient";
|
||||
|
||||
String CLIENT_ID_PROTOCOL_MAPPER = "Client ID";
|
||||
String CLIENT_HOST_PROTOCOL_MAPPER = "Client Host";
|
||||
|
|
|
@ -26,6 +26,7 @@ public class UserRepresentation {
|
|||
protected String lastName;
|
||||
protected String email;
|
||||
protected String federationLink;
|
||||
protected String serviceAccountClientId; // For rep, it points to clientId (not DB ID)
|
||||
|
||||
// Currently there is Map<String, List<String>> but for backwards compatibility, we also need to support Map<String, String>
|
||||
protected Map<String, Object> attributes;
|
||||
|
@ -218,4 +219,12 @@ public class UserRepresentation {
|
|||
public void setFederationLink(String federationLink) {
|
||||
this.federationLink = federationLink;
|
||||
}
|
||||
|
||||
public String getServiceAccountClientId() {
|
||||
return serviceAccountClientId;
|
||||
}
|
||||
|
||||
public void setServiceAccountClientId(String serviceAccountClientId) {
|
||||
this.serviceAccountClientId = serviceAccountClientId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
<!ENTITY Email SYSTEM "modules/email.xml">
|
||||
<!ENTITY Roles SYSTEM "modules/roles.xml">
|
||||
<!ENTITY DirectAccess SYSTEM "modules/direct-access.xml">
|
||||
<!ENTITY ServiceAccounts SYSTEM "modules/service-accounts.xml">
|
||||
<!ENTITY CORS SYSTEM "modules/cors.xml">
|
||||
<!ENTITY Timeouts SYSTEM "modules/timeouts.xml">
|
||||
<!ENTITY Events SYSTEM "modules/events.xml">
|
||||
|
@ -122,6 +123,7 @@ This one is short
|
|||
&AccessTypes;
|
||||
&Roles;
|
||||
&DirectAccess;
|
||||
&ServiceAccounts;
|
||||
&CORS;
|
||||
&Timeouts;
|
||||
&AdminApi;
|
||||
|
|
56
docbook/reference/en/en-US/modules/service-accounts.xml
Normal file
56
docbook/reference/en/en-US/modules/service-accounts.xml
Normal file
|
@ -0,0 +1,56 @@
|
|||
<chapter id="service-accounts">
|
||||
<title>Service Accounts</title>
|
||||
<para>
|
||||
Keycloak allows you to obtain an access token dedicated to some Client Application (not to any user).
|
||||
See <ulink url="http://tools.ietf.org/html/rfc6749#section-4.4">Client Credentials Grant</ulink>
|
||||
from OAuth 2.0 spec.
|
||||
</para>
|
||||
<para>
|
||||
To use it you must have
|
||||
registered a valid confidential Client and you need to check the switch <literal>Service Accounts Enabled</literal> in Keycloak
|
||||
admin console for this client. In tab <literal>Service Account Roles</literal> you can configure the roles available to the service account retrieved on behalf of this client.
|
||||
Don't forget that you need those roles to be available in Scopes of this client as well (unless you have <literal>Full Scope Allowed</literal> on).
|
||||
As in normal login, roles from access token are intersection of scopes and the service account roles.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The REST URL to invoke on is <literal>/{keycloak-root}/realms/{realm-name}/protocol/openid-connect/token</literal>.
|
||||
Invoking on this URL is a POST request and requires you to post the clientId and clientSecret of the client in <literal>Authorization: Basic</literal> header.
|
||||
Later we want to add more mechanisms for authenticating clients. You also need to use parameter <literal>grant_type=client_credentials</literal> as per OAuth2 specification.
|
||||
</para>
|
||||
<para>
|
||||
For example the POST invocation to retrieve service account can look like this:
|
||||
<programlisting><![CDATA[
|
||||
POST /auth/realms/demo/protocol/openid-connect/token
|
||||
Authorization: Basic cHJvZHVjdC1zYS1jbGllbnQ6cGFzc3dvcmQ=
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=client_credentials]]>
|
||||
</programlisting>
|
||||
The response would be this <ulink url="http://tools.ietf.org/html/rfc6749#section-4.4.3">standard JSON document</ulink> from the OAuth 2.0 specification.
|
||||
<programlisting><![CDATA[
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json;charset=UTF-8
|
||||
Cache-Control: no-store
|
||||
Pragma: no-cache
|
||||
|
||||
{
|
||||
"access_token":"2YotnFZFEjr1zCsicMWpAA",
|
||||
"token_type":"bearer",
|
||||
"expires_in":60,
|
||||
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
|
||||
"refresh_expires_in":600,
|
||||
"id_token":"tGzv3JOkF0XG5Qx2TlKWIA",
|
||||
"not-before-policy":0,
|
||||
"session-state":"234234-234234-234234"
|
||||
}]]>
|
||||
</programlisting>
|
||||
</para>
|
||||
<para>
|
||||
The retrieved access token can be refreshed or logged out by out-of-bound request.
|
||||
</para>
|
||||
<para>
|
||||
See the example application <literal>service-account</literal>
|
||||
from the main Keycloak <literal>demo</literal> example.
|
||||
</para>
|
||||
</chapter>
|
|
@ -140,15 +140,7 @@ public class ProductServiceAccountServlet extends HttpServlet {
|
|||
int status = response.getStatusLine().getStatusCode();
|
||||
if (status != 200) {
|
||||
String json = getContent(entity);
|
||||
String error = "Failed retrieve products.";
|
||||
|
||||
if (status == 401) {
|
||||
error = error + " You need to login first with the service account.";
|
||||
} else if (status == 403) {
|
||||
error = error + " Maybe service account user doesn't have needed role? Assign role 'user' in Keycloak admin console to user '" +
|
||||
ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + getKeycloakDeployment().getResourceName() + "' and then logout and login again.";
|
||||
}
|
||||
error = error + " Status: " + status + ", Response: " + json;
|
||||
String error = "Failed retrieve products. Status: " + status + ", Response: " + json;
|
||||
req.setAttribute(ERROR, error);
|
||||
} else if (entity == null) {
|
||||
req.setAttribute(ERROR, "No entity");
|
||||
|
|
|
@ -71,6 +71,13 @@
|
|||
"clientRoles": {
|
||||
"realm-management": [ "realm-admin" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"username" : "service-account-product-sa-client",
|
||||
"enabled": true,
|
||||
"email" : "service-account-product-sa-client@placeholder.org",
|
||||
"serviceAccountClientId": "product-sa-client",
|
||||
"realmRoles": [ "user" ]
|
||||
}
|
||||
],
|
||||
"roles" : {
|
||||
|
|
|
@ -123,7 +123,7 @@ public class ExportUtils {
|
|||
|
||||
// Finally users if needed
|
||||
if (includeUsers) {
|
||||
List<UserModel> allUsers = session.users().getUsers(realm);
|
||||
List<UserModel> allUsers = session.users().getUsers(realm, true);
|
||||
List<UserRepresentation> users = new ArrayList<UserRepresentation>();
|
||||
for (UserModel user : allUsers) {
|
||||
UserRepresentation userRep = exportUser(session, realm, user);
|
||||
|
@ -286,6 +286,15 @@ public class ExportUtils {
|
|||
userRep.setClientConsents(consentReps);
|
||||
}
|
||||
|
||||
// Service account
|
||||
if (user.getServiceAccountClientLink() != null) {
|
||||
String clientInternalId = user.getServiceAccountClientLink();
|
||||
ClientModel client = realm.getClientById(clientInternalId);
|
||||
if (client != null) {
|
||||
userRep.setServiceAccountClientId(client.getClientId());
|
||||
}
|
||||
}
|
||||
|
||||
return userRep;
|
||||
}
|
||||
|
||||
|
|
|
@ -92,7 +92,7 @@ public abstract class MultipleStepsExportProvider implements ExportProvider {
|
|||
@Override
|
||||
protected void runExportImportTask(KeycloakSession session) throws IOException {
|
||||
RealmModel realm = session.realms().getRealmByName(realmName);
|
||||
usersHolder.users = session.users().getUsers(realm, usersHolder.currentPageStart, usersHolder.currentPageEnd - usersHolder.currentPageStart);
|
||||
usersHolder.users = session.users().getUsers(realm, usersHolder.currentPageStart, usersHolder.currentPageEnd - usersHolder.currentPageStart, true);
|
||||
|
||||
writeUsers(realmName + "-users-" + (usersHolder.currentPageStart / countPerPage) + ".json", session, realm, usersHolder.users);
|
||||
|
||||
|
|
|
@ -82,8 +82,8 @@ module.config([ '$routeProvider', function($routeProvider) {
|
|||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
serverInfo : function(ServerInfoLoader) {
|
||||
return ServerInfoLoader();
|
||||
serverInfo : function(ServerInfo) {
|
||||
return ServerInfo.delay;
|
||||
}
|
||||
},
|
||||
controller : 'RealmLoginSettingsCtrl'
|
||||
|
@ -368,6 +368,9 @@ module.config([ '$routeProvider', function($routeProvider) {
|
|||
},
|
||||
clients : function(ClientListLoader) {
|
||||
return ClientListLoader();
|
||||
},
|
||||
client : function() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
controller : 'UserRoleMappingCtrl'
|
||||
|
@ -762,17 +765,23 @@ module.config([ '$routeProvider', function($routeProvider) {
|
|||
},
|
||||
controller : 'ClientInstallationCtrl'
|
||||
})
|
||||
.when('/realms/:realm/clients/:client/service-accounts', {
|
||||
templateUrl : resourceUrl + '/partials/client-service-accounts.html',
|
||||
.when('/realms/:realm/clients/:client/service-account-roles', {
|
||||
templateUrl : resourceUrl + '/partials/client-service-account-roles.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
user : function(ClientServiceAccountUserLoader) {
|
||||
return ClientServiceAccountUserLoader();
|
||||
},
|
||||
clients : function(ClientListLoader) {
|
||||
return ClientListLoader();
|
||||
},
|
||||
client : function(ClientLoader) {
|
||||
return ClientLoader();
|
||||
}
|
||||
},
|
||||
controller : 'ClientServiceAccountsCtrl'
|
||||
controller : 'UserRoleMappingCtrl'
|
||||
})
|
||||
.when('/create/client/:realm', {
|
||||
templateUrl : resourceUrl + '/partials/client-detail.html',
|
||||
|
@ -1124,7 +1133,22 @@ module.config([ '$routeProvider', function($routeProvider) {
|
|||
controller : 'AuthenticationConfigCreateCtrl'
|
||||
})
|
||||
.when('/server-info', {
|
||||
templateUrl : resourceUrl + '/partials/server-info.html'
|
||||
templateUrl : resourceUrl + '/partials/server-info.html',
|
||||
resolve : {
|
||||
serverInfo : function(ServerInfoLoader) {
|
||||
return ServerInfoLoader();
|
||||
}
|
||||
},
|
||||
controller : 'ServerInfoCtrl'
|
||||
})
|
||||
.when('/server-info/providers', {
|
||||
templateUrl : resourceUrl + '/partials/server-info-providers.html',
|
||||
resolve : {
|
||||
serverInfo : function(ServerInfoLoader) {
|
||||
return ServerInfoLoader();
|
||||
}
|
||||
},
|
||||
controller : 'ServerInfoCtrl'
|
||||
})
|
||||
.when('/logout', {
|
||||
templateUrl : resourceUrl + '/partials/home.html',
|
||||
|
@ -1858,4 +1882,4 @@ module.directive( 'kcOpen', function ( $location ) {
|
|||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -549,7 +549,7 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se
|
|||
"bearer-only"
|
||||
];
|
||||
|
||||
$scope.protocols = serverInfo.protocols;
|
||||
$scope.protocols = Object.keys(serverInfo.providers['login-protocol'].providers);
|
||||
|
||||
$scope.signatureAlgorithms = [
|
||||
"RSA_SHA1",
|
||||
|
@ -1323,25 +1323,5 @@ module.controller('ClientProtocolMapperCreateCtrl', function($scope, realm, serv
|
|||
|
||||
});
|
||||
|
||||
module.controller('ClientServiceAccountsCtrl', function($scope, $http, realm, client, Notifications, Client) {
|
||||
$scope.realm = realm;
|
||||
$scope.client = angular.copy(client);
|
||||
|
||||
$scope.serviceAccountsEnabledChanged = function() {
|
||||
if (client.serviceAccountsEnabled != $scope.client.serviceAccountsEnabled) {
|
||||
Client.update({
|
||||
realm : realm.realm,
|
||||
client : client.id
|
||||
}, $scope.client, function() {
|
||||
$scope.changed = false;
|
||||
client = angular.copy($scope.client);
|
||||
Notifications.success("Service Account settings updated.");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -7,9 +7,6 @@ module.controller('GlobalCtrl', function($scope, $http, Auth, WhoAmI, Current, $
|
|||
$scope.resourceUrl = resourceUrl;
|
||||
$scope.auth = Auth;
|
||||
$scope.serverInfo = ServerInfo.get();
|
||||
$scope.serverInfoUpdate = function() {
|
||||
$scope.serverInfo = ServerInfo.get();
|
||||
};
|
||||
|
||||
function hasAnyAccess() {
|
||||
var realmAccess = Auth.user && Auth.user['realm_access'];
|
||||
|
@ -125,6 +122,25 @@ module.controller('RealmTabCtrl', function(Dialog, $scope, Current, Realm, Notif
|
|||
};
|
||||
});
|
||||
|
||||
module.controller('ServerInfoCtrl', function($scope, ServerInfo) {
|
||||
ServerInfo.reload();
|
||||
|
||||
$scope.serverInfo = ServerInfo.get();
|
||||
|
||||
$scope.$watch($scope.serverInfo, function() {
|
||||
$scope.providers = [];
|
||||
for(var spi in $scope.serverInfo.providers) {
|
||||
var p = angular.copy($scope.serverInfo.providers[spi]);
|
||||
p.name = spi;
|
||||
$scope.providers.push(p)
|
||||
}
|
||||
});
|
||||
|
||||
$scope.serverInfoReload = function() {
|
||||
ServerInfo.reload();
|
||||
}
|
||||
});
|
||||
|
||||
module.controller('RealmListCtrl', function($scope, Realm, Current) {
|
||||
$scope.realms = Realm.query();
|
||||
Current.realms = $scope.realms;
|
||||
|
@ -1217,7 +1233,7 @@ module.controller('RealmEventsConfigCtrl', function($scope, eventsConfig, RealmE
|
|||
}
|
||||
});
|
||||
|
||||
$scope.eventListeners = serverInfo.eventListeners;
|
||||
$scope.eventListeners = Object.keys(serverInfo.providers.eventsListener.providers);
|
||||
|
||||
$scope.eventSelectOptions = {
|
||||
'multiple': true,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
module.controller('UserRoleMappingCtrl', function($scope, $http, realm, user, clients, Notifications, RealmRoleMapping,
|
||||
module.controller('UserRoleMappingCtrl', function($scope, $http, realm, user, clients, client, Notifications, RealmRoleMapping,
|
||||
ClientRoleMapping, AvailableRealmRoleMapping, AvailableClientRoleMapping,
|
||||
CompositeRealmRoleMapping, CompositeClientRoleMapping) {
|
||||
$scope.realm = realm;
|
||||
|
@ -7,6 +7,7 @@ module.controller('UserRoleMappingCtrl', function($scope, $http, realm, user, cl
|
|||
$scope.selectedRealmMappings = [];
|
||||
$scope.realmMappings = [];
|
||||
$scope.clients = clients;
|
||||
$scope.client = client;
|
||||
$scope.clientRoles = [];
|
||||
$scope.clientComposite = [];
|
||||
$scope.selectedClientRoles = [];
|
||||
|
@ -28,11 +29,11 @@ module.controller('UserRoleMappingCtrl', function($scope, $http, realm, user, cl
|
|||
$scope.realmComposite = CompositeRealmRoleMapping.query({realm : realm.realm, userId : user.id});
|
||||
$scope.selectedRealmMappings = [];
|
||||
$scope.selectRealmRoles = [];
|
||||
if ($scope.client) {
|
||||
if ($scope.targetClient) {
|
||||
console.log('load available');
|
||||
$scope.clientComposite = CompositeClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.client.id});
|
||||
$scope.clientRoles = AvailableClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.client.id});
|
||||
$scope.clientMappings = ClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.client.id});
|
||||
$scope.clientComposite = CompositeClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.targetClient.id});
|
||||
$scope.clientRoles = AvailableClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.targetClient.id});
|
||||
$scope.clientMappings = ClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.targetClient.id});
|
||||
$scope.selectedClientRoles = [];
|
||||
$scope.selectedClientMappings = [];
|
||||
}
|
||||
|
@ -49,11 +50,11 @@ module.controller('UserRoleMappingCtrl', function($scope, $http, realm, user, cl
|
|||
$scope.realmComposite = CompositeRealmRoleMapping.query({realm : realm.realm, userId : user.id});
|
||||
$scope.selectedRealmMappings = [];
|
||||
$scope.selectRealmRoles = [];
|
||||
if ($scope.client) {
|
||||
if ($scope.targetClient) {
|
||||
console.log('load available');
|
||||
$scope.clientComposite = CompositeClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.client.id});
|
||||
$scope.clientRoles = AvailableClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.client.id});
|
||||
$scope.clientMappings = ClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.client.id});
|
||||
$scope.clientComposite = CompositeClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.targetClient.id});
|
||||
$scope.clientRoles = AvailableClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.targetClient.id});
|
||||
$scope.clientMappings = ClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.targetClient.id});
|
||||
$scope.selectedClientRoles = [];
|
||||
$scope.selectedClientMappings = [];
|
||||
}
|
||||
|
@ -62,11 +63,11 @@ module.controller('UserRoleMappingCtrl', function($scope, $http, realm, user, cl
|
|||
};
|
||||
|
||||
$scope.addClientRole = function() {
|
||||
$http.post(authUrl + '/admin/realms/' + realm.realm + '/users/' + user.id + '/role-mappings/clients/' + $scope.client.id,
|
||||
$http.post(authUrl + '/admin/realms/' + realm.realm + '/users/' + user.id + '/role-mappings/clients/' + $scope.targetClient.id,
|
||||
$scope.selectedClientRoles).success(function() {
|
||||
$scope.clientMappings = ClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.client.id});
|
||||
$scope.clientRoles = AvailableClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.client.id});
|
||||
$scope.clientComposite = CompositeClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.client.id});
|
||||
$scope.clientMappings = ClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.targetClient.id});
|
||||
$scope.clientRoles = AvailableClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.targetClient.id});
|
||||
$scope.clientComposite = CompositeClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.targetClient.id});
|
||||
$scope.selectedClientRoles = [];
|
||||
$scope.selectedClientMappings = [];
|
||||
$scope.realmComposite = CompositeRealmRoleMapping.query({realm : realm.realm, userId : user.id});
|
||||
|
@ -76,11 +77,11 @@ module.controller('UserRoleMappingCtrl', function($scope, $http, realm, user, cl
|
|||
};
|
||||
|
||||
$scope.deleteClientRole = function() {
|
||||
$http.delete(authUrl + '/admin/realms/' + realm.realm + '/users/' + user.id + '/role-mappings/clients/' + $scope.client.id,
|
||||
$http.delete(authUrl + '/admin/realms/' + realm.realm + '/users/' + user.id + '/role-mappings/clients/' + $scope.targetClient.id,
|
||||
{data : $scope.selectedClientMappings, headers : {"content-type" : "application/json"}}).success(function() {
|
||||
$scope.clientMappings = ClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.client.id});
|
||||
$scope.clientRoles = AvailableClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.client.id});
|
||||
$scope.clientComposite = CompositeClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.client.id});
|
||||
$scope.clientMappings = ClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.targetClient.id});
|
||||
$scope.clientRoles = AvailableClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.targetClient.id});
|
||||
$scope.clientComposite = CompositeClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.targetClient.id});
|
||||
$scope.selectedClientRoles = [];
|
||||
$scope.selectedClientMappings = [];
|
||||
$scope.realmComposite = CompositeRealmRoleMapping.query({realm : realm.realm, userId : user.id});
|
||||
|
@ -92,11 +93,11 @@ module.controller('UserRoleMappingCtrl', function($scope, $http, realm, user, cl
|
|||
|
||||
$scope.changeClient = function() {
|
||||
console.log('changeClient');
|
||||
if ($scope.client) {
|
||||
if ($scope.targetClient) {
|
||||
console.log('load available');
|
||||
$scope.clientComposite = CompositeClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.client.id});
|
||||
$scope.clientRoles = AvailableClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.client.id});
|
||||
$scope.clientMappings = ClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.client.id});
|
||||
$scope.clientComposite = CompositeClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.targetClient.id});
|
||||
$scope.clientRoles = AvailableClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.targetClient.id});
|
||||
$scope.clientMappings = ClientRoleMapping.query({realm : realm.realm, userId : user.id, client : $scope.targetClient.id});
|
||||
} else {
|
||||
$scope.clientRoles = null;
|
||||
$scope.clientMappings = null;
|
||||
|
|
|
@ -35,8 +35,10 @@ module.factory('RealmListLoader', function(Loader, Realm, $q) {
|
|||
return Loader.get(Realm);
|
||||
});
|
||||
|
||||
module.factory('ServerInfoLoader', function(Loader, ServerInfo, $q) {
|
||||
return Loader.get(ServerInfo);
|
||||
module.factory('ServerInfoLoader', function(Loader, ServerInfo) {
|
||||
return function() {
|
||||
return ServerInfo.promise;
|
||||
};
|
||||
});
|
||||
|
||||
module.factory('RealmLoader', function(Loader, Realm, $route, $q) {
|
||||
|
@ -282,6 +284,15 @@ module.factory('ClientListLoader', function(Loader, Client, $route, $q) {
|
|||
});
|
||||
});
|
||||
|
||||
module.factory('ClientServiceAccountUserLoader', function(Loader, ClientServiceAccountUser, $route, $q) {
|
||||
return Loader.get(ClientServiceAccountUser, function() {
|
||||
return {
|
||||
realm : $route.current.params.realm,
|
||||
client : $route.current.params.client
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
module.factory('RoleMappingLoader', function(Loader, RoleMapping, $route, $q) {
|
||||
var realm = $route.current.params.realm || $route.current.params.client;
|
||||
|
|
|
@ -215,10 +215,27 @@ module.factory('RealmLDAPConnectionTester', function($resource) {
|
|||
return $resource(authUrl + '/admin/realms/:realm/testLDAPConnection');
|
||||
});
|
||||
|
||||
module.factory('ServerInfo', function($resource) {
|
||||
return $resource(authUrl + '/admin/serverinfo');
|
||||
});
|
||||
module.service('ServerInfo', function($resource, $q, $http) {
|
||||
var info = {};
|
||||
var delay = $q.defer();
|
||||
|
||||
$http.get(authUrl + '/admin/serverinfo').success(function(data) {
|
||||
info = data;
|
||||
delay.resolve(info);
|
||||
});
|
||||
|
||||
return {
|
||||
get: function() {
|
||||
return info;
|
||||
},
|
||||
reload: function() {
|
||||
$http.get(authUrl + '/admin/serverinfo').success(function(data) {
|
||||
angular.copy(data, info);
|
||||
});
|
||||
},
|
||||
promise: delay.promise
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.factory('ClientProtocolMapper', function($resource) {
|
||||
|
@ -897,6 +914,13 @@ module.factory('ClientOrigins', function($resource) {
|
|||
});
|
||||
});
|
||||
|
||||
module.factory('ClientServiceAccountUser', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/clients/:client/service-account-user', {
|
||||
realm : '@realm',
|
||||
client : '@client'
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('Current', function(Realm, $route, $rootScope) {
|
||||
var current = {
|
||||
realms: {},
|
||||
|
|
|
@ -72,6 +72,13 @@
|
|||
</div>
|
||||
<kc-tooltip>'Confidential' clients require a secret to initiate login protocol. 'Public' clients do not require a secret. 'Bearer-only' clients are web services that never initiate a login.</kc-tooltip>
|
||||
</div>
|
||||
<div class="form-group" data-ng-show="protocol == 'openid-connect' && !client.publicClient && !client.bearerOnly">
|
||||
<label class="col-md-2 control-label" for="serviceAccountsEnabled">Service Accounts Enabled</label>
|
||||
<kc-tooltip>Allows you to authenticate this client to Keycloak and retrieve access token dedicated to this client.</kc-tooltip>
|
||||
<div class="col-md-6">
|
||||
<input ng-model="client.serviceAccountsEnabled" name="serviceAccountsEnabled" id="serviceAccountsEnabled" onoffswitch />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group clearfix block" data-ng-show="protocol == 'saml'">
|
||||
<label class="col-md-2 control-label" for="samlServerSignature">Include AuthnStatement</label>
|
||||
<div class="col-sm-6">
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
<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}}/clients">Clients</a></li>
|
||||
<li>{{client.clientId}}</li>
|
||||
</ol>
|
||||
|
||||
<h1>{{client.clientId|capitalize}}</h1>
|
||||
|
||||
<kc-tabs-client></kc-tabs-client>
|
||||
|
||||
<h2><span>{{client.clientId}}</span> Service Accounts </h2>
|
||||
<p class="subtitle"></p>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageClients" data-ng-show="client.serviceAccountsEnabled">
|
||||
<div class="form-group">
|
||||
<label class="col-md-2 control-label" class="control-label">Realm Roles</label>
|
||||
<div class="col-md-10">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<label class="control-label" for="available">Available Roles</label>
|
||||
<kc-tooltip>Realm level roles that can be assigned to service account.</kc-tooltip>
|
||||
|
||||
<select id="available" class="form-control" multiple size="5"
|
||||
ng-multiple="true"
|
||||
ng-model="selectedRealmRoles"
|
||||
ng-options="r.name for r in realmRoles">
|
||||
</select>
|
||||
<button ng-disabled="selectedRealmRoles.length == 0" class="btn btn-default" type="submit" ng-click="addRealmRole()">
|
||||
Add selected <i class="fa fa-angle-double-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="control-label" for="assigned">Assigned Roles</label>
|
||||
<kc-tooltip>Realm level roles assigned to service account.</kc-tooltip>
|
||||
<select id="assigned" class="form-control" multiple size=5
|
||||
ng-multiple="true"
|
||||
ng-model="selectedRealmMappings"
|
||||
ng-options="r.name for r in realmMappings">
|
||||
</select>
|
||||
<button ng-disabled="selectedRealmMappings.length == 0" class="btn btn-default" type="submit" ng-click="deleteRealmRole()">
|
||||
<i class="fa fa-angle-double-left"></i> Remove selected
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="control-label" for="realm-composite">Effective Roles </label>
|
||||
<kc-tooltip>Assigned realm level roles that may have been inherited from a composite role.</kc-tooltip>
|
||||
<select id="realm-composite" class="form-control" multiple size=5
|
||||
disabled="true"
|
||||
ng-model="dummymodel"
|
||||
ng-options="r.name for r in realmComposite">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-md-2 control-label" class="control-label">
|
||||
<span>Client Roles</span>
|
||||
<select class="form-control" id="clients" name="clients" ng-change="changeClient()" ng-model="targetClient" ng-options="a.clientId for a in clients" ng-disabled="false"></select>
|
||||
</label>
|
||||
|
||||
<div class="col-md-10">
|
||||
<div class="row" data-ng-hide="targetClient">
|
||||
<div class="col-md-4"><span class="text-muted">Select client to view roles for client</span></div>
|
||||
</div>
|
||||
<div class="row" data-ng-show="targetClient">
|
||||
<div class="col-md-3">
|
||||
<label class="control-label" for="client-available">Available Roles</label>
|
||||
<kc-tooltip>Client roles available to be assigned.</kc-tooltip>
|
||||
<select id="client-available" class="form-control" multiple size="5"
|
||||
ng-multiple="true"
|
||||
ng-model="selectedClientRoles"
|
||||
ng-options="r.name for r in clientRoles">
|
||||
</select>
|
||||
<button ng-disabled="selectedClientRoles.length == 0" class="btn btn-default" type="submit" ng-click="addClientRole()">
|
||||
Add selected <i class="fa fa-angle-double-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="control-label" for="client-assigned">Assigned Roles</label>
|
||||
<kc-tooltip>Assigned client roles.</kc-tooltip>
|
||||
<select id="client-assigned" class="form-control" multiple size=5
|
||||
ng-multiple="true"
|
||||
ng-model="selectedClientMappings"
|
||||
ng-options="r.name for r in clientMappings">
|
||||
</select>
|
||||
<button ng-disabled="selectedClientMappings.length == 0" class="btn btn-default" type="submit" ng-click="deleteClientRole()">
|
||||
<i class="fa fa-angle-double-left"></i> Remove selected
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="control-label" for="client-composite">Effective Roles</label>
|
||||
<kc-tooltip>Assigned client roles that may have been inherited from a composite role.</kc-tooltip>
|
||||
<select id="client-composite" class="form-control" multiple size=5
|
||||
disabled="true"
|
||||
ng-model="dummymodel"
|
||||
ng-options="r.name for r in clientComposite">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageClients" data-ng-show="!client.serviceAccountsEnabled">
|
||||
<legend><span class="text">Service account is not enabled for {{client.clientId}}.</span></legend>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<kc-menu></kc-menu>
|
|
@ -1,28 +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}}/clients">Clients</a></li>
|
||||
<li>{{client.clientId}}</li>
|
||||
</ol>
|
||||
|
||||
<h1>{{client.clientId|capitalize}}</h1>
|
||||
|
||||
<kc-tabs-client></kc-tabs-client>
|
||||
|
||||
<h2><span>{{client.clientId}}</span> Service Accounts </h2>
|
||||
<p class="subtitle"></p>
|
||||
<form class="form-horizontal" name="serviceAccountsEnabledForm" novalidate kc-read-only="!access.manageClients">
|
||||
<fieldset class="border-top">
|
||||
<div class="form-group">
|
||||
<label class="col-md-2 control-label" for="serviceAccountsEnabled">Service Accounts Enabled</label>
|
||||
<kc-tooltip>Allows you to authenticate this client to Keycloak and retrieve access token dedicated to this client.</kc-tooltip>
|
||||
<div class="col-md-6">
|
||||
<input ng-model="client.serviceAccountsEnabled" ng-click="serviceAccountsEnabledChanged()" name="serviceAccountsEnabled" id="serviceAccountsEnabled" onoffswitch />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<kc-menu></kc-menu>
|
|
@ -52,13 +52,13 @@
|
|||
<div class="form-group">
|
||||
<label class="col-md-2 control-label" class="control-label">
|
||||
<span>Client Roles</span>
|
||||
<select class="form-control" id="clients" name="clients" ng-change="changeClient()" ng-model="client" ng-options="a.clientId for a in clients" ng-disabled="false"></select>
|
||||
<select class="form-control" id="clients" name="clients" ng-change="changeClient()" ng-model="targetClient" ng-options="a.clientId for a in clients" ng-disabled="false"></select>
|
||||
</label>
|
||||
<div class="col-md-10" kc-read-only="!access.manageUsers">
|
||||
<div class="row" data-ng-hide="client">
|
||||
<div class="row" data-ng-hide="targetClient">
|
||||
<div class="col-md-4"><span class="text-muted">Select client to view roles for client</span></div>
|
||||
</div>
|
||||
<div class="row" data-ng-show="client">
|
||||
<div class="row" data-ng-show="targetClient">
|
||||
<div class="col-md-3">
|
||||
<label class="control-label" for="available-client">Available Roles</label>
|
||||
<kc-tooltip>Assignable roles from this client.</kc-tooltip>
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
<h1>
|
||||
Server Info
|
||||
<i id="serverInfoReload" class="pficon pficon-restart clickable" data-ng-click="serverInfoReload()"></i>
|
||||
</h1>
|
||||
|
||||
<ul class="nav nav-tabs">
|
||||
<li><a href="#/server-info">Info</a></li>
|
||||
<li class="active"><a href="#/server-info/providers">Providers</a></li>
|
||||
</ul>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="kc-table-actions" colspan="5">
|
||||
<div class="form-inline">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Search..." data-ng-model="search" class="form-control search" onkeyup="if(event.keyCode == 13){$(this).next('I').click();}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th width="20%">SPI</th>
|
||||
<th>Providers</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr data-ng-repeat="spi in (providers | orderBy:'name' | filter:search)">
|
||||
<td>{{spi.name}}</td>
|
||||
<td>
|
||||
<div data-ng-repeat="(providerName, provider) in spi.providers">
|
||||
{{providerName}}
|
||||
<span ng-show="provider.operationalInfo">
|
||||
<button type="button" class="btn btn-default btn-xs" ng-click="collapseRep = !collapseRep">
|
||||
<span class="glyphicon glyphicon-plus" data-ng-show="!collapseRep"></span>
|
||||
<span class="glyphicon glyphicon-minus" data-ng-show="collapseRep"></span>
|
||||
</button>
|
||||
<table ng-show="collapseRep" class="table table-striped table-bordered" style="margin-top: 0px;">
|
||||
<tr ng-repeat="(key, value) in provider.operationalInfo">
|
||||
<td width="20%">{{key}}</td>
|
||||
<td>{{value}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<kc-menu></kc-menu>
|
|
@ -1,66 +1,104 @@
|
|||
<div class="col-md-12">
|
||||
<h1>Server Info</h1>
|
||||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
<h1>
|
||||
Server Info
|
||||
<i id="serverInfoReload" class="pficon pficon-restart clickable" data-ng-click="serverInfoReload()"></i>
|
||||
</h1>
|
||||
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active"><a href="#/server-info">Info</a></li>
|
||||
<li><a href="#/server-info/providers">Providers</a></li>
|
||||
</ul>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<tr>
|
||||
<td>Version</td>
|
||||
<td>{{serverInfo.version}}</td>
|
||||
<td width="20%">Keycloak Version</td>
|
||||
<td>{{serverInfo.systemInfo.version}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Server Time</td>
|
||||
<td>{{serverInfo.serverTime}} (<a data-ng-click="serverInfoUpdate()">update</a>)</td>
|
||||
<td>{{serverInfo.systemInfo.serverTime}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Server Uptime</td>
|
||||
<td>{{serverInfo.systemInfo.uptime}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<fieldset>
|
||||
<legend collapsed>Providers</legend>
|
||||
|
||||
<div class="form-group">
|
||||
<h3>Public SPIs</h3>
|
||||
<kc-tooltip>For public SPIs there are built-in providers, but it's also supported to write your own custom providers.</kc-tooltip>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>SPI</th>
|
||||
<th>Providers</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr data-ng-repeat="spi in (serverInfo.providers | filter:{internal:false} | orderBy:'name')">
|
||||
<td>{{spi.name}}</td>
|
||||
<td>
|
||||
<div data-ng-repeat="provider in (spi.implementations | orderBy:'toString()')">
|
||||
{{provider}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<h3>Internal SPIs</h3>
|
||||
<kc-tooltip>For internal SPIs there are only built-in providers. It's not recommended to write your own custom providers as internal SPIs may change or be removed without notice.</kc-tooltip>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>SPI</th>
|
||||
<th>Providers</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr data-ng-repeat="spi in (serverInfo.providers | filter:{internal:true} | orderBy:'name')">
|
||||
<td>{{spi.name}}</td>
|
||||
<td>
|
||||
<div data-ng-repeat="provider in (spi.implementations | orderBy:'toString()')">
|
||||
{{provider}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<legend>Memory</legend>
|
||||
<table class="table table-striped table-bordered" style="margin-top: 0;">
|
||||
<tr>
|
||||
<td width="20%">Total Memory</td>
|
||||
<td>{{serverInfo.memoryInfo.totalFormated}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Free Memory</td>
|
||||
<td>{{serverInfo.memoryInfo.freeFormated}} ({{serverInfo.memoryInfo.freePercentage}}%)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Used Memory</td>
|
||||
<td>{{serverInfo.memoryInfo.usedFormated}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend>System</legend>
|
||||
<table class="table table-striped table-bordered" style="margin-top: 0;">
|
||||
<tr>
|
||||
<td width="20%">Current Working Directory</td>
|
||||
<td>{{serverInfo.systemInfo.userDir}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Java Version</td>
|
||||
<td>{{serverInfo.systemInfo.javaVersion}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Java Vendor</td>
|
||||
<td>{{serverInfo.systemInfo.javaVendor}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Java Runtime</td>
|
||||
<td>{{serverInfo.systemInfo.javaRuntime}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Java VM</td>
|
||||
<td>{{serverInfo.systemInfo.javaVm}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Java VM Version</td>
|
||||
<td>{{serverInfo.systemInfo.javaVmVersion}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Java Home</td>
|
||||
<td>{{serverInfo.systemInfo.javaHome}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>User Name</td>
|
||||
<td>{{serverInfo.systemInfo.userName}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>User Timezone</td>
|
||||
<td>{{serverInfo.systemInfo.userTimezone}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>User Locale</td>
|
||||
<td>{{serverInfo.systemInfo.userLocale}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>System Encoding</td>
|
||||
<td>{{serverInfo.systemInfo.fileEncoding}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Operating System</td>
|
||||
<td>{{serverInfo.systemInfo.osName}} {{serverInfo.systemInfo.osVersion}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>OS Architecture</td>
|
||||
<td>{{serverInfo.systemInfo.osArchitecture}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<kc-menu></kc-menu>
|
|
@ -33,9 +33,9 @@
|
|||
<kc-tooltip>Helper utility for generating various client adapter configuration formats which you can download or cut and paste to configure your clients.</kc-tooltip>
|
||||
</li>
|
||||
|
||||
<li ng-class="{active: path[4] == 'service-accounts'}" data-ng-show="!client.publicClient && !client.bearerOnly">
|
||||
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/service-accounts">Service Accounts</a>
|
||||
<kc-tooltip>Allows you to authenticate this client to Keycloak and retrieve access tokens dedicated to this client.</kc-tooltip>
|
||||
<li ng-class="{active: path[4] == 'service-account-roles'}" data-ng-show="client.serviceAccountsEnabled">
|
||||
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/service-account-roles">Service Account Roles</a>
|
||||
<kc-tooltip>Allows you to authenticate role mappings for the service account dedicated to this client.</kc-tooltip>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
|
@ -32,7 +32,7 @@ public class MigrateTo1_4_0 {
|
|||
}
|
||||
|
||||
public void migrateUsers(KeycloakSession session, RealmModel realm) {
|
||||
List<UserModel> users = session.userStorage().getUsers(realm);
|
||||
List<UserModel> users = session.userStorage().getUsers(realm, false);
|
||||
for (UserModel user : users) {
|
||||
String email = user.getEmail();
|
||||
email = KeycloakModelUtils.toLowerCaseSafe(email);
|
||||
|
|
|
@ -18,6 +18,8 @@ public interface KeycloakSessionFactory extends ProviderEventManager {
|
|||
<T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz, String id);
|
||||
|
||||
List<ProviderFactory> getProviderFactories(Class<? extends Provider> clazz);
|
||||
|
||||
long getServerStartupTimestamp();
|
||||
|
||||
void close();
|
||||
}
|
||||
|
|
|
@ -204,8 +204,17 @@ public class UserFederationManager implements UserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getUsers(RealmModel realm) {
|
||||
return getUsers(realm, 0, Integer.MAX_VALUE - 1);
|
||||
public UserModel getUserByServiceAccountClient(ClientModel client) {
|
||||
UserModel user = session.userStorage().getUserByServiceAccountClient(client);
|
||||
if (user != null) {
|
||||
user = validateAndProxyUser(client.getRealm(), user);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts) {
|
||||
return getUsers(realm, 0, Integer.MAX_VALUE - 1, includeServiceAccounts);
|
||||
|
||||
}
|
||||
|
||||
|
@ -242,11 +251,11 @@ public class UserFederationManager implements UserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
|
||||
public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults, final boolean includeServiceAccounts) {
|
||||
return query(new PaginatedQuery() {
|
||||
@Override
|
||||
public List<UserModel> query(RealmModel realm, int first, int max) {
|
||||
return session.userStorage().getUsers(realm, first, max);
|
||||
return session.userStorage().getUsers(realm, first, max, includeServiceAccounts);
|
||||
}
|
||||
}, realm, firstResult, maxResults);
|
||||
}
|
||||
|
|
|
@ -104,6 +104,9 @@ public interface UserModel {
|
|||
String getFederationLink();
|
||||
void setFederationLink(String link);
|
||||
|
||||
String getServiceAccountClientLink();
|
||||
void setServiceAccountClientLink(String clientInternalId);
|
||||
|
||||
void addConsent(UserConsentModel consent);
|
||||
UserConsentModel getConsentByClient(String clientInternalId);
|
||||
List<UserConsentModel> getConsents();
|
||||
|
|
|
@ -25,9 +25,12 @@ public interface UserProvider extends Provider {
|
|||
UserModel getUserByUsername(String username, RealmModel realm);
|
||||
UserModel getUserByEmail(String email, RealmModel realm);
|
||||
UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm);
|
||||
List<UserModel> getUsers(RealmModel realm);
|
||||
UserModel getUserByServiceAccountClient(ClientModel client);
|
||||
List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts);
|
||||
|
||||
// Service account is included for counts
|
||||
int getUsersCount(RealmModel realm);
|
||||
List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults);
|
||||
List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts);
|
||||
List<UserModel> searchForUser(String search, RealmModel realm);
|
||||
List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults);
|
||||
List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm);
|
||||
|
|
|
@ -27,6 +27,7 @@ public class UserEntity extends AbstractIdentifiableEntity {
|
|||
private List<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
|
||||
private List<FederatedIdentityEntity> federatedIdentities;
|
||||
private String federationLink;
|
||||
private String serviceAccountClientLink;
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
|
@ -148,5 +149,13 @@ public class UserEntity extends AbstractIdentifiableEntity {
|
|||
public void setFederationLink(String federationLink) {
|
||||
this.federationLink = federationLink;
|
||||
}
|
||||
|
||||
public String getServiceAccountClientLink() {
|
||||
return serviceAccountClientLink;
|
||||
}
|
||||
|
||||
public void setServiceAccountClientLink(String serviceAccountClientLink) {
|
||||
this.serviceAccountClientLink = serviceAccountClientLink;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.keycloak.models.utils;
|
|||
|
||||
import org.bouncycastle.openssl.PEMWriter;
|
||||
import org.keycloak.constants.KerberosConstants;
|
||||
import org.keycloak.constants.ServiceAccountConstants;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
@ -350,6 +351,8 @@ public final class KeycloakModelUtils {
|
|||
return mapperModel;
|
||||
}
|
||||
|
||||
// END USER FEDERATION RELATED STUFF
|
||||
|
||||
public static String toLowerCaseSafe(String str) {
|
||||
return str==null ? null : str.toLowerCase();
|
||||
}
|
||||
|
|
|
@ -902,6 +902,14 @@ public class RepresentationToModel {
|
|||
user.addConsent(consentModel);
|
||||
}
|
||||
}
|
||||
if (userRep.getServiceAccountClientId() != null) {
|
||||
String clientId = userRep.getServiceAccountClientId();
|
||||
ClientModel client = clientMap.get(clientId);
|
||||
if (client == null) {
|
||||
throw new RuntimeException("Unable to find client specified for service account link. Client: " + clientId);
|
||||
}
|
||||
user.setServiceAccountClientLink(client.getId());;
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
|
|
|
@ -207,6 +207,16 @@ public class UserModelDelegate implements UserModel {
|
|||
delegate.setFederationLink(link);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceAccountClientLink() {
|
||||
return delegate.getServiceAccountClientLink();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServiceAccountClientLink(String clientInternalId) {
|
||||
delegate.setServiceAccountClientLink(clientInternalId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addConsent(UserConsentModel consent) {
|
||||
delegate.addConsent(consent);
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package org.keycloak.provider;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Marker interface for ProviderFactory of Provider which wants to show some info on "Server Info" page in Admin console.
|
||||
*
|
||||
* @author Vlastimil Elias (velias at redhat dot com)
|
||||
*/
|
||||
public interface ServerInfoAwareProviderFactory<T extends Provider> extends ProviderFactory<T> {
|
||||
|
||||
/**
|
||||
* Get operational info about given provider. This info contains informations about providers configuration and operational conditions (eg. errors in connection to remote systems etc) which is
|
||||
* shown on "Server Info" page.
|
||||
*
|
||||
* @return Map with keys describing value and relevant values itself
|
||||
*/
|
||||
public Map<String, String> getOperationalInfo();
|
||||
|
||||
}
|
|
@ -107,8 +107,18 @@ public class FileUserProvider implements UserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getUsers(RealmModel realm) {
|
||||
return getUsers(realm, -1, -1);
|
||||
public UserModel getUserByServiceAccountClient(ClientModel client) {
|
||||
for (UserModel user : inMemoryModel.getUsers(client.getRealm().getId())) {
|
||||
if (client.getId().equals(user.getServiceAccountClientLink())) {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts) {
|
||||
return getUsers(realm, -1, -1, includeServiceAccounts);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -117,12 +127,27 @@ public class FileUserProvider implements UserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
|
||||
List users = new ArrayList(inMemoryModel.getUsers(realm.getId()));
|
||||
public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts) {
|
||||
List<UserModel> users = new ArrayList<>(inMemoryModel.getUsers(realm.getId()));
|
||||
|
||||
if (!includeServiceAccounts) {
|
||||
users = filterServiceAccountUsers(users);
|
||||
}
|
||||
|
||||
List<UserModel> sortedList = sortedSubList(users, firstResult, maxResults);
|
||||
return sortedList;
|
||||
}
|
||||
|
||||
private List<UserModel> filterServiceAccountUsers(List<UserModel> users) {
|
||||
List<UserModel> result = new ArrayList<>();
|
||||
for (UserModel user : users) {
|
||||
if (user.getServiceAccountClientLink() == null) {
|
||||
result.add(user);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected List<UserModel> sortedSubList(List list, int firstResult, int maxResults) {
|
||||
if (list.isEmpty()) return list;
|
||||
|
||||
|
@ -183,6 +208,9 @@ public class FileUserProvider implements UserProvider {
|
|||
}
|
||||
}
|
||||
|
||||
// Remove users with service account link
|
||||
found = filterServiceAccountUsers(found);
|
||||
|
||||
return sortedSubList(found, firstResult, maxResults);
|
||||
}
|
||||
|
||||
|
|
|
@ -477,6 +477,16 @@ public class UserAdapter implements UserModel, Comparable {
|
|||
user.setFederationLink(link);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceAccountClientLink() {
|
||||
return user.getServiceAccountClientLink();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServiceAccountClientLink(String clientInternalId) {
|
||||
user.setServiceAccountClientLink(clientInternalId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addConsent(UserConsentModel consent) {
|
||||
// TODO
|
||||
|
|
|
@ -207,8 +207,13 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getUsers(RealmModel realm) {
|
||||
return getDelegate().getUsers(realm);
|
||||
public UserModel getUserByServiceAccountClient(ClientModel client) {
|
||||
return getDelegate().getUserByServiceAccountClient(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts) {
|
||||
return getDelegate().getUsers(realm, includeServiceAccounts);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -217,8 +222,8 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
|
||||
return getDelegate().getUsers(realm, firstResult, maxResults);
|
||||
public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts) {
|
||||
return getDelegate().getUsers(realm, firstResult, maxResults, includeServiceAccounts);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -74,8 +74,13 @@ public class NoCacheUserProvider implements CacheUserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getUsers(RealmModel realm) {
|
||||
return getDelegate().getUsers(realm);
|
||||
public UserModel getUserByServiceAccountClient(ClientModel client) {
|
||||
return getDelegate().getUserByServiceAccountClient(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts) {
|
||||
return getDelegate().getUsers(realm, includeServiceAccounts);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -84,8 +89,8 @@ public class NoCacheUserProvider implements CacheUserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
|
||||
return getDelegate().getUsers(realm, firstResult, maxResults);
|
||||
public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts) {
|
||||
return getDelegate().getUsers(realm, firstResult, maxResults, includeServiceAccounts);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -243,6 +243,18 @@ public class UserAdapter implements UserModel {
|
|||
updated.setFederationLink(link);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceAccountClientLink() {
|
||||
if (updated != null) return updated.getServiceAccountClientLink();
|
||||
return cached.getServiceAccountClientLink();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServiceAccountClientLink(String clientInternalId) {
|
||||
getDelegateForUpdate();
|
||||
updated.setServiceAccountClientLink(clientInternalId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getRealmRoleMappings() {
|
||||
if (updated != null) return updated.getRealmRoleMappings();
|
||||
|
|
|
@ -31,6 +31,7 @@ public class CachedUser implements Serializable {
|
|||
private boolean enabled;
|
||||
private boolean totp;
|
||||
private String federationLink;
|
||||
private String serviceAccountClientLink;
|
||||
private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
|
||||
private Set<String> requiredActions = new HashSet<>();
|
||||
private Set<String> roleMappings = new HashSet<String>();
|
||||
|
@ -49,6 +50,7 @@ public class CachedUser implements Serializable {
|
|||
this.enabled = user.isEnabled();
|
||||
this.totp = user.isTotp();
|
||||
this.federationLink = user.getFederationLink();
|
||||
this.serviceAccountClientLink = user.getServiceAccountClientLink();
|
||||
this.requiredActions.addAll(user.getRequiredActions());
|
||||
for (RoleModel role : user.getRoleMappings()) {
|
||||
roleMappings.add(role.getId());
|
||||
|
@ -114,4 +116,8 @@ public class CachedUser implements Serializable {
|
|||
public String getFederationLink() {
|
||||
return federationLink;
|
||||
}
|
||||
|
||||
public String getServiceAccountClientLink() {
|
||||
return serviceAccountClientLink;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -272,13 +272,29 @@ public class JpaUserProvider implements UserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getUsers(RealmModel realm) {
|
||||
return getUsers(realm, -1, -1);
|
||||
public UserModel getUserByServiceAccountClient(ClientModel client) {
|
||||
TypedQuery<UserEntity> query = em.createNamedQuery("getRealmUserByServiceAccount", UserEntity.class);
|
||||
query.setParameter("realmId", client.getRealm().getId());
|
||||
query.setParameter("clientInternalId", client.getId());
|
||||
List<UserEntity> results = query.getResultList();
|
||||
if (results.isEmpty()) {
|
||||
return null;
|
||||
} else if (results.size() > 1) {
|
||||
throw new IllegalStateException("More service account linked users found for client=" + client.getClientId() +
|
||||
", results=" + results);
|
||||
} else {
|
||||
UserEntity user = results.get(0);
|
||||
return new UserAdapter(client.getRealm(), em, user);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts) {
|
||||
return getUsers(realm, -1, -1, includeServiceAccounts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUsersCount(RealmModel realm) {
|
||||
// TODO: named query?
|
||||
Object count = em.createNamedQuery("getRealmUserCount")
|
||||
.setParameter("realmId", realm.getId())
|
||||
.getSingleResult();
|
||||
|
@ -286,8 +302,10 @@ public class JpaUserProvider implements UserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
|
||||
TypedQuery<UserEntity> query = em.createNamedQuery("getAllUsersByRealm", UserEntity.class);
|
||||
public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts) {
|
||||
String queryName = includeServiceAccounts ? "getAllUsersByRealm" : "getAllUsersByRealmExcludeServiceAccount" ;
|
||||
|
||||
TypedQuery<UserEntity> query = em.createNamedQuery(queryName, UserEntity.class);
|
||||
query.setParameter("realmId", realm.getId());
|
||||
if (firstResult != -1) {
|
||||
query.setFirstResult(firstResult);
|
||||
|
|
|
@ -543,6 +543,16 @@ public class UserAdapter implements UserModel {
|
|||
user.setFederationLink(link);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceAccountClientLink() {
|
||||
return user.getServiceAccountClientLink();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServiceAccountClientLink(String clientInternalId) {
|
||||
user.setServiceAccountClientLink(clientInternalId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addConsent(UserConsentModel consent) {
|
||||
String clientId = consent.getClient().getId();
|
||||
|
|
|
@ -21,12 +21,15 @@ import java.util.Collection;
|
|||
*/
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="getAllUsersByRealm", query="select u from UserEntity u where u.realmId = :realmId order by u.username"),
|
||||
@NamedQuery(name="searchForUser", query="select u from UserEntity u where u.realmId = :realmId and ( lower(u.username) like :search or lower(concat(u.firstName, ' ', u.lastName)) like :search or u.email like :search ) order by u.username"),
|
||||
@NamedQuery(name="getAllUsersByRealmExcludeServiceAccount", query="select u from UserEntity u where u.realmId = :realmId and (u.serviceAccountClientLink is null) order by u.username"),
|
||||
@NamedQuery(name="searchForUser", query="select u from UserEntity u where u.realmId = :realmId and (u.serviceAccountClientLink is null) and " +
|
||||
"( lower(u.username) like :search or lower(concat(u.firstName, ' ', u.lastName)) like :search or u.email like :search ) order by u.username"),
|
||||
@NamedQuery(name="getRealmUserById", query="select u from UserEntity u where u.id = :id and u.realmId = :realmId"),
|
||||
@NamedQuery(name="getRealmUserByUsername", query="select u from UserEntity u where u.username = :username and u.realmId = :realmId"),
|
||||
@NamedQuery(name="getRealmUserByEmail", query="select u from UserEntity u where u.email = :email and u.realmId = :realmId"),
|
||||
@NamedQuery(name="getRealmUserByLastName", query="select u from UserEntity u where u.lastName = :lastName and u.realmId = :realmId"),
|
||||
@NamedQuery(name="getRealmUserByFirstLastName", query="select u from UserEntity u where u.firstName = :first and u.lastName = :last and u.realmId = :realmId"),
|
||||
@NamedQuery(name="getRealmUserByServiceAccount", query="select u from UserEntity u where u.serviceAccountClientLink = :clientInternalId and u.realmId = :realmId"),
|
||||
@NamedQuery(name="getRealmUserCount", query="select count(u) from UserEntity u where u.realmId = :realmId"),
|
||||
@NamedQuery(name="deleteUsersByRealm", query="delete from UserEntity u where u.realmId = :realmId"),
|
||||
@NamedQuery(name="deleteUsersByRealmAndLink", query="delete from UserEntity u where u.realmId = :realmId and u.federationLink=:link")
|
||||
|
@ -77,6 +80,9 @@ public class UserEntity {
|
|||
@Column(name="federation_link")
|
||||
protected String federationLink;
|
||||
|
||||
@Column(name="SERVICE_ACCOUNT_CLIENT_LINK")
|
||||
protected String serviceAccountClientLink;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
@ -198,6 +204,14 @@ public class UserEntity {
|
|||
this.federationLink = federationLink;
|
||||
}
|
||||
|
||||
public String getServiceAccountClientLink() {
|
||||
return serviceAccountClientLink;
|
||||
}
|
||||
|
||||
public void setServiceAccountClientLink(String serviceAccountClientLink) {
|
||||
this.serviceAccountClientLink = serviceAccountClientLink;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -105,6 +105,16 @@ public class MongoUserProvider implements UserProvider {
|
|||
return userEntity == null ? null : new UserAdapter(session, realm, userEntity, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserByServiceAccountClient(ClientModel client) {
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("serviceAccountClientLink").is(client.getId())
|
||||
.and("realmId").is(client.getRealm().getId())
|
||||
.get();
|
||||
MongoUserEntity userEntity = getMongoStore().loadSingleEntity(MongoUserEntity.class, query, invocationContext);
|
||||
return userEntity == null ? null : new UserAdapter(session, client.getRealm(), userEntity, invocationContext);
|
||||
}
|
||||
|
||||
protected List<UserModel> convertUserEntities(RealmModel realm, List<MongoUserEntity> userEntities) {
|
||||
List<UserModel> userModels = new ArrayList<UserModel>();
|
||||
for (MongoUserEntity user : userEntities) {
|
||||
|
@ -115,8 +125,8 @@ public class MongoUserProvider implements UserProvider {
|
|||
|
||||
|
||||
@Override
|
||||
public List<UserModel> getUsers(RealmModel realm) {
|
||||
return getUsers(realm, -1, -1);
|
||||
public List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts) {
|
||||
return getUsers(realm, -1, -1, includeServiceAccounts);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -128,10 +138,15 @@ public class MongoUserProvider implements UserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("realmId").is(realm.getId())
|
||||
.get();
|
||||
public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts) {
|
||||
QueryBuilder queryBuilder = new QueryBuilder()
|
||||
.and("realmId").is(realm.getId());
|
||||
|
||||
if (!includeServiceAccounts) {
|
||||
queryBuilder = queryBuilder.and("serviceAccountClientLink").is(null);
|
||||
}
|
||||
|
||||
DBObject query = queryBuilder.get();
|
||||
DBObject sort = new BasicDBObject("username", 1);
|
||||
List<MongoUserEntity> users = getMongoStore().loadEntities(MongoUserEntity.class, query, sort, firstResult, maxResults, invocationContext);
|
||||
return convertUserEntities(realm, users);
|
||||
|
@ -170,6 +185,7 @@ public class MongoUserProvider implements UserProvider {
|
|||
|
||||
QueryBuilder builder = new QueryBuilder().and(
|
||||
new QueryBuilder().and("realmId").is(realm.getId()).get(),
|
||||
new QueryBuilder().and("serviceAccountClientLink").is(null).get(),
|
||||
new QueryBuilder().or(
|
||||
new QueryBuilder().put("username").regex(caseInsensitivePattern).get(),
|
||||
new QueryBuilder().put("email").regex(caseInsensitivePattern).get(),
|
||||
|
|
|
@ -187,7 +187,7 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
|||
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
return user.getAttributes()==null ? Collections.<String, List<String>>emptyMap() : Collections.unmodifiableMap((Map)user.getAttributes());
|
||||
return user.getAttributes()==null ? Collections.<String, List<String>>emptyMap() : Collections.unmodifiableMap((Map) user.getAttributes());
|
||||
}
|
||||
|
||||
public MongoUserEntity getUser() {
|
||||
|
@ -460,6 +460,17 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
|||
updateUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceAccountClientLink() {
|
||||
return user.getServiceAccountClientLink();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServiceAccountClientLink(String clientInternalId) {
|
||||
user.setServiceAccountClientLink(clientInternalId);
|
||||
updateUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addConsent(UserConsentModel consent) {
|
||||
String clientId = consent.getClient().getId();
|
||||
|
|
|
@ -106,20 +106,13 @@ public class ServiceAccountManager {
|
|||
protected Response finishClientAuthorization() {
|
||||
event.detail(Details.RESPONSE_TYPE, ServiceAccountConstants.CLIENT_AUTH);
|
||||
|
||||
Map<String, String> search = new HashMap<>();
|
||||
search.put(ServiceAccountConstants.SERVICE_ACCOUNT_CLIENT_ATTRIBUTE, client.getId());
|
||||
List<UserModel> users = session.users().searchForUserByUserAttributes(search, realm);
|
||||
clientUser = session.users().getUserByServiceAccountClient(client);
|
||||
|
||||
if (users.size() == 0) {
|
||||
if (clientUser == null || client.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, ServiceAccountConstants.CLIENT_ID_PROTOCOL_MAPPER) == null) {
|
||||
// May need to handle bootstrap here as well
|
||||
logger.warnf("Service account user for client '%s' not found. Creating now", client.getClientId());
|
||||
logger.infof("Service account user for client '%s' not found or default protocol mapper for service account not found. Creating now", client.getClientId());
|
||||
new ClientManager(new RealmManager(session)).enableServiceAccount(client);
|
||||
users = session.users().searchForUserByUserAttributes(search, realm);
|
||||
clientUser = users.get(0);
|
||||
} else if (users.size() == 1) {
|
||||
clientUser = users.get(0);
|
||||
} else {
|
||||
throw new ModelDuplicateException("Multiple service account users found for client '" + client.getClientId() + "' . Check your DB");
|
||||
clientUser = session.users().getUserByServiceAccountClient(client);
|
||||
}
|
||||
|
||||
String clientUsername = clientUser.getUsername();
|
||||
|
|
|
@ -28,6 +28,8 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
|
|||
private Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoriesMap = new HashMap<Class<? extends Provider>, Map<String, ProviderFactory>>();
|
||||
protected CopyOnWriteArrayList<ProviderEventListener> listeners = new CopyOnWriteArrayList<ProviderEventListener>();
|
||||
|
||||
protected long serverStartupTimestamp;
|
||||
|
||||
@Override
|
||||
public void register(ProviderEventListener listener) {
|
||||
listeners.add(listener);
|
||||
|
@ -46,6 +48,8 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
|
|||
}
|
||||
|
||||
public void init() {
|
||||
serverStartupTimestamp = System.currentTimeMillis();
|
||||
|
||||
ProviderManager pm = new ProviderManager(getClass().getClassLoader(), Config.scope().getArray("providers"));
|
||||
|
||||
for (Spi spi : ServiceLoader.load(Spi.class, getClass().getClassLoader())) {
|
||||
|
@ -148,4 +152,12 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
|
|||
return factory.getClass().getPackage().getName().startsWith("org.keycloak");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return timestamp of Keycloak server startup
|
||||
*/
|
||||
@Override
|
||||
public long getServerStartupTimestamp() {
|
||||
return serverStartupTimestamp;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -51,6 +51,12 @@ public class ClientManager {
|
|||
if (sessions != null) {
|
||||
sessions.onClientRemoved(realm, client);
|
||||
}
|
||||
|
||||
UserModel serviceAccountUser = realmManager.getSession().users().getUserByServiceAccountClient(client);
|
||||
if (serviceAccountUser != null) {
|
||||
realmManager.getSession().users().removeUser(realm, serviceAccountUser);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
@ -93,18 +99,15 @@ public class ClientManager {
|
|||
client.setServiceAccountsEnabled(true);
|
||||
|
||||
// Add dedicated user for this service account
|
||||
RealmModel realm = client.getRealm();
|
||||
Map<String, String> search = new HashMap<>();
|
||||
search.put(ServiceAccountConstants.SERVICE_ACCOUNT_CLIENT_ATTRIBUTE, client.getId());
|
||||
List<UserModel> serviceAccountUsers = realmManager.getSession().users().searchForUserByUserAttributes(search, realm);
|
||||
if (serviceAccountUsers.size() == 0) {
|
||||
if (realmManager.getSession().users().getUserByServiceAccountClient(client) == null) {
|
||||
String username = ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + client.getClientId();
|
||||
logger.infof("Creating service account user '%s'", username);
|
||||
|
||||
UserModel user = realmManager.getSession().users().addUser(realm, username);
|
||||
// Don't use federation for service account user
|
||||
UserModel user = realmManager.getSession().userStorage().addUser(client.getRealm(), username);
|
||||
user.setEnabled(true);
|
||||
user.setEmail(username + "@placeholder.org");
|
||||
user.setSingleAttribute(ServiceAccountConstants.SERVICE_ACCOUNT_CLIENT_ATTRIBUTE, client.getId());
|
||||
user.setServiceAccountClientLink(client.getId());
|
||||
}
|
||||
|
||||
// Add protocol mappers to retrieve clientId in access token
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.keycloak.services.managers.AppAuthManager;
|
|||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.services.resources.admin.info.ServerInfoAdminResource;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
|
|
|
@ -19,6 +19,7 @@ import org.keycloak.models.utils.RepresentationToModel;
|
|||
import org.keycloak.representations.adapters.action.GlobalRequestResult;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.representations.idm.UserSessionRepresentation;
|
||||
import org.keycloak.services.managers.ClientManager;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
|
@ -292,6 +293,31 @@ public class ClientResource {
|
|||
adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns user dedicated to this service account
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Path("service-account-user")
|
||||
@GET
|
||||
@NoCache
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public UserRepresentation getServiceAccountUser() {
|
||||
auth.requireView();
|
||||
|
||||
UserModel user = session.users().getUserByServiceAccountClient(client);
|
||||
if (user == null) {
|
||||
if (client.isServiceAccountsEnabled()) {
|
||||
new ClientManager(new RealmManager(session)).enableServiceAccount(client);
|
||||
user = session.users().getUserByServiceAccountClient(client);
|
||||
} else {
|
||||
throw new BadRequestException("Service account not enabled for the client '" + client.getClientId() + "'");
|
||||
}
|
||||
}
|
||||
|
||||
return ModelToRepresentation.toRepresentation(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the client has an admin URL, push the client's revocation policy to it.
|
||||
*
|
||||
|
|
|
@ -109,7 +109,7 @@ public class IdentityProviderResource {
|
|||
// Admin changed the ID (alias) of identity provider. We must update all clients and users
|
||||
logger.debug("Changing providerId in all clients and linked users. oldProviderId=" + oldProviderId + ", newProviderId=" + newProviderId);
|
||||
|
||||
updateUsersAfterProviderAliasChange(this.session.users().getUsers(this.realm), oldProviderId, newProviderId);
|
||||
updateUsersAfterProviderAliasChange(this.session.users().getUsers(this.realm, false), oldProviderId, newProviderId);
|
||||
}
|
||||
|
||||
adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(providerRep).success();
|
||||
|
|
|
@ -554,7 +554,7 @@ public class UsersResource {
|
|||
}
|
||||
userModels = session.users().searchForUserByAttributes(attributes, realm, firstResult, maxResults);
|
||||
} else {
|
||||
userModels = session.users().getUsers(realm, firstResult, maxResults);
|
||||
userModels = session.users().getUsers(realm, firstResult, maxResults, false);
|
||||
}
|
||||
|
||||
for (UserModel user : userModels) {
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
package org.keycloak.services.resources.admin.info;
|
||||
|
||||
public class MemoryInfoRepresentation {
|
||||
|
||||
protected long total;
|
||||
protected long used;
|
||||
|
||||
public static MemoryInfoRepresentation create() {
|
||||
MemoryInfoRepresentation rep = new MemoryInfoRepresentation();
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
rep.total = runtime.maxMemory();
|
||||
rep.used = runtime.totalMemory() - runtime.freeMemory();
|
||||
return rep;
|
||||
}
|
||||
|
||||
public long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public String getTotalFormated() {
|
||||
return formatMemory(getTotal());
|
||||
}
|
||||
|
||||
public long getFree() {
|
||||
return getTotal() - getUsed();
|
||||
}
|
||||
|
||||
public String getFreeFormated() {
|
||||
return formatMemory(getFree());
|
||||
}
|
||||
|
||||
public long getUsed() {
|
||||
return used;
|
||||
}
|
||||
|
||||
public String getUsedFormated() {
|
||||
return formatMemory(getUsed());
|
||||
}
|
||||
|
||||
public long getFreePercentage() {
|
||||
return getFree() * 100 / getTotal();
|
||||
}
|
||||
|
||||
private String formatMemory(long bytes) {
|
||||
if (bytes > 1024L * 1024L) {
|
||||
return bytes / (1024L * 1024L) + " MB";
|
||||
} else if (bytes > 1024L) {
|
||||
return bytes / (1024L) + " kB";
|
||||
} else {
|
||||
return bytes + " B";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package org.keycloak.services.resources.admin.info;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class ProviderRepresentation {
|
||||
|
||||
private Map<String, String> operationalInfo;
|
||||
|
||||
public Map<String, String> getOperationalInfo() {
|
||||
return operationalInfo;
|
||||
}
|
||||
|
||||
public void setOperationalInfo(Map<String, String> operationalInfo) {
|
||||
this.operationalInfo = operationalInfo;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,9 +1,7 @@
|
|||
package org.keycloak.services.resources.admin;
|
||||
package org.keycloak.services.resources.admin.info;
|
||||
|
||||
import org.keycloak.Version;
|
||||
import org.keycloak.broker.provider.IdentityProvider;
|
||||
import org.keycloak.broker.provider.IdentityProviderFactory;
|
||||
import org.keycloak.events.EventListenerProvider;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.exportimport.ClientImporter;
|
||||
|
@ -18,6 +16,7 @@ import org.keycloak.protocol.LoginProtocolFactory;
|
|||
import org.keycloak.protocol.ProtocolMapper;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.ServerInfoAwareProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
import org.keycloak.representations.idm.ConfigPropertyRepresentation;
|
||||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||
|
@ -26,14 +25,7 @@ import org.keycloak.social.SocialIdentityProvider;
|
|||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.core.Context;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -53,13 +45,12 @@ public class ServerInfoAdminResource {
|
|||
@GET
|
||||
public ServerInfoRepresentation getInfo() {
|
||||
ServerInfoRepresentation info = new ServerInfoRepresentation();
|
||||
info.version = Version.VERSION;
|
||||
info.serverTime = new Date().toString();
|
||||
info.setSystemInfo(SystemInfoRepresentation.create(session));
|
||||
info.setMemoryInfo(MemoryInfoRepresentation.create());
|
||||
|
||||
setSocialProviders(info);
|
||||
setIdentityProviders(info);
|
||||
setThemes(info);
|
||||
setEventListeners(info);
|
||||
setProtocols(info);
|
||||
setClientImporters(info);
|
||||
setProviders(info);
|
||||
setProtocolMapperTypes(info);
|
||||
|
@ -69,42 +60,55 @@ public class ServerInfoAdminResource {
|
|||
}
|
||||
|
||||
private void setProviders(ServerInfoRepresentation info) {
|
||||
List<SpiInfoRepresentation> providers = new LinkedList<>();
|
||||
Map<String, SpiInfoRepresentation> spis = new HashMap<>();
|
||||
for (Spi spi : ServiceLoader.load(Spi.class)) {
|
||||
SpiInfoRepresentation spiRep = new SpiInfoRepresentation();
|
||||
spiRep.setName(spi.getName());
|
||||
spiRep.setInternal(spi.isInternal());
|
||||
spiRep.setImplementations(session.listProviderIds(spi.getProviderClass()));
|
||||
providers.add(spiRep);
|
||||
spiRep.setSystemInfo(ServerInfoAwareProviderFactory.class.isAssignableFrom(spi.getProviderFactoryClass()));
|
||||
Set<String> providerIds = session.listProviderIds(spi.getProviderClass());
|
||||
Map<String, ProviderRepresentation> providers = new HashMap<>();
|
||||
|
||||
if (providerIds != null) {
|
||||
for (String name : providerIds) {
|
||||
ProviderRepresentation provider = new ProviderRepresentation();
|
||||
if (spiRep.isSystemInfo()) {
|
||||
provider.setOperationalInfo(((ServerInfoAwareProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(spi.getProviderClass(), name)).getOperationalInfo());
|
||||
}
|
||||
providers.put(name, provider);
|
||||
}
|
||||
}
|
||||
spiRep.setProviders(providers);
|
||||
|
||||
spis.put(spi.getName(), spiRep);
|
||||
}
|
||||
info.providers = providers;
|
||||
info.setProviders(spis);
|
||||
}
|
||||
|
||||
private void setThemes(ServerInfoRepresentation info) {
|
||||
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
|
||||
info.themes = new HashMap<String, List<String>>();
|
||||
info.setThemes(new HashMap<String, List<String>>());
|
||||
|
||||
for (Theme.Type type : Theme.Type.values()) {
|
||||
List<String> themes = new LinkedList<String>(themeProvider.nameSet(type));
|
||||
Collections.sort(themes);
|
||||
|
||||
info.themes.put(type.toString().toLowerCase(), themes);
|
||||
info.getThemes().put(type.toString().toLowerCase(), themes);
|
||||
}
|
||||
}
|
||||
|
||||
private void setSocialProviders(ServerInfoRepresentation info) {
|
||||
info.socialProviders = new LinkedList<>();
|
||||
info.setSocialProviders(new LinkedList<Map<String, String>>());
|
||||
List<ProviderFactory> providerFactories = session.getKeycloakSessionFactory().getProviderFactories(SocialIdentityProvider.class);
|
||||
setIdentityProviders(providerFactories, info.socialProviders, "Social");
|
||||
setIdentityProviders(providerFactories, info.getSocialProviders(), "Social");
|
||||
}
|
||||
|
||||
private void setIdentityProviders(ServerInfoRepresentation info) {
|
||||
info.identityProviders = new LinkedList<>();
|
||||
info.setIdentityProviders(new LinkedList<Map<String, String>>());
|
||||
List<ProviderFactory> providerFactories = session.getKeycloakSessionFactory().getProviderFactories(IdentityProvider.class);
|
||||
setIdentityProviders(providerFactories, info.identityProviders, "User-defined");
|
||||
setIdentityProviders(providerFactories, info.getIdentityProviders(), "User-defined");
|
||||
|
||||
providerFactories = session.getKeycloakSessionFactory().getProviderFactories(SocialIdentityProvider.class);
|
||||
setIdentityProviders(providerFactories, info.identityProviders, "Social");
|
||||
setIdentityProviders(providerFactories, info.getIdentityProviders(), "Social");
|
||||
}
|
||||
|
||||
public void setIdentityProviders(List<ProviderFactory> factories, List<Map<String, String>> providers, String groupName) {
|
||||
|
@ -119,31 +123,14 @@ public class ServerInfoAdminResource {
|
|||
}
|
||||
}
|
||||
|
||||
private void setEventListeners(ServerInfoRepresentation info) {
|
||||
info.eventListeners = new LinkedList<String>();
|
||||
|
||||
Set<String> providers = session.listProviderIds(EventListenerProvider.class);
|
||||
if (providers != null) {
|
||||
info.eventListeners.addAll(providers);
|
||||
}
|
||||
}
|
||||
|
||||
private void setProtocols(ServerInfoRepresentation info) {
|
||||
info.protocols = new LinkedList<String>();
|
||||
for (ProviderFactory p : session.getKeycloakSessionFactory().getProviderFactories(LoginProtocol.class)) {
|
||||
info.protocols.add(p.getId());
|
||||
}
|
||||
Collections.sort(info.protocols);
|
||||
}
|
||||
|
||||
private void setProtocolMapperTypes(ServerInfoRepresentation info) {
|
||||
info.protocolMapperTypes = new HashMap<String, List<ProtocolMapperTypeRepresentation>>();
|
||||
info.setProtocolMapperTypes(new HashMap<String, List<ProtocolMapperTypeRepresentation>>());
|
||||
for (ProviderFactory p : session.getKeycloakSessionFactory().getProviderFactories(ProtocolMapper.class)) {
|
||||
ProtocolMapper mapper = (ProtocolMapper)p;
|
||||
List<ProtocolMapperTypeRepresentation> types = info.protocolMapperTypes.get(mapper.getProtocol());
|
||||
List<ProtocolMapperTypeRepresentation> types = info.getProtocolMapperTypes().get(mapper.getProtocol());
|
||||
if (types == null) {
|
||||
types = new LinkedList<ProtocolMapperTypeRepresentation>();
|
||||
info.protocolMapperTypes.put(mapper.getProtocol(), types);
|
||||
info.getProtocolMapperTypes().put(mapper.getProtocol(), types);
|
||||
}
|
||||
ProtocolMapperTypeRepresentation rep = new ProtocolMapperTypeRepresentation();
|
||||
rep.setId(mapper.getId());
|
||||
|
@ -166,136 +153,25 @@ public class ServerInfoAdminResource {
|
|||
}
|
||||
|
||||
private void setBuiltinProtocolMappers(ServerInfoRepresentation info) {
|
||||
info.builtinProtocolMappers = new HashMap<>();
|
||||
info.setBuiltinProtocolMappers(new HashMap<String, List<ProtocolMapperRepresentation>>());
|
||||
for (ProviderFactory p : session.getKeycloakSessionFactory().getProviderFactories(LoginProtocol.class)) {
|
||||
LoginProtocolFactory factory = (LoginProtocolFactory)p;
|
||||
List<ProtocolMapperRepresentation> mappers = new LinkedList<>();
|
||||
for (ProtocolMapperModel mapper : factory.getBuiltinMappers()) {
|
||||
mappers.add(ModelToRepresentation.toRepresentation(mapper));
|
||||
}
|
||||
info.builtinProtocolMappers.put(p.getId(), mappers);
|
||||
info.getBuiltinProtocolMappers().put(p.getId(), mappers);
|
||||
}
|
||||
}
|
||||
|
||||
private void setClientImporters(ServerInfoRepresentation info) {
|
||||
info.clientImporters = new LinkedList<Map<String, String>>();
|
||||
info.setClientImporters(new LinkedList<Map<String, String>>());
|
||||
for (ProviderFactory p : session.getKeycloakSessionFactory().getProviderFactories(ClientImporter.class)) {
|
||||
ClientImporterFactory factory = (ClientImporterFactory)p;
|
||||
Map<String, String> data = new HashMap<String, String>();
|
||||
data.put("id", factory.getId());
|
||||
data.put("name", factory.getDisplayName());
|
||||
info.clientImporters.add(data);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ServerInfoRepresentation {
|
||||
|
||||
private String version;
|
||||
|
||||
private String serverTime;
|
||||
|
||||
private Map<String, List<String>> themes;
|
||||
|
||||
private List<Map<String, String>> socialProviders;
|
||||
public List<Map<String, String>> identityProviders;
|
||||
private List<String> protocols;
|
||||
private List<Map<String, String>> clientImporters;
|
||||
|
||||
private List<SpiInfoRepresentation> providers;
|
||||
|
||||
private List<String> eventListeners;
|
||||
private Map<String, List<ProtocolMapperTypeRepresentation>> protocolMapperTypes;
|
||||
private Map<String, List<ProtocolMapperRepresentation>> builtinProtocolMappers;
|
||||
|
||||
private Map<String, List<String>> enums;
|
||||
|
||||
public ServerInfoRepresentation() {
|
||||
}
|
||||
|
||||
public String getServerTime() {
|
||||
return serverTime;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getThemes() {
|
||||
return themes;
|
||||
}
|
||||
|
||||
public List<Map<String, String>> getSocialProviders() {
|
||||
return socialProviders;
|
||||
}
|
||||
|
||||
public List<Map<String, String>> getIdentityProviders() {
|
||||
return this.identityProviders;
|
||||
}
|
||||
|
||||
public List<String> getEventListeners() {
|
||||
return eventListeners;
|
||||
}
|
||||
|
||||
public List<String> getProtocols() {
|
||||
return protocols;
|
||||
}
|
||||
|
||||
public List<Map<String, String>> getClientImporters() {
|
||||
return clientImporters;
|
||||
}
|
||||
|
||||
public List<SpiInfoRepresentation> getProviders() {
|
||||
return providers;
|
||||
}
|
||||
|
||||
public Map<String, List<ProtocolMapperTypeRepresentation>> getProtocolMapperTypes() {
|
||||
return protocolMapperTypes;
|
||||
}
|
||||
|
||||
public Map<String, List<ProtocolMapperRepresentation>> getBuiltinProtocolMappers() {
|
||||
return builtinProtocolMappers;
|
||||
}
|
||||
|
||||
public void setBuiltinProtocolMappers(Map<String, List<ProtocolMapperRepresentation>> builtinProtocolMappers) {
|
||||
this.builtinProtocolMappers = builtinProtocolMappers;
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getEnums() {
|
||||
return enums;
|
||||
}
|
||||
|
||||
public void setEnums(Map<String, List<String>> enums) {
|
||||
this.enums = enums;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SpiInfoRepresentation {
|
||||
private String name;
|
||||
private boolean internal;
|
||||
private Set<String> implementations;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public boolean isInternal() {
|
||||
return internal;
|
||||
}
|
||||
|
||||
public void setInternal(boolean internal) {
|
||||
this.internal = internal;
|
||||
}
|
||||
|
||||
public Set<String> getImplementations() {
|
||||
return implementations;
|
||||
}
|
||||
|
||||
public void setImplementations(Set<String> implementations) {
|
||||
this.implementations = implementations;
|
||||
info.getClientImporters().add(data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
package org.keycloak.services.resources.admin.info;
|
||||
|
||||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||
import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class ServerInfoRepresentation {
|
||||
|
||||
private SystemInfoRepresentation systemInfo;
|
||||
private MemoryInfoRepresentation memoryInfo;
|
||||
|
||||
private Map<String, List<String>> themes;
|
||||
|
||||
private List<Map<String, String>> socialProviders;
|
||||
private List<Map<String, String>> identityProviders;
|
||||
private List<Map<String, String>> clientImporters;
|
||||
|
||||
private Map<String, SpiInfoRepresentation> providers;
|
||||
|
||||
private Map<String, List<ProtocolMapperTypeRepresentation>> protocolMapperTypes;
|
||||
private Map<String, List<ProtocolMapperRepresentation>> builtinProtocolMappers;
|
||||
|
||||
private Map<String, List<String>> enums;
|
||||
|
||||
public SystemInfoRepresentation getSystemInfo() {
|
||||
return systemInfo;
|
||||
}
|
||||
|
||||
public void setSystemInfo(SystemInfoRepresentation systemInfo) {
|
||||
this.systemInfo = systemInfo;
|
||||
}
|
||||
|
||||
public MemoryInfoRepresentation getMemoryInfo() {
|
||||
return memoryInfo;
|
||||
}
|
||||
|
||||
public void setMemoryInfo(MemoryInfoRepresentation memoryInfo) {
|
||||
this.memoryInfo = memoryInfo;
|
||||
}
|
||||
public Map<String, List<String>> getThemes() {
|
||||
return themes;
|
||||
}
|
||||
|
||||
public void setThemes(Map<String, List<String>> themes) {
|
||||
this.themes = themes;
|
||||
}
|
||||
|
||||
public List<Map<String, String>> getSocialProviders() {
|
||||
return socialProviders;
|
||||
}
|
||||
|
||||
public void setSocialProviders(List<Map<String, String>> socialProviders) {
|
||||
this.socialProviders = socialProviders;
|
||||
}
|
||||
|
||||
public List<Map<String, String>> getIdentityProviders() {
|
||||
return identityProviders;
|
||||
}
|
||||
|
||||
public void setIdentityProviders(List<Map<String, String>> identityProviders) {
|
||||
this.identityProviders = identityProviders;
|
||||
}
|
||||
|
||||
public List<Map<String, String>> getClientImporters() {
|
||||
return clientImporters;
|
||||
}
|
||||
|
||||
public void setClientImporters(List<Map<String, String>> clientImporters) {
|
||||
this.clientImporters = clientImporters;
|
||||
}
|
||||
|
||||
public Map<String, SpiInfoRepresentation> getProviders() {
|
||||
return providers;
|
||||
}
|
||||
|
||||
public void setProviders(Map<String, SpiInfoRepresentation> providers) {
|
||||
this.providers = providers;
|
||||
}
|
||||
|
||||
public Map<String, List<ProtocolMapperTypeRepresentation>> getProtocolMapperTypes() {
|
||||
return protocolMapperTypes;
|
||||
}
|
||||
|
||||
public void setProtocolMapperTypes(Map<String, List<ProtocolMapperTypeRepresentation>> protocolMapperTypes) {
|
||||
this.protocolMapperTypes = protocolMapperTypes;
|
||||
}
|
||||
|
||||
public Map<String, List<ProtocolMapperRepresentation>> getBuiltinProtocolMappers() {
|
||||
return builtinProtocolMappers;
|
||||
}
|
||||
|
||||
public void setBuiltinProtocolMappers(Map<String, List<ProtocolMapperRepresentation>> builtinProtocolMappers) {
|
||||
this.builtinProtocolMappers = builtinProtocolMappers;
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getEnums() {
|
||||
return enums;
|
||||
}
|
||||
|
||||
public void setEnums(Map<String, List<String>> enums) {
|
||||
this.enums = enums;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package org.keycloak.services.resources.admin.info;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class SpiInfoRepresentation {
|
||||
|
||||
private boolean internal;
|
||||
private boolean systemInfo;
|
||||
|
||||
private Map<String, ProviderRepresentation> providers;
|
||||
|
||||
public boolean isInternal() {
|
||||
return internal;
|
||||
}
|
||||
|
||||
public void setInternal(boolean internal) {
|
||||
this.internal = internal;
|
||||
}
|
||||
|
||||
public boolean isSystemInfo() {
|
||||
return systemInfo;
|
||||
}
|
||||
|
||||
public void setSystemInfo(boolean systemInfo) {
|
||||
this.systemInfo = systemInfo;
|
||||
}
|
||||
|
||||
public Map<String, ProviderRepresentation> getProviders() {
|
||||
return providers;
|
||||
}
|
||||
|
||||
public void setProviders(Map<String, ProviderRepresentation> providers) {
|
||||
this.providers = providers;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
package org.keycloak.services.resources.admin.info;
|
||||
|
||||
import org.keycloak.Version;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
public class SystemInfoRepresentation {
|
||||
|
||||
private String version;
|
||||
private String serverTime;
|
||||
private String uptime;
|
||||
private long uptimeMillis;
|
||||
private String javaVersion;
|
||||
private String javaVendor;
|
||||
private String javaVm;
|
||||
private String javaVmVersion;
|
||||
private String javaRuntime;
|
||||
private String javaHome;
|
||||
private String osName;
|
||||
private String osArchitecture;
|
||||
private String osVersion;
|
||||
private String fileEncoding;
|
||||
private String userName;
|
||||
private String userDir;
|
||||
private String userTimezone;
|
||||
private String userLocale;
|
||||
|
||||
public static SystemInfoRepresentation create(KeycloakSession session) {
|
||||
SystemInfoRepresentation rep = new SystemInfoRepresentation();
|
||||
rep.version = Version.VERSION;
|
||||
rep.serverTime = new Date().toString();
|
||||
rep.uptimeMillis = System.currentTimeMillis() - session.getKeycloakSessionFactory().getServerStartupTimestamp();
|
||||
rep.uptime = formatUptime(rep.uptimeMillis);
|
||||
rep.javaVersion = System.getProperty("java.version");
|
||||
rep.javaVendor = System.getProperty("java.vendor");
|
||||
rep.javaVm = System.getProperty("java.vm.name");
|
||||
rep.javaVmVersion = System.getProperty("java.vm.version");
|
||||
rep.javaRuntime = System.getProperty("java.runtime.name");
|
||||
rep.javaHome = System.getProperty("java.home");
|
||||
rep.osName = System.getProperty("os.name");
|
||||
rep.osArchitecture = System.getProperty("os.arch");
|
||||
rep.osVersion = System.getProperty("os.version");
|
||||
rep.fileEncoding = System.getProperty("file.encoding");
|
||||
rep.userName = System.getProperty("user.name");
|
||||
rep.userDir = System.getProperty("user.dir");
|
||||
rep.userTimezone = System.getProperty("user.timezone");
|
||||
rep.userLocale = (new Locale(System.getProperty("user.country"), System.getProperty("user.language")).toString());
|
||||
return rep;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getServerTime() {
|
||||
return serverTime;
|
||||
}
|
||||
|
||||
public void setServerTime(String serverTime) {
|
||||
this.serverTime = serverTime;
|
||||
}
|
||||
|
||||
public String getUptime() {
|
||||
return uptime;
|
||||
}
|
||||
|
||||
public void setUptime(String uptime) {
|
||||
this.uptime = uptime;
|
||||
}
|
||||
|
||||
public long getUptimeMillis() {
|
||||
return uptimeMillis;
|
||||
}
|
||||
|
||||
public void setUptimeMillis(long uptimeMillis) {
|
||||
this.uptimeMillis = uptimeMillis;
|
||||
}
|
||||
|
||||
public String getJavaVersion() {
|
||||
return javaVersion;
|
||||
}
|
||||
|
||||
public void setJavaVersion(String javaVersion) {
|
||||
this.javaVersion = javaVersion;
|
||||
}
|
||||
|
||||
public String getJavaVendor() {
|
||||
return javaVendor;
|
||||
}
|
||||
|
||||
public void setJavaVendor(String javaVendor) {
|
||||
this.javaVendor = javaVendor;
|
||||
}
|
||||
|
||||
public String getJavaVm() {
|
||||
return javaVm;
|
||||
}
|
||||
|
||||
public void setJavaVm(String javaVm) {
|
||||
this.javaVm = javaVm;
|
||||
}
|
||||
|
||||
public String getJavaVmVersion() {
|
||||
return javaVmVersion;
|
||||
}
|
||||
|
||||
public void setJavaVmVersion(String javaVmVersion) {
|
||||
this.javaVmVersion = javaVmVersion;
|
||||
}
|
||||
|
||||
public String getJavaRuntime() {
|
||||
return javaRuntime;
|
||||
}
|
||||
|
||||
public void setJavaRuntime(String javaRuntime) {
|
||||
this.javaRuntime = javaRuntime;
|
||||
}
|
||||
|
||||
public String getJavaHome() {
|
||||
return javaHome;
|
||||
}
|
||||
|
||||
public void setJavaHome(String javaHome) {
|
||||
this.javaHome = javaHome;
|
||||
}
|
||||
|
||||
public String getOsName() {
|
||||
return osName;
|
||||
}
|
||||
|
||||
public void setOsName(String osName) {
|
||||
this.osName = osName;
|
||||
}
|
||||
|
||||
public String getOsArchitecture() {
|
||||
return osArchitecture;
|
||||
}
|
||||
|
||||
public void setOsArchitecture(String osArchitecture) {
|
||||
this.osArchitecture = osArchitecture;
|
||||
}
|
||||
|
||||
public String getOsVersion() {
|
||||
return osVersion;
|
||||
}
|
||||
|
||||
public void setOsVersion(String osVersion) {
|
||||
this.osVersion = osVersion;
|
||||
}
|
||||
|
||||
public String getFileEncoding() {
|
||||
return fileEncoding;
|
||||
}
|
||||
|
||||
public void setFileEncoding(String fileEncoding) {
|
||||
this.fileEncoding = fileEncoding;
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
public void setUserName(String userName) {
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
public String getUserDir() {
|
||||
return userDir;
|
||||
}
|
||||
|
||||
public void setUserDir(String userDir) {
|
||||
this.userDir = userDir;
|
||||
}
|
||||
|
||||
public String getUserTimezone() {
|
||||
return userTimezone;
|
||||
}
|
||||
|
||||
public void setUserTimezone(String userTimezone) {
|
||||
this.userTimezone = userTimezone;
|
||||
}
|
||||
|
||||
public String getUserLocale() {
|
||||
return userLocale;
|
||||
}
|
||||
|
||||
public void setUserLocale(String userLocale) {
|
||||
this.userLocale = userLocale;
|
||||
}
|
||||
|
||||
private static String formatUptime(long uptime) {
|
||||
long diffInSeconds = uptime / 1000;
|
||||
long diff[] = new long[]{0, 0, 0, 0}; // sec
|
||||
diff[3] = (diffInSeconds >= 60 ? diffInSeconds % 60 : diffInSeconds); // min
|
||||
diff[2] = (diffInSeconds = (diffInSeconds / 60)) >= 60 ? diffInSeconds % 60 : diffInSeconds; // hours
|
||||
diff[1] = (diffInSeconds = (diffInSeconds / 60)) >= 24 ? diffInSeconds % 24 : diffInSeconds; // days
|
||||
diff[0] = (diffInSeconds = (diffInSeconds / 24));
|
||||
|
||||
return String.format(
|
||||
"%d day%s, %d hour%s, %d minute%s, %d second%s",
|
||||
diff[0],
|
||||
diff[0] != 1 ? "s" : "",
|
||||
diff[1],
|
||||
diff[1] != 1 ? "s" : "",
|
||||
diff[2],
|
||||
diff[2] != 1 ? "s" : "",
|
||||
diff[3],
|
||||
diff[3] != 1 ? "s" : "");
|
||||
}
|
||||
|
||||
}
|
|
@ -25,6 +25,7 @@ import org.junit.Assert;
|
|||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.Version;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.Constants;
|
||||
|
@ -55,6 +56,7 @@ import javax.ws.rs.core.UriBuilder;
|
|||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
@ -295,4 +297,62 @@ public class AdminAPITest {
|
|||
testCreateRealm("/admin-test/testrealm.json");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerInfo() {
|
||||
|
||||
String token = createToken();
|
||||
final String authHeader = "Bearer " + token;
|
||||
ClientRequestFilter authFilter = new ClientRequestFilter() {
|
||||
@Override
|
||||
public void filter(ClientRequestContext requestContext) throws IOException {
|
||||
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader);
|
||||
}
|
||||
};
|
||||
Client client = ClientBuilder.newBuilder().register(authFilter).build();
|
||||
UriBuilder authBase = UriBuilder.fromUri("http://localhost:8081/auth");
|
||||
WebTarget target = client.target(AdminRoot.adminBaseUrl(authBase).path("serverinfo"));
|
||||
|
||||
Map<?, ?> response = target.request().accept("application/json").get(Map.class);
|
||||
|
||||
Assert.assertNotNull(response);
|
||||
Assert.assertEquals(Version.VERSION, response.get("version"));
|
||||
Assert.assertNotNull(response.get("serverTime"));
|
||||
Assert.assertNotNull(response.get("providers"));
|
||||
Assert.assertNotNull(response.get("themes"));
|
||||
Assert.assertNotNull(response.get("enums"));
|
||||
|
||||
// System.out.println(response);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerInfoPage() {
|
||||
|
||||
String token = createToken();
|
||||
final String authHeader = "Bearer " + token;
|
||||
ClientRequestFilter authFilter = new ClientRequestFilter() {
|
||||
@Override
|
||||
public void filter(ClientRequestContext requestContext) throws IOException {
|
||||
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader);
|
||||
}
|
||||
};
|
||||
Client client = ClientBuilder.newBuilder().register(authFilter).build();
|
||||
UriBuilder authBase = UriBuilder.fromUri("http://localhost:8081/auth");
|
||||
WebTarget target = client.target(AdminRoot.adminBaseUrl(authBase).path("serverinfopage"));
|
||||
|
||||
Map<?, ?> response = target.request().accept("application/json").get(Map.class);
|
||||
|
||||
Assert.assertNotNull(response);
|
||||
Assert.assertEquals(Version.VERSION, response.get("version"));
|
||||
Assert.assertNotNull(response.get("serverTime"));
|
||||
Assert.assertNotNull(response.get("providers"));
|
||||
Assert.assertNotNull(response.get("serverStartupTime"));
|
||||
|
||||
Assert.assertNotNull(response.get("memoryInfo"));
|
||||
Assert.assertNotNull(response.get("systemInfo"));
|
||||
|
||||
// System.out.println(response);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -814,7 +814,7 @@ public abstract class AbstractIdentityProviderTest {
|
|||
|
||||
private void removeTestUsers() {
|
||||
RealmModel realm = getRealm();
|
||||
List<UserModel> users = this.session.users().getUsers(realm);
|
||||
List<UserModel> users = this.session.users().getUsers(realm, true);
|
||||
|
||||
for (UserModel user : users) {
|
||||
Set<FederatedIdentityModel> identities = this.session.users().getFederatedIdentities(user, realm);
|
||||
|
|
|
@ -288,14 +288,14 @@ public abstract class AbstractKerberosTest {
|
|||
RealmManager manager = new RealmManager(session);
|
||||
|
||||
RealmModel appRealm = manager.getRealm("test");
|
||||
List<UserModel> users = session.userStorage().getUsers(appRealm);
|
||||
List<UserModel> users = session.userStorage().getUsers(appRealm, true);
|
||||
for (UserModel user : users) {
|
||||
if (!user.getUsername().equals(AssertEvents.DEFAULT_USERNAME)) {
|
||||
session.userStorage().removeUser(appRealm, user);
|
||||
}
|
||||
}
|
||||
|
||||
Assert.assertEquals(1, session.userStorage().getUsers(appRealm).size());
|
||||
Assert.assertEquals(1, session.userStorage().getUsers(appRealm, true).size());
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, true);
|
||||
}
|
||||
|
|
|
@ -227,7 +227,7 @@ public class SyncProvidersTest {
|
|||
RealmModel testRealm = session.realms().getRealm("test");
|
||||
|
||||
// Remove all users from model
|
||||
for (UserModel user : session.userStorage().getUsers(testRealm)) {
|
||||
for (UserModel user : session.userStorage().getUsers(testRealm, true)) {
|
||||
session.userStorage().removeUser(testRealm, user);
|
||||
}
|
||||
|
||||
|
|
|
@ -433,7 +433,7 @@ public class AdapterTest extends AbstractModelTest {
|
|||
RealmModel otherRealm = adapter.createRealm("other");
|
||||
realmManager.getSession().users().addUser(otherRealm, "bburke");
|
||||
|
||||
Assert.assertEquals(1, realmManager.getSession().users().getUsers(otherRealm).size());
|
||||
Assert.assertEquals(1, realmManager.getSession().users().getUsers(otherRealm, false).size());
|
||||
Assert.assertEquals(1, realmManager.getSession().users().searchForUser("bu", otherRealm).size());
|
||||
}
|
||||
|
||||
|
|
|
@ -304,6 +304,14 @@ public class ImportTest extends AbstractModelTest {
|
|||
Assert.assertTrue(otherAppAdminConsent.isRoleGranted(realm.getRole("admin")));
|
||||
Assert.assertFalse(otherAppAdminConsent.isRoleGranted(application.getRole("app-admin")));
|
||||
Assert.assertTrue(otherAppAdminConsent.isProtocolMapperGranted(gssCredentialMapper));
|
||||
|
||||
// Test service accounts
|
||||
Assert.assertFalse(application.isServiceAccountsEnabled());
|
||||
Assert.assertTrue(otherApp.isServiceAccountsEnabled());
|
||||
Assert.assertNull(session.users().getUserByServiceAccountClient(application));
|
||||
UserModel linked = session.users().getUserByServiceAccountClient(otherApp);
|
||||
Assert.assertNotNull(linked);
|
||||
Assert.assertEquals("my-service-user", linked.getUsername());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -7,6 +7,7 @@ import org.keycloak.models.KeycloakSession;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserModel.RequiredAction;
|
||||
import org.keycloak.services.managers.ClientManager;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
|
@ -226,6 +227,61 @@ public class UserModelTest extends AbstractModelTest {
|
|||
Assert.assertEquals(0, users.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServiceAccountLink() throws Exception {
|
||||
RealmModel realm = realmManager.createRealm("original");
|
||||
ClientModel client = realm.addClient("foo");
|
||||
|
||||
UserModel user1 = session.users().addUser(realm, "user1");
|
||||
user1.setFirstName("John");
|
||||
user1.setLastName("Doe");
|
||||
|
||||
UserModel user2 = session.users().addUser(realm, "user2");
|
||||
user2.setFirstName("John");
|
||||
user2.setLastName("Doe");
|
||||
|
||||
// Search
|
||||
Assert.assertNull(session.users().getUserByServiceAccountClient(client));
|
||||
List<UserModel> users = session.users().searchForUser("John Doe", realm);
|
||||
Assert.assertEquals(2, users.size());
|
||||
Assert.assertTrue(users.contains(user1));
|
||||
Assert.assertTrue(users.contains(user2));
|
||||
|
||||
// Link service account
|
||||
user1.setServiceAccountClientLink(client.getId());
|
||||
|
||||
commit();
|
||||
|
||||
// Search and assert service account user not found
|
||||
realm = realmManager.getRealmByName("original");
|
||||
UserModel searched = session.users().getUserByServiceAccountClient(client);
|
||||
Assert.assertEquals(searched, user1);
|
||||
users = session.users().searchForUser("John Doe", realm);
|
||||
Assert.assertEquals(1, users.size());
|
||||
Assert.assertFalse(users.contains(user1));
|
||||
Assert.assertTrue(users.contains(user2));
|
||||
|
||||
users = session.users().getUsers(realm, false);
|
||||
Assert.assertEquals(1, users.size());
|
||||
Assert.assertFalse(users.contains(user1));
|
||||
Assert.assertTrue(users.contains(user2));
|
||||
|
||||
users = session.users().getUsers(realm, true);
|
||||
Assert.assertEquals(2, users.size());
|
||||
Assert.assertTrue(users.contains(user1));
|
||||
Assert.assertTrue(users.contains(user2));
|
||||
|
||||
Assert.assertEquals(2, session.users().getUsersCount(realm));
|
||||
|
||||
// Remove client
|
||||
new ClientManager(realmManager).removeClient(realm, client);
|
||||
commit();
|
||||
|
||||
// Assert service account removed as well
|
||||
realm = realmManager.getRealmByName("original");
|
||||
Assert.assertNull(session.users().getUserByUsername("user1", realm));
|
||||
}
|
||||
|
||||
public static void assertEquals(UserModel expected, UserModel actual) {
|
||||
Assert.assertEquals(expected.getUsername(), actual.getUsername());
|
||||
Assert.assertEquals(expected.getCreatedTimestamp(), actual.getCreatedTimestamp());
|
||||
|
|
|
@ -141,6 +141,11 @@
|
|||
"userName": "mySocialUser@gmail.com"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"username": "my-service-user",
|
||||
"enabled": true,
|
||||
"serviceAccountClientId": "OtherApp"
|
||||
}
|
||||
],
|
||||
"clients": [
|
||||
|
@ -158,6 +163,7 @@
|
|||
"clientId": "OtherApp",
|
||||
"name": "Other Application",
|
||||
"enabled": true,
|
||||
"serviceAccountsEnabled": true,
|
||||
"protocolMappers" : [
|
||||
{
|
||||
"name" : "gss delegation credential",
|
||||
|
|
Loading…
Reference in a new issue