Merge branch 'velias-KEYCLOAK-1421'

This commit is contained in:
Stian Thorgersen 2015-07-02 14:16:42 +02:00
commit b9e9d6a787
22 changed files with 161 additions and 5 deletions

View file

@ -128,5 +128,8 @@
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REQUIRED_ACTION_PROVIDER" constraintName="FK_REQ_ACT_REALM" referencedColumnNames="ID" referencedTableName="REALM"/> <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"/> <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"/> <dropColumn tableName="CLIENT_SESSION" columnName="ACTION"/>
<addColumn tableName="USER_ENTITY">
<column name="CREATED_TIMESTAMP" type="BIGINT"/>
</addColumn>
</changeSet> </changeSet>
</databaseChangeLog> </databaseChangeLog>

View file

@ -17,6 +17,7 @@ public class UserRepresentation {
protected String self; // link protected String self; // link
protected String id; protected String id;
protected Long createdTimestamp;
protected String username; protected String username;
protected boolean enabled; protected boolean enabled;
protected boolean totp; protected boolean totp;
@ -56,6 +57,14 @@ public class UserRepresentation {
this.id = id; this.id = id;
} }
public Long getCreatedTimestamp() {
return createdTimestamp;
}
public void setCreatedTimestamp(Long createdTimestamp) {
this.createdTimestamp = createdTimestamp;
}
public String getFirstName() { public String getFirstName() {
return firstName; return firstName;
} }

View file

@ -21,6 +21,13 @@
</div> </div>
</div> </div>
<div class="form-group">
<label class="col-md-2 control-label"for="id">Created at</label>
<div class="col-md-6">
{{user.createdTimestamp|date:'shortDate'}}&nbsp;{{user.createdTimestamp|date:'mediumTime'}}
</div>
</div>
<div class="form-group"> <div class="form-group">
<label class="col-md-2 control-label"for="username">Username <span class="required" data-ng-show="create">*</span></label> <label class="col-md-2 control-label"for="username">Username <span class="required" data-ng-show="create">*</span></label>
<div class="col-md-6"> <div class="col-md-6">

View file

@ -21,6 +21,13 @@ public interface UserModel {
void setUsername(String username); 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(); boolean isEnabled();
boolean isTotp(); boolean isTotp();

View file

@ -10,6 +10,7 @@ import java.util.Map;
public class UserEntity extends AbstractIdentifiableEntity { public class UserEntity extends AbstractIdentifiableEntity {
private String username; private String username;
private Long createdTimestamp;
private String firstName; private String firstName;
private String lastName; private String lastName;
private String email; private String email;
@ -35,6 +36,15 @@ public class UserEntity extends AbstractIdentifiableEntity {
this.username = username; this.username = username;
} }
public Long getCreatedTimestamp() {
return createdTimestamp;
}
public void setCreatedTimestamp(Long timestamp) {
this.createdTimestamp = timestamp;
}
public String getFirstName() { public String getFirstName() {
return firstName; return firstName;
} }

View file

@ -51,6 +51,7 @@ public class ModelToRepresentation {
UserRepresentation rep = new UserRepresentation(); UserRepresentation rep = new UserRepresentation();
rep.setId(user.getId()); rep.setId(user.getId());
rep.setUsername(user.getUsername()); rep.setUsername(user.getUsername());
rep.setCreatedTimestamp(user.getCreatedTimestamp());
rep.setLastName(user.getLastName()); rep.setLastName(user.getLastName());
rep.setFirstName(user.getFirstName()); rep.setFirstName(user.getFirstName());
rep.setEmail(user.getEmail()); rep.setEmail(user.getEmail());

View file

@ -799,6 +799,7 @@ public class RepresentationToModel {
// Import users just to user storage. Don't federate // Import users just to user storage. Don't federate
UserModel user = session.userStorage().addUser(newRealm, userRep.getId(), userRep.getUsername(), false, false); UserModel user = session.userStorage().addUser(newRealm, userRep.getId(), userRep.getUsername(), false, false);
user.setEnabled(userRep.isEnabled()); user.setEnabled(userRep.isEnabled());
user.setCreatedTimestamp(userRep.getCreatedTimestamp());
user.setEmail(userRep.getEmail()); user.setEmail(userRep.getEmail());
user.setEmailVerified(userRep.isEmailVerified()); user.setEmailVerified(userRep.isEmailVerified());
user.setFirstName(userRep.getFirstName()); user.setFirstName(userRep.getFirstName());

View file

@ -235,4 +235,14 @@ public class UserModelDelegate implements UserModel {
public UserModel getDelegate() { public UserModel getDelegate() {
return delegate; return delegate;
} }
@Override
public Long getCreatedTimestamp(){
return delegate.getCreatedTimestamp();
}
@Override
public void setCreatedTimestamp(Long timestamp){
delegate.setCreatedTimestamp(timestamp);
}
} }

View file

@ -305,6 +305,7 @@ public class FileUserProvider implements UserProvider {
UserEntity userEntity = new UserEntity(); UserEntity userEntity = new UserEntity();
userEntity.setId(userId); userEntity.setId(userId);
userEntity.setCreatedTimestamp(System.currentTimeMillis());
userEntity.setUsername(username); userEntity.setUsername(username);
// Compatibility with JPA model, which has user disabled by default // Compatibility with JPA model, which has user disabled by default
// userEntity.setEnabled(true); // userEntity.setEnabled(true);

View file

@ -98,6 +98,16 @@ public class UserAdapter implements UserModel, Comparable {
user.setUsername(username); user.setUsername(username);
} }
@Override
public Long getCreatedTimestamp() {
return user.getCreatedTimestamp();
}
@Override
public void setCreatedTimestamp(Long timestamp) {
user.setCreatedTimestamp(timestamp);
}
@Override @Override
public boolean isEnabled() { public boolean isEnabled() {
return user.isEnabled(); return user.isEnabled();

View file

@ -60,6 +60,17 @@ public class UserAdapter implements UserModel {
updated.setUsername(username); 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 @Override
public boolean isEnabled() { public boolean isEnabled() {
if (updated != null) return updated.isEnabled(); if (updated != null) return updated.isEnabled();

View file

@ -22,6 +22,7 @@ public class CachedUser implements Serializable {
private String id; private String id;
private String realm; private String realm;
private String username; private String username;
private Long createdTimestamp;
private String firstName; private String firstName;
private String lastName; private String lastName;
private String email; private String email;
@ -34,11 +35,11 @@ public class CachedUser implements Serializable {
private Set<String> requiredActions = new HashSet<>(); private Set<String> requiredActions = new HashSet<>();
private Set<String> roleMappings = new HashSet<String>(); private Set<String> roleMappings = new HashSet<String>();
public CachedUser(RealmModel realm, UserModel user) { public CachedUser(RealmModel realm, UserModel user) {
this.id = user.getId(); this.id = user.getId();
this.realm = realm.getId(); this.realm = realm.getId();
this.username = user.getUsername(); this.username = user.getUsername();
this.createdTimestamp = user.getCreatedTimestamp();
this.firstName = user.getFirstName(); this.firstName = user.getFirstName();
this.lastName = user.getLastName(); this.lastName = user.getLastName();
this.attributes.putAll(user.getAttributes()); this.attributes.putAll(user.getAttributes());
@ -66,6 +67,10 @@ public class CachedUser implements Serializable {
return username; return username;
} }
public Long getCreatedTimestamp() {
return createdTimestamp;
}
public String getFirstName() { public String getFirstName() {
return firstName; return firstName;
} }

View file

@ -52,6 +52,7 @@ public class JpaUserProvider implements UserProvider {
UserEntity entity = new UserEntity(); UserEntity entity = new UserEntity();
entity.setId(id); entity.setId(id);
entity.setCreatedTimestamp(System.currentTimeMillis());
entity.setUsername(username.toLowerCase()); entity.setUsername(username.toLowerCase());
entity.setRealmId(realm.getId()); entity.setRealmId(realm.getId());
em.persist(entity); em.persist(entity);

View file

@ -77,6 +77,16 @@ public class UserAdapter implements UserModel {
user.setUsername(username); user.setUsername(username);
} }
@Override
public Long getCreatedTimestamp() {
return user.getCreatedTimestamp();
}
@Override
public void setCreatedTimestamp(Long timestamp) {
user.setCreatedTimestamp(timestamp);
}
@Override @Override
public boolean isEnabled() { public boolean isEnabled() {
return user.isEnabled(); return user.isEnabled();

View file

@ -11,6 +11,7 @@ import javax.persistence.NamedQuery;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.UniqueConstraint; import javax.persistence.UniqueConstraint;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -44,6 +45,8 @@ public class UserEntity {
protected String username; protected String username;
@Column(name = "FIRST_NAME") @Column(name = "FIRST_NAME")
protected String firstName; protected String firstName;
@Column(name = "CREATED_TIMESTAMP")
protected Long createdTimestamp;
@Column(name = "LAST_NAME") @Column(name = "LAST_NAME")
protected String lastName; protected String lastName;
@Column(name = "EMAIL") @Column(name = "EMAIL")
@ -90,6 +93,14 @@ public class UserEntity {
this.username = username; this.username = username;
} }
public Long getCreatedTimestamp() {
return createdTimestamp;
}
public void setCreatedTimestamp(Long timestamp) {
createdTimestamp = timestamp;
}
public String getFirstName() { public String getFirstName() {
return firstName; return firstName;
} }

View file

@ -3,6 +3,7 @@ package org.keycloak.models.mongo.keycloak.adapters;
import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObject;
import com.mongodb.DBObject; import com.mongodb.DBObject;
import com.mongodb.QueryBuilder; import com.mongodb.QueryBuilder;
import org.keycloak.connections.mongo.api.MongoStore; import org.keycloak.connections.mongo.api.MongoStore;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
@ -274,6 +275,7 @@ public class MongoUserProvider implements UserProvider {
MongoUserEntity userEntity = new MongoUserEntity(); MongoUserEntity userEntity = new MongoUserEntity();
userEntity.setId(id); userEntity.setId(id);
userEntity.setUsername(username); userEntity.setUsername(username);
userEntity.setCreatedTimestamp(System.currentTimeMillis());
// Compatibility with JPA model, which has user disabled by default // Compatibility with JPA model, which has user disabled by default
// userEntity.setEnabled(true); // userEntity.setEnabled(true);
userEntity.setRealmId(realm.getId()); userEntity.setRealmId(realm.getId());

View file

@ -4,6 +4,7 @@ import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
import com.mongodb.DBObject; import com.mongodb.DBObject;
import com.mongodb.QueryBuilder; import com.mongodb.QueryBuilder;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.ProtocolMapperModel;
@ -70,6 +71,16 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
updateUser(); updateUser();
} }
@Override
public Long getCreatedTimestamp() {
return user.getCreatedTimestamp();
}
@Override
public void setCreatedTimestamp(Long timestamp) {
user.setCreatedTimestamp(timestamp);
}
@Override @Override
public boolean isEnabled() { public boolean isEnabled() {
return user.isEnabled(); return user.isEnabled();

View file

@ -686,6 +686,9 @@ public abstract class AbstractIdentityProviderTest {
UserModel federatedUser = getFederatedUser(); UserModel federatedUser = getFederatedUser();
assertNotNull(federatedUser); 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); doAssertFederatedUser(federatedUser, identityProviderModel, expectedEmail, isProfileUpdateExpected);

View file

@ -26,11 +26,15 @@ import org.junit.ClassRule;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.PasswordPolicy; import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.IDToken;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.OAuthClient; 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;
import org.keycloak.testsuite.pages.AppPage.RequestType; import org.keycloak.testsuite.pages.AppPage.RequestType;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;
@ -208,6 +212,22 @@ public class RegisterTest {
String userId = events.expectRegister("registerUserSuccess", "registerUserSuccess@email").assertEvent().getUserId(); String userId = events.expectRegister("registerUserSuccess", "registerUserSuccess@email").assertEvent().getUserId();
events.expectLogin().detail("username", "registerusersuccess").user(userId).assertEvent(); 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 @Test
@ -268,6 +288,13 @@ public class RegisterTest {
String userId = events.expectRegister("registerUserSuccessE@email", "registerUserSuccessE@email").assertEvent().getUserId(); String userId = events.expectRegister("registerUserSuccessE@email", "registerUserSuccessE@email").assertEvent().getUserId();
events.expectLogin().detail("username", "registerusersuccesse@email").user(userId).assertEvent(); 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 { } finally {
configureRelamRegistrationEmailAsUsername(false); configureRelamRegistrationEmailAsUsername(false);
} }

View file

@ -117,6 +117,8 @@ public class ImportTest extends AbstractModelTest {
// Test role mappings // Test role mappings
UserModel admin = session.users().getUserByUsername("admin", realm); UserModel admin = session.users().getUserByUsername("admin", realm);
// user without creation timestamp in import
Assert.assertNull(admin.getCreatedTimestamp());
Set<RoleModel> allRoles = admin.getRoleMappings(); Set<RoleModel> allRoles = admin.getRoleMappings();
Assert.assertEquals(3, allRoles.size()); Assert.assertEquals(3, allRoles.size());
Assert.assertTrue(allRoles.contains(realm.getRole("admin"))); Assert.assertTrue(allRoles.contains(realm.getRole("admin")));
@ -124,6 +126,8 @@ public class ImportTest extends AbstractModelTest {
Assert.assertTrue(allRoles.contains(otherApp.getRole("otherapp-admin"))); Assert.assertTrue(allRoles.contains(otherApp.getRole("otherapp-admin")));
UserModel wburke = session.users().getUserByUsername("wburke", realm); UserModel wburke = session.users().getUserByUsername("wburke", realm);
// user with creation timestamp in import
Assert.assertEquals(new Long(123654), wburke.getCreatedTimestamp());
allRoles = wburke.getRoleMappings(); allRoles = wburke.getRoleMappings();
Assert.assertEquals(2, allRoles.size()); Assert.assertEquals(2, allRoles.size());
Assert.assertFalse(allRoles.contains(realm.getRole("admin"))); Assert.assertFalse(allRoles.contains(realm.getRole("admin")));
@ -132,6 +136,10 @@ public class ImportTest extends AbstractModelTest {
Assert.assertEquals(0, wburke.getRealmRoleMappings().size()); 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(); Set<RoleModel> realmRoles = admin.getRealmRoleMappings();
Assert.assertEquals(1, realmRoles.size()); Assert.assertEquals(1, realmRoles.size());
Assert.assertEquals("admin", realmRoles.iterator().next().getName()); Assert.assertEquals("admin", realmRoles.iterator().next().getName());

View file

@ -8,6 +8,8 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction; import org.keycloak.models.UserModel.RequiredAction;
import static org.junit.Assert.assertNotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
@ -27,6 +29,9 @@ public class UserModelTest extends AbstractModelTest {
user.setFirstName("first-name"); user.setFirstName("first-name");
user.setLastName("last-name"); user.setLastName("last-name");
user.setEmail("email"); 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.CONFIGURE_TOTP);
user.addRequiredAction(RequiredAction.UPDATE_PASSWORD); user.addRequiredAction(RequiredAction.UPDATE_PASSWORD);
@ -195,6 +200,7 @@ public class UserModelTest extends AbstractModelTest {
public static void assertEquals(UserModel expected, UserModel actual) { public static void assertEquals(UserModel expected, UserModel actual) {
Assert.assertEquals(expected.getUsername(), actual.getUsername()); Assert.assertEquals(expected.getUsername(), actual.getUsername());
Assert.assertEquals(expected.getCreatedTimestamp(), actual.getCreatedTimestamp());
Assert.assertEquals(expected.getFirstName(), actual.getFirstName()); Assert.assertEquals(expected.getFirstName(), actual.getFirstName());
Assert.assertEquals(expected.getLastName(), actual.getLastName()); Assert.assertEquals(expected.getLastName(), actual.getLastName());

View file

@ -55,6 +55,7 @@
{ {
"username": "wburke", "username": "wburke",
"enabled": true, "enabled": true,
"createdTimestamp" : 123654,
"attributes": { "attributes": {
"email": "bburke@redhat.com" "email": "bburke@redhat.com"
}, },
@ -71,6 +72,7 @@
}, },
{ {
"username": "loginclient", "username": "loginclient",
"createdTimestamp" : "123655",
"enabled": true, "enabled": true,
"credentials": [ "credentials": [
{ {