[Major] Added user history to privilege

- firstLogin
- lastLogin
- lastPasswordChange
This commit is contained in:
Robert von Burg 2021-02-22 14:51:00 +01:00
parent 2ea91eb5d3
commit fd7362b2c1
14 changed files with 302 additions and 119 deletions

View File

@ -13,6 +13,7 @@ import li.strolch.privilege.base.AccessDeniedException;
import li.strolch.privilege.base.InvalidCredentialsException;
import li.strolch.privilege.model.UserState;
import li.strolch.privilege.model.internal.User;
import li.strolch.privilege.model.internal.UserHistory;
import li.strolch.privilege.policy.PrivilegePolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -139,7 +140,7 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler {
Map<String, String> properties = buildProperties(username, attrs, ldapGroups, strolchRoles);
return new User(username, username, null, null, null, -1, -1, firstName, lastName, UserState.REMOTE,
strolchRoles, locale, properties);
strolchRoles, locale, properties, new UserHistory());
}
protected abstract Map<String, String> buildProperties(String username, Attributes attrs, Set<String> ldapGroups,

View File

@ -21,7 +21,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
@ -29,10 +29,7 @@ import java.util.stream.Stream;
import li.strolch.privilege.base.*;
import li.strolch.privilege.model.*;
import li.strolch.privilege.model.internal.PrivilegeImpl;
import li.strolch.privilege.model.internal.Role;
import li.strolch.privilege.model.internal.User;
import li.strolch.privilege.model.internal.UserChallenge;
import li.strolch.privilege.model.internal.*;
import li.strolch.privilege.policy.PrivilegePolicy;
import li.strolch.privilege.xml.CertificateStubsDomWriter;
import li.strolch.privilege.xml.CertificateStubsSaxReader;
@ -396,6 +393,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
throw new PrivilegeModelException(MessageFormat.format(msg, userRep.getUsername()));
}
UserHistory history = new UserHistory();
byte[] passwordHash = null;
byte[] salt = null;
if (password != null) {
@ -408,10 +406,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
// hash password
passwordHash = this.encryptionHandler.hashPassword(password, salt);
history.setLastPasswordChange(ZonedDateTime.now());
}
// create new user
User newUser = createUser(userRep, passwordHash, salt);
User newUser = createUser(userRep, history, passwordHash, salt);
// detect privilege conflicts
assertNoPrivilegeConflict(newUser);
@ -458,6 +458,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
throw new PrivilegeModelException(MessageFormat.format(msg, userRep.getUsername()));
}
UserHistory history = existingUser.getHistory().getClone();
byte[] passwordHash = null;
byte[] salt = null;
if (password != null) {
@ -470,9 +471,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
// hash password
passwordHash = this.encryptionHandler.hashPassword(password, salt);
history.setLastPasswordChange(ZonedDateTime.now());
}
User newUser = createUser(userRep, passwordHash, salt);
User newUser = createUser(userRep, history, passwordHash, salt);
// detect privilege conflicts
assertNoPrivilegeConflict(newUser);
@ -503,7 +506,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
}
}
private User createUser(UserRep userRep, byte[] passwordHash, byte[] salt) {
private User createUser(UserRep userRep, UserHistory history, byte[] passwordHash, byte[] salt) {
String userId = userRep.getUserId();
String userName = userRep.getUsername();
String firstName = userRep.getFirstname();
@ -514,7 +517,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
Map<String, String> properties = userRep.getProperties();
return new User(userId, userName, passwordHash, salt, this.encryptionHandler.getAlgorithm(),
this.encryptionHandler.getIterations(), this.encryptionHandler.getKeyLength(), firstName, lastName,
state, roles, locale, properties);
state, roles, locale, properties, history);
}
@Override
@ -573,7 +576,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
// create new user
User newUser = new User(userId, username, password, salt, hashAlgorithm, hashIterations, hashKeyLength,
firstName, lastName, userState, roles, locale, propertyMap);
firstName, lastName, userState, roles, locale, propertyMap, existingUser.getHistory().getClone());
// detect privilege conflicts
assertNoPrivilegeConflict(newUser);
@ -654,7 +657,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(),
existingUser.getSalt(), existingUser.getHashAlgorithm(), existingUser.getHashIterations(),
existingUser.getHashKeyLength(), existingUser.getFirstname(), existingUser.getLastname(),
existingUser.getUserState(), newRoles, existingUser.getLocale(), existingUser.getProperties());
existingUser.getUserState(), newRoles, existingUser.getLocale(), existingUser.getProperties(),
existingUser.getHistory().getClone());
// detect privilege conflicts
assertNoPrivilegeConflict(newUser);
@ -701,7 +705,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(),
existingUser.getSalt(), existingUser.getHashAlgorithm(), existingUser.getHashIterations(),
existingUser.getHashKeyLength(), existingUser.getFirstname(), existingUser.getLastname(),
existingUser.getUserState(), newRoles, existingUser.getLocale(), existingUser.getProperties());
existingUser.getUserState(), newRoles, existingUser.getLocale(), existingUser.getProperties(),
existingUser.getHistory().getClone());
// delegate user replacement to persistence handler
this.persistenceHandler.replaceUser(newUser);
@ -731,7 +736,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(),
existingUser.getSalt(), existingUser.getHashAlgorithm(), existingUser.getHashIterations(),
existingUser.getHashKeyLength(), existingUser.getFirstname(), existingUser.getLastname(),
existingUser.getUserState(), existingUser.getRoles(), locale, existingUser.getProperties());
existingUser.getUserState(), existingUser.getRoles(), locale, existingUser.getProperties(),
existingUser.getHistory().getClone());
// if the user is not setting their own locale, then make sure this user may set this user's locale
if (!certificate.getUsername().equals(username)) {
@ -766,6 +772,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$
}
UserHistory history = existingUser.getHistory().getClone();
byte[] passwordHash = null;
byte[] salt = null;
if (password != null) {
@ -778,6 +786,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
// hash password
passwordHash = this.encryptionHandler.hashPassword(password, salt);
history.setLastPasswordChange(ZonedDateTime.now());
}
// create new user
@ -785,7 +795,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
this.encryptionHandler.getAlgorithm(), this.encryptionHandler.getIterations(),
this.encryptionHandler.getKeyLength(), existingUser.getFirstname(), existingUser.getLastname(),
existingUser.getUserState(), existingUser.getRoles(), existingUser.getLocale(),
existingUser.getProperties());
existingUser.getProperties(), history);
if (!certificate.getUsername().equals(username)) {
// check that the user may change their own password
@ -821,15 +831,15 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
// get User
User existingUser = this.persistenceHandler.getUser(username);
if (existingUser == null) {
if (existingUser == null)
throw new PrivilegeModelException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$
}
// create new user
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(),
existingUser.getSalt(), existingUser.getHashAlgorithm(), existingUser.getHashIterations(),
existingUser.getHashKeyLength(), existingUser.getFirstname(), existingUser.getLastname(), state,
existingUser.getRoles(), existingUser.getLocale(), existingUser.getProperties());
existingUser.getRoles(), existingUser.getLocale(), existingUser.getProperties(),
existingUser.getHistory().getClone());
// validate that this user may modify this user's state
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_SET_USER_STATE, new Tuple(existingUser, newUser)));
@ -1159,7 +1169,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
// create a new certificate, with details of the user
Usage usage = userChallenge.getUsage();
Certificate certificate = buildCertificate(usage, user, authToken, sessionId, userChallenge.getSource(),
LocalDateTime.now(), false);
ZonedDateTime.now(), false);
PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user);
this.privilegeContextMap.put(sessionId, privilegeContext);
@ -1197,10 +1207,9 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
// validate user has at least one role
Set<String> userRoles = user.getRoles();
if (userRoles.isEmpty()) {
if (userRoles.isEmpty())
throw new InvalidCredentialsException(
MessageFormat.format("User {0} does not have any roles defined!", username)); //$NON-NLS-1$
}
// get 2 auth tokens
String authToken = this.encryptionHandler.nextToken();
@ -1209,7 +1218,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
String sessionId = UUID.randomUUID().toString();
// create a new certificate, with details of the user
Certificate certificate = buildCertificate(usage, user, authToken, sessionId, source, LocalDateTime.now(),
Certificate certificate = buildCertificate(usage, user, authToken, sessionId, source, ZonedDateTime.now(),
keepAlive);
PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user);
@ -1217,6 +1226,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
persistSessions();
// save last login
if (user.getHistory().isFirstLoginEmpty())
user.getHistory().setFirstLogin(ZonedDateTime.now());
user.getHistory().setLastLogin(ZonedDateTime.now());
this.persistenceHandler.replaceUser(user);
if (this.autoPersistOnUserChangesData)
this.persistenceHandler.persist();
// log
logger.info(MessageFormat.format("User {0} authenticated: {1}", username, certificate)); //$NON-NLS-1$
@ -1249,13 +1266,17 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
User user = this.ssoHandler.authenticateSingleSignOn(data);
DBC.PRE.assertEquals("SSO Users must have UserState.REMOTE!", UserState.REMOTE, user.getUserState());
user.getHistory().setLastLogin(ZonedDateTime.now());
// persist this user
User internalUser = this.persistenceHandler.getUser(user.getUsername());
if (internalUser == null)
if (internalUser == null) {
user.getHistory().setFirstLogin(ZonedDateTime.now());
this.persistenceHandler.addUser(user);
else
} else {
user.getHistory().setFirstLogin(internalUser.getHistory().getFirstLogin());
this.persistenceHandler.replaceUser(user);
}
if (this.autoPersistOnUserChangesData)
this.persistenceHandler.persist();
@ -1267,7 +1288,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
String sessionId = UUID.randomUUID().toString();
// create a new certificate, with details of the user
Certificate certificate = buildCertificate(Usage.ANY, user, authToken, sessionId, source, LocalDateTime.now(),
Certificate certificate = buildCertificate(Usage.ANY, user, authToken, sessionId, source, ZonedDateTime.now(),
keepAlive);
PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user);
@ -1311,7 +1332,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
// create a new certificate, with details of the user
Certificate refreshedCert = buildCertificate(certificate.getUsage(), user, authToken, sessionId, source,
LocalDateTime.now(), true);
ZonedDateTime.now(), true);
PrivilegeContext privilegeContext = buildPrivilegeContext(refreshedCert, user);
this.privilegeContextMap.put(sessionId, privilegeContext);
@ -1339,7 +1360,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
}
private Certificate buildCertificate(Usage usage, User user, String authToken, String sessionId, String source,
LocalDateTime loginTime, boolean keepAlive) {
ZonedDateTime loginTime, boolean keepAlive) {
DBC.PRE.assertNotEmpty("source must not be empty!", source);
Set<String> userRoles = user.getRoles();
return new Certificate(usage, sessionId, user.getUsername(), user.getFirstname(), user.getLastname(),
@ -1527,7 +1548,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
User newUser = new User(user.getUserId(), user.getUsername(), passwordHash, salt,
this.encryptionHandler.getAlgorithm(), this.encryptionHandler.getIterations(),
this.encryptionHandler.getKeyLength(), user.getFirstname(), user.getLastname(), user.getUserState(),
user.getRoles(), user.getLocale(), user.getProperties());
user.getRoles(), user.getLocale(), user.getProperties(), user.getHistory().getClone());
// delegate user replacement to persistence handler
this.persistenceHandler.replaceUser(newUser);
@ -1685,7 +1706,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
throw new PrivilegeException(msg);
}
certificate.setLastAccess(LocalDateTime.now());
certificate.setLastAccess(ZonedDateTime.now());
if (!certificate.getSource().equals(this.identifier))
throw new IllegalStateException(
@ -1717,14 +1738,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
// validate that challenge certificate is not expired (1 hour only)
if (sessionCertificate.getUsage() != Usage.ANY) {
LocalDateTime dateTime = sessionCertificate.getLoginTime();
if (dateTime.plusHours(1).isBefore(LocalDateTime.now())) {
ZonedDateTime dateTime = sessionCertificate.getLoginTime();
if (dateTime.plusHours(1).isBefore(ZonedDateTime.now())) {
invalidate(sessionCertificate);
throw new NotAuthenticatedException("Certificate has already expired!"); //$NON-NLS-1$
}
}
certificate.setLastAccess(LocalDateTime.now());
certificate.setLastAccess(ZonedDateTime.now());
// TODO decide if we want to assert source did not change!
// if (!source.equals(SOURCE_UNKNOWN) && !certificate.getSource().equals(source)) {
@ -2150,7 +2171,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
// create a new certificate, with details of the user
Certificate systemUserCertificate = buildCertificate(Usage.ANY, user, authToken, sessionId, this.identifier,
LocalDateTime.now(), false);
ZonedDateTime.now(), false);
// create and save a new privilege context
PrivilegeContext privilegeContext = buildPrivilegeContext(systemUserCertificate, user);

View File

@ -108,6 +108,26 @@ public class XmlConstants {
*/
public static final String XML_USER = "User";
/**
* XML_USER = "User"
*/
public static final String XML_HISTORY = "History";
/**
* XML_USER = "User"
*/
public static final String XML_FIRST_LOGIN = "FirstLogin";
/**
* XML_USER = "User"
*/
public static final String XML_LAST_LOGIN = "LastLogin";
/**
* XML_USER = "User"
*/
public static final String XML_LAST_PASSWORD_CHANGE = "LastPasswordChange";
/**
* XML_PRIVILEGE = "Privilege" :
*/

View File

@ -18,7 +18,7 @@ package li.strolch.privilege.model;
import static li.strolch.privilege.base.PrivilegeConstants.*;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
@ -47,14 +47,14 @@ public final class Certificate implements Serializable {
private final UserState userState;
private final String authToken;
private final String source;
private final LocalDateTime loginTime;
private final ZonedDateTime loginTime;
private final boolean keepAlive;
private final Set<String> userRoles;
private final Map<String, String> propertyMap;
private Locale locale;
private LocalDateTime lastAccess;
private ZonedDateTime lastAccess;
/**
* Default constructor initializing with all information needed for this certificate
@ -85,7 +85,7 @@ public final class Certificate implements Serializable {
* edited and can be used for the user to change settings of this session
*/
public Certificate(Usage usage, String sessionId, String username, String firstName, String lastName,
UserState userState, String authToken, String source, LocalDateTime loginTime, boolean keepAlive,
UserState userState, String authToken, String source, ZonedDateTime loginTime, boolean keepAlive,
Locale locale, Set<String> userRoles, Map<String, String> propertyMap) {
// validate arguments are not null
@ -131,7 +131,7 @@ public final class Certificate implements Serializable {
this.propertyMap = Collections.unmodifiableMap(propertyMap);
this.userRoles = Collections.unmodifiableSet(userRoles);
this.lastAccess = LocalDateTime.now();
this.lastAccess = ZonedDateTime.now();
}
public Usage getUsage() {
@ -239,7 +239,7 @@ public final class Certificate implements Serializable {
return userState;
}
public LocalDateTime getLoginTime() {
public ZonedDateTime getLoginTime() {
return this.loginTime;
}
@ -255,11 +255,11 @@ public final class Certificate implements Serializable {
return this.source;
}
public LocalDateTime getLastAccess() {
public ZonedDateTime getLastAccess() {
return this.lastAccess;
}
public void setLastAccess(LocalDateTime lastAccess) {
public void setLastAccess(ZonedDateTime lastAccess) {
this.lastAccess = lastAccess;
}

View File

@ -50,14 +50,14 @@ public final class User {
private final String firstname;
private final String lastname;
private final UserState userState;
private final Set<String> roles;
private final UserState userState;
private final Map<String, String> propertyMap;
private final Locale locale;
private final UserHistory history;
/**
* Default constructor
*
@ -90,26 +90,24 @@ public final class User {
*/
public User(String userId, String username, byte[] password, byte[] salt, String hashAlgorithm, int hashIterations,
int hashKeyLength, String firstname, String lastname, UserState userState, Set<String> roles, Locale locale,
Map<String, String> propertyMap) {
Map<String, String> propertyMap, UserHistory history) {
if (StringHelper.isEmpty(userId)) {
if (StringHelper.isEmpty(userId))
throw new PrivilegeException("No UserId defined!"); //$NON-NLS-1$
}
if (userState == null) {
if (userState == null)
throw new PrivilegeException("No userState defined!"); //$NON-NLS-1$
}
if (StringHelper.isEmpty(username)) {
if (StringHelper.isEmpty(username))
throw new PrivilegeException("No username defined!"); //$NON-NLS-1$
}
if (userState != UserState.SYSTEM) {
if (StringHelper.isEmpty(lastname)) {
if (StringHelper.isEmpty(lastname))
throw new PrivilegeException("No lastname defined!"); //$NON-NLS-1$
}
if (StringHelper.isEmpty(firstname)) {
if (StringHelper.isEmpty(firstname))
throw new PrivilegeException("No firstname defined!"); //$NON-NLS-1$
}
}
if (history == null)
throw new PrivilegeException("History must not be null!");
// password, salt and hash* may be null, meaning not able to login
// roles may be null, meaning not able to login and must be added later
// locale may be null, meaning use system default
@ -133,7 +131,7 @@ public final class User {
if (roles == null)
this.roles = Collections.emptySet();
else
this.roles = Collections.unmodifiableSet(new HashSet<>(roles));
this.roles = Set.copyOf(roles);
if (locale == null)
this.locale = Locale.getDefault();
@ -143,7 +141,9 @@ public final class User {
if (propertyMap == null)
this.propertyMap = Collections.emptyMap();
else
this.propertyMap = Collections.unmodifiableMap(new HashMap<>(propertyMap));
this.propertyMap = Map.copyOf(propertyMap);
this.history = history;
}
/**
@ -252,6 +252,24 @@ public final class User {
return this.locale;
}
/**
* Returns the History object
*
* @return the History object
*/
public UserHistory getHistory() {
return this.history;
}
/**
* Returns true if the history for this user is empty
*
* @return true if the history for this user is empty
*/
public boolean isHistoryEmpty() {
return this.history.isEmpty();
}
/**
* Returns the property with the given key
*

View File

@ -0,0 +1,66 @@
package li.strolch.privilege.model.internal;
import java.time.ZonedDateTime;
import li.strolch.utils.iso8601.ISO8601;
public class UserHistory {
private ZonedDateTime firstLogin;
private ZonedDateTime lastLogin;
private ZonedDateTime lastPasswordChange;
public UserHistory() {
this.firstLogin = ISO8601.EMPTY_VALUE_ZONED_DATE;
this.lastLogin = ISO8601.EMPTY_VALUE_ZONED_DATE;
this.lastPasswordChange = ISO8601.EMPTY_VALUE_ZONED_DATE;
}
public ZonedDateTime getFirstLogin() {
return this.firstLogin;
}
public boolean isFirstLoginEmpty() {
return this.firstLogin.equals(ISO8601.EMPTY_VALUE_ZONED_DATE);
}
public void setFirstLogin(ZonedDateTime firstLogin) {
this.firstLogin = firstLogin;
}
public ZonedDateTime getLastLogin() {
return this.lastLogin;
}
public boolean isLastLoginEmpty() {
return this.lastLogin.equals(ISO8601.EMPTY_VALUE_ZONED_DATE);
}
public void setLastLogin(ZonedDateTime lastLogin) {
this.lastLogin = lastLogin;
}
public ZonedDateTime getLastPasswordChange() {
return this.lastPasswordChange;
}
public boolean isLastPasswordChangeEmpty() {
return this.lastPasswordChange.equals(ISO8601.EMPTY_VALUE_ZONED_DATE);
}
public void setLastPasswordChange(ZonedDateTime lastPasswordChange) {
this.lastPasswordChange = lastPasswordChange;
}
public boolean isEmpty() {
return isFirstLoginEmpty() && isLastLoginEmpty() && isLastPasswordChangeEmpty();
}
public UserHistory getClone() {
UserHistory clone = new UserHistory();
clone.firstLogin = this.firstLogin;
clone.lastLogin = this.lastLogin;
clone.lastPasswordChange = this.lastPasswordChange;
return clone;
}
}

View File

@ -20,7 +20,7 @@ import static li.strolch.privilege.helper.XmlConstants.*;
import static li.strolch.utils.helper.StringHelper.isEmpty;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@ -67,8 +67,8 @@ public class CertificateStubsSaxReader extends DefaultHandler {
stub.authToken = attributes.getValue(XML_ATTR_AUTH_TOKEN);
stub.source = attributes.getValue(XML_ATTR_SOURCE);
stub.locale = Locale.forLanguageTag(attributes.getValue(XML_ATTR_LOCALE));
stub.loginTime = ISO8601.parseToZdt(attributes.getValue(XML_ATTR_LOGIN_TIME)).toLocalDateTime();
stub.lastAccess = ISO8601.parseToZdt(attributes.getValue(XML_ATTR_LAST_ACCESS)).toLocalDateTime();
stub.loginTime = ISO8601.parseToZdt(attributes.getValue(XML_ATTR_LOGIN_TIME));
stub.lastAccess = ISO8601.parseToZdt(attributes.getValue(XML_ATTR_LAST_ACCESS));
stub.keepAlive = Boolean.parseBoolean(attributes.getValue(XML_ATTR_KEEP_ALIVE));
DBC.INTERIM.assertNotEmpty("sessionId missing on sessions data!", stub.sessionId);
@ -93,8 +93,8 @@ public class CertificateStubsSaxReader extends DefaultHandler {
private String authToken;
private String source;
private Locale locale;
private LocalDateTime loginTime;
private LocalDateTime lastAccess;
private ZonedDateTime loginTime;
private ZonedDateTime lastAccess;
private boolean keepAlive;
public Usage getUsage() {
@ -121,11 +121,11 @@ public class CertificateStubsSaxReader extends DefaultHandler {
return locale;
}
public LocalDateTime getLoginTime() {
public ZonedDateTime getLoginTime() {
return loginTime;
}
public LocalDateTime getLastAccess() {
public ZonedDateTime getLastAccess() {
return lastAccess;
}

View File

@ -16,16 +16,18 @@
package li.strolch.privilege.xml;
import static java.util.Comparator.comparing;
import static li.strolch.privilege.helper.CryptHelper.buildPasswordString;
import java.io.File;
import java.util.List;
import java.util.Map;
import li.strolch.privilege.helper.CryptHelper;
import li.strolch.privilege.helper.XmlConstants;
import li.strolch.privilege.model.internal.User;
import li.strolch.privilege.model.internal.UserHistory;
import li.strolch.utils.helper.StringHelper;
import li.strolch.utils.helper.XmlHelper;
import li.strolch.utils.iso8601.ISO8601;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@ -34,8 +36,8 @@ import org.w3c.dom.Element;
*/
public class PrivilegeUsersDomWriter {
private List<User> users;
private File modelFile;
private final List<User> users;
private final File modelFile;
public PrivilegeUsersDomWriter(List<User> users, File modelFile) {
this.users = users;
@ -98,13 +100,37 @@ public class PrivilegeUsersDomWriter {
if (!user.getProperties().isEmpty()) {
Element parametersElement = doc.createElement(XmlConstants.XML_PROPERTIES);
userElement.appendChild(parametersElement);
user.getProperties().entrySet().stream().sorted(comparing(Map.Entry::getKey)).forEach(entry -> {
user.getProperties().entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry -> {
Element paramElement = doc.createElement(XmlConstants.XML_PROPERTY);
paramElement.setAttribute(XmlConstants.XML_ATTR_NAME, entry.getKey());
paramElement.setAttribute(XmlConstants.XML_ATTR_VALUE, entry.getValue());
parametersElement.appendChild(paramElement);
});
}
if (!user.isHistoryEmpty()) {
UserHistory history = user.getHistory();
Element historyElement = doc.createElement(XmlConstants.XML_HISTORY);
userElement.appendChild(historyElement);
if (!history.isFirstLoginEmpty()) {
Element element = doc.createElement(XmlConstants.XML_FIRST_LOGIN);
element.setTextContent(ISO8601.toString(history.getFirstLogin()));
historyElement.appendChild(element);
}
if (!history.isLastLoginEmpty()) {
Element element = doc.createElement(XmlConstants.XML_LAST_LOGIN);
element.setTextContent(ISO8601.toString(history.getLastLogin()));
historyElement.appendChild(element);
}
if (!history.isLastPasswordChangeEmpty()) {
Element element = doc.createElement(XmlConstants.XML_LAST_PASSWORD_CHANGE);
element.setTextContent(ISO8601.toString(history.getLastPasswordChange()));
historyElement.appendChild(element);
}
}
});
// write the container file to disk
@ -112,15 +138,10 @@ public class PrivilegeUsersDomWriter {
}
private void writePassword(User user, Element userElement) {
if (user.getPassword() != null && user.getSalt() != null && user.getHashAlgorithm() != null
&& user.getHashIterations() != -1 && user.getHashKeyLength() != -1) {
String passwordS = CryptHelper.buildPasswordString(user);
userElement.setAttribute(XmlConstants.XML_ATTR_PASSWORD, passwordS);
userElement.setAttribute(XmlConstants.XML_ATTR_PASSWORD, buildPasswordString(user));
} else {
if (user.getPassword() != null)
userElement.setAttribute(XmlConstants.XML_ATTR_PASSWORD, StringHelper.toHexString(user.getPassword()));
if (user.getSalt() != null)

View File

@ -15,13 +15,16 @@
*/
package li.strolch.privilege.xml;
import static li.strolch.privilege.helper.XmlConstants.*;
import java.text.MessageFormat;
import java.util.*;
import li.strolch.privilege.helper.XmlConstants;
import li.strolch.privilege.model.UserState;
import li.strolch.privilege.model.internal.User;
import li.strolch.privilege.model.internal.UserHistory;
import li.strolch.utils.helper.StringHelper;
import li.strolch.utils.iso8601.ISO8601;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
@ -35,9 +38,9 @@ public class PrivilegeUsersSaxReader extends DefaultHandler {
protected static final Logger logger = LoggerFactory.getLogger(PrivilegeUsersSaxReader.class);
private Deque<ElementParser> buildersStack = new ArrayDeque<>();
private final Deque<ElementParser> buildersStack = new ArrayDeque<>();
private List<User> users;
private final List<User> users;
public PrivilegeUsersSaxReader() {
this.users = new ArrayList<>();
@ -52,9 +55,9 @@ public class PrivilegeUsersSaxReader extends DefaultHandler {
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (qName.equals(XmlConstants.XML_USER)) {
if (qName.equals(XML_USER)) {
this.buildersStack.push(new UserParser());
} else if (qName.equals(XmlConstants.XML_PROPERTIES)) {
} else if (qName.equals(XML_PROPERTIES)) {
this.buildersStack.push(new PropertyParser());
}
@ -75,9 +78,9 @@ public class PrivilegeUsersSaxReader extends DefaultHandler {
this.buildersStack.peek().endElement(uri, localName, qName);
ElementParser elementParser = null;
if (qName.equals(XmlConstants.XML_USER)) {
if (qName.equals(XML_USER)) {
elementParser = this.buildersStack.pop();
} else if (qName.equals(XmlConstants.XML_PROPERTIES)) {
} else if (qName.equals(XML_PROPERTIES)) {
elementParser = this.buildersStack.pop();
}
@ -98,6 +101,11 @@ public class PrivilegeUsersSaxReader extends DefaultHandler {
// <Property name="organization" value="eitchnet.ch" />
// <Property name="organizationalUnit" value="Development" />
// </Properties>
// <History>
// <FirstLogin>2021-02-19T15:32:09.592+01:00</FirstLogin>
// <LastLogin>2021-02-19T15:32:09.592+01:00</LastLogin>
// <LastPasswordChange>2021-02-19T15:32:09.592+01:00</LastPasswordChange>
// </History>
// </User>
public class UserParser extends ElementParserAdapter {
@ -117,6 +125,7 @@ public class PrivilegeUsersSaxReader extends DefaultHandler {
Locale locale;
Set<String> userRoles;
Map<String, String> parameters;
UserHistory history;
public UserParser() {
this.userRoles = new HashSet<>();
@ -128,13 +137,15 @@ public class PrivilegeUsersSaxReader extends DefaultHandler {
this.text = new StringBuilder();
if (qName.equals(XmlConstants.XML_USER)) {
this.userId = attributes.getValue(XmlConstants.XML_ATTR_USER_ID);
this.username = attributes.getValue(XmlConstants.XML_ATTR_USERNAME);
if (qName.equals(XML_USER)) {
this.userId = attributes.getValue(XML_ATTR_USER_ID);
this.username = attributes.getValue(XML_ATTR_USERNAME);
String password = attributes.getValue(XmlConstants.XML_ATTR_PASSWORD);
String salt = attributes.getValue(XmlConstants.XML_ATTR_SALT);
String password = attributes.getValue(XML_ATTR_PASSWORD);
String salt = attributes.getValue(XML_ATTR_SALT);
parsePassword(password, salt);
} else if (qName.equals(XML_HISTORY)) {
this.history = new UserHistory();
}
}
@ -183,36 +194,54 @@ public class PrivilegeUsersSaxReader extends DefaultHandler {
public void endElement(String uri, String localName, String qName) throws SAXException {
switch (qName) {
case XmlConstants.XML_FIRSTNAME:
case XML_FIRSTNAME:
this.firstName = this.text.toString().trim();
break;
case XmlConstants.XML_LASTNAME:
case XML_LASTNAME:
this.lastname = this.text.toString().trim();
break;
case XmlConstants.XML_STATE:
case XML_STATE:
this.userState = UserState.valueOf(this.text.toString().trim());
break;
case XmlConstants.XML_LOCALE:
case XML_LOCALE:
this.locale = Locale.forLanguageTag(this.text.toString().trim());
break;
case XmlConstants.XML_ROLE:
case XML_FIRST_LOGIN:
this.history.setFirstLogin(ISO8601.parseToZdt(this.text.toString().trim()));
break;
case XML_LAST_LOGIN:
this.history.setLastLogin(ISO8601.parseToZdt(this.text.toString().trim()));
break;
case XML_LAST_PASSWORD_CHANGE:
this.history.setLastPasswordChange(ISO8601.parseToZdt(this.text.toString().trim()));
break;
case XML_ROLE:
this.userRoles.add(this.text.toString().trim());
break;
case XmlConstants.XML_USER:
case XML_USER:
if (this.history == null)
this.history = new UserHistory();
User user = new User(this.userId, this.username, this.password, this.salt, this.hashAlgorithm,
hashIterations, hashKeyLength, this.firstName, this.lastname, this.userState, this.userRoles,
this.locale, this.parameters);
this.locale, this.parameters, this.history);
logger.info(MessageFormat.format("New User: {0}", user)); //$NON-NLS-1$
getUsers().add(user);
@ -220,9 +249,10 @@ public class PrivilegeUsersSaxReader extends DefaultHandler {
default:
if (!(qName.equals(XmlConstants.XML_ROLES) //
|| qName.equals(XmlConstants.XML_PARAMETER) //
|| qName.equals(XmlConstants.XML_PARAMETERS))) {
if (!(qName.equals(XML_ROLES) //
|| qName.equals(XML_PARAMETER) //
|| qName.equals(XML_HISTORY) //
|| qName.equals(XML_PARAMETERS))) {
throw new IllegalArgumentException("Unhandled tag " + qName);
}
@ -238,7 +268,7 @@ public class PrivilegeUsersSaxReader extends DefaultHandler {
}
}
class PropertyParser extends ElementParserAdapter {
static class PropertyParser extends ElementParserAdapter {
// <Property name="organizationalUnit" value="Development" />
@ -249,16 +279,16 @@ public class PrivilegeUsersSaxReader extends DefaultHandler {
throws SAXException {
switch (qName) {
case XmlConstants.XML_PROPERTY:
case XML_PROPERTY:
String key = attributes.getValue(XmlConstants.XML_ATTR_NAME);
String value = attributes.getValue(XmlConstants.XML_ATTR_VALUE);
String key = attributes.getValue(XML_ATTR_NAME);
String value = attributes.getValue(XML_ATTR_VALUE);
this.parameterMap.put(key, value);
break;
default:
if (!qName.equals(XmlConstants.XML_PROPERTIES)) {
if (!qName.equals(XML_PROPERTIES)) {
throw new IllegalArgumentException("Unhandled tag " + qName);
}
}

View File

@ -19,6 +19,9 @@ import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.*;
import java.io.File;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.*;
import li.strolch.privilege.handler.DefaultEncryptionHandler;
@ -27,10 +30,7 @@ import li.strolch.privilege.handler.PrivilegeHandler;
import li.strolch.privilege.handler.XmlPersistenceHandler;
import li.strolch.privilege.model.IPrivilege;
import li.strolch.privilege.model.UserState;
import li.strolch.privilege.model.internal.PrivilegeContainerModel;
import li.strolch.privilege.model.internal.PrivilegeImpl;
import li.strolch.privilege.model.internal.Role;
import li.strolch.privilege.model.internal.User;
import li.strolch.privilege.model.internal.*;
import li.strolch.privilege.test.model.DummySsoHandler;
import li.strolch.privilege.xml.*;
import li.strolch.utils.helper.FileHelper;
@ -316,16 +316,21 @@ public class XmlTest {
propertyMap.put("prop1", "value1");
userRoles = new HashSet<>();
userRoles.add("role1");
UserHistory history = new UserHistory();
history.setFirstLogin(ZonedDateTime.of(LocalDateTime.of(2020, 1, 2, 2, 3, 4, 5), ZoneId.systemDefault()));
User user1 = new User("1", "user1", "blabla".getBytes(), "blabla".getBytes(), "PBKDF2WithHmacSHA512", 10000,
256, "Bob", "White", UserState.DISABLED, userRoles, Locale.ENGLISH, propertyMap);
256, "Bob", "White", UserState.DISABLED, userRoles, Locale.ENGLISH, propertyMap, history);
users.add(user1);
propertyMap = new HashMap<>();
propertyMap.put("prop2", "value2");
userRoles = new HashSet<>();
userRoles.add("role2");
history = new UserHistory();
history.setFirstLogin(ZonedDateTime.of(LocalDateTime.of(2020, 1, 2, 2, 3, 4, 5), ZoneId.systemDefault()));
history.setLastLogin(ZonedDateTime.of(LocalDateTime.of(2020, 1, 5, 2, 3, 4, 5), ZoneId.systemDefault()));
User user2 = new User("2", "user2", "haha".getBytes(), "haha".getBytes(), null, -1, -1, "Leonard", "Sheldon",
UserState.ENABLED, userRoles, Locale.ENGLISH, propertyMap);
UserState.ENABLED, userRoles, Locale.ENGLISH, propertyMap, history);
users.add(user2);
File modelFile = new File(TARGET_TEST + "PrivilegeUsersTest.xml");

View File

@ -7,6 +7,7 @@ import li.strolch.privilege.base.PrivilegeException;
import li.strolch.privilege.handler.SingleSignOnHandler;
import li.strolch.privilege.model.UserState;
import li.strolch.privilege.model.internal.User;
import li.strolch.privilege.model.internal.UserHistory;
public class DummySsoHandler implements SingleSignOnHandler {
@ -31,6 +32,6 @@ public class DummySsoHandler implements SingleSignOnHandler {
Set<String> roles = Arrays.stream(map.get("roles").split(",")).map(String::trim).collect(Collectors.toSet());
Map<String, String> properties = new HashMap<>();
return new User(map.get("userId"), map.get("username"), null, null, null, -1, -1, map.get("firstName"),
map.get("lastName"), UserState.REMOTE, roles, Locale.ENGLISH, properties);
map.get("lastName"), UserState.REMOTE, roles, Locale.ENGLISH, properties, new UserHistory());
}
}

View File

@ -19,7 +19,7 @@ import static li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants.PRIV
import static li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants.PRIVILEGE_INVALIDATE_SESSION;
import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.Future;
@ -313,8 +313,8 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St
certificateMap = new HashMap<>(this.certificateMap);
}
LocalDateTime maxKeepAliveTime = LocalDateTime.now().minus(this.maxKeepAliveMinutes, ChronoUnit.MINUTES);
LocalDateTime timeOutTime = LocalDateTime.now().minus(this.sessionTtlMinutes, ChronoUnit.MINUTES);
ZonedDateTime maxKeepAliveTime = ZonedDateTime.now().minus(this.maxKeepAliveMinutes, ChronoUnit.MINUTES);
ZonedDateTime timeOutTime = ZonedDateTime.now().minus(this.sessionTtlMinutes, ChronoUnit.MINUTES);
for (Certificate certificate : certificateMap.values()) {
if (certificate.isKeepAlive()) {

View File

@ -15,7 +15,7 @@
*/
package li.strolch.rest.model;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.Locale;
import java.util.Set;
@ -28,14 +28,14 @@ public class UserSession {
private final boolean keepAlive;
private final String sessionId;
private final LocalDateTime loginTime;
private final ZonedDateTime loginTime;
private final String username;
private final String firstName;
private final String lastName;
private final String source;
private final Set<String> userRoles;
private final Locale locale;
private final LocalDateTime lastAccess;
private final ZonedDateTime lastAccess;
public UserSession(Certificate certificate) {
this.sessionId = certificate.getSessionId();
@ -54,7 +54,7 @@ public class UserSession {
return locale;
}
public LocalDateTime getLastAccess() {
public ZonedDateTime getLastAccess() {
return lastAccess;
}
@ -62,7 +62,7 @@ public class UserSession {
return sessionId;
}
public LocalDateTime getLoginTime() {
public ZonedDateTime getLoginTime() {
return loginTime;
}

View File

@ -19,7 +19,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThrows;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.HashSet;
import li.strolch.privilege.base.AccessDeniedException;
@ -45,7 +45,7 @@ public class ServiceTest extends AbstractServiceTest {
assertThrows(PrivilegeException.class, () -> {
TestService testService = new TestService();
getServiceHandler().doService(
new Certificate(null, null, null, null, null, null, null, null, LocalDateTime.now(), false, null,
new Certificate(null, null, null, null, null, null, null, null, ZonedDateTime.now(), false, null,
new HashSet<>(), null), testService);
});
}
@ -54,7 +54,7 @@ public class ServiceTest extends AbstractServiceTest {
public void shouldFailInvalidCertificate2() {
TestService testService = new TestService();
Certificate badCert = new Certificate(Usage.ANY, "1", "bob", "Bob", "Brown", UserState.ENABLED, "dsdf", "asd",
LocalDateTime.now(), false, null, new HashSet<>(), null);
ZonedDateTime.now(), false, null, new HashSet<>(), null);
ServiceResult svcResult = getServiceHandler().doService(badCert, testService);
assertThat(svcResult.getThrowable(), instanceOf(NotAuthenticatedException.class));
}