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