add server in version 0.5.0 as initial commit for future work on github
This commit is contained in:
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
||||
mv2/build
|
||||
mv2/.gradle
|
||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/docker/files/app.jar
|
||||
23
Dockerfile
Normal file
23
Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
||||
# Create a jdk container as a builder
|
||||
FROM openjdk:8u162-jdk-stretch as builder
|
||||
RUN apt-get update && \
|
||||
apt-get install -y maven && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create working directory
|
||||
RUN mkdir -p /usr/app
|
||||
WORKDIR /usr/app
|
||||
|
||||
# Copy the source from the local machine to the builder container and build
|
||||
COPY ./src /usr/app/src
|
||||
COPY pom.xml /usr/app
|
||||
|
||||
RUN mvn package -Ddockerfile.skip
|
||||
|
||||
# Copy the artifact
|
||||
RUN cp /usr/app/target/mv-*.jar /usr/app/app.jar
|
||||
|
||||
FROM openjdk:8-jdk-alpine
|
||||
COPY --from=builder /usr/app/app.jar /opt/app.jar
|
||||
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/opt/app.jar"]
|
||||
EXPOSE 8080
|
||||
135
mv.iml
Normal file
135
mv.iml
Normal file
@@ -0,0 +1,135 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="Spring" name="Spring">
|
||||
<configuration />
|
||||
</facet>
|
||||
<facet type="web" name="Web">
|
||||
<configuration>
|
||||
<webroots />
|
||||
<sourceRoots>
|
||||
<root url="file://$MODULE_DIR$/src/main/java" />
|
||||
<root url="file://$MODULE_DIR$/src/main/resources" />
|
||||
</sourceRoots>
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
|
||||
<output url="file://$MODULE_DIR$/target/classes" />
|
||||
<output-test url="file://$MODULE_DIR$/target/test-classes" />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Maven: javax.xml.bind:jaxb-api:2.3.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: io.spring.gradle:dependency-management-plugin:1.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-context-support:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-beans:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-context:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-expression:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-core:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-jcl:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-mail:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-autoconfigure:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-logging:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: ch.qos.logback:logback-classic:1.2.3" level="project" />
|
||||
<orderEntry type="library" name="Maven: ch.qos.logback:logback-core:1.2.3" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-to-slf4j:2.10.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-api:2.10.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.slf4j:jul-to-slf4j:1.7.25" level="project" />
|
||||
<orderEntry type="library" name="Maven: javax.annotation:javax.annotation-api:1.3.2" level="project" />
|
||||
<orderEntry type="library" scope="RUNTIME" name="Maven: org.yaml:snakeyaml:1.19" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.sun.mail:javax.mail:1.6.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: javax.activation:activation:1.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-security:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-aop:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-web:5.0.4.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-web:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-acl:5.0.4.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-core:5.0.4.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-jdbc:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-tx:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-config:5.0.4.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-data-jpa:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-aop:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.aspectj:aspectjweaver:1.8.13" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-jdbc:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.zaxxer:HikariCP:2.7.8" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.hibernate:hibernate-core:5.2.16.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.javassist:javassist:3.22.0-GA" level="project" />
|
||||
<orderEntry type="library" name="Maven: antlr:antlr:2.7.7" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.jboss:jandex:2.0.3.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: dom4j:dom4j:1.6.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.hibernate.common:hibernate-commons-annotations:5.0.1.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: javax.transaction:javax.transaction-api:1.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-jpa:2.0.6.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-commons:2.0.6.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-orm:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-aspects:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-data-rest:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-json:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.9.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml.jackson.module:jackson-module-parameter-names:2.9.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-web:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-tomcat:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-core:8.5.29" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-el:8.5.29" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-websocket:8.5.29" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-rest-webmvc:3.0.6.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-rest-core:3.0.6.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.hateoas:spring-hateoas:0.24.0.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.plugin:spring-plugin-core:1.2.0.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.atteo:evo-inflector:1.2.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.9.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-cache:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: net.sf.ehcache:ehcache:2.10.4" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.25" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.flywaydb:flyway-core:5.0.7" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.mariadb.jdbc:mariadb-java-client:2.2.3" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.h2database:h2:1.4.197" level="project" />
|
||||
<orderEntry type="library" name="Maven: io.jsonwebtoken:jjwt:0.9.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.9.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.9.5" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-starter-test:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-test:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-test-autoconfigure:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: com.jayway.jsonpath:json-path:2.4.0" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: net.minidev:json-smart:2.3" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: net.minidev:accessors-smart:1.2" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.ow2.asm:asm:5.0.4" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: junit:junit:4.12" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.assertj:assertj-core:3.9.1" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.mockito:mockito-core:2.15.0" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: net.bytebuddy:byte-buddy:1.7.11" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: net.bytebuddy:byte-buddy-agent:1.7.11" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.objenesis:objenesis:2.6" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-core:1.3" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-library:1.3" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.skyscreamer:jsonassert:1.5.0" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: com.vaadin.external.google:android-json:0.0.20131108.vaadin1" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.springframework:spring-test:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.xmlunit:xmlunit-core:2.5.1" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.security:spring-security-test:5.0.4.RELEASE" level="project" />
|
||||
<orderEntry type="library" scope="PROVIDED" name="Maven: org.projectlombok:lombok:1.16.20" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.7" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.hibernate.validator:hibernate-validator:6.0.9.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: javax.validation:validation-api:2.0.1.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.jboss.logging:jboss-logging:3.3.2.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml:classmate:1.3.4" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.hibernate.validator:hibernate-validator-cdi:6.0.9.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.glassfish:javax.el:3.0.1-b09" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.restdocs:spring-restdocs-mockmvc:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: javax.servlet:javax.servlet-api:3.1.0" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.springframework.restdocs:spring-restdocs-core:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-webmvc:5.0.5.RELEASE" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
32
out/production/resources/application.properties
Normal file
32
out/production/resources/application.properties
Normal file
@@ -0,0 +1,32 @@
|
||||
spring.jackson.serialization.INDENT_OUTPUT=true
|
||||
|
||||
spring.h2.console.enabled=true
|
||||
|
||||
spring.jpa.hibernate.ddl-auto=validate
|
||||
|
||||
spring.datasource.platform=mariadb
|
||||
spring.datasource.url=jdbc:mariadb://localhost:3307/mariadb
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=mariadb
|
||||
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
|
||||
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
|
||||
|
||||
# Removes warning from stdout
|
||||
spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false
|
||||
|
||||
jwt.header=Authorization
|
||||
jwt.secret=mySecret
|
||||
jwt.expiration=604800
|
||||
jwt.route.authentication.path=/login
|
||||
jwt.route.authentication.refresh=/refresh
|
||||
|
||||
#page.value.page=page
|
||||
#page.value.size=size
|
||||
#page.value.sort=sort
|
||||
#page.value.sortDirection=sortDirection
|
||||
|
||||
#page.default.page=0
|
||||
#page.default.size=5
|
||||
#page.default.sort=id
|
||||
#page.default.sortDirection=asc
|
||||
12
out/production/resources/banner.txt
Normal file
12
out/production/resources/banner.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
___ ___ ___ ___ ___ ___ ___
|
||||
/\ \ /\ \ /\__\ /\ \ |\__\ /\__\ /\__\
|
||||
/::\ \ /::\ \ /::| | /::\ \ |:| | /::| | /:/ /
|
||||
/:/\:\ \ /:/\:\ \ /:|:| | /:/\:\ \ |:| | /:|:| | /:/ /
|
||||
/::\~\:\ \ /::\~\:\ \ /:/|:| |__ /:/ \:\ \ |:|__|__ /:/|:|__|__ /:/__/ ___
|
||||
/:/\:\ \:\__\ /:/\:\ \:\__\ /:/ |:| /\__\ /:/__/ \:\__\ /::::\__\ /:/ |::::\__\ |:| | /\__\
|
||||
\/__\:\ \/__/ \/__\:\/:/ / \/__|:|/:/ / \:\ \ \/__/ /:/~~/~ \/__/~~/:/ / |:| |/:/ /
|
||||
\:\__\ \::/ / |:/:/ / \:\ \ /:/ / /:/ / |:|__/:/ /
|
||||
\/__/ /:/ / |::/ / \:\ \ \/__/ /:/ / \::::/__/
|
||||
/:/ / /:/ / \:\__\ /:/ / ~~~~
|
||||
\/__/ \/__/ \/__/ \/__/
|
||||
|
||||
127
out/production/resources/db/migration/V1__init.sql
Normal file
127
out/production/resources/db/migration/V1__init.sql
Normal file
@@ -0,0 +1,127 @@
|
||||
CREATE TABLE acl_sid (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
principal BOOLEAN NOT NULL,
|
||||
sid VARCHAR(100) NOT NULL,
|
||||
UNIQUE KEY unique_acl_sid (sid, principal)
|
||||
)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
CREATE TABLE acl_class (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
class VARCHAR(100) NOT NULL,
|
||||
UNIQUE KEY uk_acl_class (CLASS)
|
||||
)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
CREATE TABLE acl_object_identity (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
object_id_class BIGINT UNSIGNED NOT NULL,
|
||||
object_id_identity VARCHAR(36) NOT NULL,
|
||||
parent_object BIGINT UNSIGNED,
|
||||
owner_sid BIGINT UNSIGNED,
|
||||
entries_inheriting BOOLEAN NOT NULL,
|
||||
UNIQUE KEY uk_acl_object_identity (object_id_class, object_id_identity),
|
||||
CONSTRAINT fk_acl_object_identity_parent FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id),
|
||||
CONSTRAINT fk_acl_object_identity_class FOREIGN KEY (object_id_class) REFERENCES acl_class (id),
|
||||
CONSTRAINT fk_acl_object_identity_owner FOREIGN KEY (owner_sid) REFERENCES acl_sid (id)
|
||||
)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
CREATE TABLE acl_entry (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
acl_object_identity BIGINT UNSIGNED NOT NULL,
|
||||
ace_order INTEGER NOT NULL,
|
||||
sid BIGINT UNSIGNED NOT NULL,
|
||||
mask INTEGER UNSIGNED NOT NULL,
|
||||
granting BOOLEAN NOT NULL,
|
||||
audit_success BOOLEAN NOT NULL,
|
||||
audit_failure BOOLEAN NOT NULL,
|
||||
UNIQUE KEY unique_acl_entry (acl_object_identity, ace_order),
|
||||
CONSTRAINT fk_acl_entry_object FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity (id),
|
||||
CONSTRAINT fk_acl_entry_acl FOREIGN KEY (sid) REFERENCES acl_sid (id)
|
||||
ON DELETE CASCADE
|
||||
)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
CREATE TABLE mv_cm_contacts (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
email VARCHAR(255) UNIQUE,
|
||||
first_name VARCHAR(255),
|
||||
last_name VARCHAR(255),
|
||||
phone VARCHAR(255),
|
||||
address VARCHAR(255),
|
||||
bank_details VARCHAR(255)
|
||||
);
|
||||
|
||||
CREATE TABLE mv_cm_groups (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) UNIQUE,
|
||||
acl_sid_id BIGINT UNIQUE REFERENCES acl_sid (id)
|
||||
);
|
||||
|
||||
CREATE TABLE mv_users (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
account_non_expired BOOLEAN DEFAULT TRUE,
|
||||
account_non_locked BOOLEAN DEFAULT TRUE,
|
||||
credentials_non_expired BOOLEAN DEFAULT TRUE,
|
||||
is_admin BOOLEAN DEFAULT FALSE,
|
||||
last_password_reset_date TIMESTAMP,
|
||||
username VARCHAR(255) UNIQUE NOT NULL,
|
||||
password VARCHAR(255),
|
||||
contact_id BIGINT UNIQUE REFERENCES mv_cm_contacts (id)
|
||||
);
|
||||
|
||||
CREATE TABLE mv_authorities (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(100) UNIQUE NOT NULL,
|
||||
system_authority BOOLEAN
|
||||
);
|
||||
|
||||
CREATE TABLE mv_roles (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(50) UNIQUE NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE mv_user_group (
|
||||
user_id BIGINT NOT NULL REFERENCES mv_users (id),
|
||||
group_id BIGINT NOT NULL REFERENCES mv_cm_groups (id)
|
||||
);
|
||||
|
||||
CREATE TABLE mv_authority_role (
|
||||
authority_id BIGINT NOT NULL REFERENCES mv_authorities (id),
|
||||
role_id BIGINT NOT NULL REFERENCES mv_roles (id)
|
||||
);
|
||||
|
||||
CREATE TABLE mv_user_role (
|
||||
user_id BIGINT NOT NULL REFERENCES mv_users (id),
|
||||
role_id BIGINT NOT NULL REFERENCES mv_roles (id)
|
||||
);
|
||||
|
||||
INSERT INTO acl_sid (principal, sid) VALUES
|
||||
(FALSE, 'ROLE_ADMIN'),
|
||||
(FALSE, 'ROLE_USER');
|
||||
|
||||
INSERT INTO mv_authorities (name) VALUES
|
||||
('ROLE_ADMIN'), ('ROLE_USER'), ('CM_CONTACT_CREATE'), ('CM_CONTACT_GET_BY_ID');
|
||||
|
||||
-- Create one user object as the first user, called admin
|
||||
INSERT INTO mv_users (is_admin, enabled, account_non_expired, account_non_locked, credentials_non_expired, username, last_password_reset_date, password)
|
||||
VALUES
|
||||
(TRUE, TRUE, TRUE, TRUE, TRUE, 'admin', current_date,
|
||||
'$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi'),
|
||||
(FALSE, TRUE, TRUE, TRUE, TRUE, 'user', current_date,
|
||||
'$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC'),
|
||||
(FALSE, FALSE, TRUE, TRUE, TRUE, 'disabled', current_date,
|
||||
'$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC'),
|
||||
(TRUE, TRUE, TRUE, TRUE, TRUE, 'admin2', current_date,
|
||||
'$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi');
|
||||
|
||||
-- Create Role Admins
|
||||
INSERT INTO mv_roles (name) VALUES ('Administrators');
|
||||
|
||||
-- Add user 1 admin to Admin role
|
||||
INSERT INTO mv_user_role (user_id, role_id) VALUES (1, 1);
|
||||
|
||||
-- Give Admin role ROLE_ADMIN and other permissions
|
||||
INSERT INTO mv_authority_role (authority_id, role_id) VALUES (1, 1), (3, 1);
|
||||
8
out/production/resources/ehcache.xml
Normal file
8
out/production/resources/ehcache.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true" monitoring="autodetect" dynamicConfig="true">
|
||||
<diskStore path="java.io.tmpdir"/>
|
||||
<transactionManagerLookup class="net.sf.ehcache.transaction.manager.DefaultTransactionManagerLookup" properties="jndiName=java:/TransactionManager" propertySeparator=";"/>
|
||||
<cacheManagerEventListenerFactory class="" properties=""/>
|
||||
<cache name="aclCache" maxEntriesLocalHeap="10000" maxEntriesLocalDisk="1000" eternal="false" diskSpoolBufferSizeMB="20" timeToIdleSeconds="300" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LFU" transactionalMode="off">
|
||||
<persistence strategy="localTempSwap"/>
|
||||
</cache>
|
||||
</ehcache>
|
||||
422
out/production/resources/ehcache.xsd
Normal file
422
out/production/resources/ehcache.xsd
Normal file
@@ -0,0 +1,422 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" version="1.7">
|
||||
|
||||
<xs:element name="ehcache">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element maxOccurs="1" minOccurs="0" ref="diskStore"/>
|
||||
<xs:element maxOccurs="1" minOccurs="0" ref="sizeOfPolicy"/>
|
||||
<xs:element maxOccurs="1" minOccurs="0" ref="transactionManagerLookup"/>
|
||||
<xs:element maxOccurs="1" minOccurs="0" ref="cacheManagerEventListenerFactory"/>
|
||||
<xs:element maxOccurs="1" minOccurs="0" ref="managementRESTService"/>
|
||||
<xs:element maxOccurs="unbounded" minOccurs="0" ref="cacheManagerPeerProviderFactory"/>
|
||||
<xs:element maxOccurs="unbounded" minOccurs="0" ref="cacheManagerPeerListenerFactory"/>
|
||||
<xs:element maxOccurs="1" minOccurs="0" ref="terracottaConfig"/>
|
||||
<xs:element maxOccurs= "1" minOccurs="0" ref="defaultCache"/>
|
||||
<xs:element maxOccurs="unbounded" minOccurs="0" ref="cache"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" use="optional"/>
|
||||
<xs:attribute default="true" name="updateCheck" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute default="autodetect" name="monitoring" type="monitoringType" use="optional"/>
|
||||
<xs:attribute default="true" name="dynamicConfig" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute default="15" name="defaultTransactionTimeoutInSeconds" type="xs:integer" use="optional"/>
|
||||
<xs:attribute default="0" name="maxBytesLocalHeap" type="memoryUnitOrPercentage" use="optional"/>
|
||||
<xs:attribute default="0" name="maxBytesLocalOffHeap" type="memoryUnit" use="optional"/>
|
||||
<xs:attribute default="0" name="maxBytesLocalDisk" type="memoryUnit" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="managementRESTService">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="enabled" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute name="bind" use="optional"/>
|
||||
<xs:attribute name="securityServiceLocation" use="optional"/>
|
||||
<xs:attribute name="securityServiceTimeout" use="optional" type="xs:integer"/>
|
||||
<xs:attribute name="sslEnabled" use="optional" type="xs:boolean"/>
|
||||
<xs:attribute name="needClientAuth" use="optional" type="xs:boolean"/>
|
||||
<xs:attribute name="sampleHistorySize" use="optional" type="xs:integer"/>
|
||||
<xs:attribute name="sampleIntervalSeconds" use="optional" type="xs:integer"/>
|
||||
<xs:attribute name="sampleSearchIntervalSeconds" use="optional" type="xs:integer"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="diskStore">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="path" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="transactionManagerLookup">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required"/>
|
||||
<xs:attribute name="properties" use="optional"/>
|
||||
<xs:attribute name="propertySeparator" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="cacheManagerEventListenerFactory">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required"/>
|
||||
<xs:attribute name="properties" use="optional"/>
|
||||
<xs:attribute name="propertySeparator" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="cacheManagerPeerProviderFactory">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required"/>
|
||||
<xs:attribute name="properties" use="optional"/>
|
||||
<xs:attribute name="propertySeparator" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="cacheManagerPeerListenerFactory">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required"/>
|
||||
<xs:attribute name="properties" use="optional"/>
|
||||
<xs:attribute name="propertySeparator" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="terracottaConfig">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element maxOccurs="1" minOccurs="0" name="tc-config">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:any maxOccurs="unbounded" minOccurs="0" processContents="skip"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<xs:attribute default="localhost:9510" name="url" use="optional"/>
|
||||
<xs:attribute name="rejoin" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="wanEnabledTSA" type="xs:boolean" use="optional" default="false"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<!-- add clone support for addition of cacheExceptionHandler. Important! -->
|
||||
<xs:element name="defaultCache">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheEventListenerFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheExtensionFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheLoaderFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheDecoratorFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="bootstrapCacheLoaderFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="cacheExceptionHandlerFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="pinning"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="terracotta"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="cacheWriter"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="copyStrategy"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="elementValueComparator"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="sizeOfPolicy"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="persistence"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="diskExpiryThreadIntervalSeconds" type="xs:integer" use="optional"/>
|
||||
<xs:attribute name="diskSpoolBufferSizeMB" type="xs:integer" use="optional"/>
|
||||
<xs:attribute name="diskPersistent" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute name="diskAccessStripes" type="xs:integer" use="optional" default="1"/>
|
||||
<xs:attribute name="eternal" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="maxElementsInMemory" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="maxEntriesLocalHeap" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="clearOnFlush" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute name="memoryStoreEvictionPolicy" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="overflowToDisk" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute name="timeToIdleSeconds" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="timeToLiveSeconds" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="maxElementsOnDisk" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="maxEntriesLocalDisk" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="transactionalMode" type="transactionalMode" use="optional" default="off"/>
|
||||
<xs:attribute name="statistics" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="copyOnRead" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="copyOnWrite" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="cacheLoaderTimeoutMillis" type="xs:integer" use="optional" default="0"/>
|
||||
<xs:attribute name="overflowToOffHeap" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="maxMemoryOffHeap" type="xs:string" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="cache">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheEventListenerFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheExtensionFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheLoaderFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheDecoratorFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="bootstrapCacheLoaderFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="cacheExceptionHandlerFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="pinning"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="terracotta"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="cacheWriter"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="copyStrategy"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="searchable"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="elementValueComparator"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="sizeOfPolicy"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="persistence"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="diskExpiryThreadIntervalSeconds" type="xs:integer" use="optional"/>
|
||||
<xs:attribute name="diskSpoolBufferSizeMB" type="xs:integer" use="optional"/>
|
||||
<xs:attribute name="diskPersistent" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute name="diskAccessStripes" type="xs:integer" use="optional" default="1"/>
|
||||
<xs:attribute name="eternal" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="maxElementsInMemory" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="maxEntriesLocalHeap" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="memoryStoreEvictionPolicy" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="clearOnFlush" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute name="name" type="xs:string" use="required"/>
|
||||
<xs:attribute name="overflowToDisk" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute name="timeToIdleSeconds" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="timeToLiveSeconds" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="maxElementsOnDisk" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="maxEntriesLocalDisk" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="maxEntriesInCache" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="transactionalMode" type="transactionalMode" use="optional" default="off" />
|
||||
<xs:attribute name="statistics" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="copyOnRead" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="copyOnWrite" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="logging" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="cacheLoaderTimeoutMillis" type="xs:integer" use="optional" default="0"/>
|
||||
<xs:attribute name="overflowToOffHeap" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="maxMemoryOffHeap" type="xs:string" use="optional"/>
|
||||
<xs:attribute default="0" name="maxBytesLocalHeap" type="memoryUnitOrPercentage" use="optional"/>
|
||||
<xs:attribute default="0" name="maxBytesLocalOffHeap" type="memoryUnitOrPercentage" use="optional"/>
|
||||
<xs:attribute default="0" name="maxBytesLocalDisk" type="memoryUnitOrPercentage" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="cacheEventListenerFactory">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required"/>
|
||||
<xs:attribute name="properties" use="optional"/>
|
||||
<xs:attribute name="propertySeparator" use="optional"/>
|
||||
<xs:attribute name="listenFor" use="optional" type="notificationScope" default="all"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="bootstrapCacheLoaderFactory">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required"/>
|
||||
<xs:attribute name="properties" use="optional"/>
|
||||
<xs:attribute name="propertySeparator" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="cacheExtensionFactory">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required"/>
|
||||
<xs:attribute name="properties" use="optional"/>
|
||||
<xs:attribute name="propertySeparator" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="cacheExceptionHandlerFactory">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required"/>
|
||||
<xs:attribute name="properties" use="optional"/>
|
||||
<xs:attribute name="propertySeparator" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="cacheLoaderFactory">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required"/>
|
||||
<xs:attribute name="properties" use="optional"/>
|
||||
<xs:attribute name="propertySeparator" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="cacheDecoratorFactory">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required"/>
|
||||
<xs:attribute name="properties" use="optional"/>
|
||||
<xs:attribute name="propertySeparator" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="searchAttribute">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="name" use="required" type="xs:string" />
|
||||
<xs:attribute name="expression" type="xs:string" />
|
||||
<xs:attribute name="class" type="xs:string" />
|
||||
<xs:attribute name="type" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="properties" use="optional" />
|
||||
<xs:attribute name="propertySeparator" use="optional" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="searchable">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="searchAttribute"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="keys" use="optional" type="xs:boolean" default="true"/>
|
||||
<xs:attribute name="values" use="optional" type="xs:boolean" default="true"/>
|
||||
<xs:attribute name="allowDynamicIndexing" use="optional" type="xs:boolean" default="false"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="pinning">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="store" use="required" type="pinningStoreType"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="terracotta">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="nonstop"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="clustered" use="optional" type="xs:boolean" default="true"/>
|
||||
<xs:attribute name="coherentReads" use="optional" type="xs:boolean" default="true"/>
|
||||
<xs:attribute name="localKeyCache" use="optional" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="localKeyCacheSize" use="optional" type="xs:positiveInteger" default="300000"/>
|
||||
<xs:attribute name="orphanEviction" use="optional" type="xs:boolean" default="true"/>
|
||||
<xs:attribute name="orphanEvictionPeriod" use="optional" type="xs:positiveInteger" default="4"/>
|
||||
<xs:attribute name="copyOnRead" use="optional" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="coherent" use="optional" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="consistency" use="optional" type="consistencyType" default="eventual"/>
|
||||
<xs:attribute name="synchronousWrites" use="optional" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="concurrency" use="optional" type="xs:nonNegativeInteger" default="0"/>
|
||||
<xs:attribute name="localCacheEnabled" use="optional" type="xs:boolean" default="true"/>
|
||||
<xs:attribute name="compressionEnabled" use="optional" type="xs:boolean" default="false"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:simpleType name="consistencyType">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="strong" />
|
||||
<xs:enumeration value="eventual" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:element name="nonstop">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="timeoutBehavior"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="enabled" use="optional" type="xs:boolean" default="true"/>
|
||||
<xs:attribute name="immediateTimeout" use="optional" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="timeoutMillis" use="optional" type="xs:positiveInteger" default="30000"/>
|
||||
<xs:attribute name="searchTimeoutMillis" use="optional" type="xs:positiveInteger" default="30000"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="timeoutBehavior">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="type" use="optional" type="timeoutBehaviorType" default="exception"/>
|
||||
<xs:attribute name="properties" use="optional" default=""/>
|
||||
<xs:attribute name="propertySeparator" use="optional" default=","/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:simpleType name="timeoutBehaviorType">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="noop" />
|
||||
<xs:enumeration value="exception" />
|
||||
<xs:enumeration value="localReads" />
|
||||
<xs:enumeration value="localReadsAndExceptionOnWrite" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="monitoringType">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="autodetect"/>
|
||||
<xs:enumeration value="on"/>
|
||||
<xs:enumeration value="off"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="pinningStoreType">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="localMemory" />
|
||||
<xs:enumeration value="inCache" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="terracottaCacheValueType">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="serialization" />
|
||||
<xs:enumeration value="identity" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="transactionalMode">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="off"/>
|
||||
<xs:enumeration value="xa_strict"/>
|
||||
<xs:enumeration value="xa"/>
|
||||
<xs:enumeration value="local"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:element name="cacheWriter">
|
||||
<xs:complexType>
|
||||
<xs:sequence >
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="cacheWriterFactory"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="writeMode" use="optional" type="writeModeType" default="write-through"/>
|
||||
<xs:attribute name="notifyListenersOnException" use="optional" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="minWriteDelay" use="optional" type="xs:nonNegativeInteger" default="1"/>
|
||||
<xs:attribute name="maxWriteDelay" use="optional" type="xs:nonNegativeInteger" default="1"/>
|
||||
<xs:attribute name="rateLimitPerSecond" use="optional" type="xs:nonNegativeInteger" default="0"/>
|
||||
<xs:attribute name="writeCoalescing" use="optional" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="writeBatching" use="optional" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="writeBatchSize" use="optional" type="xs:positiveInteger" default="1"/>
|
||||
<xs:attribute name="retryAttempts" use="optional" type="xs:nonNegativeInteger" default="0"/>
|
||||
<xs:attribute name="retryAttemptDelaySeconds" use="optional" type="xs:nonNegativeInteger" default="1"/>
|
||||
<xs:attribute name="writeBehindConcurrency" use="optional" type="xs:nonNegativeInteger" default="1"/>
|
||||
<xs:attribute name="writeBehindMaxQueueSize" use="optional" type="xs:nonNegativeInteger" default="0"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:simpleType name="writeModeType">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="write-through" />
|
||||
<xs:enumeration value="write-behind" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:element name="cacheWriterFactory">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required"/>
|
||||
<xs:attribute name="properties" use="optional"/>
|
||||
<xs:attribute name="propertySeparator" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="copyStrategy">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required" type="xs:string" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="elementValueComparator">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required" type="xs:string" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="sizeOfPolicy">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="maxDepth" use="required" type="xs:integer" />
|
||||
<xs:attribute name="maxDepthExceededBehavior" use="optional" default="continue" type="maxDepthExceededBehavior" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="persistence">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="strategy" use="required" type="persistenceStrategy"/>
|
||||
<xs:attribute name="synchronousWrites" use="optional" default="false" type="xs:boolean"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:simpleType name="persistenceStrategy">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="localTempSwap"/>
|
||||
<xs:enumeration value="localRestartable"/>
|
||||
<xs:enumeration value="none"/>
|
||||
<xs:enumeration value="distributed"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="maxDepthExceededBehavior">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="continue"/>
|
||||
<xs:enumeration value="abort"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="notificationScope">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="local"/>
|
||||
<xs:enumeration value="remote"/>
|
||||
<xs:enumeration value="all"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="memoryUnit">
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:pattern value="[0-9]+[bBkKmMgG]?"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="memoryUnitOrPercentage">
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:pattern value="([0-9]+[bBkKmMgG]?|100%|[0-9]{1,2}%)"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:schema>
|
||||
11
out/production/resources/logback-spring.xml
Normal file
11
out/production/resources/logback-spring.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</root>
|
||||
|
||||
<logger name="org.zerhusen" level="DEBUG"/>
|
||||
</configuration>
|
||||
116
out/production/resources/static/index.html
Normal file
116
out/production/resources/static/index.html
Normal file
@@ -0,0 +1,116 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>JWT Spring Security Demo</title>
|
||||
|
||||
<!-- Latest compiled and minified CSS -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"
|
||||
integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
|
||||
|
||||
<!-- Optional theme -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css"
|
||||
integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>JWT Spring Security Demo</h1>
|
||||
|
||||
<div class="alert alert-danger" id="notLoggedIn">Not logged in!</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default" id="login">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Login</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form id="loginForm">
|
||||
<div class="form-role">
|
||||
<input type="text" class="form-control" id="exampleInputEmail1" placeholder="username"
|
||||
required name="username">
|
||||
</div>
|
||||
<div class="form-role">
|
||||
<input type="password" class="form-control" id="exampleInputPassword1"
|
||||
placeholder="password" required name="password">
|
||||
</div>
|
||||
<div class="well">
|
||||
Try one of the following logins
|
||||
<ul>
|
||||
<li>admin & admin</li>
|
||||
<li>user & password</li>
|
||||
<li>disabled & password</li>
|
||||
</ul>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default">login</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="userInfo">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Authenticated user</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div id="userInfoBody"></div>
|
||||
<button type="button" class="btn btn-default" id="logoutButton">logout</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="btn-role" role="role" aria-label="..." style="margin-bottom: 16px;">
|
||||
<button type="button" class="btn btn-default" id="exampleServiceBtn">call example service</button>
|
||||
<button type="button" class="btn btn-default" id="adminServiceBtn">call admin protected service</button>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Response:</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<pre id="response"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div id="loggedIn" class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Token information</h3>
|
||||
</div>
|
||||
<div class="panel-body" id="loggedInBody"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" tabindex="-1" role="dialog" id="loginErrorModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">Login unsuccessful</h4>
|
||||
</div>
|
||||
<div class="modal-body"></div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
|
||||
<script src="https://code.jquery.com/jquery-2.2.2.js" integrity="sha256-4/zUCqiq0kqxhZIyp4G0Gk+AOtCJsY1TA00k5ClsZYE="
|
||||
crossorigin="anonymous"></script>
|
||||
<!-- Latest compiled and minified JavaScript -->
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"
|
||||
integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="js/libs/jwt-decode.min.js"></script>
|
||||
<script src="js/client.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
194
out/production/resources/static/js/client.js
Normal file
194
out/production/resources/static/js/client.js
Normal file
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* Created by stephan on 20.03.16.
|
||||
*/
|
||||
|
||||
$(function () {
|
||||
// VARIABLES =============================================================
|
||||
var TOKEN_KEY = "jwtToken"
|
||||
var $notLoggedIn = $("#notLoggedIn");
|
||||
var $loggedIn = $("#loggedIn").hide();
|
||||
var $loggedInBody = $("#loggedInBody");
|
||||
var $response = $("#response");
|
||||
var $login = $("#login");
|
||||
var $userInfo = $("#userInfo").hide();
|
||||
|
||||
// FUNCTIONS =============================================================
|
||||
function getJwtToken() {
|
||||
return localStorage.getItem(TOKEN_KEY);
|
||||
}
|
||||
|
||||
function setJwtToken(token) {
|
||||
localStorage.setItem(TOKEN_KEY, token);
|
||||
}
|
||||
|
||||
function removeJwtToken() {
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
}
|
||||
|
||||
function doLogin(loginData) {
|
||||
$.ajax({
|
||||
url: "/auth",
|
||||
type: "POST",
|
||||
data: JSON.stringify(loginData),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
success: function (data, textStatus, jqXHR) {
|
||||
console.log(data);
|
||||
setJwtToken(data.token);
|
||||
$login.hide();
|
||||
$notLoggedIn.hide();
|
||||
showTokenInformation();
|
||||
showUserInformation();
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
if (jqXHR.status === 401 || jqXHR.status === 403) {
|
||||
$('#loginErrorModal')
|
||||
.modal("show")
|
||||
.find(".modal-body")
|
||||
.empty()
|
||||
.html("<p>Message from server:<br>" + jqXHR.responseText + "</p>");
|
||||
} else {
|
||||
throw new Error("an unexpected error occured: " + errorThrown);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function doLogout() {
|
||||
removeJwtToken();
|
||||
$login.show();
|
||||
$userInfo
|
||||
.hide()
|
||||
.find("#userInfoBody").empty();
|
||||
$loggedIn.hide();
|
||||
$loggedInBody.empty();
|
||||
$notLoggedIn.show();
|
||||
}
|
||||
|
||||
function createAuthorizationTokenHeader() {
|
||||
var token = getJwtToken();
|
||||
if (token) {
|
||||
return {"Authorization": "Bearer " + token};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function showUserInformation() {
|
||||
$.ajax({
|
||||
url: "/user",
|
||||
type: "GET",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
headers: createAuthorizationTokenHeader(),
|
||||
success: function (data, textStatus, jqXHR) {
|
||||
var $userInfoBody = $userInfo.find("#userInfoBody");
|
||||
|
||||
$userInfoBody.append($("<div>").text("Username: " + data.username));
|
||||
$userInfoBody.append($("<div>").text("Email: " + data.email));
|
||||
|
||||
var $authorityList = $("<ul>");
|
||||
data.authorities.forEach(function (authorityItem) {
|
||||
$authorityList.append($("<li>").text(authorityItem.authority));
|
||||
});
|
||||
var $authorities = $("<div>").text("Authorities:");
|
||||
$authorities.append($authorityList);
|
||||
|
||||
$userInfoBody.append($authorities);
|
||||
$userInfo.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showTokenInformation() {
|
||||
var jwtToken = getJwtToken();
|
||||
var decodedToken = jwt_decode(jwtToken);
|
||||
|
||||
$loggedInBody.append($("<h4>").text("Token"));
|
||||
$loggedInBody.append($("<div>").text(jwtToken).css("word-break", "break-all"));
|
||||
$loggedInBody.append($("<h4>").text("Token claims"));
|
||||
|
||||
var $table = $("<table>")
|
||||
.addClass("table table-striped");
|
||||
appendKeyValue($table, "sub", decodedToken.sub);
|
||||
appendKeyValue($table, "iat", decodedToken.iat);
|
||||
appendKeyValue($table, "exp", decodedToken.exp);
|
||||
|
||||
$loggedInBody.append($table);
|
||||
|
||||
$loggedIn.show();
|
||||
}
|
||||
|
||||
function appendKeyValue($table, key, value) {
|
||||
var $row = $("<tr>")
|
||||
.append($("<td>").text(key))
|
||||
.append($("<td>").text(value));
|
||||
$table.append($row);
|
||||
}
|
||||
|
||||
function showResponse(statusCode, message) {
|
||||
$response
|
||||
.empty()
|
||||
.text("status code: " + statusCode + "\n-------------------------\n" + message);
|
||||
}
|
||||
|
||||
// REGISTER EVENT LISTENERS =============================================================
|
||||
$("#loginForm").submit(function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
var $form = $(this);
|
||||
var formData = {
|
||||
username: $form.find('input[name="username"]').val(),
|
||||
password: $form.find('input[name="password"]').val()
|
||||
};
|
||||
|
||||
doLogin(formData);
|
||||
});
|
||||
|
||||
$("#logoutButton").click(doLogout);
|
||||
|
||||
$("#exampleServiceBtn").click(function () {
|
||||
$.ajax({
|
||||
url: "/persons",
|
||||
type: "GET",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
headers: createAuthorizationTokenHeader(),
|
||||
success: function (data, textStatus, jqXHR) {
|
||||
showResponse(jqXHR.status, JSON.stringify(data));
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
showResponse(jqXHR.status, errorThrown);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#adminServiceBtn").click(function () {
|
||||
$.ajax({
|
||||
url: "/protected",
|
||||
type: "GET",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
headers: createAuthorizationTokenHeader(),
|
||||
success: function (data, textStatus, jqXHR) {
|
||||
showResponse(jqXHR.status, data);
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
showResponse(jqXHR.status, errorThrown);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$loggedIn.click(function () {
|
||||
$loggedIn
|
||||
.toggleClass("text-hidden")
|
||||
.toggleClass("text-shown");
|
||||
});
|
||||
|
||||
// INITIAL CALLS =============================================================
|
||||
if (getJwtToken()) {
|
||||
$login.hide();
|
||||
$notLoggedIn.hide();
|
||||
showTokenInformation();
|
||||
showUserInformation();
|
||||
}
|
||||
});
|
||||
1
out/production/resources/static/js/libs/jwt-decode.min.js
vendored
Normal file
1
out/production/resources/static/js/libs/jwt-decode.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b,c){function d(a){this.message=a}function e(a){var b=String(a).replace(/=+$/,"");if(b.length%4==1)throw new d("'atob' failed: The string to be decoded is not correctly encoded.");for(var c,e,g=0,h=0,i="";e=b.charAt(h++);~e&&(c=g%4?64*c+e:e,g++%4)?i+=String.fromCharCode(255&c>>(-2*g&6)):0)e=f.indexOf(e);return i}var f="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";d.prototype=new Error,d.prototype.name="InvalidCharacterError",b.exports="undefined"!=typeof window&&window.atob&&window.atob.bind(window)||e},{}],2:[function(a,b,c){function d(a){return decodeURIComponent(e(a).replace(/(.)/g,function(a,b){var c=b.charCodeAt(0).toString(16).toUpperCase();return c.length<2&&(c="0"+c),"%"+c}))}var e=a("./atob");b.exports=function(a){var b=a.replace(/-/g,"+").replace(/_/g,"/");switch(b.length%4){case 0:break;case 2:b+="==";break;case 3:b+="=";break;default:throw"Illegal base64url string!"}try{return d(b)}catch(c){return e(b)}}},{"./atob":1}],3:[function(a,b,c){"use strict";function d(a){this.message=a}var e=a("./base64_url_decode");d.prototype=new Error,d.prototype.name="InvalidTokenError",b.exports=function(a,b){if("string"!=typeof a)throw new d("Invalid token specified");b=b||{};var c=b.header===!0?0:1;try{return JSON.parse(e(a.split(".")[c]))}catch(f){throw new d("Invalid token specified: "+f.message)}},b.exports.InvalidTokenError=d},{"./base64_url_decode":2}],4:[function(a,b,c){(function(b){var c=a("./lib/index");"function"==typeof b.window.define&&b.window.define.amd?b.window.define("jwt_decode",function(){return c}):b.window&&(b.window.jwt_decode=c)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./lib/index":3}]},{},[4]);
|
||||
33
out/test/resources/application.properties
Normal file
33
out/test/resources/application.properties
Normal file
@@ -0,0 +1,33 @@
|
||||
spring.jackson.serialization.INDENT_OUTPUT=true
|
||||
|
||||
spring.h2.console.enabled=true
|
||||
|
||||
#temporarily use mariaDB just to see tests work, later this has to be H2
|
||||
|
||||
#spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
|
||||
#spring.datasource.driverClassName=org.h2.Driver
|
||||
#spring.datasource.username=sa
|
||||
#spring.datasource.password=
|
||||
#spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
|
||||
|
||||
spring.jpa.hibernate.ddl-auto=validate
|
||||
|
||||
spring.datasource.platform=mariadb
|
||||
spring.datasource.url=jdbc:mariadb://localhost:3307/mariadb
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=mariadb
|
||||
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
|
||||
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
|
||||
|
||||
# Removes warning from stdout
|
||||
spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false
|
||||
|
||||
|
||||
|
||||
jwt.header=Authorization
|
||||
jwt.secret=mySecret
|
||||
jwt.expiration=604800
|
||||
jwt.route.authentication.path=/auth
|
||||
jwt.route.authentication.refresh=/refresh
|
||||
|
||||
239
pom.xml
Normal file
239
pom.xml
Normal file
@@ -0,0 +1,239 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>de.ul</groupId>
|
||||
<artifactId>mv</artifactId>
|
||||
<version>0.5.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>mv</name>
|
||||
<description>A membership management software</description>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>MIT license</name>
|
||||
<url>https://raw.githubusercontent.com/szerhusenBC/jwt-spring-security-demo/master/LICENSE</url>
|
||||
<comments>This license is connected to the jwt-spring-security-demo project that has been incorporated into our project</comments>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<name>Brennan Nicholson</name>
|
||||
<organization>University of Leipzig</organization>
|
||||
</developer>
|
||||
<developer>
|
||||
<name>David Reinartz</name>
|
||||
<organization>University of Leipzig</organization>
|
||||
</developer>
|
||||
<developer>
|
||||
<name>Denis Gessert</name>
|
||||
<organization>University of Leipzig</organization>
|
||||
</developer>
|
||||
<developer>
|
||||
<name>Emma Haley</name>
|
||||
<organization>University of Leipzig</organization>
|
||||
</developer>
|
||||
<developer>
|
||||
<name>Felix Förtsch</name>
|
||||
<organization>University of Leipzig</organization>
|
||||
</developer>
|
||||
<developer>
|
||||
<name>Stephan Zerhusen</name>
|
||||
<email>stephan.zerhusen@gmail.com</email>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.0.1.RELEASE</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<skipTests>false</skipTests>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
<jjwt.version>0.9.0</jjwt.version>
|
||||
<maven-compiler-plugin.version>3.7.0</maven-compiler-plugin.version>
|
||||
<dockerfile-maven-version>1.4.0</dockerfile-maven-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>javax.xml.bind</groupId>
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.spring.gradle</groupId>
|
||||
<artifactId>dependency-management-plugin</artifactId>
|
||||
<version>1.0.5.RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context-support</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-mail</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-acl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-config</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-rest</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-cache</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sf.ehcache</groupId>
|
||||
<artifactId>ehcache</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mariadb.jdbc</groupId>
|
||||
<artifactId>mariadb-java-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<!--<scope>test</scope>-->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.7</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>6.0.9.Final</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-validator-cdi</artifactId>
|
||||
<version>6.0.9.Final</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.glassfish</groupId>
|
||||
<artifactId>javax.el</artifactId>
|
||||
<version>3.0.1-b09</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.restdocs</groupId>
|
||||
<artifactId>spring-restdocs-mockmvc</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${maven-compiler-plugin.version}</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.21.0</version>
|
||||
<configuration>
|
||||
<skipTests>${skipTests}</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>com.spotify</groupId>
|
||||
<artifactId>dockerfile-maven-plugin</artifactId>
|
||||
<version>${dockerfile-maven-version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default</id>
|
||||
<goals>
|
||||
<goal>build</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<repository>swtp/mv</repository>
|
||||
<tag>${project.version}</tag>
|
||||
<buildArgs>
|
||||
<JAR_FILE>mv-${project.version}.jar</JAR_FILE>
|
||||
</buildArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
</profiles>
|
||||
|
||||
</project>
|
||||
4
reset-docker-mariadb
Executable file
4
reset-docker-mariadb
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
docker rm -f mariadb
|
||||
docker run --name mariadb -e MYSQL_ROOT_PASSWORD=mariadb -e MYSQL_DATABASE=mariadb -p 3307:3306 -d mariadb:latest
|
||||
12
src/main/java/de/ul/swtp/MvApplication.java
Normal file
12
src/main/java/de/ul/swtp/MvApplication.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package de.ul.swtp;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class MvApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MvApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package de.ul.swtp.exceptionhandler;
|
||||
|
||||
import org.hibernate.exception.ConstraintViolationException;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authentication.DisabledException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
||||
|
||||
@ControllerAdvice
|
||||
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
|
||||
|
||||
/*@ExceptionHandler(value = {IllegalStateException.class })
|
||||
protected ResponseEntity<Object> handleConflict(RuntimeException ex, WebRequest request) {
|
||||
String bodyOfResponse = "IllegalState";
|
||||
return handleExceptionInternal(ex, bodyOfResponse,
|
||||
new HttpHeaders(), HttpStatus.CONFLICT, request);
|
||||
}*/
|
||||
|
||||
/*//TODO: Change HttpStatus.IAMATEAPOT
|
||||
@ExceptionHandler(value = {Exception.class})
|
||||
protected ResponseEntity<Object> handleAllOtherExceptions(RuntimeException ex, WebRequest request) {
|
||||
String bodyOfResponse = ex.getMessage();
|
||||
return handleExceptionInternal(ex, bodyOfResponse,
|
||||
new HttpHeaders(), HttpStatus.I_AM_A_TEAPOT, request);
|
||||
}*/
|
||||
|
||||
@ExceptionHandler(value = {IllegalArgumentException.class})
|
||||
protected ResponseEntity<Object> handleIllegalArguments(RuntimeException ex, WebRequest request) {
|
||||
String bodyOfResponse = ex.getMessage();
|
||||
return handleExceptionInternal(ex, bodyOfResponse,
|
||||
new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = {EmptyResultDataAccessException.class})
|
||||
protected ResponseEntity<Object> notFound(RuntimeException ex, WebRequest request) {
|
||||
String bodyOfResponse = ex.getMessage();
|
||||
return handleExceptionInternal(ex, bodyOfResponse,
|
||||
new HttpHeaders(), HttpStatus.NOT_FOUND, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = {DisabledException.class})
|
||||
protected ResponseEntity<Object> disabled(RuntimeException ex, WebRequest request) {
|
||||
String bodyOfResponse = ex.getMessage();
|
||||
return handleExceptionInternal(ex, bodyOfResponse,
|
||||
new HttpHeaders(), HttpStatus.UNAUTHORIZED, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = {ConstraintViolationException.class})
|
||||
protected ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex, WebRequest request) {
|
||||
String bodyOfResponse = "Constraint violation on constraint: " + ex.getConstraintName() + ".";
|
||||
return handleExceptionInternal(ex, bodyOfResponse,
|
||||
new HttpHeaders(), HttpStatus.CONFLICT, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = {AccessDeniedException.class})
|
||||
protected ResponseEntity<Object> handleAccessDenied(AccessDeniedException ex, WebRequest request) {
|
||||
String bodyOfResponse = ex.getMessage();
|
||||
return handleExceptionInternal(ex, bodyOfResponse,
|
||||
new HttpHeaders(), HttpStatus.UNAUTHORIZED, request);
|
||||
}
|
||||
|
||||
/* @ExceptionHandler(value = {HttpMessageNotReadableException.class})
|
||||
protected ResponseEntity<Object> httpMessageNotReadable(RuntimeException ex, WebRequest request) {
|
||||
String bodyOfResponse = ex.getMessage();
|
||||
return handleExceptionInternal(ex, bodyOfResponse,
|
||||
new HttpHeaders(), HttpStatus.I_AM_A_TEAPOT, request);
|
||||
}*/
|
||||
|
||||
/* @ExceptionHandler(value = {SQLException.class})
|
||||
protected ResponseEntity<Object> sqlException(SQLException ex, WebRequest request) {
|
||||
String bodyOfResponse = ex.getMessage();
|
||||
return handleExceptionInternal(ex, bodyOfResponse,
|
||||
new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR, request);
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package de.ul.swtp.modules.contactmanagement;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
|
||||
import com.fasterxml.jackson.annotation.JsonIdentityReference;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
|
||||
import de.ul.swtp.system.User;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.persistence.*;
|
||||
import javax.validation.constraints.*;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
@Table(name = "mv_cm_contacts")
|
||||
@Data
|
||||
@JsonIdentityInfo(
|
||||
generator = ObjectIdGenerators.PropertyGenerator.class,
|
||||
property = "id",
|
||||
scope = Contact.class,
|
||||
resolver = ContactIdResolver.class)
|
||||
public class Contact implements Serializable {
|
||||
|
||||
//@Null may well not work here, if the validations are call on update as well as create
|
||||
//@Null
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
// perhaps we need a @NotBlank here? Are emails obligatory?
|
||||
@Email(regexp = "^.*@.*\\..*")
|
||||
private String email;
|
||||
|
||||
@javax.validation.constraints.NotBlank
|
||||
private String firstName;
|
||||
|
||||
@javax.validation.constraints.NotBlank
|
||||
private String lastName;
|
||||
|
||||
//TODO: write regex (or custom validator) for phone numbers
|
||||
//@Pattern(regexp = )
|
||||
// Note: phone numbers are currently optional
|
||||
@Digits(integer = 16, fraction = 0)
|
||||
private String phone;
|
||||
|
||||
@javax.validation.constraints.NotBlank
|
||||
private String address;
|
||||
|
||||
private String bankDetails;
|
||||
|
||||
@ManyToMany(mappedBy = "contacts", fetch = FetchType.LAZY)
|
||||
@JsonIdentityReference(alwaysAsId = true)
|
||||
private List<Group> groups;
|
||||
|
||||
@OneToOne(mappedBy = "contact"/*, cascade = CascadeType.ALL*/, fetch = FetchType.LAZY)
|
||||
@JsonIdentityReference(alwaysAsId = true)
|
||||
//@JsonIgnore
|
||||
private User user;
|
||||
|
||||
public String toString() {
|
||||
return "Contact(id=" + this.getId() + ", email=" + this.getEmail() + ", firstName=" + this.getFirstName() + ", lastName=" + this.getLastName() + ", phone=" + this.getPhone() + ", address=" + this.getAddress() + ", bankDetails=" + this.getBankDetails() + ")";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package de.ul.swtp.modules.contactmanagement;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@RestController
|
||||
@CrossOrigin(origins = "*")
|
||||
@RequestMapping(path = "/contacts")
|
||||
public class ContactController {
|
||||
|
||||
private ContactManager contactManager;
|
||||
|
||||
@Autowired
|
||||
public ContactController(ContactManager contactManager) {
|
||||
this.contactManager = contactManager;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Endpoint to retrieve a page of users.
|
||||
* Takes parameters page={pageId}, size={sizeOfPage} and sort={sortCriteria},{sortDirection}.
|
||||
* {sortCriteria} has to be an attribute of the Contact object.
|
||||
* {sortDirection} has to either be "asc" or "desc".
|
||||
* The results can be sorted by multiple criteria by adding more sort={sortCriteria},{sortDirection} parameters to the URI where the first sort parameter has the highest priority..
|
||||
*
|
||||
* @param pageable gets created by Spring DATA Rest from the parameters passed in the URI.
|
||||
* @return Page of Contacts.
|
||||
*/
|
||||
@GetMapping
|
||||
@ResponseBody
|
||||
public ResponseEntity<Page<Contact>> getContacts(Pageable pageable, @RequestParam(name = "group") Optional<Long> id, @RequestParam(name = "ids", required = false) List<Long> ids) {
|
||||
if (id.isPresent()) {
|
||||
Page<Contact> contacts = this.contactManager.getAll(pageable, id.get());
|
||||
return new ResponseEntity<>(contacts, HttpStatus.OK);
|
||||
} else if (ids != null) {
|
||||
Page<Contact> contacts = this.contactManager.getContactsByIds(ids, pageable);
|
||||
return new ResponseEntity<>(contacts, HttpStatus.OK);
|
||||
}
|
||||
Page<Contact> contacts = this.contactManager.getAll(pageable);
|
||||
return new ResponseEntity<>(contacts, HttpStatus.OK);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint to create a new contact.
|
||||
*
|
||||
* @param contact
|
||||
* @return
|
||||
*/
|
||||
@PostMapping
|
||||
@ResponseBody
|
||||
public ResponseEntity<Contact> createContact(@RequestBody Contact contact) {
|
||||
//TODO: validate that id field is empty
|
||||
Contact createdContact = contactManager.create(contact);
|
||||
return new ResponseEntity<>(createdContact, HttpStatus.CREATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint to retrieve the user with the specified id.
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@GetMapping(path = "/{id}")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Contact> getContact(@PathVariable("id") Long id) {
|
||||
Contact contact = contactManager.getContactById(id);
|
||||
return new ResponseEntity<>(contact, HttpStatus.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint to update the user with the id specified in the URI.
|
||||
* The id inside of the passed contact object will be ignored.
|
||||
*
|
||||
* @param contact New contact information.
|
||||
* @param id User to be updated.
|
||||
* @return
|
||||
*/
|
||||
@PutMapping(path = "/{id}")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Contact> updateContact(@RequestBody Contact contact, @PathVariable("id") Long id) {
|
||||
Contact updatedContact = contactManager.update(id, contact);
|
||||
return new ResponseEntity<>(updatedContact, HttpStatus.OK);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Endpoint to delete the user with the specified id.
|
||||
*
|
||||
* @param id Id of the user to be deleted.
|
||||
* @return Deleted user object.
|
||||
*/
|
||||
@DeleteMapping(path = "/{id}")
|
||||
@ResponseBody
|
||||
public ResponseEntity/*<Contact>*/ deleteContact(@PathVariable("id") Long id) {
|
||||
//TODO: Should return deletedContact, but as this one is changed by delete, a 500 error is generated.
|
||||
//Contact deletedContact = contactManager.getContactById(id);
|
||||
contactManager.delete(id);
|
||||
return new ResponseEntity<>(/*deletedContact, */HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package de.ul.swtp.modules.contactmanagement;
|
||||
|
||||
import com.fasterxml.jackson.annotation.ObjectIdGenerator.IdKey;
|
||||
import com.fasterxml.jackson.annotation.ObjectIdResolver;
|
||||
import com.fasterxml.jackson.annotation.SimpleObjectIdResolver;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
public class ContactIdResolver extends SimpleObjectIdResolver {
|
||||
private final ContactManager contactManager;
|
||||
|
||||
@Autowired
|
||||
public ContactIdResolver(ContactManager contactManager) {
|
||||
this.contactManager = contactManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindItem(IdKey id, Object pojo) {
|
||||
super.bindItem(id, pojo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolveId(IdKey id) {
|
||||
Object resolved = super.resolveId(id);
|
||||
if (resolved == null) {
|
||||
resolved = _tryToLoadFromSource(id);
|
||||
bindItem(id, resolved);
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
private Object _tryToLoadFromSource(IdKey idKey) {
|
||||
requireNonNull(idKey.scope, "Global scope not supported.");
|
||||
Long id = (Long) idKey.key;
|
||||
return contactManager.getContactById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectIdResolver newForDeserialization(Object context) {
|
||||
return new ContactIdResolver(contactManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUseFor(ObjectIdResolver resolverType) {
|
||||
return resolverType.getClass() == ContactIdResolver.class;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package de.ul.swtp.modules.contactmanagement;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.security.access.prepost.PostFilter;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.acls.model.Permission;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public interface ContactManager {
|
||||
|
||||
//No ACL permission is required because contact is yet to be created.
|
||||
@PreAuthorize("hasAuthority('CM_CONTACT_CREATE') or hasAuthority('ROLE_ADMIN')")
|
||||
Contact create(Contact contact);
|
||||
|
||||
@PreAuthorize("(hasAuthority('CM_CONTACT_GETCONTACTBYID') or hasAuthority('ROLE_ADMIN')) and hasPermission(#id, 'de.ul.swtp.modules.contactmanagement.Contact', read)")
|
||||
Contact getContactById(Long id);
|
||||
|
||||
@PreAuthorize("(hasAuthority('CM_CONTACT_UPDATE') or hasAuthority('ROLE_ADMIN')) and hasPermission(#id, 'de.ul.swtp.modules.contactmanagement.Contact', write)")
|
||||
Contact update(Long id, Contact contact);
|
||||
|
||||
@PreAuthorize("(hasAuthority('CM_CONTACT_DELETE') or hasAuthority('ROLE_ADMIN')) and hasPermission(#id, 'de.ul.swtp.modules.contactmanagement.Contact', delete)")
|
||||
void delete(Long id);
|
||||
|
||||
@PreAuthorize("(hasAuthority('CM_CONTACT_ADDPERMISSIONTOGROUP') or hasAuthority('ROLE_ADMIN')) and hasPermission(#id, 'de.ul.swtp.modules.contactmanagement.Contact', write)")
|
||||
void addPermissionToGroup(Long contactId, Long groupId, Permission permission);
|
||||
|
||||
@PreAuthorize("(hasAuthority('CM_CONTACT_ADDPERMISSIONTOGROUPS') or hasAuthority('ROLE_ADMIN')) and hasPermission(#id, 'de.ul.swtp.modules.contactmanagement.Contact', write)")
|
||||
void addPermissionToGroups(Long contactId, Collection<Long> groupIds, Permission permission);
|
||||
|
||||
/**
|
||||
* Returns a page of contacts.
|
||||
* @param pageable
|
||||
* @return
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('CM_CONTACT_GETALL') or hasAuthority('ROLE_ADMIN')")
|
||||
//@PostFilter("hasPermission(filterObject, read)")
|
||||
Page<Contact> getAll(Pageable pageable);
|
||||
|
||||
/**
|
||||
* Returns a page of contacts that are assigned to the role specified by the given id.
|
||||
* @param pageable
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('CM_CONTACT_GETALL') or hasAuthority('ROLE_ADMIN')")
|
||||
//@PostFilter("hasPermission(filterObject, read)")
|
||||
Page<Contact> getAll(Pageable pageable, Long id);
|
||||
|
||||
@PreAuthorize("hasAuthority('CM_CONTACT_GETCONTACTSBYIDS') or hasAuthority('ROLE_ADMIN')")
|
||||
//@PostFilter("hasPermission(filterObject, read)")
|
||||
Page<Contact> getContactsByIds(List<Long> contactIds, Pageable pageable);
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
package de.ul.swtp.modules.contactmanagement;
|
||||
|
||||
import de.ul.swtp.security.acl.CustomJdbcMutableAclService;
|
||||
import de.ul.swtp.system.User;
|
||||
import de.ul.swtp.system.UserManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.security.acls.domain.BasePermission;
|
||||
import org.springframework.security.acls.domain.GrantedAuthoritySid;
|
||||
import org.springframework.security.acls.domain.ObjectIdentityImpl;
|
||||
import org.springframework.security.acls.domain.PrincipalSid;
|
||||
import org.springframework.security.acls.model.*;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
@Transactional
|
||||
public class ContactManagerImpl implements ContactManager {
|
||||
|
||||
private final ContactRepository contactRepository;
|
||||
private final GroupManager groupManager;
|
||||
private final UserManager userManager;
|
||||
private final CustomJdbcMutableAclService customJdbcMutableAclService;
|
||||
|
||||
@Autowired
|
||||
public ContactManagerImpl(ContactRepository contactRepository, GroupManager groupManager, UserManager userManager, CustomJdbcMutableAclService customJdbcMutableAclService) {
|
||||
this.contactRepository = contactRepository;
|
||||
this.groupManager = groupManager;
|
||||
this.userManager = userManager;
|
||||
this.customJdbcMutableAclService = customJdbcMutableAclService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Contact create(Contact contact) {
|
||||
contact.setId(null);
|
||||
Contact createdContact = contactRepository.save(contact);
|
||||
if (createdContact.getGroups() != null) {
|
||||
addContactToGroups(createdContact);
|
||||
for (Group group : createdContact.getGroups()) {
|
||||
customJdbcMutableAclService.addAceToAcl(createdContact.getId(), new GrantedAuthoritySid("CM_GROUP_" + group.getId()), PermissionEnum.resolve(group.getPermissionEnum()), Contact.class);
|
||||
}
|
||||
}
|
||||
addPermission(createdContact.getId(), new GrantedAuthoritySid("ROLE_ADMIN"), BasePermission.ADMINISTRATION);
|
||||
addPermission(createdContact.getId(), new PrincipalSid(getUsername()), BasePermission.ADMINISTRATION);
|
||||
|
||||
//JPA and ACL between Contact and User
|
||||
if (contact.getUser() != null) addContactToUser(createdContact);
|
||||
|
||||
return createdContact;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a contact object.
|
||||
* Throws EmptyResultDataAccessException when Contact does not exist.
|
||||
*
|
||||
* @param id
|
||||
* @return Contact object.
|
||||
*/
|
||||
@Override
|
||||
public Contact getContactById(Long id) {
|
||||
return contactRepository.findById(id).orElseThrow(() -> new EmptyResultDataAccessException(
|
||||
String.format("No Contact with id %s exists!", id), 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Contact update(Long id, Contact contact) {
|
||||
Contact contactToBeUpdated = getContactById(id);
|
||||
|
||||
//Remove this contact from all groups that have it at the moment.
|
||||
removeContactFromAllGroups(contactToBeUpdated);
|
||||
|
||||
//Delete all ACEs in this contacts ACL that are specified in contactToBeUpdated.getGroups();
|
||||
//These are all ACEs in the list. Can be made way more efficient.
|
||||
for (Group group : contactToBeUpdated.getGroups()) {
|
||||
customJdbcMutableAclService.deleteAceFromAcl(id, new GrantedAuthoritySid("CM_GROUP_" + group.getId()), PermissionEnum.resolve(group.getPermissionEnum()), Contact.class);
|
||||
}
|
||||
|
||||
if (contact.getEmail() != null) contactToBeUpdated.setEmail(contact.getEmail());
|
||||
if (contact.getFirstName() != null) contactToBeUpdated.setFirstName(contact.getFirstName());
|
||||
if (contact.getLastName() != null) contactToBeUpdated.setLastName(contact.getLastName());
|
||||
if (contact.getPhone() != null) contactToBeUpdated.setPhone(contact.getPhone());
|
||||
if (contact.getAddress() != null) contactToBeUpdated.setAddress(contact.getAddress());
|
||||
if (contact.getBankDetails() != null) contactToBeUpdated.setBankDetails(contact.getBankDetails());
|
||||
if (contact.getGroups() != null) contactToBeUpdated.setGroups(contact.getGroups());
|
||||
//Only do something when contact.getUser() != null
|
||||
//Delete relation when user.getContact().getId() is 0.
|
||||
/*if (contact.getUser() != null) {
|
||||
//if sent user has a contact
|
||||
if (contact.getUser().getId().equals(0L) && contactToBeUpdated.getUser() != null) {
|
||||
//if sent contacts id = 0 and oldUser has a contact, delete relation
|
||||
removeContactFromUser(contactToBeUpdated);
|
||||
contactToBeUpdated.setUser(null);
|
||||
} else if (contactToBeUpdated.getUser() == null || !contact.getUser().getId().equals(contactToBeUpdated.getUser().getId())) {
|
||||
//if id != null and id is different than before, overwrite relation
|
||||
if (contactToBeUpdated.getUser() != null) {
|
||||
System.out.println("Access deleteAce from Contact");
|
||||
removeContactFromUser(contactToBeUpdated);
|
||||
//customJdbcMutableAclService.deleteAceFromAcl(contactToBeUpdated.getId(), new PrincipalSid(contactToBeUpdated.getUser().getUsername()), BasePermission.ADMINISTRATION, Contact.class);
|
||||
}
|
||||
contactToBeUpdated.setUser(contact.getUser());
|
||||
addContactToUser(contactToBeUpdated);
|
||||
}
|
||||
}*/
|
||||
|
||||
Contact updatedContact = contactRepository.save(contactToBeUpdated);
|
||||
|
||||
//add this Contact to all groups that are in the updated contact
|
||||
addContactToGroups(updatedContact);
|
||||
|
||||
//Add ACEs that are specified in updatedContact.getGroups()
|
||||
for (Group group : updatedContact.getGroups()) {
|
||||
System.out.println("Access addAce from Contact");
|
||||
customJdbcMutableAclService.addAceToAcl(id, new GrantedAuthoritySid("CM_GROUP_" + group.getId()), PermissionEnum.resolve(group.getPermissionEnum()), Contact.class);
|
||||
}
|
||||
|
||||
return updatedContact;
|
||||
}
|
||||
|
||||
private void removeContactFromUser(Contact contact) {
|
||||
User user = userManager.getUserById(contact.getUser().getId());
|
||||
Contact dummyContact = new Contact();
|
||||
dummyContact.setEmail("max@mustercontact.com");
|
||||
dummyContact.setId(0L);
|
||||
user.setContact(dummyContact);
|
||||
//calling update here should not be a problem because Contact is null.
|
||||
userManager.updateWithoutTouchingAcl(user.getId(), user);
|
||||
//remove ACL entry if existent
|
||||
//System.out.println("Access deleteAce from Contact 2");
|
||||
//customJdbcMutableAclService.deleteAceFromAcl(contact.getId(), new PrincipalSid(contact.getUser().getUsername()), BasePermission.ADMINISTRATION, Contact.class);
|
||||
}
|
||||
|
||||
private void addContactToUser(Contact contact) {
|
||||
User user = userManager.getUserById(contact.getUser().getId());
|
||||
user.setContact(contact);
|
||||
//Here update should not touch ACL please
|
||||
userManager.updateWithoutTouchingAcl(user.getId(), user);
|
||||
//set ACL entry
|
||||
//System.out.println("Access addAce from Contact 2");
|
||||
//customJdbcMutableAclService.addAceToAcl(contact.getId(), new PrincipalSid(contact.getUser().getUsername()), BasePermission.ADMINISTRATION, Contact.class);
|
||||
}
|
||||
|
||||
private void removeContactFromAllGroups(Contact contact) {
|
||||
List<Group> groupsThatAlreadyHaveThisRole = groupManager.getAllByContacts(contact);
|
||||
for (Group group : groupsThatAlreadyHaveThisRole) {
|
||||
group.getContacts().remove(contact);
|
||||
groupManager.updateWithoutTouchingAcl(group.getId(), group);
|
||||
}
|
||||
}
|
||||
|
||||
private void addContactToGroups(Contact contact) {
|
||||
for (Group group : contact.getGroups()) {
|
||||
group.getContacts().add(contact);
|
||||
groupManager.updateWithoutTouchingAcl(group.getId(), group);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void delete(Long id) {
|
||||
ObjectIdentity oi = new ObjectIdentityImpl(Contact.class, id);
|
||||
customJdbcMutableAclService.deleteAcl(oi, false);
|
||||
Contact contact = getContactById(id);
|
||||
removeContactFromAllGroups(contact);
|
||||
//Delete this contact from its corresponding user if relation exists + remove ACE
|
||||
if (contact.getUser() != null) removeContactFromUser(contact);
|
||||
contactRepository.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Contact> getAll(Pageable pageable) {
|
||||
return contactRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Contact> getAll(Pageable pageable, Long id) {
|
||||
Group group = groupManager.getGroupById(id);
|
||||
return contactRepository.findAllByGroups(group, pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Contact> getContactsByIds(List<Long> contactIds, Pageable pageable) {
|
||||
return contactRepository.findAllByIdIn(contactIds, pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPermissionToGroups(Long contactId, Collection<Long> groupIds, Permission permission) {
|
||||
//TODO: add functionality.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPermissionToGroup(Long contactId, Long groupId, Permission permission) {
|
||||
GrantedAuthoritySid groupSid = new GrantedAuthoritySid("CM_GROUP_" + groupId);
|
||||
addPermission(contactId, groupSid, permission);
|
||||
}
|
||||
|
||||
private void addPermission(Long contactId, Sid recipient, Permission permission) {
|
||||
MutableAcl acl;
|
||||
ObjectIdentity oid = new ObjectIdentityImpl(Contact.class, contactId);
|
||||
|
||||
try {
|
||||
acl = (MutableAcl) customJdbcMutableAclService.readAclById(oid);
|
||||
} catch (NotFoundException nfe) {
|
||||
acl = customJdbcMutableAclService.createAcl(oid);
|
||||
}
|
||||
|
||||
acl.insertAce(acl.getEntries().size(), permission, recipient, true);
|
||||
customJdbcMutableAclService.updateAcl(acl);
|
||||
|
||||
}
|
||||
|
||||
protected String getUsername() {
|
||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
if (auth.getPrincipal() instanceof UserDetails) {
|
||||
return ((UserDetails) auth.getPrincipal()).getUsername();
|
||||
} else {
|
||||
return auth.getPrincipal().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package de.ul.swtp.modules.contactmanagement;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface ContactRepository extends JpaRepository<Contact, Long> {
|
||||
Page<Contact> findAllByIdIn(List<Long> ids, Pageable pageable);
|
||||
|
||||
Page<Contact> findAllByGroups(Group group, Pageable pageable);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package de.ul.swtp.modules.contactmanagement;
|
||||
|
||||
import com.fasterxml.jackson.annotation.*;
|
||||
import de.ul.swtp.system.User;
|
||||
import lombok.Data;
|
||||
import org.hibernate.annotations.LazyCollection;
|
||||
import org.hibernate.annotations.LazyCollectionOption;
|
||||
import org.hibernate.annotations.WhereJoinTable;
|
||||
|
||||
import javax.persistence.*;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.stream.Collectors.toCollection;
|
||||
|
||||
@Data
|
||||
@Entity
|
||||
@Table(name = "mv_cm_groups")
|
||||
@JsonIdentityInfo(
|
||||
generator = ObjectIdGenerators.PropertyGenerator.class,
|
||||
property = "id",
|
||||
scope = Group.class,
|
||||
resolver = GroupIdResolver.class)
|
||||
public class Group implements Serializable {
|
||||
|
||||
public Group() {
|
||||
}
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@NotBlank
|
||||
private String name;
|
||||
|
||||
@JsonIgnore
|
||||
private Long aclSidId;
|
||||
|
||||
@NotNull
|
||||
@JsonProperty("permission")
|
||||
private PermissionEnum permissionEnum;
|
||||
|
||||
@ManyToMany
|
||||
@LazyCollection(LazyCollectionOption.FALSE)
|
||||
@JoinTable(
|
||||
name = "mv_group_contact",
|
||||
joinColumns = {@JoinColumn(name = "GROUP_ID", referencedColumnName = "ID")},
|
||||
inverseJoinColumns = {@JoinColumn(name = "CONTACT_ID", referencedColumnName = "ID")})
|
||||
@JsonIdentityReference(alwaysAsId=true)
|
||||
private List<Contact> contacts;
|
||||
|
||||
@ManyToMany
|
||||
@LazyCollection(LazyCollectionOption.FALSE)
|
||||
@JoinTable(
|
||||
name = "mv_group_contact_responsible",
|
||||
joinColumns = {@JoinColumn(name = "GROUP_ID", referencedColumnName = "ID")},
|
||||
inverseJoinColumns = {@JoinColumn(name = "CONTACT_ID", referencedColumnName = "ID")})
|
||||
@JsonIdentityReference(alwaysAsId=true)
|
||||
private List<Contact> responsibles;
|
||||
|
||||
@ManyToMany(mappedBy = "groups", fetch = FetchType.LAZY)
|
||||
@JsonIdentityReference(alwaysAsId=true)
|
||||
private List<User> users;
|
||||
|
||||
public String toString() {
|
||||
return "Group(id=" + this.getId() + ", name=" + this.getName() + ", aclSidId=" + this.getAclSidId() + ", permissionEnum=" + this.getPermissionEnum() + ", contacts=" + this.getContacts() + ", users=" + this.getUsers() + ")";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package de.ul.swtp.modules.contactmanagement;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@CrossOrigin(origins = "*")
|
||||
@RequestMapping(path = "/groups")
|
||||
public class GroupController {
|
||||
|
||||
private final GroupManager groupManager;
|
||||
|
||||
@Autowired
|
||||
public GroupController(GroupManager groupManager) {
|
||||
this.groupManager = groupManager;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ResponseBody
|
||||
public ResponseEntity<Page<Group>> getGroups(Pageable pageable, @RequestParam(name = "ids", required = false) List<Long> ids) {
|
||||
if(ids == null){
|
||||
Page<Group> groups = groupManager.getAll(pageable);
|
||||
return new ResponseEntity<>(groups, HttpStatus.OK);
|
||||
}
|
||||
Page<Group> groups = groupManager.getGroupsByIds(ids, pageable);
|
||||
return new ResponseEntity<>(groups, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ResponseBody
|
||||
public ResponseEntity<Group> createGroup(@RequestBody Group group) {
|
||||
//TODO: validate that id field is empty
|
||||
Group createdGroup = groupManager.create(group);
|
||||
return new ResponseEntity<>(createdGroup, HttpStatus.OK);
|
||||
}
|
||||
|
||||
|
||||
@GetMapping(path = "/{id}")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Group> getGroup(@PathVariable("id") Long id) {
|
||||
//TODO: Should only return {name, id, contactId[], userId[]} of the group.
|
||||
Group group = groupManager.getGroupById(id);
|
||||
return new ResponseEntity<>(group, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PutMapping(path = "/{id}")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Group> updateGroup(@RequestBody Group group, @PathVariable("id") Long id) {
|
||||
Group createdGroup = groupManager.update(id, group);
|
||||
return new ResponseEntity<>(createdGroup, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@DeleteMapping(path = "/{id}")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Group> deleteGroup(@PathVariable("id") Long id) {
|
||||
Group deletedGroup = groupManager.getGroupById(id);
|
||||
groupManager.delete(id);
|
||||
return new ResponseEntity<>(deletedGroup, HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package de.ul.swtp.modules.contactmanagement;
|
||||
|
||||
import com.fasterxml.jackson.annotation.ObjectIdGenerator.IdKey;
|
||||
import com.fasterxml.jackson.annotation.ObjectIdResolver;
|
||||
import com.fasterxml.jackson.annotation.SimpleObjectIdResolver;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
public class GroupIdResolver extends SimpleObjectIdResolver {
|
||||
private final GroupManager groupManager;
|
||||
|
||||
@Autowired
|
||||
public GroupIdResolver(GroupManager groupManager) {
|
||||
this.groupManager = groupManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindItem(IdKey id, Object pojo) {
|
||||
super.bindItem(id, pojo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolveId(IdKey id) {
|
||||
Object resolved = super.resolveId(id);
|
||||
if (resolved == null) {
|
||||
resolved = _tryToLoadFromSource(id);
|
||||
bindItem(id, resolved);
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
private Object _tryToLoadFromSource(IdKey idKey) {
|
||||
requireNonNull(idKey.scope, "Global scope not supported.");
|
||||
Long id = (Long) idKey.key;
|
||||
return groupManager.getGroupById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectIdResolver newForDeserialization(Object context) {
|
||||
return new GroupIdResolver(groupManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUseFor(ObjectIdResolver resolverType) {
|
||||
return resolverType.getClass() == GroupIdResolver.class;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package de.ul.swtp.modules.contactmanagement;
|
||||
|
||||
import de.ul.swtp.system.User;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface GroupManager {
|
||||
|
||||
@PreAuthorize("hasAuthority('CM_GROUP_CREATE') or hasAuthority('ROLE_ADMIN')")
|
||||
Group create(Group group);
|
||||
|
||||
@PreAuthorize("hasAuthority('CM_GROUP_GETGROUPBYID') or hasAuthority('ROLE_ADMIN')")
|
||||
Group getGroupById(Long id);
|
||||
|
||||
@PreAuthorize("hasAuthority('CM_GROUP_GETGROUPSBYIDS') or hasAuthority('ROLE_ADMIN')")
|
||||
Page<Group> getGroupsByIds(List<Long> groupIds, Pageable pageable);
|
||||
|
||||
@PreAuthorize("hasAuthority('CM_GROUP_GETALL') or hasAuthority('ROLE_ADMIN')")
|
||||
Page<Group> getAll(Pageable pageable);
|
||||
|
||||
@PreAuthorize("hasAuthority('CM_GROUP_GETALLBYCONTACTS') or hasAuthority('ROLE_ADMIN')")
|
||||
List<Group> getAllByContacts(Contact contact);
|
||||
|
||||
@PreAuthorize("hasAuthority('CM_GROUP_FINDUSERSINGROUP') or hasAuthority('ROLE_ADMIN')")
|
||||
List<User> findUsersInGroup(Long id);
|
||||
|
||||
@PreAuthorize("hasAuthority('CM_GROUP_UPDATE') or hasAuthority('ROLE_ADMIN')")
|
||||
Group update(Long id, Group group);
|
||||
|
||||
@PreAuthorize("hasAuthority('CM_GROUP_UPDATE') or hasAuthority('ROLE_ADMIN')")
|
||||
Group updateWithoutTouchingAcl(Long id, Group group);
|
||||
|
||||
@PreAuthorize("hasAuthority('CM_GROUP_DELETE') or hasAuthority('ROLE_ADMIN')")
|
||||
void delete(Long id);
|
||||
|
||||
@PreAuthorize("hasAuthority('CM_GROUP_ADDUSERTOGROUP') or hasAuthority('ROLE_ADMIN')")
|
||||
void addUserToGroup(Long groupId, Long userId);
|
||||
|
||||
@PreAuthorize("hasAuthority('CM_GROUP_ADDUSERSTOGROUP') or hasAuthority('ROLE_ADMIN')")
|
||||
void addUsersToGroup(Long groupId, List<Long> userIds);
|
||||
|
||||
@PreAuthorize("hasAuthority('CM_GROUP_REMOVEUSERFROMGROUP') or hasAuthority('ROLE_ADMIN')")
|
||||
void removeUserFromGroup(Long groupId, Long userId);
|
||||
|
||||
@PreAuthorize("hasAuthority('CM_GROUP_REMOVEUSERSFROMGROUP') or hasAuthority('ROLE_ADMIN')")
|
||||
void removeUsersFromGroup(Long groupId, List<Long> userIds);
|
||||
|
||||
@PreAuthorize("hasAuthority('CM_GROUP_ADDAUTHORITYTOGROUP') or hasAuthority('ROLE_ADMIN')")
|
||||
void addAuthorityToGroup(Long groupId, Long authorityId);
|
||||
|
||||
@PreAuthorize("hasAuthority('CM_GROUP_ADDAUTHORITIESTOGROUP') or hasAuthority('ROLE_ADMIN')")
|
||||
void addAuthoritiesToGroup(Long groupId, List<Long> authorityIds);
|
||||
|
||||
@PreAuthorize("hasAuthority('CM_GROUP_REMOVEAUTHORITYFROMGROUP') or hasAuthority('ROLE_ADMIN')")
|
||||
void removeAuthorityFromGroup(Long groupId, Long authorityId);
|
||||
|
||||
@PreAuthorize("hasAuthority('CM_GROUP_REMOVEAUTHORITIESFROMGROUP') or hasAuthority('ROLE_ADMIN')")
|
||||
void removeAuthoritiesFromGroup(Long groupId, List<Long> authorityIds);
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package de.ul.swtp.modules.contactmanagement;
|
||||
|
||||
import de.ul.swtp.security.acl.CustomJdbcMutableAclService;
|
||||
import de.ul.swtp.system.User;
|
||||
import de.ul.swtp.system.UserManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.security.acls.domain.GrantedAuthoritySid;
|
||||
import org.springframework.security.acls.model.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class GroupManagerImpl implements GroupManager {
|
||||
|
||||
private final GroupRepository groupRepository;
|
||||
private final UserManager userManager;
|
||||
private final CustomJdbcMutableAclService customJdbcMutableAclService;
|
||||
|
||||
@Autowired
|
||||
public GroupManagerImpl(GroupRepository groupRepository, UserManager userManager, CustomJdbcMutableAclService customJdbcMutableAclService) {
|
||||
this.groupRepository = groupRepository;
|
||||
this.userManager = userManager;
|
||||
this.customJdbcMutableAclService = customJdbcMutableAclService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Group create(Group group) {
|
||||
Group createdGroup = groupRepository.save(group);
|
||||
if (createdGroup.getUsers() != null) addGroupToUsers(createdGroup);
|
||||
Long generatedId = createdGroup.getId();
|
||||
Long groupAclSidId = customJdbcMutableAclService.createGrantedAuthoritySidForGroup(generatedId);
|
||||
createdGroup.setAclSidId(groupAclSidId);
|
||||
|
||||
if (createdGroup.getContacts() != null) {
|
||||
for (Contact contact : createdGroup.getContacts()) {
|
||||
customJdbcMutableAclService.addAceToAcl(contact.getId(), new GrantedAuthoritySid("CM_GROUP_" + createdGroup.getId()), PermissionEnum.resolve(createdGroup.getPermissionEnum()), Contact.class);
|
||||
}
|
||||
}
|
||||
return groupRepository.save(createdGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Group getGroupById(Long id) {
|
||||
return groupRepository.findById(id).orElseThrow(() -> new EmptyResultDataAccessException(
|
||||
String.format("No Group with id %s exists!", id), 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Group> getGroupsByIds(List<Long> groupIds, Pageable pageable) {
|
||||
return groupRepository.findAllByIdIn(groupIds, pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Group update(Long id, Group group) {
|
||||
Group groupToBeUpdated = getGroupById(id);
|
||||
|
||||
//JPA
|
||||
//remove this group from all users that have it at the moment.
|
||||
removeGroupFromAllUsers(groupToBeUpdated);
|
||||
|
||||
//ACL
|
||||
//delete this groups ACE from all contacts.
|
||||
for (Contact contact : groupToBeUpdated.getContacts()) {
|
||||
customJdbcMutableAclService.deleteAceFromAcl(contact.getId(), new GrantedAuthoritySid("CM_GROUP_" + id), PermissionEnum.resolve(groupToBeUpdated.getPermissionEnum()), Contact.class);
|
||||
}
|
||||
|
||||
//Main update, this also handles JPA for group <-> contact.
|
||||
if (group.getName() != null) groupToBeUpdated.setName(group.getName());
|
||||
if (group.getUsers() != null) groupToBeUpdated.setUsers(group.getUsers());
|
||||
if (group.getContacts() != null) groupToBeUpdated.setContacts(group.getContacts());
|
||||
if (group.getResponsibles() != null) groupToBeUpdated.setResponsibles(group.getResponsibles());
|
||||
if (group.getPermissionEnum() != null) groupToBeUpdated.setPermissionEnum(group.getPermissionEnum());
|
||||
|
||||
Group updatedGroup = groupRepository.save(groupToBeUpdated);
|
||||
|
||||
//JPA
|
||||
//add this group to all users that are in the updated group
|
||||
addGroupToUsers(updatedGroup);
|
||||
|
||||
//ACL
|
||||
//add ACEs back to the contacts in the contact list.
|
||||
for (Contact contact : updatedGroup.getContacts()) {
|
||||
customJdbcMutableAclService.addAceToAcl(contact.getId(), new GrantedAuthoritySid("CM_GROUP_" + updatedGroup.getId()), PermissionEnum.resolve(updatedGroup.getPermissionEnum()), Contact.class);
|
||||
}
|
||||
|
||||
return updatedGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Group updateWithoutTouchingAcl(Long id, Group group) {
|
||||
Group groupToBeUpdated = getGroupById(id);
|
||||
|
||||
//JPA
|
||||
//remove this group from all users that have it at the moment.
|
||||
removeGroupFromAllUsers(groupToBeUpdated);
|
||||
|
||||
//Main update, this also handles JPA for group <-> contact.
|
||||
if (group.getName() != null) groupToBeUpdated.setName(group.getName());
|
||||
if (group.getUsers() != null) groupToBeUpdated.setUsers(group.getUsers());
|
||||
if (group.getContacts() != null) groupToBeUpdated.setContacts(group.getContacts());
|
||||
if (group.getPermissionEnum() != null) groupToBeUpdated.setPermissionEnum(group.getPermissionEnum());
|
||||
|
||||
Group updatedGroup = groupRepository.save(groupToBeUpdated);
|
||||
|
||||
//JPA
|
||||
//add this group to all users that are in the updated group
|
||||
addGroupToUsers(updatedGroup);
|
||||
|
||||
return updatedGroup;
|
||||
}
|
||||
|
||||
private void removeGroupFromAllUsers(Group group) {
|
||||
List<User> usersThatAlreadyHaveThisGroup = group.getUsers();
|
||||
for (User user : usersThatAlreadyHaveThisGroup) {
|
||||
user.getGroups().remove(group);
|
||||
userManager.update(user.getId(), user);
|
||||
}
|
||||
}
|
||||
|
||||
private void addGroupToUsers(Group group) {
|
||||
for (User user : group.getUsers()) {
|
||||
if (group.getUsers().contains(user) && !user.getGroups().contains(group)) {
|
||||
user.getGroups().add(group);
|
||||
userManager.update(user.getId(), user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Long id) {
|
||||
Long groupAclSidId = getGroupById(id).getAclSidId();
|
||||
customJdbcMutableAclService.deleteAclSidByAclSidId(groupAclSidId);
|
||||
removeGroupFromAllUsers(getGroupById(id));
|
||||
groupRepository.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Group> getAll(Pageable pageable) {
|
||||
return groupRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Group> getAllByContacts(Contact contact) {
|
||||
return groupRepository.findAllByContacts(contact);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<User> findUsersInGroup(Long id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUserToGroup(Long userId, Long groupId) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUserFromGroup(Long userId, Long groupId) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUsersToGroup(Long groupId, List<Long> userIds) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUsersFromGroup(Long groupId, List<Long> userIds) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAuthorityToGroup(Long groupId, Long authorityId) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAuthoritiesToGroup(Long groupId, List<Long> authorityIds) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAuthorityFromGroup(Long groupId, Long authorityId) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAuthoritiesFromGroup(Long groupId, List<Long> authorityIds) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package de.ul.swtp.modules.contactmanagement;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface GroupRepository extends JpaRepository<Group, Long> {
|
||||
List<Group> findAllByContacts(Contact contact);
|
||||
|
||||
Page<Group> findAllByIdIn(List<Long> ids, Pageable pageable);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package de.ul.swtp.modules.contactmanagement;
|
||||
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.security.acls.domain.BasePermission;
|
||||
import org.springframework.security.acls.model.Permission;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
public enum PermissionEnum {
|
||||
READ, WRITE, CREATE, DELETE, ADMINISTRATION;
|
||||
|
||||
public static Permission resolve(PermissionEnum permissionEnum) throws EmptyResultDataAccessException {
|
||||
Assert.notNull(permissionEnum, "PermissionEnum must not be null!");
|
||||
switch (permissionEnum) {
|
||||
case READ:
|
||||
return BasePermission.READ;
|
||||
case WRITE:
|
||||
return BasePermission.WRITE;
|
||||
case CREATE:
|
||||
return BasePermission.CREATE;
|
||||
case DELETE:
|
||||
return BasePermission.READ;
|
||||
case ADMINISTRATION:
|
||||
return BasePermission.ADMINISTRATION;
|
||||
default:
|
||||
throw new EmptyResultDataAccessException("This Group does not have a Permission! Add that first!", 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package de.ul.swtp.modules.messaging;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
@Service
|
||||
public class JavaMailSenderConfig {
|
||||
|
||||
@Bean
|
||||
public JavaMailSender javaMailSender() {
|
||||
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
|
||||
javaMailSender.setHost("regulus.uberspace.de");
|
||||
javaMailSender.setPort(587);
|
||||
javaMailSender.setUsername("mv@felixfoertsch.de");
|
||||
javaMailSender.setPassword("zWiXUjNwWxsNNG2mW2vP5uQUzap85Q");
|
||||
|
||||
Properties props = javaMailSender.getJavaMailProperties();
|
||||
props.put("mail.transport.protocol", "smtp");
|
||||
props.put("mail.smtp.auth", "true");
|
||||
props.put("mail.smtp.starttls.enable", "true");
|
||||
props.put("mail.debug", "true");
|
||||
|
||||
return javaMailSender;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package de.ul.swtp.modules.messaging;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class SimpleMailMessageWrapper extends SimpleMailMessage {
|
||||
|
||||
@Value("${mv.email.from}")
|
||||
private String from;
|
||||
|
||||
@Value("${mv.email.subject-prefix}")
|
||||
private String prefix;
|
||||
|
||||
public SimpleMailMessageWrapper() {
|
||||
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
|
||||
simpleMailMessage.setFrom(from);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSubject(String subject) {
|
||||
String prefixedSubject = prefix + subject;
|
||||
super.setSubject(prefixedSubject);
|
||||
}
|
||||
}
|
||||
13
src/main/java/de/ul/swtp/relationships/EntryWrapper.java
Normal file
13
src/main/java/de/ul/swtp/relationships/EntryWrapper.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package de.ul.swtp.relationships;
|
||||
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class EntryWrapper {
|
||||
|
||||
private List<ImmutablePair<Long, Long>> entries;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package de.ul.swtp.relationships;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(path = "/rel")
|
||||
public class RelationshipController {
|
||||
private final RelationshipManager relationshipManager;
|
||||
private ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
public RelationshipController(RelationshipManager relationshipManager) {
|
||||
this.relationshipManager = relationshipManager;
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
@ResponseBody
|
||||
public ResponseEntity updateJoinUserRole(/*@RequestBody EntryWrapper entryWrapper*/) {
|
||||
/*System.out.println(json);
|
||||
ObjectNode node = mapper.readValue(json, ObjectNode.class);
|
||||
|
||||
if (node.get("log") != null) {
|
||||
String log = node.get("log").textValue();
|
||||
// do something with log
|
||||
node.remove("log"); // !important
|
||||
}
|
||||
|
||||
List<ImmutablePair<Long, Long>> entries = mapper.convertValue(node, ImmutablePair.class);*/
|
||||
/*List<ImmutablePair<Long, Long>> entriesAsPairs = new ArrayList<>();
|
||||
for(List<Long> entry: entries){
|
||||
entriesAsPairs.add(new ImmutablePair(entry.get(0), entry.get(1)));
|
||||
}*/
|
||||
//entries.stream().map(pairList -> new ImmutablePair(pairList.get(0), pairList.get(1))).collect(Collectors.toList());
|
||||
|
||||
|
||||
EntryWrapper entryWrapper = new EntryWrapper();
|
||||
List<ImmutablePair<Long, Long>> list = new ArrayList<>();
|
||||
//list.add(new ImmutablePair<Long, Long>(1L, 1L));
|
||||
list.add(new ImmutablePair<Long, Long>(1L, 2L));
|
||||
entryWrapper.setEntries(list);
|
||||
List<ImmutablePair<Long, Long>> entries = entryWrapper.getEntries();
|
||||
relationshipManager.updateJoinUserRole(entries);
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ResponseBody
|
||||
public ResponseEntity<EntryWrapper> getSampleList() {
|
||||
EntryWrapper entryWrapper = new EntryWrapper();
|
||||
List<ImmutablePair<Long, Long>> list = new ArrayList<>();
|
||||
list.add(new ImmutablePair<Long, Long>(1L, 2L));
|
||||
list.add(new ImmutablePair<Long, Long>(3L, 4L));
|
||||
entryWrapper.setEntries(list);
|
||||
return new ResponseEntity<>(entryWrapper, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@GetMapping(value = "/lol")
|
||||
@ResponseBody
|
||||
public ResponseEntity<ImmutablePair<Long, Long>> getSamplePair() {
|
||||
return new ResponseEntity<ImmutablePair<Long, Long>>(new ImmutablePair<Long, Long>(1L, 2L), HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package de.ul.swtp.relationships;
|
||||
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface RelationshipManager {
|
||||
/*TODO: 1st implement pair/matrix
|
||||
2nd implement updating owning side to update jointables
|
||||
3rd implement both sides to update jointables
|
||||
4th figure out how to handle ACL with this manager.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Updates (overwrites) the current Jointable.
|
||||
* @param entries List of Tuples: List[Long roleId, Long authorityId]
|
||||
*/
|
||||
void updateJoinRoleAuthority(List<ImmutablePair<Long, Long>> entries);
|
||||
|
||||
/**
|
||||
* Updates (overwrites) the current Jointable.
|
||||
* Pair can probably be changed to ImmutablePair
|
||||
* @param entries List of Tuples: List[Long userId, Long roleId]
|
||||
*/
|
||||
void updateJoinUserRole(List<ImmutablePair<Long, Long>> entries);
|
||||
void updateJoinUserGroup();
|
||||
void updateJoinGroupContact();
|
||||
|
||||
//void updateRolesOfUser(User user);
|
||||
//TODO: Jackson parse id to object, impl update methods
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package de.ul.swtp.relationships;
|
||||
|
||||
import de.ul.swtp.modules.contactmanagement.ContactManager;
|
||||
import de.ul.swtp.modules.contactmanagement.GroupManager;
|
||||
import de.ul.swtp.system.Role;
|
||||
import de.ul.swtp.system.RoleManager;
|
||||
import de.ul.swtp.system.User;
|
||||
import de.ul.swtp.system.UserManager;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
public class RelationshipManagerImpl implements RelationshipManager {
|
||||
|
||||
private final RoleManager roleManager;
|
||||
private final UserManager userManager;
|
||||
private final GroupManager groupManager;
|
||||
private final ContactManager contactManager;
|
||||
|
||||
@Autowired
|
||||
public RelationshipManagerImpl(RoleManager roleManager, UserManager userManager, GroupManager groupManager, ContactManager contactManager) {
|
||||
this.roleManager = roleManager;
|
||||
this.userManager = userManager;
|
||||
this.groupManager = groupManager;
|
||||
this.contactManager = contactManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateJoinRoleAuthority(List<ImmutablePair<Long, Long>> entries) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateJoinUserRole(List<ImmutablePair<Long, Long>> entries) {
|
||||
//UserId, RoleId
|
||||
//This is brute force updating inside of the user and group objects (delete all and save new).
|
||||
//get lists of users and roles
|
||||
Set<User> users = entries.stream().map(entry -> userManager.getUserById(entry.getLeft())).collect(Collectors.toSet());
|
||||
Set<Role> roles = entries.stream().map(entry -> roleManager.getRoleById(entry.getRight())).collect(Collectors.toSet());
|
||||
//empty the lists
|
||||
for (User user : users) {
|
||||
user.setRoles(new ArrayList<Role>());
|
||||
}
|
||||
for (Role role : roles) {
|
||||
role.setUsers(new ArrayList<User>());
|
||||
}
|
||||
//Build list of users with correct roleIds and vice versa
|
||||
for (ImmutablePair<Long, Long> entry : entries) {
|
||||
for (User user : users) {
|
||||
if (user.getId().equals(entry.getLeft())){
|
||||
List<Role> r = user.getRoles();
|
||||
r.add(roleManager.getRoleById(entry.getRight()));
|
||||
user.setRoles(r);
|
||||
}
|
||||
}
|
||||
for (Role role : roles) {
|
||||
if (role.getId().equals(entry.getRight())){
|
||||
List<User> u = role.getUsers();
|
||||
u.add(userManager.getUserById(entry.getLeft()));
|
||||
role.setUsers(u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//update all users and roles
|
||||
for (User user:users){
|
||||
userManager.update(user.getId(), user);
|
||||
}
|
||||
for (Role role: roles){
|
||||
roleManager.update(role.getId(), role);
|
||||
}
|
||||
//for each pair:
|
||||
//get User and Group, update user and Group, save.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateJoinUserGroup() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateJoinGroupContact() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package de.ul.swtp.security;
|
||||
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Component
|
||||
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
|
||||
|
||||
private static final long serialVersionUID = -8970718410437077606L;
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
AuthenticationException authException) throws IOException {
|
||||
// This is invoked when user tries to access a secured REST resource without supplying any credentials
|
||||
// We should just send a 401 Unauthorized response because there is no 'login page' to redirect to
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package de.ul.swtp.security;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class JwtAuthenticationRequest implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -8445943548965154778L;
|
||||
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
public JwtAuthenticationRequest() {
|
||||
super();
|
||||
}
|
||||
|
||||
public JwtAuthenticationRequest(String username, String password) {
|
||||
this.setUsername(username);
|
||||
this.setPassword(password);
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return this.password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package de.ul.swtp.security;
|
||||
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private UserDetailsService userDetailsService;
|
||||
private JwtTokenUtil jwtTokenUtil;
|
||||
private String tokenHeader;
|
||||
|
||||
public JwtAuthorizationTokenFilter(UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, String tokenHeader) {
|
||||
this.userDetailsService = userDetailsService;
|
||||
this.jwtTokenUtil = jwtTokenUtil;
|
||||
this.tokenHeader = tokenHeader;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
|
||||
logger.debug("Processing authentication for '{}'.", request.getRequestURL());
|
||||
|
||||
final String requestHeader = request.getHeader(this.tokenHeader);
|
||||
|
||||
String username = null;
|
||||
String authToken = null;
|
||||
if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
|
||||
authToken = requestHeader.substring(7);
|
||||
try {
|
||||
username = jwtTokenUtil.getUsernameFromToken(authToken);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("An error occured during getting username from token.", e);
|
||||
} catch (ExpiredJwtException e) {
|
||||
logger.warn("The token is expired and not valid anymore.", e);
|
||||
}
|
||||
} else {
|
||||
logger.warn("Couldn't find bearer string, will ignore the header.");
|
||||
}
|
||||
|
||||
logger.debug("Checking authentication for user '{}'.", username);
|
||||
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
logger.debug("Security context was null, so authorizing user.");
|
||||
|
||||
// It is not compelling necessary to load the use details from the database. You could also store the information
|
||||
// in the token and read it from it. It's up to you ;)
|
||||
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
|
||||
|
||||
// For simple validation it is completely sufficient to just check the token integrity. You don't have to call
|
||||
// the database compellingly. Again it's up to you ;)
|
||||
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
|
||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
logger.info("Authorized user '{}', setting security context.", username);
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
}
|
||||
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
124
src/main/java/de/ul/swtp/security/JwtTokenUtil.java
Normal file
124
src/main/java/de/ul/swtp/security/JwtTokenUtil.java
Normal file
@@ -0,0 +1,124 @@
|
||||
package de.ul.swtp.security;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Clock;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.impl.DefaultClock;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Component
|
||||
public class JwtTokenUtil implements Serializable {
|
||||
|
||||
static final String CLAIM_KEY_USERNAME = "sub";
|
||||
static final String CLAIM_KEY_CREATED = "iat";
|
||||
private static final long serialVersionUID = -3301605591108950415L;
|
||||
|
||||
private Clock clock = DefaultClock.INSTANCE;
|
||||
|
||||
@Value("${jwt.secret}")
|
||||
private String secret;
|
||||
|
||||
@Value("${jwt.expiration}")
|
||||
private Long expiration;
|
||||
|
||||
public String getUsernameFromToken(String token) {
|
||||
return getClaimFromToken(token, Claims::getSubject);
|
||||
}
|
||||
|
||||
public Date getIssuedAtDateFromToken(String token) {
|
||||
return getClaimFromToken(token, Claims::getIssuedAt);
|
||||
}
|
||||
|
||||
public Date getExpirationDateFromToken(String token) {
|
||||
return getClaimFromToken(token, Claims::getExpiration);
|
||||
}
|
||||
|
||||
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
|
||||
final Claims claims = getAllClaimsFromToken(token);
|
||||
return claimsResolver.apply(claims);
|
||||
}
|
||||
|
||||
private Claims getAllClaimsFromToken(String token) {
|
||||
return Jwts.parser()
|
||||
.setSigningKey(secret)
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
private Boolean isTokenExpired(String token) {
|
||||
final Date expiration = getExpirationDateFromToken(token);
|
||||
return expiration.before(clock.now());
|
||||
}
|
||||
|
||||
private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
|
||||
return (lastPasswordReset != null && created.before(lastPasswordReset));
|
||||
}
|
||||
|
||||
private Boolean ignoreTokenExpiration(String token) {
|
||||
// here you specify tokens, for that the expiration is ignored
|
||||
return false;
|
||||
}
|
||||
|
||||
public String generateToken(UserDetails userDetails) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
return doGenerateToken(claims, userDetails.getUsername());
|
||||
}
|
||||
|
||||
private String doGenerateToken(Map<String, Object> claims, String subject) {
|
||||
final Date createdDate = clock.now();
|
||||
final Date expirationDate = calculateExpirationDate(createdDate);
|
||||
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setSubject(subject)
|
||||
.setIssuedAt(createdDate)
|
||||
.setExpiration(expirationDate)
|
||||
.signWith(SignatureAlgorithm.HS512, secret)
|
||||
.compact();
|
||||
}
|
||||
|
||||
public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
|
||||
final Date created = getIssuedAtDateFromToken(token);
|
||||
return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
|
||||
&& (!isTokenExpired(token) || ignoreTokenExpiration(token));
|
||||
}
|
||||
|
||||
public String refreshToken(String token) {
|
||||
final Date createdDate = clock.now();
|
||||
final Date expirationDate = calculateExpirationDate(createdDate);
|
||||
|
||||
final Claims claims = getAllClaimsFromToken(token);
|
||||
claims.setIssuedAt(createdDate);
|
||||
claims.setExpiration(expirationDate);
|
||||
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.signWith(SignatureAlgorithm.HS512, secret)
|
||||
.compact();
|
||||
}
|
||||
|
||||
public Boolean validateToken(String token, UserDetails userDetails) {
|
||||
JwtUser user = (JwtUser) userDetails;
|
||||
final String username = getUsernameFromToken(token);
|
||||
final Date created = getIssuedAtDateFromToken(token);
|
||||
//final Date expiration = getExpirationDateFromToken(token);
|
||||
return (
|
||||
username.equals(user.getUsername())
|
||||
&& !isTokenExpired(token)
|
||||
&& !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())
|
||||
);
|
||||
}
|
||||
|
||||
private Date calculateExpirationDate(Date createdDate) {
|
||||
return new Date(createdDate.getTime() + expiration * 1000);
|
||||
}
|
||||
}
|
||||
83
src/main/java/de/ul/swtp/security/JwtUser.java
Normal file
83
src/main/java/de/ul/swtp/security/JwtUser.java
Normal file
@@ -0,0 +1,83 @@
|
||||
package de.ul.swtp.security;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
|
||||
public class JwtUser implements UserDetails {
|
||||
|
||||
private final Long id;
|
||||
private final String username;
|
||||
private final String password;
|
||||
private final Collection<? extends GrantedAuthority> authorities;
|
||||
private final boolean enabled;
|
||||
private final Date lastPasswordResetDate;
|
||||
|
||||
public JwtUser(
|
||||
Long id,
|
||||
String username,
|
||||
String password,
|
||||
Collection<? extends GrantedAuthority> authorities,
|
||||
boolean enabled,
|
||||
Date lastPasswordResetDate
|
||||
) {
|
||||
this.id = id;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.authorities = authorities;
|
||||
this.enabled = enabled;
|
||||
this.lastPasswordResetDate = lastPasswordResetDate;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return authorities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Date getLastPasswordResetDate() {
|
||||
return lastPasswordResetDate;
|
||||
}
|
||||
}
|
||||
32
src/main/java/de/ul/swtp/security/JwtUserFactory.java
Normal file
32
src/main/java/de/ul/swtp/security/JwtUserFactory.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package de.ul.swtp.security;
|
||||
|
||||
import de.ul.swtp.system.Authority;
|
||||
import de.ul.swtp.system.User;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class JwtUserFactory {
|
||||
|
||||
private JwtUserFactory() {
|
||||
}
|
||||
|
||||
public static JwtUser create(User user) {
|
||||
return new JwtUser(
|
||||
user.getId(),
|
||||
user.getUsername(),
|
||||
user.getPassword(),
|
||||
mapToGrantedAuthorities(user.getAuthorities()),
|
||||
user.getEnabled(),
|
||||
user.getLastPasswordResetDate()
|
||||
);
|
||||
}
|
||||
|
||||
private static List<GrantedAuthority> mapToGrantedAuthorities(List<Authority> authorities) {
|
||||
return authorities.stream()
|
||||
.map(authority -> new SimpleGrantedAuthority(authority.getName()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
139
src/main/java/de/ul/swtp/security/WebSecurityConfig.java
Normal file
139
src/main/java/de/ul/swtp/security/WebSecurityConfig.java
Normal file
@@ -0,0 +1,139 @@
|
||||
package de.ul.swtp.security;
|
||||
|
||||
import de.ul.swtp.security.service.JwtUserDetailsService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
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.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Autowired
|
||||
private JwtAuthenticationEntryPoint unauthorizedHandler;
|
||||
|
||||
@Autowired
|
||||
private JwtTokenUtil jwtTokenUtil;
|
||||
|
||||
@Autowired
|
||||
private JwtUserDetailsService jwtUserDetailsService;
|
||||
|
||||
@Value("${jwt.header}")
|
||||
private String tokenHeader;
|
||||
|
||||
@Value("${mv.url.login}")
|
||||
private String authenticationPath;
|
||||
|
||||
@Value("${mv.url.signup}")
|
||||
private String signUpPath;
|
||||
|
||||
@Autowired
|
||||
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth
|
||||
.userDetailsService(jwtUserDetailsService)
|
||||
.passwordEncoder(passwordEncoderBean());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoderBean() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public AuthenticationManager authenticationManagerBean() throws Exception {
|
||||
return super.authenticationManagerBean();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity httpSecurity) throws Exception {
|
||||
httpSecurity
|
||||
.cors()
|
||||
.and()
|
||||
|
||||
.csrf().disable()
|
||||
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
|
||||
.and()
|
||||
|
||||
// don't create session
|
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
.and()
|
||||
|
||||
.authorizeRequests()
|
||||
// Un-secure H2 Database
|
||||
.antMatchers("/h2-console/**/**").permitAll()
|
||||
.antMatchers(authenticationPath + "/**").permitAll()
|
||||
.antMatchers(signUpPath + "/**").permitAll()
|
||||
.anyRequest().authenticated();
|
||||
|
||||
// Custom JWT based security filter
|
||||
JwtAuthorizationTokenFilter authenticationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenUtil, tokenHeader);
|
||||
httpSecurity
|
||||
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
// disable page caching
|
||||
httpSecurity
|
||||
.headers()
|
||||
.frameOptions().sameOrigin() // required to set for H2 else H2 Console will be blank.
|
||||
.cacheControl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(WebSecurity web) throws Exception {
|
||||
// AuthenticationTokenFilter will ignore the below paths
|
||||
web
|
||||
.ignoring()
|
||||
.antMatchers(HttpMethod.POST, authenticationPath)
|
||||
|
||||
.and()
|
||||
.ignoring()
|
||||
.antMatchers(HttpMethod.POST, signUpPath + "/**")
|
||||
|
||||
// allow anonymous resource requests
|
||||
.and()
|
||||
.ignoring()
|
||||
.antMatchers(
|
||||
HttpMethod.GET,
|
||||
"/",
|
||||
"/*.html",
|
||||
"/favicon.ico",
|
||||
"/**/*.html",
|
||||
"/**/*.css",
|
||||
"/**/*.js"
|
||||
)
|
||||
|
||||
// Un-secure H2 Database (for testing purposes, H2 console shouldn't be unprotected in production)
|
||||
.and()
|
||||
.ignoring()
|
||||
.antMatchers("/h2-console/**/**");
|
||||
}
|
||||
|
||||
@Bean
|
||||
CorsConfigurationSource corsConfigurationSource() {
|
||||
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
CorsConfiguration corsConfiguration = new CorsConfiguration();
|
||||
corsConfiguration.applyPermitDefaultValues();
|
||||
corsConfiguration.addAllowedMethod("PUT");
|
||||
corsConfiguration.addAllowedMethod("DELETE");
|
||||
source.registerCorsConfiguration("/**", corsConfiguration);
|
||||
return source;
|
||||
}
|
||||
|
||||
}
|
||||
101
src/main/java/de/ul/swtp/security/acl/AclServiceConfig.java
Normal file
101
src/main/java/de/ul/swtp/security/acl/AclServiceConfig.java
Normal file
@@ -0,0 +1,101 @@
|
||||
package de.ul.swtp.security.acl;
|
||||
|
||||
import lombok.NonNull;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.cache.annotation.Caching;
|
||||
import org.springframework.cache.ehcache.EhCacheFactoryBean;
|
||||
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||
import org.springframework.security.acls.AclPermissionCacheOptimizer;
|
||||
import org.springframework.security.acls.AclPermissionEvaluator;
|
||||
import org.springframework.security.acls.domain.*;
|
||||
import org.springframework.security.acls.jdbc.BasicLookupStrategy;
|
||||
import org.springframework.security.acls.model.PermissionGrantingStrategy;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@Configuration
|
||||
public class AclServiceConfig {
|
||||
|
||||
/**
|
||||
* The goal of the AclConfig is to provide a service to the MV system that can interface with the access control
|
||||
* list (ACL) system of Spring. It needs a DataSource (the database where the ACL information is stored), a
|
||||
* LookupStrategy and a AclCache to operate with.
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public CustomJdbcMutableAclService aclService() {
|
||||
CustomJdbcMutableAclService customJdbcMutableAclService = new CustomJdbcMutableAclService(dataSource, lookupStrategy(), aclCache());
|
||||
customJdbcMutableAclService.setClassIdentityQuery("SELECT LAST_INSERT_ID()");
|
||||
customJdbcMutableAclService.setSidIdentityQuery("SELECT LAST_INSERT_ID()");
|
||||
return customJdbcMutableAclService;
|
||||
}
|
||||
|
||||
/**
|
||||
* The DataSource gets autowired into the system. Since Spring detects multiple DataSources a @Qualifier is added
|
||||
* that defines the primary DataSource.
|
||||
*/
|
||||
@Autowired
|
||||
@Qualifier("dataSource")
|
||||
private @NonNull DataSource dataSource;
|
||||
|
||||
|
||||
/**
|
||||
* The BasicLookupStrategy is an ACL optimized way to do database lookups. It uses the DataSource and the Cache
|
||||
* to process the lookups.
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public BasicLookupStrategy lookupStrategy() {
|
||||
return new BasicLookupStrategy(dataSource, aclCache(), aclAuthorizationStrategy(), new ConsoleAuditLogger());
|
||||
}
|
||||
|
||||
|
||||
// TODO:
|
||||
@Bean
|
||||
public AclAuthorizationStrategy aclAuthorizationStrategy() {
|
||||
return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_ADMIN"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
@Caching
|
||||
public EhCacheBasedAclCache aclCache() {
|
||||
return new EhCacheBasedAclCache(aclEhCacheFactoryBean().getObject(), permissionGrantingStrategy(), aclAuthorizationStrategy());
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public PermissionGrantingStrategy permissionGrantingStrategy() {
|
||||
return new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EhCacheFactoryBean aclEhCacheFactoryBean() {
|
||||
EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
|
||||
ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject());
|
||||
ehCacheFactoryBean.setCacheName("aclCache");
|
||||
return ehCacheFactoryBean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EhCacheManagerFactoryBean aclCacheManager() {
|
||||
return new EhCacheManagerFactoryBean();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DefaultMethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() {
|
||||
DefaultMethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||
defaultMethodSecurityExpressionHandler.setPermissionEvaluator(new CustomAclPermissionEvaluator(aclService()));
|
||||
defaultMethodSecurityExpressionHandler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService()));
|
||||
return defaultMethodSecurityExpressionHandler;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
package de.ul.swtp.security.acl;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.security.access.PermissionEvaluator;
|
||||
import org.springframework.security.acls.domain.*;
|
||||
import org.springframework.security.acls.model.*;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class CustomAclPermissionEvaluator implements PermissionEvaluator {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final AclService aclService;
|
||||
private ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl();
|
||||
private ObjectIdentityGenerator objectIdentityGenerator = new ObjectIdentityRetrievalStrategyImpl();
|
||||
private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl();
|
||||
private PermissionFactory permissionFactory = new DefaultPermissionFactory();
|
||||
|
||||
public CustomAclPermissionEvaluator(AclService aclService) {
|
||||
this.aclService = aclService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the user has the given permission(s) on the domain object using
|
||||
* the ACL configuration. If the domain object is null, returns false (this can always
|
||||
* be overridden using a null check in the expression itself).
|
||||
*/
|
||||
public boolean hasPermission(Authentication authentication, Object domainObject,
|
||||
Object permission) {
|
||||
if (domainObject == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ObjectIdentity objectIdentity = objectIdentityRetrievalStrategy
|
||||
.getObjectIdentity(domainObject);
|
||||
|
||||
return checkPermission(authentication, objectIdentity, permission);
|
||||
}
|
||||
|
||||
public boolean hasPermission(Authentication authentication, Serializable targetId,
|
||||
String targetType, Object permission) {
|
||||
ObjectIdentity objectIdentity = objectIdentityGenerator.createObjectIdentity(
|
||||
targetId, targetType);
|
||||
|
||||
return checkPermission(authentication, objectIdentity, permission);
|
||||
}
|
||||
|
||||
private boolean checkPermission(Authentication authentication, ObjectIdentity oid,
|
||||
Object permission) {
|
||||
// Obtain the SIDs applicable to the principal
|
||||
List<Sid> sids = sidRetrievalStrategy.getSids(authentication);
|
||||
List<Permission> requiredPermission = resolvePermission(permission);
|
||||
List<Permission> requiredPermissions = fillWithHigherPermissions(requiredPermission);
|
||||
|
||||
final boolean debug = logger.isDebugEnabled();
|
||||
|
||||
if (debug) {
|
||||
logger.debug("Checking permission '" + permission + "' for object '" + oid
|
||||
+ "'");
|
||||
}
|
||||
|
||||
try {
|
||||
// Lookup only ACLs for SIDs we're interested in
|
||||
Acl acl = aclService.readAclById(oid, sids);
|
||||
|
||||
if (acl.isGranted(requiredPermissions, sids, false)) {
|
||||
if (debug) {
|
||||
logger.debug("Access is granted");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
logger.debug("Returning false - ACLs returned, but insufficient permissions for this principal");
|
||||
}
|
||||
|
||||
} catch (NotFoundException nfe) {
|
||||
if (debug) {
|
||||
logger.debug("Returning false - no ACLs apply for this principal");
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
List<Permission> fillWithHigherPermissions(List<Permission> permissions) {
|
||||
Permission p = permissions.get(0);
|
||||
List<Permission> newPermissions= new ArrayList<>();
|
||||
switch (p.getMask()) {
|
||||
//READ
|
||||
//alas if READ is needed, WRITE/CREATE/DELETE/ADMINISTRATION will fulfill the purpose here.
|
||||
case 1:
|
||||
newPermissions.add(BasePermission.READ);
|
||||
newPermissions.add(BasePermission.WRITE);
|
||||
newPermissions.add(BasePermission.CREATE);
|
||||
newPermissions.add(BasePermission.DELETE);
|
||||
newPermissions.add(BasePermission.ADMINISTRATION);
|
||||
return newPermissions;
|
||||
//WRITE
|
||||
case 2:
|
||||
newPermissions.add(BasePermission.WRITE);
|
||||
newPermissions.add(BasePermission.CREATE);
|
||||
newPermissions.add(BasePermission.DELETE);
|
||||
newPermissions.add(BasePermission.ADMINISTRATION);
|
||||
return newPermissions;
|
||||
//CREATE
|
||||
case 4:
|
||||
newPermissions.add(BasePermission.CREATE);
|
||||
newPermissions.add(BasePermission.DELETE);
|
||||
newPermissions.add(BasePermission.ADMINISTRATION);
|
||||
return newPermissions;
|
||||
//DELETE
|
||||
case 8:
|
||||
newPermissions.add(BasePermission.DELETE);
|
||||
newPermissions.add(BasePermission.ADMINISTRATION);
|
||||
return newPermissions;
|
||||
//ADMINISTRATION
|
||||
case 16:
|
||||
newPermissions.add(BasePermission.ADMINISTRATION);
|
||||
return newPermissions;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported permission: " + p);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
List<Permission> resolvePermission(Object permission) {
|
||||
if (permission instanceof Integer) {
|
||||
return Arrays.asList(permissionFactory.buildFromMask(((Integer) permission)
|
||||
.intValue()));
|
||||
}
|
||||
|
||||
if (permission instanceof Permission) {
|
||||
return Arrays.asList((Permission) permission);
|
||||
}
|
||||
|
||||
if (permission instanceof Permission[]) {
|
||||
return Arrays.asList((Permission[]) permission);
|
||||
}
|
||||
|
||||
if (permission instanceof String) {
|
||||
String permString = (String) permission;
|
||||
Permission p;
|
||||
|
||||
try {
|
||||
p = permissionFactory.buildFromName(permString);
|
||||
} catch (IllegalArgumentException notfound) {
|
||||
p = permissionFactory.buildFromName(permString.toUpperCase(Locale.ENGLISH));
|
||||
}
|
||||
|
||||
if (p != null) {
|
||||
return Arrays.asList(p);
|
||||
}
|
||||
|
||||
}
|
||||
throw new IllegalArgumentException("Unsupported permission: " + permission);
|
||||
}
|
||||
|
||||
public void setObjectIdentityRetrievalStrategy(
|
||||
ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) {
|
||||
this.objectIdentityRetrievalStrategy = objectIdentityRetrievalStrategy;
|
||||
}
|
||||
|
||||
public void setObjectIdentityGenerator(ObjectIdentityGenerator objectIdentityGenerator) {
|
||||
this.objectIdentityGenerator = objectIdentityGenerator;
|
||||
}
|
||||
|
||||
public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) {
|
||||
this.sidRetrievalStrategy = sidRetrievalStrategy;
|
||||
}
|
||||
|
||||
public void setPermissionFactory(PermissionFactory permissionFactory) {
|
||||
this.permissionFactory = permissionFactory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package de.ul.swtp.security.acl;
|
||||
|
||||
import org.springframework.jdbc.core.PreparedStatementCreatorFactory;
|
||||
import org.springframework.jdbc.support.GeneratedKeyHolder;
|
||||
import org.springframework.jdbc.support.KeyHolder;
|
||||
import org.springframework.security.acls.domain.ObjectIdentityImpl;
|
||||
import org.springframework.security.acls.jdbc.JdbcMutableAclService;
|
||||
import org.springframework.security.acls.jdbc.LookupStrategy;
|
||||
import org.springframework.security.acls.model.*;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class CustomJdbcMutableAclService extends JdbcMutableAclService {
|
||||
|
||||
|
||||
public CustomJdbcMutableAclService(DataSource dataSource, LookupStrategy lookupStrategy, AclCache aclCache) {
|
||||
super(dataSource, lookupStrategy, aclCache);
|
||||
}
|
||||
|
||||
public void deleteAclSidByAclSidId(Long id) {
|
||||
String deleteAclSidByAclSidId = "DELETE FROM acl_sid WHERE id = ?";
|
||||
jdbcTemplate.update(deleteAclSidByAclSidId, id);
|
||||
}
|
||||
|
||||
public Long createGrantedAuthoritySidForGroup(Long id) {
|
||||
String groupName = "CM_GROUP_" + id;
|
||||
System.out.println("Group name: " + groupName);
|
||||
String createGrantedAuthoritySidForGroup = "INSERT INTO acl_sid (principal, sid) VALUES (FALSE, ?)";
|
||||
KeyHolder keyHolder = new GeneratedKeyHolder();
|
||||
PreparedStatementCreatorFactory factory = new PreparedStatementCreatorFactory(createGrantedAuthoritySidForGroup, new int[]{Types.VARCHAR});
|
||||
factory.setReturnGeneratedKeys(true);
|
||||
jdbcTemplate.update(factory.newPreparedStatementCreator(new Object[]{groupName}), keyHolder);
|
||||
System.out.println("keyHolder: " + keyHolder.getKey());
|
||||
return (Long) keyHolder.getKey();
|
||||
}
|
||||
|
||||
public void addAceToAcl(Long id, Sid recipient, Permission permission, Class clazz) {
|
||||
MutableAcl acl;
|
||||
ObjectIdentity oid = new ObjectIdentityImpl(clazz, id);
|
||||
|
||||
try {
|
||||
acl = (MutableAcl) readAclById(oid);
|
||||
} catch (NotFoundException nfe) {
|
||||
acl = createAcl(oid);
|
||||
}
|
||||
|
||||
acl.insertAce(acl.getEntries().size(), permission, recipient, true);
|
||||
System.out.println("Added ACE: " + acl.getEntries().get(acl.getEntries().size() - 1));
|
||||
updateAcl(acl);
|
||||
}
|
||||
|
||||
public void deleteAceFromAcl(Long id, Sid sid, Permission permission, Class clazz) {
|
||||
ObjectIdentity oid = new ObjectIdentityImpl(clazz, id);
|
||||
MutableAcl acl = (MutableAcl) readAclById(oid);
|
||||
List<AccessControlEntry> aces = acl.getEntries();
|
||||
System.out.println(aces.size()-1);
|
||||
for (int i = aces.size()-1; i >= 0; i--) {
|
||||
System.out.println(aces);
|
||||
if (aces.get(i).getSid().equals(sid)
|
||||
&& aces.get(i).getPermission().equals(permission)) {
|
||||
System.out.println("Deleting ACE: " + aces.get(i));
|
||||
acl.deleteAce(i);
|
||||
|
||||
}
|
||||
}
|
||||
updateAcl(acl);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package de.ul.swtp.security.controller;
|
||||
|
||||
public class AuthenticationException extends RuntimeException {
|
||||
public AuthenticationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package de.ul.swtp.security.controller;
|
||||
|
||||
import de.ul.swtp.security.JwtAuthenticationRequest;
|
||||
import de.ul.swtp.security.JwtTokenUtil;
|
||||
import de.ul.swtp.security.JwtUser;
|
||||
import de.ul.swtp.security.service.JwtAuthenticationResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.DisabledException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Objects;
|
||||
|
||||
@RestController
|
||||
@CrossOrigin(origins = "*")
|
||||
public class AuthenticationRestController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AuthenticationRestController.class);
|
||||
|
||||
@Value("${jwt.header}")
|
||||
private String tokenHeader;
|
||||
|
||||
@Autowired
|
||||
private AuthenticationManager authenticationManager;
|
||||
|
||||
@Autowired
|
||||
private JwtTokenUtil jwtTokenUtil;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("jwtUserDetailsService")
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
@PostMapping(value = "${mv.url.login}")
|
||||
public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtAuthenticationRequest authenticationRequest) throws AuthenticationException {
|
||||
|
||||
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
|
||||
|
||||
// Reload password post-security so we can generate the token
|
||||
final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
|
||||
logger.info(userDetails.toString());
|
||||
final String token = jwtTokenUtil.generateToken(userDetails);
|
||||
logger.info(token);
|
||||
|
||||
// Return the token
|
||||
return ResponseEntity.ok(new JwtAuthenticationResponse(token));
|
||||
}
|
||||
|
||||
@GetMapping(value = "${mv.url.refresh-jwt}")
|
||||
public ResponseEntity<?> refreshAndGetAuthenticationToken(HttpServletRequest request) {
|
||||
String authToken = request.getHeader(tokenHeader);
|
||||
final String token = authToken.substring(7);
|
||||
String username = jwtTokenUtil.getUsernameFromToken(token);
|
||||
JwtUser user = (JwtUser) userDetailsService.loadUserByUsername(username);
|
||||
|
||||
if (jwtTokenUtil.canTokenBeRefreshed(token, user.getLastPasswordResetDate())) {
|
||||
String refreshedToken = jwtTokenUtil.refreshToken(token);
|
||||
return ResponseEntity.ok(new JwtAuthenticationResponse(refreshedToken));
|
||||
} else {
|
||||
return ResponseEntity.badRequest().body(null);
|
||||
}
|
||||
}
|
||||
|
||||
@ExceptionHandler({AuthenticationException.class})
|
||||
public ResponseEntity<String> handleAuthenticationException(AuthenticationException e) {
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates the user. If something is wrong, an {@link AuthenticationException} will be thrown
|
||||
*/
|
||||
private void authenticate(String username, String password) {
|
||||
Objects.requireNonNull(username);
|
||||
Objects.requireNonNull(password);
|
||||
|
||||
try {
|
||||
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
|
||||
} catch (DisabledException e) {
|
||||
throw new AuthenticationException("User is disabled!", e);
|
||||
} catch (BadCredentialsException e) {
|
||||
throw new AuthenticationException("Bad credentials!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package de.ul.swtp.security.controller;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("protected")
|
||||
public class MethodProtectedRestController {
|
||||
|
||||
// TODO: Decide if controller should be kept or removed
|
||||
|
||||
/**
|
||||
* This is an example of some different kinds of granular restriction for endpoints. You can use the built-in SPEL expressions
|
||||
* in @PreAuthorize such as 'hasRole()' to determine if a user has access. Remember that the hasRole expression assumes a
|
||||
* 'ROLE_' prefix on all role names. So 'ADMIN' here is actually stored as 'ROLE_ADMIN' in database!
|
||||
**/
|
||||
@RequestMapping(method = RequestMethod.GET)
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
public ResponseEntity<?> getProtectedGreeting() {
|
||||
return ResponseEntity.ok("Greetings from admin protected method!");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package de.ul.swtp.security.controller;
|
||||
|
||||
import de.ul.swtp.security.JwtTokenUtil;
|
||||
import de.ul.swtp.security.JwtUser;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@RestController
|
||||
public class UserRestController {
|
||||
|
||||
// TODO: Decide if controller should be kept or removed
|
||||
|
||||
@Value("${jwt.header}")
|
||||
private String tokenHeader;
|
||||
|
||||
@Autowired
|
||||
private JwtTokenUtil jwtTokenUtil;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("jwtUserDetailsService")
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
@GetMapping(path = "/me")
|
||||
public JwtUser getAuthenticatedUser(HttpServletRequest request) {
|
||||
String token = request.getHeader(tokenHeader).substring(7);
|
||||
String username = jwtTokenUtil.getUsernameFromToken(token);
|
||||
JwtUser user = (JwtUser) userDetailsService.loadUserByUsername(username);
|
||||
return user;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package de.ul.swtp.security.service;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class JwtAuthenticationResponse implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1250166508152483573L;
|
||||
|
||||
@Getter
|
||||
private final String access_token;
|
||||
|
||||
public JwtAuthenticationResponse(String token) {
|
||||
this.access_token = token;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package de.ul.swtp.security.service;
|
||||
|
||||
import de.ul.swtp.security.JwtUserFactory;
|
||||
import de.ul.swtp.system.User;
|
||||
import de.ul.swtp.system.UserRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.DisabledException;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class JwtUserDetailsService implements UserDetailsService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
@Autowired
|
||||
public JwtUserDetailsService(UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
User user = userRepository.findByUsername(username);
|
||||
|
||||
if (user == null) {
|
||||
throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
|
||||
}
|
||||
if (!user.getEnabled()){
|
||||
throw new DisabledException(String.format("User %s is disabled.", username));
|
||||
}
|
||||
return JwtUserFactory.create(user);
|
||||
}
|
||||
}
|
||||
47
src/main/java/de/ul/swtp/system/Authority.java
Normal file
47
src/main/java/de/ul/swtp/system/Authority.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package de.ul.swtp.system;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
|
||||
import lombok.Data;
|
||||
import org.hibernate.annotations.LazyCollection;
|
||||
import org.hibernate.annotations.LazyCollectionOption;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Entity
|
||||
@Table(name = "mv_authorities")
|
||||
@JsonIdentityInfo(
|
||||
generator = ObjectIdGenerators.PropertyGenerator.class,
|
||||
property = "id",
|
||||
scope = Authority.class,
|
||||
resolver = AuthorityIdResolver.class)
|
||||
public class Authority {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
@ManyToMany(mappedBy = "authorities")
|
||||
@LazyCollection(LazyCollectionOption.FALSE)
|
||||
@JsonIgnore
|
||||
//@JsonIdentityReference(alwaysAsId=true)
|
||||
private List<Role> roles;
|
||||
|
||||
public Authority() {
|
||||
}
|
||||
|
||||
protected boolean canEqual(Object other) {
|
||||
return other instanceof Authority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Authority(id=" + this.getId() + ", name=" + this.getName() + ")";
|
||||
}
|
||||
|
||||
}
|
||||
42
src/main/java/de/ul/swtp/system/AuthorityController.java
Normal file
42
src/main/java/de/ul/swtp/system/AuthorityController.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package de.ul.swtp.system;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(path = "/permissions")
|
||||
public class AuthorityController {
|
||||
|
||||
private final AuthorityRepository authorityRepository;
|
||||
|
||||
@Autowired
|
||||
public AuthorityController(AuthorityRepository authorityRepository) {
|
||||
this.authorityRepository = authorityRepository;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ResponseBody
|
||||
public ResponseEntity<Page<Authority>> getAuthorities(Pageable pageable, @RequestParam(name = "ids", required = false) List<Long> ids) {
|
||||
if(ids == null){
|
||||
Page<Authority> authorities = this.authorityRepository.findAll(pageable);
|
||||
return new ResponseEntity<>(authorities, HttpStatus.OK);
|
||||
}
|
||||
Page<Authority> authorities = this.authorityRepository.findAllByIdIn(ids, pageable);
|
||||
return new ResponseEntity<>(authorities, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@GetMapping(path = "/{id}")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Authority> getAuthority(@PathVariable(name = "id") Long id) {
|
||||
Authority authority = this.authorityRepository.findById(id).orElseThrow(() -> new EmptyResultDataAccessException(
|
||||
String.format("No Authority with id %s exists!", id), 1));
|
||||
return new ResponseEntity<>(authority, HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
55
src/main/java/de/ul/swtp/system/AuthorityIdResolver.java
Normal file
55
src/main/java/de/ul/swtp/system/AuthorityIdResolver.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package de.ul.swtp.system;
|
||||
|
||||
import com.fasterxml.jackson.annotation.ObjectIdGenerator.IdKey;
|
||||
import com.fasterxml.jackson.annotation.ObjectIdResolver;
|
||||
import com.fasterxml.jackson.annotation.SimpleObjectIdResolver;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
public class AuthorityIdResolver extends SimpleObjectIdResolver {
|
||||
private final AuthorityRepository authorityRepository;
|
||||
|
||||
@Autowired
|
||||
public AuthorityIdResolver(AuthorityRepository authorityRepository) {
|
||||
this.authorityRepository = authorityRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindItem(IdKey id, Object pojo) {
|
||||
super.bindItem(id, pojo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolveId(IdKey id) {
|
||||
Object resolved = super.resolveId(id);
|
||||
if (resolved == null) {
|
||||
resolved = _tryToLoadFromSource(id);
|
||||
bindItem(id, resolved);
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
private Object _tryToLoadFromSource(IdKey idKey) {
|
||||
requireNonNull(idKey.scope, "Global scope not supported.");
|
||||
Long id = (Long) idKey.key;
|
||||
return authorityRepository.findById(id).orElseThrow(() -> new EmptyResultDataAccessException(
|
||||
String.format("No Authority with id %s exists!", id), 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectIdResolver newForDeserialization(Object context) {
|
||||
return new AuthorityIdResolver(authorityRepository);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUseFor(ObjectIdResolver resolverType) {
|
||||
return resolverType.getClass() == AuthorityIdResolver.class;
|
||||
}
|
||||
}
|
||||
13
src/main/java/de/ul/swtp/system/AuthorityRepository.java
Normal file
13
src/main/java/de/ul/swtp/system/AuthorityRepository.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package de.ul.swtp.system;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface AuthorityRepository extends JpaRepository<Authority, Long> {
|
||||
Page<Authority> findAllByIdIn(List<Long> ids, Pageable pageable);
|
||||
}
|
||||
61
src/main/java/de/ul/swtp/system/Role.java
Normal file
61
src/main/java/de/ul/swtp/system/Role.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package de.ul.swtp.system;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
|
||||
import com.fasterxml.jackson.annotation.JsonIdentityReference;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
|
||||
import lombok.Data;
|
||||
import org.hibernate.annotations.LazyCollection;
|
||||
import org.hibernate.annotations.LazyCollectionOption;
|
||||
|
||||
import javax.persistence.*;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Null;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Entity
|
||||
@Table(name = "mv_roles")
|
||||
@JsonIdentityInfo(
|
||||
generator = ObjectIdGenerators.PropertyGenerator.class,
|
||||
property = "id",
|
||||
scope = Role.class,
|
||||
resolver = RoleIdResolver.class)
|
||||
public class Role {
|
||||
|
||||
//@Null may well not work here, if the validations are call on update as well as create
|
||||
//@Null
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@NotBlank
|
||||
private String name;
|
||||
|
||||
@ManyToMany
|
||||
@LazyCollection(LazyCollectionOption.FALSE)
|
||||
@JoinTable(
|
||||
name = "mv_role_authority",
|
||||
joinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "ID")},
|
||||
inverseJoinColumns = {@JoinColumn(name = "AUTHORITY_ID", referencedColumnName = "ID")})
|
||||
@JsonIdentityReference(alwaysAsId=true)
|
||||
@JsonProperty("permissions")
|
||||
private List<Authority> authorities;
|
||||
|
||||
@ManyToMany(mappedBy = "roles")
|
||||
@LazyCollection(LazyCollectionOption.FALSE)
|
||||
@JsonIdentityReference(alwaysAsId=true)
|
||||
private List<User> users;
|
||||
|
||||
public Role() {
|
||||
}
|
||||
|
||||
protected boolean canEqual(Object other) {
|
||||
return other instanceof Role;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Role(id=" + this.getId() + ", name=" + this.getName() + ", authorities=" + this.getAuthorities() + ")";
|
||||
}
|
||||
|
||||
}
|
||||
63
src/main/java/de/ul/swtp/system/RoleController.java
Normal file
63
src/main/java/de/ul/swtp/system/RoleController.java
Normal file
@@ -0,0 +1,63 @@
|
||||
package de.ul.swtp.system;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@CrossOrigin(origins = "*")
|
||||
@RequestMapping(path = "/roles")
|
||||
public class RoleController {
|
||||
|
||||
private RoleManager roleManager;
|
||||
|
||||
@Autowired
|
||||
public RoleController(RoleManager roleManager) {
|
||||
this.roleManager = roleManager;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ResponseBody
|
||||
public ResponseEntity<Page<Role>> getRoles(Pageable pageable, @RequestParam(name = "ids", required = false) List<Long> ids) {
|
||||
if(ids == null){
|
||||
Page<Role> roles = roleManager.getAll(pageable);
|
||||
return new ResponseEntity<>(roles, HttpStatus.OK);
|
||||
}
|
||||
Page<Role> roles = roleManager.getRolesByIds(ids, pageable);
|
||||
return new ResponseEntity<>(roles, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ResponseBody
|
||||
public ResponseEntity<Role> createRole(@RequestBody Role role) {
|
||||
Role createdRole = roleManager.create(role);
|
||||
return new ResponseEntity<>(createdRole, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@GetMapping(path = "/{id}")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Role> getRole(@PathVariable("id") Long id) {
|
||||
Role role = roleManager.getRoleById(id);
|
||||
return new ResponseEntity<>(role, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PutMapping(path = "/{id}")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Role> updateRole(@RequestBody Role role, @PathVariable("id") Long id) {
|
||||
Role updatedRole = roleManager.update(id, role);
|
||||
return new ResponseEntity<>(updatedRole, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@DeleteMapping(path = "/{id}")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Role> deleteRole(@PathVariable("id") Long id) {
|
||||
Role deletedRole = roleManager.getRoleById(id);
|
||||
roleManager.delete(id);
|
||||
return new ResponseEntity<>(deletedRole, HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
53
src/main/java/de/ul/swtp/system/RoleIdResolver.java
Normal file
53
src/main/java/de/ul/swtp/system/RoleIdResolver.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package de.ul.swtp.system;
|
||||
|
||||
import com.fasterxml.jackson.annotation.ObjectIdGenerator.IdKey;
|
||||
import com.fasterxml.jackson.annotation.ObjectIdResolver;
|
||||
import com.fasterxml.jackson.annotation.SimpleObjectIdResolver;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
public class RoleIdResolver extends SimpleObjectIdResolver {
|
||||
private final RoleManager roleManager;
|
||||
|
||||
@Autowired
|
||||
public RoleIdResolver(RoleManager roleManager) {
|
||||
this.roleManager = roleManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindItem(IdKey id, Object pojo) {
|
||||
super.bindItem(id, pojo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolveId(IdKey id) {
|
||||
Object resolved = super.resolveId(id);
|
||||
if (resolved == null) {
|
||||
resolved = _tryToLoadFromSource(id);
|
||||
bindItem(id, resolved);
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
private Object _tryToLoadFromSource(IdKey idKey) {
|
||||
requireNonNull(idKey.scope, "Global scope not supported.");
|
||||
Long id = (Long) idKey.key;
|
||||
return roleManager.getRoleById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectIdResolver newForDeserialization(Object context) {
|
||||
return new RoleIdResolver(roleManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUseFor(ObjectIdResolver resolverType) {
|
||||
return resolverType.getClass() == RoleIdResolver.class;
|
||||
}
|
||||
}
|
||||
62
src/main/java/de/ul/swtp/system/RoleManager.java
Normal file
62
src/main/java/de/ul/swtp/system/RoleManager.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package de.ul.swtp.system;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface RoleManager {
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_ROLE_CREATE') or hasAuthority('ROLE_ADMIN')")
|
||||
Role create(Role role);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_ROLE_CREATE') or hasAuthority('ROLE_ADMIN')")
|
||||
Role create(Role role, List<Authority> authorities);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_ROLE_GETROLEBYID') or hasAuthority('ROLE_ADMIN')")
|
||||
Role getRoleById(Long roleId);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_ROLE_GETROLESBYIDS') or hasAuthority('ROLE_ADMIN')")
|
||||
Page<Role> getRolesByIds(List<Long> roleIds, Pageable pageable);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_ROLE_GETALL') or hasAuthority('ROLE_ADMIN')")
|
||||
Page<Role> getAll(Pageable pageable);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_ROLE_FINDUSERSINROLE') or hasAuthority('ROLE_ADMIN')")
|
||||
List<User> findUsersInRole(Long roleId);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_ROLE_UPDATE') or hasAuthority('ROLE_ADMIN')")
|
||||
Role update(Long id, Role role);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_ROLE_DELETE') or hasAuthority('ROLE_ADMIN')")
|
||||
void delete(Long roleId);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_ROLE_ADDUSERTOROLE') or hasAuthority('ROLE_ADMIN')")
|
||||
void addUserToRole(Role role, User user);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_ROLE_ADDUSERSTOROLE') or hasAuthority('ROLE_ADMIN')")
|
||||
void addUsersToRole(Role role, List<User> users);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_ROLE_REMOVEUSERFROMROLE') or hasAuthority('ROLE_ADMIN')")
|
||||
void removeUserFromRole(Role role, User user);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_ROLE_REMOVEUSERSFROMROLE') or hasAuthority('ROLE_ADMIN')")
|
||||
void removeUsersFromRole(Role role, List<User> users);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_ROLE_REMOVEALLUSERSFROMROLE') or hasAuthority('ROLE_ADMIN')")
|
||||
void removeAllUsersFromRole(Role role);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_ROLE_ADDAUTHORITYTOROLE') or hasAuthority('ROLE_ADMIN')")
|
||||
Role addAuthorityToRole(Long roleId, Long authorityId);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_ROLE_ADDAUTHORITIESTOROLE') or hasAuthority('ROLE_ADMIN')")
|
||||
Role addAuthoritiesToRole(Long roleId, List<Long> authorityIds);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_ROLE_REMOVEAUTHORITYFROMROLE') or hasAuthority('ROLE_ADMIN')")
|
||||
void removeAuthorityFromRole(Long roleId, Long authorityId);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_ROLE_REMOVEAUTHORITIESFROMROLE') or hasAuthority('ROLE_ADMIN')")
|
||||
void removeAuthoritiesFromRole(Long roleId, List<Long> authorityIds);
|
||||
|
||||
}
|
||||
176
src/main/java/de/ul/swtp/system/RoleManagerImpl.java
Normal file
176
src/main/java/de/ul/swtp/system/RoleManagerImpl.java
Normal file
@@ -0,0 +1,176 @@
|
||||
package de.ul.swtp.system;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Manager expects magically validated objects atm.
|
||||
* Validated means 1.) formally correct and 2.) integritally (e.g. DB Checks) correct.
|
||||
*/
|
||||
@Component
|
||||
public class RoleManagerImpl implements RoleManager {
|
||||
private final RoleRepository roleRepository;
|
||||
private final UserManager userManager;
|
||||
|
||||
@Autowired
|
||||
public RoleManagerImpl(RoleRepository roleRepository, UserManager userManager) {
|
||||
this.roleRepository = roleRepository;
|
||||
this.userManager = userManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Role create(Role role) {
|
||||
role.setId(null);
|
||||
Role createdRole = roleRepository.save(role);
|
||||
if (createdRole.getUsers() != null) addRoleToUsers(createdRole);
|
||||
return createdRole;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Role create(Role role, List<Authority> authorities) {
|
||||
//TODO: validate that id field is empty
|
||||
role.setId(null);
|
||||
Role createdRole = roleRepository.save(role);
|
||||
List<Long> authorityIds = authorities.stream().map(Authority::getId).collect(Collectors.toList());
|
||||
return addAuthoritiesToRole(createdRole.getId(), authorityIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Role getRoleById(Long id) {
|
||||
return roleRepository.findById(id).orElseThrow(() -> new EmptyResultDataAccessException(
|
||||
String.format("No Role with id %s exists!", id), 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Role> getRolesByIds(List<Long> roleIds, Pageable pageable) {
|
||||
//Does not throw not found, when there is a role in the list that is not in the db.
|
||||
return roleRepository.findAllByIdIn(roleIds, pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Role update(Long id, Role role) {
|
||||
Role roleToBeUpdated = getRoleById(id);
|
||||
|
||||
//JPA
|
||||
//remove this role from all users that have it at the moment.
|
||||
removeRoleFromAllUsers(roleToBeUpdated);
|
||||
|
||||
//id of given role is ignored.
|
||||
if (role.getName() != null) roleToBeUpdated.setName(role.getName());
|
||||
if (role.getAuthorities() != null) roleToBeUpdated.setAuthorities(role.getAuthorities());
|
||||
if (role.getUsers() != null) roleToBeUpdated.setUsers(role.getUsers());
|
||||
System.out.println(roleToBeUpdated.toString());
|
||||
Role updatedRole = roleRepository.save(roleToBeUpdated);
|
||||
|
||||
//JPA
|
||||
//add this role to all users that are in the updated role
|
||||
addRoleToUsers(updatedRole);
|
||||
|
||||
|
||||
return updatedRole;
|
||||
}
|
||||
|
||||
private void removeRoleFromAllUsers(Role role) {
|
||||
List<User> usersThatAlreadyHaveThisRole = userManager.getAllByRole(role);
|
||||
for (User user : usersThatAlreadyHaveThisRole) {
|
||||
user.getRoles().remove(role);
|
||||
userManager.update(user.getId(), user);
|
||||
}
|
||||
}
|
||||
|
||||
private void addRoleToUsers(Role role) {
|
||||
for (User user : role.getUsers()) {
|
||||
user.getRoles().add(role);
|
||||
userManager.update(user.getId(), user);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Long roleId) {
|
||||
removeRoleFromAllUsers(getRoleById(roleId));
|
||||
roleRepository.deleteById(roleId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Role> getAll(Pageable pageable) {
|
||||
return roleRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<User> findUsersInRole(Long id) {
|
||||
return getRoleById(id).getUsers();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addUserToRole(Role role, User user) {
|
||||
List<Role> roles = user.getRoles();
|
||||
roles.add(role);
|
||||
user.setRoles(roles);
|
||||
//userManager.update(user.getId(), user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUsersToRole(Role role, List<User> users) {
|
||||
for (User user : users) {
|
||||
addUserToRole(role, user);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUserFromRole(Role role, User user) {
|
||||
List<Role> roles = user.getRoles();
|
||||
//if (roles.contains(role)) {
|
||||
roles.remove(role);
|
||||
//}
|
||||
user.setRoles(roles);
|
||||
//userManager.update(user.getId(), user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUsersFromRole(Role role, List<User> users) {
|
||||
for (User user : users) {
|
||||
removeUserFromRole(role, user);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAllUsersFromRole(Role role) {
|
||||
for (User user : role.getUsers()) {
|
||||
removeUserFromRole(role, user);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Role addAuthorityToRole(Long roleId, Long authorityId) {
|
||||
return new Role();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Role addAuthoritiesToRole(Long roleId, List<Long> authorityIds) {
|
||||
Role role = getRoleById(roleId);
|
||||
for (Long authorityId : authorityIds) {
|
||||
role = addAuthorityToRole(roleId, authorityId);
|
||||
}
|
||||
return role;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAuthorityFromRole(Long roleId, Long authorityId) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAuthoritiesFromRole(Long roleId, List<Long> authorityIds) {
|
||||
for (Long authorityId : authorityIds) {
|
||||
removeAuthorityFromRole(roleId, authorityId);
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/main/java/de/ul/swtp/system/RoleRepository.java
Normal file
13
src/main/java/de/ul/swtp/system/RoleRepository.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package de.ul.swtp.system;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface RoleRepository extends JpaRepository<Role, Long> {
|
||||
Page<Role> findAllByIdIn(List<Long> ids, Pageable pageable);
|
||||
}
|
||||
114
src/main/java/de/ul/swtp/system/User.java
Normal file
114
src/main/java/de/ul/swtp/system/User.java
Normal file
@@ -0,0 +1,114 @@
|
||||
package de.ul.swtp.system;
|
||||
|
||||
import com.fasterxml.jackson.annotation.*;
|
||||
import de.ul.swtp.modules.contactmanagement.Contact;
|
||||
import de.ul.swtp.modules.contactmanagement.Group;
|
||||
import lombok.Data;
|
||||
import org.hibernate.annotations.LazyCollection;
|
||||
import org.hibernate.annotations.LazyCollectionOption;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
import org.hibernate.validator.constraints.NotBlank;
|
||||
|
||||
import javax.persistence.*;
|
||||
import javax.validation.constraints.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Data
|
||||
@Entity
|
||||
@Table(name = "mv_users")
|
||||
@JsonIdentityInfo(
|
||||
generator = ObjectIdGenerators.PropertyGenerator.class,
|
||||
property = "id",
|
||||
scope = User.class,
|
||||
resolver = UserIdResolver.class)
|
||||
public class User {
|
||||
|
||||
//TODO: check if @JsonIgnore renders the validations ineffective in the actual app
|
||||
//FIXME @Null doesn't trigger on id in Postman
|
||||
//@Null may well not work here, if the validations are call on update as well as create
|
||||
//@Null
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
//The regex ensures that only emails with top level domains are accepted (note that local email addresses are actually valid but of little use to us)
|
||||
@Email(regexp="^.*@.*\\..*")
|
||||
@javax.validation.constraints.NotBlank
|
||||
private String username;
|
||||
|
||||
@Length(min=4, max=256)
|
||||
@NotEmpty
|
||||
@JsonIgnore
|
||||
@JsonSetter
|
||||
private String password;
|
||||
|
||||
@NotNull
|
||||
private Boolean enabled;
|
||||
|
||||
@NotNull
|
||||
private Boolean admin;
|
||||
|
||||
@Past
|
||||
@JsonIgnore
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date lastPasswordResetDate;
|
||||
|
||||
@ManyToMany
|
||||
@LazyCollection(LazyCollectionOption.FALSE)
|
||||
@JoinTable(
|
||||
name = "mv_user_role",
|
||||
joinColumns = {@JoinColumn(name = "USER_ID", referencedColumnName = "ID")},
|
||||
inverseJoinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "ID")})
|
||||
@JsonIdentityReference(alwaysAsId = true)
|
||||
private List<Role> roles;
|
||||
|
||||
@ManyToMany
|
||||
@LazyCollection(LazyCollectionOption.FALSE)
|
||||
@JoinTable(
|
||||
name = "mv_user_group",
|
||||
joinColumns = {@JoinColumn(name = "USER_ID", referencedColumnName = "ID")},
|
||||
inverseJoinColumns = {@JoinColumn(name = "GROUP_ID", referencedColumnName = "ID")})
|
||||
@JsonIdentityReference(alwaysAsId = true)
|
||||
private List<Group> groups;
|
||||
|
||||
@OneToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "contact_id")
|
||||
@JsonIdentityReference(alwaysAsId=true)
|
||||
//@JsonIgnore
|
||||
private Contact contact;
|
||||
|
||||
@JsonIgnore
|
||||
public List<Authority> getAuthorities() {
|
||||
//convert groups to authorities
|
||||
List<Authority> groupsToAuthorities = new ArrayList<>();
|
||||
for (Group group : groups) {
|
||||
Authority authority = new Authority();
|
||||
authority.setName("CM_GROUP_" + group.getId());
|
||||
groupsToAuthorities.add(authority);
|
||||
}
|
||||
//resolve role authorities
|
||||
List<List<Authority>> rolesToListOfAuthorities = roles.stream().map(role -> role.getAuthorities()).collect(Collectors.toList());
|
||||
List<Authority> roleAuthorities = rolesToListOfAuthorities.stream().flatMap(List::stream).collect(Collectors.toList());
|
||||
//combine the two lists
|
||||
List<Authority> returnList = Stream.of(groupsToAuthorities, roleAuthorities).flatMap(Collection::stream).collect(Collectors.toList());
|
||||
|
||||
//Add Admin or User authority
|
||||
if (this.getAdmin()) {
|
||||
Authority authority = new Authority();
|
||||
authority.setName("ROLE_ADMIN");
|
||||
authority.setId((long)(returnList.size() + 1));
|
||||
returnList.add(authority);
|
||||
} else {
|
||||
Authority authority = new Authority();
|
||||
authority.setName("ROLE_USER");
|
||||
authority.setId((long)(returnList.size() + 1));
|
||||
returnList.add(authority);
|
||||
}
|
||||
return returnList;
|
||||
}
|
||||
}
|
||||
63
src/main/java/de/ul/swtp/system/UserController.java
Normal file
63
src/main/java/de/ul/swtp/system/UserController.java
Normal file
@@ -0,0 +1,63 @@
|
||||
package de.ul.swtp.system;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@CrossOrigin(origins = "*")
|
||||
@RequestMapping(path = "/users")
|
||||
public class UserController {
|
||||
private final UserManager userManager;
|
||||
|
||||
public UserController(UserManager userManager) {
|
||||
this.userManager = userManager;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ResponseBody
|
||||
public ResponseEntity<Page<User>> getUsers(Pageable pageable, @RequestParam(name = "ids", required = false) List<Long> ids) {
|
||||
if(ids == null){
|
||||
Page<User> users = userManager.getAll(pageable);
|
||||
return new ResponseEntity<>(users, HttpStatus.OK);
|
||||
}
|
||||
Page<User> users = userManager.getUsersByIds(ids, pageable);
|
||||
return new ResponseEntity<>(users, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ResponseBody
|
||||
public ResponseEntity<User> createUser(@RequestBody User user) {
|
||||
//TODO: validate that id field is empty
|
||||
User createdUser = userManager.create(user);
|
||||
return new ResponseEntity<>(createdUser, HttpStatus.CREATED);
|
||||
}
|
||||
|
||||
@GetMapping(path = "/{id}")
|
||||
@ResponseBody
|
||||
public ResponseEntity<User> getUser(@PathVariable("id") Long id) {
|
||||
//TODO: Should only return {id, username, permissionId[], contactId[]} of the user.
|
||||
User user = userManager.getUserById(id);
|
||||
return new ResponseEntity<>(user, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PutMapping(path = "/{id}")
|
||||
@ResponseBody
|
||||
public ResponseEntity<User> updateUser(@RequestBody User user, @PathVariable("id") Long id) {
|
||||
User updatedUser = userManager.update(id, user);
|
||||
return new ResponseEntity<>(updatedUser, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@DeleteMapping(path = "/{id}")
|
||||
@ResponseBody
|
||||
public ResponseEntity<User> deleteUser(@PathVariable("id") Long id) {
|
||||
User deletedUser = userManager.getUserById(id);
|
||||
userManager.delete(id);
|
||||
return new ResponseEntity<>(deletedUser, HttpStatus.OK);
|
||||
|
||||
}
|
||||
}
|
||||
53
src/main/java/de/ul/swtp/system/UserIdResolver.java
Normal file
53
src/main/java/de/ul/swtp/system/UserIdResolver.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package de.ul.swtp.system;
|
||||
|
||||
import com.fasterxml.jackson.annotation.ObjectIdGenerator.IdKey;
|
||||
import com.fasterxml.jackson.annotation.ObjectIdResolver;
|
||||
import com.fasterxml.jackson.annotation.SimpleObjectIdResolver;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
public class UserIdResolver extends SimpleObjectIdResolver {
|
||||
private final UserManager userManager;
|
||||
|
||||
@Autowired
|
||||
public UserIdResolver(UserManager userManager) {
|
||||
this.userManager = userManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindItem(IdKey id, Object pojo) {
|
||||
super.bindItem(id, pojo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolveId(IdKey id) {
|
||||
Object resolved = super.resolveId(id);
|
||||
if (resolved == null) {
|
||||
resolved = _tryToLoadFromSource(id);
|
||||
bindItem(id, resolved);
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
private Object _tryToLoadFromSource(IdKey idKey) {
|
||||
requireNonNull(idKey.scope, "Global scope not supported.");
|
||||
Long id = (Long) idKey.key;
|
||||
return userManager.getUserById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectIdResolver newForDeserialization(Object context) {
|
||||
return new UserIdResolver(userManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUseFor(ObjectIdResolver resolverType) {
|
||||
return resolverType.getClass() == UserIdResolver.class;
|
||||
}
|
||||
}
|
||||
45
src/main/java/de/ul/swtp/system/UserManager.java
Normal file
45
src/main/java/de/ul/swtp/system/UserManager.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package de.ul.swtp.system;
|
||||
|
||||
import de.ul.swtp.modules.contactmanagement.Group;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface UserManager {
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_USER_GETALL') or hasAuthority('ROLE_ADMIN')")
|
||||
Page<User> getAll(Pageable pageable);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_USER_GETUSERBYID') or hasAuthority('ROLE_ADMIN')")
|
||||
User getUserById(Long id);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_USER_GETUSERSBYIDS') or hasAuthority('ROLE_ADMIN')")
|
||||
Page<User> getUsersByIds(List<Long> userIds, Pageable pageable);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_USER_GETUSERBYUSERNAME') or hasAuthority('ROLE_ADMIN')")
|
||||
User getUserByUsername(String username);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_USER_GETALLBYROLE') or hasAuthority('ROLE_ADMIN')")
|
||||
List<User> getAllByRole(Role role);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_USER_GETALLBYGROUP') or hasAuthority('ROLE_ADMIN')")
|
||||
List<User> getAllByGroup(Group group);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_USER_CREATE') or hasAuthority('ROLE_ADMIN')")
|
||||
User create(User user);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_USER_UPDATE') or hasAuthority('ROLE_ADMIN')")
|
||||
User update(Long id, User user);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_USER_UPDATE') or hasAuthority('ROLE_ADMIN')")
|
||||
User updateWithoutTouchingAcl(Long id, User user);
|
||||
|
||||
@PreAuthorize("hasAuthority('SYS_USER_DELETE') or hasAuthority('ROLE_ADMIN')")
|
||||
void delete(Long id);
|
||||
|
||||
void enableUser(Long id);
|
||||
|
||||
User signUpUser(User user);
|
||||
}
|
||||
157
src/main/java/de/ul/swtp/system/UserManagerImpl.java
Normal file
157
src/main/java/de/ul/swtp/system/UserManagerImpl.java
Normal file
@@ -0,0 +1,157 @@
|
||||
package de.ul.swtp.system;
|
||||
|
||||
import de.ul.swtp.modules.contactmanagement.Contact;
|
||||
import de.ul.swtp.modules.contactmanagement.Group;
|
||||
import de.ul.swtp.security.acl.CustomJdbcMutableAclService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.security.acls.domain.BasePermission;
|
||||
import org.springframework.security.acls.domain.PrincipalSid;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class UserManagerImpl implements UserManager {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final CustomJdbcMutableAclService customJdbcMutableAclService;
|
||||
|
||||
@Autowired
|
||||
public UserManagerImpl(UserRepository userRepository, PasswordEncoder passwordEncoder, CustomJdbcMutableAclService customJdbcMutableAclService) {
|
||||
this.userRepository = userRepository;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.customJdbcMutableAclService = customJdbcMutableAclService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User create(User user) {
|
||||
user.setId(null);
|
||||
user.setPassword(passwordEncoder.encode(user.getPassword()));
|
||||
User createdUser = userRepository.save(user);
|
||||
//Create ACE if contact field is not null
|
||||
/*if (createdUser.getContact() != null) {
|
||||
customJdbcMutableAclService.addAceToAcl(createdUser.getContact().getId(), new PrincipalSid(createdUser.getUsername()), BasePermission.ADMINISTRATION, Contact.class);
|
||||
}*/
|
||||
return createdUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getUserById(Long id) {
|
||||
return userRepository.findById(id).orElseThrow(() ->
|
||||
new EmptyResultDataAccessException(String.format("No User with id %s exists!", id), 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<User> getUsersByIds(List<Long> userIds, Pageable pageable) {
|
||||
return userRepository.findAllByIdIn(userIds, pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public User update(Long id, User user) {
|
||||
User oldUser = getUserById(id);
|
||||
|
||||
if (user.getUsername() != null) oldUser.setUsername(user.getUsername());
|
||||
if (user.getEnabled() != null) oldUser.setEnabled(user.getEnabled());
|
||||
if (user.getAdmin() != null) oldUser.setAdmin(user.getAdmin());
|
||||
if (user.getRoles() != null) oldUser.setRoles(user.getRoles());
|
||||
if (user.getGroups() != null) oldUser.setGroups(user.getGroups());
|
||||
//Only do something when user.getContact() != null
|
||||
//Delete relation when user.getContact() is 0.
|
||||
/*if (user.getContact() != null) {
|
||||
//if sent user has a contact
|
||||
if (user.getContact().getId().equals(0L) && oldUser.getContact() != null) {
|
||||
//if sent contacts id = 0 and oldUser has a contact, delete relation
|
||||
System.out.println("Access deleteAce from User 1");
|
||||
customJdbcMutableAclService.deleteAceFromAcl(oldUser.getContact().getId(), new PrincipalSid(oldUser.getUsername()), BasePermission.ADMINISTRATION, Contact.class);
|
||||
oldUser.setContact(null);
|
||||
} else if (oldUser.getId() == null || !user.getContact().getId().equals(oldUser.getContact().getId())) {
|
||||
//if id != null and id is different than before, overwrite relation
|
||||
System.out.println("Access deleteAce from User");
|
||||
//maybe check here if oldUser has a Contact
|
||||
customJdbcMutableAclService.deleteAceFromAcl(oldUser.getContact().getId(), new PrincipalSid(oldUser.getUsername()), BasePermission.ADMINISTRATION, Contact.class);
|
||||
oldUser.setContact(user.getContact());
|
||||
System.out.println("Access addAce from User");
|
||||
customJdbcMutableAclService.addAceToAcl(oldUser.getContact().getId(), new PrincipalSid(oldUser.getUsername()), BasePermission.ADMINISTRATION, Contact.class);
|
||||
}
|
||||
}*/
|
||||
|
||||
return userRepository.save(oldUser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public User updateWithoutTouchingAcl(Long id, User user) {
|
||||
User oldUser = getUserById(id);
|
||||
|
||||
if (user.getUsername() != null) oldUser.setUsername(user.getUsername());
|
||||
if (user.getEnabled() != null) oldUser.setEnabled(user.getEnabled());
|
||||
if (user.getAdmin() != null) oldUser.setAdmin(user.getAdmin());
|
||||
if (user.getRoles() != null) oldUser.setRoles(user.getRoles());
|
||||
if (user.getGroups() != null) oldUser.setGroups(user.getGroups());
|
||||
/*if (user.getContact() != null) {
|
||||
if (user.getContact().getId().equals(0L) && oldUser.getContact() != null) {
|
||||
oldUser.setContact(null);
|
||||
} else if (!user.getContact().getId().equals(oldUser.getContact().getId())) {
|
||||
oldUser.setContact(user.getContact());
|
||||
}
|
||||
}*/
|
||||
return userRepository.save(oldUser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Long id) {
|
||||
User userToBeDeleted = getUserById(id);
|
||||
//customJdbcMutableAclService.deleteAceFromAcl(userToBeDeleted.getContact().getId(), new PrincipalSid(userToBeDeleted.getUsername()), BasePermission.ADMINISTRATION, Contact.class);
|
||||
userRepository.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<User> getAll(Pageable pageable) {
|
||||
return userRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<User> getAllByRole(Role role) {
|
||||
return userRepository.findAllByRoles(role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<User> getAllByGroup(Group group) {
|
||||
return userRepository.findAllByGroups(group);
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getUserByUsername(String username) {
|
||||
return userRepository.findByUsername(username);
|
||||
}
|
||||
|
||||
public void enableUser(Long id) {
|
||||
User user = getUserById(id);
|
||||
user.setEnabled(Boolean.TRUE);
|
||||
userRepository.save(user);
|
||||
}
|
||||
|
||||
public User signUpUser(User user) {
|
||||
user.setPassword(passwordEncoder.encode(user.getPassword()));
|
||||
return userRepository.save(user);
|
||||
}
|
||||
|
||||
public void resetPassword(String username) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
public void updatePassword(String password) {
|
||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
User user = userRepository.findByUsername(auth.getName());
|
||||
user.setPassword(passwordEncoder.encode(password));
|
||||
user.setLastPasswordResetDate(new Timestamp(System.currentTimeMillis()));
|
||||
userRepository.save(user);
|
||||
}
|
||||
}
|
||||
20
src/main/java/de/ul/swtp/system/UserRepository.java
Normal file
20
src/main/java/de/ul/swtp/system/UserRepository.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package de.ul.swtp.system;
|
||||
|
||||
import de.ul.swtp.modules.contactmanagement.Group;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
User findByUsername(String username);
|
||||
|
||||
List<User> findAllByRoles(Role role);
|
||||
|
||||
List<User> findAllByGroups(Group group);
|
||||
|
||||
Page<User> findAllByIdIn(List<Long> ids, Pageable pageable);
|
||||
}
|
||||
42
src/main/java/de/ul/swtp/system/api/ApiController.java
Normal file
42
src/main/java/de/ul/swtp/system/api/ApiController.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package de.ul.swtp.system.api;
|
||||
|
||||
import de.ul.swtp.modules.messaging.SimpleMailMessageWrapper;
|
||||
import de.ul.swtp.system.User;
|
||||
import de.ul.swtp.system.UserManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@CrossOrigin(origins = "*")
|
||||
@RequestMapping(path = "/api")
|
||||
public class ApiController {
|
||||
|
||||
private final UserManager userManager;
|
||||
private final JavaMailSender javaMailSender;
|
||||
|
||||
@Autowired
|
||||
public ApiController(UserManager userManager, JavaMailSender javaMailSender) {
|
||||
this.userManager = userManager;
|
||||
this.javaMailSender = javaMailSender;
|
||||
}
|
||||
|
||||
@PutMapping(path = "reset-password/{usernameOrEmail}")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Page<User>> getUsers(@PathVariable String usernameOrEmail) {
|
||||
|
||||
// find user by username
|
||||
// build jwt that allows "login" based on old password
|
||||
// redirect to reset-password page
|
||||
|
||||
|
||||
userManager.getUserByUsername(usernameOrEmail);
|
||||
SimpleMailMessageWrapper simpleMailMessageWrapper = new SimpleMailMessageWrapper();
|
||||
simpleMailMessageWrapper.setText("Please click on this link to reset your password:");
|
||||
javaMailSender.send(simpleMailMessageWrapper);
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package de.ul.swtp.system.signup;
|
||||
|
||||
import de.ul.swtp.system.User;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@Entity
|
||||
@Table(name = "mv_users_email_verification_token")
|
||||
public class EmailVerificationToken {
|
||||
private static final int EXPIRATION = 60 * 24;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
private String token;
|
||||
|
||||
@OneToOne(targetEntity = User.class, fetch = FetchType.EAGER)
|
||||
@JoinColumn(nullable = false, name = "user_id")
|
||||
private User user;
|
||||
|
||||
private Date issueDate;
|
||||
|
||||
void generateToken() {
|
||||
this.token = UUID.randomUUID().toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package de.ul.swtp.system.signup;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface EmailVerificationTokenRepository extends JpaRepository<EmailVerificationToken, Long> {
|
||||
EmailVerificationToken getEmailVerificationTokenByToken(String token);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package de.ul.swtp.system.signup;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
class SendEmailOnSignUpCompleteEvent extends ApplicationEvent {
|
||||
|
||||
@Getter
|
||||
private EmailVerificationToken emailVerificationToken;
|
||||
|
||||
SendEmailOnSignUpCompleteEvent(Object source, EmailVerificationToken emailVerificationToken) {
|
||||
super(source);
|
||||
this.emailVerificationToken = emailVerificationToken;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package de.ul.swtp.system.signup;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class SendEmailOnSignUpCompleteListener implements ApplicationListener<SendEmailOnSignUpCompleteEvent> {
|
||||
|
||||
private final JavaMailSender javaMailSender;
|
||||
|
||||
@Value("${mv.email.from}")
|
||||
private String from;
|
||||
|
||||
@Value("${mv.signup.email-subject}")
|
||||
private String subject;
|
||||
|
||||
@Value("${mv.url.api}")
|
||||
private String urlServer;
|
||||
|
||||
@Value("${mv.url.signup}")
|
||||
private String urlSignup;
|
||||
|
||||
@Autowired
|
||||
public SendEmailOnSignUpCompleteListener(JavaMailSender javaMailSender) {
|
||||
this.javaMailSender = javaMailSender;
|
||||
}
|
||||
|
||||
/*
|
||||
FYI: The redirecting is not working yet because the link is pointing to an older version of
|
||||
our system which does not include the implementation if it. To test it just replace clientUrl with
|
||||
http://localhost:8080/.
|
||||
*/
|
||||
@Override
|
||||
public void onApplicationEvent(SendEmailOnSignUpCompleteEvent event) {
|
||||
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
|
||||
simpleMailMessage.setTo(event.getEmailVerificationToken().getUser().getUsername());
|
||||
simpleMailMessage.setFrom(from);
|
||||
simpleMailMessage.setSubject(subject);
|
||||
simpleMailMessage.setText(
|
||||
"Willkommen in Ihrer Mitgliederverwaltungssoftware!\n\n"
|
||||
+ "Um Ihr Konto zu aktivieren, klicken Sie bitte hier: " + urlServer + urlSignup + "/" + event.getEmailVerificationToken().getToken() + "\n"
|
||||
+ "Vielen Dank!\n\n"
|
||||
+ "Das mf17a-Team");
|
||||
javaMailSender.send(simpleMailMessage);
|
||||
}
|
||||
}
|
||||
81
src/main/java/de/ul/swtp/system/signup/SignUpController.java
Normal file
81
src/main/java/de/ul/swtp/system/signup/SignUpController.java
Normal file
@@ -0,0 +1,81 @@
|
||||
package de.ul.swtp.system.signup;
|
||||
|
||||
import de.ul.swtp.system.User;
|
||||
import de.ul.swtp.system.UserManagerImpl;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
|
||||
|
||||
@RestController
|
||||
@CrossOrigin(origins = "*")
|
||||
@RequestMapping(path = "/signup")
|
||||
public class SignUpController {
|
||||
|
||||
@Value("${mv.signup.expiration}")
|
||||
private Long expiration;
|
||||
|
||||
@Value("${mv.url.client}")
|
||||
private String urlClient;
|
||||
|
||||
@Value("${mv.url.login}")
|
||||
private String urlLogin;
|
||||
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
|
||||
private final UserManagerImpl userManager;
|
||||
private final EmailVerificationTokenRepository emailVerificationTokenRepository;
|
||||
|
||||
@Autowired
|
||||
public SignUpController(UserManagerImpl userManager, EmailVerificationTokenRepository emailVerificationTokenRepository, ApplicationEventPublisher eventPublisher) {
|
||||
this.eventPublisher = eventPublisher;
|
||||
this.userManager = userManager;
|
||||
this.emailVerificationTokenRepository = emailVerificationTokenRepository;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ResponseBody
|
||||
public ResponseEntity<User> signUp(@RequestBody User user) {
|
||||
// Create user, set to disabled
|
||||
user.setId(null);
|
||||
user.setEnabled(false);
|
||||
user.setAdmin(false);
|
||||
user = userManager.signUpUser(user);
|
||||
|
||||
// Create Token and persist it
|
||||
EmailVerificationToken emailVerificationToken = new EmailVerificationToken();
|
||||
emailVerificationToken.setUser(user);
|
||||
emailVerificationToken.generateToken();
|
||||
emailVerificationToken.setIssueDate(new Timestamp(System.currentTimeMillis()));
|
||||
emailVerificationTokenRepository.save(emailVerificationToken);
|
||||
|
||||
// Send email to the user via system event
|
||||
eventPublisher.publishEvent(new SendEmailOnSignUpCompleteEvent(this, emailVerificationToken));
|
||||
return new ResponseEntity<>(user, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@GetMapping(path = "/{emailVerificationTokenString}")
|
||||
@ResponseBody
|
||||
public ResponseEntity<User> verifySignUp(@PathVariable String emailVerificationTokenString) {
|
||||
EmailVerificationToken emailVerificationToken = emailVerificationTokenRepository.getEmailVerificationTokenByToken(emailVerificationTokenString);
|
||||
|
||||
Long currentTime = System.currentTimeMillis();
|
||||
Long tokenIssueTime = emailVerificationToken.getIssueDate().getTime();
|
||||
|
||||
if (currentTime - tokenIssueTime < expiration) {
|
||||
userManager.enableUser(emailVerificationToken.getUser().getId());
|
||||
|
||||
//Prepare response to redirect
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("Location", urlClient + urlLogin);
|
||||
return new ResponseEntity<>(headers, HttpStatus.PERMANENT_REDIRECT);
|
||||
}
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
11
src/main/resources/application-dev-h2.properties
Normal file
11
src/main/resources/application-dev-h2.properties
Normal file
@@ -0,0 +1,11 @@
|
||||
spring.datasource.platform=h2
|
||||
# Creates a file based database in the current users home directory called 'test'
|
||||
spring.datasource.url=jdbc:h2:~/test
|
||||
spring.datasource.username=sa
|
||||
spring.datasource.password=
|
||||
spring.datasource.driver-class-name=org.h2.Driver
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
|
||||
|
||||
spring.h2.console.path=/h2
|
||||
# Removes warning from stdout
|
||||
spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false
|
||||
11
src/main/resources/application-dev-mariadb.properties
Normal file
11
src/main/resources/application-dev-mariadb.properties
Normal file
@@ -0,0 +1,11 @@
|
||||
spring.jpa.hibernate.ddl-auto=validate
|
||||
|
||||
spring.datasource.platform=mariadb
|
||||
spring.datasource.url=jdbc:mariadb://localhost:3307/mariadb
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=mariadb
|
||||
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
|
||||
|
||||
# Removes warning from stdout
|
||||
spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false
|
||||
11
src/main/resources/application-prod.properties
Normal file
11
src/main/resources/application-prod.properties
Normal file
@@ -0,0 +1,11 @@
|
||||
spring.jpa.hibernate.ddl-auto=validate
|
||||
|
||||
spring.datasource.platform=mariadb
|
||||
spring.datasource.url=jdbc:mariadb://db:3306/mvdb
|
||||
spring.datasource.username=mvuser
|
||||
spring.datasource.password=mariadb
|
||||
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
|
||||
|
||||
# Removes warning from stdout
|
||||
spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false
|
||||
19
src/main/resources/application.properties
Normal file
19
src/main/resources/application.properties
Normal file
@@ -0,0 +1,19 @@
|
||||
spring.profiles.active=dev-mariadb
|
||||
|
||||
spring.jackson.serialization.INDENT_OUTPUT=true
|
||||
|
||||
mv.url.api=https://api.swt.leoek.eu
|
||||
mv.url.client=https://swt.leoek.eu
|
||||
mv.url.login=/login
|
||||
mv.url.signup=/signup
|
||||
mv.url.refresh-jwt=/refresh
|
||||
|
||||
jwt.header=Authorization
|
||||
jwt.secret=mySecret
|
||||
jwt.expiration=604800
|
||||
|
||||
mv.email.from=mv@felixfoertsch.de
|
||||
mv.email.subject-prefix="[MV] "
|
||||
|
||||
mv.signup.expiration=604800
|
||||
mv.signup.email-subject:Aktivieren Sie Ihren Account
|
||||
12
src/main/resources/banner.txt
Normal file
12
src/main/resources/banner.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
___ ___ ___ ___ ___ ___ ___
|
||||
/\ \ /\ \ /\__\ /\ \ |\__\ /\__\ /\__\
|
||||
/::\ \ /::\ \ /::| | /::\ \ |:| | /::| | /:/ /
|
||||
/:/\:\ \ /:/\:\ \ /:|:| | /:/\:\ \ |:| | /:|:| | /:/ /
|
||||
/::\~\:\ \ /::\~\:\ \ /:/|:| |__ /:/ \:\ \ |:|__|__ /:/|:|__|__ /:/__/ ___
|
||||
/:/\:\ \:\__\ /:/\:\ \:\__\ /:/ |:| /\__\ /:/__/ \:\__\ /::::\__\ /:/ |::::\__\ |:| | /\__\
|
||||
\/__\:\ \/__/ \/__\:\/:/ / \/__|:|/:/ / \:\ \ \/__/ /:/~~/~ \/__/~~/:/ / |:| |/:/ /
|
||||
\:\__\ \::/ / |:/:/ / \:\ \ /:/ / /:/ / |:|__/:/ /
|
||||
\/__/ /:/ / |::/ / \:\ \ \/__/ /:/ / \::::/__/
|
||||
/:/ / /:/ / \:\__\ /:/ / ~~~~
|
||||
\/__/ \/__/ \/__/ \/__/
|
||||
|
||||
253
src/main/resources/db/migration/V1__init.sql
Normal file
253
src/main/resources/db/migration/V1__init.sql
Normal file
@@ -0,0 +1,253 @@
|
||||
-- ACL TABLES
|
||||
CREATE TABLE acl_sid (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
principal BOOLEAN NOT NULL,
|
||||
sid VARCHAR(100) NOT NULL,
|
||||
UNIQUE KEY unique_acl_sid (sid, principal)
|
||||
)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
CREATE TABLE acl_class (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
class VARCHAR(100) NOT NULL,
|
||||
UNIQUE KEY uk_acl_class (CLASS)
|
||||
)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
CREATE TABLE acl_object_identity (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
object_id_class BIGINT UNSIGNED NOT NULL,
|
||||
object_id_identity VARCHAR(36) NOT NULL,
|
||||
parent_object BIGINT UNSIGNED,
|
||||
owner_sid BIGINT UNSIGNED,
|
||||
entries_inheriting BOOLEAN NOT NULL,
|
||||
UNIQUE KEY uk_acl_object_identity (object_id_class, object_id_identity),
|
||||
CONSTRAINT fk_acl_object_identity_parent FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id),
|
||||
CONSTRAINT fk_acl_object_identity_class FOREIGN KEY (object_id_class) REFERENCES acl_class (id),
|
||||
CONSTRAINT fk_acl_object_identity_owner FOREIGN KEY (owner_sid) REFERENCES acl_sid (id)
|
||||
)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
CREATE TABLE acl_entry (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
acl_object_identity BIGINT UNSIGNED NOT NULL,
|
||||
ace_order INTEGER NOT NULL,
|
||||
sid BIGINT UNSIGNED NOT NULL,
|
||||
mask INTEGER UNSIGNED NOT NULL,
|
||||
granting BOOLEAN NOT NULL,
|
||||
audit_success BOOLEAN NOT NULL,
|
||||
audit_failure BOOLEAN NOT NULL,
|
||||
UNIQUE KEY unique_acl_entry (acl_object_identity, ace_order),
|
||||
CONSTRAINT fk_acl_entry_object FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity (id),
|
||||
CONSTRAINT fk_acl_entry_acl FOREIGN KEY (sid) REFERENCES acl_sid (id)
|
||||
ON DELETE CASCADE
|
||||
)
|
||||
ENGINE = InnoDB;
|
||||
|
||||
-- MV TABLES
|
||||
CREATE TABLE mv_cm_contacts (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
email VARCHAR(255) UNIQUE,
|
||||
first_name VARCHAR(255),
|
||||
last_name VARCHAR(255),
|
||||
phone VARCHAR(255),
|
||||
address VARCHAR(255),
|
||||
bank_details VARCHAR(255)
|
||||
);
|
||||
|
||||
CREATE TABLE mv_cm_groups (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) UNIQUE,
|
||||
acl_sid_id BIGINT UNIQUE REFERENCES acl_sid (id),
|
||||
permission_enum INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE mv_users (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
account_non_expired BOOLEAN DEFAULT TRUE,
|
||||
account_non_locked BOOLEAN DEFAULT TRUE,
|
||||
credentials_non_expired BOOLEAN DEFAULT TRUE,
|
||||
admin BOOLEAN DEFAULT FALSE,
|
||||
last_password_reset_date TIMESTAMP,
|
||||
username VARCHAR(255) UNIQUE NOT NULL,
|
||||
password VARCHAR(255),
|
||||
contact_id BIGINT UNIQUE REFERENCES mv_cm_contacts (id)
|
||||
);
|
||||
|
||||
CREATE TABLE mv_users_email_verification_token (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id BIGINT UNSIGNED NOT NULL REFERENCES mv_users (id),
|
||||
token VARCHAR(255),
|
||||
issue_date TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE mv_authorities (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(100) UNIQUE NOT NULL,
|
||||
system_authority BOOLEAN
|
||||
);
|
||||
|
||||
CREATE TABLE mv_roles (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(50) UNIQUE NOT NULL
|
||||
);
|
||||
|
||||
-- JOIN TABLES
|
||||
CREATE TABLE mv_user_group (
|
||||
user_id BIGINT NOT NULL REFERENCES mv_users (id)
|
||||
ON DELETE CASCADE,
|
||||
group_id BIGINT NOT NULL REFERENCES mv_cm_groups (id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE mv_role_authority (
|
||||
role_id BIGINT NOT NULL REFERENCES mv_roles (id)
|
||||
ON DELETE CASCADE,
|
||||
authority_id BIGINT NOT NULL REFERENCES mv_authorities (id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE mv_user_role (
|
||||
user_id BIGINT NOT NULL REFERENCES mv_users (id)
|
||||
ON DELETE CASCADE,
|
||||
role_id BIGINT NOT NULL REFERENCES mv_roles (id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE mv_group_contact (
|
||||
group_id BIGINT NOT NULL REFERENCES mv_cm_groups (id)
|
||||
ON DELETE CASCADE,
|
||||
contact_id BIGINT NOT NULL REFERENCES mv_cm_contacts (id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE mv_group_contact_responsible (
|
||||
group_id BIGINT NOT NULL REFERENCES mv_cm_groups (id)
|
||||
ON DELETE CASCADE,
|
||||
contact_id BIGINT NOT NULL REFERENCES mv_cm_contacts (id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
INSERT INTO acl_sid (principal, sid) VALUES
|
||||
(FALSE, 'ROLE_ADMIN'),
|
||||
(FALSE, 'ROLE_USER');
|
||||
|
||||
INSERT INTO mv_authorities (name) VALUES
|
||||
('ROLE_ADMIN'),
|
||||
('ROLE_USER'),
|
||||
-- SYSTEM AUTHORITIES
|
||||
-- USER
|
||||
('SYS_USER_GETALL'),
|
||||
('SYS_USER_GETUSERBYID'),
|
||||
('SYS_USER_GETUSERSBYIDS'),
|
||||
('SYS_USER_GETUSERBYUSERNAME'),
|
||||
('SYS_USER_GETALLBYROLE'),
|
||||
('SYS_USER_GETALLBYGROUP'),
|
||||
('SYS_USER_CREATE'),
|
||||
('SYS_USER_UPDATE'),
|
||||
('SYS_USER_DELETE'),
|
||||
-- ROLE
|
||||
('SYS_ROLE_CREATE'),
|
||||
('SYS_ROLE_GETROLEBYID'),
|
||||
('SYS_ROLE_GETROLESBYIDS'),
|
||||
('SYS_ROLE_GETALL'),
|
||||
('SYS_ROLE_FINDUSERSINROLE'),
|
||||
('SYS_ROLE_UPDATE'),
|
||||
('SYS_ROLE_DELETE'),
|
||||
('SYS_ROLE_ADDUSERTOROLE'),
|
||||
('SYS_ROLE_ADDUSERSTOROLE'),
|
||||
('SYS_ROLE_REMOVEUSERFROMROLE'),
|
||||
('SYS_ROLE_REMOVEUSERSFROMROLE'),
|
||||
('SYS_ROLE_REMOVEALLUSERSFROMROLE'),
|
||||
('SYS_ROLE_ADDAUTHORITYTOROLE'),
|
||||
('SYS_ROLE_ADDAUTHORITIESTOROLE'),
|
||||
('SYS_ROLE_REMOVEAUTHORITYFROMROLE'),
|
||||
('SYS_ROLE_REMOVEAUTHORITIESFROMROLE'),
|
||||
-- CONTACTMANAGEMENT AUTHORITIES
|
||||
-- CONTACT
|
||||
('CM_CONTACT_CREATE'),
|
||||
('CM_CONTACT_GETCONTACTBYID'),
|
||||
('CM_CONTACT_UPDATE'),
|
||||
('CM_CONTACT_DELETE'),
|
||||
('CM_CONTACT_ADDPERMISSIONTOGROUP'),
|
||||
('CM_CONTACT_ADDPERMISSIONTOGROUPS'),
|
||||
('CM_CONTACT_GETALL'),
|
||||
('CM_CONTACT_GETCONTACTSBYIDS'),
|
||||
-- GROUP
|
||||
('CM_GROUP_CREATE'),
|
||||
('CM_GROUP_GETGROUPBYID'),
|
||||
('CM_GROUP_GETGROUPSBYIDS'),
|
||||
('CM_GROUP_GETALL'),
|
||||
('CM_GROUP_GETALLBYCONTACTS'),
|
||||
('CM_GROUP_FINDUSERSINGROUP'),
|
||||
('CM_GROUP_UPDATE'),
|
||||
('CM_GROUP_DELETE'),
|
||||
('CM_GROUP_ADDUSERTOGROUP'),
|
||||
('CM_GROUP_ADDUSERSTOGROUP'),
|
||||
('CM_GROUP_REMOVEUSERFROMGROUP'),
|
||||
('CM_GROUP_REMOVEUSERSFROMGROUP'),
|
||||
('CM_GROUP_ADDAUTHORITYTOGROUP'),
|
||||
('CM_GROUP_ADDAUTHORITIESTOGROUP'),
|
||||
('CM_GROUP_REMOVEAUTHORITYFROMGROUP'),
|
||||
('CM_GROUP_REMOVEAUTHORITIESFROMGROUP');
|
||||
|
||||
-- Create one user object as the first user, called admin
|
||||
INSERT INTO mv_users (admin, enabled, account_non_expired, account_non_locked, credentials_non_expired, username, last_password_reset_date, password)
|
||||
VALUES
|
||||
(TRUE, TRUE, TRUE, TRUE, TRUE, 'admin@test.com', current_date,
|
||||
'$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi'),
|
||||
(FALSE, TRUE, TRUE, TRUE, TRUE, 'user@test.com', current_date,
|
||||
'$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC'),
|
||||
(FALSE, FALSE, TRUE, TRUE, TRUE, 'disabled@test.com', current_date,
|
||||
'$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC'),
|
||||
(FALSE, TRUE, TRUE, TRUE, TRUE, 'admin2@test.com', current_date,
|
||||
'$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi'),
|
||||
(TRUE, TRUE, TRUE, TRUE, TRUE, '1admin@test.com', current_date,
|
||||
'$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi'),
|
||||
(FALSE, TRUE, TRUE, TRUE, TRUE, '1user@test.com', current_date,
|
||||
'$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC'),
|
||||
(FALSE, FALSE, TRUE, TRUE, TRUE, '1disabled@test.com', current_date,
|
||||
'$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC'),
|
||||
(FALSE, TRUE, TRUE, TRUE, TRUE, '1admin2@test.com', current_date,
|
||||
'$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi'),
|
||||
(TRUE, TRUE, TRUE, TRUE, TRUE, '2admin@test.com', current_date,
|
||||
'$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi'),
|
||||
(FALSE, TRUE, TRUE, TRUE, TRUE, '2user@test.com', current_date,
|
||||
'$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC'),
|
||||
(FALSE, FALSE, TRUE, TRUE, TRUE, '2disabled@test.com', current_date,
|
||||
'$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC'),
|
||||
(FALSE, TRUE, TRUE, TRUE, TRUE, '2admin2@test.com', current_date,
|
||||
'$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi'),
|
||||
(TRUE, TRUE, TRUE, TRUE, TRUE, '3admin@test.com', current_date,
|
||||
'$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi'),
|
||||
(FALSE, TRUE, TRUE, TRUE, TRUE, '3user@test.com', current_date,
|
||||
'$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC'),
|
||||
(FALSE, FALSE, TRUE, TRUE, TRUE, '3disabled@test.com', current_date,
|
||||
'$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC'),
|
||||
(FALSE, TRUE, TRUE, TRUE, TRUE, '3admin2@test.com', current_date,
|
||||
'$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi'),
|
||||
(TRUE, TRUE, TRUE, TRUE, TRUE, '4admin@test.com', current_date,
|
||||
'$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi'),
|
||||
(FALSE, TRUE, TRUE, TRUE, TRUE, '4user@test.com', current_date,
|
||||
'$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC'),
|
||||
(FALSE, FALSE, TRUE, TRUE, TRUE, '4disabled@test.com', current_date,
|
||||
'$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC'),
|
||||
(FALSE, TRUE, TRUE, TRUE, TRUE, '4admin2@test.com', current_date,
|
||||
'$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi'),
|
||||
(TRUE, TRUE, TRUE, TRUE, TRUE, '5admin@test.com', current_date,
|
||||
'$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi'),
|
||||
(FALSE, TRUE, TRUE, TRUE, TRUE, '5user@test.com', current_date,
|
||||
'$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC'),
|
||||
(FALSE, FALSE, TRUE, TRUE, TRUE, '5disabled@test.com', current_date,
|
||||
'$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC'),
|
||||
(FALSE, TRUE, TRUE, TRUE, TRUE, '5admin2@test.com', current_date,
|
||||
'$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi'),
|
||||
(TRUE, TRUE, TRUE, TRUE, TRUE, '6admin@test.com', current_date,
|
||||
'$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi'),
|
||||
(FALSE, TRUE, TRUE, TRUE, TRUE, '6user@test.com', current_date,
|
||||
'$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC'),
|
||||
(FALSE, FALSE, TRUE, TRUE, TRUE, '6disabled@test.com', current_date,
|
||||
'$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC'),
|
||||
(FALSE, TRUE, TRUE, TRUE, TRUE, '6admin2@test.com', current_date,
|
||||
'$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi');
|
||||
8
src/main/resources/ehcache.xml
Normal file
8
src/main/resources/ehcache.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true" monitoring="autodetect" dynamicConfig="true">
|
||||
<diskStore path="java.io.tmpdir"/>
|
||||
<transactionManagerLookup class="net.sf.ehcache.transaction.manager.DefaultTransactionManagerLookup" properties="jndiName=java:/TransactionManager" propertySeparator=";"/>
|
||||
<cacheManagerEventListenerFactory class="" properties=""/>
|
||||
<cache name="aclCache" maxEntriesLocalHeap="10000" maxEntriesLocalDisk="1000" eternal="false" diskSpoolBufferSizeMB="20" timeToIdleSeconds="300" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LFU" transactionalMode="off">
|
||||
<persistence strategy="localTempSwap"/>
|
||||
</cache>
|
||||
</ehcache>
|
||||
422
src/main/resources/ehcache.xsd
Normal file
422
src/main/resources/ehcache.xsd
Normal file
@@ -0,0 +1,422 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" version="1.7">
|
||||
|
||||
<xs:element name="ehcache">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element maxOccurs="1" minOccurs="0" ref="diskStore"/>
|
||||
<xs:element maxOccurs="1" minOccurs="0" ref="sizeOfPolicy"/>
|
||||
<xs:element maxOccurs="1" minOccurs="0" ref="transactionManagerLookup"/>
|
||||
<xs:element maxOccurs="1" minOccurs="0" ref="cacheManagerEventListenerFactory"/>
|
||||
<xs:element maxOccurs="1" minOccurs="0" ref="managementRESTService"/>
|
||||
<xs:element maxOccurs="unbounded" minOccurs="0" ref="cacheManagerPeerProviderFactory"/>
|
||||
<xs:element maxOccurs="unbounded" minOccurs="0" ref="cacheManagerPeerListenerFactory"/>
|
||||
<xs:element maxOccurs="1" minOccurs="0" ref="terracottaConfig"/>
|
||||
<xs:element maxOccurs= "1" minOccurs="0" ref="defaultCache"/>
|
||||
<xs:element maxOccurs="unbounded" minOccurs="0" ref="cache"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" use="optional"/>
|
||||
<xs:attribute default="true" name="updateCheck" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute default="autodetect" name="monitoring" type="monitoringType" use="optional"/>
|
||||
<xs:attribute default="true" name="dynamicConfig" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute default="15" name="defaultTransactionTimeoutInSeconds" type="xs:integer" use="optional"/>
|
||||
<xs:attribute default="0" name="maxBytesLocalHeap" type="memoryUnitOrPercentage" use="optional"/>
|
||||
<xs:attribute default="0" name="maxBytesLocalOffHeap" type="memoryUnit" use="optional"/>
|
||||
<xs:attribute default="0" name="maxBytesLocalDisk" type="memoryUnit" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="managementRESTService">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="enabled" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute name="bind" use="optional"/>
|
||||
<xs:attribute name="securityServiceLocation" use="optional"/>
|
||||
<xs:attribute name="securityServiceTimeout" use="optional" type="xs:integer"/>
|
||||
<xs:attribute name="sslEnabled" use="optional" type="xs:boolean"/>
|
||||
<xs:attribute name="needClientAuth" use="optional" type="xs:boolean"/>
|
||||
<xs:attribute name="sampleHistorySize" use="optional" type="xs:integer"/>
|
||||
<xs:attribute name="sampleIntervalSeconds" use="optional" type="xs:integer"/>
|
||||
<xs:attribute name="sampleSearchIntervalSeconds" use="optional" type="xs:integer"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="diskStore">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="path" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="transactionManagerLookup">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required"/>
|
||||
<xs:attribute name="properties" use="optional"/>
|
||||
<xs:attribute name="propertySeparator" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="cacheManagerEventListenerFactory">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required"/>
|
||||
<xs:attribute name="properties" use="optional"/>
|
||||
<xs:attribute name="propertySeparator" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="cacheManagerPeerProviderFactory">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required"/>
|
||||
<xs:attribute name="properties" use="optional"/>
|
||||
<xs:attribute name="propertySeparator" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="cacheManagerPeerListenerFactory">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required"/>
|
||||
<xs:attribute name="properties" use="optional"/>
|
||||
<xs:attribute name="propertySeparator" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="terracottaConfig">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element maxOccurs="1" minOccurs="0" name="tc-config">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:any maxOccurs="unbounded" minOccurs="0" processContents="skip"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<xs:attribute default="localhost:9510" name="url" use="optional"/>
|
||||
<xs:attribute name="rejoin" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="wanEnabledTSA" type="xs:boolean" use="optional" default="false"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<!-- add clone support for addition of cacheExceptionHandler. Important! -->
|
||||
<xs:element name="defaultCache">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheEventListenerFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheExtensionFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheLoaderFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheDecoratorFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="bootstrapCacheLoaderFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="cacheExceptionHandlerFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="pinning"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="terracotta"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="cacheWriter"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="copyStrategy"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="elementValueComparator"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="sizeOfPolicy"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="persistence"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="diskExpiryThreadIntervalSeconds" type="xs:integer" use="optional"/>
|
||||
<xs:attribute name="diskSpoolBufferSizeMB" type="xs:integer" use="optional"/>
|
||||
<xs:attribute name="diskPersistent" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute name="diskAccessStripes" type="xs:integer" use="optional" default="1"/>
|
||||
<xs:attribute name="eternal" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="maxElementsInMemory" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="maxEntriesLocalHeap" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="clearOnFlush" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute name="memoryStoreEvictionPolicy" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="overflowToDisk" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute name="timeToIdleSeconds" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="timeToLiveSeconds" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="maxElementsOnDisk" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="maxEntriesLocalDisk" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="transactionalMode" type="transactionalMode" use="optional" default="off"/>
|
||||
<xs:attribute name="statistics" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="copyOnRead" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="copyOnWrite" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="cacheLoaderTimeoutMillis" type="xs:integer" use="optional" default="0"/>
|
||||
<xs:attribute name="overflowToOffHeap" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="maxMemoryOffHeap" type="xs:string" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="cache">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheEventListenerFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheExtensionFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheLoaderFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheDecoratorFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="bootstrapCacheLoaderFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="cacheExceptionHandlerFactory"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="pinning"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="terracotta"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="cacheWriter"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="copyStrategy"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="searchable"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="elementValueComparator"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="sizeOfPolicy"/>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="persistence"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="diskExpiryThreadIntervalSeconds" type="xs:integer" use="optional"/>
|
||||
<xs:attribute name="diskSpoolBufferSizeMB" type="xs:integer" use="optional"/>
|
||||
<xs:attribute name="diskPersistent" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute name="diskAccessStripes" type="xs:integer" use="optional" default="1"/>
|
||||
<xs:attribute name="eternal" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="maxElementsInMemory" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="maxEntriesLocalHeap" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="memoryStoreEvictionPolicy" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="clearOnFlush" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute name="name" type="xs:string" use="required"/>
|
||||
<xs:attribute name="overflowToDisk" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute name="timeToIdleSeconds" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="timeToLiveSeconds" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="maxElementsOnDisk" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="maxEntriesLocalDisk" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="maxEntriesInCache" type="xs:nonNegativeInteger" use="optional"/>
|
||||
<xs:attribute name="transactionalMode" type="transactionalMode" use="optional" default="off" />
|
||||
<xs:attribute name="statistics" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="copyOnRead" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="copyOnWrite" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="logging" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="cacheLoaderTimeoutMillis" type="xs:integer" use="optional" default="0"/>
|
||||
<xs:attribute name="overflowToOffHeap" type="xs:boolean" use="optional" default="false"/>
|
||||
<xs:attribute name="maxMemoryOffHeap" type="xs:string" use="optional"/>
|
||||
<xs:attribute default="0" name="maxBytesLocalHeap" type="memoryUnitOrPercentage" use="optional"/>
|
||||
<xs:attribute default="0" name="maxBytesLocalOffHeap" type="memoryUnitOrPercentage" use="optional"/>
|
||||
<xs:attribute default="0" name="maxBytesLocalDisk" type="memoryUnitOrPercentage" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="cacheEventListenerFactory">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required"/>
|
||||
<xs:attribute name="properties" use="optional"/>
|
||||
<xs:attribute name="propertySeparator" use="optional"/>
|
||||
<xs:attribute name="listenFor" use="optional" type="notificationScope" default="all"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="bootstrapCacheLoaderFactory">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required"/>
|
||||
<xs:attribute name="properties" use="optional"/>
|
||||
<xs:attribute name="propertySeparator" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="cacheExtensionFactory">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required"/>
|
||||
<xs:attribute name="properties" use="optional"/>
|
||||
<xs:attribute name="propertySeparator" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="cacheExceptionHandlerFactory">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required"/>
|
||||
<xs:attribute name="properties" use="optional"/>
|
||||
<xs:attribute name="propertySeparator" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="cacheLoaderFactory">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required"/>
|
||||
<xs:attribute name="properties" use="optional"/>
|
||||
<xs:attribute name="propertySeparator" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="cacheDecoratorFactory">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required"/>
|
||||
<xs:attribute name="properties" use="optional"/>
|
||||
<xs:attribute name="propertySeparator" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="searchAttribute">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="name" use="required" type="xs:string" />
|
||||
<xs:attribute name="expression" type="xs:string" />
|
||||
<xs:attribute name="class" type="xs:string" />
|
||||
<xs:attribute name="type" type="xs:string" use="optional"/>
|
||||
<xs:attribute name="properties" use="optional" />
|
||||
<xs:attribute name="propertySeparator" use="optional" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="searchable">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" ref="searchAttribute"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="keys" use="optional" type="xs:boolean" default="true"/>
|
||||
<xs:attribute name="values" use="optional" type="xs:boolean" default="true"/>
|
||||
<xs:attribute name="allowDynamicIndexing" use="optional" type="xs:boolean" default="false"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="pinning">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="store" use="required" type="pinningStoreType"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="terracotta">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="nonstop"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="clustered" use="optional" type="xs:boolean" default="true"/>
|
||||
<xs:attribute name="coherentReads" use="optional" type="xs:boolean" default="true"/>
|
||||
<xs:attribute name="localKeyCache" use="optional" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="localKeyCacheSize" use="optional" type="xs:positiveInteger" default="300000"/>
|
||||
<xs:attribute name="orphanEviction" use="optional" type="xs:boolean" default="true"/>
|
||||
<xs:attribute name="orphanEvictionPeriod" use="optional" type="xs:positiveInteger" default="4"/>
|
||||
<xs:attribute name="copyOnRead" use="optional" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="coherent" use="optional" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="consistency" use="optional" type="consistencyType" default="eventual"/>
|
||||
<xs:attribute name="synchronousWrites" use="optional" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="concurrency" use="optional" type="xs:nonNegativeInteger" default="0"/>
|
||||
<xs:attribute name="localCacheEnabled" use="optional" type="xs:boolean" default="true"/>
|
||||
<xs:attribute name="compressionEnabled" use="optional" type="xs:boolean" default="false"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:simpleType name="consistencyType">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="strong" />
|
||||
<xs:enumeration value="eventual" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:element name="nonstop">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="timeoutBehavior"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="enabled" use="optional" type="xs:boolean" default="true"/>
|
||||
<xs:attribute name="immediateTimeout" use="optional" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="timeoutMillis" use="optional" type="xs:positiveInteger" default="30000"/>
|
||||
<xs:attribute name="searchTimeoutMillis" use="optional" type="xs:positiveInteger" default="30000"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="timeoutBehavior">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="type" use="optional" type="timeoutBehaviorType" default="exception"/>
|
||||
<xs:attribute name="properties" use="optional" default=""/>
|
||||
<xs:attribute name="propertySeparator" use="optional" default=","/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:simpleType name="timeoutBehaviorType">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="noop" />
|
||||
<xs:enumeration value="exception" />
|
||||
<xs:enumeration value="localReads" />
|
||||
<xs:enumeration value="localReadsAndExceptionOnWrite" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="monitoringType">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="autodetect"/>
|
||||
<xs:enumeration value="on"/>
|
||||
<xs:enumeration value="off"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="pinningStoreType">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="localMemory" />
|
||||
<xs:enumeration value="inCache" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="terracottaCacheValueType">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="serialization" />
|
||||
<xs:enumeration value="identity" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="transactionalMode">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="off"/>
|
||||
<xs:enumeration value="xa_strict"/>
|
||||
<xs:enumeration value="xa"/>
|
||||
<xs:enumeration value="local"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:element name="cacheWriter">
|
||||
<xs:complexType>
|
||||
<xs:sequence >
|
||||
<xs:element minOccurs="0" maxOccurs="1" ref="cacheWriterFactory"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="writeMode" use="optional" type="writeModeType" default="write-through"/>
|
||||
<xs:attribute name="notifyListenersOnException" use="optional" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="minWriteDelay" use="optional" type="xs:nonNegativeInteger" default="1"/>
|
||||
<xs:attribute name="maxWriteDelay" use="optional" type="xs:nonNegativeInteger" default="1"/>
|
||||
<xs:attribute name="rateLimitPerSecond" use="optional" type="xs:nonNegativeInteger" default="0"/>
|
||||
<xs:attribute name="writeCoalescing" use="optional" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="writeBatching" use="optional" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="writeBatchSize" use="optional" type="xs:positiveInteger" default="1"/>
|
||||
<xs:attribute name="retryAttempts" use="optional" type="xs:nonNegativeInteger" default="0"/>
|
||||
<xs:attribute name="retryAttemptDelaySeconds" use="optional" type="xs:nonNegativeInteger" default="1"/>
|
||||
<xs:attribute name="writeBehindConcurrency" use="optional" type="xs:nonNegativeInteger" default="1"/>
|
||||
<xs:attribute name="writeBehindMaxQueueSize" use="optional" type="xs:nonNegativeInteger" default="0"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:simpleType name="writeModeType">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="write-through" />
|
||||
<xs:enumeration value="write-behind" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:element name="cacheWriterFactory">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required"/>
|
||||
<xs:attribute name="properties" use="optional"/>
|
||||
<xs:attribute name="propertySeparator" use="optional"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="copyStrategy">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required" type="xs:string" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="elementValueComparator">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="class" use="required" type="xs:string" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="sizeOfPolicy">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="maxDepth" use="required" type="xs:integer" />
|
||||
<xs:attribute name="maxDepthExceededBehavior" use="optional" default="continue" type="maxDepthExceededBehavior" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:element name="persistence">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="strategy" use="required" type="persistenceStrategy"/>
|
||||
<xs:attribute name="synchronousWrites" use="optional" default="false" type="xs:boolean"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:simpleType name="persistenceStrategy">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="localTempSwap"/>
|
||||
<xs:enumeration value="localRestartable"/>
|
||||
<xs:enumeration value="none"/>
|
||||
<xs:enumeration value="distributed"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="maxDepthExceededBehavior">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="continue"/>
|
||||
<xs:enumeration value="abort"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="notificationScope">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="local"/>
|
||||
<xs:enumeration value="remote"/>
|
||||
<xs:enumeration value="all"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="memoryUnit">
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:pattern value="[0-9]+[bBkKmMgG]?"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:simpleType name="memoryUnitOrPercentage">
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:pattern value="([0-9]+[bBkKmMgG]?|100%|[0-9]{1,2}%)"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:schema>
|
||||
11
src/main/resources/logback-spring.xml
Normal file
11
src/main/resources/logback-spring.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</root>
|
||||
|
||||
<logger name="org.zerhusen" level="DEBUG"/>
|
||||
</configuration>
|
||||
210
src/main/resources/static/index.html
Normal file
210
src/main/resources/static/index.html
Normal file
@@ -0,0 +1,210 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Title</title>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
|
||||
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.10/css/all.css"
|
||||
integrity="sha384-+d0P83n9kaQMCwj8F4RJB66tzIwOKmrdb46+porD/OvrJ+37WqIM7UoBtwHO6Nlg" crossorigin="anonymous">
|
||||
</head>
|
||||
<body style="padding-top: 100px">
|
||||
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
|
||||
<a class="navbar-brand" href="#">VerVer</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarTogglerDemo02"
|
||||
aria-controls="navbarTogglerDemo02" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarTogglerDemo02">
|
||||
<ul class="navbar-nav mr-auto mt-2 mt-lg-0">
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Link</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link disabled" href="#">Disabled</a>
|
||||
</li>
|
||||
</ul>
|
||||
<form class="form-inline my-2 my-lg-0">
|
||||
<input class="form-control mr-sm-2" type="search" placeholder="Search">
|
||||
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
|
||||
</form>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="alert alert-danger" id="notLoggedIn">Not logged in!</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="card my-3" id="login">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title">Login</h3>
|
||||
<div class="card-text">
|
||||
<p>Try one of the following logins</p>
|
||||
<ul>
|
||||
<li>admin & admin</li>
|
||||
<li>user & password</li>
|
||||
<li>disabled & password</li>
|
||||
</ul>
|
||||
</div>
|
||||
<form id="loginForm">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" id="exampleInputEmail1" placeholder="Enter Username"
|
||||
required name="username">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="password" class="form-control" id="exampleInputPassword1"
|
||||
placeholder="Enter Password" required name="password">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="userInfo">
|
||||
<div class="card my-3">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title">Authenticated user</h3>
|
||||
<div id="userInfoBody"></div>
|
||||
<button type="button" class="btn btn-danger" id="logoutButton">logout</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6">
|
||||
<div id="loggedIn">
|
||||
<div class="card my-3">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title">Token information</h3>
|
||||
<div id="loggedInBody"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card my-3">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title">Request:</h3>
|
||||
<div class="btn-role" role="role" aria-label="..." style="margin-bottom: 16px;">
|
||||
<button type="button" class="btn btn-default" id="exampleServiceBtn">call example service</button>
|
||||
<button type="button" class="btn btn-default" id="adminServiceBtn">call admin protected service
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card my-3">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title">Response:</h3>
|
||||
<pre id="response"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">ID</th>
|
||||
<th scope="col">First Name</th>
|
||||
<th scope="col">Last Name</th>
|
||||
<th scope="col">Email</th>
|
||||
<th scope="col">Phone</th>
|
||||
<th scope="col">Adress</th>
|
||||
<th scope="col">Admin</th>
|
||||
<th scope="col">Edit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">1</td>
|
||||
<td>David</td>
|
||||
<td>Reinartz</td>
|
||||
<td>david.reinartz@gmail.com</td>
|
||||
<td>
|
||||
<tel>+49 123 456 789 0</tel>
|
||||
</td>
|
||||
<td>Riebeckstraße 13</td>
|
||||
<td></td>
|
||||
<td>
|
||||
<i class="fas fa-edit"></i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td scope="row">2</td>
|
||||
<td>Felix</td>
|
||||
<td>Förtsch</td>
|
||||
<td>felixfoertsch@gmail.com</td>
|
||||
<td>+49 151 525 676 44</td>
|
||||
<td>Kurt-Eisner-Straße 55</td>
|
||||
<td>
|
||||
<i class="fas fa-check"></i>
|
||||
</td>
|
||||
<td>
|
||||
<i class="fas fa-edit"></i>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" tabindex="-1" role="dialog" id="loginErrorModal">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Login unsuccessful</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Optional JavaScript -->
|
||||
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
|
||||
<!--<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"-->
|
||||
<!--integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"-->
|
||||
<!--crossorigin="anonymous"></script>-->
|
||||
<script src="https://code.jquery.com/jquery-2.2.2.js"
|
||||
integrity="sha256-4/zUCqiq0kqxhZIyp4G0Gk+AOtCJsY1TA00k5ClsZYE="
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
|
||||
integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
|
||||
integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="js/libs/jwt-decode.min.js"></script>
|
||||
<script src="js/client.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
194
src/main/resources/static/js/client.js
Normal file
194
src/main/resources/static/js/client.js
Normal file
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* Created by stephan on 20.03.16.
|
||||
*/
|
||||
|
||||
$(function () {
|
||||
// VARIABLES =============================================================
|
||||
var TOKEN_KEY = "jwtToken"
|
||||
var $notLoggedIn = $("#notLoggedIn");
|
||||
var $loggedIn = $("#loggedIn").hide();
|
||||
var $loggedInBody = $("#loggedInBody");
|
||||
var $response = $("#response");
|
||||
var $login = $("#login");
|
||||
var $userInfo = $("#userInfo").hide();
|
||||
|
||||
// FUNCTIONS =============================================================
|
||||
function getJwtToken() {
|
||||
return localStorage.getItem(TOKEN_KEY);
|
||||
}
|
||||
|
||||
function setJwtToken(token) {
|
||||
localStorage.setItem(TOKEN_KEY, token);
|
||||
}
|
||||
|
||||
function removeJwtToken() {
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
}
|
||||
|
||||
function doLogin(loginData) {
|
||||
$.ajax({
|
||||
url: "/login",
|
||||
type: "POST",
|
||||
data: JSON.stringify(loginData),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
success: function (data, textStatus, jqXHR) {
|
||||
console.log(data);
|
||||
setJwtToken(data.access_token);
|
||||
$login.hide();
|
||||
$notLoggedIn.hide();
|
||||
showTokenInformation();
|
||||
showUserInformation();
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
if (jqXHR.status === 401 || jqXHR.status === 403) {
|
||||
$('#loginErrorModal')
|
||||
.modal("show")
|
||||
.find(".modal-body")
|
||||
.empty()
|
||||
.html("<p>Message from server:<br>" + jqXHR.responseText + "</p>");
|
||||
} else {
|
||||
throw new Error("an unexpected error occured: " + errorThrown);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function doLogout() {
|
||||
removeJwtToken();
|
||||
$login.show();
|
||||
$userInfo
|
||||
.hide()
|
||||
.find("#userInfoBody").empty();
|
||||
$loggedIn.hide();
|
||||
$loggedInBody.empty();
|
||||
$notLoggedIn.show();
|
||||
}
|
||||
|
||||
function createAuthorizationTokenHeader() {
|
||||
var token = getJwtToken();
|
||||
if (token) {
|
||||
return {"Authorization": "Bearer " + token};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function showUserInformation() {
|
||||
$.ajax({
|
||||
url: "/me",
|
||||
type: "GET",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
headers: createAuthorizationTokenHeader(),
|
||||
success: function (data, textStatus, jqXHR) {
|
||||
var $userInfoBody = $userInfo.find("#userInfoBody");
|
||||
|
||||
$userInfoBody.append($("<div>").text("Username: " + data.username));
|
||||
$userInfoBody.append($("<div>").text("Email: " + data.email));
|
||||
|
||||
var $authorityList = $("<ul>");
|
||||
data.authorities.forEach(function (authorityItem) {
|
||||
$authorityList.append($("<li>").text(authorityItem.authority));
|
||||
});
|
||||
var $authorities = $("<div>").text("Authorities:");
|
||||
$authorities.append($authorityList);
|
||||
|
||||
$userInfoBody.append($authorities);
|
||||
$userInfo.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showTokenInformation() {
|
||||
var jwtToken = getJwtToken();
|
||||
var decodedToken = jwt_decode(jwtToken);
|
||||
|
||||
$loggedInBody.append($("<h4 class='card-title'>").text("Token"));
|
||||
$loggedInBody.append($("<div class='card-text'>").text(jwtToken));
|
||||
$loggedInBody.append($("<h4 class='card-title'>").text("Token claims"));
|
||||
|
||||
var $table = $("<table>")
|
||||
.addClass("table table-striped");
|
||||
appendKeyValue($table, "sub", decodedToken.sub);
|
||||
appendKeyValue($table, "iat", decodedToken.iat);
|
||||
appendKeyValue($table, "exp", decodedToken.exp);
|
||||
|
||||
$loggedInBody.append($table);
|
||||
|
||||
$loggedIn.show();
|
||||
}
|
||||
|
||||
function appendKeyValue($table, key, value) {
|
||||
var $row = $("<tr>")
|
||||
.append($("<td>").text(key))
|
||||
.append($("<td>").text(value));
|
||||
$table.append($row);
|
||||
}
|
||||
|
||||
function showResponse(statusCode, message) {
|
||||
$response
|
||||
.empty()
|
||||
.text("status code: " + statusCode + "\n-------------------------\n" + message);
|
||||
}
|
||||
|
||||
// REGISTER EVENT LISTENERS =============================================================
|
||||
$("#loginForm").submit(function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
var $form = $(this);
|
||||
var formData = {
|
||||
username: $form.find('input[name="username"]').val(),
|
||||
password: $form.find('input[name="password"]').val()
|
||||
};
|
||||
|
||||
doLogin(formData);
|
||||
});
|
||||
|
||||
$("#logoutButton").click(doLogout);
|
||||
|
||||
$("#exampleServiceBtn").click(function () {
|
||||
$.ajax({
|
||||
url: "/persons",
|
||||
type: "GET",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
headers: createAuthorizationTokenHeader(),
|
||||
success: function (data, textStatus, jqXHR) {
|
||||
showResponse(jqXHR.status, JSON.stringify(data));
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
showResponse(jqXHR.status, errorThrown);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#adminServiceBtn").click(function () {
|
||||
$.ajax({
|
||||
url: "/protected",
|
||||
type: "GET",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
headers: createAuthorizationTokenHeader(),
|
||||
success: function (data, textStatus, jqXHR) {
|
||||
showResponse(jqXHR.status, data);
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
showResponse(jqXHR.status, errorThrown);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$loggedIn.click(function () {
|
||||
$loggedIn
|
||||
.toggleClass("text-hidden")
|
||||
.toggleClass("text-shown");
|
||||
});
|
||||
|
||||
// INITIAL CALLS =============================================================
|
||||
if (getJwtToken()) {
|
||||
$login.hide();
|
||||
$notLoggedIn.hide();
|
||||
showTokenInformation();
|
||||
showUserInformation();
|
||||
}
|
||||
});
|
||||
1
src/main/resources/static/js/libs/jwt-decode.min.js
vendored
Normal file
1
src/main/resources/static/js/libs/jwt-decode.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b,c){function d(a){this.message=a}function e(a){var b=String(a).replace(/=+$/,"");if(b.length%4==1)throw new d("'atob' failed: The string to be decoded is not correctly encoded.");for(var c,e,g=0,h=0,i="";e=b.charAt(h++);~e&&(c=g%4?64*c+e:e,g++%4)?i+=String.fromCharCode(255&c>>(-2*g&6)):0)e=f.indexOf(e);return i}var f="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";d.prototype=new Error,d.prototype.name="InvalidCharacterError",b.exports="undefined"!=typeof window&&window.atob&&window.atob.bind(window)||e},{}],2:[function(a,b,c){function d(a){return decodeURIComponent(e(a).replace(/(.)/g,function(a,b){var c=b.charCodeAt(0).toString(16).toUpperCase();return c.length<2&&(c="0"+c),"%"+c}))}var e=a("./atob");b.exports=function(a){var b=a.replace(/-/g,"+").replace(/_/g,"/");switch(b.length%4){case 0:break;case 2:b+="==";break;case 3:b+="=";break;default:throw"Illegal base64url string!"}try{return d(b)}catch(c){return e(b)}}},{"./atob":1}],3:[function(a,b,c){"use strict";function d(a){this.message=a}var e=a("./base64_url_decode");d.prototype=new Error,d.prototype.name="InvalidTokenError",b.exports=function(a,b){if("string"!=typeof a)throw new d("Invalid token specified");b=b||{};var c=b.header===!0?0:1;try{return JSON.parse(e(a.split(".")[c]))}catch(f){throw new d("Invalid token specified: "+f.message)}},b.exports.InvalidTokenError=d},{"./base64_url_decode":2}],4:[function(a,b,c){(function(b){var c=a("./lib/index");"function"==typeof b.window.define&&b.window.define.amd?b.window.define("jwt_decode",function(){return c}):b.window&&(b.window.jwt_decode=c)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./lib/index":3}]},{},[4]);
|
||||
92
src/test/java/de/ul/swtp/MvApplicationTest.java
Normal file
92
src/test/java/de/ul/swtp/MvApplicationTest.java
Normal file
@@ -0,0 +1,92 @@
|
||||
package de.ul.swtp;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@AutoConfigureRestDocs(outputDir = "target/snippets")
|
||||
public class MvApplicationTest {
|
||||
|
||||
@Autowired private MockMvc mockMvc;
|
||||
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
// just test if the application context loads
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(roles = "USER")
|
||||
public void UsersEndpointDefined() throws Exception {
|
||||
MvcResult response =
|
||||
this.mockMvc
|
||||
.perform(get("/users"))
|
||||
.andDo(document("home"))
|
||||
.andReturn();
|
||||
|
||||
Assert.assertNotEquals(response.getResponse().getStatus(), 404);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(roles = "USER")
|
||||
public void GroupsEndpointDefined() throws Exception {
|
||||
MvcResult response =
|
||||
this.mockMvc
|
||||
.perform(get("/groups"))
|
||||
.andDo(document("home"))
|
||||
.andReturn();
|
||||
|
||||
Assert.assertNotEquals(response.getResponse().getStatus(), 404);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(roles = "USER")
|
||||
public void ContactsEndpointDefined() throws Exception {
|
||||
MvcResult response =
|
||||
this.mockMvc
|
||||
.perform(get("/contacts"))
|
||||
.andDo(document("home"))
|
||||
.andReturn();
|
||||
|
||||
Assert.assertNotEquals(response.getResponse().getStatus(), 404);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(roles = "USER")
|
||||
public void PermissionsEndpointDefined() throws Exception {
|
||||
MvcResult response =
|
||||
this.mockMvc
|
||||
.perform(get("/permissions"))
|
||||
.andDo(document("home"))
|
||||
.andReturn();
|
||||
|
||||
Assert.assertNotEquals(response.getResponse().getStatus(), 404);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void LoginEndpointDefined() throws Exception {
|
||||
MvcResult response =
|
||||
this.mockMvc
|
||||
.perform(post("/login").content("{ \"username\": \"username\", \"password\": \"password\" }"))
|
||||
.andDo(document("home"))
|
||||
.andReturn();
|
||||
|
||||
Assert.assertNotEquals(response.getResponse().getStatus(), 404);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package de.ul.swtp.modules.contactmanagement;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@AutoConfigureRestDocs(outputDir = "target/snippets")
|
||||
public class ContactControllerTest {
|
||||
|
||||
@Autowired private MockMvc mockMvc;
|
||||
@Autowired private ContactRepository contactRepository;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
//create sample contacts
|
||||
for(int i = 1; i < 10; ++i){
|
||||
Contact contact = new Contact();
|
||||
|
||||
contact.setId(new Long(i));
|
||||
contact.setEmail("max"+i+"@mustercontact.com");
|
||||
contact.setFirstName("Max");
|
||||
contact.setLastName("Musterkontakt");
|
||||
contact.setPhone("017612"+i+"678");
|
||||
contact.setAddress("Musterkontaktstr. "+i);
|
||||
contact.setBankDetails("Musterbank " + i);
|
||||
|
||||
contactRepository.save(contact);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
// just test if the application context loads
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetUnauthorizedWithoutRole() throws Exception{
|
||||
|
||||
this.mockMvc.perform(get("/contacts"))
|
||||
.andExpect(status().isUnauthorized()).andDo(document("home"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = "CM_CONTACT_GETALL")
|
||||
public void getPersonsSuccessfullyWithUserRole() throws Exception{
|
||||
|
||||
this.mockMvc.perform(get("/contacts"))
|
||||
.andExpect(status().isOk()).andDo(document("home"));
|
||||
}
|
||||
|
||||
|
||||
public void createContact(String email, String firstName, String lastName, String phone, String address, String bankDetails) throws Exception{
|
||||
String validTestContact = "{\n" +
|
||||
" \"email\": \""+email+"\",\n" +
|
||||
" \"firstName\": \""+firstName+"\",\n" +
|
||||
" \"lastName\": \""+lastName+"\",\n" +
|
||||
" \"phone\": \""+phone+"\",\n" +
|
||||
" \"address\": \""+address+"\",\n" +
|
||||
" \"bankDetails\": \""+bankDetails+"\",\n" +
|
||||
" \"groups\": null\n" +
|
||||
"}";
|
||||
//add user
|
||||
this.mockMvc
|
||||
.perform(
|
||||
post("/contacts")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(validTestContact))
|
||||
.andDo(document("home"))
|
||||
.andExpect(status().isCreated());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = {"CM_CONTACT_CREATE","CM_CONTACT_GETALL"})
|
||||
public void validContactCreateTest() throws Exception{
|
||||
|
||||
|
||||
//get old list of user
|
||||
MvcResult response =
|
||||
this.mockMvc
|
||||
.perform(get("/contacts"))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
JSONObject JSONResponse = new JSONObject(response.getResponse().getContentAsString());
|
||||
JSONArray contacts = JSONResponse.getJSONArray("content");
|
||||
int oldLength = contacts.length();
|
||||
|
||||
//create new User
|
||||
createContact("max@mustercontact.com","Max","Musterkontakt","017612345678","Musterkontaktstr. 123","Musterbank");
|
||||
|
||||
//get new list of user
|
||||
response =
|
||||
this.mockMvc
|
||||
.perform(get("/contacts"))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
JSONResponse = new JSONObject(response.getResponse().getContentAsString());
|
||||
contacts = JSONResponse.getJSONArray("content");
|
||||
int newLength = contacts.length();
|
||||
|
||||
//assert that there should be one more user now
|
||||
Assert.assertThat(newLength - oldLength, equalTo(1));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* calls API to get all user and returns one random user
|
||||
*
|
||||
* @return JSONObject of contact
|
||||
* @throws Exception when API response has unexpected behaviour
|
||||
*/
|
||||
public JSONObject getRandomContact() throws Exception {
|
||||
//get list of user
|
||||
MvcResult response =
|
||||
this.mockMvc
|
||||
.perform(get("/contacts"))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
JSONObject JSONResponse = new JSONObject(response.getResponse().getContentAsString());
|
||||
JSONArray users = JSONResponse.getJSONArray("content");
|
||||
|
||||
//pick random user
|
||||
Random rand = new Random();
|
||||
return users.getJSONObject(rand.nextInt(users.length() - 1));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Picks random contact and then does API request for that contact, compares values
|
||||
*
|
||||
* @throw Exception on failure
|
||||
*/
|
||||
/*@Test
|
||||
@WithMockUser(authorities = {"CM_CONTACT_GETCONTACTBYID","CM_CONTACT_GETALL"})
|
||||
public void validGetUserByIdTest() throws Exception {
|
||||
|
||||
JSONObject contact = getRandomContact();
|
||||
String id = contact.getString("id");
|
||||
|
||||
MvcResult response =
|
||||
this.mockMvc
|
||||
.perform(get("/contacts/" + id))
|
||||
.andDo(document("home"))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
JSONObject responseContact = new JSONObject(response.getResponse().getContentAsString());
|
||||
|
||||
|
||||
//checks every field that should be implemented according to yml
|
||||
String[] fields = {
|
||||
"id", "email", "firstName", "lastName", "phone", "address", "bankDetails"
|
||||
};
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
|
||||
Assert.assertThat(responseContact.get(fields[i]), equalTo(contact.get(fields[i])));
|
||||
}
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
package de.ul.swtp.modules.contactmanagement;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import javax.validation.ValidatorFactory;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class ContactTest {
|
||||
private static Validator validator;
|
||||
private Contact testContact = new Contact();
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp() {
|
||||
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
|
||||
validator = factory.getValidator();
|
||||
}
|
||||
|
||||
// A valid test contact is created whose attributes are then changed in the tests to render it invalid
|
||||
@Before
|
||||
public void testSetUp(){
|
||||
testContact.setFirstName("validFirstName");
|
||||
testContact.setLastName("validLastName");
|
||||
testContact.setAddress("Teststr. 12 04123 Leipzig");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultsOK() {
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(0, constraintViolations.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void idNull() {
|
||||
testContact.setId(null);
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(0, constraintViolations.size());
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void idNotNull() {
|
||||
testContact.setId((long) 1234);
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must be null",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
//TODO write a more comprehensive set of test for email address verification
|
||||
|
||||
@Test
|
||||
public void validEmail1() {
|
||||
testContact.setEmail("testemailaddress@test.com");
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(0, constraintViolations.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validEmail2() {
|
||||
testContact.setEmail("testemail+address@test.com");
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(0, constraintViolations.size());
|
||||
}
|
||||
|
||||
//TODO Test blank and null email addresses
|
||||
@Test
|
||||
public void invalidEmail1() {
|
||||
testContact.setEmail("test@emailaddress@test.com");
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must be a well-formed email address",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidEmail2() {
|
||||
testContact.setEmail("waaaaaaaaaaaaaaaaaaaaaaaytooooooooooooooooooooooloooooooooooooooooooooong@gmail.com");
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must be a well-formed email address",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidEmail3() {
|
||||
testContact.setEmail("testemailaddresstest.com");
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must be a well-formed email address",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidEmail4() {
|
||||
testContact.setEmail("test\\emailaddress@test");
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must be a well-formed email address",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void invalidEmail5() {
|
||||
testContact.setEmail("testemailaddress@test");
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must be a well-formed email address",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void firstNameBlank() {
|
||||
testContact.setFirstName(" ");
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must not be blank",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void firstNameNull() {
|
||||
testContact.setFirstName(null);
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must not be blank",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lastNameBlank() {
|
||||
testContact.setLastName(" ");
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must not be blank",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lastNameNull() {
|
||||
testContact.setFirstName(null);
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must not be blank",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validPhoneNr1() {
|
||||
testContact.setPhone("123456789");
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(0, constraintViolations.size());
|
||||
}
|
||||
|
||||
// I wouldn't have expected this test to pass with the @Digit annotation
|
||||
@Test
|
||||
public void validPhoneNr2() {
|
||||
testContact.setPhone("+4912345678");
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(0, constraintViolations.size());
|
||||
}
|
||||
|
||||
// It would be good to accept phone numbers in common formats
|
||||
@Ignore
|
||||
@Test
|
||||
public void validPhoneNr3() {
|
||||
testContact.setPhone("12 345 6789");
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(0, constraintViolations.size());
|
||||
}
|
||||
|
||||
// It's important to note that at the moment phone numbers are optional, this may not be desirable.
|
||||
@Test
|
||||
public void nullPhoneValid() {
|
||||
testContact.setPhone(null);
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(0, constraintViolations.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidPhoneNr1() {
|
||||
// too long
|
||||
testContact.setPhone("041234567890123456789");
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"numeric value out of bounds (<16 digits>.<0 digits> expected)",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidPhoneNr2() {
|
||||
testContact.setPhone("zerofourone");
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"numeric value out of bounds (<16 digits>.<0 digits> expected)",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
// That this test fails demonstrates that a better solution than @Digits is needed
|
||||
@Ignore
|
||||
@Test
|
||||
public void invalidPhoneNr3() {
|
||||
testContact.setPhone("-123456789");
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"numeric value out of bounds (<16 digits>.<0 digits> expected)",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidPhoneNr4() {
|
||||
testContact.setPhone("++-123456");
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"numeric value out of bounds (<16 digits>.<0 digits> expected)",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void blankPhoneInvalid() {
|
||||
testContact.setPhone("");
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"numeric value out of bounds (<16 digits>.<0 digits> expected)",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addressNull() {
|
||||
testContact.setAddress(null);
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must not be blank",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addressBlank() {
|
||||
testContact.setAddress(" ");
|
||||
Set<ConstraintViolation<Contact>> constraintViolations = validator.validate(testContact);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must not be blank",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package de.ul.swtp.modules.contactmanagement;
|
||||
|
||||
import de.ul.swtp.modules.contactmanagement.Group;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import javax.validation.ValidatorFactory;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class GroupTest {
|
||||
private static Validator validator;
|
||||
private Group testGroup = new Group();
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp() {
|
||||
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
|
||||
validator = factory.getValidator();
|
||||
}
|
||||
|
||||
// A valid test group is created whose attributes are then changed in the tests to render it invalid
|
||||
@Before
|
||||
public void testSetUp(){
|
||||
testGroup.setName("validGroupName");
|
||||
testGroup.setPermissionEnum(PermissionEnum.DELETE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultsOK() {
|
||||
Set<ConstraintViolation<Group>> constraintViolations = validator.validate(testGroup);
|
||||
assertEquals(0, constraintViolations.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nameNull() {
|
||||
testGroup.setName(null);
|
||||
Set<ConstraintViolation<Group>> constraintViolations = validator.validate(testGroup);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must not be blank",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nameBlank() {
|
||||
testGroup.setName(" ");
|
||||
Set<ConstraintViolation<Group>> constraintViolations = validator.validate(testGroup);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must not be blank",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void permissionsEnumNull() {
|
||||
testGroup.setPermissionEnum(null);
|
||||
Set<ConstraintViolation<Group>> constraintViolations = validator.validate(testGroup);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must not be null",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
130
src/test/java/de/ul/swtp/security/JwtTokenUtilTest.java
Normal file
130
src/test/java/de/ul/swtp/security/JwtTokenUtilTest.java
Normal file
@@ -0,0 +1,130 @@
|
||||
package de.ul.swtp.security;
|
||||
|
||||
import io.jsonwebtoken.Clock;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import org.assertj.core.util.DateUtil;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.within;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class JwtTokenUtilTest {
|
||||
|
||||
private static final String TEST_USERNAME = "testUser";
|
||||
|
||||
@Mock
|
||||
private Clock clockMock;
|
||||
|
||||
@InjectMocks
|
||||
private JwtTokenUtil jwtTokenUtil;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
ReflectionTestUtils.setField(jwtTokenUtil, "expiration", 3600L); // one hour
|
||||
ReflectionTestUtils.setField(jwtTokenUtil, "secret", "mySecret");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateTokenGeneratesDifferentTokensForDifferentCreationDates() throws Exception {
|
||||
when(clockMock.now())
|
||||
.thenReturn(DateUtil.yesterday())
|
||||
.thenReturn(DateUtil.now());
|
||||
|
||||
final String token = createToken();
|
||||
final String laterToken = createToken();
|
||||
|
||||
assertThat(token).isNotEqualTo(laterToken);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUsernameFromToken() throws Exception {
|
||||
when(clockMock.now()).thenReturn(DateUtil.now());
|
||||
|
||||
final String token = createToken();
|
||||
|
||||
assertThat(jwtTokenUtil.getUsernameFromToken(token)).isEqualTo(TEST_USERNAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCreatedDateFromToken() throws Exception {
|
||||
final Date now = DateUtil.now();
|
||||
when(clockMock.now()).thenReturn(now);
|
||||
|
||||
final String token = createToken();
|
||||
|
||||
assertThat(jwtTokenUtil.getIssuedAtDateFromToken(token)).isInSameMinuteWindowAs(now);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getExpirationDateFromToken() throws Exception {
|
||||
final Date now = DateUtil.now();
|
||||
when(clockMock.now()).thenReturn(now);
|
||||
final String token = createToken();
|
||||
|
||||
final Date expirationDateFromToken = jwtTokenUtil.getExpirationDateFromToken(token);
|
||||
assertThat(DateUtil.timeDifference(expirationDateFromToken, now)).isCloseTo(3600000L, within(1000L));
|
||||
}
|
||||
|
||||
@Test(expected = ExpiredJwtException.class)
|
||||
public void expiredTokenCannotBeRefreshed() throws Exception {
|
||||
when(clockMock.now())
|
||||
.thenReturn(DateUtil.yesterday());
|
||||
String token = createToken();
|
||||
jwtTokenUtil.canTokenBeRefreshed(token, DateUtil.tomorrow());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void changedPasswordCannotBeRefreshed() throws Exception {
|
||||
when(clockMock.now())
|
||||
.thenReturn(DateUtil.now());
|
||||
String token = createToken();
|
||||
assertThat(jwtTokenUtil.canTokenBeRefreshed(token, DateUtil.tomorrow())).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notExpiredCanBeRefreshed() {
|
||||
when(clockMock.now())
|
||||
.thenReturn(DateUtil.now());
|
||||
String token = createToken();
|
||||
assertThat(jwtTokenUtil.canTokenBeRefreshed(token, DateUtil.yesterday())).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canRefreshToken() throws Exception {
|
||||
when(clockMock.now())
|
||||
.thenReturn(DateUtil.now())
|
||||
.thenReturn(DateUtil.tomorrow());
|
||||
String firstToken = createToken();
|
||||
String refreshedToken = jwtTokenUtil.refreshToken(firstToken);
|
||||
Date firstTokenDate = jwtTokenUtil.getIssuedAtDateFromToken(firstToken);
|
||||
Date refreshedTokenDate = jwtTokenUtil.getIssuedAtDateFromToken(refreshedToken);
|
||||
assertThat(firstTokenDate).isBefore(refreshedTokenDate);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canValidateToken() throws Exception {
|
||||
when(clockMock.now())
|
||||
.thenReturn(DateUtil.now());
|
||||
UserDetails userDetails = mock(JwtUser.class);
|
||||
when(userDetails.getUsername()).thenReturn(TEST_USERNAME);
|
||||
|
||||
String token = createToken();
|
||||
assertThat(jwtTokenUtil.validateToken(token, userDetails)).isTrue();
|
||||
}
|
||||
|
||||
private String createToken() {
|
||||
return jwtTokenUtil.generateToken(new UserDetailsDummy(TEST_USERNAME));
|
||||
}
|
||||
}
|
||||
53
src/test/java/de/ul/swtp/security/UserDetailsDummy.java
Normal file
53
src/test/java/de/ul/swtp/security/UserDetailsDummy.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package de.ul.swtp.security;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Created by stephan on 04.07.17.
|
||||
*/
|
||||
public class UserDetailsDummy implements UserDetails {
|
||||
|
||||
private final String username;
|
||||
|
||||
public UserDetailsDummy(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
package de.ul.swtp.security.controller;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@AutoConfigureRestDocs(outputDir = "target/snippets")
|
||||
public class UsersRestControllerTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
/*@MockBean
|
||||
private JwtTokenUtil jwtTokenUtil;
|
||||
|
||||
@MockBean
|
||||
private JwtUserDetailsService jwtUserDetailsService;
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void shouldGetUnauthorizedWithoutAuthority() throws Exception {
|
||||
|
||||
this.mockMvc.perform(get("/users"))
|
||||
.andExpect(status().isUnauthorized()).andDo(document("home"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(authorities = "SYS_USER_GETALL")
|
||||
public void getPersonsSuccessfullyWithAuthority() throws Exception {
|
||||
|
||||
/*User user = new User();
|
||||
user.setUsername("username");
|
||||
user.setEnabled(Boolean.TRUE);
|
||||
user.setIsAdmin(Boolean.FALSE);
|
||||
user.setLastPasswordResetDate(new Date(System.currentTimeMillis() + 1000 * 1000));
|
||||
|
||||
JwtUser jwtUser = JwtUserFactory.create(user);
|
||||
|
||||
when(jwtTokenUtil.getUsernameFromToken(any())).thenReturn(user.getUsername());
|
||||
|
||||
when(jwtUserDetailsService.loadUserByUsername(eq(user.getUsername()))).thenReturn(jwtUser);
|
||||
|
||||
mockMvc.perform(get("/users").header("Authorization", "Bearer nsodunsodiuv"))
|
||||
.andExpect(status().is2xxSuccessful());*/
|
||||
|
||||
mockMvc.perform(get("/users")).andExpect(status().isOk()).andDo(document("home"));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* issue # 180
|
||||
* when posting user with password but without the admin attribute, the integrity of the db is lost und the /users endpoint gives a permanent 500 error
|
||||
* this is the test that should pass after fixing this issue
|
||||
* @throws Exception if API does not give an error for the second call
|
||||
*/
|
||||
@Test
|
||||
@Ignore
|
||||
@WithMockUser(authorities = {"SYS_USER_GETALL","SYS_USER_CREATE"})
|
||||
public void invalidJSONCreatesPermanent500Test() throws Exception{
|
||||
String invalidTestUser =
|
||||
"{\n" +
|
||||
"\t\t\t\"username\": \"maria@swt.de\",\n" +
|
||||
"\t\t\t\"password\": \"password\",\n" +
|
||||
" \"enabled\": true\n" +
|
||||
"}";
|
||||
|
||||
MvcResult result = this.mockMvc
|
||||
.perform(post("/users")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(invalidTestUser))
|
||||
.andReturn();
|
||||
|
||||
|
||||
Assert.assertNotEquals(result.getResponse().getStatus(),500);
|
||||
|
||||
result = this.mockMvc
|
||||
.perform(get("/users"))
|
||||
.andReturn();
|
||||
|
||||
Assert.assertNotEquals(result.getResponse().getStatus(),500);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* tries to create a user twice
|
||||
* @throws Exception if API does not give an error for the second call
|
||||
*/
|
||||
@Test
|
||||
@WithMockUser(authorities = "SYS_USER_CREATE")
|
||||
public void invalidDuplicateUserCreateTest() throws Exception{
|
||||
String validTestUser = getValidTestUserJSON("paul");
|
||||
|
||||
//add one user
|
||||
this.mockMvc
|
||||
.perform(post("/users")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(validTestUser))
|
||||
.andDo(document("home"))
|
||||
.andExpect(status().isCreated());
|
||||
|
||||
//add second user with same information supplied
|
||||
this.mockMvc
|
||||
.perform(post("/users")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(validTestUser))
|
||||
.andDo(document("home"))
|
||||
.andExpect(status().is4xxClientError());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* calls API to get all user and returns one random user
|
||||
*
|
||||
* @return JSONObject of user
|
||||
* @throws Exception when API response has unexpected behaviour
|
||||
*/
|
||||
public JSONObject getRandomUser() throws Exception {
|
||||
//get list of user
|
||||
MvcResult response =
|
||||
this.mockMvc
|
||||
.perform(get("/users"))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
JSONObject JSONResponse = new JSONObject(response.getResponse().getContentAsString());
|
||||
JSONArray users = JSONResponse.getJSONArray("content");
|
||||
|
||||
//pick random user
|
||||
Random rand = new Random();
|
||||
return users.getJSONObject(rand.nextInt(users.length() - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public String getValidTestUserJSON(String name){
|
||||
return "{\n" +
|
||||
"\t\t\t\"username\": \""+ name +"@swt.de\",\n" +
|
||||
"\t\t\t\"password\": \"password\",\n" +
|
||||
" \"enabled\": true,\n" +
|
||||
" \"admin\": false\n" +
|
||||
"}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Picks random user and deletes it, checks whether user request afterwards gives 404
|
||||
*
|
||||
* @throws Exception on failure
|
||||
*/
|
||||
@Test
|
||||
@WithMockUser(authorities = {"SYS_USER_GETALL","SYS_USER_DELETE","SYS_USER_GETUSERBYID"})
|
||||
public void validUserDeleteTest() throws Exception {
|
||||
JSONObject user = getRandomUser();
|
||||
String id = user.getString("id");
|
||||
this.mockMvc
|
||||
.perform(
|
||||
delete("/users/" + id)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(document("home"))
|
||||
.andExpect(status().isOk());
|
||||
this.mockMvc
|
||||
.perform(
|
||||
get("/users/" + id)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andDo(document("home"))
|
||||
.andExpect(status().is(404));
|
||||
}
|
||||
|
||||
/**
|
||||
* tests whether single user can be created and whether number of user has increased by one as a
|
||||
* result
|
||||
*
|
||||
* @throws Exception on failure
|
||||
*/
|
||||
@Test
|
||||
@WithMockUser(authorities = {"SYS_USER_CREATE","SYS_USER_GETALL"})
|
||||
public void validUserCreateTest() throws Exception {
|
||||
|
||||
String validTestUser = getValidTestUserJSON("anna");
|
||||
//get old list of user
|
||||
MvcResult response =
|
||||
this.mockMvc
|
||||
.perform(get("/users"))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
JSONObject JSONResponse = new JSONObject(response.getResponse().getContentAsString());
|
||||
int oldLength = JSONResponse.getInt("totalElements");
|
||||
|
||||
//add user
|
||||
this.mockMvc
|
||||
.perform(
|
||||
post("/users")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(validTestUser))
|
||||
.andDo(document("home"))
|
||||
.andExpect(status().isCreated());
|
||||
|
||||
//get new list of user
|
||||
response =
|
||||
this.mockMvc
|
||||
.perform(get("/users"))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
JSONResponse = new JSONObject(response.getResponse().getContentAsString());
|
||||
int newLength = JSONResponse.getInt("totalElements");
|
||||
|
||||
//assert that there should be one more user now
|
||||
Assert.assertThat(newLength - oldLength, equalTo(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Picks random user and then does API request for that user, compares values
|
||||
*
|
||||
* @throw Exception on failure
|
||||
*/
|
||||
@Test
|
||||
@WithMockUser(authorities = {"SYS_USER_GETUSERBYID","SYS_USER_GETALL"})
|
||||
public void validGetUserByIdTest() throws Exception {
|
||||
|
||||
JSONObject user = getRandomUser();
|
||||
String id = user.getString("id");
|
||||
|
||||
MvcResult response =
|
||||
this.mockMvc
|
||||
.perform(get("/users/" + id))
|
||||
.andDo(document("home"))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
JSONObject responseUser = new JSONObject(response.getResponse().getContentAsString());
|
||||
|
||||
|
||||
//checks every field that should be implemented according to yml
|
||||
String[] fields = {
|
||||
"id", "username", "enabled", "admin", /*"groups", "authorities"*/
|
||||
};
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
|
||||
Assert.assertThat(responseUser.get(fields[i]), equalTo(user.get(fields[i])));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Picks random user and updates fields randomly within their definitions and then requests user to see whether fields are as expected
|
||||
* @throws Exception on failure
|
||||
*/
|
||||
@Test
|
||||
@WithMockUser(authorities = {"SYS_USER_UPDATE","SYS_USER_GETALL", "SYS_USER_GETUSERBYID"})
|
||||
public void validUserUpdateTest() throws Exception{
|
||||
|
||||
JSONObject user = getRandomUser();
|
||||
String id = user.getString("id");
|
||||
|
||||
Random rand = new Random();
|
||||
|
||||
String[] fields = {"username", "enabled", "admin", "password"};
|
||||
String[] values = {"Arya+"+(new Integer(rand.nextInt(10000))).toString()+"@winterfell.com","true","true","2018-04-13T22:00:00.000+0000","LannisterSuck"};
|
||||
|
||||
for(int i = 0; i < fields.length; i++){
|
||||
if(user.has(fields[i])){
|
||||
user.put(fields[i],values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
user.putOpt("roles",new JSONArray());
|
||||
user.putOpt("groups",new JSONArray());
|
||||
|
||||
|
||||
System.out.println("Attempt to update user "+id);
|
||||
System.out.println(user.toString());
|
||||
|
||||
mockMvc.perform(
|
||||
put("/users/" + id)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(user.toString()))
|
||||
.andDo(document("home"))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
|
||||
MvcResult response =
|
||||
this.mockMvc
|
||||
.perform(get("/users/"+id))
|
||||
.andDo(document("home"))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
|
||||
JSONObject responseUser = new JSONObject(response.getResponse().getContentAsString());
|
||||
|
||||
//checks every field that should be implemented according to yml
|
||||
for(int i = 0; i < fields.length; i++) {
|
||||
if(fields[i] != "password")
|
||||
Assert.assertThat(responseUser.get(fields[i]).toString(), equalTo(user.get(fields[i])));
|
||||
}
|
||||
|
||||
System.out.println(response.getResponse().getContentAsString());
|
||||
}
|
||||
}
|
||||
81
src/test/java/de/ul/swtp/system/RoleTest.java
Normal file
81
src/test/java/de/ul/swtp/system/RoleTest.java
Normal file
@@ -0,0 +1,81 @@
|
||||
package de.ul.swtp.system;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import javax.validation.ValidatorFactory;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class RoleTest {
|
||||
|
||||
private static Validator validator;
|
||||
private Role testRole = new Role();
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp() {
|
||||
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
|
||||
validator = factory.getValidator();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void testSetUp(){
|
||||
testRole.setName("validName");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void idNull() {
|
||||
testRole.setId(null);
|
||||
Set<ConstraintViolation<Role>> constraintViolations = validator.validate(testRole);
|
||||
assertEquals(0, constraintViolations.size());
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void idNotNull() {
|
||||
testRole.setId((long) 1234);
|
||||
Set<ConstraintViolation<Role>> constraintViolations = validator.validate(testRole);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must be null",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nameOK(){
|
||||
testRole.setName(" testName");
|
||||
Set<ConstraintViolation<Role>> constraintViolations = validator.validate(testRole);
|
||||
assertEquals(0, constraintViolations.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nameNull(){
|
||||
testRole.setName(null);
|
||||
Set<ConstraintViolation<Role>> constraintViolations = validator.validate(testRole);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must not be blank",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nameBlank(){
|
||||
testRole.setName(" ");
|
||||
Set<ConstraintViolation<Role>> constraintViolations = validator.validate(testRole);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must not be blank",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
233
src/test/java/de/ul/swtp/system/UserTest.java
Normal file
233
src/test/java/de/ul/swtp/system/UserTest.java
Normal file
@@ -0,0 +1,233 @@
|
||||
package de.ul.swtp.system;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.ValidatorFactory;
|
||||
import javax.validation.Validator;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class UserTest {
|
||||
|
||||
private static Validator validator;
|
||||
private User testUser = new User();
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp() {
|
||||
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
|
||||
validator = factory.getValidator();
|
||||
}
|
||||
|
||||
// A valid test user is created whose attributes are then changed in the tests to render it invalid
|
||||
@Before
|
||||
public void testSetUp(){
|
||||
testUser.setUsername("validUsername@test.de");
|
||||
testUser.setPassword("validPassword");
|
||||
testUser.setEnabled(true);
|
||||
testUser.setAdmin(false);
|
||||
}
|
||||
|
||||
//Validation tests
|
||||
//TODO check the constraintViolation messages to ensure the expected validations are failing
|
||||
//TODO work out how to check multiple messages
|
||||
// check multiple messages https://stackoverflow.com/questions/41941404/test-multiple-constraint-violation-return-messages
|
||||
|
||||
@Test
|
||||
public void defaultsOK() {
|
||||
Set<ConstraintViolation<User>> constraintViolations = validator.validate(testUser);
|
||||
assertEquals(0, constraintViolations.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void idNull() {
|
||||
testUser.setId(null);
|
||||
Set<ConstraintViolation<User>> constraintViolations = validator.validate(testUser);
|
||||
assertEquals(0, constraintViolations.size());
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void idNotNull() {
|
||||
testUser.setId((long) 1234);
|
||||
Set<ConstraintViolation<User>> constraintViolations = validator.validate(testUser);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must be null",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void usernameBlank1() {
|
||||
testUser.setUsername("");
|
||||
Set<ConstraintViolation<User>> constraintViolations = validator.validate(testUser);
|
||||
assertEquals(2, constraintViolations.size());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void usernameBlank2() {
|
||||
testUser.setUsername(" ");
|
||||
Set<ConstraintViolation<User>> constraintViolations = validator.validate(testUser);
|
||||
assertEquals(2, constraintViolations.size());
|
||||
/*
|
||||
assertEquals(
|
||||
"must not be blank",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
assertEquals(
|
||||
"must be a well-formed email address",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void usernameNull() {
|
||||
testUser.setUsername(null);
|
||||
Set<ConstraintViolation<User>> constraintViolations = validator.validate(testUser);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validUsername1() {
|
||||
testUser.setUsername("testemailaddress@test.com");
|
||||
Set<ConstraintViolation<User>> constraintViolations = validator.validate(testUser);
|
||||
assertEquals(0, constraintViolations.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validUsername2() {
|
||||
testUser.setUsername("testemail+address@test.com");
|
||||
Set<ConstraintViolation<User>> constraintViolations = validator.validate(testUser);
|
||||
assertEquals(0, constraintViolations.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidUsername1() {
|
||||
testUser.setUsername("test@emailaddress@test.com");
|
||||
Set<ConstraintViolation<User>> constraintViolations = validator.validate(testUser);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must be a well-formed email address",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidUsername2() {
|
||||
testUser.setUsername("testemailaddresstest.com");
|
||||
Set<ConstraintViolation<User>> constraintViolations = validator.validate(testUser);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must be a well-formed email address",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidUsername3() {
|
||||
testUser.setUsername("testemail@@addresstest.com");
|
||||
Set<ConstraintViolation<User>> constraintViolations = validator.validate(testUser);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must be a well-formed email address",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void usernameTooLongForAnEmailAddress() {
|
||||
testUser.setUsername("waaaaaaaaaaaaaaaaaaaaaaaytooooooooooooooooooooooloooooooooooooooooooooong@gmail.com");
|
||||
Set<ConstraintViolation<User>> constraintViolations = validator.validate(testUser);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must be a well-formed email address",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void usernameHasNoTopLevelDomain() {
|
||||
testUser.setUsername("testemailaddress@test");
|
||||
Set<ConstraintViolation<User>> constraintViolations = validator.validate(testUser);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must be a well-formed email address",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void passwordTooShort() {
|
||||
testUser.setPassword("aa");
|
||||
Set<ConstraintViolation<User>> constraintViolations = validator.validate(testUser);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void passwordTooShortAndEmpty() {
|
||||
testUser.setPassword("");
|
||||
Set<ConstraintViolation<User>> constraintViolations = validator.validate(testUser);
|
||||
assertEquals(2, constraintViolations.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void passwordNull() {
|
||||
testUser.setPassword(null);
|
||||
Set<ConstraintViolation<User>> constraintViolations = validator.validate(testUser);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enabledNull() {
|
||||
testUser.setEnabled(null);
|
||||
Set<ConstraintViolation<User>> constraintViolations = validator.validate(testUser);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enabledFalse() {
|
||||
testUser.setEnabled(false);
|
||||
Set<ConstraintViolation<User>> constraintViolations = validator.validate(testUser);
|
||||
assertEquals(0, constraintViolations.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adminNull() {
|
||||
testUser.setAdmin(null);
|
||||
Set<ConstraintViolation<User>> constraintViolations = validator.validate(testUser);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adminTrue() {
|
||||
testUser.setAdmin(true);
|
||||
Set<ConstraintViolation<User>> constraintViolations = validator.validate(testUser);
|
||||
assertEquals(0, constraintViolations.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lastPasswordResetDateInTheFuture() {
|
||||
Calendar c = Calendar.getInstance();
|
||||
// add one day to the current date
|
||||
c.add(Calendar.DATE, 1);
|
||||
Date tomorrow = c.getTime();
|
||||
testUser.setLastPasswordResetDate(tomorrow);
|
||||
Set<ConstraintViolation<User>> constraintViolations = validator.validate(testUser);
|
||||
assertEquals(1, constraintViolations.size());
|
||||
assertEquals(
|
||||
"must be a past date",
|
||||
constraintViolations.iterator().next().getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
27
src/test/resources/application.properties
Normal file
27
src/test/resources/application.properties
Normal file
@@ -0,0 +1,27 @@
|
||||
spring.jackson.serialization.INDENT_OUTPUT=true
|
||||
|
||||
spring.h2.console.enabled=true
|
||||
|
||||
spring.datasource.platform=h2
|
||||
# Creates a file based database in the current users home directory called 'test'
|
||||
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
|
||||
spring.datasource.driverClassName=org.h2.Driver
|
||||
spring.datasource.username=sa
|
||||
spring.datasource.password=
|
||||
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
|
||||
|
||||
mv.url.api=https://api.swt.leoek.eu
|
||||
mv.url.client=https://swt.leoek.eu
|
||||
mv.url.login=/login
|
||||
mv.url.signup=/signup
|
||||
mv.url.refresh-jwt=/refresh
|
||||
|
||||
jwt.header=Authorization
|
||||
jwt.secret=mySecret
|
||||
jwt.expiration=604800
|
||||
|
||||
mv.email.from=mv@felixfoertsch.de
|
||||
mv.email.subject-prefix="[MV] "
|
||||
|
||||
mv.signup.expiration=604800
|
||||
mv.signup.email-subject:Aktivieren Sie Ihren Account
|
||||
11
target/classes/application-dev-h2.properties
Normal file
11
target/classes/application-dev-h2.properties
Normal file
@@ -0,0 +1,11 @@
|
||||
spring.datasource.platform=h2
|
||||
# Creates a file based database in the current users home directory called 'test'
|
||||
spring.datasource.url=jdbc:h2:~/test
|
||||
spring.datasource.username=sa
|
||||
spring.datasource.password=
|
||||
spring.datasource.driver-class-name=org.h2.Driver
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
|
||||
|
||||
spring.h2.console.path=/h2
|
||||
# Removes warning from stdout
|
||||
spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false
|
||||
11
target/classes/application-dev-mariadb.properties
Normal file
11
target/classes/application-dev-mariadb.properties
Normal file
@@ -0,0 +1,11 @@
|
||||
spring.jpa.hibernate.ddl-auto=validate
|
||||
|
||||
spring.datasource.platform=mariadb
|
||||
spring.datasource.url=jdbc:mariadb://localhost:3307/mariadb
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=mariadb
|
||||
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
|
||||
|
||||
# Removes warning from stdout
|
||||
spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false
|
||||
11
target/classes/application-prod.properties
Normal file
11
target/classes/application-prod.properties
Normal file
@@ -0,0 +1,11 @@
|
||||
spring.jpa.hibernate.ddl-auto=validate
|
||||
|
||||
spring.datasource.platform=mariadb
|
||||
spring.datasource.url=jdbc:mariadb://db:3306/mvdb
|
||||
spring.datasource.username=mvuser
|
||||
spring.datasource.password=mariadb
|
||||
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
|
||||
|
||||
# Removes warning from stdout
|
||||
spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user