Reimplement ldap
Signed-off-by: Andrew DeMaria <lostonamountain@gmail.com>
This commit is contained in:
@@ -69,6 +69,11 @@
|
|||||||
<groupId>org.springframework.security</groupId>
|
<groupId>org.springframework.security</groupId>
|
||||||
<artifactId>spring-security-ldap</artifactId>
|
<artifactId>spring-security-ldap</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ldap</groupId>
|
||||||
|
<artifactId>spring-ldap-core</artifactId>
|
||||||
|
<version>2.0.2.RELEASE</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.security</groupId>
|
<groupId>org.springframework.security</groupId>
|
||||||
<artifactId>spring-security-taglibs</artifactId>
|
<artifactId>spring-security-taglibs</artifactId>
|
||||||
|
|||||||
+2
@@ -98,6 +98,8 @@ public class AdvancedSettingsController {
|
|||||||
settingsService.setSmtpPassword(command.getSmtpPassword());
|
settingsService.setSmtpPassword(command.getSmtpPassword());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
settingsService.save();
|
||||||
|
|
||||||
return "redirect:advancedSettings.view";
|
return "redirect:advancedSettings.view";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,11 +69,23 @@ public class UserDao extends AbstractDao {
|
|||||||
* Returns the user with the given username.
|
* Returns the user with the given username.
|
||||||
*
|
*
|
||||||
* @param username The username used when logging in.
|
* @param username The username used when logging in.
|
||||||
|
* @param caseSensitive
|
||||||
* @return The user, or <code>null</code> if not found.
|
* @return The user, or <code>null</code> if not found.
|
||||||
*/
|
*/
|
||||||
public User getUserByName(String username) {
|
public User getUserByName(String username, boolean caseSensitive) {
|
||||||
String sql = "select " + USER_COLUMNS + " from " + getUserTable() + " where username=?";
|
String sql;
|
||||||
User user = queryOne(sql, userRowMapper, username);
|
if(caseSensitive) {
|
||||||
|
sql = "select " + USER_COLUMNS + " from " + getUserTable() + " where username=?";
|
||||||
|
} else {
|
||||||
|
sql = "select " + USER_COLUMNS + " from " + getUserTable() + " where UPPER(username)=UPPER(?)";
|
||||||
|
}
|
||||||
|
List<User> users = query(sql, userRowMapper, username);
|
||||||
|
User user = null;
|
||||||
|
if(users.size() == 1) {
|
||||||
|
user = users.iterator().next();
|
||||||
|
} else if (users.size() > 1) {
|
||||||
|
throw new RuntimeException("Too many matching users");
|
||||||
|
}
|
||||||
if(user != null) {
|
if(user != null) {
|
||||||
readRoles(user);
|
readRoles(user);
|
||||||
}
|
}
|
||||||
|
|||||||
+133
@@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
-3
@@ -1,6 +1,7 @@
|
|||||||
package org.libresonic.player.security;
|
package org.libresonic.player.security;
|
||||||
|
|
||||||
import org.libresonic.player.service.SecurityService;
|
import org.libresonic.player.service.SecurityService;
|
||||||
|
import org.libresonic.player.service.SettingsService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@@ -18,20 +19,37 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
|||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SecurityService securityService;
|
private SecurityService securityService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private CsrfSecurityRequestMatcher csrfSecurityRequestMatcher;
|
private CsrfSecurityRequestMatcher csrfSecurityRequestMatcher;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
LoginFailureLogger loginFailureLogger;
|
LoginFailureLogger loginFailureLogger;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
SettingsService settingsService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
LibresonicUserDetailsContextMapper libresonicUserDetailsContextMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Bean(name = "authenticationManager")
|
@Bean(name = "authenticationManager")
|
||||||
public AuthenticationManager authenticationManagerBean() throws Exception {
|
public AuthenticationManager authenticationManagerBean() throws Exception {
|
||||||
return super.authenticationManagerBean();
|
return super.authenticationManagerBean();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
||||||
|
if (settingsService.isLdapEnabled()) {
|
||||||
|
auth.ldapAuthentication()
|
||||||
|
.contextSource()
|
||||||
|
.managerDn(settingsService.getLdapManagerDn())
|
||||||
|
.managerPassword(settingsService.getLdapManagerPassword())
|
||||||
|
.url(settingsService.getLdapUrl())
|
||||||
|
.and()
|
||||||
|
.userSearchFilter(settingsService.getLdapSearchFilter())
|
||||||
|
.userDetailsContextMapper(libresonicUserDetailsContextMapper);
|
||||||
|
}
|
||||||
auth.userDetailsService(securityService);
|
auth.userDetailsService(securityService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +107,6 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
|||||||
.passwordParameter("j_password")
|
.passwordParameter("j_password")
|
||||||
// see http://docs.spring.io/spring-security/site/docs/3.2.4.RELEASE/reference/htmlsingle/#csrf-logout
|
// see http://docs.spring.io/spring-security/site/docs/3.2.4.RELEASE/reference/htmlsingle/#csrf-logout
|
||||||
.and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET")).logoutSuccessUrl("/login?logout")
|
.and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET")).logoutSuccessUrl("/login?logout")
|
||||||
.and().rememberMe().userDetailsService(securityService).key("libresonic");
|
.and().rememberMe().key("libresonic");
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,11 +62,22 @@ public class SecurityService implements UserDetailsService {
|
|||||||
* @throws DataAccessException If user could not be found for a repository-specific reason.
|
* @throws DataAccessException If user could not be found for a repository-specific reason.
|
||||||
*/
|
*/
|
||||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
|
||||||
User user = getUserByName(username);
|
return loadUserByUsername(username, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserDetails loadUserByUsername(String username, boolean caseSensitive)
|
||||||
|
throws UsernameNotFoundException, DataAccessException {
|
||||||
|
User user = getUserByName(username, caseSensitive);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new UsernameNotFoundException("User \"" + username + "\" was not found.");
|
throw new UsernameNotFoundException("User \"" + username + "\" was not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<GrantedAuthority> authorities = getGrantedAuthorities(username);
|
||||||
|
|
||||||
|
return new org.springframework.security.core.userdetails.User(username, user.getPassword(), authorities);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<GrantedAuthority> getGrantedAuthorities(String username) {
|
||||||
String[] roles = userDao.getRolesForUser(username);
|
String[] roles = userDao.getRolesForUser(username);
|
||||||
List<GrantedAuthority> authorities = new ArrayList<>();
|
List<GrantedAuthority> authorities = new ArrayList<>();
|
||||||
authorities.add(new SimpleGrantedAuthority("IS_AUTHENTICATED_ANONYMOUSLY"));
|
authorities.add(new SimpleGrantedAuthority("IS_AUTHENTICATED_ANONYMOUSLY"));
|
||||||
@@ -74,8 +85,7 @@ public class SecurityService implements UserDetailsService {
|
|||||||
for (int i = 0; i < roles.length; i++) {
|
for (int i = 0; i < roles.length; i++) {
|
||||||
authorities.add(new SimpleGrantedAuthority("ROLE_" + roles[i].toUpperCase()));
|
authorities.add(new SimpleGrantedAuthority("ROLE_" + roles[i].toUpperCase()));
|
||||||
}
|
}
|
||||||
|
return authorities;
|
||||||
return new org.springframework.security.core.userdetails.User(username, user.getPassword(), authorities);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,7 +96,7 @@ public class SecurityService implements UserDetailsService {
|
|||||||
*/
|
*/
|
||||||
public User getCurrentUser(HttpServletRequest request) {
|
public User getCurrentUser(HttpServletRequest request) {
|
||||||
String username = getCurrentUsername(request);
|
String username = getCurrentUsername(request);
|
||||||
return username == null ? null : userDao.getUserByName(username);
|
return username == null ? null : getUserByName(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -106,7 +116,17 @@ public class SecurityService implements UserDetailsService {
|
|||||||
* @return The user, or <code>null</code> if not found.
|
* @return The user, or <code>null</code> if not found.
|
||||||
*/
|
*/
|
||||||
public User getUserByName(String username) {
|
public User getUserByName(String username) {
|
||||||
return userDao.getUserByName(username);
|
return getUserByName(username, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the user with the given username
|
||||||
|
* @param username
|
||||||
|
* @param caseSensitive If false, will do a case insensitive search
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public User getUserByName(String username, boolean caseSensitive) {
|
||||||
|
return userDao.getUserByName(username, caseSensitive);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
@@ -371,6 +371,7 @@ advancedsettings.ldapsearchfilter = LDAP search filter
|
|||||||
advancedsettings.ldapmanagerdn = LDAP manager DN<br><div class="detail">(Optional)</div>
|
advancedsettings.ldapmanagerdn = LDAP manager DN<br><div class="detail">(Optional)</div>
|
||||||
advancedsettings.ldapmanagerpassword = Password
|
advancedsettings.ldapmanagerpassword = Password
|
||||||
advancedsettings.ldapautoshadowing = Automatically create users in {0}
|
advancedsettings.ldapautoshadowing = Automatically create users in {0}
|
||||||
|
advancedsettings.ldapRequiresRestart = LDAP settings require a restart to take effect
|
||||||
advancedsettings.smtpPort = SMTP port
|
advancedsettings.smtpPort = SMTP port
|
||||||
advancedsettings.smtpServer = SMTP server
|
advancedsettings.smtpServer = SMTP server
|
||||||
advancedsettings.smtpEncryption = SMTP encryption
|
advancedsettings.smtpEncryption = SMTP encryption
|
||||||
|
|||||||
@@ -138,6 +138,8 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<p class="warning"><fmt:message key="advancedsettings.ldapRequiresRestart"/></p>
|
||||||
|
|
||||||
<input type="submit" value="<fmt:message key="common.save"/>" style="margin-right:0.3em">
|
<input type="submit" value="<fmt:message key="common.save"/>" style="margin-right:0.3em">
|
||||||
<input type="button" value="<fmt:message key="common.cancel"/>" onclick="location.href='nowPlaying.view'">
|
<input type="button" value="<fmt:message key="common.cancel"/>" onclick="location.href='nowPlaying.view'">
|
||||||
|
|
||||||
|
|||||||
@@ -115,15 +115,15 @@ public class UserDaoTestCase extends DaoTestCaseBean2 {
|
|||||||
User user = new User("sindre", "secret", null);
|
User user = new User("sindre", "secret", null);
|
||||||
userDao.createUser(user);
|
userDao.createUser(user);
|
||||||
|
|
||||||
User newUser = userDao.getUserByName("sindre");
|
User newUser = userDao.getUserByName("sindre", true);
|
||||||
assertNotNull("Error in getUserByName().", newUser);
|
assertNotNull("Error in getUserByName().", newUser);
|
||||||
assertUserEquals(user, newUser);
|
assertUserEquals(user, newUser);
|
||||||
|
|
||||||
assertNull("Error in getUserByName().", userDao.getUserByName("sindre2"));
|
assertNull("Error in getUserByName().", userDao.getUserByName("sindre2", true));
|
||||||
assertNull("Error in getUserByName().", userDao.getUserByName("sindre "));
|
assertNull("Error in getUserByName().", userDao.getUserByName("sindre ", true));
|
||||||
assertNull("Error in getUserByName().", userDao.getUserByName("bente"));
|
assertNull("Error in getUserByName().", userDao.getUserByName("bente", true));
|
||||||
assertNull("Error in getUserByName().", userDao.getUserByName(""));
|
assertNull("Error in getUserByName().", userDao.getUserByName("", true));
|
||||||
assertNull("Error in getUserByName().", userDao.getUserByName(null));
|
assertNull("Error in getUserByName().", userDao.getUserByName(null, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user