KEYCLOAK-5293 Add notBefore to user
This commit is contained in:
parent
8d3384666a
commit
fe5891fbdb
27 changed files with 321 additions and 21 deletions
|
@ -55,6 +55,7 @@ public class UserRepresentation {
|
||||||
protected List<String> realmRoles;
|
protected List<String> realmRoles;
|
||||||
protected Map<String, List<String>> clientRoles;
|
protected Map<String, List<String>> clientRoles;
|
||||||
protected List<UserConsentRepresentation> clientConsents;
|
protected List<UserConsentRepresentation> clientConsents;
|
||||||
|
protected Integer notBefore;
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
protected Map<String, List<String>> applicationRoles;
|
protected Map<String, List<String>> applicationRoles;
|
||||||
|
@ -216,6 +217,14 @@ public class UserRepresentation {
|
||||||
this.clientConsents = clientConsents;
|
this.clientConsents = clientConsents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getNotBefore() {
|
||||||
|
return notBefore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotBefore(Integer notBefore) {
|
||||||
|
this.notBefore = notBefore;
|
||||||
|
}
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public Map<String, List<String>> getApplicationRoles() {
|
public Map<String, List<String>> getApplicationRoles() {
|
||||||
return applicationRoles;
|
return applicationRoles;
|
||||||
|
|
|
@ -334,6 +334,8 @@ public class UserCacheSession implements UserCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected UserModel cacheUser(RealmModel realm, UserModel delegate, Long revision) {
|
protected UserModel cacheUser(RealmModel realm, UserModel delegate, Long revision) {
|
||||||
|
int notBefore = getDelegate().getNotBeforeOfUser(realm, delegate);
|
||||||
|
|
||||||
StorageId storageId = new StorageId(delegate.getId());
|
StorageId storageId = new StorageId(delegate.getId());
|
||||||
CachedUser cached = null;
|
CachedUser cached = null;
|
||||||
if (!storageId.isLocal()) {
|
if (!storageId.isLocal()) {
|
||||||
|
@ -343,7 +345,7 @@ public class UserCacheSession implements UserCache {
|
||||||
if (policy != null && policy == UserStorageProviderModel.CachePolicy.NO_CACHE) {
|
if (policy != null && policy == UserStorageProviderModel.CachePolicy.NO_CACHE) {
|
||||||
return delegate;
|
return delegate;
|
||||||
}
|
}
|
||||||
cached = new CachedUser(revision, realm, delegate);
|
cached = new CachedUser(revision, realm, delegate, notBefore);
|
||||||
if (policy == null || policy == UserStorageProviderModel.CachePolicy.DEFAULT) {
|
if (policy == null || policy == UserStorageProviderModel.CachePolicy.DEFAULT) {
|
||||||
cache.addRevisioned(cached, startupRevision);
|
cache.addRevisioned(cached, startupRevision);
|
||||||
} else {
|
} else {
|
||||||
|
@ -366,7 +368,7 @@ public class UserCacheSession implements UserCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cached = new CachedUser(revision, realm, delegate);
|
cached = new CachedUser(revision, realm, delegate, notBefore);
|
||||||
cache.addRevisioned(cached, startupRevision);
|
cache.addRevisioned(cached, startupRevision);
|
||||||
}
|
}
|
||||||
UserAdapter adapter = new UserAdapter(cached, this, session, realm);
|
UserAdapter adapter = new UserAdapter(cached, this, session, realm);
|
||||||
|
@ -765,6 +767,32 @@ public class UserCacheSession implements UserCache {
|
||||||
return consentModel;
|
return consentModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNotBeforeForUser(RealmModel realm, UserModel user, int notBefore) {
|
||||||
|
if (!isRegisteredForInvalidation(realm, user.getId())) {
|
||||||
|
UserModel foundUser = getUserById(user.getId(), realm);
|
||||||
|
if (foundUser instanceof UserAdapter) {
|
||||||
|
((UserAdapter) foundUser).invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getDelegate().setNotBeforeForUser(realm, user, notBefore);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNotBeforeOfUser(RealmModel realm, UserModel user) {
|
||||||
|
if (isRegisteredForInvalidation(realm, user.getId())) {
|
||||||
|
return getDelegate().getNotBeforeOfUser(realm, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
UserModel foundUser = getUserById(user.getId(), realm);
|
||||||
|
if (foundUser instanceof UserAdapter) {
|
||||||
|
return ((UserAdapter) foundUser).cached.getNotBefore();
|
||||||
|
} else {
|
||||||
|
return getDelegate().getNotBeforeOfUser(realm, user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
|
public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) {
|
||||||
|
|
|
@ -45,10 +45,11 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
|
||||||
private Set<String> requiredActions = new HashSet<>();
|
private Set<String> requiredActions = new HashSet<>();
|
||||||
private Set<String> roleMappings = new HashSet<>();
|
private Set<String> roleMappings = new HashSet<>();
|
||||||
private Set<String> groups = new HashSet<>();
|
private Set<String> groups = new HashSet<>();
|
||||||
|
private int notBefore;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public CachedUser(Long revision, RealmModel realm, UserModel user) {
|
public CachedUser(Long revision, RealmModel realm, UserModel user, int notBefore) {
|
||||||
super(revision, user.getId());
|
super(revision, user.getId());
|
||||||
this.realm = realm.getId();
|
this.realm = realm.getId();
|
||||||
this.username = user.getUsername();
|
this.username = user.getUsername();
|
||||||
|
@ -71,6 +72,7 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
|
||||||
groups.add(group.getId());
|
groups.add(group.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.notBefore = notBefore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRealm() {
|
public String getRealm() {
|
||||||
|
@ -129,4 +131,7 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
|
||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getNotBefore() {
|
||||||
|
return notBefore;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,6 @@ import org.keycloak.models.UserProvider;
|
||||||
import org.keycloak.models.jpa.entities.CredentialAttributeEntity;
|
import org.keycloak.models.jpa.entities.CredentialAttributeEntity;
|
||||||
import org.keycloak.models.jpa.entities.CredentialEntity;
|
import org.keycloak.models.jpa.entities.CredentialEntity;
|
||||||
import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
|
import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
|
||||||
import org.keycloak.models.jpa.entities.UserAttributeEntity;
|
|
||||||
import org.keycloak.models.jpa.entities.UserConsentEntity;
|
import org.keycloak.models.jpa.entities.UserConsentEntity;
|
||||||
import org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity;
|
import org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity;
|
||||||
import org.keycloak.models.jpa.entities.UserConsentRoleEntity;
|
import org.keycloak.models.jpa.entities.UserConsentRoleEntity;
|
||||||
|
@ -363,6 +362,18 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNotBeforeForUser(RealmModel realm, UserModel user, int notBefore) {
|
||||||
|
UserEntity entity = em.getReference(UserEntity.class, user.getId());
|
||||||
|
entity.setNotBefore(notBefore);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNotBeforeOfUser(RealmModel realm, UserModel user) {
|
||||||
|
UserEntity entity = em.getReference(UserEntity.class, user.getId());
|
||||||
|
return entity.getNotBefore();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void grantToAllUsers(RealmModel realm, RoleModel role) {
|
public void grantToAllUsers(RealmModel realm, RoleModel role) {
|
||||||
int num = em.createNamedQuery("grantRoleToAllUsers")
|
int num = em.createNamedQuery("grantRoleToAllUsers")
|
||||||
|
|
|
@ -103,6 +103,9 @@ public class UserEntity {
|
||||||
@Column(name="SERVICE_ACCOUNT_CLIENT_LINK")
|
@Column(name="SERVICE_ACCOUNT_CLIENT_LINK")
|
||||||
protected String serviceAccountClientLink;
|
protected String serviceAccountClientLink;
|
||||||
|
|
||||||
|
@Column(name="NOT_BEFORE")
|
||||||
|
protected int notBefore;
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
@ -224,6 +227,14 @@ public class UserEntity {
|
||||||
this.serviceAccountClientLink = serviceAccountClientLink;
|
this.serviceAccountClientLink = serviceAccountClientLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getNotBefore() {
|
||||||
|
return notBefore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotBefore(int notBefore) {
|
||||||
|
this.notBefore = notBefore;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|
|
@ -416,6 +416,20 @@ public class JpaUserFederatedStorageProvider implements
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNotBeforeForUser(RealmModel realm, String userId, int notBefore) {
|
||||||
|
// Track it as attribute for now
|
||||||
|
String notBeforeStr = String.valueOf(notBefore);
|
||||||
|
setSingleAttribute(realm, userId, "fedNotBefore", notBeforeStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNotBeforeOfUser(RealmModel realm, String userId) {
|
||||||
|
MultivaluedHashMap<String, String> attrs = getAttributes(realm, userId);
|
||||||
|
String notBeforeStr = attrs.getFirst("fedNotBefore");
|
||||||
|
|
||||||
|
return notBeforeStr==null ? 0 : Integer.parseInt(notBeforeStr);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<GroupModel> getGroups(RealmModel realm, String userId) {
|
public Set<GroupModel> getGroups(RealmModel realm, String userId) {
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
<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="keycloak" id="3.3.0">
|
||||||
|
<addColumn tableName="USER_ENTITY">
|
||||||
|
<column name="NOT_BEFORE" type="INT" defaultValueNumeric="0"/>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
|
@ -48,4 +48,5 @@
|
||||||
<include file="META-INF/jpa-changelog-2.5.1.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.0.0.xml"/>
|
||||||
<include file="META-INF/jpa-changelog-3.2.0.xml"/>
|
<include file="META-INF/jpa-changelog-3.2.0.xml"/>
|
||||||
|
<include file="META-INF/jpa-changelog-3.3.0.xml"/>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|
|
@ -185,6 +185,8 @@ public class ModelToRepresentation {
|
||||||
rep.setDisableableCredentialTypes(session.userCredentialManager().getDisableableCredentialTypes(realm, user));
|
rep.setDisableableCredentialTypes(session.userCredentialManager().getDisableableCredentialTypes(realm, user));
|
||||||
rep.setFederationLink(user.getFederationLink());
|
rep.setFederationLink(user.getFederationLink());
|
||||||
|
|
||||||
|
rep.setNotBefore(session.users().getNotBeforeOfUser(realm, user));
|
||||||
|
|
||||||
List<String> reqActions = new ArrayList<String>();
|
List<String> reqActions = new ArrayList<String>();
|
||||||
Set<String> requiredActions = user.getRequiredActions();
|
Set<String> requiredActions = user.getRequiredActions();
|
||||||
for (String ra : requiredActions){
|
for (String ra : requiredActions){
|
||||||
|
|
|
@ -1454,6 +1454,11 @@ public class RepresentationToModel {
|
||||||
session.users().addConsent(newRealm, user.getId(), consentModel);
|
session.users().addConsent(newRealm, user.getId(), consentModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userRep.getNotBefore() != null) {
|
||||||
|
session.users().setNotBeforeForUser(newRealm, user, userRep.getNotBefore());
|
||||||
|
}
|
||||||
|
|
||||||
if (userRep.getServiceAccountClientId() != null) {
|
if (userRep.getServiceAccountClientId() != null) {
|
||||||
String clientId = userRep.getServiceAccountClientId();
|
String clientId = userRep.getServiceAccountClientId();
|
||||||
ClientModel client = newRealm.getClientByClientId(clientId);
|
ClientModel client = newRealm.getClientByClientId(clientId);
|
||||||
|
@ -2378,6 +2383,9 @@ public class RepresentationToModel {
|
||||||
federatedStorage.addConsent(newRealm, userRep.getId(), consentModel);
|
federatedStorage.addConsent(newRealm, userRep.getId(), consentModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (userRep.getNotBefore() != null) {
|
||||||
|
federatedStorage.setNotBeforeForUser(newRealm, userRep.getId(), userRep.getNotBefore());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,8 @@ public interface UserProvider extends Provider,
|
||||||
void updateConsent(RealmModel realm, String userId, UserConsentModel consent);
|
void updateConsent(RealmModel realm, String userId, UserConsentModel consent);
|
||||||
boolean revokeConsentForClient(RealmModel realm, String userId, String clientInternalId);
|
boolean revokeConsentForClient(RealmModel realm, String userId, String clientInternalId);
|
||||||
|
|
||||||
|
void setNotBeforeForUser(RealmModel realm, UserModel user, int notBefore);
|
||||||
|
int getNotBeforeOfUser(RealmModel realm, UserModel user);
|
||||||
|
|
||||||
UserModel getServiceAccount(ClientModel client);
|
UserModel getServiceAccount(ClientModel client);
|
||||||
List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts);
|
List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts);
|
||||||
|
|
|
@ -36,6 +36,7 @@ public interface UserFederatedStorageProvider extends Provider,
|
||||||
UserAttributeFederatedStorage,
|
UserAttributeFederatedStorage,
|
||||||
UserBrokerLinkFederatedStorage,
|
UserBrokerLinkFederatedStorage,
|
||||||
UserConsentFederatedStorage,
|
UserConsentFederatedStorage,
|
||||||
|
UserNotBeforeFederatedStorage,
|
||||||
UserGroupMembershipFederatedStorage,
|
UserGroupMembershipFederatedStorage,
|
||||||
UserRequiredActionsFederatedStorage,
|
UserRequiredActionsFederatedStorage,
|
||||||
UserRoleMappingsFederatedStorage,
|
UserRoleMappingsFederatedStorage,
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* 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.storage.federated;
|
||||||
|
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public interface UserNotBeforeFederatedStorage {
|
||||||
|
|
||||||
|
void setNotBeforeForUser(RealmModel realm, String userId, int notBefore);
|
||||||
|
int getNotBeforeOfUser(RealmModel realm, String userId);
|
||||||
|
}
|
|
@ -530,6 +530,10 @@ public class ExportUtils {
|
||||||
userRep.setClientConsents(consentReps);
|
userRep.setClientConsents(consentReps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not Before
|
||||||
|
int notBefore = session.users().getNotBeforeOfUser(realm, user);
|
||||||
|
userRep.setNotBefore(notBefore);
|
||||||
|
|
||||||
// Service account
|
// Service account
|
||||||
if (user.getServiceAccountClientLink() != null) {
|
if (user.getServiceAccountClientLink() != null) {
|
||||||
String clientInternalId = user.getServiceAccountClientLink();
|
String clientInternalId = user.getServiceAccountClientLink();
|
||||||
|
@ -717,6 +721,10 @@ public class ExportUtils {
|
||||||
userRep.setClientConsents(consentReps);
|
userRep.setClientConsents(consentReps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not Before
|
||||||
|
int notBefore = session.userFederatedStorage().getNotBeforeOfUser(realm, userRep.getId());
|
||||||
|
userRep.setNotBefore(notBefore);
|
||||||
|
|
||||||
if (options.isGroupsAndRolesIncluded()) {
|
if (options.isGroupsAndRolesIncluded()) {
|
||||||
List<String> groups = new LinkedList<>();
|
List<String> groups = new LinkedList<>();
|
||||||
for (GroupModel group : session.userFederatedStorage().getGroups(realm, id)) {
|
for (GroupModel group : session.userFederatedStorage().getGroups(realm, id)) {
|
||||||
|
|
|
@ -180,6 +180,9 @@ public class TokenManager {
|
||||||
if (oldToken.getIssuedAt() < realm.getNotBefore()) {
|
if (oldToken.getIssuedAt() < realm.getNotBefore()) {
|
||||||
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale token");
|
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale token");
|
||||||
}
|
}
|
||||||
|
if (oldToken.getIssuedAt() < session.users().getNotBeforeOfUser(realm, user)) {
|
||||||
|
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale token");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// recreate token.
|
// recreate token.
|
||||||
|
@ -207,9 +210,12 @@ public class TokenManager {
|
||||||
if (!user.isEnabled()) {
|
if (!user.isEnabled()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (token.getIssuedAt() < session.users().getNotBeforeOfUser(realm, user)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
ClientModel client = realm.getClientByClientId(token.getIssuedFor());
|
ClientModel client = realm.getClientByClientId(token.getIssuedFor());
|
||||||
if (client == null || !client.isEnabled()) {
|
if (client == null || !client.isEnabled() || token.getIssuedAt() < client.getNotBefore()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -816,9 +822,13 @@ public class TokenManager {
|
||||||
res.setRefreshExpiresIn(refreshToken.getExpiration() - Time.currentTime());
|
res.setRefreshExpiresIn(refreshToken.getExpiration() - Time.currentTime());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int notBefore = realm.getNotBefore();
|
int notBefore = realm.getNotBefore();
|
||||||
if (client.getNotBefore() > notBefore) notBefore = client.getNotBefore();
|
if (client.getNotBefore() > notBefore) notBefore = client.getNotBefore();
|
||||||
|
int userNotBefore = session.users().getNotBeforeOfUser(realm, userSession.getUser());
|
||||||
|
if (userNotBefore > notBefore) notBefore = userNotBefore;
|
||||||
res.setNotBeforePolicy(notBefore);
|
res.setNotBeforePolicy(notBefore);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -817,6 +817,12 @@ public class AuthenticationManager {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int userNotBefore = session.users().getNotBeforeOfUser(realm, user);
|
||||||
|
if (token.getIssuedAt() < userNotBefore) {
|
||||||
|
logger.debug("User notBefore newer than token");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState());
|
UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState());
|
||||||
if (!isSessionValid(realm, userSession)) {
|
if (!isSessionValid(realm, userSession)) {
|
||||||
// Check if accessToken was for the offline session.
|
// Check if accessToken was for the offline session.
|
||||||
|
|
|
@ -107,6 +107,8 @@ public class ResourceAdminManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void logoutUser(URI requestUri, RealmModel realm, UserModel user, KeycloakSession keycloakSession) {
|
public void logoutUser(URI requestUri, RealmModel realm, UserModel user, KeycloakSession keycloakSession) {
|
||||||
|
keycloakSession.users().setNotBeforeForUser(realm, user, Time.currentTime());
|
||||||
|
|
||||||
List<UserSessionModel> userSessions = keycloakSession.sessions().getUserSessions(realm, user);
|
List<UserSessionModel> userSessions = keycloakSession.sessions().getUserSessions(realm, user);
|
||||||
logoutUserSessions(requestUri, realm, userSessions);
|
logoutUserSessions(requestUri, realm, userSessions);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package org.keycloak.services.resources;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.common.util.Base64Url;
|
import org.keycloak.common.util.Base64Url;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.common.util.UriUtils;
|
import org.keycloak.common.util.UriUtils;
|
||||||
import org.keycloak.credential.CredentialModel;
|
import org.keycloak.credential.CredentialModel;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
|
@ -505,6 +506,11 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
csrfCheck(stateChecker);
|
csrfCheck(stateChecker);
|
||||||
|
|
||||||
UserModel user = auth.getUser();
|
UserModel user = auth.getUser();
|
||||||
|
|
||||||
|
// Rather decrease time a bit. To avoid situation when user is immediatelly redirected to login screen, then automatically authenticated (eg. with Kerberos) and then seeing issues due the stale token
|
||||||
|
// as time on the token will be same like notBefore
|
||||||
|
session.users().setNotBeforeForUser(realm, user, Time.currentTime() - 1);
|
||||||
|
|
||||||
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
|
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
|
||||||
for (UserSessionModel userSession : userSessions) {
|
for (UserSessionModel userSession : userSessions) {
|
||||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
|
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
|
||||||
|
|
|
@ -508,6 +508,8 @@ public class UserResource {
|
||||||
public void logout() {
|
public void logout() {
|
||||||
auth.users().requireManage(user);
|
auth.users().requireManage(user);
|
||||||
|
|
||||||
|
session.users().setNotBeforeForUser(realm, user, Time.currentTime());
|
||||||
|
|
||||||
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
|
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
|
||||||
for (UserSessionModel userSession : userSessions) {
|
for (UserSessionModel userSession : userSessions) {
|
||||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
|
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
|
||||||
|
|
|
@ -231,6 +231,25 @@ public class UserStorageManager implements UserProvider, OnUserCache, OnCreateCo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNotBeforeForUser(RealmModel realm, UserModel user, int notBefore) {
|
||||||
|
if (StorageId.isLocalStorage(user)) {
|
||||||
|
localStorage().setNotBeforeForUser(realm, user, notBefore);
|
||||||
|
} else {
|
||||||
|
getFederatedStorage().setNotBeforeForUser(realm, user.getId(), notBefore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNotBeforeOfUser(RealmModel realm, UserModel user) {
|
||||||
|
if (StorageId.isLocalStorage(user)) {
|
||||||
|
return localStorage().getNotBeforeOfUser(realm, user);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return getFederatedStorage().getNotBeforeOfUser(realm, user.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows a UserStorageProvider to proxy and/or synchronize an imported user.
|
* Allows a UserStorageProvider to proxy and/or synchronize an imported user.
|
||||||
*
|
*
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
package org.keycloak.testsuite.crossdc;
|
package org.keycloak.testsuite.crossdc;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.ws.rs.NotFoundException;
|
import javax.ws.rs.NotFoundException;
|
||||||
|
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
|
@ -101,7 +104,7 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
||||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||||
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
||||||
createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics);
|
createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true);
|
||||||
|
|
||||||
// log.infof("Sleeping!");
|
// log.infof("Sleeping!");
|
||||||
// Thread.sleep(10000000);
|
// Thread.sleep(10000000);
|
||||||
|
@ -118,7 +121,7 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
||||||
|
|
||||||
|
|
||||||
// Return last used accessTokenResponse
|
// Return last used accessTokenResponse
|
||||||
private OAuthClient.AccessTokenResponse createInitialSessions(String cacheName, boolean offline, InfinispanStatistics cacheDc1Statistics, InfinispanStatistics cacheDc2Statistics) throws Exception {
|
private List<OAuthClient.AccessTokenResponse> createInitialSessions(String cacheName, boolean offline, InfinispanStatistics cacheDc1Statistics, InfinispanStatistics cacheDc2Statistics, boolean includeRemoteStats) throws Exception {
|
||||||
|
|
||||||
// Enable second DC
|
// Enable second DC
|
||||||
enableDcOnLoadBalancer(DC.SECOND);
|
enableDcOnLoadBalancer(DC.SECOND);
|
||||||
|
@ -137,9 +140,9 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
||||||
oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
|
oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuthClient.AccessTokenResponse lastAccessTokenResponse = null;
|
List<OAuthClient.AccessTokenResponse> responses = new ArrayList<>();
|
||||||
for (int i=0 ; i<SESSIONS_COUNT ; i++) {
|
for (int i=0 ; i<SESSIONS_COUNT ; i++) {
|
||||||
lastAccessTokenResponse = oauth.doGrantAccessTokenRequest("password", "login-test", "password");
|
responses.add(oauth.doGrantAccessTokenRequest("password", "login-test", "password"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assert 20 sessions exists on node1 and node2 and on remote caches
|
// Assert 20 sessions exists on node1 and node2 and on remote caches
|
||||||
|
@ -152,11 +155,14 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
||||||
|
|
||||||
Assert.assertEquals(sessions11, sessions01 + SESSIONS_COUNT);
|
Assert.assertEquals(sessions11, sessions01 + SESSIONS_COUNT);
|
||||||
Assert.assertEquals(sessions12, sessions02 + SESSIONS_COUNT);
|
Assert.assertEquals(sessions12, sessions02 + SESSIONS_COUNT);
|
||||||
Assert.assertEquals(remoteSessions11, remoteSessions01 + SESSIONS_COUNT);
|
|
||||||
Assert.assertEquals(remoteSessions12, remoteSessions02 + SESSIONS_COUNT);
|
if (includeRemoteStats) {
|
||||||
|
Assert.assertEquals(remoteSessions11, remoteSessions01 + SESSIONS_COUNT);
|
||||||
|
Assert.assertEquals(remoteSessions12, remoteSessions02 + SESSIONS_COUNT);
|
||||||
|
}
|
||||||
}, 50, 50);
|
}, 50, 50);
|
||||||
|
|
||||||
return lastAccessTokenResponse;
|
return responses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -191,7 +197,7 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
||||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||||
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
||||||
|
|
||||||
createInitialSessions(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, true, cacheDc1Statistics, cacheDc2Statistics);
|
createInitialSessions(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, true, cacheDc1Statistics, cacheDc2Statistics, true);
|
||||||
|
|
||||||
channelStatisticsCrossDc.reset();
|
channelStatisticsCrossDc.reset();
|
||||||
|
|
||||||
|
@ -210,7 +216,7 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
||||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||||
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
||||||
|
|
||||||
createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics);
|
createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true);
|
||||||
|
|
||||||
channelStatisticsCrossDc.reset();
|
channelStatisticsCrossDc.reset();
|
||||||
|
|
||||||
|
@ -229,7 +235,7 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
||||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||||
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
||||||
|
|
||||||
OAuthClient.AccessTokenResponse lastAccessTokenResponse = createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics);
|
OAuthClient.AccessTokenResponse lastAccessTokenResponse = createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true).get(SESSIONS_COUNT - 1);
|
||||||
|
|
||||||
// Assert I am able to refresh
|
// Assert I am able to refresh
|
||||||
OAuthClient.AccessTokenResponse refreshResponse = oauth.doRefreshTokenRequest(lastAccessTokenResponse.getRefreshToken(), "password");
|
OAuthClient.AccessTokenResponse refreshResponse = oauth.doRefreshTokenRequest(lastAccessTokenResponse.getRefreshToken(), "password");
|
||||||
|
@ -273,7 +279,7 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
||||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||||
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
||||||
createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics);
|
createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true);
|
||||||
|
|
||||||
// log.infof("Sleeping!");
|
// log.infof("Sleeping!");
|
||||||
// Thread.sleep(10000000);
|
// Thread.sleep(10000000);
|
||||||
|
@ -295,7 +301,7 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
||||||
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||||
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
||||||
createInitialSessions(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, true, cacheDc1Statistics, cacheDc2Statistics);
|
createInitialSessions(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, true, cacheDc1Statistics, cacheDc2Statistics, true);
|
||||||
|
|
||||||
// log.infof("Sleeping!");
|
// log.infof("Sleeping!");
|
||||||
// Thread.sleep(10000000);
|
// Thread.sleep(10000000);
|
||||||
|
@ -318,7 +324,7 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
||||||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||||
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
||||||
|
|
||||||
createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics);
|
createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true);
|
||||||
|
|
||||||
channelStatisticsCrossDc.reset();
|
channelStatisticsCrossDc.reset();
|
||||||
|
|
||||||
|
@ -340,6 +346,39 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLogoutUserWithFailover(
|
||||||
|
@JmxInfinispanCacheStatistics(dc=DC.FIRST, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics,
|
||||||
|
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||||
|
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
||||||
|
|
||||||
|
// Start node2 on first DC
|
||||||
|
startBackendNode(DC.FIRST, 1);
|
||||||
|
|
||||||
|
// Don't include remote stats. Size is smaller because of distributed cache
|
||||||
|
List<OAuthClient.AccessTokenResponse> responses = createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, false);
|
||||||
|
|
||||||
|
// Kill node2 now. Around 10 sessions (half of SESSIONS_COUNT) will be lost on Keycloak side. But not on infinispan side
|
||||||
|
stopBackendNode(DC.FIRST, 1);
|
||||||
|
|
||||||
|
channelStatisticsCrossDc.reset();
|
||||||
|
|
||||||
|
// Increase offset a bit to ensure logout happens later then token issued time
|
||||||
|
setTimeOffset(10);
|
||||||
|
|
||||||
|
// Logout user
|
||||||
|
ApiUtil.findUserByUsernameId(getAdminClient().realm(REALM_NAME), "login-test").logout();
|
||||||
|
|
||||||
|
// Assert it's not possible to refresh sessions. Works because user.notBefore
|
||||||
|
int i = 0;
|
||||||
|
for (OAuthClient.AccessTokenResponse response : responses) {
|
||||||
|
i++;
|
||||||
|
OAuthClient.AccessTokenResponse refreshTokenResponse = oauth.doRefreshTokenRequest(response.getRefreshToken(), "password");
|
||||||
|
Assert.assertNull("Failed in iteration " + i, refreshTokenResponse.getRefreshToken());
|
||||||
|
Assert.assertNotNull("Failed in iteration " + i, refreshTokenResponse.getError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// AUTH SESSIONS
|
// AUTH SESSIONS
|
||||||
|
|
|
@ -154,6 +154,8 @@ public class ExportImportUtil {
|
||||||
|
|
||||||
Assert.assertNull(realmRsc.users().get(wburke.getId()).roles().getAll().getRealmMappings());
|
Assert.assertNull(realmRsc.users().get(wburke.getId()).roles().getAll().getRealmMappings());
|
||||||
|
|
||||||
|
Assert.assertEquals((Object) 159, wburke.getNotBefore());
|
||||||
|
|
||||||
UserRepresentation loginclient = findByUsername(realmRsc, "loginclient");
|
UserRepresentation loginclient = findByUsername(realmRsc, "loginclient");
|
||||||
// user with creation timestamp as string in import
|
// user with creation timestamp as string in import
|
||||||
Assert.assertEquals(new Long(123655), loginclient.getCreatedTimestamp());
|
Assert.assertEquals(new Long(123655), loginclient.getCreatedTimestamp());
|
||||||
|
|
|
@ -21,8 +21,11 @@ import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
import org.keycloak.testsuite.Assert;
|
||||||
import org.keycloak.testsuite.AssertEvents;
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||||
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
import org.keycloak.testsuite.pages.AppPage;
|
import org.keycloak.testsuite.pages.AppPage;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
|
|
||||||
|
@ -210,4 +213,23 @@ public class LogoutTest extends AbstractTestRealmKeycloakTest {
|
||||||
events.expectLogout(sessionId2).removeDetail(Details.REDIRECT_URI).assertEvent();
|
events.expectLogout(sessionId2).removeDetail(Details.REDIRECT_URI).assertEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void logoutUserByAdmin() {
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
assertTrue(appPage.isCurrent());
|
||||||
|
String sessionId = events.expectLogin().assertEvent().getSessionId();
|
||||||
|
|
||||||
|
UserRepresentation user = ApiUtil.findUserByUsername(adminClient.realm("test"), "test-user@localhost");
|
||||||
|
Assert.assertEquals((Object) 0, user.getNotBefore());
|
||||||
|
|
||||||
|
adminClient.realm("test").users().get(user.getId()).logout();
|
||||||
|
|
||||||
|
user = adminClient.realm("test").users().get(user.getId()).toRepresentation();
|
||||||
|
Assert.assertTrue(user.getNotBefore() > 0);
|
||||||
|
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.assertCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,6 +120,7 @@
|
||||||
"username": "wburke",
|
"username": "wburke",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"createdTimestamp" : 123654,
|
"createdTimestamp" : 123654,
|
||||||
|
"notBefore": 159,
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"email": "bburke@redhat.com"
|
"email": "bburke@redhat.com"
|
||||||
},
|
},
|
||||||
|
|
|
@ -126,6 +126,14 @@ public class AdapterTestStrategy extends ExternalResource {
|
||||||
protected void after() {
|
protected void after() {
|
||||||
super.after();
|
super.after();
|
||||||
webRule.after();
|
webRule.after();
|
||||||
|
|
||||||
|
// Revert notBefore
|
||||||
|
KeycloakSession session = keycloakRule.startSession();
|
||||||
|
RealmModel realm = session.realms().getRealmByName("demo");
|
||||||
|
UserModel user = session.users().getUserByUsername("bburke@redhat.com", realm);
|
||||||
|
session.users().setNotBeforeForUser(realm, user, 0);
|
||||||
|
session.getTransactionManager().commit();
|
||||||
|
session.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSavedPostRequest() throws Exception {
|
public void testSavedPostRequest() throws Exception {
|
||||||
|
|
|
@ -137,7 +137,7 @@ public class FederatedStorageExportImportTest {
|
||||||
|
|
||||||
Assert.assertEquals(1, session.userFederatedStorage().getStoredUsersCount(realm));
|
Assert.assertEquals(1, session.userFederatedStorage().getStoredUsersCount(realm));
|
||||||
MultivaluedHashMap<String, String> attributes = session.userFederatedStorage().getAttributes(realm, userId);
|
MultivaluedHashMap<String, String> attributes = session.userFederatedStorage().getAttributes(realm, userId);
|
||||||
Assert.assertEquals(2, attributes.size());
|
Assert.assertEquals(3, attributes.size());
|
||||||
Assert.assertEquals("value1", attributes.getFirst("single1"));
|
Assert.assertEquals("value1", attributes.getFirst("single1"));
|
||||||
Assert.assertTrue(attributes.getList("list1").contains("1"));
|
Assert.assertTrue(attributes.getList("list1").contains("1"));
|
||||||
Assert.assertTrue(attributes.getList("list1").contains("2"));
|
Assert.assertTrue(attributes.getList("list1").contains("2"));
|
||||||
|
@ -174,6 +174,7 @@ public class FederatedStorageExportImportTest {
|
||||||
session.userFederatedStorage().createCredential(realm, userId, credential);
|
session.userFederatedStorage().createCredential(realm, userId, credential);
|
||||||
session.userFederatedStorage().grantRole(realm, userId, role);
|
session.userFederatedStorage().grantRole(realm, userId, role);
|
||||||
session.userFederatedStorage().joinGroup(realm, userId, group);
|
session.userFederatedStorage().joinGroup(realm, userId, group);
|
||||||
|
session.userFederatedStorage().setNotBeforeForUser(realm, userId, 50);
|
||||||
keycloakRule.stopSession(session, true);
|
keycloakRule.stopSession(session, true);
|
||||||
|
|
||||||
|
|
||||||
|
@ -203,13 +204,14 @@ public class FederatedStorageExportImportTest {
|
||||||
|
|
||||||
Assert.assertEquals(1, session.userFederatedStorage().getStoredUsersCount(realm));
|
Assert.assertEquals(1, session.userFederatedStorage().getStoredUsersCount(realm));
|
||||||
MultivaluedHashMap<String, String> attributes = session.userFederatedStorage().getAttributes(realm, userId);
|
MultivaluedHashMap<String, String> attributes = session.userFederatedStorage().getAttributes(realm, userId);
|
||||||
Assert.assertEquals(2, attributes.size());
|
Assert.assertEquals(3, attributes.size());
|
||||||
Assert.assertEquals("value1", attributes.getFirst("single1"));
|
Assert.assertEquals("value1", attributes.getFirst("single1"));
|
||||||
Assert.assertTrue(attributes.getList("list1").contains("1"));
|
Assert.assertTrue(attributes.getList("list1").contains("1"));
|
||||||
Assert.assertTrue(attributes.getList("list1").contains("2"));
|
Assert.assertTrue(attributes.getList("list1").contains("2"));
|
||||||
Assert.assertTrue(session.userFederatedStorage().getRequiredActions(realm, userId).contains("UPDATE_PASSWORD"));
|
Assert.assertTrue(session.userFederatedStorage().getRequiredActions(realm, userId).contains("UPDATE_PASSWORD"));
|
||||||
Assert.assertTrue(session.userFederatedStorage().getRoleMappings(realm, userId).contains(role));
|
Assert.assertTrue(session.userFederatedStorage().getRoleMappings(realm, userId).contains(role));
|
||||||
Assert.assertTrue(session.userFederatedStorage().getGroups(realm, userId).contains(group));
|
Assert.assertTrue(session.userFederatedStorage().getGroups(realm, userId).contains(group));
|
||||||
|
Assert.assertEquals(50, session.userFederatedStorage().getNotBeforeOfUser(realm, userId));
|
||||||
List<CredentialModel> creds = session.userFederatedStorage().getStoredCredentials(realm, userId);
|
List<CredentialModel> creds = session.userFederatedStorage().getStoredCredentials(realm, userId);
|
||||||
Assert.assertEquals(1, creds.size());
|
Assert.assertEquals(1, creds.size());
|
||||||
Assert.assertTrue(getHashProvider(session, realm.getPasswordPolicy()).verify("password", creds.get(0)));
|
Assert.assertTrue(getHashProvider(session, realm.getPasswordPolicy()).verify("password", creds.get(0)));
|
||||||
|
|
|
@ -241,7 +241,7 @@ public class UserModelTest extends AbstractModelTest {
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateUserSingleAttribute() {
|
public void testUpdateUserSingleAttribute() {
|
||||||
Map<String, List<String>> expected = ImmutableMap.of(
|
Map<String, List<String>> expected = ImmutableMap.of(
|
||||||
"key1", Arrays.asList("value3"),
|
"key1", Arrays.asList("value3"),
|
||||||
"key2", Arrays.asList("value2"));
|
"key2", Arrays.asList("value2"));
|
||||||
|
|
||||||
RealmModel realm = realmManager.createRealm("original");
|
RealmModel realm = realmManager.createRealm("original");
|
||||||
|
@ -398,6 +398,31 @@ public class UserModelTest extends AbstractModelTest {
|
||||||
Assert.assertFalse(realm2User1.hasRole(role1));
|
Assert.assertFalse(realm2User1.hasRole(role1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUserNotBefore() throws Exception {
|
||||||
|
RealmModel realm = realmManager.createRealm("original");
|
||||||
|
|
||||||
|
UserModel user1 = session.users().addUser(realm, "user1");
|
||||||
|
session.users().setNotBeforeForUser(realm, user1, 10);
|
||||||
|
|
||||||
|
commit();
|
||||||
|
|
||||||
|
realm = realmManager.getRealmByName("original");
|
||||||
|
user1 = session.users().getUserByUsername("user1", realm);
|
||||||
|
int notBefore = session.users().getNotBeforeOfUser(realm, user1);
|
||||||
|
Assert.assertEquals(10, notBefore);
|
||||||
|
|
||||||
|
// Try to update
|
||||||
|
session.users().setNotBeforeForUser(realm, user1, 20);
|
||||||
|
|
||||||
|
commit();
|
||||||
|
|
||||||
|
realm = realmManager.getRealmByName("original");
|
||||||
|
user1 = session.users().getUserByUsername("user1", realm);
|
||||||
|
notBefore = session.users().getNotBeforeOfUser(realm, user1);
|
||||||
|
Assert.assertEquals(20, notBefore);
|
||||||
|
}
|
||||||
|
|
||||||
public static void assertEquals(UserModel expected, UserModel actual) {
|
public static void assertEquals(UserModel expected, UserModel actual) {
|
||||||
Assert.assertEquals(expected.getUsername(), actual.getUsername());
|
Assert.assertEquals(expected.getUsername(), actual.getUsername());
|
||||||
Assert.assertEquals(expected.getCreatedTimestamp(), actual.getCreatedTimestamp());
|
Assert.assertEquals(expected.getCreatedTimestamp(), actual.getCreatedTimestamp());
|
||||||
|
|
Loading…
Reference in a new issue