keycloak-uncached
Changes
broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProviderFactory.java 68(+68 -0)
broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderFactory 3(+2 -1)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-keycloak-oidc.html 1(+1 -0)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html 2(+1 -1)
model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java 19(+19 -0)
model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java 35(+33 -2)
model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/UserSessionMapper.java 16(+16 -0)
model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java 9(+9 -0)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionEntity.java 24(+24 -0)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionNoteEntity.java 1(+1 -0)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java 44(+43 -1)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UserSessionAdapter.java 10(+10 -0)
model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/UserSessionEntity.java 18(+18 -0)
model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java 98(+85 -13)
model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProviderFactory.java 7(+6 -1)
model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/UserSessionAdapter.java 10(+10 -0)
model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoUserSessionEntity.java 19(+19 -0)
model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java 37(+36 -1)
model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/UserSessionAdapter.java 10(+10 -0)
social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java 146(+73 -73)
testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java 4(+3 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java 2(+1 -1)
Details
diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/FederatedIdentity.java b/broker/core/src/main/java/org/keycloak/broker/provider/FederatedIdentity.java
old mode 100644
new mode 100755
index 761decc..4d58970
--- a/broker/core/src/main/java/org/keycloak/broker/provider/FederatedIdentity.java
+++ b/broker/core/src/main/java/org/keycloak/broker/provider/FederatedIdentity.java
@@ -32,6 +32,8 @@ public class FederatedIdentity {
private String email;
private String token;
private String identityProviderId;
+ private String brokerSessionId;
+ private String brokerUserId;
public FederatedIdentity(String id) {
if (id == null) {
@@ -102,6 +104,22 @@ public class FederatedIdentity {
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
public String toString() {
return "{" +
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
index a6b9030..78c890b 100755
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
@@ -128,16 +128,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
}
protected FederatedIdentity getFederatedIdentity(Map<String, String> notes, String response) {
- AccessTokenResponse tokenResponse = null;
- 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()));
+ String accessToken = extractTokenFromResponse(response, OAUTH2_PARAMETER_ACCESS_TOKEN);
if (accessToken == null) {
throw new IdentityBrokerException("No access token from server.");
@@ -146,6 +137,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
return doGetFederatedIdentity(accessToken);
}
+
protected FederatedIdentity doGetFederatedIdentity(String accessToken) {
return null;
}
@@ -212,12 +204,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
try {
if (authorizationCode != null) {
- String response = 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).asString();
+ String response = generateTokenRequest(authorizationCode).asString();
HashMap<String, String> userNotes = new HashMap<String, String>();
FederatedIdentity federatedIdentity = getFederatedIdentity(userNotes, response);
@@ -235,5 +222,14 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
event.error(Errors.IDENTITY_PROVIDER_LOGIN_FAILURE);
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);
+ }
}
}
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java
new file mode 100755
index 0000000..637546b
--- /dev/null
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java
@@ -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
+
+ }
+
+
+
+ }
+}
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProviderFactory.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProviderFactory.java
new file mode 100755
index 0000000..9c46cd8
--- /dev/null
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProviderFactory.java
@@ -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();
+
+ }
+}
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
index 7bd80db..68b6929 100755
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
@@ -18,19 +18,24 @@
package org.keycloak.broker.oidc;
import org.codehaus.jackson.JsonNode;
+import org.jboss.resteasy.logging.Logger;
import org.keycloak.broker.oidc.util.SimpleHttp;
import org.keycloak.broker.provider.AuthenticationRequest;
import org.keycloak.broker.provider.FederatedIdentity;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.IdentityProvider;
+import org.keycloak.constants.AdapterConstants;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.AccessTokenResponse;
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.EventsManager;
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.util.JsonSerialization;
+import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
+import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
@@ -53,6 +61,7 @@ import java.util.Map;
* @author Pedro Igor
*/
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 SCOPE_OPENID = "openid";
@@ -78,6 +87,8 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
super(callback, realm, event);
}
+
+
@GET
@Path("logout_response")
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);
}
+
}
@Override
@@ -175,6 +187,11 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
identity.setName(name);
identity.setEmail(email);
+ identity.setBrokerUserId(getConfig().getAlias() + "." + id);
+ if (tokenResponse.getSessionState() != null) {
+ identity.setBrokerSessionId(getConfig().getAlias() + "." + tokenResponse.getSessionState());
+ }
+
if (preferredUsername == null) {
preferredUsername = email;
}
@@ -229,10 +246,6 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
}
}
- private String decodeJWS(String token) {
- return new JWSInput(token).readContentAsString();
- }
-
@Override
protected String getDefaultScopes() {
return "openid";
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderConfig.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderConfig.java
index 0bcdfa3..90707a7 100755
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderConfig.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderConfig.java
@@ -47,5 +47,14 @@ public class OIDCIdentityProviderConfig extends OAuth2IdentityProviderConfig {
public void setLogoutUrl(String url) {
getConfig().put("logoutUrl", url);
}
+ public String getSigningCertificate() {
+ return getConfig().get("signingCertificate");
+ }
+
+ public void setSigningCertificate(String signingCertificate) {
+ getConfig().put("signingCertificate", signingCertificate);
+ }
+
+
}
diff --git a/broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderFactory b/broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderFactory
old mode 100644
new mode 100755
index 50071ed..faa4eb8
--- a/broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderFactory
+++ b/broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderFactory
@@ -1 +1,2 @@
-org.keycloak.broker.oidc.OIDCIdentityProviderFactory
\ No newline at end of file
+org.keycloak.broker.oidc.OIDCIdentityProviderFactory
+org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory
\ No newline at end of file
diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
index 23299bd..a2a2e71 100755
--- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
@@ -78,6 +78,8 @@ import java.util.Map;
public class SAMLEndpoint {
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_SUBJECT = "SAML_FEDERATED_SUBJECT";
+ public static final String SAML_FEDERATED_SUBJECT_NAMEFORMAT = "SAML_FEDERATED_SUBJECT_NAMEFORMAT";
protected RealmModel realm;
protected EventBuilder event;
protected SAMLIdentityProviderConfig config;
@@ -179,7 +181,7 @@ public class SAMLEndpoint {
SAMLDocumentHolder holder = extractRequestDocument(samlRequest);
RequestAbstractType requestAbstractType = (RequestAbstractType) holder.getSamlObject();
// validate destination
- if (!uriInfo.getAbsolutePath().toString().equals(requestAbstractType.getDestination())) {
+ if (!uriInfo.getAbsolutePath().equals(requestAbstractType.getDestination())) {
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
event.error(Errors.INVALID_SAML_RESPONSE);
event.detail(Details.REASON, "invalid_destination");
@@ -210,66 +212,55 @@ public class SAMLEndpoint {
}
protected Response logoutRequest(LogoutRequestType request, String relayState) {
- UserModel user = session.users().getUserByUsername(request.getNameID().getValue(), realm);
- if (user == null) {
- event.event(EventType.LOGOUT);
- event.error(Errors.USER_SESSION_NOT_FOUND);
- 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) {
+ String brokerUserId = config.getAlias() + "." + request.getNameID().getValue();
+ if (request.getSessionIndex() == null || request.getSessionIndex().isEmpty()) {
+ List<UserSessionModel> userSessions = session.sessions().getUserSessionByBrokerUserId(realm, brokerUserId);
+ for (UserSessionModel userSession : userSessions) {
try {
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
} catch (Exception e) {
- logger.error("Failed to logout", e);
+ logger.warn("failed to do backchannel logout for userSession", e);
}
}
- String issuerURL = getEntityId(uriInfo, realm);
- SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
- builder.logoutRequestID(request.getID());
- builder.destination(config.getSingleLogoutServiceUrl());
- builder.issuer(issuerURL);
- builder.relayState(relayState);
- if (config.isWantAuthnRequestsSigned()) {
- builder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
- .signDocument();
- }
- try {
- if (config.isPostBindingResponse()) {
- return builder.postBinding().response();
- } else {
- return builder.redirectBinding().response();
+ } 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);
+ }
}
- } catch (ConfigurationException e) {
- throw new RuntimeException(e);
- } catch (ProcessingException e) {
- throw new RuntimeException(e);
- } catch (IOException e) {
- throw new RuntimeException(e);
}
+ }
+ String issuerURL = getEntityId(uriInfo, realm);
+ SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
+ builder.logoutRequestID(request.getID());
+ builder.destination(config.getSingleLogoutServiceUrl());
+ builder.issuer(issuerURL);
+ builder.relayState(relayState);
+ if (config.isWantAuthnRequestsSigned()) {
+ builder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
+ .signDocument();
+ }
+ try {
+ if (config.isPostBindingResponse()) {
+ return builder.postBinding().response();
+ } else {
+ return builder.redirectBinding().response();
+ }
+ } catch (ConfigurationException e) {
+ throw new RuntimeException(e);
+ } catch (ProcessingException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
}
- throw new RuntimeException("Unreachable");
+
}
private String getEntityId(UriInfo uriInfo, RealmModel realm) {
@@ -283,8 +274,8 @@ public class SAMLEndpoint {
SubjectType.STSubType subType = subject.getSubType();
NameIDType subjectNameID = (NameIDType) subType.getBaseID();
Map<String, String> notes = new HashMap<>();
- notes.put("SAML_FEDERATED_SUBJECT", subjectNameID.getValue());
- if (subjectNameID.getFormat() != null) notes.put("SAML_FEDERATED_SUBJECT_NAMEFORMAT", subjectNameID.getFormat().toString());
+ notes.put(SAML_FEDERATED_SUBJECT, subjectNameID.getValue());
+ if (subjectNameID.getFormat() != null) notes.put(SAML_FEDERATED_SUBJECT_NAMEFORMAT, subjectNameID.getFormat().toString());
FederatedIdentity identity = new FederatedIdentity(subjectNameID.getValue());
identity.setUsername(subjectNameID.getValue());
@@ -304,7 +295,10 @@ public class SAMLEndpoint {
break;
}
}
+ String brokerUserId = config.getAlias() + "." + subjectNameID.getValue();
+ identity.setBrokerUserId(brokerUserId);
if (authn != null && authn.getSessionIndex() != null) {
+ identity.setBrokerSessionId(identity.getBrokerUserId() + "." + authn.getSessionIndex());
notes.put(SAML_FEDERATED_SESSION_INDEX, authn.getSessionIndex());
}
return callback.authenticated(notes, config, identity, relayState);
diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
index fe381ca..60acd10 100755
--- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
@@ -123,8 +123,8 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
.issuer(getEntityId(uriInfo, realm))
- .sessionIndex(userSession.getNote("SAML_FEDERATED_SESSION_INDEX"))
- .userPrincipal(userSession.getNote("SAML_FEDERATED_SUBJECT"), userSession.getNote("SAML_FEDERATED_SUBJECT_NAMEFORMAT"))
+ .sessionIndex(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SESSION_INDEX))
+ .userPrincipal(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT), userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT_NAMEFORMAT))
.destination(getConfig().getSingleLogoutServiceUrl());
if (getConfig().isWantAuthnRequestsSigned()) {
logoutBuilder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
diff --git a/core/src/main/java/org/keycloak/jose/jws/Algorithm.java b/core/src/main/java/org/keycloak/jose/jws/Algorithm.java
index 95b062b..4e0f47f 100755
--- a/core/src/main/java/org/keycloak/jose/jws/Algorithm.java
+++ b/core/src/main/java/org/keycloak/jose/jws/Algorithm.java
@@ -1,18 +1,32 @@
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>
* @version $Revision: 1 $
*/
public enum Algorithm {
- none,
- HS256,
- HS384,
- HS512,
- RS256,
- RS384,
- RS512,
- ES256,
- ES384,
- ES512
+
+ none(null),
+ HS256(null),
+ HS384(null),
+ HS512(null),
+ RS256(new RSAProvider()),
+ RS384(new RSAProvider()),
+ RS512(new RSAProvider()),
+ ES256(null),
+ ES384(null),
+ ES512(null)
+ ;
+ private SignatureProvider provider;
+
+ Algorithm(SignatureProvider provider) {
+ this.provider = provider;
+ }
+
+ public SignatureProvider getProvider() {
+ return provider;
+ }
}
diff --git a/core/src/main/java/org/keycloak/jose/jws/crypto/HMACProvider.java b/core/src/main/java/org/keycloak/jose/jws/crypto/HMACProvider.java
index 4772280..fef959d 100755
--- a/core/src/main/java/org/keycloak/jose/jws/crypto/HMACProvider.java
+++ b/core/src/main/java/org/keycloak/jose/jws/crypto/HMACProvider.java
@@ -14,7 +14,7 @@ import java.security.NoSuchAlgorithmException;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class HMACProvider {
+public class HMACProvider implements SignatureProvider {
private static String getJavaAlgorithm(Algorithm alg) {
switch (alg) {
case HS256:
@@ -82,5 +82,8 @@ public class HMACProvider {
}
}
-
+ @Override
+ public boolean verify(JWSInput input, String key) {
+ return false;
+ }
}
diff --git a/core/src/main/java/org/keycloak/jose/jws/crypto/RSAProvider.java b/core/src/main/java/org/keycloak/jose/jws/crypto/RSAProvider.java
index 42baccf..fc68f6c 100755
--- a/core/src/main/java/org/keycloak/jose/jws/crypto/RSAProvider.java
+++ b/core/src/main/java/org/keycloak/jose/jws/crypto/RSAProvider.java
@@ -3,16 +3,18 @@ package org.keycloak.jose.jws.crypto;
import org.keycloak.jose.jws.Algorithm;
import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.util.PemUtils;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
+import java.security.cert.X509Certificate;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class RSAProvider {
+public class RSAProvider implements SignatureProvider {
public static String getJavaAlgorithm(Algorithm alg) {
switch (alg) {
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) {
try {
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);
+ }
+
}
diff --git a/core/src/main/java/org/keycloak/jose/jws/crypto/SignatureProvider.java b/core/src/main/java/org/keycloak/jose/jws/crypto/SignatureProvider.java
new file mode 100755
index 0000000..8480b46
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jws/crypto/SignatureProvider.java
@@ -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);
+}
diff --git a/core/src/main/java/org/keycloak/jose/jws/JWSInput.java b/core/src/main/java/org/keycloak/jose/jws/JWSInput.java
index 6db10fa..d20da6d 100755
--- a/core/src/main/java/org/keycloak/jose/jws/JWSInput.java
+++ b/core/src/main/java/org/keycloak/jose/jws/JWSInput.java
@@ -2,6 +2,7 @@ package org.keycloak.jose.jws;
import org.keycloak.util.Base64Url;
import org.keycloak.util.JsonSerialization;
+import static org.keycloak.jose.jws.Algorithm.*;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@@ -73,6 +74,13 @@ public class JWSInput {
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 {
return JsonSerialization.readValue(content, type);
}
diff --git a/core/src/main/java/org/keycloak/representations/adapters/action/LogoutAction.java b/core/src/main/java/org/keycloak/representations/adapters/action/LogoutAction.java
index a14b3f8..f85020e 100755
--- a/core/src/main/java/org/keycloak/representations/adapters/action/LogoutAction.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/action/LogoutAction.java
@@ -10,14 +10,16 @@ public class LogoutAction extends AdminAction {
public static final String LOGOUT = "LOGOUT";
protected List<String> adapterSessionIds;
protected int notBefore;
+ protected List<String> keycloakSessionIds;
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);
this.adapterSessionIds = adapterSessionIds;
this.notBefore = notBefore;
+ this.keycloakSessionIds = keycloakSessionIds;
}
@@ -33,6 +35,14 @@ public class LogoutAction extends AdminAction {
return adapterSessionIds;
}
+ public List<String> getKeycloakSessionIds() {
+ return keycloakSessionIds;
+ }
+
+ public void setKeycloakSessionIds(List<String> keycloakSessionIds) {
+ this.keycloakSessionIds = keycloakSessionIds;
+ }
+
@Override
public boolean validate() {
return LOGOUT.equals(action);
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index e2d089a..15bbb32 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -700,6 +700,9 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
}
$scope.hidePassword = true;
+ $scope.fromUrl = {
+ data: ''
+ };
if (instance && instance.alias) {
$scope.identityProvider = angular.copy(instance);
@@ -798,21 +801,22 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
return;
}
var input = {
- fromUrl: $scope.fromUrl,
+ fromUrl: $scope.fromUrl.data,
providerId: providerFactory.id
}
$http.post(authUrl + '/admin/realms/' + realm.realm + '/identity-provider/import-config', input)
.success(function(data, status, headers) {
setConfig(data);
- $scope.fromUrl = null;
+ $scope.fromUrl.data = '';
$scope.importUrl = false;
Notifications.success("Imported config information from url.");
}).error(function() {
Notifications.error("Config can not be imported. Please verify the url.");
});
};
- $scope.$watch('fromUrl', function(newVal, oldVal){
- if ($scope.fromUrl && $scope.fromUrl.length > 0) {
+ $scope.$watch('fromUrl.data', function(newVal, oldVal){
+ console.log('watch fromUrl: ' + newVal + " " + oldVal);
+ if ($scope.fromUrl.data && $scope.fromUrl.data.length > 0) {
$scope.importUrl = true;
} else{
$scope.importUrl = false;
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-keycloak-oidc.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-keycloak-oidc.html
new file mode 100755
index 0000000..d380749
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-keycloak-oidc.html
@@ -0,0 +1 @@
+<div data-ng-include data-src="resourceUrl + '/partials/realm-identity-provider-oidc.html'"></div>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html
index be46714..25e7682 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html
@@ -129,7 +129,7 @@
<div class="form-group" data-ng-show="newIdentityProvider">
<label class="col-sm-2 control-label" for="fromUrl">Import From Url</label>
<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>
<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">
diff --git a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
index 61767b5..d75e780 100755
--- a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
@@ -9,6 +9,15 @@ public interface UserSessionModel {
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();
String getLoginUsername();
diff --git a/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java b/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
index 56d4335..b7421de 100755
--- a/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
+++ b/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
@@ -15,11 +15,15 @@ public interface UserSessionProvider extends Provider {
ClientSessionModel getClientSession(RealmModel realm, 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);
List<UserSessionModel> getUserSessions(RealmModel realm, UserModel user);
List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client);
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);
void removeUserSession(RealmModel realm, UserSessionModel session);
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java
index 99c23a9..5ab9dc5 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java
@@ -12,6 +12,9 @@ public class UserSessionEntity extends SessionEntity {
private String user;
+ private String brokerSessionId;
+ private String brokerUserId;
+
private String loginUsername;
private String ipAddress;
@@ -109,4 +112,20 @@ public class UserSessionEntity extends SessionEntity {
public void setState(UserSessionModel.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;
+ }
}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
index 85d7a60..e7ff079 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -70,7 +70,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
@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();
UserSessionEntity entity = new UserSessionEntity();
@@ -81,6 +81,8 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
entity.setIpAddress(ipAddress);
entity.setAuthMethod(authMethod);
entity.setRememberMe(rememberMe);
+ entity.setBrokerSessionId(brokerSessionId);
+ entity.setBrokerUserId(brokerUserId);
int currentTime = Time.currentTime();
@@ -125,6 +127,28 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
@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
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client) {
return getUserSessions(realm, client, -1, -1);
}
@@ -173,7 +197,14 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
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)
.mappedWith(UserSessionNoteMapper.create(realm.getId()).notes(notes))
.reducedWith(new FirstResultReducer())
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/UserSessionMapper.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/UserSessionMapper.java
index f781b9e..210b6d5 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/UserSessionMapper.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/UserSessionMapper.java
@@ -30,6 +30,9 @@ public class UserSessionMapper implements Mapper<String, SessionEntity, String,
private Long expiredRefresh;
+ private String brokerSessionId;
+ private String brokerUserId;
+
public static UserSessionMapper create(String realm) {
return new UserSessionMapper(realm);
}
@@ -50,6 +53,16 @@ public class UserSessionMapper implements Mapper<String, SessionEntity, String,
return this;
}
+ public UserSessionMapper brokerSessionId(String id) {
+ this.brokerSessionId = id;
+ return this;
+ }
+
+ public UserSessionMapper brokerUserId(String id) {
+ this.brokerUserId = id;
+ return this;
+ }
+
@Override
public void map(String key, SessionEntity e, Collector collector) {
if (!(e instanceof UserSessionEntity)) {
@@ -66,6 +79,9 @@ public class UserSessionMapper implements Mapper<String, SessionEntity, String,
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) {
return;
}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java
index dfd7914..6cfc121 100755
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java
@@ -42,6 +42,15 @@ public class UserSessionAdapter implements UserSessionModel {
return entity.getId();
}
+ @Override
+ public String getBrokerSessionId() {
+ return entity.getBrokerSessionId();
+ }
+
+ @Override
+ public String getBrokerUserId() {
+ return entity.getBrokerUserId();
+ }
public UserModel getUser() {
return session.users().getUserById(entity.getUser(), realm);
}
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionEntity.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionEntity.java
index e822e75..4a40699 100755
--- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionEntity.java
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionEntity.java
@@ -20,6 +20,8 @@ import java.util.Collection;
@Table(name = "USER_SESSION")
@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 = "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 = "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"),
@@ -35,6 +37,12 @@ public class UserSessionEntity {
@Column(name="USER_ID")
protected String userId;
+ @Column(name="BROKER_SESSION_ID")
+ protected String brokerSessionId;
+
+ @Column(name="BROKER_USER_ID")
+ protected String brokerUserId;
+
@Column(name="LOGIN_USERNAME")
protected String loginUsername;
@@ -156,4 +164,20 @@ public class UserSessionEntity {
public void setNotes(Collection<UserSessionNoteEntity> 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;
+ }
}
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionNoteEntity.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionNoteEntity.java
index 762ce6a..d4bd889 100755
--- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionNoteEntity.java
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionNoteEntity.java
@@ -17,6 +17,7 @@ import java.io.Serializable;
* @version $Revision: 1 $
*/
@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 = "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))")
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java
index dcae8e2..d70c326 100755
--- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java
@@ -10,6 +10,7 @@ import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.UsernameLoginFailureModel;
import org.keycloak.models.sessions.jpa.entities.ClientSessionEntity;
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.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RealmInfoUtil;
@@ -19,6 +20,7 @@ import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -84,7 +86,7 @@ public class JpaUserSessionProvider implements UserSessionProvider {
}
@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();
entity.setId(KeycloakModelUtils.generateId());
entity.setRealmId(realm.getId());
@@ -93,6 +95,8 @@ public class JpaUserSessionProvider implements UserSessionProvider {
entity.setIpAddress(ipAddress);
entity.setAuthMethod(authMethod);
entity.setRememberMe(rememberMe);
+ entity.setBrokerSessionId(brokerSessionId);
+ entity.setBrokerUserId(brokerUserId);
int currentTime = Time.currentTime();
@@ -122,6 +126,44 @@ public class JpaUserSessionProvider implements UserSessionProvider {
}
@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
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client) {
return getUserSessions(realm, client, -1, -1);
}
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UserSessionAdapter.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UserSessionAdapter.java
index ca16427..28d4663 100755
--- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UserSessionAdapter.java
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UserSessionAdapter.java
@@ -41,6 +41,16 @@ public class UserSessionAdapter implements UserSessionModel {
}
@Override
+ public String getBrokerSessionId() {
+ return entity.getBrokerSessionId();
+ }
+
+ @Override
+ public String getBrokerUserId() {
+ return entity.getBrokerUserId();
+ }
+
+ @Override
public UserModel getUser() {
return session.users().getUserById(entity.getUserId(), realm);
}
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/UserSessionEntity.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/UserSessionEntity.java
index 16b74db..c7d0e26 100755
--- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/UserSessionEntity.java
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/UserSessionEntity.java
@@ -14,6 +14,8 @@ import java.util.Map;
public class UserSessionEntity {
private String id;
+ private String brokerSessionId;
+ private String brokerUserId;
private String realm;
private String user;
private String loginUsername;
@@ -126,4 +128,20 @@ public class UserSessionEntity {
public void setState(UserSessionModel.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;
+ }
}
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java
index 41081c0..c185a0b 100755
--- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java
@@ -19,10 +19,13 @@ import org.keycloak.util.Time;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
/**
* @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 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<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.userSessions = userSessions;
this.clientSessions = clientSessions;
this.loginFailures = loginFailures;
+ this.userSessionsByBrokerSessionId = userSessionsByBrokerSessionId;
+ this.userSessionsByBrokerUserId = userSessionsByBrokerUserId;
}
@Override
@@ -69,7 +76,7 @@ public class MemUserSessionProvider implements UserSessionProvider {
}
@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();
UserSessionEntity entity = new UserSessionEntity();
@@ -85,13 +92,56 @@ public class MemUserSessionProvider implements UserSessionProvider {
entity.setStarted(currentTime);
entity.setLastSessionRefresh(currentTime);
+ entity.setBrokerSessionId(brokerSessionId);
+ entity.setBrokerUserId(brokerUserId);
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);
}
@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
public UserSessionModel getUserSession(RealmModel realm, String id) {
UserSessionEntity entity = getUserSessionEntity(realm, id);
return entity != null ? new UserSessionAdapter(session, this, realm, entity) : null;
@@ -117,6 +167,17 @@ public class MemUserSessionProvider implements UserSessionProvider {
}
@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
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client) {
List<UserSessionEntity> userSessionEntities = new LinkedList<UserSessionEntity>();
for (ClientSessionEntity s : clientSessions.values()) {
@@ -158,9 +219,7 @@ public class MemUserSessionProvider implements UserSessionProvider {
UserSessionEntity entity = getUserSessionEntity(realm, session.getId());
if (entity != null) {
userSessions.remove(entity.getId());
- for (ClientSessionEntity clientSession : entity.getClientSessions()) {
- clientSessions.remove(clientSession.getId());
- }
+ remove(entity);
}
}
@@ -171,12 +230,29 @@ public class MemUserSessionProvider implements UserSessionProvider {
UserSessionEntity s = itr.next();
if (s.getRealm().equals(realm.getId()) && s.getUser().equals(user.getId())) {
itr.remove();
+ remove(s);
+ }
+ }
+ }
- for (ClientSessionEntity clientSession : s.getClientSessions()) {
- clientSessions.remove(clientSession.getId());
+ 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()) {
+ clientSessions.remove(clientSession.getId());
+ }
}
@Override
@@ -187,9 +263,7 @@ public class MemUserSessionProvider implements UserSessionProvider {
if (s.getRealm().equals(realm.getId()) && (s.getLastSessionRefresh() < Time.currentTime() - realm.getSsoSessionIdleTimeout() || s.getStarted() < Time.currentTime() - realm.getSsoSessionMaxLifespan())) {
itr.remove();
- for (ClientSessionEntity clientSession : s.getClientSessions()) {
- clientSessions.remove(clientSession.getId());
- }
+ remove(s);
}
}
int expired = Time.currentTime() - RealmInfoUtil.getDettachedClientSessionLifespan(realm);
@@ -210,9 +284,7 @@ public class MemUserSessionProvider implements UserSessionProvider {
if (s.getRealm().equals(realm.getId())) {
itr.remove();
- for (ClientSessionEntity clientSession : s.getClientSessions()) {
- clientSessions.remove(clientSession.getId());
- }
+ remove(s);
}
}
Iterator<ClientSessionEntity> citr = clientSessions.values().iterator();
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProviderFactory.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProviderFactory.java
index a943eca..73b7ebc 100755
--- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProviderFactory.java
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProviderFactory.java
@@ -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.UsernameLoginFailureKey;
+import java.util.Set;
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<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
public UserSessionProvider create(KeycloakSession session) {
- return new MemUserSessionProvider(session, userSessions, clientSessions, loginFailures);
+ return new MemUserSessionProvider(session, userSessions, userSessionsByBrokerSessionId, userSessionsByBrokerUserId, clientSessions, loginFailures);
}
@Override
@@ -43,6 +46,8 @@ public class MemUserSessionProviderFactory implements UserSessionProviderFactory
public void close() {
userSessions.clear();
loginFailures.clear();
+ userSessionsByBrokerSessionId.clear();
+ userSessionsByBrokerUserId.clear();
}
@Override
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/UserSessionAdapter.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/UserSessionAdapter.java
index 5b215a0..86b0187 100755
--- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/UserSessionAdapter.java
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/UserSessionAdapter.java
@@ -38,6 +38,16 @@ public class UserSessionAdapter implements UserSessionModel {
return entity.getId();
}
+ @Override
+ public String getBrokerSessionId() {
+ return entity.getBrokerSessionId();
+ }
+
+ @Override
+ public String getBrokerUserId() {
+ return entity.getBrokerUserId();
+ }
+
public void setId(String id) {
entity.setId(id);
}
diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoUserSessionEntity.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoUserSessionEntity.java
index ccf0f49..5b7e8d3 100755
--- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoUserSessionEntity.java
+++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoUserSessionEntity.java
@@ -21,6 +21,9 @@ public class MongoUserSessionEntity extends AbstractIdentifiableEntity implement
private String realmId;
+ private String brokerSessionId;
+ private String brokerUserId;
+
private String user;
private String loginUsername;
@@ -136,4 +139,20 @@ public class MongoUserSessionEntity extends AbstractIdentifiableEntity implement
public void setState(UserSessionModel.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;
+ }
}
diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
index 6630855..0bc5849 100755
--- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
+++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
@@ -74,7 +74,7 @@ public class MongoUserSessionProvider implements UserSessionProvider {
@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();
entity.setRealmId(realm.getId());
entity.setUser(user.getId());
@@ -83,6 +83,8 @@ public class MongoUserSessionProvider implements UserSessionProvider {
entity.setAuthMethod(authMethod);
entity.setRememberMe(rememberMe);
entity.setRealmId(realm.getId());
+ entity.setBrokerSessionId(brokerSessionId);
+ entity.setBrokerUserId(brokerUserId);
int currentTime = Time.currentTime();
@@ -122,6 +124,39 @@ public class MongoUserSessionProvider implements UserSessionProvider {
}
@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
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client) {
return getUserSessions(realm, client, -1, -1);
}
diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/UserSessionAdapter.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/UserSessionAdapter.java
index c12f377..644bbdb 100755
--- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/UserSessionAdapter.java
+++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/UserSessionAdapter.java
@@ -42,6 +42,16 @@ public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEnt
}
@Override
+ public String getBrokerSessionId() {
+ return entity.getBrokerSessionId();
+ }
+
+ @Override
+ public String getBrokerUserId() {
+ return entity.getBrokerUserId();
+ }
+
+ @Override
public UserModel getUser() {
return keycloakSession.users().getUserById(entity.getUser(), realm);
}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
index 5072e1c..b2772d0 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -437,14 +437,15 @@ public class SamlProtocol implements LoginProtocol {
public Response frontchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
ClientModel client = clientSession.getClient();
if (!(client instanceof ApplicationModel)) return null;
- SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(clientSession, client);
try {
if (isLogoutPostBindingForClient(clientSession)) {
String bindingUri = getLogoutServiceUrl(uriInfo, client, SAML_POST_BINDING);
+ SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(bindingUri, clientSession, client);
return logoutBuilder.postBinding().request(bindingUri);
} else {
logger.debug("frontchannel redirect binding");
String bindingUri = getLogoutServiceUrl(uriInfo, client, SAML_REDIRECT_BINDING);
+ SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(bindingUri, clientSession, client);
return logoutBuilder.redirectBinding().request(bindingUri);
}
} 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());
return;
}
- SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(clientSession, client);
+ SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(logoutUrl, clientSession, client);
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
SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
.issuer(getResponseIssuer(realm))
.userPrincipal(clientSession.getNote(SAML_NAME_ID), clientSession.getNote(SAML_NAME_ID_FORMAT))
- .destination(client.getClientId());
+ .destination(logoutUrl);
if (requiresRealmSignature(client)) {
logoutBuilder.signatureAlgorithm(getSignatureAlgorithm(client))
.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
old mode 100644
new mode 100755
index d8f9db2..d796f8f
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -314,7 +314,7 @@ public class TokenEndpoint {
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);
ClientSessionModel clientSession = sessions.createClientSession(realm, client);
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 625845f..bdc583a 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -90,8 +90,8 @@ public class AuthenticationManager {
userSession.setState(UserSessionModel.State.LOGGING_OUT);
logger.debugv("Logging out: {0} ({1})", user.getUsername(), userSession.getId());
- expireIdentityCookie(realm, uriInfo, connection);
- expireRememberMeCookie(realm, uriInfo, connection);
+ //expireIdentityCookie(realm, uriInfo, connection);
+ //expireRememberMeCookie(realm, uriInfo, connection);
for (ClientSessionModel clientSession : userSession.getClientSessions()) {
ClientModel client = clientSession.getClient();
diff --git a/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java
old mode 100644
new mode 100755
index 702b684..1cca55c
--- a/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java
@@ -116,7 +116,7 @@ public class HttpAuthenticationManager {
event.error(Errors.USER_DISABLED);
response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.ACCOUNT_DISABLED);
} 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
for (Map.Entry<String, String> entry : authState.entrySet()) {
diff --git a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
index 25a3414..1bd7fdc 100755
--- a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
@@ -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) {
ApacheHttpClient4Executor executor = createExecutor();
@@ -179,6 +162,7 @@ public class ResourceAdminManager {
// Key is host, value is list of http sessions for this host
MultivaluedHashMap<String, String> adapterSessionIds = null;
+ List<String> userSessions = new LinkedList<>();
if (clientSessions != null && clientSessions.size() > 0) {
adapterSessionIds = new MultivaluedHashMap<String, String>();
for (ClientSessionModel clientSession : clientSessions) {
@@ -187,6 +171,7 @@ public class ResourceAdminManager {
String host = clientSession.getNote(AdapterConstants.APPLICATION_SESSION_HOST);
adapterSessionIds.add(host, adapterSessionId);
}
+ if (clientSession.getUserSession() != null) userSessions.add(clientSession.getUserSession().getId());
}
}
@@ -202,7 +187,7 @@ public class ResourceAdminManager {
String host = entry.getKey();
List<String> sessionIds = entry.getValue();
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;
@@ -213,7 +198,7 @@ public class ResourceAdminManager {
allSessionIds.addAll(currentIds);
}
- return sendLogoutRequest(realm, resource, allSessionIds, client, 0, managementUrl);
+ return sendLogoutRequest(realm, resource, allSessionIds, userSessions, client, 0, managementUrl);
}
} else {
logger.debugv("Can't logout {0}: no management url", resource.getName());
@@ -265,7 +250,7 @@ public class ResourceAdminManager {
// Propagate this to all hosts
GlobalRequestResult result = new GlobalRequestResult();
for (String mgmtUrl : mgmtUrls) {
- if (sendLogoutRequest(realm, resource, null, executor, notBefore, mgmtUrl)) {
+ if (sendLogoutRequest(realm, resource, null, null, executor, notBefore, mgmtUrl)) {
result.addSuccessRequest(mgmtUrl);
} else {
result.addFailedRequest(mgmtUrl);
@@ -274,8 +259,8 @@ public class ResourceAdminManager {
return result;
}
- protected boolean sendLogoutRequest(RealmModel realm, ApplicationModel resource, List<String> adapterSessionIds, ApacheHttpClient4Executor client, int notBefore, String managementUrl) {
- LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getName(), adapterSessionIds, notBefore);
+ 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, userSessions);
String token = new TokenManager().encodeToken(realm, adminAction);
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());
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 7f155e0..f694661 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -615,8 +615,7 @@ public class AccountService {
List<UserSessionModel> sessions = session.sessions().getUserSessions(realm, user);
for (UserSessionModel s : sessions) {
if (!s.getId().equals(auth.getSession().getId())) {
- new ResourceAdminManager().logoutSession(uriInfo.getRequestUri(), realm, s);
- session.sessions().removeUserSession(realm, s);
+ AuthenticationManager.backchannelLogout(session, realm, s, uriInfo, clientConnection, headers);
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 0befaa4..e62b4ba 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -15,6 +15,7 @@ import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
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.spi.NotFoundException;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.ClientConnection;
import org.keycloak.events.Event;
import org.keycloak.events.EventQuery;
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.idm.RealmEventsConfigRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.LDAPConnectionTestManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.ResourceAdminManager;
@@ -67,6 +70,12 @@ public class RealmAdminResource {
@Context
protected UriInfo uriInfo;
+ @Context
+ protected ClientConnection connection;
+
+ @Context
+ protected HttpHeaders headers;
+
public RealmAdminResource(RealmAuth auth, RealmModel realm, TokenManager tokenManager) {
this.auth = auth;
this.realm = realm;
@@ -297,8 +306,7 @@ public class RealmAdminResource {
public void deleteSession(@PathParam("session") String sessionId) {
UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
if (userSession == null) throw new NotFoundException("Sesssion not found");
- session.sessions().removeUserSession(realm, userSession);
- new ResourceAdminManager().logoutSession(uriInfo.getRequestUri(), realm, userSession);
+ AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, connection, headers);
}
/**
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index f271a2e..e860ea8 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -34,6 +34,7 @@ import org.keycloak.representations.idm.MappingsRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.UserSessionRepresentation;
+import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.ResourceAdminManager;
@@ -51,6 +52,7 @@ import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
@@ -87,6 +89,9 @@ public class UsersResource {
@Context
protected KeycloakSession session;
+ @Context
+ protected HttpHeaders headers;
+
public UsersResource(RealmModel realm, RealmAuth auth, TokenManager tokenManager) {
this.auth = auth;
this.realm = realm;
@@ -321,8 +326,10 @@ public class UsersResource {
if (user == null) {
throw new NotFoundException("User not found");
}
- new ResourceAdminManager().logoutUser(uriInfo.getRequestUri(), realm, user, session);
- session.sessions().removeUserSessions(realm, user);
+ List<UserSessionModel> userSessions = session.sessions().getUserSessions(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);
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL);
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index 11a49c4..7a4bea5 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -285,7 +285,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
updateFederatedIdentity(federatedIdentity, federatedUser);
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.session(userSession);
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index f9ee68b..5b40535 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -344,7 +344,7 @@ public class LoginActionsService {
switch (status) {
case SUCCESS:
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);
event.session(userSession);
return authManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
@@ -885,7 +885,7 @@ public class LoginActionsService {
} else{
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);
TokenManager.attachClientSession(userSession, clientSession);
diff --git a/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java b/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java
index 8ebea50..de54cc5 100755
--- a/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java
+++ b/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java
@@ -1,73 +1,73 @@
-package org.keycloak.social.facebook;
-
-import org.codehaus.jackson.JsonNode;
-import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
-import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
-import org.keycloak.broker.oidc.util.SimpleHttp;
-import org.keycloak.broker.provider.FederatedIdentity;
-import org.keycloak.broker.provider.IdentityBrokerException;
-import org.keycloak.social.SocialIdentityProvider;
-
-/**
- * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
- */
-public class FacebookIdentityProvider extends AbstractOAuth2IdentityProvider implements SocialIdentityProvider {
-
- public static final String AUTH_URL = "https://graph.facebook.com/oauth/authorize";
- public static final String TOKEN_URL = "https://graph.facebook.com/oauth/access_token";
- public static final String PROFILE_URL = "https://graph.facebook.com/me";
- public static final String DEFAULT_SCOPE = "email";
-
- public FacebookIdentityProvider(OAuth2IdentityProviderConfig config) {
- super(config);
- config.setAuthorizationUrl(AUTH_URL);
- config.setTokenUrl(TOKEN_URL);
- config.setUserInfoUrl(PROFILE_URL);
- }
-
- protected FederatedIdentity doGetFederatedIdentity(String accessToken) {
- try {
- JsonNode profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
-
- String id = getJsonProperty(profile, "id");
-
- FederatedIdentity user = new FederatedIdentity(id);
-
- String email = getJsonProperty(profile, "email");
-
- user.setEmail(email);
-
- String username = getJsonProperty(profile, "username");
-
- if (username == null) {
- if (email != null) {
- username = email;
- } else {
- username = id;
- }
- }
-
- user.setUsername(username);
-
- String firstName = getJsonProperty(profile, "first_name");
- String lastName = getJsonProperty(profile, "last_name");
-
- if (lastName == null) {
- lastName = "";
- } else {
- lastName = " " + lastName;
- }
-
- user.setName(firstName + lastName);
-
- return user;
- } catch (Exception e) {
- throw new IdentityBrokerException("Could not obtain user profile from facebook.", e);
- }
- }
-
- @Override
- protected String getDefaultScopes() {
- return DEFAULT_SCOPE;
- }
-}
+package org.keycloak.social.facebook;
+
+import org.codehaus.jackson.JsonNode;
+import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
+import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
+import org.keycloak.broker.oidc.util.SimpleHttp;
+import org.keycloak.broker.provider.FederatedIdentity;
+import org.keycloak.broker.provider.IdentityBrokerException;
+import org.keycloak.social.SocialIdentityProvider;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class FacebookIdentityProvider extends AbstractOAuth2IdentityProvider implements SocialIdentityProvider {
+
+ public static final String AUTH_URL = "https://graph.facebook.com/oauth/authorize";
+ public static final String TOKEN_URL = "https://graph.facebook.com/oauth/access_token";
+ public static final String PROFILE_URL = "https://graph.facebook.com/me";
+ public static final String DEFAULT_SCOPE = "email";
+
+ public FacebookIdentityProvider(OAuth2IdentityProviderConfig config) {
+ super(config);
+ config.setAuthorizationUrl(AUTH_URL);
+ config.setTokenUrl(TOKEN_URL);
+ config.setUserInfoUrl(PROFILE_URL);
+ }
+
+ protected FederatedIdentity doGetFederatedIdentity(String accessToken) {
+ try {
+ JsonNode profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
+
+ String id = getJsonProperty(profile, "id");
+
+ FederatedIdentity user = new FederatedIdentity(id);
+
+ String email = getJsonProperty(profile, "email");
+
+ user.setEmail(email);
+
+ String username = getJsonProperty(profile, "username");
+
+ if (username == null) {
+ if (email != null) {
+ username = email;
+ } else {
+ username = id;
+ }
+ }
+
+ user.setUsername(username);
+
+ String firstName = getJsonProperty(profile, "first_name");
+ String lastName = getJsonProperty(profile, "last_name");
+
+ if (lastName == null) {
+ lastName = "";
+ } else {
+ lastName = " " + lastName;
+ }
+
+ user.setName(firstName + lastName);
+
+ return user;
+ } catch (Exception e) {
+ throw new IdentityBrokerException("Could not obtain user profile from facebook.", e);
+ }
+ }
+
+ @Override
+ protected String getDefaultScopes() {
+ return DEFAULT_SCOPE;
+ }
+}
diff --git a/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java b/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java
index df6a4d0..d3ff667 100755
--- a/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java
+++ b/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java
@@ -1,49 +1,49 @@
-package org.keycloak.social.github;
-
-import org.codehaus.jackson.JsonNode;
-import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
-import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
-import org.keycloak.broker.oidc.util.SimpleHttp;
-import org.keycloak.broker.provider.FederatedIdentity;
-import org.keycloak.broker.provider.IdentityBrokerException;
-import org.keycloak.social.SocialIdentityProvider;
-
-/**
- * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
- */
-public class GitHubIdentityProvider extends AbstractOAuth2IdentityProvider implements SocialIdentityProvider {
-
- public static final String AUTH_URL = "https://github.com/login/oauth/authorize";
- public static final String TOKEN_URL = "https://github.com/login/oauth/access_token";
- public static final String PROFILE_URL = "https://api.github.com/user";
- public static final String DEFAULT_SCOPE = "user:email";
-
- public GitHubIdentityProvider(OAuth2IdentityProviderConfig config) {
- super(config);
- config.setAuthorizationUrl(AUTH_URL);
- config.setTokenUrl(TOKEN_URL);
- config.setUserInfoUrl(PROFILE_URL);
- }
-
- @Override
- protected FederatedIdentity doGetFederatedIdentity(String accessToken) {
- try {
- JsonNode profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
-
- FederatedIdentity user = new FederatedIdentity(getJsonProperty(profile, "id"));
-
- user.setUsername(getJsonProperty(profile, "login"));
- user.setName(getJsonProperty(profile, "name"));
- user.setEmail(getJsonProperty(profile, "email"));
-
- return user;
- } catch (Exception e) {
- throw new IdentityBrokerException("Could not obtain user profile from github.", e);
- }
- }
-
- @Override
- protected String getDefaultScopes() {
- return DEFAULT_SCOPE;
- }
-}
+package org.keycloak.social.github;
+
+import org.codehaus.jackson.JsonNode;
+import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
+import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
+import org.keycloak.broker.oidc.util.SimpleHttp;
+import org.keycloak.broker.provider.FederatedIdentity;
+import org.keycloak.broker.provider.IdentityBrokerException;
+import org.keycloak.social.SocialIdentityProvider;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class GitHubIdentityProvider extends AbstractOAuth2IdentityProvider implements SocialIdentityProvider {
+
+ public static final String AUTH_URL = "https://github.com/login/oauth/authorize";
+ public static final String TOKEN_URL = "https://github.com/login/oauth/access_token";
+ public static final String PROFILE_URL = "https://api.github.com/user";
+ public static final String DEFAULT_SCOPE = "user:email";
+
+ public GitHubIdentityProvider(OAuth2IdentityProviderConfig config) {
+ super(config);
+ config.setAuthorizationUrl(AUTH_URL);
+ config.setTokenUrl(TOKEN_URL);
+ config.setUserInfoUrl(PROFILE_URL);
+ }
+
+ @Override
+ protected FederatedIdentity doGetFederatedIdentity(String accessToken) {
+ try {
+ JsonNode profile = SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken).asJson();
+
+ FederatedIdentity user = new FederatedIdentity(getJsonProperty(profile, "id"));
+
+ user.setUsername(getJsonProperty(profile, "login"));
+ user.setName(getJsonProperty(profile, "name"));
+ user.setEmail(getJsonProperty(profile, "email"));
+
+ return user;
+ } catch (Exception e) {
+ throw new IdentityBrokerException("Could not obtain user profile from github.", e);
+ }
+ }
+
+ @Override
+ protected String getDefaultScopes() {
+ return DEFAULT_SCOPE;
+ }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index 00b071e..c8371bd 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -157,7 +157,7 @@ public class AccountTest {
});
}
- @Test @Ignore
+ //@Test @Ignore
public void runit() throws Exception {
Thread.sleep(10000000);
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
index cfc9534..b7d39fc 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
@@ -39,6 +39,7 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.ResourceAdminManager;
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.UriBuilder;
import java.net.URI;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
@@ -138,7 +140,7 @@ public class AdapterTestStrategy extends ExternalResource {
ApplicationModel adminConsole = adminRealm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
TokenManager tm = new TokenManager();
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);
return tm.encodeToken(adminRealm, token);
} finally {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java
index 01a5861..56f8b8f 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java
@@ -86,7 +86,7 @@ public class RelativeUriAdapterTest {
ApplicationModel adminConsole = adminRealm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
TokenManager tm = new TokenManager();
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);
adminToken = tm.encodeToken(adminRealm, token);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java
index 302dd8d..873bbff 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java
@@ -78,7 +78,7 @@ public class AdminAPITest {
ApplicationModel adminConsole = adminRealm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
TokenManager tm = new TokenManager();
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);
return tm.encodeToken(adminRealm, token);
} finally {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
index 2d173ae..35cf6fc 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
@@ -251,11 +251,11 @@ public class UserSessionProviderTest {
Set<String> expiredClientSessions = new HashSet<String>();
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());
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(0);
expired.add(s.getId());
@@ -267,7 +267,7 @@ public class UserSessionProviderTest {
Set<String> valid = 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());
resetSession();
@@ -376,7 +376,7 @@ public class UserSessionProviderTest {
try {
for (int i = 0; i < 25; 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"));
clientSession.setUserSession(userSession);
clientSession.setRedirectUri("http://redirect");
@@ -481,7 +481,7 @@ public class UserSessionProviderTest {
private UserSessionModel[] createSessions() {
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>();
roles.add("one");
@@ -490,10 +490,10 @@ public class UserSessionProviderTest {
createClientSession(realm.findClient("test-app"), sessions[0], "http://redirect", "state", roles);
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>());
- 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>());
resetSession();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
index 34342e0..7120b36 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
@@ -454,6 +454,17 @@ public class AccessTokenTest {
Assert.assertEquals(401, response.getStatus());
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
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java
index b8e9e1c..91970e6 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java
@@ -420,7 +420,7 @@ public class SamlBindingTest {
ApplicationModel adminConsole = adminRealm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
TokenManager tm = new TokenManager();
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);
return tm.encodeToken(adminRealm, token);
} finally {