add server in version 0.5.0 as initial commit for future work on github

This commit is contained in:
Felix Förtsch
2018-04-26 21:45:52 +02:00
parent a22c06a222
commit 7542d769b8
205 changed files with 9790 additions and 0 deletions

2
.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
mv2/build
mv2/.gradle

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/docker/files/app.jar

23
Dockerfile Normal file
View 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
View 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>

View 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

View File

@@ -0,0 +1,12 @@
___ ___ ___ ___ ___ ___ ___
/\ \ /\ \ /\__\ /\ \ |\__\ /\__\ /\__\
/::\ \ /::\ \ /::| | /::\ \ |:| | /::| | /:/ /
/:/\:\ \ /:/\:\ \ /:|:| | /:/\:\ \ |:| | /:|:| | /:/ /
/::\~\:\ \ /::\~\:\ \ /:/|:| |__ /:/ \:\ \ |:|__|__ /:/|:|__|__ /:/__/ ___
/:/\:\ \:\__\ /:/\:\ \:\__\ /:/ |:| /\__\ /:/__/ \:\__\ /::::\__\ /:/ |::::\__\ |:| | /\__\
\/__\:\ \/__/ \/__\:\/:/ / \/__|:|/:/ / \:\ \ \/__/ /:/~~/~ \/__/~~/:/ / |:| |/:/ /
\:\__\ \::/ / |:/:/ / \:\ \ /:/ / /:/ / |:|__/:/ /
\/__/ /:/ / |::/ / \:\ \ \/__/ /:/ / \::::/__/
/:/ / /:/ / \:\__\ /:/ / ~~~~
\/__/ \/__/ \/__/ \/__/

View 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);

View 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>

View 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>

View 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>

View 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">&times;</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>

View 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();
}
});

View 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]);

View 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
View 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
View 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

View 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);
}
}

View File

@@ -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);
}*/
}

View File

@@ -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() + ")";
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}

View File

@@ -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() + ")";
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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) {
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View 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;
}

View File

@@ -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);
}
}

View File

@@ -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
}

View File

@@ -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() {
}
}

View File

@@ -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");
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View 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);
}
}

View 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;
}
}

View 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());
}
}

View 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;
}
}

View 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,7 @@
package de.ul.swtp.security.controller;
public class AuthenticationException extends RuntimeException {
public AuthenticationException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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!");
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View 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() + ")";
}
}

View 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);
}
}

View 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;
}
}

View 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);
}

View 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() + ")";
}
}

View 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);
}
}

View 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;
}
}

View 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);
}

View 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);
}
}
}

View 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);
}

View 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;
}
}

View 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);
}
}

View 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;
}
}

View 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);
}

View 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);
}
}

View 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);
}

View 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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View 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);
}
}

View 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

View 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

View 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

View 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

View File

@@ -0,0 +1,12 @@
___ ___ ___ ___ ___ ___ ___
/\ \ /\ \ /\__\ /\ \ |\__\ /\__\ /\__\
/::\ \ /::\ \ /::| | /::\ \ |:| | /::| | /:/ /
/:/\:\ \ /:/\:\ \ /:|:| | /:/\:\ \ |:| | /:|:| | /:/ /
/::\~\:\ \ /::\~\:\ \ /:/|:| |__ /:/ \:\ \ |:|__|__ /:/|:|__|__ /:/__/ ___
/:/\:\ \:\__\ /:/\:\ \:\__\ /:/ |:| /\__\ /:/__/ \:\__\ /::::\__\ /:/ |::::\__\ |:| | /\__\
\/__\:\ \/__/ \/__\:\/:/ / \/__|:|/:/ / \:\ \ \/__/ /:/~~/~ \/__/~~/:/ / |:| |/:/ /
\:\__\ \::/ / |:/:/ / \:\ \ /:/ / /:/ / |:|__/:/ /
\/__/ /:/ / |::/ / \:\ \ \/__/ /:/ / \::::/__/
/:/ / /:/ / \:\__\ /:/ / ~~~~
\/__/ \/__/ \/__/ \/__/

View 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');

View 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>

View 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>

View 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>

View 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">&times;</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>

View 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();
}
});

View 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]);

View 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);
}
}

View File

@@ -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])));
}
}*/
}

View File

@@ -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()
);
}
}

View File

@@ -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()
);
}
}

View 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));
}
}

View 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;
}
}

View File

@@ -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());
}
}

View 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()
);
}
}

View 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()
);
}
}

View 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

View 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

View 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

View 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