Merge pull request #29 from fabioformosa/develop

Develop
This commit is contained in:
Fabio Formosa
2020-12-26 17:56:56 +01:00
committed by GitHub
18 changed files with 557 additions and 384 deletions

View File

@@ -11,7 +11,7 @@
</button>
<button
class="greeting-button"
*ngIf="hasSignedIn()"
*ngIf="hasSignedIn() && !noAuthenticationRequired()"
mat-button mat-ripple
[matMenuTriggerFor]="accountMenu">
<span>Hi, {{userName()}}</span>

View File

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

View File

@@ -1,14 +1,55 @@
import { TestBed, async, inject } from '@angular/core/testing';
import { Router } from '@angular/router';
import { UserService } from '../services';
import { NO_AUTH, UserService } from '../services';
import { AdminGuard } from './admin.guard';
import { MockUserService } from '../services/mocks';
import {jest} from '@jest/globals'
export class RouterStub {
navigate(commands?: any[], extras?: any) {}
}
describe('AdminGuard', () => {
const RouterSpy = jest.spyOn(RouterStub.prototype, 'navigate');
const MockUserServiceNoAuth = jest.fn(() => ({currentUser: NO_AUTH}));
const MockUserService = jest.fn(() => ({
currentUser: {
authorities: ['ROLE_ADMIN']
}
}));
const MockUserServiceForbidden = jest.fn(() => ({
currentUser: {
authorities: ['ROLE_GUEST']
}
}));
describe('AdminGuard NoAuth', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
AdminGuard,
{
provide: Router,
useClass: RouterStub
},
{
provide: UserService,
useClass: MockUserServiceNoAuth
}
]
});
});
it('should run', inject([AdminGuard], (guard: AdminGuard) => {
expect(guard).toBeTruthy();
}));
it('returns true if user is NO_AUTH',inject([AdminGuard], (guard: AdminGuard) => {
expect(guard.canActivate(null, null)).toBeTruthy();
}));
});
describe('AdminGuard activates the route', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
@@ -25,7 +66,40 @@ describe('AdminGuard', () => {
});
});
it('should ...', inject([AdminGuard], (guard: AdminGuard) => {
it('should run', inject([AdminGuard], (guard: AdminGuard) => {
expect(guard).toBeTruthy();
}));
it('returns true if user has admin role',inject([AdminGuard], (guard: AdminGuard) => {
expect(guard.canActivate(null, null)).toBeTruthy();
}));
});
describe('AdminGuard redirects to 403', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
AdminGuard,
{
provide: Router,
useClass: RouterStub
},
{
provide: UserService,
useClass: MockUserServiceForbidden
}
]
});
});
it('should run', inject([AdminGuard], (guard: AdminGuard) => {
expect(guard).toBeTruthy();
}));
it('returns false if user is not authorized',inject([AdminGuard], (guard: AdminGuard) => {
expect(guard.canActivate(null, null)).toBeFalsy();
expect(RouterSpy).toHaveBeenCalledTimes(1);
}));
});

View File

@@ -9,6 +9,8 @@ export class AdminGuard implements CanActivate {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
if (this.userService.currentUser) {
if(this.userService.currentUser === 'NO_AUTH')
return true;
if (JSON.stringify(this.userService.currentUser.authorities).search('ROLE_ADMIN') !== -1)
return true;
else {

View File

@@ -0,0 +1,83 @@
import { TestBed } from "@angular/core/testing";
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { ApiService } from './api.service';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Router} from '@angular/router';
import {jest} from '@jest/globals'
class Data{
name: string
}
class HttpResponseMock {
constructor(
public body: unknown,
public opts?: {
headers?:
| HttpHeaders
| {
[name: string]: string | string[];
};
status?: number;
statusText?: string;
}
) {}
}
const routerSpy = jest.spyOn(Router.prototype, 'navigateByUrl');
describe('ApiServiceTest', () => {
let apiService: ApiService;
let httpClient: HttpClient;
let httpTestingController: HttpTestingController;
const SAMPLE_URL = '/sample-url';
const URL_401 = '/url-response-401';
const testData: Data = {name: 'Test Data'};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [ApiService, {provide: Router, useValue: routerSpy}]
});
apiService = TestBed.inject(ApiService);
httpClient = TestBed.inject(HttpClient);
httpTestingController = TestBed.inject(HttpTestingController);
});
it('should be created', (): void => {
expect(apiService).toBeTruthy();
});
it('can test HttpClient.get', (): void => {
apiService.get(SAMPLE_URL).subscribe((res: Data) => {
expect(res).toEqual(testData);
});
const req = httpTestingController.expectOne(SAMPLE_URL)
expect(req.request.method).toEqual('GET');
req.flush(new HttpResponseMock(testData));
httpTestingController.verify();
});
it('doesn\'t do anything if 401 is received', (): void => {
apiService.get(URL_401).subscribe((res: Data) => {
expect(false);
}, (error) =>
{
expect(error.status).toBe(401);
expect(routerSpy).toHaveBeenCalledTimes(1);
});
const req = httpTestingController.expectOne(URL_401)
expect(req.request.method).toEqual('GET');
req.flush(null, {status: 401, statusText: 'unauthenticated'});
httpTestingController.verify();
});
});

View File

@@ -1,4 +1,5 @@
import { HttpClient, HttpHeaders, HttpResponse, HttpRequest, HttpEventType, HttpParams } from '@angular/common/http';
import { Router} from '@angular/router';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { catchError, map, filter, tap } from 'rxjs/operators'
@@ -35,7 +36,7 @@ export class ApiService {
private jwtToken: string;
constructor( private http: HttpClient) { }
constructor( private http: HttpClient, private router: Router) { }
setToken(token: string) {
this.jwtToken = token;
@@ -98,7 +99,7 @@ export class ApiService {
// Display error if logged in, otherwise redirect to IDP
private checkError(error: any): any {
if (error && error.status === 401) {
// this.redirectIfUnauth(error);
this.router.navigate(['/login']);
} else {
// this.displayError(error);
}

View File

@@ -3,8 +3,7 @@ import { HttpHeaders, HttpResponse } from '@angular/common/http';
import { ApiService } from './api.service';
import { UserService } from './user.service';
import { ConfigService } from './config.service';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { map } from 'rxjs/operators';
@Injectable()
export class AuthService {

View File

@@ -1,5 +1,4 @@
import { Injectable } from '@angular/core';
import { environment } from '../../environments/environment';
@Injectable()
export class ConfigService {

View File

@@ -4,6 +4,8 @@ import { ConfigService } from './config.service';
import { map } from 'rxjs/operators'
export const NO_AUTH: string = 'NO_AUTH'
@Injectable()
export class UserService {
@@ -34,6 +36,7 @@ export class UserService {
this.currentUser = user;
}, err => {
//not logged
console.log(`error retrieving current user due to ` + err);
});
}

View File

@@ -25,52 +25,52 @@ import it.fabioformosa.quartzmanager.scheduler.TriggerMonitor;
@Component
public class WebSocketProgressNotifier implements ProgressNotifier {
@Autowired
private SimpMessageSendingOperations messagingTemplate;
@Autowired
private SimpMessageSendingOperations messagingTemplate;
@Resource
private Scheduler scheduler;
@Resource
private Scheduler scheduler;
@Resource
private TriggerMonitor triggerMonitor;
@Resource
private TriggerMonitor triggerMonitor;
//@AfterReturning("execution(* logAndSend(..))")
// @Override
// public void updateProgress(JoinPoint joinPoint) {
// log.info("PROGRESS UPDATE!!!");
// }
//@AfterReturning("execution(* logAndSend(..))")
// @Override
// public void updateProgress(JoinPoint joinPoint) {
// log.info("PROGRESS UPDATE!!!");
// }
@Override
public void send() throws SchedulerException {
TriggerStatus currTriggerStatus = new TriggerStatus();
@Override
public void send() throws SchedulerException {
TriggerStatus currTriggerStatus = new TriggerStatus();
Trigger trigger = scheduler.getTrigger(triggerMonitor.getTrigger().getKey());
currTriggerStatus.setFinalFireTime(trigger.getFinalFireTime());
currTriggerStatus.setNextFireTime(trigger.getNextFireTime());
currTriggerStatus.setPreviousFireTime(trigger.getPreviousFireTime());
Trigger trigger = scheduler.getTrigger(triggerMonitor.getTrigger().getKey());
currTriggerStatus.setFinalFireTime(trigger.getFinalFireTime());
currTriggerStatus.setNextFireTime(trigger.getNextFireTime());
currTriggerStatus.setPreviousFireTime(trigger.getPreviousFireTime());
int timesTriggered = 0;
int repeatCount = 0;
int timesTriggered = 0;
int repeatCount = 0;
if (trigger instanceof SimpleTrigger) {
SimpleTrigger simpleTrigger = (SimpleTrigger) trigger;
timesTriggered = simpleTrigger.getTimesTriggered();
repeatCount = simpleTrigger.getRepeatCount();
} else if (trigger instanceof DailyTimeIntervalTrigger) {
DailyTimeIntervalTrigger dailyTrigger = (DailyTimeIntervalTrigger) trigger;
timesTriggered = dailyTrigger.getTimesTriggered();
repeatCount = dailyTrigger.getRepeatCount();
}
if (trigger instanceof SimpleTrigger) {
SimpleTrigger simpleTrigger = (SimpleTrigger) trigger;
timesTriggered = simpleTrigger.getTimesTriggered();
repeatCount = simpleTrigger.getRepeatCount();
} else if (trigger instanceof DailyTimeIntervalTrigger) {
DailyTimeIntervalTrigger dailyTrigger = (DailyTimeIntervalTrigger) trigger;
timesTriggered = dailyTrigger.getTimesTriggered();
repeatCount = dailyTrigger.getRepeatCount();
}
Trigger jobTrigger = triggerMonitor.getTrigger();
if (jobTrigger != null && jobTrigger.getJobKey() != null) {
currTriggerStatus.setJobKey(jobTrigger.getJobKey().getName());
currTriggerStatus.setJobClass(jobTrigger.getClass().getSimpleName());
currTriggerStatus.setTimesTriggered(timesTriggered);
currTriggerStatus.setRepeatCount(repeatCount + 1);
}
Trigger jobTrigger = triggerMonitor.getTrigger();
if (jobTrigger != null && jobTrigger.getJobKey() != null) {
currTriggerStatus.setJobKey(jobTrigger.getJobKey().getName());
currTriggerStatus.setJobClass(jobTrigger.getClass().getSimpleName());
currTriggerStatus.setTimesTriggered(timesTriggered);
currTriggerStatus.setRepeatCount(repeatCount + 1);
}
messagingTemplate.convertAndSend("/topic/progress", currTriggerStatus);
}
messagingTemplate.convertAndSend("/topic/progress", currTriggerStatus);
}
}

View File

@@ -14,6 +14,7 @@ import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
@@ -24,75 +25,76 @@ import it.fabioformosa.quartzmanager.scheduler.AutowiringSpringBeanJobFactory;
import it.fabioformosa.quartzmanager.scheduler.TriggerMonitor;
import it.fabioformosa.quartzmanager.scheduler.TriggerMonitorImpl;
@ComponentScan(basePackages = {"it.fabioformosa.quartzmanager.controllers"})
@Configuration
@ConditionalOnProperty(name = "quartz.enabled")
public class SchedulerConfig {
private static JobDetailFactoryBean createJobDetail(Class<? extends Job> jobClass) {
JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
factoryBean.setJobClass(jobClass);
factoryBean.setDurability(false);
return factoryBean;
}
private static JobDetailFactoryBean createJobDetail(Class<? extends Job> jobClass) {
JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
factoryBean.setJobClass(jobClass);
factoryBean.setDurability(false);
return factoryBean;
}
private static SimpleTriggerFactoryBean createTrigger(JobDetail jobDetail, long pollFrequencyMs,
int repeatCount) {
SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
factoryBean.setJobDetail(jobDetail);
factoryBean.setStartDelay(3000L);
factoryBean.setRepeatInterval(pollFrequencyMs);
factoryBean.setRepeatCount(repeatCount);
factoryBean
.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT);// in case of misfire, ignore all missed triggers and continue
return factoryBean;
}
private static SimpleTriggerFactoryBean createTrigger(JobDetail jobDetail, long pollFrequencyMs,
int repeatCount) {
SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
factoryBean.setJobDetail(jobDetail);
factoryBean.setStartDelay(3000L);
factoryBean.setRepeatInterval(pollFrequencyMs);
factoryBean.setRepeatCount(repeatCount);
factoryBean
.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT);// in case of misfire, ignore all missed triggers and continue
return factoryBean;
}
@Value("${quartz-manager.jobClass}")
private String jobClassname;
@Value("${quartz-manager.jobClass}")
private String jobClassname;
@Bean(name = "triggerMonitor")
public TriggerMonitor createTriggerMonitor(@Qualifier("jobTrigger") Trigger trigger) {
TriggerMonitor triggerMonitor = new TriggerMonitorImpl();
triggerMonitor.setTrigger(trigger);
return triggerMonitor;
}
@Bean(name = "triggerMonitor")
public TriggerMonitor createTriggerMonitor(@Qualifier("jobTrigger") Trigger trigger) {
TriggerMonitor triggerMonitor = new TriggerMonitorImpl();
triggerMonitor.setTrigger(trigger);
return triggerMonitor;
}
@Bean
@SuppressWarnings("unchecked")
public JobDetailFactoryBean jobDetail() throws ClassNotFoundException {
Class<? extends Job> JobClass = (Class<? extends Job>) Class.forName(jobClassname);
return createJobDetail(JobClass);
}
@Bean
@SuppressWarnings("unchecked")
public JobDetailFactoryBean jobDetail() throws ClassNotFoundException {
Class<? extends Job> JobClass = (Class<? extends Job>) Class.forName(jobClassname);
return createJobDetail(JobClass);
}
@Bean
public JobFactory jobFactory(ApplicationContext applicationContext) {
AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
return jobFactory;
}
@Bean
public JobFactory jobFactory(ApplicationContext applicationContext) {
AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
return jobFactory;
}
@Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
@Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
@Bean(name = "jobTrigger")
public SimpleTriggerFactoryBean sampleJobTrigger(@Qualifier("jobDetail") JobDetail jobDetail,
@Value("${job.frequency}") long frequency, @Value("${job.repeatCount}") int repeatCount) {
return createTrigger(jobDetail, frequency, repeatCount);
}
@Bean(name = "jobTrigger")
public SimpleTriggerFactoryBean sampleJobTrigger(@Qualifier("jobDetail") JobDetail jobDetail,
@Value("${job.frequency}") long frequency, @Value("${job.repeatCount}") int repeatCount) {
return createTrigger(jobDetail, frequency, repeatCount);
}
@Bean(name = "scheduler")
public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory,
@Qualifier("jobTrigger") Trigger sampleJobTrigger) throws IOException {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setJobFactory(jobFactory);
factory.setQuartzProperties(quartzProperties());
factory.setTriggers(sampleJobTrigger);
factory.setAutoStartup(false);
return factory;
}
@Bean(name = "scheduler")
public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory,
@Qualifier("jobTrigger") Trigger sampleJobTrigger) throws IOException {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setJobFactory(jobFactory);
factory.setQuartzProperties(quartzProperties());
factory.setTriggers(sampleJobTrigger);
factory.setAutoStartup(false);
return factory;
}
}

View File

@@ -1,5 +1,6 @@
package it.fabioformosa.quartzmanager.configuration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
@@ -7,19 +8,20 @@ import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBr
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
@Configuration
@ComponentScan(basePackages = {"it.fabioformosa.quartzmanager.aspects"})
@EnableWebSocketMessageBroker
public class WebsocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/job");
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/job");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/logs").setAllowedOrigins("/**").withSockJS();
registry.addEndpoint("/progress").setAllowedOrigins("/**").withSockJS();
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/quartz-manager/logs").setAllowedOrigins("/**").withSockJS();
registry.addEndpoint("/quartz-manager/progress").setAllowedOrigins("/**").withSockJS();
}
}

View File

@@ -36,119 +36,119 @@ import it.fabioformosa.quartzmanager.scheduler.TriggerMonitor;
*
*/
@RestController
@RequestMapping("/scheduler")
@RequestMapping("/quartz-manager/scheduler")
@Api(value = "scheduler")
public class SchedulerController {
private static final int MILLS_IN_A_DAY = 1000 * 60 * 60 * 24;
private static final int SEC_IN_A_DAY = 60 * 60 * 24;
private static final int MILLS_IN_A_DAY = 1000 * 60 * 60 * 24;
private static final int SEC_IN_A_DAY = 60 * 60 * 24;
private final Logger log = LoggerFactory.getLogger(SchedulerController.class);
private final Logger log = LoggerFactory.getLogger(SchedulerController.class);
@Resource
private Scheduler scheduler;
@Resource
private Scheduler scheduler;
@Resource
private TriggerMonitor triggerMonitor;
@Resource
private TriggerMonitor triggerMonitor;
@GetMapping("/config")
public SchedulerConfigParam getConfig() {
log.debug("SCHEDULER - GET CONFIG params");
SimpleTrigger simpleTrigger = (SimpleTrigger) triggerMonitor.getTrigger();
private long fromMillsIntervalToTriggerPerDay(long repeatIntervalInMills) {
return (int) Math.ceil(MILLS_IN_A_DAY / repeatIntervalInMills);
}
int maxCount = simpleTrigger.getRepeatCount() + 1;
long triggersPerDay = fromMillsIntervalToTriggerPerDay(simpleTrigger.getRepeatInterval());
private int fromTriggerPerDayToMillsInterval(long triggerPerDay) {
return (int) Math.ceil(Long.valueOf(MILLS_IN_A_DAY) / triggerPerDay); // with ceil the triggerPerDay is a max value
}
return new SchedulerConfigParam(triggersPerDay, maxCount);
}
@SuppressWarnings("unused")
private int fromTriggerPerDayToSecInterval(long triggerPerDay) {
return (int) Math.ceil(Long.valueOf(SEC_IN_A_DAY) / triggerPerDay);
}
@GetMapping("/progress")
public TriggerStatus getProgressInfo() throws SchedulerException {
log.trace("SCHEDULER - GET PROGRESS INFO");
TriggerStatus progress = new TriggerStatus();
@GetMapping("/config")
public SchedulerConfigParam getConfig() {
log.debug("SCHEDULER - GET CONFIG params");
SimpleTrigger simpleTrigger = (SimpleTrigger) triggerMonitor.getTrigger();
SimpleTriggerImpl jobTrigger = (SimpleTriggerImpl) scheduler.getTrigger(triggerMonitor.getTrigger().getKey());
if (jobTrigger != null && jobTrigger.getJobKey() != null) {
progress.setJobKey(jobTrigger.getJobKey().getName());
progress.setJobClass(jobTrigger.getClass().getSimpleName());
progress.setTimesTriggered(jobTrigger.getTimesTriggered());
progress.setRepeatCount(jobTrigger.getRepeatCount());
progress.setFinalFireTime(jobTrigger.getFinalFireTime());
progress.setNextFireTime(jobTrigger.getNextFireTime());
progress.setPreviousFireTime(jobTrigger.getPreviousFireTime());
}
int maxCount = simpleTrigger.getRepeatCount() + 1;
long triggersPerDay = fromMillsIntervalToTriggerPerDay(simpleTrigger.getRepeatInterval());
return progress;
}
return new SchedulerConfigParam(triggersPerDay, maxCount);
}
@GetMapping(produces = "application/json")
public Map<String, String> getStatus() throws SchedulerException {
log.trace("SCHEDULER - GET STATUS");
String schedulerState = "";
if (scheduler.isShutdown() || !scheduler.isStarted())
schedulerState = SchedulerStates.STOPPED.toString();
else if (scheduler.isStarted() && scheduler.isInStandbyMode())
schedulerState = SchedulerStates.PAUSED.toString();
else
schedulerState = SchedulerStates.RUNNING.toString();
return Collections.singletonMap("data", schedulerState.toLowerCase());
}
@GetMapping("/progress")
public TriggerStatus getProgressInfo() throws SchedulerException {
log.trace("SCHEDULER - GET PROGRESS INFO");
TriggerStatus progress = new TriggerStatus();
@GetMapping("/pause")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void pause() throws SchedulerException {
log.info("SCHEDULER - PAUSE COMMAND");
scheduler.standby();
}
SimpleTriggerImpl jobTrigger = (SimpleTriggerImpl) scheduler.getTrigger(triggerMonitor.getTrigger().getKey());
if (jobTrigger != null && jobTrigger.getJobKey() != null) {
progress.setJobKey(jobTrigger.getJobKey().getName());
progress.setJobClass(jobTrigger.getClass().getSimpleName());
progress.setTimesTriggered(jobTrigger.getTimesTriggered());
progress.setRepeatCount(jobTrigger.getRepeatCount());
progress.setFinalFireTime(jobTrigger.getFinalFireTime());
progress.setNextFireTime(jobTrigger.getNextFireTime());
progress.setPreviousFireTime(jobTrigger.getPreviousFireTime());
}
@PostMapping("/config")
public SchedulerConfigParam postConfig(@RequestBody SchedulerConfigParam config) throws SchedulerException {
log.info("SCHEDULER - NEW CONFIG {}", config);
SimpleTrigger trigger = (SimpleTrigger) triggerMonitor.getTrigger();
return progress;
}
TriggerBuilder<SimpleTrigger> triggerBuilder = trigger.getTriggerBuilder();
@GetMapping(produces = "application/json")
public Map<String, String> getStatus() throws SchedulerException {
log.trace("SCHEDULER - GET STATUS");
String schedulerState = "";
if (scheduler.isShutdown() || !scheduler.isStarted())
schedulerState = SchedulerStates.STOPPED.toString();
else if (scheduler.isStarted() && scheduler.isInStandbyMode())
schedulerState = SchedulerStates.PAUSED.toString();
else
schedulerState = SchedulerStates.RUNNING.toString();
return Collections.singletonMap("data", schedulerState.toLowerCase());
}
int intervalInMills = fromTriggerPerDayToMillsInterval(config.getTriggerPerDay());
Trigger newTrigger = triggerBuilder.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInMilliseconds(intervalInMills).withRepeatCount(config.getMaxCount() - 1)).build();
@GetMapping("/pause")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void pause() throws SchedulerException {
log.info("SCHEDULER - PAUSE COMMAND");
scheduler.standby();
}
scheduler.rescheduleJob(triggerMonitor.getTrigger().getKey(), newTrigger);
triggerMonitor.setTrigger(newTrigger);
return config;
}
@PostMapping("/config")
public SchedulerConfigParam postConfig(@RequestBody SchedulerConfigParam config) throws SchedulerException {
log.info("SCHEDULER - NEW CONFIG {}", config);
SimpleTrigger trigger = (SimpleTrigger) triggerMonitor.getTrigger();
@GetMapping("/resume")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void resume() throws SchedulerException {
log.info("SCHEDULER - RESUME COMMAND");
scheduler.start();
}
TriggerBuilder<SimpleTrigger> triggerBuilder = trigger.getTriggerBuilder();
@GetMapping("/run")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void run() throws SchedulerException {
log.info("SCHEDULER - START COMMAND");
scheduler.start();
}
int intervalInMills = fromTriggerPerDayToMillsInterval(config.getTriggerPerDay());
Trigger newTrigger = triggerBuilder.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInMilliseconds(intervalInMills).withRepeatCount(config.getMaxCount() - 1)).build();
@GetMapping("/stop")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void stop() throws SchedulerException {
log.info("SCHEDULER - STOP COMMAND");
scheduler.shutdown(true);
}
scheduler.rescheduleJob(triggerMonitor.getTrigger().getKey(), newTrigger);
triggerMonitor.setTrigger(newTrigger);
return config;
}
private long fromMillsIntervalToTriggerPerDay(long repeatIntervalInMills) {
return (int) Math.ceil(MILLS_IN_A_DAY / repeatIntervalInMills);
}
@GetMapping("/resume")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void resume() throws SchedulerException {
log.info("SCHEDULER - RESUME COMMAND");
scheduler.start();
}
private int fromTriggerPerDayToMillsInterval(long triggerPerDay) {
return (int) Math.ceil(Long.valueOf(MILLS_IN_A_DAY) / triggerPerDay); // with ceil the triggerPerDay is a max value
}
@GetMapping("/run")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void run() throws SchedulerException {
log.info("SCHEDULER - START COMMAND");
scheduler.start();
}
@SuppressWarnings("unused")
private int fromTriggerPerDayToSecInterval(long triggerPerDay) {
return (int) Math.ceil(Long.valueOf(SEC_IN_A_DAY) / triggerPerDay);
}
@GetMapping("/stop")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void stop() throws SchedulerException {
log.info("SCHEDULER - STOP COMMAND");
scheduler.shutdown(true);
}
}

View File

@@ -7,10 +7,10 @@ import org.springframework.stereotype.Controller;
@Controller
public class WebsocketController {
@MessageMapping({ "/logs", "/progress" })
@SendTo("/topic/logs")
public String subscribe() throws Exception {
return "subscribed";
}
@MessageMapping({ "/quartz-manager/logs", "/quartz-manager/progress" })
@SendTo("/topic/logs")
public String subscribe() throws Exception {
return "subscribed";
}
}

View File

@@ -0,0 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
it.fabioformosa.quartzmanager.configuration.SchedulerConfig,\
it.fabioformosa.quartzmanager.configuration.SwaggerConfig,\
it.fabioformosa.quartzmanager.configuration.WebsocketConfig

View File

@@ -37,9 +37,9 @@ import it.fabioformosa.quartzmanager.security.helpers.impl.JwtAuthenticationSucc
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.QuartzManagerHttpSecurity;
import it.fabioformosa.quartzmanager.security.helpers.impl.JwtUsernamePasswordFiterLoginConfig;
import it.fabioformosa.quartzmanager.security.helpers.impl.LogoutSuccess;
import it.fabioformosa.quartzmanager.security.helpers.impl.QuartzManagerHttpSecurity;
/**
*
@@ -51,142 +51,142 @@ import it.fabioformosa.quartzmanager.security.helpers.impl.LogoutSuccess;
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfigJWT extends WebSecurityConfigurerAdapter {
private static final String[] PATTERNS_SWAGGER_UI = {"/swagger-ui.html", "/v2/api-docs", "/swagger-resources/**", "/webjars/**"};
private static final String[] PATTERNS_SWAGGER_UI = {"/swagger-ui.html", "/v2/api-docs", "/swagger-resources/**", "/webjars/**"};
private static final String LOGIN_PATH = "/api/login";
private static final String LOGOUT_PATH = "/api/logout";
private static final String LOGIN_PATH = "/quartz-manager/api/login";
private static final String LOGOUT_PATH = "/quartz-manager/api/logout";
@Value("${server.servlet.context-path}")
private String contextPath;
@Value("${server.servlet.context-path}")
private String contextPath;
@Value("${app.name}")
private String APP_NAME;
@Value("${app.name}")
private String APP_NAME;
@Value("${quartz-manager.security.login-model.form-login-enabled}")
private Boolean formLoginEnabled;
@Value("${quartz-manager.security.login-model.userpwd-filter-enabled}")
private Boolean userpwdFilterEnabled;
@Value("${quartz-manager.security.login-model.form-login-enabled}")
private Boolean formLoginEnabled;
@Value("${quartz-manager.security.login-model.userpwd-filter-enabled}")
private Boolean userpwdFilterEnabled;
@Autowired
private JwtSecurityProperties jwtSecurityProps;
@Autowired
private JwtSecurityProperties jwtSecurityProps;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private InMemoryAccountProperties inMemoryAccountProps;
@Autowired
private InMemoryAccountProperties inMemoryAccountProps;
@Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder)throws Exception {
configureInMemoryAuthentication(authenticationManagerBuilder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() //
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() //
.exceptionHandling().authenticationEntryPoint(restAuthEntryPoint()).and() //
.addFilterBefore(jwtAuthenticationTokenFilter(), BasicAuthenticationFilter.class) //
.authorizeRequests().anyRequest().authenticated();
QuartzManagerHttpSecurity.from(http).withLoginConfigurer(loginConfigurer(), logoutConfigurer()) //
.login(LOGIN_PATH, authenticationManager()).logout(LOGOUT_PATH);
// temporary disabled csfr
// http.csrf().ignoringAntMatchers("/api/login", "/api/signup") //
// .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) //
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()//
.antMatchers(HttpMethod.GET, PATTERNS_SWAGGER_UI) //
.antMatchers(HttpMethod.GET,"/css/**", "/js/**", "/img/**", "/lib/**");
}
private void configureInMemoryAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
if(inMemoryAccountProps.isEnabled() && inMemoryAccountProps.getUsers() != null && !inMemoryAccountProps.getUsers().isEmpty()) {
InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuth = authenticationManagerBuilder.inMemoryAuthentication();
inMemoryAccountProps.getUsers()
.forEach(u -> inMemoryAuth
.withUser(u.getName())
.password(encoder.encode(u.getPassword()))
.roles(u.getRoles().toArray(new String[0])));
@Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder)throws Exception {
configureInMemoryAuthentication(authenticationManagerBuilder);
}
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() //
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() //
.exceptionHandling().authenticationEntryPoint(restAuthEntryPoint()).and() //
.addFilterBefore(jwtAuthenticationTokenFilter(), BasicAuthenticationFilter.class) //
.authorizeRequests().anyRequest().authenticated();
@Bean
public LoginConfigurer formLoginConfigurer() {
JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler = jwtAuthenticationSuccessHandler();
AuthenticationSuccessHandler authenticationSuccessHandler = new AuthenticationSuccessHandler(jwtAuthenticationSuccessHandler);
AuthenticationFailureHandler authenticationFailureHandler = new AuthenticationFailureHandler();
LoginConfigurer loginConfigurer = new FormLoginConfig(authenticationSuccessHandler, authenticationFailureHandler);
return loginConfigurer;
}
QuartzManagerHttpSecurity.from(http).withLoginConfigurer(loginConfigurer(), logoutConfigurer()) //
.login(LOGIN_PATH, authenticationManager()).logout(LOGOUT_PATH);
@Bean
public JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler() {
JwtTokenHelper jwtTokenHelper = jwtTokenHelper();
JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler = new JwtAuthenticationSuccessHandlerImpl(contextPath, jwtSecurityProps, jwtTokenHelper, objectMapper);
return jwtAuthenticationSuccessHandler;
}
// temporary disabled csfr
// http.csrf().ignoringAntMatchers("/api/login", "/api/signup") //
// .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) //
}
@Bean
public JwtTokenAuthenticationFilter jwtAuthenticationTokenFilter() throws Exception {
return new JwtTokenAuthenticationFilter(jwtTokenHelper(), userDetailsService);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()//
.antMatchers(HttpMethod.GET, PATTERNS_SWAGGER_UI) //
.antMatchers(HttpMethod.GET,"/css/**", "/js/**", "/img/**", "/lib/**");
}
@Bean
public JwtTokenHelper jwtTokenHelper() {
return new JwtTokenHelper(APP_NAME, jwtSecurityProps);
}
private void configureInMemoryAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
if(inMemoryAccountProps.isEnabled() && inMemoryAccountProps.getUsers() != null && !inMemoryAccountProps.getUsers().isEmpty()) {
InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuth = authenticationManagerBuilder.inMemoryAuthentication();
inMemoryAccountProps.getUsers()
.forEach(u -> inMemoryAuth
.withUser(u.getName())
.password(encoder.encode(u.getPassword()))
.roles(u.getRoles().toArray(new String[0])));
}
}
@Bean
public LoginConfigurer loginConfigurer() {
if(BooleanUtils.isTrue(userpwdFilterEnabled))
return userpwdFilterLoginConfigurer();
if(BooleanUtils.isNotFalse(formLoginEnabled))
return formLoginConfigurer();
throw new RuntimeException("No login configurer enabled!");
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
@Bean
public LogoutSuccess logoutConfigurer() {
return new LogoutSuccess(objectMapper);
}
@Bean
public LoginConfigurer formLoginConfigurer() {
JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler = jwtAuthenticationSuccessHandler();
AuthenticationSuccessHandler authenticationSuccessHandler = new AuthenticationSuccessHandler(jwtAuthenticationSuccessHandler);
AuthenticationFailureHandler authenticationFailureHandler = new AuthenticationFailureHandler();
LoginConfigurer loginConfigurer = new FormLoginConfig(authenticationSuccessHandler, authenticationFailureHandler);
return loginConfigurer;
}
@Bean
public AuthenticationEntryPoint restAuthEntryPoint() {
return new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED);
}
@Bean
public JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler() {
JwtTokenHelper jwtTokenHelper = jwtTokenHelper();
JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler = new JwtAuthenticationSuccessHandlerImpl(contextPath, jwtSecurityProps, jwtTokenHelper, objectMapper);
return jwtAuthenticationSuccessHandler;
}
@Bean
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
@Bean
public JwtTokenAuthenticationFilter jwtAuthenticationTokenFilter() throws Exception {
return new JwtTokenAuthenticationFilter(jwtTokenHelper(), userDetailsService);
}
@Bean
public LoginConfigurer userpwdFilterLoginConfigurer() {
LoginConfigurer loginConfigurer = new JwtUsernamePasswordFiterLoginConfig(jwtAuthenticationSuccessHandler());
return loginConfigurer;
}
@Bean
public JwtTokenHelper jwtTokenHelper() {
return new JwtTokenHelper(APP_NAME, jwtSecurityProps);
}
// @Bean
// public PasswordEncoder passwordEncoder() {
// return new BCryptPasswordEncoder();
// }
@Bean
public LoginConfigurer loginConfigurer() {
if(BooleanUtils.isTrue(userpwdFilterEnabled))
return userpwdFilterLoginConfigurer();
if(BooleanUtils.isNotFalse(formLoginEnabled))
return formLoginConfigurer();
throw new RuntimeException("No login configurer enabled!");
}
@Bean
public LogoutSuccess logoutConfigurer() {
return new LogoutSuccess(objectMapper);
}
@Bean
public AuthenticationEntryPoint restAuthEntryPoint() {
return new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED);
}
@Bean
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
@Bean
public LoginConfigurer userpwdFilterLoginConfigurer() {
LoginConfigurer loginConfigurer = new JwtUsernamePasswordFiterLoginConfig(jwtAuthenticationSuccessHandler());
return loginConfigurer;
}
// @Bean
// public PasswordEncoder passwordEncoder() {
// return new BCryptPasswordEncoder();
// }
}

View File

@@ -8,66 +8,66 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/api", produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(value = "/quartz-manager/api", produces = MediaType.APPLICATION_JSON_VALUE)
public class UserController {
/**
* JWT Temporary disabled
*
* @author Fabio.Formosa
*
*/
/**
* JWT Temporary disabled
*
* @author Fabio.Formosa
*
*/
// @Autowired
// private UserService userService;
// @Autowired
// private UserService userService;
// @RequestMapping(method = POST, value = "/signup")
// public ResponseEntity<?> addUser(@RequestBody UserRequest userRequest,
// UriComponentsBuilder ucBuilder) {
//
// User existUser = this.userService.findByUsername(userRequest.getUsername());
// if (existUser != null)
// throw new ResourceConflictException(userRequest.getId(), "Username already exists");
// User user = this.userService.save(userRequest);
// HttpHeaders headers = new HttpHeaders();
// headers.setLocation(ucBuilder.path("/api/user/{userId}").buildAndExpand(user.getId()).toUri());
// return new ResponseEntity<>(user, HttpStatus.CREATED);
// }
//
// @RequestMapping(method = GET, value = "/user/all")
// public List<User> loadAll() {
// return this.userService.findAll();
// }
//
// @RequestMapping(method = GET, value = "/user/{userId}")
// public User loadById(@PathVariable Long userId) {
// return this.userService.findById(userId);
// }
//
//
// @RequestMapping(method = GET, value = "/user/reset-credentials")
// public ResponseEntity<Map> resetCredentials() {
// this.userService.resetCredentials();
// Map<String, String> result = new HashMap<>();
// result.put("result", "success");
// return ResponseEntity.accepted().body(result);
// }
// @RequestMapping(method = POST, value = "/signup")
// public ResponseEntity<?> addUser(@RequestBody UserRequest userRequest,
// UriComponentsBuilder ucBuilder) {
//
// User existUser = this.userService.findByUsername(userRequest.getUsername());
// if (existUser != null)
// throw new ResourceConflictException(userRequest.getId(), "Username already exists");
// User user = this.userService.save(userRequest);
// HttpHeaders headers = new HttpHeaders();
// headers.setLocation(ucBuilder.path("/api/user/{userId}").buildAndExpand(user.getId()).toUri());
// return new ResponseEntity<>(user, HttpStatus.CREATED);
// }
//
// @RequestMapping(method = GET, value = "/user/all")
// public List<User> loadAll() {
// return this.userService.findAll();
// }
//
// @RequestMapping(method = GET, value = "/user/{userId}")
// public User loadById(@PathVariable Long userId) {
// return this.userService.findById(userId);
// }
//
//
// @RequestMapping(method = GET, value = "/user/reset-credentials")
// public ResponseEntity<Map> resetCredentials() {
// this.userService.resetCredentials();
// Map<String, String> result = new HashMap<>();
// result.put("result", "success");
// return ResponseEntity.accepted().body(result);
// }
/*
* We are not using userService.findByUsername here(we could), so it is good that we are making
* sure that the user has role "ROLE_USER" to access this endpoint.
*/
// @RequestMapping("/whoami")
// // @PreAuthorize("hasRole('USER')")
// public User user() {
// return (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
// }
/*
* We are not using userService.findByUsername here(we could), so it is good that we are making
* sure that the user has role "ROLE_USER" to access this endpoint.
*/
// @RequestMapping("/whoami")
// // @PreAuthorize("hasRole('USER')")
// public User user() {
// return (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
// }
@GetMapping("/whoami")
@PreAuthorize("isAuthenticated()")
public Object user() {
return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
@GetMapping("/whoami")
@PreAuthorize("isAuthenticated()")
public Object user() {
return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
}

View File

@@ -1,6 +1,6 @@
server:
servlet:
context-path: /quartz-manager
context-path: /
session.timeout : 28800
port: 8080