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 Map<String, List<String>> clientRoles;
|
||||
protected List<UserConsentRepresentation> clientConsents;
|
||||
protected Integer notBefore;
|
||||
|
||||
@Deprecated
|
||||
protected Map<String, List<String>> applicationRoles;
|
||||
|
@ -216,6 +217,14 @@ public class UserRepresentation {
|
|||
this.clientConsents = clientConsents;
|
||||
}
|
||||
|
||||
public Integer getNotBefore() {
|
||||
return notBefore;
|
||||
}
|
||||
|
||||
public void setNotBefore(Integer notBefore) {
|
||||
this.notBefore = notBefore;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Map<String, List<String>> getApplicationRoles() {
|
||||
return applicationRoles;
|
||||
|
|
|
@ -334,6 +334,8 @@ public class UserCacheSession implements UserCache {
|
|||
}
|
||||
|
||||
protected UserModel cacheUser(RealmModel realm, UserModel delegate, Long revision) {
|
||||
int notBefore = getDelegate().getNotBeforeOfUser(realm, delegate);
|
||||
|
||||
StorageId storageId = new StorageId(delegate.getId());
|
||||
CachedUser cached = null;
|
||||
if (!storageId.isLocal()) {
|
||||
|
@ -343,7 +345,7 @@ public class UserCacheSession implements UserCache {
|
|||
if (policy != null && policy == UserStorageProviderModel.CachePolicy.NO_CACHE) {
|
||||
return delegate;
|
||||
}
|
||||
cached = new CachedUser(revision, realm, delegate);
|
||||
cached = new CachedUser(revision, realm, delegate, notBefore);
|
||||
if (policy == null || policy == UserStorageProviderModel.CachePolicy.DEFAULT) {
|
||||
cache.addRevisioned(cached, startupRevision);
|
||||
} else {
|
||||
|
@ -366,7 +368,7 @@ public class UserCacheSession implements UserCache {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
cached = new CachedUser(revision, realm, delegate);
|
||||
cached = new CachedUser(revision, realm, delegate, notBefore);
|
||||
cache.addRevisioned(cached, startupRevision);
|
||||
}
|
||||
UserAdapter adapter = new UserAdapter(cached, this, session, realm);
|
||||
|
@ -765,6 +767,32 @@ public class UserCacheSession implements UserCache {
|
|||
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
|
||||
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> roleMappings = 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());
|
||||
this.realm = realm.getId();
|
||||
this.username = user.getUsername();
|
||||
|
@ -71,6 +72,7 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
|
|||
groups.add(group.getId());
|
||||
}
|
||||
}
|
||||
this.notBefore = notBefore;
|
||||
}
|
||||
|
||||
public String getRealm() {
|
||||
|
@ -129,4 +131,7 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
|
|||
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.CredentialEntity;
|
||||
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.UserConsentProtocolMapperEntity;
|
||||
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
|
||||
public void grantToAllUsers(RealmModel realm, RoleModel role) {
|
||||
int num = em.createNamedQuery("grantRoleToAllUsers")
|
||||
|
|
|
@ -103,6 +103,9 @@ public class UserEntity {
|
|||
@Column(name="SERVICE_ACCOUNT_CLIENT_LINK")
|
||||
protected String serviceAccountClientLink;
|
||||
|
||||
@Column(name="NOT_BEFORE")
|
||||
protected int notBefore;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
@ -224,6 +227,14 @@ public class UserEntity {
|
|||
this.serviceAccountClientLink = serviceAccountClientLink;
|
||||
}
|
||||
|
||||
public int getNotBefore() {
|
||||
return notBefore;
|
||||
}
|
||||
|
||||
public void setNotBefore(int notBefore) {
|
||||
this.notBefore = notBefore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
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
|
||||
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-3.0.0.xml"/>
|
||||
<include file="META-INF/jpa-changelog-3.2.0.xml"/>
|
||||
<include file="META-INF/jpa-changelog-3.3.0.xml"/>
|
||||
</databaseChangeLog>
|
||||
|
|
|
@ -185,6 +185,8 @@ public class ModelToRepresentation {
|
|||
rep.setDisableableCredentialTypes(session.userCredentialManager().getDisableableCredentialTypes(realm, user));
|
||||
rep.setFederationLink(user.getFederationLink());
|
||||
|
||||
rep.setNotBefore(session.users().getNotBeforeOfUser(realm, user));
|
||||
|
||||
List<String> reqActions = new ArrayList<String>();
|
||||
Set<String> requiredActions = user.getRequiredActions();
|
||||
for (String ra : requiredActions){
|
||||
|
|
|
@ -1454,6 +1454,11 @@ public class RepresentationToModel {
|
|||
session.users().addConsent(newRealm, user.getId(), consentModel);
|
||||
}
|
||||
}
|
||||
|
||||
if (userRep.getNotBefore() != null) {
|
||||
session.users().setNotBeforeForUser(newRealm, user, userRep.getNotBefore());
|
||||
}
|
||||
|
||||
if (userRep.getServiceAccountClientId() != null) {
|
||||
String clientId = userRep.getServiceAccountClientId();
|
||||
ClientModel client = newRealm.getClientByClientId(clientId);
|
||||
|
@ -2378,6 +2383,9 @@ public class RepresentationToModel {
|
|||
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);
|
||||
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);
|
||||
List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts);
|
||||
|
|
|
@ -36,6 +36,7 @@ public interface UserFederatedStorageProvider extends Provider,
|
|||
UserAttributeFederatedStorage,
|
||||
UserBrokerLinkFederatedStorage,
|
||||
UserConsentFederatedStorage,
|
||||
UserNotBeforeFederatedStorage,
|
||||
UserGroupMembershipFederatedStorage,
|
||||
UserRequiredActionsFederatedStorage,
|
||||
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);
|
||||
}
|
||||
|
||||
// Not Before
|
||||
int notBefore = session.users().getNotBeforeOfUser(realm, user);
|
||||
userRep.setNotBefore(notBefore);
|
||||
|
||||
// Service account
|
||||
if (user.getServiceAccountClientLink() != null) {
|
||||
String clientInternalId = user.getServiceAccountClientLink();
|
||||
|
@ -717,6 +721,10 @@ public class ExportUtils {
|
|||
userRep.setClientConsents(consentReps);
|
||||
}
|
||||
|
||||
// Not Before
|
||||
int notBefore = session.userFederatedStorage().getNotBeforeOfUser(realm, userRep.getId());
|
||||
userRep.setNotBefore(notBefore);
|
||||
|
||||
if (options.isGroupsAndRolesIncluded()) {
|
||||
List<String> groups = new LinkedList<>();
|
||||
for (GroupModel group : session.userFederatedStorage().getGroups(realm, id)) {
|
||||
|
|
|
@ -180,6 +180,9 @@ public class TokenManager {
|
|||
if (oldToken.getIssuedAt() < realm.getNotBefore()) {
|
||||
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.
|
||||
|
@ -207,9 +210,12 @@ public class TokenManager {
|
|||
if (!user.isEnabled()) {
|
||||
return false;
|
||||
}
|
||||
if (token.getIssuedAt() < session.users().getNotBeforeOfUser(realm, user)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ClientModel client = realm.getClientByClientId(token.getIssuedFor());
|
||||
if (client == null || !client.isEnabled()) {
|
||||
if (client == null || !client.isEnabled() || token.getIssuedAt() < client.getNotBefore()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -816,9 +822,13 @@ public class TokenManager {
|
|||
res.setRefreshExpiresIn(refreshToken.getExpiration() - Time.currentTime());
|
||||
}
|
||||
}
|
||||
|
||||
int notBefore = realm.getNotBefore();
|
||||
if (client.getNotBefore() > notBefore) notBefore = client.getNotBefore();
|
||||
int userNotBefore = session.users().getNotBeforeOfUser(realm, userSession.getUser());
|
||||
if (userNotBefore > notBefore) notBefore = userNotBefore;
|
||||
res.setNotBeforePolicy(notBefore);
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -817,6 +817,12 @@ public class AuthenticationManager {
|
|||
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());
|
||||
if (!isSessionValid(realm, userSession)) {
|
||||
// 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) {
|
||||
keycloakSession.users().setNotBeforeForUser(realm, user, Time.currentTime());
|
||||
|
||||
List<UserSessionModel> userSessions = keycloakSession.sessions().getUserSessions(realm, user);
|
||||
logoutUserSessions(requestUri, realm, userSessions);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.keycloak.services.resources;
|
|||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.common.util.UriUtils;
|
||||
import org.keycloak.credential.CredentialModel;
|
||||
import org.keycloak.events.Details;
|
||||
|
@ -505,6 +506,11 @@ public class AccountService extends AbstractSecuredLocalService {
|
|||
csrfCheck(stateChecker);
|
||||
|
||||
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);
|
||||
for (UserSessionModel userSession : userSessions) {
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
|
||||
|
|
|
@ -508,6 +508,8 @@ public class UserResource {
|
|||
public void logout() {
|
||||
auth.users().requireManage(user);
|
||||
|
||||
session.users().setNotBeforeForUser(realm, user, Time.currentTime());
|
||||
|
||||
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
|
||||
for (UserSessionModel userSession : userSessions) {
|
||||
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.
|
||||
*
|
||||
|
|
|
@ -18,6 +18,9 @@
|
|||
package org.keycloak.testsuite.crossdc;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.ws.rs.NotFoundException;
|
||||
|
||||
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.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@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!");
|
||||
// Thread.sleep(10000000);
|
||||
|
@ -118,7 +121,7 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
|
||||
|
||||
// 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
|
||||
enableDcOnLoadBalancer(DC.SECOND);
|
||||
|
@ -137,9 +140,9 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
|
||||
}
|
||||
|
||||
OAuthClient.AccessTokenResponse lastAccessTokenResponse = null;
|
||||
List<OAuthClient.AccessTokenResponse> responses = new ArrayList<>();
|
||||
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
|
||||
|
@ -152,11 +155,14 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
|
||||
Assert.assertEquals(sessions11, sessions01 + 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);
|
||||
|
||||
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,
|
||||
@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();
|
||||
|
||||
|
@ -210,7 +216,7 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
||||
|
||||
createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics);
|
||||
createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true);
|
||||
|
||||
channelStatisticsCrossDc.reset();
|
||||
|
||||
|
@ -229,7 +235,7 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
|||
@JmxInfinispanCacheStatistics(dc=DC.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@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
|
||||
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.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@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!");
|
||||
// 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.SECOND, dcNodeIndex=0, cacheName=InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics,
|
||||
@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!");
|
||||
// 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,
|
||||
@JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
|
||||
|
||||
createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics);
|
||||
createInitialSessions(InfinispanConnectionProvider.SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, true);
|
||||
|
||||
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
|
||||
|
|
|
@ -154,6 +154,8 @@ public class ExportImportUtil {
|
|||
|
||||
Assert.assertNull(realmRsc.users().get(wburke.getId()).roles().getAll().getRealmMappings());
|
||||
|
||||
Assert.assertEquals((Object) 159, wburke.getNotBefore());
|
||||
|
||||
UserRepresentation loginclient = findByUsername(realmRsc, "loginclient");
|
||||
// user with creation timestamp as string in import
|
||||
Assert.assertEquals(new Long(123655), loginclient.getCreatedTimestamp());
|
||||
|
|
|
@ -21,8 +21,11 @@ import org.junit.Rule;
|
|||
import org.junit.Test;
|
||||
import org.keycloak.events.Details;
|
||||
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.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
|
||||
|
@ -210,4 +213,23 @@ public class LogoutTest extends AbstractTestRealmKeycloakTest {
|
|||
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",
|
||||
"enabled": true,
|
||||
"createdTimestamp" : 123654,
|
||||
"notBefore": 159,
|
||||
"attributes": {
|
||||
"email": "bburke@redhat.com"
|
||||
},
|
||||
|
|
|
@ -126,6 +126,14 @@ public class AdapterTestStrategy extends ExternalResource {
|
|||
protected void after() {
|
||||
super.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 {
|
||||
|
|
|
@ -137,7 +137,7 @@ public class FederatedStorageExportImportTest {
|
|||
|
||||
Assert.assertEquals(1, session.userFederatedStorage().getStoredUsersCount(realm));
|
||||
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.assertTrue(attributes.getList("list1").contains("1"));
|
||||
Assert.assertTrue(attributes.getList("list1").contains("2"));
|
||||
|
@ -174,6 +174,7 @@ public class FederatedStorageExportImportTest {
|
|||
session.userFederatedStorage().createCredential(realm, userId, credential);
|
||||
session.userFederatedStorage().grantRole(realm, userId, role);
|
||||
session.userFederatedStorage().joinGroup(realm, userId, group);
|
||||
session.userFederatedStorage().setNotBeforeForUser(realm, userId, 50);
|
||||
keycloakRule.stopSession(session, true);
|
||||
|
||||
|
||||
|
@ -203,13 +204,14 @@ public class FederatedStorageExportImportTest {
|
|||
|
||||
Assert.assertEquals(1, session.userFederatedStorage().getStoredUsersCount(realm));
|
||||
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.assertTrue(attributes.getList("list1").contains("1"));
|
||||
Assert.assertTrue(attributes.getList("list1").contains("2"));
|
||||
Assert.assertTrue(session.userFederatedStorage().getRequiredActions(realm, userId).contains("UPDATE_PASSWORD"));
|
||||
Assert.assertTrue(session.userFederatedStorage().getRoleMappings(realm, userId).contains(role));
|
||||
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);
|
||||
Assert.assertEquals(1, creds.size());
|
||||
Assert.assertTrue(getHashProvider(session, realm.getPasswordPolicy()).verify("password", creds.get(0)));
|
||||
|
|
|
@ -398,6 +398,31 @@ public class UserModelTest extends AbstractModelTest {
|
|||
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) {
|
||||
Assert.assertEquals(expected.getUsername(), actual.getUsername());
|
||||
Assert.assertEquals(expected.getCreatedTimestamp(), actual.getCreatedTimestamp());
|
||||
|
|
Loading…
Reference in a new issue