keycloak-aplcache

KEYCLOAK-1421 Added timestamp of keycloak user creation into

6/15/2015 12:02:52 PM

Changes

Details

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 3134b12..dac2660 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 @@
         <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REQUIRED_ACTION_PROVIDER" constraintName="FK_REQ_ACT_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
         <addForeignKeyConstraint baseColumnNames="CLIENT_SESSION" baseTableName="CLIENT_USER_SESSION_NOTE" constraintName="FK_CL_USR_SES_NOTE" referencedColumnNames="ID" referencedTableName="CLIENT_SESSION"/>
         <dropColumn tableName="CLIENT_SESSION" columnName="ACTION"/>
-    </changeSet>
-</databaseChangeLog>
+        <addColumn tableName="USER_ENTITY">
+            <column name="CREATED_TIMESTAMP" type="BIGINT"/>
+        </addColumn>
+    </changeSet>
+</databaseChangeLog>
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 747b64c..e0e4ad2 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/entities/UserEntity.java b/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java
index 064697b..914d5a5 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/UserModel.java b/model/api/src/main/java/org/keycloak/models/UserModel.java
index 645250e..c4d92ad 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/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 467040f..de8237c 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 245e6c6..a0ff8ce 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 7123c3e..b488925 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/adapter/UserAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java
index 39024c1..92cd6f1 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
@@ -99,6 +99,16 @@ public class UserAdapter implements UserModel, Comparable {
     }
 
     @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/file/src/main/java/org/keycloak/models/file/FileUserProvider.java b/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java
index eb28d30..9416170 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/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 bbef81b..257ca1e 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<String> requiredActions = new HashSet<>();
     private Set<String> roleMappings = new HashSet<String>();
 
-
     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/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 aa80a25..aedaf25 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 java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
 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.UserConsentModel;
 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;
-
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @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();
     }
 
@@ -60,14 +64,27 @@ public class UserAdapter implements UserModel {
     }
 
     @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<String, String> getAttributes() {
-        if (updated != null) return updated.getAttributes();
+        if (updated != null)
+            return updated.getAttributes();
         return cached.getAttributes();
     }
 
     @Override
     public Set<String> 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<UserCredentialValueModel> 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<RoleModel> getRealmRoleMappings() {
-        if (updated != null) return updated.getRealmRoleMappings();
+        if (updated != null)
+            return updated.getRealmRoleMappings();
         Set<RoleModel> roleMappings = getRoleMappings();
         Set<RoleModel> realmMappings = new HashSet<RoleModel>();
         for (RoleModel role : roleMappings) {
@@ -233,7 +260,8 @@ public class UserAdapter implements UserModel {
 
     @Override
     public Set<RoleModel> getClientRoleMappings(ClientModel app) {
-        if (updated != null) return updated.getClientRoleMappings(app);
+        if (updated != null)
+            return updated.getClientRoleMappings(app);
         Set<RoleModel> roleMappings = getRoleMappings();
         Set<RoleModel> appMappings = new HashSet<RoleModel>();
         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<RoleModel> 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<RoleModel> getRoleMappings() {
-        if (updated != null) return updated.getRoleMappings();
+        if (updated != null)
+            return updated.getRoleMappings();
         Set<RoleModel> roles = new HashSet<RoleModel>();
         for (String id : cached.getRoleMappings()) {
             RoleModel roleById = keycloakSession.realms().getRoleById(id, realm);
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 d812a34..443c8eb 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/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index ca30006..b2915ed 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 670f5f0..7e52860 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
@@ -77,6 +77,16 @@ public class UserAdapter implements UserModel {
     }
 
     @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/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 6c692de..efee051 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 79a6260..6ee4348 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;
@@ -72,6 +73,16 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
     }
 
     @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 3f4ffa5..8406d4a 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 cf2fd77..81b0900 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 276698d..6807c8b 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<RoleModel> 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<RoleModel> 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 d9e1aa5..108399b 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 0e41313..943a713 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": [
                 {