Implemented a prototype KorAP OAuth2 web client

Change-Id: I2e1ab809e4792c9d610c07b02ed75430e6299741
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0884b12
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+/application.properties
+target
+.classpath
+.project
+.settings
+
+HELP.md
+mvnw
+mvnw.cmd
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..8db1a83
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.springframework.boot</groupId>
+		<artifactId>spring-boot-starter-parent</artifactId>
+		<version>2.7.8</version>
+		<relativePath /> <!-- lookup parent from repository -->
+	</parent>
+	<groupId>de.ids_mannheim.korap.client</groupId>
+	<artifactId>KorAP-OAuth2-Client</artifactId>
+	<version>0.0.1-SNAPSHOT</version>
+	<name>KorAP-OAuth2-Client</name>
+	<description>KorAP OAuth2 Client</description>
+	<packaging>jar</packaging>
+	<properties>
+		<java.version>8</java.version>
+	</properties>
+	<dependencies>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-oauth2-client</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-web</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-test</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-tomcat</artifactId>
+			<scope>provided</scope>
+		</dependency>
+
+		<dependency>
+			<groupId>org.mock-server</groupId>
+			<artifactId>mockserver-netty</artifactId>
+			<version>5.13.2</version>
+			<scope>test</scope>
+		</dependency>
+
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<version>4.13.2</version>
+			<scope>test</scope>
+		</dependency>
+
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.springframework.boot</groupId>
+				<artifactId>spring-boot-maven-plugin</artifactId>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-surefire-plugin</artifactId>
+				<version>2.22.2</version>
+				<configuration>
+					<argLine>-Dmockserver.logLevel=OFF
+						-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager</argLine>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+
+</project>
diff --git a/src/main/java/de/ids_mannheim/korap/client/OAuth2ClientApplication.java b/src/main/java/de/ids_mannheim/korap/client/OAuth2ClientApplication.java
new file mode 100644
index 0000000..1610337
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/client/OAuth2ClientApplication.java
@@ -0,0 +1,13 @@
+package de.ids_mannheim.korap.client;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class OAuth2ClientApplication{
+
+	public static void main(String[] args) {
+		SpringApplication.run(OAuth2ClientApplication.class, args);
+	}
+	
+}
diff --git a/src/main/java/de/ids_mannheim/korap/client/OAuth2Controller.java b/src/main/java/de/ids_mannheim/korap/client/OAuth2Controller.java
new file mode 100644
index 0000000..0a841c7
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/client/OAuth2Controller.java
@@ -0,0 +1,28 @@
+package de.ids_mannheim.korap.client;
+
+import java.io.UnsupportedEncodingException;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.view.RedirectView;
+
+@RestController
+public class OAuth2Controller {
+
+    @Autowired
+    private OAuth2Service oAuth2Service;
+    
+    @GetMapping("/authorize")
+    public RedirectView redirect () throws UnsupportedEncodingException {
+        return oAuth2Service.createRedirect();
+    }
+
+
+    @GetMapping("/redirect")
+    public String handleAuthorizationCode (@RequestParam String code,
+            @RequestParam String state) {
+        return oAuth2Service.requestToken(code,state);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/de/ids_mannheim/korap/client/OAuth2Service.java b/src/main/java/de/ids_mannheim/korap/client/OAuth2Service.java
new file mode 100644
index 0000000..0063540
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/client/OAuth2Service.java
@@ -0,0 +1,61 @@
+package de.ids_mannheim.korap.client;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.servlet.view.RedirectView;
+
+@Service
+public class OAuth2Service {
+
+    @Value("${client.id}")
+    private String clientId;
+    @Value("${client.secret}")
+    private String clientSecret;
+
+    @Value("${korap.authorize.url}")
+    private String korapAuthorizeUrl;
+    @Value("${korap.token.url}")
+    private String korapTokenUrl;
+
+    @Value("${client.redirect.uri}")
+    private String redirectUri;
+    
+    public RedirectView createRedirect () throws UnsupportedEncodingException {
+        String encodedURL = URLEncoder.encode(redirectUri, "UTF-8");
+        String locationUri = korapAuthorizeUrl + "/authorize?client_id="
+                + clientId + "&redirect_uri=" + encodedURL
+                + "&response_type=code&state=ZMwDGTZ2RY";
+
+        RedirectView redirectView = new RedirectView();
+        redirectView.setUrl(locationUri);
+        return redirectView;
+    }
+
+    public String requestToken (String code, String state) {
+        List<MediaType> list = new ArrayList<MediaType>();
+        list.add(MediaType.APPLICATION_JSON);
+        
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        headers.setAccept(list);
+
+        String tokenRequestParams = "client_id=" + clientId + "&client_secret="
+                + clientSecret + "&grant_type=authorization_code&redirect_uri="
+                + redirectUri + "&code=" + code;
+        
+        HttpEntity<String> request = new HttpEntity<String>(tokenRequestParams, headers);
+        
+        RestTemplate restTemplate = new RestTemplate();
+        String result = restTemplate.postForObject(korapTokenUrl, request, String.class);
+        return result;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/client/WebSecurityConfig.java b/src/main/java/de/ids_mannheim/korap/client/WebSecurityConfig.java
new file mode 100644
index 0000000..9f23c96
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/client/WebSecurityConfig.java
@@ -0,0 +1,14 @@
+package de.ids_mannheim.korap.client;
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+
+@EnableWebSecurity
+public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
+
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        http.csrf().disable();
+    }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 0000000..0c2a02e
--- /dev/null
+++ b/src/main/resources/application.properties
@@ -0,0 +1,12 @@
+client.id=client_id
+client.secret=client_secret
+client.redirect.uri=http://localhost:7071/redirect
+
+server.port=7071
+
+korap.authorize.url=http://localhost:1080/oauth
+#korap.authorize.url=https://korap.ids-mannheim.de/instance/test/settings/oauth
+korap.token.url=http://localhost:1080/token
+#korap.token.url=https://korap.ids-mannheim.de/instance/test/api/v1.0/oauth2/token
+
+oauth.scope=search
\ No newline at end of file
diff --git a/src/test/java/de/ids_mannheim/korap/client/OAuth2ClientApplicationTests.java b/src/test/java/de/ids_mannheim/korap/client/OAuth2ClientApplicationTests.java
new file mode 100644
index 0000000..1f57087
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/client/OAuth2ClientApplicationTests.java
@@ -0,0 +1,114 @@
+package de.ids_mannheim.korap.client;
+
+import static org.mockserver.integration.ClientAndServer.startClientAndServer;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockserver.client.MockServerClient;
+import org.mockserver.integration.ClientAndServer;
+import org.mockserver.model.Header;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+class OAuth2ClientApplicationTests {
+
+    @Value("${client.id}")
+    private String clientId;
+    @Value("${client.secret}")
+    private String clientSecret;
+    @Value("${client.redirect.uri}")
+    private String redirectUri;
+
+    @Value("${korap.authorize.url}")
+    private String korapAuthorizeUrl;
+    @Value("${korap.token.url}")
+    private String korapTokenUrl;
+
+
+
+    @Autowired
+    private MockMvc mockMvc;
+
+    private static ClientAndServer mockServer;
+    private static MockServerClient mockClient;
+
+
+    @BeforeAll
+    public static void startMockServer () throws UnsupportedEncodingException {
+        mockServer = startClientAndServer(1080);
+        mockClient = new MockServerClient("localhost", mockServer.getPort());
+    }
+
+
+    @AfterAll
+    public static void stopMockServer () {
+        mockServer.stop();
+    }
+
+    @Test
+    public void testRedirect () throws Exception {
+        String encodedURL = URLEncoder.encode(redirectUri, "UTF-8");
+        String locationUri = korapAuthorizeUrl + "/authorize?client_id="
+                + clientId + "&redirect_uri=" + encodedURL
+                + "&response_type=code&state=ZMwDGTZ2RY";
+
+        mockMvc.perform(get("/authorize"))
+                .andExpect(status().is3xxRedirection())
+                .andExpect(header().string("location", locationUri));
+    }
+
+
+    @Test
+    public void testAuthorization () throws Exception {
+        String authorizationCode = "a8uZoar3t5zpif";
+        String state = "9GKqaj4fX5cl";
+        String oauth2Response = "{\"access_token\": \"4dcf8784ccfd26fac9bdb82778fe60e2\","
+                + "\"refresh_token\" : \"hlWci75xb8atDiq3924NUSvOdtAh7Nlf9z\","
+                + "\"token_type\": \"Bearer\","
+                + "\"expires_in\": 259200,"
+                + "\"state\":\""+state+"\"}";
+
+        String tokenRequestParams = "client_id=" + clientId + "&client_secret="
+                + clientSecret + "&grant_type=authorization_code&redirect_uri="
+                + redirectUri + "&code=" + authorizationCode;
+
+        // Simplified authorization server handling token requests
+        mockClient.reset()
+                .when(request()
+                        .withMethod("POST")
+                        .withPath("/token")
+                        .withHeader(HttpHeaders.CONTENT_TYPE,
+                                MediaType.APPLICATION_FORM_URLENCODED_VALUE)
+                        .withBody(tokenRequestParams))
+                .respond(response()
+                        .withHeader(new Header("Content-Type",
+                                "application/json; charset=utf-8"))
+                        .withBody(oauth2Response).withStatusCode(200));
+
+        
+
+        // OAuth2 client receiving an authorization code and sending a token request        
+        mockMvc.perform(get("/redirect")
+                .param("code", authorizationCode)
+                .param("state", state))
+                .andExpect(status().isOk())
+                .andExpect(content().json(oauth2Response));
+    }
+}