From 9304aef2a974de7d4621e2378be94cd7bc3131a3 Mon Sep 17 00:00:00 2001
From: Matej Hrica <mtjhrc@gmail.com>
Date: Sat, 27 May 2023 16:53:09 +0200
Subject: [PATCH] Add token-grabber - a confidential client for getting an
 access token

---
 pom.xml                                       |   1 +
 token-grabber/pom.xml                         |  77 ++++++++++++++++++
 .../pa165/oauth2/client/MainController.java   |  60 ++++++++++++++
 .../cz/muni/pa165/oauth2/client/WebApp.java   |  63 ++++++++++++++
 .../src/main/resources/application.yml        |  45 ++++++++++
 .../src/main/resources/messages.properties    |   3 +
 .../src/main/resources/static/favicon.ico     | Bin 0 -> 1150 bytes
 .../src/main/resources/static/robots.txt      |   2 +
 .../src/main/resources/static/style.css       |   4 +
 .../src/main/resources/templates/index.html   |  14 ++++
 .../src/main/resources/templates/layout.html  |  24 ++++++
 .../src/main/resources/templates/success.html |  31 +++++++
 12 files changed, 324 insertions(+)
 create mode 100644 token-grabber/pom.xml
 create mode 100644 token-grabber/src/main/java/cz/muni/pa165/oauth2/client/MainController.java
 create mode 100644 token-grabber/src/main/java/cz/muni/pa165/oauth2/client/WebApp.java
 create mode 100644 token-grabber/src/main/resources/application.yml
 create mode 100644 token-grabber/src/main/resources/messages.properties
 create mode 100644 token-grabber/src/main/resources/static/favicon.ico
 create mode 100644 token-grabber/src/main/resources/static/robots.txt
 create mode 100644 token-grabber/src/main/resources/static/style.css
 create mode 100644 token-grabber/src/main/resources/templates/index.html
 create mode 100644 token-grabber/src/main/resources/templates/layout.html
 create mode 100644 token-grabber/src/main/resources/templates/success.html

diff --git a/pom.xml b/pom.xml
index 903a024..4fb280b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -14,6 +14,7 @@
         <module>user-client</module>
         <module>report-client</module>
         <module>weather-client</module>
+        <module>token-grabber</module>
     </modules>
     <packaging>pom</packaging>
     <name>airport manager parent</name>
diff --git a/token-grabber/pom.xml b/token-grabber/pom.xml
new file mode 100644
index 0000000..5d195d9
--- /dev/null
+++ b/token-grabber/pom.xml
@@ -0,0 +1,77 @@
+<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>cz.muni.fi.pa165</groupId>
+        <artifactId>airport-manager</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>token-grabber</artifactId>
+    <packaging>jar</packaging>
+    <name>OAuth 2/OIDC Confidential Client for grabbing the token</name>
+
+    <build>
+        <defaultGoal>spring-boot:run</defaultGoal>
+        <finalName>>token-grabber</finalName>
+
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <!-- https://docs.spring.io/spring-boot/docs/current/reference/html/deployment.html#deployment.installing -->
+                    <executable>true</executable>
+                </configuration>
+            </plugin>
+            <!-- run integration tests in "mvn verify" phase -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-failsafe-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <!-- Spring MVC -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <!-- Thymeleaf for HTML pages templates -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-thymeleaf</artifactId>
+        </dependency>
+        <!-- Thymeleaf layout -->
+        <dependency>
+            <groupId>nz.net.ultraq.thymeleaf</groupId>
+            <artifactId>thymeleaf-layout-dialect</artifactId>
+        </dependency>
+        <!-- for Spring application.yml properties handling -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <!-- OAuth2/OIDC client -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-oauth2-client</artifactId>
+        </dependency>
+        <!-- web jars for Bootstrap -->
+        <dependency>
+            <groupId>org.webjars</groupId>
+            <artifactId>webjars-locator-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.webjars</groupId>
+            <artifactId>bootstrap</artifactId>
+            <version>5.2.3</version>
+        </dependency>
+
+    </dependencies>
+
+</project>
diff --git a/token-grabber/src/main/java/cz/muni/pa165/oauth2/client/MainController.java b/token-grabber/src/main/java/cz/muni/pa165/oauth2/client/MainController.java
new file mode 100644
index 0000000..105f56d
--- /dev/null
+++ b/token-grabber/src/main/java/cz/muni/pa165/oauth2/client/MainController.java
@@ -0,0 +1,60 @@
+package cz.muni.pa165.oauth2.client;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
+import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.oidc.user.OidcUser;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+
+
+/**
+ * Spring MVC Controller.
+ * Handles HTTP requests by preparing data in model and passing it to Thymeleaf HTML templates.
+ */
+@Controller
+public class MainController {
+
+    private static final Logger log = LoggerFactory.getLogger(MainController.class);
+
+    /**
+     * Home page accessible even to non-authenticated users. Displays user personal data.
+     */
+    @GetMapping("/")
+    public String index(Model model, @AuthenticationPrincipal OidcUser user) {
+         log.debug("index() user {}", user == null ? "is anonymous" : user.getSubject());
+
+        // user is logged in
+        if (user != null) {
+            return "redirect:/success";
+        }
+
+        return "index";
+    }
+
+    @GetMapping("/success")
+    public String loggedIn(Model model,  @AuthenticationPrincipal OidcUser user,
+                           @RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient oauth2Client) {
+        OAuth2AccessToken accessToken = oauth2Client.getAccessToken();
+
+        log.info("/success :");
+
+        var subject = user.getSubject();
+        model.addAttribute("subject", subject);
+        log.info("subject: " + subject);
+
+        var scopes = accessToken.getScopes();
+        model.addAttribute("scopes", scopes);
+        log.info("scopes: " + scopes);
+
+        String accessTokenValue = accessToken.getTokenValue();
+        model.addAttribute("accessToken", accessTokenValue);
+        log.info("accessToken : " + accessTokenValue);
+
+        return "success";
+    }
+}
\ No newline at end of file
diff --git a/token-grabber/src/main/java/cz/muni/pa165/oauth2/client/WebApp.java b/token-grabber/src/main/java/cz/muni/pa165/oauth2/client/WebApp.java
new file mode 100644
index 0000000..63bd80b
--- /dev/null
+++ b/token-grabber/src/main/java/cz/muni/pa165/oauth2/client/WebApp.java
@@ -0,0 +1,63 @@
+package cz.muni.pa165.oauth2.client;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.event.EventListener;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
+import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
+
+
+@SpringBootApplication
+public class WebApp {
+
+    private static final Logger log = LoggerFactory.getLogger(WebApp.class);
+
+    public static void main(String[] args) {
+        SpringApplication.run(WebApp.class, args);
+    }
+
+    /**
+     * Configuration of Spring Security. Sets up OAuth2/OIDC authentication
+     * for all URLS except a list of public ones.
+     */
+    @Bean
+    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
+        httpSecurity
+                .authorizeHttpRequests(x -> x
+                        // allow anonymous access to listed URLs
+                        .requestMatchers("/", "/error", "/robots.txt", "/style.css", "/favicon.ico", "/webjars/**").permitAll()
+                        // all other requests must be authenticated
+                        .anyRequest().authenticated()
+                )
+                .oauth2Login(x -> x
+                        .defaultSuccessUrl("/success")
+                )
+                .logout(x -> x
+                        .logoutSuccessUrl("/")
+                )
+                .csrf(c -> c
+                        //set CSRF token cookie "XSRF-TOKEN" with httpOnly=false that can be read by JavaScript
+                        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
+                        //replace the default XorCsrfTokenRequestAttributeHandler with one that can use value from the cookie
+                        .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
+                );
+        return httpSecurity.build();
+    }
+
+    /**
+     * Display a hint in the log.
+     */
+    @EventListener
+    public void onApplicationEvent(final ServletWebServerInitializedEvent event) {
+        log.info("**************************");
+        log.info("visit http://localhost:{}/", event.getWebServer().getPort());
+        log.info("**************************");
+    }
+
+}
diff --git a/token-grabber/src/main/resources/application.yml b/token-grabber/src/main/resources/application.yml
new file mode 100644
index 0000000..bafecd5
--- /dev/null
+++ b/token-grabber/src/main/resources/application.yml
@@ -0,0 +1,45 @@
+# TCP port for HTTP requests
+server:
+  port: 8081
+
+# OAuth client config
+spring:
+  security:
+    oauth2:
+      client:
+        registration:
+          muni:
+            client-id: 7e02a0a9-446a-412d-ad2b-90add47b0fdd
+            client-secret: 48a2b2e3-4b2b-471e-b7b7-b81a85b6eeef22f347f2-3fc9-4e16-8698-3e2492701a89
+            client-name: "MUNI Unified Login"
+            provider: muni
+            scope:
+              - openid
+              - test_1
+              - test_2
+              - test_3
+        provider:
+          muni:
+            # URL to which .well-know/openid-configuration will be added to download metadata
+            issuer-uri: https://oidc.muni.cz/oidc/
+
+# logging config to see interesting things happening
+logging:
+  pattern:
+    console: '%clr(%d{HH:mm:ss.SSS}){blue} %clr(%-5p) %clr(%logger){blue} %clr(:){red} %m %n'
+  level:
+    root: info
+    cz.muni: debug
+    org.springframework.web.client.RestTemplate: debug
+    org.springframework.security: debug
+    org.springframework.security.web.DefaultSecurityFilterChain: warn
+    org.springframework.security.web.context.HttpSessionSecurityContextRepository: info
+    org.springframework.security.web.FilterChainProxy: info
+    org.springframework.security.web.authentication.AnonymousAuthenticationFilter: info
+    org.springframework.security.config.annotation.authentication.configuration: info
+    org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext: warn
+    org.springframework.boot.web.embedded.tomcat: warn
+    org.apache.catalina.core: warn
+
+
+
diff --git a/token-grabber/src/main/resources/messages.properties b/token-grabber/src/main/resources/messages.properties
new file mode 100644
index 0000000..3b98c76
--- /dev/null
+++ b/token-grabber/src/main/resources/messages.properties
@@ -0,0 +1,3 @@
+index.title=Airport Manager Login
+index.body.annon=You are not logged in. Please log in using your IS MUNI account.
+index.body.authuser=You are logged in now.
\ No newline at end of file
diff --git a/token-grabber/src/main/resources/static/favicon.ico b/token-grabber/src/main/resources/static/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..43c88af640c084c0aa91fcdae47e5124bca566f2
GIT binary patch
literal 1150
zcmaiz!7GDt7{?#IN;xnoSB`R1ZVpZk{sbwm%S0>TqPe+fXeM!RS}Bo}q84&+adMF&
z)5@f<BD2h1pJ(s;+g@+8ZGHAUzwh(?{`TAZz7uKS9S8`YT@q{*=@5}_KtgeJiU_m+
zURE^U14&qf7Qat5n1C&4uQG2#e*_zHFl#*YgKz}zp!1NKc`JI%ovTOrf*r`aYt@P{
z;?0>Ject0Ud-&@Io*|R*R`lsoewFXuhjoa-4s3vPrU<pun=?In7>btWui+W);03n9
z$sZWMw=xUDZppKP_X@HuPt;9t@><7DmfnJ4cm>;9h9ta!$!pc~=)RRbCUp<iQ;;s}
zdiF!O1U<KAZ{Q5{dQ|e-Ct%N~IUYU7IoyNp`8Q|Q-{`#%f*H{J{{VWAZ7U9ma`p!;
z1IM8Ec@DznI+$vml&;4pv&uP~evO$v!%fCptKz#Y?-9+X=*MdL{WJXfo6PTw(>MD)
z_10uQLH|qr^}N;Q<oaN#+RSK~oJjgYB*j(9Dy>e{^{FMY;2L#Z;HfVRh!mBGhz-94
CZI5IC

literal 0
HcmV?d00001

diff --git a/token-grabber/src/main/resources/static/robots.txt b/token-grabber/src/main/resources/static/robots.txt
new file mode 100644
index 0000000..1f53798
--- /dev/null
+++ b/token-grabber/src/main/resources/static/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /
diff --git a/token-grabber/src/main/resources/static/style.css b/token-grabber/src/main/resources/static/style.css
new file mode 100644
index 0000000..439f08b
--- /dev/null
+++ b/token-grabber/src/main/resources/static/style.css
@@ -0,0 +1,4 @@
+h1#title a {
+    text-decoration: none;
+    color: black;
+}
diff --git a/token-grabber/src/main/resources/templates/index.html b/token-grabber/src/main/resources/templates/index.html
new file mode 100644
index 0000000..1c0324f
--- /dev/null
+++ b/token-grabber/src/main/resources/templates/index.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en" xmlns:th="http://www.thymeleaf.org/"
+      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+      layout:decorate="~{layout.html}" th:with="title=${#messages.msg('index.title')}">
+<body>
+<th:block layout:fragment="body">
+    <p th:text="#{index.body.annon}">text about anonymous user</p>
+    <p>
+    <form method="post" th:action="@{/oauth2/authorization/muni}">
+        <button class="btn btn-outline-primary" type="submit">Login MUNI</button>
+    </form>
+</th:block>
+</body>
+</html>
\ No newline at end of file
diff --git a/token-grabber/src/main/resources/templates/layout.html b/token-grabber/src/main/resources/templates/layout.html
new file mode 100644
index 0000000..a982cc3
--- /dev/null
+++ b/token-grabber/src/main/resources/templates/layout.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html lang="en" th:lang="${#locale.language}"
+      xmlns:th="http://www.thymeleaf.org/"
+      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <link rel="SHORTCUT ICON" th:href="@{/favicon.ico}">
+    <link rel="stylesheet" th:href="@{/webjars/bootstrap/css/bootstrap.min.css}"/>
+    <link rel="stylesheet" type="text/css" th:href="@{/style.css}"/>
+    <script th:src="@{/webjars/jquery/jquery.min.js}"></script>
+    <script th:src="@{/webjars/js-cookie/js.cookie.min.js}"></script>
+    <script th:src="@{/webjars/bootstrap/js/bootstrap.bundle.min.js}"></script>
+    <title th:text="${title}">Page title</title>
+</head>
+<body>
+<div class="container">
+    <h1 id="title"><a th:href="@{/}" th:text="${title}">Page title</a></h1>
+    <th:block layout:fragment="body">
+        some placeholder text in the page body
+    </th:block>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/token-grabber/src/main/resources/templates/success.html b/token-grabber/src/main/resources/templates/success.html
new file mode 100644
index 0000000..c527b1a
--- /dev/null
+++ b/token-grabber/src/main/resources/templates/success.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html lang="en" xmlns:th="http://www.thymeleaf.org/"
+      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+      layout:decorate="~{layout.html}" th:with="title=${#messages.msg('index.title')}">
+<body>
+<th:block layout:fragment="body">
+    <p>You have successfully logged in! Here is your information:
+    <p>
+    <table class="table">
+        <tbody>
+        <tr>
+            <th scope="row">subject</th>
+            <td th:text="${subject}"></td>
+        </tr>
+        <tr>
+            <th scope="row">scopes</th>
+            <td th:text="${scopes}"></td>
+        </tr>
+        <tr>
+            <th scope="row">accessToken</th>
+            <td> <textarea readonly th:text="${accessToken}"></textarea></td>
+        </tr>
+        </tbody>
+    </table>
+    <p>
+    <form method="post" th:action="@{/logout}">
+        <button class="btn btn-outline-primary" type="submit">Logout</button>
+    </form>
+</th:block>
+</body>
+</html>
\ No newline at end of file
-- 
GitLab