Merge pull request #1088 from patriot1burke/master
oidc and saml broker backchannel logout
This commit is contained in:
commit
d36ee0d0af
54 changed files with 899 additions and 289 deletions
18
broker/core/src/main/java/org/keycloak/broker/provider/FederatedIdentity.java
Normal file → Executable file
18
broker/core/src/main/java/org/keycloak/broker/provider/FederatedIdentity.java
Normal file → Executable file
|
@ -32,6 +32,8 @@ public class FederatedIdentity {
|
|||
private String email;
|
||||
private String 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 "{" +
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
package org.keycloak.broker.oidc;
|
||||
|
||||
import org.keycloak.broker.oidc.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.IdentityProvider;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.representations.adapters.action.AdminAction;
|
||||
import org.keycloak.representations.adapters.action.LogoutAction;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider {
|
||||
|
||||
public KeycloakOIDCIdentityProvider(OIDCIdentityProviderConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
|
||||
return new KeycloakEndpoint(callback, realm, event);
|
||||
}
|
||||
|
||||
protected class KeycloakEndpoint extends OIDCEndpoint {
|
||||
public KeycloakEndpoint(AuthenticationCallback callback, RealmModel realm, EventBuilder event) {
|
||||
super(callback, realm, event);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path(AdapterConstants.K_LOGOUT)
|
||||
public Response backchannelLogout(String input) {
|
||||
JWSInput token = new JWSInput(input);
|
||||
String signingCert = getConfig().getSigningCertificate();
|
||||
if (signingCert != null && !signingCert.trim().equals("")) {
|
||||
if (!token.verify(getConfig().getSigningCertificate())) {
|
||||
return Response.status(400).build(); }
|
||||
}
|
||||
LogoutAction action = null;
|
||||
try {
|
||||
action = JsonSerialization.readValue(token.getContent(), LogoutAction.class);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (!validateAction(action)) return Response.status(400).build();
|
||||
if (action.getKeycloakSessionIds() != null) {
|
||||
for (String sessionId : action.getKeycloakSessionIds()) {
|
||||
String brokerSessionId = getConfig().getAlias() + "." + sessionId;
|
||||
UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId);
|
||||
if (userSession != null) {
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
protected boolean validateAction(AdminAction action) {
|
||||
if (!action.validate()) {
|
||||
logger.warn("admin request failed, not validated" + action.getAction());
|
||||
return false;
|
||||
}
|
||||
if (action.isExpired()) {
|
||||
logger.warn("admin request failed, expired token");
|
||||
return false;
|
||||
}
|
||||
if (!getConfig().getClientId().equals(action.getResource())) {
|
||||
logger.warn("Resource name does not match");
|
||||
return false;
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleHttp generateTokenRequest(String authorizationCode) {
|
||||
return super.generateTokenRequest(authorizationCode)
|
||||
.param(AdapterConstants.APPLICATION_SESSION_STATE, "n/a"); // hack to get backchannel logout to work
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.broker.oidc;
|
||||
|
||||
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public class KeycloakOIDCIdentityProviderFactory extends AbstractIdentityProviderFactory<KeycloakOIDCIdentityProvider> {
|
||||
|
||||
public static final String PROVIDER_ID = "keycloak-oidc";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Keycloak OpenID Connect";
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakOIDCIdentityProvider create(IdentityProviderModel model) {
|
||||
return new KeycloakOIDCIdentityProvider(new OIDCIdentityProviderConfig(model));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> parseConfig(InputStream inputStream) {
|
||||
OIDCConfigurationRepresentation rep = null;
|
||||
try {
|
||||
rep = JsonSerialization.readValue(inputStream, OIDCConfigurationRepresentation.class);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("failed to load openid connect metadata", e);
|
||||
}
|
||||
OIDCIdentityProviderConfig config = new OIDCIdentityProviderConfig(new IdentityProviderModel());
|
||||
config.setIssuer(rep.getIssuer());
|
||||
config.setLogoutUrl(rep.getLogoutEndpoint());
|
||||
config.setAuthorizationUrl(rep.getAuthorizationEndpoint());
|
||||
config.setTokenUrl(rep.getTokenEndpoint());
|
||||
config.setUserInfoUrl(rep.getUserinfoEndpoint());
|
||||
return config.getConfig();
|
||||
|
||||
}
|
||||
}
|
|
@ -18,19 +18,24 @@
|
|||
package org.keycloak.broker.oidc;
|
||||
|
||||
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";
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
3
broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderFactory
Normal file → Executable file
3
broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderFactory
Normal file → Executable file
|
@ -1 +1,2 @@
|
|||
org.keycloak.broker.oidc.OIDCIdentityProviderFactory
|
||||
org.keycloak.broker.oidc.OIDCIdentityProviderFactory
|
||||
org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory
|
|
@ -78,6 +78,8 @@ import java.util.Map;
|
|||
public class SAMLEndpoint {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
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<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);
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
11
core/src/main/java/org/keycloak/jose/jws/crypto/SignatureProvider.java
Executable file
11
core/src/main/java/org/keycloak/jose/jws/crypto/SignatureProvider.java
Executable file
|
@ -0,0 +1,11 @@
|
|||
package org.keycloak.jose.jws.crypto;
|
||||
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public interface SignatureProvider {
|
||||
boolean verify(JWSInput input, String key);
|
||||
}
|
|
@ -10,14 +10,16 @@ public class LogoutAction extends AdminAction {
|
|||
public static final String LOGOUT = "LOGOUT";
|
||||
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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<div data-ng-include data-src="resourceUrl + '/partials/realm-identity-provider-oidc.html'"></div>
|
|
@ -129,7 +129,7 @@
|
|||
<div class="form-group" data-ng-show="newIdentityProvider">
|
||||
<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">
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<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())
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
@ -121,6 +125,44 @@ public class JpaUserSessionProvider implements UserSessionProvider {
|
|||
return sessions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserSessionModel> getUserSessionByBrokerUserId(RealmModel realm, String brokerUserId) {
|
||||
List<UserSessionModel> sessions = new LinkedList<UserSessionModel>();
|
||||
TypedQuery<UserSessionEntity> query = em.createNamedQuery("getUserSessionByBrokerUserId", UserSessionEntity.class)
|
||||
.setParameter("realmId", realm.getId())
|
||||
.setParameter("brokerUserId", brokerUserId);
|
||||
for (UserSessionEntity e : query.getResultList()) {
|
||||
sessions.add(new UserSessionAdapter(session, em, realm, e));
|
||||
}
|
||||
return sessions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserSessionModel getUserSessionByBrokerSessionId(RealmModel realm, String brokerSessionId) {
|
||||
List<UserSessionModel> sessions = new LinkedList<UserSessionModel>();
|
||||
TypedQuery<UserSessionEntity> query = em.createNamedQuery("getUserSessionByBrokerSessionId", UserSessionEntity.class)
|
||||
.setParameter("realmId", realm.getId())
|
||||
.setParameter("brokerSessionId", brokerSessionId);
|
||||
for (UserSessionEntity e : query.getResultList()) {
|
||||
sessions.add(new UserSessionAdapter(session, em, realm, e));
|
||||
}
|
||||
if (sessions.isEmpty()) return null;
|
||||
return sessions.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserSessionModel> getUserSessionsByNote(RealmModel realm, String noteName, String noteValue) {
|
||||
List<UserSessionModel> sessions = new LinkedList<UserSessionModel>();
|
||||
TypedQuery<UserSessionNoteEntity> query = em.createNamedQuery("selectNoteByNameValue", UserSessionNoteEntity.class)
|
||||
.setParameter("name", noteName)
|
||||
.setParameter("value", noteValue);
|
||||
for (UserSessionNoteEntity note : query.getResultList()) {
|
||||
if (!note.getUserSession().getRealmId().equals(realm.getId())) continue;
|
||||
sessions.add(new UserSessionAdapter(session, em, realm, note.getUserSession()));
|
||||
}
|
||||
return sessions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client) {
|
||||
return getUserSessions(realm, client, -1, -1);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))")
|
||||
|
|
|
@ -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,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<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);
|
||||
|
@ -116,6 +166,17 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
|||
return userSessions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserSessionModel> getUserSessionsByNote(RealmModel realm, String noteName, String noteValue) {
|
||||
List<UserSessionModel> userSessions = new LinkedList<UserSessionModel>();
|
||||
for (UserSessionEntity s : this.userSessions.values()) {
|
||||
if (s.getRealm().equals(realm.getId()) && noteValue.equals(s.getNotes().get(noteName))) {
|
||||
userSessions.add(new UserSessionAdapter(session, this, realm, s));
|
||||
}
|
||||
}
|
||||
return userSessions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client) {
|
||||
List<UserSessionEntity> userSessionEntities = new LinkedList<UserSessionEntity>();
|
||||
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<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);
|
||||
|
|
|
@ -41,6 +41,16 @@ public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEnt
|
|||
return entity.getId();
|
||||
}
|
||||
|
||||
@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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
2
services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
Normal file → Executable file
2
services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
Normal file → Executable file
|
@ -314,7 +314,7 @@ public class TokenEndpoint {
|
|||
|
||||
UserSessionProvider sessions = session.sessions();
|
||||
|
||||
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);
|
||||
|
|
|
@ -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();
|
||||
|
|
2
services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java
Normal file → Executable file
2
services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java
Normal file → Executable file
|
@ -116,7 +116,7 @@ public class HttpAuthenticationManager {
|
|||
event.error(Errors.USER_DISABLED);
|
||||
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()) {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -157,7 +157,7 @@ public class AccountTest {
|
|||
});
|
||||
}
|
||||
|
||||
@Test @Ignore
|
||||
//@Test @Ignore
|
||||
public void runit() throws Exception {
|
||||
Thread.sleep(10000000);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue