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 761decc1d0..4d58970d7f --- 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 a6b9030aa3..78c890b24d 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 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 userNotes = new HashMap(); FederatedIdentity federatedIdentity = getFederatedIdentity(userNotes, response); @@ -235,5 +222,14 @@ public abstract class AbstractOAuth2IdentityProviderBill Burke + * @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 0000000000..9c46cd8d4b --- /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 { + + 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 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 7bd80dba31..68b69294dd 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 { + 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 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 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); } - } - throw new RuntimeException("Unreachable"); + + String issuerURL = getEntityId(uriInfo, realm); + SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder(); + builder.logoutRequestID(request.getID()); + builder.destination(config.getSingleLogoutServiceUrl()); + builder.issuer(issuerURL); + builder.relayState(relayState); + if (config.isWantAuthnRequestsSigned()) { + builder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()) + .signDocument(); + } + try { + if (config.isPostBindingResponse()) { + return builder.postBinding().response(); + } else { + return builder.redirectBinding().response(); + } + } catch (ConfigurationException e) { + throw new RuntimeException(e); + } catch (ProcessingException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } private String getEntityId(UriInfo uriInfo, RealmModel realm) { @@ -283,8 +274,8 @@ public class SAMLEndpoint { SubjectType.STSubType subType = subject.getSubType(); NameIDType subjectNameID = (NameIDType) subType.getBaseID(); Map 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 fe381ca32d..60acd10c7f 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 AbstractIdentityProviderBill Burke * @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/JWSInput.java b/core/src/main/java/org/keycloak/jose/jws/JWSInput.java index 6db10fa077..d20da6dbc6 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 readJsonContent(Class type) throws IOException { return JsonSerialization.readValue(content, type); } 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 4772280540..fef959dcb5 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 Bill Burke * @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 42baccffe6..fc68f6cd62 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 Bill Burke * @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 0000000000..8480b46b92 --- /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 Bill Burke + * @version $Revision: 1 $ + */ +public interface SignatureProvider { + boolean verify(JWSInput input, String key); +} 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 a14b3f8340..f85020e800 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 adapterSessionIds; protected int notBefore; + protected List keycloakSessionIds; public LogoutAction() { } - public LogoutAction(String id, int expiration, String resource, List adapterSessionIds, int notBefore) { + public LogoutAction(String id, int expiration, String resource, List adapterSessionIds, int notBefore, List 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 getKeycloakSessionIds() { + return keycloakSessionIds; + } + + public void setKeycloakSessionIds(List 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 e2d089abd6..15bbb32288 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 0000000000..d3807496c2 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-keycloak-oidc.html @@ -0,0 +1 @@ +
\ 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 be46714761..25e7682365 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 @@
- +
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 61767b54ec..d75e78052e 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 56d433562a..b7421deb1f 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 getUserSessions(RealmModel realm, UserModel user); List getUserSessions(RealmModel realm, ClientModel client); List getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults); + List getUserSessionByBrokerUserId(RealmModel realm, String brokerUserId); + UserSessionModel getUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId); + + List 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/InfinispanUserSessionProvider.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java index 85d7a604ca..e7ff079f62 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(); @@ -124,6 +126,28 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { return wrapUserSessions(realm, sessions.values()); } + @Override + public List getUserSessionByBrokerUserId(RealmModel realm, String brokerUserId) { + Map 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 sessions = new MapReduceTask(sessionCache) + .mappedWith(UserSessionMapper.create(realm.getId()).brokerSessionId(brokerSessionId)) + .reducedWith(new FirstResultReducer()) + .execute(); + + List userSessionModels = wrapUserSessions(realm, sessions.values()); + if (userSessionModels.isEmpty()) return null; + return userSessionModels.get(0); + } + @Override public List getUserSessions(RealmModel realm, ClientModel client) { return getUserSessions(realm, client, -1, -1); @@ -173,7 +197,14 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { return userSessions; } - public List getUserSessionsByNote(RealmModel realm, Map notes) { + @Override + public List getUserSessionsByNote(RealmModel realm, String noteName, String noteValue) { + HashMap notes = new HashMap<>(); + notes.put(noteName, noteValue); + return getUserSessionsByNotes(realm, notes); + } + + public List getUserSessionsByNotes(RealmModel realm, Map notes) { Map 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/UserSessionAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java index dfd7914e58..6cfc12182e 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-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 99c23a934b..5ab9dc55a2 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/mapreduce/UserSessionMapper.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/UserSessionMapper.java index f781b9e014..210b6d5a28 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 expired && entity.getLastSessionRefresh() > expiredRefresh) { return; } 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 dcae8e2579..d70c3263fb 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 Stian Thorgersen @@ -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(); @@ -121,6 +125,44 @@ public class JpaUserSessionProvider implements UserSessionProvider { return sessions; } + @Override + public List getUserSessionByBrokerUserId(RealmModel realm, String brokerUserId) { + List sessions = new LinkedList(); + TypedQuery 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 sessions = new LinkedList(); + TypedQuery 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 getUserSessionsByNote(RealmModel realm, String noteName, String noteValue) { + List sessions = new LinkedList(); + TypedQuery 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 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 ca164270c5..28d466300e 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 @@ -40,6 +40,16 @@ public class UserSessionAdapter implements UserSessionModel { return entity.getId(); } + @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-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 e822e759fb..4a40699d3c 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 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 762ce6a317..d4bd889722 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-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 41081c0c6d..c185a0b05c 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 Stian Thorgersen @@ -31,14 +34,18 @@ public class MemUserSessionProvider implements UserSessionProvider { private final KeycloakSession session; private final ConcurrentHashMap userSessions; + private final ConcurrentHashMap userSessionsByBrokerSessionId; + private final ConcurrentHashMap> userSessionsByBrokerUserId; private final ConcurrentHashMap clientSessions; private final ConcurrentHashMap loginFailures; - public MemUserSessionProvider(KeycloakSession session, ConcurrentHashMap userSessions, ConcurrentHashMap clientSessions, ConcurrentHashMap loginFailures) { + public MemUserSessionProvider(KeycloakSession session, ConcurrentHashMap userSessions, ConcurrentHashMap userSessionsByBrokerSessionId, ConcurrentHashMap> userSessionsByBrokerUserId, ConcurrentHashMap clientSessions, ConcurrentHashMap 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,12 +92,55 @@ 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 set = userSessionsByBrokerUserId.get(brokerUserId); + if (set == null) { + Set 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 getUserSessionByBrokerUserId(RealmModel realm, String brokerUserId) { + Set sessions = userSessionsByBrokerUserId.get(brokerUserId); + if (sessions == null) return Collections.emptyList(); + List userSessions = new LinkedList(); + 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); @@ -116,6 +166,17 @@ public class MemUserSessionProvider implements UserSessionProvider { return userSessions; } + @Override + public List getUserSessionsByNote(RealmModel realm, String noteName, String noteValue) { + List userSessions = new LinkedList(); + 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 getUserSessions(RealmModel realm, ClientModel client) { List userSessionEntities = new LinkedList(); @@ -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 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 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 a943eca7da..73b7ebca06 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 clientSessions = new ConcurrentHashMap(); private ConcurrentHashMap loginFailures = new ConcurrentHashMap(); + private final ConcurrentHashMap userSessionsByBrokerSessionId = new ConcurrentHashMap<>(); + private final ConcurrentHashMap> 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 5b215a055f..86b01877eb 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-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 16b74db626..c7d0e26cd4 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-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 6630855871..0bc584940f 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(); @@ -121,6 +123,39 @@ public class MongoUserSessionProvider implements UserSessionProvider { return sessions; } + @Override + public List getUserSessionByBrokerUserId(RealmModel realm, String brokerUserId) { + DBObject query = new BasicDBObject("brokerUserId", brokerUserId); + List sessions = new LinkedList(); + 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 sessions = new LinkedList(); + 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 getUserSessionsByNote(RealmModel realm, String noteName, String noteValue) { + DBObject query = new QueryBuilder() + .and("realmId").is(realm.getId()) + .and("notes." + noteName).is(noteValue).get(); + List sessions = new LinkedList(); + for (MongoUserSessionEntity e : mongoStore.loadEntities(MongoUserSessionEntity.class, query, invocationContext)) { + sessions.add(new UserSessionAdapter(session, this, e, realm, invocationContext)); + } + return sessions; + } + @Override public List 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 c12f377861..644bbdbec4 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 @@ -41,6 +41,16 @@ public class UserSessionAdapter extends AbstractMongoAdapter 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 25a3414b04..1bd7fdc30e 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 clientSessions = new MultivaluedHashMap(); - putClientSessions(clientSessions, session); - - logger.debugv("logging out {0} resources ", clientSessions.size()); - for (Map.Entry> 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 adapterSessionIds = null; + List userSessions = new LinkedList<>(); if (clientSessions != null && clientSessions.size() > 0) { adapterSessionIds = new MultivaluedHashMap(); 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 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 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 adapterSessionIds, List 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 7f155e0ee4..f694661a62 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 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/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java index 11a49c4170..7a4bea5d58 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 f9ee68b9c4..5b405355a6 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/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index 0befaa4c87..e62b4ba446 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 f271a2e073..e860ea8d84 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 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/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java b/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java index 8ebea5096b..de54cc5c72 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 Stian Thorgersen - */ -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 Stian Thorgersen + */ +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 df6a4d031d..d3ff667d04 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 Stian Thorgersen - */ -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 Stian Thorgersen + */ +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 00b071e25c..c8371bdb5c 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 cfc9534e7b..b7d39fcef8 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 01a5861350..56f8b8f2a1 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 302dd8da86..873bbff52b 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 2d173aeccb..35cf6fc106 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 expiredClientSessions = new HashSet(); 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 valid = new HashSet(); Set validClientSessions = new HashSet(); - 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 roles = new HashSet(); 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()); - 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()); - 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()); 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 34342e04b9..7120b36fea 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 b8e9e1c861..91970e6e2d 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 {