TSK-177 Add authentication and login page for taskana spring rest example

This commit is contained in:
Martin Rojas Miguel Angel 2018-05-14 17:27:06 +02:00 committed by Holger Hagen
parent 66bdaab7cf
commit 99b882f867
54 changed files with 721 additions and 272 deletions

View File

@ -1,19 +1,20 @@
<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>
<groupId>pro.taskana</groupId>
<artifactId>taskana-rest-parent</artifactId>
<version>0.1.4-SNAPSHOT</version>
<packaging>pom</packaging>
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>
<groupId>pro.taskana</groupId>
<artifactId>taskana-rest-parent</artifactId>
<version>0.1.4-SNAPSHOT</version>
<packaging>pom</packaging>
<description>This pom is parent to all taskana rest modules and serves the common build.</description>
<description>This pom is parent to all taskana rest modules and serves the common build.</description>
<properties>
<java.version>1.8</java.version>
</properties>
<properties>
<java.version>1.8</java.version>
</properties>
<modules>
<module>taskana-rest-spring</module>
<module>taskana-rest-spring-example</module>
</modules>
<modules>
<module>taskana-rest-spring</module>
<module>../web</module>
<module>taskana-rest-spring-example</module>
</modules>
</project>

View File

@ -52,7 +52,16 @@
<artifactId>h2</artifactId>
<version>1.4.197</version>
</dependency>
<!--
Since taskana-web packs its content in /static, we do not have to unpack it again.
However, when any local change has to be done to that folder you have to copy
target/classes/static manually from taskana-web.
-->
<dependency>
<groupId>pro.taskana</groupId>
<artifactId>taskana-web</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Tests -->
<dependency>
<groupId>org.springframework.boot</groupId>
@ -60,7 +69,6 @@
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
@ -69,4 +77,5 @@
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,12 @@
package pro.taskana.rest.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class ViewController {
@RequestMapping({ "/administration*/**", "/workplace*/**", "/monitor*/**" })
public String index() {
return "forward:/index.html";
}
}

View File

@ -0,0 +1,25 @@
package pro.taskana.rest.models;
public class User {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@ -10,13 +10,14 @@ import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import pro.taskana.security.GroupPrincipal;
import pro.taskana.security.UserPrincipal;
/**
* TODO.
*/
public class SampleLoginModule implements LoginModule {
public class SampleLoginModule extends UsernamePasswordAuthenticationFilter implements LoginModule {
private NameCallback nameCallback;
@ -24,6 +25,7 @@ public class SampleLoginModule implements LoginModule {
private Subject subject;
@Override
public boolean abort() throws LoginException {
return true;

View File

@ -1,5 +1,6 @@
package pro.taskana.rest.security;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -11,13 +12,16 @@ import org.springframework.security.authentication.jaas.JaasAuthenticationProvid
import org.springframework.security.authentication.jaas.JaasNameCallbackHandler;
import org.springframework.security.authentication.jaas.JaasPasswordCallbackHandler;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@ -28,26 +32,43 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${devMode}")
private boolean devMode;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authenticationProvider(jaasAuthProvider())
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/**")
.authenticated()
.and()
.httpBasic()
.and()
.addFilter(new JaasApiIntegrationFilter());
.disable()
.authenticationProvider(jaasAuthProvider())
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/**")
.authenticated()
.and()
.httpBasic()
.and()
.addFilter(new JaasApiIntegrationFilter());
if (devMode) {
return;
}
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll()
.and()
.logout()
.permitAll();
}
@Bean
public JaasAuthenticationProvider jaasAuthProvider() {
JaasAuthenticationProvider authenticationProvider = new JaasAuthenticationProvider();
authenticationProvider.setAuthorityGranters(new AuthorityGranter[] {new SampleRoleGranter()});
authenticationProvider.setCallbackHandlers(new JaasAuthenticationCallbackHandler[] {
new JaasNameCallbackHandler(), new JaasPasswordCallbackHandler()});
authenticationProvider.setAuthorityGranters(new AuthorityGranter[]{new SampleRoleGranter()});
authenticationProvider.setCallbackHandlers(new JaasAuthenticationCallbackHandler[]{
new JaasNameCallbackHandler(), new JaasPasswordCallbackHandler()});
authenticationProvider.setLoginContextName("taskana");
authenticationProvider.setLoginConfig(new ClassPathResource("pss_jaas.config"));
return authenticationProvider;
@ -72,6 +93,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.addAllowedMethod("POST");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0);

View File

@ -4,3 +4,4 @@ datasource.url=jdbc:h2:mem:taskana;IGNORECASE=TRUE;LOCK_MODE=0;INIT=CREATE SCHEM
datasource.driverClassName=org.h2.Driver
datasource.username=sa
datasource.password=sa
devMode=false

View File

@ -0,0 +1,27 @@
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Custom Login Page</title>
</head>
<body>
<div>
<form name="f" th:action="@{/login}" method="post">
<fieldset>
<legend>Please Login</legend>
<div th:if="${param.error}" class="alert alert-error">
Invalid username and password.
</div>
<div th:if="${param.logout}" class="alert alert-success">
You have been logged out.
</div>
<label for="username">Username</label>
<input type="text" id="username" name="username"/>
<label for="password">Password</label>
<input type="password" id="password" name="password"/>
<div class="form-actions">
<button type="submit" class="btn">Log in</button>
</div>
</fieldset>
</form>
</div>
</body>
</html>

View File

@ -39,7 +39,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import pro.taskana.rest.resource.ClassificationSummaryResource;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {"devMode=true"})
@Import(RestConfiguration.class)
public class ClassificationControllerIntTest {

View File

@ -43,7 +43,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import pro.taskana.rest.resource.TaskSummaryResource;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {"devMode=true"})
@Import(RestConfiguration.class)
public class TaskControllerIntTest {

View File

@ -32,7 +32,7 @@ import pro.taskana.TaskanaRole;
import pro.taskana.rest.resource.TaskanaUserInfoResource;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {"devMode=true"})
@Import(RestConfiguration.class)
public class TaskanaEngineControllerIntTest {

View File

@ -40,7 +40,7 @@ import pro.taskana.rest.resource.DistributionTargetResource;
import pro.taskana.rest.resource.WorkbasketSummaryResource;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {"devMode=true"})
@Import(RestConfiguration.class)
public class WorkbasketControllerIntTest {

32
web/package-lock.json generated
View File

@ -1221,7 +1221,17 @@
"boom": {
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
"integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8="
"integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
"requires": {
"hoek": "2.16.3"
},
"dependencies": {
"hoek": {
"version": "2.16.3",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
"integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
}
}
},
"bootstrap": {
"version": "3.3.7",
@ -5168,7 +5178,15 @@
"requires": {
"boom": "2.10.1",
"cryptiles": "2.0.5",
"hoek": "2.16.3",
"sntp": "1.0.9"
},
"dependencies": {
"hoek": {
"version": "2.16.3",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
"integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
}
}
},
"he": {
@ -10004,7 +10022,17 @@
"sntp": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
"integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg="
"integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
"requires": {
"hoek": "2.16.3"
},
"dependencies": {
"hoek": {
"version": "2.16.3",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
"integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
}
}
},
"socket.io": {
"version": "2.0.4",

View File

@ -41,7 +41,7 @@ const routes: Routes = [
{
path: 'classifications',
component: MasterAndDetailComponent,
canActivate: [DomainGuard],
canActivate: [DomainGuard],
children: [
{
path: '',

View File

@ -3,7 +3,6 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Ng2AutoCompleteModule } from 'ng2-auto-complete';
import { HttpClientModule } from '@angular/common/http';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { AlertModule } from 'ngx-bootstrap';
import { SharedModule } from 'app/shared/shared.module';
@ -45,11 +44,10 @@ const MODULES = [
CommonModule,
FormsModule,
Ng2AutoCompleteModule,
HttpClientModule,
AngularSvgIconModule,
AlertModule,
SharedModule,
AdministrationRoutingModule,
AdministrationRoutingModule
];
const DECLARATIONS = [

View File

@ -4,23 +4,19 @@ import { Injectable } from '@angular/core';
import { environment } from 'environments/environment';
import { Observable } from 'rxjs/Observable';
import { ReplaySubject } from 'rxjs/ReplaySubject';
@Injectable()
export class ClassificationCategoriesService {
private url = environment.taskanaRestUrl + '/v1/classification-categories';
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Basic VEVBTUxFQURfMTpURUFNTEVBRF8x'
})
};
private dataObs$ = new ReplaySubject<Array<string>>(1);
constructor(private httpClient: HttpClient) { }
constructor(
private httpClient: HttpClient) { }
getCategories(forceRefresh = false): Observable<Array<string>> {
if (!this.dataObs$.observers.length || forceRefresh) {
this.httpClient.get<Array<string>>(this.url, this.httpOptions).subscribe(
this.httpClient.get<Array<string>>(this.url).subscribe(
data => this.dataObs$.next(data),
error => {
this.dataObs$.error(error);

View File

@ -11,21 +11,16 @@ import { TaskanaDate } from 'app/shared/util/taskana.date';
export class ClassificationDefinitionService {
url = environment.taskanaRestUrl + '/v1/classificationdefinitions';
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Basic VEVBTUxFQURfMTpURUFNTEVBRF8x'
})
};
constructor(private httpClient: HttpClient, private alertService: AlertService) {
constructor(
private httpClient: HttpClient,
private alertService: AlertService
) {
}
// GET
exportClassifications(domain: string) {
domain = (domain === '' ? '' : '?domain=' + domain);
this.httpClient.get<ClassificationDefinition[]>(this.url + domain, this.httpOptions)
this.httpClient.get<ClassificationDefinition[]>(this.url + domain)
.subscribe(
response => saveAs(new Blob([JSON.stringify(response)], { type: 'text/plain;charset=utf-8' }),
'Classifications_' + TaskanaDate.getDate() + '.json')
@ -36,7 +31,7 @@ export class ClassificationDefinitionService {
// TODO handle error
importClassifications(classifications: any) {
this.httpClient.post(this.url + '/import',
JSON.parse(classifications), this.httpOptions).subscribe(
JSON.parse(classifications)).subscribe(
classificationsUpdated => this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, 'Import was successful')),
error => this.alertService.triggerAlert(new AlertModel(AlertType.DANGER, 'Import was not successful'))
);

View File

@ -9,22 +9,17 @@ import { ReplaySubject } from 'rxjs/ReplaySubject';
@Injectable()
export class ClassificationTypesService {
private url = environment.taskanaRestUrl + '/v1/classification-types';
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Basic VEVBTUxFQURfMTpURUFNTEVBRF8x'
})
};
private classificationTypeSelectedValue = 'TASK';
private classificationTypeSelected = new BehaviorSubject<string>(this.classificationTypeSelectedValue);
private dataObs$ = new ReplaySubject<Array<string>>(1);
constructor(private httpClient: HttpClient) { }
constructor(private httpClient: HttpClient
) { }
getClassificationTypes(forceRefresh = false): Observable<Array<string>> {
if (!this.dataObs$.observers.length || forceRefresh) {
this.httpClient.get<Array<string>>(this.url, this.httpOptions).subscribe(
this.httpClient.get<Array<string>>(this.url).subscribe(
data => this.dataObs$.next(data),
error => {
this.dataObs$.error(error);

View File

@ -22,13 +22,6 @@ export class ClassificationsService {
private classificationSelected = new Subject<string>();
private classificationSaved = new Subject<number>();
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Basic VEVBTUxFQURfMTpURUFNTEVBRF8x'
})
};
private classificationTypes: Array<string>;
constructor(
@ -42,8 +35,7 @@ export class ClassificationsService {
return this.domainService.getSelectedDomain().mergeMap(domain => {
const classificationTypes = this.classificationTypeService.getSelectedClassificationType();
return this.getClassificationObservable(this.httpClient.get<ClassificationResource>(
`${environment.taskanaRestUrl}/v1/classifications/?domain=${domain}`,
this.httpOptions));
`${environment.taskanaRestUrl}/v1/classifications/?domain=${domain}`));
}).do(() => {
this.domainService.domainChangedComplete();
@ -52,7 +44,7 @@ export class ClassificationsService {
// GET
getClassification(id: string): Observable<ClassificationDefinition> {
return this.httpClient.get<ClassificationDefinition>(`${environment.taskanaRestUrl}/v1/classifications/${id}`, this.httpOptions)
return this.httpClient.get<ClassificationDefinition>(`${environment.taskanaRestUrl}/v1/classifications/${id}`)
.do((classification: ClassificationDefinition) => {
if (classification) {
this.classificationTypeService.selectClassificationType(classification.type);
@ -63,18 +55,17 @@ export class ClassificationsService {
// POST
postClassification(classification: Classification): Observable<Classification> {
return this.httpClient.post<Classification>(`${environment.taskanaRestUrl}/v1/classifications`, classification,
this.httpOptions);
return this.httpClient.post<Classification>(`${environment.taskanaRestUrl}/v1/classifications`, classification);
}
// PUT
putClassification(url: string, classification: Classification): Observable<Classification> {
return this.httpClient.put<Classification>(url, classification, this.httpOptions);
return this.httpClient.put<Classification>(url, classification);
}
// DELETE
deleteClassification(url: string): Observable<string> {
return this.httpClient.delete<string>(url, this.httpOptions);
return this.httpClient.delete<string>(url);
}
// #region "Service extras"

View File

@ -1,37 +1,29 @@
import {Injectable} from '@angular/core';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {environment} from 'app/../environments/environment';
import {saveAs} from 'file-saver/FileSaver';
import {AlertService} from 'app/services/alert/alert.service';
import {WorkbasketDefinition} from 'app/models/workbasket-definition';
import {AlertModel, AlertType} from 'app/models/alert';
import {TaskanaDate} from 'app/shared/util/taskana.date';
import {ErrorModel} from 'app/models/modal-error';
import {ErrorModalService} from 'app/services/errorModal/error-modal.service';
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { environment } from 'app/../environments/environment';
import { saveAs } from 'file-saver/FileSaver';
import { AlertService } from 'app/services/alert/alert.service';
import { WorkbasketDefinition } from 'app/models/workbasket-definition';
import { AlertModel, AlertType } from 'app/models/alert';
import { TaskanaDate } from 'app/shared/util/taskana.date';
import { ErrorModel } from 'app/models/modal-error';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
@Injectable()
export class WorkbasketDefinitionService {
url: string = environment.taskanaRestUrl + '/v1/workbasketdefinitions';
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Basic VEVBTUxFQURfMTpURUFNTEVBRF8x'
})
};
constructor(private httpClient: HttpClient, private alertService: AlertService,
private errorModalService: ErrorModalService) {
}
private errorModalService: ErrorModalService) {
}
// GET
exportWorkbaskets(domain: string) {
domain = (domain === '' ? '' : '?domain=' + domain);
this.httpClient.get<WorkbasketDefinition[]>(this.url + domain, this.httpOptions).subscribe(
this.httpClient.get<WorkbasketDefinition[]>(this.url + domain).subscribe(
response => {
saveAs(new Blob([JSON.stringify(response)], {type: 'text/plain;charset=utf-8'}),
saveAs(new Blob([JSON.stringify(response)], { type: 'text/plain;charset=utf-8' }),
'Workbaskets_' + TaskanaDate.getDate() + '.json');
}
);
@ -40,10 +32,10 @@ export class WorkbasketDefinitionService {
// POST
importWorkbasketDefinitions(workbasketDefinitions: any) {
this.httpClient.post(environment.taskanaRestUrl + '/v1/workbasketdefinitions/import',
JSON.parse(workbasketDefinitions), this.httpOptions).subscribe(
workbasketsUpdated => this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, 'Import was successful')),
error => this.errorModalService.triggerError(new ErrorModel(
`There was an error importing workbaskets`, error.message))
);
JSON.parse(workbasketDefinitions)).subscribe(
workbasketsUpdated => this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, 'Import was successful')),
error => this.errorModalService.triggerError(new ErrorModel(
`There was an error importing workbaskets`, error.message))
);
}
}

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { environment } from 'app/../environments/environment';
@ -43,12 +43,6 @@ export class WorkbasketService {
// Domain
readonly DOMAIN = 'domain';
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Basic VEVBTUxFQURfMTpURUFNTEVBRF8x'
})
};
page = 1;
pageSize = 9;
@ -86,7 +80,7 @@ export class WorkbasketService {
`${environment.taskanaRestUrl}/v1/workbaskets/${this.getWorkbasketSummaryQueryParameters(
sortBy, order, name,
nameLike, descLike, owner, ownerLike, type, key, keyLike, requiredPermission,
!allPages ? this.page : undefined, !allPages ? this.pageSize : undefined, domain)}`, this.httpOptions)
!allPages ? this.page : undefined, !allPages ? this.pageSize : undefined, domain)}`)
.do(workbaskets => {
return workbaskets;
});
@ -96,51 +90,50 @@ export class WorkbasketService {
}
// GET
getWorkBasket(id: string): Observable<Workbasket> {
return this.httpClient.get<Workbasket>(`${environment.taskanaRestUrl}/v1/workbaskets/${id}`, this.httpOptions);
return this.httpClient.get<Workbasket>(`${environment.taskanaRestUrl}/v1/workbaskets/${id}`);
}
// POST
createWorkbasket(workbasket: Workbasket): Observable<Workbasket> {
return this.httpClient
.post<Workbasket>(`${environment.taskanaRestUrl}/v1/workbaskets`, workbasket, this.httpOptions);
.post<Workbasket>(`${environment.taskanaRestUrl}/v1/workbaskets`, workbasket);
}
// PUT
updateWorkbasket(url: string, workbasket: Workbasket): Observable<Workbasket> {
return this.httpClient
.put<Workbasket>(url, workbasket, this.httpOptions)
.put<Workbasket>(url, workbasket)
.catch(this.handleError);
}
// DELETE
deleteWorkbasket(url: string) {
return this.httpClient.delete(url, this.httpOptions);
return this.httpClient.delete(url);
}
// GET
getWorkBasketAccessItems(url: string): Observable<WorkbasketAccessItemsResource> {
return this.httpClient.get<WorkbasketAccessItemsResource>(url, this.httpOptions);
return this.httpClient.get<WorkbasketAccessItemsResource>(url);
}
// POST
createWorkBasketAccessItem(url: string, workbasketAccessItem: WorkbasketAccessItems): Observable<WorkbasketAccessItems> {
return this.httpClient.post<WorkbasketAccessItems>(url, workbasketAccessItem, this.httpOptions);
return this.httpClient.post<WorkbasketAccessItems>(url, workbasketAccessItem);
}
// PUT
updateWorkBasketAccessItem(url: string, workbasketAccessItem: Array<WorkbasketAccessItems>): Observable<string> {
return this.httpClient.put<string>(url,
workbasketAccessItem,
this.httpOptions);
workbasketAccessItem);
}
// GET
getWorkBasketsDistributionTargets(url: string): Observable<WorkbasketDistributionTargetsResource> {
return this.httpClient.get<WorkbasketDistributionTargetsResource>(url, this.httpOptions);
return this.httpClient.get<WorkbasketDistributionTargetsResource>(url);
}
// PUT
updateWorkBasketsDistributionTargets(url: string, distributionTargetsIds: Array<string>):
Observable<WorkbasketDistributionTargetsResource> {
return this.httpClient.put<WorkbasketDistributionTargetsResource>(url, distributionTargetsIds, this.httpOptions);
return this.httpClient.put<WorkbasketDistributionTargetsResource>(url, distributionTargetsIds);
}
// DELETE
removeDistributionTarget(url: string) {
return this.httpClient.delete<string>(url, this.httpOptions);
return this.httpClient.delete<string>(url);
}
@ -214,5 +207,6 @@ export class WorkbasketService {
return Observable.throw(errMsg);
}
// #endregion
}

View File

@ -1,6 +1,6 @@
<div class="container-scrollable">
<taskana-spinner [isRunning]="requestInProgress" ></taskana-spinner>
<taskana-no-access *ngIf="!requestInProgress && (!hasPermission && !workbasket || !workbasket && selectedId)"></taskana-no-access>
<taskana-no-access *ngIf="!requestInProgress && (!workbasket || !workbasket && selectedId)"></taskana-no-access>
<div id="workbasket-details" *ngIf="workbasket && !requestInProgress">
<ul class="nav nav-tabs" role="tablist">
<li *ngIf="showDetail" class="visible-xs visible-sm hidden">

View File

@ -20,7 +20,6 @@ import { LinksWorkbasketSummary } from 'app/models/links-workbasket-summary';
import { WorkbasketService } from 'app/administration/services/workbasket/workbasket.service';
import { MasterAndDetailService } from 'app/services/masterAndDetail/master-and-detail.service';
import { PermissionService } from 'app/services/permission/permission.service';
import { AlertService } from 'app/services/alert/alert.service';
import { SavingWorkbasketService } from 'app/administration/services/saving-workbaskets/saving-workbaskets.service';
@ -79,7 +78,7 @@ describe('WorkbasketDetailsComponent', () => {
declarations: [WorkbasketDetailsComponent, NoAccessComponent, WorkbasketInformationComponent, SpinnerComponent,
IconTypeComponent, MapValuesPipe, RemoveNoneTypePipe, AlertComponent, GeneralMessageModalComponent, AccessItemsComponent,
DistributionTargetsComponent, FilterComponent, DualListComponent, DummyDetailComponent, SelectWorkBasketPipe],
providers: [WorkbasketService, MasterAndDetailService, PermissionService, ErrorModalService, RequestInProgressService,
providers: [WorkbasketService, MasterAndDetailService, ErrorModalService, RequestInProgressService,
AlertService, SavingWorkbasketService, {
provide: DomainService,
useClass: DomainServiceMock
@ -92,7 +91,6 @@ describe('WorkbasketDetailsComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(WorkbasketDetailsComponent);
component = fixture.componentInstance;
component.hasPermission = false;
debugElement = fixture.debugElement.nativeElement;
router = TestBed.get(Router)
fixture.detectChanges();
@ -129,16 +127,10 @@ describe('WorkbasketDetailsComponent', () => {
expect(component).toBeTruthy();
});
it('should has created taskana-no-access if workbasket is not defined and hasPermission is false', () => {
expect(component.workbasket).toBeUndefined();
component.hasPermission = false;
fixture.detectChanges();
expect(debugElement.querySelector('taskana-no-access')).toBeTruthy();
});
it('should has created taskana-workbasket-details if workbasket is defined and taskana-no-access should dissapear', () => {
expect(component.workbasket).toBeUndefined();
component.hasPermission = false;
fixture.detectChanges();
expect(debugElement.querySelector('taskana-no-access')).toBeTruthy();

View File

@ -11,7 +11,6 @@ import { ACTION } from 'app/models/action';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { WorkbasketService } from 'app/administration/services/workbasket/workbasket.service'
import { PermissionService } from 'app/services/permission/permission.service';
import { MasterAndDetailService } from 'app/services/masterAndDetail/master-and-detail.service'
import { DomainService } from 'app/services/domain/domain.service';
@ -28,7 +27,6 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
workbasketCopy: Workbasket;
selectedId: string = undefined;
showDetail = false;
hasPermission = true;
requestInProgress = false;
action: string;
@ -43,7 +41,6 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
private route: ActivatedRoute,
private router: Router,
private masterAndDetailService: MasterAndDetailService,
private permissionService: PermissionService,
private domainService: DomainService) { }
@ -80,14 +77,6 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
this.masterAndDetailSubscription = this.masterAndDetailService.getShowDetail().subscribe(showDetail => {
this.showDetail = showDetail;
});
this.permissionSubscription = this.permissionService.hasPermission().subscribe(permission => {
this.hasPermission = permission;
if (!this.hasPermission) {
this.requestInProgress = false;
}
})
}
backClicked(): void {

View File

@ -2,13 +2,17 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AppComponent } from './app.component';
import { BusinessAdminGuard } from 'app/guards/business-admin-guard';
import { MonitorGuard } from 'app/guards/monitor-guard';
const appRoutes: Routes = [
{
canActivate: [BusinessAdminGuard],
path: 'administration',
loadChildren: './administration/administration.module#AdministrationModule',
},
{
canActivate: [MonitorGuard],
path: 'monitor',
loadChildren: './monitor/monitor.module#MonitorModule',
},

View File

@ -19,8 +19,6 @@ import { SharedModule } from 'app/shared/shared.module';
* Services
*/
import { HttpClientInterceptor } from 'app/services/httpClientInterceptor/http-client-interceptor.service';
import { PermissionService } from 'app/services/permission/permission.service';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { OrientationService } from 'app/services/orientation/orientation.service';
@ -32,19 +30,22 @@ import { MasterAndDetailService } from 'app/services/masterAndDetail/master-and-
import { TreeService } from 'app/services/tree/tree.service';
import { TitlesService } from 'app/services/titles/titles.service';
import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service';
import { WindowRefService } from 'app/services/window/window.service';
import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service';
/**
* Components
*/
import { AppComponent } from './app.component';
import { NavBarComponent } from 'app/components/nav-bar/nav-bar.component';
import { UserInformationComponent } from 'app/components/user-information/user-information.component';
/**
* Guards
*/
import { DomainGuard } from './guards/domain-guard';
import { BusinessAdminGuard } from './guards/business-admin-guard';
import { MonitorGuard } from './guards/monitor-guard';
import { APP_BASE_HREF } from '@angular/common';
@ -64,29 +65,31 @@ const MODULES = [
const DECLARATIONS = [
AppComponent,
NavBarComponent
NavBarComponent,
UserInformationComponent
];
export function startupServiceFactory(startupService: StartupService): Function {
return () => startupService.load();
export function startupServiceFactory(startupService: StartupService): () => Promise<any> {
return (): Promise<any> => {
return startupService.load();
};
}
@NgModule({
declarations: DECLARATIONS,
imports: MODULES,
providers: [
WindowRefService,
{ provide: APP_BASE_HREF, useValue: '/' },
DomainService,
{
provide: HTTP_INTERCEPTORS,
useClass: HttpClientInterceptor,
multi: true
},
ErrorModalService,
RequestInProgressService,
OrientationService,
SelectedRouteService,
DomainGuard,
BusinessAdminGuard,
MonitorGuard,
StartupService,
{
provide: APP_INITIALIZER,
@ -95,11 +98,11 @@ export function startupServiceFactory(startupService: StartupService): Function
multi: true
},
AlertService,
PermissionService,
MasterAndDetailService,
TreeService,
TitlesService,
CustomFieldsService
CustomFieldsService,
TaskanaEngineService
],
bootstrap: [AppComponent]
})

View File

@ -3,7 +3,7 @@
<div class="pull-left col-sm-3 col-md-4">
<button type="button" *ngIf="!showNavbar" class="btn btn-default navbar-toggle show pull-left" (click)="toogleNavBar();"
aria-expanded="true" aria-controls="navbar">
<span class="glyphicon glyphicon-arrow-right white"></span>
<span class="glyphicon glyphicon glyphicon-th-list white"></span>
</button>
<span>&nbsp;</span>
</div>
@ -13,7 +13,7 @@
<h2 class="navbar-brand no-margin"> {{title}}</h2>
</ul>
</div>
<div *ngIf="selectedRoute.indexOf('workbaskets') !== -1 || selectedRoute.indexOf('classifications') !== -1"class="pull-right domain-form">
<div *ngIf="selectedRoute.indexOf('workbaskets') !== -1 || selectedRoute.indexOf('classifications') !== -1" class="pull-right domain-form">
<div class="dropdown clearfix btn-group">
<label class="control-label hidden-xs">Working on </label>
<button type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
@ -36,23 +36,25 @@
<ul class="nav">
<svg-icon class="logo white visible-xs" src="./assets/icons/logo.svg"></svg-icon>
<h2 class="navbar-brand no-margin logo visible-xs"> {{title}}</h2>
<button type="button" *ngIf="showNavbar" class="btn btn-default navbar-toggle show pull-right" (click)="toogleNavBar();"
aria-expanded="true" aria-controls="navbar">
<span class="glyphicon glyphicon-arrow-left white"></span>
<button type="button" class="btn btn-default logout navbar-toggle show pull-right" data-toggle="tooltip" title="logout" (click)="logout()" aria-expanded="true"
aria-controls="logout">
<span class="glyphicon glyphicon-share white"></span>
</button>
</ul>
</div>
<div class="nav-content">
<div class="row menu">
<taskana-user-information></taskana-user-information>
<div *ngIf="administrationAccess" class="row menu">
<span (click)="toogleNavBar()" routerLink="administration/workbaskets" aria-controls="administration" routerLinkActive="active">Administration</span>
<div class="row submenu" [ngClass]="{'selected': selectedRoute.indexOf('workbaskets') !== -1 }">
<span (click)="toogleNavBar()" class="col-xs-6" routerLink="administration/workbaskets" aria-controls="Workbaskets" routerLinkActive="active">Workbaskets</span>
</div>
<div class="row submenu" [ngClass]="{'selected': selectedRoute.indexOf('classifications') !== -1}">
<span (click)="toogleNavBar()" class="col-xs-6" routerLink="administration/classifications" aria-controls="Classifications" routerLinkActive="active">Classifications</span>
<span (click)="toogleNavBar()" class="col-xs-6" routerLink="administration/classifications" aria-controls="Classifications"
routerLinkActive="active">Classifications</span>
</div>
</div>
<div class="row menu" [ngClass]="{'selected': selectedRoute.indexOf('monitor') !== -1}">
<div *ngIf="monitorAccess" class="row menu" [ngClass]="{'selected': selectedRoute.indexOf('monitor') !== -1}">
<span (click)="toogleNavBar()" routerLink="{{monitorUrl}}" aria-controls="Monitor" routerLinkActive="active">Monitor</span>
</div>
<div class="row menu" [ngClass]="{'selected': selectedRoute.indexOf('workplace') !== -1 || selectedRoute === ''}">

View File

@ -10,6 +10,10 @@ $selected-border-color: #22a39f;
.navbar-toggle{
margin-right: 0px;
}
button.navbar-toggle:hover > span{
color:$selected-border-color;
}
ul.nav > p {
white-space: nowrap;
@ -29,7 +33,6 @@ svg-icon.logo {
padding: 6px 5px 5px 5px;
}
h2.navbar-brand{
vertical-align: middle;
text-decoration: none;
@ -67,8 +70,7 @@ h2.navbar-brand{
.nav-content{
margin-top: 25px;
margin-left: 8px;
margin-top: 5px;
}
/*

View File

@ -3,16 +3,28 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http';
import { SharedModule } from 'app/shared/shared.module';
import { Observable } from 'rxjs/Observable';
import { NavBarComponent } from './nav-bar.component';
import { UserInformationComponent } from 'app/components/user-information/user-information.component';
import { SelectedRouteService } from 'app/services/selected-route/selected-route';
import { DomainService } from 'app/services/domain/domain.service';
import { DomainServiceMock } from 'app/services/domain/domain.service.mock';
import { BusinessAdminGuard } from 'app/guards/business-admin-guard';
import { MonitorGuard } from 'app/guards/monitor-guard';
import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service';
import { WindowRefService } from 'app/services/window/window.service';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { UserInfoModel } from 'app/models/user-info';
describe('NavBarComponent', () => {
let component: NavBarComponent;
let fixture: ComponentFixture<NavBarComponent>;
let debugElement, navBar;
let debugElement, navBar, taskanaEngineService;
const routes: Routes = [
{ path: 'classifications', component: NavBarComponent }
@ -20,16 +32,23 @@ describe('NavBarComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [NavBarComponent],
declarations: [NavBarComponent, UserInformationComponent],
imports: [
AngularSvgIconModule,
HttpClientModule,
RouterTestingModule.withRoutes(routes),
SharedModule
],
providers: [SelectedRouteService, {
provide: DomainService,
useClass: DomainServiceMock
}]
},
BusinessAdminGuard,
MonitorGuard,
TaskanaEngineService,
WindowRefService,
ErrorModalService,
RequestInProgressService]
})
.compileComponents();
}));
@ -39,6 +58,8 @@ describe('NavBarComponent', () => {
component = fixture.componentInstance;
debugElement = fixture.debugElement.nativeElement;
navBar = fixture.debugElement.componentInstance;
taskanaEngineService = TestBed.get(TaskanaEngineService);
spyOn(taskanaEngineService, 'getUserInformation').and.returnValue(Observable.of(new UserInfoModel));
fixture.detectChanges();
});

View File

@ -4,7 +4,9 @@ import { SelectedRouteService } from 'app/services/selected-route/selected-route
import { Subscription } from 'rxjs/Subscription';
import { trigger, state, style, transition, keyframes, animate } from '@angular/animations';
import { DomainService } from 'app/services/domain/domain.service';
import { BusinessAdminGuard } from 'app/guards/business-admin-guard';
import { MonitorGuard } from 'app/guards/monitor-guard';
import { WindowRefService } from 'app/services/window/window.service';
@Component({
selector: 'taskana-nav-bar',
templateUrl: './nav-bar.component.html',
@ -38,11 +40,17 @@ export class NavBarComponent implements OnInit, OnDestroy {
monitorUrl = './monitor';
workplaceUrl = './workplace';
administrationAccess = false;
monitorAccess = false;
selectedRouteSubscription: Subscription;
constructor(
private selectedRouteService: SelectedRouteService,
private domainService: DomainService) { }
private domainService: DomainService,
private businessAdminGuard: BusinessAdminGuard,
private monitorGuard: MonitorGuard,
private window: WindowRefService) { }
ngOnInit() {
this.selectedRouteSubscription = this.selectedRouteService.getSelectedRoute().subscribe((value: string) => {
@ -56,6 +64,13 @@ export class NavBarComponent implements OnInit, OnDestroy {
this.domainService.getSelectedDomain().subscribe(domain => {
this.selectedDomain = domain;
});
this.businessAdminGuard.canActivate().subscribe(hasAccess => {
this.administrationAccess = hasAccess
});
this.monitorGuard.canActivate().subscribe(hasAccess => {
this.monitorAccess = hasAccess
});
}
switchDomain(domain) {
@ -65,6 +80,12 @@ export class NavBarComponent implements OnInit, OnDestroy {
toogleNavBar() {
this.showNavbar = !this.showNavbar;
}
logout() {
this.window.nativeWindow.location.href = environment.taskanaRestUrl + '/login?logout';
}
private setTitle(value: string = 'workbaskets') {
if (value.indexOf('workbaskets') === 0 || value.indexOf('classifications') === 0) {
this.title = this.titleAdministration;

View File

@ -0,0 +1,16 @@
<div class="row">
<div class="icon">
<div class="icon-wrap col-xs-offset-5">
<svg-icon class="blue big" src="./assets/icons/user.svg"></svg-icon>
</div>
</div>
<div class="user-info white">
<span>Logged as: {{userInformation.userId}}</span>
<button type="button" class="btn btn-default white pull-right transparent" (click)="toogleRoles();" aria-expanded="true" aria-controls="roles">
<span>Roles</span>
</button>
</div>
<div class="white pull-right roles col-xs-12" [@toggle]="showRoles" *ngIf="showRoles">
<span><i>{{roles}}</i></span>
</div>
</div>

View File

@ -0,0 +1,68 @@
.big {
width: 64px;
height: 64px;
}
.user-info {
margin-top:85px;
> span {
margin: 0 15px 0 15px;
font-size: 20px;
font-family: inherit;
font-weight: 500;
line-height: 1.1;
}
}
.icon, {
width: 100%;
position: relative;
& > .icon-wrap {
border-radius: 50%;
border: solid 3px #22a39f;
width: 74px;
height: 74px;
overflow: hidden;
background-color: #175263;
position: absolute;
-webkit-box-shadow: 0px 3px 3px #416b6a;
-moz-box-shadow: 0px 3px 3px #416b6a;
box-shadow: 0px 3px 3px #416b6a;
&> svg-icon {
border-radius: 50%;
overflow: hidden;
background-color: white;
position: absolute;
margin:2px
}
}
}
.icon:before { content: '';
position: absolute;
border-bottom: solid 3px #22a39f;
width: 100%;
height: 60px;
transform: translateY(-33%);
-webkit-box-shadow: 0px 3px 3px #416b6a;
-moz-box-shadow: 0px 3px 3px #416b6a;
box-shadow: 0px 3px 3px #416b6a;
}
button.transparent, {
border: none;
background-color: transparent;
&:hover{
border: none;
background-color: transparent;
color:#22a39f
}
&:focus{
border: none;
background-color: transparent;
outline: none;
color:white
}
}

View File

@ -0,0 +1,37 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { HttpModule } from '@angular/http';
import { UserInformationComponent } from './user-information.component';
import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service';
import { UserInfoModel } from 'app/models/user-info';
describe('UserInformationComponent', () => {
let component: UserInformationComponent;
let fixture: ComponentFixture<UserInformationComponent>;
let taskanaEngineService;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [AngularSvgIconModule,
HttpClientModule, HttpModule],
declarations: [UserInformationComponent],
providers: [TaskanaEngineService]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(UserInformationComponent);
component = fixture.componentInstance;
taskanaEngineService = TestBed.get(TaskanaEngineService);
spyOn(taskanaEngineService, 'getUserInformation').and.returnValue(Observable.of(new UserInfoModel));
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,40 @@
import { Component, OnInit } from '@angular/core';
import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service';
import { UserInfoModel } from 'app/models/user-info';
import { trigger, transition, animate, keyframes, style } from '@angular/animations';
@Component({
selector: 'taskana-user-information',
templateUrl: './user-information.component.html',
styleUrls: ['./user-information.component.scss'],
animations: [
trigger('toggle', [
transition('void => *', animate('300ms ease-in', keyframes([
style({ opacity: 0, height: '0px' }),
style({ opacity: 1, height: '10px' }),
style({ opacity: 1, height: '*' })]))),
transition('* => void', animate('300ms ease-out', keyframes([
style({ opacity: 1, height: '*' }),
style({ opacity: 0, height: '10px' }),
style({ opacity: 0, height: '0px' })])))
]
)],
})
export class UserInformationComponent implements OnInit {
userInformation: UserInfoModel;
roles = '';
showRoles = false;
constructor(private taskanaEngineService: TaskanaEngineService) { }
ngOnInit() {
this.taskanaEngineService.getUserInformation().subscribe(userInfo => {
this.userInformation = userInfo;
this.roles = '[' + userInfo.roles.join(',') + ']';
})
}
toogleRoles() {
this.showRoles = !this.showRoles;
}
}

View File

@ -0,0 +1,37 @@
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
import { CanActivate, Router } from '@angular/router';
import { Injectable } from '@angular/core';
import { DomainService } from 'app/services/domain/domain.service';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { ErrorModel } from 'app/models/modal-error';
import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service';
@Injectable()
export class AdminGuard implements CanActivate {
constructor(private taskanaEngineService: TaskanaEngineService, public router: Router) { }
canActivate() {
return this.taskanaEngineService.getUserInformation().map(userInfo => {
if (userInfo.roles.length === 0) {
return this.navigateToWorplace();
}
const adminRole = userInfo.roles.find(role => {
if (role === 'ADMIN') {
return true;
}
});
if (adminRole) {
return true;
}
return this.navigateToWorplace();
}).catch(() => {
return Observable.of(this.navigateToWorplace())
});
}
navigateToWorplace(): boolean {
this.router.navigate(['workplace']);
return false
}
}

View File

@ -0,0 +1,38 @@
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
import { CanActivate, Router } from '@angular/router';
import { Injectable } from '@angular/core';
import { DomainService } from 'app/services/domain/domain.service';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { ErrorModel } from 'app/models/modal-error';
import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service';
@Injectable()
export class BusinessAdminGuard implements CanActivate {
constructor(private taskanaEngineService: TaskanaEngineService, public router: Router) { }
canActivate() {
return this.taskanaEngineService.getUserInformation().map(userInfo => {
if (userInfo.roles.length === 0) {
return this.navigateToWorplace();
}
const adminRole = userInfo.roles.find(role => {
if (role === 'BUSINESS_ADMIN' || role === 'ADMIN' ) {
return true;
}
});
if (adminRole) {
return true;
}
return this.navigateToWorplace();
}).catch(() => {
return Observable.of(this.navigateToWorplace())
});
}
navigateToWorplace(): boolean {
this.router.navigate(['workplace']);
return false
}
}

View File

@ -0,0 +1,38 @@
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
import { CanActivate, Router } from '@angular/router';
import { Injectable } from '@angular/core';
import { DomainService } from 'app/services/domain/domain.service';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { ErrorModel } from 'app/models/modal-error';
import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service';
@Injectable()
export class MonitorGuard implements CanActivate {
constructor(private taskanaEngineService: TaskanaEngineService, public router: Router) { }
canActivate() {
return this.taskanaEngineService.getUserInformation().map(userInfo => {
if (userInfo.roles.length === 0) {
return this.navigateToWorplace();
}
const adminRole = userInfo.roles.find(role => {
if (role === 'MONITOR' || role === 'ADMIN' ) {
return true;
}
});
if (adminRole) {
return true;
}
return this.navigateToWorplace();
}).catch(() => {
return Observable.of(this.navigateToWorplace())
});
}
navigateToWorplace(): boolean {
this.router.navigate(['workplace']);
return false
}
}

View File

@ -0,0 +1,8 @@
export class UserInfoModel {
constructor(
public userId: string = undefined,
public groupIds: Array<string> = [],
public roles: Array<string> = []) { };
}

View File

@ -7,29 +7,22 @@ import { WorkbasketCounter } from 'app/monitor/models/workbasket-counter';
@Injectable()
export class RestConnectorService {
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/hal+json',
'Authorization': 'Basic VEVBTUxFQURfMTpURUFNTEVBRF8x'
})
};
constructor(private httpClient: HttpClient) {
}
getTaskStatistics(): Observable<State[]> {
return this.httpClient.get<Array<State>>(environment.taskanaRestUrl + '/v1/monitor/countByState?states=READY,CLAIMED,COMPLETED',
this.httpOptions)
return this.httpClient.get<Array<State>>(environment.taskanaRestUrl + '/v1/monitor/countByState?states=READY,CLAIMED,COMPLETED')
}
getWorkbasketStatistics(): Observable<WorkbasketCounter> {
return this.httpClient.get<WorkbasketCounter>(environment.taskanaRestUrl
+ '/v1/monitor/taskcountByWorkbasketDaysAndState?daysInPast=5&states=READY,CLAIMED,COMPLETED',
this.httpOptions)
+ '/v1/monitor/taskcountByWorkbasketDaysAndState?daysInPast=5&states=READY,CLAIMED,COMPLETED')
}
getTaskStatusReport(): Observable<Object> {
return this.httpClient.get(environment.taskanaRestUrl + '/v1/monitor/taskStatusReport', this.httpOptions)
return this.httpClient.get(environment.taskanaRestUrl + '/v1/monitor/taskStatusReport')
}
}

View File

@ -13,12 +13,6 @@ export class DomainService {
url = environment.taskanaRestUrl + '/v1/domains';
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Basic VEVBTUxFQURfMTpURUFNTEVBRF8x'
})
};
private domainSelectedValue;
private domainSelected = new BehaviorSubject<string>('');
private domainSwitched = new Subject<string>();
@ -33,7 +27,7 @@ export class DomainService {
// GET
getDomains(forceRefresh = false): Observable<string[]> {
if (!this.dataObs$.observers.length || forceRefresh) {
this.httpClient.get<string[]>(this.url, this.httpOptions).subscribe(
this.httpClient.get<string[]>(this.url).subscribe(
domains => {
this.dataObs$.next(domains);
if (!this.domainSelectedValue && domains && domains.length > 0) {

View File

@ -1,15 +0,0 @@
import { TestBed, inject } from '@angular/core/testing';
import { PermissionService } from './permission.service';
describe('PermissionService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [PermissionService]
});
});
it('should be created', inject([PermissionService], (service: PermissionService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -1,18 +0,0 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@Injectable()
export class PermissionService {
private permission = new BehaviorSubject<boolean>(true);
setPermission(permission: boolean) {
this.permission.next(permission);
}
hasPermission(): Observable<boolean> {
return this.permission.asObservable();
}
}

View File

@ -2,20 +2,23 @@ import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
import { CanActivate } from '@angular/router';
import { environment } from 'app/../environments/environment';
import { Injectable } from '@angular/core';
import { Injectable, Inject } from '@angular/core';
import { TitlesService } from 'app/services/titles/titles.service';
import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service';
@Injectable()
export class StartupService {
constructor(
private httpClient: HttpClient,
private titlesService: TitlesService,
private customFieldsService: CustomFieldsService) { }
load(): Promise<any> {
return this.loadEnvironment();
}
private loadEnvironment() {
return this.httpClient.get<any>('environments/data-sources/environment-information.json').map(jsonFile => {
if (jsonFile) {

View File

@ -0,0 +1,32 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { environment } from 'environments/environment';
import { UserInfoModel } from 'app/models/user-info';
import { ReplaySubject } from 'rxjs/ReplaySubject';
@Injectable()
export class TaskanaEngineService {
private dataObs$ = new ReplaySubject<UserInfoModel>(1);
constructor(
private httpClient: HttpClient
) { }
// GET
getUserInformation(forceRefresh = false): Observable<UserInfoModel> {
if (!this.dataObs$.observers.length || forceRefresh) {
this.httpClient.get<UserInfoModel>(`${environment.taskanaRestUrl}/v1/current-user-info`).subscribe(
data => this.dataObs$.next(data),
error => {
this.dataObs$.error(error);
this.dataObs$ = new ReplaySubject(1);
}
);
}
return this.dataObs$;
}
}

View File

@ -0,0 +1,18 @@
import { Injectable } from '@angular/core';
// This interface is optional, showing how you can add strong typings for custom globals.
// Just use "Window" as the type if you don't have custom global stuff
export interface IWindowService extends Window {
__custom_global_stuff: string;
}
function getWindow (): any {
return window;
}
@Injectable()
export class WindowRefService {
get nativeWindow (): IWindowService {
return getWindow();
}
}

View File

@ -3,7 +3,6 @@ import { HttpClientModule } from '@angular/common/http';
import { HttpModule } from '@angular/http';
import { HttpClientInterceptor } from './http-client-interceptor.service';
import { PermissionService } from 'app/services/permission/permission.service';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
@ -11,7 +10,7 @@ describe('HttpExtensionService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientModule, HttpModule],
providers: [HttpClientInterceptor, PermissionService, ErrorModalService, RequestInProgressService]
providers: [HttpClientInterceptor, ErrorModalService, RequestInProgressService]
});
});

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
@ -7,28 +7,31 @@ import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { ErrorModel } from 'app/models/modal-error';
import { PermissionService } from 'app/services/permission/permission.service';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { environment } from 'environments/environment';
@Injectable()
export class HttpClientInterceptor implements HttpInterceptor {
constructor(
private permissionService: PermissionService,
private errorModalService: ErrorModalService,
private requestInProgressService: RequestInProgressService) {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
req = req.clone({ headers: req.headers.set('Content-Type', 'application/hal+json') });
if (!environment.production) {
req = req.clone({ headers: req.headers.set('Authorization', 'Basic VEVBTUxFQURfMTpURUFNTEVBRF8x') });
}
return next.handle(req).do(event => {
this.permissionService.setPermission(true);
}, err => {
this.requestInProgressService.setRequestInProgress(false);
if (err instanceof HttpErrorResponse && (err.status === 401 || err.status === 403)) {
this.permissionService.setPermission(false)
this.errorModalService.triggerError(
new ErrorModel('You have no access to this resource ', err));
} else if (err instanceof HttpErrorResponse && (err.status === 404) && err.url.indexOf('environment-information.json')) {
// ignore this error message
} else {

View File

@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { AlertModule } from 'ngx-bootstrap';
import { RouterModule } from '@angular/router';
@ -22,6 +22,7 @@ import { SelectWorkBasketPipe } from './pipes/selectedWorkbasket/seleted-workbas
import { SpreadNumberPipe } from './pipes/spreadNumber/spread-number';
import { OrderBy } from './pipes/orderBy/orderBy';
import { MapToIterable } from './pipes/mapToIterable/mapToIterable';
import { HttpClientInterceptor } from './services/httpClientInterceptor/http-client-interceptor.service';
const MODULES = [
@ -51,7 +52,14 @@ const DECLARATIONS = [
@NgModule({
declarations: DECLARATIONS,
imports: MODULES,
exports: DECLARATIONS
exports: DECLARATIONS,
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: HttpClientInterceptor,
multi: true
}
]
})
export class SharedModule {
}

View File

@ -0,0 +1,19 @@
import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { environment } from 'environments/environment';
@Injectable()
export class CustomHttpClientInterceptor implements HttpInterceptor {
constructor() {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (!environment.production) {
req = req.clone({ headers: req.headers.set('Authorization', 'Basic YWRtaW46YWRtaW4=') });
}
return next.handle(req);
}
}

View File

@ -8,35 +8,27 @@ import { environment } from 'app/../environments/environment';
export class TaskService {
url = environment.taskanaRestUrl + '/v1/tasks';
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/hal+json',
'Authorization': 'Basic YWRtaW46YWRtaW4=',
'user': 'user_1_1'
})
};
constructor(private httpClient: HttpClient) {
}
findTaskWithWorkbaskets(basketKey: string): Observable<Task[]> {
return this.httpClient.get<Task[]>(this.url + '?workbasketId=' + basketKey, this.httpOptions);
return this.httpClient.get<Task[]>(this.url + '?workbasketId=' + basketKey);
}
getTask(id: string): Observable<Task> {
return this.httpClient.get<Task>(this.url + '/' + id, this.httpOptions);
return this.httpClient.get<Task>(this.url + '/' + id);
}
completeTask(id: string): Observable<Task> {
return this.httpClient.post<Task>(this.url + '/' + id + '/complete', '', this.httpOptions);
return this.httpClient.post<Task>(this.url + '/' + id + '/complete', '');
}
claimTask(id: string): Observable<Task> {
return this.httpClient.post<Task>(this.url + '/' + id + '/claim', 'test', this.httpOptions);
return this.httpClient.post<Task>(this.url + '/' + id + '/claim', 'test');
}
transferTask(taskId: string, workbasketKey: string): Observable<Task> {
return this.httpClient.post<Task>(this.url + '/' + taskId
+ '/transfer/' + workbasketKey, '', this.httpOptions);
+ '/transfer/' + workbasketKey, '');
}
}

View File

@ -1,25 +1,17 @@
import {Injectable} from '@angular/core';
import { Injectable } from '@angular/core';
import {Workbasket} from 'app/models/workbasket';
import {environment} from 'app/../environments/environment';
import {Observable} from 'rxjs/Observable';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import { Workbasket } from 'app/models/workbasket';
import { environment } from 'app/../environments/environment';
import { Observable } from 'rxjs/Observable';
import { HttpClient, HttpHeaders } from '@angular/common/http';
@Injectable()
export class WorkbasketService {
url = environment.taskanaRestUrl + '/v1/workbaskets';
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/hal+json',
'Authorization': 'Basic VEVBTUxFQURfMTpURUFNTEVBRF8x'
})
};
constructor(private httpClient: HttpClient) {
}
getAllWorkBaskets(): Observable<Workbasket[]> {
return this.httpClient.get<Workbasket[]>(this.url + '?required-permission=OPEN', this.httpOptions);
return this.httpClient.get<Workbasket[]>(this.url + '?required-permission=OPEN');
}
}

View File

@ -2,10 +2,11 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Ng2AutoCompleteModule } from 'ng2-auto-complete';
import { HttpClientModule } from '@angular/common/http';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { WorkplaceRoutingModule } from './workplace-routing.module';
import { AlertModule } from 'ngx-bootstrap';
import { SharedModule } from 'app/shared/shared.module';
import { WorkplaceComponent } from './workplace.component';
import { SelectorComponent } from './workbasket-selector/workbasket-selector.component';
@ -19,6 +20,8 @@ import { OrderTasksByPipe } from './util/orderTasksBy.pipe';
import { DataService } from './services/data.service';
import { TaskService } from './services/task.service';
import { WorkbasketService } from './services/workbasket.service';
import { CustomHttpClientInterceptor } from './services/custom-http-interceptor/custom-http-interceptor.service';
const MODULES = [
CommonModule,
@ -27,7 +30,8 @@ const MODULES = [
HttpClientModule,
AngularSvgIconModule,
WorkplaceRoutingModule,
AlertModule
AlertModule,
SharedModule
];
const DECLARATIONS = [
@ -46,7 +50,12 @@ const DECLARATIONS = [
providers: [
DataService,
TaskService,
WorkbasketService
WorkbasketService,
{
provide: HTTP_INTERCEPTORS,
useClass: CustomHttpClientInterceptor,
multi: true
},
]
})
export class WorkplaceModule {

View File

@ -3,4 +3,5 @@ $blue: #2e9eca;
$green: #246972;
$grey: grey;
$brown: #f0ad4e;
$invalid: #a94442;
$invalid: #a94442;
$aquamarine: #22a39f;

View File

@ -77,6 +77,13 @@
}
}
.blue-green{
color: $blue-green;
& svg {
fill: $blue-green;
}
}
.green {
color: $green;
& svg {
@ -112,6 +119,13 @@
}
}
.aquamarine {
color: $aquamarine;
& svg {
fill: $aquamarine;
}
}
svg-icon.fa-fw > svg {
text-align: center;
width: 1.25em;
@ -282,4 +296,5 @@ li.list-group-item:hover, {
color: #555;
text-decoration: none;
background-color: #f5f5f5;
}
}