Reimplement ldap

Signed-off-by: Andrew DeMaria <lostonamountain@gmail.com>
master
Andrew DeMaria 8 years ago
parent e15eec7fdd
commit 488a7d720b
No known key found for this signature in database
GPG Key ID: 0A3F5E91F8364EDF
  1. 5
      libresonic-main/pom.xml
  2. 2
      libresonic-main/src/main/java/org/libresonic/player/controller/AdvancedSettingsController.java
  3. 18
      libresonic-main/src/main/java/org/libresonic/player/dao/UserDao.java
  4. 133
      libresonic-main/src/main/java/org/libresonic/player/security/LibresonicUserDetailsContextMapper.java
  5. 23
      libresonic-main/src/main/java/org/libresonic/player/security/WebSecurityConfig.java
  6. 30
      libresonic-main/src/main/java/org/libresonic/player/service/SecurityService.java
  7. 1
      libresonic-main/src/main/resources/org/libresonic/player/i18n/ResourceBundle_en.properties
  8. 2
      libresonic-main/src/main/webapp/WEB-INF/jsp/advancedSettings.jsp
  9. 12
      libresonic-main/src/test/java/org/libresonic/player/dao/UserDaoTestCase.java

@ -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>

@ -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);
} }

@ -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;
}
}

@ -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);
} }
/** /**

@ -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

Loading…
Cancel
Save