/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.security; import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.geoserver.security.impl.GeoServerRole; import org.geoserver.security.impl.GeoServerUser; import org.geoserver.security.impl.UserDetailsWrapper; import org.geoserver.security.password.GeoServerMultiplexingPasswordEncoder; import org.geoserver.security.password.UserDetailsPasswordWrapper; import org.springframework.dao.DataAccessException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.codec.Hex; import org.springframework.security.web.authentication.www.DigestAuthenticationFilter; /** * {@link UserDetailsService} implementation to be used for * HTTP digest authentication * * {@link UserDetails} objects have their password alreay md5a1 encoded. * * {@link DigestAuthenticationFilter#setPasswordAlreadyEncoded(boolean)} must * be called with a value of <code>true</code> * * @author christian * */ public class HttpDigestUserDetailsServiceWrapper implements UserDetailsService { static public class DigestUserDetails extends UserDetailsWrapper { private static final long serialVersionUID = 1L; private String password; private Collection<GrantedAuthority> roles; public DigestUserDetails(UserDetails details, String password, Collection<GrantedAuthority> roles) { super(details); this.password=password; this.roles=roles; } @Override public Collection<GrantedAuthority> getAuthorities() { return roles; } @Override public String getPassword() { return password; } } private GeoServerSecurityManager manager; protected GeoServerUserGroupService service; protected Charset charSet; protected final char[] delimArray= new char[] {':' }; protected MessageDigest digest; protected GeoServerMultiplexingPasswordEncoder enc; public HttpDigestUserDetailsServiceWrapper(GeoServerUserGroupService service,Charset charSet) { this.service= service; this.charSet=charSet; manager = service.getSecurityManager(); enc = new GeoServerMultiplexingPasswordEncoder(service.getSecurityManager(),service); try { digest = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("No MD5 algorithm available!"); } } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { if (GeoServerUser.ROOT_USERNAME.equals(username)) return prepareForRootUser (); GeoServerUser user = (GeoServerUser) service.loadUserByUsername(username); return prepareForUser(user); } UserDetails prepareForUser (GeoServerUser user) { char[] pw = null; try { pw = enc.decodeToCharArray(user.getPassword()); } catch (UnsupportedOperationException ex) { pw = user.getPassword().toCharArray(); } String a1 = encodePasswordInA1Format(user.getUsername(), GeoServerSecurityManager.REALM, pw); manager.disposePassword(pw); List<GrantedAuthority> roles = new ArrayList<GrantedAuthority>(); roles.addAll(user.getAuthorities()); roles.add(GeoServerRole.AUTHENTICATED_ROLE); return new DigestUserDetails(user, a1,roles); } UserDetails prepareForRootUser () { char[] mpw = null; try { mpw= manager.getMasterPassword(); String a1 = encodePasswordInA1Format(GeoServerUser.ROOT_USERNAME, GeoServerSecurityManager.REALM, mpw); return new UserDetailsPasswordWrapper( GeoServerUser.createRoot(), a1); } finally { if (mpw!=null) manager.disposePassword(mpw); } } String encodePasswordInA1Format(String username, String realm, char[] password) { char[] array = null; try { char[] usernameArray = username.toCharArray(); char[] realmArray = realm.toCharArray(); array = new char[usernameArray.length+realmArray.length+password.length+2]; int pos=0; System.arraycopy(usernameArray, 0, array, pos, usernameArray.length); pos+=usernameArray.length; System.arraycopy(delimArray, 0, array, pos, 1); pos++; System.arraycopy(realmArray, 0, array, pos, realmArray.length); pos+=realmArray.length; System.arraycopy(delimArray, 0, array, pos, 1); pos++; System.arraycopy(password, 0, array, pos, password.length); MessageDigest md=null; try { md = (MessageDigest) digest.clone(); // thread safe } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } return new String(Hex.encode(md.digest(SecurityUtils.toBytes(array, charSet)))); } finally { if (array!=null) manager.disposePassword(array); } } }