/* This program is free software: you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package org.opentripplanner.web.authentication;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.TimeZone;
import org.opentripplanner.common.model.T2;
import org.opentripplanner.web.authentication.WSSEAuthentication.WSSEAuthDetails;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.codec.Base64;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.util.Assert;
/**
* WSSE UsernameToken Spring Authentication Provider
*
* @link http://www.xml.com/pub/a/2003/12/17/dive.html
*
*/
public class WSSEAuthenticationProvider implements AuthenticationProvider, InitializingBean {
private Queue<T2<String, Long>> recentlyUsedNonceList;
private HashSet<String> recentlyUsedNonceSet;
private UserDetailsService userDetailsService;
private MessageDigest sha1;
private static long NONCE_EXPIRATION_MILLIS = 1000 * 60 * 5; // Five minutes
private SimpleDateFormat dateFormat;
WSSEAuthenticationProvider() {
dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
recentlyUsedNonceList = new LinkedList<T2<String, Long>>();
recentlyUsedNonceSet = new HashSet<String>();
try {
sha1 = MessageDigest.getInstance("SHA1");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(userDetailsService,
"An userDetailsService must be set");
}
@Override
public Authentication authenticate(Authentication auth)
throws AuthenticationException {
String username = auth.getName();
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
String password = userDetails.getPassword();
WSSEAuthentication.WSSEAuthDetails authDetails = (WSSEAuthDetails) auth
.getDetails();
String nonce = authDetails.getNonce();
long now = System.currentTimeMillis();
// clear out old nonces
while (!recentlyUsedNonceList.isEmpty()) {
T2<String, Long> oldNonce = recentlyUsedNonceList.peek();
if (now - oldNonce.getSecond() > NONCE_EXPIRATION_MILLIS) {
// expire nonce
recentlyUsedNonceList.poll();
recentlyUsedNonceSet.remove(oldNonce.getFirst());
} else {
break; // end of expired nonces
}
}
// check for reused nonces
if (recentlyUsedNonceSet.contains(nonce)) {
throw new BadCredentialsException("reused nonce");
}
String created = authDetails.getCreated();
// check date
try {
Date requestDate = dateFormat.parse(created);
if (Math.abs(now - requestDate.getTime()) > NONCE_EXPIRATION_MILLIS) {
throw new BadCredentialsException("Date out of range");
}
} catch (ParseException e) {
throw new BadCredentialsException("Bad date format", e);
}
sha1.reset();
byte[] digest = sha1.digest((nonce + created + password).getBytes());
byte[] base64Digest = Base64.encode(digest);
if (!Arrays.equals(base64Digest, authDetails.getPasswordDigest().getBytes())) {
throw new BadCredentialsException("bad digest");
}
recentlyUsedNonceList.add(new T2<String, Long>(nonce, now));
recentlyUsedNonceSet.add(nonce);
auth.setAuthenticated(true);
return auth;
}
@Override
public boolean supports(Class<? extends Object> cls) {
return WSSEAuthentication.class.isAssignableFrom(cls);
}
public void setUserDetailsService(UserDetailsService service) {
this.userDetailsService = service;
}
public UserDetailsService getUserDetailsService() {
return userDetailsService;
}
}