package org.synyx.urlaubsverwaltung.security;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.ldap.userdetails.UserDetailsContextMapper;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.synyx.urlaubsverwaltung.core.person.Person;
import org.synyx.urlaubsverwaltung.core.person.PersonService;
import org.synyx.urlaubsverwaltung.core.person.Role;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Optional;
import javax.naming.NamingException;
/**
* Map granted authorities to application roles described in {@link Role}.
*
* @author Aljona Murygina - murygina@synyx.de
*/
@Component
@ConditionalOnExpression("'${auth}'=='activeDirectory' or '${auth}'=='ldap'")
public class PersonContextMapper implements UserDetailsContextMapper {
private static final Logger LOG = Logger.getLogger(PersonContextMapper.class);
private final PersonService personService;
private final LdapSyncService ldapSyncService;
private final LdapUserMapper ldapUserMapper;
@Autowired
public PersonContextMapper(PersonService personService, LdapSyncService ldapSyncService,
LdapUserMapper ldapUserMapper) {
this.personService = personService;
this.ldapSyncService = ldapSyncService;
this.ldapUserMapper = ldapUserMapper;
}
@Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Collection<? extends GrantedAuthority> authorities) {
LdapUser ldapUser;
try {
ldapUser = ldapUserMapper.mapFromContext(ctx);
} catch (InvalidSecurityConfigurationException | NamingException | UnsupportedMemberAffiliationException ex) {
LOG.info("User '" + username + "' can not sign in because: " + ex.getMessage());
throw new BadCredentialsException("No authentication possible for user = " + username, ex);
}
String login = ldapUser.getUsername();
Optional<Person> optionalPerson = personService.getPersonByLogin(login);
Person person;
if (optionalPerson.isPresent()) {
Person existentPerson = optionalPerson.get();
if (existentPerson.hasRole(Role.INACTIVE)) {
LOG.info("User '" + username + "' has been deactivated and can not sign in therefore");
throw new DisabledException("User '" + username + "' has been deactivated");
}
person = ldapSyncService.syncPerson(existentPerson, ldapUser.getFirstName(), ldapUser.getLastName(),
ldapUser.getEmail());
} else {
LOG.info("No user found for username '" + username + "'");
person = ldapSyncService.createPerson(login, ldapUser.getFirstName(), ldapUser.getLastName(),
ldapUser.getEmail());
}
/**
* NOTE: If the system has no office user yet, grant office permissions to successfully signed in user
*/
boolean noOfficeUserYet = personService.getPersonsByRole(Role.OFFICE).isEmpty();
// TODO: Think about if this logic could be dangerous?!
if (noOfficeUserYet) {
ldapSyncService.appointPersonAsOfficeUser(person);
}
org.springframework.security.ldap.userdetails.Person.Essence user =
new org.springframework.security.ldap.userdetails.Person.Essence(ctx);
user.setUsername(login);
user.setAuthorities(getGrantedAuthorities(person));
LOG.info("User '" + username + "' has signed in with roles: " + person.getPermissions());
return user.createUserDetails();
}
/**
* Gets the granted authorities using the {@link Role}s of the given {@link Person}.
*
* @param person to get the granted authorities for, may not be {@code null}
*
* @return the granted authorities for the person
*/
Collection<GrantedAuthority> getGrantedAuthorities(Person person) {
Assert.notNull(person, "Person must be given.");
Collection<Role> permissions = person.getPermissions();
if (permissions.isEmpty()) {
throw new IllegalStateException("Every user must have at least one role, data seems to be corrupt.");
}
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
permissions.stream().forEach(role -> grantedAuthorities.add(role::name));
return grantedAuthorities;
}
@Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
throw new UnsupportedOperationException("PersonContextMapper only supports reading from a context. Please"
+ "use a subclass if mapUserToContext() is required.");
}
}