Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Bill Burke 2015-09-30 11:01:55 -04:00
commit 07d3b26722
62 changed files with 1943 additions and 599 deletions

View file

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

View file

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

View file

@ -36,6 +36,7 @@ public class UserRepresentation {
protected List<String> realmRoles; protected List<String> realmRoles;
protected Map<String, List<String>> clientRoles; protected Map<String, List<String>> clientRoles;
protected List<UserConsentRepresentation> clientConsents; protected List<UserConsentRepresentation> clientConsents;
protected List<OfflineUserSessionRepresentation> offlineUserSessions;
@Deprecated @Deprecated
protected Map<String, List<String>> applicationRoles; protected Map<String, List<String>> applicationRoles;
@ -218,4 +219,12 @@ public class UserRepresentation {
public void setServiceAccountClientId(String serviceAccountClientId) { public void setServiceAccountClientId(String serviceAccountClientId) {
this.serviceAccountClientId = serviceAccountClientId; this.serviceAccountClientId = serviceAccountClientId;
} }
public List<OfflineUserSessionRepresentation> getOfflineUserSessions() {
return offlineUserSessions;
}
public void setOfflineUserSessions(List<OfflineUserSessionRepresentation> offlineUserSessions) {
this.offlineUserSessions = offlineUserSessions;
}
} }

View file

@ -3,6 +3,7 @@ package org.keycloak.util;
import java.io.IOException; import java.io.IOException;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.representations.RefreshToken; 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 * @param decodedToken
* @return * @return
@ -39,9 +40,9 @@ public class RefreshTokenUtil {
return JsonSerialization.readValue(decodedToken, RefreshToken.class); return JsonSerialization.readValue(decodedToken, RefreshToken.class);
} }
private static RefreshToken getRefreshToken(String refreshToken) throws IOException { public static RefreshToken getRefreshToken(String refreshToken) throws IOException {
byte[] decodedToken = Base64Url.decode(refreshToken); byte[] encodedContent = new JWSInput(refreshToken).getContent();
return getRefreshToken(decodedToken); return getRefreshToken(encodedContent);
} }
/** /**

View file

@ -1,2 +1,2 @@
version=${project.version} version=${product.version}
build-time=${timestamp} build-time=${product.build-time}

View file

@ -30,11 +30,6 @@
<name>Keycloak Feature Pack: Server</name> <name>Keycloak Feature Pack: Server</name>
<packaging>pom</packaging> <packaging>pom</packaging>
<properties>
<product.name>Keycloak</product.name>
<product.version>${project.version}</product.version>
</properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>

View file

@ -1,4 +1,4 @@
<chapter id="timeouts"> <chapter id="timeouts">
<title>Cookie settings, Session Timeouts, and Token Lifespans</title> <title>Cookie settings, Session Timeouts, and Token Lifespans</title>
<para> <para>
Keycloak has a bunch of fine-grain settings to manage browser cookies, user login sessions, and token lifespans. 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. back to your application as an authentnicated user. This value is relatively short and is usually measured in minutes.
</para> </para>
</section> </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> </chapter>

View file

@ -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) [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 Admin Console
========================== ==========================

View file

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

View file

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

View file

@ -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();
}

View file

@ -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();
}
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,3 @@
<script>
window.location = "/offline-access-portal/app";
</script>

View file

@ -37,6 +37,7 @@
<module>third-party</module> <module>third-party</module>
<module>third-party-cdi</module> <module>third-party-cdi</module>
<module>service-account</module> <module>service-account</module>
<module>offline-access-app</module>
</modules> </modules>
<profiles> <profiles>

View file

@ -1,6 +1,9 @@
package org.keycloak.example; 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> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
public class ProductSAClientSignedJWTServlet extends ProductServiceAccountServlet { public class ProductSAClientSignedJWTServlet extends ProductServiceAccountServlet {

View file

@ -22,7 +22,7 @@
{ "type" : "password", { "type" : "password",
"value" : "password" } "value" : "password" }
], ],
"realmRoles": [ "user" ], "realmRoles": [ "user", "offline_access" ],
"clientRoles": { "clientRoles": {
"account": [ "manage-account" ] "account": [ "manage-account" ]
} }
@ -37,7 +37,7 @@
{ "type" : "password", { "type" : "password",
"value" : "password" } "value" : "password" }
], ],
"realmRoles": [ "user" ], "realmRoles": [ "user", "offline_access" ],
"clientRoles": { "clientRoles": {
"account": [ "manage-account" ] "account": [ "manage-account" ]
} }
@ -52,7 +52,7 @@
{ "type" : "password", { "type" : "password",
"value" : "password" } "value" : "password" }
], ],
"realmRoles": [ "user" ], "realmRoles": [ "user", "offline_access" ],
"clientRoles": { "clientRoles": {
"account": [ "manage-account" ] "account": [ "manage-account" ]
} }
@ -103,6 +103,10 @@
{ {
"client": "third-party", "client": "third-party",
"roles": ["user"] "roles": ["user"]
},
{
"client": "offline-access-portal",
"roles": ["user", "offline_access"]
} }
], ],
"clients": [ "clients": [
@ -190,6 +194,17 @@
"attributes": { "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==" "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": { "clientScopeMappings": {

View file

@ -1,5 +1,8 @@
package org.keycloak.exportimport.util; 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.keycloak.util.Base64;
import org.codehaus.jackson.JsonEncoding; import org.codehaus.jackson.JsonEncoding;
import org.codehaus.jackson.JsonFactory; 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; return userRep;
} }

View file

@ -202,7 +202,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
attributes.put("sessions", new SessionsBean(realm, sessions)); attributes.put("sessions", new SessionsBean(realm, sessions));
break; break;
case APPLICATIONS: case APPLICATIONS:
attributes.put("applications", new ApplicationsBean(realm, user)); attributes.put("applications", new ApplicationsBean(session, realm, user));
attributes.put("advancedMsg", new AdvancedMessageFormatterMethod(locale, messagesBundle)); attributes.put("advancedMsg", new AdvancedMessageFormatterMethod(locale, messagesBundle));
break; break;
case PASSWORD: case PASSWORD:

View file

@ -6,6 +6,7 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserConsentModel; import org.keycloak.models.UserConsentModel;
import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
@ -22,9 +23,9 @@ public class ApplicationsBean {
private List<ApplicationEntry> applications = new LinkedList<ApplicationEntry>(); 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(); List<ClientModel> realmClients = realm.getClients();
for (ClientModel client : realmClients) { for (ClientModel client : realmClients) {

View file

@ -637,6 +637,21 @@ module.config([ '$routeProvider', function($routeProvider) {
}, },
controller : 'ClientSessionsCtrl' 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', { .when('/realms/:realm/clients/:client/credentials', {
templateUrl : resourceUrl + '/partials/client-credentials.html', templateUrl : resourceUrl + '/partials/client-credentials.html',
resolve : { resolve : {

View file

@ -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, module.controller('ClientRoleDetailCtrl', function($scope, realm, client, role, roles, clients,
Role, ClientRole, RoleById, RoleRealmComposites, RoleClientComposites, Role, ClientRole, RoleById, RoleRealmComposites, RoleClientComposites,
$http, $location, Dialog, Notifications) { $http, $location, Dialog, Notifications) {

View file

@ -208,9 +208,9 @@ module.controller('UserConsentsCtrl', function($scope, realm, user, userConsents
UserConsents.query({realm: realm.realm, user: user.id}, function(updated) { UserConsents.query({realm: realm.realm, user: user.id}, function(updated) {
$scope.userConsents = updated; $scope.userConsents = updated;
}) })
Notifications.success('Consent revoked successfully'); Notifications.success('Grant revoked successfully');
}, function() { }, function() {
Notifications.error("Consent couldn't be revoked"); Notifications.error("Grant couldn't be revoked");
}); });
console.log("Revoke consent " + clientId); console.log("Revoke consent " + clientId);
} }

View file

@ -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) { module.factory('ClientClaimsLoader', function(Loader, ClientClaims, $route, $q) {
return Loader.get(ClientClaims, function() { return Loader.get(ClientClaims, function() {
return { return {

View file

@ -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) { module.factory('ClientLogoutAll', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/clients/:client/logout-all', { return $resource(authUrl + '/admin/realms/:realm/clients/:client/logout-all', {
realm : '@realm', realm : '@realm',

View file

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

View file

@ -12,6 +12,7 @@
<th>Client</th> <th>Client</th>
<th>Granted Roles</th> <th>Granted Roles</th>
<th>Granted Protocol Mappers</th> <th>Granted Protocol Mappers</th>
<th>Additional Grants</th>
<th>Action</th> <th>Action</th>
</tr> </tr>
</thead> </thead>
@ -35,6 +36,11 @@
</span> </span>
</span> </span>
</td> </td>
<td>
<span data-ng-repeat="additionalGrant in consent.additionalGrants">
<span ng-if="!$first">, </span>{{additionalGrant}}
</span>
</td>
<td class="kc-action-cell"> <td class="kc-action-cell">
<button class="btn btn-default btn-block btn-sm" ng-click="revokeConsent(consent.clientId)"> <button class="btn btn-default btn-block btn-sm" ng-click="revokeConsent(consent.clientId)">
<i class="pficon pficon-delete"></i> Revoke <i class="pficon pficon-delete"></i> Revoke

View file

@ -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> <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>
<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] == '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'"> <li ng-class="{active: path[4] == 'installation'}" data-ng-show="client.protocol != 'saml'">

View file

@ -3,8 +3,10 @@ package org.keycloak.migration.migrators;
import java.util.List; import java.util.List;
import org.keycloak.migration.ModelVersion; import org.keycloak.migration.ModelVersion;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
/** /**
@ -17,6 +19,16 @@ public class MigrateTo1_6_0 {
public void migrate(KeycloakSession session) { public void migrate(KeycloakSession session) {
List<RealmModel> realms = session.realms().getRealms(); List<RealmModel> realms = session.realms().getRealms();
for (RealmModel realm : realms) { 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); KeycloakModelUtils.setupOfflineTokens(realm);
} }

View file

@ -8,6 +8,7 @@ public class OfflineClientSessionModel {
private String clientSessionId; private String clientSessionId;
private String userSessionId; private String userSessionId;
private String clientId; private String clientId;
private String userId;
private String data; private String data;
public String getClientSessionId() { public String getClientSessionId() {
@ -34,6 +35,14 @@ public class OfflineClientSessionModel {
this.clientId = clientId; this.clientId = clientId;
} }
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getData() { public String getData() {
return data; return data;
} }

View file

@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -474,6 +475,70 @@ public class UserFederationManager implements UserProvider {
return (result != null) ? result : CredentialValidationOutput.failed(); 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 @Override
public void close() { public void close() {
} }

View file

@ -114,15 +114,6 @@ public interface UserModel {
void updateConsent(UserConsentModel consent); void updateConsent(UserConsentModel consent);
boolean revokeConsentForClient(String clientInternalId); 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 { public static enum RequiredAction {
VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD
} }

View file

@ -2,6 +2,7 @@ package org.keycloak.models;
import org.keycloak.provider.Provider; import org.keycloak.provider.Provider;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -55,5 +56,17 @@ public interface UserProvider extends Provider {
boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input); boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input);
CredentialValidationOutput validCredentials(RealmModel realm, 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(); void close();
} }

View file

@ -10,6 +10,8 @@ import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.ModelException; import org.keycloak.models.ModelException;
import org.keycloak.models.OTPPolicy; import org.keycloak.models.OTPPolicy;
import org.keycloak.models.OfflineClientSessionModel;
import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel; 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.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation; 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.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmEventsConfigRepresentation; import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
@ -43,6 +47,7 @@ import org.keycloak.representations.idm.UserSessionRepresentation;
import org.keycloak.util.Time; import org.keycloak.util.Time;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
@ -506,7 +511,31 @@ public class ModelToRepresentation {
return rep; 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;
}

View file

@ -1,5 +1,9 @@
package org.keycloak.models.utils; 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.keycloak.util.Base64;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.enums.SslRequired; import org.keycloak.enums.SslRequired;
@ -981,6 +985,11 @@ public class RepresentationToModel {
user.addConsent(consentModel); user.addConsent(consentModel);
} }
} }
if (userRep.getOfflineUserSessions() != null) {
for (OfflineUserSessionRepresentation sessionRep : userRep.getOfflineUserSessions()) {
importOfflineSession(session, newRealm, user, sessionRep);
}
}
if (userRep.getServiceAccountClientId() != null) { if (userRep.getServiceAccountClientId() != null) {
String clientId = userRep.getServiceAccountClientId(); String clientId = userRep.getServiceAccountClientId();
ClientModel client = clientMap.get(clientId); ClientModel client = clientMap.get(clientId);
@ -1151,6 +1160,29 @@ public class RepresentationToModel {
return consentModel; 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) { public static AuthenticationFlowModel toModel(AuthenticationFlowRepresentation rep) {
AuthenticationFlowModel model = new AuthenticationFlowModel(); AuthenticationFlowModel model = new AuthenticationFlowModel();
model.setBuiltIn(rep.isBuiltIn()); model.setBuiltIn(rep.isBuiltIn());

View file

@ -258,44 +258,4 @@ public class UserModelDelegate implements UserModel {
public void setCreatedTimestamp(Long timestamp){ public void setCreatedTimestamp(Long timestamp){
delegate.setCreatedTimestamp(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);
}
} }

View file

@ -23,6 +23,9 @@ import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException; 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.ProtocolMapperModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel; import org.keycloak.models.RequiredActionProviderModel;
@ -32,6 +35,8 @@ import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider; import org.keycloak.models.UserProvider;
import org.keycloak.models.entities.FederatedIdentityEntity; 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.entities.UserEntity;
import org.keycloak.models.file.adapter.UserAdapter; import org.keycloak.models.file.adapter.UserAdapter;
import org.keycloak.models.utils.CredentialValidation; import org.keycloak.models.utils.CredentialValidation;
@ -41,6 +46,7 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -489,4 +495,187 @@ public class FileUserProvider implements UserProvider {
return null; // not supported yet 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;
}
} }

View file

@ -574,141 +574,6 @@ public class UserAdapter implements UserModel, Comparable {
return false; 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 @Override
public boolean equals(Object o) { public boolean equals(Object o) {

View file

@ -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 @Override
public UserModel getUserById(String id, RealmModel realm) { public UserModel getUserById(String id, RealmModel realm) {
if (!cache.isEnabled()) return getDelegate().getUserById(id, realm); if (!cache.isEnabled()) return getDelegate().getUserById(id, realm);
if (realmInvalidations.contains(realm.getId())) { if (isRegisteredForInvalidation(realm, id)) {
return getDelegate().getUserById(id, realm);
}
if (userInvalidations.containsKey(id)) {
return getDelegate().getUserById(id, realm); return getDelegate().getUserById(id, realm);
} }
@ -120,7 +121,7 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
if (model == null) return null; if (model == null) return null;
if (managedUsers.containsKey(id)) return managedUsers.get(id); if (managedUsers.containsKey(id)) return managedUsers.get(id);
if (userInvalidations.containsKey(id)) return model; if (userInvalidations.containsKey(id)) return model;
cached = new CachedUser(realm, model); cached = new CachedUser(this, realm, model);
cache.addCachedUser(realm.getId(), cached); cache.addCachedUser(realm.getId(), cached);
} else if (managedUsers.containsKey(id)) { } else if (managedUsers.containsKey(id)) {
return managedUsers.get(id); return managedUsers.get(id);
@ -145,7 +146,7 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
if (model == null) return null; if (model == null) return null;
if (managedUsers.containsKey(model.getId())) return managedUsers.get(model.getId()); if (managedUsers.containsKey(model.getId())) return managedUsers.get(model.getId());
if (userInvalidations.containsKey(model.getId())) return model; if (userInvalidations.containsKey(model.getId())) return model;
cached = new CachedUser(realm, model); cached = new CachedUser(this, realm, model);
cache.addCachedUser(realm.getId(), cached); cache.addCachedUser(realm.getId(), cached);
} else if (userInvalidations.containsKey(cached.getId())) { } else if (userInvalidations.containsKey(cached.getId())) {
return getDelegate().getUserById(cached.getId(), realm); return getDelegate().getUserById(cached.getId(), realm);
@ -172,7 +173,7 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
UserModel model = getDelegate().getUserByEmail(email, realm); UserModel model = getDelegate().getUserByEmail(email, realm);
if (model == null) return null; if (model == null) return null;
if (userInvalidations.containsKey(model.getId())) return model; if (userInvalidations.containsKey(model.getId())) return model;
cached = new CachedUser(realm, model); cached = new CachedUser(this, realm, model);
cache.addCachedUser(realm.getId(), cached); cache.addCachedUser(realm.getId(), cached);
} else if (userInvalidations.containsKey(cached.getId())) { } else if (userInvalidations.containsKey(cached.getId())) {
return getDelegate().getUserByEmail(email, realm); return getDelegate().getUserByEmail(email, realm);
@ -327,4 +328,94 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
public void preRemove(ClientModel client, ProtocolMapperModel protocolMapper) { public void preRemove(ClientModel client, ProtocolMapperModel protocolMapper) {
getDelegate().preRemove(client, 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);
}
} }

View file

@ -348,52 +348,4 @@ public class UserAdapter implements UserModel {
getDelegateForUpdate(); getDelegateForUpdate();
return updated.revokeConsentForClient(clientId); 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);
}
} }

View file

@ -6,6 +6,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialValueModel; import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.cache.CacheUserProvider;
import org.keycloak.util.MultivaluedHashMap; import org.keycloak.util.MultivaluedHashMap;
import java.io.Serializable; import java.io.Serializable;
@ -40,7 +41,7 @@ public class CachedUser implements Serializable {
private Map<String, OfflineUserSessionModel> offlineUserSessions = new HashMap<>(); private Map<String, OfflineUserSessionModel> offlineUserSessions = new HashMap<>();
private Map<String, OfflineClientSessionModel> offlineClientSessions = 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.id = user.getId();
this.realm = realm.getId(); this.realm = realm.getId();
this.username = user.getUsername(); this.username = user.getUsername();
@ -59,10 +60,10 @@ public class CachedUser implements Serializable {
for (RoleModel role : user.getRoleMappings()) { for (RoleModel role : user.getRoleMappings()) {
roleMappings.add(role.getId()); roleMappings.add(role.getId());
} }
for (OfflineUserSessionModel offlineSession : user.getOfflineUserSessions()) { for (OfflineUserSessionModel offlineSession : cacheUserProvider.getDelegate().getOfflineUserSessions(realm, user)) {
offlineUserSessions.put(offlineSession.getUserSessionId(), offlineSession); offlineUserSessions.put(offlineSession.getUserSessionId(), offlineSession);
} }
for (OfflineClientSessionModel offlineSession : user.getOfflineClientSessions()) { for (OfflineClientSessionModel offlineSession : cacheUserProvider.getDelegate().getOfflineClientSessions(realm, user)) {
offlineClientSessions.put(offlineSession.getClientSessionId(), offlineSession); offlineClientSessions.put(offlineSession.getClientSessionId(), offlineSession);
} }
} }

View file

@ -4,6 +4,8 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OfflineClientSessionModel;
import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel; import org.keycloak.models.RequiredActionProviderModel;
@ -13,6 +15,8 @@ import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider; import org.keycloak.models.UserProvider;
import org.keycloak.models.jpa.entities.FederatedIdentityEntity; 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.UserAttributeEntity;
import org.keycloak.models.jpa.entities.UserEntity; import org.keycloak.models.jpa.entities.UserEntity;
import org.keycloak.models.utils.CredentialValidation; import org.keycloak.models.utils.CredentialValidation;
@ -22,8 +26,10 @@ import javax.persistence.EntityManager;
import javax.persistence.Query; import javax.persistence.Query;
import javax.persistence.TypedQuery; import javax.persistence.TypedQuery;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -473,4 +479,167 @@ public class JpaUserProvider implements UserProvider {
// Not supported yet // Not supported yet
return null; 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;
}
} }

View file

@ -755,124 +755,6 @@ public class UserAdapter implements UserModel {
em.flush(); 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 @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;

View file

@ -16,7 +16,9 @@ import javax.persistence.Table;
@NamedQueries({ @NamedQueries({
@NamedQuery(name="deleteOfflineClientSessionsByRealm", query="delete from OfflineClientSessionEntity sess where sess.user IN (select u from UserEntity u where u.realmId=:realmId)"), @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="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") @Table(name="OFFLINE_CLIENT_SESSION")
@Entity @Entity

View file

@ -10,6 +10,10 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeycloakSession; 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.ProtocolMapperModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel; import org.keycloak.models.RequiredActionProviderModel;
@ -26,8 +30,10 @@ import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
import org.keycloak.models.utils.CredentialValidation; import org.keycloak.models.utils.CredentialValidation;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -51,7 +57,7 @@ public class MongoUserProvider implements UserProvider {
} }
@Override @Override
public UserModel getUserById(String id, RealmModel realm) { public UserAdapter getUserById(String id, RealmModel realm) {
MongoUserEntity user = getMongoStore().loadEntity(MongoUserEntity.class, id, invocationContext); MongoUserEntity user = getMongoStore().loadEntity(MongoUserEntity.class, id, invocationContext);
// Check that it's user from this realm // Check that it's user from this realm
@ -244,8 +250,8 @@ public class MongoUserProvider implements UserProvider {
@Override @Override
public Set<FederatedIdentityModel> getFederatedIdentities(UserModel userModel, RealmModel realm) { public Set<FederatedIdentityModel> getFederatedIdentities(UserModel userModel, RealmModel realm) {
UserModel user = getUserById(userModel.getId(), realm); UserAdapter user = getUserById(userModel.getId(), realm);
MongoUserEntity userEntity = ((UserAdapter) user).getUser(); MongoUserEntity userEntity = user.getUser();
List<FederatedIdentityEntity> linkEntities = userEntity.getFederatedIdentities(); List<FederatedIdentityEntity> linkEntities = userEntity.getFederatedIdentities();
if (linkEntities == null) { if (linkEntities == null) {
@ -263,8 +269,8 @@ public class MongoUserProvider implements UserProvider {
@Override @Override
public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) { public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) {
user = getUserById(user.getId(), realm); UserAdapter mongoUser = getUserById(user.getId(), realm);
MongoUserEntity userEntity = ((UserAdapter) user).getUser(); MongoUserEntity userEntity = mongoUser.getUser();
FederatedIdentityEntity federatedIdentityEntity = findFederatedIdentityLink(userEntity, socialProvider); FederatedIdentityEntity federatedIdentityEntity = findFederatedIdentityLink(userEntity, socialProvider);
return federatedIdentityEntity != null ? new FederatedIdentityModel(federatedIdentityEntity.getIdentityProvider(), federatedIdentityEntity.getUserId(), return federatedIdentityEntity != null ? new FederatedIdentityModel(federatedIdentityEntity.getIdentityProvider(), federatedIdentityEntity.getUserId(),
@ -320,8 +326,8 @@ public class MongoUserProvider implements UserProvider {
@Override @Override
public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel identity) { public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel identity) {
user = getUserById(user.getId(), realm); UserAdapter mongoUser = getUserById(user.getId(), realm);
MongoUserEntity userEntity = ((UserAdapter) user).getUser(); MongoUserEntity userEntity = mongoUser.getUser();
FederatedIdentityEntity federatedIdentityEntity = new FederatedIdentityEntity(); FederatedIdentityEntity federatedIdentityEntity = new FederatedIdentityEntity();
federatedIdentityEntity.setIdentityProvider(identity.getIdentityProvider()); federatedIdentityEntity.setIdentityProvider(identity.getIdentityProvider());
federatedIdentityEntity.setUserId(identity.getUserId()); federatedIdentityEntity.setUserId(identity.getUserId());
@ -333,8 +339,8 @@ public class MongoUserProvider implements UserProvider {
@Override @Override
public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) { public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) {
federatedUser = getUserById(federatedUser.getId(), realm); UserAdapter mongoUser = getUserById(federatedUser.getId(), realm);
MongoUserEntity userEntity = ((UserAdapter) federatedUser).getUser(); MongoUserEntity userEntity = mongoUser.getUser();
FederatedIdentityEntity federatedIdentityEntity = findFederatedIdentityLink(userEntity, federatedIdentityModel.getIdentityProvider()); FederatedIdentityEntity federatedIdentityEntity = findFederatedIdentityLink(userEntity, federatedIdentityModel.getIdentityProvider());
federatedIdentityEntity.setToken(federatedIdentityModel.getToken()); federatedIdentityEntity.setToken(federatedIdentityModel.getToken());
@ -342,8 +348,8 @@ public class MongoUserProvider implements UserProvider {
@Override @Override
public boolean removeFederatedIdentity(RealmModel realm, UserModel userModel, String socialProvider) { public boolean removeFederatedIdentity(RealmModel realm, UserModel userModel, String socialProvider) {
UserModel user = getUserById(userModel.getId(), realm); UserAdapter user = getUserById(userModel.getId(), realm);
MongoUserEntity userEntity = ((UserAdapter) user).getUser(); MongoUserEntity userEntity = user.getUser();
FederatedIdentityEntity federatedIdentityEntity = findFederatedIdentityLink(userEntity, socialProvider); FederatedIdentityEntity federatedIdentityEntity = findFederatedIdentityLink(userEntity, socialProvider);
if (federatedIdentityEntity == null) { if (federatedIdentityEntity == null) {
return false; return false;
@ -476,4 +482,205 @@ public class MongoUserProvider implements UserProvider {
// Not supported yet // Not supported yet
return null; 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;
}
} }

View file

@ -632,145 +632,6 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
return getMongoStore().removeEntity(entity, invocationContext); 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 @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;

View file

@ -18,6 +18,10 @@
<packaging>pom</packaging> <packaging>pom</packaging>
<properties> <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.version>2.0.0-M17</apacheds.version>
<apacheds.codec.version>1.0.0-M23</apacheds.codec.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> <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> <google.client.version>1.14.1-beta</google.client.version>
<github.relaxng.version>2011.1</github.relaxng.version> <github.relaxng.version>2011.1</github.relaxng.version>
<winzipaes.version>1.0.1</winzipaes.version> <winzipaes.version>1.0.1</winzipaes.version>
<freemarker.version>2.3.20</freemarker.version> <freemarker.version>2.3.23</freemarker.version>
<twitter4j.version>3.0.5</twitter4j.version> <twitter4j.version>4.0.4</twitter4j.version>
<selenium.version>2.35.0</selenium.version> <selenium.version>2.35.0</selenium.version>
<sun.istack.version>2.21</sun.istack.version> <sun.istack.version>2.21</sun.istack.version>
<sun.jaxb.version>2.2.11</sun.jaxb.version> <sun.jaxb.version>2.2.11</sun.jaxb.version>

View file

@ -98,7 +98,7 @@ public class TokenManager {
ClientSessionModel clientSession = null; ClientSessionModel clientSession = null;
if (RefreshTokenUtil.TOKEN_TYPE_OFFLINE.equals(oldToken.getType())) { 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) { if (clientSession != null) {
userSession = clientSession.getUserSession(); userSession = clientSession.getUserSession();
} }
@ -496,7 +496,7 @@ public class TokenManager {
refreshToken = new RefreshToken(accessToken); refreshToken = new RefreshToken(accessToken);
refreshToken.type(RefreshTokenUtil.TOKEN_TYPE_OFFLINE); refreshToken.type(RefreshTokenUtil.TOKEN_TYPE_OFFLINE);
OfflineTokenUtils.persistOfflineSession(clientSession, userSession); OfflineTokenUtils.persistOfflineSession(session, realm, clientSession, userSession);
} else { } else {
refreshToken = new RefreshToken(accessToken); refreshToken = new RefreshToken(accessToken);
refreshToken.expiration(Time.currentTime() + realm.getSsoSessionIdleTimeout()); refreshToken.expiration(Time.currentTime() + realm.getSsoSessionIdleTimeout());

View file

@ -75,7 +75,7 @@ public class OfflineClientSessionAdapter implements ClientSessionModel {
@Override @Override
public String getRedirectUri() { public String getRedirectUri() {
return data.getRedirectUri(); return getData().getRedirectUri();
} }
@Override @Override
@ -85,7 +85,7 @@ public class OfflineClientSessionAdapter implements ClientSessionModel {
@Override @Override
public int getTimestamp() { public int getTimestamp() {
return 0; return getData().getTimestamp();
} }
@Override @Override
@ -238,6 +238,9 @@ public class OfflineClientSessionAdapter implements ClientSessionModel {
@JsonProperty("authenticatorStatus") @JsonProperty("authenticatorStatus")
private Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus = new HashMap<>(); private Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus = new HashMap<>();
@JsonProperty("timestamp")
private int timestamp;
public String getAuthMethod() { public String getAuthMethod() {
return authMethod; return authMethod;
} }
@ -285,5 +288,13 @@ public class OfflineClientSessionAdapter implements ClientSessionModel {
public void setAuthenticatorStatus(Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus) { public void setAuthenticatorStatus(Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus) {
this.authenticatorStatus = authenticatorStatus; this.authenticatorStatus = authenticatorStatus;
} }
public int getTimestamp() {
return timestamp;
}
public void setTimestamp(int timestamp) {
this.timestamp = timestamp;
}
} }
} }

View file

@ -9,6 +9,7 @@ import org.jboss.logging.Logger;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelException; import org.keycloak.models.ModelException;
import org.keycloak.models.OfflineClientSessionModel; import org.keycloak.models.OfflineClientSessionModel;
import org.keycloak.models.OfflineUserSessionModel; import org.keycloak.models.OfflineUserSessionModel;
@ -17,9 +18,9 @@ import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import org.keycloak.util.Time;
/** /**
* TODO: Change to utils?
* *
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @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); 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(); UserModel user = userSession.getUser();
ClientModel client = clientSession.getClient(); 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) // 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) { for (OfflineClientSessionModel existing : clientSessions) {
if (existing.getClientId().equals(client.getId())) { if (existing.getClientId().equals(client.getId())) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
@ -40,28 +41,28 @@ public class OfflineTokenUtils {
user.getUsername(), client.getClientId(), existing.getClientSessionId()); 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 // Check if userSession is ours. If not, then check if it has other clientSessions and remove it otherwise
if (!existing.getUserSessionId().equals(userSession.getId())) { 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 // 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) { if (userSessionRep == null) {
createOfflineUserSession(user, userSession); createOfflineUserSession(kcSession, realm, user, userSession);
} }
// Create clientRep and save to DB. // 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 // 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) { public static ClientSessionModel findOfflineClientSession(KeycloakSession kcSession, RealmModel realm, UserModel user, String clientSessionId, String userSessionId) {
OfflineClientSessionModel clientSession = user.getOfflineClientSession(clientSessionId); OfflineClientSessionModel clientSession = kcSession.users().getOfflineClientSession(realm, user, clientSessionId);
if (clientSession == null) { if (clientSession == null) {
return null; return null;
} }
@ -71,7 +72,7 @@ public class OfflineTokenUtils {
" Wanted user session: " + userSessionId); " Wanted user session: " + userSessionId);
} }
OfflineUserSessionModel userSession = user.getOfflineUserSession(userSessionId); OfflineUserSessionModel userSession = kcSession.users().getOfflineUserSession(realm, user, userSessionId);
if (userSession == null) { if (userSession == null) {
throw new ModelException("Found clientSession " + clientSessionId + " but not userSession " + userSessionId); throw new ModelException("Found clientSession " + clientSessionId + " but not userSession " + userSessionId);
} }
@ -79,13 +80,11 @@ public class OfflineTokenUtils {
OfflineUserSessionAdapter userSessionAdapter = new OfflineUserSessionAdapter(userSession, user); OfflineUserSessionAdapter userSessionAdapter = new OfflineUserSessionAdapter(userSession, user);
ClientModel client = realm.getClientById(clientSession.getClientId()); ClientModel client = realm.getClientById(clientSession.getClientId());
OfflineClientSessionAdapter clientSessionAdapter = new OfflineClientSessionAdapter(clientSession, realm, client, userSessionAdapter); return new OfflineClientSessionAdapter(clientSession, realm, client, userSessionAdapter);
return clientSessionAdapter;
} }
public static Set<ClientModel> findClientsWithOfflineToken(RealmModel realm, UserModel user) { public static Set<ClientModel> findClientsWithOfflineToken(KeycloakSession kcSession, RealmModel realm, UserModel user) {
Collection<OfflineClientSessionModel> clientSessions = user.getOfflineClientSessions(); Collection<OfflineClientSessionModel> clientSessions = kcSession.users().getOfflineClientSessions(realm, user);
Set<ClientModel> clients = new HashSet<>(); Set<ClientModel> clients = new HashSet<>();
for (OfflineClientSessionModel clientSession : clientSessions) { for (OfflineClientSessionModel clientSession : clientSessions) {
ClientModel client = realm.getClientById(clientSession.getClientId()); ClientModel client = realm.getClientById(clientSession.getClientId());
@ -94,8 +93,8 @@ public class OfflineTokenUtils {
return clients; return clients;
} }
public static boolean revokeOfflineToken(UserModel user, ClientModel client) { public static boolean revokeOfflineToken(KeycloakSession kcSession, RealmModel realm, UserModel user, ClientModel client) {
Collection<OfflineClientSessionModel> clientSessions = user.getOfflineClientSessions(); Collection<OfflineClientSessionModel> clientSessions = kcSession.users().getOfflineClientSessions(realm, user);
boolean anyRemoved = false; boolean anyRemoved = false;
for (OfflineClientSessionModel clientSession : clientSessions) { for (OfflineClientSessionModel clientSession : clientSessions) {
if (clientSession.getClientId().equals(client.getId())) { if (clientSession.getClientId().equals(client.getId())) {
@ -104,8 +103,8 @@ public class OfflineTokenUtils {
user.getUsername(), client.getClientId(), clientSession.getClientSessionId()); user.getUsername(), client.getClientId(), clientSession.getClientSessionId());
} }
user.removeOfflineClientSession(clientSession.getClientSessionId()); kcSession.users().removeOfflineClientSession(realm, user, clientSession.getClientSessionId());
checkUserSessionHasClientSessions(user, clientSession.getUserSessionId()); checkUserSessionHasClientSessions(kcSession, realm, user, clientSession.getUserSessionId());
anyRemoved = true; anyRemoved = true;
} }
} }
@ -123,7 +122,7 @@ public class OfflineTokenUtils {
return clientSession.getRoles().contains(offlineAccessRole.getId()); 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()) { if (logger.isTraceEnabled()) {
logger.tracef("Creating new offline user session. UserSessionID: '%s' , Username: '%s'", userSession.getId(), user.getUsername()); 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(); OfflineUserSessionModel sessionModel = new OfflineUserSessionModel();
sessionModel.setUserSessionId(userSession.getId()); sessionModel.setUserSessionId(userSession.getId());
sessionModel.setData(stringRep); sessionModel.setData(stringRep);
user.addOfflineUserSession(sessionModel); kcSession.users().addOfflineUserSession(realm, user, sessionModel);
} catch (IOException ioe) { } catch (IOException ioe) {
throw new ModelException(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()) { if (logger.isTraceEnabled()) {
logger.tracef("Creating new offline token client session. ClientSessionId: '%s', UserSessionID: '%s' , Username: '%s', Client: '%s'" , 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()); clientSession.getId(), userSession.getId(), user.getUsername(), clientSession.getClient().getClientId());
@ -159,23 +158,25 @@ public class OfflineTokenUtils {
rep.setRoles(clientSession.getRoles()); rep.setRoles(clientSession.getRoles());
rep.setNotes(clientSession.getNotes()); rep.setNotes(clientSession.getNotes());
rep.setAuthenticatorStatus(clientSession.getExecutionStatus()); rep.setAuthenticatorStatus(clientSession.getExecutionStatus());
rep.setTimestamp(Time.currentTime());
try { try {
String stringRep = JsonSerialization.writeValueAsString(rep); String stringRep = JsonSerialization.writeValueAsString(rep);
OfflineClientSessionModel clsModel = new OfflineClientSessionModel(); OfflineClientSessionModel clsModel = new OfflineClientSessionModel();
clsModel.setClientSessionId(clientSession.getId()); clsModel.setClientSessionId(clientSession.getId());
clsModel.setClientId(clientSession.getClient().getId()); clsModel.setClientId(clientSession.getClient().getId());
clsModel.setUserId(user.getId());
clsModel.setUserSessionId(userSession.getId()); clsModel.setUserSessionId(userSession.getId());
clsModel.setData(stringRep); clsModel.setData(stringRep);
user.addOfflineClientSession(clsModel); kcSession.users().addOfflineClientSession(realm, clsModel);
} catch (IOException ioe) { } catch (IOException ioe) {
throw new ModelException(ioe); throw new ModelException(ioe);
} }
} }
// Check if userSession has any offline clientSessions attached to it. Remove userSession if not // Check if userSession has any offline clientSessions attached to it. Remove userSession if not
private static void checkUserSessionHasClientSessions(UserModel user, String userSessionId) { private static void checkUserSessionHasClientSessions(KeycloakSession kcSession, RealmModel realm, UserModel user, String userSessionId) {
Collection<OfflineClientSessionModel> clientSessions = user.getOfflineClientSessions(); Collection<OfflineClientSessionModel> clientSessions = kcSession.users().getOfflineClientSessions(realm, user);
for (OfflineClientSessionModel clientSession : clientSessions) { for (OfflineClientSessionModel clientSession : clientSessions) {
if (clientSession.getUserSessionId().equals(userSessionId)) { if (clientSession.getUserSessionId().equals(userSessionId)) {
@ -186,6 +187,6 @@ public class OfflineTokenUtils {
if (logger.isTraceEnabled()) { 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); 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);
} }
} }

View file

@ -486,7 +486,7 @@ public class AccountService extends AbstractSecuredLocalService {
// Revoke grant in UserModel // Revoke grant in UserModel
UserModel user = auth.getUser(); UserModel user = auth.getUser();
user.revokeConsentForClient(client.getId()); user.revokeConsentForClient(client.getId());
OfflineTokenUtils.revokeOfflineToken(user, client); OfflineTokenUtils.revokeOfflineToken(session, realm, user, client);
// Logout clientSessions for this user and client // Logout clientSessions for this user and client
AuthenticationManager.backchannelUserFromClient(session, realm, user, client, uriInfo, headers); AuthenticationManager.backchannelUserFromClient(session, realm, user, client, uriInfo, headers);

View file

@ -7,8 +7,11 @@ import org.jboss.resteasy.spi.NotFoundException;
import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.OperationType;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.OfflineClientSessionModel;
import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel; 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.ClientManager;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.ResourceAdminManager; 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.resources.KeycloakApplication;
import org.keycloak.services.ErrorResponse; import org.keycloak.services.ErrorResponse;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
@ -390,6 +395,65 @@ public class ClientResource {
return sessions; 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 * Logout all sessions
* *

View file

@ -66,15 +66,18 @@ import javax.ws.rs.WebApplicationException;
import java.net.URI; import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.keycloak.models.UsernameLoginFailureModel; import org.keycloak.models.UsernameLoginFailureModel;
import org.keycloak.services.managers.BruteForceProtector; import org.keycloak.services.managers.BruteForceProtector;
import org.keycloak.services.offline.OfflineTokenUtils;
import org.keycloak.services.resources.AccountService; import org.keycloak.services.resources.AccountService;
/** /**
@ -439,25 +442,44 @@ public class UsersResource {
@GET @GET
@NoCache @NoCache
@Produces(MediaType.APPLICATION_JSON) @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(); auth.requireView();
UserModel user = session.users().getUserById(id, realm); UserModel user = session.users().getUserById(id, realm);
if (user == null) { if (user == null) {
throw new NotFoundException("User not found"); throw new NotFoundException("User not found");
} }
List<UserConsentModel> consents = user.getConsents(); List<Map<String, Object>> result = new LinkedList<>();
List<UserConsentRepresentation> result = new ArrayList<UserConsentRepresentation>();
for (UserConsentModel consent : consents) { Set<ClientModel> offlineClients = OfflineTokenUtils.findClientsWithOfflineToken(session, realm, user);
UserConsentRepresentation rep = ModelToRepresentation.toRepresentation(consent);
result.add(rep); 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; return result;
} }
/** /**
* Revoke consent for particular client from user * Revoke consent and offline tokens for particular client from user
* *
* @param id User id * @param id User id
* @param clientId Client id * @param clientId Client id
@ -473,12 +495,16 @@ public class UsersResource {
} }
ClientModel client = realm.getClientByClientId(clientId); ClientModel client = realm.getClientByClientId(clientId);
boolean revoked = user.revokeConsentForClient(client.getId()); boolean revokedConsent = user.revokeConsentForClient(client.getId());
if (revoked) { boolean revokedOfflineToken = OfflineTokenUtils.revokeOfflineToken(session, realm, user, client);
if (revokedConsent) {
// Logout clientSessions for this user and client // Logout clientSessions for this user and client
AuthenticationManager.backchannelUserFromClient(session, realm, user, client, uriInfo, headers); 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(); adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
} }

View file

@ -17,37 +17,30 @@
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-social-core</artifactId> <artifactId>keycloak-social-core</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-broker-oidc</artifactId> <artifactId>keycloak-broker-oidc</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-events-api</artifactId> <artifactId>keycloak-events-api</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId> <artifactId>keycloak-services</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.codehaus.jackson</groupId> <groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId> <artifactId>jackson-mapper-asl</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jboss.logging</groupId> <groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId> <artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.twitter4j</groupId> <groupId>org.twitter4j</groupId>
<artifactId>twitter4j-core</artifactId> <artifactId>twitter4j-core</artifactId>
<scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View file

@ -15,6 +15,8 @@ import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants; import org.keycloak.models.LDAPConstants;
import org.keycloak.models.OfflineClientSessionModel;
import org.keycloak.models.OfflineUserSessionModel;
import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel; 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.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -327,6 +330,21 @@ public class ImportTest extends AbstractModelTest {
Assert.assertFalse(otherAppAdminConsent.isRoleGranted(application.getRole("app-admin"))); Assert.assertFalse(otherAppAdminConsent.isRoleGranted(application.getRole("app-admin")));
Assert.assertTrue(otherAppAdminConsent.isProtocolMapperGranted(gssCredentialMapper)); 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 // Test service accounts
Assert.assertFalse(application.isServiceAccountsEnabled()); Assert.assertFalse(application.isServiceAccountsEnabled());
Assert.assertTrue(otherApp.isServiceAccountsEnabled()); Assert.assertTrue(otherApp.isServiceAccountsEnabled());

View file

@ -15,6 +15,7 @@ import static org.junit.Assert.assertNotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -292,12 +293,35 @@ public class UserModelTest extends AbstractModelTest {
ClientModel barClient = realm.addClient("bar"); ClientModel barClient = realm.addClient("bar");
UserModel user1 = session.users().addUser(realm, "user1"); UserModel user1 = session.users().addUser(realm, "user1");
addOfflineUserSession(user1, "123", "something1"); UserModel user2 = session.users().addUser(realm, "user2");
addOfflineClientSession(user1, "456", "123", fooClient.getId(), "something2");
addOfflineClientSession(user1, "789", "123", barClient.getId(), "something3"); 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(); 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 = realmManager.getRealmByName("original");
realm.removeClient(barClient.getId()); realm.removeClient(barClient.getId());
@ -305,9 +329,9 @@ public class UserModelTest extends AbstractModelTest {
realm = realmManager.getRealmByName("original"); realm = realmManager.getRealmByName("original");
user1 = session.users().getUserByUsername("user1", realm); user1 = session.users().getUserByUsername("user1", realm);
Assert.assertEquals("something1", user1.getOfflineUserSession("123").getData()); Assert.assertEquals("something1", session.users().getOfflineUserSession(realm, user1, "123").getData());
Assert.assertEquals("something2", user1.getOfflineClientSession("456").getData()); Assert.assertEquals("something2", session.users().getOfflineClientSession(realm, user1, "456").getData());
Assert.assertNull(user1.getOfflineClientSession("789")); Assert.assertNull(session.users().getOfflineClientSession(realm, user1, "789"));
realm.removeClient(fooClient.getId()); realm.removeClient(fooClient.getId());
@ -315,27 +339,28 @@ public class UserModelTest extends AbstractModelTest {
realm = realmManager.getRealmByName("original"); realm = realmManager.getRealmByName("original");
user1 = session.users().getUserByUsername("user1", realm); user1 = session.users().getUserByUsername("user1", realm);
Assert.assertNull(user1.getOfflineClientSession("456")); Assert.assertNull(session.users().getOfflineClientSession(realm, user1, "456"));
Assert.assertNull(user1.getOfflineClientSession("789")); Assert.assertNull(session.users().getOfflineClientSession(realm, user1, "789"));
Assert.assertNull(user1.getOfflineUserSession("123")); Assert.assertNull(session.users().getOfflineUserSession(realm, user1, "123"));
Assert.assertEquals(0, user1.getOfflineUserSessions().size()); Assert.assertEquals(0, session.users().getOfflineUserSessions(realm, user1).size());
Assert.assertEquals(0, user1.getOfflineClientSessions().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(); OfflineUserSessionModel model = new OfflineUserSessionModel();
model.setUserSessionId(userSessionId); model.setUserSessionId(userSessionId);
model.setData(data); 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(); OfflineClientSessionModel model = new OfflineClientSessionModel();
model.setClientSessionId(clientSessionId); model.setClientSessionId(clientSessionId);
model.setUserSessionId(userSessionId); model.setUserSessionId(userSessionId);
model.setUserId(user.getId());
model.setClientId(clientId); model.setClientId(clientId);
model.setData(data); model.setData(data);
user.addOfflineClientSession(model); session.users().addOfflineClientSession(realm, model);
} }
public static void assertEquals(UserModel expected, UserModel actual) { public static void assertEquals(UserModel expected, UserModel actual) {
@ -352,5 +377,14 @@ public class UserModelTest extends AbstractModelTest {
Assert.assertArrayEquals(expectedRequiredActions, actualRequiredActions); 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);
}
} }

View file

@ -119,6 +119,19 @@
"openid-connect": [ "gss delegation credential" ] "openid-connect": [ "gss delegation credential" ]
} }
} }
],
"offlineUserSessions": [
{
"userSessionId": "123",
"data": "something1",
"offlineClientSessions": [
{
"clientSessionId": "456",
"client": "OtherApp",
"data": "something2"
}
]
}
] ]
}, },
{ {