Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
07d3b26722
62 changed files with 1943 additions and 599 deletions
|
@ -0,0 +1,35 @@
|
|||
package org.keycloak.representations.idm;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class OfflineClientSessionRepresentation {
|
||||
|
||||
private String clientSessionId;
|
||||
private String client; // clientId (not DB ID)
|
||||
private String data;
|
||||
|
||||
public String getClientSessionId() {
|
||||
return clientSessionId;
|
||||
}
|
||||
|
||||
public void setClientSessionId(String clientSessionId) {
|
||||
this.clientSessionId = clientSessionId;
|
||||
}
|
||||
|
||||
public String getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(String client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package org.keycloak.representations.idm;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class OfflineUserSessionRepresentation {
|
||||
|
||||
private String userSessionId;
|
||||
private String data;
|
||||
private List<OfflineClientSessionRepresentation> offlineClientSessions;
|
||||
|
||||
public String getUserSessionId() {
|
||||
return userSessionId;
|
||||
}
|
||||
|
||||
public void setUserSessionId(String userSessionId) {
|
||||
this.userSessionId = userSessionId;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public List<OfflineClientSessionRepresentation> getOfflineClientSessions() {
|
||||
return offlineClientSessions;
|
||||
}
|
||||
|
||||
public void setOfflineClientSessions(List<OfflineClientSessionRepresentation> offlineClientSessions) {
|
||||
this.offlineClientSessions = offlineClientSessions;
|
||||
}
|
||||
}
|
|
@ -36,6 +36,7 @@ public class UserRepresentation {
|
|||
protected List<String> realmRoles;
|
||||
protected Map<String, List<String>> clientRoles;
|
||||
protected List<UserConsentRepresentation> clientConsents;
|
||||
protected List<OfflineUserSessionRepresentation> offlineUserSessions;
|
||||
|
||||
@Deprecated
|
||||
protected Map<String, List<String>> applicationRoles;
|
||||
|
@ -218,4 +219,12 @@ public class UserRepresentation {
|
|||
public void setServiceAccountClientId(String serviceAccountClientId) {
|
||||
this.serviceAccountClientId = serviceAccountClientId;
|
||||
}
|
||||
|
||||
public List<OfflineUserSessionRepresentation> getOfflineUserSessions() {
|
||||
return offlineUserSessions;
|
||||
}
|
||||
|
||||
public void setOfflineUserSessions(List<OfflineUserSessionRepresentation> offlineUserSessions) {
|
||||
this.offlineUserSessions = offlineUserSessions;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.keycloak.util;
|
|||
import java.io.IOException;
|
||||
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
|
||||
/**
|
||||
|
@ -30,7 +31,7 @@ public class RefreshTokenUtil {
|
|||
|
||||
|
||||
/**
|
||||
* Return refresh token or offline otkne
|
||||
* Return refresh token or offline token
|
||||
*
|
||||
* @param decodedToken
|
||||
* @return
|
||||
|
@ -39,9 +40,9 @@ public class RefreshTokenUtil {
|
|||
return JsonSerialization.readValue(decodedToken, RefreshToken.class);
|
||||
}
|
||||
|
||||
private static RefreshToken getRefreshToken(String refreshToken) throws IOException {
|
||||
byte[] decodedToken = Base64Url.decode(refreshToken);
|
||||
return getRefreshToken(decodedToken);
|
||||
public static RefreshToken getRefreshToken(String refreshToken) throws IOException {
|
||||
byte[] encodedContent = new JWSInput(refreshToken).getContent();
|
||||
return getRefreshToken(encodedContent);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
version=${project.version}
|
||||
build-time=${timestamp}
|
||||
version=${product.version}
|
||||
build-time=${product.build-time}
|
|
@ -30,11 +30,6 @@
|
|||
<name>Keycloak Feature Pack: Server</name>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<properties>
|
||||
<product.name>Keycloak</product.name>
|
||||
<product.version>${project.version}</product.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<chapter id="timeouts">
|
||||
<chapter id="timeouts">
|
||||
<title>Cookie settings, Session Timeouts, and Token Lifespans</title>
|
||||
<para>
|
||||
Keycloak has a bunch of fine-grain settings to manage browser cookies, user login sessions, and token lifespans.
|
||||
|
@ -52,4 +52,38 @@
|
|||
back to your application as an authentnicated user. This value is relatively short and is usually measured in minutes.
|
||||
</para>
|
||||
</section>
|
||||
<section id="offline-access">
|
||||
<title>Offline Access</title>
|
||||
<para>
|
||||
The Offline access is the feature described in <ulink url="http://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess">OpenID Connect specification</ulink> .
|
||||
The idea is that during login, your client application will request Offline token instead of classic Refresh token.
|
||||
Then the application can save this offline token in the database and can use it anytime later even if user is logged out.
|
||||
This is useful for example if your application needs to do some "offline" actions on behalf of user even if user is not online. For example
|
||||
periodic backup of some data every night etc.
|
||||
</para>
|
||||
<para>
|
||||
Your application is responsible for persist the offline token in some storage (usually database) and then use it to
|
||||
manually retrieve new access token from Keycloak server.
|
||||
</para>
|
||||
<para>
|
||||
The difference between classic Refresh token and Offline token is, that offline token will never expire and is not subject of <literal>SSO Session Idle timeout</literal> .
|
||||
The offline token is valid even after user logout or server restart. User can revoke the offline tokens in Account management UI. The admin
|
||||
user can revoke offline tokens for individual users in admin console (The <literal>Consent</literal> tab of particular user) and he can
|
||||
see all the offline tokens of all users for particular client application in the settings of the client. Revoking of all offline tokens for particular
|
||||
client is possible by set <literal>notBefore</literal> policy for the client.
|
||||
</para>
|
||||
<para>
|
||||
For requesting the offline token, user needs to be in realm role <literal>offline_access</literal> and client needs to have
|
||||
scope for this role. If client has <literal>Full scope allowed</literal>, the scope is granted by default. Also users are automatically
|
||||
members of the role as it's the default role.
|
||||
</para>
|
||||
<para>
|
||||
The client can request offline token by adding parameter <literal>scope=offline_access</literal>
|
||||
when sending authorization request to Keycloak. The adapter automatically adds this parameter when you use it to access secured
|
||||
URL of your application (ie. http://localhost:8080/customer-portal/secured?scope=offline_access ).
|
||||
The <link linkend='direct-access-grants'>Direct Access Grant</link> or <link linkend="service-accounts">Service account</link> flows also support
|
||||
offline tokens if you include <literal>scope=offline_access</literal> in the body of the authentication request. For more details,
|
||||
see the <literal>offline-access-app</literal> example from Keycloak demo.
|
||||
</para>
|
||||
</section>
|
||||
</chapter>
|
|
@ -216,7 +216,19 @@ An example for retrieve service account dedicated to the Client Application itse
|
|||
|
||||
[http://localhost:8080/service-account-portal](http://localhost:8080/service-account-portal)
|
||||
|
||||
Client authentication is done with OAuth2 Client Credentials Grant in out-of-bound request (Not Keycloak login screen displayed)
|
||||
Client authentication is done with OAuth2 Client Credentials Grant in out-of-bound request (Not Keycloak login screen displayed) .
|
||||
|
||||
The example also shows different methods of client authentication. There is ProductSAClientSecretServlet using traditional authentication with clientId and client_secret,
|
||||
but there is also ProductSAClientSignedJWTServlet using client authentication with JWT signed by client private key.
|
||||
|
||||
Step 11: Offline Access Example
|
||||
===============================
|
||||
An example for retrieve offline token, which is then saved to the database and can be used by application anytime later. Offline token
|
||||
is valid even if user is already logged out from SSO. Server restart also won't invalidate offline token. Offline token can be revoked by the user in
|
||||
account management or by admin in admin console.
|
||||
|
||||
[http://localhost:8080/offline-access-portal](http://localhost:8080/offline-access-portal)
|
||||
|
||||
|
||||
Admin Console
|
||||
==========================
|
||||
|
|
0
examples/demo-template/customer-app/src/main/webapp/index.html
Executable file → Normal file
0
examples/demo-template/customer-app/src/main/webapp/index.html
Executable file → Normal file
73
examples/demo-template/offline-access-app/pom.xml
Normal file
73
examples/demo-template/offline-access-app/pom.xml
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-examples-demo-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.6.0.Final-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.keycloak.example.demo</groupId>
|
||||
<artifactId>offline-access-example</artifactId>
|
||||
<packaging>war</packaging>
|
||||
<name>Offline Access Portal</name>
|
||||
<description/>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jboss</id>
|
||||
<name>jboss repo</name>
|
||||
<url>http://repository.jboss.org/nexus/content/groups/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-spi</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>offline-access-portal</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jboss.as.plugins</groupId>
|
||||
<artifactId>jboss-as-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>false</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.wildfly.plugins</groupId>
|
||||
<artifactId>wildfly-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>false</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,226 @@
|
|||
package org.keycloak.example;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import javax.security.cert.X509Certificate;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.keycloak.adapters.HttpFacade;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.ServerRequest;
|
||||
import org.keycloak.constants.ServiceUrlConstants;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.util.KeycloakUriBuilder;
|
||||
import org.keycloak.util.RefreshTokenUtil;
|
||||
import org.keycloak.util.StreamUtil;
|
||||
import org.keycloak.util.Time;
|
||||
import org.keycloak.util.UriUtils;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class OfflineAccessPortalServlet extends HttpServlet {
|
||||
|
||||
|
||||
@Override
|
||||
public void init() throws ServletException {
|
||||
getServletContext().setAttribute(HttpClient.class.getName(), new DefaultHttpClient());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
getHttpClient().getConnectionManager().shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
|
||||
if (req.getRequestURI().endsWith("/login")) {
|
||||
storeToken(req);
|
||||
req.getRequestDispatcher("/WEB-INF/pages/loginCallback.jsp").forward(req, resp);
|
||||
return;
|
||||
}
|
||||
|
||||
String refreshToken = RefreshTokenDAO.loadToken();
|
||||
String refreshTokenInfo;
|
||||
boolean savedTokenAvailable;
|
||||
if (refreshToken == null) {
|
||||
refreshTokenInfo = "No token saved in database. Please login first";
|
||||
savedTokenAvailable = false;
|
||||
} else {
|
||||
RefreshToken refreshTokenDecoded = RefreshTokenUtil.getRefreshToken(refreshToken);
|
||||
String exp = (refreshTokenDecoded.getExpiration() == 0) ? "NEVER" : Time.toDate(refreshTokenDecoded.getExpiration()).toString();
|
||||
refreshTokenInfo = String.format("<p>Type: %s</p><p>ID: %s</p><p>Expires: %s</p>", refreshTokenDecoded.getType(), refreshTokenDecoded.getId(), exp);
|
||||
savedTokenAvailable = true;
|
||||
}
|
||||
req.setAttribute("tokenInfo", refreshTokenInfo);
|
||||
req.setAttribute("savedTokenAvailable", savedTokenAvailable);
|
||||
|
||||
String customers;
|
||||
if (req.getRequestURI().endsWith("/loadCustomers")) {
|
||||
customers = loadCustomers(req, refreshToken);
|
||||
} else {
|
||||
customers = "";
|
||||
}
|
||||
req.setAttribute("customers", customers);
|
||||
|
||||
req.getRequestDispatcher("/WEB-INF/pages/view.jsp").forward(req, resp);
|
||||
}
|
||||
|
||||
private void storeToken(HttpServletRequest req) throws IOException {
|
||||
RefreshableKeycloakSecurityContext ctx = (RefreshableKeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName());
|
||||
String refreshToken = ctx.getRefreshToken();
|
||||
|
||||
RefreshTokenDAO.saveToken(refreshToken);
|
||||
|
||||
RefreshToken refreshTokenDecoded = RefreshTokenUtil.getRefreshToken(refreshToken);
|
||||
Boolean isOfflineToken = refreshTokenDecoded.getType().equals(RefreshTokenUtil.TOKEN_TYPE_OFFLINE);
|
||||
req.setAttribute("isOfflineToken", isOfflineToken);
|
||||
}
|
||||
|
||||
private String loadCustomers(HttpServletRequest req, String refreshToken) throws ServletException, IOException {
|
||||
// Retrieve accessToken first with usage of refresh (offline) token from DB
|
||||
String accessToken = null;
|
||||
try {
|
||||
KeycloakDeployment deployment = getDeployment(req);
|
||||
AccessTokenResponse response = ServerRequest.invokeRefresh(deployment, refreshToken);
|
||||
accessToken = response.getToken();
|
||||
} catch (ServerRequest.HttpFailure failure) {
|
||||
return "Failed to refresh token. Status from auth-server request: " + failure.getStatus() + ", Error: " + failure.getError();
|
||||
}
|
||||
|
||||
// Load customers now
|
||||
HttpGet get = new HttpGet(UriUtils.getOrigin(req.getRequestURL().toString()) + "/database/customers");
|
||||
get.addHeader("Authorization", "Bearer " + accessToken);
|
||||
|
||||
HttpResponse response = getHttpClient().execute(get);
|
||||
InputStream is = response.getEntity().getContent();
|
||||
try {
|
||||
if (response.getStatusLine().getStatusCode() != 200) {
|
||||
return "Error when loading customer. Status: " + response.getStatusLine().getStatusCode() + ", error: " + StreamUtil.readString(is);
|
||||
} else {
|
||||
List<String> list = JsonSerialization.readValue(is, TypedList.class);
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (String customer : list) {
|
||||
result.append(customer + "<br />");
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
} finally {
|
||||
is.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private KeycloakDeployment getDeployment(HttpServletRequest servletRequest) throws ServletException {
|
||||
// The facade object is needed just if you have relative "auth-server-url" in keycloak.json. Otherwise you can call deploymentContext.resolveDeployment(null)
|
||||
HttpFacade facade = getFacade(servletRequest);
|
||||
|
||||
AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext) getServletContext().getAttribute(AdapterDeploymentContext.class.getName());
|
||||
if (deploymentContext == null) {
|
||||
throw new ServletException("AdapterDeploymentContext not set");
|
||||
}
|
||||
return deploymentContext.resolveDeployment(facade);
|
||||
}
|
||||
|
||||
// TODO: Merge with facade in ServletOAuthClient and move to some common servlet adapter
|
||||
private HttpFacade getFacade(final HttpServletRequest servletRequest) {
|
||||
return new HttpFacade() {
|
||||
|
||||
@Override
|
||||
public Request getRequest() {
|
||||
return new Request() {
|
||||
|
||||
@Override
|
||||
public String getMethod() {
|
||||
return servletRequest.getMethod();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getURI() {
|
||||
return servletRequest.getRequestURL().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure() {
|
||||
return servletRequest.isSecure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryParamValue(String param) {
|
||||
return servletRequest.getParameter(param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstParam(String param) {
|
||||
return servletRequest.getParameter(param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cookie getCookie(String cookieName) {
|
||||
// not needed
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name) {
|
||||
return servletRequest.getHeader(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getHeaders(String name) {
|
||||
// not needed
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
try {
|
||||
return servletRequest.getInputStream();
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteAddr() {
|
||||
return servletRequest.getRemoteAddr();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response getResponse() {
|
||||
throw new IllegalStateException("Not yet implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getCertificateChain() {
|
||||
throw new IllegalStateException("Not yet implemented");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private HttpClient getHttpClient() {
|
||||
return (HttpClient) getServletContext().getAttribute(HttpClient.class.getName());
|
||||
}
|
||||
|
||||
static class TypedList extends ArrayList<String> {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package org.keycloak.example;
|
||||
|
||||
import org.keycloak.constants.ServiceUrlConstants;
|
||||
import org.keycloak.util.KeycloakUriBuilder;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class OfflineExampleUris {
|
||||
|
||||
|
||||
public static final String LOGIN_CLASSIC = "/offline-access-portal/app/login";
|
||||
|
||||
|
||||
public static final String LOGIN_WITH_OFFLINE_TOKEN = "/offline-access-portal/app/login?scope=offline_access";
|
||||
|
||||
|
||||
public static final String LOAD_CUSTOMERS = "/offline-access-portal/app/loadCustomers";
|
||||
|
||||
|
||||
public static final String ACCOUNT_MGMT = KeycloakUriBuilder.fromUri("/auth").path(ServiceUrlConstants.ACCOUNT_SERVICE_PATH + "/applications")
|
||||
.queryParam("referrer", "offline-access-portal").build("demo").toString();
|
||||
|
||||
|
||||
public static final String LOGOUT = KeycloakUriBuilder.fromUri("/auth").path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
|
||||
.queryParam("redirect_uri", "/offline-access-portal").build("demo").toString();
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package org.keycloak.example;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import org.keycloak.util.StreamUtil;
|
||||
|
||||
/**
|
||||
* Very simple DAO, which stores/loads just one token per whole application into file in tmp directory. Useful just for example purposes.
|
||||
* In real environment, token should be stored in database.
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class RefreshTokenDAO {
|
||||
|
||||
public static final String FILE = System.getProperty("java.io.tmpdir") + "/offline-access-portal";
|
||||
|
||||
public static void saveToken(final String token) throws IOException {
|
||||
PrintWriter writer = null;
|
||||
try {
|
||||
writer = new PrintWriter(new BufferedWriter(new FileWriter(FILE)));
|
||||
writer.print(token);
|
||||
} finally {
|
||||
if (writer != null) {
|
||||
writer.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String loadToken() throws IOException {
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(FILE);
|
||||
return StreamUtil.readString(fis);
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
return null;
|
||||
} finally {
|
||||
if (fis != null) {
|
||||
fis.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<jboss-deployment-structure>
|
||||
<deployment>
|
||||
<dependencies>
|
||||
<!-- the Demo code uses classes in these modules. These are optional to import if you are not using
|
||||
Apache Http Client or the HttpClientBuilder that comes with the adapter core -->
|
||||
<module name="org.apache.httpcomponents"/>
|
||||
<module name="org.keycloak.keycloak-adapter-spi"/>
|
||||
</dependencies>
|
||||
</deployment>
|
||||
</jboss-deployment-structure>
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"realm": "demo",
|
||||
"resource": "offline-access-portal",
|
||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
"auth-server-url": "/auth",
|
||||
"ssl-required" : "external",
|
||||
"credentials": {
|
||||
"secret": "password"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
|
||||
pageEncoding="ISO-8859-1" %>
|
||||
<%@ page session="false" %>
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Offline Access Example</title>
|
||||
</head>
|
||||
<body bgcolor="#ffffff">
|
||||
<h1>Offline Access Example</h1>
|
||||
|
||||
<hr />
|
||||
|
||||
<p>
|
||||
Login finished and refresh token saved successfully.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<div style="background-color: #ddd; border: 1px solid #ccc; padding: 10px;">
|
||||
<% if ((Boolean) request.getAttribute("isOfflineToken")) { %>
|
||||
Token type <b>is</b> offline token! You will be able to load customers even after logout or server restart. Offline token can be revoked in account management or by admin in admin console.
|
||||
<% } else { %>
|
||||
Token <b>is not</b> offline token! Once you logout or restart server, token won't be valid anymore and you won't be able to load customers.
|
||||
<% } %>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="/offline-access-portal/app">Back to home page</a>
|
||||
</p>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,45 @@
|
|||
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
|
||||
pageEncoding="ISO-8859-1" %>
|
||||
<%@ page import="org.keycloak.example.OfflineExampleUris" %>
|
||||
<%@ page session="false" %>
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Offline Access Example</title>
|
||||
</head>
|
||||
<body bgcolor="#ffffff">
|
||||
<h1>Offline Access Example</h1>
|
||||
|
||||
<hr />
|
||||
|
||||
<% if (request.getRemoteUser() == null) { %>
|
||||
<a href="<%= OfflineExampleUris.LOGIN_CLASSIC %>">Login classic</a> |
|
||||
<a href="<%= OfflineExampleUris.LOGIN_WITH_OFFLINE_TOKEN %>">Login with offline access</a> |
|
||||
<% } else { %>
|
||||
<a href='<%= OfflineExampleUris.LOGOUT %>'>Logout</a> |
|
||||
<% } %>
|
||||
|
||||
<a href='<%= OfflineExampleUris.ACCOUNT_MGMT %>'>Account management</a> |
|
||||
|
||||
<% if ((Boolean) request.getAttribute("savedTokenAvailable")) { %>
|
||||
<a href="<%= OfflineExampleUris.LOAD_CUSTOMERS %>">Load customers with saved token</a> |
|
||||
<% } %>
|
||||
|
||||
<hr />
|
||||
|
||||
<h2>Saved Refresh Token Info</h2>
|
||||
<div style="background-color: #ddd; border: 1px solid #ccc; padding: 10px;">
|
||||
<%= request.getAttribute("tokenInfo") %>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<h2>Customers</h2>
|
||||
<div style="background-color: #ddd; border: 1px solid #ccc; padding: 10px;">
|
||||
<%= request.getAttribute("customers") %>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
||||
version="3.0">
|
||||
|
||||
<module-name>offline-access-portal</module-name>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>OfflineAccessPortalServle</servlet-name>
|
||||
<servlet-class>org.keycloak.example.OfflineAccessPortalServlet</servlet-class>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>OfflineAccessPortalServle</servlet-name>
|
||||
<url-pattern>/app/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<web-resource-name>User</web-resource-name>
|
||||
<url-pattern>/app/login/*</url-pattern>
|
||||
</web-resource-collection>
|
||||
<auth-constraint>
|
||||
<role-name>user</role-name>
|
||||
</auth-constraint>
|
||||
</security-constraint>
|
||||
|
||||
<!--
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</web-resource-collection>
|
||||
<user-data-constraint>
|
||||
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
|
||||
</user-data-constraint>
|
||||
</security-constraint> -->
|
||||
|
||||
<login-config>
|
||||
<auth-method>KEYCLOAK</auth-method>
|
||||
<realm-name>demo</realm-name>
|
||||
</login-config>
|
||||
|
||||
<security-role>
|
||||
<role-name>admin</role-name>
|
||||
</security-role>
|
||||
<security-role>
|
||||
<role-name>user</role-name>
|
||||
</security-role>
|
||||
</web-app>
|
|
@ -0,0 +1,3 @@
|
|||
<script>
|
||||
window.location = "/offline-access-portal/app";
|
||||
</script>
|
|
@ -37,6 +37,7 @@
|
|||
<module>third-party</module>
|
||||
<module>third-party-cdi</module>
|
||||
<module>service-account</module>
|
||||
<module>offline-access-app</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package org.keycloak.example;
|
||||
|
||||
/**
|
||||
* Client authentication based on JWT signed by client private key .
|
||||
* See Keycloak documentation and <a href="https://tools.ietf.org/html/rfc7519">specs</a> for more details.
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ProductSAClientSignedJWTServlet extends ProductServiceAccountServlet {
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
],
|
||||
"realmRoles": [ "user" ],
|
||||
"realmRoles": [ "user", "offline_access" ],
|
||||
"clientRoles": {
|
||||
"account": [ "manage-account" ]
|
||||
}
|
||||
|
@ -37,7 +37,7 @@
|
|||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
],
|
||||
"realmRoles": [ "user" ],
|
||||
"realmRoles": [ "user", "offline_access" ],
|
||||
"clientRoles": {
|
||||
"account": [ "manage-account" ]
|
||||
}
|
||||
|
@ -52,7 +52,7 @@
|
|||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
],
|
||||
"realmRoles": [ "user" ],
|
||||
"realmRoles": [ "user", "offline_access" ],
|
||||
"clientRoles": {
|
||||
"account": [ "manage-account" ]
|
||||
}
|
||||
|
@ -103,6 +103,10 @@
|
|||
{
|
||||
"client": "third-party",
|
||||
"roles": ["user"]
|
||||
},
|
||||
{
|
||||
"client": "offline-access-portal",
|
||||
"roles": ["user", "offline_access"]
|
||||
}
|
||||
],
|
||||
"clients": [
|
||||
|
@ -190,6 +194,17 @@
|
|||
"attributes": {
|
||||
"jwt.credential.certificate": "MIICnTCCAYUCBgFPPLDaTzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE3MjI0N1oXDTI1MDgxNzE3MjQyN1owEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIUjjgv+V3s96O+Za9002Lp/trtGuHBeaeVL9dFKMKzO2MPqdRmHB4PqNlDdd28Rwf5Xn6iWdFpyUKOnI/yXDLhdcuFpR0sMNK/C9Lt+hSpPFLuzDqgtPgDotlMxiHIWDOZ7g9/gPYNXbNvjv8nSiyqoguoCQiiafW90bPHsiVLdP7ZIUwCcfi1qQm7FhxRJ1NiW5dvUkuCnnWEf0XR+Wzc5eC9EgB0taLFiPsSEIlWMm5xlahYyXkPdNOqZjiRnrTWm5Y4uk8ZcsD/KbPTf/7t7cQXipVaswgjdYi1kK2/zRwOhg1QwWFX/qmvdd+fLxV0R6VqRDhn7Qep2cxwMxLsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAKE6OA46sf20bz8LZPoiNsqRwBUDkaMGXfnob7s/hJZIIwDEx0IAQ3uKsG7q9wb+aA6s+v7S340zb2k3IxuhFaHaZpAd4CyR5cn1FHylbzoZ7rI/3ASqHDqpljdJaFqPH+m7nZWtyDvtZf+gkZ8OjsndwsSBK1d/jMZPp29qYbl1+XfO7RCp/jDqro/R3saYFaIFiEZPeKn1hUJn6BO48vxH1xspSu9FmlvDOEAOz4AuM58z4zRMP49GcFdCWr1wkonJUHaSptJaQwmBwLFUkCbE5I1ixGMb7mjEud6Y5jhfzJiZMo2U8RfcjNbrN0diZl3jB6LQIwESnhYSghaTjNQ=="
|
||||
}
|
||||
},
|
||||
{
|
||||
"clientId": "offline-access-portal",
|
||||
"enabled": true,
|
||||
"consentRequired": true,
|
||||
"adminUrl": "/offline-access-portal",
|
||||
"baseUrl": "/offline-access-portal",
|
||||
"redirectUris": [
|
||||
"/offline-access-portal/*"
|
||||
],
|
||||
"secret": "password"
|
||||
}
|
||||
],
|
||||
"clientScopeMappings": {
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package org.keycloak.exportimport.util;
|
||||
|
||||
import org.keycloak.models.OfflineClientSessionModel;
|
||||
import org.keycloak.models.OfflineUserSessionModel;
|
||||
import org.keycloak.representations.idm.OfflineUserSessionRepresentation;
|
||||
import org.keycloak.util.Base64;
|
||||
import org.codehaus.jackson.JsonEncoding;
|
||||
import org.codehaus.jackson.JsonFactory;
|
||||
|
@ -295,6 +298,28 @@ public class ExportUtils {
|
|||
}
|
||||
}
|
||||
|
||||
// Offline sessions
|
||||
List<OfflineUserSessionRepresentation> offlineSessionReps = new LinkedList<>();
|
||||
Collection<OfflineUserSessionModel> offlineSessions = session.users().getOfflineUserSessions(realm, user);
|
||||
Collection<OfflineClientSessionModel> offlineClientSessions = session.users().getOfflineClientSessions(realm, user);
|
||||
|
||||
Map<String, List<OfflineClientSessionModel>> processed = new HashMap<>();
|
||||
for (OfflineClientSessionModel clsm : offlineClientSessions) {
|
||||
String userSessionId = clsm.getUserSessionId();
|
||||
List<OfflineClientSessionModel> current = processed.get(userSessionId);
|
||||
if (current == null) {
|
||||
current = new LinkedList<>();
|
||||
processed.put(userSessionId, current);
|
||||
}
|
||||
current.add(clsm);
|
||||
}
|
||||
|
||||
for (OfflineUserSessionModel userSession : offlineSessions) {
|
||||
OfflineUserSessionRepresentation sessionRep = ModelToRepresentation.toRepresentation(realm, userSession, processed.get(userSession.getUserSessionId()));
|
||||
offlineSessionReps.add(sessionRep);
|
||||
}
|
||||
userRep.setOfflineUserSessions(offlineSessionReps);
|
||||
|
||||
return userRep;
|
||||
}
|
||||
|
||||
|
|
|
@ -202,7 +202,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
|
|||
attributes.put("sessions", new SessionsBean(realm, sessions));
|
||||
break;
|
||||
case APPLICATIONS:
|
||||
attributes.put("applications", new ApplicationsBean(realm, user));
|
||||
attributes.put("applications", new ApplicationsBean(session, realm, user));
|
||||
attributes.put("advancedMsg", new AdvancedMessageFormatterMethod(locale, messagesBundle));
|
||||
break;
|
||||
case PASSWORD:
|
||||
|
|
|
@ -6,6 +6,7 @@ import java.util.List;
|
|||
import java.util.Set;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.UserConsentModel;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
@ -22,9 +23,9 @@ public class ApplicationsBean {
|
|||
|
||||
private List<ApplicationEntry> applications = new LinkedList<ApplicationEntry>();
|
||||
|
||||
public ApplicationsBean(RealmModel realm, UserModel user) {
|
||||
public ApplicationsBean(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
|
||||
Set<ClientModel> offlineClients = OfflineTokenUtils.findClientsWithOfflineToken(realm, user);
|
||||
Set<ClientModel> offlineClients = OfflineTokenUtils.findClientsWithOfflineToken(session, realm, user);
|
||||
|
||||
List<ClientModel> realmClients = realm.getClients();
|
||||
for (ClientModel client : realmClients) {
|
||||
|
|
|
@ -637,6 +637,21 @@ module.config([ '$routeProvider', function($routeProvider) {
|
|||
},
|
||||
controller : 'ClientSessionsCtrl'
|
||||
})
|
||||
.when('/realms/:realm/clients/:client/offline-access', {
|
||||
templateUrl : resourceUrl + '/partials/client-offline-sessions.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
client : function(ClientLoader) {
|
||||
return ClientLoader();
|
||||
},
|
||||
offlineSessionCount : function(ClientOfflineSessionCountLoader) {
|
||||
return ClientOfflineSessionCountLoader();
|
||||
}
|
||||
},
|
||||
controller : 'ClientOfflineSessionsCtrl'
|
||||
})
|
||||
.when('/realms/:realm/clients/:client/credentials', {
|
||||
templateUrl : resourceUrl + '/partials/client-credentials.html',
|
||||
resolve : {
|
||||
|
|
|
@ -507,6 +507,54 @@ module.controller('ClientSessionsCtrl', function($scope, realm, sessionCount, cl
|
|||
};
|
||||
});
|
||||
|
||||
module.controller('ClientOfflineSessionsCtrl', function($scope, realm, offlineSessionCount, client,
|
||||
ClientOfflineSessions) {
|
||||
$scope.realm = realm;
|
||||
$scope.count = offlineSessionCount.count;
|
||||
$scope.sessions = [];
|
||||
$scope.client = client;
|
||||
|
||||
$scope.page = 0;
|
||||
|
||||
$scope.query = {
|
||||
realm : realm.realm,
|
||||
client: $scope.client.id,
|
||||
max : 5,
|
||||
first : 0
|
||||
}
|
||||
|
||||
$scope.firstPage = function() {
|
||||
$scope.query.first = 0;
|
||||
if ($scope.query.first < 0) {
|
||||
$scope.query.first = 0;
|
||||
}
|
||||
$scope.loadUsers();
|
||||
}
|
||||
|
||||
$scope.previousPage = function() {
|
||||
$scope.query.first -= parseInt($scope.query.max);
|
||||
if ($scope.query.first < 0) {
|
||||
$scope.query.first = 0;
|
||||
}
|
||||
$scope.loadUsers();
|
||||
}
|
||||
|
||||
$scope.nextPage = function() {
|
||||
$scope.query.first += parseInt($scope.query.max);
|
||||
$scope.loadUsers();
|
||||
}
|
||||
|
||||
$scope.toDate = function(val) {
|
||||
return new Date(val);
|
||||
};
|
||||
|
||||
$scope.loadUsers = function() {
|
||||
ClientOfflineSessions.query($scope.query, function(updated) {
|
||||
$scope.sessions = updated;
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
module.controller('ClientRoleDetailCtrl', function($scope, realm, client, role, roles, clients,
|
||||
Role, ClientRole, RoleById, RoleRealmComposites, RoleClientComposites,
|
||||
$http, $location, Dialog, Notifications) {
|
||||
|
|
|
@ -208,9 +208,9 @@ module.controller('UserConsentsCtrl', function($scope, realm, user, userConsents
|
|||
UserConsents.query({realm: realm.realm, user: user.id}, function(updated) {
|
||||
$scope.userConsents = updated;
|
||||
})
|
||||
Notifications.success('Consent revoked successfully');
|
||||
Notifications.success('Grant revoked successfully');
|
||||
}, function() {
|
||||
Notifications.error("Consent couldn't be revoked");
|
||||
Notifications.error("Grant couldn't be revoked");
|
||||
});
|
||||
console.log("Revoke consent " + clientId);
|
||||
}
|
||||
|
|
|
@ -246,6 +246,15 @@ module.factory('ClientSessionCountLoader', function(Loader, ClientSessionCount,
|
|||
});
|
||||
});
|
||||
|
||||
module.factory('ClientOfflineSessionCountLoader', function(Loader, ClientOfflineSessionCount, $route, $q) {
|
||||
return Loader.get(ClientOfflineSessionCount, function() {
|
||||
return {
|
||||
realm : $route.current.params.realm,
|
||||
client : $route.current.params.client
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('ClientClaimsLoader', function(Loader, ClientClaims, $route, $q) {
|
||||
return Loader.get(ClientClaims, function() {
|
||||
return {
|
||||
|
|
|
@ -841,6 +841,20 @@ module.factory('ClientUserSessions', function($resource) {
|
|||
});
|
||||
});
|
||||
|
||||
module.factory('ClientOfflineSessionCount', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/clients/:client/offline-session-count', {
|
||||
realm : '@realm',
|
||||
client : "@client"
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('ClientOfflineSessions', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/clients/:client/offline-sessions', {
|
||||
realm : '@realm',
|
||||
client : "@client"
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('ClientLogoutAll', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/clients/:client/logout-all', {
|
||||
realm : '@realm',
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
<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>
|
||||
|
||||
<kc-tabs-client></kc-tabs-client>
|
||||
|
||||
<form class="form-horizontal" name="sessionStats">
|
||||
<fieldset class="border-top">
|
||||
<div class="form-group">
|
||||
<label class="col-md-2 control-label" for="activeSessions">Offline Tokens</label>
|
||||
<div class="col-md-6">
|
||||
<input class="form-control" type="text" id="activeSessions" name="activeSessions" data-ng-model="count" ng-disabled="true">
|
||||
</div>
|
||||
<kc-tooltip>Total number of active offline tokens for this client.</kc-tooltip>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
<table class="table table-striped table-bordered" data-ng-show="count > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="kc-table-actions" colspan="3">
|
||||
<div class="pull-right">
|
||||
<a class="btn btn-default" ng-click="loadUsers()" tooltip-placement="left" tooltip-trigger="mouseover mouseout" tooltip="Warning, this is a potentially expensive operation depending on number of offline tokens.">Show Offline Tokens</a>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
<tr data-ng-show="sessions">
|
||||
<th>User</th>
|
||||
<th>From IP</th>
|
||||
<th>Token Issued</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot data-ng-show="sessions && (sessions.length >= 5 || query.first != 0)">
|
||||
<tr>
|
||||
<td colspan="7">
|
||||
<div class="table-nav">
|
||||
<button data-ng-click="firstPage()" class="first" ng-disabled="query.first == 0">First page</button>
|
||||
<button data-ng-click="previousPage()" class="prev" ng-disabled="query.first == 0">Previous page</button>
|
||||
<button data-ng-click="nextPage()" class="next" ng-disabled="sessions.length < query.max">Next page</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
<tbody>
|
||||
<tr data-ng-repeat="session in sessions">
|
||||
<td><a href="#/realms/{{realm.realm}}/users/{{session.userId}}">{{session.username}}</a></td>
|
||||
<td>{{session.ipAddress}}</td>
|
||||
<td>{{session.start | date:'medium'}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<kc-menu></kc-menu>
|
|
@ -12,6 +12,7 @@
|
|||
<th>Client</th>
|
||||
<th>Granted Roles</th>
|
||||
<th>Granted Protocol Mappers</th>
|
||||
<th>Additional Grants</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -35,6 +36,11 @@
|
|||
</span>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span data-ng-repeat="additionalGrant in consent.additionalGrants">
|
||||
<span ng-if="!$first">, </span>{{additionalGrant}}
|
||||
</span>
|
||||
</td>
|
||||
<td class="kc-action-cell">
|
||||
<button class="btn btn-default btn-block btn-sm" ng-click="revokeConsent(consent.clientId)">
|
||||
<i class="pficon pficon-delete"></i> Revoke
|
||||
|
|
|
@ -26,6 +26,13 @@
|
|||
<kc-tooltip>View active sessions for this client. Allows you to see which users are active and when they logged in.</kc-tooltip>
|
||||
</li>
|
||||
|
||||
<li ng-class="{active: path[4] == 'offline-access'}" data-ng-show="!client.bearerOnly">
|
||||
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/offline-access">Offline Access</a>
|
||||
<kc-tooltip>View offline sessions for this client. Allows you to see which users retrieve offline token and when they retrieve it.
|
||||
To revoke all tokens for the client, go to Revocation tab and set new not before value.
|
||||
</kc-tooltip>
|
||||
</li>
|
||||
|
||||
<li ng-class="{active: path[4] == 'clustering'}" data-ng-show="!client.publicClient"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/clustering">Clustering</a></li>
|
||||
|
||||
<li ng-class="{active: path[4] == 'installation'}" data-ng-show="client.protocol != 'saml'">
|
||||
|
|
|
@ -3,8 +3,10 @@ package org.keycloak.migration.migrators;
|
|||
import java.util.List;
|
||||
|
||||
import org.keycloak.migration.ModelVersion;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
/**
|
||||
|
@ -17,6 +19,16 @@ public class MigrateTo1_6_0 {
|
|||
public void migrate(KeycloakSession session) {
|
||||
List<RealmModel> realms = session.realms().getRealms();
|
||||
for (RealmModel realm : realms) {
|
||||
|
||||
for (RoleModel realmRole : realm.getRoles()) {
|
||||
realmRole.setScopeParamRequired(false);
|
||||
}
|
||||
for (ClientModel client : realm.getClients()) {
|
||||
for (RoleModel clientRole : client.getRoles()) {
|
||||
clientRole.setScopeParamRequired(false);
|
||||
}
|
||||
}
|
||||
|
||||
KeycloakModelUtils.setupOfflineTokens(realm);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ public class OfflineClientSessionModel {
|
|||
private String clientSessionId;
|
||||
private String userSessionId;
|
||||
private String clientId;
|
||||
private String userId;
|
||||
private String data;
|
||||
|
||||
public String getClientSessionId() {
|
||||
|
@ -34,6 +35,14 @@ public class OfflineClientSessionModel {
|
|||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
@ -474,6 +475,70 @@ public class UserFederationManager implements UserProvider {
|
|||
return (result != null) ? result : CredentialValidationOutput.failed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineUserSession(RealmModel realm, UserModel user, OfflineUserSessionModel offlineUserSession) {
|
||||
validateUser(realm, user);
|
||||
if (user == null) throw new IllegalStateException("Federated user no longer valid");
|
||||
session.userStorage().addOfflineUserSession(realm, user, offlineUserSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineUserSessionModel getOfflineUserSession(RealmModel realm, UserModel user, String userSessionId) {
|
||||
validateUser(realm, user);
|
||||
if (user == null) throw new IllegalStateException("Federated user no longer valid");
|
||||
return session.userStorage().getOfflineUserSession(realm, user, userSessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineUserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel user) {
|
||||
validateUser(realm, user);
|
||||
if (user == null) throw new IllegalStateException("Federated user no longer valid");
|
||||
return session.userStorage().getOfflineUserSessions(realm, user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOfflineUserSession(RealmModel realm, UserModel user, String userSessionId) {
|
||||
validateUser(realm, user);
|
||||
if (user == null) throw new IllegalStateException("Federated user no longer valid");
|
||||
return session.userStorage().removeOfflineUserSession(realm, user, userSessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineClientSession(RealmModel realm, OfflineClientSessionModel offlineClientSession) {
|
||||
session.userStorage().addOfflineClientSession(realm, offlineClientSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineClientSessionModel getOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId) {
|
||||
validateUser(realm, user);
|
||||
if (user == null) throw new IllegalStateException("Federated user no longer valid");
|
||||
return session.userStorage().getOfflineClientSession(realm, user, clientSessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, UserModel user) {
|
||||
validateUser(realm, user);
|
||||
if (user == null) throw new IllegalStateException("Federated user no longer valid");
|
||||
return session.userStorage().getOfflineClientSessions(realm, user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId) {
|
||||
validateUser(realm, user);
|
||||
if (user == null) throw new IllegalStateException("Federated user no longer valid");
|
||||
return session.userStorage().removeOfflineClientSession(realm, user, clientSessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOfflineClientSessionsCount(RealmModel realm, ClientModel client) {
|
||||
return session.userStorage().getOfflineClientSessionsCount(realm, client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
|
||||
return session.userStorage().getOfflineClientSessions(realm, client, firstResult, maxResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
|
|
@ -114,15 +114,6 @@ public interface UserModel {
|
|||
void updateConsent(UserConsentModel consent);
|
||||
boolean revokeConsentForClient(String clientInternalId);
|
||||
|
||||
void addOfflineUserSession(OfflineUserSessionModel offlineUserSession);
|
||||
OfflineUserSessionModel getOfflineUserSession(String userSessionId);
|
||||
Collection<OfflineUserSessionModel> getOfflineUserSessions();
|
||||
boolean removeOfflineUserSession(String userSessionId);
|
||||
void addOfflineClientSession(OfflineClientSessionModel offlineClientSession);
|
||||
OfflineClientSessionModel getOfflineClientSession(String clientSessionId);
|
||||
Collection<OfflineClientSessionModel> getOfflineClientSessions();
|
||||
boolean removeOfflineClientSession(String clientSessionId);
|
||||
|
||||
public static enum RequiredAction {
|
||||
VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.keycloak.models;
|
|||
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -55,5 +56,17 @@ public interface UserProvider extends Provider {
|
|||
boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input);
|
||||
CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel... input);
|
||||
|
||||
void addOfflineUserSession(RealmModel realm, UserModel user, OfflineUserSessionModel offlineUserSession);
|
||||
OfflineUserSessionModel getOfflineUserSession(RealmModel realm, UserModel user, String userSessionId);
|
||||
Collection<OfflineUserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel user);
|
||||
boolean removeOfflineUserSession(RealmModel realm, UserModel user, String userSessionId);
|
||||
void addOfflineClientSession(RealmModel realm, OfflineClientSessionModel offlineClientSession);
|
||||
OfflineClientSessionModel getOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId);
|
||||
Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, UserModel user);
|
||||
boolean removeOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId);
|
||||
|
||||
int getOfflineClientSessionsCount(RealmModel realm, ClientModel client);
|
||||
Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, ClientModel client, int first, int max);
|
||||
|
||||
void close();
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import org.keycloak.models.IdentityProviderMapperModel;
|
|||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.OTPPolicy;
|
||||
import org.keycloak.models.OfflineClientSessionModel;
|
||||
import org.keycloak.models.OfflineUserSessionModel;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RequiredActionProviderModel;
|
||||
|
@ -30,6 +32,8 @@ import org.keycloak.representations.idm.CredentialRepresentation;
|
|||
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
|
||||
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
|
||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||
import org.keycloak.representations.idm.OfflineClientSessionRepresentation;
|
||||
import org.keycloak.representations.idm.OfflineUserSessionRepresentation;
|
||||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
|
@ -43,6 +47,7 @@ import org.keycloak.representations.idm.UserSessionRepresentation;
|
|||
import org.keycloak.util.Time;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
|
@ -506,7 +511,31 @@ public class ModelToRepresentation {
|
|||
return rep;
|
||||
}
|
||||
|
||||
public static OfflineUserSessionRepresentation toRepresentation(RealmModel realm, OfflineUserSessionModel model, Collection<OfflineClientSessionModel> clientSessions) {
|
||||
OfflineUserSessionRepresentation rep = new OfflineUserSessionRepresentation();
|
||||
rep.setData(model.getData());
|
||||
rep.setUserSessionId(model.getUserSessionId());
|
||||
|
||||
List<OfflineClientSessionRepresentation> clientSessionReps = new LinkedList<>();
|
||||
for (OfflineClientSessionModel clsm : clientSessions) {
|
||||
OfflineClientSessionRepresentation clrep = toRepresentation(realm, clsm);
|
||||
clientSessionReps.add(clrep);
|
||||
}
|
||||
rep.setOfflineClientSessions(clientSessionReps);
|
||||
return rep;
|
||||
}
|
||||
|
||||
public static OfflineClientSessionRepresentation toRepresentation(RealmModel realm, OfflineClientSessionModel model) {
|
||||
OfflineClientSessionRepresentation rep = new OfflineClientSessionRepresentation();
|
||||
|
||||
String clientInternalId = model.getClientId();
|
||||
ClientModel client = realm.getClientById(clientInternalId);
|
||||
rep.setClient(client.getClientId());
|
||||
|
||||
rep.setClientSessionId(model.getClientSessionId());
|
||||
rep.setData(model.getData());
|
||||
return rep;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package org.keycloak.models.utils;
|
||||
|
||||
import org.keycloak.models.OfflineClientSessionModel;
|
||||
import org.keycloak.models.OfflineUserSessionModel;
|
||||
import org.keycloak.representations.idm.OfflineClientSessionRepresentation;
|
||||
import org.keycloak.representations.idm.OfflineUserSessionRepresentation;
|
||||
import org.keycloak.util.Base64;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.enums.SslRequired;
|
||||
|
@ -981,6 +985,11 @@ public class RepresentationToModel {
|
|||
user.addConsent(consentModel);
|
||||
}
|
||||
}
|
||||
if (userRep.getOfflineUserSessions() != null) {
|
||||
for (OfflineUserSessionRepresentation sessionRep : userRep.getOfflineUserSessions()) {
|
||||
importOfflineSession(session, newRealm, user, sessionRep);
|
||||
}
|
||||
}
|
||||
if (userRep.getServiceAccountClientId() != null) {
|
||||
String clientId = userRep.getServiceAccountClientId();
|
||||
ClientModel client = clientMap.get(clientId);
|
||||
|
@ -1151,6 +1160,29 @@ public class RepresentationToModel {
|
|||
return consentModel;
|
||||
}
|
||||
|
||||
public static void importOfflineSession(KeycloakSession session, RealmModel newRealm, UserModel user, OfflineUserSessionRepresentation sessionRep) {
|
||||
OfflineUserSessionModel model = new OfflineUserSessionModel();
|
||||
model.setUserSessionId(sessionRep.getUserSessionId());
|
||||
model.setData(sessionRep.getData());
|
||||
session.users().addOfflineUserSession(newRealm, user, model);
|
||||
|
||||
for (OfflineClientSessionRepresentation csRep : sessionRep.getOfflineClientSessions()) {
|
||||
OfflineClientSessionModel csModel = new OfflineClientSessionModel();
|
||||
String clientId = csRep.getClient();
|
||||
ClientModel client = newRealm.getClientByClientId(clientId);
|
||||
if (client == null) {
|
||||
throw new RuntimeException("Unable to find client " + clientId + " referenced from offlineClientSession of user " + user.getUsername());
|
||||
}
|
||||
csModel.setClientId(client.getId());
|
||||
csModel.setUserId(user.getId());
|
||||
csModel.setClientSessionId(csRep.getClientSessionId());
|
||||
csModel.setUserSessionId(sessionRep.getUserSessionId());
|
||||
csModel.setData(csRep.getData());
|
||||
|
||||
session.users().addOfflineClientSession(newRealm, csModel);
|
||||
}
|
||||
}
|
||||
|
||||
public static AuthenticationFlowModel toModel(AuthenticationFlowRepresentation rep) {
|
||||
AuthenticationFlowModel model = new AuthenticationFlowModel();
|
||||
model.setBuiltIn(rep.isBuiltIn());
|
||||
|
|
|
@ -258,44 +258,4 @@ public class UserModelDelegate implements UserModel {
|
|||
public void setCreatedTimestamp(Long timestamp){
|
||||
delegate.setCreatedTimestamp(timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineUserSession(OfflineUserSessionModel userSession) {
|
||||
delegate.addOfflineUserSession(userSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineUserSessionModel getOfflineUserSession(String userSessionId) {
|
||||
return delegate.getOfflineUserSession(userSessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineUserSessionModel> getOfflineUserSessions() {
|
||||
return delegate.getOfflineUserSessions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOfflineUserSession(String userSessionId) {
|
||||
return delegate.removeOfflineUserSession(userSessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineClientSession(OfflineClientSessionModel clientSession) {
|
||||
delegate.addOfflineClientSession(clientSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineClientSessionModel getOfflineClientSession(String clientSessionId) {
|
||||
return delegate.getOfflineClientSession(clientSessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineClientSessionModel> getOfflineClientSessions() {
|
||||
return delegate.getOfflineClientSessions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOfflineClientSession(String clientSessionId) {
|
||||
return delegate.removeOfflineClientSession(clientSessionId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,9 @@ import org.keycloak.models.CredentialValidationOutput;
|
|||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.OfflineClientSessionModel;
|
||||
import org.keycloak.models.OfflineUserSessionModel;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RequiredActionProviderModel;
|
||||
|
@ -32,6 +35,8 @@ import org.keycloak.models.UserFederationProviderModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserProvider;
|
||||
import org.keycloak.models.entities.FederatedIdentityEntity;
|
||||
import org.keycloak.models.entities.OfflineClientSessionEntity;
|
||||
import org.keycloak.models.entities.OfflineUserSessionEntity;
|
||||
import org.keycloak.models.entities.UserEntity;
|
||||
import org.keycloak.models.file.adapter.UserAdapter;
|
||||
import org.keycloak.models.utils.CredentialValidation;
|
||||
|
@ -41,6 +46,7 @@ import java.util.ArrayList;
|
|||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -489,4 +495,187 @@ public class FileUserProvider implements UserProvider {
|
|||
return null; // not supported yet
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineUserSession(RealmModel realm, UserModel userModel, OfflineUserSessionModel userSession) {
|
||||
userModel = getUserById(userModel.getId(), realm);
|
||||
UserEntity userEntity = ((UserAdapter) userModel).getUserEntity();
|
||||
|
||||
if (userEntity.getOfflineUserSessions() == null) {
|
||||
userEntity.setOfflineUserSessions(new ArrayList<OfflineUserSessionEntity>());
|
||||
}
|
||||
|
||||
if (getUserSessionEntityById(userEntity, userSession.getUserSessionId()) != null) {
|
||||
throw new ModelDuplicateException("User session already exists with id " + userSession.getUserSessionId() + " for user " + userEntity.getUsername());
|
||||
}
|
||||
|
||||
OfflineUserSessionEntity entity = new OfflineUserSessionEntity();
|
||||
entity.setUserSessionId(userSession.getUserSessionId());
|
||||
entity.setData(userSession.getData());
|
||||
entity.setOfflineClientSessions(new ArrayList<OfflineClientSessionEntity>());
|
||||
userEntity.getOfflineUserSessions().add(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineUserSessionModel getOfflineUserSession(RealmModel realm, UserModel userModel, String userSessionId) {
|
||||
userModel = getUserById(userModel.getId(), realm);
|
||||
UserEntity userEntity = ((UserAdapter) userModel).getUserEntity();
|
||||
|
||||
OfflineUserSessionEntity entity = getUserSessionEntityById(userEntity, userSessionId);
|
||||
return entity==null ? null : toModel(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineUserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel userModel) {
|
||||
userModel = getUserById(userModel.getId(), realm);
|
||||
UserEntity user = ((UserAdapter) userModel).getUserEntity();
|
||||
|
||||
if (user.getOfflineUserSessions()==null) {
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
List<OfflineUserSessionModel> result = new ArrayList<>();
|
||||
for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
|
||||
result.add(toModel(entity));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private OfflineUserSessionModel toModel(OfflineUserSessionEntity entity) {
|
||||
OfflineUserSessionModel model = new OfflineUserSessionModel();
|
||||
model.setUserSessionId(entity.getUserSessionId());
|
||||
model.setData(entity.getData());
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOfflineUserSession(RealmModel realm, UserModel userModel, String userSessionId) {
|
||||
userModel = getUserById(userModel.getId(), realm);
|
||||
UserEntity user = ((UserAdapter) userModel).getUserEntity();
|
||||
|
||||
OfflineUserSessionEntity entity = getUserSessionEntityById(user, userSessionId);
|
||||
if (entity != null) {
|
||||
user.getOfflineUserSessions().remove(entity);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private OfflineUserSessionEntity getUserSessionEntityById(UserEntity user, String userSessionId) {
|
||||
if (user.getOfflineUserSessions() != null) {
|
||||
for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
|
||||
if (entity.getUserSessionId().equals(userSessionId)) {
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineClientSession(RealmModel realm, OfflineClientSessionModel clientSession) {
|
||||
UserModel userModel = getUserById(clientSession.getUserId(), realm);
|
||||
UserEntity user = ((UserAdapter) userModel).getUserEntity();
|
||||
|
||||
OfflineUserSessionEntity userSessionEntity = getUserSessionEntityById(user, clientSession.getUserSessionId());
|
||||
if (userSessionEntity == null) {
|
||||
throw new ModelException("OfflineUserSession with ID " + clientSession.getUserSessionId() + " doesn't exist for user " + user.getUsername());
|
||||
}
|
||||
|
||||
OfflineClientSessionEntity clEntity = new OfflineClientSessionEntity();
|
||||
clEntity.setClientSessionId(clientSession.getClientSessionId());
|
||||
clEntity.setClientId(clientSession.getClientId());
|
||||
clEntity.setData(clientSession.getData());
|
||||
|
||||
userSessionEntity.getOfflineClientSessions().add(clEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineClientSessionModel getOfflineClientSession(RealmModel realm, UserModel userModel, String clientSessionId) {
|
||||
userModel = getUserById(userModel.getId(), realm);
|
||||
UserEntity user = ((UserAdapter) userModel).getUserEntity();
|
||||
|
||||
if (user.getOfflineUserSessions() != null) {
|
||||
for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
|
||||
for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
|
||||
if (clSession.getClientSessionId().equals(clientSessionId)) {
|
||||
return toModel(clSession, userSession.getUserSessionId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private OfflineClientSessionModel toModel(OfflineClientSessionEntity cls, String userSessionId) {
|
||||
OfflineClientSessionModel model = new OfflineClientSessionModel();
|
||||
model.setClientSessionId(cls.getClientSessionId());
|
||||
model.setClientId(cls.getClientId());
|
||||
model.setData(cls.getData());
|
||||
model.setUserSessionId(userSessionId);
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, UserModel userModel) {
|
||||
userModel = getUserById(userModel.getId(), realm);
|
||||
UserEntity user = ((UserAdapter) userModel).getUserEntity();
|
||||
|
||||
List<OfflineClientSessionModel> result = new ArrayList<>();
|
||||
|
||||
if (user.getOfflineUserSessions() != null) {
|
||||
for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
|
||||
for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
|
||||
result.add(toModel(clSession, userSession.getUserSessionId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOfflineClientSession(RealmModel realm, UserModel userModel, String clientSessionId) {
|
||||
userModel = getUserById(userModel.getId(), realm);
|
||||
UserEntity user = ((UserAdapter) userModel).getUserEntity();
|
||||
|
||||
if (user.getOfflineUserSessions() != null) {
|
||||
for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
|
||||
for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
|
||||
if (clSession.getClientSessionId().equals(clientSessionId)) {
|
||||
userSession.getOfflineClientSessions().remove(clSession);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOfflineClientSessionsCount(RealmModel realm, ClientModel client) {
|
||||
return getOfflineClientSessions(realm, client, -1, -1).size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
|
||||
List<OfflineClientSessionModel> result = new LinkedList<>();
|
||||
|
||||
List<UserModel> users = new ArrayList<>(inMemoryModel.getUsers(realm.getId()));
|
||||
users = sortedSubList(users, firstResult, maxResults);
|
||||
|
||||
for (UserModel userModel : users) {
|
||||
UserEntity user = ((UserAdapter) userModel).getUserEntity();
|
||||
for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
|
||||
for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
|
||||
if (clSession.getClientId().equals(client.getId())) {
|
||||
result.add(toModel(clSession, userSession.getUserSessionId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -574,141 +574,6 @@ public class UserAdapter implements UserModel, Comparable {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineUserSession(OfflineUserSessionModel userSession) {
|
||||
if (user.getOfflineUserSessions() == null) {
|
||||
user.setOfflineUserSessions(new ArrayList<OfflineUserSessionEntity>());
|
||||
}
|
||||
|
||||
if (getUserSessionEntityById(userSession.getUserSessionId()) != null) {
|
||||
throw new ModelDuplicateException("User session already exists with id " + userSession.getUserSessionId() + " for user " + user.getUsername());
|
||||
}
|
||||
|
||||
OfflineUserSessionEntity entity = new OfflineUserSessionEntity();
|
||||
entity.setUserSessionId(userSession.getUserSessionId());
|
||||
entity.setData(userSession.getData());
|
||||
entity.setOfflineClientSessions(new ArrayList<OfflineClientSessionEntity>());
|
||||
user.getOfflineUserSessions().add(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineUserSessionModel getOfflineUserSession(String userSessionId) {
|
||||
OfflineUserSessionEntity entity = getUserSessionEntityById(userSessionId);
|
||||
return entity==null ? null : toModel(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineUserSessionModel> getOfflineUserSessions() {
|
||||
if (user.getOfflineUserSessions()==null) {
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
List<OfflineUserSessionModel> result = new ArrayList<>();
|
||||
for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
|
||||
result.add(toModel(entity));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private OfflineUserSessionModel toModel(OfflineUserSessionEntity entity) {
|
||||
OfflineUserSessionModel model = new OfflineUserSessionModel();
|
||||
model.setUserSessionId(entity.getUserSessionId());
|
||||
model.setData(entity.getData());
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOfflineUserSession(String userSessionId) {
|
||||
OfflineUserSessionEntity entity = getUserSessionEntityById(userSessionId);
|
||||
if (entity != null) {
|
||||
user.getOfflineUserSessions().remove(entity);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private OfflineUserSessionEntity getUserSessionEntityById(String userSessionId) {
|
||||
if (user.getOfflineUserSessions() != null) {
|
||||
for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
|
||||
if (entity.getUserSessionId().equals(userSessionId)) {
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineClientSession(OfflineClientSessionModel clientSession) {
|
||||
OfflineUserSessionEntity userSessionEntity = getUserSessionEntityById(clientSession.getUserSessionId());
|
||||
if (userSessionEntity == null) {
|
||||
throw new ModelException("OfflineUserSession with ID " + clientSession.getUserSessionId() + " doesn't exist for user " + user.getUsername());
|
||||
}
|
||||
|
||||
OfflineClientSessionEntity clEntity = new OfflineClientSessionEntity();
|
||||
clEntity.setClientSessionId(clientSession.getClientSessionId());
|
||||
clEntity.setClientId(clientSession.getClientId());
|
||||
clEntity.setData(clientSession.getData());
|
||||
|
||||
userSessionEntity.getOfflineClientSessions().add(clEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineClientSessionModel getOfflineClientSession(String clientSessionId) {
|
||||
if (user.getOfflineUserSessions() != null) {
|
||||
for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
|
||||
for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
|
||||
if (clSession.getClientSessionId().equals(clientSessionId)) {
|
||||
return toModel(clSession, userSession.getUserSessionId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private OfflineClientSessionModel toModel(OfflineClientSessionEntity cls, String userSessionId) {
|
||||
OfflineClientSessionModel model = new OfflineClientSessionModel();
|
||||
model.setClientSessionId(cls.getClientSessionId());
|
||||
model.setClientId(cls.getClientId());
|
||||
model.setData(cls.getData());
|
||||
model.setUserSessionId(userSessionId);
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineClientSessionModel> getOfflineClientSessions() {
|
||||
List<OfflineClientSessionModel> result = new ArrayList<>();
|
||||
|
||||
if (user.getOfflineUserSessions() != null) {
|
||||
for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
|
||||
for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
|
||||
result.add(toModel(clSession, userSession.getUserSessionId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOfflineClientSession(String clientSessionId) {
|
||||
if (user.getOfflineUserSessions() != null) {
|
||||
for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
|
||||
for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
|
||||
if (clSession.getClientSessionId().equals(clientSessionId)) {
|
||||
userSession.getOfflineClientSessions().remove(clSession);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
|
|
|
@ -104,13 +104,14 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
|
|||
};
|
||||
}
|
||||
|
||||
private boolean isRegisteredForInvalidation(RealmModel realm, String userId) {
|
||||
return realmInvalidations.contains(realm.getId()) || userInvalidations.containsKey(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserById(String id, RealmModel realm) {
|
||||
if (!cache.isEnabled()) return getDelegate().getUserById(id, realm);
|
||||
if (realmInvalidations.contains(realm.getId())) {
|
||||
return getDelegate().getUserById(id, realm);
|
||||
}
|
||||
if (userInvalidations.containsKey(id)) {
|
||||
if (isRegisteredForInvalidation(realm, id)) {
|
||||
return getDelegate().getUserById(id, realm);
|
||||
}
|
||||
|
||||
|
@ -120,7 +121,7 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
|
|||
if (model == null) return null;
|
||||
if (managedUsers.containsKey(id)) return managedUsers.get(id);
|
||||
if (userInvalidations.containsKey(id)) return model;
|
||||
cached = new CachedUser(realm, model);
|
||||
cached = new CachedUser(this, realm, model);
|
||||
cache.addCachedUser(realm.getId(), cached);
|
||||
} else if (managedUsers.containsKey(id)) {
|
||||
return managedUsers.get(id);
|
||||
|
@ -145,7 +146,7 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
|
|||
if (model == null) return null;
|
||||
if (managedUsers.containsKey(model.getId())) return managedUsers.get(model.getId());
|
||||
if (userInvalidations.containsKey(model.getId())) return model;
|
||||
cached = new CachedUser(realm, model);
|
||||
cached = new CachedUser(this, realm, model);
|
||||
cache.addCachedUser(realm.getId(), cached);
|
||||
} else if (userInvalidations.containsKey(cached.getId())) {
|
||||
return getDelegate().getUserById(cached.getId(), realm);
|
||||
|
@ -172,7 +173,7 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
|
|||
UserModel model = getDelegate().getUserByEmail(email, realm);
|
||||
if (model == null) return null;
|
||||
if (userInvalidations.containsKey(model.getId())) return model;
|
||||
cached = new CachedUser(realm, model);
|
||||
cached = new CachedUser(this, realm, model);
|
||||
cache.addCachedUser(realm.getId(), cached);
|
||||
} else if (userInvalidations.containsKey(cached.getId())) {
|
||||
return getDelegate().getUserByEmail(email, realm);
|
||||
|
@ -327,4 +328,94 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
|
|||
public void preRemove(ClientModel client, ProtocolMapperModel protocolMapper) {
|
||||
getDelegate().preRemove(client, protocolMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineUserSession(RealmModel realm, UserModel user, OfflineUserSessionModel offlineUserSession) {
|
||||
registerUserInvalidation(realm, user.getId());
|
||||
getDelegate().addOfflineUserSession(realm, user, offlineUserSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineUserSessionModel getOfflineUserSession(RealmModel realm, UserModel user, String userSessionId) {
|
||||
if (isRegisteredForInvalidation(realm, user.getId())) {
|
||||
return getDelegate().getOfflineUserSession(realm, user, userSessionId);
|
||||
}
|
||||
|
||||
CachedUser cachedUser = cache.getCachedUser(realm.getId(), user.getId());
|
||||
if (cachedUser == null) {
|
||||
return getDelegate().getOfflineUserSession(realm, user, userSessionId);
|
||||
} else {
|
||||
return cachedUser.getOfflineUserSessions().get(userSessionId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineUserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel user) {
|
||||
if (isRegisteredForInvalidation(realm, user.getId())) {
|
||||
return getDelegate().getOfflineUserSessions(realm, user);
|
||||
}
|
||||
|
||||
CachedUser cachedUser = cache.getCachedUser(realm.getId(), user.getId());
|
||||
if (cachedUser == null) {
|
||||
return getDelegate().getOfflineUserSessions(realm, user);
|
||||
} else {
|
||||
return cachedUser.getOfflineUserSessions().values();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOfflineUserSession(RealmModel realm, UserModel user, String userSessionId) {
|
||||
registerUserInvalidation(realm, user.getId());
|
||||
return getDelegate().removeOfflineUserSession(realm, user, userSessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineClientSession(RealmModel realm, OfflineClientSessionModel offlineClientSession) {
|
||||
registerUserInvalidation(realm, offlineClientSession.getUserId());
|
||||
getDelegate().addOfflineClientSession(realm, offlineClientSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineClientSessionModel getOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId) {
|
||||
if (isRegisteredForInvalidation(realm, user.getId())) {
|
||||
return getDelegate().getOfflineClientSession(realm, user, clientSessionId);
|
||||
}
|
||||
|
||||
CachedUser cachedUser = cache.getCachedUser(realm.getId(), user.getId());
|
||||
if (cachedUser == null) {
|
||||
return getDelegate().getOfflineClientSession(realm, user, clientSessionId);
|
||||
} else {
|
||||
return cachedUser.getOfflineClientSessions().get(clientSessionId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, UserModel user) {
|
||||
if (isRegisteredForInvalidation(realm, user.getId())) {
|
||||
return getDelegate().getOfflineClientSessions(realm, user);
|
||||
}
|
||||
|
||||
CachedUser cachedUser = cache.getCachedUser(realm.getId(), user.getId());
|
||||
if (cachedUser == null) {
|
||||
return getDelegate().getOfflineClientSessions(realm, user);
|
||||
} else {
|
||||
return cachedUser.getOfflineClientSessions().values();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId) {
|
||||
registerUserInvalidation(realm, user.getId());
|
||||
return getDelegate().removeOfflineClientSession(realm, user, clientSessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOfflineClientSessionsCount(RealmModel realm, ClientModel client) {
|
||||
return getDelegate().getOfflineClientSessionsCount(realm, client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
|
||||
return getDelegate().getOfflineClientSessions(realm, client, firstResult, maxResults);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -348,52 +348,4 @@ public class UserAdapter implements UserModel {
|
|||
getDelegateForUpdate();
|
||||
return updated.revokeConsentForClient(clientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineUserSession(OfflineUserSessionModel userSession) {
|
||||
getDelegateForUpdate();
|
||||
updated.addOfflineUserSession(userSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineUserSessionModel getOfflineUserSession(String userSessionId) {
|
||||
if (updated != null) return updated.getOfflineUserSession(userSessionId);
|
||||
return cached.getOfflineUserSessions().get(userSessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineUserSessionModel> getOfflineUserSessions() {
|
||||
if (updated != null) return updated.getOfflineUserSessions();
|
||||
return cached.getOfflineUserSessions().values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOfflineUserSession(String userSessionId) {
|
||||
getDelegateForUpdate();
|
||||
return updated.removeOfflineUserSession(userSessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineClientSession(OfflineClientSessionModel clientSession) {
|
||||
getDelegateForUpdate();
|
||||
updated.addOfflineClientSession(clientSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineClientSessionModel getOfflineClientSession(String clientSessionId) {
|
||||
if (updated != null) return updated.getOfflineClientSession(clientSessionId);
|
||||
return cached.getOfflineClientSessions().get(clientSessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineClientSessionModel> getOfflineClientSessions() {
|
||||
if (updated != null) return updated.getOfflineClientSessions();
|
||||
return cached.getOfflineClientSessions().values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOfflineClientSession(String clientSessionId) {
|
||||
getDelegateForUpdate();
|
||||
return updated.removeOfflineClientSession(clientSessionId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserCredentialValueModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.cache.CacheUserProvider;
|
||||
import org.keycloak.util.MultivaluedHashMap;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
@ -40,7 +41,7 @@ public class CachedUser implements Serializable {
|
|||
private Map<String, OfflineUserSessionModel> offlineUserSessions = new HashMap<>();
|
||||
private Map<String, OfflineClientSessionModel> offlineClientSessions = new HashMap<>();
|
||||
|
||||
public CachedUser(RealmModel realm, UserModel user) {
|
||||
public CachedUser(CacheUserProvider cacheUserProvider, RealmModel realm, UserModel user) {
|
||||
this.id = user.getId();
|
||||
this.realm = realm.getId();
|
||||
this.username = user.getUsername();
|
||||
|
@ -59,10 +60,10 @@ public class CachedUser implements Serializable {
|
|||
for (RoleModel role : user.getRoleMappings()) {
|
||||
roleMappings.add(role.getId());
|
||||
}
|
||||
for (OfflineUserSessionModel offlineSession : user.getOfflineUserSessions()) {
|
||||
for (OfflineUserSessionModel offlineSession : cacheUserProvider.getDelegate().getOfflineUserSessions(realm, user)) {
|
||||
offlineUserSessions.put(offlineSession.getUserSessionId(), offlineSession);
|
||||
}
|
||||
for (OfflineClientSessionModel offlineSession : user.getOfflineClientSessions()) {
|
||||
for (OfflineClientSessionModel offlineSession : cacheUserProvider.getDelegate().getOfflineClientSessions(realm, user)) {
|
||||
offlineClientSessions.put(offlineSession.getClientSessionId(), offlineSession);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import org.keycloak.models.ClientModel;
|
|||
import org.keycloak.models.CredentialValidationOutput;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.OfflineClientSessionModel;
|
||||
import org.keycloak.models.OfflineUserSessionModel;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RequiredActionProviderModel;
|
||||
|
@ -13,6 +15,8 @@ import org.keycloak.models.UserFederationProviderModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserProvider;
|
||||
import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
|
||||
import org.keycloak.models.jpa.entities.OfflineClientSessionEntity;
|
||||
import org.keycloak.models.jpa.entities.OfflineUserSessionEntity;
|
||||
import org.keycloak.models.jpa.entities.UserAttributeEntity;
|
||||
import org.keycloak.models.jpa.entities.UserEntity;
|
||||
import org.keycloak.models.utils.CredentialValidation;
|
||||
|
@ -22,8 +26,10 @@ import javax.persistence.EntityManager;
|
|||
import javax.persistence.Query;
|
||||
import javax.persistence.TypedQuery;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -473,4 +479,167 @@ public class JpaUserProvider implements UserProvider {
|
|||
// Not supported yet
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineUserSession(RealmModel realm, UserModel user, OfflineUserSessionModel offlineUserSession) {
|
||||
UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
|
||||
|
||||
OfflineUserSessionEntity entity = new OfflineUserSessionEntity();
|
||||
entity.setUser(userEntity);
|
||||
entity.setUserSessionId(offlineUserSession.getUserSessionId());
|
||||
entity.setData(offlineUserSession.getData());
|
||||
em.persist(entity);
|
||||
userEntity.getOfflineUserSessions().add(entity);
|
||||
em.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineUserSessionModel getOfflineUserSession(RealmModel realm, UserModel user, String userSessionId) {
|
||||
UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
|
||||
|
||||
for (OfflineUserSessionEntity entity : userEntity.getOfflineUserSessions()) {
|
||||
if (entity.getUserSessionId().equals(userSessionId)) {
|
||||
return toModel(entity);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private OfflineUserSessionModel toModel(OfflineUserSessionEntity entity) {
|
||||
OfflineUserSessionModel model = new OfflineUserSessionModel();
|
||||
model.setUserSessionId(entity.getUserSessionId());
|
||||
model.setData(entity.getData());
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineUserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel user) {
|
||||
UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
|
||||
|
||||
List<OfflineUserSessionModel> result = new LinkedList<>();
|
||||
for (OfflineUserSessionEntity entity : userEntity.getOfflineUserSessions()) {
|
||||
result.add(toModel(entity));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOfflineUserSession(RealmModel realm, UserModel user, String userSessionId) {
|
||||
UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
|
||||
|
||||
OfflineUserSessionEntity found = null;
|
||||
for (OfflineUserSessionEntity session : userEntity.getOfflineUserSessions()) {
|
||||
if (session.getUserSessionId().equals(userSessionId)) {
|
||||
found = session;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found == null) {
|
||||
return false;
|
||||
} else {
|
||||
userEntity.getOfflineUserSessions().remove(found);
|
||||
em.remove(found);
|
||||
em.flush();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineClientSession(RealmModel realm, OfflineClientSessionModel offlineClientSession) {
|
||||
UserEntity userEntity = em.getReference(UserEntity.class, offlineClientSession.getUserId());
|
||||
|
||||
OfflineClientSessionEntity entity = new OfflineClientSessionEntity();
|
||||
entity.setUser(userEntity);
|
||||
entity.setClientSessionId(offlineClientSession.getClientSessionId());
|
||||
entity.setUserSessionId(offlineClientSession.getUserSessionId());
|
||||
entity.setClientId(offlineClientSession.getClientId());
|
||||
entity.setData(offlineClientSession.getData());
|
||||
em.persist(entity);
|
||||
userEntity.getOfflineClientSessions().add(entity);
|
||||
em.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineClientSessionModel getOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId) {
|
||||
UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
|
||||
|
||||
for (OfflineClientSessionEntity entity : userEntity.getOfflineClientSessions()) {
|
||||
if (entity.getClientSessionId().equals(clientSessionId)) {
|
||||
return toModel(entity);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private OfflineClientSessionModel toModel(OfflineClientSessionEntity entity) {
|
||||
OfflineClientSessionModel model = new OfflineClientSessionModel();
|
||||
model.setClientSessionId(entity.getClientSessionId());
|
||||
model.setClientId(entity.getClientId());
|
||||
model.setUserId(entity.getUser().getId());
|
||||
model.setUserSessionId(entity.getUserSessionId());
|
||||
model.setData(entity.getData());
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, UserModel user) {
|
||||
UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
|
||||
|
||||
List<OfflineClientSessionModel> result = new LinkedList<>();
|
||||
for (OfflineClientSessionEntity entity : userEntity.getOfflineClientSessions()) {
|
||||
result.add(toModel(entity));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId) {
|
||||
UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
|
||||
|
||||
OfflineClientSessionEntity found = null;
|
||||
for (OfflineClientSessionEntity session : userEntity.getOfflineClientSessions()) {
|
||||
if (session.getClientSessionId().equals(clientSessionId)) {
|
||||
found = session;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found == null) {
|
||||
return false;
|
||||
} else {
|
||||
userEntity.getOfflineClientSessions().remove(found);
|
||||
em.remove(found);
|
||||
em.flush();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOfflineClientSessionsCount(RealmModel realm, ClientModel client) {
|
||||
Query query = em.createNamedQuery("findOfflineClientSessionsCountByClient");
|
||||
query.setParameter("clientId", client.getId());
|
||||
Number n = (Number) query.getSingleResult();
|
||||
return n.intValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
|
||||
TypedQuery<OfflineClientSessionEntity> query = em.createNamedQuery("findOfflineClientSessionsByClient", OfflineClientSessionEntity.class);
|
||||
query.setParameter("clientId", client.getId());
|
||||
|
||||
if (firstResult != -1) {
|
||||
query.setFirstResult(firstResult);
|
||||
}
|
||||
if (maxResults != -1) {
|
||||
query.setMaxResults(maxResults);
|
||||
}
|
||||
|
||||
List<OfflineClientSessionEntity> results = query.getResultList();
|
||||
Set<OfflineClientSessionModel> set = new HashSet<>();
|
||||
for (OfflineClientSessionEntity entity : results) {
|
||||
set.add(toModel(entity));
|
||||
}
|
||||
return set;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -755,124 +755,6 @@ public class UserAdapter implements UserModel {
|
|||
em.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineUserSession(OfflineUserSessionModel offlineSession) {
|
||||
OfflineUserSessionEntity entity = new OfflineUserSessionEntity();
|
||||
entity.setUser(user);
|
||||
entity.setUserSessionId(offlineSession.getUserSessionId());
|
||||
entity.setData(offlineSession.getData());
|
||||
em.persist(entity);
|
||||
user.getOfflineUserSessions().add(entity);
|
||||
em.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineUserSessionModel getOfflineUserSession(String userSessionId) {
|
||||
for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
|
||||
if (entity.getUserSessionId().equals(userSessionId)) {
|
||||
return toModel(entity);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private OfflineUserSessionModel toModel(OfflineUserSessionEntity entity) {
|
||||
OfflineUserSessionModel model = new OfflineUserSessionModel();
|
||||
model.setUserSessionId(entity.getUserSessionId());
|
||||
model.setData(entity.getData());
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineUserSessionModel> getOfflineUserSessions() {
|
||||
List<OfflineUserSessionModel> result = new LinkedList<>();
|
||||
for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
|
||||
result.add(toModel(entity));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOfflineUserSession(String userSessionId) {
|
||||
OfflineUserSessionEntity found = null;
|
||||
for (OfflineUserSessionEntity session : user.getOfflineUserSessions()) {
|
||||
if (session.getUserSessionId().equals(userSessionId)) {
|
||||
found = session;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found == null) {
|
||||
return false;
|
||||
} else {
|
||||
user.getOfflineUserSessions().remove(found);
|
||||
em.remove(found);
|
||||
em.flush();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineClientSession(OfflineClientSessionModel clientSession) {
|
||||
OfflineClientSessionEntity entity = new OfflineClientSessionEntity();
|
||||
entity.setUser(user);
|
||||
entity.setClientSessionId(clientSession.getClientSessionId());
|
||||
entity.setUserSessionId(clientSession.getUserSessionId());
|
||||
entity.setClientId(clientSession.getClientId());
|
||||
entity.setData(clientSession.getData());
|
||||
em.persist(entity);
|
||||
user.getOfflineClientSessions().add(entity);
|
||||
em.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineClientSessionModel getOfflineClientSession(String clientSessionId) {
|
||||
for (OfflineClientSessionEntity entity : user.getOfflineClientSessions()) {
|
||||
if (entity.getClientSessionId().equals(clientSessionId)) {
|
||||
return toModel(entity);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private OfflineClientSessionModel toModel(OfflineClientSessionEntity entity) {
|
||||
OfflineClientSessionModel model = new OfflineClientSessionModel();
|
||||
model.setClientSessionId(entity.getClientSessionId());
|
||||
model.setClientId(entity.getClientId());
|
||||
model.setUserSessionId(entity.getUserSessionId());
|
||||
model.setData(entity.getData());
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineClientSessionModel> getOfflineClientSessions() {
|
||||
List<OfflineClientSessionModel> result = new LinkedList<>();
|
||||
for (OfflineClientSessionEntity entity : user.getOfflineClientSessions()) {
|
||||
result.add(toModel(entity));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOfflineClientSession(String clientSessionId) {
|
||||
OfflineClientSessionEntity found = null;
|
||||
for (OfflineClientSessionEntity session : user.getOfflineClientSessions()) {
|
||||
if (session.getClientSessionId().equals(clientSessionId)) {
|
||||
found = session;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found == null) {
|
||||
return false;
|
||||
} else {
|
||||
user.getOfflineClientSessions().remove(found);
|
||||
em.remove(found);
|
||||
em.flush();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -16,7 +16,9 @@ import javax.persistence.Table;
|
|||
@NamedQueries({
|
||||
@NamedQuery(name="deleteOfflineClientSessionsByRealm", query="delete from OfflineClientSessionEntity sess where sess.user IN (select u from UserEntity u where u.realmId=:realmId)"),
|
||||
@NamedQuery(name="deleteOfflineClientSessionsByRealmAndLink", query="delete from OfflineClientSessionEntity sess where sess.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)"),
|
||||
@NamedQuery(name="deleteOfflineClientSessionsByClient", query="delete from OfflineClientSessionEntity sess where sess.clientId=:clientId")
|
||||
@NamedQuery(name="deleteOfflineClientSessionsByClient", query="delete from OfflineClientSessionEntity sess where sess.clientId=:clientId"),
|
||||
@NamedQuery(name="findOfflineClientSessionsCountByClient", query="select count(sess) from OfflineClientSessionEntity sess where sess.clientId=:clientId"),
|
||||
@NamedQuery(name="findOfflineClientSessionsByClient", query="select sess from OfflineClientSessionEntity sess where sess.clientId=:clientId order by sess.user.username")
|
||||
})
|
||||
@Table(name="OFFLINE_CLIENT_SESSION")
|
||||
@Entity
|
||||
|
|
|
@ -10,6 +10,10 @@ import org.keycloak.models.ClientModel;
|
|||
import org.keycloak.models.CredentialValidationOutput;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.OfflineClientSessionModel;
|
||||
import org.keycloak.models.OfflineUserSessionModel;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RequiredActionProviderModel;
|
||||
|
@ -26,8 +30,10 @@ import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
|
|||
import org.keycloak.models.utils.CredentialValidation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -51,7 +57,7 @@ public class MongoUserProvider implements UserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserById(String id, RealmModel realm) {
|
||||
public UserAdapter getUserById(String id, RealmModel realm) {
|
||||
MongoUserEntity user = getMongoStore().loadEntity(MongoUserEntity.class, id, invocationContext);
|
||||
|
||||
// Check that it's user from this realm
|
||||
|
@ -244,8 +250,8 @@ public class MongoUserProvider implements UserProvider {
|
|||
|
||||
@Override
|
||||
public Set<FederatedIdentityModel> getFederatedIdentities(UserModel userModel, RealmModel realm) {
|
||||
UserModel user = getUserById(userModel.getId(), realm);
|
||||
MongoUserEntity userEntity = ((UserAdapter) user).getUser();
|
||||
UserAdapter user = getUserById(userModel.getId(), realm);
|
||||
MongoUserEntity userEntity = user.getUser();
|
||||
List<FederatedIdentityEntity> linkEntities = userEntity.getFederatedIdentities();
|
||||
|
||||
if (linkEntities == null) {
|
||||
|
@ -263,8 +269,8 @@ public class MongoUserProvider implements UserProvider {
|
|||
|
||||
@Override
|
||||
public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) {
|
||||
user = getUserById(user.getId(), realm);
|
||||
MongoUserEntity userEntity = ((UserAdapter) user).getUser();
|
||||
UserAdapter mongoUser = getUserById(user.getId(), realm);
|
||||
MongoUserEntity userEntity = mongoUser.getUser();
|
||||
FederatedIdentityEntity federatedIdentityEntity = findFederatedIdentityLink(userEntity, socialProvider);
|
||||
|
||||
return federatedIdentityEntity != null ? new FederatedIdentityModel(federatedIdentityEntity.getIdentityProvider(), federatedIdentityEntity.getUserId(),
|
||||
|
@ -320,8 +326,8 @@ public class MongoUserProvider implements UserProvider {
|
|||
|
||||
@Override
|
||||
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel identity) {
|
||||
user = getUserById(user.getId(), realm);
|
||||
MongoUserEntity userEntity = ((UserAdapter) user).getUser();
|
||||
UserAdapter mongoUser = getUserById(user.getId(), realm);
|
||||
MongoUserEntity userEntity = mongoUser.getUser();
|
||||
FederatedIdentityEntity federatedIdentityEntity = new FederatedIdentityEntity();
|
||||
federatedIdentityEntity.setIdentityProvider(identity.getIdentityProvider());
|
||||
federatedIdentityEntity.setUserId(identity.getUserId());
|
||||
|
@ -333,8 +339,8 @@ public class MongoUserProvider implements UserProvider {
|
|||
|
||||
@Override
|
||||
public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) {
|
||||
federatedUser = getUserById(federatedUser.getId(), realm);
|
||||
MongoUserEntity userEntity = ((UserAdapter) federatedUser).getUser();
|
||||
UserAdapter mongoUser = getUserById(federatedUser.getId(), realm);
|
||||
MongoUserEntity userEntity = mongoUser.getUser();
|
||||
FederatedIdentityEntity federatedIdentityEntity = findFederatedIdentityLink(userEntity, federatedIdentityModel.getIdentityProvider());
|
||||
|
||||
federatedIdentityEntity.setToken(federatedIdentityModel.getToken());
|
||||
|
@ -342,8 +348,8 @@ public class MongoUserProvider implements UserProvider {
|
|||
|
||||
@Override
|
||||
public boolean removeFederatedIdentity(RealmModel realm, UserModel userModel, String socialProvider) {
|
||||
UserModel user = getUserById(userModel.getId(), realm);
|
||||
MongoUserEntity userEntity = ((UserAdapter) user).getUser();
|
||||
UserAdapter user = getUserById(userModel.getId(), realm);
|
||||
MongoUserEntity userEntity = user.getUser();
|
||||
FederatedIdentityEntity federatedIdentityEntity = findFederatedIdentityLink(userEntity, socialProvider);
|
||||
if (federatedIdentityEntity == null) {
|
||||
return false;
|
||||
|
@ -476,4 +482,205 @@ public class MongoUserProvider implements UserProvider {
|
|||
// Not supported yet
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineUserSession(RealmModel realm, UserModel userModel, OfflineUserSessionModel userSession) {
|
||||
MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
|
||||
|
||||
if (user.getOfflineUserSessions() == null) {
|
||||
user.setOfflineUserSessions(new ArrayList<OfflineUserSessionEntity>());
|
||||
}
|
||||
|
||||
if (getUserSessionEntityById(user, userSession.getUserSessionId()) != null) {
|
||||
throw new ModelDuplicateException("User session already exists with id " + userSession.getUserSessionId() + " for user " + user.getUsername());
|
||||
}
|
||||
|
||||
OfflineUserSessionEntity entity = new OfflineUserSessionEntity();
|
||||
entity.setUserSessionId(userSession.getUserSessionId());
|
||||
entity.setData(userSession.getData());
|
||||
entity.setOfflineClientSessions(new ArrayList<OfflineClientSessionEntity>());
|
||||
user.getOfflineUserSessions().add(entity);
|
||||
|
||||
getMongoStore().updateEntity(user, invocationContext);
|
||||
}
|
||||
|
||||
private OfflineUserSessionModel toModel(OfflineUserSessionEntity entity) {
|
||||
OfflineUserSessionModel model = new OfflineUserSessionModel();
|
||||
model.setUserSessionId(entity.getUserSessionId());
|
||||
model.setData(entity.getData());
|
||||
return model;
|
||||
}
|
||||
|
||||
private OfflineUserSessionEntity getUserSessionEntityById(MongoUserEntity user, String userSessionId) {
|
||||
if (user.getOfflineUserSessions() != null) {
|
||||
for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
|
||||
if (entity.getUserSessionId().equals(userSessionId)) {
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public OfflineUserSessionModel getOfflineUserSession(RealmModel realm, UserModel userModel, String userSessionId) {
|
||||
MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
|
||||
|
||||
OfflineUserSessionEntity entity = getUserSessionEntityById(user, userSessionId);
|
||||
return entity==null ? null : toModel(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineUserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel userModel) {
|
||||
MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
|
||||
|
||||
if (user.getOfflineUserSessions()==null) {
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
List<OfflineUserSessionModel> result = new ArrayList<>();
|
||||
for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
|
||||
result.add(toModel(entity));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOfflineUserSession(RealmModel realm, UserModel userModel, String userSessionId) {
|
||||
MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
|
||||
|
||||
OfflineUserSessionEntity entity = getUserSessionEntityById(user, userSessionId);
|
||||
if (entity != null) {
|
||||
user.getOfflineUserSessions().remove(entity);
|
||||
getMongoStore().updateEntity(user, invocationContext);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineClientSession(RealmModel realm, OfflineClientSessionModel clientSession) {
|
||||
MongoUserEntity user = getUserById(clientSession.getUserId(), realm).getUser();
|
||||
|
||||
OfflineUserSessionEntity userSessionEntity = getUserSessionEntityById(user, clientSession.getUserSessionId());
|
||||
if (userSessionEntity == null) {
|
||||
throw new ModelException("OfflineUserSession with ID " + clientSession.getUserSessionId() + " doesn't exist for user " + user.getUsername());
|
||||
}
|
||||
|
||||
OfflineClientSessionEntity clEntity = new OfflineClientSessionEntity();
|
||||
clEntity.setClientSessionId(clientSession.getClientSessionId());
|
||||
clEntity.setClientId(clientSession.getClientId());
|
||||
clEntity.setData(clientSession.getData());
|
||||
|
||||
userSessionEntity.getOfflineClientSessions().add(clEntity);
|
||||
getMongoStore().updateEntity(user, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineClientSessionModel getOfflineClientSession(RealmModel realm, UserModel userModel, String clientSessionId) {
|
||||
MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
|
||||
|
||||
if (user.getOfflineUserSessions() != null) {
|
||||
for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
|
||||
for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
|
||||
if (clSession.getClientSessionId().equals(clientSessionId)) {
|
||||
return toModel(clSession, user.getId(), userSession.getUserSessionId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private OfflineClientSessionModel toModel(OfflineClientSessionEntity cls, String userId, String userSessionId) {
|
||||
OfflineClientSessionModel model = new OfflineClientSessionModel();
|
||||
model.setClientSessionId(cls.getClientSessionId());
|
||||
model.setClientId(cls.getClientId());
|
||||
model.setUserId(userId);
|
||||
model.setData(cls.getData());
|
||||
model.setUserSessionId(userSessionId);
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, UserModel userModel) {
|
||||
MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
|
||||
|
||||
List<OfflineClientSessionModel> result = new ArrayList<>();
|
||||
|
||||
if (user.getOfflineUserSessions() != null) {
|
||||
for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
|
||||
for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
|
||||
result.add(toModel(clSession, user.getId(), userSession.getUserSessionId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOfflineClientSession(RealmModel realm, UserModel userModel, String clientSessionId) {
|
||||
MongoUserEntity user = getUserById(userModel.getId(), realm).getUser();
|
||||
boolean updated = false;
|
||||
|
||||
if (user.getOfflineUserSessions() != null) {
|
||||
for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
|
||||
for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
|
||||
if (clSession.getClientSessionId().equals(clientSessionId)) {
|
||||
userSession.getOfflineClientSessions().remove(clSession);
|
||||
updated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (updated && userSession.getOfflineClientSessions().isEmpty()) {
|
||||
user.getOfflineUserSessions().remove(userSession);
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
getMongoStore().updateEntity(user, invocationContext);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOfflineClientSessionsCount(RealmModel realm, ClientModel client) {
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("realmId").is(realm.getId())
|
||||
.and("offlineUserSessions.offlineClientSessions.clientId").is(client.getId())
|
||||
.get();
|
||||
return getMongoStore().countEntities(MongoUserEntity.class, query, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineClientSessionModel> getOfflineClientSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
|
||||
DBObject query = new QueryBuilder()
|
||||
.and("realmId").is(realm.getId())
|
||||
.and("offlineUserSessions.offlineClientSessions.clientId").is(client.getId())
|
||||
.get();
|
||||
DBObject sort = new BasicDBObject("username", 1);
|
||||
List<MongoUserEntity> users = getMongoStore().loadEntities(MongoUserEntity.class, query, sort, firstResult, maxResults, invocationContext);
|
||||
|
||||
List<OfflineClientSessionModel> result = new LinkedList<>();
|
||||
for (MongoUserEntity user : users) {
|
||||
for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
|
||||
for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
|
||||
if (clSession.getClientId().equals(client.getId())) {
|
||||
OfflineClientSessionModel model = toModel(clSession, user.getId(), userSession.getUserSessionId());
|
||||
result.add(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -632,145 +632,6 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
|||
return getMongoStore().removeEntity(entity, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineUserSession(OfflineUserSessionModel userSession) {
|
||||
if (user.getOfflineUserSessions() == null) {
|
||||
user.setOfflineUserSessions(new ArrayList<OfflineUserSessionEntity>());
|
||||
}
|
||||
|
||||
if (getUserSessionEntityById(userSession.getUserSessionId()) != null) {
|
||||
throw new ModelDuplicateException("User session already exists with id " + userSession.getUserSessionId() + " for user " + getMongoEntity().getUsername());
|
||||
}
|
||||
|
||||
OfflineUserSessionEntity entity = new OfflineUserSessionEntity();
|
||||
entity.setUserSessionId(userSession.getUserSessionId());
|
||||
entity.setData(userSession.getData());
|
||||
entity.setOfflineClientSessions(new ArrayList<OfflineClientSessionEntity>());
|
||||
user.getOfflineUserSessions().add(entity);
|
||||
updateUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineUserSessionModel getOfflineUserSession(String userSessionId) {
|
||||
OfflineUserSessionEntity entity = getUserSessionEntityById(userSessionId);
|
||||
return entity==null ? null : toModel(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineUserSessionModel> getOfflineUserSessions() {
|
||||
if (user.getOfflineUserSessions()==null) {
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
List<OfflineUserSessionModel> result = new ArrayList<>();
|
||||
for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
|
||||
result.add(toModel(entity));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private OfflineUserSessionModel toModel(OfflineUserSessionEntity entity) {
|
||||
OfflineUserSessionModel model = new OfflineUserSessionModel();
|
||||
model.setUserSessionId(entity.getUserSessionId());
|
||||
model.setData(entity.getData());
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOfflineUserSession(String userSessionId) {
|
||||
OfflineUserSessionEntity entity = getUserSessionEntityById(userSessionId);
|
||||
if (entity != null) {
|
||||
user.getOfflineUserSessions().remove(entity);
|
||||
updateUser();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private OfflineUserSessionEntity getUserSessionEntityById(String userSessionId) {
|
||||
if (user.getOfflineUserSessions() != null) {
|
||||
for (OfflineUserSessionEntity entity : user.getOfflineUserSessions()) {
|
||||
if (entity.getUserSessionId().equals(userSessionId)) {
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOfflineClientSession(OfflineClientSessionModel clientSession) {
|
||||
OfflineUserSessionEntity userSessionEntity = getUserSessionEntityById(clientSession.getUserSessionId());
|
||||
if (userSessionEntity == null) {
|
||||
throw new ModelException("OfflineUserSession with ID " + clientSession.getUserSessionId() + " doesn't exist for user " + getMongoEntity().getUsername());
|
||||
}
|
||||
|
||||
OfflineClientSessionEntity clEntity = new OfflineClientSessionEntity();
|
||||
clEntity.setClientSessionId(clientSession.getClientSessionId());
|
||||
clEntity.setClientId(clientSession.getClientId());
|
||||
clEntity.setData(clientSession.getData());
|
||||
|
||||
userSessionEntity.getOfflineClientSessions().add(clEntity);
|
||||
updateUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineClientSessionModel getOfflineClientSession(String clientSessionId) {
|
||||
if (user.getOfflineUserSessions() != null) {
|
||||
for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
|
||||
for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
|
||||
if (clSession.getClientSessionId().equals(clientSessionId)) {
|
||||
return toModel(clSession, userSession.getUserSessionId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private OfflineClientSessionModel toModel(OfflineClientSessionEntity cls, String userSessionId) {
|
||||
OfflineClientSessionModel model = new OfflineClientSessionModel();
|
||||
model.setClientSessionId(cls.getClientSessionId());
|
||||
model.setClientId(cls.getClientId());
|
||||
model.setData(cls.getData());
|
||||
model.setUserSessionId(userSessionId);
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<OfflineClientSessionModel> getOfflineClientSessions() {
|
||||
List<OfflineClientSessionModel> result = new ArrayList<>();
|
||||
|
||||
if (user.getOfflineUserSessions() != null) {
|
||||
for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
|
||||
for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
|
||||
result.add(toModel(clSession, userSession.getUserSessionId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOfflineClientSession(String clientSessionId) {
|
||||
if (user.getOfflineUserSessions() != null) {
|
||||
for (OfflineUserSessionEntity userSession : user.getOfflineUserSessions()) {
|
||||
for (OfflineClientSessionEntity clSession : userSession.getOfflineClientSessions()) {
|
||||
if (clSession.getClientSessionId().equals(clientSessionId)) {
|
||||
userSession.getOfflineClientSessions().remove(clSession);
|
||||
updateUser();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
8
pom.xml
8
pom.xml
|
@ -18,6 +18,10 @@
|
|||
<packaging>pom</packaging>
|
||||
|
||||
<properties>
|
||||
<product.name>Keycloak</product.name>
|
||||
<product.version>${project.version}</product.version>
|
||||
<product.build-time>${timestamp}</product.build-time>
|
||||
|
||||
<apacheds.version>2.0.0-M17</apacheds.version>
|
||||
<apacheds.codec.version>1.0.0-M23</apacheds.codec.version>
|
||||
<org.apache.james.apache-mime4j.version>0.6</org.apache.james.apache-mime4j.version>
|
||||
|
@ -56,8 +60,8 @@
|
|||
<google.client.version>1.14.1-beta</google.client.version>
|
||||
<github.relaxng.version>2011.1</github.relaxng.version>
|
||||
<winzipaes.version>1.0.1</winzipaes.version>
|
||||
<freemarker.version>2.3.20</freemarker.version>
|
||||
<twitter4j.version>3.0.5</twitter4j.version>
|
||||
<freemarker.version>2.3.23</freemarker.version>
|
||||
<twitter4j.version>4.0.4</twitter4j.version>
|
||||
<selenium.version>2.35.0</selenium.version>
|
||||
<sun.istack.version>2.21</sun.istack.version>
|
||||
<sun.jaxb.version>2.2.11</sun.jaxb.version>
|
||||
|
|
|
@ -98,7 +98,7 @@ public class TokenManager {
|
|||
ClientSessionModel clientSession = null;
|
||||
if (RefreshTokenUtil.TOKEN_TYPE_OFFLINE.equals(oldToken.getType())) {
|
||||
|
||||
clientSession = OfflineTokenUtils.findOfflineClientSession(realm, user, oldToken.getClientSession(), oldToken.getSessionState());
|
||||
clientSession = OfflineTokenUtils.findOfflineClientSession(session, realm, user, oldToken.getClientSession(), oldToken.getSessionState());
|
||||
if (clientSession != null) {
|
||||
userSession = clientSession.getUserSession();
|
||||
}
|
||||
|
@ -496,7 +496,7 @@ public class TokenManager {
|
|||
|
||||
refreshToken = new RefreshToken(accessToken);
|
||||
refreshToken.type(RefreshTokenUtil.TOKEN_TYPE_OFFLINE);
|
||||
OfflineTokenUtils.persistOfflineSession(clientSession, userSession);
|
||||
OfflineTokenUtils.persistOfflineSession(session, realm, clientSession, userSession);
|
||||
} else {
|
||||
refreshToken = new RefreshToken(accessToken);
|
||||
refreshToken.expiration(Time.currentTime() + realm.getSsoSessionIdleTimeout());
|
||||
|
|
|
@ -75,7 +75,7 @@ public class OfflineClientSessionAdapter implements ClientSessionModel {
|
|||
|
||||
@Override
|
||||
public String getRedirectUri() {
|
||||
return data.getRedirectUri();
|
||||
return getData().getRedirectUri();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -85,7 +85,7 @@ public class OfflineClientSessionAdapter implements ClientSessionModel {
|
|||
|
||||
@Override
|
||||
public int getTimestamp() {
|
||||
return 0;
|
||||
return getData().getTimestamp();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -238,6 +238,9 @@ public class OfflineClientSessionAdapter implements ClientSessionModel {
|
|||
@JsonProperty("authenticatorStatus")
|
||||
private Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus = new HashMap<>();
|
||||
|
||||
@JsonProperty("timestamp")
|
||||
private int timestamp;
|
||||
|
||||
public String getAuthMethod() {
|
||||
return authMethod;
|
||||
}
|
||||
|
@ -285,5 +288,13 @@ public class OfflineClientSessionAdapter implements ClientSessionModel {
|
|||
public void setAuthenticatorStatus(Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus) {
|
||||
this.authenticatorStatus = authenticatorStatus;
|
||||
}
|
||||
|
||||
public int getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(int timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.jboss.logging.Logger;
|
|||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.OfflineClientSessionModel;
|
||||
import org.keycloak.models.OfflineUserSessionModel;
|
||||
|
@ -17,9 +18,9 @@ import org.keycloak.models.RoleModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
/**
|
||||
* TODO: Change to utils?
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
|
@ -27,12 +28,12 @@ public class OfflineTokenUtils {
|
|||
|
||||
protected static Logger logger = Logger.getLogger(OfflineTokenUtils.class);
|
||||
|
||||
public static void persistOfflineSession(ClientSessionModel clientSession, UserSessionModel userSession) {
|
||||
public static void persistOfflineSession(KeycloakSession kcSession, RealmModel realm, ClientSessionModel clientSession, UserSessionModel userSession) {
|
||||
UserModel user = userSession.getUser();
|
||||
ClientModel client = clientSession.getClient();
|
||||
|
||||
// First verify if we already have offlineToken for this user+client . If yes, then invalidate it (This is to avoid leaks)
|
||||
Collection<OfflineClientSessionModel> clientSessions = user.getOfflineClientSessions();
|
||||
Collection<OfflineClientSessionModel> clientSessions = kcSession.users().getOfflineClientSessions(realm, user);
|
||||
for (OfflineClientSessionModel existing : clientSessions) {
|
||||
if (existing.getClientId().equals(client.getId())) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
|
@ -40,28 +41,28 @@ public class OfflineTokenUtils {
|
|||
user.getUsername(), client.getClientId(), existing.getClientSessionId());
|
||||
}
|
||||
|
||||
user.removeOfflineClientSession(existing.getClientSessionId());
|
||||
kcSession.users().removeOfflineClientSession(realm, user, existing.getClientSessionId());
|
||||
|
||||
// Check if userSession is ours. If not, then check if it has other clientSessions and remove it otherwise
|
||||
if (!existing.getUserSessionId().equals(userSession.getId())) {
|
||||
checkUserSessionHasClientSessions(user, existing.getUserSessionId());
|
||||
checkUserSessionHasClientSessions(kcSession, realm, user, existing.getUserSessionId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify if we already have UserSession with this ID. If yes, don't create another one
|
||||
OfflineUserSessionModel userSessionRep = user.getOfflineUserSession(userSession.getId());
|
||||
OfflineUserSessionModel userSessionRep = kcSession.users().getOfflineUserSession(realm, user, userSession.getId());
|
||||
if (userSessionRep == null) {
|
||||
createOfflineUserSession(user, userSession);
|
||||
createOfflineUserSession(kcSession, realm, user, userSession);
|
||||
}
|
||||
|
||||
// Create clientRep and save to DB.
|
||||
createOfflineClientSession(user, clientSession, userSession);
|
||||
createOfflineClientSession(kcSession, realm, user, clientSession, userSession);
|
||||
}
|
||||
|
||||
// userSessionId is provided from offline token. It's used just to verify if it match the ID from clientSession representation
|
||||
public static ClientSessionModel findOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId, String userSessionId) {
|
||||
OfflineClientSessionModel clientSession = user.getOfflineClientSession(clientSessionId);
|
||||
public static ClientSessionModel findOfflineClientSession(KeycloakSession kcSession, RealmModel realm, UserModel user, String clientSessionId, String userSessionId) {
|
||||
OfflineClientSessionModel clientSession = kcSession.users().getOfflineClientSession(realm, user, clientSessionId);
|
||||
if (clientSession == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -71,7 +72,7 @@ public class OfflineTokenUtils {
|
|||
" Wanted user session: " + userSessionId);
|
||||
}
|
||||
|
||||
OfflineUserSessionModel userSession = user.getOfflineUserSession(userSessionId);
|
||||
OfflineUserSessionModel userSession = kcSession.users().getOfflineUserSession(realm, user, userSessionId);
|
||||
if (userSession == null) {
|
||||
throw new ModelException("Found clientSession " + clientSessionId + " but not userSession " + userSessionId);
|
||||
}
|
||||
|
@ -79,13 +80,11 @@ public class OfflineTokenUtils {
|
|||
OfflineUserSessionAdapter userSessionAdapter = new OfflineUserSessionAdapter(userSession, user);
|
||||
|
||||
ClientModel client = realm.getClientById(clientSession.getClientId());
|
||||
OfflineClientSessionAdapter clientSessionAdapter = new OfflineClientSessionAdapter(clientSession, realm, client, userSessionAdapter);
|
||||
|
||||
return clientSessionAdapter;
|
||||
return new OfflineClientSessionAdapter(clientSession, realm, client, userSessionAdapter);
|
||||
}
|
||||
|
||||
public static Set<ClientModel> findClientsWithOfflineToken(RealmModel realm, UserModel user) {
|
||||
Collection<OfflineClientSessionModel> clientSessions = user.getOfflineClientSessions();
|
||||
public static Set<ClientModel> findClientsWithOfflineToken(KeycloakSession kcSession, RealmModel realm, UserModel user) {
|
||||
Collection<OfflineClientSessionModel> clientSessions = kcSession.users().getOfflineClientSessions(realm, user);
|
||||
Set<ClientModel> clients = new HashSet<>();
|
||||
for (OfflineClientSessionModel clientSession : clientSessions) {
|
||||
ClientModel client = realm.getClientById(clientSession.getClientId());
|
||||
|
@ -94,8 +93,8 @@ public class OfflineTokenUtils {
|
|||
return clients;
|
||||
}
|
||||
|
||||
public static boolean revokeOfflineToken(UserModel user, ClientModel client) {
|
||||
Collection<OfflineClientSessionModel> clientSessions = user.getOfflineClientSessions();
|
||||
public static boolean revokeOfflineToken(KeycloakSession kcSession, RealmModel realm, UserModel user, ClientModel client) {
|
||||
Collection<OfflineClientSessionModel> clientSessions = kcSession.users().getOfflineClientSessions(realm, user);
|
||||
boolean anyRemoved = false;
|
||||
for (OfflineClientSessionModel clientSession : clientSessions) {
|
||||
if (clientSession.getClientId().equals(client.getId())) {
|
||||
|
@ -104,8 +103,8 @@ public class OfflineTokenUtils {
|
|||
user.getUsername(), client.getClientId(), clientSession.getClientSessionId());
|
||||
}
|
||||
|
||||
user.removeOfflineClientSession(clientSession.getClientSessionId());
|
||||
checkUserSessionHasClientSessions(user, clientSession.getUserSessionId());
|
||||
kcSession.users().removeOfflineClientSession(realm, user, clientSession.getClientSessionId());
|
||||
checkUserSessionHasClientSessions(kcSession, realm, user, clientSession.getUserSessionId());
|
||||
anyRemoved = true;
|
||||
}
|
||||
}
|
||||
|
@ -123,7 +122,7 @@ public class OfflineTokenUtils {
|
|||
return clientSession.getRoles().contains(offlineAccessRole.getId());
|
||||
}
|
||||
|
||||
private static void createOfflineUserSession(UserModel user, UserSessionModel userSession) {
|
||||
private static void createOfflineUserSession(KeycloakSession kcSession, RealmModel realm, UserModel user, UserSessionModel userSession) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracef("Creating new offline user session. UserSessionID: '%s' , Username: '%s'", userSession.getId(), user.getUsername());
|
||||
}
|
||||
|
@ -141,13 +140,13 @@ public class OfflineTokenUtils {
|
|||
OfflineUserSessionModel sessionModel = new OfflineUserSessionModel();
|
||||
sessionModel.setUserSessionId(userSession.getId());
|
||||
sessionModel.setData(stringRep);
|
||||
user.addOfflineUserSession(sessionModel);
|
||||
kcSession.users().addOfflineUserSession(realm, user, sessionModel);
|
||||
} catch (IOException ioe) {
|
||||
throw new ModelException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
private static void createOfflineClientSession(UserModel user, ClientSessionModel clientSession, UserSessionModel userSession) {
|
||||
private static void createOfflineClientSession(KeycloakSession kcSession, RealmModel realm, UserModel user, ClientSessionModel clientSession, UserSessionModel userSession) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracef("Creating new offline token client session. ClientSessionId: '%s', UserSessionID: '%s' , Username: '%s', Client: '%s'" ,
|
||||
clientSession.getId(), userSession.getId(), user.getUsername(), clientSession.getClient().getClientId());
|
||||
|
@ -159,23 +158,25 @@ public class OfflineTokenUtils {
|
|||
rep.setRoles(clientSession.getRoles());
|
||||
rep.setNotes(clientSession.getNotes());
|
||||
rep.setAuthenticatorStatus(clientSession.getExecutionStatus());
|
||||
rep.setTimestamp(Time.currentTime());
|
||||
|
||||
try {
|
||||
String stringRep = JsonSerialization.writeValueAsString(rep);
|
||||
OfflineClientSessionModel clsModel = new OfflineClientSessionModel();
|
||||
clsModel.setClientSessionId(clientSession.getId());
|
||||
clsModel.setClientId(clientSession.getClient().getId());
|
||||
clsModel.setUserId(user.getId());
|
||||
clsModel.setUserSessionId(userSession.getId());
|
||||
clsModel.setData(stringRep);
|
||||
user.addOfflineClientSession(clsModel);
|
||||
kcSession.users().addOfflineClientSession(realm, clsModel);
|
||||
} catch (IOException ioe) {
|
||||
throw new ModelException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if userSession has any offline clientSessions attached to it. Remove userSession if not
|
||||
private static void checkUserSessionHasClientSessions(UserModel user, String userSessionId) {
|
||||
Collection<OfflineClientSessionModel> clientSessions = user.getOfflineClientSessions();
|
||||
private static void checkUserSessionHasClientSessions(KeycloakSession kcSession, RealmModel realm, UserModel user, String userSessionId) {
|
||||
Collection<OfflineClientSessionModel> clientSessions = kcSession.users().getOfflineClientSessions(realm, user);
|
||||
|
||||
for (OfflineClientSessionModel clientSession : clientSessions) {
|
||||
if (clientSession.getUserSessionId().equals(userSessionId)) {
|
||||
|
@ -186,6 +187,6 @@ public class OfflineTokenUtils {
|
|||
if (logger.isTraceEnabled()) {
|
||||
logger.tracef("Removing offline userSession for user %s as it doesn't have any client sessions attached. UserSessionID: %s", user.getUsername(), userSessionId);
|
||||
}
|
||||
user.removeOfflineUserSession(userSessionId);
|
||||
kcSession.users().removeOfflineUserSession(realm, user, userSessionId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -486,7 +486,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
|||
// Revoke grant in UserModel
|
||||
UserModel user = auth.getUser();
|
||||
user.revokeConsentForClient(client.getId());
|
||||
OfflineTokenUtils.revokeOfflineToken(user, client);
|
||||
OfflineTokenUtils.revokeOfflineToken(session, realm, user, client);
|
||||
|
||||
// Logout clientSessions for this user and client
|
||||
AuthenticationManager.backchannelUserFromClient(session, realm, user, client, uriInfo, headers);
|
||||
|
|
|
@ -7,8 +7,11 @@ import org.jboss.resteasy.spi.NotFoundException;
|
|||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.OfflineClientSessionModel;
|
||||
import org.keycloak.models.OfflineUserSessionModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
@ -24,6 +27,8 @@ import org.keycloak.representations.idm.UserSessionRepresentation;
|
|||
import org.keycloak.services.managers.ClientManager;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.managers.ResourceAdminManager;
|
||||
import org.keycloak.services.offline.OfflineClientSessionAdapter;
|
||||
import org.keycloak.services.offline.OfflineUserSessionAdapter;
|
||||
import org.keycloak.services.resources.KeycloakApplication;
|
||||
import org.keycloak.services.ErrorResponse;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
@ -390,6 +395,65 @@ public class ClientResource {
|
|||
return sessions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get application offline session count
|
||||
*
|
||||
* Returns a number of offline user sessions associated with this client
|
||||
*
|
||||
* {
|
||||
* "count": number
|
||||
* }
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Path("offline-session-count")
|
||||
@GET
|
||||
@NoCache
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Map<String, Integer> getOfflineSessionCount() {
|
||||
auth.requireView();
|
||||
Map<String, Integer> map = new HashMap<String, Integer>();
|
||||
map.put("count", session.users().getOfflineClientSessionsCount(client.getRealm(), client));
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get offline sessions for client
|
||||
*
|
||||
* Returns a list of offline user sessions associated with this client
|
||||
*
|
||||
* @param firstResult Paging offset
|
||||
* @param maxResults Paging size
|
||||
* @return
|
||||
*/
|
||||
@Path("offline-sessions")
|
||||
@GET
|
||||
@NoCache
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public List<UserSessionRepresentation> getOfflineUserSessions(@QueryParam("first") Integer firstResult, @QueryParam("max") Integer maxResults) {
|
||||
auth.requireView();
|
||||
firstResult = firstResult != null ? firstResult : -1;
|
||||
maxResults = maxResults != null ? maxResults : -1;
|
||||
List<UserSessionRepresentation> sessions = new ArrayList<UserSessionRepresentation>();
|
||||
for (OfflineClientSessionModel offlineClientSession : session.users().getOfflineClientSessions(client.getRealm(), client, firstResult, maxResults)) {
|
||||
UserModel user = session.users().getUserById(offlineClientSession.getUserId(), client.getRealm());
|
||||
OfflineUserSessionModel offlineUserSession = session.users().getOfflineUserSession(client.getRealm(), user, offlineClientSession.getUserSessionId());
|
||||
OfflineUserSessionAdapter sessionAdapter = new OfflineUserSessionAdapter(offlineUserSession, user);
|
||||
OfflineClientSessionAdapter clientSessionAdapter = new OfflineClientSessionAdapter(offlineClientSession, client.getRealm(), client, sessionAdapter);
|
||||
|
||||
UserSessionRepresentation rep = new UserSessionRepresentation();
|
||||
rep.setId(sessionAdapter.getId());
|
||||
rep.setStart(Time.toMillis(clientSessionAdapter.getTimestamp()));
|
||||
rep.setUsername(user.getUsername());
|
||||
rep.setUserId(user.getId());
|
||||
rep.setIpAddress(sessionAdapter.getIpAddress());
|
||||
|
||||
sessions.add(rep);
|
||||
}
|
||||
return sessions;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Logout all sessions
|
||||
*
|
||||
|
|
|
@ -66,15 +66,18 @@ import javax.ws.rs.WebApplicationException;
|
|||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.keycloak.models.UsernameLoginFailureModel;
|
||||
import org.keycloak.services.managers.BruteForceProtector;
|
||||
import org.keycloak.services.offline.OfflineTokenUtils;
|
||||
import org.keycloak.services.resources.AccountService;
|
||||
|
||||
/**
|
||||
|
@ -439,25 +442,44 @@ public class UsersResource {
|
|||
@GET
|
||||
@NoCache
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public List<UserConsentRepresentation> getConsents(final @PathParam("id") String id) {
|
||||
public List<Map<String, Object>> getConsents(final @PathParam("id") String id) {
|
||||
auth.requireView();
|
||||
UserModel user = session.users().getUserById(id, realm);
|
||||
if (user == null) {
|
||||
throw new NotFoundException("User not found");
|
||||
}
|
||||
|
||||
List<UserConsentModel> consents = user.getConsents();
|
||||
List<UserConsentRepresentation> result = new ArrayList<UserConsentRepresentation>();
|
||||
List<Map<String, Object>> result = new LinkedList<>();
|
||||
|
||||
for (UserConsentModel consent : consents) {
|
||||
UserConsentRepresentation rep = ModelToRepresentation.toRepresentation(consent);
|
||||
result.add(rep);
|
||||
Set<ClientModel> offlineClients = OfflineTokenUtils.findClientsWithOfflineToken(session, realm, user);
|
||||
|
||||
for (ClientModel client : realm.getClients()) {
|
||||
UserConsentModel consent = user.getConsentByClient(client.getId());
|
||||
boolean hasOfflineToken = offlineClients.contains(client);
|
||||
|
||||
if (consent == null && !hasOfflineToken) {
|
||||
continue;
|
||||
}
|
||||
|
||||
UserConsentRepresentation rep = (consent == null) ? null : ModelToRepresentation.toRepresentation(consent);
|
||||
|
||||
Map<String, Object> currentRep = new HashMap<>();
|
||||
currentRep.put("clientId", client.getClientId());
|
||||
currentRep.put("grantedProtocolMappers", (rep==null ? Collections.emptyMap() : rep.getGrantedProtocolMappers()));
|
||||
currentRep.put("grantedRealmRoles", (rep==null ? Collections.emptyList() : rep.getGrantedRealmRoles()));
|
||||
currentRep.put("grantedClientRoles", (rep==null ? Collections.emptyMap() : rep.getGrantedClientRoles()));
|
||||
|
||||
List<String> additionalGrants = hasOfflineToken ? Arrays.asList("Offline Token") : Collections.<String>emptyList();
|
||||
currentRep.put("additionalGrants", additionalGrants);
|
||||
|
||||
result.add(currentRep);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke consent for particular client from user
|
||||
* Revoke consent and offline tokens for particular client from user
|
||||
*
|
||||
* @param id User id
|
||||
* @param clientId Client id
|
||||
|
@ -473,12 +495,16 @@ public class UsersResource {
|
|||
}
|
||||
|
||||
ClientModel client = realm.getClientByClientId(clientId);
|
||||
boolean revoked = user.revokeConsentForClient(client.getId());
|
||||
if (revoked) {
|
||||
boolean revokedConsent = user.revokeConsentForClient(client.getId());
|
||||
boolean revokedOfflineToken = OfflineTokenUtils.revokeOfflineToken(session, realm, user, client);
|
||||
|
||||
if (revokedConsent) {
|
||||
// Logout clientSessions for this user and client
|
||||
AuthenticationManager.backchannelUserFromClient(session, realm, user, client, uriInfo, headers);
|
||||
} else {
|
||||
throw new NotFoundException("Consent not found");
|
||||
}
|
||||
|
||||
if (!revokedConsent && !revokedOfflineToken) {
|
||||
throw new NotFoundException("Consent nor offline token not found");
|
||||
}
|
||||
adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
|
||||
}
|
||||
|
|
|
@ -17,37 +17,30 @@
|
|||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-social-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-broker-oidc</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-events-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-services</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.twitter4j</groupId>
|
||||
<artifactId>twitter4j-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ import org.keycloak.models.FederatedIdentityModel;
|
|||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.models.OfflineClientSessionModel;
|
||||
import org.keycloak.models.OfflineUserSessionModel;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RequiredCredentialModel;
|
||||
|
@ -32,6 +34,7 @@ import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
|
|||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -327,6 +330,21 @@ public class ImportTest extends AbstractModelTest {
|
|||
Assert.assertFalse(otherAppAdminConsent.isRoleGranted(application.getRole("app-admin")));
|
||||
Assert.assertTrue(otherAppAdminConsent.isProtocolMapperGranted(gssCredentialMapper));
|
||||
|
||||
// Test offline sessions
|
||||
Collection<OfflineUserSessionModel> offlineUserSessions = session.users().getOfflineUserSessions(realm, admin);
|
||||
Collection<OfflineClientSessionModel> offlineClientSessions = session.users().getOfflineClientSessions(realm, admin);
|
||||
Assert.assertEquals(offlineUserSessions.size(), 1);
|
||||
Assert.assertEquals(offlineClientSessions.size(), 1);
|
||||
OfflineUserSessionModel offlineSession = offlineUserSessions.iterator().next();
|
||||
OfflineClientSessionModel offlineClSession = offlineClientSessions.iterator().next();
|
||||
Assert.assertEquals(offlineSession.getData(), "something1");
|
||||
Assert.assertEquals(offlineSession.getUserSessionId(), "123");
|
||||
Assert.assertEquals(offlineClSession.getClientId(), otherApp.getId());
|
||||
Assert.assertEquals(offlineClSession.getUserSessionId(), "123");
|
||||
Assert.assertEquals(offlineClSession.getUserId(), admin.getId());
|
||||
Assert.assertEquals(offlineClSession.getData(), "something2");
|
||||
|
||||
|
||||
// Test service accounts
|
||||
Assert.assertFalse(application.isServiceAccountsEnabled());
|
||||
Assert.assertTrue(otherApp.isServiceAccountsEnabled());
|
||||
|
|
|
@ -15,6 +15,7 @@ import static org.junit.Assert.assertNotNull;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -292,12 +293,35 @@ public class UserModelTest extends AbstractModelTest {
|
|||
ClientModel barClient = realm.addClient("bar");
|
||||
|
||||
UserModel user1 = session.users().addUser(realm, "user1");
|
||||
addOfflineUserSession(user1, "123", "something1");
|
||||
addOfflineClientSession(user1, "456", "123", fooClient.getId(), "something2");
|
||||
addOfflineClientSession(user1, "789", "123", barClient.getId(), "something3");
|
||||
UserModel user2 = session.users().addUser(realm, "user2");
|
||||
|
||||
addOfflineUserSession(realm, user1, "123", "something1");
|
||||
addOfflineClientSession(realm, user1, "456", "123", fooClient.getId(), "something2");
|
||||
addOfflineClientSession(realm, user1, "789", "123", barClient.getId(), "something3");
|
||||
|
||||
addOfflineUserSession(realm, user2, "2123", "something4");
|
||||
addOfflineClientSession(realm, user2, "2456", "2123", fooClient.getId(), "something5");
|
||||
|
||||
commit();
|
||||
|
||||
// Searching by clients
|
||||
Assert.assertEquals(2, session.users().getOfflineClientSessionsCount(realm, fooClient));
|
||||
Assert.assertEquals(1, session.users().getOfflineClientSessionsCount(realm, barClient));
|
||||
|
||||
Collection<OfflineClientSessionModel> clientSessions = session.users().getOfflineClientSessions(realm, fooClient, 0, 10);
|
||||
Assert.assertEquals(2, clientSessions.size());
|
||||
clientSessions = session.users().getOfflineClientSessions(realm, fooClient, 0, 1);
|
||||
OfflineClientSessionModel cls = clientSessions.iterator().next();
|
||||
assertSessionEquals(cls, "456", "123", fooClient.getId(), user1.getId(), "something2");
|
||||
clientSessions = session.users().getOfflineClientSessions(realm, fooClient, 1, 1);
|
||||
cls = clientSessions.iterator().next();
|
||||
assertSessionEquals(cls, "2456", "2123", fooClient.getId(), user2.getId(), "something5");
|
||||
|
||||
clientSessions = session.users().getOfflineClientSessions(realm, barClient, 0, 10);
|
||||
Assert.assertEquals(1, clientSessions.size());
|
||||
cls = clientSessions.iterator().next();
|
||||
assertSessionEquals(cls, "789", "123", barClient.getId(), user1.getId(), "something3");
|
||||
|
||||
realm = realmManager.getRealmByName("original");
|
||||
realm.removeClient(barClient.getId());
|
||||
|
||||
|
@ -305,9 +329,9 @@ public class UserModelTest extends AbstractModelTest {
|
|||
|
||||
realm = realmManager.getRealmByName("original");
|
||||
user1 = session.users().getUserByUsername("user1", realm);
|
||||
Assert.assertEquals("something1", user1.getOfflineUserSession("123").getData());
|
||||
Assert.assertEquals("something2", user1.getOfflineClientSession("456").getData());
|
||||
Assert.assertNull(user1.getOfflineClientSession("789"));
|
||||
Assert.assertEquals("something1", session.users().getOfflineUserSession(realm, user1, "123").getData());
|
||||
Assert.assertEquals("something2", session.users().getOfflineClientSession(realm, user1, "456").getData());
|
||||
Assert.assertNull(session.users().getOfflineClientSession(realm, user1, "789"));
|
||||
|
||||
realm.removeClient(fooClient.getId());
|
||||
|
||||
|
@ -315,27 +339,28 @@ public class UserModelTest extends AbstractModelTest {
|
|||
|
||||
realm = realmManager.getRealmByName("original");
|
||||
user1 = session.users().getUserByUsername("user1", realm);
|
||||
Assert.assertNull(user1.getOfflineClientSession("456"));
|
||||
Assert.assertNull(user1.getOfflineClientSession("789"));
|
||||
Assert.assertNull(user1.getOfflineUserSession("123"));
|
||||
Assert.assertEquals(0, user1.getOfflineUserSessions().size());
|
||||
Assert.assertEquals(0, user1.getOfflineClientSessions().size());
|
||||
Assert.assertNull(session.users().getOfflineClientSession(realm, user1, "456"));
|
||||
Assert.assertNull(session.users().getOfflineClientSession(realm, user1, "789"));
|
||||
Assert.assertNull(session.users().getOfflineUserSession(realm, user1, "123"));
|
||||
Assert.assertEquals(0, session.users().getOfflineUserSessions(realm, user1).size());
|
||||
Assert.assertEquals(0, session.users().getOfflineClientSessions(realm, user1).size());
|
||||
}
|
||||
|
||||
private void addOfflineUserSession(UserModel user, String userSessionId, String data) {
|
||||
private void addOfflineUserSession(RealmModel realm, UserModel user, String userSessionId, String data) {
|
||||
OfflineUserSessionModel model = new OfflineUserSessionModel();
|
||||
model.setUserSessionId(userSessionId);
|
||||
model.setData(data);
|
||||
user.addOfflineUserSession(model);
|
||||
session.users().addOfflineUserSession(realm, user, model);
|
||||
}
|
||||
|
||||
private void addOfflineClientSession(UserModel user, String clientSessionId, String userSessionId, String clientId, String data) {
|
||||
private void addOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId, String userSessionId, String clientId, String data) {
|
||||
OfflineClientSessionModel model = new OfflineClientSessionModel();
|
||||
model.setClientSessionId(clientSessionId);
|
||||
model.setUserSessionId(userSessionId);
|
||||
model.setUserId(user.getId());
|
||||
model.setClientId(clientId);
|
||||
model.setData(data);
|
||||
user.addOfflineClientSession(model);
|
||||
session.users().addOfflineClientSession(realm, model);
|
||||
}
|
||||
|
||||
public static void assertEquals(UserModel expected, UserModel actual) {
|
||||
|
@ -352,5 +377,14 @@ public class UserModelTest extends AbstractModelTest {
|
|||
Assert.assertArrayEquals(expectedRequiredActions, actualRequiredActions);
|
||||
}
|
||||
|
||||
private static void assertSessionEquals(OfflineClientSessionModel cls, String expectedClientSessionId, String expectedUserSessionId,
|
||||
String expectedClientId, String expectedUserId, String expectedData) {
|
||||
Assert.assertEquals(cls.getData(), expectedData);
|
||||
Assert.assertEquals(cls.getClientSessionId(), expectedClientSessionId);
|
||||
Assert.assertEquals(cls.getUserSessionId(), expectedUserSessionId);
|
||||
Assert.assertEquals(cls.getUserId(), expectedUserId);
|
||||
Assert.assertEquals(cls.getClientId(), expectedClientId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -119,6 +119,19 @@
|
|||
"openid-connect": [ "gss delegation credential" ]
|
||||
}
|
||||
}
|
||||
],
|
||||
"offlineUserSessions": [
|
||||
{
|
||||
"userSessionId": "123",
|
||||
"data": "something1",
|
||||
"offlineClientSessions": [
|
||||
{
|
||||
"clientSessionId": "456",
|
||||
"client": "OtherApp",
|
||||
"data": "something2"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue