/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package li.strolch.privilege.handler;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.crypto.SecretKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import li.strolch.privilege.base.AccessDeniedException;
import li.strolch.privilege.base.InvalidCredentialsException;
import li.strolch.privilege.base.PrivilegeConflictResolution;
import li.strolch.privilege.base.PrivilegeException;
import li.strolch.privilege.model.Certificate;
import li.strolch.privilege.model.IPrivilege;
import li.strolch.privilege.model.PrivilegeContext;
import li.strolch.privilege.model.PrivilegeRep;
import li.strolch.privilege.model.RoleRep;
import li.strolch.privilege.model.SimpleRestrictable;
import li.strolch.privilege.model.UserRep;
import li.strolch.privilege.model.UserState;
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.policy.PrivilegePolicy;
import li.strolch.privilege.xml.CertificateStubsDomWriter;
import li.strolch.privilege.xml.CertificateStubsSaxReader;
import li.strolch.privilege.xml.CertificateStubsSaxReader.CertificateStub;
import li.strolch.utils.collections.Tuple;
import li.strolch.utils.helper.AesCryptoHelper;
import li.strolch.utils.helper.StringHelper;
/**
* <p>
* This is default implementation of the {@link PrivilegeHandler}
* </p>
*
* The following list describes implementation details:
* <ul>
* <li>any methods which change the model are first validated by checking if the certificate is for an admin user by
* calling {@link #assertIsPrivilegeAdmin(Certificate)}</li>
* <li>all model requests are delegated to the configured {@link PrivilegeHandler}, except for the session id to
* {@link Certificate} map, no model data is kept in this implementation. This also means that to return the
* representation objects, for every new model query, a new representation object is created</li>
* <li>when creating new users, or editing users then a null password is understood as no password set</li>
* <li>Password requirements are simple: Non null and non empty/length 0</li>
* </ul>
*
* @author Robert von Burg <eitch@eitchnet.ch>
*/
public class DefaultPrivilegeHandler implements PrivilegeHandler {
/**
* slf4j logger
*/
protected static final Logger logger = LoggerFactory.getLogger(DefaultPrivilegeHandler.class);
/**
* Map keeping a reference to all active sessions
*/
private Map<String, PrivilegeContext> privilegeContextMap;
/**
* Map of {@link PrivilegePolicy} classes
*/
private Map<String, Class<PrivilegePolicy>> policyMap;
/**
* The persistence handler is used for getting objects and saving changes
*/
private PersistenceHandler persistenceHandler;
/**
* The encryption handler is used for generating hashes and tokens
*/
private EncryptionHandler encryptionHandler;
/**
* flag to define if already initialized
*/
private boolean initialized;
/**
* flag to define if a persist should be performed after a user changes their own data
*/
private boolean autoPersistOnUserChangesData;
/**
* flag to define if sessions should be persisted
*/
private boolean persistSessions;
/**
* Path to sessions file for persistence
*/
private File persistSessionsPath;
/**
* Secret key
*/
private SecretKey secretKey;
private PrivilegeConflictResolution privilegeConflictResolution;
@Override
public EncryptionHandler getEncryptionHandler() throws PrivilegeException {
return this.encryptionHandler;
}
@Override
public RoleRep getRole(Certificate certificate, String roleName) {
// validate user actually has this type of privilege
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.assertHasPrivilege(PRIVILEGE_GET_ROLE);
Role role = this.persistenceHandler.getRole(roleName);
if (role == null)
return null;
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_ROLE, new Tuple(null, role)));
return role.asRoleRep();
}
@Override
public UserRep getUser(Certificate certificate, String username) {
// validate user actually has this type of privilege
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.assertHasPrivilege(PRIVILEGE_GET_USER);
User user = this.persistenceHandler.getUser(username);
if (user == null)
return null;
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_USER, new Tuple(null, user)));
return user.asUserRep();
}
@Override
public Map<String, String> getPolicyDefs(Certificate certificate) {
// validate user actually has this type of privilege
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ACTION, PRIVILEGE_ACTION_GET_POLICIES));
Map<String, String> policyDef = new HashMap<>(this.policyMap.size());
for (Entry<String, Class<PrivilegePolicy>> entry : this.policyMap.entrySet()) {
policyDef.put(entry.getKey(), entry.getValue().getName());
}
return policyDef;
}
@Override
public List<Certificate> getCertificates(Certificate certificate) {
// validate user actually has this type of privilege
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ACTION, PRIVILEGE_ACTION_GET_CERTIFICATES));
return this.privilegeContextMap.values().stream().map(p -> p.getCertificate()).collect(Collectors.toList());
}
@Override
public List<RoleRep> getRoles(Certificate certificate) {
// validate user actually has this type of privilege
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.assertHasPrivilege(PRIVILEGE_GET_ROLE);
Stream<Role> rolesStream = this.persistenceHandler.getAllRoles().stream();
// validate access to each role
// TODO throwing and catching exception ain't cool
rolesStream = rolesStream.filter(role -> {
try {
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_ROLE, new Tuple(null, role)));
return true;
} catch (AccessDeniedException e) {
return false;
}
});
List<RoleRep> roles = rolesStream.map(r -> r.asRoleRep()).collect(Collectors.toList());
return roles;
}
@Override
public List<UserRep> getUsers(Certificate certificate) {
// validate user actually has this type of privilege
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.assertHasPrivilege(PRIVILEGE_GET_USER);
Stream<User> usersStream = this.persistenceHandler.getAllUsers().stream();
// validate access to each user
// TODO throwing and catching exception ain't cool
usersStream = usersStream.filter(user -> {
try {
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_USER, new Tuple(null, user)));
return true;
} catch (AccessDeniedException e) {
return false;
}
});
List<UserRep> users = usersStream.map(u -> u.asUserRep()).collect(Collectors.toList());
return users;
}
@Override
public List<UserRep> queryUsers(Certificate certificate, UserRep selectorRep) {
// validate user actually has this type of privilege
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.assertHasPrivilege(PRIVILEGE_GET_USER);
String selUserId = selectorRep.getUserId();
String selUsername = selectorRep.getUsername();
String selFirstname = selectorRep.getFirstname();
String selLastname = selectorRep.getLastname();
UserState selUserState = selectorRep.getUserState();
Locale selLocale = selectorRep.getLocale();
Set<String> selRoles = selectorRep.getRoles();
Map<String, String> selPropertyMap = selectorRep.getPropertyMap();
List<UserRep> result = new ArrayList<>();
List<User> allUsers = this.persistenceHandler.getAllUsers();
for (User user : allUsers) {
// TODO throwing and catching exception ain't cool
try {
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_USER, new Tuple(null, user)));
} catch (AccessDeniedException e) {
continue;
}
// selections
boolean userIdSelected;
boolean usernameSelected;
boolean firstnameSelected;
boolean lastnameSelected;
boolean userStateSelected;
boolean localeSelected;
boolean roleSelected;
boolean propertySelected;
// userId
if (selUserId == null)
userIdSelected = true;
else if (selUserId.equals(user.getUserId()))
userIdSelected = true;
else
userIdSelected = false;
// username
if (selUsername == null)
usernameSelected = true;
else if (selUsername.equals(user.getUsername()))
usernameSelected = true;
else
usernameSelected = false;
// firstname
if (selFirstname == null)
firstnameSelected = true;
else if (selFirstname.equals(user.getFirstname()))
firstnameSelected = true;
else
firstnameSelected = false;
// lastname
if (selLastname == null)
lastnameSelected = true;
else if (selLastname.equals(user.getLastname()))
lastnameSelected = true;
else
lastnameSelected = false;
// user state
if (selUserState == null)
userStateSelected = true;
else if (selUserState.equals(user.getUserState()))
userStateSelected = true;
else
userStateSelected = false;
// locale
if (selLocale == null)
localeSelected = true;
else if (selLocale.equals(user.getLocale()))
localeSelected = true;
else
localeSelected = false;
// roles
roleSelected = isSelectedByRole(selRoles, user.getRoles());
// properties
propertySelected = isSelectedByProperty(selPropertyMap, user.getProperties());
boolean selected = userIdSelected && usernameSelected && firstnameSelected && lastnameSelected
&& userStateSelected && localeSelected && roleSelected && propertySelected;
if (selected)
result.add(user.asUserRep());
}
return result;
}
/**
* Checks if the given properties contains values which are contained in the selectionMap. If the selectionMap is
* null or empty, then true is returned. If a key/value pair from the selectionMap is not in the properties, then
* false is returned
*
* @param selectionMap
* the map defining the expected properties
* @param properties
* the properties which must be a sub set of selectionMap to have this method return true
*
* @return If the selectionMap is null or empty, then true is returned. If a key/value pair from the selectionMap is
* not in the properties, then false is returned
*/
private boolean isSelectedByProperty(Map<String, String> selectionMap, Map<String, String> properties) {
if (selectionMap == null)
return true;
if (selectionMap.isEmpty() && properties.isEmpty())
return true;
for (String selKey : selectionMap.keySet()) {
String value = properties.get(selKey);
if (value == null || !value.equals(selectionMap.get(selKey)))
return false;
}
return true;
}
/**
* Checks if the given roles contains the given selectionRoles, if this is the case, or selectionRoles is null or
* empty, then true is returned, otherwise false
*
* @param selectionRoles
* the required roles
* @param roles
* the roles to check if they contain the selectionRoles
*
* @return Checks if the given roles contains the given selectionRoles, if this is the case, or selectionRoles is
* null or empty, then true is returned, otherwise false
*/
private boolean isSelectedByRole(Set<String> selectionRoles, Set<String> roles) {
if (selectionRoles == null)
return true;
return roles.containsAll(selectionRoles);
}
@Override
public UserRep addUser(Certificate certificate, UserRep userRep, byte[] password) {
try {
// validate user actually has this type of privilege
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.assertHasPrivilege(PRIVILEGE_ADD_USER);
// make sure userId is not set
if (StringHelper.isNotEmpty(userRep.getUserId())) {
String msg = "UserId can not be set when adding a new user!";
throw new PrivilegeException(MessageFormat.format(msg, userRep.getUsername()));
}
// set userId
userRep.setUserId(StringHelper.getUniqueId());
// first validate user
userRep.validate();
validateRolesExist(userRep);
// validate user does not already exist
if (this.persistenceHandler.getUser(userRep.getUsername()) != null) {
String msg = "User {0} can not be added as it already exists!";
throw new PrivilegeException(MessageFormat.format(msg, userRep.getUsername()));
}
String passwordHash = null;
if (password != null) {
// validate password meets basic requirements
validatePassword(password);
// hash password
passwordHash = this.encryptionHandler.convertToHash(password);
}
// create new user
User newUser = createUser(userRep, passwordHash);
// detect privilege conflicts
assertNoPrivilegeConflict(newUser);
// validate this user may create such a user
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ADD_USER, new Tuple(null, newUser)));
// delegate to persistence handler
this.persistenceHandler.addUser(newUser);
return newUser.asUserRep();
} finally {
clearPassword(password);
}
}
@Override
public UserRep replaceUser(Certificate certificate, UserRep userRep, byte[] password) {
try {
// validate user actually has this type of privilege
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_USER);
// first validate user
userRep.validate();
validateRolesExist(userRep);
// validate user exists
User existingUser = this.persistenceHandler.getUser(userRep.getUsername());
if (existingUser == null) {
String msg = "User {0} can not be replaced as it does not exist!";
throw new PrivilegeException(MessageFormat.format(msg, userRep.getUsername()));
}
// validate same userId
if (!existingUser.getUserId().equals(userRep.getUserId())) {
String msg = "UserId of existing user {0} does not match userRep {1}";
msg = MessageFormat.format(msg, existingUser.getUserId(), userRep.getUserId());
throw new PrivilegeException(MessageFormat.format(msg, userRep.getUsername()));
}
String passwordHash = null;
if (password != null) {
// validate password meets basic requirements
validatePassword(password);
// hash password
passwordHash = this.encryptionHandler.convertToHash(password);
}
User newUser = createUser(userRep, passwordHash);
// detect privilege conflicts
assertNoPrivilegeConflict(newUser);
// validate this user may modify this user
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_USER, new Tuple(existingUser, newUser)));
// delegate to persistence handler
this.persistenceHandler.replaceUser(newUser);
return newUser.asUserRep();
} finally {
clearPassword(password);
}
}
private void validateRolesExist(UserRep userRep) {
// validate all roles exist
for (String role : userRep.getRoles()) {
if (this.persistenceHandler.getRole(role) == null) {
String msg = "Can not add user {0} as role {1} does not exist!";
msg = MessageFormat.format(msg, userRep.getUsername(), role);
throw new PrivilegeException(msg);
}
}
}
private User createUser(UserRep userRep, String passwordHash) {
User user = new User(userRep.getUserId(), userRep.getUsername(), passwordHash, userRep.getFirstname(),
userRep.getLastname(), userRep.getUserState(), userRep.getRoles(), userRep.getLocale(),
userRep.getPropertyMap());
return user;
}
@Override
public UserRep updateUser(Certificate certificate, UserRep userRep)
throws AccessDeniedException, PrivilegeException {
// validate user actually has this type of privilege
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_USER);
// get existing user
User existingUser = this.persistenceHandler.getUser(userRep.getUsername());
if (existingUser == null) {
throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", userRep.getUsername())); //$NON-NLS-1$
}
// if nothing to do, then stop
if (StringHelper.isEmpty(userRep.getFirstname()) && StringHelper.isEmpty(userRep.getLastname())
&& userRep.getLocale() == null
&& (userRep.getProperties() == null || userRep.getProperties().isEmpty())) {
throw new PrivilegeException(MessageFormat.format("All updateable fields are empty for update of user {0}", //$NON-NLS-1$
userRep.getUsername()));
}
String userId = existingUser.getUserId();
String username = existingUser.getUsername();
String password = existingUser.getPassword();
String firstname = existingUser.getFirstname();
String lastname = existingUser.getLastname();
UserState userState = existingUser.getUserState();
Set<String> roles = existingUser.getRoles();
Locale locale = existingUser.getLocale();
Map<String, String> propertyMap = existingUser.getProperties();
// get updated fields
if (StringHelper.isNotEmpty(userRep.getFirstname()))
firstname = userRep.getFirstname();
if (StringHelper.isNotEmpty(userRep.getLastname()))
lastname = userRep.getLastname();
if (userRep.getLocale() != null)
locale = userRep.getLocale();
if (userRep.getProperties() != null && !userRep.getProperties().isEmpty())
propertyMap = userRep.getPropertyMap();
// create new user
User newUser = new User(userId, username, password, firstname, lastname, userState, roles, locale, propertyMap);
// detect privilege conflicts
assertNoPrivilegeConflict(newUser);
// validate this user may modify this user
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_USER, new Tuple(existingUser, newUser)));
// delegate to persistence handler
this.persistenceHandler.replaceUser(newUser);
return newUser.asUserRep();
}
@Override
public UserRep removeUser(Certificate certificate, String username) {
// validate user actually has this type of privilege
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.assertHasPrivilege(PRIVILEGE_REMOVE_USER);
// validate user exists
User existingUser = this.persistenceHandler.getUser(username);
if (existingUser == null) {
String msg = "Can not remove User {0} because user does not exist!";
throw new PrivilegeException(MessageFormat.format(msg, username));
}
// validate this user may remove this user
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_REMOVE_USER, new Tuple(null, existingUser)));
// delegate user removal to persistence handler
this.persistenceHandler.removeUser(username);
return existingUser.asUserRep();
}
@Override
public UserRep addRoleToUser(Certificate certificate, String username, String roleName) {
// validate user actually has this type of privilege
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.assertHasPrivilege(PRIVILEGE_ADD_ROLE_TO_USER);
// get user
User existingUser = this.persistenceHandler.getUser(username);
if (existingUser == null) {
throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$
}
// validate that this user may add this role to this user
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ADD_ROLE_TO_USER, new Tuple(existingUser, roleName)));
// check that user not already has role
Set<String> currentRoles = existingUser.getRoles();
if (currentRoles.contains(roleName)) {
String msg = MessageFormat.format("User {0} already has role {1}", username, roleName); //$NON-NLS-1$
throw new PrivilegeException(msg);
}
// validate that the role exists
if (this.persistenceHandler.getRole(roleName) == null) {
String msg = MessageFormat.format("Role {0} does not exist!", roleName); //$NON-NLS-1$
throw new PrivilegeException(msg);
}
// create new user
Set<String> newRoles = new HashSet<>(currentRoles);
newRoles.add(roleName);
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(),
existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), newRoles,
existingUser.getLocale(), existingUser.getProperties());
// detect privilege conflicts
assertNoPrivilegeConflict(newUser);
// delegate user replacement to persistence handler
this.persistenceHandler.replaceUser(newUser);
return newUser.asUserRep();
}
@Override
public UserRep removeRoleFromUser(Certificate certificate, String username, String roleName) {
// validate user actually has this type of privilege
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.assertHasPrivilege(PRIVILEGE_REMOVE_ROLE_FROM_USER);
// get User
User existingUser = this.persistenceHandler.getUser(username);
if (existingUser == null) {
throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$
}
// validate that this user may remove this role from this user
prvCtx.validateAction(
new SimpleRestrictable(PRIVILEGE_REMOVE_ROLE_FROM_USER, new Tuple(existingUser, roleName)));
// ignore if user does not have role
Set<String> currentRoles = existingUser.getRoles();
if (!currentRoles.contains(roleName)) {
String msg = MessageFormat.format("User {0} does not have role {1}", existingUser.getUsername(), roleName); //$NON-NLS-1$
throw new PrivilegeException(msg);
}
// create new user
Set<String> newRoles = new HashSet<>(currentRoles);
newRoles.remove(roleName);
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(),
existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), newRoles,
existingUser.getLocale(), existingUser.getProperties());
// delegate user replacement to persistence handler
this.persistenceHandler.replaceUser(newUser);
return newUser.asUserRep();
}
@Override
public UserRep setUserLocale(Certificate certificate, String username, Locale locale) {
// validate user actually has this type of privilege
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.assertHasPrivilege(PRIVILEGE_SET_USER_LOCALE);
// get User
User existingUser = this.persistenceHandler.getUser(username);
if (existingUser == null) {
throw new PrivilegeException(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.getFirstname(), existingUser.getLastname(), existingUser.getUserState(),
existingUser.getRoles(), locale, existingUser.getProperties());
// 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)) {
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_SET_USER_LOCALE, new Tuple(existingUser, newUser)));
}
// delegate user replacement to persistence handler
this.persistenceHandler.replaceUser(newUser);
// perform automatic persisting, if enabled
if (this.autoPersistOnUserChangesData) {
this.persistenceHandler.persist();
}
return newUser.asUserRep();
}
@Override
public void setUserPassword(Certificate certificate, String username, byte[] password) {
try {
// validate user actually has this type of privilege
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.assertHasPrivilege(PRIVILEGE_SET_USER_PASSWORD);
// get User
User existingUser = this.persistenceHandler.getUser(username);
if (existingUser == null) {
throw new PrivilegeException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$
}
String passwordHash = null;
if (password != null) {
// validate password meets basic requirements
validatePassword(password);
// hash password
passwordHash = this.encryptionHandler.convertToHash(password);
}
// create new user
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), passwordHash,
existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(),
existingUser.getRoles(), existingUser.getLocale(), existingUser.getProperties());
// if the user is not setting their own password, then make sure this user may set this user's password
if (!certificate.getUsername().equals(username)) {
prvCtx.validateAction(
new SimpleRestrictable(PRIVILEGE_SET_USER_PASSWORD, new Tuple(existingUser, newUser)));
}
// delegate user replacement to persistence handler
this.persistenceHandler.replaceUser(newUser);
// perform automatic persisting, if enabled
if (this.autoPersistOnUserChangesData) {
this.persistenceHandler.persist();
}
} finally {
clearPassword(password);
}
}
@Override
public UserRep setUserState(Certificate certificate, String username, UserState state) {
// validate user actually has this type of privilege
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.assertHasPrivilege(PRIVILEGE_SET_USER_STATE);
// get User
User existingUser = this.persistenceHandler.getUser(username);
if (existingUser == null) {
throw new PrivilegeException(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.getFirstname(), existingUser.getLastname(), state, existingUser.getRoles(),
existingUser.getLocale(), existingUser.getProperties());
// validate that this user may modify this user's state
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_SET_USER_STATE, new Tuple(existingUser, newUser)));
// delegate user replacement to persistence handler
this.persistenceHandler.replaceUser(newUser);
return newUser.asUserRep();
}
@Override
public RoleRep addRole(Certificate certificate, RoleRep roleRep) {
// validate user actually has this type of privilege
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.assertHasPrivilege(PRIVILEGE_ADD_ROLE);
// first validate role
roleRep.validate();
// validate role does not exist
if (this.persistenceHandler.getRole(roleRep.getName()) != null) {
String msg = MessageFormat.format("Can not add role {0} as it already exists!", roleRep.getName());
throw new PrivilegeException(msg);
}
// create new role from RoleRep
Role newRole = new Role(roleRep);
// validate that this user may add this new role
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ADD_ROLE, new Tuple(null, newRole)));
// validate policy if not null
validatePolicies(newRole);
// delegate to persistence handler
this.persistenceHandler.addRole(newRole);
return newRole.asRoleRep();
}
@Override
public RoleRep replaceRole(Certificate certificate, RoleRep roleRep) {
// validate user actually has this type of privilege
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_ROLE);
// first validate role
roleRep.validate();
// validate role does exist
Role existingRole = this.persistenceHandler.getRole(roleRep.getName());
if (existingRole == null) {
String msg = MessageFormat.format("Can not replace role {0} as it does not exist!", roleRep.getName());
throw new PrivilegeException(msg);
}
// create new role from RoleRep
Role newRole = new Role(roleRep);
// detect privilege conflicts
assertNoPrivilegeConflict(newRole);
// validate that this user may modify this role
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_ROLE, new Tuple(existingRole, newRole)));
// validate policy if not null
validatePolicies(newRole);
// delegate to persistence handler
this.persistenceHandler.replaceRole(newRole);
return newRole.asRoleRep();
}
@Override
public RoleRep removeRole(Certificate certificate, String roleName) {
// validate user actually has this type of privilege
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.assertHasPrivilege(PRIVILEGE_REMOVE_ROLE);
// validate no user is using this role
Set<String> roles = new HashSet<>(Arrays.asList(roleName));
UserRep selector = new UserRep(null, null, null, null, null, roles, null, null);
List<UserRep> usersWithRole = queryUsers(certificate, selector);
if (!usersWithRole.isEmpty()) {
String usersS = usersWithRole.stream().map(UserRep::getUsername).collect(Collectors.joining(", "));
String msg = "The role {0} can not be removed as the following {1} user have the role assigned: {2}";
msg = MessageFormat.format(msg, roleName, usersWithRole.size(), usersS);
throw new PrivilegeException(msg);
}
// validate role exists
Role existingRole = this.persistenceHandler.getRole(roleName);
if (existingRole == null) {
String msg = "Can not remove Role {0} because role does not exist!";
throw new PrivilegeException(MessageFormat.format(msg, roleName));
}
// validate that this user may remove this role
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_REMOVE_ROLE, new Tuple(null, existingRole)));
// delegate role removal to persistence handler
this.persistenceHandler.removeRole(roleName);
return existingRole.asRoleRep();
}
@Override
public RoleRep addOrReplacePrivilegeOnRole(Certificate certificate, String roleName, PrivilegeRep privilegeRep) {
// validate user actually has this type of privilege
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_ROLE);
// validate PrivilegeRep
privilegeRep.validate();
// get role
Role existingRole = this.persistenceHandler.getRole(roleName);
if (existingRole == null) {
String msg = MessageFormat.format("Role {0} does not exist!", roleName); //$NON-NLS-1$
throw new PrivilegeException(msg);
}
// validate that policy exists if needed
String policy = privilegeRep.getPolicy();
if (policy != null && !this.policyMap.containsKey(policy)) {
String msg = "Policy {0} for Privilege {1} does not exist"; //$NON-NLS-1$
msg = MessageFormat.format(msg, policy, privilegeRep.getName());
throw new PrivilegeException(msg);
}
// create new role with the additional privilege
IPrivilege newPrivilege = new PrivilegeImpl(privilegeRep);
// copy existing privileges
Set<String> existingPrivilegeNames = existingRole.getPrivilegeNames();
Map<String, IPrivilege> privilegeMap = new HashMap<>(existingPrivilegeNames.size() + 1);
for (String name : existingPrivilegeNames) {
IPrivilege privilege = existingRole.getPrivilege(name);
privilegeMap.put(name, privilege);
}
// add new one
privilegeMap.put(newPrivilege.getName(), newPrivilege);
// create new role
Role newRole = new Role(existingRole.getName(), privilegeMap);
// detect privilege conflicts
assertNoPrivilegeConflict(newRole);
// validate that this user may modify this role
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_ROLE, new Tuple(existingRole, newRole)));
// delegate role replacement to persistence handler
this.persistenceHandler.replaceRole(newRole);
return newRole.asRoleRep();
}
@Override
public RoleRep removePrivilegeFromRole(Certificate certificate, String roleName, String privilegeName) {
// validate user actually has this type of privilege
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.assertHasPrivilege(PRIVILEGE_MODIFY_ROLE);
// get role
Role existingRole = this.persistenceHandler.getRole(roleName);
if (existingRole == null) {
throw new PrivilegeException(MessageFormat.format("Role {0} does not exist!", roleName)); //$NON-NLS-1$
}
// ignore if role does not have privilege
if (!existingRole.hasPrivilege(privilegeName)) {
String msg = MessageFormat.format("Role {0} does not have Privilege {1}", roleName, privilegeName); //$NON-NLS-1$
throw new PrivilegeException(msg);
}
// create new set of privileges with out the to removed privilege
Set<String> privilegeNames = existingRole.getPrivilegeNames();
Map<String, IPrivilege> newPrivileges = new HashMap<>(privilegeNames.size() - 1);
for (String name : privilegeNames) {
IPrivilege privilege = existingRole.getPrivilege(name);
if (!privilege.getName().equals(privilegeName))
newPrivileges.put(privilege.getName(), privilege);
}
// create new role
Role newRole = new Role(existingRole.getName(), newPrivileges);
// validate that this user may modify this role
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_MODIFY_ROLE, new Tuple(existingRole, newRole)));
// delegate user replacement to persistence handler
this.persistenceHandler.replaceRole(newRole);
return newRole.asRoleRep();
}
@Override
public Certificate authenticate(String username, byte[] password) {
try {
// username must be at least 2 characters in length
if (username == null || username.length() < 2) {
String msg = MessageFormat.format("The given username ''{0}'' is shorter than 2 characters", username); //$NON-NLS-1$
throw new PrivilegeException(msg);
}
// check the password
User user = checkCredentialsAndUserState(username, password);
// validate user has at least one role
Set<String> userRoles = user.getRoles();
if (userRoles.isEmpty()) {
throw new PrivilegeException(
MessageFormat.format("User {0} does not have any roles defined!", username)); //$NON-NLS-1$
}
// get 2 auth tokens
String authToken = this.encryptionHandler.convertToHash(this.encryptionHandler.nextToken());
// get next session id
String sessionId = UUID.randomUUID().toString();
// create a new certificate, with details of the user
Certificate certificate = new Certificate(sessionId, username, user.getFirstname(), user.getLastname(),
user.getUserState(), authToken, new Date(), user.getLocale(), userRoles,
new HashMap<>(user.getProperties()));
certificate.setLastAccess(new Date());
PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user);
this.privilegeContextMap.put(sessionId, privilegeContext);
persistSessions();
// log
DefaultPrivilegeHandler.logger
.info(MessageFormat.format("User {0} authenticated: {1}", username, certificate)); //$NON-NLS-1$
// return the certificate
return certificate;
} catch (RuntimeException e) {
String msg = "User {0} Failed to authenticate: {1}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, username, e.getMessage());
DefaultPrivilegeHandler.logger.error(msg);
throw e;
} finally {
clearPassword(password);
}
}
private boolean persistSessions() {
if (!this.persistSessions)
return false;
List<Certificate> sessions = this.privilegeContextMap.values().stream().map(p -> p.getCertificate())
.filter(c -> !c.getUserState().isSystem()).collect(Collectors.toList());
try (FileOutputStream fout = new FileOutputStream(this.persistSessionsPath);
OutputStream outputStream = AesCryptoHelper.wrapEncrypt(this.secretKey, fout)) {
CertificateStubsDomWriter writer = new CertificateStubsDomWriter(sessions, outputStream);
writer.write();
outputStream.flush();
} catch (Exception e) {
throw new PrivilegeException("Failed to persist sessions!", e);
}
return true;
}
private boolean loadSessions() {
if (!this.persistSessions) {
logger.info("Persisteding of sessions not enabled, so not loading!.");
return false;
}
if (!this.persistSessionsPath.exists()) {
logger.info("No persisted sessions exist to be loaded.");
return false;
}
if (!this.persistSessionsPath.isFile())
throw new PrivilegeException(
"Sessions data file is not a file but exists at " + this.persistSessionsPath.getAbsolutePath());
List<CertificateStub> certificateStubs;
try (FileInputStream fin = new FileInputStream(this.persistSessionsPath);
InputStream inputStream = AesCryptoHelper.wrapDecrypt(this.secretKey, fin)) {
CertificateStubsSaxReader reader = new CertificateStubsSaxReader(inputStream);
certificateStubs = reader.read();
} catch (Exception e) {
logger.error("Failed to load sessions!", e);
this.persistSessionsPath.delete();
return false;
}
if (certificateStubs.isEmpty()) {
logger.info("No persisted sessions exist to be loaded.");
return false;
}
for (CertificateStub certificateStub : certificateStubs) {
String username = certificateStub.getUsername();
String sessionId = certificateStub.getSessionId();
String authToken = certificateStub.getAuthToken();
User user = this.persistenceHandler.getUser(username);
if (user == null) {
logger.error("Ignoring session data for missing user " + username);
continue;
}
Set<String> userRoles = user.getRoles();
if (userRoles.isEmpty()) {
logger.error("Ignoring session data for user " + username + " which has not roles defined!");
continue;
}
// create a new certificate, with details of the user
Certificate certificate = new Certificate(sessionId, username, user.getFirstname(), user.getLastname(),
user.getUserState(), authToken, certificateStub.getLoginTime(), certificateStub.getLocale(),
userRoles, new HashMap<>(user.getProperties()));
certificate.setLastAccess(certificateStub.getLastAccess());
PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user);
this.privilegeContextMap.put(sessionId, privilegeContext);
}
logger.info("Loaded " + this.privilegeContextMap.size() + " sessions.");
return true;
}
/**
* Checks the credentials and validates that the user may log in.
*
* @param username
* the username of the {@link User} to check against
* @param password
* the password of this user
*
* @return the {@link User} if the credentials are valid and the user may login
*
* @throws AccessDeniedException
* if anything is wrong with the credentials or the user state
* @throws InvalidCredentialsException
* if the given credentials are invalid, the user does not exist, or has no password set
*/
private User checkCredentialsAndUserState(String username, byte[] password)
throws InvalidCredentialsException, AccessDeniedException {
// and validate the password
validatePassword(password);
// we only work with hashed passwords
String passwordHash = this.encryptionHandler.convertToHash(password);
// get user object
User user = this.persistenceHandler.getUser(username);
// no user means no authentication
if (user == null) {
String msg = MessageFormat.format("There is no user defined with the username {0}", username); //$NON-NLS-1$
throw new InvalidCredentialsException(msg);
}
// make sure not a system user - they may not login in
if (user.getUserState() == UserState.SYSTEM) {
String msg = "User {0} is a system user and may not login!"; //$NON-NLS-1$
msg = MessageFormat.format(msg, username);
throw new InvalidCredentialsException(msg);
}
// validate password
String pwHash = user.getPassword();
if (pwHash == null)
throw new AccessDeniedException(
MessageFormat.format("User {0} has no password and may not login!", username)); //$NON-NLS-1$
if (!pwHash.equals(passwordHash))
throw new InvalidCredentialsException(MessageFormat.format("Password is incorrect for {0}", username)); //$NON-NLS-1$
// validate if user is allowed to login
// this also capture the trying to login of SYSTEM user
if (user.getUserState() != UserState.ENABLED) {
String msg = "User {0} does not have state {1} and can not login!"; //$NON-NLS-1$
msg = MessageFormat.format(msg, username, UserState.ENABLED);
throw new AccessDeniedException(msg);
}
return user;
}
/**
* Builds a {@link PrivilegeContext} for the given {@link User} and its {@link Certificate}
*
* @param certificate
* @param user
*
* @return
*/
private PrivilegeContext buildPrivilegeContext(Certificate certificate, User user) {
Set<String> userRoles = user.getRoles();
Map<String, IPrivilege> privileges = new HashMap<>();
Map<String, PrivilegePolicy> policies = new HashMap<>();
// get a cache of the privileges and policies for this user
for (String roleName : userRoles) {
Role role = this.persistenceHandler.getRole(roleName);
Set<String> privilegeNames = role.getPrivilegeNames();
for (String privilegeName : privilegeNames) {
IPrivilege privilege = role.getPrivilege(privilegeName);
if (privilege == null) {
String msg = "The Privilege {0} does not exist for role {1}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, privilegeName, roleName);
throw new PrivilegeException(msg);
}
// cache the privilege
if (privileges.containsKey(privilegeName)) {
if (this.privilegeConflictResolution.isStrict()) {
String msg = "User has conflicts for privilege {0} with role {1}";
msg = MessageFormat.format(msg, privilegeName, roleName);
throw new PrivilegeException(msg);
}
IPrivilege priv = privileges.get(privilegeName);
boolean allAllowed = priv.isAllAllowed() || privilege.isAllAllowed();
Set<String> allowList;
Set<String> denyList;
if (allAllowed) {
allowList = Collections.emptySet();
denyList = Collections.emptySet();
} else {
allowList = new HashSet<>(priv.getAllowList());
allowList.addAll(privilege.getAllowList());
denyList = new HashSet<>(priv.getDenyList());
denyList.addAll(privilege.getDenyList());
}
priv = new PrivilegeImpl(priv.getName(), priv.getPolicy(), allAllowed, denyList, allowList);
privileges.put(privilegeName, priv);
continue;
}
privileges.put(privilegeName, privilege);
// cache the policy for the privilege
String policyName = privilege.getPolicy();
if (policies.containsKey(policyName))
continue;
PrivilegePolicy policy = getPolicy(policyName);
if (policy == null) {
String msg = "The Policy {0} does not exist for Privilege {1}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, policyName, privilegeName);
throw new PrivilegeException(msg);
}
policies.put(policyName, policy);
}
}
UserRep userRep = user.asUserRep();
PrivilegeContext privilegeContext = new PrivilegeContext(userRep, certificate, privileges, policies);
return privilegeContext;
}
@Override
public boolean invalidateSession(Certificate certificate) {
// first validate certificate
isCertificateValid(certificate);
// remove registration
PrivilegeContext privilegeContext = this.privilegeContextMap.remove(certificate.getSessionId());
// persist sessions
persistSessions();
// return true if object was really removed
boolean loggedOut = privilegeContext != null;
if (loggedOut)
DefaultPrivilegeHandler.logger
.info(MessageFormat.format("User {0} logged out.", certificate.getUsername())); //$NON-NLS-1$
else
DefaultPrivilegeHandler.logger.warn("User already logged out!"); //$NON-NLS-1$
return loggedOut;
}
@Override
public void isCertificateValid(Certificate certificate) {
// certificate must not be null
if (certificate == null)
throw new PrivilegeException("Certificate may not be null!"); //$NON-NLS-1$
// first see if a session exists for this certificate
PrivilegeContext privilegeContext = this.privilegeContextMap.get(certificate.getSessionId());
if (privilegeContext == null) {
String msg = MessageFormat.format("There is no session information for {0}", certificate); //$NON-NLS-1$
throw new AccessDeniedException(msg);
}
// validate certificate has not been tampered with
Certificate sessionCertificate = privilegeContext.getCertificate();
if (!sessionCertificate.equals(certificate)) {
String msg = "Received illegal certificate for session id {0}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, certificate.getSessionId());
throw new PrivilegeException(msg);
}
// get user object
User user = this.persistenceHandler.getUser(privilegeContext.getUsername());
// if user exists, then certificate is valid
if (user == null) {
String msg = "Oh boy, how did this happen: No User in user map although the certificate is valid!"; //$NON-NLS-1$
throw new PrivilegeException(msg);
}
// everything is ok
}
@Override
public void checkPassword(Certificate certificate, byte[] password) throws PrivilegeException {
try {
isCertificateValid(certificate);
checkCredentialsAndUserState(certificate.getUsername(), password);
} finally {
clearPassword(password);
}
}
@Override
public PrivilegeContext getPrivilegeContext(Certificate certificate) throws PrivilegeException {
// first validate certificate
isCertificateValid(certificate);
return this.privilegeContextMap.get(certificate.getSessionId());
}
/**
* This simple implementation validates that the password is not null, and that the password string is not empty
*
* @see li.strolch.privilege.handler.PrivilegeHandler#validatePassword(byte[])
*/
@Override
public void validatePassword(byte[] password) throws PrivilegeException {
if (password == null || password.length == 0) {
throw new PrivilegeException("A password may not be empty!"); //$NON-NLS-1$
}
if (password.length < 3) {
throw new PrivilegeException("The given password is shorter than 3 characters"); //$NON-NLS-1$
}
}
@Override
public boolean persist(Certificate certificate) {
// validate who is doing this
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ACTION, PRIVILEGE_ACTION_PERSIST));
return this.persistenceHandler.persist();
}
@Override
public boolean persistSessions(Certificate certificate) {
// validate who is doing this
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ACTION, PRIVILEGE_ACTION_PERSIST_SESSIONS));
return persistSessions();
}
@Override
public boolean reload(Certificate certificate) {
// validate who is doing this
PrivilegeContext prvCtx = getPrivilegeContext(certificate);
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ACTION, PRIVILEGE_ACTION_RELOAD));
return this.persistenceHandler.reload();
}
/**
* Initializes the concrete {@link EncryptionHandler}. The passed parameter map contains any configuration this
* {@link PrivilegeHandler} might need. This method may only be called once and this must be enforced by the
* concrete implementation
*
* @param parameterMap
* a map containing configuration properties
* @param encryptionHandler
* the {@link EncryptionHandler} instance for this {@link PrivilegeHandler}
* @param persistenceHandler
* the {@link PersistenceHandler} instance for this {@link PrivilegeHandler}
* @param policyMap
* map of {@link PrivilegePolicy} classes
*
* @throws PrivilegeException
* if the this method is called multiple times or an initialization exception occurs
*/
public synchronized void initialize(Map<String, String> parameterMap, EncryptionHandler encryptionHandler,
PersistenceHandler persistenceHandler, Map<String, Class<PrivilegePolicy>> policyMap) {
if (this.initialized)
throw new PrivilegeException("Already initialized!"); //$NON-NLS-1$
this.policyMap = policyMap;
this.encryptionHandler = encryptionHandler;
this.persistenceHandler = persistenceHandler;
handleAutoPersistOnUserDataChange(parameterMap);
handlePersistSessionsParam(parameterMap);
handleConflictResolutionParam(parameterMap);
handleSecretParams(parameterMap);
// validate policies on privileges of Roles
for (Role role : persistenceHandler.getAllRoles()) {
validatePolicies(role);
}
// validate privilege conflicts
validatePrivilegeConflicts();
this.privilegeContextMap = Collections.synchronizedMap(new HashMap<String, PrivilegeContext>());
loadSessions();
this.initialized = true;
}
private void handleAutoPersistOnUserDataChange(Map<String, String> parameterMap) {
String autoPersistS = parameterMap.get(PARAM_AUTO_PERSIST_ON_USER_CHANGES_DATA);
if (StringHelper.isEmpty(autoPersistS) || autoPersistS.equals(Boolean.FALSE.toString())) {
this.autoPersistOnUserChangesData = false;
} else if (autoPersistS.equals(Boolean.TRUE.toString())) {
this.autoPersistOnUserChangesData = true;
logger.info("Enabling automatic persistence when user changes their data."); //$NON-NLS-1$
} else {
String msg = "Parameter {0} has illegal value {1}. Overriding with {2}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, PARAM_AUTO_PERSIST_ON_USER_CHANGES_DATA, autoPersistS, Boolean.FALSE);
logger.error(msg);
this.autoPersistOnUserChangesData = false;
}
}
private void handlePersistSessionsParam(Map<String, String> parameterMap) {
String persistSessionsS = parameterMap.get(PARAM_PERSIST_SESSIONS);
if (StringHelper.isEmpty(persistSessionsS) || persistSessionsS.equals(Boolean.FALSE.toString())) {
this.persistSessions = false;
} else if (persistSessionsS.equals(Boolean.TRUE.toString())) {
this.persistSessions = true;
String persistSessionsPathS = parameterMap.get(PARAM_PERSIST_SESSIONS_PATH);
if (StringHelper.isEmpty(persistSessionsPathS)) {
String msg = "Parameter {0} has illegal value {1}."; //$NON-NLS-1$
msg = MessageFormat.format(msg, PARAM_PERSIST_SESSIONS_PATH, persistSessionsPathS);
throw new PrivilegeException(msg);
}
File persistSessionsPath = new File(persistSessionsPathS);
if (!persistSessionsPath.getParentFile().isDirectory()) {
String msg = "Path for param {0} is invalid as parent does not exist or is not a directory. Value: {1}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, PARAM_PERSIST_SESSIONS_PATH, persistSessionsPath.getAbsolutePath());
throw new PrivilegeException(msg);
}
if (persistSessionsPath.exists() && (!persistSessionsPath.isFile() || !persistSessionsPath.canWrite())) {
String msg = "Path for param {0} is invalid as file exists but is not a file or not writeable. Value: {1}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, PARAM_PERSIST_SESSIONS_PATH, persistSessionsPath.getAbsolutePath());
throw new PrivilegeException(msg);
}
this.persistSessionsPath = persistSessionsPath;
logger.info(MessageFormat.format("Enabling persistence of sessions to {0}", //$NON-NLS-1$
this.persistSessionsPath.getAbsolutePath()));
} else {
String msg = "Parameter {0} has illegal value {1}. Overriding with {2}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, PARAM_PERSIST_SESSIONS, persistSessionsS, Boolean.FALSE);
logger.error(msg);
this.persistSessions = false;
}
}
private void handleConflictResolutionParam(Map<String, String> parameterMap) {
String privilegeConflictResolutionS = parameterMap.get(PARAM_PRIVILEGE_CONFLICT_RESOLUTION);
if (privilegeConflictResolutionS == null) {
this.privilegeConflictResolution = PrivilegeConflictResolution.STRICT;
String msg = "No {0} parameter defined. Using {1}";
msg = MessageFormat.format(msg, PARAM_PRIVILEGE_CONFLICT_RESOLUTION, this.privilegeConflictResolution);
logger.info(msg);
} else {
try {
this.privilegeConflictResolution = PrivilegeConflictResolution.valueOf(privilegeConflictResolutionS);
} catch (Exception e) {
String msg = "Parameter {0} has illegal value {1}."; //$NON-NLS-1$
msg = MessageFormat.format(msg, PARAM_PRIVILEGE_CONFLICT_RESOLUTION, privilegeConflictResolutionS);
throw new PrivilegeException(msg);
}
}
logger.info("Privilege conflict resolution set to " + this.privilegeConflictResolution); //$NON-NLS-1$
}
private void handleSecretParams(Map<String, String> parameterMap) {
if (!this.persistSessions)
return;
String secretKeyS = parameterMap.get(PARAM_SECRET_KEY);
if (StringHelper.isEmpty(secretKeyS)) {
String msg = "Parameter {0} may not be empty if parameter {1} is enabled."; //$NON-NLS-1$
msg = MessageFormat.format(msg, PARAM_SECRET_KEY, PARAM_PRIVILEGE_CONFLICT_RESOLUTION);
throw new PrivilegeException(msg);
}
String secretSaltS = parameterMap.get(PARAM_SECRET_SALT);
if (StringHelper.isEmpty(secretSaltS)) {
String msg = "Parameter {0} may not be empty if parameter {1} is enabled."; //$NON-NLS-1$
msg = MessageFormat.format(msg, PARAM_SECRET_SALT, PARAM_PRIVILEGE_CONFLICT_RESOLUTION);
throw new PrivilegeException(msg);
}
this.secretKey = AesCryptoHelper.buildSecret(secretKeyS.toCharArray(), secretSaltS.getBytes());
}
private void validatePrivilegeConflicts() {
if (!this.privilegeConflictResolution.isStrict()) {
return;
}
List<String> conflicts = new ArrayList<>();
List<User> users = this.persistenceHandler.getAllUsers();
for (User user : users) {
Map<String, String> privilegeNames = new HashMap<>();
conflicts.addAll(detectPrivilegeConflicts(privilegeNames, user));
}
if (!conflicts.isEmpty()) {
for (String conflict : conflicts) {
logger.error(conflict);
}
throw new PrivilegeException("There are " + conflicts.size() + " privilege conflicts!");
}
}
private void assertNoPrivilegeConflict(User user) {
if (this.privilegeConflictResolution.isStrict()) {
Map<String, String> privilegeNames = new HashMap<>();
List<String> conflicts = detectPrivilegeConflicts(privilegeNames, user);
if (!conflicts.isEmpty()) {
String msg = conflicts.stream().collect(Collectors.joining("\n"));
throw new PrivilegeException(msg);
}
}
}
private void assertNoPrivilegeConflict(Role role) {
if (!this.privilegeConflictResolution.isStrict())
return;
Map<String, String> privilegeNames = new HashMap<>();
for (String privilegeName : role.getPrivilegeNames()) {
privilegeNames.put(privilegeName, role.getName());
}
List<String> conflicts = new ArrayList<>();
List<User> users = this.persistenceHandler.getAllUsers();
for (User user : users) {
if (user.hasRole(role.getName()))
conflicts.addAll(detectPrivilegeConflicts(privilegeNames, user));
}
if (!conflicts.isEmpty()) {
String msg = conflicts.stream().collect(Collectors.joining("\n"));
throw new PrivilegeException(msg);
}
}
private List<String> detectPrivilegeConflicts(Map<String, String> privilegeNames, User user) {
List<String> conflicts = new ArrayList<>();
Set<String> userRoles = user.getRoles();
for (String roleName : userRoles) {
Role role = this.persistenceHandler.getRole(roleName);
for (String privilegeName : role.getPrivilegeNames()) {
if (!privilegeNames.containsKey(privilegeName)) {
privilegeNames.put(privilegeName, roleName);
} else {
String roleOrigin = privilegeNames.get(privilegeName);
String msg = "User {0} has conflicts for privilege {1} on roles {2} and {3}";
msg = MessageFormat.format(msg, user.getUsername(), privilegeName, roleOrigin, roleName);
conflicts.add(msg);
}
}
}
return conflicts;
}
/**
* Validates that the policies which are not null on the privileges of the role exist
*
* @param role
* the role for which the policies are to be checked
*/
private void validatePolicies(Role role) {
for (String privilegeName : role.getPrivilegeNames()) {
IPrivilege privilege = role.getPrivilege(privilegeName);
String policy = privilege.getPolicy();
if (policy != null && !this.policyMap.containsKey(policy)) {
String msg = "Policy {0} for Privilege {1} does not exist on role {2}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, policy, privilege.getName(), role);
throw new PrivilegeException(msg);
}
}
}
/**
* Passwords should not be kept as strings, as string are immutable, this method thus clears the byte array so that
* the password is not in memory anymore
*
* @param password
* the byte array containing the passwort which is to be set to zeroes
*/
private void clearPassword(byte[] password) {
if (password != null) {
for (int i = 0; i < password.length; i++) {
password[i] = 0;
}
}
}
@Override
public <T extends SystemUserAction> T runAsSystem(String systemUsername, T action) throws PrivilegeException {
if (systemUsername == null)
throw new PrivilegeException("systemUsername may not be null!"); //$NON-NLS-1$
if (action == null)
throw new PrivilegeException("action may not be null!"); //$NON-NLS-1$
// get the system user
User systemUser = this.persistenceHandler.getUser(systemUsername);
if (systemUser == null)
throw new PrivilegeException(MessageFormat.format("System user {0} does not exist!", systemUsername)); //$NON-NLS-1$
// validate this is a system user
if (systemUser.getUserState() != UserState.SYSTEM)
throw new PrivilegeException(MessageFormat.format("User {0} is not a System user!", systemUsername)); //$NON-NLS-1$
// get privilegeContext for this system user
PrivilegeContext systemUserPrivilegeContext = getSystemUserPrivilegeContext(systemUsername);
// validate this system user may perform the given action
systemUserPrivilegeContext.validateAction(action);
String sessionId = systemUserPrivilegeContext.getCertificate().getSessionId();
this.privilegeContextMap.put(sessionId, systemUserPrivilegeContext);
try {
// perform the action
action.execute(systemUserPrivilegeContext);
} finally {
this.privilegeContextMap.remove(sessionId);
}
return action;
}
/**
* Returns the {@link Certificate} for the given system username. If it does not yet exist, then it is created by
* authenticating the system user
*
* @param systemUsername
* the name of the system user
*
* @return the {@link Certificate} for this system user
*/
private PrivilegeContext getSystemUserPrivilegeContext(String systemUsername) {
// get user object
User user = this.persistenceHandler.getUser(systemUsername);
// no user means no authentication
if (user == null) {
String msg = MessageFormat.format("The system user with username {0} does not exist!", systemUsername); //$NON-NLS-1$
throw new AccessDeniedException(msg);
}
// validate password
String pwHash = user.getPassword();
if (pwHash != null) {
String msg = MessageFormat.format("System users must not have a password: {0}", systemUsername); //$NON-NLS-1$
throw new AccessDeniedException(msg);
}
// validate user state is system
if (user.getUserState() != UserState.SYSTEM) {
String msg = "The system {0} user does not have expected user state {1}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, systemUsername, UserState.SYSTEM);
throw new PrivilegeException(msg);
}
// validate user has at least one role
if (user.getRoles().isEmpty()) {
String msg = MessageFormat.format("The system user {0} does not have any roles defined!", systemUsername); //$NON-NLS-1$
throw new PrivilegeException(msg);
}
// get 2 auth tokens
String authToken = this.encryptionHandler.nextToken();
// get next session id
String sessionId = UUID.randomUUID().toString();
// create a new certificate, with details of the user
Certificate systemUserCertificate = new Certificate(sessionId, systemUsername, user.getFirstname(),
user.getLastname(), user.getUserState(), authToken, new Date(), user.getLocale(), user.getRoles(),
new HashMap<>(user.getProperties()));
systemUserCertificate.setLastAccess(new Date());
// create and save a new privilege context
PrivilegeContext privilegeContext = buildPrivilegeContext(systemUserCertificate, user);
// log
String msg = "The system user ''{0}'' is logged in with session {1}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, systemUsername, systemUserCertificate.getSessionId());
DefaultPrivilegeHandler.logger.info(msg);
return privilegeContext;
}
/**
* <p>
* This method instantiates a {@link PrivilegePolicy} object from the given policyName. The {@link PrivilegePolicy}
* is not stored in a database. The privilege name is a class name and is then used to instantiate a new
* {@link PrivilegePolicy} object
* </p>
*
* @param policyName
* the class name of the {@link PrivilegePolicy} object to return
*
* @return the {@link PrivilegePolicy} object
*
* @throws PrivilegeException
* if the {@link PrivilegePolicy} object for the given policy name could not be instantiated
*/
private PrivilegePolicy getPolicy(String policyName) {
// get the policies class
Class<PrivilegePolicy> policyClazz = this.policyMap.get(policyName);
if (policyClazz == null) {
return null;
}
// instantiate the policy
PrivilegePolicy policy;
try {
policy = policyClazz.newInstance();
} catch (Exception e) {
String msg = "The class for the policy with the name {0} does not exist!{1}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, policyName, policyName);
throw new PrivilegeException(msg, e);
}
return policy;
}
}