/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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 org.libresonic.player.security;
import org.libresonic.player.Logger;
import org.libresonic.player.domain.User;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.ldap.ppolicy.PasswordPolicyControl;
import org.springframework.security.ldap.ppolicy.PasswordPolicyResponseControl;
import org.springframework.security.ldap.userdetails.LdapUserDetailsImpl;
import org.springframework.security.ldap.userdetails.UserDetailsContextMapper;
import org.springframework.stereotype.Component;
import java.util.Collection;
@Component
public class LibresonicUserDetailsContextMapper implements UserDetailsContextMapper {
// ~ Instance fields
// ================================================================================================
private final Logger logger = Logger.getLogger(LibresonicUserDetailsContextMapper.class);
private String passwordAttributeName = "userPassword";
@Autowired
SecurityService securityService;
@Autowired
SettingsService settingsService;
// ~ Methods
// ========================================================================================================
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Collection<? extends GrantedAuthority> authorities) {
String dn = ctx.getNameInNamespace();
logger.debug("Mapping user details from context with DN: " + dn);
// User must be defined in Libresonic, unless auto-shadowing is enabled.
User user = securityService.getUserByName(username, false);
if (user == null && !settingsService.isLdapAutoShadowing()) {
throw new BadCredentialsException("User does not exist.");
}
if (user == null) {
User newUser = new User(username, "", null, true, 0L, 0L, 0L);
newUser.setStreamRole(true);
newUser.setSettingsRole(true);
securityService.createUser(newUser);
logger.info("Created local user '" + username + "' for DN " + dn);
user = securityService.getUserByName(username, false);
}
// LDAP authentication must be enabled for the given user.
if (!user.isLdapAuthenticated()) {
throw new BadCredentialsException("LDAP authentication disabled for user.");
}
LdapUserDetailsImpl.Essence essence = new LdapUserDetailsImpl.Essence();
essence.setDn(dn);
Object passwordValue = ctx.getObjectAttribute(passwordAttributeName);
if (passwordValue != null) {
essence.setPassword(mapPassword(passwordValue));
}
essence.setUsername(user.getUsername());
// Add the supplied authorities
for (GrantedAuthority authority : securityService.getGrantedAuthorities(user.getUsername())) {
essence.addAuthority(authority);
}
// Check for PPolicy data
PasswordPolicyResponseControl ppolicy = (PasswordPolicyResponseControl) ctx
.getObjectAttribute(PasswordPolicyControl.OID);
if (ppolicy != null) {
essence.setTimeBeforeExpiration(ppolicy.getTimeBeforeExpiration());
essence.setGraceLoginsRemaining(ppolicy.getGraceLoginsRemaining());
}
return essence.createUserDetails();
}
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
throw new UnsupportedOperationException(
"LdapUserDetailsMapper only supports reading from a context. Please"
+ "use a subclass if mapUserToContext() is required.");
}
/**
* Extension point to allow customized creation of the user's password from the
* attribute stored in the directory.
*
* @param passwordValue the value of the password attribute
* @return a String representation of the password.
*/
protected String mapPassword(Object passwordValue) {
if (!(passwordValue instanceof String)) {
// Assume it's binary
passwordValue = new String((byte[]) passwordValue);
}
return (String) passwordValue;
}
}