KEYCLOAK-4929
This commit is contained in:
commit
2cac8b1bb7
335 changed files with 13075 additions and 4297 deletions
|
@ -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) {
|
||||
|
|
|
@ -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 + "\"");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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 ***
|
|
@ -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 ***
|
|
@ -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 ***
|
|
@ -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 ***
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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": {}
|
||||
}
|
|
@ -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`.
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
|
@ -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() {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.models.sessions.infinispan.InfinispanActionTokenStoreProviderFactory
|
|
@ -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
|
|
@ -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
|
|
@ -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) {
|
||||
|
|
|
@ -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";
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
}
|
|
@ -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> {
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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> {
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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
Loading…
Reference in a new issue