Merge pull request #1088 from patriot1burke/master
oidc and saml broker backchannel logout
This commit is contained in:
commit
d36ee0d0af
54 changed files with 899 additions and 289 deletions
18
broker/core/src/main/java/org/keycloak/broker/provider/FederatedIdentity.java
Normal file → Executable file
18
broker/core/src/main/java/org/keycloak/broker/provider/FederatedIdentity.java
Normal file → Executable file
|
@ -32,6 +32,8 @@ public class FederatedIdentity {
|
||||||
private String email;
|
private String email;
|
||||||
private String token;
|
private String token;
|
||||||
private String identityProviderId;
|
private String identityProviderId;
|
||||||
|
private String brokerSessionId;
|
||||||
|
private String brokerUserId;
|
||||||
|
|
||||||
public FederatedIdentity(String id) {
|
public FederatedIdentity(String id) {
|
||||||
if (id == null) {
|
if (id == null) {
|
||||||
|
@ -102,6 +104,22 @@ public class FederatedIdentity {
|
||||||
this.identityProviderId = identityProviderId;
|
this.identityProviderId = identityProviderId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getBrokerSessionId() {
|
||||||
|
return brokerSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBrokerSessionId(String brokerSessionId) {
|
||||||
|
this.brokerSessionId = brokerSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBrokerUserId() {
|
||||||
|
return brokerUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBrokerUserId(String brokerUserId) {
|
||||||
|
this.brokerUserId = brokerUserId;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "{" +
|
return "{" +
|
||||||
|
|
|
@ -128,16 +128,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
||||||
}
|
}
|
||||||
|
|
||||||
protected FederatedIdentity getFederatedIdentity(Map<String, String> notes, String response) {
|
protected FederatedIdentity getFederatedIdentity(Map<String, String> notes, String response) {
|
||||||
AccessTokenResponse tokenResponse = null;
|
String accessToken = extractTokenFromResponse(response, OAUTH2_PARAMETER_ACCESS_TOKEN);
|
||||||
try {
|
|
||||||
tokenResponse = JsonSerialization.readValue(response, AccessTokenResponse.class);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new IdentityBrokerException("Could not decode access token response.", e);
|
|
||||||
}
|
|
||||||
String accessToken = tokenResponse.getToken();
|
|
||||||
notes.put(FEDERATED_ACCESS_TOKEN, accessToken);
|
|
||||||
notes.put(FEDERATED_REFRESH_TOKEN, tokenResponse.getRefreshToken());
|
|
||||||
notes.put(FEDERATED_TOKEN_EXPIRATION, Long.toString(tokenResponse.getExpiresIn()));
|
|
||||||
|
|
||||||
if (accessToken == null) {
|
if (accessToken == null) {
|
||||||
throw new IdentityBrokerException("No access token from server.");
|
throw new IdentityBrokerException("No access token from server.");
|
||||||
|
@ -146,6 +137,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
||||||
return doGetFederatedIdentity(accessToken);
|
return doGetFederatedIdentity(accessToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected FederatedIdentity doGetFederatedIdentity(String accessToken) {
|
protected FederatedIdentity doGetFederatedIdentity(String accessToken) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -212,12 +204,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if (authorizationCode != null) {
|
if (authorizationCode != null) {
|
||||||
String response = SimpleHttp.doPost(getConfig().getTokenUrl())
|
String response = generateTokenRequest(authorizationCode).asString();
|
||||||
.param(OAUTH2_PARAMETER_CODE, authorizationCode)
|
|
||||||
.param(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
|
|
||||||
.param(OAUTH2_PARAMETER_CLIENT_SECRET, getConfig().getClientSecret())
|
|
||||||
.param(OAUTH2_PARAMETER_REDIRECT_URI, uriInfo.getAbsolutePath().toString())
|
|
||||||
.param(OAUTH2_PARAMETER_GRANT_TYPE, OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE).asString();
|
|
||||||
|
|
||||||
HashMap<String, String> userNotes = new HashMap<String, String>();
|
HashMap<String, String> userNotes = new HashMap<String, String>();
|
||||||
FederatedIdentity federatedIdentity = getFederatedIdentity(userNotes, response);
|
FederatedIdentity federatedIdentity = getFederatedIdentity(userNotes, response);
|
||||||
|
@ -235,5 +222,14 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
||||||
event.error(Errors.IDENTITY_PROVIDER_LOGIN_FAILURE);
|
event.error(Errors.IDENTITY_PROVIDER_LOGIN_FAILURE);
|
||||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SimpleHttp generateTokenRequest(String authorizationCode) {
|
||||||
|
return SimpleHttp.doPost(getConfig().getTokenUrl())
|
||||||
|
.param(OAUTH2_PARAMETER_CODE, authorizationCode)
|
||||||
|
.param(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
|
||||||
|
.param(OAUTH2_PARAMETER_CLIENT_SECRET, getConfig().getClientSecret())
|
||||||
|
.param(OAUTH2_PARAMETER_REDIRECT_URI, uriInfo.getAbsolutePath().toString())
|
||||||
|
.param(OAUTH2_PARAMETER_GRANT_TYPE, OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
package org.keycloak.broker.oidc;
|
||||||
|
|
||||||
|
import org.keycloak.broker.oidc.util.SimpleHttp;
|
||||||
|
import org.keycloak.broker.provider.IdentityProvider;
|
||||||
|
import org.keycloak.constants.AdapterConstants;
|
||||||
|
import org.keycloak.events.EventBuilder;
|
||||||
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.representations.adapters.action.AdminAction;
|
||||||
|
import org.keycloak.representations.adapters.action.LogoutAction;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
import javax.ws.rs.POST;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider {
|
||||||
|
|
||||||
|
public KeycloakOIDCIdentityProvider(OIDCIdentityProviderConfig config) {
|
||||||
|
super(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
|
||||||
|
return new KeycloakEndpoint(callback, realm, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class KeycloakEndpoint extends OIDCEndpoint {
|
||||||
|
public KeycloakEndpoint(AuthenticationCallback callback, RealmModel realm, EventBuilder event) {
|
||||||
|
super(callback, realm, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path(AdapterConstants.K_LOGOUT)
|
||||||
|
public Response backchannelLogout(String input) {
|
||||||
|
JWSInput token = new JWSInput(input);
|
||||||
|
String signingCert = getConfig().getSigningCertificate();
|
||||||
|
if (signingCert != null && !signingCert.trim().equals("")) {
|
||||||
|
if (!token.verify(getConfig().getSigningCertificate())) {
|
||||||
|
return Response.status(400).build(); }
|
||||||
|
}
|
||||||
|
LogoutAction action = null;
|
||||||
|
try {
|
||||||
|
action = JsonSerialization.readValue(token.getContent(), LogoutAction.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
if (!validateAction(action)) return Response.status(400).build();
|
||||||
|
if (action.getKeycloakSessionIds() != null) {
|
||||||
|
for (String sessionId : action.getKeycloakSessionIds()) {
|
||||||
|
String brokerSessionId = getConfig().getAlias() + "." + sessionId;
|
||||||
|
UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId);
|
||||||
|
if (userSession != null) {
|
||||||
|
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean validateAction(AdminAction action) {
|
||||||
|
if (!action.validate()) {
|
||||||
|
logger.warn("admin request failed, not validated" + action.getAction());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (action.isExpired()) {
|
||||||
|
logger.warn("admin request failed, expired token");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!getConfig().getClientId().equals(action.getResource())) {
|
||||||
|
logger.warn("Resource name does not match");
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimpleHttp generateTokenRequest(String authorizationCode) {
|
||||||
|
return super.generateTokenRequest(authorizationCode)
|
||||||
|
.param(AdapterConstants.APPLICATION_SESSION_STATE, "n/a"); // hack to get backchannel logout to work
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* JBoss, Home of Professional Open Source
|
||||||
|
*
|
||||||
|
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.broker.oidc;
|
||||||
|
|
||||||
|
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
|
||||||
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Pedro Igor
|
||||||
|
*/
|
||||||
|
public class KeycloakOIDCIdentityProviderFactory extends AbstractIdentityProviderFactory<KeycloakOIDCIdentityProvider> {
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = "keycloak-oidc";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "Keycloak OpenID Connect";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeycloakOIDCIdentityProvider create(IdentityProviderModel model) {
|
||||||
|
return new KeycloakOIDCIdentityProvider(new OIDCIdentityProviderConfig(model));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> parseConfig(InputStream inputStream) {
|
||||||
|
OIDCConfigurationRepresentation rep = null;
|
||||||
|
try {
|
||||||
|
rep = JsonSerialization.readValue(inputStream, OIDCConfigurationRepresentation.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("failed to load openid connect metadata", e);
|
||||||
|
}
|
||||||
|
OIDCIdentityProviderConfig config = new OIDCIdentityProviderConfig(new IdentityProviderModel());
|
||||||
|
config.setIssuer(rep.getIssuer());
|
||||||
|
config.setLogoutUrl(rep.getLogoutEndpoint());
|
||||||
|
config.setAuthorizationUrl(rep.getAuthorizationEndpoint());
|
||||||
|
config.setTokenUrl(rep.getTokenEndpoint());
|
||||||
|
config.setUserInfoUrl(rep.getUserinfoEndpoint());
|
||||||
|
return config.getConfig();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,19 +18,24 @@
|
||||||
package org.keycloak.broker.oidc;
|
package org.keycloak.broker.oidc;
|
||||||
|
|
||||||
import org.codehaus.jackson.JsonNode;
|
import org.codehaus.jackson.JsonNode;
|
||||||
|
import org.jboss.resteasy.logging.Logger;
|
||||||
import org.keycloak.broker.oidc.util.SimpleHttp;
|
import org.keycloak.broker.oidc.util.SimpleHttp;
|
||||||
import org.keycloak.broker.provider.AuthenticationRequest;
|
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||||
import org.keycloak.broker.provider.FederatedIdentity;
|
import org.keycloak.broker.provider.FederatedIdentity;
|
||||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||||
import org.keycloak.broker.provider.IdentityProvider;
|
import org.keycloak.broker.provider.IdentityProvider;
|
||||||
|
import org.keycloak.constants.AdapterConstants;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
|
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
import org.keycloak.representations.IDToken;
|
import org.keycloak.representations.IDToken;
|
||||||
|
import org.keycloak.representations.adapters.action.AdminAction;
|
||||||
|
import org.keycloak.representations.adapters.action.LogoutAction;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.EventsManager;
|
import org.keycloak.services.managers.EventsManager;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
|
@ -39,10 +44,13 @@ import org.keycloak.services.resources.RealmsResource;
|
||||||
import org.keycloak.services.resources.flows.Flows;
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
@ -53,6 +61,7 @@ import java.util.Map;
|
||||||
* @author Pedro Igor
|
* @author Pedro Igor
|
||||||
*/
|
*/
|
||||||
public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIdentityProviderConfig> {
|
public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIdentityProviderConfig> {
|
||||||
|
protected static final Logger logger = Logger.getLogger(OIDCIdentityProvider.class);
|
||||||
|
|
||||||
public static final String OAUTH2_PARAMETER_PROMPT = "prompt";
|
public static final String OAUTH2_PARAMETER_PROMPT = "prompt";
|
||||||
public static final String SCOPE_OPENID = "openid";
|
public static final String SCOPE_OPENID = "openid";
|
||||||
|
@ -78,6 +87,8 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
||||||
super(callback, realm, event);
|
super(callback, realm, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("logout_response")
|
@Path("logout_response")
|
||||||
public Response logoutResponse(@Context UriInfo uriInfo,
|
public Response logoutResponse(@Context UriInfo uriInfo,
|
||||||
|
@ -99,6 +110,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
||||||
}
|
}
|
||||||
return AuthenticationManager.finishBrowserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
return AuthenticationManager.finishBrowserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -175,6 +187,11 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
||||||
identity.setName(name);
|
identity.setName(name);
|
||||||
identity.setEmail(email);
|
identity.setEmail(email);
|
||||||
|
|
||||||
|
identity.setBrokerUserId(getConfig().getAlias() + "." + id);
|
||||||
|
if (tokenResponse.getSessionState() != null) {
|
||||||
|
identity.setBrokerSessionId(getConfig().getAlias() + "." + tokenResponse.getSessionState());
|
||||||
|
}
|
||||||
|
|
||||||
if (preferredUsername == null) {
|
if (preferredUsername == null) {
|
||||||
preferredUsername = email;
|
preferredUsername = email;
|
||||||
}
|
}
|
||||||
|
@ -229,10 +246,6 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String decodeJWS(String token) {
|
|
||||||
return new JWSInput(token).readContentAsString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getDefaultScopes() {
|
protected String getDefaultScopes() {
|
||||||
return "openid";
|
return "openid";
|
||||||
|
|
|
@ -47,5 +47,14 @@ public class OIDCIdentityProviderConfig extends OAuth2IdentityProviderConfig {
|
||||||
public void setLogoutUrl(String url) {
|
public void setLogoutUrl(String url) {
|
||||||
getConfig().put("logoutUrl", url);
|
getConfig().put("logoutUrl", url);
|
||||||
}
|
}
|
||||||
|
public String getSigningCertificate() {
|
||||||
|
return getConfig().get("signingCertificate");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSigningCertificate(String signingCertificate) {
|
||||||
|
getConfig().put("signingCertificate", signingCertificate);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
1
broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderFactory
Normal file → Executable file
1
broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderFactory
Normal file → Executable file
|
@ -1 +1,2 @@
|
||||||
org.keycloak.broker.oidc.OIDCIdentityProviderFactory
|
org.keycloak.broker.oidc.OIDCIdentityProviderFactory
|
||||||
|
org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory
|
|
@ -78,6 +78,8 @@ import java.util.Map;
|
||||||
public class SAMLEndpoint {
|
public class SAMLEndpoint {
|
||||||
protected static final Logger logger = Logger.getLogger(SAMLEndpoint.class);
|
protected static final Logger logger = Logger.getLogger(SAMLEndpoint.class);
|
||||||
public static final String SAML_FEDERATED_SESSION_INDEX = "SAML_FEDERATED_SESSION_INDEX";
|
public static final String SAML_FEDERATED_SESSION_INDEX = "SAML_FEDERATED_SESSION_INDEX";
|
||||||
|
public static final String SAML_FEDERATED_SUBJECT = "SAML_FEDERATED_SUBJECT";
|
||||||
|
public static final String SAML_FEDERATED_SUBJECT_NAMEFORMAT = "SAML_FEDERATED_SUBJECT_NAMEFORMAT";
|
||||||
protected RealmModel realm;
|
protected RealmModel realm;
|
||||||
protected EventBuilder event;
|
protected EventBuilder event;
|
||||||
protected SAMLIdentityProviderConfig config;
|
protected SAMLIdentityProviderConfig config;
|
||||||
|
@ -179,7 +181,7 @@ public class SAMLEndpoint {
|
||||||
SAMLDocumentHolder holder = extractRequestDocument(samlRequest);
|
SAMLDocumentHolder holder = extractRequestDocument(samlRequest);
|
||||||
RequestAbstractType requestAbstractType = (RequestAbstractType) holder.getSamlObject();
|
RequestAbstractType requestAbstractType = (RequestAbstractType) holder.getSamlObject();
|
||||||
// validate destination
|
// validate destination
|
||||||
if (!uriInfo.getAbsolutePath().toString().equals(requestAbstractType.getDestination())) {
|
if (!uriInfo.getAbsolutePath().equals(requestAbstractType.getDestination())) {
|
||||||
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
||||||
event.error(Errors.INVALID_SAML_RESPONSE);
|
event.error(Errors.INVALID_SAML_RESPONSE);
|
||||||
event.detail(Details.REASON, "invalid_destination");
|
event.detail(Details.REASON, "invalid_destination");
|
||||||
|
@ -210,66 +212,55 @@ public class SAMLEndpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Response logoutRequest(LogoutRequestType request, String relayState) {
|
protected Response logoutRequest(LogoutRequestType request, String relayState) {
|
||||||
UserModel user = session.users().getUserByUsername(request.getNameID().getValue(), realm);
|
String brokerUserId = config.getAlias() + "." + request.getNameID().getValue();
|
||||||
if (user == null) {
|
if (request.getSessionIndex() == null || request.getSessionIndex().isEmpty()) {
|
||||||
event.event(EventType.LOGOUT);
|
List<UserSessionModel> userSessions = session.sessions().getUserSessionByBrokerUserId(realm, brokerUserId);
|
||||||
event.error(Errors.USER_SESSION_NOT_FOUND);
|
for (UserSessionModel userSession : userSessions) {
|
||||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
|
||||||
}
|
|
||||||
List<UserSessionModel> sessions = session.sessions().getUserSessions(realm, user);
|
|
||||||
if (sessions == null || sessions.size() == 0) {
|
|
||||||
event.event(EventType.LOGOUT);
|
|
||||||
event.error(Errors.USER_SESSION_NOT_FOUND);
|
|
||||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
|
||||||
}
|
|
||||||
for (UserSessionModel userSession : sessions) {
|
|
||||||
String brokerId = userSession.getNote(IdentityBrokerService.BROKER_PROVIDER_ID);
|
|
||||||
if (!config.getAlias().equals(brokerId)) continue;
|
|
||||||
boolean logout = false;
|
|
||||||
if (request.getSessionIndex() == null || request.getSessionIndex().size() == 0) {
|
|
||||||
logout = true;
|
|
||||||
} else {
|
|
||||||
for (String sessionIndex : request.getSessionIndex()) {
|
|
||||||
if (sessionIndex.equals(userSession.getNote(SAML_FEDERATED_SESSION_INDEX))) {
|
|
||||||
logout = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (logout) {
|
|
||||||
try {
|
try {
|
||||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Failed to logout", e);
|
logger.warn("failed to do backchannel logout for userSession", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String issuerURL = getEntityId(uriInfo, realm);
|
} else {
|
||||||
SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
|
for (String sessionIndex : request.getSessionIndex()) {
|
||||||
builder.logoutRequestID(request.getID());
|
String brokerSessionId = brokerUserId + "." + sessionIndex;
|
||||||
builder.destination(config.getSingleLogoutServiceUrl());
|
UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId);
|
||||||
builder.issuer(issuerURL);
|
if (userSession != null) {
|
||||||
builder.relayState(relayState);
|
try {
|
||||||
if (config.isWantAuthnRequestsSigned()) {
|
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||||
builder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
} catch (Exception e) {
|
||||||
.signDocument();
|
logger.warn("failed to do backchannel logout for userSession", e);
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
if (config.isPostBindingResponse()) {
|
|
||||||
return builder.postBinding().response();
|
|
||||||
} else {
|
|
||||||
return builder.redirectBinding().response();
|
|
||||||
}
|
}
|
||||||
} catch (ConfigurationException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (ProcessingException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
throw new RuntimeException("Unreachable");
|
|
||||||
|
String issuerURL = getEntityId(uriInfo, realm);
|
||||||
|
SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
|
||||||
|
builder.logoutRequestID(request.getID());
|
||||||
|
builder.destination(config.getSingleLogoutServiceUrl());
|
||||||
|
builder.issuer(issuerURL);
|
||||||
|
builder.relayState(relayState);
|
||||||
|
if (config.isWantAuthnRequestsSigned()) {
|
||||||
|
builder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
||||||
|
.signDocument();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (config.isPostBindingResponse()) {
|
||||||
|
return builder.postBinding().response();
|
||||||
|
} else {
|
||||||
|
return builder.redirectBinding().response();
|
||||||
|
}
|
||||||
|
} catch (ConfigurationException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} catch (ProcessingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getEntityId(UriInfo uriInfo, RealmModel realm) {
|
private String getEntityId(UriInfo uriInfo, RealmModel realm) {
|
||||||
|
@ -283,8 +274,8 @@ public class SAMLEndpoint {
|
||||||
SubjectType.STSubType subType = subject.getSubType();
|
SubjectType.STSubType subType = subject.getSubType();
|
||||||
NameIDType subjectNameID = (NameIDType) subType.getBaseID();
|
NameIDType subjectNameID = (NameIDType) subType.getBaseID();
|
||||||
Map<String, String> notes = new HashMap<>();
|
Map<String, String> notes = new HashMap<>();
|
||||||
notes.put("SAML_FEDERATED_SUBJECT", subjectNameID.getValue());
|
notes.put(SAML_FEDERATED_SUBJECT, subjectNameID.getValue());
|
||||||
if (subjectNameID.getFormat() != null) notes.put("SAML_FEDERATED_SUBJECT_NAMEFORMAT", subjectNameID.getFormat().toString());
|
if (subjectNameID.getFormat() != null) notes.put(SAML_FEDERATED_SUBJECT_NAMEFORMAT, subjectNameID.getFormat().toString());
|
||||||
FederatedIdentity identity = new FederatedIdentity(subjectNameID.getValue());
|
FederatedIdentity identity = new FederatedIdentity(subjectNameID.getValue());
|
||||||
|
|
||||||
identity.setUsername(subjectNameID.getValue());
|
identity.setUsername(subjectNameID.getValue());
|
||||||
|
@ -304,7 +295,10 @@ public class SAMLEndpoint {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
String brokerUserId = config.getAlias() + "." + subjectNameID.getValue();
|
||||||
|
identity.setBrokerUserId(brokerUserId);
|
||||||
if (authn != null && authn.getSessionIndex() != null) {
|
if (authn != null && authn.getSessionIndex() != null) {
|
||||||
|
identity.setBrokerSessionId(identity.getBrokerUserId() + "." + authn.getSessionIndex());
|
||||||
notes.put(SAML_FEDERATED_SESSION_INDEX, authn.getSessionIndex());
|
notes.put(SAML_FEDERATED_SESSION_INDEX, authn.getSessionIndex());
|
||||||
}
|
}
|
||||||
return callback.authenticated(notes, config, identity, relayState);
|
return callback.authenticated(notes, config, identity, relayState);
|
||||||
|
|
|
@ -123,8 +123,8 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||||
|
|
||||||
SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
|
SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
|
||||||
.issuer(getEntityId(uriInfo, realm))
|
.issuer(getEntityId(uriInfo, realm))
|
||||||
.sessionIndex(userSession.getNote("SAML_FEDERATED_SESSION_INDEX"))
|
.sessionIndex(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SESSION_INDEX))
|
||||||
.userPrincipal(userSession.getNote("SAML_FEDERATED_SUBJECT"), userSession.getNote("SAML_FEDERATED_SUBJECT_NAMEFORMAT"))
|
.userPrincipal(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT), userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT_NAMEFORMAT))
|
||||||
.destination(getConfig().getSingleLogoutServiceUrl());
|
.destination(getConfig().getSingleLogoutServiceUrl());
|
||||||
if (getConfig().isWantAuthnRequestsSigned()) {
|
if (getConfig().isWantAuthnRequestsSigned()) {
|
||||||
logoutBuilder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
logoutBuilder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
||||||
|
|
|
@ -1,18 +1,32 @@
|
||||||
package org.keycloak.jose.jws;
|
package org.keycloak.jose.jws;
|
||||||
|
|
||||||
|
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||||
|
import org.keycloak.jose.jws.crypto.SignatureProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public enum Algorithm {
|
public enum Algorithm {
|
||||||
none,
|
|
||||||
HS256,
|
none(null),
|
||||||
HS384,
|
HS256(null),
|
||||||
HS512,
|
HS384(null),
|
||||||
RS256,
|
HS512(null),
|
||||||
RS384,
|
RS256(new RSAProvider()),
|
||||||
RS512,
|
RS384(new RSAProvider()),
|
||||||
ES256,
|
RS512(new RSAProvider()),
|
||||||
ES384,
|
ES256(null),
|
||||||
ES512
|
ES384(null),
|
||||||
|
ES512(null)
|
||||||
|
;
|
||||||
|
private SignatureProvider provider;
|
||||||
|
|
||||||
|
Algorithm(SignatureProvider provider) {
|
||||||
|
this.provider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignatureProvider getProvider() {
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package org.keycloak.jose.jws;
|
||||||
|
|
||||||
import org.keycloak.util.Base64Url;
|
import org.keycloak.util.Base64Url;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
import static org.keycloak.jose.jws.Algorithm.*;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
@ -73,6 +74,13 @@ public class JWSInput {
|
||||||
return signature;
|
return signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean verify(String key) {
|
||||||
|
if (header.getAlgorithm().getProvider() == null) {
|
||||||
|
throw new RuntimeException("signing algorithm not supported");
|
||||||
|
}
|
||||||
|
return header.getAlgorithm().getProvider().verify(this, key);
|
||||||
|
}
|
||||||
|
|
||||||
public <T> T readJsonContent(Class<T> type) throws IOException {
|
public <T> T readJsonContent(Class<T> type) throws IOException {
|
||||||
return JsonSerialization.readValue(content, type);
|
return JsonSerialization.readValue(content, type);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import java.security.NoSuchAlgorithmException;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class HMACProvider {
|
public class HMACProvider implements SignatureProvider {
|
||||||
private static String getJavaAlgorithm(Algorithm alg) {
|
private static String getJavaAlgorithm(Algorithm alg) {
|
||||||
switch (alg) {
|
switch (alg) {
|
||||||
case HS256:
|
case HS256:
|
||||||
|
@ -82,5 +82,8 @@ public class HMACProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean verify(JWSInput input, String key) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,18 @@ package org.keycloak.jose.jws.crypto;
|
||||||
|
|
||||||
import org.keycloak.jose.jws.Algorithm;
|
import org.keycloak.jose.jws.Algorithm;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
|
import org.keycloak.util.PemUtils;
|
||||||
|
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class RSAProvider {
|
public class RSAProvider implements SignatureProvider {
|
||||||
public static String getJavaAlgorithm(Algorithm alg) {
|
public static String getJavaAlgorithm(Algorithm alg) {
|
||||||
switch (alg) {
|
switch (alg) {
|
||||||
case RS256:
|
case RS256:
|
||||||
|
@ -45,6 +47,16 @@ public class RSAProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean verifyViaCertificate(JWSInput input, String cert) {
|
||||||
|
X509Certificate certificate = null;
|
||||||
|
try {
|
||||||
|
certificate = PemUtils.decodeCertificate(cert);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return verify(input, certificate.getPublicKey());
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean verify(JWSInput input, PublicKey publicKey) {
|
public static boolean verify(JWSInput input, PublicKey publicKey) {
|
||||||
try {
|
try {
|
||||||
Signature verifier = getSignature(input.getHeader().getAlgorithm());
|
Signature verifier = getSignature(input.getHeader().getAlgorithm());
|
||||||
|
@ -57,5 +69,10 @@ public class RSAProvider {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean verify(JWSInput input, String key) {
|
||||||
|
return verifyViaCertificate(input, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
11
core/src/main/java/org/keycloak/jose/jws/crypto/SignatureProvider.java
Executable file
11
core/src/main/java/org/keycloak/jose/jws/crypto/SignatureProvider.java
Executable file
|
@ -0,0 +1,11 @@
|
||||||
|
package org.keycloak.jose.jws.crypto;
|
||||||
|
|
||||||
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public interface SignatureProvider {
|
||||||
|
boolean verify(JWSInput input, String key);
|
||||||
|
}
|
|
@ -10,14 +10,16 @@ public class LogoutAction extends AdminAction {
|
||||||
public static final String LOGOUT = "LOGOUT";
|
public static final String LOGOUT = "LOGOUT";
|
||||||
protected List<String> adapterSessionIds;
|
protected List<String> adapterSessionIds;
|
||||||
protected int notBefore;
|
protected int notBefore;
|
||||||
|
protected List<String> keycloakSessionIds;
|
||||||
|
|
||||||
public LogoutAction() {
|
public LogoutAction() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public LogoutAction(String id, int expiration, String resource, List<String> adapterSessionIds, int notBefore) {
|
public LogoutAction(String id, int expiration, String resource, List<String> adapterSessionIds, int notBefore, List<String> keycloakSessionIds) {
|
||||||
super(id, expiration, resource, LOGOUT);
|
super(id, expiration, resource, LOGOUT);
|
||||||
this.adapterSessionIds = adapterSessionIds;
|
this.adapterSessionIds = adapterSessionIds;
|
||||||
this.notBefore = notBefore;
|
this.notBefore = notBefore;
|
||||||
|
this.keycloakSessionIds = keycloakSessionIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,6 +35,14 @@ public class LogoutAction extends AdminAction {
|
||||||
return adapterSessionIds;
|
return adapterSessionIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getKeycloakSessionIds() {
|
||||||
|
return keycloakSessionIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKeycloakSessionIds(List<String> keycloakSessionIds) {
|
||||||
|
this.keycloakSessionIds = keycloakSessionIds;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean validate() {
|
public boolean validate() {
|
||||||
return LOGOUT.equals(action);
|
return LOGOUT.equals(action);
|
||||||
|
|
|
@ -700,6 +700,9 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.hidePassword = true;
|
$scope.hidePassword = true;
|
||||||
|
$scope.fromUrl = {
|
||||||
|
data: ''
|
||||||
|
};
|
||||||
|
|
||||||
if (instance && instance.alias) {
|
if (instance && instance.alias) {
|
||||||
$scope.identityProvider = angular.copy(instance);
|
$scope.identityProvider = angular.copy(instance);
|
||||||
|
@ -798,21 +801,22 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var input = {
|
var input = {
|
||||||
fromUrl: $scope.fromUrl,
|
fromUrl: $scope.fromUrl.data,
|
||||||
providerId: providerFactory.id
|
providerId: providerFactory.id
|
||||||
}
|
}
|
||||||
$http.post(authUrl + '/admin/realms/' + realm.realm + '/identity-provider/import-config', input)
|
$http.post(authUrl + '/admin/realms/' + realm.realm + '/identity-provider/import-config', input)
|
||||||
.success(function(data, status, headers) {
|
.success(function(data, status, headers) {
|
||||||
setConfig(data);
|
setConfig(data);
|
||||||
$scope.fromUrl = null;
|
$scope.fromUrl.data = '';
|
||||||
$scope.importUrl = false;
|
$scope.importUrl = false;
|
||||||
Notifications.success("Imported config information from url.");
|
Notifications.success("Imported config information from url.");
|
||||||
}).error(function() {
|
}).error(function() {
|
||||||
Notifications.error("Config can not be imported. Please verify the url.");
|
Notifications.error("Config can not be imported. Please verify the url.");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
$scope.$watch('fromUrl', function(newVal, oldVal){
|
$scope.$watch('fromUrl.data', function(newVal, oldVal){
|
||||||
if ($scope.fromUrl && $scope.fromUrl.length > 0) {
|
console.log('watch fromUrl: ' + newVal + " " + oldVal);
|
||||||
|
if ($scope.fromUrl.data && $scope.fromUrl.data.length > 0) {
|
||||||
$scope.importUrl = true;
|
$scope.importUrl = true;
|
||||||
} else{
|
} else{
|
||||||
$scope.importUrl = false;
|
$scope.importUrl = false;
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<div data-ng-include data-src="resourceUrl + '/partials/realm-identity-provider-oidc.html'"></div>
|
|
@ -129,7 +129,7 @@
|
||||||
<div class="form-group" data-ng-show="newIdentityProvider">
|
<div class="form-group" data-ng-show="newIdentityProvider">
|
||||||
<label class="col-sm-2 control-label" for="fromUrl">Import From Url</label>
|
<label class="col-sm-2 control-label" for="fromUrl">Import From Url</label>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<input class="form-control" id="fromUrl" type="text" ng-model="fromUrl">
|
<input class="form-control" id="fromUrl" type="text" ng-model="fromUrl.data">
|
||||||
</div>
|
</div>
|
||||||
<span tooltip-placement="right" tooltip="Import metadata from a remote IDP discovery descriptor." class="fa fa-info-circle"></span>
|
<span tooltip-placement="right" tooltip="Import metadata from a remote IDP discovery descriptor." class="fa fa-info-circle"></span>
|
||||||
<div class="col-sm-4" data-ng-show="importUrl">
|
<div class="col-sm-4" data-ng-show="importUrl">
|
||||||
|
|
|
@ -9,6 +9,15 @@ public interface UserSessionModel {
|
||||||
|
|
||||||
String getId();
|
String getId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If created via a broker external login, this is an identifier that can be
|
||||||
|
* used to match external broker backchannel logout requests to a UserSession
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
String getBrokerSessionId();
|
||||||
|
String getBrokerUserId();
|
||||||
|
|
||||||
UserModel getUser();
|
UserModel getUser();
|
||||||
|
|
||||||
String getLoginUsername();
|
String getLoginUsername();
|
||||||
|
|
|
@ -15,11 +15,15 @@ public interface UserSessionProvider extends Provider {
|
||||||
ClientSessionModel getClientSession(RealmModel realm, String id);
|
ClientSessionModel getClientSession(RealmModel realm, String id);
|
||||||
ClientSessionModel getClientSession(String id);
|
ClientSessionModel getClientSession(String id);
|
||||||
|
|
||||||
UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe);
|
UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId);
|
||||||
UserSessionModel getUserSession(RealmModel realm, String id);
|
UserSessionModel getUserSession(RealmModel realm, String id);
|
||||||
List<UserSessionModel> getUserSessions(RealmModel realm, UserModel user);
|
List<UserSessionModel> getUserSessions(RealmModel realm, UserModel user);
|
||||||
List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client);
|
List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client);
|
||||||
List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults);
|
List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults);
|
||||||
|
List<UserSessionModel> getUserSessionByBrokerUserId(RealmModel realm, String brokerUserId);
|
||||||
|
UserSessionModel getUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId);
|
||||||
|
|
||||||
|
List<UserSessionModel> getUserSessionsByNote(RealmModel realm, String noteName, String noteValue);
|
||||||
|
|
||||||
int getActiveUserSessions(RealmModel realm, ClientModel client);
|
int getActiveUserSessions(RealmModel realm, ClientModel client);
|
||||||
void removeUserSession(RealmModel realm, UserSessionModel session);
|
void removeUserSession(RealmModel realm, UserSessionModel session);
|
||||||
|
|
|
@ -70,7 +70,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe) {
|
public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
|
||||||
String id = KeycloakModelUtils.generateId();
|
String id = KeycloakModelUtils.generateId();
|
||||||
|
|
||||||
UserSessionEntity entity = new UserSessionEntity();
|
UserSessionEntity entity = new UserSessionEntity();
|
||||||
|
@ -81,6 +81,8 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
entity.setIpAddress(ipAddress);
|
entity.setIpAddress(ipAddress);
|
||||||
entity.setAuthMethod(authMethod);
|
entity.setAuthMethod(authMethod);
|
||||||
entity.setRememberMe(rememberMe);
|
entity.setRememberMe(rememberMe);
|
||||||
|
entity.setBrokerSessionId(brokerSessionId);
|
||||||
|
entity.setBrokerUserId(brokerUserId);
|
||||||
|
|
||||||
int currentTime = Time.currentTime();
|
int currentTime = Time.currentTime();
|
||||||
|
|
||||||
|
@ -124,6 +126,28 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
return wrapUserSessions(realm, sessions.values());
|
return wrapUserSessions(realm, sessions.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserSessionModel> getUserSessionByBrokerUserId(RealmModel realm, String brokerUserId) {
|
||||||
|
Map<String, UserSessionEntity> sessions = new MapReduceTask(sessionCache)
|
||||||
|
.mappedWith(UserSessionMapper.create(realm.getId()).brokerUserId(brokerUserId))
|
||||||
|
.reducedWith(new FirstResultReducer())
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
return wrapUserSessions(realm, sessions.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserSessionModel getUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId) {
|
||||||
|
Map<String, UserSessionEntity> sessions = new MapReduceTask(sessionCache)
|
||||||
|
.mappedWith(UserSessionMapper.create(realm.getId()).brokerSessionId(brokerSessionId))
|
||||||
|
.reducedWith(new FirstResultReducer())
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
List<UserSessionModel> userSessionModels = wrapUserSessions(realm, sessions.values());
|
||||||
|
if (userSessionModels.isEmpty()) return null;
|
||||||
|
return userSessionModels.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client) {
|
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client) {
|
||||||
return getUserSessions(realm, client, -1, -1);
|
return getUserSessions(realm, client, -1, -1);
|
||||||
|
@ -173,7 +197,14 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
||||||
return userSessions;
|
return userSessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<UserSessionModel> getUserSessionsByNote(RealmModel realm, Map<String, String> notes) {
|
@Override
|
||||||
|
public List<UserSessionModel> getUserSessionsByNote(RealmModel realm, String noteName, String noteValue) {
|
||||||
|
HashMap<String, String> notes = new HashMap<>();
|
||||||
|
notes.put(noteName, noteValue);
|
||||||
|
return getUserSessionsByNotes(realm, notes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<UserSessionModel> getUserSessionsByNotes(RealmModel realm, Map<String, String> notes) {
|
||||||
Map<String, UserSessionEntity> sessions = new MapReduceTask(sessionCache)
|
Map<String, UserSessionEntity> sessions = new MapReduceTask(sessionCache)
|
||||||
.mappedWith(UserSessionNoteMapper.create(realm.getId()).notes(notes))
|
.mappedWith(UserSessionNoteMapper.create(realm.getId()).notes(notes))
|
||||||
.reducedWith(new FirstResultReducer())
|
.reducedWith(new FirstResultReducer())
|
||||||
|
|
|
@ -42,6 +42,15 @@ public class UserSessionAdapter implements UserSessionModel {
|
||||||
return entity.getId();
|
return entity.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBrokerSessionId() {
|
||||||
|
return entity.getBrokerSessionId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBrokerUserId() {
|
||||||
|
return entity.getBrokerUserId();
|
||||||
|
}
|
||||||
public UserModel getUser() {
|
public UserModel getUser() {
|
||||||
return session.users().getUserById(entity.getUser(), realm);
|
return session.users().getUserById(entity.getUser(), realm);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,9 @@ public class UserSessionEntity extends SessionEntity {
|
||||||
|
|
||||||
private String user;
|
private String user;
|
||||||
|
|
||||||
|
private String brokerSessionId;
|
||||||
|
private String brokerUserId;
|
||||||
|
|
||||||
private String loginUsername;
|
private String loginUsername;
|
||||||
|
|
||||||
private String ipAddress;
|
private String ipAddress;
|
||||||
|
@ -109,4 +112,20 @@ public class UserSessionEntity extends SessionEntity {
|
||||||
public void setState(UserSessionModel.State state) {
|
public void setState(UserSessionModel.State state) {
|
||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getBrokerSessionId() {
|
||||||
|
return brokerSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBrokerSessionId(String brokerSessionId) {
|
||||||
|
this.brokerSessionId = brokerSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBrokerUserId() {
|
||||||
|
return brokerUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBrokerUserId(String brokerUserId) {
|
||||||
|
this.brokerUserId = brokerUserId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,9 @@ public class UserSessionMapper implements Mapper<String, SessionEntity, String,
|
||||||
|
|
||||||
private Long expiredRefresh;
|
private Long expiredRefresh;
|
||||||
|
|
||||||
|
private String brokerSessionId;
|
||||||
|
private String brokerUserId;
|
||||||
|
|
||||||
public static UserSessionMapper create(String realm) {
|
public static UserSessionMapper create(String realm) {
|
||||||
return new UserSessionMapper(realm);
|
return new UserSessionMapper(realm);
|
||||||
}
|
}
|
||||||
|
@ -50,6 +53,16 @@ public class UserSessionMapper implements Mapper<String, SessionEntity, String,
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UserSessionMapper brokerSessionId(String id) {
|
||||||
|
this.brokerSessionId = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserSessionMapper brokerUserId(String id) {
|
||||||
|
this.brokerUserId = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void map(String key, SessionEntity e, Collector collector) {
|
public void map(String key, SessionEntity e, Collector collector) {
|
||||||
if (!(e instanceof UserSessionEntity)) {
|
if (!(e instanceof UserSessionEntity)) {
|
||||||
|
@ -66,6 +79,9 @@ public class UserSessionMapper implements Mapper<String, SessionEntity, String,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (brokerSessionId != null && !brokerSessionId.equals(entity.getBrokerSessionId())) return;
|
||||||
|
if (brokerUserId != null && !brokerUserId.equals(entity.getBrokerUserId())) return;
|
||||||
|
|
||||||
if (expired != null && expiredRefresh != null && entity.getStarted() > expired && entity.getLastSessionRefresh() > expiredRefresh) {
|
if (expired != null && expiredRefresh != null && entity.getStarted() > expired && entity.getLastSessionRefresh() > expiredRefresh) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import org.keycloak.models.UserSessionProvider;
|
||||||
import org.keycloak.models.UsernameLoginFailureModel;
|
import org.keycloak.models.UsernameLoginFailureModel;
|
||||||
import org.keycloak.models.sessions.jpa.entities.ClientSessionEntity;
|
import org.keycloak.models.sessions.jpa.entities.ClientSessionEntity;
|
||||||
import org.keycloak.models.sessions.jpa.entities.UserSessionEntity;
|
import org.keycloak.models.sessions.jpa.entities.UserSessionEntity;
|
||||||
|
import org.keycloak.models.sessions.jpa.entities.UserSessionNoteEntity;
|
||||||
import org.keycloak.models.sessions.jpa.entities.UsernameLoginFailureEntity;
|
import org.keycloak.models.sessions.jpa.entities.UsernameLoginFailureEntity;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.models.utils.RealmInfoUtil;
|
import org.keycloak.models.utils.RealmInfoUtil;
|
||||||
|
@ -19,6 +20,7 @@ import javax.persistence.EntityManager;
|
||||||
import javax.persistence.TypedQuery;
|
import javax.persistence.TypedQuery;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -84,7 +86,7 @@ public class JpaUserSessionProvider implements UserSessionProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe) {
|
public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
|
||||||
UserSessionEntity entity = new UserSessionEntity();
|
UserSessionEntity entity = new UserSessionEntity();
|
||||||
entity.setId(KeycloakModelUtils.generateId());
|
entity.setId(KeycloakModelUtils.generateId());
|
||||||
entity.setRealmId(realm.getId());
|
entity.setRealmId(realm.getId());
|
||||||
|
@ -93,6 +95,8 @@ public class JpaUserSessionProvider implements UserSessionProvider {
|
||||||
entity.setIpAddress(ipAddress);
|
entity.setIpAddress(ipAddress);
|
||||||
entity.setAuthMethod(authMethod);
|
entity.setAuthMethod(authMethod);
|
||||||
entity.setRememberMe(rememberMe);
|
entity.setRememberMe(rememberMe);
|
||||||
|
entity.setBrokerSessionId(brokerSessionId);
|
||||||
|
entity.setBrokerUserId(brokerUserId);
|
||||||
|
|
||||||
int currentTime = Time.currentTime();
|
int currentTime = Time.currentTime();
|
||||||
|
|
||||||
|
@ -121,6 +125,44 @@ public class JpaUserSessionProvider implements UserSessionProvider {
|
||||||
return sessions;
|
return sessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserSessionModel> getUserSessionByBrokerUserId(RealmModel realm, String brokerUserId) {
|
||||||
|
List<UserSessionModel> sessions = new LinkedList<UserSessionModel>();
|
||||||
|
TypedQuery<UserSessionEntity> query = em.createNamedQuery("getUserSessionByBrokerUserId", UserSessionEntity.class)
|
||||||
|
.setParameter("realmId", realm.getId())
|
||||||
|
.setParameter("brokerUserId", brokerUserId);
|
||||||
|
for (UserSessionEntity e : query.getResultList()) {
|
||||||
|
sessions.add(new UserSessionAdapter(session, em, realm, e));
|
||||||
|
}
|
||||||
|
return sessions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserSessionModel getUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId) {
|
||||||
|
List<UserSessionModel> sessions = new LinkedList<UserSessionModel>();
|
||||||
|
TypedQuery<UserSessionEntity> query = em.createNamedQuery("getUserSessionByBrokerSessionId", UserSessionEntity.class)
|
||||||
|
.setParameter("realmId", realm.getId())
|
||||||
|
.setParameter("brokerSessionId", brokerSessionId);
|
||||||
|
for (UserSessionEntity e : query.getResultList()) {
|
||||||
|
sessions.add(new UserSessionAdapter(session, em, realm, e));
|
||||||
|
}
|
||||||
|
if (sessions.isEmpty()) return null;
|
||||||
|
return sessions.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserSessionModel> getUserSessionsByNote(RealmModel realm, String noteName, String noteValue) {
|
||||||
|
List<UserSessionModel> sessions = new LinkedList<UserSessionModel>();
|
||||||
|
TypedQuery<UserSessionNoteEntity> query = em.createNamedQuery("selectNoteByNameValue", UserSessionNoteEntity.class)
|
||||||
|
.setParameter("name", noteName)
|
||||||
|
.setParameter("value", noteValue);
|
||||||
|
for (UserSessionNoteEntity note : query.getResultList()) {
|
||||||
|
if (!note.getUserSession().getRealmId().equals(realm.getId())) continue;
|
||||||
|
sessions.add(new UserSessionAdapter(session, em, realm, note.getUserSession()));
|
||||||
|
}
|
||||||
|
return sessions;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client) {
|
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client) {
|
||||||
return getUserSessions(realm, client, -1, -1);
|
return getUserSessions(realm, client, -1, -1);
|
||||||
|
|
|
@ -40,6 +40,16 @@ public class UserSessionAdapter implements UserSessionModel {
|
||||||
return entity.getId();
|
return entity.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBrokerSessionId() {
|
||||||
|
return entity.getBrokerSessionId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBrokerUserId() {
|
||||||
|
return entity.getBrokerUserId();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel getUser() {
|
public UserModel getUser() {
|
||||||
return session.users().getUserById(entity.getUserId(), realm);
|
return session.users().getUserById(entity.getUserId(), realm);
|
||||||
|
|
|
@ -20,6 +20,8 @@ import java.util.Collection;
|
||||||
@Table(name = "USER_SESSION")
|
@Table(name = "USER_SESSION")
|
||||||
@NamedQueries({
|
@NamedQueries({
|
||||||
@NamedQuery(name = "getUserSessionByUser", query = "select s from UserSessionEntity s where s.realmId = :realmId and s.userId = :userId order by s.started, s.id"),
|
@NamedQuery(name = "getUserSessionByUser", query = "select s from UserSessionEntity s where s.realmId = :realmId and s.userId = :userId order by s.started, s.id"),
|
||||||
|
@NamedQuery(name = "getUserSessionByBrokerSessionId", query = "select s from UserSessionEntity s where s.realmId = :realmId and s.brokerSessionId = :brokerSessionId order by s.started, s.id"),
|
||||||
|
@NamedQuery(name = "getUserSessionByBrokerUserId", query = "select s from UserSessionEntity s where s.realmId = :realmId and s.brokerUserId = :brokerUserId order by s.started, s.id"),
|
||||||
@NamedQuery(name = "getUserSessionByClient", query = "select s from UserSessionEntity s join s.clientSessions c where s.realmId = :realmId and c.clientId = :clientId order by s.started, s.id"),
|
@NamedQuery(name = "getUserSessionByClient", query = "select s from UserSessionEntity s join s.clientSessions c where s.realmId = :realmId and c.clientId = :clientId order by s.started, s.id"),
|
||||||
@NamedQuery(name = "getActiveUserSessionByClient", query = "select count(s) from UserSessionEntity s join s.clientSessions c where s.realmId = :realmId and c.clientId = :clientId"),
|
@NamedQuery(name = "getActiveUserSessionByClient", query = "select count(s) from UserSessionEntity s join s.clientSessions c where s.realmId = :realmId and c.clientId = :clientId"),
|
||||||
@NamedQuery(name = "removeUserSessionByRealm", query = "delete from UserSessionEntity s where s.realmId = :realmId"),
|
@NamedQuery(name = "removeUserSessionByRealm", query = "delete from UserSessionEntity s where s.realmId = :realmId"),
|
||||||
|
@ -35,6 +37,12 @@ public class UserSessionEntity {
|
||||||
@Column(name="USER_ID")
|
@Column(name="USER_ID")
|
||||||
protected String userId;
|
protected String userId;
|
||||||
|
|
||||||
|
@Column(name="BROKER_SESSION_ID")
|
||||||
|
protected String brokerSessionId;
|
||||||
|
|
||||||
|
@Column(name="BROKER_USER_ID")
|
||||||
|
protected String brokerUserId;
|
||||||
|
|
||||||
@Column(name="LOGIN_USERNAME")
|
@Column(name="LOGIN_USERNAME")
|
||||||
protected String loginUsername;
|
protected String loginUsername;
|
||||||
|
|
||||||
|
@ -156,4 +164,20 @@ public class UserSessionEntity {
|
||||||
public void setNotes(Collection<UserSessionNoteEntity> notes) {
|
public void setNotes(Collection<UserSessionNoteEntity> notes) {
|
||||||
this.notes = notes;
|
this.notes = notes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getBrokerSessionId() {
|
||||||
|
return brokerSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBrokerSessionId(String brokerSessionId) {
|
||||||
|
this.brokerSessionId = brokerSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBrokerUserId() {
|
||||||
|
return brokerUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBrokerUserId(String brokerUserId) {
|
||||||
|
this.brokerUserId = brokerUserId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import java.io.Serializable;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
@NamedQueries({
|
@NamedQueries({
|
||||||
|
@NamedQuery(name = "selectNoteByNameValue", query="select r from UserSessionNoteEntity r where r.name = :name and r.value = :value"),
|
||||||
@NamedQuery(name = "removeUserSessionNoteByUser", query="delete from UserSessionNoteEntity r where r.userSession IN (select s from UserSessionEntity s where s.realmId = :realmId and s.userId = :userId)"),
|
@NamedQuery(name = "removeUserSessionNoteByUser", query="delete from UserSessionNoteEntity r where r.userSession IN (select s from UserSessionEntity s where s.realmId = :realmId and s.userId = :userId)"),
|
||||||
@NamedQuery(name = "removeUserSessionNoteByRealm", query="delete from UserSessionNoteEntity r where r.userSession IN (select c from UserSessionEntity c where c.realmId = :realmId)"),
|
@NamedQuery(name = "removeUserSessionNoteByRealm", query="delete from UserSessionNoteEntity r where r.userSession IN (select c from UserSessionEntity c where c.realmId = :realmId)"),
|
||||||
@NamedQuery(name = "removeUserSessionNoteByExpired", query = "delete from UserSessionNoteEntity r where r.userSession IN (select s from UserSessionEntity s where s.realmId = :realmId and (s.started < :maxTime or s.lastSessionRefresh < :idleTime))")
|
@NamedQuery(name = "removeUserSessionNoteByExpired", query = "delete from UserSessionNoteEntity r where r.userSession IN (select s from UserSessionEntity s where s.realmId = :realmId and (s.started < :maxTime or s.lastSessionRefresh < :idleTime))")
|
||||||
|
|
|
@ -19,10 +19,13 @@ import org.keycloak.util.Time;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -31,14 +34,18 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
||||||
|
|
||||||
private final KeycloakSession session;
|
private final KeycloakSession session;
|
||||||
private final ConcurrentHashMap<String, UserSessionEntity> userSessions;
|
private final ConcurrentHashMap<String, UserSessionEntity> userSessions;
|
||||||
|
private final ConcurrentHashMap<String, String> userSessionsByBrokerSessionId;
|
||||||
|
private final ConcurrentHashMap<String, Set<String>> userSessionsByBrokerUserId;
|
||||||
private final ConcurrentHashMap<String, ClientSessionEntity> clientSessions;
|
private final ConcurrentHashMap<String, ClientSessionEntity> clientSessions;
|
||||||
private final ConcurrentHashMap<UsernameLoginFailureKey, UsernameLoginFailureEntity> loginFailures;
|
private final ConcurrentHashMap<UsernameLoginFailureKey, UsernameLoginFailureEntity> loginFailures;
|
||||||
|
|
||||||
public MemUserSessionProvider(KeycloakSession session, ConcurrentHashMap<String, UserSessionEntity> userSessions, ConcurrentHashMap<String, ClientSessionEntity> clientSessions, ConcurrentHashMap<UsernameLoginFailureKey, UsernameLoginFailureEntity> loginFailures) {
|
public MemUserSessionProvider(KeycloakSession session, ConcurrentHashMap<String, UserSessionEntity> userSessions, ConcurrentHashMap<String, String> userSessionsByBrokerSessionId, ConcurrentHashMap<String, Set<String>> userSessionsByBrokerUserId, ConcurrentHashMap<String, ClientSessionEntity> clientSessions, ConcurrentHashMap<UsernameLoginFailureKey, UsernameLoginFailureEntity> loginFailures) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.userSessions = userSessions;
|
this.userSessions = userSessions;
|
||||||
this.clientSessions = clientSessions;
|
this.clientSessions = clientSessions;
|
||||||
this.loginFailures = loginFailures;
|
this.loginFailures = loginFailures;
|
||||||
|
this.userSessionsByBrokerSessionId = userSessionsByBrokerSessionId;
|
||||||
|
this.userSessionsByBrokerUserId = userSessionsByBrokerUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -69,7 +76,7 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe) {
|
public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
|
||||||
String id = KeycloakModelUtils.generateId();
|
String id = KeycloakModelUtils.generateId();
|
||||||
|
|
||||||
UserSessionEntity entity = new UserSessionEntity();
|
UserSessionEntity entity = new UserSessionEntity();
|
||||||
|
@ -85,12 +92,55 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
||||||
|
|
||||||
entity.setStarted(currentTime);
|
entity.setStarted(currentTime);
|
||||||
entity.setLastSessionRefresh(currentTime);
|
entity.setLastSessionRefresh(currentTime);
|
||||||
|
entity.setBrokerSessionId(brokerSessionId);
|
||||||
|
entity.setBrokerUserId(brokerUserId);
|
||||||
|
|
||||||
userSessions.put(id, entity);
|
userSessions.put(id, entity);
|
||||||
|
if (brokerSessionId != null) {
|
||||||
|
userSessionsByBrokerSessionId.put(brokerSessionId, id);
|
||||||
|
}
|
||||||
|
if (brokerUserId != null) {
|
||||||
|
while (true) { // while loop gets around a race condition when a user session is removed
|
||||||
|
Set<String> set = userSessionsByBrokerUserId.get(brokerUserId);
|
||||||
|
if (set == null) {
|
||||||
|
Set<String> value = new HashSet<>();
|
||||||
|
set = userSessionsByBrokerUserId.putIfAbsent(brokerUserId, value);
|
||||||
|
if (set == null) {
|
||||||
|
set = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
synchronized (set) {
|
||||||
|
set.add(id);
|
||||||
|
}
|
||||||
|
if (userSessionsByBrokerUserId.get(brokerUserId) == set) {
|
||||||
|
// we are ensured set isn't deleted before the new id is added
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new UserSessionAdapter(session, this, realm, entity);
|
return new UserSessionAdapter(session, this, realm, entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserSessionModel> getUserSessionByBrokerUserId(RealmModel realm, String brokerUserId) {
|
||||||
|
Set<String> sessions = userSessionsByBrokerUserId.get(brokerUserId);
|
||||||
|
if (sessions == null) return Collections.emptyList();
|
||||||
|
List<UserSessionModel> userSessions = new LinkedList<UserSessionModel>();
|
||||||
|
for (String id : sessions) {
|
||||||
|
UserSessionModel userSession = getUserSession(realm, id);
|
||||||
|
if (userSession != null) userSessions.add(userSession);
|
||||||
|
}
|
||||||
|
return userSessions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserSessionModel getUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId) {
|
||||||
|
String id = userSessionsByBrokerSessionId.get(brokerSessionId);
|
||||||
|
if (id == null) return null;
|
||||||
|
return getUserSession(realm, id);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserSessionModel getUserSession(RealmModel realm, String id) {
|
public UserSessionModel getUserSession(RealmModel realm, String id) {
|
||||||
UserSessionEntity entity = getUserSessionEntity(realm, id);
|
UserSessionEntity entity = getUserSessionEntity(realm, id);
|
||||||
|
@ -116,6 +166,17 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
||||||
return userSessions;
|
return userSessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserSessionModel> getUserSessionsByNote(RealmModel realm, String noteName, String noteValue) {
|
||||||
|
List<UserSessionModel> userSessions = new LinkedList<UserSessionModel>();
|
||||||
|
for (UserSessionEntity s : this.userSessions.values()) {
|
||||||
|
if (s.getRealm().equals(realm.getId()) && noteValue.equals(s.getNotes().get(noteName))) {
|
||||||
|
userSessions.add(new UserSessionAdapter(session, this, realm, s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return userSessions;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client) {
|
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client) {
|
||||||
List<UserSessionEntity> userSessionEntities = new LinkedList<UserSessionEntity>();
|
List<UserSessionEntity> userSessionEntities = new LinkedList<UserSessionEntity>();
|
||||||
|
@ -158,9 +219,7 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
||||||
UserSessionEntity entity = getUserSessionEntity(realm, session.getId());
|
UserSessionEntity entity = getUserSessionEntity(realm, session.getId());
|
||||||
if (entity != null) {
|
if (entity != null) {
|
||||||
userSessions.remove(entity.getId());
|
userSessions.remove(entity.getId());
|
||||||
for (ClientSessionEntity clientSession : entity.getClientSessions()) {
|
remove(entity);
|
||||||
clientSessions.remove(clientSession.getId());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,12 +230,29 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
||||||
UserSessionEntity s = itr.next();
|
UserSessionEntity s = itr.next();
|
||||||
if (s.getRealm().equals(realm.getId()) && s.getUser().equals(user.getId())) {
|
if (s.getRealm().equals(realm.getId()) && s.getUser().equals(user.getId())) {
|
||||||
itr.remove();
|
itr.remove();
|
||||||
|
remove(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (ClientSessionEntity clientSession : s.getClientSessions()) {
|
protected void remove(UserSessionEntity s) {
|
||||||
clientSessions.remove(clientSession.getId());
|
if (s.getBrokerSessionId() != null) {
|
||||||
|
userSessionsByBrokerSessionId.remove(s.getBrokerSessionId());
|
||||||
|
}
|
||||||
|
if (s.getBrokerUserId() != null) {
|
||||||
|
Set<String> set = userSessionsByBrokerUserId.get(s.getBrokerUserId());
|
||||||
|
if (set != null) {
|
||||||
|
synchronized (set) {
|
||||||
|
set.remove(s.getId());
|
||||||
|
// this is a race condition :(
|
||||||
|
// Since it will be very rare for a user to have concurrent sessions, I'm hoping we never hit this
|
||||||
|
if (set.isEmpty()) userSessionsByBrokerUserId.remove(s.getBrokerUserId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (ClientSessionEntity clientSession : s.getClientSessions()) {
|
||||||
|
clientSessions.remove(clientSession.getId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -187,9 +263,7 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
||||||
if (s.getRealm().equals(realm.getId()) && (s.getLastSessionRefresh() < Time.currentTime() - realm.getSsoSessionIdleTimeout() || s.getStarted() < Time.currentTime() - realm.getSsoSessionMaxLifespan())) {
|
if (s.getRealm().equals(realm.getId()) && (s.getLastSessionRefresh() < Time.currentTime() - realm.getSsoSessionIdleTimeout() || s.getStarted() < Time.currentTime() - realm.getSsoSessionMaxLifespan())) {
|
||||||
itr.remove();
|
itr.remove();
|
||||||
|
|
||||||
for (ClientSessionEntity clientSession : s.getClientSessions()) {
|
remove(s);
|
||||||
clientSessions.remove(clientSession.getId());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int expired = Time.currentTime() - RealmInfoUtil.getDettachedClientSessionLifespan(realm);
|
int expired = Time.currentTime() - RealmInfoUtil.getDettachedClientSessionLifespan(realm);
|
||||||
|
@ -210,9 +284,7 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
||||||
if (s.getRealm().equals(realm.getId())) {
|
if (s.getRealm().equals(realm.getId())) {
|
||||||
itr.remove();
|
itr.remove();
|
||||||
|
|
||||||
for (ClientSessionEntity clientSession : s.getClientSessions()) {
|
remove(s);
|
||||||
clientSessions.remove(clientSession.getId());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Iterator<ClientSessionEntity> citr = clientSessions.values().iterator();
|
Iterator<ClientSessionEntity> citr = clientSessions.values().iterator();
|
||||||
|
|
|
@ -10,6 +10,7 @@ import org.keycloak.models.sessions.mem.entities.UserSessionEntity;
|
||||||
import org.keycloak.models.sessions.mem.entities.UsernameLoginFailureEntity;
|
import org.keycloak.models.sessions.mem.entities.UsernameLoginFailureEntity;
|
||||||
import org.keycloak.models.sessions.mem.entities.UsernameLoginFailureKey;
|
import org.keycloak.models.sessions.mem.entities.UsernameLoginFailureKey;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,10 +25,12 @@ public class MemUserSessionProviderFactory implements UserSessionProviderFactory
|
||||||
private ConcurrentHashMap<String, ClientSessionEntity> clientSessions = new ConcurrentHashMap<String, ClientSessionEntity>();
|
private ConcurrentHashMap<String, ClientSessionEntity> clientSessions = new ConcurrentHashMap<String, ClientSessionEntity>();
|
||||||
|
|
||||||
private ConcurrentHashMap<UsernameLoginFailureKey, UsernameLoginFailureEntity> loginFailures = new ConcurrentHashMap<UsernameLoginFailureKey, UsernameLoginFailureEntity>();
|
private ConcurrentHashMap<UsernameLoginFailureKey, UsernameLoginFailureEntity> loginFailures = new ConcurrentHashMap<UsernameLoginFailureKey, UsernameLoginFailureEntity>();
|
||||||
|
private final ConcurrentHashMap<String, String> userSessionsByBrokerSessionId = new ConcurrentHashMap<>();
|
||||||
|
private final ConcurrentHashMap<String, Set<String>> userSessionsByBrokerUserId = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserSessionProvider create(KeycloakSession session) {
|
public UserSessionProvider create(KeycloakSession session) {
|
||||||
return new MemUserSessionProvider(session, userSessions, clientSessions, loginFailures);
|
return new MemUserSessionProvider(session, userSessions, userSessionsByBrokerSessionId, userSessionsByBrokerUserId, clientSessions, loginFailures);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -43,6 +46,8 @@ public class MemUserSessionProviderFactory implements UserSessionProviderFactory
|
||||||
public void close() {
|
public void close() {
|
||||||
userSessions.clear();
|
userSessions.clear();
|
||||||
loginFailures.clear();
|
loginFailures.clear();
|
||||||
|
userSessionsByBrokerSessionId.clear();
|
||||||
|
userSessionsByBrokerUserId.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -38,6 +38,16 @@ public class UserSessionAdapter implements UserSessionModel {
|
||||||
return entity.getId();
|
return entity.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBrokerSessionId() {
|
||||||
|
return entity.getBrokerSessionId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBrokerUserId() {
|
||||||
|
return entity.getBrokerUserId();
|
||||||
|
}
|
||||||
|
|
||||||
public void setId(String id) {
|
public void setId(String id) {
|
||||||
entity.setId(id);
|
entity.setId(id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,8 @@ import java.util.Map;
|
||||||
public class UserSessionEntity {
|
public class UserSessionEntity {
|
||||||
|
|
||||||
private String id;
|
private String id;
|
||||||
|
private String brokerSessionId;
|
||||||
|
private String brokerUserId;
|
||||||
private String realm;
|
private String realm;
|
||||||
private String user;
|
private String user;
|
||||||
private String loginUsername;
|
private String loginUsername;
|
||||||
|
@ -126,4 +128,20 @@ public class UserSessionEntity {
|
||||||
public void setState(UserSessionModel.State state) {
|
public void setState(UserSessionModel.State state) {
|
||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getBrokerSessionId() {
|
||||||
|
return brokerSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBrokerSessionId(String brokerSessionId) {
|
||||||
|
this.brokerSessionId = brokerSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBrokerUserId() {
|
||||||
|
return brokerUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBrokerUserId(String brokerUserId) {
|
||||||
|
this.brokerUserId = brokerUserId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@ public class MongoUserSessionProvider implements UserSessionProvider {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe) {
|
public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
|
||||||
MongoUserSessionEntity entity = new MongoUserSessionEntity();
|
MongoUserSessionEntity entity = new MongoUserSessionEntity();
|
||||||
entity.setRealmId(realm.getId());
|
entity.setRealmId(realm.getId());
|
||||||
entity.setUser(user.getId());
|
entity.setUser(user.getId());
|
||||||
|
@ -83,6 +83,8 @@ public class MongoUserSessionProvider implements UserSessionProvider {
|
||||||
entity.setAuthMethod(authMethod);
|
entity.setAuthMethod(authMethod);
|
||||||
entity.setRememberMe(rememberMe);
|
entity.setRememberMe(rememberMe);
|
||||||
entity.setRealmId(realm.getId());
|
entity.setRealmId(realm.getId());
|
||||||
|
entity.setBrokerSessionId(brokerSessionId);
|
||||||
|
entity.setBrokerUserId(brokerUserId);
|
||||||
|
|
||||||
int currentTime = Time.currentTime();
|
int currentTime = Time.currentTime();
|
||||||
|
|
||||||
|
@ -121,6 +123,39 @@ public class MongoUserSessionProvider implements UserSessionProvider {
|
||||||
return sessions;
|
return sessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserSessionModel> getUserSessionByBrokerUserId(RealmModel realm, String brokerUserId) {
|
||||||
|
DBObject query = new BasicDBObject("brokerUserId", brokerUserId);
|
||||||
|
List<UserSessionModel> sessions = new LinkedList<UserSessionModel>();
|
||||||
|
for (MongoUserSessionEntity e : mongoStore.loadEntities(MongoUserSessionEntity.class, query, invocationContext)) {
|
||||||
|
sessions.add(new UserSessionAdapter(session, this, e, realm, invocationContext));
|
||||||
|
}
|
||||||
|
return sessions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserSessionModel getUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId) {
|
||||||
|
DBObject query = new BasicDBObject("brokerSessionId", brokerSessionId);
|
||||||
|
List<UserSessionModel> sessions = new LinkedList<UserSessionModel>();
|
||||||
|
for (MongoUserSessionEntity e : mongoStore.loadEntities(MongoUserSessionEntity.class, query, invocationContext)) {
|
||||||
|
sessions.add(new UserSessionAdapter(session, this, e, realm, invocationContext));
|
||||||
|
}
|
||||||
|
if (sessions.isEmpty()) return null;
|
||||||
|
return sessions.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserSessionModel> getUserSessionsByNote(RealmModel realm, String noteName, String noteValue) {
|
||||||
|
DBObject query = new QueryBuilder()
|
||||||
|
.and("realmId").is(realm.getId())
|
||||||
|
.and("notes." + noteName).is(noteValue).get();
|
||||||
|
List<UserSessionModel> sessions = new LinkedList<UserSessionModel>();
|
||||||
|
for (MongoUserSessionEntity e : mongoStore.loadEntities(MongoUserSessionEntity.class, query, invocationContext)) {
|
||||||
|
sessions.add(new UserSessionAdapter(session, this, e, realm, invocationContext));
|
||||||
|
}
|
||||||
|
return sessions;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client) {
|
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client) {
|
||||||
return getUserSessions(realm, client, -1, -1);
|
return getUserSessions(realm, client, -1, -1);
|
||||||
|
|
|
@ -41,6 +41,16 @@ public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEnt
|
||||||
return entity.getId();
|
return entity.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBrokerSessionId() {
|
||||||
|
return entity.getBrokerSessionId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBrokerUserId() {
|
||||||
|
return entity.getBrokerUserId();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel getUser() {
|
public UserModel getUser() {
|
||||||
return keycloakSession.users().getUserById(entity.getUser(), realm);
|
return keycloakSession.users().getUserById(entity.getUser(), realm);
|
||||||
|
|
|
@ -21,6 +21,9 @@ public class MongoUserSessionEntity extends AbstractIdentifiableEntity implement
|
||||||
|
|
||||||
private String realmId;
|
private String realmId;
|
||||||
|
|
||||||
|
private String brokerSessionId;
|
||||||
|
private String brokerUserId;
|
||||||
|
|
||||||
private String user;
|
private String user;
|
||||||
|
|
||||||
private String loginUsername;
|
private String loginUsername;
|
||||||
|
@ -136,4 +139,20 @@ public class MongoUserSessionEntity extends AbstractIdentifiableEntity implement
|
||||||
public void setState(UserSessionModel.State state) {
|
public void setState(UserSessionModel.State state) {
|
||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getBrokerSessionId() {
|
||||||
|
return brokerSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBrokerSessionId(String brokerSessionId) {
|
||||||
|
this.brokerSessionId = brokerSessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBrokerUserId() {
|
||||||
|
return brokerUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBrokerUserId(String brokerUserId) {
|
||||||
|
this.brokerUserId = brokerUserId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -437,14 +437,15 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
public Response frontchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
|
public Response frontchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||||
ClientModel client = clientSession.getClient();
|
ClientModel client = clientSession.getClient();
|
||||||
if (!(client instanceof ApplicationModel)) return null;
|
if (!(client instanceof ApplicationModel)) return null;
|
||||||
SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(clientSession, client);
|
|
||||||
try {
|
try {
|
||||||
if (isLogoutPostBindingForClient(clientSession)) {
|
if (isLogoutPostBindingForClient(clientSession)) {
|
||||||
String bindingUri = getLogoutServiceUrl(uriInfo, client, SAML_POST_BINDING);
|
String bindingUri = getLogoutServiceUrl(uriInfo, client, SAML_POST_BINDING);
|
||||||
|
SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(bindingUri, clientSession, client);
|
||||||
return logoutBuilder.postBinding().request(bindingUri);
|
return logoutBuilder.postBinding().request(bindingUri);
|
||||||
} else {
|
} else {
|
||||||
logger.debug("frontchannel redirect binding");
|
logger.debug("frontchannel redirect binding");
|
||||||
String bindingUri = getLogoutServiceUrl(uriInfo, client, SAML_REDIRECT_BINDING);
|
String bindingUri = getLogoutServiceUrl(uriInfo, client, SAML_REDIRECT_BINDING);
|
||||||
|
SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(bindingUri, clientSession, client);
|
||||||
return logoutBuilder.redirectBinding().request(bindingUri);
|
return logoutBuilder.redirectBinding().request(bindingUri);
|
||||||
}
|
}
|
||||||
} catch (ConfigurationException e) {
|
} catch (ConfigurationException e) {
|
||||||
|
@ -504,7 +505,7 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
logger.warnv("Can't do backchannel logout. No SingleLogoutService POST Binding registered for client: {1}", client.getClientId());
|
logger.warnv("Can't do backchannel logout. No SingleLogoutService POST Binding registered for client: {1}", client.getClientId());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(clientSession, client);
|
SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(logoutUrl, clientSession, client);
|
||||||
|
|
||||||
|
|
||||||
String logoutRequestString = null;
|
String logoutRequestString = null;
|
||||||
|
@ -549,12 +550,12 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SAML2LogoutRequestBuilder createLogoutRequest(ClientSessionModel clientSession, ClientModel client) {
|
protected SAML2LogoutRequestBuilder createLogoutRequest(String logoutUrl, ClientSessionModel clientSession, ClientModel client) {
|
||||||
// build userPrincipal with subject used at login
|
// build userPrincipal with subject used at login
|
||||||
SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
|
SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
|
||||||
.issuer(getResponseIssuer(realm))
|
.issuer(getResponseIssuer(realm))
|
||||||
.userPrincipal(clientSession.getNote(SAML_NAME_ID), clientSession.getNote(SAML_NAME_ID_FORMAT))
|
.userPrincipal(clientSession.getNote(SAML_NAME_ID), clientSession.getNote(SAML_NAME_ID_FORMAT))
|
||||||
.destination(client.getClientId());
|
.destination(logoutUrl);
|
||||||
if (requiresRealmSignature(client)) {
|
if (requiresRealmSignature(client)) {
|
||||||
logoutBuilder.signatureAlgorithm(getSignatureAlgorithm(client))
|
logoutBuilder.signatureAlgorithm(getSignatureAlgorithm(client))
|
||||||
.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
||||||
|
|
2
services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
Normal file → Executable file
2
services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
Normal file → Executable file
|
@ -314,7 +314,7 @@ public class TokenEndpoint {
|
||||||
|
|
||||||
UserSessionProvider sessions = session.sessions();
|
UserSessionProvider sessions = session.sessions();
|
||||||
|
|
||||||
UserSessionModel userSession = sessions.createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "oauth_credentials", false);
|
UserSessionModel userSession = sessions.createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "oauth_credentials", false, null, null);
|
||||||
event.session(userSession);
|
event.session(userSession);
|
||||||
|
|
||||||
ClientSessionModel clientSession = sessions.createClientSession(realm, client);
|
ClientSessionModel clientSession = sessions.createClientSession(realm, client);
|
||||||
|
|
|
@ -90,8 +90,8 @@ public class AuthenticationManager {
|
||||||
userSession.setState(UserSessionModel.State.LOGGING_OUT);
|
userSession.setState(UserSessionModel.State.LOGGING_OUT);
|
||||||
|
|
||||||
logger.debugv("Logging out: {0} ({1})", user.getUsername(), userSession.getId());
|
logger.debugv("Logging out: {0} ({1})", user.getUsername(), userSession.getId());
|
||||||
expireIdentityCookie(realm, uriInfo, connection);
|
//expireIdentityCookie(realm, uriInfo, connection);
|
||||||
expireRememberMeCookie(realm, uriInfo, connection);
|
//expireRememberMeCookie(realm, uriInfo, connection);
|
||||||
|
|
||||||
for (ClientSessionModel clientSession : userSession.getClientSessions()) {
|
for (ClientSessionModel clientSession : userSession.getClientSessions()) {
|
||||||
ClientModel client = clientSession.getClient();
|
ClientModel client = clientSession.getClient();
|
||||||
|
|
2
services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java
Normal file → Executable file
2
services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java
Normal file → Executable file
|
@ -116,7 +116,7 @@ public class HttpAuthenticationManager {
|
||||||
event.error(Errors.USER_DISABLED);
|
event.error(Errors.USER_DISABLED);
|
||||||
response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.ACCOUNT_DISABLED);
|
response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.ACCOUNT_DISABLED);
|
||||||
} else {
|
} else {
|
||||||
UserSessionModel userSession = session.sessions().createUserSession(realm, user, user.getUsername(), clientConnection.getRemoteAddr(), authMethod, false);
|
UserSessionModel userSession = session.sessions().createUserSession(realm, user, user.getUsername(), clientConnection.getRemoteAddr(), authMethod, false, null, null);
|
||||||
|
|
||||||
// Propagate state (like kerberos delegation credentials etc) as attributes of userSession
|
// Propagate state (like kerberos delegation credentials etc) as attributes of userSession
|
||||||
for (Map.Entry<String, String> entry : authState.entrySet()) {
|
for (Map.Entry<String, String> entry : authState.entrySet()) {
|
||||||
|
|
|
@ -131,23 +131,6 @@ public class ResourceAdminManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void logoutSession(URI requestUri, RealmModel realm, UserSessionModel session) {
|
|
||||||
ApacheHttpClient4Executor executor = createExecutor();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Map from "app" to clientSessions for this app
|
|
||||||
MultivaluedHashMap<ApplicationModel, ClientSessionModel> clientSessions = new MultivaluedHashMap<ApplicationModel, ClientSessionModel>();
|
|
||||||
putClientSessions(clientSessions, session);
|
|
||||||
|
|
||||||
logger.debugv("logging out {0} resources ", clientSessions.size());
|
|
||||||
for (Map.Entry<ApplicationModel, List<ClientSessionModel>> entry : clientSessions.entrySet()) {
|
|
||||||
logoutClientSessions(requestUri, realm, entry.getKey(), entry.getValue(), executor);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
executor.getHttpClient().getConnectionManager().shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void logoutUserFromApplication(URI requestUri, RealmModel realm, ApplicationModel resource, UserModel user, KeycloakSession session) {
|
public void logoutUserFromApplication(URI requestUri, RealmModel realm, ApplicationModel resource, UserModel user, KeycloakSession session) {
|
||||||
ApacheHttpClient4Executor executor = createExecutor();
|
ApacheHttpClient4Executor executor = createExecutor();
|
||||||
|
|
||||||
|
@ -179,6 +162,7 @@ public class ResourceAdminManager {
|
||||||
|
|
||||||
// Key is host, value is list of http sessions for this host
|
// Key is host, value is list of http sessions for this host
|
||||||
MultivaluedHashMap<String, String> adapterSessionIds = null;
|
MultivaluedHashMap<String, String> adapterSessionIds = null;
|
||||||
|
List<String> userSessions = new LinkedList<>();
|
||||||
if (clientSessions != null && clientSessions.size() > 0) {
|
if (clientSessions != null && clientSessions.size() > 0) {
|
||||||
adapterSessionIds = new MultivaluedHashMap<String, String>();
|
adapterSessionIds = new MultivaluedHashMap<String, String>();
|
||||||
for (ClientSessionModel clientSession : clientSessions) {
|
for (ClientSessionModel clientSession : clientSessions) {
|
||||||
|
@ -187,6 +171,7 @@ public class ResourceAdminManager {
|
||||||
String host = clientSession.getNote(AdapterConstants.APPLICATION_SESSION_HOST);
|
String host = clientSession.getNote(AdapterConstants.APPLICATION_SESSION_HOST);
|
||||||
adapterSessionIds.add(host, adapterSessionId);
|
adapterSessionIds.add(host, adapterSessionId);
|
||||||
}
|
}
|
||||||
|
if (clientSession.getUserSession() != null) userSessions.add(clientSession.getUserSession().getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,7 +187,7 @@ public class ResourceAdminManager {
|
||||||
String host = entry.getKey();
|
String host = entry.getKey();
|
||||||
List<String> sessionIds = entry.getValue();
|
List<String> sessionIds = entry.getValue();
|
||||||
String currentHostMgmtUrl = managementUrl.replace(APPLICATION_SESSION_HOST_PROPERTY, host);
|
String currentHostMgmtUrl = managementUrl.replace(APPLICATION_SESSION_HOST_PROPERTY, host);
|
||||||
allPassed = sendLogoutRequest(realm, resource, sessionIds, client, 0, currentHostMgmtUrl) && allPassed;
|
allPassed = sendLogoutRequest(realm, resource, sessionIds, userSessions, client, 0, currentHostMgmtUrl) && allPassed;
|
||||||
}
|
}
|
||||||
|
|
||||||
return allPassed;
|
return allPassed;
|
||||||
|
@ -213,7 +198,7 @@ public class ResourceAdminManager {
|
||||||
allSessionIds.addAll(currentIds);
|
allSessionIds.addAll(currentIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sendLogoutRequest(realm, resource, allSessionIds, client, 0, managementUrl);
|
return sendLogoutRequest(realm, resource, allSessionIds, userSessions, client, 0, managementUrl);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.debugv("Can't logout {0}: no management url", resource.getName());
|
logger.debugv("Can't logout {0}: no management url", resource.getName());
|
||||||
|
@ -265,7 +250,7 @@ public class ResourceAdminManager {
|
||||||
// Propagate this to all hosts
|
// Propagate this to all hosts
|
||||||
GlobalRequestResult result = new GlobalRequestResult();
|
GlobalRequestResult result = new GlobalRequestResult();
|
||||||
for (String mgmtUrl : mgmtUrls) {
|
for (String mgmtUrl : mgmtUrls) {
|
||||||
if (sendLogoutRequest(realm, resource, null, executor, notBefore, mgmtUrl)) {
|
if (sendLogoutRequest(realm, resource, null, null, executor, notBefore, mgmtUrl)) {
|
||||||
result.addSuccessRequest(mgmtUrl);
|
result.addSuccessRequest(mgmtUrl);
|
||||||
} else {
|
} else {
|
||||||
result.addFailedRequest(mgmtUrl);
|
result.addFailedRequest(mgmtUrl);
|
||||||
|
@ -274,8 +259,8 @@ public class ResourceAdminManager {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean sendLogoutRequest(RealmModel realm, ApplicationModel resource, List<String> adapterSessionIds, ApacheHttpClient4Executor client, int notBefore, String managementUrl) {
|
protected boolean sendLogoutRequest(RealmModel realm, ApplicationModel resource, List<String> adapterSessionIds, List<String> userSessions, ApacheHttpClient4Executor client, int notBefore, String managementUrl) {
|
||||||
LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getName(), adapterSessionIds, notBefore);
|
LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getName(), adapterSessionIds, notBefore, userSessions);
|
||||||
String token = new TokenManager().encodeToken(realm, adminAction);
|
String token = new TokenManager().encodeToken(realm, adminAction);
|
||||||
if (logger.isDebugEnabled()) logger.debugv("logout resource {0} url: {1} sessionIds: " + adapterSessionIds, resource.getName(), managementUrl);
|
if (logger.isDebugEnabled()) logger.debugv("logout resource {0} url: {1} sessionIds: " + adapterSessionIds, resource.getName(), managementUrl);
|
||||||
ClientRequest request = client.createRequest(UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_LOGOUT).build().toString());
|
ClientRequest request = client.createRequest(UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_LOGOUT).build().toString());
|
||||||
|
|
|
@ -615,8 +615,7 @@ public class AccountService {
|
||||||
List<UserSessionModel> sessions = session.sessions().getUserSessions(realm, user);
|
List<UserSessionModel> sessions = session.sessions().getUserSessions(realm, user);
|
||||||
for (UserSessionModel s : sessions) {
|
for (UserSessionModel s : sessions) {
|
||||||
if (!s.getId().equals(auth.getSession().getId())) {
|
if (!s.getId().equals(auth.getSession().getId())) {
|
||||||
new ResourceAdminManager().logoutSession(uriInfo.getRequestUri(), realm, s);
|
AuthenticationManager.backchannelLogout(session, realm, s, uriInfo, clientConnection, headers);
|
||||||
session.sessions().removeUserSession(realm, s);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -285,7 +285,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
||||||
updateFederatedIdentity(federatedIdentity, federatedUser);
|
updateFederatedIdentity(federatedIdentity, federatedUser);
|
||||||
|
|
||||||
UserSessionModel userSession = this.session.sessions()
|
UserSessionModel userSession = this.session.sessions()
|
||||||
.createUserSession(this.realmModel, federatedUser, federatedUser.getUsername(), this.clientConnection.getRemoteAddr(), "broker", false);
|
.createUserSession(this.realmModel, federatedUser, federatedUser.getUsername(), this.clientConnection.getRemoteAddr(), "broker", false, federatedIdentity.getBrokerSessionId(), federatedIdentity.getBrokerUserId());
|
||||||
|
|
||||||
this.event.user(federatedUser);
|
this.event.user(federatedUser);
|
||||||
this.event.session(userSession);
|
this.event.session(userSession);
|
||||||
|
|
|
@ -344,7 +344,7 @@ public class LoginActionsService {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case SUCCESS:
|
case SUCCESS:
|
||||||
case ACTIONS_REQUIRED:
|
case ACTIONS_REQUIRED:
|
||||||
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", remember);
|
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", remember, null, null);
|
||||||
TokenManager.attachClientSession(userSession, clientSession);
|
TokenManager.attachClientSession(userSession, clientSession);
|
||||||
event.session(userSession);
|
event.session(userSession);
|
||||||
return authManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
|
return authManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
|
||||||
|
@ -885,7 +885,7 @@ public class LoginActionsService {
|
||||||
} else{
|
} else{
|
||||||
event.user(user);
|
event.user(user);
|
||||||
|
|
||||||
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", false);
|
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", false, null, null);
|
||||||
event.session(userSession);
|
event.session(userSession);
|
||||||
TokenManager.attachClientSession(userSession, clientSession);
|
TokenManager.attachClientSession(userSession, clientSession);
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
@ -23,6 +24,7 @@ import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.jboss.resteasy.spi.NotFoundException;
|
import org.jboss.resteasy.spi.NotFoundException;
|
||||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
|
import org.keycloak.ClientConnection;
|
||||||
import org.keycloak.events.Event;
|
import org.keycloak.events.Event;
|
||||||
import org.keycloak.events.EventQuery;
|
import org.keycloak.events.EventQuery;
|
||||||
import org.keycloak.events.EventStoreProvider;
|
import org.keycloak.events.EventStoreProvider;
|
||||||
|
@ -42,6 +44,7 @@ import org.keycloak.protocol.oidc.TokenManager;
|
||||||
import org.keycloak.representations.adapters.action.GlobalRequestResult;
|
import org.keycloak.representations.adapters.action.GlobalRequestResult;
|
||||||
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.LDAPConnectionTestManager;
|
import org.keycloak.services.managers.LDAPConnectionTestManager;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.services.managers.ResourceAdminManager;
|
import org.keycloak.services.managers.ResourceAdminManager;
|
||||||
|
@ -67,6 +70,12 @@ public class RealmAdminResource {
|
||||||
@Context
|
@Context
|
||||||
protected UriInfo uriInfo;
|
protected UriInfo uriInfo;
|
||||||
|
|
||||||
|
@Context
|
||||||
|
protected ClientConnection connection;
|
||||||
|
|
||||||
|
@Context
|
||||||
|
protected HttpHeaders headers;
|
||||||
|
|
||||||
public RealmAdminResource(RealmAuth auth, RealmModel realm, TokenManager tokenManager) {
|
public RealmAdminResource(RealmAuth auth, RealmModel realm, TokenManager tokenManager) {
|
||||||
this.auth = auth;
|
this.auth = auth;
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
|
@ -297,8 +306,7 @@ public class RealmAdminResource {
|
||||||
public void deleteSession(@PathParam("session") String sessionId) {
|
public void deleteSession(@PathParam("session") String sessionId) {
|
||||||
UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
|
UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
|
||||||
if (userSession == null) throw new NotFoundException("Sesssion not found");
|
if (userSession == null) throw new NotFoundException("Sesssion not found");
|
||||||
session.sessions().removeUserSession(realm, userSession);
|
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, connection, headers);
|
||||||
new ResourceAdminManager().logoutSession(uriInfo.getRequestUri(), realm, userSession);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.keycloak.representations.idm.MappingsRepresentation;
|
||||||
import org.keycloak.representations.idm.RoleRepresentation;
|
import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.representations.idm.UserSessionRepresentation;
|
import org.keycloak.representations.idm.UserSessionRepresentation;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.ClientSessionCode;
|
import org.keycloak.services.managers.ClientSessionCode;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.services.managers.ResourceAdminManager;
|
import org.keycloak.services.managers.ResourceAdminManager;
|
||||||
|
@ -51,6 +52,7 @@ import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
|
@ -87,6 +89,9 @@ public class UsersResource {
|
||||||
@Context
|
@Context
|
||||||
protected KeycloakSession session;
|
protected KeycloakSession session;
|
||||||
|
|
||||||
|
@Context
|
||||||
|
protected HttpHeaders headers;
|
||||||
|
|
||||||
public UsersResource(RealmModel realm, RealmAuth auth, TokenManager tokenManager) {
|
public UsersResource(RealmModel realm, RealmAuth auth, TokenManager tokenManager) {
|
||||||
this.auth = auth;
|
this.auth = auth;
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
|
@ -321,8 +326,10 @@ public class UsersResource {
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new NotFoundException("User not found");
|
throw new NotFoundException("User not found");
|
||||||
}
|
}
|
||||||
new ResourceAdminManager().logoutUser(uriInfo.getRequestUri(), realm, user, session);
|
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
|
||||||
session.sessions().removeUserSessions(realm, user);
|
for (UserSessionModel userSession : userSessions) {
|
||||||
|
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -728,7 +735,7 @@ public class UsersResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", false);
|
UserSessionModel userSession = session.sessions().createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "form", false, null, null);
|
||||||
//audit.session(userSession);
|
//audit.session(userSession);
|
||||||
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
|
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
|
||||||
clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||||
|
|
|
@ -157,7 +157,7 @@ public class AccountTest {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test @Ignore
|
//@Test @Ignore
|
||||||
public void runit() throws Exception {
|
public void runit() throws Exception {
|
||||||
Thread.sleep(10000000);
|
Thread.sleep(10000000);
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
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.resources.admin.AdminRoot;
|
import org.keycloak.services.resources.admin.AdminRoot;
|
||||||
|
@ -62,6 +63,7 @@ import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
@ -138,7 +140,7 @@ public class AdapterTestStrategy extends ExternalResource {
|
||||||
ApplicationModel adminConsole = adminRealm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
|
ApplicationModel adminConsole = adminRealm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
|
||||||
TokenManager tm = new TokenManager();
|
TokenManager tm = new TokenManager();
|
||||||
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
|
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
|
||||||
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false);
|
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false, null, null);
|
||||||
AccessToken token = tm.createClientAccessToken(session, TokenManager.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession, null);
|
AccessToken token = tm.createClientAccessToken(session, TokenManager.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession, null);
|
||||||
return tm.encodeToken(adminRealm, token);
|
return tm.encodeToken(adminRealm, token);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -86,7 +86,7 @@ public class RelativeUriAdapterTest {
|
||||||
ApplicationModel adminConsole = adminRealm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
|
ApplicationModel adminConsole = adminRealm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
|
||||||
TokenManager tm = new TokenManager();
|
TokenManager tm = new TokenManager();
|
||||||
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
|
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
|
||||||
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "user", null, "form", false);
|
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "user", null, "form", false, null, null);
|
||||||
AccessToken token = tm.createClientAccessToken(session, tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession, null);
|
AccessToken token = tm.createClientAccessToken(session, tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession, null);
|
||||||
adminToken = tm.encodeToken(adminRealm, token);
|
adminToken = tm.encodeToken(adminRealm, token);
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,7 @@ public class AdminAPITest {
|
||||||
ApplicationModel adminConsole = adminRealm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
|
ApplicationModel adminConsole = adminRealm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
|
||||||
TokenManager tm = new TokenManager();
|
TokenManager tm = new TokenManager();
|
||||||
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
|
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
|
||||||
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false);
|
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false, null, null);
|
||||||
AccessToken token = tm.createClientAccessToken(session, tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession, null);
|
AccessToken token = tm.createClientAccessToken(session, tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession, null);
|
||||||
return tm.encodeToken(adminRealm, token);
|
return tm.encodeToken(adminRealm, token);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -251,11 +251,11 @@ public class UserSessionProviderTest {
|
||||||
Set<String> expiredClientSessions = new HashSet<String>();
|
Set<String> expiredClientSessions = new HashSet<String>();
|
||||||
|
|
||||||
Time.setOffset(-(realm.getSsoSessionMaxLifespan() + 1));
|
Time.setOffset(-(realm.getSsoSessionMaxLifespan() + 1));
|
||||||
expired.add(session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true).getId());
|
expired.add(session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null).getId());
|
||||||
expiredClientSessions.add(session.sessions().createClientSession(realm, client).getId());
|
expiredClientSessions.add(session.sessions().createClientSession(realm, client).getId());
|
||||||
|
|
||||||
Time.setOffset(0);
|
Time.setOffset(0);
|
||||||
UserSessionModel s = session.sessions().createUserSession(realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.1", "form", true);
|
UserSessionModel s = session.sessions().createUserSession(realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.1", "form", true, null, null);
|
||||||
//s.setLastSessionRefresh(Time.currentTime() - (realm.getSsoSessionIdleTimeout() + 1));
|
//s.setLastSessionRefresh(Time.currentTime() - (realm.getSsoSessionIdleTimeout() + 1));
|
||||||
s.setLastSessionRefresh(0);
|
s.setLastSessionRefresh(0);
|
||||||
expired.add(s.getId());
|
expired.add(s.getId());
|
||||||
|
@ -267,7 +267,7 @@ public class UserSessionProviderTest {
|
||||||
Set<String> valid = new HashSet<String>();
|
Set<String> valid = new HashSet<String>();
|
||||||
Set<String> validClientSessions = new HashSet<String>();
|
Set<String> validClientSessions = new HashSet<String>();
|
||||||
|
|
||||||
valid.add(session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true).getId());
|
valid.add(session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null).getId());
|
||||||
validClientSessions.add(session.sessions().createClientSession(realm, client).getId());
|
validClientSessions.add(session.sessions().createClientSession(realm, client).getId());
|
||||||
|
|
||||||
resetSession();
|
resetSession();
|
||||||
|
@ -376,7 +376,7 @@ public class UserSessionProviderTest {
|
||||||
try {
|
try {
|
||||||
for (int i = 0; i < 25; i++) {
|
for (int i = 0; i < 25; i++) {
|
||||||
Time.setOffset(i);
|
Time.setOffset(i);
|
||||||
UserSessionModel userSession = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0." + i, "form", false);
|
UserSessionModel userSession = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0." + i, "form", false, null, null);
|
||||||
ClientSessionModel clientSession = session.sessions().createClientSession(realm, realm.findClient("test-app"));
|
ClientSessionModel clientSession = session.sessions().createClientSession(realm, realm.findClient("test-app"));
|
||||||
clientSession.setUserSession(userSession);
|
clientSession.setUserSession(userSession);
|
||||||
clientSession.setRedirectUri("http://redirect");
|
clientSession.setRedirectUri("http://redirect");
|
||||||
|
@ -481,7 +481,7 @@ public class UserSessionProviderTest {
|
||||||
|
|
||||||
private UserSessionModel[] createSessions() {
|
private UserSessionModel[] createSessions() {
|
||||||
UserSessionModel[] sessions = new UserSessionModel[3];
|
UserSessionModel[] sessions = new UserSessionModel[3];
|
||||||
sessions[0] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true);
|
sessions[0] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
|
||||||
|
|
||||||
Set<String> roles = new HashSet<String>();
|
Set<String> roles = new HashSet<String>();
|
||||||
roles.add("one");
|
roles.add("one");
|
||||||
|
@ -490,10 +490,10 @@ public class UserSessionProviderTest {
|
||||||
createClientSession(realm.findClient("test-app"), sessions[0], "http://redirect", "state", roles);
|
createClientSession(realm.findClient("test-app"), sessions[0], "http://redirect", "state", roles);
|
||||||
createClientSession(realm.findClient("third-party"), sessions[0], "http://redirect", "state", new HashSet<String>());
|
createClientSession(realm.findClient("third-party"), sessions[0], "http://redirect", "state", new HashSet<String>());
|
||||||
|
|
||||||
sessions[1] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true);
|
sessions[1] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
|
||||||
createClientSession(realm.findClient("test-app"), sessions[1], "http://redirect", "state", new HashSet<String>());
|
createClientSession(realm.findClient("test-app"), sessions[1], "http://redirect", "state", new HashSet<String>());
|
||||||
|
|
||||||
sessions[2] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.3", "form", true);
|
sessions[2] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.3", "form", true, null, null);
|
||||||
createClientSession(realm.findClient("test-app"), sessions[2], "http://redirect", "state", new HashSet<String>());
|
createClientSession(realm.findClient("test-app"), sessions[2], "http://redirect", "state", new HashSet<String>());
|
||||||
|
|
||||||
resetSession();
|
resetSession();
|
||||||
|
|
|
@ -454,6 +454,17 @@ public class AccessTokenTest {
|
||||||
Assert.assertEquals(401, response.getStatus());
|
Assert.assertEquals(401, response.getStatus());
|
||||||
response.close();
|
response.close();
|
||||||
}
|
}
|
||||||
|
{ // test no password
|
||||||
|
String header = BasicAuthHelper.createHeader("test-app", "password");
|
||||||
|
Form form = new Form();
|
||||||
|
form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD);
|
||||||
|
form.param("username", "test-user@localhost");
|
||||||
|
Response response = grantTarget.request()
|
||||||
|
.header(HttpHeaders.AUTHORIZATION, header)
|
||||||
|
.post(Entity.form(form));
|
||||||
|
Assert.assertEquals(401, response.getStatus());
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
|
||||||
{ // test bearer-only
|
{ // test bearer-only
|
||||||
|
|
||||||
|
|
|
@ -420,7 +420,7 @@ public class SamlBindingTest {
|
||||||
ApplicationModel adminConsole = adminRealm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
|
ApplicationModel adminConsole = adminRealm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
|
||||||
TokenManager tm = new TokenManager();
|
TokenManager tm = new TokenManager();
|
||||||
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
|
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
|
||||||
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false);
|
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false, null, null);
|
||||||
AccessToken token = tm.createClientAccessToken(session, tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession, null);
|
AccessToken token = tm.createClientAccessToken(session, tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession, null);
|
||||||
return tm.encodeToken(adminRealm, token);
|
return tm.encodeToken(adminRealm, token);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
Loading…
Reference in a new issue