package org.jboss.resteasy.test.security;

import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.util.EntityUtils;
import org.jboss.resteasy.client.ClientExecutor;
import org.jboss.resteasy.client.ClientRequest;
import org.jboss.resteasy.client.ClientResponse;
import org.jboss.resteasy.client.ClientResponseFailure;
import org.jboss.resteasy.client.ProxyFactory;
import org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor;
import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.plugins.server.embedded.SimpleSecurityDomain;
import org.jboss.resteasy.test.EmbeddedContainer;
import org.jboss.resteasy.util.HttpResponseCodes;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.SecurityContext;

import java.util.List;

import static org.jboss.resteasy.test.TestPortProvider.*;

/**
 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
 * @version $Revision: 1 $
 */
public class BasicAuthTest
{
   private static Dispatcher dispatcher;

   @Path("/secured")
   public static interface BaseProxy
   {
      @GET
      String get();

      @GET
      @Path("/authorized")
      String getAuthorized();

      @GET
      @Path("/deny")
      String deny();

      @GET
      @Path("/failure")
      List<String> getFailure();
   }

   @Path("/secured")
   public static class BaseResource
   {
      @GET
      @Path("/failure")
      @RolesAllowed("admin")
      public List<String> getFailure()
      {
         return null;
      }

      @GET
      public String get(@Context SecurityContext ctx)
      {
         System.out.println("********* IN SECURE CLIENT");
         if (!ctx.isUserInRole("admin"))
         {
            System.out.println("NOT IN ROLE!!!!");
            throw new WebApplicationException(401);
         }
         return "hello";
      }

      @GET
      @Path("/authorized")
      @RolesAllowed("admin")
      public String getAuthorized()
      {
         return "authorized";
      }

      @GET
      @Path("/deny")
      @DenyAll
      public String deny()
      {
         return "SHOULD NOT BE REACHED";
      }
   }

   @Path("/secured2")
   public static class BaseResource2
   {
      public String get(@Context SecurityContext ctx)
      {
         System.out.println("********* IN SECURE CLIENT");
         if (!ctx.isUserInRole("admin"))
         {
            System.out.println("NOT IN ROLE!!!!");
            throw new WebApplicationException(401);
         }
         return "hello";
      }

      @GET
      @Path("/authorized")
      @RolesAllowed("admin")
      public String getAuthorized()
      {
         return "authorized";
      }

   }

   @Path("/secured3")
   @RolesAllowed("admin")
   public static class BaseResource3
   {
      @GET
      @Path("/authorized")
      public String getAuthorized()
      {
         return "authorized";
      }

      @GET
      @Path("/anybody")
      @PermitAll
      public String getAnybody()
      {
         return "anybody";
      }
   }

   @BeforeClass
   public static void before() throws Exception
   {
      SimpleSecurityDomain domain = new SimpleSecurityDomain();
      String[] roles =
              {"admin"};
      String[] basic =
              {"user"};
      domain.addUser("bill", "password", roles);
      domain.addUser("mo", "password", basic);
      dispatcher = EmbeddedContainer.start("", domain).getDispatcher();
      dispatcher.getRegistry().addPerRequestResource(BaseResource.class);
      dispatcher.getRegistry().addPerRequestResource(BaseResource2.class);
      dispatcher.getRegistry().addPerRequestResource(BaseResource3.class);
   }

   @AfterClass
   public static void after() throws Exception
   {
      EmbeddedContainer.stop();
   }

   @Test
   public void testProxy() throws Exception
   {
      DefaultHttpClient client = new DefaultHttpClient();
      UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("bill", "password");
      client.getCredentialsProvider().setCredentials(new AuthScope(AuthScope.ANY), credentials);
      ClientExecutor executor = createAuthenticatingExecutor(client);
      BaseProxy proxy = ProxyFactory.create(BaseProxy.class, generateURL(""), executor);
      String val = proxy.get();
      Assert.assertEquals(val, "hello");
      val = proxy.getAuthorized();
      Assert.assertEquals(val, "authorized");
   }

   @Test
   public void testProxyFailure() throws Exception
   {
      BaseProxy proxy = ProxyFactory.create(BaseProxy.class, generateURL(""));
      try
      {
         proxy.getFailure();
      }
      catch (ClientResponseFailure e)
      {
         Assert.assertEquals(e.getResponse().getStatus(), 401);
      }
   }

   @Test
   public void testSecurity() throws Exception
   {
      DefaultHttpClient client = new DefaultHttpClient();
      UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("bill", "password");
      client.getCredentialsProvider().setCredentials(new AuthScope(AuthScope.ANY), credentials);
      ClientExecutor executor = createAuthenticatingExecutor(client);
 
      {
         ClientRequest request = new ClientRequest(generateURL("/secured"), executor);
         ClientResponse<String> response = request.get(String.class);
         Assert.assertEquals(HttpResponseCodes.SC_OK, response.getStatus());
         Assert.assertEquals("hello", response.getEntity());
         response.releaseConnection();
      }
      
      {
         ClientRequest request = new ClientRequest(generateURL("/secured/authorized"), executor);
         ClientResponse<String> response = request.get(String.class);
         Assert.assertEquals(HttpResponseCodes.SC_OK, response.getStatus());
         Assert.assertEquals("authorized", response.getEntity());  
         response.releaseConnection();
      }
      
      {
         ClientRequest request = new ClientRequest(generateURL("/secured/deny"), executor);
         ClientResponse<String> response = request.get(String.class);
         Assert.assertEquals(HttpResponseCodes.SC_UNAUTHORIZED, response.getStatus());
         response.releaseConnection();
      }
      {
         ClientRequest request = new ClientRequest(generateURL("/secured3/authorized"), executor);
         ClientResponse<String> response = request.get(String.class);
         Assert.assertEquals(HttpResponseCodes.SC_OK, response.getStatus());
         Assert.assertEquals("authorized", response.getEntity());
         response.releaseConnection();
      }
      {
         ClientRequest request = new ClientRequest(generateURL("/secured3/authorized"));
         ClientResponse<String> response = request.get(String.class);
         Assert.assertEquals(401, response.getStatus());
         response.releaseConnection();
      }
      {
         ClientRequest request = new ClientRequest(generateURL("/secured3/anybody"));
         ClientResponse<String> response = request.get(String.class);
         Assert.assertEquals(200, response.getStatus());
         response.releaseConnection();
      }
   }

   /**
    * RESTEASY-579
    *
    * Found 579 bug when doing 575 so the test is here out of laziness
    *
    * @throws Exception
    */
   @Test
   public void test579() throws Exception
   {
      DefaultHttpClient client = new DefaultHttpClient();
      UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("bill", "password");
      client.getCredentialsProvider().setCredentials(new AuthScope(AuthScope.ANY), credentials);
      ClientExecutor executor = createAuthenticatingExecutor(client);
      ClientRequest request = new ClientRequest(generateURL("/secured2"), executor);
      ClientResponse<?> response = request.get();
      Assert.assertEquals(404, response.getStatus());
      response.releaseConnection();
   }

   @Test
   public void testSecurityFailure() throws Exception
   {
      DefaultHttpClient client = new DefaultHttpClient();

      {
         HttpGet method = new HttpGet(generateURL("/secured"));
         HttpResponse response = client.execute(method);
         Assert.assertEquals(401, response.getStatusLine().getStatusCode());
         EntityUtils.consume(response.getEntity());
      }

      ClientExecutor executor = createAuthenticatingExecutor(client);
      
      {
         UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("bill", "password");
         client.getCredentialsProvider().setCredentials(new AuthScope(AuthScope.ANY), credentials);
         ClientRequest request = new ClientRequest(generateURL("/secured/authorized"), executor);
         ClientResponse<String> response = request.get(String.class);
         Assert.assertEquals(HttpResponseCodes.SC_OK, response.getStatus());
         Assert.assertEquals("authorized", response.getEntity());  
      }
      
      {
         UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("mo", "password");
         client.getCredentialsProvider().setCredentials(new AuthScope(AuthScope.ANY), credentials);
         ClientRequest request = new ClientRequest(generateURL("/secured/authorized"), executor);
         ClientResponse<?> response = request.get();
         Assert.assertEquals(HttpResponseCodes.SC_UNAUTHORIZED, response.getStatus());
         response.releaseConnection();
      }
   }

   /**
    * Create a ClientExecutor which does preemptive authentication.
    */
   
   static private ClientExecutor createAuthenticatingExecutor(DefaultHttpClient client)
   {
      // Create AuthCache instance
      AuthCache authCache = new BasicAuthCache();
      
      // Generate BASIC scheme object and add it to the local auth cache
      BasicScheme basicAuth = new BasicScheme();
      HttpHost targetHost = new HttpHost("localhost", 8081);
      authCache.put(targetHost, basicAuth);

      // Add AuthCache to the execution context
      BasicHttpContext localContext = new BasicHttpContext();
      localContext.setAttribute(ClientContext.AUTH_CACHE, authCache);
      
      // Create ClientExecutor.
      ApacheHttpClient4Executor executor = new ApacheHttpClient4Executor(client, localContext);
      return executor;
   }
}
