Compare commits

...

25 Commits

Author SHA1 Message Date
Fabio Formosa
e6cf2e9390 bump version to 4.0.5 2022-11-20 13:43:07 +01:00
Fabio Formosa
4bcea96789 Merge remote-tracking branch 'origin/master' into develop 2022-11-20 13:42:52 +01:00
Fabio Formosa
866062bdcb #62 added a missing test for the MisfireTestJob 2022-11-20 13:36:25 +01:00
Fabio Formosa
53a54ddbda #62 added a missing test to the UserController 2022-11-20 13:16:42 +01:00
Fabio Formosa
8bdb85b878 Update README.md 2022-11-20 12:50:26 +01:00
Fabio Formosa
9a26be41a8 #78 excluded lombok annotations from the coverage 2022-11-19 18:59:24 +01:00
Fabio Formosa
5cf39b3861 #78 fixed sonar errors 2022-11-19 18:53:13 +01:00
Fabio Formosa
99d87636d2 #78 removed an unused CORS config 2022-11-19 18:45:47 +01:00
Fabio Formosa
bccd50ac4a #78 put a secureRandom value as default value for the JWT token 2022-11-19 18:28:39 +01:00
Fabio Formosa
3bb30accfd #78 fixed some sonar warnings 2022-11-19 16:34:36 +01:00
Fabio Formosa
5d2b71652c #78 fixed 2 sonar warnings 2022-11-19 16:25:53 +01:00
Fabio Formosa
b70af4dafe Merge remote-tracking branch 'origin/master' into develop 2022-11-19 16:21:07 +01:00
Fabio Formosa
a95cf20c6b bump version to 4.0.5-SNAPSHOT 2022-11-19 13:54:25 +01:00
Fabio Formosa
b58a5dbe9f Update README.md 2022-11-19 13:31:52 +01:00
Fabio Formosa
f7222d65ae moved the nexus plugin under the maven profile 2022-11-19 13:29:14 +01:00
Fabio Formosa
ff43103f37 Merge pull request #89 from fabioformosa/develop
v4.0.4
2022-11-19 13:01:44 +01:00
Fabio Formosa
93ab9c55bc bump version to 4.0.4 2022-11-19 13:00:39 +01:00
Fabio Formosa
28715cdf62 enhanced the info message within the UnsupportedMultipleJobsDialog 2022-11-19 12:40:23 +01:00
Fabio Formosa
bd5276116b fixed the redirect to the login in case of security layer enabled 2022-11-19 12:18:36 +01:00
Fabio Formosa
b20fb9e9c3 bump version to 4.0.4-SNAPSHOT 2022-11-19 12:09:41 +01:00
Fabio Formosa
d3a406a382 Update maven-release.yml 2022-11-19 11:38:35 +01:00
Fabio Formosa
c70fe687e1 Update README.md 2022-11-19 11:37:55 +01:00
Fabio Formosa
fc81685044 Update README.md 2022-11-19 11:22:00 +01:00
Fabio Formosa
6e24d7caf5 bump version to 4.0.3 2022-11-19 11:05:57 +01:00
Fabio Formosa
6786cffb4d fixed the maven-release.yml 2022-11-19 11:05:23 +01:00
26 changed files with 281 additions and 153 deletions

View File

@@ -29,7 +29,7 @@ jobs:
run: mvn -B package --file quartz-manager-parent/pom.xml
- name: Publish to maven central
run: mvn deploy --file quartz-manager-parent/pom.xml --batch-mode -P release-maven-central, build-webjar
run: mvn deploy --file quartz-manager-parent/pom.xml --batch-mode -P "release-maven-central,build-webjar"
env:
MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }}
@@ -41,6 +41,6 @@ jobs:
java-version: '11'
distribution: 'temurin'
- name: Publish to GitHub Packages Apache Maven
run: mvn deploy --file quartz-manager-parent/pom.xml -P deploy-github, build-webjar
run: mvn deploy --file quartz-manager-parent/pom.xml -P "deploy-github,build-webjar"
env:
GITHUB_TOKEN: ${{ github.token }}

View File

@@ -14,13 +14,13 @@ Quartz Manager is a Java library you can import in your Spring-Based Web Applica
The **Quartz Manager UI** is a dashboard in the form of a single-page-application provided by the Quartz Manager Java library itself. You can have it embedded in your project, as well as you get embedded the Swagger UI.
It leverages the websockets to receive in real-time the trigger updates and the outcomes of the job executions.
![](https://github.com/fabioformosa/quartz-manager/blob/develop/quartz-manager-parent/quartz-manager-web-showcase/src/main/resources/quartz-manager-4-screenshot.png)
![](https://github.com/fabioformosa/quartz-manager/blob/master/quartz-manager-parent/quartz-manager-web-showcase/src/main/resources/quartz-manager-4-screenshot.png)
## QUARTZ MANAGER API
Quart-Manager exposes REST endpoints to interact with the Quartz Scheduler. This endpoints are invoked by Quartz Manager UI also.
The REST API are documented by an OpenAPI Specification interface.
![](https://github.com/fabioformosa/quartz-manager/blob/develop/quartz-manager-parent/quartz-manager-web-showcase/src/main/resources/quartz-manager-4-swagger.png)
![](https://github.com/fabioformosa/quartz-manager/blob/master/quartz-manager-parent/quartz-manager-web-showcase/src/main/resources/quartz-manager-4-swagger.png)
# HOW IT WORKS
@@ -52,7 +52,7 @@ In order to decrease the overall configuration time for the project, all modules
Below the list of the quartz-manager modules you can import.
## Quartz Manager API Lib
## Quartz Manager Starter API Lib
This is the only mandatory module of the library.
Add the dependency, make eligible for Quart Manager the job classes and set the props in your `application.properties` file.
@@ -63,12 +63,12 @@ Add the dependency, make eligible for Quart Manager the job classes and set the
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-starter-api</artifactId>
<version>4.0.0</version>
<version>4.0.4</version>
</dependency>
```
#### Gradle
```
implementation group: 'it.fabioformosa.quartz-manager', name: 'quartz-manager-starter-api', version: '4.0.0'
implementation group: 'it.fabioformosa.quartz-manager', name: 'quartz-manager-starter-api', version: '4.0.4'
```
### Step 2. Quartz Manager Job Classes
@@ -96,11 +96,17 @@ In this way, Quartz Manager is able to collect and display the outcomes at the U
### REST API & OpenAPI Specification
Set the app prop `quartz-manager.oas.enabled=true` you want to expose the OpenApi Specification of the Quartz Manager APIs.
Set the app prop `quartz-manager.oas.enabled=true` if you want to expose the OpenApi Specification of the Quartz Manager APIs.
Reach out the swagger-ui at the URL:
[http://localhost:8080/swagger-ui.html](http://localhost:8080/swagger-ui.html)
If your project has already an OpenAPI instance and you've set `quartz-manager.oas.enabled=true`, then make sure to add an OpenApiGroup to group the API of your application. Quart Manager exposes its API in group called "Quartz Manager".
If your project has already an OpenAPI instance and you've set `quartz-manager.oas.enabled=true`, then make sure to add an OpenApiGroup to group the API of your application. Quart Manager exposes its API in group called "Quartz Manager". If you use OAS Annotations:
```
@Bean
public GroupedOpenApi simplySpringDemoGroupedOpenApi() {
return GroupedOpenApi.builder().group("myapp").packagesToScan("com.example.myapp").build();
}
```
### QUARTZ SETTINGS
Quartz Manager creates its own instance of a [Quartz Scheduler](http://www.quartz-scheduler.org/).
@@ -122,7 +128,7 @@ The prerequesite is that you've imported a quartz scheduler ver 2.3.x.
You can configure the Quartz instance managed by Quartz Manager through the file `managed-quartz.properties` and your own Quartz instance through the file `quartz.properties`.
If you've created a `SchedulerFactoryBean`, tag it as @Primary to avoid conflicts in-type, since Quartz Manager creates another bean of the same type.
If you've created a `SchedulerFactoryBean`, tag it as `@Primary` to avoid conflicts in-type, since Quartz Manager creates another bean of the same type.
```
@Primary
@@ -135,7 +141,7 @@ If you've created a `SchedulerFactoryBean`, tag it as @Primary to avoid conflict
```
## Quartz Manager UI Lib
## Quartz Manager Starter UI Lib
You can optionally import the following dependency to have the UI Dashboard to interact with the Quartz Manager API.
### Dependency
@@ -145,12 +151,12 @@ You can optionally import the following dependency to have the UI Dashboard to i
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-starter-ui</artifactId>
<version>4.0.0</version>
<version>4.0.4</version>
</dependency>
```
#### Gradle
```
implementation group: 'it.fabioformosa.quartz-manager', name: 'quartz-manager-starter-ui', version: '4.0.0'
implementation group: 'it.fabioformosa.quartz-manager', name: 'quartz-manager-starter-ui', version: '4.0.4'
```
### Reach out the UI Console at URL
@@ -158,7 +164,7 @@ if you run locally [http://localhost:8080/quartz-manager-ui/index.html](http://l
## Quartz Manager Security Lib
## Quartz Manager Starter Security Lib
Import this optional dependency, if you want to enable a security layer and allow the access to the REST API and UI only to authenticated users.
The authentication model of Quartz Manager is based on [JWT](https://jwt.io/).
@@ -180,14 +186,14 @@ Future development: the Quart Manager Security OAuth2 client.
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-starter-security</artifactId>
<version>4.0.0</version>
<version>4.0.4</version>
</dependency>
```
#### Gradle
```
compile group: 'it.fabioformosa.quartz-manager', name: 'quartz-manager-starter-security', version: '4.0.0'
compile group: 'it.fabioformosa.quartz-manager', name: 'quartz-manager-starter-security', version: '4.0.4'
```
@@ -203,11 +209,11 @@ compile group: 'it.fabioformosa.quartz-manager', name: 'quartz-manager-starter-s
|quartz-manager.security.accounts.in-memory.users[0].roles[0] | string | yes | | set the value ADMIN |
## Quart Manager Persistence Lib
## Quart Manager Starter Persistence Lib
By default, Quartz Manager runs with a `org.quartz.simpl.RAMJobStore` that stores your managed scheduler in memory. The RAMJobStore is the simplest store and also the most performant. However it comes with the drawback that all scheduling data are lost if your applications ends or crashes. In case of a restarting of your app, you can't resume the scheduler from the point it stopped.
Import the Quartz Manager Persistence Module if you want to persist your scheduler data.
The pre-requesite is the availability of Postgresql database where Quartz Manager can store its information. You have to provide it a bare database and a postresql user granted for DDL and DML queries. About the DDL, consider that Quartz Manager Persistence will create all tables it needs to work at the bootstrap.
By default, Quartz Manager runs with a `org.quartz.simpl.RAMJobStore` that stores your managed scheduler in memory. The RAMJobStore is the simplest store and also the most performant. However it comes with the drawback that all scheduling data are lost if your applications ends or crashes. In case of a restarting of your app, you can't resume the scheduler from the point it stopped.
Import the Quartz Manager Persistence Module if you want to persist your scheduler data.
The pre-requesite is the availability of Postgresql database where Quartz Manager can store its information. You have to provide it a bare database and a postresql user granted for DDL and DML queries. About the DDL, consider that Quartz Manager Persistence will create all tables, it needs to work, at the bootstrap.
### Dependency
@@ -217,14 +223,14 @@ The pre-requesite is the availability of Postgresql database where Quartz Manage
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-starter-persistence</artifactId>
<version>4.0.0</version>
<version>4.0.4</version>
</dependency>
```
#### Gradle
```
compile group: 'it.fabioformosa.quartz-manager', name: 'quartz-manager-starter-persistence', version: '4.0.0'
compile group: 'it.fabioformosa.quartz-manager', name: 'quartz-manager-starter-persistence', version: '4.0.4'
```
### Quartz Manager Persistence Lib - App Props
@@ -239,13 +245,13 @@ compile group: 'it.fabioformosa.quartz-manager', name: 'quartz-manager-starter-p
## Examples
You can find some examples of different scenarios of projects which import Quartz Manager at the repository [quartz-manager-use-cases] (https://github.com/fabioformosa/quartz-manager-use-cases)
* *simply-spring* - tipical scenario in which you create a minimal spring project from scratch dedicated to launch your scheduled jobs. Imported libraries: Quartz Manager API, Quartz Manager UI and Quartz Manager security.
* *simply-spring-no-security* - as simple-spring, without the security
* *existing-security-cookie-based* - It demonstrates how Quartz Manager stays under the security of your project, in case of an auth model based on cookies
* *existing-security-header-based* - It demonstrates how Quartz Manager Security can coexists with another Spring Security Config present in your project
* *existing-quartz* - It demonstrates how to Quartz Manager can coexist with a Quartz instance already present in your project
* *existing-quartz-and-storage* - It demonstrates how to Quartz Manager Persistence can coexist with a Quartz instance already present in your project
You can find some examples of different scenarios of projects which import Quartz Manager at the repository [quartz-manager-use-cases](https://github.com/fabioformosa/quartz-manager-use-cases)
* *simply-spring* - tipical scenario in which you create a minimal spring project from scratch dedicated to launch your scheduled jobs. Imported libraries: Quartz Manager API, Quartz Manager UI and Quartz Manager Security.
* *simply-spring-no-security* - as simple-spring, without the security. Imported libraries: Quartz Manager API, Quartz Manager UI.
* *existing-security-cookie-based* - It demonstrates how Quartz Manager stays under the security of your project, in case of an auth model based on cookies. Imported libraries: Quartz Manager API, Quartz Manager UI.
* *existing-security-header-based* - It demonstrates how Quartz Manager Security can coexists with another Spring Security Config present in your project. Imported libraries: Quartz Manager API, Quartz Manager UI and Quartz Manager Security.
* *existing-quartz* - It demonstrates how to Quartz Manager can coexist with a Quartz instance already present in your project Imported libraries: Quartz Manager API, Quartz Manager UI.
* *existing-quartz-and-storage* - It demonstrates how to Quartz Manager Persistence can coexist with a Quartz instance already present in your project. Imported libraries: Quartz Manager API, Quartz Manager UI and Quartz Manager Persistence.
## Limitations

View File

@@ -2,13 +2,25 @@ import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {TriggerService} from '../../services/trigger.service';
import {TriggerKey} from '../../model/triggerKey.model';
import {SimpleTrigger} from '../../model/simple-trigger.model';
import {MatDialog} from '@angular/material/dialog';
import {MatDialog, MatDialogRef} from '@angular/material/dialog';
@Component({
template: 'Multiple jobs not supported yet - Coming Soon...',
template: `
<h3 mat-dialog-title>Coming Soon</h3>
<div mat-dialog-content>
<p>This feature is in roadmap and it will come with the next releases</p>
</div>
<div mat-dialog-actions>
<button mat-button (click)="closeDialog()" style="padding: 0.5em;width: 5em;">Ok</button>
</div>`,
})
// tslint:disable-next-line:component-class-suffix
export class UnsupportedMultipleJobsDialog {
constructor(public dialogRef: MatDialogRef<UnsupportedMultipleJobsDialog>) {
}
closeDialog(): void {
this.dialogRef.close();
}
}
@Component({
@@ -69,7 +81,7 @@ export class TriggerListComponent implements OnInit {
}
onNewTriggerBtnClicked() {
if (this.triggerKeys && this.triggerKeys.length > 0) {
if (this.getTriggerKeyList() && this.getTriggerKeyList().length > 0) {
this.dialog.open(UnsupportedMultipleJobsDialog)
} else {
this.onNewTriggerClicked.emit();

View File

@@ -42,8 +42,9 @@ export class UserService {
this.router.initialNavigation();
return;
}
if (httpErrorResponse.status < 200 || httpErrorResponse.status > 399)
if (httpErrorResponse.status !== 401 && (httpErrorResponse.status < 200 || httpErrorResponse.status > 399)) {
this.router.navigateByUrl('/error');
}
});
}

View File

@@ -0,0 +1,2 @@
config.stopBubbling = true
lombok.addLombokGeneratedAnnotation = true

View File

@@ -10,7 +10,7 @@
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>4.0.2</version>
<version>4.0.5</version>
<packaging>pom</packaging>
@@ -69,27 +69,27 @@
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-common</artifactId>
<version>4.0.2</version>
<version>4.0.5</version>
</dependency>
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-starter-api</artifactId>
<version>4.0.2</version>
<version>4.0.5</version>
</dependency>
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-starter-security</artifactId>
<version>4.0.2</version>
<version>4.0.5</version>
</dependency>
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-starter-persistence</artifactId>
<version>4.0.2</version>
<version>4.0.5</version>
</dependency>
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-starter-ui</artifactId>
<version>4.0.2</version>
<version>4.0.5</version>
</dependency>
</dependencies>
</dependencyManagement>
@@ -179,17 +179,6 @@
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>${nexus-staging-maven-plugin.version}</version>
<extensions>true</extensions>
<configuration>
<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>
@@ -233,6 +222,17 @@
</distributionManagement>
<build>
<plugins>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>${nexus-staging-maven-plugin.version}</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>4.0.2</version>
<version>4.0.5</version>
</parent>
<artifactId>quartz-manager-common</artifactId>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>4.0.2</version>
<version>4.0.5</version>
</parent>
<artifactId>quartz-manager-starter-api</artifactId>
@@ -106,6 +106,10 @@
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<!-- QUARTZ -->
<dependency>
@@ -118,34 +122,6 @@
<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>
<!-- OAS -->
<dependency>
<groupId>org.springdoc</groupId>

View File

@@ -23,6 +23,5 @@ public class JobKeyToJobDetailDTO extends AbstractBaseConverterToDTO<JobKey, Job
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
jobDetailDTO.setJobClassName(jobDetail.getJobClass().getName());
jobDetailDTO.setDescription(jobDetail.getDescription());
//jobDetail.getJobDataMap();
}
}

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>4.0.2</version>
<version>4.0.5</version>
</parent>
<artifactId>quartz-manager-starter-persistence</artifactId>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>4.0.2</version>
<version>4.0.5</version>
</parent>
<artifactId>quartz-manager-starter-security</artifactId>

View File

@@ -34,9 +34,6 @@ import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.ArrayList;
import java.util.List;
@@ -101,7 +98,9 @@ public class QuartzManagerSecurityConfig {
@Order(Ordered.HIGHEST_PRECEDENCE)
@Bean(name = "quartzManagerFilterChain")
public SecurityFilterChain filterChain(HttpSecurity http, @Qualifier("quartzManagerInMemoryAuthentication") InMemoryUserDetailsManager userDetailsService, AuthenticationManager authenticationManager) throws Exception {
public SecurityFilterChain filterChain(HttpSecurity http,
@Qualifier("quartzManagerInMemoryAuthentication") InMemoryUserDetailsManager userDetailsService,
AuthenticationManager authenticationManager) throws Exception {
http.antMatcher(QUARTZ_MANAGER_API_ANT_MATCHER).csrf().disable() //
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() //
.exceptionHandling().authenticationEntryPoint(restAuthEntryPoint()).and() //
@@ -128,14 +127,6 @@ public class QuartzManagerSecurityConfig {
};
}
@Bean(name = "quartzManagerCorsConfigurationSource")
public CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration(QUARTZ_MANAGER_API_ANT_MATCHER, new CorsConfiguration().applyPermitDefaultValues());
source.registerCorsConfiguration(QUARTZ_MANAGER_UI_ANT_MATCHER, new CorsConfiguration().applyPermitDefaultValues());
return source;
}
public LoginConfigurer formLoginConfigurer() {
JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler = jwtAuthenticationSuccessHandler();
AuthenticationSuccessHandler authenticationSuccessHandler = new AuthenticationSuccessHandler(jwtAuthenticationSuccessHandler);

View File

@@ -20,13 +20,14 @@ import static it.fabioformosa.quartzmanager.api.common.config.QuartzManagerPaths
@RequestMapping(value = QUARTZ_MANAGER_AUTH_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
public class UserController {
public static final String WHOAMI_URL = "/whoami";
@GetMapping("/whoami")
@GetMapping(WHOAMI_URL)
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);
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}

View File

@@ -1,24 +1,38 @@
package it.fabioformosa.quartzmanager.api.security.properties;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.security.SecureRandom;
import java.util.Base64;
@Configuration
@ConfigurationProperties(prefix = "quartz-manager.security.jwt")
@Data
@AllArgsConstructor
@Getter
@Setter
public class JwtSecurityProperties {
private String secret = RandomStringUtils.randomAlphabetic(10);
private String secret;
private long expirationInSec = 28800;
private CookieStrategy cookieStrategy = new CookieStrategy();
private HeaderStrategy headerStrategy = new HeaderStrategy();
public JwtSecurityProperties() {
SecureRandom random = new SecureRandom();
byte[] bytes = new byte[20];
random.nextBytes(bytes);
Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding();
secret = encoder.encodeToString(bytes);
}
@Data
public static class CookieStrategy {
private boolean enabled = false;

View File

@@ -2,6 +2,8 @@ package it.fabioformosa.quartzmanager.api.security;
import it.fabioformosa.quartzmanager.api.common.config.QuartzManagerPaths;
import it.fabioformosa.quartzmanager.api.security.controllers.TestController;
import it.fabioformosa.quartzmanager.api.security.properties.JwtSecurityProperties;
import org.assertj.core.api.Assertions;
import org.hamcrest.core.IsNot;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -19,9 +21,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(properties = {
"quartz-manager.security.jwt.enabled=true",
"quartz-manager.security.jwt.secret=bibidibobidiboo",
"quartz-manager.security.jwt.expiration-in-sec=28800",
"quartz-manager.security.jwt.expiration-in-sec=36000",
"quartz-manager.security.jwt.header-strategy.enabled=false",
"quartz-manager.security.jwt.header-strategy.header=Authorization",
"quartz-manager.security.jwt.cookie-strategy.enabled=true",
@@ -31,11 +32,14 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
"quartz-manager.security.accounts.in-memory.users[0].password=bar",
"quartz-manager.security.accounts.in-memory.users[0].roles[0]=admin",
})
public class SecurityControllerTest {
class SecurityControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private JwtSecurityProperties jwtSecurityProperties;
@Test
void givenAnAnonymousUser_whenCalledADMZController_thenShouldRaiseForbidden() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/dmz"))
@@ -72,4 +76,10 @@ public class SecurityControllerTest {
.andExpect(status().isOk());
}
@Test
void givenSecurityProps_whenTheBootstrapHasCompleted_thenJWTPropertiesShouldBeSetAccordingly() throws Exception {
Assertions.assertThat(jwtSecurityProperties.getExpirationInSec()).isEqualTo(36000);
Assertions.assertThat(jwtSecurityProperties.getSecret()).isEqualTo("bibidibobidiboo");
}
}

View File

@@ -13,7 +13,6 @@ import org.springframework.test.context.TestPropertySource;
@TestPropertySource(properties = {
"quartz-manager.security.login-model.form-login-enabled = false",
"quartz-manager.security.login-model.userpwd-filter-enabled = true",
"quartz-manager.security.jwt.enabled=true",
"quartz-manager.security.jwt.secret=bibidibobidiboo",
"quartz-manager.security.jwt.expiration-in-sec=28800",
"quartz-manager.security.jwt.header-strategy.enabled=true",

View File

@@ -12,7 +12,6 @@ import org.springframework.test.context.TestPropertySource;
@TestPropertySource(properties = {
"quartz-manager.security.login-model.form-login-enabled = true",
"quartz-manager.security.login-model.userpwd-filter-enabled = false",
"quartz-manager.security.jwt.enabled=true",
"quartz-manager.security.jwt.secret=bibidibobidiboo",
"quartz-manager.security.jwt.expiration-in-sec=28800",
"quartz-manager.security.jwt.header-strategy.enabled=true",

View File

@@ -0,0 +1,41 @@
package it.fabioformosa.quartzmanager.api.security.controllers;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static it.fabioformosa.quartzmanager.api.common.config.QuartzManagerPaths.QUARTZ_MANAGER_AUTH_PATH;
import static it.fabioformosa.quartzmanager.api.security.controllers.UserController.WHOAMI_URL;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(properties = {
"quartz-manager.security.accounts.in-memory.enabled=true",
"quartz-manager.security.accounts.in-memory.users[0].username=admin",
"quartz-manager.security.accounts.in-memory.users[0].password=admin",
"quartz-manager.security.accounts.in-memory.users[0].roles[0]=admin",
})
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
@WithMockUser("admin")
void givenAnUser_whenCalledTheWhoamiEndpoint_thenShouldReturn2xx() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get(QUARTZ_MANAGER_AUTH_PATH + WHOAMI_URL))
.andExpect(status().isOk());
}
@Test
void givenAnAnonymousUser_whenCalledTheWhoamiEndpoint_thenShouldReturnNotFound() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get(QUARTZ_MANAGER_AUTH_PATH + WHOAMI_URL))
.andExpect(status().isUnauthorized());
}
}

View File

@@ -0,0 +1,39 @@
package it.fabioformosa.quartzmanager.api.security.properties;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import javax.validation.Validation;
import javax.validation.Validator;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
public abstract class AbstractPropertyValidatorTest {
protected static Validator propertyValidator;
@BeforeAll
public static void setup() {
propertyValidator = Validation.buildDefaultValidatorFactory().getValidator();
}
protected static <T> T inflateConfigurationPropertyFromAMap(Map<String, String> properties, String configurationPropName, Class<T> propClass) {
ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
Binder binder = new Binder(source);
BindResult<T> result = binder.bind(configurationPropName, propClass);
if (properties != null && !properties.isEmpty()) {
Assertions.assertThat(result.isBound()).isTrue();
T configPropObject = result.get();
return configPropObject;
} else {
try {
return propClass.getConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}
}

View File

@@ -1,25 +1,16 @@
package it.fabioformosa.quartzmanager.api.security.properties;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public class InMemoryUsersValidationControllerTest {
private static Validator propertyValidator;
class InMemoryUsersValidationControllerTest extends AbstractPropertyValidatorTest {
static Stream<Arguments> notValidInMemoryProps = Stream.of(
Arguments.of(
@@ -34,26 +25,15 @@ public class InMemoryUsersValidationControllerTest {
);
@BeforeAll
public static void setup() {
propertyValidator = Validation.buildDefaultValidatorFactory().getValidator();
}
static Stream<Arguments> getNotValidInMemoryProps(){
static Stream<Arguments> getNotValidInMemoryProps() {
return notValidInMemoryProps;
}
@ParameterizedTest
@MethodSource("it.fabioformosa.quartzmanager.api.security.properties.InMemoryUsersValidationControllerTest#getNotValidInMemoryProps")
void givenAMissingUsername_whenThePropertyValidationIsApplied_thenShouldRaiseValidationError(Map<String, String> properties) {
ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
Binder binder = new Binder(source);
BindResult<InMemoryAccountProperties> result = binder.bind("quartz-manager.security.accounts.in-memory", InMemoryAccountProperties.class);
Assertions.assertThat(result.isBound()).isTrue();
InMemoryAccountProperties inMemoryAccountProperties = result.get();
InMemoryAccountProperties inMemoryAccountProperties = inflateConfigurationPropertyFromAMap(properties,
"quartz-manager.security.accounts.in-memory", InMemoryAccountProperties.class);
Assertions.assertThat(propertyValidator.validate(inMemoryAccountProperties)).isNotEmpty();
}
@@ -65,14 +45,9 @@ public class InMemoryUsersValidationControllerTest {
properties.put("quartz-manager.security.accounts.in-memory.users[0].password", "bar");
properties.put("quartz-manager.security.accounts.in-memory.users[0].roles[0]", "admin");
ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
InMemoryAccountProperties inMemoryAccountProperties = inflateConfigurationPropertyFromAMap(properties,
"quartz-manager.security.accounts.in-memory", InMemoryAccountProperties.class);
Binder binder = new Binder(source);
BindResult<InMemoryAccountProperties> result = binder.bind("quartz-manager.security.accounts.in-memory", InMemoryAccountProperties.class);
Assertions.assertThat(result.isBound()).isTrue();
InMemoryAccountProperties inMemoryAccountProperties = result.get();
Assertions.assertThat(propertyValidator.validate(inMemoryAccountProperties)).isEmpty();
}

View File

@@ -0,0 +1,39 @@
package it.fabioformosa.quartzmanager.api.security.properties;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.Map;
class JwtSecurityPropertiesTest extends AbstractPropertyValidatorTest {
@Test
void givenAllJWTSecurityPropSet_whenThePropertyValidationIsApplied_thenShouldBeValid() {
Map<String, String> properties = new HashMap<>();
String secret = "helloworld";
properties.put("quartz-manager.security.jwt.secret", secret);
String expirationInSec = "36000";
properties.put("quartz-manager.security.jwt.expirationInSec", expirationInSec);
JwtSecurityProperties jwtSecurityProperties = inflateConfigurationPropertyFromAMap(properties,
"quartz-manager.security.jwt", JwtSecurityProperties.class);
Assertions.assertThat(propertyValidator.validate(jwtSecurityProperties)).isEmpty();
Assertions.assertThat(jwtSecurityProperties.getExpirationInSec()).isEqualTo(Long.valueOf(expirationInSec));
Assertions.assertThat(jwtSecurityProperties.getSecret()).isEqualTo(secret);
}
@Test
void givenTheMandatoryJWTSecurityPropUnset_whenThePropertyValidationIsApplied_thenShouldBeSetWithDefault() {
Map<String, String> properties = new HashMap<>();
JwtSecurityProperties jwtSecurityProperties = inflateConfigurationPropertyFromAMap(properties,
"quartz-manager.security.jwt", JwtSecurityProperties.class);
Assertions.assertThat(jwtSecurityProperties.getExpirationInSec()).isEqualTo(28800L);
Assertions.assertThat(jwtSecurityProperties.getSecret()).isNotBlank();
}
}

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>4.0.2</version>
<version>4.0.5</version>
</parent>
<artifactId>quartz-manager-starter-ui</artifactId>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-parent</artifactId>
<version>4.0.2</version>
<version>4.0.5</version>
</parent>
<artifactId>quartz-manager-web-showcase</artifactId>
@@ -31,10 +31,10 @@
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-starter-ui</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>it.fabioformosa.quartz-manager</groupId>-->
<!-- <artifactId>quartz-manager-starter-security</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>it.fabioformosa.quartz-manager</groupId>
<artifactId>quartz-manager-starter-security</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>it.fabioformosa.quartz-manager</groupId>-->
<!-- <artifactId>quartz-manager-starter-persistence</artifactId>-->
@@ -129,12 +129,12 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>

View File

@@ -2,9 +2,9 @@ package it.fabioformosa.quartzmanager.jobs.tests;
import it.fabioformosa.quartzmanager.api.jobs.AbstractQuartzManagerJob;
import it.fabioformosa.quartzmanager.api.jobs.entities.LogRecord;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This job can be used to test the misfire policy. It pretends to be a long
@@ -13,20 +13,26 @@ import org.slf4j.LoggerFactory;
* @author Fabio.Formosa
*
*/
@Slf4j
@NoArgsConstructor
public class MisfireTestJob extends AbstractQuartzManagerJob {
private Logger log = LoggerFactory.getLogger(MisfireTestJob.class);
private long sleepPeriodInMs = 10 * 1000L;
public MisfireTestJob(long sleepPeriodInMs) {
this.sleepPeriodInMs = sleepPeriodInMs;
}
@Override
public LogRecord doIt(JobExecutionContext jobExecutionContext) {
try {
log.info("{} is going to sleep...", Thread.currentThread().getName());
Thread.sleep(10 * 1000);
Thread.sleep(sleepPeriodInMs);
log.info("{} woke up!", Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
throw new IllegalStateException(e);
}
return new LogRecord(LogRecord.LogType.INFO, "Hello!");

View File

@@ -9,7 +9,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
public class QuartManagerApplicationTests {
@Test
public void contextLoads() {
void contextLoads() {
}
}

View File

@@ -0,0 +1,18 @@
package it.fabioformosa.quartzmanager.jobs.tests;
import it.fabioformosa.quartzmanager.api.jobs.entities.LogRecord;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
class MisfireTestJobTest {
@Test
void givenAMisfireTestJob_whenIsExecuted_shoulReturnALogRecord() {
MisfireTestJob misfireTestJob = new MisfireTestJob(10L);
LogRecord logRecord = misfireTestJob.doIt(null);
Assertions.assertThat(logRecord.getMessage()).isEqualTo("Hello!");
}
}