#63 ui gets 404 calling whoami in case of no sec config

This commit is contained in:
Fabio Formosa
2022-10-04 20:17:57 +02:00
parent 93152f8157
commit a313d8b19d
29 changed files with 446 additions and 364 deletions

View File

@@ -25,11 +25,6 @@ export const routes: Routes = [
component: LoginComponent,
canActivate: [GuestGuard]
},
// {
// path: 'change-password',
// component: ChangePasswordComponent,
// canActivate: [LoginGuard]
// },
{
path: '404',
component: NotFoundComponent
@@ -45,7 +40,9 @@ export const routes: Routes = [
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
imports: [RouterModule.forRoot(routes, {
initialNavigation: false
})],
exports: [RouterModule],
providers: []
})

View File

@@ -64,7 +64,7 @@ import {SimpleTriggerConfigComponent} from './components/simple-trigger-config';
import JobService from './services/job.service';
export function initUserFactory(userService: UserService) {
return () => userService.jsessionInitUser();
return () => userService.fetchLoggedUser();
}

View File

@@ -6,7 +6,7 @@
<div class="right">
<div fxFlex="1 1 auto" fxLayout="row" fxLayoutAlign="flex-end center">
<button *ngIf="!hasSignedIn()" routerLink="/login" mat-button mat-ripple>
<button *ngIf="!noAuthenticationRequired()" routerLink="/login" mat-button mat-ripple>
<span>Login</span>
</button>
<button

View File

@@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
import {
UserService,
AuthService,
NO_AUTH
// NO_AUTH
} from '../../services';
import { Router } from '@angular/router';
@@ -32,8 +32,8 @@ export class HeaderComponent implements OnInit {
return !!this.userService.currentUser;
}
noAuthenticationRequired = () => this.hasSignedIn() && this.userService.currentUser === NO_AUTH;
noAuthenticationRequired = () => !this.hasSignedIn() && this.userService.isAnAnonymousUser === true;
userName() {
const user = this.userService.currentUser;

View File

@@ -1,25 +1,27 @@
import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { UserService } from '../services';
import { Observable } from 'rxjs';
import {Injectable} from '@angular/core';
import {Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot} from '@angular/router';
import {UserService} from '../services';
import {Observable} from 'rxjs';
@Injectable()
export class AdminGuard implements CanActivate {
constructor(private router: Router, private userService: UserService) {}
constructor(private router: Router, private userService: UserService) {
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
if (this.userService.isAnAnonymousUser) {
return true;
}
if (this.userService.currentUser) {
if(this.userService.currentUser === 'NO_AUTH')
if (JSON.stringify(this.userService.currentUser.authorities).search('ROLE_ADMIN') !== -1) {
return true;
if (JSON.stringify(this.userService.currentUser.authorities).search('ROLE_ADMIN') !== -1)
return true;
else {
} else {
this.router.navigate(['/403']);
return false;
}
} else {
console.log('NOT AN ADMIN ROLE');
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});
this.router.navigate(['/login'], {queryParams: {returnUrl: state.url}});
return false;
}
}

View File

@@ -25,7 +25,7 @@ export class AuthService {
.pipe(
map(() => {
console.log('Login success');
this.userService.getMyInfo().subscribe();
this.userService.getUserInfo().subscribe();
})
);
}

View File

@@ -28,19 +28,15 @@ export function getBaseUrl() {
@Injectable()
export class ConfigService {
private _api_url = getBaseUrl() + `${CONTEXT_PATH}/api`
private _auth_url = getBaseUrl() + `${CONTEXT_PATH}/auth`
private _refresh_token_url = this._api_url + '/refresh';
private _refresh_token_url = this._auth_url + '/refresh';
private _login_url = this._api_url + '/login';
private _login_url = this._auth_url + '/login';
private _logout_url = this._api_url + '/logout';
private _logout_url = this._auth_url + '/logout';
private _whoami_url = this._api_url + '/whoami';
private _user_url = this._api_url + '/user';
private _users_url = this._user_url + '/all';
private _whoami_url = this._auth_url + '/whoami';
get refresh_token_url(): string {
return this._refresh_token_url;
@@ -50,10 +46,6 @@ export class ConfigService {
return this._whoami_url;
}
get users_url(): string {
return this._users_url;
}
get login_url(): string {
return this._login_url;
}

View File

@@ -1,51 +1,56 @@
import { Injectable } from '@angular/core';
import { ApiService } from './api.service';
import { ConfigService } from './config.service';
import {Injectable} from '@angular/core';
import {ApiService} from './api.service';
import {ConfigService} from './config.service';
import { map } from 'rxjs/operators'
export const NO_AUTH: string = 'NO_AUTH'
import {map} from 'rxjs/operators'
import {HttpErrorResponse} from '@angular/common/http';
import {Router} from '@angular/router';
@Injectable()
export class UserService {
currentUser;
isAnAnonymousUser: boolean;
currentUser: any;
constructor(
private apiService: ApiService,
private config: ConfigService
) { }
private config: ConfigService,
private router: Router
) {
}
jwtInitUser() {
refreshToken() {
const promise = this.apiService.get(this.config.refresh_token_url).toPromise()
.then(res => {
if (res.access_token !== null) {
return this.getMyInfo().toPromise()
.then(user => {
this.currentUser = user;
});
}
})
.catch(() => null);
.then(res => {
if (res.access_token !== null) {
return this.getUserInfo().toPromise()
.then(user => {
this.currentUser = user;
});
}
})
.catch(() => null);
return promise;
}
jsessionInitUser() {
return this.getMyInfo().toPromise()
.then(user => {
this.currentUser = user;
}, err => {
//not logged
console.log(`error retrieving current user due to ` + err);
});
fetchLoggedUser() {
this.getUserInfo().subscribe(user => {
this.currentUser = user;
this.router.initialNavigation();
}, err => {
console.log(`error retrieving current user due to ` + err);
const httpErrorResponse = err as HttpErrorResponse;
if (httpErrorResponse.status === 404) {
this.isAnAnonymousUser = true;
this.router.initialNavigation();
}
// TODO generic error!
});
}
getMyInfo() {
return this.apiService.get(this.config.whoami_url).pipe(map(user => this.currentUser = user));
}
getAll() {
return this.apiService.get(this.config.users_url);
getUserInfo() {
return this.apiService.get(this.config.whoami_url)
.pipe(map(user => this.currentUser = user));
}
}

View File

@@ -1,4 +1,5 @@
<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">
<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>
@@ -50,7 +51,7 @@
<dependencyManagement>
<dependencies>
<dependency>
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-common</artifactId>
<version>3.1.1-SNAPSHOT</version>
@@ -80,13 +81,13 @@
<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<id>ossrh</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
<repository>
<id>ossrh</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/
</url>
<id>ossrh</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/
</url>
</repository>
</distributionManagement>
@@ -126,9 +127,9 @@
</configuration>
<dependencies>
<dependency>
<groupId>org.apache.maven.scm</groupId>
<artifactId>maven-scm-provider-gitexe</artifactId>
<version>1.9.5</version>
<groupId>org.apache.maven.scm</groupId>
<artifactId>maven-scm-provider-gitexe</artifactId>
<version>1.9.5</version>
</dependency>
</dependencies>
</plugin>
@@ -138,26 +139,26 @@
<version>1.6.7</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<doclint>none</doclint>
</configuration>
</execution>
</executions>
</plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<doclint>none</doclint>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
@@ -166,26 +167,26 @@
<profile>
<id>release-sign-artifacts</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

View File

@@ -0,0 +1,7 @@
package it.fabioformosa.quartzmanager.common.config;
public class OpenAPIConfigConsts {
final static public String BASIC_AUTH_SEC_OAS_SCHEME = "basic-auth";
}

View File

@@ -0,0 +1,13 @@
package it.fabioformosa.quartzmanager.common.config;
public class QuartzManagerPaths {
public static final String QUARTZ_MANAGER_BASE_CONTEXT_PATH = "/quartz-manager";
public static final String WEBJAR_PATH = "/quartz-manager-ui";
public static final String QUARTZ_MANAGER_AUTH_PATH = QUARTZ_MANAGER_BASE_CONTEXT_PATH + "/auth";
public static final String QUARTZ_MANAGER_LOGIN_PATH = QUARTZ_MANAGER_AUTH_PATH + "/login";
public static final String QUARTZ_MANAGER_LOGOUT_PATH = QUARTZ_MANAGER_AUTH_PATH + "/logout";
}

View File

@@ -1,174 +1,175 @@
<?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>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>3.1.1-SNAPSHOT</version>
</parent>
<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>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>3.1.1-SNAPSHOT</version>
</parent>
<artifactId>quartz-manager-starter-api</artifactId>
<artifactId>quartz-manager-starter-api</artifactId>
<name>Quartz Manager Starter API</name>
<description>REST API layer for your scheduler and triggered jobs, to be included in your spring webapp</description>
<name>Quartz Manager Starter API</name>
<description>REST API layer for your scheduler and triggered jobs, to be included in your spring webapp</description>
<url>https://github.com/fabioformosa/quartz-manager</url>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<openapi.version>1.5.12</openapi.version>
<java.version>1.8</java.version>
</properties>
<url>https://github.com/fabioformosa/quartz-manager</url>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<springdoc-openapi.version>1.5.12</springdoc-openapi.version>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-common</artifactId>
</dependency>
<dependencies>
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-common</artifactId>
</dependency>
<!-- SPRING -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- SPRING -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.security</groupId>-->
<!-- <artifactId>spring-security-core</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- MISC -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>it.fabioformosa</groupId>
<artifactId>metamorphosis-core</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.2.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</dependency>
<!-- MISC -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>it.fabioformosa</groupId>
<artifactId>metamorphosis-core</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.2.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</dependency>
<!-- QUARTZ -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
<!-- Reactor -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-net</artifactId>
<version>2.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>io.projectreactor.spring</groupId>
<artifactId>reactor-spring-context</artifactId>
<version>2.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-net</artifactId>
<version>2.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>io.projectreactor.spring</groupId>
<artifactId>reactor-spring-context</artifactId>
<version>2.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<!-- OAS -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>${openapi.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-security</artifactId>
<version>${openapi.version}</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>2.1.11</version>
</dependency>
<!-- OAS -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>${springdoc-openapi.version}</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springdoc</groupId>-->
<!-- <artifactId>springdoc-openapi-security</artifactId>-->
<!-- <version>${openapi.version}</version>-->
<!-- </dependency>-->
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>2.1.11</version>
</dependency>
<!-- TEST -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
</dependencies>
<!-- TEST -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -11,22 +11,27 @@ import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import io.swagger.v3.oas.models.security.SecurityScheme;
import it.fabioformosa.quartzmanager.common.config.QuartzManagerPaths;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
import static it.fabioformosa.quartzmanager.common.config.OpenAPIConfigConsts.BASIC_AUTH_SEC_OAS_SCHEME;
@Configuration
public class OpenApiConfig {
static private String BASIC_AUTH_SEC_SCHEME = "basic-auth";
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(apiInfo())
.components(new Components().addSecuritySchemes(BASIC_AUTH_SEC_SCHEME, buildBasicAuthScheme()))
.path("/quartz-manager/api/login",
public OpenAPI customOpenAPI(@Autowired(required = false) SecurityDiscover securityDiscover) {
OpenAPI openAPI = new OpenAPI()
.info(apiInfo());
if(securityDiscover != null)
openAPI
.components(new Components().addSecuritySchemes(BASIC_AUTH_SEC_OAS_SCHEME, buildBasicAuthScheme()))
.path(QuartzManagerPaths.QUARTZ_MANAGER_LOGIN_PATH,
new PathItem().post(new Operation()
.operationId("login")
.tags(Arrays.asList("auth"))
@@ -39,6 +44,8 @@ public class OpenApiConfig {
.responses(new ApiResponses().addApiResponse("200", new ApiResponse().description("JWT Token to authenticate the next requests")))
.responses(new ApiResponses().addApiResponse("401", new ApiResponse().description("Unauthorized - Username or password are incorrect!")))
));
return openAPI;
}
private SecurityScheme buildBasicAuthScheme() {

View File

@@ -0,0 +1,6 @@
package it.fabioformosa.quartzmanager.configuration;
public class SecurityDiscover {
public SecurityDiscover() {
}
}

View File

@@ -0,0 +1,19 @@
package it.fabioformosa.quartzmanager.configuration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Slf4j
@ConditionalOnClass(name = {"it.fabioformosa.quartzmanager.security.WebSecurityConfigJWT"})
@Configuration
public class SecurityDiscoverConfig {
@Bean
public SecurityDiscover securityIsEnabled(){
log.info("Quartz Manager Security Layer is enabled!");
return new SecurityDiscover();
}
}

View File

@@ -1,7 +0,0 @@
package it.fabioformosa.quartzmanager.controllers;
abstract public class AbstractQuartzManagerController {
protected static final String QUARTZ_MANAGER_CONTEXT_PATH = "/quartz-manager";
}

View File

@@ -1,5 +1,11 @@
package it.fabioformosa.quartzmanager.controllers;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import it.fabioformosa.quartzmanager.services.JobService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -8,11 +14,13 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.stream.Collectors;
import static it.fabioformosa.quartzmanager.controllers.AbstractQuartzManagerController.QUARTZ_MANAGER_CONTEXT_PATH;
import static it.fabioformosa.quartzmanager.common.config.OpenAPIConfigConsts.BASIC_AUTH_SEC_OAS_SCHEME;
import static it.fabioformosa.quartzmanager.common.config.QuartzManagerPaths.QUARTZ_MANAGER_BASE_CONTEXT_PATH;
@RequestMapping(QUARTZ_MANAGER_CONTEXT_PATH + "/jobs")
@RequestMapping(QUARTZ_MANAGER_BASE_CONTEXT_PATH + "/jobs")
@SecurityRequirement(name = BASIC_AUTH_SEC_OAS_SCHEME)
@RestController
public class JobController extends AbstractQuartzManagerController {
public class JobController {
final private JobService jobService;
public JobController(JobService jobService) {
@@ -20,6 +28,12 @@ public class JobController extends AbstractQuartzManagerController {
}
@GetMapping
@Operation(summary = "Get the list of job classes eligible for Quartz-Manager")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Return a list of qualified java classes",
content = { @Content(mediaType = "application/json",
schema = @Schema(implementation = String.class)) })
})
public List<String> listJobs(){
return jobService.getJobClasses().stream().map(Class::getName).collect(Collectors.toList());
}

View File

@@ -16,7 +16,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import static it.fabioformosa.quartzmanager.controllers.AbstractQuartzManagerController.QUARTZ_MANAGER_CONTEXT_PATH;
import static it.fabioformosa.quartzmanager.common.config.OpenAPIConfigConsts.BASIC_AUTH_SEC_OAS_SCHEME;
import static it.fabioformosa.quartzmanager.common.config.QuartzManagerPaths.QUARTZ_MANAGER_BASE_CONTEXT_PATH;
/**
* This controller provides scheduler info about config and status. It provides
@@ -26,11 +27,11 @@ import static it.fabioformosa.quartzmanager.controllers.AbstractQuartzManagerCon
*/
@Slf4j
@RestController
@SecurityRequirement(name = "basic-auth")
@SecurityRequirement(name = BASIC_AUTH_SEC_OAS_SCHEME)
@RequestMapping(SchedulerController.SCHEDULER_CONTROLLER_BASE_URL)
public class SchedulerController {
static protected final String SCHEDULER_CONTROLLER_BASE_URL = QUARTZ_MANAGER_CONTEXT_PATH + "/scheduler";
static protected final String SCHEDULER_CONTROLLER_BASE_URL = QUARTZ_MANAGER_BASE_CONTEXT_PATH + "/scheduler";
final private SchedulerService schedulerService;

View File

@@ -7,8 +7,8 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import it.fabioformosa.quartzmanager.dto.SimpleTriggerCommandDTO;
import it.fabioformosa.quartzmanager.dto.SimpleTriggerInputDTO;
import it.fabioformosa.quartzmanager.dto.SimpleTriggerDTO;
import it.fabioformosa.quartzmanager.dto.SimpleTriggerInputDTO;
import it.fabioformosa.quartzmanager.dto.TriggerDTO;
import it.fabioformosa.quartzmanager.exceptions.TriggerNotFoundException;
import it.fabioformosa.quartzmanager.services.SimpleTriggerService;
@@ -19,13 +19,16 @@ import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import static it.fabioformosa.quartzmanager.common.config.OpenAPIConfigConsts.BASIC_AUTH_SEC_OAS_SCHEME;
import static it.fabioformosa.quartzmanager.common.config.QuartzManagerPaths.QUARTZ_MANAGER_BASE_CONTEXT_PATH;
@Slf4j
@RequestMapping(SimpleTriggerController.SIMPLE_TRIGGER_CONTROLLER_BASE_URL)
@SecurityRequirement(name = "basic-auth")
@SecurityRequirement(name = BASIC_AUTH_SEC_OAS_SCHEME)
@RestController
public class SimpleTriggerController extends AbstractQuartzManagerController {
public class SimpleTriggerController {
static protected final String SIMPLE_TRIGGER_CONTROLLER_BASE_URL = QUARTZ_MANAGER_CONTEXT_PATH + "/simple-triggers";
static protected final String SIMPLE_TRIGGER_CONTROLLER_BASE_URL = QUARTZ_MANAGER_BASE_CONTEXT_PATH + "/simple-triggers";
final private SimpleTriggerService simpleSchedulerService;

View File

@@ -1,7 +1,12 @@
package it.fabioformosa.quartzmanager.controllers;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import it.fabioformosa.quartzmanager.dto.TriggerDTO;
import it.fabioformosa.quartzmanager.dto.TriggerKeyDTO;
import it.fabioformosa.quartzmanager.services.TriggerService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.SchedulerException;
@@ -11,13 +16,16 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static it.fabioformosa.quartzmanager.common.config.OpenAPIConfigConsts.BASIC_AUTH_SEC_OAS_SCHEME;
import static it.fabioformosa.quartzmanager.common.config.QuartzManagerPaths.QUARTZ_MANAGER_BASE_CONTEXT_PATH;
@Slf4j
@RequestMapping(TriggerController.TRIGGER_CONTROLLER_BASE_URL)
@SecurityRequirement(name = "basic-auth")
@SecurityRequirement(name = BASIC_AUTH_SEC_OAS_SCHEME)
@RestController
public class TriggerController extends AbstractQuartzManagerController {
public class TriggerController {
static protected final String TRIGGER_CONTROLLER_BASE_URL = QUARTZ_MANAGER_CONTEXT_PATH + "/triggers";
static protected final String TRIGGER_CONTROLLER_BASE_URL = QUARTZ_MANAGER_BASE_CONTEXT_PATH + "/triggers";
final private TriggerService triggerService;
@@ -26,7 +34,13 @@ public class TriggerController extends AbstractQuartzManagerController {
}
@GetMapping
public List<TriggerDTO> listTriggers() throws SchedulerException {
@Operation(summary = "Get a list of triggers")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Got the trigger list",
content = { @Content(mediaType = "application/json",
schema = @Schema(implementation = TriggerKeyDTO.class)) })
})
public List<TriggerKeyDTO> listTriggers() throws SchedulerException {
return triggerService.fetchTriggers();
}

View File

@@ -1,27 +0,0 @@
package it.fabioformosa.quartzmanager.controllers;
import org.springframework.http.MediaType;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import static it.fabioformosa.quartzmanager.controllers.AbstractQuartzManagerController.QUARTZ_MANAGER_CONTEXT_PATH;
@RestController
@RequestMapping(value = UserController.USER_CONTROLLER_BASE_URL, produces = MediaType.APPLICATION_JSON_VALUE)
public class UserController {
static protected final String USER_CONTROLLER_BASE_URL = QUARTZ_MANAGER_CONTEXT_PATH + "/api";
@GetMapping("/whoami")
public @ResponseBody Object user() {
SecurityContext context = SecurityContextHolder.getContext();
if (context != null && context.getAuthentication() != null)
return context.getAuthentication().getPrincipal();
return "\"NO_AUTH\"";
}
}

View File

@@ -4,12 +4,12 @@ import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import static it.fabioformosa.quartzmanager.controllers.AbstractQuartzManagerController.QUARTZ_MANAGER_CONTEXT_PATH;
import static it.fabioformosa.quartzmanager.common.config.QuartzManagerPaths.QUARTZ_MANAGER_BASE_CONTEXT_PATH;
@Controller
public class WebsocketController {
@MessageMapping({ QUARTZ_MANAGER_CONTEXT_PATH + "/logs", QUARTZ_MANAGER_CONTEXT_PATH + "/progress" })
@MessageMapping({ QUARTZ_MANAGER_BASE_CONTEXT_PATH + "/logs", QUARTZ_MANAGER_BASE_CONTEXT_PATH + "/progress" })
@SendTo("/topic/logs")
public String subscribe() {
return "subscribed";

View File

@@ -1,6 +1,5 @@
package it.fabioformosa.quartzmanager.services;
import it.fabioformosa.quartzmanager.dto.TriggerDTO;
import it.fabioformosa.quartzmanager.dto.TriggerKeyDTO;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
@@ -24,9 +23,9 @@ public class TriggerService {
this.conversionService = conversionService;
}
public List<TriggerDTO> fetchTriggers() throws SchedulerException {
public List<TriggerKeyDTO> fetchTriggers() throws SchedulerException {
Set<TriggerKey> triggerKeys = scheduler.getTriggerKeys(GroupMatcher.anyTriggerGroup());
return (List<TriggerDTO>) conversionService.convert(triggerKeys,
return (List<TriggerKeyDTO>) conversionService.convert(triggerKeys,
TypeDescriptor.collection(Set.class, TypeDescriptor.valueOf(TriggerKey.class)),
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(TriggerKeyDTO.class)));
}

View File

@@ -18,9 +18,18 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<springdoc-openapi.version>1.5.12</springdoc-openapi.version>
</properties>
<dependencies>
<!-- QUARTZ-MANAGER DEPs -->
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-common</artifactId>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
@@ -30,6 +39,8 @@
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- Misc -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
@@ -50,6 +61,13 @@
<scope>provided</scope>
</dependency>
<!-- OAS -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>${springdoc-openapi.version}</version>
</dependency>
<!-- TEST -->
<dependency>
<groupId>org.junit.platform</groupId>

View File

@@ -1,5 +1,11 @@
package it.fabioformosa.quartzmanager.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import it.fabioformosa.quartzmanager.common.config.QuartzManagerPaths;
import it.fabioformosa.quartzmanager.security.helpers.LoginConfigurer;
import it.fabioformosa.quartzmanager.security.helpers.impl.*;
import it.fabioformosa.quartzmanager.security.properties.InMemoryAccountProperties;
import it.fabioformosa.quartzmanager.security.properties.JwtSecurityProperties;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -26,21 +32,8 @@ import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import com.fasterxml.jackson.databind.ObjectMapper;
import it.fabioformosa.quartzmanager.security.properties.InMemoryAccountProperties;
import it.fabioformosa.quartzmanager.security.properties.JwtSecurityProperties;
import it.fabioformosa.quartzmanager.security.helpers.LoginConfigurer;
import it.fabioformosa.quartzmanager.security.helpers.impl.AuthenticationFailureHandler;
import it.fabioformosa.quartzmanager.security.helpers.impl.AuthenticationSuccessHandler;
import it.fabioformosa.quartzmanager.security.helpers.impl.FormLoginConfig;
import it.fabioformosa.quartzmanager.security.helpers.impl.JwtAuthenticationSuccessHandler;
import it.fabioformosa.quartzmanager.security.helpers.impl.JwtAuthenticationSuccessHandlerImpl;
import it.fabioformosa.quartzmanager.security.helpers.impl.JwtTokenAuthenticationFilter;
import it.fabioformosa.quartzmanager.security.helpers.impl.JwtTokenHelper;
import it.fabioformosa.quartzmanager.security.helpers.impl.JwtUsernamePasswordFiterLoginConfig;
import it.fabioformosa.quartzmanager.security.helpers.impl.LogoutSuccess;
import it.fabioformosa.quartzmanager.security.helpers.impl.QuartzManagerHttpSecurity;
import static it.fabioformosa.quartzmanager.common.config.QuartzManagerPaths.QUARTZ_MANAGER_LOGIN_PATH;
import static it.fabioformosa.quartzmanager.common.config.QuartzManagerPaths.QUARTZ_MANAGER_LOGOUT_PATH;
/**
* @author Fabio.Formosa
@@ -53,12 +46,6 @@ public class WebSecurityConfigJWT extends WebSecurityConfigurerAdapter {
private static final String[] PATTERNS_SWAGGER_UI = {"/swagger-ui.html", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**"};
public static final String QUARTZ_MANAGER_CONTEXT_PATH = "/quartz-manager";
protected static final String LOGIN_PATH = QUARTZ_MANAGER_CONTEXT_PATH + "/api/login";
private static final String LOGOUT_PATH = QUARTZ_MANAGER_CONTEXT_PATH + "/api/logout";
private static final String WEBJAR_PATH = "/quartz-manager-ui";
@Value("${server.servlet.context-path:/}")
private String contextPath;
@@ -97,7 +84,7 @@ public class WebSecurityConfigJWT extends WebSecurityConfigurerAdapter {
.authorizeRequests().anyRequest().authenticated();
QuartzManagerHttpSecurity.from(http).withLoginConfigurer(loginConfigurer(), logoutConfigurer()) //
.login(LOGIN_PATH, authenticationManager()).logout(LOGOUT_PATH);
.login(QUARTZ_MANAGER_LOGIN_PATH, authenticationManager()).logout(QUARTZ_MANAGER_LOGOUT_PATH);
// temporary disabled csfr
// http.csrf().ignoringAntMatchers("/api/login", "/api/signup") //
@@ -108,7 +95,7 @@ public class WebSecurityConfigJWT extends WebSecurityConfigurerAdapter {
public void configure(WebSecurity web) {
web.ignoring()//
.antMatchers(HttpMethod.GET, PATTERNS_SWAGGER_UI) //
.antMatchers(HttpMethod.GET, WEBJAR_PATH + "/css/**", WEBJAR_PATH + "/js/**", WEBJAR_PATH + "/img/**", WEBJAR_PATH + "/lib/**", WEBJAR_PATH + "/assets/**");
.antMatchers(HttpMethod.GET, QuartzManagerPaths.WEBJAR_PATH + "/css/**", QuartzManagerPaths.WEBJAR_PATH + "/js/**", QuartzManagerPaths.WEBJAR_PATH + "/img/**", QuartzManagerPaths.WEBJAR_PATH + "/lib/**", QuartzManagerPaths.WEBJAR_PATH + "/assets/**");
}
private void configureInMemoryAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {

View File

@@ -0,0 +1,32 @@
package it.fabioformosa.quartzmanager.security.controllers;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import it.fabioformosa.quartzmanager.common.config.OpenAPIConfigConsts;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static it.fabioformosa.quartzmanager.common.config.QuartzManagerPaths.QUARTZ_MANAGER_AUTH_PATH;
@RestController
@Hidden
@SecurityRequirement(name = OpenAPIConfigConsts.BASIC_AUTH_SEC_OAS_SCHEME)
@RequestMapping(value = QUARTZ_MANAGER_AUTH_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
public class UserController {
@GetMapping("/whoami")
public ResponseEntity<Object> getLoggedUser() {
SecurityContext context = SecurityContextHolder.getContext();
if (context != null && context.getAuthentication() != null)
return new ResponseEntity<>(context.getAuthentication().getPrincipal(), HttpStatus.OK);
return new ResponseEntity<>(null, HttpStatus.NOT_FOUND);
}
}

View File

@@ -45,10 +45,6 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>

View File

@@ -1,5 +1,6 @@
package it.fabioformosa.quartzmanager.controllers;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
@@ -10,15 +11,17 @@ import org.springframework.web.bind.annotation.RestController;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Hidden
@RestController
@RequestMapping
public class QuartzManagerController {
@ResponseStatus(code = HttpStatus.OK)
@GetMapping("/")
@Operation(description = "Healthy Check")
public void healthyCheck() {
log.debug("Healthy check called");
@Operation(description = "Health Check")
public String healthCheck() {
log.trace("Health check called");
return "OK";
}

View File

@@ -6,7 +6,6 @@ import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -21,7 +20,7 @@ public class SessionController {
private final Logger log = LoggerFactory.getLogger(SessionController.class);
@GetMapping("/invalidate")
@PreAuthorize("hasAuthority('ADMIN')")
//@PreAuthorize("hasAuthority('ADMIN')") TODO
@ResponseStatus(HttpStatus.NO_CONTENT)
@Operation(hidden = true)
public void invalidateSession(HttpSession session) {
@@ -30,7 +29,7 @@ public class SessionController {
}
@GetMapping("/refresh")
@PreAuthorize("hasAuthority('ADMIN')")
// @PreAuthorize("hasAuthority('ADMIN')") TODO
@Operation(hidden = true)
public HttpEntity<Void> refreshSession(HttpSession session) {
return new ResponseEntity<>(HttpStatus.OK);