diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.4.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.4.0.xml
index 3134b12a33..dac26603ae 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.4.0.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.4.0.xml
@@ -106,5 +106,8 @@
-
-
+
+
+
+
+
diff --git a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
index 747b64cc0b..e0e4ad22f7 100755
--- a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
@@ -13,6 +13,7 @@ public class UserRepresentation {
protected String self; // link
protected String id;
+ protected Long createdTimestamp;
protected String username;
protected boolean enabled;
protected boolean totp;
@@ -50,6 +51,14 @@ public class UserRepresentation {
this.id = id;
}
+ public Long getCreatedTimestamp() {
+ return createdTimestamp;
+ }
+
+ public void setCreatedTimestamp(Long createdTimestamp) {
+ this.createdTimestamp = createdTimestamp;
+ }
+
public String getFirstName() {
return firstName;
}
diff --git a/model/api/src/main/java/org/keycloak/models/UserModel.java b/model/api/src/main/java/org/keycloak/models/UserModel.java
index 645250e91b..c4d92ad334 100755
--- a/model/api/src/main/java/org/keycloak/models/UserModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserModel.java
@@ -20,6 +20,13 @@ public interface UserModel {
String getUsername();
void setUsername(String username);
+
+ /**
+ * Get timestamp of user creation. May be null for old users created before this feature introduction.
+ */
+ Long getCreatedTimestamp();
+
+ void setCreatedTimestamp(Long timestamp);
boolean isEnabled();
@@ -88,4 +95,4 @@ public interface UserModel {
public static enum RequiredAction {
VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD
}
-}
\ No newline at end of file
+}
diff --git a/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java b/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java
index 064697b00a..914d5a516e 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java
@@ -12,6 +12,7 @@ import java.util.Map;
public class UserEntity extends AbstractIdentifiableEntity {
private String username;
+ private Long createdTimestamp;
private String firstName;
private String lastName;
private String email;
@@ -36,6 +37,15 @@ public class UserEntity extends AbstractIdentifiableEntity {
public void setUsername(String username) {
this.username = username;
}
+
+ public Long getCreatedTimestamp() {
+ return createdTimestamp;
+ }
+
+ public void setCreatedTimestamp(Long timestamp) {
+ this.createdTimestamp = timestamp;
+ }
+
public String getFirstName() {
return firstName;
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 467040f448..de8237c17a 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -50,6 +50,7 @@ public class ModelToRepresentation {
UserRepresentation rep = new UserRepresentation();
rep.setId(user.getId());
rep.setUsername(user.getUsername());
+ rep.setCreatedTimestamp(user.getCreatedTimestamp());
rep.setLastName(user.getLastName());
rep.setFirstName(user.getFirstName());
rep.setEmail(user.getEmail());
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 245e6c6e8b..a0ff8ceccb 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -798,6 +798,7 @@ public class RepresentationToModel {
// Import users just to user storage. Don't federate
UserModel user = session.userStorage().addUser(newRealm, userRep.getId(), userRep.getUsername(), false, false);
user.setEnabled(userRep.isEnabled());
+ user.setCreatedTimestamp(userRep.getCreatedTimestamp());
user.setEmail(userRep.getEmail());
user.setEmailVerified(userRep.isEmailVerified());
user.setFirstName(userRep.getFirstName());
diff --git a/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java b/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
index 7123c3e1d5..b4889251b7 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java
@@ -225,4 +225,14 @@ public class UserModelDelegate implements UserModel {
public UserModel getDelegate() {
return delegate;
}
+
+ @Override
+ public Long getCreatedTimestamp(){
+ return delegate.getCreatedTimestamp();
+ }
+
+ @Override
+ public void setCreatedTimestamp(Long timestamp){
+ delegate.setCreatedTimestamp(timestamp);
+ }
}
diff --git a/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java b/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java
index eb28d309bd..94161700f0 100755
--- a/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java
+++ b/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java
@@ -305,6 +305,7 @@ public class FileUserProvider implements UserProvider {
UserEntity userEntity = new UserEntity();
userEntity.setId(userId);
+ userEntity.setCreatedTimestamp(System.currentTimeMillis());
userEntity.setUsername(username);
// Compatibility with JPA model, which has user disabled by default
// userEntity.setEnabled(true);
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
index 39024c1100..92cd6f109e 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
@@ -98,6 +98,16 @@ public class UserAdapter implements UserModel, Comparable {
user.setUsername(username);
}
+ @Override
+ public Long getCreatedTimestamp() {
+ return user.getCreatedTimestamp();
+ }
+
+ @Override
+ public void setCreatedTimestamp(Long timestamp) {
+ user.setCreatedTimestamp(timestamp);
+ }
+
@Override
public boolean isEnabled() {
return user.isEnabled();
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java
index aa80a25fc1..aedaf254d2 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java
@@ -1,21 +1,21 @@
package org.keycloak.models.cache;
-import org.keycloak.models.ClientModel;
-import org.keycloak.models.UserConsentModel;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.RoleContainerModel;
-import org.keycloak.models.RoleModel;
-import org.keycloak.models.UserCredentialModel;
-import org.keycloak.models.UserCredentialValueModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.models.cache.entities.CachedUser;
-
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleContainerModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserConsentModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.cache.entities.CachedUser;
+
/**
* @author Bill Burke
* @version $Revision: 1 $
@@ -38,18 +38,22 @@ public class UserAdapter implements UserModel {
if (updated == null) {
userProviderCache.registerUserInvalidation(realm, getId());
updated = userProviderCache.getDelegate().getUserById(getId(), realm);
- if (updated == null) throw new IllegalStateException("Not found in database");
+ if (updated == null)
+ throw new IllegalStateException("Not found in database");
}
}
+
@Override
public String getId() {
- if (updated != null) return updated.getId();
+ if (updated != null)
+ return updated.getId();
return cached.getId();
}
@Override
public String getUsername() {
- if (updated != null) return updated.getUsername();
+ if (updated != null)
+ return updated.getUsername();
return cached.getUsername();
}
@@ -59,15 +63,28 @@ public class UserAdapter implements UserModel {
updated.setUsername(username);
}
+ @Override
+ public Long getCreatedTimestamp() {
+ // get from cached always as it is immutable
+ return cached.getCreatedTimestamp();
+ }
+
+ @Override
+ public void setCreatedTimestamp(Long timestamp) {
+ // nothing to do as this value is immutable
+ }
+
@Override
public boolean isEnabled() {
- if (updated != null) return updated.isEnabled();
+ if (updated != null)
+ return updated.isEnabled();
return cached.isEnabled();
}
@Override
public boolean isTotp() {
- if (updated != null) return updated.isTotp();
+ if (updated != null)
+ return updated.isTotp();
return cached.isTotp();
}
@@ -91,19 +108,22 @@ public class UserAdapter implements UserModel {
@Override
public String getAttribute(String name) {
- if (updated != null) return updated.getAttribute(name);
+ if (updated != null)
+ return updated.getAttribute(name);
return cached.getAttributes().get(name);
}
@Override
public Map getAttributes() {
- if (updated != null) return updated.getAttributes();
+ if (updated != null)
+ return updated.getAttributes();
return cached.getAttributes();
}
@Override
public Set getRequiredActions() {
- if (updated != null) return updated.getRequiredActions();
+ if (updated != null)
+ return updated.getRequiredActions();
return cached.getRequiredActions();
}
@@ -133,7 +153,8 @@ public class UserAdapter implements UserModel {
@Override
public String getFirstName() {
- if (updated != null) return updated.getFirstName();
+ if (updated != null)
+ return updated.getFirstName();
return cached.getFirstName();
}
@@ -145,7 +166,8 @@ public class UserAdapter implements UserModel {
@Override
public String getLastName() {
- if (updated != null) return updated.getLastName();
+ if (updated != null)
+ return updated.getLastName();
return cached.getLastName();
}
@@ -157,7 +179,8 @@ public class UserAdapter implements UserModel {
@Override
public String getEmail() {
- if (updated != null) return updated.getEmail();
+ if (updated != null)
+ return updated.getEmail();
return cached.getEmail();
}
@@ -169,7 +192,8 @@ public class UserAdapter implements UserModel {
@Override
public boolean isEmailVerified() {
- if (updated != null) return updated.isEmailVerified();
+ if (updated != null)
+ return updated.isEmailVerified();
return cached.isEmailVerified();
}
@@ -193,7 +217,8 @@ public class UserAdapter implements UserModel {
@Override
public List getCredentialsDirectly() {
- if (updated != null) return updated.getCredentialsDirectly();
+ if (updated != null)
+ return updated.getCredentialsDirectly();
return cached.getCredentials();
}
@@ -205,7 +230,8 @@ public class UserAdapter implements UserModel {
@Override
public String getFederationLink() {
- if (updated != null) return updated.getFederationLink();
+ if (updated != null)
+ return updated.getFederationLink();
return cached.getFederationLink();
}
@@ -213,11 +239,12 @@ public class UserAdapter implements UserModel {
public void setFederationLink(String link) {
getDelegateForUpdate();
updated.setFederationLink(link);
- }
+ }
@Override
public Set getRealmRoleMappings() {
- if (updated != null) return updated.getRealmRoleMappings();
+ if (updated != null)
+ return updated.getRealmRoleMappings();
Set roleMappings = getRoleMappings();
Set realmMappings = new HashSet();
for (RoleModel role : roleMappings) {
@@ -233,7 +260,8 @@ public class UserAdapter implements UserModel {
@Override
public Set getClientRoleMappings(ClientModel app) {
- if (updated != null) return updated.getClientRoleMappings(app);
+ if (updated != null)
+ return updated.getClientRoleMappings(app);
Set roleMappings = getRoleMappings();
Set appMappings = new HashSet();
for (RoleModel role : roleMappings) {
@@ -249,12 +277,15 @@ public class UserAdapter implements UserModel {
@Override
public boolean hasRole(RoleModel role) {
- if (updated != null) return updated.hasRole(role);
- if (cached.getRoleMappings().contains(role.getId())) return true;
+ if (updated != null)
+ return updated.hasRole(role);
+ if (cached.getRoleMappings().contains(role.getId()))
+ return true;
Set mappings = getRoleMappings();
- for (RoleModel mapping: mappings) {
- if (mapping.hasRole(role)) return true;
+ for (RoleModel mapping : mappings) {
+ if (mapping.hasRole(role))
+ return true;
}
return false;
}
@@ -267,7 +298,8 @@ public class UserAdapter implements UserModel {
@Override
public Set getRoleMappings() {
- if (updated != null) return updated.getRoleMappings();
+ if (updated != null)
+ return updated.getRoleMappings();
Set roles = new HashSet();
for (String id : cached.getRoleMappings()) {
RoleModel roleById = keycloakSession.realms().getRoleById(id, realm);
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
index bbef81b759..257ca1ea2e 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
@@ -21,6 +21,7 @@ public class CachedUser implements Serializable {
private String id;
private String realm;
private String username;
+ private Long createdTimestamp;
private String firstName;
private String lastName;
private String email;
@@ -33,11 +34,11 @@ public class CachedUser implements Serializable {
private Set requiredActions = new HashSet<>();
private Set roleMappings = new HashSet();
-
public CachedUser(RealmModel realm, UserModel user) {
this.id = user.getId();
this.realm = realm.getId();
this.username = user.getUsername();
+ this.createdTimestamp = user.getCreatedTimestamp();
this.firstName = user.getFirstName();
this.lastName = user.getLastName();
this.attributes.putAll(user.getAttributes());
@@ -65,6 +66,10 @@ public class CachedUser implements Serializable {
return username;
}
+ public Long getCreatedTimestamp() {
+ return createdTimestamp;
+ }
+
public String getFirstName() {
return firstName;
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index ca30006d16..b2915ed70c 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
@@ -52,6 +52,7 @@ public class JpaUserProvider implements UserProvider {
UserEntity entity = new UserEntity();
entity.setId(id);
+ entity.setCreatedTimestamp(System.currentTimeMillis());
entity.setUsername(username.toLowerCase());
entity.setRealmId(realm.getId());
em.persist(entity);
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
index 670f5f039b..7e52860c29 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
@@ -76,6 +76,16 @@ public class UserAdapter implements UserModel {
user.setUsername(username);
}
+ @Override
+ public Long getCreatedTimestamp() {
+ return user.getCreatedTimestamp();
+ }
+
+ @Override
+ public void setCreatedTimestamp(Long timestamp) {
+ user.setCreatedTimestamp(timestamp);
+ }
+
@Override
public boolean isEnabled() {
return user.isEnabled();
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
index d812a3441b..443c8ebfa9 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
@@ -11,6 +11,7 @@ import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
+
import java.util.ArrayList;
import java.util.Collection;
@@ -44,6 +45,8 @@ public class UserEntity {
protected String username;
@Column(name = "FIRST_NAME")
protected String firstName;
+ @Column(name = "CREATED_TIMESTAMP")
+ protected Long createdTimestamp;
@Column(name = "LAST_NAME")
protected String lastName;
@Column(name = "EMAIL")
@@ -90,6 +93,14 @@ public class UserEntity {
this.username = username;
}
+ public Long getCreatedTimestamp() {
+ return createdTimestamp;
+ }
+
+ public void setCreatedTimestamp(Long timestamp) {
+ createdTimestamp = timestamp;
+ }
+
public String getFirstName() {
return firstName;
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
index 6c692de3c3..efee051e85 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
@@ -3,6 +3,7 @@ package org.keycloak.models.mongo.keycloak.adapters;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;
+
import org.keycloak.connections.mongo.api.MongoStore;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.ClientModel;
@@ -274,6 +275,7 @@ public class MongoUserProvider implements UserProvider {
MongoUserEntity userEntity = new MongoUserEntity();
userEntity.setId(id);
userEntity.setUsername(username);
+ userEntity.setCreatedTimestamp(System.currentTimeMillis());
// Compatibility with JPA model, which has user disabled by default
// userEntity.setEnabled(true);
userEntity.setRealmId(realm.getId());
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
index 79a6260b9d..6ee43481e2 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
@@ -4,6 +4,7 @@ import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;
+
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ProtocolMapperModel;
@@ -71,6 +72,16 @@ public class UserAdapter extends AbstractMongoAdapter implement
updateUser();
}
+ @Override
+ public Long getCreatedTimestamp() {
+ return user.getCreatedTimestamp();
+ }
+
+ @Override
+ public void setCreatedTimestamp(Long timestamp) {
+ user.setCreatedTimestamp(timestamp);
+ }
+
@Override
public boolean isEnabled() {
return user.isEnabled();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
index 3f4ffa5957..8406d4a680 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
@@ -686,6 +686,9 @@ public abstract class AbstractIdentityProviderTest {
UserModel federatedUser = getFederatedUser();
assertNotNull(federatedUser);
+ assertNotNull(federatedUser.getCreatedTimestamp());
+ // test that timestamp is current with 10s tollerance
+ Assert.assertTrue((System.currentTimeMillis() - federatedUser.getCreatedTimestamp()) < 10000);
doAssertFederatedUser(federatedUser, identityProviderModel, expectedEmail, isProfileUpdateExpected);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java
index cf2fd77d1a..81b0900441 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java
@@ -26,11 +26,15 @@ import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.events.Details;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.representations.IDToken;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.broker.util.UserSessionStatusServlet.UserSessionStatus;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.AppPage.RequestType;
import org.keycloak.testsuite.pages.LoginPage;
@@ -191,6 +195,22 @@ public class RegisterTest {
String userId = events.expectRegister("registerUserSuccess", "registerUserSuccess@email").assertEvent().getUserId();
events.expectLogin().detail("username", "registerusersuccess").user(userId).assertEvent();
+
+ UserModel user = getUser(userId);
+ Assert.assertNotNull(user);
+ Assert.assertNotNull(user.getCreatedTimestamp());
+ // test that timestamp is current with 10s tollerance
+ Assert.assertTrue((System.currentTimeMillis() - user.getCreatedTimestamp()) < 10000);
+ }
+
+ protected UserModel getUser(String userId) {
+ KeycloakSession samlServerSession = keycloakRule.startSession();
+ try {
+ RealmModel brokerRealm = samlServerSession.realms().getRealm("test");
+ return samlServerSession.users().getUserById(userId, brokerRealm);
+ } finally {
+ keycloakRule.stopSession(samlServerSession, false);
+ }
}
@Test
@@ -251,6 +271,12 @@ public class RegisterTest {
String userId = events.expectRegister("registerUserSuccessE@email", "registerUserSuccessE@email").assertEvent().getUserId();
events.expectLogin().detail("username", "registerusersuccesse@email").user(userId).assertEvent();
+
+ UserModel user = getUser(userId);
+ Assert.assertNotNull(user);
+ Assert.assertNotNull(user.getCreatedTimestamp());
+ // test that timestamp is current with 10s tollerance
+ Assert.assertTrue((System.currentTimeMillis() - user.getCreatedTimestamp()) < 10000);
} finally {
configureRelamRegistrationEmailAsUsername(false);
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
index 276698de93..6807c8be73 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
@@ -117,6 +117,8 @@ public class ImportTest extends AbstractModelTest {
// Test role mappings
UserModel admin = session.users().getUserByUsername("admin", realm);
+ // user without creation timestamp in import
+ Assert.assertNull(admin.getCreatedTimestamp());
Set allRoles = admin.getRoleMappings();
Assert.assertEquals(3, allRoles.size());
Assert.assertTrue(allRoles.contains(realm.getRole("admin")));
@@ -124,6 +126,8 @@ public class ImportTest extends AbstractModelTest {
Assert.assertTrue(allRoles.contains(otherApp.getRole("otherapp-admin")));
UserModel wburke = session.users().getUserByUsername("wburke", realm);
+ // user with creation timestamp in import
+ Assert.assertEquals(new Long(123654), wburke.getCreatedTimestamp());
allRoles = wburke.getRoleMappings();
Assert.assertEquals(2, allRoles.size());
Assert.assertFalse(allRoles.contains(realm.getRole("admin")));
@@ -132,6 +136,10 @@ public class ImportTest extends AbstractModelTest {
Assert.assertEquals(0, wburke.getRealmRoleMappings().size());
+ UserModel loginclient = session.users().getUserByUsername("loginclient", realm);
+ // user with creation timestamp as string in import
+ Assert.assertEquals(new Long(123655), loginclient.getCreatedTimestamp());
+
Set realmRoles = admin.getRealmRoleMappings();
Assert.assertEquals(1, realmRoles.size());
Assert.assertEquals("admin", realmRoles.iterator().next().getName());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
index d9e1aa5d0e..108399b6df 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
@@ -8,6 +8,8 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction;
+import static org.junit.Assert.assertNotNull;
+
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -26,6 +28,9 @@ public class UserModelTest extends AbstractModelTest {
user.setFirstName("first-name");
user.setLastName("last-name");
user.setEmail("email");
+ assertNotNull(user.getCreatedTimestamp());
+ // test that timestamp is current with 10s tollerance
+ Assert.assertTrue((System.currentTimeMillis() - user.getCreatedTimestamp()) < 10000);
user.addRequiredAction(RequiredAction.CONFIGURE_TOTP);
user.addRequiredAction(RequiredAction.UPDATE_PASSWORD);
@@ -139,6 +144,7 @@ public class UserModelTest extends AbstractModelTest {
public static void assertEquals(UserModel expected, UserModel actual) {
Assert.assertEquals(expected.getUsername(), actual.getUsername());
+ Assert.assertEquals(expected.getCreatedTimestamp(), actual.getCreatedTimestamp());
Assert.assertEquals(expected.getFirstName(), actual.getFirstName());
Assert.assertEquals(expected.getLastName(), actual.getLastName());
diff --git a/testsuite/integration/src/test/resources/model/testrealm.json b/testsuite/integration/src/test/resources/model/testrealm.json
index 0e41313879..943a713bc1 100755
--- a/testsuite/integration/src/test/resources/model/testrealm.json
+++ b/testsuite/integration/src/test/resources/model/testrealm.json
@@ -55,6 +55,7 @@
{
"username": "wburke",
"enabled": true,
+ "createdTimestamp" : 123654,
"attributes": {
"email": "bburke@redhat.com"
},
@@ -71,6 +72,7 @@
},
{
"username": "loginclient",
+ "createdTimestamp" : "123655",
"enabled": true,
"credentials": [
{