securing with nextcloud oidc

This commit is contained in:
Jottyfan
2023-12-26 18:28:37 +01:00
parent 6fdd4a57e0
commit 47bcd311ea
15 changed files with 86 additions and 43 deletions

View File

@ -1,14 +1,14 @@
plugins { plugins {
id 'java' id 'java'
id 'org.springframework.boot' version '3.1.3' id 'org.springframework.boot' version '3.2.0'
id "io.spring.dependency-management" version "1.1.2" id "io.spring.dependency-management" version "1.1.4"
id 'war' id 'war'
id 'eclipse' id 'eclipse'
id 'application' id 'application'
} }
group = 'de.jottyfan.bico' group = 'de.jottyfan.bico'
version = '0.0.5' version = '0.0.6'
description = """BibleClassOrganizer""" description = """BibleClassOrganizer"""
@ -44,6 +44,7 @@ dependencies {
implementation 'de.jottyfan:bicolib:4' implementation 'de.jottyfan:bicolib:4'
implementation 'org.springframework.boot:spring-boot-starter-jooq' implementation 'org.springframework.boot:spring-boot-starter-jooq'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-security'

View File

@ -4,7 +4,6 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
/** /**
* *
@ -12,8 +11,8 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
* *
*/ */
@SpringBootApplication @SpringBootApplication
@EnableWebSecurity
public class Main extends SpringBootServletInitializer { public class Main extends SpringBootServletInitializer {
@Override @Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Main.class); return application.sources(Main.class);

View File

@ -1,15 +1,9 @@
package de.jottyfan.bico.config; package de.jottyfan.bico.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
/** /**
* *
@ -17,18 +11,15 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager;
* *
*/ */
@Configuration @Configuration
@EnableWebSecurity
public class SecurityConfig { public class SecurityConfig {
@Bean @Bean
@ConditionalOnMissingBean(UserDetailsService.class) SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
InMemoryUserDetailsManager imudm() { http.authorizeHttpRequests(
return new InMemoryUserDetailsManager(User.withUsername("user").password("{noop}password").roles("USER").build()); // @formatter:off
} r -> r.requestMatchers("/", "/error", "/css/**", "/js/**", "/webjars/**", "/template").permitAll()
.requestMatchers("/**").authenticated())
@Bean .oauth2Login(l -> l.authorizationEndpoint(e -> e.baseUri("/oauth2/authorize-client")));
@ConditionalOnMissingBean(AuthenticationEventPublisher.class) // @formatter:on
DefaultAuthenticationEventPublisher daep(ApplicationEventPublisher delegate) { return http.build();
return new DefaultAuthenticationEventPublisher(delegate);
} }
} }

View File

@ -1,7 +1,14 @@
package de.jottyfan.bico.modules; package de.jottyfan.bico.modules;
import java.security.Principal;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ui.Model; import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.web.bind.annotation.ModelAttribute;
import de.jottyfan.bico.modules.profile.ProfileService; import de.jottyfan.bico.modules.profile.ProfileService;
@ -15,15 +22,31 @@ public abstract class CommonController {
@Autowired @Autowired
private ProfileService profileService; private ProfileService profileService;
@Value("${spring.security.oauth2.client.provider.nextcloud.issuer-uri}")
private String nextcloudUrl;
@ModelAttribute("hasBUrole")
public Boolean hasBURole(Principal principal) {
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) principal;
OAuth2User user = token.getPrincipal();
@SuppressWarnings("unchecked")
List<String> roles = (List<String>) user.getAttributes().get("roles");
return roles.contains("Bibelunterricht");
}
/** /**
* get the theme for the current session * get the theme for the current session
* *
* @return the theme; light or dark at the moment * @return the theme; light or dark at the moment
*/ */
public Model useThemedModel(Model model) { @ModelAttribute("theme")
// TODO: add profile's user name public String getTheme() {
String username = "jotty"; String username = SecurityContextHolder.getContext().getAuthentication().getName();
model.addAttribute("theme", profileService.getTheme(username)); return profileService.getTheme(username);
return model; }
@ModelAttribute("nextcloudUrl")
public String getNextcloudUrl() {
return nextcloudUrl;
} }
} }

View File

@ -1,7 +1,6 @@
package de.jottyfan.bico.modules.index; package de.jottyfan.bico.modules.index;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import de.jottyfan.bico.modules.CommonController; import de.jottyfan.bico.modules.CommonController;
@ -14,8 +13,7 @@ import de.jottyfan.bico.modules.CommonController;
@Controller @Controller
public class IndexController extends CommonController { public class IndexController extends CommonController {
@GetMapping("/") @GetMapping("/")
public String getIndex(Model model) { public String getIndex() {
useThemedModel(model);
return "redirect:/sheet"; return "redirect:/sheet";
} }
} }

View File

@ -20,7 +20,7 @@ public class SheetController extends CommonController {
@GetMapping("/sheet") @GetMapping("/sheet")
public String getSheet(Model model) { public String getSheet(Model model) {
useThemedModel(model).addAttribute("list", service.getList()); model.addAttribute("list", service.getList());
return "/sheet"; return "/sheet";
} }
} }

View File

@ -0,0 +1,9 @@
{
"properties": [
{
"name": "nextcloud.url",
"type": "java.lang.String",
"description": "the URL to nextcloud for logout operations"
}
]
}

View File

@ -10,4 +10,11 @@ spring.datasource.password = ${db.password}
server.servlet.context-path = ${my.context-path:/BiCO} server.servlet.context-path = ${my.context-path:/BiCO}
# for development only # for development only
server.port = 8081 server.port = ${server.port}
# nextcloud open ID connection
spring.security.oauth2.client.provider.nextcloud.issuer-uri = ${nextcloud.issuer-uri}
spring.security.oauth2.client.registration.nextcloud.client-id = ${nextcloud.client-id}
spring.security.oauth2.client.registration.nextcloud.client-secret = ${nextcloud.client-secret}
spring.security.oauth2.client.registration.nextcloud.authorization-grant-type = authorization_code
spring.security.oauth2.client.registration.nextcloud.redirect-uri = ${nextcloud.redirect-uri}

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" layout:decorate="~{template}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<body>
<th:block layout:fragment="content">
<div class="borderdist">
<div class="alert alert-danger">Es ist ein Fehler aufgetreten. Wenden Sie sich bitte an Ihren Entwickler.</div>
</div>
</th:block>
</body>
</html>

View File

@ -3,7 +3,7 @@
<body> <body>
<th:block layout:fragment="content"> <th:block layout:fragment="content">
<div class="borderdist"> <div class="borderdist">
<div class="container"> <div class="container" sec:authorize="hasRole('Bibelunterricht')">
<div class="row g-2"> <div class="row g-2">
<div class="col-sm-12"> <div class="col-sm-12">
<h2>Dozent-Reservierung</h2> <h2>Dozent-Reservierung</h2>

View File

@ -3,7 +3,7 @@
<body> <body>
<th:block layout:fragment="content"> <th:block layout:fragment="content">
<div class="borderdist"> <div class="borderdist">
<table id="table" class="table table-striped"> <table id="table" class="table table-striped" sec:authorize="hasRole('Bibelunterricht')">
<thead> <thead>
<tr> <tr>
<th>Tag</th> <th>Tag</th>

View File

@ -2,7 +2,7 @@
<html xmlns:th="http://www.thymeleaf.org" layout:decorate="~{template}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"> <html xmlns:th="http://www.thymeleaf.org" layout:decorate="~{template}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<body> <body>
<th:block layout:fragment="content"> <th:block layout:fragment="content">
<div class="borderdist"> <div class="borderdist" sec:authorize="hasRole('Bibelunterricht')">
<div class="alert alert-danger" th:if="${bean}"> <div class="alert alert-danger" th:if="${bean}">
Wollen Sie den Slot <span th:text="${#temporals.format(bean.slotDay, 'dd.MM.yyyy')}"></span> wirklich löschen?<br /> Wollen Sie den Slot <span th:text="${#temporals.format(bean.slotDay, 'dd.MM.yyyy')}"></span> wirklich löschen?<br />
<a th:href="@{/slot/{id}/destroy(id=${bean.pkSlot})}" class="btn btn-outline-danger" th:if="${bean.pkSlot}">Ja, definitiv</a> <a th:href="@{/slot/{id}/destroy(id=${bean.pkSlot})}" class="btn btn-outline-danger" th:if="${bean.pkSlot}">Ja, definitiv</a>

View File

@ -3,7 +3,7 @@
<body> <body>
<th:block layout:fragment="content"> <th:block layout:fragment="content">
<div class="borderdist"> <div class="borderdist">
<div class="container"> <div class="container" sec:authorize="hasRole('Bibelunterricht')">
<div class="row g-2"> <div class="row g-2">
<div class="col-sm-12"> <div class="col-sm-12">
<h2>Terminfestlegung</h2> <h2>Terminfestlegung</h2>

View File

@ -21,7 +21,7 @@
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarSupportedContent" style="margin-right: 20px"> <div class="collapse navbar-collapse" id="navbarSupportedContent" style="margin-right: 20px">
<ul class="navbar-nav mb-2 mb-lg-0"> <ul class="navbar-nav mb-2 mb-lg-0" th:if="${hasBUrole}">
<li class="nav-item"><a class="btn btn-outline-secondary" th:href="@{/}" style="margin-left: 12px">Einteilung</a></li> <li class="nav-item"><a class="btn btn-outline-secondary" th:href="@{/}" style="margin-left: 12px">Einteilung</a></li>
<li class="nav-item"><a class="btn btn-outline-secondary" th:href="@{/subject/list}" style="margin-left: 12px">Themen</a></li> <li class="nav-item"><a class="btn btn-outline-secondary" th:href="@{/subject/list}" style="margin-left: 12px">Themen</a></li>
</ul> </ul>
@ -34,11 +34,16 @@
<a href="#" class="btn btn-outline-secondary" onclick="toggleDarkMode()"><i class="bi bi-moon"></i></a> <a href="#" class="btn btn-outline-secondary" onclick="toggleDarkMode()"><i class="bi bi-moon"></i></a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a th:href="@{/logout}" class="btn btn-outline-secondary">abmelden</a> <a th:href="@{${nextcloudUrl}}" class="btn btn-outline-secondary">&#8594; nextcloud</a>
</li> </li>
</ul> </ul>
</div> </div>
</nav> </nav>
<div layout:fragment="content">content</div> <div layout:fragment="content" th:if="${hasBUrole}">content</div>
<div th:unless="${hasBUrole}">
<div class="borderdist">
<div class="alert alert-danger">Leider fehlen Ihnen die Berechtigungen, um diese Anwendung nutzen zu können.</div>
</div>
</div>
</body> </body>
</html> </html>

View File

@ -3,7 +3,7 @@
<body> <body>
<th:block layout:fragment="content"> <th:block layout:fragment="content">
<div class="borderdist"> <div class="borderdist">
<div class="container"> <div class="container" sec:authorize="hasRole('Bibelunterricht')">
<div class="row g-2"> <div class="row g-2">
<h1> <h1>
Themen für den <span th:text="${#temporals.format(day, 'dd.MM.yyyy')}"></span> Themen für den <span th:text="${#temporals.format(day, 'dd.MM.yyyy')}"></span>