/*
 * Copyright (C) 2013 The University of Manchester
 * 
 * See the file "LICENSE.txt" for license terms.
 */
package org.taverna.server.master.identity;

import static org.springframework.web.context.request.RequestContextHolder.getRequestAttributes;
import static org.taverna.server.master.common.Roles.SELF;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.taverna.server.master.worker.RunDatabaseDAO;

import edu.umd.cs.findbugs.annotations.NonNull;

/**
 * A special authentication provider that allows a workflow to authenticate to
 * itself. This is used to allow the workflow to publish to its own interaction
 * feed.
 * 
 * @author Donal Fellows
 */
public class WorkflowInternalAuthProvider extends
		AbstractUserDetailsAuthenticationProvider {
	private static final Log log = LogFactory.getLog("Taverna.Server.UserDB");
	public static final String PREFIX = "wfrun_";
	private RunDatabaseDAO dao;

	@Required
	public void setDao(RunDatabaseDAO dao) {
		this.dao = dao;
	}

	public void setAuthorizedAddresses(String[] addresses) {
		authorizedAddresses = new HashSet<String>(localAddresses);
		for (String s : addresses)
			authorizedAddresses.add(s);
	}

	@PostConstruct
	public void logConfig() {
		log.info("authorized addresses for automatic access: "
				+ authorizedAddresses);
	}

	private final Set<String> localAddresses = new HashSet<String>();
	private Set<String> authorizedAddresses;
	{
		localAddresses.add("127.0.0.1"); // IPv4
		localAddresses.add("::1"); // IPv6
		try {
			InetAddress addr = InetAddress.getLocalHost();
			if (!addr.isLoopbackAddress())
				localAddresses.add(addr.getHostAddress());
		} catch (UnknownHostException e) {
			// Ignore the exception
		}
		authorizedAddresses = new HashSet<String>(localAddresses);
	}

	@Override
	protected void additionalAuthenticationChecks(UserDetails userRecord,
			UsernamePasswordAuthenticationToken token)
			throws AuthenticationException {
		WebAuthenticationDetails wad = (WebAuthenticationDetails) token
				.getDetails();
		HttpServletRequest req = ((ServletRequestAttributes) getRequestAttributes())
				.getRequest();

		// Are we coming from a "local" address?
		if (!req.getLocalAddr().equals(wad.getRemoteAddress())
				&& !authorizedAddresses.contains(wad.getRemoteAddress()))
			throw new BadCredentialsException("bad login token");

		// Does the password match?
		if (!token.getCredentials().equals(userRecord.getPassword()))
			throw new BadCredentialsException("bad login token");
	}

	@Override
	@NonNull
	protected UserDetails retrieveUser(String username,
			UsernamePasswordAuthenticationToken token)
			throws AuthenticationException {
		if (token.getDetails() == null
				|| !(token.getDetails() instanceof WebAuthenticationDetails))
			throw new UsernameNotFoundException("context unsupported");
		if (!username.startsWith(PREFIX))
			throw new UsernameNotFoundException(
					"unsupported username for this provider");
		String securityToken = dao.getSecurityToken(username.substring(PREFIX
				.length()));
		if (securityToken == null)
			throw new UsernameNotFoundException("no such user");
		return new User(username, securityToken, true, true, true, true,
				Arrays.asList(new LiteralGrantedAuthority(SELF),
						new LiteralGrantedAuthority(username)));
	}
}

@@ -1850,16 +1850,67 @@
 erDB%22);%0A
+%09private static final boolean logDecisions = true;%0A
 %09public 
@@ -3337,24 +3337,227 @@
 eAddress()))
+ %7B%0A%09%09%09if (logDecisions)%0A%09%09%09%09log.info(%22attempt to use workflow magic token from untrusted address:%22%0A%09%09%09%09%09%09+ %22 token=%22%0A%09%09%09%09%09%09+ userRecord.getUsername()%0A%09%09%09%09%09%09+ %22, address=%22%0A%09%09%09%09%09%09+ wad.getRemoteAddress());
 %0A%09%09%09throw ne
@@ -3601,16 +3601,20 @@
 token%22);
+%0A%09%09%7D
 %0A%0A%09%09// D
@@ -3704,63 +3704,371 @@
 ()))
-%0A%09%09%09throw new BadCredentialsException(%22bad login token%22
+ %7B%0A%09%09%09if (logDecisions)%0A%09%09%09%09log.info(%22workflow magic token is untrusted due to password mismatch:%22%0A%09%09%09%09%09%09+ %22 wanted=%22%0A%09%09%09%09%09%09+ userRecord.getPassword()%0A%09%09%09%09%09%09+ %22, got=%22%0A%09%09%09%09%09%09+ token.getCredentials());%0A%09%09%09throw new BadCredentialsException(%22bad login token%22);%0A%09%09%7D%0A%0A%09%09if (logDecisions)%0A%09%09%09log.info(%22granted role %22 + SELF + %22 to user %22%0A%09%09%09%09%09+ userRecord.getUsername()
 );%0A%09
@@ -4512,16 +4512,90 @@
 ider%22);%0A
+%09%09if (logDecisions)%0A%09%09%09log.info(%22request for auth for user %22 + username);%0A
 %09%09String

Add some logging to help track down problems…