Merge pull request #1088 from patriot1burke/master

oidc and saml broker backchannel logout
This commit is contained in:
Bill Burke 2015-03-26 08:43:21 -04:00
commit d36ee0d0af
54 changed files with 899 additions and 289 deletions

View 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 "{" +

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1 +1,2 @@
org.keycloak.broker.oidc.OIDCIdentityProviderFactory org.keycloak.broker.oidc.OIDCIdentityProviderFactory
org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory

View file

@ -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,37 +212,28 @@ 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);
}
}
} else {
for (String sessionIndex : request.getSessionIndex()) {
String brokerSessionId = brokerUserId + "." + sessionIndex;
UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId);
if (userSession != null) {
try {
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
} catch (Exception e) {
logger.warn("failed to do backchannel logout for userSession", e);
}
}
} }
} }
@ -269,8 +262,6 @@ public class SAMLEndpoint {
} }
} }
throw new RuntimeException("Unreachable");
}
private String getEntityId(UriInfo uriInfo, RealmModel realm) { private String getEntityId(UriInfo uriInfo, RealmModel realm) {
return UriBuilder.fromUri(uriInfo.getBaseUri()).path("realms").path(realm.getName()).build().toString(); return UriBuilder.fromUri(uriInfo.getBaseUri()).path("realms").path(realm.getName()).build().toString();
@ -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);

View file

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

View file

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

View file

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

View file

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

View file

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

View 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);
}

View file

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

View file

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

View file

@ -0,0 +1 @@
<div data-ng-include data-src="resourceUrl + '/partials/realm-identity-provider-oidc.html'"></div>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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,13 +230,30 @@ 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);
}
}
}
protected void remove(UserSessionEntity s) {
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()) { for (ClientSessionEntity clientSession : s.getClientSessions()) {
clientSessions.remove(clientSession.getId()); clientSessions.remove(clientSession.getId());
} }
} }
}
}
@Override @Override
public void removeExpiredUserSessions(RealmModel realm) { public void removeExpiredUserSessions(RealmModel realm) {
@ -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();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View 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()) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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