package cz.muni.fi.lasaris.sbms.auth;

import java.io.IOException;
import java.security.Principal;
import java.util.Properties;

import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.SecurityContext;

import org.apache.log4j.Logger;
import org.glassfish.jersey.internal.util.Base64;

//http://stackoverflow.com/questions/17068528/authorization-with-rolesalloweddynamicfeature-and-jersey
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
	final static Logger logger = Logger.getLogger(AuthenticationFilter.class);

	private AuthProvider ap;
	
	private Properties props = null; 
	private String appName;
	
	public AuthenticationFilter(String appName, Properties props) {
		super();
		this.props = props;
		try {
			String className = this.props.getProperty("auth.provider");
			if(className != null) {
				Class<?> c = Class.forName(className);
				this.appName = appName;
				this.ap = (AuthProvider)c.newInstance();
				this.ap.init(this.props);
				logger.debug("Initing custom auth provider for app " + appName);
			} else {
				throw new IllegalArgumentException(appName + ": customAuth is required but no AuthProvider implementation is provided.");
			}
		} catch(Exception ex) {
			logger.error(ex);
			logger.error(appName + ": Unable to load AuthProvider - allowing all users.");
			ap = new AuthProvider() {
				
				
				public boolean authenticate(String user, String password, ContainerRequestContext requestContext) {
					return true;
				}

				public boolean authorize(String user, String role, ContainerRequestContext requestContext) {
					return true;
				}

				public void init(Properties props) {
				}
				
			};
		}
	}
	
	public void filter(final ContainerRequestContext requestContext) throws IOException {
		requestContext.setSecurityContext(new SecurityContext() {
			private Principal user;
			
			{
				String auth = requestContext.getHeaderString("authorization");
				// no credentials provided
				if(auth == null) {
					logger.debug(appName + ": No auth provided.");
					user = null;
				} else {
					final String[] creds = decodeAuth(auth);
					if(ap.authenticate(creds[0], creds[1], requestContext)) {
						logger.debug(appName + ": Authenticated.");
						user = new Principal() {

							public String getName() {
								return creds[0];
							}  
						};
					} else {
					logger.debug(appName + ": Auth failed.");
					user = null;
				}
				}
			}
			
			
			
			public Principal getUserPrincipal() {
				return user;
			}

			public boolean isUserInRole(String role) { 
				if(getUserPrincipal() == null) {
					return false;
				}
				String user = getUserPrincipal().getName();
				if(ap.authorize(user, role, requestContext)) {
					logger.debug(appName + ": Authorized: " + user + " in " + role);
					return true;  
				} else {
					logger.debug(appName + ": Not authorized: " + user + " in " + role);
					return false;
				}
			}

			public boolean isSecure() {
				return requestContext.getSecurityContext().isSecure();
			}

			public String getAuthenticationScheme() {
				return requestContext.getSecurityContext().getAuthenticationScheme();
			}

		});    
	}

	// https://simplapi.wordpress.com/2013/01/24/jersey-jax-rs-implements-a-http-basic-auth-decoder/	  
	private String[] decodeAuth(String header) {
		String auth = header.replaceFirst("[B|b]asic ", "");

		//Decode the Base64 into byte[]
		//Base64 decoder = 
		//byte[] decodedBytes = DatatypeConverter.parseBase64Binary(auth);
		byte[] decodedBytes = Base64.decode(auth.getBytes());
		//If the decode fails in any case
		if(decodedBytes == null || decodedBytes.length == 0){
			return null;
		}

		//Now we can convert the byte[] into a splitted array :
		//  - the first one is login,
		//  - the second one password
		return new String(decodedBytes).split(":", 2);
	}

}
