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

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

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 javax.xml.bind.DatatypeConverter;

import org.apache.log4j.Logger;

import cz.muni.fi.lasaris.sbms.SBMSApplication;

//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;

	public AuthenticationFilter() {
		super();
		try {
			String className = SBMSApplication.getInstance().getSBMSProperties().getProperty("api.customAuth.provider");
			if(className != null) {
				Class<?> c = Class.forName(className);
				ap = (AuthProvider)c.newInstance();
			} else {
				throw new IllegalArgumentException("customAuth is required but no AuthProvider implementation is provided.");
			}
		} catch(Exception ex) {
			logger.error(ex);
			logger.error("Unable to load AuthProvider - allowing all users.");
			ap = new AuthProvider() {

				@Override
				public boolean authenticate(String user, String password, ContainerRequestContext requestContext) {
					return true;
				}

				@Override
				public boolean authorize(String user, String role, ContainerRequestContext requestContext) {
					return true;
				}
				
			};
		}
	}
	
	@Override
	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("No auth provided.");
					user = null;
				} else {
					final String[] creds = decodeAuth(auth);
					if(ap.authenticate(creds[0], creds[1], requestContext)) {
						logger.debug("Authenticated.");
						user = new Principal() {

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

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

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

			@Override
			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[]
		byte[] decodedBytes = DatatypeConverter.parseBase64Binary(auth);

		//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);
	}

}
