KEYCLOAK-4929

This commit is contained in:
Bill Burke 2017-05-18 16:53:31 -04:00
commit 2cac8b1bb7
335 changed files with 13075 additions and 4297 deletions

View file

@ -170,8 +170,8 @@ public class HttpClientBuilder {
return this;
}
public HttpClientBuilder disableCookieCache() {
this.disableCookieCache = true;
public HttpClientBuilder disableCookieCache(boolean disable) {
this.disableCookieCache = disable;
return this;
}
@ -334,7 +334,7 @@ public class HttpClientBuilder {
}
public HttpClient build(AdapterHttpClientConfig adapterConfig) {
disableCookieCache(); // disable cookie cache as we don't want sticky sessions for load balancing
disableCookieCache(true); // disable cookie cache as we don't want sticky sessions for load balancing
String truststorePath = adapterConfig.getTruststore();
if (truststorePath != null) {

View file

@ -17,6 +17,8 @@
*/
package org.keycloak.adapters.authorization;
import java.util.Set;
import org.jboss.logging.Logger;
import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.spi.HttpFacade;
@ -26,8 +28,6 @@ import org.keycloak.authorization.client.resource.PermissionResource;
import org.keycloak.authorization.client.resource.ProtectionResource;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import java.util.Set;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@ -52,7 +52,7 @@ public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer {
private void challengeEntitlementAuthentication(OIDCHttpFacade facade) {
HttpFacade.Response response = facade.getResponse();
AuthzClient authzClient = getAuthzClient();
String clientId = authzClient.getConfiguration().getClientId();
String clientId = authzClient.getConfiguration().getResource();
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/entitlement";
response.setStatus(401);
response.setHeader("WWW-Authenticate", "KC_ETT realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\"");
@ -65,7 +65,7 @@ public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer {
HttpFacade.Response response = facade.getResponse();
AuthzClient authzClient = getAuthzClient();
String ticket = getPermissionTicket(pathConfig, requiredScopes, authzClient);
String clientId = authzClient.getConfiguration().getClientId();
String clientId = authzClient.getConfiguration().getResource();
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/authorize";
response.setStatus(401);
response.setHeader("WWW-Authenticate", "UMA realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\",ticket=\"" + ticket + "\"");

View file

@ -127,7 +127,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
AccessToken token = httpFacade.getSecurityContext().getToken();
if (token.getAuthorization() == null) {
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getClientId());
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getResource());
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
} else {
EntitlementRequest request = new EntitlementRequest();
@ -137,7 +137,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
permissionRequest.setScopes(new HashSet<>(pathConfig.getScopes()));
LOGGER.debugf("Sending entitlements request: resource_set_id [%s], resource_set_name [%s], scopes [%s].", permissionRequest.getResourceSetId(), permissionRequest.getResourceSetName(), permissionRequest.getScopes());
request.addPermission(permissionRequest);
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getClientId(), request);
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getResource(), request);
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
}
}

View file

@ -168,7 +168,7 @@ public class OIDCFilterSessionStore extends FilterSessionStore implements Adapte
HttpSession httpSession = request.getSession();
httpSession.setAttribute(KeycloakAccount.class.getName(), sAccount);
httpSession.setAttribute(KeycloakSecurityContext.class.getName(), sAccount.getKeycloakSecurityContext());
if (idMapper != null) idMapper.map(account.getKeycloakSecurityContext().getToken().getClientSession(), account.getPrincipal().getName(), httpSession.getId());
if (idMapper != null) idMapper.map(account.getKeycloakSecurityContext().getToken().getSessionState(), account.getPrincipal().getName(), httpSession.getId());
//String username = securityContext.getToken().getSubject();
//log.fine("userSessionManagement.login: " + username);
}

View file

@ -17,44 +17,33 @@
*/
package org.keycloak.authorization.client;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClients;
import org.keycloak.util.BasicAuthHelper;
import java.util.HashMap;
import java.util.Map;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClients;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.util.BasicAuthHelper;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class Configuration {
public class Configuration extends AdapterConfig {
@JsonIgnore
private HttpClient httpClient;
@JsonProperty("auth-server-url")
protected String authServerUrl;
@JsonProperty("realm")
protected String realm;
@JsonProperty("resource")
protected String clientId;
@JsonProperty("credentials")
protected Map<String, Object> clientCredentials = new HashMap<>();
public Configuration() {
}
public Configuration(String authServerUrl, String realm, String clientId, Map<String, Object> clientCredentials, HttpClient httpClient) {
this.authServerUrl = authServerUrl;
this.realm = realm;
this.clientId = clientId;
this.clientCredentials = clientCredentials;
setAuthServerUrl(authServerUrl);
setRealm(realm);
setResource(clientId);
setCredentials(clientCredentials);
this.httpClient = httpClient;
}
@ -62,13 +51,13 @@ public class Configuration {
private ClientAuthenticator clientAuthenticator = new ClientAuthenticator() {
@Override
public void configureClientCredentials(HashMap<String, String> requestParams, HashMap<String, String> requestHeaders) {
String secret = (String) clientCredentials.get("secret");
String secret = (String) getCredentials().get("secret");
if (secret == null) {
throw new RuntimeException("Client secret not provided.");
}
requestHeaders.put("Authorization", BasicAuthHelper.createHeader(clientId, secret));
requestHeaders.put("Authorization", BasicAuthHelper.createHeader(getResource(), secret));
}
};
@ -80,23 +69,7 @@ public class Configuration {
return httpClient;
}
public String getClientId() {
return clientId;
}
public String getAuthServerUrl() {
return authServerUrl;
}
public ClientAuthenticator getClientAuthenticator() {
return this.clientAuthenticator;
}
public Map<String, Object> getClientCredentials() {
return clientCredentials;
}
public String getRealm() {
return realm;
}
}

View file

@ -29,10 +29,10 @@ import java.security.PublicKey;
*/
public class RSATokenVerifier {
private TokenVerifier tokenVerifier;
private final TokenVerifier<AccessToken> tokenVerifier;
private RSATokenVerifier(String tokenString) {
this.tokenVerifier = TokenVerifier.create(tokenString);
this.tokenVerifier = TokenVerifier.create(tokenString, AccessToken.class).withDefaultChecks();
}
public static RSATokenVerifier create(String tokenString) {

View file

@ -18,7 +18,8 @@
package org.keycloak;
import org.keycloak.common.VerificationException;
import org.keycloak.jose.jws.Algorithm;
import org.keycloak.exceptions.TokenNotActiveException;
import org.keycloak.exceptions.TokenSignatureInvalidException;
import org.keycloak.jose.jws.AlgorithmType;
import org.keycloak.jose.jws.JWSHeader;
import org.keycloak.jose.jws.JWSInput;
@ -26,67 +27,280 @@ import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.HMACProvider;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.util.TokenUtil;
import javax.crypto.SecretKey;
import java.security.PublicKey;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class TokenVerifier {
public class TokenVerifier<T extends JsonWebToken> {
private final String tokenString;
private static final Logger LOG = Logger.getLogger(TokenVerifier.class.getName());
// This interface is here as JDK 7 is a requirement for this project.
// Once JDK 8 would become mandatory, java.util.function.Predicate would be used instead.
/**
* Functional interface of checks that verify some part of a JWT.
* @param <T> Type of the token handled by this predicate.
*/
// @FunctionalInterface
public static interface Predicate<T extends JsonWebToken> {
/**
* Performs a single check on the given token verifier.
* @param t Token, guaranteed to be non-null.
* @return
* @throws VerificationException
*/
boolean test(T t) throws VerificationException;
}
public static final Predicate<JsonWebToken> SUBJECT_EXISTS_CHECK = new Predicate<JsonWebToken>() {
@Override
public boolean test(JsonWebToken t) throws VerificationException {
String subject = t.getSubject();
if (subject == null) {
throw new VerificationException("Subject missing in token");
}
return true;
}
};
/**
* Check for token being neither expired nor used before it gets valid.
* @see JsonWebToken#isActive()
*/
public static final Predicate<JsonWebToken> IS_ACTIVE = new Predicate<JsonWebToken>() {
@Override
public boolean test(JsonWebToken t) throws VerificationException {
if (! t.isActive()) {
throw new TokenNotActiveException(t, "Token is not active");
}
return true;
}
};
public static class RealmUrlCheck implements Predicate<JsonWebToken> {
private static final RealmUrlCheck NULL_INSTANCE = new RealmUrlCheck(null);
private final String realmUrl;
public RealmUrlCheck(String realmUrl) {
this.realmUrl = realmUrl;
}
@Override
public boolean test(JsonWebToken t) throws VerificationException {
if (this.realmUrl == null) {
throw new VerificationException("Realm URL not set");
}
if (! this.realmUrl.equals(t.getIssuer())) {
throw new VerificationException("Invalid token issuer. Expected '" + this.realmUrl + "', but was '" + t.getIssuer() + "'");
}
return true;
}
};
public static class TokenTypeCheck implements Predicate<JsonWebToken> {
private static final TokenTypeCheck INSTANCE_BEARER = new TokenTypeCheck(TokenUtil.TOKEN_TYPE_BEARER);
private final String tokenType;
public TokenTypeCheck(String tokenType) {
this.tokenType = tokenType;
}
@Override
public boolean test(JsonWebToken t) throws VerificationException {
if (! tokenType.equalsIgnoreCase(t.getType())) {
throw new VerificationException("Token type is incorrect. Expected '" + tokenType + "' but was '" + t.getType() + "'");
}
return true;
}
};
private String tokenString;
private Class<? extends T> clazz;
private PublicKey publicKey;
private SecretKey secretKey;
private String realmUrl;
private String expectedTokenType = TokenUtil.TOKEN_TYPE_BEARER;
private boolean checkTokenType = true;
private boolean checkActive = true;
private boolean checkRealmUrl = true;
private final LinkedList<Predicate<? super T>> checks = new LinkedList<>();
private JWSInput jws;
private AccessToken token;
private T token;
protected TokenVerifier(String tokenString) {
protected TokenVerifier(String tokenString, Class<T> clazz) {
this.tokenString = tokenString;
this.clazz = clazz;
}
public static TokenVerifier create(String tokenString) {
return new TokenVerifier(tokenString);
protected TokenVerifier(T token) {
this.token = token;
}
public TokenVerifier publicKey(PublicKey publicKey) {
/**
* Creates an instance of {@code TokenVerifier} from the given string on a JWT of the given class.
* The token verifier has no checks defined. Note that the checks are only tested when
* {@link #verify()} method is invoked.
* @param <T> Type of the token
* @param tokenString String representation of JWT
* @param clazz Class of the token
* @return
*/
public static <T extends JsonWebToken> TokenVerifier<T> create(String tokenString, Class<T> clazz) {
return new TokenVerifier(tokenString, clazz);
}
/**
* Creates an instance of {@code TokenVerifier} from the given string on a JWT of the given class.
* The token verifier has no checks defined. Note that the checks are only tested when
* {@link #verify()} method is invoked.
* @return
*/
public static <T extends JsonWebToken> TokenVerifier<T> create(T token) {
return new TokenVerifier(token);
}
/**
* Adds default checks to the token verification:
* <ul>
* <li>Realm URL (JWT issuer field: {@code iss}) has to be defined and match realm set via {@link #realmUrl(java.lang.String)} method</li>
* <li>Subject (JWT subject field: {@code sub}) has to be defined</li>
* <li>Token type (JWT type field: {@code typ}) has to be {@code Bearer}. The type can be set via {@link #tokenType(java.lang.String)} method</li>
* <li>Token has to be active, ie. both not expired and not used before its validity (JWT issuer fields: {@code exp} and {@code nbf})</li>
* </ul>
* @return This token verifier.
*/
public TokenVerifier<T> withDefaultChecks() {
return withChecks(
RealmUrlCheck.NULL_INSTANCE,
SUBJECT_EXISTS_CHECK,
TokenTypeCheck.INSTANCE_BEARER,
IS_ACTIVE
);
}
private void removeCheck(Class<? extends Predicate<?>> checkClass) {
for (Iterator<Predicate<? super T>> it = checks.iterator(); it.hasNext();) {
if (it.next().getClass() == checkClass) {
it.remove();
}
}
}
private void removeCheck(Predicate<? super T> check) {
checks.remove(check);
}
private <P extends Predicate<? super T>> TokenVerifier<T> replaceCheck(Class<? extends Predicate<?>> checkClass, boolean active, P predicate) {
removeCheck(checkClass);
if (active) {
checks.add(predicate);
}
return this;
}
private <P extends Predicate<? super T>> TokenVerifier<T> replaceCheck(Predicate<? super T> check, boolean active, P predicate) {
removeCheck(check);
if (active) {
checks.add(predicate);
}
return this;
}
/**
* Will test the given checks in {@link #verify()} method in addition to already set checks.
* @param checks
* @return
*/
public TokenVerifier<T> withChecks(Predicate<? super T>... checks) {
if (checks != null) {
this.checks.addAll(Arrays.asList(checks));
}
return this;
}
/**
* Sets the key for verification of RSA-based signature.
* @param publicKey
* @return
*/
public TokenVerifier<T> publicKey(PublicKey publicKey) {
this.publicKey = publicKey;
return this;
}
public TokenVerifier secretKey(SecretKey secretKey) {
/**
* Sets the key for verification of HMAC-based signature.
* @param secretKey
* @return
*/
public TokenVerifier<T> secretKey(SecretKey secretKey) {
this.secretKey = secretKey;
return this;
}
public TokenVerifier realmUrl(String realmUrl) {
/**
* @deprecated This method is here only for backward compatibility with previous version of {@code TokenVerifier}.
* @return This token verifier
*/
public TokenVerifier<T> realmUrl(String realmUrl) {
this.realmUrl = realmUrl;
return this;
return replaceCheck(RealmUrlCheck.class, checkRealmUrl, new RealmUrlCheck(realmUrl));
}
public TokenVerifier checkTokenType(boolean checkTokenType) {
/**
* @deprecated This method is here only for backward compatibility with previous version of {@code TokenVerifier}.
* @return This token verifier
*/
public TokenVerifier<T> checkTokenType(boolean checkTokenType) {
this.checkTokenType = checkTokenType;
return this;
return replaceCheck(TokenTypeCheck.class, this.checkTokenType, new TokenTypeCheck(expectedTokenType));
}
public TokenVerifier checkActive(boolean checkActive) {
this.checkActive = checkActive;
return this;
/**
* @deprecated This method is here only for backward compatibility with previous version of {@code TokenVerifier}.
* @return This token verifier
*/
public TokenVerifier<T> tokenType(String tokenType) {
this.expectedTokenType = tokenType;
return replaceCheck(TokenTypeCheck.class, this.checkTokenType, new TokenTypeCheck(expectedTokenType));
}
public TokenVerifier checkRealmUrl(boolean checkRealmUrl) {
/**
* @deprecated This method is here only for backward compatibility with previous version of {@code TokenVerifier}.
* @return This token verifier
*/
public TokenVerifier<T> checkActive(boolean checkActive) {
return replaceCheck(IS_ACTIVE, checkActive, IS_ACTIVE);
}
/**
* @deprecated This method is here only for backward compatibility with previous version of {@code TokenVerifier}.
* @return This token verifier
*/
public TokenVerifier<T> checkRealmUrl(boolean checkRealmUrl) {
this.checkRealmUrl = checkRealmUrl;
return this;
return replaceCheck(RealmUrlCheck.class, this.checkRealmUrl, new RealmUrlCheck(realmUrl));
}
public TokenVerifier parse() throws VerificationException {
public TokenVerifier<T> parse() throws VerificationException {
if (jws == null) {
if (tokenString == null) {
throw new VerificationException("Token not set");
@ -100,7 +314,7 @@ public class TokenVerifier {
try {
token = jws.readJsonContent(AccessToken.class);
token = jws.readJsonContent(clazz);
} catch (JWSInputException e) {
throw new VerificationException("Failed to read access token from JWT", e);
}
@ -108,8 +322,10 @@ public class TokenVerifier {
return this;
}
public AccessToken getToken() throws VerificationException {
parse();
public T getToken() throws VerificationException {
if (token == null) {
parse();
}
return token;
}
@ -118,53 +334,97 @@ public class TokenVerifier {
return jws.getHeader();
}
public TokenVerifier verify() throws VerificationException {
parse();
if (checkRealmUrl && realmUrl == null) {
throw new VerificationException("Realm URL not set");
}
public void verifySignature() throws VerificationException {
AlgorithmType algorithmType = getHeader().getAlgorithm().getType();
if (AlgorithmType.RSA.equals(algorithmType)) {
if (publicKey == null) {
throw new VerificationException("Public key not set");
}
if (null == algorithmType) {
throw new VerificationException("Unknown or unsupported token algorithm");
} else switch (algorithmType) {
case RSA:
if (publicKey == null) {
throw new VerificationException("Public key not set");
}
if (!RSAProvider.verify(jws, publicKey)) {
throw new TokenSignatureInvalidException(token, "Invalid token signature");
} break;
case HMAC:
if (secretKey == null) {
throw new VerificationException("Secret key not set");
}
if (!HMACProvider.verify(jws, secretKey)) {
throw new TokenSignatureInvalidException(token, "Invalid token signature");
} break;
default:
throw new VerificationException("Unknown or unsupported token algorithm");
}
}
if (!RSAProvider.verify(jws, publicKey)) {
throw new VerificationException("Invalid token signature");
}
} else if (AlgorithmType.HMAC.equals(algorithmType)) {
if (secretKey == null) {
throw new VerificationException("Secret key not set");
}
if (!HMACProvider.verify(jws, secretKey)) {
throw new VerificationException("Invalid token signature");
}
} else {
throw new VerificationException("Unknown or unsupported token algorith");
public TokenVerifier<T> verify() throws VerificationException {
if (getToken() == null) {
parse();
}
if (jws != null) {
verifySignature();
}
String user = token.getSubject();
if (user == null) {
throw new VerificationException("Subject missing in token");
}
if (checkRealmUrl && !realmUrl.equals(token.getIssuer())) {
throw new VerificationException("Invalid token issuer. Expected '" + realmUrl + "', but was '" + token.getIssuer() + "'");
}
if (checkTokenType && !TokenUtil.TOKEN_TYPE_BEARER.equalsIgnoreCase(token.getType())) {
throw new VerificationException("Token type is incorrect. Expected '" + TokenUtil.TOKEN_TYPE_BEARER + "' but was '" + token.getType() + "'");
}
if (checkActive && !token.isActive()) {
throw new VerificationException("Token is not active");
for (Predicate<? super T> check : checks) {
if (! check.test(getToken())) {
throw new VerificationException("JWT check failed for check " + check);
}
}
return this;
}
/**
* Creates an optional predicate from a predicate that will proceed with check but always pass.
* @param <T>
* @param mandatoryPredicate
* @return
*/
public static <T extends JsonWebToken> Predicate<T> optional(final Predicate<T> mandatoryPredicate) {
return new Predicate<T>() {
@Override
public boolean test(T t) throws VerificationException {
try {
if (! mandatoryPredicate.test(t)) {
LOG.finer("[optional] predicate failed: " + mandatoryPredicate);
}
return true;
} catch (VerificationException ex) {
LOG.log(Level.FINER, "[optional] predicate " + mandatoryPredicate + " failed.", ex);
return true;
}
}
};
}
/**
* Creates a predicate that will proceed with checks of the given predicates
* and will pass if and only if at least one of the given predicates passes.
* @param <T>
* @param predicates
* @return
*/
public static <T extends JsonWebToken> Predicate<T> alternative(final Predicate<? super T>... predicates) {
return new Predicate<T>() {
@Override
public boolean test(T t) throws VerificationException {
for (Predicate<? super T> predicate : predicates) {
try {
if (predicate.test(t)) {
return true;
}
LOG.finer("[alternative] predicate failed: " + predicate);
} catch (VerificationException ex) {
LOG.log(Level.FINER, "[alternative] predicate " + predicate + " failed.", ex);
}
}
return false;
}
};
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.exceptions;
import org.keycloak.representations.JsonWebToken;
/**
* Exception thrown for cases when token is invalid due to time constraints (expired, or not yet valid).
* Cf. {@link JsonWebToken#isActive()}.
* @author hmlnarik
*/
public class TokenNotActiveException extends TokenVerificationException {
public TokenNotActiveException(JsonWebToken token) {
super(token);
}
public TokenNotActiveException(JsonWebToken token, String message) {
super(token, message);
}
public TokenNotActiveException(JsonWebToken token, String message, Throwable cause) {
super(token, message, cause);
}
public TokenNotActiveException(JsonWebToken token, Throwable cause) {
super(token, cause);
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.exceptions;
import org.keycloak.representations.JsonWebToken;
/**
* Thrown when token signature is invalid.
* @author hmlnarik
*/
public class TokenSignatureInvalidException extends TokenVerificationException {
public TokenSignatureInvalidException(JsonWebToken token) {
super(token);
}
public TokenSignatureInvalidException(JsonWebToken token, String message) {
super(token, message);
}
public TokenSignatureInvalidException(JsonWebToken token, String message, Throwable cause) {
super(token, message, cause);
}
public TokenSignatureInvalidException(JsonWebToken token, Throwable cause) {
super(token, cause);
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.exceptions;
import org.keycloak.common.VerificationException;
import org.keycloak.representations.JsonWebToken;
/**
* Exception thrown on failed verification of a token.
*
* @author hmlnarik
*/
public class TokenVerificationException extends VerificationException {
private final JsonWebToken token;
public TokenVerificationException(JsonWebToken token) {
this.token = token;
}
public TokenVerificationException(JsonWebToken token, String message) {
super(message);
this.token = token;
}
public TokenVerificationException(JsonWebToken token, String message, Throwable cause) {
super(message, cause);
this.token = token;
}
public TokenVerificationException(JsonWebToken token, Throwable cause) {
super(cause);
this.token = token;
}
public JsonWebToken getToken() {
return token;
}
}

View file

@ -97,9 +97,6 @@ public class AccessToken extends IDToken {
}
}
@JsonProperty("client_session")
protected String clientSession;
@JsonProperty("trusted-certs")
protected Set<String> trustedCertificates;
@ -156,10 +153,6 @@ public class AccessToken extends IDToken {
return resourceAccess.get(resource);
}
public String getClientSession() {
return clientSession;
}
public Access addAccess(String service) {
Access access = resourceAccess.get(service);
if (access != null) return access;
@ -168,11 +161,6 @@ public class AccessToken extends IDToken {
return access;
}
public AccessToken clientSession(String session) {
this.clientSession = session;
return this;
}
@Override
public AccessToken id(String id) {
return (AccessToken) super.id(id);

View file

@ -40,7 +40,6 @@ public class RefreshToken extends AccessToken {
*/
public RefreshToken(AccessToken token) {
this();
this.clientSession = token.getClientSession();
this.issuer = token.issuer;
this.subject = token.subject;
this.issuedFor = token.issuedFor;

View file

@ -46,6 +46,8 @@ public class RealmRepresentation {
protected Integer accessCodeLifespan;
protected Integer accessCodeLifespanUserAction;
protected Integer accessCodeLifespanLogin;
protected Integer actionTokenGeneratedByAdminLifespan;
protected Integer actionTokenGeneratedByUserLifespan;
protected Boolean enabled;
protected String sslRequired;
@Deprecated
@ -338,6 +340,22 @@ public class RealmRepresentation {
this.accessCodeLifespanLogin = accessCodeLifespanLogin;
}
public Integer getActionTokenGeneratedByAdminLifespan() {
return actionTokenGeneratedByAdminLifespan;
}
public void setActionTokenGeneratedByAdminLifespan(Integer actionTokenGeneratedByAdminLifespan) {
this.actionTokenGeneratedByAdminLifespan = actionTokenGeneratedByAdminLifespan;
}
public Integer getActionTokenGeneratedByUserLifespan() {
return actionTokenGeneratedByUserLifespan;
}
public void setActionTokenGeneratedByUserLifespan(Integer actionTokenGeneratedByUserLifespan) {
this.actionTokenGeneratedByUserLifespan = actionTokenGeneratedByUserLifespan;
}
public List<String> getDefaultRoles() {
return defaultRoles;
}

View file

@ -89,9 +89,11 @@
<eviction max-entries="10000" strategy="LRU"/>
</local-cache>
<local-cache name="sessions"/>
<local-cache name="authenticationSessions"/>
<local-cache name="offlineSessions"/>
<local-cache name="loginFailures"/>
<local-cache name="authorization"/>
<local-cache name="actionTokens"/>
<local-cache name="work"/>
<local-cache name="keys">
<eviction max-entries="1000" strategy="LRU"/>

View file

@ -199,4 +199,30 @@ if ((result.default-provider == undefined) && (result.provider.default.enabled =
echo
end-if
# Migrate from 3.0.0 to 3.2.0
if (outcome == failed) of /profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions/:read-resource
echo Adding distributed-cache=authenticationSessions to keycloak cache container...
/profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions/:add(mode=SYNC,owners=1)
echo
end-if
if (outcome == failed) of /profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/:read-resource
echo Adding local-cache=actionTokens to keycloak cache container...
/profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/:add(indexing=NONE,start=LAZY)
/profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=eviction/:write-attribute(name=strategy,value=NONE)
/profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=eviction/:write-attribute(name=max-entries,value=-1)
/profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=expiration/:write-attribute(name=interval,value=300000)
/profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=expiration/:write-attribute(name=max-idle,value=-1)
echo
end-if
if (outcome == success) of /profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/distributed-cache=authorization/:read-resource
echo Replacing distributed-cache=authorization with local-cache=authorization
/profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/distributed-cache=authorization/:remove
/profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/local-cache=authorization/:add
/profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/local-cache=authorization/component=eviction/:write-attribute(name=strategy,value=LRU)
/profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak/local-cache=authorization/component=eviction/:write-attribute(name=max-entries,value=10000)
echo
end-if
echo *** End Migration of /profile=$clusteredProfile ***

View file

@ -187,4 +187,22 @@ if ((result.default-provider == undefined) && (result.provider.default.enabled =
echo
end-if
# Migrate from 3.0.0 to 3.2.0
if (outcome == failed) of /profile=$standaloneProfile/subsystem=infinispan/cache-container=keycloak/local-cache=authenticationSessions/:read-resource
echo Adding local-cache=authenticationSessions to keycloak cache container...
/profile=$standaloneProfile/subsystem=infinispan/cache-container=keycloak/local-cache=authenticationSessions/:add(indexing=NONE,start=LAZY)
echo
end-if
if (outcome == failed) of /profile=$standaloneProfile/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/:read-resource
echo Adding local-cache=actionTokens to keycloak cache container...
/profile=$standaloneProfile/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/:add(indexing=NONE,start=LAZY)
/profile=$standaloneProfile/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=eviction/:write-attribute(name=strategy,value=NONE)
/profile=$standaloneProfile/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=eviction/:write-attribute(name=max-entries,value=-1)
/profile=$standaloneProfile/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=expiration/:write-attribute(name=interval,value=300000)
/profile=$standaloneProfile/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=expiration/:write-attribute(name=max-idle,value=-1)
echo
end-if
echo *** End Migration of /profile=$standaloneProfile ***

View file

@ -203,4 +203,31 @@ if ((result.default-provider == undefined) && (result.provider.default.enabled =
/subsystem=keycloak-server/spi=connectionsInfinispan/:write-attribute(name=default-provider,value=default)
echo
end-if
# Migrate from 3.0.0 to 3.2.0
if (outcome == failed) of /subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions/:read-resource
echo Adding distributed-cache=authenticationSessions to keycloak cache container...
/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions/:add(mode=SYNC,owners=1)
echo
end-if
if (outcome == failed) of /subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/:read-resource
echo Adding local-cache=actionTokens to keycloak cache container...
/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/:add(indexing=NONE,start=LAZY)
/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=eviction/:write-attribute(name=strategy,value=NONE)
/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=eviction/:write-attribute(name=max-entries,value=-1)
/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=expiration/:write-attribute(name=interval,value=300000)
/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=expiration/:write-attribute(name=max-idle,value=-1)
echo
end-if
if (outcome == success) of /subsystem=infinispan/cache-container=keycloak/distributed-cache=authorization/:read-resource
echo Replacing distributed-cache=authorization with local-cache=authorization
/subsystem=infinispan/cache-container=keycloak/distributed-cache=authorization/:remove
/subsystem=infinispan/cache-container=keycloak/local-cache=authorization/:add
/subsystem=infinispan/cache-container=keycloak/local-cache=authorization/component=eviction/:write-attribute(name=strategy,value=LRU)
/subsystem=infinispan/cache-container=keycloak/local-cache=authorization/component=eviction/:write-attribute(name=max-entries,value=10000)
echo
end-if
echo *** End Migration ***

View file

@ -195,4 +195,22 @@ if ((result.default-provider == undefined) && (result.provider.default.enabled =
echo
end-if
# Migrate from 3.0.0 to 3.2.0
if (outcome == failed) of /subsystem=infinispan/cache-container=keycloak/local-cache=authenticationSessions/:read-resource
echo Adding local-cache=authenticationSessions to keycloak cache container...
/subsystem=infinispan/cache-container=keycloak/local-cache=authenticationSessions/:add(indexing=NONE,start=LAZY)
echo
end-if
if (outcome == failed) of /subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/:read-resource
echo Adding local-cache=actionTokens to keycloak cache container...
/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/:add(indexing=NONE,start=LAZY)
/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=eviction/:write-attribute(name=strategy,value=NONE)
/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=eviction/:write-attribute(name=max-entries,value=-1)
/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=expiration/:write-attribute(name=interval,value=300000)
/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/component=expiration/:write-attribute(name=max-idle,value=-1)
echo
end-if
echo *** End Migration ***

View file

@ -33,6 +33,7 @@
<module name="org.infinispan.commons"/>
<module name="org.infinispan.cachestore.remote"/>
<module name="org.infinispan.client.hotrod"/>
<module name="org.jgroups"/>
<module name="org.jboss.logging"/>
<module name="javax.api"/>
</dependencies>

View file

@ -6,6 +6,7 @@ embed-server --server-config=standalone.xml
/subsystem=infinispan/cache-container=keycloak/local-cache=users:add()
/subsystem=infinispan/cache-container=keycloak/local-cache=users/eviction=EVICTION:add(max-entries=10000,strategy=LRU)
/subsystem=infinispan/cache-container=keycloak/local-cache=sessions:add()
/subsystem=infinispan/cache-container=keycloak/local-cache=authenticationSessions:add()
/subsystem=infinispan/cache-container=keycloak/local-cache=offlineSessions:add()
/subsystem=infinispan/cache-container=keycloak/local-cache=loginFailures:add()
/subsystem=infinispan/cache-container=keycloak/local-cache=work:add()
@ -14,4 +15,7 @@ embed-server --server-config=standalone.xml
/subsystem=infinispan/cache-container=keycloak/local-cache=keys:add()
/subsystem=infinispan/cache-container=keycloak/local-cache=keys/eviction=EVICTION:add(max-entries=1000,strategy=LRU)
/subsystem=infinispan/cache-container=keycloak/local-cache=keys/expiration=EXPIRATION:add(max-idle=3600000)
/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens:add()
/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/eviction=EVICTION:add(max-entries=-1,strategy=NONE)
/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/expiration=EXPIRATION:add(max-idle=-1,interval=300000)
/extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem)

View file

@ -7,6 +7,7 @@ embed-server --server-config=standalone-ha.xml
/subsystem=infinispan/cache-container=keycloak/local-cache=users:add()
/subsystem=infinispan/cache-container=keycloak/local-cache=users/eviction=EVICTION:add(max-entries=10000,strategy=LRU)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:add(mode="SYNC",owners="1")
/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions:add(mode="SYNC",owners="1")
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:add(mode="SYNC",owners="1")
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:add(mode="SYNC",owners="1")
/subsystem=infinispan/cache-container=keycloak/local-cache=authorization:add()
@ -15,4 +16,7 @@ embed-server --server-config=standalone-ha.xml
/subsystem=infinispan/cache-container=keycloak/local-cache=keys:add()
/subsystem=infinispan/cache-container=keycloak/local-cache=keys/eviction=EVICTION:add(max-entries=1000,strategy=LRU)
/subsystem=infinispan/cache-container=keycloak/local-cache=keys/expiration=EXPIRATION:add(max-idle=3600000)
/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens:add()
/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/eviction=EVICTION:add(max-entries=-1,strategy=NONE)
/subsystem=infinispan/cache-container=keycloak/local-cache=actionTokens/expiration=EXPIRATION:add(max-idle=-1,interval=300000)
/extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem)

View file

@ -1,13 +1,10 @@
{
"realm": "servlet-authz",
"auth-server-url" : "http://localhost:8080/auth",
"ssl-required" : "external",
"resource" : "servlet-authz-app",
"public-client" : false,
"auth-server-url": "http://localhost:8080/auth",
"ssl-required": "external",
"resource": "servlet-authz-app",
"credentials": {
"secret": "secret"
},
"policy-enforcer": {
"on-deny-redirect-to" : "/servlet-authz-app/accessDenied.jsp"
}
"policy-enforcer": {}
}

View file

@ -47,7 +47,7 @@ is in your `/etc/hosts` before other records for the 127.0.0.1 host to avoid iss
**5)** Configure Kerberos client (On linux it's in file `/etc/krb5.conf` ). You need to configure `KEYCLOAK.ORG` realm for host `localhost` and enable `forwardable` flag, which is needed
for credential delegation example, as application needs to forward Kerberos ticket and authenticate with it against LDAP server.
See [this file](https://github.com/keycloak/keycloak/blob/master/testsuite/integration/src/test/resources/kerberos/test-krb5.conf) for inspiration.
See [this file](https://github.com/keycloak/keycloak/blob/master/testsuite/integration-arquillian/tests/base/src/test/resources/kerberos/test-krb5.conf) for inspiration.
On OS X the file to edit (or create) is `/Library/Preferences/edu.mit.Kerberos` with the same syntax as `krb5.conf`.
On Windows the file to edit (or create) is `c:\Windows\krb5.ini` with the same syntax as `krb5.conf`.

View file

@ -27,6 +27,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
/**

View file

@ -132,6 +132,12 @@ kinit hnelson@KEYCLOAK.ORG
and provide password `secret`
Now when you access `http://localhost:8081/auth/realms/master/account` you should be logged in automatically as user `hnelson` .
Simple loadbalancer
-------------------
You can run class `SimpleUndertowLoadBalancer` from IDE. By default, it executes the embedded undertow loadbalancer running on `http://localhost:8180`, which communicates with 2 backend Keycloak nodes
running on `http://localhost:8181` and `http://localhost:8182` . See javadoc for more details.
Create many users or offline sessions

View file

@ -19,6 +19,8 @@ package org.keycloak.connections.infinispan;
import java.util.concurrent.TimeUnit;
import org.infinispan.commons.util.FileLookup;
import org.infinispan.commons.util.FileLookupFactory;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.configuration.cache.ConfigurationBuilder;
@ -27,12 +29,13 @@ import org.infinispan.eviction.EvictionStrategy;
import org.infinispan.eviction.EvictionType;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.persistence.remote.configuration.ExhaustedAction;
import org.infinispan.persistence.remote.configuration.RemoteStoreConfigurationBuilder;
import org.infinispan.remoting.transport.jgroups.JGroupsTransport;
import org.infinispan.transaction.LockingMode;
import org.infinispan.transaction.TransactionMode;
import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
import org.jboss.logging.Logger;
import org.jgroups.JChannel;
import org.keycloak.Config;
import org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory;
import org.keycloak.models.KeycloakSession;
@ -118,7 +121,10 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
cacheManager.defineConfiguration(InfinispanConnectionProvider.USER_REVISIONS_CACHE_NAME, getRevisionCacheConfig(userRevisionsMaxEntries));
cacheManager.getCache(InfinispanConnectionProvider.USER_REVISIONS_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.KEYS_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE, true);
long authzRevisionsMaxEntries = cacheManager.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME).getCacheConfiguration().eviction().maxEntries();
authzRevisionsMaxEntries = authzRevisionsMaxEntries > 0
@ -147,7 +153,8 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
boolean allowDuplicateJMXDomains = config.getBoolean("allowDuplicateJMXDomains", true);
if (clustered) {
gcb.transport().defaultTransport();
String nodeName = config.get("nodeName", System.getProperty(InfinispanConnectionProvider.JBOSS_NODE_NAME));
configureTransport(gcb, nodeName);
}
gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
@ -190,6 +197,13 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
cacheManager.defineConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCacheConfiguration);
cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, sessionCacheConfiguration);
cacheManager.defineConfiguration(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, sessionCacheConfiguration);
cacheManager.defineConfiguration(InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME, sessionCacheConfiguration);
// Retrieve caches to enforce rebalance
cacheManager.getCache(InfinispanConnectionProvider.SESSION_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, true);
cacheManager.getCache(InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME, true);
ConfigurationBuilder replicationConfigBuilder = new ConfigurationBuilder();
if (clustered) {
@ -229,6 +243,9 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
cacheManager.defineConfiguration(InfinispanConnectionProvider.KEYS_CACHE_NAME, getKeysCacheConfig());
cacheManager.getCache(InfinispanConnectionProvider.KEYS_CACHE_NAME, true);
cacheManager.defineConfiguration(InfinispanConnectionProvider.ACTION_TOKEN_CACHE, getActionTokenCacheConfig());
cacheManager.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE, true);
long authzRevisionsMaxEntries = cacheManager.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME).getCacheConfiguration().eviction().maxEntries();
authzRevisionsMaxEntries = authzRevisionsMaxEntries > 0
? 2 * authzRevisionsMaxEntries
@ -286,4 +303,40 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
return cb.build();
}
private Configuration getActionTokenCacheConfig() {
ConfigurationBuilder cb = new ConfigurationBuilder();
cb.eviction()
.strategy(EvictionStrategy.NONE)
.type(EvictionType.COUNT)
.size(InfinispanConnectionProvider.ACTION_TOKEN_CACHE_DEFAULT_MAX);
cb.expiration()
.maxIdle(InfinispanConnectionProvider.ACTION_TOKEN_MAX_IDLE_SECONDS, TimeUnit.SECONDS)
.wakeUpInterval(InfinispanConnectionProvider.ACTION_TOKEN_WAKE_UP_INTERVAL_SECONDS, TimeUnit.SECONDS);
return cb.build();
}
protected void configureTransport(GlobalConfigurationBuilder gcb, String nodeName) {
if (nodeName == null) {
gcb.transport().defaultTransport();
} else {
FileLookup fileLookup = FileLookupFactory.newInstance();
try {
// Compatibility with Wildfly
JChannel channel = new JChannel(fileLookup.lookupFileLocation("default-configs/default-jgroups-udp.xml", this.getClass().getClassLoader()));
channel.setName(nodeName);
JGroupsTransport transport = new JGroupsTransport(channel);
gcb.transport().nodeName(nodeName);
gcb.transport().transport(transport);
logger.infof("Configured jgroups transport with the channel name: %s", nodeName);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}

View file

@ -36,15 +36,24 @@ public interface InfinispanConnectionProvider extends Provider {
String SESSION_CACHE_NAME = "sessions";
String OFFLINE_SESSION_CACHE_NAME = "offlineSessions";
String LOGIN_FAILURE_CACHE_NAME = "loginFailures";
String AUTHENTICATION_SESSIONS_CACHE_NAME = "authenticationSessions";
String WORK_CACHE_NAME = "work";
String AUTHORIZATION_CACHE_NAME = "authorization";
String AUTHORIZATION_REVISIONS_CACHE_NAME = "authorizationRevisions";
int AUTHORIZATION_REVISIONS_CACHE_DEFAULT_MAX = 20000;
String ACTION_TOKEN_CACHE = "actionTokens";
int ACTION_TOKEN_CACHE_DEFAULT_MAX = -1;
int ACTION_TOKEN_MAX_IDLE_SECONDS = -1;
long ACTION_TOKEN_WAKE_UP_INTERVAL_SECONDS = 5 * 60 * 1000l;
String KEYS_CACHE_NAME = "keys";
int KEYS_CACHE_DEFAULT_MAX = 1000;
int KEYS_CACHE_MAX_IDLE_SECONDS = 3600;
// System property used on Wildfly to identify distributedCache address and sticky session route
String JBOSS_NODE_NAME = "jboss.node.name";
<K, V> Cache<K, V> getCache(String name);

View file

@ -0,0 +1,50 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models.cache.infinispan;
import org.keycloak.cluster.ClusterEvent;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
/**
* Event requesting adding of an invalidated action token.
*/
public class AddInvalidatedActionTokenEvent implements ClusterEvent {
private final ActionTokenReducedKey key;
private final int expirationInSecs;
private final ActionTokenValueEntity tokenValue;
public AddInvalidatedActionTokenEvent(ActionTokenReducedKey key, int expirationInSecs, ActionTokenValueEntity tokenValue) {
this.key = key;
this.expirationInSecs = expirationInSecs;
this.tokenValue = tokenValue;
}
public ActionTokenReducedKey getKey() {
return key;
}
public int getExpirationInSecs() {
return expirationInSecs;
}
public ActionTokenValueEntity getTokenValue() {
return tokenValue;
}
}

View file

@ -474,6 +474,30 @@ public class RealmAdapter implements CachedRealmModel {
updated.setAccessCodeLifespanLogin(seconds);
}
@Override
public int getActionTokenGeneratedByAdminLifespan() {
if (isUpdated()) return updated.getActionTokenGeneratedByAdminLifespan();
return cached.getActionTokenGeneratedByAdminLifespan();
}
@Override
public void setActionTokenGeneratedByAdminLifespan(int seconds) {
getDelegateForUpdate();
updated.setActionTokenGeneratedByAdminLifespan(seconds);
}
@Override
public int getActionTokenGeneratedByUserLifespan() {
if (isUpdated()) return updated.getActionTokenGeneratedByUserLifespan();
return cached.getActionTokenGeneratedByUserLifespan();
}
@Override
public void setActionTokenGeneratedByUserLifespan(int seconds) {
getDelegateForUpdate();
updated.setActionTokenGeneratedByUserLifespan(seconds);
}
@Override
public List<RequiredCredentialModel> getRequiredCredentials() {
if (isUpdated()) return updated.getRequiredCredentials();

View file

@ -0,0 +1,42 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models.cache.infinispan;
import org.keycloak.cluster.ClusterEvent;
/**
* Event requesting removal of the action tokens with the given user and action regardless of nonce.
*/
public class RemoveActionTokensSpecificEvent implements ClusterEvent {
private final String userId;
private final String actionId;
public RemoveActionTokensSpecificEvent(String userId, String actionId) {
this.userId = userId;
this.actionId = actionId;
}
public String getUserId() {
return userId;
}
public String getActionId() {
return actionId;
}
}

View file

@ -83,6 +83,8 @@ public class CachedRealm extends AbstractExtendableRevisioned {
protected int accessCodeLifespan;
protected int accessCodeLifespanUserAction;
protected int accessCodeLifespanLogin;
protected int actionTokenGeneratedByAdminLifespan;
protected int actionTokenGeneratedByUserLifespan;
protected int notBefore;
protected PasswordPolicy passwordPolicy;
protected OTPPolicy otpPolicy;
@ -175,6 +177,8 @@ public class CachedRealm extends AbstractExtendableRevisioned {
accessCodeLifespan = model.getAccessCodeLifespan();
accessCodeLifespanUserAction = model.getAccessCodeLifespanUserAction();
accessCodeLifespanLogin = model.getAccessCodeLifespanLogin();
actionTokenGeneratedByAdminLifespan = model.getActionTokenGeneratedByAdminLifespan();
actionTokenGeneratedByUserLifespan = model.getActionTokenGeneratedByUserLifespan();
notBefore = model.getNotBefore();
passwordPolicy = model.getPasswordPolicy();
otpPolicy = model.getOTPPolicy();
@ -399,6 +403,14 @@ public class CachedRealm extends AbstractExtendableRevisioned {
return accessCodeLifespanLogin;
}
public int getActionTokenGeneratedByAdminLifespan() {
return actionTokenGeneratedByAdminLifespan;
}
public int getActionTokenGeneratedByUserLifespan() {
return actionTokenGeneratedByUserLifespan;
}
public List<RequiredCredentialModel> getRequiredCredentials() {
return requiredCredentials;
}

View file

@ -0,0 +1,53 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models.cache.infinispan.events;
import org.keycloak.cluster.ClusterEvent;
import java.util.LinkedHashMap;
import java.util.Map;
/**
*
* @author hmlnarik
*/
public class AuthenticationSessionAuthNoteUpdateEvent implements ClusterEvent {
private String authSessionId;
private Map<String, String> authNotesFragment;
public static AuthenticationSessionAuthNoteUpdateEvent create(String authSessionId, Map<String, String> authNotesFragment) {
AuthenticationSessionAuthNoteUpdateEvent event = new AuthenticationSessionAuthNoteUpdateEvent();
event.authSessionId = authSessionId;
event.authNotesFragment = new LinkedHashMap<>(authNotesFragment);
return event;
}
public String getAuthSessionId() {
return authSessionId;
}
public Map<String, String> getAuthNotesFragment() {
return authNotesFragment;
}
@Override
public String toString() {
return String.format("AuthenticationSessionAuthNoteUpdateEvent [ authSessionId=%s, authNotesFragment=%s ]", authSessionId, authNotesFragment);
}
}

View file

@ -0,0 +1,197 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models.sessions.infinispan;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.infinispan.Cache;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSessionModel {
private final AuthenticatedClientSessionEntity entity;
private final ClientModel client;
private final InfinispanUserSessionProvider provider;
private final Cache<String, SessionEntity> cache;
private UserSessionAdapter userSession;
public AuthenticatedClientSessionAdapter(AuthenticatedClientSessionEntity entity, ClientModel client, UserSessionAdapter userSession, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache) {
this.provider = provider;
this.entity = entity;
this.client = client;
this.cache = cache;
this.userSession = userSession;
}
private void update() {
provider.getTx().replace(cache, userSession.getEntity().getId(), userSession.getEntity());
}
@Override
public void setUserSession(UserSessionModel userSession) {
String clientUUID = client.getId();
UserSessionEntity sessionEntity = this.userSession.getEntity();
// Dettach userSession
if (userSession == null) {
if (sessionEntity.getAuthenticatedClientSessions() != null) {
sessionEntity.getAuthenticatedClientSessions().remove(clientUUID);
update();
this.userSession = null;
}
} else {
this.userSession = (UserSessionAdapter) userSession;
if (sessionEntity.getAuthenticatedClientSessions() == null) {
sessionEntity.setAuthenticatedClientSessions(new HashMap<>());
}
sessionEntity.getAuthenticatedClientSessions().put(clientUUID, entity);
update();
}
}
@Override
public UserSessionModel getUserSession() {
return this.userSession;
}
@Override
public String getRedirectUri() {
return entity.getRedirectUri();
}
@Override
public void setRedirectUri(String uri) {
entity.setRedirectUri(uri);
update();
}
@Override
public String getId() {
return null;
}
@Override
public RealmModel getRealm() {
return userSession.getRealm();
}
@Override
public ClientModel getClient() {
return client;
}
@Override
public int getTimestamp() {
return entity.getTimestamp();
}
@Override
public void setTimestamp(int timestamp) {
entity.setTimestamp(timestamp);
update();
}
@Override
public String getAction() {
return entity.getAction();
}
@Override
public void setAction(String action) {
entity.setAction(action);
update();
}
@Override
public String getProtocol() {
return entity.getAuthMethod();
}
@Override
public void setProtocol(String method) {
entity.setAuthMethod(method);
update();
}
@Override
public Set<String> getRoles() {
return entity.getRoles();
}
@Override
public void setRoles(Set<String> roles) {
entity.setRoles(roles);
update();
}
@Override
public Set<String> getProtocolMappers() {
return entity.getProtocolMappers();
}
@Override
public void setProtocolMappers(Set<String> protocolMappers) {
entity.setProtocolMappers(protocolMappers);
update();
}
@Override
public String getNote(String name) {
return entity.getNotes()==null ? null : entity.getNotes().get(name);
}
@Override
public void setNote(String name, String value) {
if (entity.getNotes() == null) {
entity.setNotes(new HashMap<>());
}
entity.getNotes().put(name, value);
update();
}
@Override
public void removeNote(String name) {
if (entity.getNotes() != null) {
entity.getNotes().remove(name);
update();
}
}
@Override
public Map<String, String> getNotes() {
if (entity.getNotes() == null || entity.getNotes().isEmpty()) return Collections.emptyMap();
Map<String, String> copy = new HashMap<>();
copy.putAll(entity.getNotes());
return copy;
}
}

View file

@ -17,44 +17,48 @@
package org.keycloak.models.sessions.infinispan;
import org.infinispan.Cache;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import java.util.Collections;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.infinispan.Cache;
import org.keycloak.common.util.Time;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.sessions.infinispan.entities.AuthenticationSessionEntity;
import org.keycloak.sessions.AuthenticationSessionModel;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
* NOTE: Calling setter doesn't automatically enlist for update
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ClientSessionAdapter implements ClientSessionModel {
public class AuthenticationSessionAdapter implements AuthenticationSessionModel {
private KeycloakSession session;
private InfinispanUserSessionProvider provider;
private Cache<String, SessionEntity> cache;
private InfinispanAuthenticationSessionProvider provider;
private Cache<String, AuthenticationSessionEntity> cache;
private RealmModel realm;
private ClientSessionEntity entity;
private boolean offline;
private AuthenticationSessionEntity entity;
public ClientSessionAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache, RealmModel realm,
ClientSessionEntity entity, boolean offline) {
public AuthenticationSessionAdapter(KeycloakSession session, InfinispanAuthenticationSessionProvider provider, Cache<String, AuthenticationSessionEntity> cache, RealmModel realm,
AuthenticationSessionEntity entity) {
this.session = session;
this.provider = provider;
this.cache = cache;
this.realm = realm;
this.entity = entity;
this.offline = offline;
}
void update() {
provider.tx.replace(cache, entity.getId(), entity);
}
@Override
public String getId() {
return entity.getId();
@ -67,36 +71,7 @@ public class ClientSessionAdapter implements ClientSessionModel {
@Override
public ClientModel getClient() {
return realm.getClientById(entity.getClient());
}
@Override
public UserSessionAdapter getUserSession() {
return entity.getUserSession() != null ? provider.getUserSession(realm, entity.getUserSession(), offline) : null;
}
@Override
public void setUserSession(UserSessionModel userSession) {
if (userSession == null) {
if (entity.getUserSession() != null) {
provider.dettachSession(getUserSession(), this);
}
entity.setUserSession(null);
} else {
UserSessionAdapter userSessionAdapter = (UserSessionAdapter) userSession;
if (entity.getUserSession() != null) {
if (entity.getUserSession().equals(userSession.getId())) {
return;
} else {
provider.dettachSession(userSessionAdapter, this);
}
} else {
provider.attachSession(userSessionAdapter, this);
}
entity.setUserSession(userSession.getId());
}
update();
return realm.getClientById(entity.getClientUuid());
}
@Override
@ -157,52 +132,104 @@ public class ClientSessionAdapter implements ClientSessionModel {
}
@Override
public String getAuthMethod() {
return entity.getAuthMethod();
public String getProtocol() {
return entity.getProtocol();
}
@Override
public void setAuthMethod(String authMethod) {
entity.setAuthMethod(authMethod);
public void setProtocol(String protocol) {
entity.setProtocol(protocol);
update();
}
@Override
public String getNote(String name) {
return entity.getNotes() != null ? entity.getNotes().get(name) : null;
public String getClientNote(String name) {
return (entity.getClientNotes() != null && name != null) ? entity.getClientNotes().get(name) : null;
}
@Override
public void setNote(String name, String value) {
if (entity.getNotes() == null) {
entity.setNotes(new HashMap<String, String>());
public void setClientNote(String name, String value) {
if (entity.getClientNotes() == null) {
entity.setClientNotes(new ConcurrentHashMap<>());
}
if (name != null) {
if (value == null) {
entity.getClientNotes().remove(name);
} else {
entity.getClientNotes().put(name, value);
}
}
entity.getNotes().put(name, value);
update();
}
@Override
public void removeNote(String name) {
if (entity.getNotes() != null) {
entity.getNotes().remove(name);
update();
public void removeClientNote(String name) {
if (entity.getClientNotes() != null && name != null) {
entity.getClientNotes().remove(name);
}
update();
}
@Override
public Map<String, String> getNotes() {
if (entity.getNotes() == null || entity.getNotes().isEmpty()) return Collections.emptyMap();
Map<String, String> copy = new HashMap<>();
copy.putAll(entity.getNotes());
public Map<String, String> getClientNotes() {
if (entity.getClientNotes() == null || entity.getClientNotes().isEmpty()) return Collections.emptyMap();
Map<String, String> copy = new ConcurrentHashMap<>();
copy.putAll(entity.getClientNotes());
return copy;
}
@Override
public void clearClientNotes() {
entity.setClientNotes(new ConcurrentHashMap<>());
update();
}
@Override
public String getAuthNote(String name) {
return (entity.getAuthNotes() != null && name != null) ? entity.getAuthNotes().get(name) : null;
}
@Override
public void setAuthNote(String name, String value) {
if (entity.getAuthNotes() == null) {
entity.setAuthNotes(new ConcurrentHashMap<>());
}
if (name != null) {
if (value == null) {
entity.getAuthNotes().remove(name);
} else {
entity.getAuthNotes().put(name, value);
}
}
update();
}
@Override
public void removeAuthNote(String name) {
if (entity.getAuthNotes() != null && name != null) {
entity.getAuthNotes().remove(name);
}
update();
}
@Override
public void clearAuthNotes() {
entity.setAuthNotes(new ConcurrentHashMap<>());
update();
}
@Override
public void setUserSessionNote(String name, String value) {
if (entity.getUserSessionNotes() == null) {
entity.setUserSessionNotes(new HashMap<String, String>());
entity.setUserSessionNotes(new ConcurrentHashMap<>());
}
if (name != null) {
if (value == null) {
entity.getUserSessionNotes().remove(name);
} else {
entity.getUserSessionNotes().put(name, value);
}
}
entity.getUserSessionNotes().put(name, value);
update();
}
@ -212,14 +239,14 @@ public class ClientSessionAdapter implements ClientSessionModel {
if (entity.getUserSessionNotes() == null) {
return Collections.EMPTY_MAP;
}
HashMap<String, String> copy = new HashMap<>();
ConcurrentHashMap<String, String> copy = new ConcurrentHashMap<>();
copy.putAll(entity.getUserSessionNotes());
return copy;
}
@Override
public void clearUserSessionNotes() {
entity.setUserSessionNotes(new HashMap<String, String>());
entity.setUserSessionNotes(new ConcurrentHashMap<>());
update();
}
@ -248,7 +275,6 @@ public class ClientSessionAdapter implements ClientSessionModel {
@Override
public void addRequiredAction(UserModel.RequiredAction action) {
addRequiredAction(action.name());
}
@Override
@ -256,24 +282,22 @@ public class ClientSessionAdapter implements ClientSessionModel {
removeRequiredAction(action.name());
}
void update() {
provider.getTx().replace(cache, entity.getId(), entity);
}
@Override
public Map<String, ExecutionStatus> getExecutionStatus() {
return entity.getAuthenticatorStatus();
public Map<String, AuthenticationSessionModel.ExecutionStatus> getExecutionStatus() {
return entity.getExecutionStatus();
}
@Override
public void setExecutionStatus(String authenticator, ExecutionStatus status) {
entity.getAuthenticatorStatus().put(authenticator, status);
public void setExecutionStatus(String authenticator, AuthenticationSessionModel.ExecutionStatus status) {
entity.getExecutionStatus().put(authenticator, status);
update();
}
@Override
public void clearExecutionStatus() {
entity.getAuthenticatorStatus().clear();
entity.getExecutionStatus().clear();
update();
}
@ -286,7 +310,22 @@ public class ClientSessionAdapter implements ClientSessionModel {
if (user == null) entity.setAuthUserId(null);
else entity.setAuthUserId(user.getId());
update();
}
@Override
public void updateClient(ClientModel client) {
entity.setClientUuid(client.getId());
update();
}
@Override
public void restartSession(RealmModel realm, ClientModel client) {
String id = entity.getId();
entity = new AuthenticationSessionEntity();
entity.setId(id);
entity.setRealm(realm.getId());
entity.setClientUuid(client.getId());
entity.setTimestamp(Time.currentTime());
update();
}
}

View file

@ -19,11 +19,9 @@ package org.keycloak.models.sessions.infinispan;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@ -41,21 +39,6 @@ public class Consumers {
return new UserSessionModelsConsumer(provider, realm, offline);
}
public static class UserSessionIdAndTimestampConsumer implements Consumer<Map.Entry<String, SessionEntity>> {
private Map<String, Integer> sessions = new HashMap<>();
@Override
public void accept(Map.Entry<String, SessionEntity> entry) {
SessionEntity e = entry.getValue();
if (e instanceof ClientSessionEntity) {
ClientSessionEntity ce = (ClientSessionEntity) e;
sessions.put(ce.getUserSession(), ce.getTimestamp());
}
}
}
public static class UserSessionModelsConsumer implements Consumer<Map.Entry<String, SessionEntity>> {
private InfinispanUserSessionProvider provider;

View file

@ -0,0 +1,98 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models.sessions.infinispan;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.models.*;
import org.keycloak.models.cache.infinispan.AddInvalidatedActionTokenEvent;
import org.keycloak.models.cache.infinispan.RemoveActionTokensSpecificEvent;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
import java.util.*;
import org.infinispan.Cache;
/**
*
* @author hmlnarik
*/
public class InfinispanActionTokenStoreProvider implements ActionTokenStoreProvider {
private final Cache<ActionTokenReducedKey, ActionTokenValueEntity> actionKeyCache;
private final InfinispanKeycloakTransaction tx;
private final KeycloakSession session;
public InfinispanActionTokenStoreProvider(KeycloakSession session, Cache<ActionTokenReducedKey, ActionTokenValueEntity> actionKeyCache) {
this.session = session;
this.actionKeyCache = actionKeyCache;
this.tx = new InfinispanKeycloakTransaction();
session.getTransactionManager().enlistAfterCompletion(tx);
}
@Override
public void close() {
}
@Override
public void put(ActionTokenKeyModel key, Map<String, String> notes) {
if (key == null || key.getUserId() == null || key.getActionId() == null) {
return;
}
ActionTokenReducedKey tokenKey = new ActionTokenReducedKey(key.getUserId(), key.getActionId(), key.getActionVerificationNonce());
ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(notes);
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
this.tx.notify(cluster, InfinispanActionTokenStoreProviderFactory.ACTION_TOKEN_EVENTS, new AddInvalidatedActionTokenEvent(tokenKey, key.getExpiration(), tokenValue), false);
}
@Override
public ActionTokenValueModel get(ActionTokenKeyModel actionTokenKey) {
if (actionTokenKey == null || actionTokenKey.getUserId() == null || actionTokenKey.getActionId() == null) {
return null;
}
ActionTokenReducedKey key = new ActionTokenReducedKey(actionTokenKey.getUserId(), actionTokenKey.getActionId(), actionTokenKey.getActionVerificationNonce());
return this.actionKeyCache.getAdvancedCache().get(key);
}
@Override
public ActionTokenValueModel remove(ActionTokenKeyModel actionTokenKey) {
if (actionTokenKey == null || actionTokenKey.getUserId() == null || actionTokenKey.getActionId() == null) {
return null;
}
ActionTokenReducedKey key = new ActionTokenReducedKey(actionTokenKey.getUserId(), actionTokenKey.getActionId(), actionTokenKey.getActionVerificationNonce());
ActionTokenValueEntity value = this.actionKeyCache.get(key);
if (value != null) {
this.tx.remove(actionKeyCache, key);
}
return value;
}
public void removeAll(String userId, String actionId) {
if (userId == null || actionId == null) {
return;
}
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
this.tx.notify(cluster, InfinispanActionTokenStoreProviderFactory.ACTION_TOKEN_EVENTS, new RemoveActionTokensSpecificEvent(userId, actionId), false);
}
}

View file

@ -0,0 +1,100 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models.sessions.infinispan;
import org.keycloak.Config;
import org.keycloak.Config.Scope;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.common.util.Time;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.*;
import org.keycloak.models.cache.infinispan.AddInvalidatedActionTokenEvent;
import org.keycloak.models.cache.infinispan.RemoveActionTokensSpecificEvent;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import org.infinispan.Cache;
import org.infinispan.context.Flag;
/**
*
* @author hmlnarik
*/
public class InfinispanActionTokenStoreProviderFactory implements ActionTokenStoreProviderFactory {
public static final String ACTION_TOKEN_EVENTS = "ACTION_TOKEN_EVENTS";
/**
* If expiration is set to this value, no expiration is set on the corresponding cache entry (hence cache default is honored)
*/
private static final int DEFAULT_CACHE_EXPIRATION = 0;
private Config.Scope config;
@Override
public ActionTokenStoreProvider create(KeycloakSession session) {
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
Cache<ActionTokenReducedKey, ActionTokenValueEntity> actionTokenCache = connections.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE);
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
cluster.registerListener(ACTION_TOKEN_EVENTS, event -> {
if (event instanceof RemoveActionTokensSpecificEvent) {
RemoveActionTokensSpecificEvent e = (RemoveActionTokensSpecificEvent) event;
actionTokenCache
.getAdvancedCache()
.withFlags(Flag.CACHE_MODE_LOCAL, Flag.SKIP_CACHE_LOAD)
.keySet()
.stream()
.filter(k -> Objects.equals(k.getUserId(), e.getUserId()) && Objects.equals(k.getActionId(), e.getActionId()))
.forEach(actionTokenCache::remove);
} else if (event instanceof AddInvalidatedActionTokenEvent) {
AddInvalidatedActionTokenEvent e = (AddInvalidatedActionTokenEvent) event;
if (e.getExpirationInSecs() == DEFAULT_CACHE_EXPIRATION) {
actionTokenCache.put(e.getKey(), e.getTokenValue());
} else {
actionTokenCache.put(e.getKey(), e.getTokenValue(), e.getExpirationInSecs() - Time.currentTime(), TimeUnit.SECONDS);
}
}
});
return new InfinispanActionTokenStoreProvider(session, actionTokenCache);
}
@Override
public void init(Scope config) {
this.config = config;
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "infinispan";
}
}

View file

@ -0,0 +1,162 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models.sessions.infinispan;
import org.keycloak.cluster.ClusterProvider;
import java.util.Iterator;
import java.util.Map;
import org.infinispan.Cache;
import org.infinispan.context.Flag;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Time;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.cache.infinispan.events.AuthenticationSessionAuthNoteUpdateEvent;
import org.keycloak.models.sessions.infinispan.entities.AuthenticationSessionEntity;
import org.keycloak.models.sessions.infinispan.stream.AuthenticationSessionPredicate;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RealmInfoUtil;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.sessions.AuthenticationSessionProvider;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class InfinispanAuthenticationSessionProvider implements AuthenticationSessionProvider {
private static final Logger log = Logger.getLogger(InfinispanAuthenticationSessionProvider.class);
private final KeycloakSession session;
private final Cache<String, AuthenticationSessionEntity> cache;
protected final InfinispanKeycloakTransaction tx;
public InfinispanAuthenticationSessionProvider(KeycloakSession session, Cache<String, AuthenticationSessionEntity> cache) {
this.session = session;
this.cache = cache;
this.tx = new InfinispanKeycloakTransaction();
session.getTransactionManager().enlistAfterCompletion(tx);
}
@Override
public AuthenticationSessionModel createAuthenticationSession(RealmModel realm, ClientModel client) {
String id = KeycloakModelUtils.generateId();
return createAuthenticationSession(id, realm, client);
}
@Override
public AuthenticationSessionModel createAuthenticationSession(String id, RealmModel realm, ClientModel client) {
AuthenticationSessionEntity entity = new AuthenticationSessionEntity();
entity.setId(id);
entity.setRealm(realm.getId());
entity.setTimestamp(Time.currentTime());
entity.setClientUuid(client.getId());
tx.put(cache, id, entity);
AuthenticationSessionAdapter wrap = wrap(realm, entity);
return wrap;
}
private AuthenticationSessionAdapter wrap(RealmModel realm, AuthenticationSessionEntity entity) {
return entity==null ? null : new AuthenticationSessionAdapter(session, this, cache, realm, entity);
}
@Override
public AuthenticationSessionModel getAuthenticationSession(RealmModel realm, String authenticationSessionId) {
AuthenticationSessionEntity entity = getAuthenticationSessionEntity(realm, authenticationSessionId);
return wrap(realm, entity);
}
private AuthenticationSessionEntity getAuthenticationSessionEntity(RealmModel realm, String authSessionId) {
// Chance created in this transaction
AuthenticationSessionEntity entity = tx.get(cache, authSessionId);
if (entity == null) {
entity = cache.get(authSessionId);
}
return entity;
}
@Override
public void removeAuthenticationSession(RealmModel realm, AuthenticationSessionModel authenticationSession) {
tx.remove(cache, authenticationSession.getId());
}
@Override
public void removeExpired(RealmModel realm) {
log.debugf("Removing expired sessions");
int expired = Time.currentTime() - RealmInfoUtil.getDettachedClientSessionLifespan(realm);
// Each cluster node cleanups just local sessions, which are those owned by himself (+ few more taking l1 cache into account)
Iterator<Map.Entry<String, AuthenticationSessionEntity>> itr = cache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL)
.entrySet().stream().filter(AuthenticationSessionPredicate.create(realm.getId()).expired(expired)).iterator();
int counter = 0;
while (itr.hasNext()) {
counter++;
AuthenticationSessionEntity entity = itr.next().getValue();
tx.remove(cache, entity.getId());
}
log.debugf("Removed %d expired user sessions for realm '%s'", counter, realm.getName());
}
// TODO: Should likely listen to "RealmRemovedEvent" received from cluster and clean just local sessions
@Override
public void onRealmRemoved(RealmModel realm) {
Iterator<Map.Entry<String, AuthenticationSessionEntity>> itr = cache.entrySet().stream().filter(AuthenticationSessionPredicate.create(realm.getId())).iterator();
while (itr.hasNext()) {
cache.remove(itr.next().getKey());
}
}
// TODO: Should likely listen to "ClientRemovedEvent" received from cluster and clean just local sessions
@Override
public void onClientRemoved(RealmModel realm, ClientModel client) {
Iterator<Map.Entry<String, AuthenticationSessionEntity>> itr = cache.entrySet().stream().filter(AuthenticationSessionPredicate.create(realm.getId()).client(client.getId())).iterator();
while (itr.hasNext()) {
cache.remove(itr.next().getKey());
}
}
@Override
public void updateNonlocalSessionAuthNotes(String authSessionId, Map<String, String> authNotesFragment) {
if (authSessionId == null) {
return;
}
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
cluster.notify(
InfinispanAuthenticationSessionProviderFactory.AUTHENTICATION_SESSION_EVENTS,
AuthenticationSessionAuthNoteUpdateEvent.create(authSessionId, authNotesFragment),
true
);
}
@Override
public void close() {
}
}

View file

@ -0,0 +1,113 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models.sessions.infinispan;
import org.infinispan.Cache;
import org.keycloak.Config;
import org.keycloak.cluster.ClusterEvent;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.cache.infinispan.events.AuthenticationSessionAuthNoteUpdateEvent;
import org.keycloak.models.sessions.infinispan.entities.AuthenticationSessionEntity;
import org.keycloak.sessions.AuthenticationSessionProvider;
import org.keycloak.sessions.AuthenticationSessionProviderFactory;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.jboss.logging.Logger;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class InfinispanAuthenticationSessionProviderFactory implements AuthenticationSessionProviderFactory {
private static final Logger log = Logger.getLogger(InfinispanAuthenticationSessionProviderFactory.class);
private volatile Cache<String, AuthenticationSessionEntity> authSessionsCache;
public static final String AUTHENTICATION_SESSION_EVENTS = "AUTHENTICATION_SESSION_EVENTS";
@Override
public void init(Config.Scope config) {
}
@Override
public AuthenticationSessionProvider create(KeycloakSession session) {
lazyInit(session);
return new InfinispanAuthenticationSessionProvider(session, authSessionsCache);
}
private void updateAuthNotes(ClusterEvent clEvent) {
if (! (clEvent instanceof AuthenticationSessionAuthNoteUpdateEvent)) {
return;
}
AuthenticationSessionAuthNoteUpdateEvent event = (AuthenticationSessionAuthNoteUpdateEvent) clEvent;
AuthenticationSessionEntity authSession = this.authSessionsCache.get(event.getAuthSessionId());
updateAuthSession(authSession, event.getAuthNotesFragment());
}
private static void updateAuthSession(AuthenticationSessionEntity authSession, Map<String, String> authNotesFragment) {
if (authSession != null) {
if (authSession.getAuthNotes() == null) {
authSession.setAuthNotes(new ConcurrentHashMap<>());
}
for (Entry<String, String> me : authNotesFragment.entrySet()) {
String value = me.getValue();
if (value == null) {
authSession.getAuthNotes().remove(me.getKey());
} else {
authSession.getAuthNotes().put(me.getKey(), value);
}
}
}
}
private void lazyInit(KeycloakSession session) {
if (authSessionsCache == null) {
synchronized (this) {
if (authSessionsCache == null) {
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
authSessionsCache = connections.getCache(InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME);
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
cluster.registerListener(AUTHENTICATION_SESSION_EVENTS, this::updateAuthNotes);
log.debug("Registered cluster listeners");
}
}
}
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "infinispan";
}
}

View file

@ -0,0 +1,218 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models.sessions.infinispan;
import org.keycloak.cluster.ClusterEvent;
import org.keycloak.cluster.ClusterProvider;
import org.infinispan.context.Flag;
import org.keycloak.models.KeycloakTransaction;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.infinispan.Cache;
import org.jboss.logging.Logger;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class InfinispanKeycloakTransaction implements KeycloakTransaction {
private final static Logger log = Logger.getLogger(InfinispanKeycloakTransaction.class);
public enum CacheOperation {
ADD, ADD_WITH_LIFESPAN, REMOVE, REPLACE, ADD_IF_ABSENT // ADD_IF_ABSENT throws an exception if there is existing value
}
private boolean active;
private boolean rollback;
private final Map<Object, CacheTask> tasks = new LinkedHashMap<>();
@Override
public void begin() {
active = true;
}
@Override
public void commit() {
if (rollback) {
throw new RuntimeException("Rollback only!");
}
tasks.values().forEach(CacheTask::execute);
}
@Override
public void rollback() {
tasks.clear();
}
@Override
public void setRollbackOnly() {
rollback = true;
}
@Override
public boolean getRollbackOnly() {
return rollback;
}
@Override
public boolean isActive() {
return active;
}
public <K, V> void put(Cache<K, V> cache, K key, V value) {
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.ADD, key);
Object taskKey = getTaskKey(cache, key);
if (tasks.containsKey(taskKey)) {
throw new IllegalStateException("Can't add session: task in progress for session");
} else {
tasks.put(taskKey, new CacheTaskWithValue<V>(value) {
@Override
public void execute() {
decorateCache(cache).put(key, value);
}
});
}
}
public <K, V> void put(Cache<K, V> cache, K key, V value, long lifespan, TimeUnit lifespanUnit) {
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.ADD_WITH_LIFESPAN, key);
Object taskKey = getTaskKey(cache, key);
if (tasks.containsKey(taskKey)) {
throw new IllegalStateException("Can't add session: task in progress for session");
} else {
tasks.put(taskKey, new CacheTaskWithValue<V>(value) {
@Override
public void execute() {
decorateCache(cache).put(key, value, lifespan, lifespanUnit);
}
});
}
}
public <K, V> void putIfAbsent(Cache<K, V> cache, K key, V value) {
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.ADD_IF_ABSENT, key);
Object taskKey = getTaskKey(cache, key);
if (tasks.containsKey(taskKey)) {
throw new IllegalStateException("Can't add session: task in progress for session");
} else {
tasks.put(taskKey, new CacheTaskWithValue<V>(value) {
@Override
public void execute() {
V existing = cache.putIfAbsent(key, value);
if (existing != null) {
throw new IllegalStateException("There is already existing value in cache for key " + key);
}
}
});
}
}
public <K, V> void replace(Cache<K, V> cache, K key, V value) {
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.REPLACE, key);
Object taskKey = getTaskKey(cache, key);
CacheTask current = tasks.get(taskKey);
if (current != null) {
if (current instanceof CacheTaskWithValue) {
((CacheTaskWithValue<V>) current).setValue(value);
}
} else {
tasks.put(taskKey, new CacheTaskWithValue<V>(value) {
@Override
public void execute() {
decorateCache(cache).replace(key, value);
}
});
}
}
public <K, V> void notify(ClusterProvider clusterProvider, String taskKey, ClusterEvent event, boolean ignoreSender) {
log.tracev("Adding cache operation SEND_EVENT: {0}", event);
String theTaskKey = taskKey;
int i = 1;
while (tasks.containsKey(theTaskKey)) {
theTaskKey = taskKey + "-" + (i++);
}
tasks.put(taskKey, () -> clusterProvider.notify(taskKey, event, ignoreSender));
}
public <K, V> void remove(Cache<K, V> cache, K key) {
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.REMOVE, key);
Object taskKey = getTaskKey(cache, key);
tasks.put(taskKey, () -> decorateCache(cache).remove(key));
}
// This is for possibility to lookup for session by id, which was created in this transaction
public <K, V> V get(Cache<K, V> cache, K key) {
Object taskKey = getTaskKey(cache, key);
CacheTask<V> current = tasks.get(taskKey);
if (current != null) {
if (current instanceof CacheTaskWithValue) {
return ((CacheTaskWithValue<V>) current).getValue();
}
return null;
}
// Should we have per-transaction cache for lookups?
return cache.get(key);
}
private static <K, V> Object getTaskKey(Cache<K, V> cache, K key) {
if (key instanceof String) {
return new StringBuilder(cache.getName())
.append("::")
.append(key).toString();
} else {
return key;
}
}
public interface CacheTask<V> {
void execute();
}
public abstract class CacheTaskWithValue<V> implements CacheTask<V> {
protected V value;
public CacheTaskWithValue(V value) {
this.value = value;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
}
// Ignore return values. Should have better performance within cluster / cross-dc env
private static <K, V> Cache<K, V> decorateCache(Cache<K, V> cache) {
return cache.getAdvancedCache()
.withFlags(Flag.IGNORE_RETURN_VALUES, Flag.SKIP_REMOTE_LOOKUP);
}
}

View file

@ -0,0 +1,78 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models.sessions.infinispan;
import org.infinispan.Cache;
import org.infinispan.distribution.DistributionManager;
import org.infinispan.remoting.transport.Address;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.sessions.StickySessionEncoderProvider;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class InfinispanStickySessionEncoderProvider implements StickySessionEncoderProvider {
private final KeycloakSession session;
private final String myNodeName;
public InfinispanStickySessionEncoderProvider(KeycloakSession session, String myNodeName) {
this.session = session;
this.myNodeName = myNodeName;
}
@Override
public String encodeSessionId(String sessionId) {
String nodeName = getNodeName(sessionId);
if (nodeName != null) {
return sessionId + '.' + nodeName;
} else {
return sessionId;
}
}
@Override
public String decodeSessionId(String encodedSessionId) {
int index = encodedSessionId.indexOf('.');
return index == -1 ? encodedSessionId : encodedSessionId.substring(0, index);
}
@Override
public void close() {
}
private String getNodeName(String sessionId) {
InfinispanConnectionProvider ispnProvider = session.getProvider(InfinispanConnectionProvider.class);
Cache cache = ispnProvider.getCache(InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME);
DistributionManager distManager = cache.getAdvancedCache().getDistributionManager();
if (distManager != null) {
// Sticky session to the node, who owns this authenticationSession
Address address = distManager.getPrimaryLocation(sessionId);
return address.toString();
} else {
// Fallback to jbossNodeName if authSession cache is local
return myNodeName;
}
}
}

View file

@ -0,0 +1,58 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models.sessions.infinispan;
import org.keycloak.Config;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.sessions.StickySessionEncoderProvider;
import org.keycloak.sessions.StickySessionEncoderProviderFactory;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class InfinispanStickySessionEncoderProviderFactory implements StickySessionEncoderProviderFactory {
private String myNodeName;
@Override
public StickySessionEncoderProvider create(KeycloakSession session) {
return new InfinispanStickySessionEncoderProvider(session, myNodeName);
}
@Override
public void init(Config.Scope config) {
myNodeName = config.get("nodeName", System.getProperty(InfinispanConnectionProvider.JBOSS_NODE_NAME));
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "infinispan";
}
}

View file

@ -23,10 +23,9 @@ import org.infinispan.context.Flag;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Time;
import org.keycloak.models.ClientInitialAccessModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserLoginFailureModel;
import org.keycloak.models.UserModel;
@ -34,31 +33,29 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.session.UserSessionPersisterProvider;
import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import org.keycloak.models.sessions.infinispan.stream.ClientInitialAccessPredicate;
import org.keycloak.models.sessions.infinispan.stream.ClientSessionPredicate;
import org.keycloak.models.sessions.infinispan.stream.Comparators;
import org.keycloak.models.sessions.infinispan.stream.Mappers;
import org.keycloak.models.sessions.infinispan.stream.SessionPredicate;
import org.keycloak.models.sessions.infinispan.stream.UserLoginFailurePredicate;
import org.keycloak.models.sessions.infinispan.stream.UserSessionPredicate;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RealmInfoUtil;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
@ -90,28 +87,27 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
@Override
public ClientSessionModel createClientSession(RealmModel realm, ClientModel client) {
String id = KeycloakModelUtils.generateId();
public AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession) {
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity();
ClientSessionEntity entity = new ClientSessionEntity();
entity.setId(id);
entity.setRealm(realm.getId());
entity.setTimestamp(Time.currentTime());
entity.setClient(client.getId());
tx.put(sessionCache, id, entity);
ClientSessionAdapter wrap = wrap(realm, entity, false);
return wrap;
AuthenticatedClientSessionAdapter adapter = new AuthenticatedClientSessionAdapter(entity, client, (UserSessionAdapter) userSession, this, sessionCache);
adapter.setUserSession(userSession);
return adapter;
}
@Override
public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
String id = KeycloakModelUtils.generateId();
public UserSessionModel createUserSession(String id, RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
UserSessionEntity entity = new UserSessionEntity();
entity.setId(id);
updateSessionEntity(entity, realm, user, loginUsername, ipAddress, authMethod, rememberMe, brokerSessionId, brokerUserId);
tx.putIfAbsent(sessionCache, id, entity);
return wrap(realm, entity, false);
}
void updateSessionEntity(UserSessionEntity entity, RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
entity.setRealm(realm.getId());
entity.setUser(user.getId());
entity.setLoginUsername(loginUsername);
@ -126,42 +122,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
entity.setStarted(currentTime);
entity.setLastSessionRefresh(currentTime);
tx.put(sessionCache, id, entity);
return wrap(realm, entity, false);
}
@Override
public ClientSessionModel getClientSession(RealmModel realm, String id) {
return getClientSession(realm, id, false);
}
protected ClientSessionModel getClientSession(RealmModel realm, String id, boolean offline) {
Cache<String, SessionEntity> cache = getCache(offline);
ClientSessionEntity entity = (ClientSessionEntity) cache.get(id);
// Chance created in this transaction
if (entity == null) {
entity = (ClientSessionEntity) tx.get(cache, id);
}
return wrap(realm, entity, offline);
}
@Override
public ClientSessionModel getClientSession(String id) {
ClientSessionEntity entity = (ClientSessionEntity) sessionCache.get(id);
// Chance created in this transaction
if (entity == null) {
entity = (ClientSessionEntity) tx.get(sessionCache, id);
}
if (entity != null) {
RealmModel realm = session.realms().getRealm(entity.getRealm());
return wrap(realm, entity, false);
}
return null;
}
@Override
@ -171,11 +132,10 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
protected UserSessionAdapter getUserSession(RealmModel realm, String id, boolean offline) {
Cache<String, SessionEntity> cache = getCache(offline);
UserSessionEntity entity = (UserSessionEntity) cache.get(id);
UserSessionEntity entity = (UserSessionEntity) tx.get(cache, id); // Chance created in this transaction
// Chance created in this transaction
if (entity == null) {
entity = (UserSessionEntity) tx.get(cache, id);
entity = (UserSessionEntity) cache.get(id);
}
return wrap(realm, entity, offline);
@ -221,37 +181,50 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
protected List<UserSessionModel> getUserSessions(final RealmModel realm, ClientModel client, int firstResult, int maxResults, final boolean offline) {
final Cache<String, SessionEntity> cache = getCache(offline);
Iterator<UserSessionTimestamp> itr = cache.entrySet().stream()
.filter(ClientSessionPredicate.create(realm.getId()).client(client.getId()).requireUserSession())
.map(Mappers.clientSessionToUserSessionTimestamp())
.iterator();
Stream<UserSessionEntity> stream = cache.entrySet().stream()
.filter(UserSessionPredicate.create(realm.getId()).client(client.getId()))
.map(Mappers.userSessionEntity())
.sorted(Comparators.userSessionLastSessionRefresh());
Map<String, UserSessionTimestamp> m = new HashMap<>();
while(itr.hasNext()) {
UserSessionTimestamp next = itr.next();
if (!m.containsKey(next.getUserSessionId()) || m.get(next.getUserSessionId()).getClientSessionTimestamp() < next.getClientSessionTimestamp()) {
m.put(next.getUserSessionId(), next);
}
// Doesn't work due to ISPN-6575 . TODO Fix once infinispan upgraded to 8.2.2.Final or 9.0
// if (firstResult > 0) {
// stream = stream.skip(firstResult);
// }
//
// if (maxResults > 0) {
// stream = stream.limit(maxResults);
// }
//
// List<UserSessionEntity> entities = stream.collect(Collectors.toList());
// Workaround for ISPN-6575 TODO Fix once infinispan upgraded to 8.2.2.Final or 9.0 and replace with the more effective code above
if (firstResult < 0) {
firstResult = 0;
}
if (maxResults < 0) {
maxResults = Integer.MAX_VALUE;
}
Stream<UserSessionTimestamp> stream = new LinkedList<>(m.values()).stream().sorted(Comparators.userSessionTimestamp());
int count = firstResult + maxResults;
if (count > 0) {
stream = stream.limit(count);
}
List<UserSessionEntity> entities = stream.collect(Collectors.toList());
if (firstResult > 0) {
stream = stream.skip(firstResult);
if (firstResult > entities.size()) {
return Collections.emptyList();
}
if (maxResults > 0) {
stream = stream.limit(maxResults);
}
maxResults = Math.min(maxResults, entities.size() - firstResult);
entities = entities.subList(firstResult, firstResult + maxResults);
final List<UserSessionModel> sessions = new LinkedList<>();
stream.forEach(new Consumer<UserSessionTimestamp>() {
entities.stream().forEach(new Consumer<UserSessionEntity>() {
@Override
public void accept(UserSessionTimestamp userSessionTimestamp) {
SessionEntity entity = cache.get(userSessionTimestamp.getUserSessionId());
if (entity != null) {
sessions.add(wrap(realm, (UserSessionEntity) entity, offline));
}
public void accept(UserSessionEntity userSessionEntity) {
sessions.add(wrap(realm, userSessionEntity, offline));
}
});
@ -264,7 +237,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
protected long getUserSessionsCount(RealmModel realm, ClientModel client, boolean offline) {
return getCache(offline).entrySet().stream().filter(ClientSessionPredicate.create(realm.getId()).client(client.getId()).requireUserSession()).map(Mappers.clientSessionToUserSessionId()).distinct().count();
return getCache(offline).entrySet().stream()
.filter(UserSessionPredicate.create(realm.getId()).client(client.getId()))
.count();
}
@Override
@ -294,9 +269,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
public void removeExpired(RealmModel realm) {
log.debugf("Removing expired sessions");
removeExpiredUserSessions(realm);
removeExpiredClientSessions(realm);
removeExpiredOfflineUserSessions(realm);
removeExpiredOfflineClientSessions(realm);
removeExpiredClientInitialAccess(realm);
}
@ -313,33 +286,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
counter++;
UserSessionEntity entity = (UserSessionEntity) itr.next().getValue();
tx.remove(sessionCache, entity.getId());
if (entity.getClientSessions() != null) {
for (String clientSessionId : entity.getClientSessions()) {
tx.remove(sessionCache, clientSessionId);
}
}
}
log.debugf("Removed %d expired user sessions for realm '%s'", counter, realm.getName());
}
private void removeExpiredClientSessions(RealmModel realm) {
int expiredDettachedClientSession = Time.currentTime() - RealmInfoUtil.getDettachedClientSessionLifespan(realm);
// Each cluster node cleanups just local sessions, which are those owned by himself (+ few more taking l1 cache into account)
Iterator<Map.Entry<String, SessionEntity>> itr = sessionCache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL)
.entrySet().stream().filter(ClientSessionPredicate.create(realm.getId()).expiredRefresh(expiredDettachedClientSession).requireNullUserSession()).iterator();
int counter = 0;
while (itr.hasNext()) {
counter++;
tx.remove(sessionCache, itr.next().getKey());
}
log.debugf("Removed %d expired client sessions for realm '%s'", counter, realm.getName());
}
private void removeExpiredOfflineUserSessions(RealmModel realm) {
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
int expiredOffline = Time.currentTime() - realm.getOfflineSessionIdleTimeout();
@ -357,33 +308,14 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
persister.removeUserSession(entity.getId(), true);
for (String clientSessionId : entity.getClientSessions()) {
tx.remove(offlineSessionCache, clientSessionId);
for (String clientUUID : entity.getAuthenticatedClientSessions().keySet()) {
persister.removeClientSession(entity.getId(), clientUUID, true);
}
}
log.debugf("Removed %d expired offline user sessions for realm '%s'", counter, realm.getName());
}
private void removeExpiredOfflineClientSessions(RealmModel realm) {
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
int expiredOffline = Time.currentTime() - realm.getOfflineSessionIdleTimeout();
// Each cluster node cleanups just local sessions, which are those owned by himself (+ few more taking l1 cache into account)
Iterator<String> itr = offlineSessionCache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL)
.entrySet().stream().filter(ClientSessionPredicate.create(realm.getId()).expiredRefresh(expiredOffline)).map(Mappers.sessionId()).iterator();
int counter = 0;
while (itr.hasNext()) {
counter++;
String sessionId = itr.next();
tx.remove(offlineSessionCache, sessionId);
persister.removeClientSession(sessionId, true);
}
log.debugf("Removed %d expired offline client sessions for realm '%s'", counter, realm.getName());
}
private void removeExpiredClientInitialAccess(RealmModel realm) {
Iterator<String> itr = sessionCache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL)
.entrySet().stream().filter(ClientInitialAccessPredicate.create(realm.getId()).expired(Time.currentTime())).map(Mappers.sessionId()).iterator();
@ -445,21 +377,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
public void onClientRemoved(RealmModel realm, ClientModel client) {
onClientRemoved(realm, client, true);
onClientRemoved(realm, client, false);
}
private void onClientRemoved(RealmModel realm, ClientModel client, boolean offline) {
Cache<String, SessionEntity> cache = getCache(offline);
Iterator<Map.Entry<String, SessionEntity>> itr = cache.entrySet().stream().filter(ClientSessionPredicate.create(realm.getId()).client(client.getId())).iterator();
while (itr.hasNext()) {
ClientSessionEntity entity = (ClientSessionEntity) itr.next().getValue();
ClientSessionAdapter adapter = wrap(realm, entity, offline);
adapter.setUserSession(null);
tx.remove(cache, entity.getId());
}
// Nothing for now. userSession.getAuthenticatedClientSessions() will check lazily if particular client exists and update userSession on-the-fly.
}
@ -475,55 +393,10 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
public void close() {
}
void attachSession(UserSessionAdapter userSession, ClientSessionModel clientSession) {
UserSessionEntity entity = userSession.getEntity();
String clientSessionId = clientSession.getId();
if (!entity.getClientSessions().contains(clientSessionId)) {
entity.getClientSessions().add(clientSessionId);
userSession.update();
}
}
@Override
public void removeClientSession(RealmModel realm, ClientSessionModel clientSession) {
removeClientSession(realm, clientSession, false);
}
protected void removeClientSession(RealmModel realm, ClientSessionModel clientSession, boolean offline) {
Cache<String, SessionEntity> cache = getCache(offline);
UserSessionModel userSession = clientSession.getUserSession();
if (userSession != null) {
UserSessionEntity entity = ((UserSessionAdapter) userSession).getEntity();
if (entity.getClientSessions() != null) {
entity.getClientSessions().remove(clientSession.getId());
}
tx.replace(cache, entity.getId(), entity);
}
tx.remove(cache, clientSession.getId());
}
void dettachSession(UserSessionAdapter userSession, ClientSessionModel clientSession) {
UserSessionEntity entity = userSession.getEntity();
String clientSessionId = clientSession.getId();
if (entity.getClientSessions() != null && entity.getClientSessions().contains(clientSessionId)) {
entity.getClientSessions().remove(clientSessionId);
userSession.update();
}
}
protected void removeUserSession(RealmModel realm, UserSessionEntity sessionEntity, boolean offline) {
Cache<String, SessionEntity> cache = getCache(offline);
tx.remove(cache, sessionEntity.getId());
if (sessionEntity.getClientSessions() != null) {
for (String clientSessionId : sessionEntity.getClientSessions()) {
tx.remove(cache, clientSessionId);
}
}
}
InfinispanKeycloakTransaction getTx() {
@ -551,11 +424,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return models;
}
ClientSessionAdapter wrap(RealmModel realm, ClientSessionEntity entity, boolean offline) {
Cache<String, SessionEntity> cache = getCache(offline);
return entity != null ? new ClientSessionAdapter(session, this, cache, realm, entity, offline) : null;
}
ClientInitialAccessAdapter wrap(RealmModel realm, ClientInitialAccessEntity entity) {
Cache<String, SessionEntity> cache = getCache(false);
return entity != null ? new ClientInitialAccessAdapter(session, this, cache, realm, entity) : null;
@ -565,14 +433,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return entity != null ? new UserLoginFailureAdapter(this, loginFailureCache, key, entity) : null;
}
List<ClientSessionModel> wrapClientSessions(RealmModel realm, Collection<ClientSessionEntity> entities, boolean offline) {
List<ClientSessionModel> models = new LinkedList<>();
for (ClientSessionEntity e : entities) {
models.add(wrap(realm, e, offline));
}
return models;
}
UserSessionEntity getUserSessionEntity(UserSessionModel userSession, boolean offline) {
if (userSession instanceof UserSessionAdapter) {
return ((UserSessionAdapter) userSession).getEntity();
@ -585,7 +445,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
public UserSessionModel createOfflineUserSession(UserSessionModel userSession) {
UserSessionAdapter offlineUserSession = importUserSession(userSession, true);
UserSessionAdapter offlineUserSession = importUserSession(userSession, true, false);
// started and lastSessionRefresh set to current time
int currentTime = Time.currentTime();
@ -596,7 +456,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
@Override
public UserSessionModel getOfflineUserSession(RealmModel realm, String userSessionId) {
public UserSessionAdapter getOfflineUserSession(RealmModel realm, String userSessionId) {
return getUserSession(realm, userSessionId, true);
}
@ -608,9 +468,14 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
}
@Override
public ClientSessionModel createOfflineClientSession(ClientSessionModel clientSession) {
ClientSessionAdapter offlineClientSession = importClientSession(clientSession, true);
public AuthenticatedClientSessionModel createOfflineClientSession(AuthenticatedClientSessionModel clientSession, UserSessionModel offlineUserSession) {
UserSessionAdapter userSessionAdapter = (offlineUserSession instanceof UserSessionAdapter) ? (UserSessionAdapter) offlineUserSession :
getOfflineUserSession(offlineUserSession.getRealm(), offlineUserSession.getId());
AuthenticatedClientSessionAdapter offlineClientSession = importClientSession(userSessionAdapter, clientSession);
// update timestamp to current time
offlineClientSession.setTimestamp(Time.currentTime());
@ -619,38 +484,17 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
@Override
public ClientSessionModel getOfflineClientSession(RealmModel realm, String clientSessionId) {
return getClientSession(realm, clientSessionId, true);
}
@Override
public List<ClientSessionModel> getOfflineClientSessions(RealmModel realm, UserModel user) {
public List<UserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel user) {
Iterator<Map.Entry<String, SessionEntity>> itr = offlineSessionCache.entrySet().stream().filter(UserSessionPredicate.create(realm.getId()).user(user.getId())).iterator();
List<ClientSessionModel> clientSessions = new LinkedList<>();
List<UserSessionModel> userSessions = new LinkedList<>();
while(itr.hasNext()) {
UserSessionEntity entity = (UserSessionEntity) itr.next().getValue();
Set<String> currClientSessions = entity.getClientSessions();
if (currClientSessions == null) {
continue;
}
for (String clientSessionId : currClientSessions) {
ClientSessionEntity cls = (ClientSessionEntity) offlineSessionCache.get(clientSessionId);
if (cls != null) {
clientSessions.add(wrap(realm, cls, true));
}
}
UserSessionModel userSession = wrap(realm, entity, true);
userSessions.add(userSession);
}
return clientSessions;
}
@Override
public void removeOfflineClientSession(RealmModel realm, String clientSessionId) {
ClientSessionModel clientSession = getOfflineClientSession(realm, clientSessionId);
removeClientSession(realm, clientSession, true);
return userSessions;
}
@Override
@ -664,7 +508,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
@Override
public UserSessionAdapter importUserSession(UserSessionModel userSession, boolean offline) {
public UserSessionAdapter importUserSession(UserSessionModel userSession, boolean offline, boolean importAuthenticatedClientSessions) {
UserSessionEntity entity = new UserSessionEntity();
entity.setId(userSession.getId());
entity.setRealm(userSession.getRealm().getId());
@ -682,34 +526,45 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
entity.setStarted(userSession.getStarted());
entity.setLastSessionRefresh(userSession.getLastSessionRefresh());
Cache<String, SessionEntity> cache = getCache(offline);
tx.put(cache, userSession.getId(), entity);
return wrap(userSession.getRealm(), entity, offline);
UserSessionAdapter importedSession = wrap(userSession.getRealm(), entity, offline);
// Handle client sessions
if (importAuthenticatedClientSessions) {
for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
importClientSession(importedSession, clientSession);
}
}
return importedSession;
}
@Override
public ClientSessionAdapter importClientSession(ClientSessionModel clientSession, boolean offline) {
ClientSessionEntity entity = new ClientSessionEntity();
entity.setId(clientSession.getId());
entity.setRealm(clientSession.getRealm().getId());
private AuthenticatedClientSessionAdapter importClientSession(UserSessionAdapter importedUserSession, AuthenticatedClientSessionModel clientSession) {
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity();
entity.setAction(clientSession.getAction());
entity.setAuthenticatorStatus(clientSession.getExecutionStatus());
entity.setAuthMethod(clientSession.getAuthMethod());
if (clientSession.getAuthenticatedUser() != null) {
entity.setAuthUserId(clientSession.getAuthenticatedUser().getId());
}
entity.setClient(clientSession.getClient().getId());
entity.setAuthMethod(clientSession.getProtocol());
entity.setNotes(clientSession.getNotes());
entity.setProtocolMappers(clientSession.getProtocolMappers());
entity.setRedirectUri(clientSession.getRedirectUri());
entity.setRoles(clientSession.getRoles());
entity.setTimestamp(clientSession.getTimestamp());
entity.setUserSessionNotes(clientSession.getUserSessionNotes());
Cache<String, SessionEntity> cache = getCache(offline);
tx.put(cache, clientSession.getId(), entity);
return wrap(clientSession.getRealm(), entity, offline);
Map<String, AuthenticatedClientSessionEntity> clientSessions = importedUserSession.getEntity().getAuthenticatedClientSessions();
if (clientSessions == null) {
clientSessions = new HashMap<>();
importedUserSession.getEntity().setAuthenticatedClientSessions(clientSessions);
}
clientSessions.put(clientSession.getClient().getId(), entity);
importedUserSession.update();
return new AuthenticatedClientSessionAdapter(entity, clientSession.getClient(), importedUserSession, this, importedUserSession.getCache());
}
@Override
@ -732,11 +587,10 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
public ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id) {
Cache<String, SessionEntity> cache = getCache(false);
ClientInitialAccessEntity entity = (ClientInitialAccessEntity) cache.get(id);
ClientInitialAccessEntity entity = (ClientInitialAccessEntity) tx.get(cache, id); // Chance created in this transaction
// If created in this transaction
if (entity == null) {
entity = (ClientInitialAccessEntity) tx.get(cache, id);
entity = (ClientInitialAccessEntity) cache.get(id);
}
return wrap(realm, entity);
@ -757,145 +611,4 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return list;
}
class InfinispanKeycloakTransaction implements KeycloakTransaction {
private boolean active;
private boolean rollback;
private Map<Object, CacheTask> tasks = new HashMap<>();
@Override
public void begin() {
active = true;
}
@Override
public void commit() {
if (rollback) {
throw new RuntimeException("Rollback only!");
}
for (CacheTask task : tasks.values()) {
task.execute();
}
}
@Override
public void rollback() {
tasks.clear();
}
@Override
public void setRollbackOnly() {
rollback = true;
}
@Override
public boolean getRollbackOnly() {
return rollback;
}
@Override
public boolean isActive() {
return active;
}
public void put(Cache cache, Object key, Object value) {
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.ADD, key);
Object taskKey = getTaskKey(cache, key);
if (tasks.containsKey(taskKey)) {
throw new IllegalStateException("Can't add session: task in progress for session");
} else {
tasks.put(taskKey, new CacheTask(cache, CacheOperation.ADD, key, value));
}
}
public void replace(Cache cache, Object key, Object value) {
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.REPLACE, key);
Object taskKey = getTaskKey(cache, key);
CacheTask current = tasks.get(taskKey);
if (current != null) {
switch (current.operation) {
case ADD:
case REPLACE:
current.value = value;
return;
case REMOVE:
return;
}
} else {
tasks.put(taskKey, new CacheTask(cache, CacheOperation.REPLACE, key, value));
}
}
public void remove(Cache cache, Object key) {
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.REMOVE, key);
Object taskKey = getTaskKey(cache, key);
tasks.put(taskKey, new CacheTask(cache, CacheOperation.REMOVE, key, null));
}
// This is for possibility to lookup for session by id, which was created in this transaction
public Object get(Cache cache, Object key) {
Object taskKey = getTaskKey(cache, key);
CacheTask current = tasks.get(taskKey);
if (current != null) {
switch (current.operation) {
case ADD:
case REPLACE:
return current.value; }
}
return null;
}
private Object getTaskKey(Cache cache, Object key) {
if (key instanceof String) {
return new StringBuilder(cache.getName())
.append("::")
.append(key.toString()).toString();
} else {
// loginFailure cache
return key;
}
}
public class CacheTask {
private Cache cache;
private CacheOperation operation;
private Object key;
private Object value;
public CacheTask(Cache cache, CacheOperation operation, Object key, Object value) {
this.cache = cache;
this.operation = operation;
this.key = key;
this.value = value;
}
public void execute() {
log.tracev("Executing cache operation: {0} on {1}", operation, key);
switch (operation) {
case ADD:
cache.put(key, value);
break;
case REMOVE:
cache.remove(key);
break;
case REPLACE:
cache.replace(key, value);
break;
}
}
}
}
public enum CacheOperation {
ADD, REMOVE, REPLACE
}
}

View file

@ -18,11 +18,13 @@
package org.keycloak.models.sessions.infinispan;
import org.infinispan.Cache;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
@ -60,6 +62,36 @@ public class UserSessionAdapter implements UserSessionModel {
this.offline = offline;
}
@Override
public Map<String, AuthenticatedClientSessionModel> getAuthenticatedClientSessions() {
Map<String, AuthenticatedClientSessionEntity> clientSessionEntities = entity.getAuthenticatedClientSessions();
Map<String, AuthenticatedClientSessionModel> result = new HashMap<>();
List<String> removedClientUUIDS = new LinkedList<>();
if (clientSessionEntities != null) {
clientSessionEntities.forEach((String key, AuthenticatedClientSessionEntity value) -> {
// Check if client still exists
ClientModel client = realm.getClientById(key);
if (client != null) {
result.put(key, new AuthenticatedClientSessionAdapter(value, client, this, provider, cache));
} else {
removedClientUUIDS.add(key);
}
});
}
// Update user session
if (!removedClientUUIDS.isEmpty()) {
for (String clientUUID : removedClientUUIDS) {
entity.getAuthenticatedClientSessions().remove(clientUUID);
}
update();
}
return Collections.unmodifiableMap(result);
}
public String getId() {
return entity.getId();
}
@ -82,6 +114,12 @@ public class UserSessionAdapter implements UserSessionModel {
return session.users().getUserById(entity.getUser(), realm);
}
@Override
public void setUser(UserModel user) {
entity.setUser(user.getId());
update();
}
@Override
public String getLoginUsername() {
return entity.getLoginUsername();
@ -159,19 +197,14 @@ public class UserSessionAdapter implements UserSessionModel {
}
@Override
public List<ClientSessionModel> getClientSessions() {
if (entity.getClientSessions() != null) {
List<ClientSessionModel> clientSessions = new LinkedList<>();
for (String c : entity.getClientSessions()) {
ClientSessionModel clientSession = provider.getClientSession(realm, c, offline);
if (clientSession != null) {
clientSessions.add(clientSession);
}
}
return clientSessions;
} else {
return Collections.emptyList();
}
public void restartSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
provider.updateSessionEntity(entity, realm, user, loginUsername, ipAddress, authMethod, rememberMe, brokerSessionId, brokerUserId);
entity.setState(null);
entity.setNotes(null);
entity.setAuthenticatedClientSessions(null);
update();
}
@Override
@ -196,4 +229,7 @@ public class UserSessionAdapter implements UserSessionModel {
provider.getTx().replace(cache, entity.getId(), entity);
}
Cache<String, SessionEntity> getCache() {
return cache;
}
}

View file

@ -0,0 +1,104 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models.sessions.infinispan.entities;
import java.io.*;
import java.util.Objects;
import java.util.UUID;
import org.infinispan.commons.marshall.Externalizer;
import org.infinispan.commons.marshall.SerializeWith;
/**
*
* @author hmlnarik
*/
@SerializeWith(value = ActionTokenReducedKey.ExternalizerImpl.class)
public class ActionTokenReducedKey implements Serializable {
private final String userId;
private final String actionId;
/**
* Nonce that must match.
*/
private final UUID actionVerificationNonce;
public ActionTokenReducedKey(String userId, String actionId, UUID actionVerificationNonce) {
this.userId = userId;
this.actionId = actionId;
this.actionVerificationNonce = actionVerificationNonce;
}
public String getUserId() {
return userId;
}
public String getActionId() {
return actionId;
}
public UUID getActionVerificationNonce() {
return actionVerificationNonce;
}
@Override
public int hashCode() {
int hash = 7;
hash = 71 * hash + Objects.hashCode(this.userId);
hash = 71 * hash + Objects.hashCode(this.actionId);
hash = 71 * hash + Objects.hashCode(this.actionVerificationNonce);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ActionTokenReducedKey other = (ActionTokenReducedKey) obj;
return Objects.equals(this.userId, other.getUserId())
&& Objects.equals(this.actionId, other.getActionId())
&& Objects.equals(this.actionVerificationNonce, other.getActionVerificationNonce());
}
public static class ExternalizerImpl implements Externalizer<ActionTokenReducedKey> {
@Override
public void writeObject(ObjectOutput output, ActionTokenReducedKey t) throws IOException {
output.writeUTF(t.userId);
output.writeUTF(t.actionId);
output.writeLong(t.actionVerificationNonce.getMostSignificantBits());
output.writeLong(t.actionVerificationNonce.getLeastSignificantBits());
}
@Override
public ActionTokenReducedKey readObject(ObjectInput input) throws IOException, ClassNotFoundException {
return new ActionTokenReducedKey(
input.readUTF(),
input.readUTF(),
new UUID(input.readLong(), input.readLong())
);
}
}
}

View file

@ -0,0 +1,76 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models.sessions.infinispan.entities;
import org.keycloak.models.ActionTokenValueModel;
import java.io.*;
import java.util.*;
import org.infinispan.commons.marshall.Externalizer;
import org.infinispan.commons.marshall.SerializeWith;
/**
* @author hmlnarik
*/
@SerializeWith(ActionTokenValueEntity.ExternalizerImpl.class)
public class ActionTokenValueEntity implements ActionTokenValueModel {
private final Map<String, String> notes;
public ActionTokenValueEntity(Map<String, String> notes) {
this.notes = notes == null ? Collections.EMPTY_MAP : new HashMap<>(notes);
}
@Override
public Map<String, String> getNotes() {
return Collections.unmodifiableMap(notes);
}
@Override
public String getNote(String name) {
return notes.get(name);
}
public static class ExternalizerImpl implements Externalizer<ActionTokenValueEntity> {
private static final int VERSION_1 = 1;
@Override
public void writeObject(ObjectOutput output, ActionTokenValueEntity t) throws IOException {
output.writeByte(VERSION_1);
output.writeBoolean(! t.notes.isEmpty());
if (! t.notes.isEmpty()) {
output.writeObject(t.notes);
}
}
@Override
public ActionTokenValueEntity readObject(ObjectInput input) throws IOException, ClassNotFoundException {
byte version = input.readByte();
if (version != VERSION_1) {
throw new IOException("Invalid version: " + version);
}
boolean notesEmpty = input.readBoolean();
Map<String, String> notes = notesEmpty ? Collections.EMPTY_MAP : (Map<String, String>) input.readObject();
return new ActionTokenValueEntity(notes);
}
}
}

View file

@ -0,0 +1,94 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models.sessions.infinispan.entities;
import java.io.Serializable;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class AuthenticatedClientSessionEntity implements Serializable {
private String authMethod;
private String redirectUri;
private int timestamp;
private String action;
private Set<String> roles;
private Set<String> protocolMappers;
private Map<String, String> notes;
public String getAuthMethod() {
return authMethod;
}
public void setAuthMethod(String authMethod) {
this.authMethod = authMethod;
}
public String getRedirectUri() {
return redirectUri;
}
public void setRedirectUri(String redirectUri) {
this.redirectUri = redirectUri;
}
public int getTimestamp() {
return timestamp;
}
public void setTimestamp(int timestamp) {
this.timestamp = timestamp;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public Set<String> getRoles() {
return roles;
}
public void setRoles(Set<String> roles) {
this.roles = roles;
}
public Set<String> getProtocolMappers() {
return protocolMappers;
}
public void setProtocolMappers(Set<String> protocolMappers) {
this.protocolMappers = protocolMappers;
}
public Map<String, String> getNotes() {
return notes;
}
public void setNotes(Map<String, String> notes) {
this.notes = notes;
}
}

View file

@ -17,61 +17,49 @@
package org.keycloak.models.sessions.infinispan.entities;
import org.keycloak.models.ClientSessionModel;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.keycloak.sessions.AuthenticationSessionModel;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ClientSessionEntity extends SessionEntity {
public class AuthenticationSessionEntity extends SessionEntity {
private String client;
private String userSession;
private String authMethod;
private String clientUuid;
private String authUserId;
private String redirectUri;
private int timestamp;
private String action;
private Set<String> roles;
private Set<String> protocolMappers;
private Map<String, String> notes;
private Map<String, AuthenticationSessionModel.ExecutionStatus> executionStatus = new HashMap<>();;
private String protocol;
private Map<String, String> clientNotes;
private Map<String, String> authNotes;
private Set<String> requiredActions = new HashSet<>();
private Map<String, String> userSessionNotes;
private Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus = new HashMap<>();
private String authUserId;
private Set<String> requiredActions = new HashSet<>();
public String getClient() {
return client;
public String getClientUuid() {
return clientUuid;
}
public void setClient(String client) {
this.client = client;
public void setClientUuid(String clientUuid) {
this.clientUuid = clientUuid;
}
public String getUserSession() {
return userSession;
public String getAuthUserId() {
return authUserId;
}
public void setUserSession(String userSession) {
this.userSession = userSession;
}
public String getAuthMethod() {
return authMethod;
}
public void setAuthMethod(String authMethod) {
this.authMethod = authMethod;
public void setAuthUserId(String authUserId) {
this.authUserId = authUserId;
}
public String getRedirectUri() {
@ -114,28 +102,36 @@ public class ClientSessionEntity extends SessionEntity {
this.protocolMappers = protocolMappers;
}
public Map<String, String> getNotes() {
return notes;
public Map<String, AuthenticationSessionModel.ExecutionStatus> getExecutionStatus() {
return executionStatus;
}
public void setNotes(Map<String, String> notes) {
this.notes = notes;
public void setExecutionStatus(Map<String, AuthenticationSessionModel.ExecutionStatus> executionStatus) {
this.executionStatus = executionStatus;
}
public Map<String, ClientSessionModel.ExecutionStatus> getAuthenticatorStatus() {
return authenticatorStatus;
public String getProtocol() {
return protocol;
}
public void setAuthenticatorStatus(Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus) {
this.authenticatorStatus = authenticatorStatus;
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public String getAuthUserId() {
return authUserId;
public Map<String, String> getClientNotes() {
return clientNotes;
}
public void setAuthUserId(String authUserId) {
this.authUserId = authUserId;
public void setClientNotes(Map<String, String> clientNotes) {
this.clientNotes = clientNotes;
}
public Set<String> getRequiredActions() {
return requiredActions;
}
public void setRequiredActions(Set<String> requiredActions) {
this.requiredActions = requiredActions;
}
public Map<String, String> getUserSessionNotes() {
@ -146,7 +142,11 @@ public class ClientSessionEntity extends SessionEntity {
this.userSessionNotes = userSessionNotes;
}
public Set<String> getRequiredActions() {
return requiredActions;
public Map<String, String> getAuthNotes() {
return authNotes;
}
public void setAuthNotes(Map<String, String> authNotes) {
this.authNotes = authNotes;
}
}

View file

@ -46,12 +46,12 @@ public class UserSessionEntity extends SessionEntity {
private int lastSessionRefresh;
private Set<String> clientSessions = new CopyOnWriteArraySet<>();
private UserSessionModel.State state;
private Map<String, String> notes = new ConcurrentHashMap<>();
private Map<String, AuthenticatedClientSessionEntity> authenticatedClientSessions;
public String getUser() {
return user;
}
@ -108,10 +108,6 @@ public class UserSessionEntity extends SessionEntity {
this.lastSessionRefresh = lastSessionRefresh;
}
public Set<String> getClientSessions() {
return clientSessions;
}
public Map<String, String> getNotes() {
return notes;
}
@ -120,6 +116,14 @@ public class UserSessionEntity extends SessionEntity {
this.notes = notes;
}
public Map<String, AuthenticatedClientSessionEntity> getAuthenticatedClientSessions() {
return authenticatedClientSessions;
}
public void setAuthenticatedClientSessions(Map<String, AuthenticatedClientSessionEntity> authenticatedClientSessions) {
this.authenticatedClientSessions = authenticatedClientSessions;
}
public UserSessionModel.State getState() {
return state;
}

View file

@ -19,7 +19,6 @@ package org.keycloak.models.sessions.infinispan.initializer;
import org.jboss.logging.Logger;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.session.UserSessionPersisterProvider;
@ -64,12 +63,7 @@ public class OfflineUserSessionLoader implements SessionLoader {
for (UserSessionModel persistentSession : sessions) {
// Save to memory/infinispan
UserSessionModel offlineUserSession = session.sessions().importUserSession(persistentSession, true);
for (ClientSessionModel persistentClientSession : persistentSession.getClientSessions()) {
ClientSessionModel offlineClientSession = session.sessions().importClientSession(persistentClientSession, true);
offlineClientSession.setUserSession(offlineUserSession);
}
UserSessionModel offlineUserSession = session.sessions().importUserSession(persistentSession, true, true);
}
return true;

View file

@ -1,129 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models.sessions.infinispan.mapreduce;
import org.infinispan.distexec.mapreduce.Collector;
import org.infinispan.distexec.mapreduce.Mapper;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import java.io.Serializable;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ClientSessionMapper implements Mapper<String, SessionEntity, String, Object>, Serializable {
public ClientSessionMapper(String realm) {
this.realm = realm;
}
private enum EmitValue {
KEY, ENTITY, USER_SESSION_AND_TIMESTAMP
}
private String realm;
private EmitValue emit = EmitValue.ENTITY;
private String client;
private String userSession;
private Long expiredRefresh;
private Boolean requireNullUserSession = false;
public static ClientSessionMapper create(String realm) {
return new ClientSessionMapper(realm);
}
public ClientSessionMapper emitKey() {
emit = EmitValue.KEY;
return this;
}
public ClientSessionMapper emitUserSessionAndTimestamp() {
emit = EmitValue.USER_SESSION_AND_TIMESTAMP;
return this;
}
public ClientSessionMapper client(String client) {
this.client = client;
return this;
}
public ClientSessionMapper userSession(String userSession) {
this.userSession = userSession;
return this;
}
public ClientSessionMapper expiredRefresh(long expiredRefresh) {
this.expiredRefresh = expiredRefresh;
return this;
}
public ClientSessionMapper requireNullUserSession(boolean requireNullUserSession) {
this.requireNullUserSession = requireNullUserSession;
return this;
}
@Override
public void map(String key, SessionEntity e, Collector collector) {
if (!realm.equals(e.getRealm())) {
return;
}
if (!(e instanceof ClientSessionEntity)) {
return;
}
ClientSessionEntity entity = (ClientSessionEntity) e;
if (client != null && !entity.getClient().equals(client)) {
return;
}
if (userSession != null && !userSession.equals(entity.getUserSession())) {
return;
}
if (requireNullUserSession && entity.getUserSession() != null) {
return;
}
if (expiredRefresh != null && entity.getTimestamp() > expiredRefresh) {
return;
}
switch (emit) {
case KEY:
collector.emit(key, key);
break;
case ENTITY:
collector.emit(key, entity);
break;
case USER_SESSION_AND_TIMESTAMP:
if (entity.getUserSession() != null) {
collector.emit(entity.getUserSession(), entity.getTimestamp());
}
break;
}
}
}

View file

@ -1,77 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models.sessions.infinispan.mapreduce;
import org.infinispan.distexec.mapreduce.Collector;
import org.infinispan.distexec.mapreduce.Mapper;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import java.io.Serializable;
import java.util.Collection;
/**
* Return all clientSessions attached to any from input list of userSessions
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ClientSessionsOfUserSessionMapper implements Mapper<String, SessionEntity, String, Object>, Serializable {
private String realm;
private Collection<String> userSessions;
private EmitValue emit = EmitValue.ENTITY;
private enum EmitValue {
KEY, ENTITY
}
public ClientSessionsOfUserSessionMapper(String realm, Collection<String> userSessions) {
this.realm = realm;
this.userSessions = userSessions;
}
public ClientSessionsOfUserSessionMapper emitKey() {
emit = EmitValue.KEY;
return this;
}
@Override
public void map(String key, SessionEntity e, Collector<String, Object> collector) {
if (!realm.equals(e.getRealm())) {
return;
}
if (!(e instanceof ClientSessionEntity)) {
return;
}
ClientSessionEntity entity = (ClientSessionEntity) e;
if (userSessions.contains(entity.getUserSession())) {
switch (emit) {
case KEY:
collector.emit(entity.getId(), entity.getId());
break;
case ENTITY:
collector.emit(entity.getId(), entity);
break;
}
}
}
}

View file

@ -0,0 +1,105 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models.sessions.infinispan.stream;
import java.io.Serializable;
import java.util.Map;
import java.util.function.Predicate;
import org.keycloak.models.sessions.infinispan.entities.AuthenticationSessionEntity;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class AuthenticationSessionPredicate implements Predicate<Map.Entry<String, AuthenticationSessionEntity>>, Serializable {
private String realm;
private String client;
private String user;
private Integer expired;
//private String brokerSessionId;
//private String brokerUserId;
private AuthenticationSessionPredicate(String realm) {
this.realm = realm;
}
public static AuthenticationSessionPredicate create(String realm) {
return new AuthenticationSessionPredicate(realm);
}
public AuthenticationSessionPredicate user(String user) {
this.user = user;
return this;
}
public AuthenticationSessionPredicate client(String client) {
this.client = client;
return this;
}
public AuthenticationSessionPredicate expired(Integer expired) {
this.expired = expired;
return this;
}
// public UserSessionPredicate brokerSessionId(String id) {
// this.brokerSessionId = id;
// return this;
// }
// public UserSessionPredicate brokerUserId(String id) {
// this.brokerUserId = id;
// return this;
// }
@Override
public boolean test(Map.Entry<String, AuthenticationSessionEntity> entry) {
AuthenticationSessionEntity entity = entry.getValue();
if (!realm.equals(entity.getRealm())) {
return false;
}
if (user != null && !entity.getAuthUserId().equals(user)) {
return false;
}
if (client != null && !entity.getClientUuid().equals(client)) {
return false;
}
// if (brokerSessionId != null && !brokerSessionId.equals(entity.getBrokerSessionId())) {
// return false;
// }
//
// if (brokerUserId != null && !brokerUserId.equals(entity.getBrokerUserId())) {
// return false;
// }
if (expired != null && entity.getTimestamp() > expired) {
return false;
}
return true;
}
}

View file

@ -1,114 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models.sessions.infinispan.stream;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import java.io.Serializable;
import java.util.Map;
import java.util.function.Predicate;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ClientSessionPredicate implements Predicate<Map.Entry<String, SessionEntity>>, Serializable {
private String realm;
private String client;
private String userSession;
private Long expiredRefresh;
private Boolean requireUserSession = false;
private Boolean requireNullUserSession = false;
private ClientSessionPredicate(String realm) {
this.realm = realm;
}
public static ClientSessionPredicate create(String realm) {
return new ClientSessionPredicate(realm);
}
public ClientSessionPredicate client(String client) {
this.client = client;
return this;
}
public ClientSessionPredicate userSession(String userSession) {
this.userSession = userSession;
return this;
}
public ClientSessionPredicate expiredRefresh(long expiredRefresh) {
this.expiredRefresh = expiredRefresh;
return this;
}
public ClientSessionPredicate requireUserSession() {
requireUserSession = true;
return this;
}
public ClientSessionPredicate requireNullUserSession() {
requireNullUserSession = true;
return this;
}
@Override
public boolean test(Map.Entry<String, SessionEntity> entry) {
SessionEntity e = entry.getValue();
if (!realm.equals(e.getRealm())) {
return false;
}
if (!(e instanceof ClientSessionEntity)) {
return false;
}
ClientSessionEntity entity = (ClientSessionEntity) e;
if (client != null && !entity.getClient().equals(client)) {
return false;
}
if (userSession != null && !userSession.equals(entity.getUserSession())) {
return false;
}
if (requireUserSession && entity.getUserSession() == null) {
return false;
}
if (requireNullUserSession && entity.getUserSession() != null) {
return false;
}
if (expiredRefresh != null && entity.getTimestamp() > expiredRefresh) {
return false;
}
return true;
}
}

View file

@ -18,6 +18,8 @@
package org.keycloak.models.sessions.infinispan.stream;
import org.keycloak.models.sessions.infinispan.UserSessionTimestamp;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import java.io.Serializable;
import java.util.Comparator;
@ -38,4 +40,17 @@ public class Comparators {
}
}
public static Comparator<UserSessionEntity> userSessionLastSessionRefresh() {
return new UserSessionLastSessionRefreshComparator();
}
private static class UserSessionLastSessionRefreshComparator implements Comparator<UserSessionEntity>, Serializable {
@Override
public int compare(UserSessionEntity u1, UserSessionEntity u2) {
return u1.getLastSessionRefresh() - u2.getLastSessionRefresh();
}
}
}

View file

@ -18,10 +18,10 @@
package org.keycloak.models.sessions.infinispan.stream;
import org.keycloak.models.sessions.infinispan.UserSessionTimestamp;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import java.io.Serializable;
import java.util.Map;
@ -33,10 +33,6 @@ import java.util.function.Function;
*/
public class Mappers {
public static Function<Map.Entry<String, SessionEntity>, UserSessionTimestamp> clientSessionToUserSessionTimestamp() {
return new ClientSessionToUserSessionTimestampMapper();
}
public static Function<Map.Entry<String, Optional<UserSessionTimestamp>>, UserSessionTimestamp> userSessionTimestamp() {
return new UserSessionTimestampMapper();
}
@ -49,23 +45,14 @@ public class Mappers {
return new SessionEntityMapper();
}
public static Function<Map.Entry<String, SessionEntity>, UserSessionEntity> userSessionEntity() {
return new UserSessionEntityMapper();
}
public static Function<Map.Entry<LoginFailureKey, LoginFailureEntity>, LoginFailureKey> loginFailureId() {
return new LoginFailureIdMapper();
}
public static Function<Map.Entry<String, SessionEntity>, String> clientSessionToUserSessionId() {
return new ClientSessionToUserSessionIdMapper();
}
private static class ClientSessionToUserSessionTimestampMapper implements Function<Map.Entry<String, SessionEntity>, UserSessionTimestamp>, Serializable {
@Override
public UserSessionTimestamp apply(Map.Entry<String, SessionEntity> entry) {
SessionEntity e = entry.getValue();
ClientSessionEntity entity = (ClientSessionEntity) e;
return new UserSessionTimestamp(entity.getUserSession(), entity.getTimestamp());
}
}
private static class UserSessionTimestampMapper implements Function<Map.Entry<String, Optional<org.keycloak.models.sessions.infinispan.UserSessionTimestamp>>, org.keycloak.models.sessions.infinispan.UserSessionTimestamp>, Serializable {
@Override
public org.keycloak.models.sessions.infinispan.UserSessionTimestamp apply(Map.Entry<String, Optional<org.keycloak.models.sessions.infinispan.UserSessionTimestamp>> e) {
@ -87,6 +74,13 @@ public class Mappers {
}
}
private static class UserSessionEntityMapper implements Function<Map.Entry<String, SessionEntity>, UserSessionEntity>, Serializable {
@Override
public UserSessionEntity apply(Map.Entry<String, SessionEntity> entry) {
return (UserSessionEntity) entry.getValue();
}
}
private static class LoginFailureIdMapper implements Function<Map.Entry<LoginFailureKey, LoginFailureEntity>, LoginFailureKey>, Serializable {
@Override
public LoginFailureKey apply(Map.Entry<LoginFailureKey, LoginFailureEntity> entry) {
@ -94,12 +88,4 @@ public class Mappers {
}
}
private static class ClientSessionToUserSessionIdMapper implements Function<Map.Entry<String, SessionEntity>, String>, Serializable {
@Override
public String apply(Map.Entry<String, SessionEntity> entry) {
SessionEntity e = entry.getValue();
ClientSessionEntity entity = (ClientSessionEntity) e;
return entity.getUserSession();
}
}
}

View file

@ -33,6 +33,8 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
private String user;
private String client;
private Integer expired;
private Integer expiredRefresh;
@ -53,6 +55,11 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
return this;
}
public UserSessionPredicate client(String clientUUID) {
this.client = clientUUID;
return this;
}
public UserSessionPredicate expired(Integer expired, Integer expiredRefresh) {
this.expired = expired;
this.expiredRefresh = expiredRefresh;
@ -87,6 +94,10 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
return false;
}
if (client != null && (entity.getAuthenticatedClientSessions() == null || !entity.getAuthenticatedClientSessions().containsKey(client))) {
return false;
}
if (brokerSessionId != null && !brokerSessionId.equals(entity.getBrokerSessionId())) {
return false;
}

View file

@ -0,0 +1 @@
org.keycloak.models.sessions.infinispan.InfinispanActionTokenStoreProviderFactory

View file

@ -0,0 +1,18 @@
#
# Copyright 2016 Red Hat, Inc. and/or its affiliates
# and other contributors as indicated by the @author tags.
#
# 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.
#
org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSessionProviderFactory

View file

@ -0,0 +1,18 @@
#
# Copyright 2016 Red Hat, Inc. and/or its affiliates
# and other contributors as indicated by the @author tags.
#
# 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.
#
org.keycloak.models.sessions.infinispan.InfinispanStickySessionEncoderProviderFactory

View file

@ -512,6 +512,26 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
em.flush();
}
@Override
public int getActionTokenGeneratedByAdminLifespan() {
return getAttribute(RealmAttributes.ACTION_TOKEN_GENERATED_BY_ADMIN_LIFESPAN, 12 * 60 * 60);
}
@Override
public void setActionTokenGeneratedByAdminLifespan(int actionTokenGeneratedByAdminLifespan) {
setAttribute(RealmAttributes.ACTION_TOKEN_GENERATED_BY_ADMIN_LIFESPAN, actionTokenGeneratedByAdminLifespan);
}
@Override
public int getActionTokenGeneratedByUserLifespan() {
return getAttribute(RealmAttributes.ACTION_TOKEN_GENERATED_BY_USER_LIFESPAN, getAccessCodeLifespanUserAction());
}
@Override
public void setActionTokenGeneratedByUserLifespan(int actionTokenGeneratedByUserLifespan) {
setAttribute(RealmAttributes.ACTION_TOKEN_GENERATED_BY_USER_LIFESPAN, actionTokenGeneratedByUserLifespan);
}
protected RequiredCredentialModel initRequiredCredentialModel(String type) {
RequiredCredentialModel model = RequiredCredentialModel.BUILT_IN.get(type);
if (model == null) {

View file

@ -26,4 +26,8 @@ public interface RealmAttributes {
String DISPLAY_NAME_HTML = "displayNameHtml";
String ACTION_TOKEN_GENERATED_BY_ADMIN_LIFESPAN = "actionTokenGeneratedByAdminLifespan";
String ACTION_TOKEN_GENERATED_BY_USER_LIFESPAN = "actionTokenGeneratedByUserLifespan";
}

View file

@ -17,14 +17,14 @@
package org.keycloak.models.jpa.session;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.session.PersistentClientSessionAdapter;
import org.keycloak.models.session.PersistentAuthenticatedClientSessionAdapter;
import org.keycloak.models.session.PersistentClientSessionModel;
import org.keycloak.models.session.PersistentUserSessionAdapter;
import org.keycloak.models.session.PersistentUserSessionModel;
@ -34,8 +34,9 @@ import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -68,12 +69,11 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
}
@Override
public void createClientSession(ClientSessionModel clientSession, boolean offline) {
PersistentClientSessionAdapter adapter = new PersistentClientSessionAdapter(clientSession);
public void createClientSession(AuthenticatedClientSessionModel clientSession, boolean offline) {
PersistentAuthenticatedClientSessionAdapter adapter = new PersistentAuthenticatedClientSessionAdapter(clientSession);
PersistentClientSessionModel model = adapter.getUpdatedModel();
PersistentClientSessionEntity entity = new PersistentClientSessionEntity();
entity.setClientSessionId(clientSession.getId());
entity.setClientId(clientSession.getClient().getId());
entity.setTimestamp(clientSession.getTimestamp());
String offlineStr = offlineToString(offline);
@ -121,9 +121,9 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
}
@Override
public void removeClientSession(String clientSessionId, boolean offline) {
public void removeClientSession(String userSessionId, String clientUUID, boolean offline) {
String offlineStr = offlineToString(offline);
PersistentClientSessionEntity sessionEntity = em.find(PersistentClientSessionEntity.class, new PersistentClientSessionEntity.Key(clientSessionId, offlineStr));
PersistentClientSessionEntity sessionEntity = em.find(PersistentClientSessionEntity.class, new PersistentClientSessionEntity.Key(userSessionId, clientUUID, offlineStr));
if (sessionEntity != null) {
em.remove(sessionEntity);
@ -227,14 +227,14 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
int j = 0;
for (UserSessionModel ss : result) {
PersistentUserSessionAdapter userSession = (PersistentUserSessionAdapter) ss;
List<ClientSessionModel> currentClientSessions = userSession.getClientSessions(); // This is empty now and we want to fill it
Map<String, AuthenticatedClientSessionModel> currentClientSessions = userSession.getAuthenticatedClientSessions(); // This is empty now and we want to fill it
boolean next = true;
while (next && j < clientSessions.size()) {
PersistentClientSessionEntity clientSession = clientSessions.get(j);
if (clientSession.getUserSessionId().equals(userSession.getId())) {
PersistentClientSessionAdapter clientSessAdapter = toAdapter(userSession.getRealm(), userSession, clientSession);
currentClientSessions.add(clientSessAdapter);
PersistentAuthenticatedClientSessionAdapter clientSessAdapter = toAdapter(userSession.getRealm(), userSession, clientSession);
currentClientSessions.put(clientSession.getClientId(), clientSessAdapter);
j++;
} else {
next = false;
@ -243,6 +243,7 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
}
}
return result;
}
@ -252,21 +253,20 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
model.setLastSessionRefresh(entity.getLastSessionRefresh());
model.setData(entity.getData());
List<ClientSessionModel> clientSessions = new LinkedList<>();
Map<String, AuthenticatedClientSessionModel> clientSessions = new HashMap<>();
return new PersistentUserSessionAdapter(model, realm, user, clientSessions);
}
private PersistentClientSessionAdapter toAdapter(RealmModel realm, PersistentUserSessionAdapter userSession, PersistentClientSessionEntity entity) {
private PersistentAuthenticatedClientSessionAdapter toAdapter(RealmModel realm, PersistentUserSessionAdapter userSession, PersistentClientSessionEntity entity) {
ClientModel client = realm.getClientById(entity.getClientId());
PersistentClientSessionModel model = new PersistentClientSessionModel();
model.setClientSessionId(entity.getClientSessionId());
model.setClientId(entity.getClientId());
model.setUserSessionId(userSession.getId());
model.setUserId(userSession.getUser().getId());
model.setTimestamp(entity.getTimestamp());
model.setData(entity.getData());
return new PersistentClientSessionAdapter(model, realm, client, userSession);
return new PersistentAuthenticatedClientSessionAdapter(model, realm, client, userSession);
}
@Override

View file

@ -20,7 +20,6 @@ package org.keycloak.models.jpa.session;
import org.keycloak.Config;
import org.keycloak.connections.jpa.JpaConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.session.UserSessionPersisterProvider;
import org.keycloak.models.session.UserSessionPersisterProviderFactory;

View file

@ -45,12 +45,10 @@ import java.io.Serializable;
public class PersistentClientSessionEntity {
@Id
@Column(name="CLIENT_SESSION_ID", length = 36)
protected String clientSessionId;
@Column(name = "USER_SESSION_ID", length = 36)
protected String userSessionId;
@Id
@Column(name="CLIENT_ID", length = 36)
protected String clientId;
@ -64,14 +62,6 @@ public class PersistentClientSessionEntity {
@Column(name="DATA")
protected String data;
public String getClientSessionId() {
return clientSessionId;
}
public void setClientSessionId(String clientSessionId) {
this.clientSessionId = clientSessionId;
}
public String getUserSessionId() {
return userSessionId;
}
@ -114,20 +104,27 @@ public class PersistentClientSessionEntity {
public static class Key implements Serializable {
protected String clientSessionId;
protected String userSessionId;
protected String clientId;
protected String offline;
public Key() {
}
public Key(String clientSessionId, String offline) {
this.clientSessionId = clientSessionId;
public Key(String userSessionId, String clientId, String offline) {
this.userSessionId = userSessionId;
this.clientId = clientId;
this.offline = offline;
}
public String getClientSessionId() {
return clientSessionId;
public String getUserSessionId() {
return userSessionId;
}
public String getClientId() {
return clientId;
}
public String getOffline() {
@ -141,7 +138,8 @@ public class PersistentClientSessionEntity {
Key key = (Key) o;
if (this.clientSessionId != null ? !this.clientSessionId.equals(key.clientSessionId) : key.clientSessionId != null) return false;
if (this.userSessionId != null ? !this.userSessionId.equals(key.userSessionId) : key.userSessionId != null) return false;
if (this.clientId != null ? !this.clientId.equals(key.clientId) : key.clientId != null) return false;
if (this.offline != null ? !this.offline.equals(key.offline) : key.offline != null) return false;
return true;
@ -149,7 +147,8 @@ public class PersistentClientSessionEntity {
@Override
public int hashCode() {
int result = this.clientSessionId != null ? this.clientSessionId.hashCode() : 0;
int result = this.userSessionId != null ? this.userSessionId.hashCode() : 0;
result = 37 * result + (this.clientId != null ? this.clientId.hashCode() : 0);
result = 31 * result + (this.offline != null ? this.offline.hashCode() : 0);
return result;
}

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ 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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet author="mposolda@redhat.com" id="3.2.0">
<dropPrimaryKey constraintName="CONSTRAINT_OFFL_CL_SES_PK2" tableName="OFFLINE_CLIENT_SESSION" />
<dropColumn tableName="OFFLINE_CLIENT_SESSION" columnName="CLIENT_SESSION_ID" />
<addPrimaryKey columnNames="USER_SESSION_ID,CLIENT_ID, OFFLINE_FLAG" constraintName="CONSTRAINT_OFFL_CL_SES_PK3" tableName="OFFLINE_CLIENT_SESSION"/>
</changeSet>
</databaseChangeLog>

View file

@ -47,4 +47,5 @@
<include file="META-INF/jpa-changelog-2.5.0.xml"/>
<include file="META-INF/jpa-changelog-2.5.1.xml"/>
<include file="META-INF/jpa-changelog-3.0.0.xml"/>
<include file="META-INF/jpa-changelog-3.2.0.xml"/>
</databaseChangeLog>

View file

@ -18,10 +18,10 @@
package org.keycloak.authentication;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.sessions.AuthenticationSessionModel;
import java.net.URI;
@ -62,7 +62,7 @@ public interface AuthenticationFlowContext extends AbstractAuthenticationFlowCon
*
* @return
*/
ClientSessionModel getClientSession();
AuthenticationSessionModel getAuthenticationSession();
/**
* Create a Freemarker form builder that presets the user, action URI, and a generated access code
@ -80,11 +80,19 @@ public interface AuthenticationFlowContext extends AbstractAuthenticationFlowCon
URI getActionUrl(String code);
/**
* Get the action URL for the required action. This auto-generates the access code.
* Get the action URL for the action token executor.
*
* @param tokenString String representation (JWT) of action token
* @return
*/
URI getActionTokenUrl(String tokenString);
/**
* Get the refresh URL for the required action.
*
* @return
*/
URI getActionUrl();
URI getRefreshExecutionUrl();
/**
* End the flow and redirect browser based on protocol specific respones. This should only be executed
@ -99,6 +107,12 @@ public interface AuthenticationFlowContext extends AbstractAuthenticationFlowCon
*/
void resetFlow();
/**
* Reset the current flow to the beginning and restarts it. Allows to add additional listener, which is triggered after flow restarted
*
*/
void resetFlow(Runnable afterResetListener);
/**
* Fork the current flow. The client session will be cloned and set to point at the realm's browser login flow. The Response will be the result
* of this fork. The previous flow will still be set at the current execution. This is used by reset password when it sends an email.

View file

@ -22,10 +22,10 @@ import org.keycloak.common.ClientConnection;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.sessions.AuthenticationSessionModel;
import javax.ws.rs.core.UriInfo;
@ -79,11 +79,11 @@ public interface FormContext {
RealmModel getRealm();
/**
* ClientSessionModel attached to this flow
* AuthenticationSessionModel attached to this flow
*
* @return
*/
ClientSessionModel getClientSession();
AuthenticationSessionModel getAuthenticationSession();
/**
* Information about the IP address from the connecting HTTP client.

View file

@ -21,11 +21,10 @@ import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.common.ClientConnection;
import org.keycloak.events.EventBuilder;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.sessions.AuthenticationSessionModel;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
@ -90,8 +89,7 @@ public interface RequiredActionContext {
*/
UserModel getUser();
RealmModel getRealm();
ClientSessionModel getClientSession();
UserSessionModel getUserSession();
AuthenticationSessionModel getAuthenticationSession();
ClientConnection getConnection();
UriInfo getUriInfo();
KeycloakSession getSession();

View file

@ -35,4 +35,13 @@ public interface RequiredActionFactory extends ProviderFactory<RequiredActionPro
* @return
*/
String getDisplayText();
/**
* Flag indicating whether the execution of the required action by the same circumstances
* (e.g. by one and the same action token) should only be permitted once.
* @return
*/
default boolean isOneTimeAction() {
return false;
}
}

View file

@ -17,12 +17,12 @@
package org.keycloak.broker.provider;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.sessions.AuthenticationSessionModel;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
@ -75,7 +75,7 @@ public abstract class AbstractIdentityProvider<C extends IdentityProviderModel>
}
@Override
public void attachUserSession(UserSessionModel userSession, ClientSessionModel clientSession, BrokeredIdentityContext context) {
public void authenticationFinished(AuthenticationSessionModel authSession, BrokeredIdentityContext context) {
}

View file

@ -17,9 +17,9 @@
package org.keycloak.broker.provider;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.sessions.AuthenticationSessionModel;
import javax.ws.rs.core.UriInfo;
@ -34,16 +34,16 @@ public class AuthenticationRequest {
private final HttpRequest httpRequest;
private final RealmModel realm;
private final String redirectUri;
private final ClientSessionModel clientSession;
private final AuthenticationSessionModel authSession;
public AuthenticationRequest(KeycloakSession session, RealmModel realm, ClientSessionModel clientSession, HttpRequest httpRequest, UriInfo uriInfo, String state, String redirectUri) {
public AuthenticationRequest(KeycloakSession session, RealmModel realm, AuthenticationSessionModel authSession, HttpRequest httpRequest, UriInfo uriInfo, String state, String redirectUri) {
this.session = session;
this.realm = realm;
this.httpRequest = httpRequest;
this.uriInfo = uriInfo;
this.state = state;
this.redirectUri = redirectUri;
this.clientSession = clientSession;
this.authSession = authSession;
}
public KeycloakSession getSession() {
@ -76,7 +76,7 @@ public class AuthenticationRequest {
return this.redirectUri;
}
public ClientSessionModel getClientSession() {
return this.clientSession;
public AuthenticationSessionModel getAuthenticationSession() {
return this.authSession;
}
}

View file

@ -16,9 +16,9 @@
*/
package org.keycloak.broker.provider;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.Constants;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.sessions.AuthenticationSessionModel;
import java.util.ArrayList;
import java.util.HashMap;
@ -46,7 +46,7 @@ public class BrokeredIdentityContext {
private IdentityProviderModel idpConfig;
private IdentityProvider idp;
private Map<String, Object> contextData = new HashMap<>();
private ClientSessionModel clientSession;
private AuthenticationSessionModel authenticationSession;
public BrokeredIdentityContext(String id) {
if (id == null) {
@ -190,12 +190,12 @@ public class BrokeredIdentityContext {
this.lastName = lastName;
}
public ClientSessionModel getClientSession() {
return clientSession;
public AuthenticationSessionModel getAuthenticationSession() {
return authenticationSession;
}
public void setClientSession(ClientSessionModel clientSession) {
this.clientSession = clientSession;
public void setAuthenticationSession(AuthenticationSessionModel authenticationSession) {
this.authenticationSession = authenticationSession;
}
public void setName(String name) {

View file

@ -17,7 +17,6 @@
package org.keycloak.broker.provider;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
@ -25,6 +24,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.provider.Provider;
import org.keycloak.sessions.AuthenticationSessionModel;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
@ -51,7 +51,7 @@ public interface IdentityProvider<C extends IdentityProviderModel> extends Provi
void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, BrokeredIdentityContext context);
void attachUserSession(UserSessionModel userSession, ClientSessionModel clientSession, BrokeredIdentityContext context);
void authenticationFinished(AuthenticationSessionModel authSession, BrokeredIdentityContext context);
void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context);
void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context);

View file

@ -25,6 +25,7 @@ public interface Details {
String EMAIL = "email";
String PREVIOUS_EMAIL = "previous_email";
String UPDATED_EMAIL = "updated_email";
String ACTION = "action";
String CODE_ID = "code_id";
String REDIRECT_URI = "redirect_uri";
String RESPONSE_TYPE = "response_type";
@ -63,4 +64,6 @@ public interface Details {
String CLIENT_REGISTRATION_POLICY = "client_registration_policy";
String EXISTING_USER = "previous_user";
}

View file

@ -37,6 +37,7 @@ public interface Errors {
String USER_DISABLED = "user_disabled";
String USER_TEMPORARILY_DISABLED = "user_temporarily_disabled";
String INVALID_USER_CREDENTIALS = "invalid_user_credentials";
String DIFFERENT_USER_AUTHENTICATED = "different_user_authenticated";
String USERNAME_MISSING = "username_missing";
String USERNAME_IN_USE = "username_in_use";

View file

@ -79,6 +79,9 @@ public enum EventType {
RESET_PASSWORD(true),
RESET_PASSWORD_ERROR(true),
RESTART_AUTHENTICATION(true),
RESTART_AUTHENTICATION_ERROR(true),
INVALID_SIGNATURE(false),
INVALID_SIGNATURE_ERROR(false),
REGISTER_NODE(false),
@ -89,6 +92,8 @@ public enum EventType {
USER_INFO_REQUEST(false),
USER_INFO_REQUEST_ERROR(false),
IDENTITY_PROVIDER_LINK_ACCOUNT(true),
IDENTITY_PROVIDER_LINK_ACCOUNT_ERROR(true),
IDENTITY_PROVIDER_LOGIN(false),
IDENTITY_PROVIDER_LOGIN_ERROR(false),
IDENTITY_PROVIDER_FIRST_LOGIN(true),
@ -105,6 +110,8 @@ public enum EventType {
CUSTOM_REQUIRED_ACTION_ERROR(true),
EXECUTE_ACTIONS(true),
EXECUTE_ACTIONS_ERROR(true),
EXECUTE_ACTION_TOKEN(true),
EXECUTE_ACTION_TOKEN_ERROR(true),
CLIENT_INFO(false),
CLIENT_INFO_ERROR(false),
@ -124,6 +131,10 @@ public enum EventType {
this.saveByDefault = saveByDefault;
}
/**
* Determines whether this event is stored when the admin has not set a specific set of event types to save.
* @return
*/
public boolean isSaveByDefault() {
return saveByDefault;
}

View file

@ -24,6 +24,6 @@ public enum LoginFormsPages {
LOGIN, LOGIN_TOTP, LOGIN_CONFIG_TOTP, LOGIN_VERIFY_EMAIL,
LOGIN_IDP_LINK_CONFIRM, LOGIN_IDP_LINK_EMAIL,
OAUTH_GRANT, LOGIN_RESET_PASSWORD, LOGIN_UPDATE_PASSWORD, REGISTER, INFO, ERROR, LOGIN_UPDATE_PROFILE, CODE;
OAUTH_GRANT, LOGIN_RESET_PASSWORD, LOGIN_UPDATE_PASSWORD, REGISTER, INFO, ERROR, LOGIN_UPDATE_PROFILE, LOGIN_PAGE_EXPIRED, CODE;
}

View file

@ -17,12 +17,12 @@
package org.keycloak.forms.login;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.provider.Provider;
import org.keycloak.sessions.AuthenticationSessionModel;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
@ -68,16 +68,16 @@ public interface LoginFormsProvider extends Provider {
public Response createIdpLinkEmailPage();
public Response createLoginExpiredPage();
public Response createErrorPage();
public Response createOAuthGrant(ClientSessionModel clientSessionModel);
public Response createOAuthGrant();
public Response createCode();
public LoginFormsProvider setClientSessionCode(String accessCode);
public LoginFormsProvider setClientSession(ClientSessionModel clientSession);
public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String,RoleModel> resourceRolesRequested, List<ProtocolMapperModel> protocolMappers);
public LoginFormsProvider setAccessRequest(String message);

View file

@ -0,0 +1,54 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models;
import org.keycloak.provider.Provider;
import java.util.Map;
/**
* Internal action token store provider.
* @author hmlnarik
*/
public interface ActionTokenStoreProvider extends Provider {
/**
* Adds a given token to token store.
* @param actionTokenKey key
* @param notes Optional notes to be stored with the token. Can be {@code null} in which case it is treated as an empty map.
*/
void put(ActionTokenKeyModel actionTokenKey, Map<String, String> notes);
/**
* Returns token corresponding to the given key from the internal action token store
* @param key key
* @return {@code null} if no token is found for given key and nonce, value otherwise
*/
ActionTokenValueModel get(ActionTokenKeyModel key);
/**
* Removes token corresponding to the given key from the internal action token store, and returns the stored value
* @param key key
* @param nonce nonce that must match a given key
* @return {@code null} if no token is found for given key and nonce, value otherwise
*/
ActionTokenValueModel remove(ActionTokenKeyModel key);
void removeAll(String userId, String actionId);
}

View file

@ -0,0 +1,27 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models;
import org.keycloak.provider.ProviderFactory;
/**
*
* @author hmlnarik
*/
public interface ActionTokenStoreProviderFactory extends ProviderFactory<ActionTokenStoreProvider> {
}

View file

@ -0,0 +1,50 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models;
import org.keycloak.provider.*;
/**
* SPI for action tokens.
*
* @author hmlnarik
*/
public class ActionTokenStoreSpi implements Spi {
public static final String NAME = "actionToken";
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return NAME;
}
@Override
public Class<? extends Provider> getProviderClass() {
return ActionTokenStoreProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return ActionTokenStoreProviderFactory.class;
}
}

View file

@ -18,8 +18,8 @@
package org.keycloak.models.session;
import org.keycloak.Config;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
@ -70,7 +70,7 @@ public class DisabledUserSessionPersisterProvider implements UserSessionPersiste
}
@Override
public void createClientSession(ClientSessionModel clientSession, boolean offline) {
public void createClientSession(AuthenticatedClientSessionModel clientSession, boolean offline) {
}
@ -85,7 +85,7 @@ public class DisabledUserSessionPersisterProvider implements UserSessionPersiste
}
@Override
public void removeClientSession(String clientSessionId, boolean offline) {
public void removeClientSession(String userSessionId, String clientUUID, boolean offline) {
}

View file

@ -18,25 +18,23 @@
package org.keycloak.models.session;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.util.JsonSerialization;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class PersistentClientSessionAdapter implements ClientSessionModel {
public class PersistentAuthenticatedClientSessionAdapter implements AuthenticatedClientSessionModel {
private final PersistentClientSessionModel model;
private final RealmModel realm;
@ -45,23 +43,18 @@ public class PersistentClientSessionAdapter implements ClientSessionModel {
private PersistentClientSessionData data;
public PersistentClientSessionAdapter(ClientSessionModel clientSession) {
public PersistentAuthenticatedClientSessionAdapter(AuthenticatedClientSessionModel clientSession) {
data = new PersistentClientSessionData();
data.setAction(clientSession.getAction());
data.setAuthMethod(clientSession.getAuthMethod());
data.setExecutionStatus(clientSession.getExecutionStatus());
data.setAuthMethod(clientSession.getProtocol());
data.setNotes(clientSession.getNotes());
data.setProtocolMappers(clientSession.getProtocolMappers());
data.setRedirectUri(clientSession.getRedirectUri());
data.setRoles(clientSession.getRoles());
data.setUserSessionNotes(clientSession.getUserSessionNotes());
model = new PersistentClientSessionModel();
model.setClientId(clientSession.getClient().getId());
model.setClientSessionId(clientSession.getId());
if (clientSession.getAuthenticatedUser() != null) {
model.setUserId(clientSession.getAuthenticatedUser().getId());
}
model.setUserId(clientSession.getUserSession().getUser().getId());
model.setUserSessionId(clientSession.getUserSession().getId());
model.setTimestamp(clientSession.getTimestamp());
@ -70,7 +63,7 @@ public class PersistentClientSessionAdapter implements ClientSessionModel {
userSession = clientSession.getUserSession();
}
public PersistentClientSessionAdapter(PersistentClientSessionModel model, RealmModel realm, ClientModel client, UserSessionModel userSession) {
public PersistentAuthenticatedClientSessionAdapter(PersistentClientSessionModel model, RealmModel realm, ClientModel client, UserSessionModel userSession) {
this.model = model;
this.realm = realm;
this.client = client;
@ -104,7 +97,7 @@ public class PersistentClientSessionAdapter implements ClientSessionModel {
@Override
public String getId() {
return model.getClientSessionId();
return null;
}
@Override
@ -178,37 +171,12 @@ public class PersistentClientSessionAdapter implements ClientSessionModel {
}
@Override
public Map<String, ExecutionStatus> getExecutionStatus() {
return getData().getExecutionStatus();
}
@Override
public void setExecutionStatus(String authenticator, ExecutionStatus status) {
getData().getExecutionStatus().put(authenticator, status);
}
@Override
public void clearExecutionStatus() {
getData().getExecutionStatus().clear();
}
@Override
public UserModel getAuthenticatedUser() {
return userSession.getUser();
}
@Override
public void setAuthenticatedUser(UserModel user) {
throw new IllegalStateException("Not supported setAuthenticatedUser");
}
@Override
public String getAuthMethod() {
public String getProtocol() {
return getData().getAuthMethod();
}
@Override
public void setAuthMethod(String method) {
public void setProtocol(String method) {
getData().setAuthMethod(method);
}
@ -222,7 +190,7 @@ public class PersistentClientSessionAdapter implements ClientSessionModel {
public void setNote(String name, String value) {
PersistentClientSessionData entity = getData();
if (entity.getNotes() == null) {
entity.setNotes(new HashMap<String, String>());
entity.setNotes(new HashMap<>());
}
entity.getNotes().put(name, value);
}
@ -242,59 +210,12 @@ public class PersistentClientSessionAdapter implements ClientSessionModel {
return entity.getNotes();
}
@Override
public Set<String> getRequiredActions() {
return getData().getRequiredActions();
}
@Override
public void addRequiredAction(String action) {
getData().getRequiredActions().add(action);
}
@Override
public void removeRequiredAction(String action) {
getData().getRequiredActions().remove(action);
}
@Override
public void addRequiredAction(UserModel.RequiredAction action) {
addRequiredAction(action.name());
}
@Override
public void removeRequiredAction(UserModel.RequiredAction action) {
removeRequiredAction(action.name());
}
@Override
public void setUserSessionNote(String name, String value) {
PersistentClientSessionData entity = getData();
if (entity.getUserSessionNotes() == null) {
entity.setUserSessionNotes(new HashMap<String, String>());
}
entity.getUserSessionNotes().put(name, value);
}
@Override
public Map<String, String> getUserSessionNotes() {
PersistentClientSessionData entity = getData();
if (entity.getUserSessionNotes() == null || entity.getUserSessionNotes().isEmpty()) return Collections.emptyMap();
return entity.getUserSessionNotes();
}
@Override
public void clearUserSessionNotes() {
PersistentClientSessionData entity = getData();
entity.setUserSessionNotes(new HashMap<String, String>());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof ClientSessionModel)) return false;
if (o == null || !(o instanceof AuthenticatedClientSessionModel)) return false;
ClientSessionModel that = (ClientSessionModel) o;
AuthenticatedClientSessionModel that = (AuthenticatedClientSessionModel) o;
return that.getId().equals(getId());
}
@ -320,17 +241,17 @@ public class PersistentClientSessionAdapter implements ClientSessionModel {
@JsonProperty("notes")
private Map<String, String> notes;
@JsonProperty("userSessionNotes")
private Map<String, String> userSessionNotes;
@JsonProperty("executionStatus")
private Map<String, ClientSessionModel.ExecutionStatus> executionStatus = new HashMap<>();
@JsonProperty("action")
private String action;
// TODO: Keeping those just for backwards compatibility. @JsonIgnoreProperties doesn't work on Wildfly - probably due to classloading issues
@JsonProperty("userSessionNotes")
private Map<String, String> userSessionNotes;
@JsonProperty("executionStatus")
private Map<String, Object> executionStatus;
@JsonProperty("requiredActions")
private Set<String> requiredActions = new HashSet<>();
private Set<String> requiredActions;
public String getAuthMethod() {
return authMethod;
@ -372,6 +293,14 @@ public class PersistentClientSessionAdapter implements ClientSessionModel {
this.notes = notes;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public Map<String, String> getUserSessionNotes() {
return userSessionNotes;
}
@ -380,22 +309,14 @@ public class PersistentClientSessionAdapter implements ClientSessionModel {
this.userSessionNotes = userSessionNotes;
}
public Map<String, ClientSessionModel.ExecutionStatus> getExecutionStatus() {
public Map<String, Object> getExecutionStatus() {
return executionStatus;
}
public void setExecutionStatus(Map<String, ClientSessionModel.ExecutionStatus> executionStatus) {
public void setExecutionStatus(Map<String, Object> executionStatus) {
this.executionStatus = executionStatus;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public Set<String> getRequiredActions() {
return requiredActions;
}

View file

@ -22,20 +22,12 @@ package org.keycloak.models.session;
*/
public class PersistentClientSessionModel {
private String clientSessionId;
private String userSessionId;
private String clientId;
private String userId;
private int timestamp;
private String data;
public String getClientSessionId() {
return clientSessionId;
}
public void setClientSessionId(String clientSessionId) {
this.clientSessionId = clientSessionId;
}
public String getUserSessionId() {
return userSessionId;

View file

@ -18,7 +18,7 @@
package org.keycloak.models.session;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@ -27,7 +27,6 @@ import org.keycloak.util.JsonSerialization;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@ -38,7 +37,7 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
private final PersistentUserSessionModel model;
private final UserModel user;
private final RealmModel realm;
private final List<ClientSessionModel> clientSessions;
private final Map<String, AuthenticatedClientSessionModel> authenticatedClientSessions;
private PersistentUserSessionData data;
@ -51,7 +50,9 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
data.setNotes(other.getNotes());
data.setRememberMe(other.isRememberMe());
data.setStarted(other.getStarted());
data.setState(other.getState());
if (other.getState() != null) {
data.setState(other.getState().toString());
}
this.model = new PersistentUserSessionModel();
this.model.setUserSessionId(other.getId());
@ -59,14 +60,14 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
this.user = other.getUser();
this.realm = other.getRealm();
this.clientSessions = other.getClientSessions();
this.authenticatedClientSessions = other.getAuthenticatedClientSessions();
}
public PersistentUserSessionAdapter(PersistentUserSessionModel model, RealmModel realm, UserModel user, List<ClientSessionModel> clientSessions) {
public PersistentUserSessionAdapter(PersistentUserSessionModel model, RealmModel realm, UserModel user, Map<String, AuthenticatedClientSessionModel> clientSessions) {
this.model = model;
this.realm = realm;
this.user = user;
this.clientSessions = clientSessions;
this.authenticatedClientSessions = clientSessions;
}
// Lazily init data
@ -114,6 +115,11 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
return user;
}
@Override
public void setUser(UserModel user) {
throw new IllegalStateException("Not supported");
}
@Override
public RealmModel getRealm() {
return realm;
@ -155,8 +161,8 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
}
@Override
public List<ClientSessionModel> getClientSessions() {
return clientSessions;
public Map<String, AuthenticatedClientSessionModel> getAuthenticatedClientSessions() {
return authenticatedClientSessions;
}
@Override
@ -188,12 +194,29 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
@Override
public State getState() {
return getData().getState();
String state = getData().getState();
if (state == null) {
return null;
}
// Migration to Keycloak 3.2
if (state.equals("LOGGING_IN")) {
return State.LOGGED_IN;
}
return State.valueOf(state);
}
@Override
public void setState(State state) {
getData().setState(state);
String stateStr = state==null ? null : state.toString();
getData().setState(stateStr);
}
@Override
public void restartSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
throw new IllegalStateException("Not supported");
}
@Override
@ -234,7 +257,7 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
private Map<String, String> notes;
@JsonProperty("state")
private State state;
private String state;
public String getBrokerSessionId() {
return brokerSessionId;
@ -292,11 +315,11 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
this.notes = notes;
}
public State getState() {
public String getState() {
return state;
}
public void setState(State state) {
public void setState(String state) {
this.state = state;
}
}

View file

@ -17,8 +17,8 @@
package org.keycloak.models.session;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
@ -35,7 +35,7 @@ public interface UserSessionPersisterProvider extends Provider {
void createUserSession(UserSessionModel userSession, boolean offline);
// Assuming that corresponding userSession is already persisted
void createClientSession(ClientSessionModel clientSession, boolean offline);
void createClientSession(AuthenticatedClientSessionModel clientSession, boolean offline);
void updateUserSession(UserSessionModel userSession, boolean offline);
@ -43,7 +43,7 @@ public interface UserSessionPersisterProvider extends Provider {
void removeUserSession(String userSessionId, boolean offline);
// Called during revoke. It will remove userSession too if this was last clientSession attached to it
void removeClientSession(String clientSessionId, boolean offline);
void removeClientSession(String userSessionId, String clientUUID, boolean offline);
void onRealmRemoved(RealmModel realm);
void onClientRemoved(RealmModel realm, ClientModel client);

View file

@ -45,8 +45,8 @@ import org.keycloak.events.admin.AuthDetails;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.ClientTemplateModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.GroupModel;
@ -303,6 +303,8 @@ public class ModelToRepresentation {
rep.setAccessCodeLifespan(realm.getAccessCodeLifespan());
rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction());
rep.setAccessCodeLifespanLogin(realm.getAccessCodeLifespanLogin());
rep.setActionTokenGeneratedByAdminLifespan(realm.getActionTokenGeneratedByAdminLifespan());
rep.setActionTokenGeneratedByUserLifespan(realm.getActionTokenGeneratedByUserLifespan());
rep.setSmtpServer(new HashMap<>(realm.getSmtpConfig()));
rep.setBrowserSecurityHeaders(realm.getBrowserSecurityHeaders());
rep.setAccountTheme(realm.getAccountTheme());
@ -485,7 +487,7 @@ public class ModelToRepresentation {
rep.setUsername(session.getUser().getUsername());
rep.setUserId(session.getUser().getId());
rep.setIpAddress(session.getIpAddress());
for (ClientSessionModel clientSession : session.getClientSessions()) {
for (AuthenticatedClientSessionModel clientSession : session.getAuthenticatedClientSessions().values()) {
ClientModel client = clientSession.getClient();
rep.getClients().put(client.getId(), client.getClientId());
}

View file

@ -189,6 +189,14 @@ public class RepresentationToModel {
newRealm.setAccessCodeLifespanLogin(rep.getAccessCodeLifespanLogin());
else newRealm.setAccessCodeLifespanLogin(1800);
if (rep.getActionTokenGeneratedByAdminLifespan() != null)
newRealm.setActionTokenGeneratedByAdminLifespan(rep.getActionTokenGeneratedByAdminLifespan());
else newRealm.setActionTokenGeneratedByAdminLifespan(12 * 60 * 60);
if (rep.getActionTokenGeneratedByUserLifespan() != null)
newRealm.setActionTokenGeneratedByUserLifespan(rep.getActionTokenGeneratedByUserLifespan());
else newRealm.setActionTokenGeneratedByUserLifespan(newRealm.getAccessCodeLifespanUserAction());
if (rep.getSslRequired() != null)
newRealm.setSslRequired(SslRequired.valueOf(rep.getSslRequired().toUpperCase()));
if (rep.isRegistrationAllowed() != null) newRealm.setRegistrationAllowed(rep.isRegistrationAllowed());
@ -812,6 +820,10 @@ public class RepresentationToModel {
realm.setAccessCodeLifespanUserAction(rep.getAccessCodeLifespanUserAction());
if (rep.getAccessCodeLifespanLogin() != null)
realm.setAccessCodeLifespanLogin(rep.getAccessCodeLifespanLogin());
if (rep.getActionTokenGeneratedByAdminLifespan() != null)
realm.setActionTokenGeneratedByAdminLifespan(rep.getActionTokenGeneratedByAdminLifespan());
if (rep.getActionTokenGeneratedByUserLifespan() != null)
realm.setActionTokenGeneratedByUserLifespan(rep.getActionTokenGeneratedByUserLifespan());
if (rep.getNotBefore() != null) realm.setNotBefore(rep.getNotBefore());
if (rep.getRevokeRefreshToken() != null) realm.setRevokeRefreshToken(rep.getRevokeRefreshToken());
if (rep.getAccessTokenLifespan() != null) realm.setAccessTokenLifespan(rep.getAccessTokenLifespan());

View file

@ -18,12 +18,12 @@
package org.keycloak.protocol;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.provider.Provider;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.sessions.AuthenticationSessionModel;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
@ -66,19 +66,19 @@ public interface LoginProtocol extends Provider {
LoginProtocol setEventBuilder(EventBuilder event);
Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode);
Response authenticated(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession);
Response sendError(ClientSessionModel clientSession, Error error);
Response sendError(AuthenticationSessionModel authSession, Error error);
void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession);
Response frontchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession);
void backchannelLogout(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession);
Response frontchannelLogout(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession);
Response finishLogout(UserSessionModel userSession);
/**
* @param userSession
* @param clientSession
* @param authSession
* @return true if SSO cookie authentication can't be used. User will need to "actively" reauthenticate
*/
boolean requireReauthentication(UserSessionModel userSession, ClientSessionModel clientSession);
boolean requireReauthentication(UserSessionModel userSession, AuthenticationSessionModel authSession);
}

View file

@ -0,0 +1,26 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.sessions;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface AuthenticationSessionProviderFactory extends ProviderFactory<AuthenticationSessionProvider> {
}

View file

@ -0,0 +1,49 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.sessions;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class AuthenticationSessionSpi implements Spi {
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return "authenticationSessions";
}
@Override
public Class<? extends Provider> getProviderClass() {
return AuthenticationSessionProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return AuthenticationSessionProviderFactory.class;
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.sessions;
import org.keycloak.provider.Provider;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface StickySessionEncoderProvider extends Provider {
String encodeSessionId(String sessionId);
String decodeSessionId(String encodedSessionId);
}

View file

@ -0,0 +1,26 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.sessions;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface StickySessionEncoderProviderFactory extends ProviderFactory<StickySessionEncoderProvider> {
}

View file

@ -0,0 +1,48 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.sessions;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class StickySessionEncoderSpi implements Spi {
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return "stickySessionEncoder";
}
@Override
public Class<? extends Provider> getProviderClass() {
return StickySessionEncoderProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return StickySessionEncoderProviderFactory.class;
}
}

View file

@ -19,6 +19,7 @@ org.keycloak.provider.ExceptionConverterSpi
org.keycloak.storage.UserStorageProviderSpi
org.keycloak.storage.federated.UserFederatedStorageProviderSpi
org.keycloak.models.RealmSpi
org.keycloak.models.ActionTokenStoreSpi
org.keycloak.models.UserSessionSpi
org.keycloak.models.UserSpi
org.keycloak.models.session.UserSessionPersisterSpi
@ -32,6 +33,8 @@ org.keycloak.timer.TimerSpi
org.keycloak.scripting.ScriptingSpi
org.keycloak.services.managers.BruteForceProtectorSpi
org.keycloak.services.resource.RealmResourceSPI
org.keycloak.sessions.AuthenticationSessionSpi
org.keycloak.sessions.StickySessionEncoderSpi
org.keycloak.protocol.ClientInstallationSpi
org.keycloak.protocol.LoginProtocolSpi
org.keycloak.protocol.ProtocolMapperSpi

View file

@ -0,0 +1,46 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models;
import java.util.UUID;
/**
*
* @author hmlnarik
*/
public interface ActionTokenKeyModel {
/**
* @return ID of user which this token is for.
*/
String getUserId();
/**
* @return Action identifier this token is for.
*/
String getActionId();
/**
* Returns absolute number of seconds since the epoch in UTC timezone when the token expires.
*/
int getExpiration();
/**
* @return Single-use random value used for verification whether the relevant action is allowed.
*/
UUID getActionVerificationNonce();
}

View file

@ -0,0 +1,39 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models;
import java.util.Map;
import java.util.UUID;
/**
* This model represents contents of an action token shareable among Keycloak instances in the cluster.
* @author hmlnarik
*/
public interface ActionTokenValueModel {
/**
* Returns unmodifiable map of all notes.
* @return see description. Returns empty map if no note is set, never returns {@code null}.
*/
Map<String,String> getNotes();
/**
* Returns value of the given note (or {@code null} when no note of this name is present)
* @return see description
*/
String getNote(String name);
}

View file

@ -0,0 +1,37 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.models;
import java.util.Map;
import org.keycloak.sessions.CommonClientSessionModel;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface AuthenticatedClientSessionModel extends CommonClientSessionModel {
void setUserSession(UserSessionModel userSession);
UserSessionModel getUserSession();
String getNote(String name);
void setNote(String name, String value);
void removeNote(String name);
Map<String, String> getNotes();
}

Some files were not shown because too many files have changed in this diff Show more