Handled scopes & added request token with authorization code tests.

Change-Id: I775141b8b94bf2d1c86ad873807fcb1b12f3914f
diff --git a/full/src/test/java/de/ids_mannheim/korap/config/SpringJerseyTest.java b/full/src/test/java/de/ids_mannheim/korap/config/SpringJerseyTest.java
index 619b795..246e14e 100644
--- a/full/src/test/java/de/ids_mannheim/korap/config/SpringJerseyTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/config/SpringJerseyTest.java
@@ -11,6 +11,7 @@
 import org.springframework.context.support.GenericApplicationContext;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.web.context.ContextLoaderListener;
 import org.springframework.web.context.support.AbstractRefreshableWebApplicationContext;
 
 import com.sun.jersey.spi.spring.container.servlet.SpringServlet;
@@ -72,8 +73,9 @@
         return new WebAppDescriptor.Builder(classPackages)
                 .servletClass(SpringServlet.class)
                 .contextListenerClass(StaticContextLoaderListener.class)
-                //                .contextParam("contextConfigLocation",
-                //                        "classpath:test-config.xml")
+//                .contextListenerClass(ContextLoaderListener.class)
+//                                .contextParam("contextConfigLocation",
+//                                        "classpath:test-config.xml")
                 .build();
     }
 
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
index ca4d0ee..216d7a1 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
@@ -18,12 +18,14 @@
 import com.fasterxml.jackson.databind.JsonNode;
 import com.google.common.net.HttpHeaders;
 import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.uri.UriComponent;
 import com.sun.jersey.core.util.MultivaluedMapImpl;
 
 import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
 import de.ids_mannheim.korap.config.Attributes;
 import de.ids_mannheim.korap.config.SpringJerseyTest;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
 import de.ids_mannheim.korap.utils.JsonUtils;
 
 /**
@@ -34,7 +36,7 @@
 
     @Autowired
     private HttpAuthorizationHandler handler;
-    
+
     private ClientResponse requestAuthorizationConfidentialClient (
             MultivaluedMap<String, String> form) throws KustvaktException {
 
@@ -127,6 +129,25 @@
                 node.at("/error_description").asText());
     }
 
+    @Test
+    public void testAuthorizeInvalidScope () throws KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("response_type", "code");
+        form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        form.add("username", "dory");
+        form.add("password", "password");
+        form.add("scope", "read_address");
+
+        ClientResponse response = requestAuthorizationConfidentialClient(form);
+        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+
+        String entity = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(OAuth2Error.INVALID_SCOPE, node.at("/error").asText());
+        assertEquals("read_address is an invalid scope",
+                node.at("/error_description").asText());
+    }
+
     private ClientResponse requestToken (MultivaluedMap<String, String> form)
             throws KustvaktException {
         return resource().path("oauth2").path("token")
@@ -139,24 +160,30 @@
     @Test
     public void testRequestTokenAuthorizationConfidential ()
             throws KustvaktException {
-        
+
         MultivaluedMap<String, String> authForm = new MultivaluedMapImpl();
         authForm.add("response_type", "code");
         authForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
         authForm.add("username", "dory");
         authForm.add("password", "password");
         authForm.add("scope", "read_username");
-        
-        ClientResponse response = requestAuthorizationConfidentialClient(authForm);
+
+        ClientResponse response =
+                requestAuthorizationConfidentialClient(authForm);
         URI redirectUri = response.getLocation();
-        String code = redirectUri.getQuery().split("=")[1];
-        
+        MultivaluedMap<String, String> params =
+                UriComponent.decodeQuery(redirectUri, true);
+        String code = params.get("code").get(0);
+        String scopes = params.get("scope").get(0);
+
+        assertEquals(scopes, "read_username");
+
         MultivaluedMap<String, String> tokenForm = new MultivaluedMapImpl();
         tokenForm.add("grant_type", "authorization_code");
         tokenForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
         tokenForm.add("client_secret", "secret");
         tokenForm.add("code", code);
-        
+
         response = requestToken(tokenForm);
         String entity = response.getEntity(String.class);
         JsonNode node = JsonUtils.readTree(entity);
@@ -165,8 +192,115 @@
         assertEquals(TokenType.BEARER.toString(),
                 node.at("/token_type").asText());
         assertNotNull(node.at("/expires_in").asText());
+
+        testRequestTokenWithUsedAuthorization(tokenForm);
     }
-    
+
+    private void testRequestTokenWithUsedAuthorization (
+            MultivaluedMap<String, String> form) throws KustvaktException {
+        ClientResponse response = requestToken(form);
+        String entity = response.getEntity(String.class);
+
+        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(OAuthError.TokenResponse.INVALID_GRANT,
+                node.at("/error").asText());
+        assertEquals("Invalid authorization",
+                node.at("/error_description").asText());
+    }
+
+    @Test
+    public void testRequestTokenInvalidAuthorizationCode ()
+            throws KustvaktException {
+        MultivaluedMap<String, String> tokenForm = new MultivaluedMapImpl();
+        tokenForm.add("grant_type", "authorization_code");
+        tokenForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        tokenForm.add("client_secret", "secret");
+        tokenForm.add("code", "blahblah");
+
+        ClientResponse response = requestToken(tokenForm);
+        String entity = response.getEntity(String.class);
+
+        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(OAuthError.TokenResponse.INVALID_REQUEST,
+                node.at("/error").asText());
+    }
+
+    @Test
+    public void testRequestTokenAuthorizationReplyAttack ()
+            throws KustvaktException {
+        String uri = "https://korap.ids-mannheim.de/confidential/redirect";
+        MultivaluedMap<String, String> authForm = new MultivaluedMapImpl();
+        authForm.add("response_type", "code");
+        authForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        authForm.add("username", "dory");
+        authForm.add("password", "password");
+        authForm.add("scope", "read_username");
+        authForm.add("redirect_uri", uri);
+
+        ClientResponse response =
+                requestAuthorizationConfidentialClient(authForm);
+        URI redirectUri = response.getLocation();
+        MultivaluedMap<String, String> params =
+                UriComponent.decodeQuery(redirectUri, true);
+        String code = params.get("code").get(0);
+
+        testRequestTokenAuthorizationInvalidClient(code);
+        testRequestTokenAuthorizationInvalidRedirectUri(code);
+        testRequestTokenAuthorizationRevoked(code, uri);
+    }
+
+    private void testRequestTokenAuthorizationInvalidClient (String code)
+            throws KustvaktException {
+        MultivaluedMap<String, String> tokenForm = new MultivaluedMapImpl();
+        tokenForm.add("grant_type", "authorization_code");
+        tokenForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        tokenForm.add("client_secret", "blah");
+        tokenForm.add("code", code);
+
+        ClientResponse response = requestToken(tokenForm);
+        String entity = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(OAuth2Error.INVALID_CLIENT, node.at("/error").asText());
+    }
+
+    private void testRequestTokenAuthorizationInvalidRedirectUri (String code)
+            throws KustvaktException {
+        MultivaluedMap<String, String> tokenForm = new MultivaluedMapImpl();
+        tokenForm.add("grant_type", "authorization_code");
+        tokenForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        tokenForm.add("client_secret", "secret");
+        tokenForm.add("code", code);
+        tokenForm.add("redirect_uri", "https://blahblah.com");
+
+        ClientResponse response = requestToken(tokenForm);
+        String entity = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(OAuth2Error.INVALID_GRANT, node.at("/error").asText());
+    }
+
+    private void testRequestTokenAuthorizationRevoked (String code, String uri)
+            throws KustvaktException {
+        MultivaluedMap<String, String> tokenForm = new MultivaluedMapImpl();
+        tokenForm.add("grant_type", "authorization_code");
+        tokenForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        tokenForm.add("client_secret", "secret");
+        tokenForm.add("code", code);
+        tokenForm.add("redirect_uri", uri);
+
+        ClientResponse response = requestToken(tokenForm);
+        String entity = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(OAuthError.TokenResponse.INVALID_GRANT,
+                node.at("/error").asText());
+        assertEquals("Invalid authorization",
+                node.at("/error_description").asText());
+    }
+
+
     @Test
     public void testRequestTokenPasswordGrantConfidential ()
             throws KustvaktException {
@@ -228,7 +362,7 @@
         assertEquals("Missing parameters: client_id",
                 node.at("/error_description").asText());
     }
-    
+
     @Test
     public void testRequestTokenPasswordGrantPublic ()
             throws KustvaktException {
@@ -293,6 +427,30 @@
     }
 
     @Test
+    public void testRequestTokenClientCredentialsGrantReducedScope ()
+            throws KustvaktException {
+
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("grant_type", "client_credentials");
+        form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        form.add("client_secret", "secret");
+        form.add("scope", "read_username read_client_info");
+
+        ClientResponse response = requestToken(form);
+        String entity = response.getEntity(String.class);
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+        JsonNode node = JsonUtils.readTree(entity);
+        // length?
+        assertNotNull(node.at("/access_token").asText());
+        assertNotNull(node.at("/refresh_token").asText());
+        assertEquals(TokenType.BEARER.toString(),
+                node.at("/token_type").asText());
+        assertNotNull(node.at("/expires_in").asText());
+        assertEquals("read_client_info", node.at("/scope").asText());
+    }
+
+    @Test
     public void testRequestTokenMissingGrantType () throws KustvaktException {
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         ClientResponse response = requestToken(form);
diff --git a/full/src/test/resources/kustvakt-test.conf b/full/src/test/resources/kustvakt-test.conf
index b26384b..d9eda87 100644
--- a/full/src/test/resources/kustvakt-test.conf
+++ b/full/src/test/resources/kustvakt-test.conf
@@ -48,6 +48,10 @@
 ### oauth.password.authentication values)
 oauth.password.authentication = TEST
 oauth.native.client.host=korap.ids-mannheim.de
+oauth2.max.attempts = 2
+# -- scopes separated by space
+oauth2.default.scopes = read_username read_email 
+oauth2.client.credentials.scopes = read_client_info
 
 # JWT
 security.jwt.issuer=korap.ids-mannheim.de