package org.openplans.security;
import java.io.BufferedReader;
import java.io.File;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.logging.Logger;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.TreeSet;
import java.util.Iterator;
import java.util.Properties;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.BadCredentialsException;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.GrantedAuthorityImpl;
import org.acegisecurity.providers.AuthenticationProvider;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.userdetails.User;
import org.vfny.geoserver.global.GeoserverDataDirectory;
import org.vfny.geoserver.global.ConfigurationException;
/**
* An authentication provider that can validate OpenPlans.org username and authentication token pairs.
* The authentication token consists of a hash of the username and a secret key; validation is done by
* hashing the plaintext username with the secret key and comparing with the token.
*
* @author David Winslow - TOPP
*/
public class OpenPlansAuthenticationProvider implements AuthenticationProvider {
/**
* The secret key.
*/
final String secret;
private Map roles;
static Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geoserver.community");
private void loadRoles() {
Map tempRoles = new HashMap();
try{
File securityDir = GeoserverDataDirectory.findCreateConfigDir("security");
File roleFile = new File(securityDir, "roles.properties");
Properties roleProperties = new Properties();
roleProperties.load(new BufferedInputStream(new FileInputStream(roleFile)));
Iterator it = roleProperties.entrySet().iterator();
while(it.hasNext()){
try {
Map.Entry entry = (Map.Entry)it.next();
String username = (String)entry.getKey();
String[] roleArray = ((String)entry.getValue()).split(",");
Set roleSet = new TreeSet();
for (int i = 0; i < roleArray.length; i++){
roleSet.add(roleArray[i]);
}
tempRoles.put(username, roleSet);
} catch (Exception e){
continue; // of course any problems can be ignored while parsing the file! we have defaults!
}
}
} catch (ConfigurationException gce){
LOGGER.warning("Couldn't find or create geoserver security directory!!!");
} catch (IOException ioe){
LOGGER.warning("Couldn't read extra roles file");
}
roles = tempRoles;
}
/**
* Override the default constructor to read in the secret key from a file on disk.
*/
public OpenPlansAuthenticationProvider (){
loadRoles();
String tempSecret = "";
try{
File secretFile = new File(
GeoserverDataDirectory.findCreateConfigDir("security"), "secret.txt"
);
BufferedReader br =
new BufferedReader(new InputStreamReader(new FileInputStream(secretFile)));
tempSecret = br.readLine();
} catch (IOException ioe){
tempSecret = "blah";
LOGGER.severe("couldn't read file for secret");
}
secret = tempSecret;
}
public Authentication authenticate(Authentication arg0)
throws AuthenticationException {
UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken)arg0;
String token = (auth.getCredentials() == null ? "" : auth.getCredentials().toString());
if (token.length() > 40)
token = token.substring(0, 40);
// the token is expected to be 40 characters, this may change depending on the hash function used
// the truncating is only needed to deal with weird garbage characters added by tomcat
LOGGER.info("input:" + token);
if (getAuth(auth.getName()).equals(token)) {
return createNewAuthentication(auth);
}
throw new BadCredentialsException("something went wrong");
}
/**
* Create an authenticated AuthenticationToken with the same credentials as an existing one.
* @param auth the original authentication token
* @return the authenticated one
*/
private Authentication createNewAuthentication(UsernamePasswordAuthenticationToken auth) {
GrantedAuthority[] ga = null;
Set roleSet = null;
try {
roleSet = (Set)roles.get(auth.getName());
} catch (Exception e){
// we can ignore this error because we handle the case where the map.get() returns null anyway
}
if (roleSet == null) {
roleSet = new TreeSet();
roleSet.add("ROLE_AUTHENTICATED");
}
ga = new GrantedAuthority[roleSet.size() + (auth.getAuthorities() == null? 0 : auth.getAuthorities().length)];
for (int i = 0;
auth.getAuthorities() != null
&& i < auth.getAuthorities().length;
i++){
ga[i] = auth.getAuthorities()[i];
}
Iterator iter = roleSet.iterator();
for (int i = (auth.getAuthorities() != null ? auth.getAuthorities().length : 0);
i < ga.length;
i++){
ga[i] = new GrantedAuthorityImpl((String)iter.next());
}
UsernamePasswordAuthenticationToken upat =
new UsernamePasswordAuthenticationToken(
new User(auth.getName(),
(auth.getCredentials() == null ? null : auth.getCredentials().toString()),
true,
true,
true,
true,
ga
),
auth.getCredentials(),
ga
);
return upat;
}
public boolean supports(Class arg0) {
return UsernamePasswordAuthenticationToken.class.equals(arg0);
}
/**
* Find the authentication token for a particular username
* @param username the username to authenticate
* @return the token for that username
*/
private String getAuth(String username) {
String auth = "";
SecretKeySpec key = new SecretKeySpec(secret.getBytes(), "SHA");
try {
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(key);
mac.update(username.getBytes());
byte[] result = (mac.doFinal());
String charmap = "0123456789abcdef";
for (int i = 0; i < result.length; i++) {
int first = (result[i] >> 4) & 0x0f;
int second = result[i] & 0x0f;
auth += (charmap.charAt(first));
auth += (charmap.charAt(second));
}
} catch (Exception nsae) {
nsae.printStackTrace();
}
LOGGER.info("auth: " + auth);
return auth;
}
}