From 9216126e012f346138a58c6172ea92770493679b Mon Sep 17 00:00:00 2001 From: rylyade1 Date: Sun, 16 Oct 2022 23:42:42 +0200 Subject: [PATCH 1/2] Generated with JHipster 6.6.0 --- .editorconfig | 23 + .gitattributes | 148 + .gitignore | 146 + .jhipster/AccessToken.json | 57 + .jhipster/TicketSystemInstance.json | 57 + .mvn/wrapper/MavenWrapperDownloader.java | 117 + .mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes .mvn/wrapper/maven-wrapper.properties | 2 + .prettierignore | 4 + .prettierrc | 12 + .yo-rc.json | 39 + README.md | 107 + checkstyle.xml | 21 + mvnw | 310 ++ mvnw.cmd | 182 + package.json | 17 + pom.xml | 1030 +++++ sonar-project.properties | 25 + src/main/docker/app.yml | 28 + .../docker/central-server-config/README.md | 6 + .../central-server-config/application.yml | 9 + src/main/docker/config/git2consul.json | 18 + src/main/docker/consul.yml | 22 + .../grafana/provisioning/dashboards/JVM.json | 3778 +++++++++++++++++ .../provisioning/dashboards/dashboard.yml | 11 + .../provisioning/datasources/datasource.yml | 50 + src/main/docker/mariadb.yml | 13 + src/main/docker/monitoring.yml | 26 + src/main/docker/prometheus/prometheus.yml | 31 + src/main/docker/sonar.yml | 7 + .../casemanagement/ApplicationWebXml.java | 22 + .../casemanagement/CaseManagementApp.java | 107 + .../aop/logging/LoggingAspect.java | 99 + .../client/AuthorizedFeignClient.java | 55 + .../OAuth2InterceptedFeignConfiguration.java | 15 + .../client/TokenRelayRequestInterceptor.java | 26 + .../config/ApplicationProperties.java | 13 + .../config/AsyncConfiguration.java | 47 + .../config/CloudDatabaseConfiguration.java | 28 + .../casemanagement/config/Constants.java | 17 + .../config/DatabaseConfiguration.java | 59 + .../config/DateTimeFormatConfiguration.java | 20 + .../config/FeignConfiguration.java | 22 + .../config/JacksonConfiguration.java | 61 + .../config/LiquibaseConfiguration.java | 60 + .../config/LocaleConfiguration.java | 27 + .../config/LoggingAspectConfiguration.java | 19 + .../config/LoggingConfiguration.java | 59 + .../config/MethodSecurityConfiguration.java | 16 + .../config/SecurityConfiguration.java | 101 + .../casemanagement/config/WebConfigurer.java | 70 + .../config/audit/AuditEventConverter.java | 86 + .../config/audit/package-info.java | 4 + .../casemanagement/config/package-info.java | 4 + .../domain/AbstractAuditingEntity.java | 77 + .../casemanagement/domain/AccessToken.java | 159 + .../casemanagement/domain/Authority.java | 57 + .../domain/PersistentAuditEvent.java | 106 + .../domain/TicketSystemInstance.java | 190 + .../casemanagement/domain/User.java | 173 + .../domain/enumeration/TicketSystem.java | 8 + .../casemanagement/domain/package-info.java | 4 + .../repository/AccessTokenRepository.java | 19 + .../repository/AuthorityRepository.java | 11 + .../CustomAuditEventRepository.java | 89 + .../PersistenceAuditEventRepository.java | 23 + .../TicketSystemInstanceRepository.java | 15 + .../repository/UserRepository.java | 36 + .../repository/package-info.java | 4 + .../security/AuthoritiesConstants.java | 16 + .../security/SecurityUtils.java | 100 + .../security/SpringSecurityAuditorAware.java | 20 + .../security/oauth2/AudienceValidator.java | 33 + .../oauth2/AuthorizationHeaderUtil.java | 157 + .../oauth2/JwtAuthorityExtractor.java | 21 + .../oauth2/OAuthIdpTokenResponseDTO.java | 141 + .../casemanagement/security/package-info.java | 4 + .../service/AuditEventService.java | 74 + .../casemanagement/service/UserService.java | 252 ++ .../casemanagement/service/dto/UserDTO.java | 196 + .../service/dto/package-info.java | 4 + .../service/mapper/UserMapper.java | 81 + .../service/mapper/package-info.java | 4 + .../casemanagement/service/package-info.java | 4 + .../web/rest/AccessTokenResource.java | 143 + .../web/rest/AuditResource.java | 79 + .../rest/TicketSystemInstanceResource.java | 131 + .../casemanagement/web/rest/UserResource.java | 102 + .../rest/errors/BadRequestAlertException.java | 42 + .../web/rest/errors/ErrorConstants.java | 15 + .../web/rest/errors/ExceptionTranslator.java | 107 + .../web/rest/errors/FieldErrorVM.java | 33 + .../web/rest/errors/package-info.java | 6 + .../casemanagement/web/rest/package-info.java | 4 + .../web/rest/vm/ManagedUserVM.java | 18 + .../web/rest/vm/package-info.java | 4 + src/main/jib/entrypoint.sh | 4 + src/main/resources/.h2.server.properties | 5 + src/main/resources/banner.txt | 10 + src/main/resources/config/application-dev.yml | 131 + .../resources/config/application-prod.yml | 150 + src/main/resources/config/application-tls.yml | 19 + src/main/resources/config/application.yml | 195 + src/main/resources/config/bootstrap-prod.yml | 15 + src/main/resources/config/bootstrap.yml | 28 + .../00000000000000_initial_schema.xml | 126 + ...0201108111723_added_entity_AccessToken.xml | 69 + ...3_added_entity_constraints_AccessToken.xml | 24 + ...2112_added_entity_TicketSystemInstance.xml | 70 + .../liquibase/fake-data/access_token.csv | 11 + .../fake-data/ticket_system_instance.csv | 11 + .../resources/config/liquibase/master.xml | 22 + src/main/resources/i18n/messages.properties | 21 + src/main/resources/logback-spring.xml | 70 + src/main/resources/static/index.html | 103 + src/main/resources/templates/error.html | 87 + .../securityrat/casemanagement/ArchTest.java | 29 + .../config/TestSecurityConfiguration.java | 71 + .../config/WebConfigurerTest.java | 146 + .../config/WebConfigurerTestController.java | 16 + .../config/timezone/HibernateTimeZoneIT.java | 174 + .../domain/AccessTokenTest.java | 22 + .../domain/TicketSystemInstanceTest.java | 22 + .../CustomAuditEventRepositoryIT.java | 164 + .../repository/timezone/DateTimeWrapper.java | 131 + .../timezone/DateTimeWrapperRepository.java | 12 + .../security/SecurityUtilsUnitTest.java | 87 + .../oauth2/AudienceValidatorTest.java | 41 + .../casemanagement/service/UserServiceIT.java | 175 + .../service/mapper/UserMapperTest.java | 134 + .../web/rest/AccessTokenResourceIT.java | 313 ++ .../web/rest/AuditResourceIT.java | 165 + .../casemanagement/web/rest/TestUtil.java | 158 + .../rest/TicketSystemInstanceResourceIT.java | 325 ++ .../web/rest/UserResourceIT.java | 264 ++ .../rest/errors/ExceptionTranslatorIT.java | 126 + .../ExceptionTranslatorTestController.java | 71 + src/test/resources/config/application.yml | 115 + src/test/resources/config/bootstrap.yml | 9 + src/test/resources/logback.xml | 46 + 140 files changed, 14082 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .jhipster/AccessToken.json create mode 100644 .jhipster/TicketSystemInstance.json create mode 100644 .mvn/wrapper/MavenWrapperDownloader.java create mode 100644 .mvn/wrapper/maven-wrapper.jar create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 .yo-rc.json create mode 100644 README.md create mode 100644 checkstyle.xml create mode 100755 mvnw create mode 100644 mvnw.cmd create mode 100644 package.json create mode 100644 pom.xml create mode 100644 sonar-project.properties create mode 100644 src/main/docker/app.yml create mode 100644 src/main/docker/central-server-config/README.md create mode 100644 src/main/docker/central-server-config/application.yml create mode 100644 src/main/docker/config/git2consul.json create mode 100644 src/main/docker/consul.yml create mode 100644 src/main/docker/grafana/provisioning/dashboards/JVM.json create mode 100644 src/main/docker/grafana/provisioning/dashboards/dashboard.yml create mode 100644 src/main/docker/grafana/provisioning/datasources/datasource.yml create mode 100644 src/main/docker/mariadb.yml create mode 100644 src/main/docker/monitoring.yml create mode 100644 src/main/docker/prometheus/prometheus.yml create mode 100644 src/main/docker/sonar.yml create mode 100644 src/main/java/org/securityrat/casemanagement/ApplicationWebXml.java create mode 100644 src/main/java/org/securityrat/casemanagement/CaseManagementApp.java create mode 100644 src/main/java/org/securityrat/casemanagement/aop/logging/LoggingAspect.java create mode 100644 src/main/java/org/securityrat/casemanagement/client/AuthorizedFeignClient.java create mode 100644 src/main/java/org/securityrat/casemanagement/client/OAuth2InterceptedFeignConfiguration.java create mode 100644 src/main/java/org/securityrat/casemanagement/client/TokenRelayRequestInterceptor.java create mode 100644 src/main/java/org/securityrat/casemanagement/config/ApplicationProperties.java create mode 100644 src/main/java/org/securityrat/casemanagement/config/AsyncConfiguration.java create mode 100644 src/main/java/org/securityrat/casemanagement/config/CloudDatabaseConfiguration.java create mode 100644 src/main/java/org/securityrat/casemanagement/config/Constants.java create mode 100644 src/main/java/org/securityrat/casemanagement/config/DatabaseConfiguration.java create mode 100644 src/main/java/org/securityrat/casemanagement/config/DateTimeFormatConfiguration.java create mode 100644 src/main/java/org/securityrat/casemanagement/config/FeignConfiguration.java create mode 100644 src/main/java/org/securityrat/casemanagement/config/JacksonConfiguration.java create mode 100644 src/main/java/org/securityrat/casemanagement/config/LiquibaseConfiguration.java create mode 100644 src/main/java/org/securityrat/casemanagement/config/LocaleConfiguration.java create mode 100644 src/main/java/org/securityrat/casemanagement/config/LoggingAspectConfiguration.java create mode 100644 src/main/java/org/securityrat/casemanagement/config/LoggingConfiguration.java create mode 100644 src/main/java/org/securityrat/casemanagement/config/MethodSecurityConfiguration.java create mode 100644 src/main/java/org/securityrat/casemanagement/config/SecurityConfiguration.java create mode 100644 src/main/java/org/securityrat/casemanagement/config/WebConfigurer.java create mode 100644 src/main/java/org/securityrat/casemanagement/config/audit/AuditEventConverter.java create mode 100644 src/main/java/org/securityrat/casemanagement/config/audit/package-info.java create mode 100644 src/main/java/org/securityrat/casemanagement/config/package-info.java create mode 100644 src/main/java/org/securityrat/casemanagement/domain/AbstractAuditingEntity.java create mode 100644 src/main/java/org/securityrat/casemanagement/domain/AccessToken.java create mode 100644 src/main/java/org/securityrat/casemanagement/domain/Authority.java create mode 100644 src/main/java/org/securityrat/casemanagement/domain/PersistentAuditEvent.java create mode 100644 src/main/java/org/securityrat/casemanagement/domain/TicketSystemInstance.java create mode 100644 src/main/java/org/securityrat/casemanagement/domain/User.java create mode 100644 src/main/java/org/securityrat/casemanagement/domain/enumeration/TicketSystem.java create mode 100644 src/main/java/org/securityrat/casemanagement/domain/package-info.java create mode 100644 src/main/java/org/securityrat/casemanagement/repository/AccessTokenRepository.java create mode 100644 src/main/java/org/securityrat/casemanagement/repository/AuthorityRepository.java create mode 100644 src/main/java/org/securityrat/casemanagement/repository/CustomAuditEventRepository.java create mode 100644 src/main/java/org/securityrat/casemanagement/repository/PersistenceAuditEventRepository.java create mode 100644 src/main/java/org/securityrat/casemanagement/repository/TicketSystemInstanceRepository.java create mode 100644 src/main/java/org/securityrat/casemanagement/repository/UserRepository.java create mode 100644 src/main/java/org/securityrat/casemanagement/repository/package-info.java create mode 100644 src/main/java/org/securityrat/casemanagement/security/AuthoritiesConstants.java create mode 100644 src/main/java/org/securityrat/casemanagement/security/SecurityUtils.java create mode 100644 src/main/java/org/securityrat/casemanagement/security/SpringSecurityAuditorAware.java create mode 100644 src/main/java/org/securityrat/casemanagement/security/oauth2/AudienceValidator.java create mode 100644 src/main/java/org/securityrat/casemanagement/security/oauth2/AuthorizationHeaderUtil.java create mode 100644 src/main/java/org/securityrat/casemanagement/security/oauth2/JwtAuthorityExtractor.java create mode 100644 src/main/java/org/securityrat/casemanagement/security/oauth2/OAuthIdpTokenResponseDTO.java create mode 100644 src/main/java/org/securityrat/casemanagement/security/package-info.java create mode 100644 src/main/java/org/securityrat/casemanagement/service/AuditEventService.java create mode 100644 src/main/java/org/securityrat/casemanagement/service/UserService.java create mode 100644 src/main/java/org/securityrat/casemanagement/service/dto/UserDTO.java create mode 100644 src/main/java/org/securityrat/casemanagement/service/dto/package-info.java create mode 100644 src/main/java/org/securityrat/casemanagement/service/mapper/UserMapper.java create mode 100644 src/main/java/org/securityrat/casemanagement/service/mapper/package-info.java create mode 100644 src/main/java/org/securityrat/casemanagement/service/package-info.java create mode 100644 src/main/java/org/securityrat/casemanagement/web/rest/AccessTokenResource.java create mode 100644 src/main/java/org/securityrat/casemanagement/web/rest/AuditResource.java create mode 100644 src/main/java/org/securityrat/casemanagement/web/rest/TicketSystemInstanceResource.java create mode 100644 src/main/java/org/securityrat/casemanagement/web/rest/UserResource.java create mode 100644 src/main/java/org/securityrat/casemanagement/web/rest/errors/BadRequestAlertException.java create mode 100644 src/main/java/org/securityrat/casemanagement/web/rest/errors/ErrorConstants.java create mode 100644 src/main/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslator.java create mode 100644 src/main/java/org/securityrat/casemanagement/web/rest/errors/FieldErrorVM.java create mode 100644 src/main/java/org/securityrat/casemanagement/web/rest/errors/package-info.java create mode 100644 src/main/java/org/securityrat/casemanagement/web/rest/package-info.java create mode 100644 src/main/java/org/securityrat/casemanagement/web/rest/vm/ManagedUserVM.java create mode 100644 src/main/java/org/securityrat/casemanagement/web/rest/vm/package-info.java create mode 100644 src/main/jib/entrypoint.sh create mode 100644 src/main/resources/.h2.server.properties create mode 100644 src/main/resources/banner.txt create mode 100644 src/main/resources/config/application-dev.yml create mode 100644 src/main/resources/config/application-prod.yml create mode 100644 src/main/resources/config/application-tls.yml create mode 100644 src/main/resources/config/application.yml create mode 100644 src/main/resources/config/bootstrap-prod.yml create mode 100644 src/main/resources/config/bootstrap.yml create mode 100644 src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml create mode 100644 src/main/resources/config/liquibase/changelog/20201108111723_added_entity_AccessToken.xml create mode 100644 src/main/resources/config/liquibase/changelog/20201108111723_added_entity_constraints_AccessToken.xml create mode 100644 src/main/resources/config/liquibase/changelog/20201108112112_added_entity_TicketSystemInstance.xml create mode 100644 src/main/resources/config/liquibase/fake-data/access_token.csv create mode 100644 src/main/resources/config/liquibase/fake-data/ticket_system_instance.csv create mode 100644 src/main/resources/config/liquibase/master.xml create mode 100644 src/main/resources/i18n/messages.properties create mode 100644 src/main/resources/logback-spring.xml create mode 100644 src/main/resources/static/index.html create mode 100644 src/main/resources/templates/error.html create mode 100644 src/test/java/org/securityrat/casemanagement/ArchTest.java create mode 100644 src/test/java/org/securityrat/casemanagement/config/TestSecurityConfiguration.java create mode 100644 src/test/java/org/securityrat/casemanagement/config/WebConfigurerTest.java create mode 100644 src/test/java/org/securityrat/casemanagement/config/WebConfigurerTestController.java create mode 100644 src/test/java/org/securityrat/casemanagement/config/timezone/HibernateTimeZoneIT.java create mode 100644 src/test/java/org/securityrat/casemanagement/domain/AccessTokenTest.java create mode 100644 src/test/java/org/securityrat/casemanagement/domain/TicketSystemInstanceTest.java create mode 100644 src/test/java/org/securityrat/casemanagement/repository/CustomAuditEventRepositoryIT.java create mode 100644 src/test/java/org/securityrat/casemanagement/repository/timezone/DateTimeWrapper.java create mode 100644 src/test/java/org/securityrat/casemanagement/repository/timezone/DateTimeWrapperRepository.java create mode 100644 src/test/java/org/securityrat/casemanagement/security/SecurityUtilsUnitTest.java create mode 100644 src/test/java/org/securityrat/casemanagement/security/oauth2/AudienceValidatorTest.java create mode 100644 src/test/java/org/securityrat/casemanagement/service/UserServiceIT.java create mode 100644 src/test/java/org/securityrat/casemanagement/service/mapper/UserMapperTest.java create mode 100644 src/test/java/org/securityrat/casemanagement/web/rest/AccessTokenResourceIT.java create mode 100644 src/test/java/org/securityrat/casemanagement/web/rest/AuditResourceIT.java create mode 100644 src/test/java/org/securityrat/casemanagement/web/rest/TestUtil.java create mode 100644 src/test/java/org/securityrat/casemanagement/web/rest/TicketSystemInstanceResourceIT.java create mode 100644 src/test/java/org/securityrat/casemanagement/web/rest/UserResourceIT.java create mode 100644 src/test/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslatorIT.java create mode 100644 src/test/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslatorTestController.java create mode 100644 src/test/resources/config/application.yml create mode 100644 src/test/resources/config/bootstrap.yml create mode 100644 src/test/resources/logback.xml diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0439866 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] + +# We recommend you to keep these unchanged +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# Change these settings to your own preference +indent_style = space +indent_size = 4 + +[*.{ts,tsx,js,jsx,json,css,scss,yml}] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c013844 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,148 @@ +# This file is inspired by https://github.com/alexkaratarakis/gitattributes +# +# Auto detect text files and perform LF normalization +# http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/ +* text=auto + +# The above will handle all files NOT found below +# These files are text and should be normalized (Convert crlf => lf) + +*.bat text eol=crlf +*.coffee text +*.css text +*.cql text +*.df text +*.ejs text +*.html text +*.java text +*.js text +*.json text +*.less text +*.properties text +*.sass text +*.scss text +*.sh text eol=lf +*.sql text +*.txt text +*.ts text +*.xml text +*.yaml text +*.yml text + +# Documents +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain +*.markdown text +*.md text +*.adoc text +*.textile text +*.mustache text +*.csv text +*.tab text +*.tsv text +*.txt text +AUTHORS text +CHANGELOG text +CHANGES text +CONTRIBUTING text +COPYING text +copyright text +*COPYRIGHT* text +INSTALL text +license text +LICENSE text +NEWS text +readme text +*README* text +TODO text + +# Graphics +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.tif binary +*.tiff binary +*.ico binary +# SVG treated as an asset (binary) by default. If you want to treat it as text, +# comment-out the following line and uncomment the line after. +*.svg binary +#*.svg text +*.eps binary + +# These files are binary and should be left untouched +# (binary is a macro for -text -diff) +*.class binary +*.jar binary +*.war binary + +## LINTERS +.csslintrc text +.eslintrc text +.jscsrc text +.jshintrc text +.jshintignore text +.stylelintrc text + +## CONFIGS +*.conf text +*.config text +.editorconfig text +.gitattributes text +.gitconfig text +.gitignore text +.htaccess text +*.npmignore text + +## HEROKU +Procfile text +.slugignore text + +## AUDIO +*.kar binary +*.m4a binary +*.mid binary +*.midi binary +*.mp3 binary +*.ogg binary +*.ra binary + +## VIDEO +*.3gpp binary +*.3gp binary +*.as binary +*.asf binary +*.asx binary +*.fla binary +*.flv binary +*.m4v binary +*.mng binary +*.mov binary +*.mp4 binary +*.mpeg binary +*.mpg binary +*.swc binary +*.swf binary +*.webm binary + +## ARCHIVES +*.7z binary +*.gz binary +*.rar binary +*.tar binary +*.zip binary + +## FONTS +*.ttf binary +*.eot binary +*.otf binary +*.woff binary +*.woff2 binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..50d9fe1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,146 @@ +###################### +# Project Specific +###################### +/target/classes/static/** +/src/test/javascript/coverage/ + +###################### +# Node +###################### +/node/ +node_tmp/ +node_modules/ +npm-debug.log.* +/.awcache/* +/.cache-loader/* + +###################### +# SASS +###################### +.sass-cache/ + +###################### +# Eclipse +###################### +*.pydevproject +.project +.metadata +tmp/ +tmp/**/* +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath +.factorypath +/src/main/resources/rebel.xml + +# External tool builders +.externalToolBuilders/** + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + +# STS-specific +/.sts4-cache/* +###################### +# Intellij +###################### +.idea/ +*.iml +*.iws +*.ipr +*.ids +*.orig +classes/ +out/ + +###################### +# Visual Studio Code +###################### +.vscode/ + +###################### +# Maven +###################### +/log/ +/target/ + +###################### +# Gradle +###################### +.gradle/ +/build/ + +###################### +# Package Files +###################### +*.jar +*.war +*.ear +*.db + +###################### +# Windows +###################### +# Windows image file caches +Thumbs.db + +# Folder config file +Desktop.ini + +###################### +# Mac OSX +###################### +.DS_Store +.svn + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + +###################### +# Directories +###################### +/bin/ +/deploy/ + +###################### +# Logs +###################### +*.log* + +###################### +# Others +###################### +*.class +*.*~ +*~ +.merge_file* + +###################### +# Gradle Wrapper +###################### +!gradle/wrapper/gradle-wrapper.jar + +###################### +# Maven Wrapper +###################### +!.mvn/wrapper/maven-wrapper.jar + +###################### +# ESLint +###################### +.eslintcache diff --git a/.jhipster/AccessToken.json b/.jhipster/AccessToken.json new file mode 100644 index 0000000..d09b855 --- /dev/null +++ b/.jhipster/AccessToken.json @@ -0,0 +1,57 @@ +{ + "fluentMethods": true, + "clientRootFolder": "caseManagement", + "relationships": [ + { + "relationshipType": "many-to-one", + "otherEntityName": "user", + "otherEntityRelationshipName": "accessToken", + "relationshipName": "user", + "otherEntityField": "id" + }, + { + "relationshipType": "many-to-one", + "otherEntityName": "ticketSystemInstance", + "otherEntityRelationshipName": "accessToken", + "relationshipName": "ticketInstance", + "otherEntityField": "id" + } + ], + "fields": [ + { + "fieldName": "token", + "fieldType": "String", + "fieldValidateRules": [ + "required" + ] + }, + { + "fieldName": "expirationDate", + "fieldType": "ZonedDateTime" + }, + { + "fieldName": "salt", + "fieldType": "String", + "fieldValidateRules": [ + "required" + ] + }, + { + "fieldName": "refreshToken", + "fieldType": "String" + } + ], + "changelogDate": "20201108111723", + "dto": "no", + "searchEngine": false, + "service": "no", + "entityTableName": "access_token", + "databaseType": "sql", + "readOnly": false, + "jpaMetamodelFiltering": false, + "pagination": "infinite-scroll", + "microserviceName": "caseManagement", + "name": "AccessToken", + "applications": "*", + "skipClient": true +} diff --git a/.jhipster/TicketSystemInstance.json b/.jhipster/TicketSystemInstance.json new file mode 100644 index 0000000..def79fe --- /dev/null +++ b/.jhipster/TicketSystemInstance.json @@ -0,0 +1,57 @@ +{ + "name": "TicketSystemInstance", + "fields": [ + { + "fieldName": "name", + "fieldType": "String" + }, + { + "fieldName": "type", + "fieldType": "TicketSystem", + "fieldValues": "JIRA", + "fieldValidateRules": [ + "required" + ] + }, + { + "fieldName": "url", + "fieldType": "String", + "fieldValidateRules": [ + "required" + ] + }, + { + "fieldName": "consumerKey", + "fieldType": "String" + }, + { + "fieldName": "clientId", + "fieldType": "String" + }, + { + "fieldName": "clientSecret", + "fieldType": "String" + } + ], + "relationships": [ + { + "relationshipType": "one-to-many", + "otherEntityName": "accessToken", + "otherEntityRelationshipName": "ticketInstance", + "relationshipName": "accessToken" + } + ], + "changelogDate": "20201108112112", + "entityTableName": "ticket_system_instance", + "dto": "yes", + "pagination": "infinite-scroll", + "service": "no", + "jpaMetamodelFiltering": false, + "fluentMethods": true, + "readOnly": false, + "clientRootFolder": "caseManagement", + "applications": "*", + "microserviceName": "caseManagement", + "searchEngine": false, + "databaseType": "sql" +} diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..c32394f --- /dev/null +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,117 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.5"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..0d5e649888a4843c1520054d9672f80c62ebbb48 GIT binary patch literal 50710 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�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 JS$+5G{{hr`vzY(@ literal 0 HcmV?d00001 diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..7d59a01 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.2/apache-maven-3.6.2-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..91b1ba3 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +node_modules +target +package-lock.json +.git diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..b749286 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,12 @@ +# Prettier configuration + +printWidth: 140 +singleQuote: true +tabWidth: 2 +useTabs: false + +# js and ts rules: +arrowParens: avoid + +# jsx and tsx rules: +jsxBracketSameLine: false diff --git a/.yo-rc.json b/.yo-rc.json new file mode 100644 index 0000000..bcced94 --- /dev/null +++ b/.yo-rc.json @@ -0,0 +1,39 @@ +{ + "generator-jhipster": { + "promptValues": { + "packageName": "org.securityrat.casemanagement" + }, + "jhipsterVersion": "6.6.0", + "baseName": "caseManagement", + "packageName": "org.securityrat.casemanagement", + "packageFolder": "org/securityrat/casemanagement", + "serverPort": "8082", + "authenticationType": "oauth2", + "cacheProvider": "no", + "enableHibernateCache": false, + "websocket": false, + "databaseType": "sql", + "devDatabaseType": "h2Disk", + "prodDatabaseType": "mariadb", + "searchEngine": false, + "messageBroker": false, + "serviceDiscoveryType": "consul", + "buildTool": "maven", + "enableSocialSignIn": false, + "enableSwaggerCodegen": false, + "jwtSecretKey": "ebcc96a1801b3e946498deeae1c992287d454acc", + "enableTranslation": false, + "applicationType": "microservice", + "testFrameworks": [], + "jhiPrefix": "jhi", + "clientPackageManager": "yarn", + "skipClient": true, + "skipUserManagement": true, + "entitySuffix": "", + "dtoSuffix": "DTO", + "otherModules": [], + "blueprints": [], + "embeddableLaunchScript": false, + "creationTimestamp": 1577042681890 + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..1145d54 --- /dev/null +++ b/README.md @@ -0,0 +1,107 @@ +# caseManagement + +This application was generated using JHipster 6.6.0, you can find documentation and help at [https://www.jhipster.tech/documentation-archive/v6.6.0](https://www.jhipster.tech/documentation-archive/v6.6.0). + +This is a "microservice" application intended to be part of a microservice architecture, please refer to the [Doing microservices with JHipster][] page of the documentation for more information. + +This application is configured for Service Discovery and Configuration with Consul. On launch, it will refuse to start if it is not able to connect to Consul at [http://localhost:8500](http://localhost:8500). For more information, read our documentation on [Service Discovery and Configuration with Consul][]. + +## Development + +To start your application in the dev profile, run: + + ./mvnw + +For further instructions on how to develop with JHipster, have a look at [Using JHipster in development][]. + +## Building for production + +### Packaging as jar + +To build the final jar and optimize the caseManagement application for production, run: + + ./mvnw -Pprod clean verify + +To ensure everything worked, run: + + java -jar target/*.jar + +Refer to [Using JHipster in production][] for more details. + +### Packaging as war + +To package your application as a war in order to deploy it to an application server, run: + + ./mvnw -Pprod,war clean verify + +## Testing + +To launch your application's tests, run: + + ./mvnw verify + +For more information, refer to the [Running tests page][]. + +### Code quality + +Sonar is used to analyse code quality. You can start a local Sonar server (accessible on http://localhost:9001) with: + +``` +docker-compose -f src/main/docker/sonar.yml up -d +``` + +You can run a Sonar analysis with using the [sonar-scanner](https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner) or by using the maven plugin. + +Then, run a Sonar analysis: + +``` +./mvnw -Pprod clean verify sonar:sonar +``` + +If you need to re-run the Sonar phase, please be sure to specify at least the `initialize` phase since Sonar properties are loaded from the sonar-project.properties file. + +``` +./mvnw initialize sonar:sonar +``` + +or + +For more information, refer to the [Code quality page][]. + +## Using Docker to simplify development (optional) + +You can use Docker to improve your JHipster development experience. A number of docker-compose configuration are available in the [src/main/docker](src/main/docker) folder to launch required third party services. + +For example, to start a mariadb database in a docker container, run: + + docker-compose -f src/main/docker/mariadb.yml up -d + +To stop it and remove the container, run: + + docker-compose -f src/main/docker/mariadb.yml down + +You can also fully dockerize your application and all the services that it depends on. +To achieve this, first build a docker image of your app by running: + + ./mvnw -Pprod verify jib:dockerBuild + +Then run: + + docker-compose -f src/main/docker/app.yml up -d + +For more information refer to [Using Docker and Docker-Compose][], this page also contains information on the docker-compose sub-generator (`jhipster docker-compose`), which is able to generate docker configurations for one or several JHipster applications. + +## Continuous Integration (optional) + +To configure CI for your project, run the ci-cd sub-generator (`jhipster ci-cd`), this will let you generate configuration files for a number of Continuous Integration systems. Consult the [Setting up Continuous Integration][] page for more information. + +[jhipster homepage and latest documentation]: https://www.jhipster.tech +[jhipster 6.6.0 archive]: https://www.jhipster.tech/documentation-archive/v6.6.0 +[doing microservices with jhipster]: https://www.jhipster.tech/documentation-archive/v6.6.0/microservices-architecture/ +[using jhipster in development]: https://www.jhipster.tech/documentation-archive/v6.6.0/development/ +[service discovery and configuration with consul]: https://www.jhipster.tech/documentation-archive/v6.6.0/microservices-architecture/#consul +[using docker and docker-compose]: https://www.jhipster.tech/documentation-archive/v6.6.0/docker-compose +[using jhipster in production]: https://www.jhipster.tech/documentation-archive/v6.6.0/production/ +[running tests page]: https://www.jhipster.tech/documentation-archive/v6.6.0/running-tests/ +[code quality page]: https://www.jhipster.tech/documentation-archive/v6.6.0/code-quality/ +[setting up continuous integration]: https://www.jhipster.tech/documentation-archive/v6.6.0/setting-up-ci/ diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 0000000..10a479f --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..d2f0ea3 --- /dev/null +++ b/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..b26ab24 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/package.json b/package.json new file mode 100644 index 0000000..50e9c9d --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "case-management", + "version": "0.0.0", + "description": "Description for caseManagement", + "private": true, + "license": "UNLICENSED", + "cacheDirectories": [ + "node_modules" + ], + "devDependencies": { + "generator-jhipster": "6.6.0", + "@openapitools/openapi-generator-cli": "0.0.14-4.0.2" + }, + "engines": { + "node": ">=8.9.0" + } +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..9cdeca8 --- /dev/null +++ b/pom.xml @@ -0,0 +1,1030 @@ + + + 4.0.0 + + org.securityrat.casemanagement + case-management + 0.0.1-SNAPSHOT + jar + Case Management + + + + + + + + + + + + + + 3.3.9 + 1.8 + UTF-8 + UTF-8 + ${project.build.directory}/test-results + yyyyMMddHHmmss + ${java.version} + ${java.version} + -Djava.security.egd=file:/dev/./urandom -Xmx256m + jdt_apt + false + + + + + + + 3.1.0 + + 2.1.11.RELEASE + + 5.1.12.RELEASE + + 5.3.14.Final + + 3.23.2-GA + + 3.6.3 + 3.6 + + 5.3.8.Final + 1.4.200 + 2.0.1.Final + 2.3.2 + 0.12.0 + 1.3.1.Final + + 3.1.0 + 3.8.1 + 3.1.1 + 2.10 + 3.0.0-M3 + 3.0.0-M4 + 2.2.1 + 3.1.0 + 3.0.0-M4 + 3.2.3 + 3.1.0 + 8.27 + 0.0.4.RELEASE + 4.0.0 + 0.8.5 + 1.8.0 + 1.0.0 + 1.0.0 + 3.7.0.1746 + ${project.build.directory}/jacoco/test + ${jacoco.utReportFolder}/test.exec + ${project.build.directory}/jacoco/integrationTest + ${jacoco.itReportFolder}/integrationTest.exec + ${project.testresult.directory}/test + ${project.testresult.directory}/integrationTest + + + + + + + io.github.jhipster + jhipster-dependencies + ${jhipster-dependencies.version} + pom + import + + + + + + + + io.github.jhipster + jhipster-framework + + + + com.fasterxml.jackson.datatype + jackson-datatype-hibernate5 + + + com.fasterxml.jackson.datatype + jackson-datatype-hppc + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + com.fasterxml.jackson.module + jackson-module-afterburner + + + com.h2database + h2 + test + + + com.jayway.jsonpath + json-path + test + + + + io.springfox + springfox-swagger2 + + + io.springfox + springfox-bean-validators + + + com.zaxxer + HikariCP + + + commons-io + commons-io + + + org.apache.commons + commons-lang3 + + + org.mariadb.jdbc + mariadb-java-client + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.assertj + assertj-core + test + + + org.hibernate + hibernate-jpamodelgen + provided + + + org.hibernate + hibernate-core + + + org.hibernate.validator + hibernate-validator + + + org.liquibase + liquibase-core + + + net.logstash.logback + logstash-logback-encoder + + + org.mapstruct + mapstruct + + + org.mapstruct + mapstruct-processor + provided + + + org.springframework.boot + spring-boot-configuration-processor + provided + + + org.springframework.boot + spring-boot-loader-tools + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-aop + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-logging + + + org.springframework.boot + spring-boot-starter-mail + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + junit + junit + + + + + org.springframework.boot + spring-boot-test + test + + + org.springframework.security + spring-security-test + test + + + com.tngtech.archunit + archunit-junit5-api + ${archunit-junit5.version} + test + + + + + com.tngtech.archunit + archunit-junit5-engine + ${archunit-junit5.version} + test + + + org.zalando + problem-spring-web + + + + org.springframework.boot + spring-boot-starter-oauth2-client + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + + + org.springframework.security.oauth + spring-security-oauth2 + + + org.springframework.security + spring-security-jwt + + + + org.springframework.cloud + spring-cloud-starter + + + org.springframework.cloud + spring-cloud-starter-netflix-ribbon + + + org.springframework.cloud + spring-cloud-starter-netflix-hystrix + + + org.springframework.retry + spring-retry + + + org.springframework.cloud + spring-cloud-starter-consul-discovery + + + org.springframework.cloud + spring-cloud-starter-consul-config + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + org.springframework.boot + spring-boot-starter-cloud-connectors + + + org.springframework.security + spring-security-data + + + io.micrometer + micrometer-registry-prometheus + + + io.dropwizard.metrics + metrics-core + + + + + + spring-boot:run + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + org.springframework.boot + spring-boot-configuration-processor + ${spring-boot.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + org.hibernate + hibernate-jpamodelgen + ${hibernate.version} + + + org.glassfish.jaxb + jaxb-runtime + ${jaxb-runtime.version} + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${maven-checkstyle.version} + + + com.puppycrawl.tools + checkstyle + ${checkstyle.version} + + + io.spring.nohttp + nohttp-checkstyle + ${spring-nohttp-checkstyle.version} + + + + checkstyle.xml + pom.xml,README.md + .git/**/*,target/**/*,node_modules/**/*,node/**/* + ./ + + + + + check + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + ${maven.compiler.source} + + + + org.apache.maven.plugins + maven-eclipse-plugin + + + org.apache.maven.plugins + maven-enforcer-plugin + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.apache.maven.plugins + maven-idea-plugin + + + org.apache.maven.plugins + maven-resources-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.jacoco + jacoco-maven-plugin + + + org.sonarsource.scanner.maven + sonar-maven-plugin + + + org.liquibase + liquibase-maven-plugin + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + ${start-class} + true + + + + + com.google.cloud.tools + jib-maven-plugin + + + org.codehaus.mojo + properties-maven-plugin + ${properties-maven-plugin.version} + + + initialize + + read-project-properties + + + + sonar-project.properties + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + pl.project13.maven + git-commit-id-plugin + ${git-commit-id-plugin.version} + + + + revision + + + + + false + false + true + + ^git.commit.id.abbrev$ + ^git.commit.id.describe$ + ^git.branch$ + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco-maven-plugin.version} + + + pre-unit-tests + + prepare-agent + + + + ${jacoco.utReportFile} + + + + + post-unit-test + test + + report + + + ${jacoco.utReportFile} + ${jacoco.utReportFolder} + + + + pre-integration-tests + + prepare-agent-integration + + + + ${jacoco.itReportFile} + + + + + post-integration-tests + post-integration-test + + report-integration + + + ${jacoco.itReportFile} + ${jacoco.itReportFolder} + + + + + + com.google.cloud.tools + jib-maven-plugin + ${jib-maven-plugin.version} + + + adoptopenjdk:11-jre-hotspot + + + casemanagement:latest + + + + bash + + chmod +x /entrypoint.sh && sync && /entrypoint.sh + + + 8082 + + + ALWAYS + 0 + + USE_CURRENT_TIMESTAMP + + + + + org.liquibase + liquibase-maven-plugin + ${liquibase.version} + + ${project.basedir}/src/main/resources/config/liquibase/master.xml + ${project.basedir}/src/main/resources/config/liquibase/changelog/${maven.build.timestamp}_changelog.xml + org.h2.Driver + jdbc:h2:file:${project.build.directory}/h2db/db/casemanagement + + caseManagement + + hibernate:spring:org.securityrat.casemanagement.domain?dialect=org.hibernate.dialect.H2Dialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + true + debug + !test + oauth_access_token, oauth_approvals, oauth_client_details, oauth_client_token, oauth_code, oauth_refresh_token + + + + io.github.jhipster + jhipster-framework + ${jhipster-dependencies.version} + + + org.hibernate + hibernate-core + ${hibernate-core.version} + + + org.javassist + javassist + ${javassist.version} + + + org.liquibase.ext + liquibase-hibernate5 + ${liquibase-hibernate5.version} + + + org.springframework.boot + spring-boot-starter-data-jpa + ${spring-boot.version} + + + javax.validation + validation-api + ${validation-api.version} + + + com.h2database + h2 + ${h2.version} + + + org.springframework + spring-core + ${spring.version} + + + org.springframework + spring-context + ${spring.version} + + + org.springframework + spring-beans + ${spring.version} + + + + + maven-clean-plugin + ${maven-clean-plugin.version} + + + org.apache.maven.plugins + maven-eclipse-plugin + ${maven-eclipse-plugin.version} + + true + true + + + + org.apache.maven.plugins + maven-enforcer-plugin + ${maven-enforcer-plugin.version} + + + enforce-versions + + enforce + + + + enforce-dependencyConvergence + + + + + false + + + enforce + + + + + + + You are running an older version of Maven. JHipster requires at least Maven ${maven.version} + [${maven.version},) + + + You are running an incompatible version of Java. JHipster supports JDK 8 to 13. + [1.8,14) + + + + + + org.apache.maven.plugins + maven-idea-plugin + ${maven-idea-plugin.version} + + node_modules + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + default-resources + validate + + copy-resources + + + ${project.build.directory}/classes + false + + # + + + + src/main/resources/ + true + + config/*.yml + + + + src/main/resources/ + false + + config/*.yml + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + alphabetical + ${junit.utReportFolder} + + **/*IT* + **/*IntTest* + + + + + org.apache.maven.plugins + maven-failsafe-plugin + ${maven-failsafe-plugin.version} + + + ${project.build.outputDirectory} + + alphabetical + ${junit.itReportFolder} + + **/*IT* + **/*IntTest* + + + + + integration-test + + integration-test + + + + verify + + verify + + + + + + org.sonarsource.scanner.maven + sonar-maven-plugin + ${sonar-maven-plugin.version} + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + + + + + no-liquibase + + ,no-liquibase + + + + swagger + + ,swagger + + + + tls + + ,tls + + + + dev + + true + + + + org.springframework.boot + spring-boot-starter-undertow + + + org.springframework.boot + spring-boot-devtools + true + + + com.h2database + h2 + + + + + dev${profile.tls}${profile.no-liquibase} + + + + prod + + + org.springframework.boot + spring-boot-starter-undertow + + + + + + maven-clean-plugin + + + + target/classes/static/ + + + + + + org.springframework.boot + spring-boot-maven-plugin + + ${start-class} + + + + + build-info + + + + + + pl.project13.maven + git-commit-id-plugin + + + + + + prod${profile.swagger}${profile.no-liquibase} + + + + war + + + + org.apache.maven.plugins + maven-war-plugin + ${maven-war-plugin.version} + + + + war + + package + + + + WEB-INF/**,META-INF/** + false + + + + + + + + zipkin + + + org.springframework.cloud + spring-cloud-starter-zipkin + + + + + + IDE + + + org.mapstruct + mapstruct-processor + + + org.hibernate + hibernate-jpamodelgen + + + + + + eclipse + + + m2e.version + + + + + + org.springframework.boot + spring-boot-starter-undertow + + + + + + + + org.eclipse.m2e + lifecycle-mapping + ${lifecycle-mapping.version} + + + + + + org.jacoco + + jacoco-maven-plugin + + + ${jacoco-maven-plugin.version} + + + prepare-agent + + + + + + + + + + + + + + + + + diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..ce1a060 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,25 @@ +sonar.projectKey=caseManagement +sonar.projectName=caseManagement generated by jhipster +sonar.projectVersion=1.0 + +sonar.sources=src/main/ +sonar.host.url=http://localhost:9001 + +sonar.tests=src/test/ +sonar.coverage.jacoco.xmlReportPaths=target/jacoco/test/jacoco.xml,target/jacoco/integrationTest/jacoco.xml +sonar.java.codeCoveragePlugin=jacoco +sonar.junit.reportPaths=target/test-results/test,target/test-results/integrationTest + +sonar.sourceEncoding=UTF-8 +sonar.exclusions=src/main/webapp/content/**/*.*, src/main/webapp/i18n/*.js, target/classes/static/**/*.* + +sonar.issue.ignore.multicriteria=S3437,S4684,UndocumentedApi +# Rule https://sonarcloud.io/coding_rules?open=squid%3AS3437&rule_key=squid%3AS3437 is ignored, as a JPA-managed field cannot be transient +sonar.issue.ignore.multicriteria.S3437.resourceKey=src/main/java/**/* +sonar.issue.ignore.multicriteria.S3437.ruleKey=squid:S3437 +# Rule https://sonarcloud.io/coding_rules?open=squid%3AUndocumentedApi&rule_key=squid%3AUndocumentedApi is ignored, as we want to follow "clean code" guidelines and classes, methods and arguments names should be self-explanatory +sonar.issue.ignore.multicriteria.UndocumentedApi.resourceKey=src/main/java/**/* +sonar.issue.ignore.multicriteria.UndocumentedApi.ruleKey=squid:UndocumentedApi +# Rule https://sonarcloud.io/coding_rules?open=squid%3AS4684&rule_key=squid%3AS4684 +sonar.issue.ignore.multicriteria.S4684.resourceKey=src/main/java/**/* +sonar.issue.ignore.multicriteria.S4684.ruleKey=squid:S4684 diff --git a/src/main/docker/app.yml b/src/main/docker/app.yml new file mode 100644 index 0000000..9f766b3 --- /dev/null +++ b/src/main/docker/app.yml @@ -0,0 +1,28 @@ +version: '2' +services: + casemanagement-app: + image: casemanagement + environment: + - _JAVA_OPTIONS=-Xmx512m -Xms256m + - SPRING_PROFILES_ACTIVE=prod,swagger + - MANAGEMENT_METRICS_EXPORT_PROMETHEUS_ENABLED=true + - SPRING_CLOUD_CONSUL_HOST=consul + - SPRING_CLOUD_CONSUL_PORT=8500 + - SPRING_DATASOURCE_URL=jdbc:mariadb://casemanagement-mariadb:3306/casemanagement + - SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER_URI=http://keycloak:9080/auth/realms/jhipster + - SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_ID=internal + - SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_SECRET=internal + - JHIPSTER_SLEEP=120 # gives time for mariadb server to start + casemanagement-mariadb: + extends: + file: mariadb.yml + service: casemanagement-mariadb + consul: + extends: + file: consul.yml + service: consul + + consul-config-loader: + extends: + file: consul.yml + service: consul-config-loader diff --git a/src/main/docker/central-server-config/README.md b/src/main/docker/central-server-config/README.md new file mode 100644 index 0000000..f4d2fb2 --- /dev/null +++ b/src/main/docker/central-server-config/README.md @@ -0,0 +1,6 @@ +# Central configuration sources details + +When running the consul.yml or app.yml docker-compose files, files located in `central-server-config/` +will get automatically loaded in Consul's K/V store. Adding or editing files will trigger a reloading. + +For more info, refer to https://www.jhipster.tech/consul/ diff --git a/src/main/docker/central-server-config/application.yml b/src/main/docker/central-server-config/application.yml new file mode 100644 index 0000000..e2398f0 --- /dev/null +++ b/src/main/docker/central-server-config/application.yml @@ -0,0 +1,9 @@ +configserver: + name: Docker Consul Service + status: Connected to Consul Server running in Docker + +jhipster: + security: + authentication: + jwt: + secret: my-secret-key-which-should-be-changed-in-production-and-be-base64-encoded diff --git a/src/main/docker/config/git2consul.json b/src/main/docker/config/git2consul.json new file mode 100644 index 0000000..dcb9f6b --- /dev/null +++ b/src/main/docker/config/git2consul.json @@ -0,0 +1,18 @@ +{ + "version": "1.0", + "repos": [ + { + "name": "config", + "url": "https://github.com/jhipster/generator-jhipster.git", + "branches": ["master"], + "include_branch_name": false, + "source_root": "generators/server/src/main/docker/config/consul-config/", + "hooks": [ + { + "type": "polling", + "interval": "1" + } + ] + } + ] +} diff --git a/src/main/docker/consul.yml b/src/main/docker/consul.yml new file mode 100644 index 0000000..2aa4598 --- /dev/null +++ b/src/main/docker/consul.yml @@ -0,0 +1,22 @@ +version: '2' +services: + consul: + image: consul:1.6.2 + ports: + - 8300:8300 + - 8500:8500 + - 8600:8600 + command: consul agent -dev -ui -client 0.0.0.0 + + consul-config-loader: + image: jhipster/consul-config-loader:v0.3.0 + volumes: + - ./central-server-config:/config + environment: + - INIT_SLEEP_SECONDS=5 + - CONSUL_URL=consul + - CONSUL_PORT=8500 + # Uncomment to load configuration into Consul from a Git repository + # as configured in central-server-config/git2consul.json + # Also set SPRING_CLOUD_CONSUL_CONFIG_FORMAT=files on your apps + # - CONFIG_MODE=git diff --git a/src/main/docker/grafana/provisioning/dashboards/JVM.json b/src/main/docker/grafana/provisioning/dashboards/JVM.json new file mode 100644 index 0000000..5104abc --- /dev/null +++ b/src/main/docker/grafana/provisioning/dashboards/JVM.json @@ -0,0 +1,3778 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "limit": 100, + "name": "Annotations & Alerts", + "showIn": 0, + "type": "dashboard" + }, + { + "datasource": "Prometheus", + "enable": true, + "expr": "resets(process_uptime_seconds{application=\"$application\", instance=\"$instance\"}[1m]) > 0", + "iconColor": "rgba(255, 96, 96, 1)", + "name": "Restart Detection", + "showIn": 0, + "step": "1m", + "tagKeys": "restart-tag", + "textFormat": "uptime reset", + "titleFormat": "Restart" + } + ] + }, + "description": "Dashboard for Micrometer instrumented applications (Java, Spring Boot, Micronaut)", + "editable": true, + "gnetId": 4701, + "graphTooltip": 1, + "iteration": 1553765841423, + "links": [], + "panels": [ + { + "content": "\n# Acknowledgments\n\nThank you to [Michael Weirauch](https://twitter.com/emwexx) for creating this dashboard: see original JVM (Micrometer) dashboard at [https://grafana.com/dashboards/4701](https://grafana.com/dashboards/4701)\n\n\n\n", + "gridPos": { + "h": 3, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 141, + "links": [], + "mode": "markdown", + "timeFrom": null, + "timeShift": null, + "title": "Acknowledgments", + "type": "text" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 125, + "panels": [], + "repeat": null, + "title": "Quick Facts", + "type": "row" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"], + "datasource": "Prometheus", + "decimals": 1, + "editable": true, + "error": false, + "format": "s", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 4 + }, + "height": "", + "id": 63, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "70%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "process_uptime_seconds{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "", + "metric": "", + "refId": "A", + "step": 14400 + } + ], + "thresholds": "", + "title": "Uptime", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"], + "datasource": "Prometheus", + "decimals": null, + "editable": true, + "error": false, + "format": "dateTimeAsIso", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 6, + "y": 4 + }, + "height": "", + "id": 92, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "70%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "process_start_time_seconds{application=\"$application\", instance=\"$instance\"}*1000", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "", + "metric": "", + "refId": "A", + "step": 14400 + } + ], + "thresholds": "", + "title": "Start time", + "type": "singlestat", + "valueFontSize": "70%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": ["rgba(50, 172, 45, 0.97)", "rgba(237, 129, 40, 0.89)", "rgba(245, 54, 54, 0.9)"], + "datasource": "Prometheus", + "decimals": 2, + "editable": true, + "error": false, + "format": "percent", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 12, + "y": 4 + }, + "id": 65, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "70%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})*100/sum(jvm_memory_max_bytes{application=\"$application\",instance=\"$instance\", area=\"heap\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "", + "refId": "A", + "step": 14400 + } + ], + "thresholds": "70,90", + "title": "Heap used", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": ["rgba(50, 172, 45, 0.97)", "rgba(237, 129, 40, 0.89)", "rgba(245, 54, 54, 0.9)"], + "datasource": "Prometheus", + "decimals": 2, + "editable": true, + "error": false, + "format": "percent", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 18, + "y": 4 + }, + "id": 75, + "interval": null, + "links": [], + "mappingType": 2, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "70%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + }, + { + "from": "-99999999999999999999999999999999", + "text": "N/A", + "to": "0" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})*100/sum(jvm_memory_max_bytes{application=\"$application\",instance=\"$instance\", area=\"nonheap\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "", + "refId": "A", + "step": 14400 + } + ], + "thresholds": "70,90", + "title": "Non-Heap used", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + }, + { + "op": "=", + "text": "x", + "value": "" + } + ], + "valueName": "current" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 7 + }, + "id": 126, + "panels": [], + "repeat": null, + "title": "I/O Overview", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 8 + }, + "id": 111, + "legend": { + "avg": false, + "current": true, + "max": false, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(http_server_requests_seconds_count{application=\"$application\", instance=\"$instance\"}[1m]))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "HTTP", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Rate", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "ops", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "HTTP": "#890f02", + "HTTP - 5xx": "#bf1b00" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 8 + }, + "id": 112, + "legend": { + "avg": false, + "current": true, + "max": false, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(http_server_requests_seconds_count{application=\"$application\", instance=\"$instance\", status=~\"5..\"}[1m]))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "HTTP - 5xx", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Errors", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "ops", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 8 + }, + "id": 113, + "legend": { + "avg": false, + "current": true, + "max": false, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(http_server_requests_seconds_sum{application=\"$application\", instance=\"$instance\", status!~\"5..\"}[1m]))/sum(rate(http_server_requests_seconds_count{application=\"$application\", instance=\"$instance\", status!~\"5..\"}[1m]))", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "HTTP - AVG", + "refId": "A" + }, + { + "expr": "max(http_server_requests_seconds_max{application=\"$application\", instance=\"$instance\", status!~\"5..\"})", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "HTTP - MAX", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Duration", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 15 + }, + "id": 127, + "panels": [], + "repeat": null, + "title": "JVM Memory", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 16 + }, + "id": 24, + "legend": { + "avg": false, + "current": true, + "max": true, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "expr": "sum(jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "committed", + "refId": "B", + "step": 2400 + }, + { + "expr": "sum(jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "max", + "refId": "C", + "step": 2400 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "JVM Heap", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["mbytes", "short"], + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 16 + }, + "id": 25, + "legend": { + "avg": false, + "current": true, + "max": true, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "expr": "sum(jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "committed", + "refId": "B", + "step": 2400 + }, + { + "expr": "sum(jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "max", + "refId": "C", + "step": 2400 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "JVM Non-Heap", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["mbytes", "short"], + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 16 + }, + "id": 26, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "max": true, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "expr": "sum(jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "committed", + "refId": "B", + "step": 2400 + }, + { + "expr": "sum(jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "max", + "refId": "C", + "step": 2400 + }, + { + "expr": "process_memory_vss_bytes{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": true, + "intervalFactor": 2, + "legendFormat": "vss", + "metric": "", + "refId": "D", + "step": 2400 + }, + { + "expr": "process_memory_rss_bytes{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "rss", + "refId": "E", + "step": 2400 + }, + { + "expr": "process_memory_pss_bytes{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "pss", + "refId": "F", + "step": 2400 + }, + { + "expr": "process_memory_swap_bytes{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "swap", + "refId": "G", + "step": 2400 + }, + { + "expr": "process_memory_swappss_bytes{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "swappss", + "refId": "H", + "step": 2400 + }, + { + "expr": "process_memory_pss_bytes{application=\"$application\", instance=\"$instance\"} + process_memory_swap_bytes{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "phys (pss+swap)", + "refId": "I", + "step": 2400 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "JVM Total", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["mbytes", "short"], + "yaxes": [ + { + "format": "bytes", + "label": "", + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 23 + }, + "id": 128, + "panels": [], + "repeat": null, + "title": "JVM Misc", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 24 + }, + "id": 106, + "legend": { + "avg": false, + "current": true, + "max": true, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "system_cpu_usage{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "system", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "expr": "process_cpu_usage{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "process", + "refId": "B" + }, + { + "expr": "avg_over_time(process_cpu_usage{application=\"$application\", instance=\"$instance\"}[1h])", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "process-1h", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "CPU", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["short", "short"], + "yaxes": [ + { + "decimals": 1, + "format": "percentunit", + "label": "", + "logBase": 1, + "max": "1", + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 6, + "y": 24 + }, + "id": 93, + "legend": { + "avg": false, + "current": true, + "max": true, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "system_load_average_1m{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "system-1m", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "expr": "", + "format": "time_series", + "intervalFactor": 2, + "refId": "B" + }, + { + "expr": "system_cpu_count{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "cpu", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Load", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["short", "short"], + "yaxes": [ + { + "decimals": 1, + "format": "short", + "label": "", + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 12, + "y": 24 + }, + "id": 32, + "legend": { + "avg": false, + "current": true, + "max": true, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_threads_live{application=\"$application\", instance=\"$instance\"} or jvm_threads_live_threads{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "live", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "expr": "jvm_threads_daemon{application=\"$application\", instance=\"$instance\"} or jvm_threads_daemon_threads{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "daemon", + "metric": "", + "refId": "B", + "step": 2400 + }, + { + "expr": "jvm_threads_peak{application=\"$application\", instance=\"$instance\"} or jvm_threads_peak_threads{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "peak", + "refId": "C", + "step": 2400 + }, + { + "expr": "process_threads{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "process", + "refId": "D", + "step": 2400 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Threads", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["short", "short"], + "yaxes": [ + { + "decimals": 0, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "blocked": "#bf1b00", + "new": "#fce2de", + "runnable": "#7eb26d", + "terminated": "#511749", + "timed-waiting": "#c15c17", + "waiting": "#eab839" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 24 + }, + "id": 124, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_threads_states_threads{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{state}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Thread States", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "debug": "#1F78C1", + "error": "#BF1B00", + "info": "#508642", + "trace": "#6ED0E0", + "warn": "#EAB839" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 18, + "x": 0, + "y": 31 + }, + "height": "", + "id": 91, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": true, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "error", + "yaxis": 1 + }, + { + "alias": "warn", + "yaxis": 1 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "increase(logback_events_total{application=\"$application\", instance=\"$instance\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{level}}", + "metric": "", + "refId": "A", + "step": 1200 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Log Events (1m)", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["short", "short"], + "yaxes": [ + { + "decimals": 0, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 31 + }, + "id": 61, + "legend": { + "avg": false, + "current": true, + "max": true, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "process_open_fds{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "open", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "expr": "process_max_fds{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "max", + "metric": "", + "refId": "B", + "step": 2400 + }, + { + "expr": "process_files_open{application=\"$application\", instance=\"$instance\"} or process_files_open_files{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "open", + "refId": "C" + }, + { + "expr": "process_files_max{application=\"$application\", instance=\"$instance\"} or process_files_max_files{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "max", + "refId": "D" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "File Descriptors", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["short", "short"], + "yaxes": [ + { + "decimals": 0, + "format": "short", + "label": null, + "logBase": 10, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 38 + }, + "id": 129, + "panels": [], + "repeat": "persistence_counts", + "title": "JVM Memory Pools (Heap)", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 39 + }, + "id": 3, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "maxPerRow": 3, + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": "jvm_memory_pool_heap", + "scopedVars": { + "jvm_memory_pool_heap": { + "selected": false, + "text": "PS Eden Space", + "value": "PS Eden Space" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 1800 + }, + { + "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "commited", + "metric": "", + "refId": "B", + "step": 1800 + }, + { + "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "max", + "metric": "", + "refId": "C", + "step": 1800 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "$jvm_memory_pool_heap", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["mbytes", "short"], + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 39 + }, + "id": 134, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "maxPerRow": 3, + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": null, + "repeatIteration": 1553765841423, + "repeatPanelId": 3, + "scopedVars": { + "jvm_memory_pool_heap": { + "selected": false, + "text": "PS Old Gen", + "value": "PS Old Gen" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 1800 + }, + { + "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "commited", + "metric": "", + "refId": "B", + "step": 1800 + }, + { + "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "max", + "metric": "", + "refId": "C", + "step": 1800 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "$jvm_memory_pool_heap", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["mbytes", "short"], + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 39 + }, + "id": 135, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "maxPerRow": 3, + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": null, + "repeatIteration": 1553765841423, + "repeatPanelId": 3, + "scopedVars": { + "jvm_memory_pool_heap": { + "selected": false, + "text": "PS Survivor Space", + "value": "PS Survivor Space" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 1800 + }, + { + "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "commited", + "metric": "", + "refId": "B", + "step": 1800 + }, + { + "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "max", + "metric": "", + "refId": "C", + "step": 1800 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "$jvm_memory_pool_heap", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["mbytes", "short"], + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 46 + }, + "id": 130, + "panels": [], + "repeat": null, + "title": "JVM Memory Pools (Non-Heap)", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 47 + }, + "id": 78, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "maxPerRow": 3, + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": "jvm_memory_pool_nonheap", + "scopedVars": { + "jvm_memory_pool_nonheap": { + "selected": false, + "text": "Metaspace", + "value": "Metaspace" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 1800 + }, + { + "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "commited", + "metric": "", + "refId": "B", + "step": 1800 + }, + { + "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "max", + "metric": "", + "refId": "C", + "step": 1800 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "$jvm_memory_pool_nonheap", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["mbytes", "short"], + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 47 + }, + "id": 136, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "maxPerRow": 3, + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": null, + "repeatIteration": 1553765841423, + "repeatPanelId": 78, + "scopedVars": { + "jvm_memory_pool_nonheap": { + "selected": false, + "text": "Compressed Class Space", + "value": "Compressed Class Space" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 1800 + }, + { + "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "commited", + "metric": "", + "refId": "B", + "step": 1800 + }, + { + "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "max", + "metric": "", + "refId": "C", + "step": 1800 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "$jvm_memory_pool_nonheap", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["mbytes", "short"], + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 47 + }, + "id": 137, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "maxPerRow": 3, + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": null, + "repeatIteration": 1553765841423, + "repeatPanelId": 78, + "scopedVars": { + "jvm_memory_pool_nonheap": { + "selected": false, + "text": "Code Cache", + "value": "Code Cache" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 1800 + }, + { + "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "commited", + "metric": "", + "refId": "B", + "step": 1800 + }, + { + "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "max", + "metric": "", + "refId": "C", + "step": 1800 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "$jvm_memory_pool_nonheap", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["mbytes", "short"], + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 54 + }, + "id": 131, + "panels": [], + "repeat": null, + "title": "Garbage Collection", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 55 + }, + "id": 98, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(jvm_gc_pause_seconds_count{application=\"$application\", instance=\"$instance\"}[1m])", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "{{action}} ({{cause}})", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Collections", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ops", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 55 + }, + "id": 101, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(jvm_gc_pause_seconds_sum{application=\"$application\", instance=\"$instance\"}[1m])/rate(jvm_gc_pause_seconds_count{application=\"$application\", instance=\"$instance\"}[1m])", + "format": "time_series", + "hide": false, + "instant": false, + "intervalFactor": 1, + "legendFormat": "avg {{action}} ({{cause}})", + "refId": "A" + }, + { + "expr": "jvm_gc_pause_seconds_max{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": false, + "instant": false, + "intervalFactor": 1, + "legendFormat": "max {{action}} ({{cause}})", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Pause Durations", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 55 + }, + "id": 99, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(jvm_gc_memory_allocated_bytes_total{application=\"$application\", instance=\"$instance\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "allocated", + "refId": "A" + }, + { + "expr": "rate(jvm_gc_memory_promoted_bytes_total{application=\"$application\", instance=\"$instance\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "promoted", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Allocated/Promoted", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 62 + }, + "id": 132, + "panels": [], + "repeat": null, + "title": "Classloading", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 63 + }, + "id": 37, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_classes_loaded{application=\"$application\", instance=\"$instance\"} or jvm_classes_loaded_classes{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "loaded", + "metric": "", + "refId": "A", + "step": 1200 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Classes loaded", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["short", "short"], + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 63 + }, + "id": 38, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "delta(jvm_classes_loaded{application=\"$application\",instance=\"$instance\"}[5m]) or delta(jvm_classes_loaded_classes{application=\"$application\",instance=\"$instance\"}[5m])", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "delta", + "metric": "", + "refId": "A", + "step": 1200 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Class delta (5m)", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["ops", "short"], + "yaxes": [ + { + "decimals": null, + "format": "short", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 70 + }, + "id": 133, + "panels": [], + "repeat": null, + "title": "Buffer Pools", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 71 + }, + "id": 33, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_buffer_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"direct\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "expr": "jvm_buffer_total_capacity_bytes{application=\"$application\", instance=\"$instance\", id=\"direct\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "capacity", + "metric": "", + "refId": "B", + "step": 2400 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Direct Buffers", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["short", "short"], + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 6, + "y": 71 + }, + "id": 83, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_buffer_count{application=\"$application\", instance=\"$instance\", id=\"direct\"} or jvm_buffer_count_buffers{application=\"$application\", instance=\"$instance\", id=\"direct\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "count", + "metric": "", + "refId": "A", + "step": 2400 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Direct Buffers", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["short", "short"], + "yaxes": [ + { + "decimals": 0, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 12, + "y": 71 + }, + "id": 85, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_buffer_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"mapped\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "expr": "jvm_buffer_total_capacity_bytes{application=\"$application\", instance=\"$instance\", id=\"mapped\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "capacity", + "metric": "", + "refId": "B", + "step": 2400 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Mapped Buffers", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["short", "short"], + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 71 + }, + "id": 84, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_buffer_count{application=\"$application\", instance=\"$instance\", id=\"mapped\"} or jvm_buffer_count_buffers{application=\"$application\", instance=\"$instance\", id=\"mapped\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "count", + "metric": "", + "refId": "A", + "step": 2400 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Mapped Buffers", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["short", "short"], + "yaxes": [ + { + "decimals": 0, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "10s", + "schemaVersion": 18, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": null, + "current": { + "text": "test", + "value": "test" + }, + "datasource": "Prometheus", + "definition": "", + "hide": 0, + "includeAll": false, + "label": "Application", + "multi": false, + "name": "application", + "options": [], + "query": "label_values(application)", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allFormat": "glob", + "allValue": null, + "current": { + "text": "localhost:8080", + "value": "localhost:8080" + }, + "datasource": "Prometheus", + "definition": "", + "hide": 0, + "includeAll": false, + "label": "Instance", + "multi": false, + "multiFormat": "glob", + "name": "instance", + "options": [], + "query": "label_values(jvm_memory_used_bytes{application=\"$application\"}, instance)", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allFormat": "glob", + "allValue": null, + "current": { + "text": "All", + "value": "$__all" + }, + "datasource": "Prometheus", + "definition": "", + "hide": 0, + "includeAll": true, + "label": "JVM Memory Pools Heap", + "multi": false, + "multiFormat": "glob", + "name": "jvm_memory_pool_heap", + "options": [], + "query": "label_values(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"},id)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allFormat": "glob", + "allValue": null, + "current": { + "text": "All", + "value": "$__all" + }, + "datasource": "Prometheus", + "definition": "", + "hide": 0, + "includeAll": true, + "label": "JVM Memory Pools Non-Heap", + "multi": false, + "multiFormat": "glob", + "name": "jvm_memory_pool_nonheap", + "options": [], + "query": "label_values(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"},id)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 2, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-30m", + "to": "now" + }, + "timepicker": { + "now": true, + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], + "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + }, + "timezone": "browser", + "title": "JVM (Micrometer)", + "uid": "Ud1CFe3iz", + "version": 1 +} diff --git a/src/main/docker/grafana/provisioning/dashboards/dashboard.yml b/src/main/docker/grafana/provisioning/dashboards/dashboard.yml new file mode 100644 index 0000000..4817a83 --- /dev/null +++ b/src/main/docker/grafana/provisioning/dashboards/dashboard.yml @@ -0,0 +1,11 @@ +apiVersion: 1 + +providers: + - name: 'Prometheus' + orgId: 1 + folder: '' + type: file + disableDeletion: false + editable: true + options: + path: /etc/grafana/provisioning/dashboards diff --git a/src/main/docker/grafana/provisioning/datasources/datasource.yml b/src/main/docker/grafana/provisioning/datasources/datasource.yml new file mode 100644 index 0000000..57b2bb3 --- /dev/null +++ b/src/main/docker/grafana/provisioning/datasources/datasource.yml @@ -0,0 +1,50 @@ +apiVersion: 1 + +# list of datasources that should be deleted from the database +deleteDatasources: + - name: Prometheus + orgId: 1 + +# list of datasources to insert/update depending +# whats available in the database +datasources: + # name of the datasource. Required + - name: Prometheus + # datasource type. Required + type: prometheus + # access mode. direct or proxy. Required + access: proxy + # org id. will default to orgId 1 if not specified + orgId: 1 + # url + # On MacOS, replace localhost by host.docker.internal + url: http://localhost:9090 + # database password, if used + password: + # database user, if used + user: + # database name, if used + database: + # enable/disable basic auth + basicAuth: false + # basic auth username + basicAuthUser: admin + # basic auth password + basicAuthPassword: admin + # enable/disable with credentials headers + withCredentials: + # mark as default datasource. Max one per org + isDefault: true + # fields that will be converted to json and stored in json_data + jsonData: + graphiteVersion: '1.1' + tlsAuth: false + tlsAuthWithCACert: false + # json object of data that will be encrypted. + secureJsonData: + tlsCACert: '...' + tlsClientCert: '...' + tlsClientKey: '...' + version: 1 + # allow users to edit datasources from the UI. + editable: true diff --git a/src/main/docker/mariadb.yml b/src/main/docker/mariadb.yml new file mode 100644 index 0000000..ea2b077 --- /dev/null +++ b/src/main/docker/mariadb.yml @@ -0,0 +1,13 @@ +version: '2' +services: + casemanagement-mariadb: + image: mariadb:10.4.10 + # volumes: + # - ~/volumes/jhipster/caseManagement/mysql/:/var/lib/mysql/ + environment: + - MYSQL_USER=root + - MYSQL_ALLOW_EMPTY_PASSWORD=yes + - MYSQL_DATABASE=casemanagement + ports: + - 3306:3306 + command: mysqld --lower_case_table_names=1 --skip-ssl --character_set_server=utf8mb4 --explicit_defaults_for_timestamp diff --git a/src/main/docker/monitoring.yml b/src/main/docker/monitoring.yml new file mode 100644 index 0000000..c1ac16e --- /dev/null +++ b/src/main/docker/monitoring.yml @@ -0,0 +1,26 @@ +version: '2' +services: + casemanagement-prometheus: + image: prom/prometheus:v2.14.0 + volumes: + - ./prometheus/:/etc/prometheus/ + command: + - '--config.file=/etc/prometheus/prometheus.yml' + ports: + - 9090:9090 + # On MacOS, remove next line and replace localhost by host.docker.internal in prometheus/prometheus.yml and + # grafana/provisioning/datasources/datasource.yml + network_mode: 'host' # to test locally running service + casemanagement-grafana: + image: grafana/grafana:6.5.1 + volumes: + - ./grafana/provisioning/:/etc/grafana/provisioning/ + environment: + - GF_SECURITY_ADMIN_PASSWORD=admin + - GF_USERS_ALLOW_SIGN_UP=false + - GF_INSTALL_PLUGINS=grafana-piechart-panel + ports: + - 3000:3000 + # On MacOS, remove next line and replace localhost by host.docker.internal in prometheus/prometheus.yml and + # grafana/provisioning/datasources/datasource.yml + network_mode: 'host' # to test locally running service diff --git a/src/main/docker/prometheus/prometheus.yml b/src/main/docker/prometheus/prometheus.yml new file mode 100644 index 0000000..bd6f15c --- /dev/null +++ b/src/main/docker/prometheus/prometheus.yml @@ -0,0 +1,31 @@ +# Sample global config for monitoring JHipster applications +global: + scrape_interval: 15s # By default, scrape targets every 15 seconds. + evaluation_interval: 15s # By default, scrape targets every 15 seconds. + # scrape_timeout is set to the global default (10s). + + # Attach these labels to any time series or alerts when communicating with + # external systems (federation, remote storage, Alertmanager). + external_labels: + monitor: 'jhipster' + +# A scrape configuration containing exactly one endpoint to scrape: +# Here it's Prometheus itself. +scrape_configs: + # The job name is added as a label `job=` to any timeseries scraped from this config. + - job_name: 'prometheus' + + # Override the global default and scrape targets from this job every 5 seconds. + scrape_interval: 5s + + # scheme defaults to 'http' enable https in case your application is server via https + #scheme: https + # basic auth is not needed by default. See https://www.jhipster.tech/monitoring/#configuring-metrics-forwarding for details + #basic_auth: + # username: admin + # password: admin + metrics_path: /management/prometheus + static_configs: + - targets: + # On MacOS, replace localhost by host.docker.internal + - localhost:8082 diff --git a/src/main/docker/sonar.yml b/src/main/docker/sonar.yml new file mode 100644 index 0000000..622d796 --- /dev/null +++ b/src/main/docker/sonar.yml @@ -0,0 +1,7 @@ +version: '2' +services: + casemanagement-sonar: + image: sonarqube:7.9.1-community + ports: + - 9001:9000 + - 9092:9092 diff --git a/src/main/java/org/securityrat/casemanagement/ApplicationWebXml.java b/src/main/java/org/securityrat/casemanagement/ApplicationWebXml.java new file mode 100644 index 0000000..2346a2a --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/ApplicationWebXml.java @@ -0,0 +1,22 @@ +package org.securityrat.casemanagement; + +import io.github.jhipster.config.DefaultProfileUtil; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +/** + * This is a helper Java class that provides an alternative to creating a {@code web.xml}. + * This will be invoked only when the application is deployed to a Servlet container like Tomcat, JBoss etc. + */ +public class ApplicationWebXml extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + /** + * set a default to use when no profile is configured. + */ + DefaultProfileUtil.addDefaultProfile(application.application()); + return application.sources(CaseManagementApp.class); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/CaseManagementApp.java b/src/main/java/org/securityrat/casemanagement/CaseManagementApp.java new file mode 100644 index 0000000..46f1405 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/CaseManagementApp.java @@ -0,0 +1,107 @@ +package org.securityrat.casemanagement; + +import org.securityrat.casemanagement.config.ApplicationProperties; + +import io.github.jhipster.config.DefaultProfileUtil; +import io.github.jhipster.config.JHipsterConstants; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.core.env.Environment; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Collection; + +@SpringBootApplication +@EnableConfigurationProperties({LiquibaseProperties.class, ApplicationProperties.class}) +@EnableDiscoveryClient +public class CaseManagementApp implements InitializingBean { + + private static final Logger log = LoggerFactory.getLogger(CaseManagementApp.class); + + private final Environment env; + + public CaseManagementApp(Environment env) { + this.env = env; + } + + /** + * Initializes caseManagement. + *

+ * Spring profiles can be configured with a program argument --spring.profiles.active=your-active-profile + *

+ * You can find more information on how profiles work with JHipster on https://www.jhipster.tech/profiles/. + */ + @Override + public void afterPropertiesSet() throws Exception { + Collection activeProfiles = Arrays.asList(env.getActiveProfiles()); + if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) && activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_PRODUCTION)) { + log.error("You have misconfigured your application! It should not run " + + "with both the 'dev' and 'prod' profiles at the same time."); + } + if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) && activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_CLOUD)) { + log.error("You have misconfigured your application! It should not " + + "run with both the 'dev' and 'cloud' profiles at the same time."); + } + } + + /** + * Main method, used to run the application. + * + * @param args the command line arguments. + */ + public static void main(String[] args) { + SpringApplication app = new SpringApplication(CaseManagementApp.class); + DefaultProfileUtil.addDefaultProfile(app); + Environment env = app.run(args).getEnvironment(); + logApplicationStartup(env); + } + + private static void logApplicationStartup(Environment env) { + String protocol = "http"; + if (env.getProperty("server.ssl.key-store") != null) { + protocol = "https"; + } + String serverPort = env.getProperty("server.port"); + String contextPath = env.getProperty("server.servlet.context-path"); + if (StringUtils.isBlank(contextPath)) { + contextPath = "/"; + } + String hostAddress = "localhost"; + try { + hostAddress = InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + log.warn("The host name could not be determined, using `localhost` as fallback"); + } + log.info("\n----------------------------------------------------------\n\t" + + "Application '{}' is running! Access URLs:\n\t" + + "Local: \t\t{}://localhost:{}{}\n\t" + + "External: \t{}://{}:{}{}\n\t" + + "Profile(s): \t{}\n----------------------------------------------------------", + env.getProperty("spring.application.name"), + protocol, + serverPort, + contextPath, + protocol, + hostAddress, + serverPort, + contextPath, + env.getActiveProfiles()); + + String configServerStatus = env.getProperty("configserver.status"); + if (configServerStatus == null) { + configServerStatus = "Not found or not setup for this application"; + } + log.info("\n----------------------------------------------------------\n\t" + + "Config Server: \t{}\n----------------------------------------------------------", configServerStatus); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/aop/logging/LoggingAspect.java b/src/main/java/org/securityrat/casemanagement/aop/logging/LoggingAspect.java new file mode 100644 index 0000000..e065893 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/aop/logging/LoggingAspect.java @@ -0,0 +1,99 @@ +package org.securityrat.casemanagement.aop.logging; + +import io.github.jhipster.config.JHipsterConstants; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; + +import java.util.Arrays; + +/** + * Aspect for logging execution of service and repository Spring components. + * + * By default, it only runs with the "dev" profile. + */ +@Aspect +public class LoggingAspect { + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + private final Environment env; + + public LoggingAspect(Environment env) { + this.env = env; + } + + /** + * Pointcut that matches all repositories, services and Web REST endpoints. + */ + @Pointcut("within(@org.springframework.stereotype.Repository *)" + + " || within(@org.springframework.stereotype.Service *)" + + " || within(@org.springframework.web.bind.annotation.RestController *)") + public void springBeanPointcut() { + // Method is empty as this is just a Pointcut, the implementations are in the advices. + } + + /** + * Pointcut that matches all Spring beans in the application's main packages. + */ + @Pointcut("within(org.securityrat.casemanagement.repository..*)"+ + " || within(org.securityrat.casemanagement.service..*)"+ + " || within(org.securityrat.casemanagement.web.rest..*)") + public void applicationPackagePointcut() { + // Method is empty as this is just a Pointcut, the implementations are in the advices. + } + + /** + * Advice that logs methods throwing exceptions. + * + * @param joinPoint join point for advice. + * @param e exception. + */ + @AfterThrowing(pointcut = "applicationPackagePointcut() && springBeanPointcut()", throwing = "e") + public void logAfterThrowing(JoinPoint joinPoint, Throwable e) { + if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT))) { + log.error("Exception in {}.{}() with cause = \'{}\' and exception = \'{}\'", joinPoint.getSignature().getDeclaringTypeName(), + joinPoint.getSignature().getName(), e.getCause() != null? e.getCause() : "NULL", e.getMessage(), e); + + } else { + log.error("Exception in {}.{}() with cause = {}", joinPoint.getSignature().getDeclaringTypeName(), + joinPoint.getSignature().getName(), e.getCause() != null? e.getCause() : "NULL"); + } + } + + /** + * Advice that logs when a method is entered and exited. + * + * @param joinPoint join point for advice. + * @return result. + * @throws Throwable throws {@link IllegalArgumentException}. + */ + @Around("applicationPackagePointcut() && springBeanPointcut()") + public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { + if (log.isDebugEnabled()) { + log.debug("Enter: {}.{}() with argument[s] = {}", joinPoint.getSignature().getDeclaringTypeName(), + joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs())); + } + try { + Object result = joinPoint.proceed(); + if (log.isDebugEnabled()) { + log.debug("Exit: {}.{}() with result = {}", joinPoint.getSignature().getDeclaringTypeName(), + joinPoint.getSignature().getName(), result); + } + return result; + } catch (IllegalArgumentException e) { + log.error("Illegal argument: {} in {}.{}()", Arrays.toString(joinPoint.getArgs()), + joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName()); + + throw e; + } + } +} diff --git a/src/main/java/org/securityrat/casemanagement/client/AuthorizedFeignClient.java b/src/main/java/org/securityrat/casemanagement/client/AuthorizedFeignClient.java new file mode 100644 index 0000000..d589ae4 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/client/AuthorizedFeignClient.java @@ -0,0 +1,55 @@ +package org.securityrat.casemanagement.client; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.cloud.openfeign.FeignClientsConfiguration; +import org.springframework.core.annotation.AliasFor; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@FeignClient +public @interface AuthorizedFeignClient { + + @AliasFor(annotation = FeignClient.class, attribute = "name") + String name() default ""; + + /** + * A custom {@code @Configuration} for the feign client. + * + * Can contain override {@code @Bean} definition for the pieces that + * make up the client, for instance {@link feign.codec.Decoder}, + * {@link feign.codec.Encoder}, {@link feign.Contract}. + * + * @return the custom {@code @Configuration} for the feign client. + * @see FeignClientsConfiguration for the defaults. + */ + @AliasFor(annotation = FeignClient.class, attribute = "configuration") + Class[] configuration() default OAuth2InterceptedFeignConfiguration.class; + + /** + * An absolute URL or resolvable hostname (the protocol is optional). + * @return the URL. + */ + String url() default ""; + + /** + * Whether 404s should be decoded instead of throwing FeignExceptions. + * @return true if 404s will be decoded; false otherwise. + */ + boolean decode404() default false; + + /** + * Fallback class for the specified Feign client interface. The fallback class must + * implement the interface annotated by this annotation and be a valid Spring bean. + * @return the fallback class for the specified Feign client interface. + */ + Class fallback() default void.class; + + /** + * Path prefix to be used by all method-level mappings. Can be used with or without {@code @RibbonClient}. + * @return the path prefix to be used by all method-level mappings. + */ + String path() default ""; +} diff --git a/src/main/java/org/securityrat/casemanagement/client/OAuth2InterceptedFeignConfiguration.java b/src/main/java/org/securityrat/casemanagement/client/OAuth2InterceptedFeignConfiguration.java new file mode 100644 index 0000000..659d076 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/client/OAuth2InterceptedFeignConfiguration.java @@ -0,0 +1,15 @@ +package org.securityrat.casemanagement.client; + +import org.springframework.context.annotation.Bean; + +import feign.RequestInterceptor; + +import org.securityrat.casemanagement.security.oauth2.AuthorizationHeaderUtil; + +public class OAuth2InterceptedFeignConfiguration { + + @Bean(name = "oauth2RequestInterceptor") + public RequestInterceptor getOAuth2RequestInterceptor(AuthorizationHeaderUtil authorizationHeaderUtil) { + return new TokenRelayRequestInterceptor(authorizationHeaderUtil); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/client/TokenRelayRequestInterceptor.java b/src/main/java/org/securityrat/casemanagement/client/TokenRelayRequestInterceptor.java new file mode 100644 index 0000000..5104c0a --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/client/TokenRelayRequestInterceptor.java @@ -0,0 +1,26 @@ +package org.securityrat.casemanagement.client; + +import org.securityrat.casemanagement.security.oauth2.AuthorizationHeaderUtil; + +import feign.RequestInterceptor; +import feign.RequestTemplate; + +import java.util.Optional; + +public class TokenRelayRequestInterceptor implements RequestInterceptor { + + public static final String AUTHORIZATION = "Authorization"; + + private final AuthorizationHeaderUtil authorizationHeaderUtil; + + public TokenRelayRequestInterceptor(AuthorizationHeaderUtil authorizationHeaderUtil) { + super(); + this.authorizationHeaderUtil = authorizationHeaderUtil; + } + + @Override + public void apply(RequestTemplate template) { + Optional authorizationHeader = authorizationHeaderUtil.getAuthorizationHeader(); + authorizationHeader.ifPresent(s -> template.header(AUTHORIZATION, s)); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/config/ApplicationProperties.java b/src/main/java/org/securityrat/casemanagement/config/ApplicationProperties.java new file mode 100644 index 0000000..edac8d7 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/config/ApplicationProperties.java @@ -0,0 +1,13 @@ +package org.securityrat.casemanagement.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Properties specific to Case Management. + *

+ * Properties are configured in the {@code application.yml} file. + * See {@link io.github.jhipster.config.JHipsterProperties} for a good example. + */ +@ConfigurationProperties(prefix = "application", ignoreUnknownFields = false) +public class ApplicationProperties { +} diff --git a/src/main/java/org/securityrat/casemanagement/config/AsyncConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/AsyncConfiguration.java new file mode 100644 index 0000000..30010be --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/config/AsyncConfiguration.java @@ -0,0 +1,47 @@ +package org.securityrat.casemanagement.config; + +import io.github.jhipster.async.ExceptionHandlingAsyncTaskExecutor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler; +import org.springframework.boot.autoconfigure.task.TaskExecutionProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; + +@Configuration +@EnableAsync +@EnableScheduling +public class AsyncConfiguration implements AsyncConfigurer { + + private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class); + + private final TaskExecutionProperties taskExecutionProperties; + + public AsyncConfiguration(TaskExecutionProperties taskExecutionProperties) { + this.taskExecutionProperties = taskExecutionProperties; + } + + @Override + @Bean(name = "taskExecutor") + public Executor getAsyncExecutor() { + log.debug("Creating Async Task Executor"); + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(taskExecutionProperties.getPool().getCoreSize()); + executor.setMaxPoolSize(taskExecutionProperties.getPool().getMaxSize()); + executor.setQueueCapacity(taskExecutionProperties.getPool().getQueueCapacity()); + executor.setThreadNamePrefix(taskExecutionProperties.getThreadNamePrefix()); + return new ExceptionHandlingAsyncTaskExecutor(executor); + } + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return new SimpleAsyncUncaughtExceptionHandler(); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/config/CloudDatabaseConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/CloudDatabaseConfiguration.java new file mode 100644 index 0000000..61bb763 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/config/CloudDatabaseConfiguration.java @@ -0,0 +1,28 @@ +package org.securityrat.casemanagement.config; + +import io.github.jhipster.config.JHipsterConstants; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.config.java.AbstractCloudConfig; +import org.springframework.context.annotation.*; + +import javax.sql.DataSource; +import org.springframework.boot.context.properties.ConfigurationProperties; + + +@Configuration +@Profile(JHipsterConstants.SPRING_PROFILE_CLOUD) +public class CloudDatabaseConfiguration extends AbstractCloudConfig { + + private final Logger log = LoggerFactory.getLogger(CloudDatabaseConfiguration.class); + + private static final String CLOUD_CONFIGURATION_HIKARI_PREFIX = "spring.datasource.hikari"; + + @Bean + @ConfigurationProperties(CLOUD_CONFIGURATION_HIKARI_PREFIX) + public DataSource dataSource() { + log.info("Configuring JDBC datasource from a cloud provider"); + return connectionFactory().dataSource(); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/config/Constants.java b/src/main/java/org/securityrat/casemanagement/config/Constants.java new file mode 100644 index 0000000..8559954 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/config/Constants.java @@ -0,0 +1,17 @@ +package org.securityrat.casemanagement.config; + +/** + * Application constants. + */ +public final class Constants { + + // Regex for acceptable logins + public static final String LOGIN_REGEX = "^[_.@A-Za-z0-9-]*$"; + + public static final String SYSTEM_ACCOUNT = "system"; + public static final String DEFAULT_LANGUAGE = "en"; + public static final String ANONYMOUS_USER = "anonymoususer"; + + private Constants() { + } +} diff --git a/src/main/java/org/securityrat/casemanagement/config/DatabaseConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/DatabaseConfiguration.java new file mode 100644 index 0000000..78fa2d3 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/config/DatabaseConfiguration.java @@ -0,0 +1,59 @@ +package org.securityrat.casemanagement.config; + +import io.github.jhipster.config.JHipsterConstants; +import io.github.jhipster.config.h2.H2ConfigurationHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +import org.springframework.core.env.Environment; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import java.sql.SQLException; + +@Configuration +@EnableJpaRepositories("org.securityrat.casemanagement.repository") +@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware") +@EnableTransactionManagement +public class DatabaseConfiguration { + + private final Logger log = LoggerFactory.getLogger(DatabaseConfiguration.class); + + private final Environment env; + + public DatabaseConfiguration(Environment env) { + this.env = env; + } + + /** + * Open the TCP port for the H2 database, so it is available remotely. + * + * @return the H2 database TCP server. + * @throws SQLException if the server failed to start. + */ + @Bean(initMethod = "start", destroyMethod = "stop") + @Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) + public Object h2TCPServer() throws SQLException { + String port = getValidPortForH2(); + log.debug("H2 database is available on port {}", port); + return H2ConfigurationHelper.createServer(port); + } + + private String getValidPortForH2() { + int port = Integer.parseInt(env.getProperty("server.port")); + if (port < 10000) { + port = 10000 + port; + } else { + if (port < 63536) { + port = port + 2000; + } else { + port = port - 2000; + } + } + return String.valueOf(port); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/config/DateTimeFormatConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/DateTimeFormatConfiguration.java new file mode 100644 index 0000000..a3ee995 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/config/DateTimeFormatConfiguration.java @@ -0,0 +1,20 @@ +package org.securityrat.casemanagement.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.format.FormatterRegistry; +import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * Configure the converters to use the ISO format for dates by default. + */ +@Configuration +public class DateTimeFormatConfiguration implements WebMvcConfigurer { + + @Override + public void addFormatters(FormatterRegistry registry) { + DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar(); + registrar.setUseIsoFormat(true); + registrar.registerFormatters(registry); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/config/FeignConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/FeignConfiguration.java new file mode 100644 index 0000000..5dd7837 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/config/FeignConfiguration.java @@ -0,0 +1,22 @@ +package org.securityrat.casemanagement.config; + +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.cloud.openfeign.FeignClientsConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@EnableFeignClients(basePackages = "org.securityrat.casemanagement") +@Import(FeignClientsConfiguration.class) +public class FeignConfiguration { + + /** + * Set the Feign specific log level to log client REST requests. + */ + @Bean + feign.Logger.Level feignLoggerLevel() { + return feign.Logger.Level.BASIC; + } + +} diff --git a/src/main/java/org/securityrat/casemanagement/config/JacksonConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/JacksonConfiguration.java new file mode 100644 index 0000000..a83db54 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/config/JacksonConfiguration.java @@ -0,0 +1,61 @@ +package org.securityrat.casemanagement.config; + +import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.module.afterburner.AfterburnerModule; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.zalando.problem.ProblemModule; +import org.zalando.problem.violations.ConstraintViolationProblemModule; + +@Configuration +public class JacksonConfiguration { + + /** + * Support for Java date and time API. + * @return the corresponding Jackson module. + */ + @Bean + public JavaTimeModule javaTimeModule() { + return new JavaTimeModule(); + } + + @Bean + public Jdk8Module jdk8TimeModule() { + return new Jdk8Module(); + } + + /* + * Support for Hibernate types in Jackson. + */ + @Bean + public Hibernate5Module hibernate5Module() { + return new Hibernate5Module(); + } + + /* + * Jackson Afterburner module to speed up serialization/deserialization. + */ + @Bean + public AfterburnerModule afterburnerModule() { + return new AfterburnerModule(); + } + + /* + * Module for serialization/deserialization of RFC7807 Problem. + */ + @Bean + ProblemModule problemModule() { + return new ProblemModule(); + } + + /* + * Module for serialization/deserialization of ConstraintViolationProblem. + */ + @Bean + ConstraintViolationProblemModule constraintViolationProblemModule() { + return new ConstraintViolationProblemModule(); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/config/LiquibaseConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/LiquibaseConfiguration.java new file mode 100644 index 0000000..16d770f --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/config/LiquibaseConfiguration.java @@ -0,0 +1,60 @@ +package org.securityrat.casemanagement.config; + +import io.github.jhipster.config.JHipsterConstants; +import io.github.jhipster.config.liquibase.SpringLiquibaseUtil; +import liquibase.integration.spring.SpringLiquibase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseDataSource; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; + +import javax.sql.DataSource; +import java.util.concurrent.Executor; + +@Configuration +public class LiquibaseConfiguration { + + private final Logger log = LoggerFactory.getLogger(LiquibaseConfiguration.class); + + private final Environment env; + + public LiquibaseConfiguration(Environment env) { + this.env = env; + } + + @Bean + public SpringLiquibase liquibase(@Qualifier("taskExecutor") Executor executor, + @LiquibaseDataSource ObjectProvider liquibaseDataSource, LiquibaseProperties liquibaseProperties, + ObjectProvider dataSource, DataSourceProperties dataSourceProperties) { + + // If you don't want Liquibase to start asynchronously, substitute by this: + // SpringLiquibase liquibase = SpringLiquibaseUtil.createSpringLiquibase(liquibaseDataSource.getIfAvailable(), liquibaseProperties, dataSource.getIfUnique(), dataSourceProperties); + SpringLiquibase liquibase = SpringLiquibaseUtil.createAsyncSpringLiquibase(this.env, executor, liquibaseDataSource.getIfAvailable(), liquibaseProperties, dataSource.getIfUnique(), dataSourceProperties); + liquibase.setChangeLog("classpath:config/liquibase/master.xml"); + liquibase.setContexts(liquibaseProperties.getContexts()); + liquibase.setDefaultSchema(liquibaseProperties.getDefaultSchema()); + liquibase.setLiquibaseSchema(liquibaseProperties.getLiquibaseSchema()); + liquibase.setLiquibaseTablespace(liquibaseProperties.getLiquibaseTablespace()); + liquibase.setDatabaseChangeLogLockTable(liquibaseProperties.getDatabaseChangeLogLockTable()); + liquibase.setDatabaseChangeLogTable(liquibaseProperties.getDatabaseChangeLogTable()); + liquibase.setDropFirst(liquibaseProperties.isDropFirst()); + liquibase.setLabels(liquibaseProperties.getLabels()); + liquibase.setChangeLogParameters(liquibaseProperties.getParameters()); + liquibase.setRollbackFile(liquibaseProperties.getRollbackFile()); + liquibase.setTestRollbackOnUpdate(liquibaseProperties.isTestRollbackOnUpdate()); + if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_NO_LIQUIBASE))) { + liquibase.setShouldRun(false); + } else { + liquibase.setShouldRun(liquibaseProperties.isEnabled()); + log.debug("Configuring Liquibase"); + } + return liquibase; + } +} diff --git a/src/main/java/org/securityrat/casemanagement/config/LocaleConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/LocaleConfiguration.java new file mode 100644 index 0000000..a29995e --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/config/LocaleConfiguration.java @@ -0,0 +1,27 @@ +package org.securityrat.casemanagement.config; + +import io.github.jhipster.config.locale.AngularCookieLocaleResolver; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.config.annotation.*; +import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; + +@Configuration +public class LocaleConfiguration implements WebMvcConfigurer { + + @Bean(name = "localeResolver") + public LocaleResolver localeResolver() { + AngularCookieLocaleResolver cookieLocaleResolver = new AngularCookieLocaleResolver(); + cookieLocaleResolver.setCookieName("NG_TRANSLATE_LANG_KEY"); + return cookieLocaleResolver; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor(); + localeChangeInterceptor.setParamName("language"); + registry.addInterceptor(localeChangeInterceptor); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/config/LoggingAspectConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/LoggingAspectConfiguration.java new file mode 100644 index 0000000..868191f --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/config/LoggingAspectConfiguration.java @@ -0,0 +1,19 @@ +package org.securityrat.casemanagement.config; + +import org.securityrat.casemanagement.aop.logging.LoggingAspect; + +import io.github.jhipster.config.JHipsterConstants; + +import org.springframework.context.annotation.*; +import org.springframework.core.env.Environment; + +@Configuration +@EnableAspectJAutoProxy +public class LoggingAspectConfiguration { + + @Bean + @Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) + public LoggingAspect loggingAspect(Environment env) { + return new LoggingAspect(env); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/config/LoggingConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/LoggingConfiguration.java new file mode 100644 index 0000000..d315346 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/config/LoggingConfiguration.java @@ -0,0 +1,59 @@ +package org.securityrat.casemanagement.config; + +import ch.qos.logback.classic.LoggerContext; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.jhipster.config.JHipsterProperties; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.info.BuildProperties; +import org.springframework.cloud.consul.serviceregistry.ConsulRegistration; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Configuration; + +import java.util.HashMap; +import java.util.Map; + +import static io.github.jhipster.config.logging.LoggingUtils.*; + +/* + * Configures the console and Logstash log appenders from the app properties + */ +@Configuration +@RefreshScope +public class LoggingConfiguration { + + public LoggingConfiguration(@Value("${spring.application.name}") String appName, + @Value("${server.port}") String serverPort, + JHipsterProperties jHipsterProperties, + ObjectProvider consulRegistration, + ObjectProvider buildProperties, + ObjectMapper mapper) throws JsonProcessingException { + + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + + Map map = new HashMap<>(); + map.put("app_name", appName); + map.put("app_port", serverPort); + buildProperties.ifAvailable(it -> map.put("version", it.getVersion())); + consulRegistration.ifAvailable(it -> map.put("instance_id", it.getInstanceId())); + String customFields = mapper.writeValueAsString(map); + + JHipsterProperties.Logging loggingProperties = jHipsterProperties.getLogging(); + JHipsterProperties.Logging.Logstash logstashProperties = loggingProperties.getLogstash(); + + if (loggingProperties.isUseJsonFormat()) { + addJsonConsoleAppender(context, customFields); + } + if (logstashProperties.isEnabled()) { + addLogstashTcpSocketAppender(context, customFields, logstashProperties); + } + if (loggingProperties.isUseJsonFormat() || logstashProperties.isEnabled()) { + addContextListener(context, customFields, loggingProperties); + } + if (jHipsterProperties.getMetrics().getLogs().isEnabled()) { + setMetricsMarkerLogbackFilter(context, loggingProperties.isUseJsonFormat()); + } + } +} diff --git a/src/main/java/org/securityrat/casemanagement/config/MethodSecurityConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/MethodSecurityConfiguration.java new file mode 100644 index 0000000..088d156 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/config/MethodSecurityConfiguration.java @@ -0,0 +1,16 @@ +package org.securityrat.casemanagement.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; +import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler; + +@Configuration +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) +public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration { + @Override + protected MethodSecurityExpressionHandler createExpressionHandler() { + return new OAuth2MethodSecurityExpressionHandler(); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/config/SecurityConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/SecurityConfiguration.java new file mode 100644 index 0000000..64170e8 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/config/SecurityConfiguration.java @@ -0,0 +1,101 @@ +package org.securityrat.casemanagement.config; + +import org.securityrat.casemanagement.security.*; + +import io.github.jhipster.config.JHipsterProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +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.securityrat.casemanagement.security.oauth2.AudienceValidator; +import org.securityrat.casemanagement.security.SecurityUtils; +import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; +import org.springframework.security.oauth2.core.OAuth2TokenValidator; +import org.springframework.security.oauth2.jwt.*; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.GrantedAuthority; +import java.util.*; +import org.securityrat.casemanagement.security.oauth2.JwtAuthorityExtractor; +import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter; +import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport; + +@EnableWebSecurity +@Import(SecurityProblemSupport.class) +public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Value("${spring.security.oauth2.client.provider.oidc.issuer-uri}") + private String issuerUri; + + private final JHipsterProperties jHipsterProperties; + private final JwtAuthorityExtractor jwtAuthorityExtractor; + private final SecurityProblemSupport problemSupport; + + public SecurityConfiguration(JwtAuthorityExtractor jwtAuthorityExtractor, JHipsterProperties jHipsterProperties, SecurityProblemSupport problemSupport) { + this.problemSupport = problemSupport; + this.jwtAuthorityExtractor = jwtAuthorityExtractor; + this.jHipsterProperties = jHipsterProperties; + } + @Override + public void configure(WebSecurity web) { + web.ignoring() + .antMatchers("/h2-console/**"); + } + + @Override + public void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .csrf() + .disable() + .exceptionHandling() + .authenticationEntryPoint(problemSupport) + .accessDeniedHandler(problemSupport) + .and() + .headers() + .contentSecurityPolicy("default-src 'self'; frame-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://storage.googleapis.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:") + .and() + .referrerPolicy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN) + .and() + .featurePolicy("geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; speaker 'none'; fullscreen 'self'; payment 'none'") + .and() + .frameOptions() + .deny() + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authorizeRequests() + .antMatchers("/api/auth-info").permitAll() + .antMatchers("/api/**").authenticated() + .antMatchers("/management/health").permitAll() + .antMatchers("/management/info").permitAll() + .antMatchers("/management/prometheus").permitAll() + .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN) + .and() + .oauth2ResourceServer() + .jwt() + .jwtAuthenticationConverter(jwtAuthorityExtractor) + .and() + .and() + .oauth2Client(); + // @formatter:on + } + + + @Bean + JwtDecoder jwtDecoder() { + NimbusJwtDecoderJwkSupport jwtDecoder = (NimbusJwtDecoderJwkSupport) + JwtDecoders.fromOidcIssuerLocation(issuerUri); + + OAuth2TokenValidator audienceValidator = new AudienceValidator(jHipsterProperties.getSecurity().getOauth2().getAudience()); + OAuth2TokenValidator withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri); + OAuth2TokenValidator withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator); + + jwtDecoder.setJwtValidator(withAudience); + + return jwtDecoder; + } +} diff --git a/src/main/java/org/securityrat/casemanagement/config/WebConfigurer.java b/src/main/java/org/securityrat/casemanagement/config/WebConfigurer.java new file mode 100644 index 0000000..1abc415 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/config/WebConfigurer.java @@ -0,0 +1,70 @@ +package org.securityrat.casemanagement.config; + +import io.github.jhipster.config.JHipsterConstants; +import io.github.jhipster.config.JHipsterProperties; +import io.github.jhipster.config.h2.H2ConfigurationHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.web.server.*; +import org.springframework.boot.web.servlet.ServletContextInitializer; +import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +import javax.servlet.*; + +/** + * Configuration of web application with Servlet 3.0 APIs. + */ +@Configuration +public class WebConfigurer implements ServletContextInitializer { + + private final Logger log = LoggerFactory.getLogger(WebConfigurer.class); + + private final Environment env; + + private final JHipsterProperties jHipsterProperties; + + public WebConfigurer(Environment env, JHipsterProperties jHipsterProperties) { + this.env = env; + this.jHipsterProperties = jHipsterProperties; + } + + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + if (env.getActiveProfiles().length != 0) { + log.info("Web application configuration, using profiles: {}", (Object[]) env.getActiveProfiles()); + } + if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT))) { + initH2Console(servletContext); + } + log.info("Web application fully configured"); + } + + @Bean + public CorsFilter corsFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration config = jHipsterProperties.getCors(); + if (config.getAllowedOrigins() != null && !config.getAllowedOrigins().isEmpty()) { + log.debug("Registering CORS filter"); + source.registerCorsConfiguration("/api/**", config); + source.registerCorsConfiguration("/management/**", config); + source.registerCorsConfiguration("/v2/api-docs", config); + } + return new CorsFilter(source); + } + + /** + * Initializes H2 console. + */ + private void initH2Console(ServletContext servletContext) { + log.debug("Initialize H2 console"); + H2ConfigurationHelper.initH2Console(servletContext); + } + +} diff --git a/src/main/java/org/securityrat/casemanagement/config/audit/AuditEventConverter.java b/src/main/java/org/securityrat/casemanagement/config/audit/AuditEventConverter.java new file mode 100644 index 0000000..a049c52 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/config/audit/AuditEventConverter.java @@ -0,0 +1,86 @@ +package org.securityrat.casemanagement.config.audit; + +import org.securityrat.casemanagement.domain.PersistentAuditEvent; + +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.security.web.authentication.WebAuthenticationDetails; +import org.springframework.stereotype.Component; + +import java.util.*; + +@Component +public class AuditEventConverter { + + /** + * Convert a list of {@link PersistentAuditEvent}s to a list of {@link AuditEvent}s. + * + * @param persistentAuditEvents the list to convert. + * @return the converted list. + */ + public List convertToAuditEvent(Iterable persistentAuditEvents) { + if (persistentAuditEvents == null) { + return Collections.emptyList(); + } + List auditEvents = new ArrayList<>(); + for (PersistentAuditEvent persistentAuditEvent : persistentAuditEvents) { + auditEvents.add(convertToAuditEvent(persistentAuditEvent)); + } + return auditEvents; + } + + /** + * Convert a {@link PersistentAuditEvent} to an {@link AuditEvent}. + * + * @param persistentAuditEvent the event to convert. + * @return the converted list. + */ + public AuditEvent convertToAuditEvent(PersistentAuditEvent persistentAuditEvent) { + if (persistentAuditEvent == null) { + return null; + } + return new AuditEvent(persistentAuditEvent.getAuditEventDate(), persistentAuditEvent.getPrincipal(), + persistentAuditEvent.getAuditEventType(), convertDataToObjects(persistentAuditEvent.getData())); + } + + /** + * Internal conversion. This is needed to support the current SpringBoot actuator {@code AuditEventRepository} interface. + * + * @param data the data to convert. + * @return a map of {@link String}, {@link Object}. + */ + public Map convertDataToObjects(Map data) { + Map results = new HashMap<>(); + + if (data != null) { + for (Map.Entry entry : data.entrySet()) { + results.put(entry.getKey(), entry.getValue()); + } + } + return results; + } + + /** + * Internal conversion. This method will allow to save additional data. + * By default, it will save the object as string. + * + * @param data the data to convert. + * @return a map of {@link String}, {@link String}. + */ + public Map convertDataToStrings(Map data) { + Map results = new HashMap<>(); + + if (data != null) { + for (Map.Entry entry : data.entrySet()) { + // Extract the data that will be saved. + if (entry.getValue() instanceof WebAuthenticationDetails) { + WebAuthenticationDetails authenticationDetails = (WebAuthenticationDetails) entry.getValue(); + results.put("remoteAddress", authenticationDetails.getRemoteAddress()); + results.put("sessionId", authenticationDetails.getSessionId()); + } else { + results.put(entry.getKey(), Objects.toString(entry.getValue())); + } + } + } + return results; + } +} diff --git a/src/main/java/org/securityrat/casemanagement/config/audit/package-info.java b/src/main/java/org/securityrat/casemanagement/config/audit/package-info.java new file mode 100644 index 0000000..033145f --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/config/audit/package-info.java @@ -0,0 +1,4 @@ +/** + * Audit specific code. + */ +package org.securityrat.casemanagement.config.audit; diff --git a/src/main/java/org/securityrat/casemanagement/config/package-info.java b/src/main/java/org/securityrat/casemanagement/config/package-info.java new file mode 100644 index 0000000..ceddf5a --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/config/package-info.java @@ -0,0 +1,4 @@ +/** + * Spring Framework configuration files. + */ +package org.securityrat.casemanagement.config; diff --git a/src/main/java/org/securityrat/casemanagement/domain/AbstractAuditingEntity.java b/src/main/java/org/securityrat/casemanagement/domain/AbstractAuditingEntity.java new file mode 100644 index 0000000..6a5e210 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/domain/AbstractAuditingEntity.java @@ -0,0 +1,77 @@ +package org.securityrat.casemanagement.domain; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.io.Serializable; +import java.time.Instant; +import javax.persistence.Column; +import javax.persistence.EntityListeners; +import javax.persistence.MappedSuperclass; + +/** + * Base abstract class for entities which will hold definitions for created, last modified by and created, + * last modified by date. + */ +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public abstract class AbstractAuditingEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + @CreatedBy + @Column(name = "created_by", nullable = false, length = 50, updatable = false) + @JsonIgnore + private String createdBy; + + @CreatedDate + @Column(name = "created_date", updatable = false) + @JsonIgnore + private Instant createdDate = Instant.now(); + + @LastModifiedBy + @Column(name = "last_modified_by", length = 50) + @JsonIgnore + private String lastModifiedBy; + + @LastModifiedDate + @Column(name = "last_modified_date") + @JsonIgnore + private Instant lastModifiedDate = Instant.now(); + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + public Instant getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(Instant createdDate) { + this.createdDate = createdDate; + } + + public String getLastModifiedBy() { + return lastModifiedBy; + } + + public void setLastModifiedBy(String lastModifiedBy) { + this.lastModifiedBy = lastModifiedBy; + } + + public Instant getLastModifiedDate() { + return lastModifiedDate; + } + + public void setLastModifiedDate(Instant lastModifiedDate) { + this.lastModifiedDate = lastModifiedDate; + } +} diff --git a/src/main/java/org/securityrat/casemanagement/domain/AccessToken.java b/src/main/java/org/securityrat/casemanagement/domain/AccessToken.java new file mode 100644 index 0000000..dd5bbfc --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/domain/AccessToken.java @@ -0,0 +1,159 @@ +package org.securityrat.casemanagement.domain; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import javax.persistence.*; +import javax.validation.constraints.*; + +import java.io.Serializable; +import java.time.ZonedDateTime; + +/** + * A AccessToken. + */ +@Entity +@Table(name = "access_token") +public class AccessToken implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @NotNull + @Column(name = "token", nullable = false) + private String token; + + @Column(name = "expiration_date") + private ZonedDateTime expirationDate; + + @NotNull + @Column(name = "salt", nullable = false) + private String salt; + + @Column(name = "refresh_token") + private String refreshToken; + + @ManyToOne + @JsonIgnoreProperties("accessTokens") + private User user; + + @ManyToOne + @JsonIgnoreProperties("accessTokens") + private TicketSystemInstance ticketInstance; + + // jhipster-needle-entity-add-field - JHipster will add fields here, do not remove + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getToken() { + return token; + } + + public AccessToken token(String token) { + this.token = token; + return this; + } + + public void setToken(String token) { + this.token = token; + } + + public ZonedDateTime getExpirationDate() { + return expirationDate; + } + + public AccessToken expirationDate(ZonedDateTime expirationDate) { + this.expirationDate = expirationDate; + return this; + } + + public void setExpirationDate(ZonedDateTime expirationDate) { + this.expirationDate = expirationDate; + } + + public String getSalt() { + return salt; + } + + public AccessToken salt(String salt) { + this.salt = salt; + return this; + } + + public void setSalt(String salt) { + this.salt = salt; + } + + public String getRefreshToken() { + return refreshToken; + } + + public AccessToken refreshToken(String refreshToken) { + this.refreshToken = refreshToken; + return this; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public User getUser() { + return user; + } + + public AccessToken user(User user) { + this.user = user; + return this; + } + + public void setUser(User user) { + this.user = user; + } + + public TicketSystemInstance getTicketInstance() { + return ticketInstance; + } + + public AccessToken ticketInstance(TicketSystemInstance ticketSystemInstance) { + this.ticketInstance = ticketSystemInstance; + return this; + } + + public void setTicketInstance(TicketSystemInstance ticketSystemInstance) { + this.ticketInstance = ticketSystemInstance; + } + // jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here, do not remove + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof AccessToken)) { + return false; + } + return id != null && id.equals(((AccessToken) o).id); + } + + @Override + public int hashCode() { + return 31; + } + + @Override + public String toString() { + return "AccessToken{" + + "id=" + getId() + + ", token='" + getToken() + "'" + + ", expirationDate='" + getExpirationDate() + "'" + + ", salt='" + getSalt() + "'" + + ", refreshToken='" + getRefreshToken() + "'" + + "}"; + } +} diff --git a/src/main/java/org/securityrat/casemanagement/domain/Authority.java b/src/main/java/org/securityrat/casemanagement/domain/Authority.java new file mode 100644 index 0000000..4d7ada1 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/domain/Authority.java @@ -0,0 +1,57 @@ +package org.securityrat.casemanagement.domain; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Column; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.io.Serializable; +import java.util.Objects; + +/** + * An authority (a security role) used by Spring Security. + */ +@Entity +@Table(name = "jhi_authority") +public class Authority implements Serializable { + + private static final long serialVersionUID = 1L; + + @NotNull + @Size(max = 50) + @Id + @Column(length = 50) + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Authority)) { + return false; + } + return Objects.equals(name, ((Authority) o).name); + } + + @Override + public int hashCode() { + return Objects.hashCode(name); + } + + @Override + public String toString() { + return "Authority{" + + "name='" + name + '\'' + + "}"; + } +} diff --git a/src/main/java/org/securityrat/casemanagement/domain/PersistentAuditEvent.java b/src/main/java/org/securityrat/casemanagement/domain/PersistentAuditEvent.java new file mode 100644 index 0000000..bf997bf --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/domain/PersistentAuditEvent.java @@ -0,0 +1,106 @@ +package org.securityrat.casemanagement.domain; + +import javax.persistence.*; +import javax.validation.constraints.NotNull; +import java.io.Serializable; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; + +/** + * Persist AuditEvent managed by the Spring Boot actuator. + * + * @see org.springframework.boot.actuate.audit.AuditEvent + */ +@Entity +@Table(name = "jhi_persistent_audit_event") +public class PersistentAuditEvent implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "event_id") + private Long id; + + @NotNull + @Column(nullable = false) + private String principal; + + @Column(name = "event_date") + private Instant auditEventDate; + + @Column(name = "event_type") + private String auditEventType; + + @ElementCollection + @MapKeyColumn(name = "name") + @Column(name = "value") + @CollectionTable(name = "jhi_persistent_audit_evt_data", joinColumns=@JoinColumn(name="event_id")) + private Map data = new HashMap<>(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getPrincipal() { + return principal; + } + + public void setPrincipal(String principal) { + this.principal = principal; + } + + public Instant getAuditEventDate() { + return auditEventDate; + } + + public void setAuditEventDate(Instant auditEventDate) { + this.auditEventDate = auditEventDate; + } + + public String getAuditEventType() { + return auditEventType; + } + + public void setAuditEventType(String auditEventType) { + this.auditEventType = auditEventType; + } + + public Map getData() { + return data; + } + + public void setData(Map data) { + this.data = data; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof PersistentAuditEvent)) { + return false; + } + return id != null && id.equals(((PersistentAuditEvent) o).id); + } + + @Override + public int hashCode() { + return 31; + } + + @Override + public String toString() { + return "PersistentAuditEvent{" + + "principal='" + principal + '\'' + + ", auditEventDate=" + auditEventDate + + ", auditEventType='" + auditEventType + '\'' + + '}'; + } +} diff --git a/src/main/java/org/securityrat/casemanagement/domain/TicketSystemInstance.java b/src/main/java/org/securityrat/casemanagement/domain/TicketSystemInstance.java new file mode 100644 index 0000000..b66a101 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/domain/TicketSystemInstance.java @@ -0,0 +1,190 @@ +package org.securityrat.casemanagement.domain; + +import javax.persistence.*; +import javax.validation.constraints.*; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; + +import org.securityrat.casemanagement.domain.enumeration.TicketSystem; + +/** + * A TicketSystemInstance. + */ +@Entity +@Table(name = "ticket_system_instance") +public class TicketSystemInstance implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "name") + private String name; + + @NotNull + @Enumerated(EnumType.STRING) + @Column(name = "type", nullable = false) + private TicketSystem type; + + @NotNull + @Column(name = "url", nullable = false) + private String url; + + @Column(name = "consumer_key") + private String consumerKey; + + @Column(name = "client_id") + private String clientId; + + @Column(name = "client_secret") + private String clientSecret; + + @OneToMany(mappedBy = "ticketInstance") + private Set accessTokens = new HashSet<>(); + + // jhipster-needle-entity-add-field - JHipster will add fields here, do not remove + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public TicketSystemInstance name(String name) { + this.name = name; + return this; + } + + public void setName(String name) { + this.name = name; + } + + public TicketSystem getType() { + return type; + } + + public TicketSystemInstance type(TicketSystem type) { + this.type = type; + return this; + } + + public void setType(TicketSystem type) { + this.type = type; + } + + public String getUrl() { + return url; + } + + public TicketSystemInstance url(String url) { + this.url = url; + return this; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getConsumerKey() { + return consumerKey; + } + + public TicketSystemInstance consumerKey(String consumerKey) { + this.consumerKey = consumerKey; + return this; + } + + public void setConsumerKey(String consumerKey) { + this.consumerKey = consumerKey; + } + + public String getClientId() { + return clientId; + } + + public TicketSystemInstance clientId(String clientId) { + this.clientId = clientId; + return this; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getClientSecret() { + return clientSecret; + } + + public TicketSystemInstance clientSecret(String clientSecret) { + this.clientSecret = clientSecret; + return this; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public Set getAccessTokens() { + return accessTokens; + } + + public TicketSystemInstance accessTokens(Set accessTokens) { + this.accessTokens = accessTokens; + return this; + } + + public TicketSystemInstance addAccessToken(AccessToken accessToken) { + this.accessTokens.add(accessToken); + accessToken.setTicketInstance(this); + return this; + } + + public TicketSystemInstance removeAccessToken(AccessToken accessToken) { + this.accessTokens.remove(accessToken); + accessToken.setTicketInstance(null); + return this; + } + + public void setAccessTokens(Set accessTokens) { + this.accessTokens = accessTokens; + } + // jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here, do not remove + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof TicketSystemInstance)) { + return false; + } + return id != null && id.equals(((TicketSystemInstance) o).id); + } + + @Override + public int hashCode() { + return 31; + } + + @Override + public String toString() { + return "TicketSystemInstance{" + + "id=" + getId() + + ", name='" + getName() + "'" + + ", type='" + getType() + "'" + + ", url='" + getUrl() + "'" + + ", consumerKey='" + getConsumerKey() + "'" + + ", clientId='" + getClientId() + "'" + + ", clientSecret='" + getClientSecret() + "'" + + "}"; + } +} diff --git a/src/main/java/org/securityrat/casemanagement/domain/User.java b/src/main/java/org/securityrat/casemanagement/domain/User.java new file mode 100644 index 0000000..1ec91c3 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/domain/User.java @@ -0,0 +1,173 @@ +package org.securityrat.casemanagement.domain; + +import org.securityrat.casemanagement.config.Constants; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.apache.commons.lang3.StringUtils; +import org.hibernate.annotations.BatchSize; + +import javax.persistence.*; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; +import java.io.Serializable; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +/** + * A user. + */ +@Entity +@Table(name = "jhi_user") +public class User extends AbstractAuditingEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + private String id; + + @NotNull + @Pattern(regexp = Constants.LOGIN_REGEX) + @Size(min = 1, max = 50) + @Column(length = 50, unique = true, nullable = false) + private String login; + + @Size(max = 50) + @Column(name = "first_name", length = 50) + private String firstName; + + @Size(max = 50) + @Column(name = "last_name", length = 50) + private String lastName; + + @Email + @Size(min = 5, max = 254) + @Column(length = 254, unique = true) + private String email; + + @NotNull + @Column(nullable = false) + private boolean activated = false; + + @Size(min = 2, max = 10) + @Column(name = "lang_key", length = 10) + private String langKey; + + @Size(max = 256) + @Column(name = "image_url", length = 256) + private String imageUrl; + + @JsonIgnore + @ManyToMany + @JoinTable( + name = "jhi_user_authority", + joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")}, + inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "name")}) + + @BatchSize(size = 20) + private Set authorities = new HashSet<>(); + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getLogin() { + return login; + } + + // Lowercase the login before saving it in database + public void setLogin(String login) { + this.login = StringUtils.lowerCase(login, Locale.ENGLISH); + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public boolean getActivated() { + return activated; + } + + public void setActivated(boolean activated) { + this.activated = activated; + } + + public String getLangKey() { + return langKey; + } + + public void setLangKey(String langKey) { + this.langKey = langKey; + } + + public Set getAuthorities() { + return authorities; + } + + public void setAuthorities(Set authorities) { + this.authorities = authorities; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof User)) { + return false; + } + return id != null && id.equals(((User) o).id); + } + + @Override + public int hashCode() { + return 31; + } + + @Override + public String toString() { + return "User{" + + "login='" + login + '\'' + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", email='" + email + '\'' + + ", imageUrl='" + imageUrl + '\'' + + ", activated='" + activated + '\'' + + ", langKey='" + langKey + '\'' + + "}"; + } +} diff --git a/src/main/java/org/securityrat/casemanagement/domain/enumeration/TicketSystem.java b/src/main/java/org/securityrat/casemanagement/domain/enumeration/TicketSystem.java new file mode 100644 index 0000000..3bd33ee --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/domain/enumeration/TicketSystem.java @@ -0,0 +1,8 @@ +package org.securityrat.casemanagement.domain.enumeration; + +/** + * The TicketSystem enumeration. + */ +public enum TicketSystem { + JIRA +} diff --git a/src/main/java/org/securityrat/casemanagement/domain/package-info.java b/src/main/java/org/securityrat/casemanagement/domain/package-info.java new file mode 100644 index 0000000..878450c --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/domain/package-info.java @@ -0,0 +1,4 @@ +/** + * JPA domain objects. + */ +package org.securityrat.casemanagement.domain; diff --git a/src/main/java/org/securityrat/casemanagement/repository/AccessTokenRepository.java b/src/main/java/org/securityrat/casemanagement/repository/AccessTokenRepository.java new file mode 100644 index 0000000..0a87416 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/repository/AccessTokenRepository.java @@ -0,0 +1,19 @@ +package org.securityrat.casemanagement.repository; + +import org.securityrat.casemanagement.domain.AccessToken; +import org.springframework.data.jpa.repository.*; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * Spring Data repository for the AccessToken entity. + */ +@SuppressWarnings("unused") +@Repository +public interface AccessTokenRepository extends JpaRepository { + + @Query("select accessToken from AccessToken accessToken where accessToken.user.login = ?#{principal.preferredUsername}") + List findByUserIsCurrentUser(); + +} diff --git a/src/main/java/org/securityrat/casemanagement/repository/AuthorityRepository.java b/src/main/java/org/securityrat/casemanagement/repository/AuthorityRepository.java new file mode 100644 index 0000000..aa346eb --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/repository/AuthorityRepository.java @@ -0,0 +1,11 @@ +package org.securityrat.casemanagement.repository; + +import org.securityrat.casemanagement.domain.Authority; + +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * Spring Data JPA repository for the {@link Authority} entity. + */ +public interface AuthorityRepository extends JpaRepository { +} diff --git a/src/main/java/org/securityrat/casemanagement/repository/CustomAuditEventRepository.java b/src/main/java/org/securityrat/casemanagement/repository/CustomAuditEventRepository.java new file mode 100644 index 0000000..3da6fc0 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/repository/CustomAuditEventRepository.java @@ -0,0 +1,89 @@ +package org.securityrat.casemanagement.repository; + +import org.securityrat.casemanagement.config.Constants; +import org.securityrat.casemanagement.config.audit.AuditEventConverter; +import org.securityrat.casemanagement.domain.PersistentAuditEvent; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.boot.actuate.audit.AuditEventRepository; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Instant; +import java.util.*; + +/** + * An implementation of Spring Boot's {@link AuditEventRepository}. + */ +@Repository +public class CustomAuditEventRepository implements AuditEventRepository { + + private static final String AUTHORIZATION_FAILURE = "AUTHORIZATION_FAILURE"; + + /** + * Should be the same as in Liquibase migration. + */ + protected static final int EVENT_DATA_COLUMN_MAX_LENGTH = 255; + + private final PersistenceAuditEventRepository persistenceAuditEventRepository; + + private final AuditEventConverter auditEventConverter; + + private final Logger log = LoggerFactory.getLogger(getClass()); + + public CustomAuditEventRepository(PersistenceAuditEventRepository persistenceAuditEventRepository, + AuditEventConverter auditEventConverter) { + + this.persistenceAuditEventRepository = persistenceAuditEventRepository; + this.auditEventConverter = auditEventConverter; + } + + @Override + public List find(String principal, Instant after, String type) { + Iterable persistentAuditEvents = + persistenceAuditEventRepository.findByPrincipalAndAuditEventDateAfterAndAuditEventType(principal, after, type); + return auditEventConverter.convertToAuditEvent(persistentAuditEvents); + } + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void add(AuditEvent event) { + if (!AUTHORIZATION_FAILURE.equals(event.getType()) && + !Constants.ANONYMOUS_USER.equals(event.getPrincipal())) { + + PersistentAuditEvent persistentAuditEvent = new PersistentAuditEvent(); + persistentAuditEvent.setPrincipal(event.getPrincipal()); + persistentAuditEvent.setAuditEventType(event.getType()); + persistentAuditEvent.setAuditEventDate(event.getTimestamp()); + Map eventData = auditEventConverter.convertDataToStrings(event.getData()); + persistentAuditEvent.setData(truncate(eventData)); + persistenceAuditEventRepository.save(persistentAuditEvent); + } + } + + /** + * Truncate event data that might exceed column length. + */ + private Map truncate(Map data) { + Map results = new HashMap<>(); + + if (data != null) { + for (Map.Entry entry : data.entrySet()) { + String value = entry.getValue(); + if (value != null) { + int length = value.length(); + if (length > EVENT_DATA_COLUMN_MAX_LENGTH) { + value = value.substring(0, EVENT_DATA_COLUMN_MAX_LENGTH); + log.warn("Event data for {} too long ({}) has been truncated to {}. Consider increasing column width.", + entry.getKey(), length, EVENT_DATA_COLUMN_MAX_LENGTH); + } + } + results.put(entry.getKey(), value); + } + } + return results; + } +} diff --git a/src/main/java/org/securityrat/casemanagement/repository/PersistenceAuditEventRepository.java b/src/main/java/org/securityrat/casemanagement/repository/PersistenceAuditEventRepository.java new file mode 100644 index 0000000..702baaa --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/repository/PersistenceAuditEventRepository.java @@ -0,0 +1,23 @@ +package org.securityrat.casemanagement.repository; + +import org.securityrat.casemanagement.domain.PersistentAuditEvent; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.time.Instant; +import java.util.List; + +/** + * Spring Data JPA repository for the {@link PersistentAuditEvent} entity. + */ +public interface PersistenceAuditEventRepository extends JpaRepository { + + List findByPrincipal(String principal); + + List findByPrincipalAndAuditEventDateAfterAndAuditEventType(String principal, Instant after, String type); + + Page findAllByAuditEventDateBetween(Instant fromDate, Instant toDate, Pageable pageable); + + List findByAuditEventDateBefore(Instant before); +} diff --git a/src/main/java/org/securityrat/casemanagement/repository/TicketSystemInstanceRepository.java b/src/main/java/org/securityrat/casemanagement/repository/TicketSystemInstanceRepository.java new file mode 100644 index 0000000..0fbeb99 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/repository/TicketSystemInstanceRepository.java @@ -0,0 +1,15 @@ +package org.securityrat.casemanagement.repository; + +import org.securityrat.casemanagement.domain.TicketSystemInstance; +import org.springframework.data.jpa.repository.*; +import org.springframework.stereotype.Repository; + + +/** + * Spring Data repository for the TicketSystemInstance entity. + */ +@SuppressWarnings("unused") +@Repository +public interface TicketSystemInstanceRepository extends JpaRepository { + +} diff --git a/src/main/java/org/securityrat/casemanagement/repository/UserRepository.java b/src/main/java/org/securityrat/casemanagement/repository/UserRepository.java new file mode 100644 index 0000000..0b10459 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/repository/UserRepository.java @@ -0,0 +1,36 @@ +package org.securityrat.casemanagement.repository; + +import org.securityrat.casemanagement.domain.User; + +import org.springframework.data.domain.Page; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; +import java.time.Instant; + +/** + * Spring Data JPA repository for the {@link User} entity. + */ +@Repository +public interface UserRepository extends JpaRepository { + + Optional findOneByEmailIgnoreCase(String email); + + Optional findOneByLogin(String login); + + @EntityGraph(attributePaths = "authorities") + Optional findOneWithAuthoritiesById(Long id); + + @EntityGraph(attributePaths = "authorities") + Optional findOneWithAuthoritiesByLogin(String login); + + @EntityGraph(attributePaths = "authorities") + Optional findOneWithAuthoritiesByEmailIgnoreCase(String email); + + Page findAllByLoginNot(Pageable pageable, String login); +} diff --git a/src/main/java/org/securityrat/casemanagement/repository/package-info.java b/src/main/java/org/securityrat/casemanagement/repository/package-info.java new file mode 100644 index 0000000..7fe0ed7 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/repository/package-info.java @@ -0,0 +1,4 @@ +/** + * Spring Data JPA repositories. + */ +package org.securityrat.casemanagement.repository; diff --git a/src/main/java/org/securityrat/casemanagement/security/AuthoritiesConstants.java b/src/main/java/org/securityrat/casemanagement/security/AuthoritiesConstants.java new file mode 100644 index 0000000..924381d --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/security/AuthoritiesConstants.java @@ -0,0 +1,16 @@ +package org.securityrat.casemanagement.security; + +/** + * Constants for Spring Security authorities. + */ +public final class AuthoritiesConstants { + + public static final String ADMIN = "ROLE_ADMIN"; + + public static final String USER = "ROLE_USER"; + + public static final String ANONYMOUS = "ROLE_ANONYMOUS"; + + private AuthoritiesConstants() { + } +} diff --git a/src/main/java/org/securityrat/casemanagement/security/SecurityUtils.java b/src/main/java/org/securityrat/casemanagement/security/SecurityUtils.java new file mode 100644 index 0000000..42b807c --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/security/SecurityUtils.java @@ -0,0 +1,100 @@ +package org.securityrat.casemanagement.security; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Utility class for Spring Security. + */ +public final class SecurityUtils { + + private SecurityUtils() { + } + + /** + * Get the login of the current user. + * + * @return the login of the current user. + */ + public static Optional getCurrentUserLogin() { + SecurityContext securityContext = SecurityContextHolder.getContext(); + return Optional.ofNullable(securityContext.getAuthentication()) + .map(authentication -> { + if (authentication.getPrincipal() instanceof UserDetails) { + UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal(); + return springSecurityUser.getUsername(); + } else if (authentication instanceof JwtAuthenticationToken) { + return (String) ((JwtAuthenticationToken)authentication).getToken().getClaims().get("preferred_username"); + } else if (authentication.getPrincipal() instanceof DefaultOidcUser) { + Map attributes = ((DefaultOidcUser) authentication.getPrincipal()).getAttributes(); + if (attributes.containsKey("preferred_username")) { + return (String) attributes.get("preferred_username"); + } + } else if (authentication.getPrincipal() instanceof String) { + return (String) authentication.getPrincipal(); + } + return null; + }); + } + + /** + * Check if a user is authenticated. + * + * @return true if the user is authenticated, false otherwise. + */ + public static boolean isAuthenticated() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + return authentication != null && + getAuthorities(authentication).noneMatch(AuthoritiesConstants.ANONYMOUS::equals); + } + + /** + * If the current user has a specific authority (security role). + *

+ * The name of this method comes from the {@code isUserInRole()} method in the Servlet API. + * + * @param authority the authority to check. + * @return true if the current user has the authority, false otherwise. + */ + public static boolean isCurrentUserInRole(String authority) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + return authentication != null && + getAuthorities(authentication).anyMatch(authority::equals); + } + + private static Stream getAuthorities(Authentication authentication) { + Collection authorities = authentication instanceof JwtAuthenticationToken ? + extractAuthorityFromClaims(((JwtAuthenticationToken) authentication).getToken().getClaims()) + : authentication.getAuthorities(); + return authorities.stream() + .map(GrantedAuthority::getAuthority); + } + + public static List extractAuthorityFromClaims(Map claims) { + return mapRolesToGrantedAuthorities( + getRolesFromClaims(claims)); + } + + @SuppressWarnings("unchecked") + private static Collection getRolesFromClaims(Map claims) { + return (Collection) claims.getOrDefault("groups", + claims.getOrDefault("roles", new ArrayList<>())); + } + + private static List mapRolesToGrantedAuthorities(Collection roles) { + return roles.stream() + .filter(role -> role.startsWith("ROLE_")) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/security/SpringSecurityAuditorAware.java b/src/main/java/org/securityrat/casemanagement/security/SpringSecurityAuditorAware.java new file mode 100644 index 0000000..02b0056 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/security/SpringSecurityAuditorAware.java @@ -0,0 +1,20 @@ +package org.securityrat.casemanagement.security; + +import org.securityrat.casemanagement.config.Constants; + +import java.util.Optional; + +import org.springframework.data.domain.AuditorAware; +import org.springframework.stereotype.Component; + +/** + * Implementation of {@link AuditorAware} based on Spring Security. + */ +@Component +public class SpringSecurityAuditorAware implements AuditorAware { + + @Override + public Optional getCurrentAuditor() { + return Optional.of(SecurityUtils.getCurrentUserLogin().orElse(Constants.SYSTEM_ACCOUNT)); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/security/oauth2/AudienceValidator.java b/src/main/java/org/securityrat/casemanagement/security/oauth2/AudienceValidator.java new file mode 100644 index 0000000..d403a6b --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/security/oauth2/AudienceValidator.java @@ -0,0 +1,33 @@ +package org.securityrat.casemanagement.security.oauth2; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.OAuth2TokenValidator; +import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.util.Assert; + +import java.util.List; + +public class AudienceValidator implements OAuth2TokenValidator { + private final Logger log = LoggerFactory.getLogger(AudienceValidator.class); + private OAuth2Error error = new OAuth2Error("invalid_token", "The required audience is missing", null); + + private final List allowedAudience; + + public AudienceValidator(List allowedAudience) { + Assert.notEmpty(allowedAudience, "Allowed audience should not be null or empty."); + this.allowedAudience = allowedAudience; + } + + public OAuth2TokenValidatorResult validate(Jwt jwt) { + List audience = jwt.getAudience(); + if(audience.stream().anyMatch(aud -> allowedAudience.contains(aud))) { + return OAuth2TokenValidatorResult.success(); + } else { + log.warn("Invalid audience: {}", audience); + return OAuth2TokenValidatorResult.failure(error); + } + } +} diff --git a/src/main/java/org/securityrat/casemanagement/security/oauth2/AuthorizationHeaderUtil.java b/src/main/java/org/securityrat/casemanagement/security/oauth2/AuthorizationHeaderUtil.java new file mode 100644 index 0000000..574386c --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/security/oauth2/AuthorizationHeaderUtil.java @@ -0,0 +1,157 @@ +package org.securityrat.casemanagement.security.oauth2; + +import java.net.URI; +import java.time.Duration; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.securityrat.casemanagement.security.oauth2.OAuthIdpTokenResponseDTO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.FormHttpMessageConverter; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler; +import org.springframework.security.oauth2.core.*; +import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +@Component +public class AuthorizationHeaderUtil { + + private final OAuth2AuthorizedClientService clientService; + private final RestTemplateBuilder restTemplateBuilder; + private final Logger log = LoggerFactory.getLogger(AuthorizationHeaderUtil.class); + + public AuthorizationHeaderUtil(OAuth2AuthorizedClientService clientService, RestTemplateBuilder restTemplateBuilder) { + this.clientService = clientService; + this.restTemplateBuilder = restTemplateBuilder; + } + + public Optional getAuthorizationHeader() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication instanceof OAuth2AuthenticationToken) { + OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication; + String name = oauthToken.getName(); + String registrationId = oauthToken.getAuthorizedClientRegistrationId(); + OAuth2AuthorizedClient client = clientService.loadAuthorizedClient( + registrationId, + name); + + if (null == client) { + throw new OAuth2AuthorizationException(new OAuth2Error("access_denied", "The token is expired", null)); + } + OAuth2AccessToken accessToken = client.getAccessToken(); + + if (accessToken != null) { + String tokenType = accessToken.getTokenType().getValue(); + String accessTokenValue = accessToken.getTokenValue(); + if (isExpired(accessToken)) { + log.info("AccessToken expired, refreshing automatically"); + accessTokenValue = refreshToken(client, oauthToken); + if (null == accessTokenValue) { + SecurityContextHolder.getContext().setAuthentication(null); + throw new OAuth2AuthorizationException(new OAuth2Error("access_denied", "The token is expired", null)); + } + } + String authorizationHeaderValue = String.format("%s %s", tokenType, accessTokenValue); + return Optional.of(authorizationHeaderValue); + } + + } else if (authentication instanceof JwtAuthenticationToken) { + JwtAuthenticationToken accessToken = (JwtAuthenticationToken) authentication; + String tokenValue = accessToken.getToken().getTokenValue(); + String authorizationHeaderValue = String.format("%s %s", OAuth2AccessToken.TokenType.BEARER.getValue(), tokenValue); + return Optional.of(authorizationHeaderValue); + } + return Optional.empty(); + } + + private String refreshToken(OAuth2AuthorizedClient client, OAuth2AuthenticationToken oauthToken) { + OAuth2AccessTokenResponse atr = refreshTokenClient(client); + if (atr == null || atr.getAccessToken() == null) { + log.info("Failed to refresh token for user"); + return null; + } + + OAuth2RefreshToken refreshToken = atr.getRefreshToken() != null ? atr.getRefreshToken(): client.getRefreshToken(); + OAuth2AuthorizedClient updatedClient = new OAuth2AuthorizedClient( + client.getClientRegistration(), + client.getPrincipalName(), + atr.getAccessToken(), + refreshToken + ); + + clientService.saveAuthorizedClient(updatedClient, oauthToken); + return atr.getAccessToken().getTokenValue(); + } + + private OAuth2AccessTokenResponse refreshTokenClient(OAuth2AuthorizedClient currentClient) { + + MultiValueMap formParameters = new LinkedMultiValueMap<>(); + formParameters.add(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.REFRESH_TOKEN.getValue()); + formParameters.add(OAuth2ParameterNames.REFRESH_TOKEN, currentClient.getRefreshToken().getTokenValue()); + formParameters.add(OAuth2ParameterNames.CLIENT_ID, currentClient.getClientRegistration().getClientId()); + RequestEntity requestEntity = RequestEntity + .post(URI.create(currentClient.getClientRegistration().getProviderDetails().getTokenUri())) + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .body(formParameters); + try { + RestTemplate r = restTemplate(currentClient.getClientRegistration().getClientId(), currentClient.getClientRegistration().getClientSecret()); + ResponseEntity responseEntity = r.exchange(requestEntity, OAuthIdpTokenResponseDTO.class); + return toOAuth2AccessTokenResponse(responseEntity.getBody()); + } catch (OAuth2AuthorizationException e) { + log.error("Unable to refresh token", e); + throw new OAuth2AuthenticationException(e.getError(), e); + } + } + + private OAuth2AccessTokenResponse toOAuth2AccessTokenResponse(OAuthIdpTokenResponseDTO oAuthIdpResponse) { + Map additionalParameters = new HashMap<>(); + additionalParameters.put("id_token", oAuthIdpResponse.getIdToken()); + additionalParameters.put("not-before-policy", oAuthIdpResponse.getNotBefore()); + additionalParameters.put("refresh_expires_in", oAuthIdpResponse.getRefreshExpiresIn()); + additionalParameters.put("session_state", oAuthIdpResponse.getSessionState()); + return OAuth2AccessTokenResponse.withToken(oAuthIdpResponse.getAccessToken()) + .expiresIn(oAuthIdpResponse.getExpiresIn()) + .refreshToken(oAuthIdpResponse.getRefreshToken()) + .scopes(Pattern.compile("\\s").splitAsStream(oAuthIdpResponse.getScope()).collect(Collectors.toSet())) + .tokenType(OAuth2AccessToken.TokenType.BEARER) + .additionalParameters(additionalParameters) + .build(); + } + + private RestTemplate restTemplate(String clientId, String clientSecret) { + return restTemplateBuilder + .additionalMessageConverters( + new FormHttpMessageConverter(), + new OAuth2AccessTokenResponseHttpMessageConverter()) + .errorHandler(new OAuth2ErrorResponseErrorHandler()) + .basicAuthentication(clientId, clientSecret) + .build(); + } + + private boolean isExpired(OAuth2AccessToken accessToken) { + Instant now = Instant.now(); + Instant expiresAt = accessToken.getExpiresAt(); + return now.isAfter(expiresAt.minus(Duration.ofMinutes(1L))); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/security/oauth2/JwtAuthorityExtractor.java b/src/main/java/org/securityrat/casemanagement/security/oauth2/JwtAuthorityExtractor.java new file mode 100644 index 0000000..41cbc55 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/security/oauth2/JwtAuthorityExtractor.java @@ -0,0 +1,21 @@ +package org.securityrat.casemanagement.security.oauth2; + +import org.securityrat.casemanagement.security.SecurityUtils; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; +import org.springframework.stereotype.Component; + +import java.util.Collection; + +@Component +public class JwtAuthorityExtractor extends JwtAuthenticationConverter { + + public JwtAuthorityExtractor() { + } + + @Override + protected Collection extractAuthorities(Jwt jwt) { + return SecurityUtils.extractAuthorityFromClaims(jwt.getClaims()); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/security/oauth2/OAuthIdpTokenResponseDTO.java b/src/main/java/org/securityrat/casemanagement/security/oauth2/OAuthIdpTokenResponseDTO.java new file mode 100644 index 0000000..f6d1b4d --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/security/oauth2/OAuthIdpTokenResponseDTO.java @@ -0,0 +1,141 @@ +package org.securityrat.casemanagement.security.oauth2; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.io.Serializable; +import java.util.UUID; + +public class OAuthIdpTokenResponseDTO implements Serializable { + + @JsonProperty("token_type") + private String tokenType; + + private String scope; + + @JsonProperty("expires_in") + private Long expiresIn; + + @JsonProperty("ext_expires_in") + private Long extExpiresIn; + + @JsonProperty("expires_on") + private Long expiresOn; + + @JsonProperty("not-before-policy") + private Long notBefore; + + private UUID resource; + + @JsonProperty("access_token") + private String accessToken; + + @JsonProperty("refresh_token") + private String refreshToken; + + @JsonProperty("id_token") + private String idToken; + + @JsonProperty("session_state") + private String sessionState; + + @JsonProperty("refresh_expires_in") + private String refreshExpiresIn; + + public OAuthIdpTokenResponseDTO() {} + + public String getRefreshExpiresIn() { + return refreshExpiresIn; + } + + public void setRefreshExpiresIn(String refreshExpiresIn) { + this.refreshExpiresIn = refreshExpiresIn; + } + + public String getSessionState() { + return sessionState; + } + + public void setSessionState(String sessionState) { + this.sessionState = sessionState; + } + + public String getTokenType() { + return tokenType; + } + + public void setTokenType(String tokenType) { + this.tokenType = tokenType; + } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + + public Long getExpiresIn() { + return expiresIn; + } + + public void setExpiresIn(Long expiresIn) { + this.expiresIn = expiresIn; + } + + public Long getExtExpiresIn() { + return extExpiresIn; + } + + public void setExtExpiresIn(Long extExpiresIn) { + this.extExpiresIn = extExpiresIn; + } + + public Long getExpiresOn() { + return expiresOn; + } + + public void setExpiresOn(Long expiresOn) { + this.expiresOn = expiresOn; + } + + public Long getNotBefore() { + return notBefore; + } + + public void setNotBefore(Long notBefore) { + this.notBefore = notBefore; + } + + public UUID getResource() { + return resource; + } + + public void setResource(UUID resource) { + this.resource = resource; + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public String getIdToken() { + return idToken; + } + + public void setIdToken(String idToken) { + this.idToken = idToken; + } +} diff --git a/src/main/java/org/securityrat/casemanagement/security/package-info.java b/src/main/java/org/securityrat/casemanagement/security/package-info.java new file mode 100644 index 0000000..884d4c7 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/security/package-info.java @@ -0,0 +1,4 @@ +/** + * Spring Security configuration. + */ +package org.securityrat.casemanagement.security; diff --git a/src/main/java/org/securityrat/casemanagement/service/AuditEventService.java b/src/main/java/org/securityrat/casemanagement/service/AuditEventService.java new file mode 100644 index 0000000..4969ab2 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/service/AuditEventService.java @@ -0,0 +1,74 @@ +package org.securityrat.casemanagement.service; + +import io.github.jhipster.config.JHipsterProperties; +import org.securityrat.casemanagement.config.audit.AuditEventConverter; +import org.securityrat.casemanagement.repository.PersistenceAuditEventRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Optional; + +/** + * Service for managing audit events. + *

+ * This is the default implementation to support SpringBoot Actuator {@code AuditEventRepository}. + */ +@Service +@Transactional +public class AuditEventService { + + private final Logger log = LoggerFactory.getLogger(AuditEventService.class); + + private final JHipsterProperties jHipsterProperties; + + private final PersistenceAuditEventRepository persistenceAuditEventRepository; + + private final AuditEventConverter auditEventConverter; + + public AuditEventService( + PersistenceAuditEventRepository persistenceAuditEventRepository, + AuditEventConverter auditEventConverter, JHipsterProperties jhipsterProperties) { + + this.persistenceAuditEventRepository = persistenceAuditEventRepository; + this.auditEventConverter = auditEventConverter; + this.jHipsterProperties = jhipsterProperties; + } + + /** + * Old audit events should be automatically deleted after 30 days. + * + * This is scheduled to get fired at 12:00 (am). + */ + @Scheduled(cron = "0 0 12 * * ?") + public void removeOldAuditEvents() { + persistenceAuditEventRepository + .findByAuditEventDateBefore(Instant.now().minus(jHipsterProperties.getAuditEvents().getRetentionPeriod(), ChronoUnit.DAYS)) + .forEach(auditEvent -> { + log.debug("Deleting audit data {}", auditEvent); + persistenceAuditEventRepository.delete(auditEvent); + }); + } + + public Page findAll(Pageable pageable) { + return persistenceAuditEventRepository.findAll(pageable) + .map(auditEventConverter::convertToAuditEvent); + } + + public Page findByDates(Instant fromDate, Instant toDate, Pageable pageable) { + return persistenceAuditEventRepository.findAllByAuditEventDateBetween(fromDate, toDate, pageable) + .map(auditEventConverter::convertToAuditEvent); + } + + public Optional find(Long id) { + return persistenceAuditEventRepository.findById(id) + .map(auditEventConverter::convertToAuditEvent); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/service/UserService.java b/src/main/java/org/securityrat/casemanagement/service/UserService.java new file mode 100644 index 0000000..3c92469 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/service/UserService.java @@ -0,0 +1,252 @@ +package org.securityrat.casemanagement.service; + +import org.securityrat.casemanagement.config.Constants; +import org.securityrat.casemanagement.domain.Authority; +import org.securityrat.casemanagement.domain.User; +import org.securityrat.casemanagement.repository.AuthorityRepository; +import org.securityrat.casemanagement.repository.UserRepository; +import org.securityrat.casemanagement.security.SecurityUtils; +import org.securityrat.casemanagement.service.dto.UserDTO; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Instant; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Service class for managing users. + */ +@Service +@Transactional +public class UserService { + + private final Logger log = LoggerFactory.getLogger(UserService.class); + + private final UserRepository userRepository; + + private final AuthorityRepository authorityRepository; + + public UserService(UserRepository userRepository, AuthorityRepository authorityRepository) { + this.userRepository = userRepository; + this.authorityRepository = authorityRepository; + } + + /** + * Update basic information (first name, last name, email, language) for the current user. + * + * @param firstName first name of user. + * @param lastName last name of user. + * @param email email id of user. + * @param langKey language key. + * @param imageUrl image URL of user. + */ + public void updateUser(String firstName, String lastName, String email, String langKey, String imageUrl) { + SecurityUtils.getCurrentUserLogin() + .flatMap(userRepository::findOneByLogin) + .ifPresent(user -> { + user.setFirstName(firstName); + user.setLastName(lastName); + if (email != null) { + user.setEmail(email.toLowerCase()); + } + user.setLangKey(langKey); + user.setImageUrl(imageUrl); + log.debug("Changed Information for User: {}", user); + }); + } + + /** + * Update all information for a specific user, and return the modified user. + * + * @param userDTO user to update. + * @return updated user. + */ + public Optional updateUser(UserDTO userDTO) { + return Optional.of(userRepository + .findById(userDTO.getId())) + .filter(Optional::isPresent) + .map(Optional::get) + .map(user -> { + user.setLogin(userDTO.getLogin().toLowerCase()); + user.setFirstName(userDTO.getFirstName()); + user.setLastName(userDTO.getLastName()); + if (userDTO.getEmail() != null) { + user.setEmail(userDTO.getEmail().toLowerCase()); + } + user.setImageUrl(userDTO.getImageUrl()); + user.setActivated(userDTO.isActivated()); + user.setLangKey(userDTO.getLangKey()); + Set managedAuthorities = user.getAuthorities(); + managedAuthorities.clear(); + userDTO.getAuthorities().stream() + .map(authorityRepository::findById) + .filter(Optional::isPresent) + .map(Optional::get) + .forEach(managedAuthorities::add); + log.debug("Changed Information for User: {}", user); + return user; + }) + .map(UserDTO::new); + } + + public void deleteUser(String login) { + userRepository.findOneByLogin(login).ifPresent(user -> { + userRepository.delete(user); + log.debug("Deleted User: {}", user); + }); + } + + @Transactional(readOnly = true) + public Page getAllManagedUsers(Pageable pageable) { + return userRepository.findAllByLoginNot(pageable, Constants.ANONYMOUS_USER).map(UserDTO::new); + } + + @Transactional(readOnly = true) + public Optional getUserWithAuthoritiesByLogin(String login) { + return userRepository.findOneWithAuthoritiesByLogin(login); + } + + @Transactional(readOnly = true) + public Optional getUserWithAuthorities(Long id) { + return userRepository.findOneWithAuthoritiesById(id); + } + + @Transactional(readOnly = true) + public Optional getUserWithAuthorities() { + return SecurityUtils.getCurrentUserLogin().flatMap(userRepository::findOneWithAuthoritiesByLogin); + } + + /** + * Gets a list of all the authorities. + * @return a list of all the authorities. + */ + public List getAuthorities() { + return authorityRepository.findAll().stream().map(Authority::getName).collect(Collectors.toList()); + } + + private User syncUserWithIdP(Map details, User user) { + // save authorities in to sync user roles/groups between IdP and JHipster's local database + Collection dbAuthorities = getAuthorities(); + Collection userAuthorities = + user.getAuthorities().stream().map(Authority::getName).collect(Collectors.toList()); + for (String authority : userAuthorities) { + if (!dbAuthorities.contains(authority)) { + log.debug("Saving authority '{}' in local database", authority); + Authority authorityToSave = new Authority(); + authorityToSave.setName(authority); + authorityRepository.save(authorityToSave); + } + } + // save account in to sync users between IdP and JHipster's local database + Optional existingUser = userRepository.findOneByLogin(user.getLogin()); + if (existingUser.isPresent()) { + // if IdP sends last updated information, use it to determine if an update should happen + if (details.get("updated_at") != null) { + Instant dbModifiedDate = existingUser.get().getLastModifiedDate(); + Instant idpModifiedDate = new Date(Long.valueOf((Integer) details.get("updated_at"))).toInstant(); + if (idpModifiedDate.isAfter(dbModifiedDate)) { + log.debug("Updating user '{}' in local database", user.getLogin()); + updateUser(user.getFirstName(), user.getLastName(), user.getEmail(), + user.getLangKey(), user.getImageUrl()); + } + // no last updated info, blindly update + } else { + log.debug("Updating user '{}' in local database", user.getLogin()); + updateUser(user.getFirstName(), user.getLastName(), user.getEmail(), + user.getLangKey(), user.getImageUrl()); + } + } else { + log.debug("Saving user '{}' in local database", user.getLogin()); + userRepository.save(user); + } + return user; + } + + /** + * Returns the user from an OAuth 2.0 login or resource server with JWT. + * Synchronizes the user in the local repository. + * + * @param authToken the authentication token. + * @return the user from the authentication. + */ + public UserDTO getUserFromAuthentication(AbstractAuthenticationToken authToken) { + Map attributes; + if (authToken instanceof OAuth2AuthenticationToken) { + attributes = ((OAuth2AuthenticationToken) authToken).getPrincipal().getAttributes(); + } else if (authToken instanceof JwtAuthenticationToken) { + attributes = ((JwtAuthenticationToken) authToken).getTokenAttributes(); + } else { + throw new IllegalArgumentException("AuthenticationToken is not OAuth2 or JWT!"); + } + User user = getUser(attributes); + user.setAuthorities(authToken.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .map(authority -> { + Authority auth = new Authority(); + auth.setName(authority); + return auth; + }) + .collect(Collectors.toSet())); + return new UserDTO(syncUserWithIdP(attributes, user)); + } + + private static User getUser(Map details) { + User user = new User(); + // handle resource server JWT, where sub claim is email and uid is ID + if (details.get("uid") != null) { + user.setId((String) details.get("uid")); + user.setLogin((String) details.get("sub")); + } else { + user.setId((String) details.get("sub")); + } + if (details.get("preferred_username") != null) { + user.setLogin(((String) details.get("preferred_username")).toLowerCase()); + } else if (user.getLogin() == null) { + user.setLogin(user.getId()); + } + if (details.get("given_name") != null) { + user.setFirstName((String) details.get("given_name")); + } + if (details.get("family_name") != null) { + user.setLastName((String) details.get("family_name")); + } + if (details.get("email_verified") != null) { + user.setActivated((Boolean) details.get("email_verified")); + } + if (details.get("email") != null) { + user.setEmail(((String) details.get("email")).toLowerCase()); + } else { + user.setEmail((String) details.get("sub")); + } + if (details.get("langKey") != null) { + user.setLangKey((String) details.get("langKey")); + } else if (details.get("locale") != null) { + // trim off country code if it exists + String locale = (String) details.get("locale"); + if (locale.contains("_")) { + locale = locale.substring(0, locale.indexOf("_")); + } else if (locale.contains("-")) { + locale = locale.substring(0, locale.indexOf("-")); + } + user.setLangKey(locale.toLowerCase()); + } else { + // set langKey to default if not specified by IdP + user.setLangKey(Constants.DEFAULT_LANGUAGE); + } + if (details.get("picture") != null) { + user.setImageUrl((String) details.get("picture")); + } + user.setActivated(true); + return user; + } +} diff --git a/src/main/java/org/securityrat/casemanagement/service/dto/UserDTO.java b/src/main/java/org/securityrat/casemanagement/service/dto/UserDTO.java new file mode 100644 index 0000000..6a3a0a0 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/service/dto/UserDTO.java @@ -0,0 +1,196 @@ +package org.securityrat.casemanagement.service.dto; + +import org.securityrat.casemanagement.config.Constants; + +import org.securityrat.casemanagement.domain.Authority; +import org.securityrat.casemanagement.domain.User; + +import javax.validation.constraints.*; +import java.time.Instant; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * A DTO representing a user, with his authorities. + */ +public class UserDTO { + + private String id; + + @NotBlank + @Pattern(regexp = Constants.LOGIN_REGEX) + @Size(min = 1, max = 50) + private String login; + + @Size(max = 50) + private String firstName; + + @Size(max = 50) + private String lastName; + + @Email + @Size(min = 5, max = 254) + private String email; + + @Size(max = 256) + private String imageUrl; + + private boolean activated = false; + + @Size(min = 2, max = 10) + private String langKey; + + private String createdBy; + + private Instant createdDate; + + private String lastModifiedBy; + + private Instant lastModifiedDate; + + private Set authorities; + + public UserDTO() { + // Empty constructor needed for Jackson. + } + + public UserDTO(User user) { + this.id = user.getId(); + this.login = user.getLogin(); + this.firstName = user.getFirstName(); + this.lastName = user.getLastName(); + this.email = user.getEmail(); + this.activated = user.getActivated(); + this.imageUrl = user.getImageUrl(); + this.langKey = user.getLangKey(); + this.createdBy = user.getCreatedBy(); + this.createdDate = user.getCreatedDate(); + this.lastModifiedBy = user.getLastModifiedBy(); + this.lastModifiedDate = user.getLastModifiedDate(); + this.authorities = user.getAuthorities().stream() + .map(Authority::getName) + .collect(Collectors.toSet()); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public boolean isActivated() { + return activated; + } + + public void setActivated(boolean activated) { + this.activated = activated; + } + + public String getLangKey() { + return langKey; + } + + public void setLangKey(String langKey) { + this.langKey = langKey; + } + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + public Instant getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(Instant createdDate) { + this.createdDate = createdDate; + } + + public String getLastModifiedBy() { + return lastModifiedBy; + } + + public void setLastModifiedBy(String lastModifiedBy) { + this.lastModifiedBy = lastModifiedBy; + } + + public Instant getLastModifiedDate() { + return lastModifiedDate; + } + + public void setLastModifiedDate(Instant lastModifiedDate) { + this.lastModifiedDate = lastModifiedDate; + } + + public Set getAuthorities() { + return authorities; + } + + public void setAuthorities(Set authorities) { + this.authorities = authorities; + } + + @Override + public String toString() { + return "UserDTO{" + + "login='" + login + '\'' + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", email='" + email + '\'' + + ", imageUrl='" + imageUrl + '\'' + + ", activated=" + activated + + ", langKey='" + langKey + '\'' + + ", createdBy=" + createdBy + + ", createdDate=" + createdDate + + ", lastModifiedBy='" + lastModifiedBy + '\'' + + ", lastModifiedDate=" + lastModifiedDate + + ", authorities=" + authorities + + "}"; + } +} diff --git a/src/main/java/org/securityrat/casemanagement/service/dto/package-info.java b/src/main/java/org/securityrat/casemanagement/service/dto/package-info.java new file mode 100644 index 0000000..6df8a6b --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/service/dto/package-info.java @@ -0,0 +1,4 @@ +/** + * Data Transfer Objects. + */ +package org.securityrat.casemanagement.service.dto; diff --git a/src/main/java/org/securityrat/casemanagement/service/mapper/UserMapper.java b/src/main/java/org/securityrat/casemanagement/service/mapper/UserMapper.java new file mode 100644 index 0000000..53456ba --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/service/mapper/UserMapper.java @@ -0,0 +1,81 @@ +package org.securityrat.casemanagement.service.mapper; + +import org.securityrat.casemanagement.domain.Authority; +import org.securityrat.casemanagement.domain.User; +import org.securityrat.casemanagement.service.dto.UserDTO; + +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * Mapper for the entity {@link User} and its DTO called {@link UserDTO}. + * + * Normal mappers are generated using MapStruct, this one is hand-coded as MapStruct + * support is still in beta, and requires a manual step with an IDE. + */ +@Service +public class UserMapper { + + public List usersToUserDTOs(List users) { + return users.stream() + .filter(Objects::nonNull) + .map(this::userToUserDTO) + .collect(Collectors.toList()); + } + + public UserDTO userToUserDTO(User user) { + return new UserDTO(user); + } + + public List userDTOsToUsers(List userDTOs) { + return userDTOs.stream() + .filter(Objects::nonNull) + .map(this::userDTOToUser) + .collect(Collectors.toList()); + } + + public User userDTOToUser(UserDTO userDTO) { + if (userDTO == null) { + return null; + } else { + User user = new User(); + user.setId(userDTO.getId()); + user.setLogin(userDTO.getLogin()); + user.setFirstName(userDTO.getFirstName()); + user.setLastName(userDTO.getLastName()); + user.setEmail(userDTO.getEmail()); + user.setImageUrl(userDTO.getImageUrl()); + user.setActivated(userDTO.isActivated()); + user.setLangKey(userDTO.getLangKey()); + Set authorities = this.authoritiesFromStrings(userDTO.getAuthorities()); + user.setAuthorities(authorities); + return user; + } + } + + + private Set authoritiesFromStrings(Set authoritiesAsString) { + Set authorities = new HashSet<>(); + + if(authoritiesAsString != null){ + authorities = authoritiesAsString.stream().map(string -> { + Authority auth = new Authority(); + auth.setName(string); + return auth; + }).collect(Collectors.toSet()); + } + + return authorities; + } + + public User userFromId(String id) { + if (id == null) { + return null; + } + User user = new User(); + user.setId(id); + return user; + } +} diff --git a/src/main/java/org/securityrat/casemanagement/service/mapper/package-info.java b/src/main/java/org/securityrat/casemanagement/service/mapper/package-info.java new file mode 100644 index 0000000..0c7c9ae --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/service/mapper/package-info.java @@ -0,0 +1,4 @@ +/** + * MapStruct mappers for mapping domain objects and Data Transfer Objects. + */ +package org.securityrat.casemanagement.service.mapper; diff --git a/src/main/java/org/securityrat/casemanagement/service/package-info.java b/src/main/java/org/securityrat/casemanagement/service/package-info.java new file mode 100644 index 0000000..059eba9 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/service/package-info.java @@ -0,0 +1,4 @@ +/** + * Service layer beans. + */ +package org.securityrat.casemanagement.service; diff --git a/src/main/java/org/securityrat/casemanagement/web/rest/AccessTokenResource.java b/src/main/java/org/securityrat/casemanagement/web/rest/AccessTokenResource.java new file mode 100644 index 0000000..1f686c6 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/web/rest/AccessTokenResource.java @@ -0,0 +1,143 @@ +package org.securityrat.casemanagement.web.rest; + +import org.securityrat.casemanagement.domain.AccessToken; +import org.securityrat.casemanagement.repository.AccessTokenRepository; +import org.securityrat.casemanagement.repository.UserRepository; +import org.securityrat.casemanagement.web.rest.errors.BadRequestAlertException; + +import io.github.jhipster.web.util.HeaderUtil; +import io.github.jhipster.web.util.PaginationUtil; +import io.github.jhipster.web.util.ResponseUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.net.URI; +import java.net.URISyntaxException; + +import java.util.List; +import java.util.Optional; + +/** + * REST controller for managing {@link org.securityrat.casemanagement.domain.AccessToken}. + */ +@RestController +@RequestMapping("/api") +public class AccessTokenResource { + + private final Logger log = LoggerFactory.getLogger(AccessTokenResource.class); + + private static final String ENTITY_NAME = "caseManagementAccessToken"; + + @Value("${jhipster.clientApp.name}") + private String applicationName; + + private final AccessTokenRepository accessTokenRepository; + + private final UserRepository userRepository; + + public AccessTokenResource(AccessTokenRepository accessTokenRepository, UserRepository userRepository) { + this.accessTokenRepository = accessTokenRepository; + this.userRepository = userRepository; + } + + /** + * {@code POST /access-tokens} : Create a new accessToken. + * + * @param accessToken the accessToken to create. + * @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new accessToken, or with status {@code 400 (Bad Request)} if the accessToken has already an ID. + * @throws URISyntaxException if the Location URI syntax is incorrect. + */ + @PostMapping("/access-tokens") + public ResponseEntity createAccessToken(@Valid @RequestBody AccessToken accessToken) throws URISyntaxException { + log.debug("REST request to save AccessToken : {}", accessToken); + if (accessToken.getId() != null) { + throw new BadRequestAlertException("A new accessToken cannot already have an ID", ENTITY_NAME, "idexists"); + } + + if (accessToken.getUser() != null) { + // Save user in case it's new and only exists in gateway + userRepository.save(accessToken.getUser()); + } + AccessToken result = accessTokenRepository.save(accessToken); + return ResponseEntity.created(new URI("/api/access-tokens/" + result.getId())) + .headers(HeaderUtil.createEntityCreationAlert(applicationName, false, ENTITY_NAME, result.getId().toString())) + .body(result); + } + + /** + * {@code PUT /access-tokens} : Updates an existing accessToken. + * + * @param accessToken the accessToken to update. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated accessToken, + * or with status {@code 400 (Bad Request)} if the accessToken is not valid, + * or with status {@code 500 (Internal Server Error)} if the accessToken couldn't be updated. + * @throws URISyntaxException if the Location URI syntax is incorrect. + */ + @PutMapping("/access-tokens") + public ResponseEntity updateAccessToken(@Valid @RequestBody AccessToken accessToken) throws URISyntaxException { + log.debug("REST request to update AccessToken : {}", accessToken); + if (accessToken.getId() == null) { + throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull"); + } + + if (accessToken.getUser() != null) { + // Save user in case it's new and only exists in gateway + userRepository.save(accessToken.getUser()); + } + AccessToken result = accessTokenRepository.save(accessToken); + return ResponseEntity.ok() + .headers(HeaderUtil.createEntityUpdateAlert(applicationName, false, ENTITY_NAME, accessToken.getId().toString())) + .body(result); + } + + /** + * {@code GET /access-tokens} : get all the accessTokens. + * + + * @param pageable the pagination information. + + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of accessTokens in body. + */ + @GetMapping("/access-tokens") + public ResponseEntity> getAllAccessTokens(Pageable pageable) { + log.debug("REST request to get a page of AccessTokens"); + Page page = accessTokenRepository.findAll(pageable); + HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page); + return ResponseEntity.ok().headers(headers).body(page.getContent()); + } + + /** + * {@code GET /access-tokens/:id} : get the "id" accessToken. + * + * @param id the id of the accessToken to retrieve. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the accessToken, or with status {@code 404 (Not Found)}. + */ + @GetMapping("/access-tokens/{id}") + public ResponseEntity getAccessToken(@PathVariable Long id) { + log.debug("REST request to get AccessToken : {}", id); + Optional accessToken = accessTokenRepository.findById(id); + return ResponseUtil.wrapOrNotFound(accessToken); + } + + /** + * {@code DELETE /access-tokens/:id} : delete the "id" accessToken. + * + * @param id the id of the accessToken to delete. + * @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}. + */ + @DeleteMapping("/access-tokens/{id}") + public ResponseEntity deleteAccessToken(@PathVariable Long id) { + log.debug("REST request to delete AccessToken : {}", id); + accessTokenRepository.deleteById(id); + return ResponseEntity.noContent().headers(HeaderUtil.createEntityDeletionAlert(applicationName, false, ENTITY_NAME, id.toString())).build(); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/web/rest/AuditResource.java b/src/main/java/org/securityrat/casemanagement/web/rest/AuditResource.java new file mode 100644 index 0000000..277e3e4 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/web/rest/AuditResource.java @@ -0,0 +1,79 @@ +package org.securityrat.casemanagement.web.rest; + +import org.securityrat.casemanagement.service.AuditEventService; + +import io.github.jhipster.web.util.PaginationUtil; +import io.github.jhipster.web.util.ResponseUtil; +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.List; + +/** + * REST controller for getting the {@link AuditEvent}s. + */ +@RestController +@RequestMapping("/management/audits") +public class AuditResource { + + private final AuditEventService auditEventService; + + public AuditResource(AuditEventService auditEventService) { + this.auditEventService = auditEventService; + } + + /** + * {@code GET /audits} : get a page of {@link AuditEvent}s. + * + * @param pageable the pagination information. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of {@link AuditEvent}s in body. + */ + @GetMapping + public ResponseEntity> getAll(Pageable pageable) { + Page page = auditEventService.findAll(pageable); + HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page); + return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK); + } + + /** + * {@code GET /audits} : get a page of {@link AuditEvent} between the {@code fromDate} and {@code toDate}. + * + * @param fromDate the start of the time period of {@link AuditEvent} to get. + * @param toDate the end of the time period of {@link AuditEvent} to get. + * @param pageable the pagination information. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of {@link AuditEvent} in body. + */ + @GetMapping(params = {"fromDate", "toDate"}) + public ResponseEntity> getByDates( + @RequestParam(value = "fromDate") LocalDate fromDate, + @RequestParam(value = "toDate") LocalDate toDate, + Pageable pageable) { + + Instant from = fromDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); + Instant to = toDate.atStartOfDay(ZoneId.systemDefault()).plusDays(1).toInstant(); + + Page page = auditEventService.findByDates(from, to, pageable); + HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page); + return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK); + } + + /** + * {@code GET /audits/:id} : get an {@link AuditEvent} by id. + * + * @param id the id of the entity to get. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and the {@link AuditEvent} in body, or status {@code 404 (Not Found)}. + */ + @GetMapping("/{id:.+}") + public ResponseEntity get(@PathVariable Long id) { + return ResponseUtil.wrapOrNotFound(auditEventService.find(id)); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/web/rest/TicketSystemInstanceResource.java b/src/main/java/org/securityrat/casemanagement/web/rest/TicketSystemInstanceResource.java new file mode 100644 index 0000000..c2c2b9a --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/web/rest/TicketSystemInstanceResource.java @@ -0,0 +1,131 @@ +package org.securityrat.casemanagement.web.rest; + +import org.securityrat.casemanagement.domain.TicketSystemInstance; +import org.securityrat.casemanagement.repository.TicketSystemInstanceRepository; +import org.securityrat.casemanagement.web.rest.errors.BadRequestAlertException; + +import io.github.jhipster.web.util.HeaderUtil; +import io.github.jhipster.web.util.PaginationUtil; +import io.github.jhipster.web.util.ResponseUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.net.URI; +import java.net.URISyntaxException; + +import java.util.List; +import java.util.Optional; + +/** + * REST controller for managing {@link org.securityrat.casemanagement.domain.TicketSystemInstance}. + */ +@RestController +@RequestMapping("/api") +@Transactional +public class TicketSystemInstanceResource { + + private final Logger log = LoggerFactory.getLogger(TicketSystemInstanceResource.class); + + private static final String ENTITY_NAME = "caseManagementTicketSystemInstance"; + + @Value("${jhipster.clientApp.name}") + private String applicationName; + + private final TicketSystemInstanceRepository ticketSystemInstanceRepository; + + public TicketSystemInstanceResource(TicketSystemInstanceRepository ticketSystemInstanceRepository) { + this.ticketSystemInstanceRepository = ticketSystemInstanceRepository; + } + + /** + * {@code POST /ticket-system-instances} : Create a new ticketSystemInstance. + * + * @param ticketSystemInstance the ticketSystemInstance to create. + * @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new ticketSystemInstance, or with status {@code 400 (Bad Request)} if the ticketSystemInstance has already an ID. + * @throws URISyntaxException if the Location URI syntax is incorrect. + */ + @PostMapping("/ticket-system-instances") + public ResponseEntity createTicketSystemInstance(@Valid @RequestBody TicketSystemInstance ticketSystemInstance) throws URISyntaxException { + log.debug("REST request to save TicketSystemInstance : {}", ticketSystemInstance); + if (ticketSystemInstance.getId() != null) { + throw new BadRequestAlertException("A new ticketSystemInstance cannot already have an ID", ENTITY_NAME, "idexists"); + } + TicketSystemInstance result = ticketSystemInstanceRepository.save(ticketSystemInstance); + return ResponseEntity.created(new URI("/api/ticket-system-instances/" + result.getId())) + .headers(HeaderUtil.createEntityCreationAlert(applicationName, false, ENTITY_NAME, result.getId().toString())) + .body(result); + } + + /** + * {@code PUT /ticket-system-instances} : Updates an existing ticketSystemInstance. + * + * @param ticketSystemInstance the ticketSystemInstance to update. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated ticketSystemInstance, + * or with status {@code 400 (Bad Request)} if the ticketSystemInstance is not valid, + * or with status {@code 500 (Internal Server Error)} if the ticketSystemInstance couldn't be updated. + * @throws URISyntaxException if the Location URI syntax is incorrect. + */ + @PutMapping("/ticket-system-instances") + public ResponseEntity updateTicketSystemInstance(@Valid @RequestBody TicketSystemInstance ticketSystemInstance) throws URISyntaxException { + log.debug("REST request to update TicketSystemInstance : {}", ticketSystemInstance); + if (ticketSystemInstance.getId() == null) { + throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull"); + } + TicketSystemInstance result = ticketSystemInstanceRepository.save(ticketSystemInstance); + return ResponseEntity.ok() + .headers(HeaderUtil.createEntityUpdateAlert(applicationName, false, ENTITY_NAME, ticketSystemInstance.getId().toString())) + .body(result); + } + + /** + * {@code GET /ticket-system-instances} : get all the ticketSystemInstances. + * + + * @param pageable the pagination information. + + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of ticketSystemInstances in body. + */ + @GetMapping("/ticket-system-instances") + public ResponseEntity> getAllTicketSystemInstances(Pageable pageable) { + log.debug("REST request to get a page of TicketSystemInstances"); + Page page = ticketSystemInstanceRepository.findAll(pageable); + HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page); + return ResponseEntity.ok().headers(headers).body(page.getContent()); + } + + /** + * {@code GET /ticket-system-instances/:id} : get the "id" ticketSystemInstance. + * + * @param id the id of the ticketSystemInstance to retrieve. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the ticketSystemInstance, or with status {@code 404 (Not Found)}. + */ + @GetMapping("/ticket-system-instances/{id}") + public ResponseEntity getTicketSystemInstance(@PathVariable Long id) { + log.debug("REST request to get TicketSystemInstance : {}", id); + Optional ticketSystemInstance = ticketSystemInstanceRepository.findById(id); + return ResponseUtil.wrapOrNotFound(ticketSystemInstance); + } + + /** + * {@code DELETE /ticket-system-instances/:id} : delete the "id" ticketSystemInstance. + * + * @param id the id of the ticketSystemInstance to delete. + * @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}. + */ + @DeleteMapping("/ticket-system-instances/{id}") + public ResponseEntity deleteTicketSystemInstance(@PathVariable Long id) { + log.debug("REST request to delete TicketSystemInstance : {}", id); + ticketSystemInstanceRepository.deleteById(id); + return ResponseEntity.noContent().headers(HeaderUtil.createEntityDeletionAlert(applicationName, false, ENTITY_NAME, id.toString())).build(); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/web/rest/UserResource.java b/src/main/java/org/securityrat/casemanagement/web/rest/UserResource.java new file mode 100644 index 0000000..2679703 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/web/rest/UserResource.java @@ -0,0 +1,102 @@ +package org.securityrat.casemanagement.web.rest; + +import org.securityrat.casemanagement.config.Constants; +import org.securityrat.casemanagement.security.AuthoritiesConstants; +import org.securityrat.casemanagement.service.UserService; +import org.securityrat.casemanagement.service.dto.UserDTO; + +import io.github.jhipster.web.util.HeaderUtil; +import io.github.jhipster.web.util.PaginationUtil; +import io.github.jhipster.web.util.ResponseUtil; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import java.util.*; + +/** + * REST controller for managing users. + *

+ * This class accesses the {@link org.securityrat.casemanagement.domain.User} entity, and needs to fetch its collection of authorities. + *

+ * For a normal use-case, it would be better to have an eager relationship between User and Authority, + * and send everything to the client side: there would be no View Model and DTO, a lot less code, and an outer-join + * which would be good for performance. + *

+ * We use a View Model and a DTO for 3 reasons: + *

    + *
  • We want to keep a lazy association between the user and the authorities, because people will + * quite often do relationships with the user, and we don't want them to get the authorities all + * the time for nothing (for performance reasons). This is the #1 goal: we should not impact our users' + * application because of this use-case.
  • + *
  • Not having an outer join causes n+1 requests to the database. This is not a real issue as + * we have by default a second-level cache. This means on the first HTTP call we do the n+1 requests, + * but then all authorities come from the cache, so in fact it's much better than doing an outer join + * (which will get lots of data from the database, for each HTTP call).
  • + *
  • As this manages users, for security reasons, we'd rather have a DTO layer.
  • + *
+ *

+ * Another option would be to have a specific JPA entity graph to handle this case. + */ +@RestController +@RequestMapping("/api") +public class UserResource { + + private final Logger log = LoggerFactory.getLogger(UserResource.class); + + @Value("${jhipster.clientApp.name}") + private String applicationName; + + private final UserService userService; + + public UserResource(UserService userService) { + + this.userService = userService; + } + + /** + * {@code GET /users} : get all users. + * + * @param pageable the pagination information. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body all users. + */ + @GetMapping("/users") + public ResponseEntity> getAllUsers(Pageable pageable) { + final Page page = userService.getAllManagedUsers(pageable); + HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page); + return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK); + } + + /** + * Gets a list of all roles. + * @return a string list of all roles. + */ + @GetMapping("/users/authorities") + @PreAuthorize("hasRole(\"" + AuthoritiesConstants.ADMIN + "\")") + public List getAuthorities() { + return userService.getAuthorities(); + } + + /** + * {@code GET /users/:login} : get the "login" user. + * + * @param login the login of the user to find. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the "login" user, or with status {@code 404 (Not Found)}. + */ + @GetMapping("/users/{login:" + Constants.LOGIN_REGEX + "}") + public ResponseEntity getUser(@PathVariable String login) { + log.debug("REST request to get User : {}", login); + return ResponseUtil.wrapOrNotFound( + userService.getUserWithAuthoritiesByLogin(login) + .map(UserDTO::new)); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/web/rest/errors/BadRequestAlertException.java b/src/main/java/org/securityrat/casemanagement/web/rest/errors/BadRequestAlertException.java new file mode 100644 index 0000000..4494b21 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/web/rest/errors/BadRequestAlertException.java @@ -0,0 +1,42 @@ +package org.securityrat.casemanagement.web.rest.errors; + +import org.zalando.problem.AbstractThrowableProblem; +import org.zalando.problem.Status; + +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +public class BadRequestAlertException extends AbstractThrowableProblem { + + private static final long serialVersionUID = 1L; + + private final String entityName; + + private final String errorKey; + + public BadRequestAlertException(String defaultMessage, String entityName, String errorKey) { + this(ErrorConstants.DEFAULT_TYPE, defaultMessage, entityName, errorKey); + } + + public BadRequestAlertException(URI type, String defaultMessage, String entityName, String errorKey) { + super(type, defaultMessage, Status.BAD_REQUEST, null, null, null, getAlertParameters(entityName, errorKey)); + this.entityName = entityName; + this.errorKey = errorKey; + } + + public String getEntityName() { + return entityName; + } + + public String getErrorKey() { + return errorKey; + } + + private static Map getAlertParameters(String entityName, String errorKey) { + Map parameters = new HashMap<>(); + parameters.put("message", "error." + errorKey); + parameters.put("params", entityName); + return parameters; + } +} diff --git a/src/main/java/org/securityrat/casemanagement/web/rest/errors/ErrorConstants.java b/src/main/java/org/securityrat/casemanagement/web/rest/errors/ErrorConstants.java new file mode 100644 index 0000000..e85bd99 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/web/rest/errors/ErrorConstants.java @@ -0,0 +1,15 @@ +package org.securityrat.casemanagement.web.rest.errors; + +import java.net.URI; + +public final class ErrorConstants { + + public static final String ERR_CONCURRENCY_FAILURE = "error.concurrencyFailure"; + public static final String ERR_VALIDATION = "error.validation"; + public static final String PROBLEM_BASE_URL = "https://www.jhipster.tech/problem"; + public static final URI DEFAULT_TYPE = URI.create(PROBLEM_BASE_URL + "/problem-with-message"); + public static final URI CONSTRAINT_VIOLATION_TYPE = URI.create(PROBLEM_BASE_URL + "/constraint-violation"); + + private ErrorConstants() { + } +} diff --git a/src/main/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslator.java b/src/main/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslator.java new file mode 100644 index 0000000..ac9e551 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslator.java @@ -0,0 +1,107 @@ +package org.securityrat.casemanagement.web.rest.errors; + +import io.github.jhipster.web.util.HeaderUtil; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.dao.ConcurrencyFailureException; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.NativeWebRequest; +import org.zalando.problem.DefaultProblem; +import org.zalando.problem.Problem; +import org.zalando.problem.ProblemBuilder; +import org.zalando.problem.Status; +import org.zalando.problem.spring.web.advice.ProblemHandling; +import org.zalando.problem.spring.web.advice.security.SecurityAdviceTrait; +import org.zalando.problem.violations.ConstraintViolationProblem; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Controller advice to translate the server side exceptions to client-friendly json structures. + * The error response follows RFC7807 - Problem Details for HTTP APIs (https://tools.ietf.org/html/rfc7807). + */ +@ControllerAdvice +public class ExceptionTranslator implements ProblemHandling, SecurityAdviceTrait { + + private static final String FIELD_ERRORS_KEY = "fieldErrors"; + private static final String MESSAGE_KEY = "message"; + private static final String PATH_KEY = "path"; + private static final String VIOLATIONS_KEY = "violations"; + + @Value("${jhipster.clientApp.name}") + private String applicationName; + + /** + * Post-process the Problem payload to add the message key for the front-end if needed. + */ + @Override + public ResponseEntity process(@Nullable ResponseEntity entity, NativeWebRequest request) { + if (entity == null) { + return entity; + } + Problem problem = entity.getBody(); + if (!(problem instanceof ConstraintViolationProblem || problem instanceof DefaultProblem)) { + return entity; + } + ProblemBuilder builder = Problem.builder() + .withType(Problem.DEFAULT_TYPE.equals(problem.getType()) ? ErrorConstants.DEFAULT_TYPE : problem.getType()) + .withStatus(problem.getStatus()) + .withTitle(problem.getTitle()) + .with(PATH_KEY, request.getNativeRequest(HttpServletRequest.class).getRequestURI()); + + if (problem instanceof ConstraintViolationProblem) { + builder + .with(VIOLATIONS_KEY, ((ConstraintViolationProblem) problem).getViolations()) + .with(MESSAGE_KEY, ErrorConstants.ERR_VALIDATION); + } else { + builder + .withCause(((DefaultProblem) problem).getCause()) + .withDetail(problem.getDetail()) + .withInstance(problem.getInstance()); + problem.getParameters().forEach(builder::with); + if (!problem.getParameters().containsKey(MESSAGE_KEY) && problem.getStatus() != null) { + builder.with(MESSAGE_KEY, "error.http." + problem.getStatus().getStatusCode()); + } + } + return new ResponseEntity<>(builder.build(), entity.getHeaders(), entity.getStatusCode()); + } + + @Override + public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, @Nonnull NativeWebRequest request) { + BindingResult result = ex.getBindingResult(); + List fieldErrors = result.getFieldErrors().stream() + .map(f -> new FieldErrorVM(f.getObjectName().replaceFirst("DTO$", ""), f.getField(), f.getCode())) + .collect(Collectors.toList()); + + Problem problem = Problem.builder() + .withType(ErrorConstants.CONSTRAINT_VIOLATION_TYPE) + .withTitle("Method argument not valid") + .withStatus(defaultConstraintViolationStatus()) + .with(MESSAGE_KEY, ErrorConstants.ERR_VALIDATION) + .with(FIELD_ERRORS_KEY, fieldErrors) + .build(); + return create(ex, problem, request); + } + + @ExceptionHandler + public ResponseEntity handleBadRequestAlertException(BadRequestAlertException ex, NativeWebRequest request) { + return create(ex, request, HeaderUtil.createFailureAlert(applicationName, false, ex.getEntityName(), ex.getErrorKey(), ex.getMessage())); + } + + @ExceptionHandler + public ResponseEntity handleConcurrencyFailure(ConcurrencyFailureException ex, NativeWebRequest request) { + Problem problem = Problem.builder() + .withStatus(Status.CONFLICT) + .with(MESSAGE_KEY, ErrorConstants.ERR_CONCURRENCY_FAILURE) + .build(); + return create(ex, problem, request); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/web/rest/errors/FieldErrorVM.java b/src/main/java/org/securityrat/casemanagement/web/rest/errors/FieldErrorVM.java new file mode 100644 index 0000000..71dd84d --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/web/rest/errors/FieldErrorVM.java @@ -0,0 +1,33 @@ +package org.securityrat.casemanagement.web.rest.errors; + +import java.io.Serializable; + +public class FieldErrorVM implements Serializable { + + private static final long serialVersionUID = 1L; + + private final String objectName; + + private final String field; + + private final String message; + + public FieldErrorVM(String dto, String field, String message) { + this.objectName = dto; + this.field = field; + this.message = message; + } + + public String getObjectName() { + return objectName; + } + + public String getField() { + return field; + } + + public String getMessage() { + return message; + } + +} diff --git a/src/main/java/org/securityrat/casemanagement/web/rest/errors/package-info.java b/src/main/java/org/securityrat/casemanagement/web/rest/errors/package-info.java new file mode 100644 index 0000000..254f7c7 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/web/rest/errors/package-info.java @@ -0,0 +1,6 @@ +/** + * Specific errors used with Zalando's "problem-spring-web" library. + * + * More information on https://github.com/zalando/problem-spring-web + */ +package org.securityrat.casemanagement.web.rest.errors; diff --git a/src/main/java/org/securityrat/casemanagement/web/rest/package-info.java b/src/main/java/org/securityrat/casemanagement/web/rest/package-info.java new file mode 100644 index 0000000..29ec7a2 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/web/rest/package-info.java @@ -0,0 +1,4 @@ +/** + * Spring MVC REST controllers. + */ +package org.securityrat.casemanagement.web.rest; diff --git a/src/main/java/org/securityrat/casemanagement/web/rest/vm/ManagedUserVM.java b/src/main/java/org/securityrat/casemanagement/web/rest/vm/ManagedUserVM.java new file mode 100644 index 0000000..b2ad38f --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/web/rest/vm/ManagedUserVM.java @@ -0,0 +1,18 @@ +package org.securityrat.casemanagement.web.rest.vm; + +import org.securityrat.casemanagement.service.dto.UserDTO; + +/** + * View Model extending the UserDTO, which is meant to be used in the user management UI. + */ +public class ManagedUserVM extends UserDTO { + + public ManagedUserVM() { + // Empty constructor needed for Jackson. + } + + @Override + public String toString() { + return "ManagedUserVM{" + super.toString() + "} "; + } +} diff --git a/src/main/java/org/securityrat/casemanagement/web/rest/vm/package-info.java b/src/main/java/org/securityrat/casemanagement/web/rest/vm/package-info.java new file mode 100644 index 0000000..f325272 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/web/rest/vm/package-info.java @@ -0,0 +1,4 @@ +/** + * View Models used by Spring MVC REST controllers. + */ +package org.securityrat.casemanagement.web.rest.vm; diff --git a/src/main/jib/entrypoint.sh b/src/main/jib/entrypoint.sh new file mode 100644 index 0000000..fb7a84f --- /dev/null +++ b/src/main/jib/entrypoint.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +echo "The application will start in ${JHIPSTER_SLEEP}s..." && sleep ${JHIPSTER_SLEEP} +exec java ${JAVA_OPTS} -noverify -XX:+AlwaysPreTouch -Djava.security.egd=file:/dev/./urandom -cp /app/resources/:/app/classes/:/app/libs/* "org.securityrat.casemanagement.CaseManagementApp" "$@" diff --git a/src/main/resources/.h2.server.properties b/src/main/resources/.h2.server.properties new file mode 100644 index 0000000..3c49b47 --- /dev/null +++ b/src/main/resources/.h2.server.properties @@ -0,0 +1,5 @@ +#H2 Server Properties +0=JHipster H2 (Disk)|org.h2.Driver|jdbc\:h2\:file\:./target/h2db/db/casemanagement|caseManagement +webAllowOthers=true +webPort=8082 +webSSL=false diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt new file mode 100644 index 0000000..e0bc55a --- /dev/null +++ b/src/main/resources/banner.txt @@ -0,0 +1,10 @@ + + ${AnsiColor.GREEN} ██╗${AnsiColor.RED} ██╗ ██╗ ████████╗ ███████╗ ██████╗ ████████╗ ████████╗ ███████╗ + ${AnsiColor.GREEN} ██║${AnsiColor.RED} ██║ ██║ ╚══██╔══╝ ██╔═══██╗ ██╔════╝ ╚══██╔══╝ ██╔═════╝ ██╔═══██╗ + ${AnsiColor.GREEN} ██║${AnsiColor.RED} ████████║ ██║ ███████╔╝ ╚█████╗ ██║ ██████╗ ███████╔╝ + ${AnsiColor.GREEN}██╗ ██║${AnsiColor.RED} ██╔═══██║ ██║ ██╔════╝ ╚═══██╗ ██║ ██╔═══╝ ██╔══██║ + ${AnsiColor.GREEN}╚██████╔╝${AnsiColor.RED} ██║ ██║ ████████╗ ██║ ██████╔╝ ██║ ████████╗ ██║ ╚██╗ + ${AnsiColor.GREEN} ╚═════╝ ${AnsiColor.RED} ╚═╝ ╚═╝ ╚═══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══════╝ ╚═╝ ╚═╝ + +${AnsiColor.BRIGHT_BLUE}:: JHipster 🤓 :: Running Spring Boot ${spring-boot.version} :: +:: https://www.jhipster.tech ::${AnsiColor.DEFAULT} diff --git a/src/main/resources/config/application-dev.yml b/src/main/resources/config/application-dev.yml new file mode 100644 index 0000000..d496665 --- /dev/null +++ b/src/main/resources/config/application-dev.yml @@ -0,0 +1,131 @@ +# =================================================================== +# Spring Boot configuration for the "dev" profile. +# +# This configuration overrides the application.yml file. +# +# More information on profiles: https://www.jhipster.tech/profiles/ +# More information on configuration properties: https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +# =================================================================== +# Standard Spring Boot properties. +# Full reference is available at: +# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +# =================================================================== + +logging: + level: + ROOT: DEBUG + io.github.jhipster: DEBUG + org.securityrat.casemanagement: DEBUG + +spring: + profiles: + active: dev + include: + - swagger + # Uncomment to activate TLS for the dev profile + #- tls + devtools: + restart: + enabled: true + additional-exclude: static/**,.h2.server.properties + livereload: + enabled: false # we use Webpack dev server + BrowserSync for livereload + jackson: + serialization: + indent-output: true + cloud: + consul: + discovery: + prefer-ip-address: true + host: localhost + port: 8500 + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: jdbc:h2:file:./target/h2db/db/casemanagement;DB_CLOSE_DELAY=-1 + username: caseManagement + password: + hikari: + poolName: Hikari + auto-commit: false + h2: + console: + enabled: false + jpa: + database-platform: io.github.jhipster.domain.util.FixedH2Dialect + database: H2 + show-sql: true + properties: + hibernate.id.new_generator_mappings: true + hibernate.connection.provider_disables_autocommit: true + hibernate.cache.use_second_level_cache: false + hibernate.cache.use_query_cache: false + hibernate.generate_statistics: false + liquibase: + # Remove 'faker' if you do not want the sample data to be loaded automatically + contexts: dev, faker + mail: + host: localhost + port: 25 + username: + password: + messages: + cache-duration: PT1S # 1 second, see the ISO 8601 standard + thymeleaf: + cache: false + sleuth: + sampler: + probability: 1 # report 100% of traces + zipkin: # Use the "zipkin" Maven profile to have the Spring Cloud Zipkin dependencies + base-url: http://localhost:9411 + enabled: false + locator: + discovery: + enabled: true + +server: + port: 8082 + +# =================================================================== +# JHipster specific properties +# +# Full reference is available at: https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +jhipster: + # CORS is disabled by default on microservices, as you should access them through a gateway. + # If you want to enable it, please uncomment the configuration below. + # cors: + # allowed-origins: "*" + # allowed-methods: "*" + # allowed-headers: "*" + # exposed-headers: "Authorization,Link,X-Total-Count" + # allow-credentials: true + # max-age: 1800 + mail: # specific JHipster mail property, for standard properties see MailProperties + base-url: http://127.0.0.1:8082 + metrics: + logs: # Reports metrics in the logs + enabled: false + report-frequency: 60 # in seconds + logging: + use-json-format: false # By default, logs are not in Json format + logstash: # Forward logs to logstash over a socket, used by LoggingConfiguration + enabled: false + host: localhost + port: 5000 + queue-size: 512 + audit-events: + retention-period: 30 # Number of days before audit events are deleted. + +# =================================================================== +# Application specific properties +# Add your own application properties here, see the ApplicationProperties class +# to have type-safe configuration, like in the JHipsterProperties above +# +# More documentation is available at: +# https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +# application: diff --git a/src/main/resources/config/application-prod.yml b/src/main/resources/config/application-prod.yml new file mode 100644 index 0000000..2b0da31 --- /dev/null +++ b/src/main/resources/config/application-prod.yml @@ -0,0 +1,150 @@ +# =================================================================== +# Spring Boot configuration for the "prod" profile. +# +# This configuration overrides the application.yml file. +# +# More information on profiles: https://www.jhipster.tech/profiles/ +# More information on configuration properties: https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +# =================================================================== +# Standard Spring Boot properties. +# Full reference is available at: +# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +# =================================================================== + +logging: + level: + ROOT: INFO + io.github.jhipster: INFO + org.securityrat.casemanagement: INFO + +management: + metrics: + export: + prometheus: + enabled: false + +spring: + devtools: + restart: + enabled: false + livereload: + enabled: false + cloud: + consul: + discovery: + prefer-ip-address: true + host: localhost + port: 8500 + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: jdbc:mariadb://localhost:3306/caseManagement?useLegacyDatetimeCode=false&serverTimezone=UTC + username: root + password: + hikari: + poolName: Hikari + auto-commit: false + data-source-properties: + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + jpa: + database-platform: org.hibernate.dialect.MariaDB103Dialect + database: MYSQL + show-sql: false + properties: + hibernate.id.new_generator_mappings: true + hibernate.connection.provider_disables_autocommit: true + hibernate.cache.use_second_level_cache: false + hibernate.cache.use_query_cache: false + hibernate.generate_statistics: false + # modify batch size as necessary + hibernate.jdbc.batch_size: 25 + hibernate.order_inserts: true + hibernate.order_updates: true + hibernate.query.fail_on_pagination_over_collection_fetch: true + hibernate.query.in_clause_parameter_padding: true + # Replace by 'prod, faker' to add the faker context and have sample data loaded in production + liquibase: + contexts: prod + mail: + host: localhost + port: 25 + username: + password: + thymeleaf: + cache: true + sleuth: + sampler: + probability: 1 # report 100% of traces + zipkin: # Use the "zipkin" Maven profile to have the Spring Cloud Zipkin dependencies + base-url: http://localhost:9411 + enabled: false + locator: + discovery: + enabled: true + +# =================================================================== +# To enable TLS in production, generate a certificate using: +# keytool -genkey -alias casemanagement -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 3650 +# +# You can also use Let's Encrypt: +# https://maximilian-boehm.com/hp2121/Create-a-Java-Keystore-JKS-from-Let-s-Encrypt-Certificates.htm +# +# Then, modify the server.ssl properties so your "server" configuration looks like: +# +# server: +# port: 443 +# ssl: +# key-store: classpath:config/tls/keystore.p12 +# key-store-password: password +# key-store-type: PKCS12 +# key-alias: casemanagement +# # The ciphers suite enforce the security by deactivating some old and deprecated SSL cipher, this list was tested against SSL Labs (https://www.ssllabs.com/ssltest/) +# ciphers: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ,TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 ,TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 ,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,TLS_RSA_WITH_CAMELLIA_256_CBC_SHA,TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA,TLS_RSA_WITH_CAMELLIA_128_CBC_SHA +# =================================================================== +server: + port: 8082 + compression: + enabled: true + mime-types: text/html,text/xml,text/plain,text/css, application/javascript, application/json + min-response-size: 1024 + +# =================================================================== +# JHipster specific properties +# +# Full reference is available at: https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +jhipster: + http: + cache: # Used by the CachingHttpHeadersFilter + timeToLiveInDays: 1461 + mail: # specific JHipster mail property, for standard properties see MailProperties + base-url: http://my-server-url-to-change # Modify according to your server's URL + metrics: + logs: # Reports metrics in the logs + enabled: false + report-frequency: 60 # in seconds + logging: + use-json-format: false # By default, logs are not in Json format + logstash: # Forward logs to logstash over a socket, used by LoggingConfiguration + enabled: false + host: localhost + port: 5000 + queue-size: 512 + audit-events: + retention-period: 30 # Number of days before audit events are deleted. + +# =================================================================== +# Application specific properties +# Add your own application properties here, see the ApplicationProperties class +# to have type-safe configuration, like in the JHipsterProperties above +# +# More documentation is available at: +# https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +# application: diff --git a/src/main/resources/config/application-tls.yml b/src/main/resources/config/application-tls.yml new file mode 100644 index 0000000..27c9cd8 --- /dev/null +++ b/src/main/resources/config/application-tls.yml @@ -0,0 +1,19 @@ +# =================================================================== +# Activate this profile to enable TLS and HTTP/2. +# +# JHipster has generated a self-signed certificate, which will be used to encrypt traffic. +# As your browser will not understand this certificate, you will need to import it. +# +# Another (easiest) solution with Chrome is to enable the "allow-insecure-localhost" flag +# at chrome://flags/#allow-insecure-localhost +# =================================================================== +server: + ssl: + key-store: classpath:config/tls/keystore.p12 + key-store-password: password + key-store-type: PKCS12 + key-alias: selfsigned + ciphers: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 + enabled-protocols: TLSv1.2 + http2: + enabled: true diff --git a/src/main/resources/config/application.yml b/src/main/resources/config/application.yml new file mode 100644 index 0000000..8a75c26 --- /dev/null +++ b/src/main/resources/config/application.yml @@ -0,0 +1,195 @@ +# =================================================================== +# Spring Boot configuration. +# +# This configuration will be overridden by the Spring profile you use, +# for example application-dev.yml if you use the "dev" profile. +# +# More information on profiles: https://www.jhipster.tech/profiles/ +# More information on configuration properties: https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +# =================================================================== +# Standard Spring Boot properties. +# Full reference is available at: +# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +# =================================================================== + +feign: + hystrix: + enabled: true + # client: + # config: + # default: + # connectTimeout: 5000 + # readTimeout: 5000 + +# See https://github.com/Netflix/Hystrix/wiki/Configuration +hystrix: + command: + default: + execution: + isolation: + strategy: SEMAPHORE + # See https://github.com/spring-cloud/spring-cloud-netflix/issues/1330 + # thread: + # timeoutInMilliseconds: 10000 + shareSecurityContext: true + +management: + endpoints: + web: + base-path: /management + exposure: + include: ['configprops', 'env', 'health', 'info', 'jhimetrics', 'logfile', 'loggers', 'prometheus', 'threaddump'] + endpoint: + health: + show-details: when-authorized + roles: 'ROLE_ADMIN' + jhimetrics: + enabled: true + info: + git: + mode: full + health: + mail: + enabled: false # When using the MailService, configure an SMTP server and set this to true + metrics: + export: + # Prometheus is the default metrics backend + prometheus: + enabled: true + step: 60 + enable: + http: true + jvm: true + logback: true + process: true + system: true + distribution: + percentiles-histogram: + all: true + percentiles: + all: 0, 0.5, 0.75, 0.95, 0.99, 1.0 + tags: + application: ${spring.application.name} + web: + server: + auto-time-requests: true + +spring: + application: + name: caseManagement + cloud: + consul: + discovery: + healthCheckPath: /management/health + instanceId: casemanagement:${spring.application.instance-id:${random.value}} + service-name: casemanagement + config: + watch: + enabled: false + jmx: + enabled: false + data: + jpa: + repositories: + bootstrap-mode: deferred + jpa: + open-in-view: false + properties: + hibernate.jdbc.time_zone: UTC + hibernate: + ddl-auto: none + naming: + physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy + implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + messages: + basename: i18n/messages + main: + allow-bean-definition-overriding: true + mvc: + favicon: + enabled: false + task: + execution: + thread-name-prefix: case-management-task- + pool: + core-size: 2 + max-size: 50 + queue-capacity: 10000 + scheduling: + thread-name-prefix: case-management-scheduling- + pool: + size: 2 + thymeleaf: + mode: HTML + output: + ansi: + console-available: true + security: + oauth2: + client: + provider: + oidc: + issuer-uri: http://localhost:9080/auth/realms/jhipster + registration: + oidc: + client-id: internal + client-secret: internal + +server: + servlet: + session: + cookie: + http-only: true + +# Properties to be exposed on the /info management endpoint +info: + # Comma separated list of profiles that will trigger the ribbon to show + display-ribbon-on-profiles: 'dev' + +# =================================================================== +# JHipster specific properties +# +# Full reference is available at: https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +jhipster: + clientApp: + name: 'caseManagementApp' + # By default CORS is disabled. Uncomment to enable. + # cors: + # allowed-origins: "*" + # allowed-methods: "*" + # allowed-headers: "*" + # exposed-headers: "Authorization,Link,X-Total-Count" + # allow-credentials: true + # max-age: 1800 + mail: + from: caseManagement@localhost + swagger: + default-include-pattern: /api/.* + title: caseManagement API + description: caseManagement API documentation + version: 0.0.1 + terms-of-service-url: + contact-name: + contact-url: + contact-email: + license: + license-url: + security: + oauth2: + audience: + - account + - api://default +# =================================================================== +# Application specific properties +# Add your own application properties here, see the ApplicationProperties class +# to have type-safe configuration, like in the JHipsterProperties above +# +# More documentation is available at: +# https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +# application: diff --git a/src/main/resources/config/bootstrap-prod.yml b/src/main/resources/config/bootstrap-prod.yml new file mode 100644 index 0000000..276f330 --- /dev/null +++ b/src/main/resources/config/bootstrap-prod.yml @@ -0,0 +1,15 @@ +# =================================================================== +# Spring Cloud Consul Config bootstrap configuration for the "prod" profile +# =================================================================== + +spring: + cloud: + consul: + config: + fail-fast: true + format: yaml # set this to "files" if using git2consul + profile-separator: '-' + retry: + initial-interval: 1000 + max-interval: 2000 + max-attempts: 100 diff --git a/src/main/resources/config/bootstrap.yml b/src/main/resources/config/bootstrap.yml new file mode 100644 index 0000000..a4bdb71 --- /dev/null +++ b/src/main/resources/config/bootstrap.yml @@ -0,0 +1,28 @@ +# =================================================================== +# Spring Cloud Consul Config bootstrap configuration for the "dev" profile +# In prod profile, properties will be overwritten by the ones defined in bootstrap-prod.yml +# =================================================================== + +spring: + application: + name: caseManagement + profiles: + # The commented value for `active` can be replaced with valid Spring profiles to load. + # Otherwise, it will be filled in by maven when building the JAR file + # Either way, it can be overridden by `--spring.profiles.active` value passed in the commandline or `-Dspring.profiles.active` set in `JAVA_OPTS` + active: #spring.profiles.active# + cloud: + consul: + config: + fail-fast: false # if not in "prod" profile, do not force to use Spring Cloud Config + format: yaml + profile-separator: '-' + discovery: + tags: + - profile=${spring.profiles.active} + - version=#project.version# + - git-version=${git.commit.id.describe:} + - git-commit=${git.commit.id.abbrev:} + - git-branch=${git.branch:} + host: localhost + port: 8500 diff --git a/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml b/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml new file mode 100644 index 0000000..b1ac55b --- /dev/null +++ b/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/config/liquibase/changelog/20201108111723_added_entity_AccessToken.xml b/src/main/resources/config/liquibase/changelog/20201108111723_added_entity_AccessToken.xml new file mode 100644 index 0000000..e5f71c7 --- /dev/null +++ b/src/main/resources/config/liquibase/changelog/20201108111723_added_entity_AccessToken.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/config/liquibase/changelog/20201108111723_added_entity_constraints_AccessToken.xml b/src/main/resources/config/liquibase/changelog/20201108111723_added_entity_constraints_AccessToken.xml new file mode 100644 index 0000000..52da1c6 --- /dev/null +++ b/src/main/resources/config/liquibase/changelog/20201108111723_added_entity_constraints_AccessToken.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/src/main/resources/config/liquibase/changelog/20201108112112_added_entity_TicketSystemInstance.xml b/src/main/resources/config/liquibase/changelog/20201108112112_added_entity_TicketSystemInstance.xml new file mode 100644 index 0000000..b414c01 --- /dev/null +++ b/src/main/resources/config/liquibase/changelog/20201108112112_added_entity_TicketSystemInstance.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/config/liquibase/fake-data/access_token.csv b/src/main/resources/config/liquibase/fake-data/access_token.csv new file mode 100644 index 0000000..04e0d8c --- /dev/null +++ b/src/main/resources/config/liquibase/fake-data/access_token.csv @@ -0,0 +1,11 @@ +id;token;expiration_date;salt;refresh_token +1;Operations;2020-11-07T17:45:42;e-services Toys;Forge +2;protocol;2020-11-07T11:29:33;Customer didactic;Cotton auxiliary web services +3;reboot;2020-11-07T18:33:57;harness matrix;Frozen +4;Credit Card Account;2020-11-08T02:18:42;firewall asymmetric payment;Data Awesome Granite Chips +5;firmware bypassing;2020-11-07T11:45:18;Nevada Gloves;utilize +6;Technician Alaska;2020-11-08T04:15:28;Hawaii;Analyst Music Health +7;bypass executive;2020-11-08T04:35:03;Bedfordshire Unbranded Cotton Salad Sleek;architecture +8;array hard drive;2020-11-07T17:22:21;Legacy quantify;Washington Handmade Soft Mouse +9;Small Oregon;2020-11-07T22:36:36;content;connecting +10;Data;2020-11-07T19:52:23;Configuration deposit Intranet;solution diff --git a/src/main/resources/config/liquibase/fake-data/ticket_system_instance.csv b/src/main/resources/config/liquibase/fake-data/ticket_system_instance.csv new file mode 100644 index 0000000..bc5b78b --- /dev/null +++ b/src/main/resources/config/liquibase/fake-data/ticket_system_instance.csv @@ -0,0 +1,11 @@ +id;name;type;url;consumer_key;client_id;client_secret +1;Indiana Intelligent Soft Bacon;JIRA;https://andreanne.net;Chips Grocery groupware;override Bermuda;Sausages bypass +2;transmit;JIRA;https://jalen.net;navigate tan;1080p Gorgeous Granite Tuna;extend e-services 24/7 +3;Jewelery;JIRA;https://emmanuel.org;Valleys Grocery New Mexico;Surinam Dollar Handmade Frozen Bacon;Forward cross-platform Guarani +4;Regional;JIRA;http://anabelle.com;knowledge base Administrator;e-commerce e-tailers;Borders Internal +5;JSON Intelligent;JIRA;https://caterina.biz;rich invoice;back up;Dynamic +6;Cambridgeshire utilize Haiti;JIRA;https://rhett.com;Plain Credit Card Account COM;New Caledonia cyan Venezuela;magnetic invoice Music +7;Tools;JIRA;http://aditya.com;infomediaries productivity;sticky Tuna;Buckinghamshire Enterprise-wide Automotive +8;Consultant;JIRA;https://luigi.name;Home protocol Handcrafted Metal Chicken;revolutionary;Music +9;COM deposit Steel;JIRA;https://elroy.net;Intelligent Rubber Shoes Swiss Franc;Nepalese Rupee experiences Legacy;solutions mobile +10;quantifying indigo;JIRA;http://sheila.org;New Israeli Sheqel Administrator functionalities;Awesome Granite Bacon Borders seamless;Pakistan Progressive diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml new file mode 100644 index 0000000..93aded6 --- /dev/null +++ b/src/main/resources/config/liquibase/master.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties new file mode 100644 index 0000000..3d8dcf1 --- /dev/null +++ b/src/main/resources/i18n/messages.properties @@ -0,0 +1,21 @@ +# Error page +error.title=Your request cannot be processed +error.subtitle=Sorry, an error has occurred. +error.status=Status: +error.message=Message: + +# Activation email +email.activation.title=caseManagement account activation is required +email.activation.greeting=Dear {0} +email.activation.text1=Your caseManagement account has been created, please click on the URL below to activate it: +email.activation.text2=Regards, +email.signature=caseManagement Team. + +# Creation email +email.creation.text1=Your caseManagement account has been created, please click on the URL below to access it: + +# Reset email +email.reset.title=caseManagement password reset +email.reset.greeting=Dear {0} +email.reset.text1=For your caseManagement account a password reset was requested, please click on the URL below to reset it: +email.reset.text2=Regards, diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..0377650 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html new file mode 100644 index 0000000..68659ba --- /dev/null +++ b/src/main/resources/static/index.html @@ -0,0 +1,103 @@ + + + + + JHipster microservice homepage + + + +

+

Welcome, Java Hipster!

+ +

This application is a microservice, which has been generated using JHipster.

+ +
    +
  • It does not have a front-end. The front-end should be generated on a JHipster gateway
  • +
  • It is serving REST APIs, under the '/api' URLs.
  • +
  • Swagger documentation endpoint for those APIs is at /v2/api-docs, but if you want access to the full Swagger UI, you should use a JHipster gateway, which will serve as an API developer portal
  • +
+ +

+ If you have any question on JHipster: +

+ + + +

+ If you like JHipster, don't forget to give us a star on GitHub! +

+ +
+ + diff --git a/src/main/resources/templates/error.html b/src/main/resources/templates/error.html new file mode 100644 index 0000000..b703488 --- /dev/null +++ b/src/main/resources/templates/error.html @@ -0,0 +1,87 @@ + + + + + + Your request cannot be processed + + + +
+

Your request cannot be processed :(

+ +

Sorry, an error has occurred.

+ + Status:  ()
+ + Message: 
+
+
+ + diff --git a/src/test/java/org/securityrat/casemanagement/ArchTest.java b/src/test/java/org/securityrat/casemanagement/ArchTest.java new file mode 100644 index 0000000..56c3f49 --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/ArchTest.java @@ -0,0 +1,29 @@ +package org.securityrat.casemanagement; + +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.importer.ClassFileImporter; +import com.tngtech.archunit.core.importer.ImportOption; +import org.junit.jupiter.api.Test; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; + +class ArchTest { + + @Test + void servicesAndRepositoriesShouldNotDependOnWebLayer() { + + JavaClasses importedClasses = new ClassFileImporter() + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) + .importPackages("org.securityrat.casemanagement"); + + noClasses() + .that() + .resideInAnyPackage("..service..") + .or() + .resideInAnyPackage("..repository..") + .should().dependOnClassesThat() + .resideInAnyPackage("..org.securityrat.casemanagement.web..") + .because("Services and repositories should not depend on web layer") + .check(importedClasses); + } +} diff --git a/src/test/java/org/securityrat/casemanagement/config/TestSecurityConfiguration.java b/src/test/java/org/securityrat/casemanagement/config/TestSecurityConfiguration.java new file mode 100644 index 0000000..6d3cd7a --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/config/TestSecurityConfiguration.java @@ -0,0 +1,71 @@ +package org.securityrat.casemanagement.config; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import org.springframework.security.oauth2.jwt.JwtDecoder; + +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.Mockito.mock; + +/** + * This class allows you to run unit and integration tests without an IdP. + */ +@TestConfiguration +public class TestSecurityConfiguration { + private final ClientRegistration clientRegistration; + + public TestSecurityConfiguration() { + this.clientRegistration = clientRegistration().build(); + } + + @Bean + ClientRegistrationRepository clientRegistrationRepository() { + return new InMemoryClientRegistrationRepository(clientRegistration); + } + + private ClientRegistration.Builder clientRegistration() { + Map metadata = new HashMap<>(); + metadata.put("end_session_endpoint", "https://jhipster.org/logout"); + + return ClientRegistration.withRegistrationId("oidc") + .redirectUriTemplate("{baseUrl}/{action}/oauth2/code/{registrationId}") + .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .scope("read:user") + .authorizationUri("https://jhipster.org/login/oauth/authorize") + .tokenUri("https://jhipster.org/login/oauth/access_token") + .jwkSetUri("https://jhipster.org/oauth/jwk") + .userInfoUri("https://api.jhipster.org/user") + .providerConfigurationMetadata(metadata) + .userNameAttributeName("id") + .clientName("Client Name") + .clientId("client-id") + .clientSecret("client-secret"); + } + + @Bean + JwtDecoder jwtDecoder() { + return mock(JwtDecoder.class); + } + + @Bean + public OAuth2AuthorizedClientService authorizedClientService(ClientRegistrationRepository clientRegistrationRepository) { + return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository); + } + + @Bean + public OAuth2AuthorizedClientRepository authorizedClientRepository(OAuth2AuthorizedClientService authorizedClientService) { + return new AuthenticatedPrincipalOAuth2AuthorizedClientRepository(authorizedClientService); + } +} diff --git a/src/test/java/org/securityrat/casemanagement/config/WebConfigurerTest.java b/src/test/java/org/securityrat/casemanagement/config/WebConfigurerTest.java new file mode 100644 index 0000000..8205a83 --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/config/WebConfigurerTest.java @@ -0,0 +1,146 @@ +package org.securityrat.casemanagement.config; + +import io.github.jhipster.config.JHipsterConstants; +import io.github.jhipster.config.JHipsterProperties; +import org.h2.server.web.WebServlet; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpHeaders; +import org.springframework.mock.env.MockEnvironment; +import org.springframework.mock.web.MockServletContext; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import javax.servlet.*; +import java.util.*; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Unit tests for the {@link WebConfigurer} class. + */ +public class WebConfigurerTest { + + private WebConfigurer webConfigurer; + + private MockServletContext servletContext; + + private MockEnvironment env; + + private JHipsterProperties props; + + @BeforeEach + public void setup() { + servletContext = spy(new MockServletContext()); + doReturn(mock(FilterRegistration.Dynamic.class)) + .when(servletContext).addFilter(anyString(), any(Filter.class)); + doReturn(mock(ServletRegistration.Dynamic.class)) + .when(servletContext).addServlet(anyString(), any(Servlet.class)); + + env = new MockEnvironment(); + props = new JHipsterProperties(); + + webConfigurer = new WebConfigurer(env, props); + } + + @Test + public void testStartUpProdServletContext() throws ServletException { + env.setActiveProfiles(JHipsterConstants.SPRING_PROFILE_PRODUCTION); + webConfigurer.onStartup(servletContext); + + verify(servletContext, never()).addServlet(eq("H2Console"), any(WebServlet.class)); + } + + @Test + public void testStartUpDevServletContext() throws ServletException { + env.setActiveProfiles(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT); + webConfigurer.onStartup(servletContext); + + verify(servletContext).addServlet(eq("H2Console"), any(WebServlet.class)); + } + + @Test + public void testCorsFilterOnApiPath() throws Exception { + props.getCors().setAllowedOrigins(Collections.singletonList("*")); + props.getCors().setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE")); + props.getCors().setAllowedHeaders(Collections.singletonList("*")); + props.getCors().setMaxAge(1800L); + props.getCors().setAllowCredentials(true); + + MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new WebConfigurerTestController()) + .addFilters(webConfigurer.corsFilter()) + .build(); + + mockMvc.perform( + options("/api/test-cors") + .header(HttpHeaders.ORIGIN, "other.domain.com") + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "POST")) + .andExpect(status().isOk()) + .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "other.domain.com")) + .andExpect(header().string(HttpHeaders.VARY, "Origin")) + .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET,POST,PUT,DELETE")) + .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true")) + .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "1800")); + + mockMvc.perform( + get("/api/test-cors") + .header(HttpHeaders.ORIGIN, "other.domain.com")) + .andExpect(status().isOk()) + .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "other.domain.com")); + } + + @Test + public void testCorsFilterOnOtherPath() throws Exception { + props.getCors().setAllowedOrigins(Collections.singletonList("*")); + props.getCors().setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE")); + props.getCors().setAllowedHeaders(Collections.singletonList("*")); + props.getCors().setMaxAge(1800L); + props.getCors().setAllowCredentials(true); + + MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new WebConfigurerTestController()) + .addFilters(webConfigurer.corsFilter()) + .build(); + + mockMvc.perform( + get("/test/test-cors") + .header(HttpHeaders.ORIGIN, "other.domain.com")) + .andExpect(status().isOk()) + .andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + } + + @Test + public void testCorsFilterDeactivated() throws Exception { + props.getCors().setAllowedOrigins(null); + + MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new WebConfigurerTestController()) + .addFilters(webConfigurer.corsFilter()) + .build(); + + mockMvc.perform( + get("/api/test-cors") + .header(HttpHeaders.ORIGIN, "other.domain.com")) + .andExpect(status().isOk()) + .andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + } + + @Test + public void testCorsFilterDeactivated2() throws Exception { + props.getCors().setAllowedOrigins(new ArrayList<>()); + + MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new WebConfigurerTestController()) + .addFilters(webConfigurer.corsFilter()) + .build(); + + mockMvc.perform( + get("/api/test-cors") + .header(HttpHeaders.ORIGIN, "other.domain.com")) + .andExpect(status().isOk()) + .andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + } +} diff --git a/src/test/java/org/securityrat/casemanagement/config/WebConfigurerTestController.java b/src/test/java/org/securityrat/casemanagement/config/WebConfigurerTestController.java new file mode 100644 index 0000000..54e2715 --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/config/WebConfigurerTestController.java @@ -0,0 +1,16 @@ +package org.securityrat.casemanagement.config; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class WebConfigurerTestController { + + @GetMapping("/api/test-cors") + public void testCorsOnApiPath() { + } + + @GetMapping("/test/test-cors") + public void testCorsOnOtherPath() { + } +} diff --git a/src/test/java/org/securityrat/casemanagement/config/timezone/HibernateTimeZoneIT.java b/src/test/java/org/securityrat/casemanagement/config/timezone/HibernateTimeZoneIT.java new file mode 100644 index 0000000..8aa6a63 --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/config/timezone/HibernateTimeZoneIT.java @@ -0,0 +1,174 @@ +package org.securityrat.casemanagement.config.timezone; + +import org.securityrat.casemanagement.CaseManagementApp; +import org.securityrat.casemanagement.config.TestSecurityConfiguration; +import org.securityrat.casemanagement.repository.timezone.DateTimeWrapper; +import org.securityrat.casemanagement.repository.timezone.DateTimeWrapperRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.rowset.SqlRowSet; +import org.springframework.transaction.annotation.Transactional; + +import java.time.*; +import java.time.format.DateTimeFormatter; + +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for the UTC Hibernate configuration. + */ +@SpringBootTest(classes = {CaseManagementApp.class, TestSecurityConfiguration.class}) +public class HibernateTimeZoneIT { + + @Autowired + private DateTimeWrapperRepository dateTimeWrapperRepository; + @Autowired + private JdbcTemplate jdbcTemplate; + + private DateTimeWrapper dateTimeWrapper; + private DateTimeFormatter dateTimeFormatter; + private DateTimeFormatter timeFormatter; + private DateTimeFormatter dateFormatter; + + @BeforeEach + public void setup() { + dateTimeWrapper = new DateTimeWrapper(); + dateTimeWrapper.setInstant(Instant.parse("2014-11-12T05:50:00.0Z")); + dateTimeWrapper.setLocalDateTime(LocalDateTime.parse("2014-11-12T07:50:00.0")); + dateTimeWrapper.setOffsetDateTime(OffsetDateTime.parse("2011-12-14T08:30:00.0Z")); + dateTimeWrapper.setZonedDateTime(ZonedDateTime.parse("2011-12-14T08:30:00.0Z")); + dateTimeWrapper.setLocalTime(LocalTime.parse("14:30:00")); + dateTimeWrapper.setOffsetTime(OffsetTime.parse("14:30:00+02:00")); + dateTimeWrapper.setLocalDate(LocalDate.parse("2016-09-10")); + + dateTimeFormatter = DateTimeFormatter + .ofPattern("yyyy-MM-dd HH:mm:ss.S") + .withZone(ZoneId.of("UTC")); + + timeFormatter = DateTimeFormatter + .ofPattern("HH:mm:ss") + .withZone(ZoneId.of("UTC")); + + dateFormatter = DateTimeFormatter + .ofPattern("yyyy-MM-dd"); + } + + @Test + @Transactional + public void storeInstantWithUtcConfigShouldBeStoredOnGMTTimeZone() { + dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); + + String request = generateSqlRequest("instant", dateTimeWrapper.getId()); + SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); + String expectedValue = dateTimeFormatter.format(dateTimeWrapper.getInstant()); + + assertThatDateStoredValueIsEqualToInsertDateValueOnGMTTimeZone(resultSet, expectedValue); + } + + @Test + @Transactional + public void storeLocalDateTimeWithUtcConfigShouldBeStoredOnGMTTimeZone() { + dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); + + String request = generateSqlRequest("local_date_time", dateTimeWrapper.getId()); + SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); + String expectedValue = dateTimeWrapper + .getLocalDateTime() + .atZone(ZoneId.systemDefault()) + .format(dateTimeFormatter); + + assertThatDateStoredValueIsEqualToInsertDateValueOnGMTTimeZone(resultSet, expectedValue); + } + + @Test + @Transactional + public void storeOffsetDateTimeWithUtcConfigShouldBeStoredOnGMTTimeZone() { + dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); + + String request = generateSqlRequest("offset_date_time", dateTimeWrapper.getId()); + SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); + String expectedValue = dateTimeWrapper + .getOffsetDateTime() + .format(dateTimeFormatter); + + assertThatDateStoredValueIsEqualToInsertDateValueOnGMTTimeZone(resultSet, expectedValue); + } + + @Test + @Transactional + public void storeZoneDateTimeWithUtcConfigShouldBeStoredOnGMTTimeZone() { + dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); + + String request = generateSqlRequest("zoned_date_time", dateTimeWrapper.getId()); + SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); + String expectedValue = dateTimeWrapper + .getZonedDateTime() + .format(dateTimeFormatter); + + assertThatDateStoredValueIsEqualToInsertDateValueOnGMTTimeZone(resultSet, expectedValue); + } + + @Test + @Transactional + public void storeLocalTimeWithUtcConfigShouldBeStoredOnGMTTimeZoneAccordingToHis1stJan1970Value() { + dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); + + String request = generateSqlRequest("local_time", dateTimeWrapper.getId()); + SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); + String expectedValue = dateTimeWrapper + .getLocalTime() + .atDate(LocalDate.of(1970, Month.JANUARY, 1)) + .atZone(ZoneId.systemDefault()) + .format(timeFormatter); + + assertThatDateStoredValueIsEqualToInsertDateValueOnGMTTimeZone(resultSet, expectedValue); + } + + @Test + @Transactional + public void storeOffsetTimeWithUtcConfigShouldBeStoredOnGMTTimeZoneAccordingToHis1stJan1970Value() { + dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); + + String request = generateSqlRequest("offset_time", dateTimeWrapper.getId()); + SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); + String expectedValue = dateTimeWrapper + .getOffsetTime() + .toLocalTime() + .atDate(LocalDate.of(1970, Month.JANUARY, 1)) + .atZone(ZoneId.systemDefault()) + .format(timeFormatter); + + assertThatDateStoredValueIsEqualToInsertDateValueOnGMTTimeZone(resultSet, expectedValue); + } + + @Test + @Transactional + public void storeLocalDateWithUtcConfigShouldBeStoredWithoutTransformation() { + dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); + + String request = generateSqlRequest("local_date", dateTimeWrapper.getId()); + SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); + String expectedValue = dateTimeWrapper + .getLocalDate() + .format(dateFormatter); + + assertThatDateStoredValueIsEqualToInsertDateValueOnGMTTimeZone(resultSet, expectedValue); + } + + private String generateSqlRequest(String fieldName, long id) { + return format("SELECT %s FROM jhi_date_time_wrapper where id=%d", fieldName, id); + } + + private void assertThatDateStoredValueIsEqualToInsertDateValueOnGMTTimeZone(SqlRowSet sqlRowSet, String expectedValue) { + while (sqlRowSet.next()) { + String dbValue = sqlRowSet.getString(1); + + assertThat(dbValue).isNotNull(); + assertThat(dbValue).isEqualTo(expectedValue); + } + } +} diff --git a/src/test/java/org/securityrat/casemanagement/domain/AccessTokenTest.java b/src/test/java/org/securityrat/casemanagement/domain/AccessTokenTest.java new file mode 100644 index 0000000..a30fb99 --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/domain/AccessTokenTest.java @@ -0,0 +1,22 @@ +package org.securityrat.casemanagement.domain; + +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; +import org.securityrat.casemanagement.web.rest.TestUtil; + +public class AccessTokenTest { + + @Test + public void equalsVerifier() throws Exception { + TestUtil.equalsVerifier(AccessToken.class); + AccessToken accessToken1 = new AccessToken(); + accessToken1.setId(1L); + AccessToken accessToken2 = new AccessToken(); + accessToken2.setId(accessToken1.getId()); + assertThat(accessToken1).isEqualTo(accessToken2); + accessToken2.setId(2L); + assertThat(accessToken1).isNotEqualTo(accessToken2); + accessToken1.setId(null); + assertThat(accessToken1).isNotEqualTo(accessToken2); + } +} diff --git a/src/test/java/org/securityrat/casemanagement/domain/TicketSystemInstanceTest.java b/src/test/java/org/securityrat/casemanagement/domain/TicketSystemInstanceTest.java new file mode 100644 index 0000000..eb964c8 --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/domain/TicketSystemInstanceTest.java @@ -0,0 +1,22 @@ +package org.securityrat.casemanagement.domain; + +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; +import org.securityrat.casemanagement.web.rest.TestUtil; + +public class TicketSystemInstanceTest { + + @Test + public void equalsVerifier() throws Exception { + TestUtil.equalsVerifier(TicketSystemInstance.class); + TicketSystemInstance ticketSystemInstance1 = new TicketSystemInstance(); + ticketSystemInstance1.setId(1L); + TicketSystemInstance ticketSystemInstance2 = new TicketSystemInstance(); + ticketSystemInstance2.setId(ticketSystemInstance1.getId()); + assertThat(ticketSystemInstance1).isEqualTo(ticketSystemInstance2); + ticketSystemInstance2.setId(2L); + assertThat(ticketSystemInstance1).isNotEqualTo(ticketSystemInstance2); + ticketSystemInstance1.setId(null); + assertThat(ticketSystemInstance1).isNotEqualTo(ticketSystemInstance2); + } +} diff --git a/src/test/java/org/securityrat/casemanagement/repository/CustomAuditEventRepositoryIT.java b/src/test/java/org/securityrat/casemanagement/repository/CustomAuditEventRepositoryIT.java new file mode 100644 index 0000000..785d95b --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/repository/CustomAuditEventRepositoryIT.java @@ -0,0 +1,164 @@ +package org.securityrat.casemanagement.repository; + +import org.securityrat.casemanagement.CaseManagementApp; +import org.securityrat.casemanagement.config.Constants; +import org.securityrat.casemanagement.config.TestSecurityConfiguration; +import org.securityrat.casemanagement.config.audit.AuditEventConverter; +import org.securityrat.casemanagement.domain.PersistentAuditEvent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.security.web.authentication.WebAuthenticationDetails; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.HttpSession; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.securityrat.casemanagement.repository.CustomAuditEventRepository.EVENT_DATA_COLUMN_MAX_LENGTH; + +/** + * Integration tests for {@link CustomAuditEventRepository}. + */ +@SpringBootTest(classes = {CaseManagementApp.class, TestSecurityConfiguration.class}) +@Transactional +public class CustomAuditEventRepositoryIT { + + @Autowired + private PersistenceAuditEventRepository persistenceAuditEventRepository; + + @Autowired + private AuditEventConverter auditEventConverter; + + private CustomAuditEventRepository customAuditEventRepository; + + private PersistentAuditEvent testUserEvent; + + private PersistentAuditEvent testOtherUserEvent; + + private PersistentAuditEvent testOldUserEvent; + + @BeforeEach + public void setup() { + customAuditEventRepository = new CustomAuditEventRepository(persistenceAuditEventRepository, auditEventConverter); + persistenceAuditEventRepository.deleteAll(); + Instant oneHourAgo = Instant.now().minusSeconds(3600); + + testUserEvent = new PersistentAuditEvent(); + testUserEvent.setPrincipal("test-user"); + testUserEvent.setAuditEventType("test-type"); + testUserEvent.setAuditEventDate(oneHourAgo); + Map data = new HashMap<>(); + data.put("test-key", "test-value"); + testUserEvent.setData(data); + + testOldUserEvent = new PersistentAuditEvent(); + testOldUserEvent.setPrincipal("test-user"); + testOldUserEvent.setAuditEventType("test-type"); + testOldUserEvent.setAuditEventDate(oneHourAgo.minusSeconds(10000)); + + testOtherUserEvent = new PersistentAuditEvent(); + testOtherUserEvent.setPrincipal("other-test-user"); + testOtherUserEvent.setAuditEventType("test-type"); + testOtherUserEvent.setAuditEventDate(oneHourAgo); + } + + @Test + public void addAuditEvent() { + Map data = new HashMap<>(); + data.put("test-key", "test-value"); + AuditEvent event = new AuditEvent("test-user", "test-type", data); + customAuditEventRepository.add(event); + List persistentAuditEvents = persistenceAuditEventRepository.findAll(); + assertThat(persistentAuditEvents).hasSize(1); + PersistentAuditEvent persistentAuditEvent = persistentAuditEvents.get(0); + assertThat(persistentAuditEvent.getPrincipal()).isEqualTo(event.getPrincipal()); + assertThat(persistentAuditEvent.getAuditEventType()).isEqualTo(event.getType()); + assertThat(persistentAuditEvent.getData()).containsKey("test-key"); + assertThat(persistentAuditEvent.getData().get("test-key")).isEqualTo("test-value"); + assertThat(persistentAuditEvent.getAuditEventDate().truncatedTo(ChronoUnit.MILLIS)) + .isEqualTo(event.getTimestamp().truncatedTo(ChronoUnit.MILLIS)); + } + + @Test + public void addAuditEventTruncateLargeData() { + Map data = new HashMap<>(); + StringBuilder largeData = new StringBuilder(); + for (int i = 0; i < EVENT_DATA_COLUMN_MAX_LENGTH + 10; i++) { + largeData.append("a"); + } + data.put("test-key", largeData); + AuditEvent event = new AuditEvent("test-user", "test-type", data); + customAuditEventRepository.add(event); + List persistentAuditEvents = persistenceAuditEventRepository.findAll(); + assertThat(persistentAuditEvents).hasSize(1); + PersistentAuditEvent persistentAuditEvent = persistentAuditEvents.get(0); + assertThat(persistentAuditEvent.getPrincipal()).isEqualTo(event.getPrincipal()); + assertThat(persistentAuditEvent.getAuditEventType()).isEqualTo(event.getType()); + assertThat(persistentAuditEvent.getData()).containsKey("test-key"); + String actualData = persistentAuditEvent.getData().get("test-key"); + assertThat(actualData.length()).isEqualTo(EVENT_DATA_COLUMN_MAX_LENGTH); + assertThat(actualData).isSubstringOf(largeData); + assertThat(persistentAuditEvent.getAuditEventDate().truncatedTo(ChronoUnit.MILLIS)) + .isEqualTo(event.getTimestamp().truncatedTo(ChronoUnit.MILLIS)); + } + + @Test + public void testAddEventWithWebAuthenticationDetails() { + HttpSession session = new MockHttpSession(null, "test-session-id"); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setSession(session); + request.setRemoteAddr("1.2.3.4"); + WebAuthenticationDetails details = new WebAuthenticationDetails(request); + Map data = new HashMap<>(); + data.put("test-key", details); + AuditEvent event = new AuditEvent("test-user", "test-type", data); + customAuditEventRepository.add(event); + List persistentAuditEvents = persistenceAuditEventRepository.findAll(); + assertThat(persistentAuditEvents).hasSize(1); + PersistentAuditEvent persistentAuditEvent = persistentAuditEvents.get(0); + assertThat(persistentAuditEvent.getData().get("remoteAddress")).isEqualTo("1.2.3.4"); + assertThat(persistentAuditEvent.getData().get("sessionId")).isEqualTo("test-session-id"); + } + + @Test + public void testAddEventWithNullData() { + Map data = new HashMap<>(); + data.put("test-key", null); + AuditEvent event = new AuditEvent("test-user", "test-type", data); + customAuditEventRepository.add(event); + List persistentAuditEvents = persistenceAuditEventRepository.findAll(); + assertThat(persistentAuditEvents).hasSize(1); + PersistentAuditEvent persistentAuditEvent = persistentAuditEvents.get(0); + assertThat(persistentAuditEvent.getData().get("test-key")).isEqualTo("null"); + } + + @Test + public void addAuditEventWithAnonymousUser() { + Map data = new HashMap<>(); + data.put("test-key", "test-value"); + AuditEvent event = new AuditEvent(Constants.ANONYMOUS_USER, "test-type", data); + customAuditEventRepository.add(event); + List persistentAuditEvents = persistenceAuditEventRepository.findAll(); + assertThat(persistentAuditEvents).hasSize(0); + } + + @Test + public void addAuditEventWithAuthorizationFailureType() { + Map data = new HashMap<>(); + data.put("test-key", "test-value"); + AuditEvent event = new AuditEvent("test-user", "AUTHORIZATION_FAILURE", data); + customAuditEventRepository.add(event); + List persistentAuditEvents = persistenceAuditEventRepository.findAll(); + assertThat(persistentAuditEvents).hasSize(0); + } + +} diff --git a/src/test/java/org/securityrat/casemanagement/repository/timezone/DateTimeWrapper.java b/src/test/java/org/securityrat/casemanagement/repository/timezone/DateTimeWrapper.java new file mode 100644 index 0000000..6dc59bf --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/repository/timezone/DateTimeWrapper.java @@ -0,0 +1,131 @@ +package org.securityrat.casemanagement.repository.timezone; + +import javax.persistence.*; +import java.io.Serializable; +import java.time.*; +import java.util.Objects; + +@Entity +@Table(name = "jhi_date_time_wrapper") +public class DateTimeWrapper implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "instant") + private Instant instant; + + @Column(name = "local_date_time") + private LocalDateTime localDateTime; + + @Column(name = "offset_date_time") + private OffsetDateTime offsetDateTime; + + @Column(name = "zoned_date_time") + private ZonedDateTime zonedDateTime; + + @Column(name = "local_time") + private LocalTime localTime; + + @Column(name = "offset_time") + private OffsetTime offsetTime; + + @Column(name = "local_date") + private LocalDate localDate; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Instant getInstant() { + return instant; + } + + public void setInstant(Instant instant) { + this.instant = instant; + } + + public LocalDateTime getLocalDateTime() { + return localDateTime; + } + + public void setLocalDateTime(LocalDateTime localDateTime) { + this.localDateTime = localDateTime; + } + + public OffsetDateTime getOffsetDateTime() { + return offsetDateTime; + } + + public void setOffsetDateTime(OffsetDateTime offsetDateTime) { + this.offsetDateTime = offsetDateTime; + } + + public ZonedDateTime getZonedDateTime() { + return zonedDateTime; + } + + public void setZonedDateTime(ZonedDateTime zonedDateTime) { + this.zonedDateTime = zonedDateTime; + } + + public LocalTime getLocalTime() { + return localTime; + } + + public void setLocalTime(LocalTime localTime) { + this.localTime = localTime; + } + + public OffsetTime getOffsetTime() { + return offsetTime; + } + + public void setOffsetTime(OffsetTime offsetTime) { + this.offsetTime = offsetTime; + } + + public LocalDate getLocalDate() { + return localDate; + } + + public void setLocalDate(LocalDate localDate) { + this.localDate = localDate; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + DateTimeWrapper dateTimeWrapper = (DateTimeWrapper) o; + return !(dateTimeWrapper.getId() == null || getId() == null) && Objects.equals(getId(), dateTimeWrapper.getId()); + } + + @Override + public int hashCode() { + return Objects.hashCode(getId()); + } + + @Override + public String toString() { + return "TimeZoneTest{" + + "id=" + id + + ", instant=" + instant + + ", localDateTime=" + localDateTime + + ", offsetDateTime=" + offsetDateTime + + ", zonedDateTime=" + zonedDateTime + + '}'; + } +} diff --git a/src/test/java/org/securityrat/casemanagement/repository/timezone/DateTimeWrapperRepository.java b/src/test/java/org/securityrat/casemanagement/repository/timezone/DateTimeWrapperRepository.java new file mode 100644 index 0000000..699bbc3 --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/repository/timezone/DateTimeWrapperRepository.java @@ -0,0 +1,12 @@ +package org.securityrat.casemanagement.repository.timezone; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +/** + * Spring Data JPA repository for the {@link DateTimeWrapper} entity. + */ +@Repository +public interface DateTimeWrapperRepository extends JpaRepository { + +} diff --git a/src/test/java/org/securityrat/casemanagement/security/SecurityUtilsUnitTest.java b/src/test/java/org/securityrat/casemanagement/security/SecurityUtilsUnitTest.java new file mode 100644 index 0000000..c2fdf89 --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/security/SecurityUtilsUnitTest.java @@ -0,0 +1,87 @@ +package org.securityrat.casemanagement.security; + +import org.junit.jupiter.api.Test; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.core.oidc.OidcIdToken; +import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; + +import java.time.Instant; +import java.util.*; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames.ID_TOKEN; + +/** + * Test class for the {@link SecurityUtils} utility class. + */ +public class SecurityUtilsUnitTest { + + @Test + public void testGetCurrentUserLogin() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("admin", "admin")); + SecurityContextHolder.setContext(securityContext); + Optional login = SecurityUtils.getCurrentUserLogin(); + assertThat(login).contains("admin"); + } + + @Test + public void testGetCurrentUserLoginForOAuth2() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + Map claims = new HashMap<>(); + claims.put("groups", "ROLE_USER"); + claims.put("sub", 123); + claims.put("preferred_username", "admin"); + OidcIdToken idToken = new OidcIdToken(ID_TOKEN, Instant.now(), + Instant.now().plusSeconds(60), claims); + Collection authorities = new ArrayList<>(); + authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.USER)); + OidcUser user = new DefaultOidcUser(authorities, idToken); + OAuth2AuthenticationToken bla = new OAuth2AuthenticationToken(user, authorities, "oidc"); + securityContext.setAuthentication(bla); + SecurityContextHolder.setContext(securityContext); + + Optional login = SecurityUtils.getCurrentUserLogin(); + + assertThat(login).contains("admin"); + } + + @Test + public void testIsAuthenticated() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("admin", "admin")); + SecurityContextHolder.setContext(securityContext); + boolean isAuthenticated = SecurityUtils.isAuthenticated(); + assertThat(isAuthenticated).isTrue(); + } + + @Test + public void testAnonymousIsNotAuthenticated() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + Collection authorities = new ArrayList<>(); + authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ANONYMOUS)); + securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("anonymous", "anonymous", authorities)); + SecurityContextHolder.setContext(securityContext); + boolean isAuthenticated = SecurityUtils.isAuthenticated(); + assertThat(isAuthenticated).isFalse(); + } + + @Test + public void testIsCurrentUserInRole() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + Collection authorities = new ArrayList<>(); + authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.USER)); + securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("user", "user", authorities)); + SecurityContextHolder.setContext(securityContext); + + assertThat(SecurityUtils.isCurrentUserInRole(AuthoritiesConstants.USER)).isTrue(); + assertThat(SecurityUtils.isCurrentUserInRole(AuthoritiesConstants.ADMIN)).isFalse(); + } + +} diff --git a/src/test/java/org/securityrat/casemanagement/security/oauth2/AudienceValidatorTest.java b/src/test/java/org/securityrat/casemanagement/security/oauth2/AudienceValidatorTest.java new file mode 100644 index 0000000..efd25ae --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/security/oauth2/AudienceValidatorTest.java @@ -0,0 +1,41 @@ +package org.securityrat.casemanagement.security.oauth2; + +import org.junit.jupiter.api.Test; +import org.springframework.security.oauth2.jwt.Jwt; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Test class for the {@link AudienceValidator} utility class. + */ +public class AudienceValidatorTest { + + private AudienceValidator validator = new AudienceValidator(Arrays.asList("api://default")); + + @Test + @SuppressWarnings("unchecked") + public void testInvalidAudience() { + Map claims = new HashMap<>(); + claims.put("aud", "bar"); + Jwt badJwt = mock(Jwt.class); + when(badJwt.getAudience()).thenReturn(new ArrayList(claims.values())); + assertThat(validator.validate(badJwt).hasErrors()).isTrue(); + } + + @Test + @SuppressWarnings("unchecked") + public void testValidAudience() { + Map claims = new HashMap<>(); + claims.put("aud", "api://default"); + Jwt jwt = mock(Jwt.class); + when(jwt.getAudience()).thenReturn(new ArrayList(claims.values())); + assertThat(validator.validate(jwt).hasErrors()).isFalse(); + } +} diff --git a/src/test/java/org/securityrat/casemanagement/service/UserServiceIT.java b/src/test/java/org/securityrat/casemanagement/service/UserServiceIT.java new file mode 100644 index 0000000..e0f4b72 --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/service/UserServiceIT.java @@ -0,0 +1,175 @@ +package org.securityrat.casemanagement.service; + +import org.securityrat.casemanagement.CaseManagementApp; +import org.securityrat.casemanagement.config.Constants; +import org.securityrat.casemanagement.config.TestSecurityConfiguration; +import org.securityrat.casemanagement.domain.User; +import org.securityrat.casemanagement.repository.UserRepository; +import org.securityrat.casemanagement.security.AuthoritiesConstants; +import org.securityrat.casemanagement.service.dto.UserDTO; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link UserService}. + */ +@SpringBootTest(classes = {CaseManagementApp.class, TestSecurityConfiguration.class}) +@Transactional +public class UserServiceIT { + + private static final String DEFAULT_LOGIN = "johndoe"; + + private static final String DEFAULT_EMAIL = "johndoe@localhost"; + + private static final String DEFAULT_FIRSTNAME = "john"; + + private static final String DEFAULT_LASTNAME = "doe"; + + private static final String DEFAULT_IMAGEURL = "http://placehold.it/50x50"; + + private static final String DEFAULT_LANGKEY = "dummy"; + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserService userService; + + private User user; + + private Map userDetails; + + @BeforeEach + public void init() { + user = new User(); + user.setLogin(DEFAULT_LOGIN); + user.setActivated(true); + user.setEmail(DEFAULT_EMAIL); + user.setFirstName(DEFAULT_FIRSTNAME); + user.setLastName(DEFAULT_LASTNAME); + user.setImageUrl(DEFAULT_IMAGEURL); + user.setLangKey(DEFAULT_LANGKEY); + + userDetails = new HashMap<>(); + userDetails.put("sub", DEFAULT_LOGIN); + userDetails.put("email", DEFAULT_EMAIL); + userDetails.put("given_name", DEFAULT_FIRSTNAME); + userDetails.put("family_name", DEFAULT_LASTNAME); + userDetails.put("picture", DEFAULT_IMAGEURL); + } + + @Test + @Transactional + public void assertThatAnonymousUserIsNotGet() { + user.setId(Constants.ANONYMOUS_USER); + user.setLogin(Constants.ANONYMOUS_USER); + if (!userRepository.findOneByLogin(Constants.ANONYMOUS_USER).isPresent()) { + userRepository.saveAndFlush(user); + } + final PageRequest pageable = PageRequest.of(0, (int) userRepository.count()); + final Page allManagedUsers = userService.getAllManagedUsers(pageable); + assertThat(allManagedUsers.getContent().stream() + .noneMatch(user -> Constants.ANONYMOUS_USER.equals(user.getLogin()))) + .isTrue(); + } + + @Test + @Transactional + public void testDefaultUserDetails() { + OAuth2AuthenticationToken authentication = createMockOAuth2AuthenticationToken(userDetails); + UserDTO userDTO = userService.getUserFromAuthentication(authentication); + + assertThat(userDTO.getLogin()).isEqualTo(DEFAULT_LOGIN); + assertThat(userDTO.getFirstName()).isEqualTo(DEFAULT_FIRSTNAME); + assertThat(userDTO.getLastName()).isEqualTo(DEFAULT_LASTNAME); + assertThat(userDTO.getEmail()).isEqualTo(DEFAULT_EMAIL); + assertThat(userDTO.isActivated()).isTrue(); + assertThat(userDTO.getLangKey()).isEqualTo(Constants.DEFAULT_LANGUAGE); + assertThat(userDTO.getImageUrl()).isEqualTo(DEFAULT_IMAGEURL); + assertThat(userDTO.getAuthorities()).contains(AuthoritiesConstants.ANONYMOUS); + } + + @Test + @Transactional + public void testUserDetailsWithUsername() { + userDetails.put("preferred_username", "TEST"); + + OAuth2AuthenticationToken authentication = createMockOAuth2AuthenticationToken(userDetails); + UserDTO userDTO = userService.getUserFromAuthentication(authentication); + + assertThat(userDTO.getLogin()).isEqualTo("test"); + } + + @Test + @Transactional + public void testUserDetailsWithLangKey() { + userDetails.put("langKey", DEFAULT_LANGKEY); + userDetails.put("locale", "en-US"); + + OAuth2AuthenticationToken authentication = createMockOAuth2AuthenticationToken(userDetails); + UserDTO userDTO = userService.getUserFromAuthentication(authentication); + + assertThat(userDTO.getLangKey()).isEqualTo(DEFAULT_LANGKEY); + } + + @Test + @Transactional + public void testUserDetailsWithLocale() { + userDetails.put("locale", "it-IT"); + + OAuth2AuthenticationToken authentication = createMockOAuth2AuthenticationToken(userDetails); + UserDTO userDTO = userService.getUserFromAuthentication(authentication); + + assertThat(userDTO.getLangKey()).isEqualTo("it"); + } + + @Test + @Transactional + public void testUserDetailsWithUSLocaleUnderscore() { + userDetails.put("locale", "en_US"); + + OAuth2AuthenticationToken authentication = createMockOAuth2AuthenticationToken(userDetails); + UserDTO userDTO = userService.getUserFromAuthentication(authentication); + + assertThat(userDTO.getLangKey()).isEqualTo("en"); + } + + @Test + @Transactional + public void testUserDetailsWithUSLocaleDash() { + userDetails.put("locale", "en-US"); + + OAuth2AuthenticationToken authentication = createMockOAuth2AuthenticationToken(userDetails); + UserDTO userDTO = userService.getUserFromAuthentication(authentication); + + assertThat(userDTO.getLangKey()).isEqualTo("en"); + } + + private OAuth2AuthenticationToken createMockOAuth2AuthenticationToken(Map userDetails) { + Collection authorities = Collections.singletonList(new SimpleGrantedAuthority(AuthoritiesConstants.ANONYMOUS)); + UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(Constants.ANONYMOUS_USER, Constants.ANONYMOUS_USER, authorities); + usernamePasswordAuthenticationToken.setDetails(userDetails); + OAuth2User user = new DefaultOAuth2User(authorities, userDetails, "sub"); + + return new OAuth2AuthenticationToken(user, authorities, "oidc"); + } +} diff --git a/src/test/java/org/securityrat/casemanagement/service/mapper/UserMapperTest.java b/src/test/java/org/securityrat/casemanagement/service/mapper/UserMapperTest.java new file mode 100644 index 0000000..b9f1b56 --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/service/mapper/UserMapperTest.java @@ -0,0 +1,134 @@ +package org.securityrat.casemanagement.service.mapper; + +import org.securityrat.casemanagement.domain.User; +import org.securityrat.casemanagement.service.dto.UserDTO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link UserMapper}. + */ +public class UserMapperTest { + + private static final String DEFAULT_LOGIN = "johndoe"; + private static final String DEFAULT_ID = "id1"; + + private UserMapper userMapper; + private User user; + private UserDTO userDto; + + @BeforeEach + public void init() { + userMapper = new UserMapper(); + user = new User(); + user.setLogin(DEFAULT_LOGIN); + user.setActivated(true); + user.setEmail("johndoe@localhost"); + user.setFirstName("john"); + user.setLastName("doe"); + user.setImageUrl("image_url"); + user.setLangKey("en"); + + userDto = new UserDTO(user); + } + + @Test + public void usersToUserDTOsShouldMapOnlyNonNullUsers() { + List users = new ArrayList<>(); + users.add(user); + users.add(null); + + List userDTOS = userMapper.usersToUserDTOs(users); + + assertThat(userDTOS).isNotEmpty(); + assertThat(userDTOS).size().isEqualTo(1); + } + + @Test + public void userDTOsToUsersShouldMapOnlyNonNullUsers() { + List usersDto = new ArrayList<>(); + usersDto.add(userDto); + usersDto.add(null); + + List users = userMapper.userDTOsToUsers(usersDto); + + assertThat(users).isNotEmpty(); + assertThat(users).size().isEqualTo(1); + } + + @Test + public void userDTOsToUsersWithAuthoritiesStringShouldMapToUsersWithAuthoritiesDomain() { + Set authoritiesAsString = new HashSet<>(); + authoritiesAsString.add("ADMIN"); + userDto.setAuthorities(authoritiesAsString); + + List usersDto = new ArrayList<>(); + usersDto.add(userDto); + + List users = userMapper.userDTOsToUsers(usersDto); + + assertThat(users).isNotEmpty(); + assertThat(users).size().isEqualTo(1); + assertThat(users.get(0).getAuthorities()).isNotNull(); + assertThat(users.get(0).getAuthorities()).isNotEmpty(); + assertThat(users.get(0).getAuthorities().iterator().next().getName()).isEqualTo("ADMIN"); + } + + @Test + public void userDTOsToUsersMapWithNullAuthoritiesStringShouldReturnUserWithEmptyAuthorities() { + userDto.setAuthorities(null); + + List usersDto = new ArrayList<>(); + usersDto.add(userDto); + + List users = userMapper.userDTOsToUsers(usersDto); + + assertThat(users).isNotEmpty(); + assertThat(users).size().isEqualTo(1); + assertThat(users.get(0).getAuthorities()).isNotNull(); + assertThat(users.get(0).getAuthorities()).isEmpty(); + } + + @Test + public void userDTOToUserMapWithAuthoritiesStringShouldReturnUserWithAuthorities() { + Set authoritiesAsString = new HashSet<>(); + authoritiesAsString.add("ADMIN"); + userDto.setAuthorities(authoritiesAsString); + + User user = userMapper.userDTOToUser(userDto); + + assertThat(user).isNotNull(); + assertThat(user.getAuthorities()).isNotNull(); + assertThat(user.getAuthorities()).isNotEmpty(); + assertThat(user.getAuthorities().iterator().next().getName()).isEqualTo("ADMIN"); + } + + @Test + public void userDTOToUserMapWithNullAuthoritiesStringShouldReturnUserWithEmptyAuthorities() { + userDto.setAuthorities(null); + + User user = userMapper.userDTOToUser(userDto); + + assertThat(user).isNotNull(); + assertThat(user.getAuthorities()).isNotNull(); + assertThat(user.getAuthorities()).isEmpty(); + } + + @Test + public void userDTOToUserMapWithNullUserShouldReturnNull() { + assertThat(userMapper.userDTOToUser(null)).isNull(); + } + + @Test + public void testUserFromId() { + assertThat(userMapper.userFromId(DEFAULT_ID).getId()).isEqualTo(DEFAULT_ID); + assertThat(userMapper.userFromId(null)).isNull(); + } +} diff --git a/src/test/java/org/securityrat/casemanagement/web/rest/AccessTokenResourceIT.java b/src/test/java/org/securityrat/casemanagement/web/rest/AccessTokenResourceIT.java new file mode 100644 index 0000000..e4f3061 --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/web/rest/AccessTokenResourceIT.java @@ -0,0 +1,313 @@ +package org.securityrat.casemanagement.web.rest; + +import org.securityrat.casemanagement.CaseManagementApp; +import org.securityrat.casemanagement.config.TestSecurityConfiguration; +import org.securityrat.casemanagement.domain.AccessToken; +import org.securityrat.casemanagement.repository.UserRepository; +import org.securityrat.casemanagement.repository.AccessTokenRepository; +import org.securityrat.casemanagement.web.rest.errors.ExceptionTranslator; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.web.PageableHandlerMethodArgumentResolver; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.Validator; + +import javax.persistence.EntityManager; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.time.ZoneOffset; +import java.time.ZoneId; +import java.util.List; + +import static org.securityrat.casemanagement.web.rest.TestUtil.sameInstant; +import static org.securityrat.casemanagement.web.rest.TestUtil.createFormattingConversionService; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Integration tests for the {@link AccessTokenResource} REST controller. + */ +@SpringBootTest(classes = {CaseManagementApp.class, TestSecurityConfiguration.class}) +public class AccessTokenResourceIT { + + private static final String DEFAULT_TOKEN = "AAAAAAAAAA"; + private static final String UPDATED_TOKEN = "BBBBBBBBBB"; + + private static final ZonedDateTime DEFAULT_EXPIRATION_DATE = ZonedDateTime.ofInstant(Instant.ofEpochMilli(0L), ZoneOffset.UTC); + private static final ZonedDateTime UPDATED_EXPIRATION_DATE = ZonedDateTime.now(ZoneId.systemDefault()).withNano(0); + + private static final String DEFAULT_SALT = "AAAAAAAAAA"; + private static final String UPDATED_SALT = "BBBBBBBBBB"; + + private static final String DEFAULT_REFRESH_TOKEN = "AAAAAAAAAA"; + private static final String UPDATED_REFRESH_TOKEN = "BBBBBBBBBB"; + + @Autowired + private AccessTokenRepository accessTokenRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private MappingJackson2HttpMessageConverter jacksonMessageConverter; + + @Autowired + private PageableHandlerMethodArgumentResolver pageableArgumentResolver; + + @Autowired + private ExceptionTranslator exceptionTranslator; + + @Autowired + private EntityManager em; + + @Autowired + private Validator validator; + + private MockMvc restAccessTokenMockMvc; + + private AccessToken accessToken; + + @BeforeEach + public void setup() { + MockitoAnnotations.initMocks(this); + final AccessTokenResource accessTokenResource = new AccessTokenResource(accessTokenRepository, userRepository); + this.restAccessTokenMockMvc = MockMvcBuilders.standaloneSetup(accessTokenResource) + .setCustomArgumentResolvers(pageableArgumentResolver) + .setControllerAdvice(exceptionTranslator) + .setConversionService(createFormattingConversionService()) + .setMessageConverters(jacksonMessageConverter) + .setValidator(validator).build(); + } + + /** + * Create an entity for this test. + * + * This is a static method, as tests for other entities might also need it, + * if they test an entity which requires the current entity. + */ + public static AccessToken createEntity(EntityManager em) { + AccessToken accessToken = new AccessToken() + .token(DEFAULT_TOKEN) + .expirationDate(DEFAULT_EXPIRATION_DATE) + .salt(DEFAULT_SALT) + .refreshToken(DEFAULT_REFRESH_TOKEN); + return accessToken; + } + /** + * Create an updated entity for this test. + * + * This is a static method, as tests for other entities might also need it, + * if they test an entity which requires the current entity. + */ + public static AccessToken createUpdatedEntity(EntityManager em) { + AccessToken accessToken = new AccessToken() + .token(UPDATED_TOKEN) + .expirationDate(UPDATED_EXPIRATION_DATE) + .salt(UPDATED_SALT) + .refreshToken(UPDATED_REFRESH_TOKEN); + return accessToken; + } + + @BeforeEach + public void initTest() { + accessToken = createEntity(em); + } + + @Test + @Transactional + public void createAccessToken() throws Exception { + int databaseSizeBeforeCreate = accessTokenRepository.findAll().size(); + + // Create the AccessToken + restAccessTokenMockMvc.perform(post("/api/access-tokens") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(accessToken))) + .andExpect(status().isCreated()); + + // Validate the AccessToken in the database + List accessTokenList = accessTokenRepository.findAll(); + assertThat(accessTokenList).hasSize(databaseSizeBeforeCreate + 1); + AccessToken testAccessToken = accessTokenList.get(accessTokenList.size() - 1); + assertThat(testAccessToken.getToken()).isEqualTo(DEFAULT_TOKEN); + assertThat(testAccessToken.getExpirationDate()).isEqualTo(DEFAULT_EXPIRATION_DATE); + assertThat(testAccessToken.getSalt()).isEqualTo(DEFAULT_SALT); + assertThat(testAccessToken.getRefreshToken()).isEqualTo(DEFAULT_REFRESH_TOKEN); + } + + @Test + @Transactional + public void createAccessTokenWithExistingId() throws Exception { + int databaseSizeBeforeCreate = accessTokenRepository.findAll().size(); + + // Create the AccessToken with an existing ID + accessToken.setId(1L); + + // An entity with an existing ID cannot be created, so this API call must fail + restAccessTokenMockMvc.perform(post("/api/access-tokens") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(accessToken))) + .andExpect(status().isBadRequest()); + + // Validate the AccessToken in the database + List accessTokenList = accessTokenRepository.findAll(); + assertThat(accessTokenList).hasSize(databaseSizeBeforeCreate); + } + + + @Test + @Transactional + public void checkTokenIsRequired() throws Exception { + int databaseSizeBeforeTest = accessTokenRepository.findAll().size(); + // set the field null + accessToken.setToken(null); + + // Create the AccessToken, which fails. + + restAccessTokenMockMvc.perform(post("/api/access-tokens") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(accessToken))) + .andExpect(status().isBadRequest()); + + List accessTokenList = accessTokenRepository.findAll(); + assertThat(accessTokenList).hasSize(databaseSizeBeforeTest); + } + + @Test + @Transactional + public void checkSaltIsRequired() throws Exception { + int databaseSizeBeforeTest = accessTokenRepository.findAll().size(); + // set the field null + accessToken.setSalt(null); + + // Create the AccessToken, which fails. + + restAccessTokenMockMvc.perform(post("/api/access-tokens") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(accessToken))) + .andExpect(status().isBadRequest()); + + List accessTokenList = accessTokenRepository.findAll(); + assertThat(accessTokenList).hasSize(databaseSizeBeforeTest); + } + + @Test + @Transactional + public void getAllAccessTokens() throws Exception { + // Initialize the database + accessTokenRepository.saveAndFlush(accessToken); + + // Get all the accessTokenList + restAccessTokenMockMvc.perform(get("/api/access-tokens?sort=id,desc")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("$.[*].id").value(hasItem(accessToken.getId().intValue()))) + .andExpect(jsonPath("$.[*].token").value(hasItem(DEFAULT_TOKEN))) + .andExpect(jsonPath("$.[*].expirationDate").value(hasItem(sameInstant(DEFAULT_EXPIRATION_DATE)))) + .andExpect(jsonPath("$.[*].salt").value(hasItem(DEFAULT_SALT))) + .andExpect(jsonPath("$.[*].refreshToken").value(hasItem(DEFAULT_REFRESH_TOKEN))); + } + + @Test + @Transactional + public void getAccessToken() throws Exception { + // Initialize the database + accessTokenRepository.saveAndFlush(accessToken); + + // Get the accessToken + restAccessTokenMockMvc.perform(get("/api/access-tokens/{id}", accessToken.getId())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("$.id").value(accessToken.getId().intValue())) + .andExpect(jsonPath("$.token").value(DEFAULT_TOKEN)) + .andExpect(jsonPath("$.expirationDate").value(sameInstant(DEFAULT_EXPIRATION_DATE))) + .andExpect(jsonPath("$.salt").value(DEFAULT_SALT)) + .andExpect(jsonPath("$.refreshToken").value(DEFAULT_REFRESH_TOKEN)); + } + + @Test + @Transactional + public void getNonExistingAccessToken() throws Exception { + // Get the accessToken + restAccessTokenMockMvc.perform(get("/api/access-tokens/{id}", Long.MAX_VALUE)) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + public void updateAccessToken() throws Exception { + // Initialize the database + accessTokenRepository.saveAndFlush(accessToken); + + int databaseSizeBeforeUpdate = accessTokenRepository.findAll().size(); + + // Update the accessToken + AccessToken updatedAccessToken = accessTokenRepository.findById(accessToken.getId()).get(); + // Disconnect from session so that the updates on updatedAccessToken are not directly saved in db + em.detach(updatedAccessToken); + updatedAccessToken + .token(UPDATED_TOKEN) + .expirationDate(UPDATED_EXPIRATION_DATE) + .salt(UPDATED_SALT) + .refreshToken(UPDATED_REFRESH_TOKEN); + + restAccessTokenMockMvc.perform(put("/api/access-tokens") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(updatedAccessToken))) + .andExpect(status().isOk()); + + // Validate the AccessToken in the database + List accessTokenList = accessTokenRepository.findAll(); + assertThat(accessTokenList).hasSize(databaseSizeBeforeUpdate); + AccessToken testAccessToken = accessTokenList.get(accessTokenList.size() - 1); + assertThat(testAccessToken.getToken()).isEqualTo(UPDATED_TOKEN); + assertThat(testAccessToken.getExpirationDate()).isEqualTo(UPDATED_EXPIRATION_DATE); + assertThat(testAccessToken.getSalt()).isEqualTo(UPDATED_SALT); + assertThat(testAccessToken.getRefreshToken()).isEqualTo(UPDATED_REFRESH_TOKEN); + } + + @Test + @Transactional + public void updateNonExistingAccessToken() throws Exception { + int databaseSizeBeforeUpdate = accessTokenRepository.findAll().size(); + + // Create the AccessToken + + // If the entity doesn't have an ID, it will throw BadRequestAlertException + restAccessTokenMockMvc.perform(put("/api/access-tokens") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(accessToken))) + .andExpect(status().isBadRequest()); + + // Validate the AccessToken in the database + List accessTokenList = accessTokenRepository.findAll(); + assertThat(accessTokenList).hasSize(databaseSizeBeforeUpdate); + } + + @Test + @Transactional + public void deleteAccessToken() throws Exception { + // Initialize the database + accessTokenRepository.saveAndFlush(accessToken); + + int databaseSizeBeforeDelete = accessTokenRepository.findAll().size(); + + // Delete the accessToken + restAccessTokenMockMvc.perform(delete("/api/access-tokens/{id}", accessToken.getId()) + .accept(TestUtil.APPLICATION_JSON_UTF8)) + .andExpect(status().isNoContent()); + + // Validate the database contains one less item + List accessTokenList = accessTokenRepository.findAll(); + assertThat(accessTokenList).hasSize(databaseSizeBeforeDelete - 1); + } +} diff --git a/src/test/java/org/securityrat/casemanagement/web/rest/AuditResourceIT.java b/src/test/java/org/securityrat/casemanagement/web/rest/AuditResourceIT.java new file mode 100644 index 0000000..555ae5b --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/web/rest/AuditResourceIT.java @@ -0,0 +1,165 @@ +package org.securityrat.casemanagement.web.rest; + +import org.securityrat.casemanagement.CaseManagementApp; +import org.securityrat.casemanagement.config.TestSecurityConfiguration; +import io.github.jhipster.config.JHipsterProperties; +import org.securityrat.casemanagement.config.audit.AuditEventConverter; +import org.securityrat.casemanagement.domain.PersistentAuditEvent; +import org.securityrat.casemanagement.repository.PersistenceAuditEventRepository; + +import org.securityrat.casemanagement.service.AuditEventService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.web.PageableHandlerMethodArgumentResolver; +import org.springframework.format.support.FormattingConversionService; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Instant; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Integration tests for the {@link AuditResource} REST controller. + */ +@SpringBootTest(classes = {CaseManagementApp.class, TestSecurityConfiguration.class}) +@Transactional +public class AuditResourceIT { + + private static final String SAMPLE_PRINCIPAL = "SAMPLE_PRINCIPAL"; + private static final String SAMPLE_TYPE = "SAMPLE_TYPE"; + private static final Instant SAMPLE_TIMESTAMP = Instant.parse("2015-08-04T10:11:30Z"); + private static final long SECONDS_PER_DAY = 60 * 60 * 24; + + @Autowired + private PersistenceAuditEventRepository auditEventRepository; + + @Autowired + private AuditEventConverter auditEventConverter; + + @Autowired + private JHipsterProperties jhipsterProperties; + + @Autowired + private MappingJackson2HttpMessageConverter jacksonMessageConverter; + + @Autowired + @Qualifier("mvcConversionService") + private FormattingConversionService formattingConversionService; + + @Autowired + private PageableHandlerMethodArgumentResolver pageableArgumentResolver; + + private PersistentAuditEvent auditEvent; + + private MockMvc restAuditMockMvc; + + @BeforeEach + public void setup() { + MockitoAnnotations.initMocks(this); + AuditEventService auditEventService = + new AuditEventService(auditEventRepository, auditEventConverter, jhipsterProperties); + AuditResource auditResource = new AuditResource(auditEventService); + this.restAuditMockMvc = MockMvcBuilders.standaloneSetup(auditResource) + .setCustomArgumentResolvers(pageableArgumentResolver) + .setConversionService(formattingConversionService) + .setMessageConverters(jacksonMessageConverter).build(); + } + + @BeforeEach + public void initTest() { + auditEventRepository.deleteAll(); + auditEvent = new PersistentAuditEvent(); + auditEvent.setAuditEventType(SAMPLE_TYPE); + auditEvent.setPrincipal(SAMPLE_PRINCIPAL); + auditEvent.setAuditEventDate(SAMPLE_TIMESTAMP); + } + + @Test + public void getAllAudits() throws Exception { + // Initialize the database + auditEventRepository.save(auditEvent); + + // Get all the audits + restAuditMockMvc.perform(get("/management/audits")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("$.[*].principal").value(hasItem(SAMPLE_PRINCIPAL))); + } + + @Test + public void getAudit() throws Exception { + // Initialize the database + auditEventRepository.save(auditEvent); + + // Get the audit + restAuditMockMvc.perform(get("/management/audits/{id}", auditEvent.getId())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("$.principal").value(SAMPLE_PRINCIPAL)); + } + + @Test + public void getAuditsByDate() throws Exception { + // Initialize the database + auditEventRepository.save(auditEvent); + + // Generate dates for selecting audits by date, making sure the period will contain the audit + String fromDate = SAMPLE_TIMESTAMP.minusSeconds(SECONDS_PER_DAY).toString().substring(0, 10); + String toDate = SAMPLE_TIMESTAMP.plusSeconds(SECONDS_PER_DAY).toString().substring(0, 10); + + // Get the audit + restAuditMockMvc.perform(get("/management/audits?fromDate="+fromDate+"&toDate="+toDate)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("$.[*].principal").value(hasItem(SAMPLE_PRINCIPAL))); + } + + @Test + public void getNonExistingAuditsByDate() throws Exception { + // Initialize the database + auditEventRepository.save(auditEvent); + + // Generate dates for selecting audits by date, making sure the period will not contain the sample audit + String fromDate = SAMPLE_TIMESTAMP.minusSeconds(2*SECONDS_PER_DAY).toString().substring(0, 10); + String toDate = SAMPLE_TIMESTAMP.minusSeconds(SECONDS_PER_DAY).toString().substring(0, 10); + + // Query audits but expect no results + restAuditMockMvc.perform(get("/management/audits?fromDate=" + fromDate + "&toDate=" + toDate)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(header().string("X-Total-Count", "0")); + } + + @Test + public void getNonExistingAudit() throws Exception { + // Get the audit + restAuditMockMvc.perform(get("/management/audits/{id}", Long.MAX_VALUE)) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + public void testPersistentAuditEventEquals() throws Exception { + TestUtil.equalsVerifier(PersistentAuditEvent.class); + PersistentAuditEvent auditEvent1 = new PersistentAuditEvent(); + auditEvent1.setId(1L); + PersistentAuditEvent auditEvent2 = new PersistentAuditEvent(); + auditEvent2.setId(auditEvent1.getId()); + assertThat(auditEvent1).isEqualTo(auditEvent2); + auditEvent2.setId(2L); + assertThat(auditEvent1).isNotEqualTo(auditEvent2); + auditEvent1.setId(null); + assertThat(auditEvent1).isNotEqualTo(auditEvent2); + } +} diff --git a/src/test/java/org/securityrat/casemanagement/web/rest/TestUtil.java b/src/test/java/org/securityrat/casemanagement/web/rest/TestUtil.java new file mode 100644 index 0000000..3568b2d --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/web/rest/TestUtil.java @@ -0,0 +1,158 @@ +package org.securityrat.casemanagement.web.rest; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; +import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar; +import org.springframework.format.support.DefaultFormattingConversionService; +import org.springframework.format.support.FormattingConversionService; +import org.springframework.http.MediaType; + +import java.io.IOException; +import java.time.ZonedDateTime; +import java.time.format.DateTimeParseException; +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Utility class for testing REST controllers. + */ +public final class TestUtil { + + private static final ObjectMapper mapper = createObjectMapper(); + + /** MediaType for JSON UTF8 */ + public static final MediaType APPLICATION_JSON_UTF8 = MediaType.APPLICATION_JSON_UTF8; + + private static ObjectMapper createObjectMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + mapper.registerModule(new JavaTimeModule()); + return mapper; + } + + /** + * Convert an object to JSON byte array. + * + * @param object the object to convert. + * @return the JSON byte array. + * @throws IOException + */ + public static byte[] convertObjectToJsonBytes(Object object) throws IOException { + return mapper.writeValueAsBytes(object); + } + + /** + * Create a byte array with a specific size filled with specified data. + * + * @param size the size of the byte array. + * @param data the data to put in the byte array. + * @return the JSON byte array. + */ + public static byte[] createByteArray(int size, String data) { + byte[] byteArray = new byte[size]; + for (int i = 0; i < size; i++) { + byteArray[i] = Byte.parseByte(data, 2); + } + return byteArray; + } + + /** + * A matcher that tests that the examined string represents the same instant as the reference datetime. + */ + public static class ZonedDateTimeMatcher extends TypeSafeDiagnosingMatcher { + + private final ZonedDateTime date; + + public ZonedDateTimeMatcher(ZonedDateTime date) { + this.date = date; + } + + @Override + protected boolean matchesSafely(String item, Description mismatchDescription) { + try { + if (!date.isEqual(ZonedDateTime.parse(item))) { + mismatchDescription.appendText("was ").appendValue(item); + return false; + } + return true; + } catch (DateTimeParseException e) { + mismatchDescription.appendText("was ").appendValue(item) + .appendText(", which could not be parsed as a ZonedDateTime"); + return false; + } + + } + + @Override + public void describeTo(Description description) { + description.appendText("a String representing the same Instant as ").appendValue(date); + } + } + + /** + * Creates a matcher that matches when the examined string represents the same instant as the reference datetime. + * @param date the reference datetime against which the examined string is checked. + */ + public static ZonedDateTimeMatcher sameInstant(ZonedDateTime date) { + return new ZonedDateTimeMatcher(date); + } + + /** + * Verifies the equals/hashcode contract on the domain object. + */ + public static void equalsVerifier(Class clazz) throws Exception { + T domainObject1 = clazz.getConstructor().newInstance(); + assertThat(domainObject1.toString()).isNotNull(); + assertThat(domainObject1).isEqualTo(domainObject1); + assertThat(domainObject1.hashCode()).isEqualTo(domainObject1.hashCode()); + // Test with an instance of another class + Object testOtherObject = new Object(); + assertThat(domainObject1).isNotEqualTo(testOtherObject); + assertThat(domainObject1).isNotEqualTo(null); + // Test with an instance of the same class + T domainObject2 = clazz.getConstructor().newInstance(); + assertThat(domainObject1).isNotEqualTo(domainObject2); + // HashCodes are equals because the objects are not persisted yet + assertThat(domainObject1.hashCode()).isEqualTo(domainObject2.hashCode()); + } + + /** + * Create a {@link FormattingConversionService} which use ISO date format, instead of the localized one. + * @return the {@link FormattingConversionService}. + */ + public static FormattingConversionService createFormattingConversionService() { + DefaultFormattingConversionService dfcs = new DefaultFormattingConversionService (); + DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar(); + registrar.setUseIsoFormat(true); + registrar.registerFormatters(dfcs); + return dfcs; + } + + /** + * Makes a an executes a query to the EntityManager finding all stored objects. + * @param The type of objects to be searched + * @param em The instance of the EntityManager + * @param clss The class type to be searched + * @return A list of all found objects + */ + public static List findAll(EntityManager em, Class clss) { + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(clss); + Root rootEntry = cq.from(clss); + CriteriaQuery all = cq.select(rootEntry); + TypedQuery allQuery = em.createQuery(all); + return allQuery.getResultList(); + } + + private TestUtil() {} +} diff --git a/src/test/java/org/securityrat/casemanagement/web/rest/TicketSystemInstanceResourceIT.java b/src/test/java/org/securityrat/casemanagement/web/rest/TicketSystemInstanceResourceIT.java new file mode 100644 index 0000000..3468222 --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/web/rest/TicketSystemInstanceResourceIT.java @@ -0,0 +1,325 @@ +package org.securityrat.casemanagement.web.rest; + +import org.securityrat.casemanagement.CaseManagementApp; +import org.securityrat.casemanagement.config.TestSecurityConfiguration; +import org.securityrat.casemanagement.domain.TicketSystemInstance; +import org.securityrat.casemanagement.repository.TicketSystemInstanceRepository; +import org.securityrat.casemanagement.web.rest.errors.ExceptionTranslator; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.web.PageableHandlerMethodArgumentResolver; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.Validator; + +import javax.persistence.EntityManager; +import java.util.List; + +import static org.securityrat.casemanagement.web.rest.TestUtil.createFormattingConversionService; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.securityrat.casemanagement.domain.enumeration.TicketSystem; +/** + * Integration tests for the {@link TicketSystemInstanceResource} REST controller. + */ +@SpringBootTest(classes = {CaseManagementApp.class, TestSecurityConfiguration.class}) +public class TicketSystemInstanceResourceIT { + + private static final String DEFAULT_NAME = "AAAAAAAAAA"; + private static final String UPDATED_NAME = "BBBBBBBBBB"; + + private static final TicketSystem DEFAULT_TYPE = TicketSystem.JIRA; + private static final TicketSystem UPDATED_TYPE = TicketSystem.JIRA; + + private static final String DEFAULT_URL = "AAAAAAAAAA"; + private static final String UPDATED_URL = "BBBBBBBBBB"; + + private static final String DEFAULT_CONSUMER_KEY = "AAAAAAAAAA"; + private static final String UPDATED_CONSUMER_KEY = "BBBBBBBBBB"; + + private static final String DEFAULT_CLIENT_ID = "AAAAAAAAAA"; + private static final String UPDATED_CLIENT_ID = "BBBBBBBBBB"; + + private static final String DEFAULT_CLIENT_SECRET = "AAAAAAAAAA"; + private static final String UPDATED_CLIENT_SECRET = "BBBBBBBBBB"; + + @Autowired + private TicketSystemInstanceRepository ticketSystemInstanceRepository; + + @Autowired + private MappingJackson2HttpMessageConverter jacksonMessageConverter; + + @Autowired + private PageableHandlerMethodArgumentResolver pageableArgumentResolver; + + @Autowired + private ExceptionTranslator exceptionTranslator; + + @Autowired + private EntityManager em; + + @Autowired + private Validator validator; + + private MockMvc restTicketSystemInstanceMockMvc; + + private TicketSystemInstance ticketSystemInstance; + + @BeforeEach + public void setup() { + MockitoAnnotations.initMocks(this); + final TicketSystemInstanceResource ticketSystemInstanceResource = new TicketSystemInstanceResource(ticketSystemInstanceRepository); + this.restTicketSystemInstanceMockMvc = MockMvcBuilders.standaloneSetup(ticketSystemInstanceResource) + .setCustomArgumentResolvers(pageableArgumentResolver) + .setControllerAdvice(exceptionTranslator) + .setConversionService(createFormattingConversionService()) + .setMessageConverters(jacksonMessageConverter) + .setValidator(validator).build(); + } + + /** + * Create an entity for this test. + * + * This is a static method, as tests for other entities might also need it, + * if they test an entity which requires the current entity. + */ + public static TicketSystemInstance createEntity(EntityManager em) { + TicketSystemInstance ticketSystemInstance = new TicketSystemInstance() + .name(DEFAULT_NAME) + .type(DEFAULT_TYPE) + .url(DEFAULT_URL) + .consumerKey(DEFAULT_CONSUMER_KEY) + .clientId(DEFAULT_CLIENT_ID) + .clientSecret(DEFAULT_CLIENT_SECRET); + return ticketSystemInstance; + } + /** + * Create an updated entity for this test. + * + * This is a static method, as tests for other entities might also need it, + * if they test an entity which requires the current entity. + */ + public static TicketSystemInstance createUpdatedEntity(EntityManager em) { + TicketSystemInstance ticketSystemInstance = new TicketSystemInstance() + .name(UPDATED_NAME) + .type(UPDATED_TYPE) + .url(UPDATED_URL) + .consumerKey(UPDATED_CONSUMER_KEY) + .clientId(UPDATED_CLIENT_ID) + .clientSecret(UPDATED_CLIENT_SECRET); + return ticketSystemInstance; + } + + @BeforeEach + public void initTest() { + ticketSystemInstance = createEntity(em); + } + + @Test + @Transactional + public void createTicketSystemInstance() throws Exception { + int databaseSizeBeforeCreate = ticketSystemInstanceRepository.findAll().size(); + + // Create the TicketSystemInstance + restTicketSystemInstanceMockMvc.perform(post("/api/ticket-system-instances") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(ticketSystemInstance))) + .andExpect(status().isCreated()); + + // Validate the TicketSystemInstance in the database + List ticketSystemInstanceList = ticketSystemInstanceRepository.findAll(); + assertThat(ticketSystemInstanceList).hasSize(databaseSizeBeforeCreate + 1); + TicketSystemInstance testTicketSystemInstance = ticketSystemInstanceList.get(ticketSystemInstanceList.size() - 1); + assertThat(testTicketSystemInstance.getName()).isEqualTo(DEFAULT_NAME); + assertThat(testTicketSystemInstance.getType()).isEqualTo(DEFAULT_TYPE); + assertThat(testTicketSystemInstance.getUrl()).isEqualTo(DEFAULT_URL); + assertThat(testTicketSystemInstance.getConsumerKey()).isEqualTo(DEFAULT_CONSUMER_KEY); + assertThat(testTicketSystemInstance.getClientId()).isEqualTo(DEFAULT_CLIENT_ID); + assertThat(testTicketSystemInstance.getClientSecret()).isEqualTo(DEFAULT_CLIENT_SECRET); + } + + @Test + @Transactional + public void createTicketSystemInstanceWithExistingId() throws Exception { + int databaseSizeBeforeCreate = ticketSystemInstanceRepository.findAll().size(); + + // Create the TicketSystemInstance with an existing ID + ticketSystemInstance.setId(1L); + + // An entity with an existing ID cannot be created, so this API call must fail + restTicketSystemInstanceMockMvc.perform(post("/api/ticket-system-instances") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(ticketSystemInstance))) + .andExpect(status().isBadRequest()); + + // Validate the TicketSystemInstance in the database + List ticketSystemInstanceList = ticketSystemInstanceRepository.findAll(); + assertThat(ticketSystemInstanceList).hasSize(databaseSizeBeforeCreate); + } + + + @Test + @Transactional + public void checkTypeIsRequired() throws Exception { + int databaseSizeBeforeTest = ticketSystemInstanceRepository.findAll().size(); + // set the field null + ticketSystemInstance.setType(null); + + // Create the TicketSystemInstance, which fails. + + restTicketSystemInstanceMockMvc.perform(post("/api/ticket-system-instances") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(ticketSystemInstance))) + .andExpect(status().isBadRequest()); + + List ticketSystemInstanceList = ticketSystemInstanceRepository.findAll(); + assertThat(ticketSystemInstanceList).hasSize(databaseSizeBeforeTest); + } + + @Test + @Transactional + public void checkUrlIsRequired() throws Exception { + int databaseSizeBeforeTest = ticketSystemInstanceRepository.findAll().size(); + // set the field null + ticketSystemInstance.setUrl(null); + + // Create the TicketSystemInstance, which fails. + + restTicketSystemInstanceMockMvc.perform(post("/api/ticket-system-instances") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(ticketSystemInstance))) + .andExpect(status().isBadRequest()); + + List ticketSystemInstanceList = ticketSystemInstanceRepository.findAll(); + assertThat(ticketSystemInstanceList).hasSize(databaseSizeBeforeTest); + } + + @Test + @Transactional + public void getAllTicketSystemInstances() throws Exception { + // Initialize the database + ticketSystemInstanceRepository.saveAndFlush(ticketSystemInstance); + + // Get all the ticketSystemInstanceList + restTicketSystemInstanceMockMvc.perform(get("/api/ticket-system-instances?sort=id,desc")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("$.[*].id").value(hasItem(ticketSystemInstance.getId().intValue()))) + .andExpect(jsonPath("$.[*].name").value(hasItem(DEFAULT_NAME))) + .andExpect(jsonPath("$.[*].type").value(hasItem(DEFAULT_TYPE.toString()))) + .andExpect(jsonPath("$.[*].url").value(hasItem(DEFAULT_URL))) + .andExpect(jsonPath("$.[*].consumerKey").value(hasItem(DEFAULT_CONSUMER_KEY))) + .andExpect(jsonPath("$.[*].clientId").value(hasItem(DEFAULT_CLIENT_ID))) + .andExpect(jsonPath("$.[*].clientSecret").value(hasItem(DEFAULT_CLIENT_SECRET))); + } + + @Test + @Transactional + public void getTicketSystemInstance() throws Exception { + // Initialize the database + ticketSystemInstanceRepository.saveAndFlush(ticketSystemInstance); + + // Get the ticketSystemInstance + restTicketSystemInstanceMockMvc.perform(get("/api/ticket-system-instances/{id}", ticketSystemInstance.getId())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("$.id").value(ticketSystemInstance.getId().intValue())) + .andExpect(jsonPath("$.name").value(DEFAULT_NAME)) + .andExpect(jsonPath("$.type").value(DEFAULT_TYPE.toString())) + .andExpect(jsonPath("$.url").value(DEFAULT_URL)) + .andExpect(jsonPath("$.consumerKey").value(DEFAULT_CONSUMER_KEY)) + .andExpect(jsonPath("$.clientId").value(DEFAULT_CLIENT_ID)) + .andExpect(jsonPath("$.clientSecret").value(DEFAULT_CLIENT_SECRET)); + } + + @Test + @Transactional + public void getNonExistingTicketSystemInstance() throws Exception { + // Get the ticketSystemInstance + restTicketSystemInstanceMockMvc.perform(get("/api/ticket-system-instances/{id}", Long.MAX_VALUE)) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + public void updateTicketSystemInstance() throws Exception { + // Initialize the database + ticketSystemInstanceRepository.saveAndFlush(ticketSystemInstance); + + int databaseSizeBeforeUpdate = ticketSystemInstanceRepository.findAll().size(); + + // Update the ticketSystemInstance + TicketSystemInstance updatedTicketSystemInstance = ticketSystemInstanceRepository.findById(ticketSystemInstance.getId()).get(); + // Disconnect from session so that the updates on updatedTicketSystemInstance are not directly saved in db + em.detach(updatedTicketSystemInstance); + updatedTicketSystemInstance + .name(UPDATED_NAME) + .type(UPDATED_TYPE) + .url(UPDATED_URL) + .consumerKey(UPDATED_CONSUMER_KEY) + .clientId(UPDATED_CLIENT_ID) + .clientSecret(UPDATED_CLIENT_SECRET); + + restTicketSystemInstanceMockMvc.perform(put("/api/ticket-system-instances") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(updatedTicketSystemInstance))) + .andExpect(status().isOk()); + + // Validate the TicketSystemInstance in the database + List ticketSystemInstanceList = ticketSystemInstanceRepository.findAll(); + assertThat(ticketSystemInstanceList).hasSize(databaseSizeBeforeUpdate); + TicketSystemInstance testTicketSystemInstance = ticketSystemInstanceList.get(ticketSystemInstanceList.size() - 1); + assertThat(testTicketSystemInstance.getName()).isEqualTo(UPDATED_NAME); + assertThat(testTicketSystemInstance.getType()).isEqualTo(UPDATED_TYPE); + assertThat(testTicketSystemInstance.getUrl()).isEqualTo(UPDATED_URL); + assertThat(testTicketSystemInstance.getConsumerKey()).isEqualTo(UPDATED_CONSUMER_KEY); + assertThat(testTicketSystemInstance.getClientId()).isEqualTo(UPDATED_CLIENT_ID); + assertThat(testTicketSystemInstance.getClientSecret()).isEqualTo(UPDATED_CLIENT_SECRET); + } + + @Test + @Transactional + public void updateNonExistingTicketSystemInstance() throws Exception { + int databaseSizeBeforeUpdate = ticketSystemInstanceRepository.findAll().size(); + + // Create the TicketSystemInstance + + // If the entity doesn't have an ID, it will throw BadRequestAlertException + restTicketSystemInstanceMockMvc.perform(put("/api/ticket-system-instances") + .contentType(TestUtil.APPLICATION_JSON_UTF8) + .content(TestUtil.convertObjectToJsonBytes(ticketSystemInstance))) + .andExpect(status().isBadRequest()); + + // Validate the TicketSystemInstance in the database + List ticketSystemInstanceList = ticketSystemInstanceRepository.findAll(); + assertThat(ticketSystemInstanceList).hasSize(databaseSizeBeforeUpdate); + } + + @Test + @Transactional + public void deleteTicketSystemInstance() throws Exception { + // Initialize the database + ticketSystemInstanceRepository.saveAndFlush(ticketSystemInstance); + + int databaseSizeBeforeDelete = ticketSystemInstanceRepository.findAll().size(); + + // Delete the ticketSystemInstance + restTicketSystemInstanceMockMvc.perform(delete("/api/ticket-system-instances/{id}", ticketSystemInstance.getId()) + .accept(TestUtil.APPLICATION_JSON_UTF8)) + .andExpect(status().isNoContent()); + + // Validate the database contains one less item + List ticketSystemInstanceList = ticketSystemInstanceRepository.findAll(); + assertThat(ticketSystemInstanceList).hasSize(databaseSizeBeforeDelete - 1); + } +} diff --git a/src/test/java/org/securityrat/casemanagement/web/rest/UserResourceIT.java b/src/test/java/org/securityrat/casemanagement/web/rest/UserResourceIT.java new file mode 100644 index 0000000..f6dbe5c --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/web/rest/UserResourceIT.java @@ -0,0 +1,264 @@ +package org.securityrat.casemanagement.web.rest; + +import org.securityrat.casemanagement.CaseManagementApp; +import org.securityrat.casemanagement.config.TestSecurityConfiguration; +import org.securityrat.casemanagement.domain.Authority; +import org.securityrat.casemanagement.domain.User; +import org.securityrat.casemanagement.repository.UserRepository; +import org.securityrat.casemanagement.security.AuthoritiesConstants; + +import org.securityrat.casemanagement.service.UserService; +import org.securityrat.casemanagement.service.dto.UserDTO; +import org.securityrat.casemanagement.service.mapper.UserMapper; +import org.securityrat.casemanagement.web.rest.errors.ExceptionTranslator; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.web.PageableHandlerMethodArgumentResolver; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; + +import javax.persistence.EntityManager; +import java.time.Instant; +import java.util.*; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.hasItem; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * Integration tests for the {@link UserResource} REST controller. + */ +@SpringBootTest(classes = {CaseManagementApp.class, TestSecurityConfiguration.class}) +public class UserResourceIT { + + private static final String DEFAULT_LOGIN = "johndoe"; + + private static final String DEFAULT_ID = "id1"; + + private static final String DEFAULT_PASSWORD = "passjohndoe"; + + private static final String DEFAULT_EMAIL = "johndoe@localhost"; + + private static final String DEFAULT_FIRSTNAME = "john"; + + private static final String DEFAULT_LASTNAME = "doe"; + + private static final String DEFAULT_IMAGEURL = "http://placehold.it/50x50"; + + private static final String DEFAULT_LANGKEY = "en"; + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserService userService; + + @Autowired + private UserMapper userMapper; + + @Autowired + private MappingJackson2HttpMessageConverter jacksonMessageConverter; + + @Autowired + private PageableHandlerMethodArgumentResolver pageableArgumentResolver; + + @Autowired + private ExceptionTranslator exceptionTranslator; + + @Autowired + private EntityManager em; + + private MockMvc restUserMockMvc; + + private User user; + + @BeforeEach + public void setup() { + UserResource userResource = new UserResource(userService); + + this.restUserMockMvc = MockMvcBuilders.standaloneSetup(userResource) + .setCustomArgumentResolvers(pageableArgumentResolver) + .setControllerAdvice(exceptionTranslator) + .setMessageConverters(jacksonMessageConverter) + .build(); + } + + /** + * Create a User. + * + * This is a static method, as tests for other entities might also need it, + * if they test an entity which has a required relationship to the User entity. + */ + public static User createEntity(EntityManager em) { + User user = new User(); + user.setId(UUID.randomUUID().toString()); + user.setLogin(DEFAULT_LOGIN + RandomStringUtils.randomAlphabetic(5)); + user.setActivated(true); + user.setEmail(RandomStringUtils.randomAlphabetic(5) + DEFAULT_EMAIL); + user.setFirstName(DEFAULT_FIRSTNAME); + user.setLastName(DEFAULT_LASTNAME); + user.setImageUrl(DEFAULT_IMAGEURL); + user.setLangKey(DEFAULT_LANGKEY); + return user; + } + + @BeforeEach + public void initTest() { + user = createEntity(em); + user.setLogin(DEFAULT_LOGIN); + user.setEmail(DEFAULT_EMAIL); + } + + @Test + @Transactional + public void getAllUsers() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + + // Get all the users + restUserMockMvc.perform(get("/api/users?sort=id,desc") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("$.[*].login").value(hasItem(DEFAULT_LOGIN))) + .andExpect(jsonPath("$.[*].firstName").value(hasItem(DEFAULT_FIRSTNAME))) + .andExpect(jsonPath("$.[*].lastName").value(hasItem(DEFAULT_LASTNAME))) + .andExpect(jsonPath("$.[*].email").value(hasItem(DEFAULT_EMAIL))) + .andExpect(jsonPath("$.[*].imageUrl").value(hasItem(DEFAULT_IMAGEURL))) + .andExpect(jsonPath("$.[*].langKey").value(hasItem(DEFAULT_LANGKEY))); + } + + @Test + @Transactional + public void getUser() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + + // Get the user + restUserMockMvc.perform(get("/api/users/{login}", user.getLogin())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(jsonPath("$.login").value(user.getLogin())) + .andExpect(jsonPath("$.firstName").value(DEFAULT_FIRSTNAME)) + .andExpect(jsonPath("$.lastName").value(DEFAULT_LASTNAME)) + .andExpect(jsonPath("$.email").value(DEFAULT_EMAIL)) + .andExpect(jsonPath("$.imageUrl").value(DEFAULT_IMAGEURL)) + .andExpect(jsonPath("$.langKey").value(DEFAULT_LANGKEY)); + + } + + @Test + @Transactional + public void getNonExistingUser() throws Exception { + restUserMockMvc.perform(get("/api/users/unknown")) + .andExpect(status().isNotFound()); + } + + @Test + @Transactional + public void testUserEquals() throws Exception { + TestUtil.equalsVerifier(User.class); + User user1 = new User(); + user1.setId("id1"); + User user2 = new User(); + user2.setId(user1.getId()); + assertThat(user1).isEqualTo(user2); + user2.setId("id2"); + assertThat(user1).isNotEqualTo(user2); + user1.setId(null); + assertThat(user1).isNotEqualTo(user2); + } + + @Test + public void testUserDTOtoUser() { + UserDTO userDTO = new UserDTO(); + userDTO.setId(DEFAULT_ID); + userDTO.setLogin(DEFAULT_LOGIN); + userDTO.setFirstName(DEFAULT_FIRSTNAME); + userDTO.setLastName(DEFAULT_LASTNAME); + userDTO.setEmail(DEFAULT_EMAIL); + userDTO.setActivated(true); + userDTO.setImageUrl(DEFAULT_IMAGEURL); + userDTO.setLangKey(DEFAULT_LANGKEY); + userDTO.setCreatedBy(DEFAULT_LOGIN); + userDTO.setLastModifiedBy(DEFAULT_LOGIN); + userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + User user = userMapper.userDTOToUser(userDTO); + assertThat(user.getId()).isEqualTo(DEFAULT_ID); + assertThat(user.getLogin()).isEqualTo(DEFAULT_LOGIN); + assertThat(user.getFirstName()).isEqualTo(DEFAULT_FIRSTNAME); + assertThat(user.getLastName()).isEqualTo(DEFAULT_LASTNAME); + assertThat(user.getEmail()).isEqualTo(DEFAULT_EMAIL); + assertThat(user.getActivated()).isEqualTo(true); + assertThat(user.getImageUrl()).isEqualTo(DEFAULT_IMAGEURL); + assertThat(user.getLangKey()).isEqualTo(DEFAULT_LANGKEY); + assertThat(user.getCreatedBy()).isNull(); + assertThat(user.getCreatedDate()).isNotNull(); + assertThat(user.getLastModifiedBy()).isNull(); + assertThat(user.getLastModifiedDate()).isNotNull(); + assertThat(user.getAuthorities()).extracting("name").containsExactly(AuthoritiesConstants.USER); + } + + @Test + public void testUserToUserDTO() { + user.setId(DEFAULT_ID); + user.setCreatedBy(DEFAULT_LOGIN); + user.setCreatedDate(Instant.now()); + user.setLastModifiedBy(DEFAULT_LOGIN); + user.setLastModifiedDate(Instant.now()); + Set authorities = new HashSet<>(); + Authority authority = new Authority(); + authority.setName(AuthoritiesConstants.USER); + authorities.add(authority); + user.setAuthorities(authorities); + + UserDTO userDTO = userMapper.userToUserDTO(user); + + assertThat(userDTO.getId()).isEqualTo(DEFAULT_ID); + assertThat(userDTO.getLogin()).isEqualTo(DEFAULT_LOGIN); + assertThat(userDTO.getFirstName()).isEqualTo(DEFAULT_FIRSTNAME); + assertThat(userDTO.getLastName()).isEqualTo(DEFAULT_LASTNAME); + assertThat(userDTO.getEmail()).isEqualTo(DEFAULT_EMAIL); + assertThat(userDTO.isActivated()).isEqualTo(true); + assertThat(userDTO.getImageUrl()).isEqualTo(DEFAULT_IMAGEURL); + assertThat(userDTO.getLangKey()).isEqualTo(DEFAULT_LANGKEY); + assertThat(userDTO.getCreatedBy()).isEqualTo(DEFAULT_LOGIN); + assertThat(userDTO.getCreatedDate()).isEqualTo(user.getCreatedDate()); + assertThat(userDTO.getLastModifiedBy()).isEqualTo(DEFAULT_LOGIN); + assertThat(userDTO.getLastModifiedDate()).isEqualTo(user.getLastModifiedDate()); + assertThat(userDTO.getAuthorities()).containsExactly(AuthoritiesConstants.USER); + assertThat(userDTO.toString()).isNotNull(); + } + + @Test + public void testAuthorityEquals() { + Authority authorityA = new Authority(); + assertThat(authorityA).isEqualTo(authorityA); + assertThat(authorityA).isNotEqualTo(null); + assertThat(authorityA).isNotEqualTo(new Object()); + assertThat(authorityA.hashCode()).isEqualTo(0); + assertThat(authorityA.toString()).isNotNull(); + + Authority authorityB = new Authority(); + assertThat(authorityA).isEqualTo(authorityB); + + authorityB.setName(AuthoritiesConstants.ADMIN); + assertThat(authorityA).isNotEqualTo(authorityB); + + authorityA.setName(AuthoritiesConstants.USER); + assertThat(authorityA).isNotEqualTo(authorityB); + + authorityB.setName(AuthoritiesConstants.USER); + assertThat(authorityA).isEqualTo(authorityB); + assertThat(authorityA.hashCode()).isEqualTo(authorityB.hashCode()); + } +} diff --git a/src/test/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslatorIT.java b/src/test/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslatorIT.java new file mode 100644 index 0000000..d4bb7b2 --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslatorIT.java @@ -0,0 +1,126 @@ +package org.securityrat.casemanagement.web.rest.errors; + +import org.securityrat.casemanagement.CaseManagementApp; +import org.securityrat.casemanagement.config.TestSecurityConfiguration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +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.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Integration tests {@link ExceptionTranslator} controller advice. + */ +@SpringBootTest(classes = {CaseManagementApp.class, TestSecurityConfiguration.class}) +public class ExceptionTranslatorIT { + + @Autowired + private ExceptionTranslatorTestController controller; + + @Autowired + private ExceptionTranslator exceptionTranslator; + + @Autowired + private MappingJackson2HttpMessageConverter jacksonMessageConverter; + + private MockMvc mockMvc; + + @BeforeEach + public void setup() { + mockMvc = MockMvcBuilders.standaloneSetup(controller) + .setControllerAdvice(exceptionTranslator) + .setMessageConverters(jacksonMessageConverter) + .build(); + } + + @Test + public void testConcurrencyFailure() throws Exception { + mockMvc.perform(get("/test/concurrency-failure")) + .andExpect(status().isConflict()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value(ErrorConstants.ERR_CONCURRENCY_FAILURE)); + } + + @Test + public void testMethodArgumentNotValid() throws Exception { + mockMvc.perform(post("/test/method-argument").content("{}").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value(ErrorConstants.ERR_VALIDATION)) + .andExpect(jsonPath("$.fieldErrors.[0].objectName").value("test")) + .andExpect(jsonPath("$.fieldErrors.[0].field").value("test")) + .andExpect(jsonPath("$.fieldErrors.[0].message").value("NotNull")); + } + + @Test + public void testMissingServletRequestPartException() throws Exception { + mockMvc.perform(get("/test/missing-servlet-request-part")) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value("error.http.400")); + } + + @Test + public void testMissingServletRequestParameterException() throws Exception { + mockMvc.perform(get("/test/missing-servlet-request-parameter")) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value("error.http.400")); + } + + @Test + public void testAccessDenied() throws Exception { + mockMvc.perform(get("/test/access-denied")) + .andExpect(status().isForbidden()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value("error.http.403")) + .andExpect(jsonPath("$.detail").value("test access denied!")); + } + + @Test + public void testUnauthorized() throws Exception { + mockMvc.perform(get("/test/unauthorized")) + .andExpect(status().isUnauthorized()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value("error.http.401")) + .andExpect(jsonPath("$.path").value("/test/unauthorized")) + .andExpect(jsonPath("$.detail").value("test authentication failed!")); + } + + @Test + public void testMethodNotSupported() throws Exception { + mockMvc.perform(post("/test/access-denied")) + .andExpect(status().isMethodNotAllowed()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value("error.http.405")) + .andExpect(jsonPath("$.detail").value("Request method 'POST' not supported")); + } + + @Test + public void testExceptionWithResponseStatus() throws Exception { + mockMvc.perform(get("/test/response-status")) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value("error.http.400")) + .andExpect(jsonPath("$.title").value("test response status")); + } + + @Test + public void testInternalServerError() throws Exception { + mockMvc.perform(get("/test/internal-server-error")) + .andExpect(status().isInternalServerError()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value("error.http.500")) + .andExpect(jsonPath("$.title").value("Internal Server Error")); + } + +} diff --git a/src/test/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslatorTestController.java b/src/test/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslatorTestController.java new file mode 100644 index 0000000..13ea32c --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslatorTestController.java @@ -0,0 +1,71 @@ +package org.securityrat.casemanagement.web.rest.errors; + +import org.springframework.dao.ConcurrencyFailureException; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; + +@RestController +public class ExceptionTranslatorTestController { + + @GetMapping("/test/concurrency-failure") + public void concurrencyFailure() { + throw new ConcurrencyFailureException("test concurrency failure"); + } + + @PostMapping("/test/method-argument") + public void methodArgument(@Valid @RequestBody TestDTO testDTO) { + } + + @GetMapping("/test/missing-servlet-request-part") + public void missingServletRequestPartException(@RequestPart String part) { + } + + @GetMapping("/test/missing-servlet-request-parameter") + public void missingServletRequestParameterException(@RequestParam String param) { + } + + @GetMapping("/test/access-denied") + public void accessdenied() { + throw new AccessDeniedException("test access denied!"); + } + + @GetMapping("/test/unauthorized") + public void unauthorized() { + throw new BadCredentialsException("test authentication failed!"); + } + + @GetMapping("/test/response-status") + public void exceptionWithResponseStatus() { + throw new TestResponseStatusException(); + } + + @GetMapping("/test/internal-server-error") + public void internalServerError() { + throw new RuntimeException(); + } + + public static class TestDTO { + + @NotNull + private String test; + + public String getTest() { + return test; + } + + public void setTest(String test) { + this.test = test; + } + } + + @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "test response status") + @SuppressWarnings("serial") + public static class TestResponseStatusException extends RuntimeException { + } + +} diff --git a/src/test/resources/config/application.yml b/src/test/resources/config/application.yml new file mode 100644 index 0000000..a930a08 --- /dev/null +++ b/src/test/resources/config/application.yml @@ -0,0 +1,115 @@ +# =================================================================== +# Spring Boot configuration. +# +# This configuration is used for unit/integration tests. +# +# More information on profiles: https://www.jhipster.tech/profiles/ +# More information on configuration properties: https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +# =================================================================== +# Standard Spring Boot properties. +# Full reference is available at: +# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +# =================================================================== + +spring: + application: + name: caseManagement + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: jdbc:h2:mem:caseManagement;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE + name: + username: + password: + hikari: + auto-commit: false + jpa: + database-platform: io.github.jhipster.domain.util.FixedH2Dialect + database: H2 + open-in-view: false + show-sql: false + hibernate: + ddl-auto: none + naming: + physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy + implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + properties: + hibernate.id.new_generator_mappings: true + hibernate.connection.provider_disables_autocommit: true + hibernate.cache.use_second_level_cache: false + hibernate.cache.use_query_cache: false + hibernate.generate_statistics: false + hibernate.hbm2ddl.auto: validate + hibernate.jdbc.time_zone: UTC + liquibase: + contexts: test + mail: + host: localhost + main: + allow-bean-definition-overriding: true + messages: + basename: i18n/messages + mvc: + favicon: + enabled: false + task: + execution: + thread-name-prefix: case-management-task- + pool: + core-size: 1 + max-size: 50 + queue-capacity: 10000 + scheduling: + thread-name-prefix: case-management-scheduling- + pool: + size: 1 + thymeleaf: + mode: HTML + # Allow SecurityConfiguration to initialize w/o specifying an empty issuer-uri is OK + security: + oauth2: + client: + provider: + oidc: + issuer-uri: http://DO_NOT_CALL:9080/auth/realms/jhipster + +server: + port: 10344 + address: localhost + +# =================================================================== +# JHipster specific properties +# +# Full reference is available at: https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +jhipster: + clientApp: + name: 'caseManagementApp' + logging: + # To test json console appender + use-json-format: true # By default, logs are in Json format + logstash: + enabled: false + host: localhost + port: 5000 + queue-size: 512 + mail: + from: test@localhost + base-url: http://127.0.0.1:8080 + metrics: + logs: # Reports metrics in the logs + enabled: true + report-frequency: 60 # in seconds + +# =================================================================== +# Application specific properties +# Add your own application properties here, see the ApplicationProperties class +# to have type-safe configuration, like in the JHipsterProperties above +# +# More documentation is available at: +# https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +# application: diff --git a/src/test/resources/config/bootstrap.yml b/src/test/resources/config/bootstrap.yml new file mode 100644 index 0000000..3c3a073 --- /dev/null +++ b/src/test/resources/config/bootstrap.yml @@ -0,0 +1,9 @@ +spring: + cloud: + consul: + discovery: + enabled: false + instanceId: ${spring.application.name}:${spring.application.instance-id:${random.value}} + config: + enabled: false + enabled: false diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml new file mode 100644 index 0000000..111cd1d --- /dev/null +++ b/src/test/resources/logback.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From fab3e3c553a9014f96156732eb911486d5a7329c Mon Sep 17 00:00:00 2001 From: rylyade1 Date: Sun, 16 Oct 2022 23:44:34 +0200 Subject: [PATCH 2/2] Generated with JHipster 7.0.0 and --- .editorconfig | 2 +- .gitattributes | 2 + .gitignore | 10 +- .huskyrc | 5 + .jhipster/AccessToken.json | 106 ++--- .jhipster/TicketSystemInstance.json | 106 ++--- .lintstagedrc.js | 3 + .mvn/wrapper/MavenWrapperDownloader.java | 2 +- .mvn/wrapper/maven-wrapper.jar | Bin 50710 -> 50710 bytes .mvn/wrapper/maven-wrapper.properties | 4 +- .prettierignore | 4 + .prettierrc | 6 + .yo-rc.json | 16 +- README.md | 61 ++- checkstyle.xml | 7 +- mvnw | 8 +- mvnw.cmd | 8 +- package.json | 58 ++- pom.xml | 442 ++++++++---------- sonar-project.properties | 12 +- src/main/docker/app.yml | 48 +- .../central-server-config/application.yml | 3 +- src/main/docker/config/git2consul.json | 2 +- src/main/docker/consul.yml | 17 +- src/main/docker/jhipster-control-center.yml | 54 +++ src/main/{ => docker}/jib/entrypoint.sh | 0 src/main/docker/mariadb.yml | 11 +- src/main/docker/monitoring.yml | 15 +- src/main/docker/sonar.yml | 14 +- .../casemanagement/ApplicationWebXml.java | 7 +- .../casemanagement/CaseManagementApp.java | 84 ++-- .../casemanagement/GeneratedByJHipster.java | 13 + .../aop/logging/LoggingAspect.java | 62 ++- .../client/AuthorizedFeignClient.java | 6 +- .../OAuth2InterceptedFeignConfiguration.java | 4 +- .../client/TokenRelayRequestInterceptor.java | 4 +- .../config/ApplicationProperties.java | 5 +- .../config/AsyncConfiguration.java | 5 +- .../config/CloudDatabaseConfiguration.java | 28 -- .../casemanagement/config/Constants.java | 10 +- .../config/DatabaseConfiguration.java | 8 +- .../config/FeignConfiguration.java | 1 - .../config/JacksonConfiguration.java | 14 +- .../config/LiquibaseConfiguration.java | 29 +- .../config/LocaleConfiguration.java | 5 +- .../config/LoggingAspectConfiguration.java | 4 +- .../config/LoggingConfiguration.java | 29 +- .../config/MethodSecurityConfiguration.java | 16 - .../config/SecurityConfiguration.java | 42 +- .../casemanagement/config/WebConfigurer.java | 17 +- .../config/audit/AuditEventConverter.java | 86 ---- .../config/audit/package-info.java | 4 - .../domain/AbstractAuditingEntity.java | 15 +- .../casemanagement/domain/AccessToken.java | 43 +- .../casemanagement/domain/Authority.java | 7 +- .../domain/PersistentAuditEvent.java | 106 ----- .../domain/TicketSystemInstance.java | 47 +- .../casemanagement/domain/User.java | 28 +- .../domain/enumeration/TicketSystem.java | 2 +- .../repository/AccessTokenRepository.java | 7 +- .../repository/AuthorityRepository.java | 4 +- .../CustomAuditEventRepository.java | 89 ---- .../PersistenceAuditEventRepository.java | 23 - .../TicketSystemInstanceRepository.java | 7 +- .../repository/UserRepository.java | 19 +- .../security/AuthoritiesConstants.java | 3 +- .../security/SecurityUtils.java | 77 ++- .../security/SpringSecurityAuditorAware.java | 6 +- .../security/oauth2/AudienceValidator.java | 8 +- .../oauth2/AuthorizationHeaderUtil.java | 26 +- ...java => JwtGrantedAuthorityConverter.java} | 12 +- .../oauth2/OAuthIdpTokenResponseDTO.java | 5 +- .../service/AuditEventService.java | 74 --- .../casemanagement/service/UserService.java | 136 ++---- .../service/dto/AdminUserDTO.java | 193 ++++++++ .../casemanagement/service/dto/UserDTO.java | 158 +------ .../service/mapper/UserMapper.java | 110 ++++- .../web/rest/AccessTokenResource.java | 117 ++++- .../web/rest/AuditResource.java | 79 ---- .../web/rest/PublicUserResource.java | 52 +++ .../rest/TicketSystemInstanceResource.java | 124 ++++- .../casemanagement/web/rest/UserResource.java | 47 +- .../rest/errors/BadRequestAlertException.java | 5 +- .../web/rest/errors/ErrorConstants.java | 3 +- .../web/rest/errors/ExceptionTranslator.java | 128 ++++- .../web/rest/errors/FieldErrorVM.java | 1 - .../web/rest/vm/ManagedUserVM.java | 7 +- src/main/resources/.h2.server.properties | 2 +- src/main/resources/config/application-dev.yml | 55 +-- .../resources/config/application-prod.yml | 48 +- src/main/resources/config/application.yml | 70 ++- src/main/resources/config/bootstrap.yml | 2 + .../00000000000000_initial_schema.xml | 48 +- ...0201108111723_added_entity_AccessToken.xml | 22 +- ...3_added_entity_constraints_AccessToken.xml | 6 +- ...2112_added_entity_TicketSystemInstance.xml | 16 +- .../liquibase/fake-data/access_token.csv | 20 +- .../fake-data/ticket_system_instance.csv | 20 +- .../resources/config/liquibase/master.xml | 8 +- src/main/resources/i18n/messages.properties | 15 - src/main/resources/logback-spring.xml | 1 + src/main/resources/static/index.html | 221 +++++---- src/main/resources/templates/error.html | 145 +++--- .../securityrat/casemanagement/ArchTest.java | 18 +- .../casemanagement/IntegrationTest.java | 18 + .../config/TestSecurityConfiguration.java | 16 +- .../config/WebConfigurerTest.java | 98 ++-- .../config/WebConfigurerTestController.java | 6 +- .../config/timezone/HibernateTimeZoneIT.java | 72 ++- .../domain/AccessTokenTest.java | 7 +- .../domain/TicketSystemInstanceTest.java | 7 +- .../CustomAuditEventRepositoryIT.java | 164 ------- .../repository/timezone/DateTimeWrapper.java | 6 +- .../timezone/DateTimeWrapperRepository.java | 4 +- .../security/SecurityUtilsUnitTest.java | 45 +- .../oauth2/AudienceValidatorTest.java | 19 +- .../oauth2/AuthorizationHeaderUtilTest.java | 226 +++++++++ .../casemanagement/service/UserServiceIT.java | 73 ++- .../service/mapper/UserMapperTest.java | 52 +-- .../web/rest/AccessTokenResourceIT.java | 363 ++++++++++---- .../web/rest/AuditResourceIT.java | 165 ------- .../web/rest/PublicUserResourceIT.java | 65 +++ .../casemanagement/web/rest/TestUtil.java | 106 ++++- .../rest/TicketSystemInstanceResourceIT.java | 357 ++++++++++---- .../web/rest/UserResourceIT.java | 139 +++--- .../rest/errors/ExceptionTranslatorIT.java | 105 ++--- .../ExceptionTranslatorTestController.java | 35 +- .../config/application-testcontainers.yml | 27 ++ src/test/resources/config/application.yml | 31 +- src/test/resources/logback.xml | 9 +- .../resources/testcontainers/mariadb/my.cnf | 2 + 131 files changed, 3271 insertions(+), 2940 deletions(-) create mode 100644 .huskyrc create mode 100644 .lintstagedrc.js create mode 100644 src/main/docker/jhipster-control-center.yml rename src/main/{ => docker}/jib/entrypoint.sh (100%) create mode 100644 src/main/java/org/securityrat/casemanagement/GeneratedByJHipster.java delete mode 100644 src/main/java/org/securityrat/casemanagement/config/CloudDatabaseConfiguration.java delete mode 100644 src/main/java/org/securityrat/casemanagement/config/MethodSecurityConfiguration.java delete mode 100644 src/main/java/org/securityrat/casemanagement/config/audit/AuditEventConverter.java delete mode 100644 src/main/java/org/securityrat/casemanagement/config/audit/package-info.java delete mode 100644 src/main/java/org/securityrat/casemanagement/domain/PersistentAuditEvent.java delete mode 100644 src/main/java/org/securityrat/casemanagement/repository/CustomAuditEventRepository.java delete mode 100644 src/main/java/org/securityrat/casemanagement/repository/PersistenceAuditEventRepository.java rename src/main/java/org/securityrat/casemanagement/security/oauth2/{JwtAuthorityExtractor.java => JwtGrantedAuthorityConverter.java} (58%) delete mode 100644 src/main/java/org/securityrat/casemanagement/service/AuditEventService.java create mode 100644 src/main/java/org/securityrat/casemanagement/service/dto/AdminUserDTO.java delete mode 100644 src/main/java/org/securityrat/casemanagement/web/rest/AuditResource.java create mode 100644 src/main/java/org/securityrat/casemanagement/web/rest/PublicUserResource.java create mode 100644 src/test/java/org/securityrat/casemanagement/IntegrationTest.java delete mode 100644 src/test/java/org/securityrat/casemanagement/repository/CustomAuditEventRepositoryIT.java create mode 100644 src/test/java/org/securityrat/casemanagement/security/oauth2/AuthorizationHeaderUtilTest.java delete mode 100644 src/test/java/org/securityrat/casemanagement/web/rest/AuditResourceIT.java create mode 100644 src/test/java/org/securityrat/casemanagement/web/rest/PublicUserResourceIT.java create mode 100644 src/test/resources/config/application-testcontainers.yml create mode 100644 src/test/resources/testcontainers/mariadb/my.cnf diff --git a/.editorconfig b/.editorconfig index 0439866..c2fa6a2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -16,7 +16,7 @@ insert_final_newline = true indent_style = space indent_size = 4 -[*.{ts,tsx,js,jsx,json,css,scss,yml}] +[*.{ts,tsx,js,jsx,json,css,scss,yml,html,vue}] indent_size = 2 [*.md] diff --git a/.gitattributes b/.gitattributes index c013844..ca61722 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,6 +8,8 @@ # These files are text and should be normalized (Convert crlf => lf) *.bat text eol=crlf +*.cmd text eol=crlf +*.ps1 text eol=crlf *.coffee text *.css text *.cql text diff --git a/.gitignore b/.gitignore index 50d9fe1..2b19086 100644 --- a/.gitignore +++ b/.gitignore @@ -52,8 +52,9 @@ local.properties # STS-specific /.sts4-cache/* + ###################### -# Intellij +# IntelliJ ###################### .idea/ *.iml @@ -67,7 +68,12 @@ out/ ###################### # Visual Studio Code ###################### -.vscode/ +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace ###################### # Maven diff --git a/.huskyrc b/.huskyrc new file mode 100644 index 0000000..4d077c8 --- /dev/null +++ b/.huskyrc @@ -0,0 +1,5 @@ +{ + "hooks": { + "pre-commit": "lint-staged" + } +} diff --git a/.jhipster/AccessToken.json b/.jhipster/AccessToken.json index d09b855..d20813f 100644 --- a/.jhipster/AccessToken.json +++ b/.jhipster/AccessToken.json @@ -1,57 +1,53 @@ { - "fluentMethods": true, - "clientRootFolder": "caseManagement", - "relationships": [ - { - "relationshipType": "many-to-one", - "otherEntityName": "user", - "otherEntityRelationshipName": "accessToken", - "relationshipName": "user", - "otherEntityField": "id" - }, - { - "relationshipType": "many-to-one", - "otherEntityName": "ticketSystemInstance", - "otherEntityRelationshipName": "accessToken", - "relationshipName": "ticketInstance", - "otherEntityField": "id" - } - ], - "fields": [ - { - "fieldName": "token", - "fieldType": "String", - "fieldValidateRules": [ - "required" - ] - }, - { - "fieldName": "expirationDate", - "fieldType": "ZonedDateTime" - }, - { - "fieldName": "salt", - "fieldType": "String", - "fieldValidateRules": [ - "required" - ] - }, - { - "fieldName": "refreshToken", - "fieldType": "String" - } - ], - "changelogDate": "20201108111723", - "dto": "no", - "searchEngine": false, - "service": "no", - "entityTableName": "access_token", - "databaseType": "sql", - "readOnly": false, - "jpaMetamodelFiltering": false, - "pagination": "infinite-scroll", - "microserviceName": "caseManagement", - "name": "AccessToken", - "applications": "*", - "skipClient": true + "fluentMethods": true, + "clientRootFolder": "caseManagement", + "relationships": [ + { + "relationshipType": "many-to-one", + "otherEntityName": "user", + "otherEntityRelationshipName": "accessToken", + "relationshipName": "user", + "otherEntityField": "id" + }, + { + "relationshipType": "many-to-one", + "otherEntityName": "ticketSystemInstance", + "otherEntityRelationshipName": "accessToken", + "relationshipName": "ticketInstance", + "otherEntityField": "id" + } + ], + "fields": [ + { + "fieldName": "token", + "fieldType": "String", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "expirationDate", + "fieldType": "ZonedDateTime" + }, + { + "fieldName": "salt", + "fieldType": "String", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "refreshToken", + "fieldType": "String" + } + ], + "changelogDate": "20201108111723", + "dto": "no", + "searchEngine": false, + "service": "no", + "entityTableName": "access_token", + "databaseType": "sql", + "readOnly": false, + "jpaMetamodelFiltering": false, + "pagination": "infinite-scroll", + "microserviceName": "caseManagement", + "name": "AccessToken", + "applications": "*", + "skipClient": true } diff --git a/.jhipster/TicketSystemInstance.json b/.jhipster/TicketSystemInstance.json index def79fe..c1fede3 100644 --- a/.jhipster/TicketSystemInstance.json +++ b/.jhipster/TicketSystemInstance.json @@ -1,57 +1,53 @@ { - "name": "TicketSystemInstance", - "fields": [ - { - "fieldName": "name", - "fieldType": "String" - }, - { - "fieldName": "type", - "fieldType": "TicketSystem", - "fieldValues": "JIRA", - "fieldValidateRules": [ - "required" - ] - }, - { - "fieldName": "url", - "fieldType": "String", - "fieldValidateRules": [ - "required" - ] - }, - { - "fieldName": "consumerKey", - "fieldType": "String" - }, - { - "fieldName": "clientId", - "fieldType": "String" - }, - { - "fieldName": "clientSecret", - "fieldType": "String" - } - ], - "relationships": [ - { - "relationshipType": "one-to-many", - "otherEntityName": "accessToken", - "otherEntityRelationshipName": "ticketInstance", - "relationshipName": "accessToken" - } - ], - "changelogDate": "20201108112112", - "entityTableName": "ticket_system_instance", - "dto": "yes", - "pagination": "infinite-scroll", - "service": "no", - "jpaMetamodelFiltering": false, - "fluentMethods": true, - "readOnly": false, - "clientRootFolder": "caseManagement", - "applications": "*", - "microserviceName": "caseManagement", - "searchEngine": false, - "databaseType": "sql" + "name": "TicketSystemInstance", + "fields": [ + { + "fieldName": "name", + "fieldType": "String" + }, + { + "fieldName": "type", + "fieldType": "TicketSystem", + "fieldValues": "JIRA", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "url", + "fieldType": "String", + "fieldValidateRules": ["required"] + }, + { + "fieldName": "consumerKey", + "fieldType": "String" + }, + { + "fieldName": "clientId", + "fieldType": "String" + }, + { + "fieldName": "clientSecret", + "fieldType": "String" + } + ], + "relationships": [ + { + "relationshipType": "one-to-many", + "otherEntityName": "accessToken", + "otherEntityRelationshipName": "ticketInstance", + "relationshipName": "accessToken" + } + ], + "changelogDate": "20201108112112", + "entityTableName": "ticket_system_instance", + "dto": "yes", + "pagination": "infinite-scroll", + "service": "no", + "jpaMetamodelFiltering": false, + "fluentMethods": true, + "readOnly": false, + "clientRootFolder": "caseManagement", + "applications": "*", + "microserviceName": "caseManagement", + "searchEngine": false, + "databaseType": "sql" } diff --git a/.lintstagedrc.js b/.lintstagedrc.js new file mode 100644 index 0000000..dad2893 --- /dev/null +++ b/.lintstagedrc.js @@ -0,0 +1,3 @@ +module.exports = { + '{,src/**/}*.{md,json,yml,html,java}': ['prettier --write'], +}; diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java index c32394f..b901097 100644 --- a/.mvn/wrapper/MavenWrapperDownloader.java +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -20,7 +20,7 @@ public class MavenWrapperDownloader { - private static final String WRAPPER_VERSION = "0.5.5"; + private static final String WRAPPER_VERSION = "0.5.6"; /** * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. */ diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar index 0d5e649888a4843c1520054d9672f80c62ebbb48..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 100644 GIT binary patch delta 3148 zcmV-S472l=j02X81CVcj!Y~j;_kr9Y7f7fTMY^-DDhPtbt%PKXL)#=wW5wIsR#d1f zcf zy__;pIHO)tEBCe@*KSmDNtfx|J72@u6HrSB2uq>eF|!*1M*#ujlVbuf3QV*>1v&r# z0674YApsn-xdP=I0ade~9+M3U<)K>#+z$W%h#!+tNgT6SER_O(CC#v9TgqtCG)aeS zE5z}v?bvnF)?DPXtyH>GPN$3x3Z+0gv=u)G)3zY(Y%Z{rj)nH83$Adk`j%yK1U}anQK zEa2_TQ}`E$ zm@wiRMV9gicMrgyv`um4^ z`r6#^PM_-w_x2p_>vZD+fkoVrjQZPqy4_eMuyAK!cVMT0TYl&b4IFf1jlktIoa!u^ zorQg-Xsg-YYCaa#12;qyWVb&Oi|SG9a)FBahCzYKR%29i!AILmecFRCK5Bz9yp|O~){Y^nL z0s^j+GH^X(B`h7ZlxMjf+Z1fa4uPuuS*vTysWa%t#dJk55Z@sW?N)FJ_VAE|s6Hyt zJTDuv{<7unRj?2H>2J$$J_=k=Uq;7jFsKR^qKf!`>Wu1IcPf5NGY8aTF-_v!h^Vna z)r`uuz3D*(TBK3ysIWB=O$s!2&O6nZCY5nM1yJs z7Z0`WP|%4k(r0BIQccEg+Qz66JP?g(`y^$Lf?ixDuslnd#YR-4?ibb<)0a8kuV6qP zcbSHNVYNj~xi_d_NV<1PmgS&kCaISjO!V3T^?G{DXWQ<}8t?PqD6Vzk8iDn*daKhI zAJYjY&M)YGE2ni{8eCvn<^Qv&s~dNvz{@M$fQvJ7%P zgB$3`e74M~llmzSZpN)LGoLH4wM@Y%mu@aRkL7GuX}zj75lSYtIE}S& z{{jWK%cNUu(;a3_K4_QZ+wdYOIpM*Lc!_*_sqMnq&B*D$Z+4<0>9(>jV=0`KyoKqR zw78Ikrr%OcOX_?FS|mVTso+(3HIrC>vjf5iN6R69c4cw_nuDgC26j!WRPAJ|DI<|J zq!?-3NR)taS#n0YtSF!QEaIn%fk!aSAEdlz`=jH!YNbqyJv5Knuzx-{yPW8Mt3YU0 zJ=hZjOG`ql5s$0-sJxTdIi1v;aNdO$7v3%qn3Gq**Y| z=ws1w+s>VH0JSVPyP(~T_b7NT-X{xMEM}ZKtY5Da)jiJDC9tD@j_OdJtz`NE1s}v& z_Fs1476_LQ!knCKcB2LS_es<5SMUHn#1x1)?i6VIABNB5(=fB~aUsomPy`Ccy-vsk zTIUurpSTm4%Oj=k64>_tI@pDi8ee8TH^3_>>Euq)M}DRBGoz zsuq*C{iAq{7~UtUN($Oii+z_&$E%IE1dS2r=eUo8feg8>f4oBrBE~wax@fX~_OZ z_=yWYW^Xq)rGlU0XO7?G4AO6tzces^G#s=03jT;cG5Iaya9{h3W9dB14*nOv0L6)gil`DUiX(koCEhMAgSVq)G$;qS^VmkCg+~#KrlBvJ z3yH*SF2S*RO`#+D&*+wVI_FakQL$VgmlVf?E+J&rbuk!ieX?7u zCU&|FpDoar7s8OF^wEIq@_9#2h;@p%P^@R@Wrvo>Q3*Q^tzv_mik;C%p5EdXn`kOU zWH)`fJx%(O2`v&Gi)y33sO~!^$5fxGMYL$b;?uDx;1OFyy-RGJp7@G?#VMjePMixP zF(awD1;_MFtwxG`ieWYltgL+6bY3(w9|Y%j|J((uFhiJm<_u5QmUYAQUvUb{c5MOM zJPtU^iHjAnOa6(Xim}x6lbl|iqT%#;wWZ^7K=Q!`Hr^Egs!=Bgb-vZX#V3Ku_XYO; zqJp);^^$`1Qm&U3te10t?JZc>aD73+x|Zt|1?!bu*A=W+!^5|=SjU|UxpRfB9k6B! z>xaD?GPo#%hDijyJJZ;e#-$J7vit0VSMb-vC*UKKW?X6Sf^4?um<9P1d@l4)B9uQ? zWpA#xk0=fSTG3{EC5!{4dIsMs`Q@O^<|%6E;xm@Pq0Yt(y1V#))7PEB;k}i%npac7 zx=KgE431AGXsYV2^=i35uxefz-t01po4ACc*R%JKCGl>jUUPj1#1P_vbgGY}1*~lIxCmFZ~`g#ge z(u3ZICUGup%Q=epoA+O7JbfqXLA~{?5AUq1ROBR4A8Kc zh+f5<@ZmT%GiREaDccdJz6NPe@Vvp(308p<#D_^PNe0=nE%(!wYP5-<*k=2oS!`#< z2=b^~6C3EY1*BBygB^C%ZqG%{poY@R+r8DSX&wGYq1X|S?)NGgv2(bia7(`2kIU@f zpeQJ3_1WTda mF|$Rq7XbuJv_J*3V6{gB1m&Sy2a~I~r34XhbG@@vxxx%M3nM-N delta 3147 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 diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 7d59a01..642d572 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,2 +1,2 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.2/apache-maven-3.6.2-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/.prettierignore b/.prettierignore index 91b1ba3..ab0567f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,8 @@ node_modules target +build package-lock.json .git +.mvn +gradle +.gradle diff --git a/.prettierrc b/.prettierrc index b749286..f9b9911 100644 --- a/.prettierrc +++ b/.prettierrc @@ -10,3 +10,9 @@ arrowParens: avoid # jsx and tsx rules: jsxBracketSameLine: false + +# java rules: +overrides: + - files: "*.java" + options: + tabWidth: 4 diff --git a/.yo-rc.json b/.yo-rc.json index bcced94..12a5d7b 100644 --- a/.yo-rc.json +++ b/.yo-rc.json @@ -3,7 +3,7 @@ "promptValues": { "packageName": "org.securityrat.casemanagement" }, - "jhipsterVersion": "6.6.0", + "jhipsterVersion": "7.0.0", "baseName": "caseManagement", "packageName": "org.securityrat.casemanagement", "packageFolder": "org/securityrat/casemanagement", @@ -34,6 +34,18 @@ "otherModules": [], "blueprints": [], "embeddableLaunchScript": false, - "creationTimestamp": 1577042681890 + "creationTimestamp": 1577042681890, + "skipServer": false, + "skipCheckLengthOfIdentifier": false, + "skipFakeData": false, + "pages": [], + "reactive": false, + "clientFramework": "angularX", + "clientTheme": "none", + "clientThemeVariant": "", + "withAdminUi": true, + "nativeLanguage": "en", + "languages": ["en", "fr"], + "entities": ["AccessToken", "TicketSystemInstance"] } } diff --git a/README.md b/README.md index 1145d54..4e472fa 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,17 @@ # caseManagement -This application was generated using JHipster 6.6.0, you can find documentation and help at [https://www.jhipster.tech/documentation-archive/v6.6.0](https://www.jhipster.tech/documentation-archive/v6.6.0). +This application was generated using JHipster 7.0.0, you can find documentation and help at [https://www.jhipster.tech/documentation-archive/v7.0.0](https://www.jhipster.tech/documentation-archive/v7.0.0). This is a "microservice" application intended to be part of a microservice architecture, please refer to the [Doing microservices with JHipster][] page of the documentation for more information. - This application is configured for Service Discovery and Configuration with Consul. On launch, it will refuse to start if it is not able to connect to Consul at [http://localhost:8500](http://localhost:8500). For more information, read our documentation on [Service Discovery and Configuration with Consul][]. ## Development To start your application in the dev profile, run: - ./mvnw +``` +./mvnw +``` For further instructions on how to develop with JHipster, have a look at [Using JHipster in development][]. @@ -20,11 +21,15 @@ For further instructions on how to develop with JHipster, have a look at [Using To build the final jar and optimize the caseManagement application for production, run: - ./mvnw -Pprod clean verify +``` +./mvnw -Pprod clean verify +``` To ensure everything worked, run: - java -jar target/*.jar +``` +java -jar target/*.jar +``` Refer to [Using JHipster in production][] for more details. @@ -32,13 +37,17 @@ Refer to [Using JHipster in production][] for more details. To package your application as a war in order to deploy it to an application server, run: - ./mvnw -Pprod,war clean verify +``` +./mvnw -Pprod,war clean verify +``` ## Testing To launch your application's tests, run: - ./mvnw verify +``` +./mvnw verify +``` For more information, refer to the [Running tests page][]. @@ -50,6 +59,8 @@ Sonar is used to analyse code quality. You can start a local Sonar server (acces docker-compose -f src/main/docker/sonar.yml up -d ``` +Note: we have turned off authentication in [src/main/docker/sonar.yml](src/main/docker/sonar.yml) for out of the box experience while trying out SonarQube, for real use cases turn it back on. + You can run a Sonar analysis with using the [sonar-scanner](https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner) or by using the maven plugin. Then, run a Sonar analysis: @@ -64,8 +75,6 @@ If you need to re-run the Sonar phase, please be sure to specify at least the `i ./mvnw initialize sonar:sonar ``` -or - For more information, refer to the [Code quality page][]. ## Using Docker to simplify development (optional) @@ -74,20 +83,28 @@ You can use Docker to improve your JHipster development experience. A number of For example, to start a mariadb database in a docker container, run: - docker-compose -f src/main/docker/mariadb.yml up -d +``` +docker-compose -f src/main/docker/mariadb.yml up -d +``` To stop it and remove the container, run: - docker-compose -f src/main/docker/mariadb.yml down +``` +docker-compose -f src/main/docker/mariadb.yml down +``` You can also fully dockerize your application and all the services that it depends on. To achieve this, first build a docker image of your app by running: - ./mvnw -Pprod verify jib:dockerBuild +``` +./mvnw -Pprod verify jib:dockerBuild +``` Then run: - docker-compose -f src/main/docker/app.yml up -d +``` +docker-compose -f src/main/docker/app.yml up -d +``` For more information refer to [Using Docker and Docker-Compose][], this page also contains information on the docker-compose sub-generator (`jhipster docker-compose`), which is able to generate docker configurations for one or several JHipster applications. @@ -96,12 +113,12 @@ For more information refer to [Using Docker and Docker-Compose][], this page als To configure CI for your project, run the ci-cd sub-generator (`jhipster ci-cd`), this will let you generate configuration files for a number of Continuous Integration systems. Consult the [Setting up Continuous Integration][] page for more information. [jhipster homepage and latest documentation]: https://www.jhipster.tech -[jhipster 6.6.0 archive]: https://www.jhipster.tech/documentation-archive/v6.6.0 -[doing microservices with jhipster]: https://www.jhipster.tech/documentation-archive/v6.6.0/microservices-architecture/ -[using jhipster in development]: https://www.jhipster.tech/documentation-archive/v6.6.0/development/ -[service discovery and configuration with consul]: https://www.jhipster.tech/documentation-archive/v6.6.0/microservices-architecture/#consul -[using docker and docker-compose]: https://www.jhipster.tech/documentation-archive/v6.6.0/docker-compose -[using jhipster in production]: https://www.jhipster.tech/documentation-archive/v6.6.0/production/ -[running tests page]: https://www.jhipster.tech/documentation-archive/v6.6.0/running-tests/ -[code quality page]: https://www.jhipster.tech/documentation-archive/v6.6.0/code-quality/ -[setting up continuous integration]: https://www.jhipster.tech/documentation-archive/v6.6.0/setting-up-ci/ +[jhipster 7.0.0 archive]: https://www.jhipster.tech/documentation-archive/v7.0.0 +[doing microservices with jhipster]: https://www.jhipster.tech/documentation-archive/v7.0.0/microservices-architecture/ +[using jhipster in development]: https://www.jhipster.tech/documentation-archive/v7.0.0/development/ +[service discovery and configuration with consul]: https://www.jhipster.tech/documentation-archive/v7.0.0/microservices-architecture/#consul +[using docker and docker-compose]: https://www.jhipster.tech/documentation-archive/v7.0.0/docker-compose +[using jhipster in production]: https://www.jhipster.tech/documentation-archive/v7.0.0/production/ +[running tests page]: https://www.jhipster.tech/documentation-archive/v7.0.0/running-tests/ +[code quality page]: https://www.jhipster.tech/documentation-archive/v7.0.0/code-quality/ +[setting up continuous integration]: https://www.jhipster.tech/documentation-archive/v7.0.0/setting-up-ci/ diff --git a/checkstyle.xml b/checkstyle.xml index 10a479f..5d5ae65 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -1,6 +1,6 @@ + "https://checkstyle.org/dtds/configuration_1_3.dtd"> @@ -9,8 +9,7 @@ - + - \ No newline at end of file + diff --git a/mvnw b/mvnw index d2f0ea3..41c0f0c 100755 --- a/mvnw +++ b/mvnw @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Maven2 Start Up Batch script +# Maven Start Up Batch script # # Required ENV vars: # ------------------ @@ -212,9 +212,9 @@ else echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." fi if [ -n "$MVNW_REPOURL" ]; then - jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" else - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" fi while IFS="=" read key value; do case "$key" in (wrapperUrl) jarUrl="$value"; break ;; @@ -246,7 +246,7 @@ else else curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f fi - + else if [ "$MVNW_VERBOSE" = true ]; then echo "Falling back to using Java to download" diff --git a/mvnw.cmd b/mvnw.cmd index b26ab24..8611571 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -18,7 +18,7 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script +@REM Maven Start Up Batch script @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @@ -26,7 +26,7 @@ @REM Optional ENV vars @REM M2_HOME - location of maven2's installed home dir @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM e.g. to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @@ -120,7 +120,7 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B @@ -134,7 +134,7 @@ if exist %WRAPPER_JAR% ( ) ) else ( if not "%MVNW_REPOURL%" == "" ( - SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" ) if "%MVNW_VERBOSE%" == "true" ( echo Couldn't find %WRAPPER_JAR%, downloading it ... diff --git a/package.json b/package.json index 50e9c9d..d888e9b 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,59 @@ { "name": "case-management", "version": "0.0.0", - "description": "Description for caseManagement", "private": true, + "description": "Description for caseManagement", "license": "UNLICENSED", - "cacheDirectories": [ - "node_modules" - ], + "scripts": { + "prettier:check": "prettier --check \"{,src/**/}*.{md,json,yml,html,java}\"", + "prettier:format": "prettier --write \"{,src/**/}*.{md,json,yml,html,java}\"", + "docker:db:up": "docker-compose -f src/main/docker/mariadb.yml up -d", + "docker:db:down": "docker-compose -f src/main/docker/mariadb.yml down -v --remove-orphans", + "docker:consul:up": "docker-compose -f src/main/docker/consul.yml up -d", + "docker:consul:down": "docker-compose -f src/main/docker/consul.yml down -v --remove-orphans", + "docker:others:await": "", + "predocker:others:up": "", + "docker:others:up": "npm run docker:consul:up", + "docker:others:down": "npm run docker:consul:down", + "ci:e2e:prepare:docker": "npm run docker:db:up && npm run docker:others:up && docker ps -a", + "ci:e2e:prepare": "npm run ci:e2e:prepare:docker", + "ci:e2e:teardown:docker": "npm run docker:db:down --if-present && npm run docker:others:down && docker ps -a", + "ci:e2e:teardown": "npm run ci:e2e:teardown:docker", + "backend:info": "./mvnw -ntp enforcer:display-info --batch-mode", + "backend:doc:test": "./mvnw -ntp javadoc:javadoc --batch-mode", + "backend:nohttp:test": "./mvnw -ntp checkstyle:check --batch-mode", + "java:jar": "./mvnw -ntp verify -DskipTests --batch-mode", + "java:war": "./mvnw -ntp verify -DskipTests --batch-mode -Pwar", + "java:docker": "./mvnw -ntp verify -DskipTests jib:dockerBuild", + "backend:unit:test": "./mvnw -ntp -P-webapp verify --batch-mode -Dlogging.level.ROOT=OFF -Dlogging.level.org.zalando=OFF -Dlogging.level.tech.jhipster=OFF -Dlogging.level.org.securityrat.casemanagement=OFF -Dlogging.level.org.springframework=OFF -Dlogging.level.org.springframework.web=OFF -Dlogging.level.org.springframework.security=OFF", + "java:jar:dev": "npm run java:jar -- -Pdev,webapp", + "java:jar:prod": "npm run java:jar -- -Pprod", + "java:war:dev": "npm run java:war -- -Pdev,webapp", + "java:war:prod": "npm run java:war -- -Pprod", + "java:docker:dev": "npm run java:docker -- -Pdev,webapp", + "java:docker:prod": "npm run java:docker -- -Pprod", + "ci:backend:test": "npm run backend:info && npm run backend:doc:test && npm run backend:nohttp:test && npm run backend:unit:test", + "ci:server:package": "npm run java:$npm_package_config_packaging:$npm_package_config_default_environment", + "ci:e2e:package": "npm run java:$npm_package_config_packaging:$npm_package_config_default_environment -- -Pe2e -Denforcer.skip=true", + "preci:e2e:server:start": "npm run docker:db:await --if-present && npm run docker:others:await --if-present", + "ci:e2e:server:start": "java -jar target/e2e.$npm_package_config_packaging --spring.profiles.active=$npm_package_config_default_environment -Dlogging.level.ROOT=OFF -Dlogging.level.org.zalando=OFF -Dlogging.level.tech.jhipster=OFF -Dlogging.level.org.securityrat.casemanagement=OFF -Dlogging.level.org.springframework=OFF -Dlogging.level.org.springframework.web=OFF -Dlogging.level.org.springframework.security=OFF --logging.level.org.springframework.web=ERROR" + }, + "config": { + "backend_port": "8082", + "packaging": "jar" + }, "devDependencies": { - "generator-jhipster": "6.6.0", - "@openapitools/openapi-generator-cli": "0.0.14-4.0.2" + "generator-jhipster": "7.0.0", + "husky": "4.3.8", + "lint-staged": "10.5.4", + "prettier": "2.2.1", + "prettier-plugin-java": "1.0.2", + "prettier-plugin-packagejson": "2.2.10" }, "engines": { - "node": ">=8.9.0" - } + "node": ">=14.16.0" + }, + "cacheDirectories": [ + "node_modules" + ] } diff --git a/pom.xml b/pom.xml index 9cdeca8..18114ca 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,8 @@ - + 4.0.0 org.securityrat.casemanagement @@ -22,79 +24,69 @@ 3.3.9 - 1.8 + 11 UTF-8 UTF-8 ${project.build.directory}/test-results yyyyMMddHHmmss ${java.version} ${java.version} + org.securityrat.casemanagement.CaseManagementApp -Djava.security.egd=file:/dev/./urandom -Xmx256m jdt_apt false - + - 3.1.0 + 7.0.0 - 2.1.11.RELEASE - - 5.1.12.RELEASE + https://mvnrepository.com/artifact/tech.jhipster/jhipster-dependencies/${jhipster-dependencies.version} --> + 2.4.4 - 5.3.14.Final + 5.4.29.Final - 3.23.2-GA + 3.27.0-GA - 3.6.3 - 3.6 - - 5.3.8.Final + 4.3.1 + 4.3.1 1.4.200 2.0.1.Final - 2.3.2 - 0.12.0 - 1.3.1.Final + 2.3.3 + 0.17.0 + 1.4.2.Final 3.1.0 3.8.1 - 3.1.1 + 3.2.0 2.10 3.0.0-M3 - 3.0.0-M4 + 3.0.0-M5 2.2.1 - 3.1.0 - 3.0.0-M4 - 3.2.3 - 3.1.0 - 8.27 - 0.0.4.RELEASE - 4.0.0 - 0.8.5 - 1.8.0 + 3.2.0 + 3.0.0-M5 + 3.3.1 + 3.1.2 + 8.40 + 0.0.5.RELEASE + 4.0.3 + 0.8.6 + 2.8.0 1.0.0 1.0.0 - 3.7.0.1746 - ${project.build.directory}/jacoco/test - ${jacoco.utReportFolder}/test.exec - ${project.build.directory}/jacoco/integrationTest - ${jacoco.itReportFolder}/integrationTest.exec - ${project.testresult.directory}/test - ${project.testresult.directory}/integrationTest + 3.8.0.2131 - io.github.jhipster + tech.jhipster jhipster-dependencies ${jhipster-dependencies.version} pom @@ -106,10 +98,17 @@ - io.github.jhipster + tech.jhipster jhipster-framework - + + javax.annotation + javax.annotation-api + + + com.fasterxml.jackson.module + jackson-module-jaxb-annotations + com.fasterxml.jackson.datatype jackson-datatype-hibernate5 @@ -122,20 +121,14 @@ com.fasterxml.jackson.datatype jackson-datatype-jsr310 - - com.fasterxml.jackson.module - jackson-module-afterburner - com.h2database h2 test - com.jayway.jsonpath - json-path - test - + io.springfox + springfox-oas io.springfox @@ -149,27 +142,18 @@ com.zaxxer HikariCP - - commons-io - commons-io - org.apache.commons commons-lang3 - org.mariadb.jdbc - mariadb-java-client - - - org.junit.jupiter - junit-jupiter-engine + org.testcontainers + mariadb test - org.assertj - assertj-core - test + org.mariadb.jdbc + mariadb-java-client org.hibernate @@ -187,18 +171,18 @@ org.liquibase liquibase-core - - - net.logstash.logback - logstash-logback-encoder + + ${liquibase.version} org.mapstruct mapstruct + ${mapstruct.version} org.mapstruct mapstruct-processor + ${mapstruct.version} provided @@ -214,10 +198,6 @@ org.springframework.boot spring-boot-starter-actuator - - org.springframework.boot - spring-boot-starter-aop - org.springframework.boot spring-boot-starter-data-jpa @@ -244,8 +224,8 @@ test - junit - junit + org.junit.vintage + junit-vintage-engine @@ -286,23 +266,11 @@ org.springframework.boot spring-boot-starter-oauth2-resource-server - - org.springframework.security.oauth - spring-security-oauth2 - - - org.springframework.security - spring-security-jwt - org.springframework.cloud spring-cloud-starter - - org.springframework.cloud - spring-cloud-starter-netflix-ribbon - org.springframework.cloud spring-cloud-starter-netflix-hystrix @@ -311,6 +279,10 @@ org.springframework.retry spring-retry + + org.springframework.cloud + spring-cloud-starter-bootstrap + org.springframework.cloud spring-cloud-starter-consul-discovery @@ -323,10 +295,7 @@ org.springframework.cloud spring-cloud-starter-openfeign - - org.springframework.boot - spring-boot-starter-cloud-connectors - + org.springframework.security spring-security-data @@ -348,72 +317,14 @@ org.apache.maven.plugins maven-compiler-plugin - - ${java.version} - ${java.version} - - - org.springframework.boot - spring-boot-configuration-processor - ${spring-boot.version} - - - org.mapstruct - mapstruct-processor - ${mapstruct.version} - - - - org.hibernate - hibernate-jpamodelgen - ${hibernate.version} - - - org.glassfish.jaxb - jaxb-runtime - ${jaxb-runtime.version} - - - - org.apache.maven.plugins maven-checkstyle-plugin - ${maven-checkstyle.version} - - - com.puppycrawl.tools - checkstyle - ${checkstyle.version} - - - io.spring.nohttp - nohttp-checkstyle - ${spring-nohttp-checkstyle.version} - - - - checkstyle.xml - pom.xml,README.md - .git/**/*,target/**/*,node_modules/**/*,node/**/* - ./ - - - - - check - - - org.apache.maven.plugins maven-javadoc-plugin - ${maven-javadoc-plugin.version} - - ${maven.compiler.source} - org.apache.maven.plugins @@ -454,21 +365,6 @@ org.springframework.boot spring-boot-maven-plugin - - - - repackage - - - - - ${start-class} - true - - com.google.cloud.tools @@ -477,30 +373,119 @@ org.codehaus.mojo properties-maven-plugin - ${properties-maven-plugin.version} - - - initialize - - read-project-properties - - - - sonar-project.properties - - - - + + org.apache.maven.plugins + maven-checkstyle-plugin + ${maven-checkstyle.version} + + + com.puppycrawl.tools + checkstyle + ${checkstyle.version} + + + io.spring.nohttp + nohttp-checkstyle + ${spring-nohttp-checkstyle.version} + + + + checkstyle.xml + pom.xml,README.md + .git/**/*,target/**/*,node_modules/**/*,node/**/* + ./ + + + + + check + + + + org.apache.maven.plugins maven-compiler-plugin ${maven-compiler-plugin.version} + + ${java.version} + ${java.version} + + + org.springframework.boot + spring-boot-configuration-processor + ${spring-boot.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + org.hibernate + hibernate-jpamodelgen + ${hibernate.version} + + + org.glassfish.jaxb + jaxb-runtime + ${jaxb-runtime.version} + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + ${maven.compiler.source} + + + org.apache.maven.plugins + maven-war-plugin + ${maven-war-plugin.version} + + + default-war + + war + + package + + + + WEB-INF/**,META-INF/** + false + + + + org.codehaus.mojo + properties-maven-plugin + ${properties-maven-plugin.version} + + + initialize + + read-project-properties + + + + sonar-project.properties + + + + + + pl.project13.maven git-commit-id-plugin @@ -533,10 +518,6 @@ prepare-agent - - - ${jacoco.utReportFile} - @@ -545,20 +526,12 @@ report - - ${jacoco.utReportFile} - ${jacoco.utReportFolder} - pre-integration-tests prepare-agent-integration - - - ${jacoco.itReportFile} - @@ -567,10 +540,6 @@ report-integration - - ${jacoco.itReportFile} - ${jacoco.itReportFolder} - @@ -589,7 +558,7 @@ bash - chmod +x /entrypoint.sh && sync && /entrypoint.sh + /entrypoint.sh 8082 @@ -599,7 +568,17 @@ 0 USE_CURRENT_TIMESTAMP + 1000 + + src/main/docker/jib + + + /entrypoint.sh + 755 + + + @@ -622,19 +601,9 @@ - io.github.jhipster - jhipster-framework - ${jhipster-dependencies.version} - - - org.hibernate - hibernate-core - ${hibernate-core.version} - - - org.javassist - javassist - ${javassist.version} + org.liquibase + liquibase-core + ${liquibase.version} org.liquibase.ext @@ -651,26 +620,16 @@ validation-api ${validation-api.version} + + org.javassist + javassist + ${javassist.version} + com.h2database h2 ${h2.version} - - org.springframework - spring-core - ${spring.version} - - - org.springframework - spring-context - ${spring.version} - - - org.springframework - spring-beans - ${spring.version} - @@ -717,8 +676,8 @@ [${maven.version},) - You are running an incompatible version of Java. JHipster supports JDK 8 to 13. - [1.8,14) + You are running an incompatible version of Java. JHipster supports JDK 8 to 15. + [1.8,16) @@ -775,7 +734,6 @@ alphabetical - ${junit.utReportFolder} **/*IT* **/*IntTest* @@ -792,7 +750,6 @@ ${project.build.outputDirectory} alphabetical - ${junit.itReportFolder} **/*IT* **/*IntTest* @@ -822,6 +779,22 @@ org.springframework.boot spring-boot-maven-plugin ${spring-boot.version} + + + + repackage + + + + + ${start-class} + true + + + @@ -835,9 +808,9 @@ - swagger + api-docs - ,swagger + ,api-docs @@ -894,9 +867,6 @@ org.springframework.boot spring-boot-maven-plugin - - ${start-class} - @@ -913,7 +883,7 @@ - prod${profile.swagger}${profile.no-liquibase} + prod${profile.api-docs}${profile.tls}${profile.no-liquibase} @@ -923,19 +893,6 @@ org.apache.maven.plugins maven-war-plugin - ${maven-war-plugin.version} - - - - war - - package - - - - WEB-INF/**,META-INF/** - false - @@ -963,6 +920,7 @@ org.mapstruct mapstruct-processor + ${mapstruct.version} org.hibernate diff --git a/sonar-project.properties b/sonar-project.properties index ce1a060..fdcdfce 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -6,20 +6,20 @@ sonar.sources=src/main/ sonar.host.url=http://localhost:9001 sonar.tests=src/test/ -sonar.coverage.jacoco.xmlReportPaths=target/jacoco/test/jacoco.xml,target/jacoco/integrationTest/jacoco.xml +sonar.coverage.jacoco.xmlReportPaths=target/site/**/jacoco*.xml sonar.java.codeCoveragePlugin=jacoco -sonar.junit.reportPaths=target/test-results/test,target/test-results/integrationTest +sonar.junit.reportPaths=target/surefire-reports,target/failsafe-reports sonar.sourceEncoding=UTF-8 sonar.exclusions=src/main/webapp/content/**/*.*, src/main/webapp/i18n/*.js, target/classes/static/**/*.* sonar.issue.ignore.multicriteria=S3437,S4684,UndocumentedApi -# Rule https://sonarcloud.io/coding_rules?open=squid%3AS3437&rule_key=squid%3AS3437 is ignored, as a JPA-managed field cannot be transient +# Rule https://rules.sonarsource.com/java/RSPEC-3437 is ignored, as a JPA-managed field cannot be transient sonar.issue.ignore.multicriteria.S3437.resourceKey=src/main/java/**/* sonar.issue.ignore.multicriteria.S3437.ruleKey=squid:S3437 -# Rule https://sonarcloud.io/coding_rules?open=squid%3AUndocumentedApi&rule_key=squid%3AUndocumentedApi is ignored, as we want to follow "clean code" guidelines and classes, methods and arguments names should be self-explanatory +# Rule https://rules.sonarsource.com/java/RSPEC-1176 is ignored, as we want to follow "clean code" guidelines and classes, methods and arguments names should be self-explanatory sonar.issue.ignore.multicriteria.UndocumentedApi.resourceKey=src/main/java/**/* sonar.issue.ignore.multicriteria.UndocumentedApi.ruleKey=squid:UndocumentedApi -# Rule https://sonarcloud.io/coding_rules?open=squid%3AS4684&rule_key=squid%3AS4684 +# Rule https://rules.sonarsource.com/java/RSPEC-4684 sonar.issue.ignore.multicriteria.S4684.resourceKey=src/main/java/**/* -sonar.issue.ignore.multicriteria.S4684.ruleKey=squid:S4684 +sonar.issue.ignore.multicriteria.S4684.ruleKey=java:S4684 diff --git a/src/main/docker/app.yml b/src/main/docker/app.yml index 9f766b3..eed6a30 100644 --- a/src/main/docker/app.yml +++ b/src/main/docker/app.yml @@ -1,28 +1,52 @@ -version: '2' +# This configuration is intended for development purpose, it's **your** responsibility to harden it for production +version: '3.8' services: casemanagement-app: image: casemanagement environment: - _JAVA_OPTIONS=-Xmx512m -Xms256m - - SPRING_PROFILES_ACTIVE=prod,swagger + - SPRING_PROFILES_ACTIVE=prod,api-docs - MANAGEMENT_METRICS_EXPORT_PROMETHEUS_ENABLED=true - SPRING_CLOUD_CONSUL_HOST=consul - SPRING_CLOUD_CONSUL_PORT=8500 - - SPRING_DATASOURCE_URL=jdbc:mariadb://casemanagement-mariadb:3306/casemanagement + - SPRING_DATASOURCE_URL=jdbc:mariadb://casemanagement-mariadb:3306/casemanagement?useLegacyDatetimeCode=false&serverTimezone=UTC + - SPRING_LIQUIBASE_URL=jdbc:mariadb://casemanagement-mariadb:3306/casemanagement?useLegacyDatetimeCode=false&serverTimezone=UTC - SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER_URI=http://keycloak:9080/auth/realms/jhipster - SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_ID=internal - SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_SECRET=internal - JHIPSTER_SLEEP=120 # gives time for mariadb server to start casemanagement-mariadb: - extends: - file: mariadb.yml - service: casemanagement-mariadb + image: mariadb:10.5.9 + # volumes: + # - ~/volumes/jhipster/caseManagement/mysql/:/var/lib/mysql/ + environment: + - MYSQL_USER=root + - MYSQL_ALLOW_EMPTY_PASSWORD=yes + - MYSQL_DATABASE=casemanagement + # If you want to expose these ports outside your dev PC, + # remove the "127.0.0.1:" prefix + ports: + - 127.0.0.1:3306:3306 + command: mysqld --lower_case_table_names=1 --skip-ssl --character_set_server=utf8mb4 --explicit_defaults_for_timestamp consul: - extends: - file: consul.yml - service: consul + image: consul:1.9.4 + # If you want to expose these ports outside your dev PC, + # remove the "127.0.0.1:" prefix + ports: + - 127.0.0.1:8300:8300 + - 127.0.0.1:8500:8500 + - 127.0.0.1:8600:8600 + command: consul agent -dev -ui -client 0.0.0.0 -log-level=INFO consul-config-loader: - extends: - file: consul.yml - service: consul-config-loader + image: jhipster/consul-config-loader:v0.4.1 + volumes: + - ./central-server-config:/config + environment: + - INIT_SLEEP_SECONDS=5 + - CONSUL_URL=consul + - CONSUL_PORT=8500 + # Uncomment to load configuration into Consul from a Git repository + # as configured in central-server-config/git2consul.json + # Also set SPRING_CLOUD_CONSUL_CONFIG_FORMAT=files on your apps + # - CONFIG_MODE=git diff --git a/src/main/docker/central-server-config/application.yml b/src/main/docker/central-server-config/application.yml index e2398f0..af04448 100644 --- a/src/main/docker/central-server-config/application.yml +++ b/src/main/docker/central-server-config/application.yml @@ -6,4 +6,5 @@ jhipster: security: authentication: jwt: - secret: my-secret-key-which-should-be-changed-in-production-and-be-base64-encoded + # secret key which should be base64 encoded and changed in production + base64-secret: ebcc96a1801b3e946498deeae1c992287d454acc diff --git a/src/main/docker/config/git2consul.json b/src/main/docker/config/git2consul.json index dcb9f6b..000854d 100644 --- a/src/main/docker/config/git2consul.json +++ b/src/main/docker/config/git2consul.json @@ -4,7 +4,7 @@ { "name": "config", "url": "https://github.com/jhipster/generator-jhipster.git", - "branches": ["master"], + "branches": ["main"], "include_branch_name": false, "source_root": "generators/server/src/main/docker/config/consul-config/", "hooks": [ diff --git a/src/main/docker/consul.yml b/src/main/docker/consul.yml index 2aa4598..546ac4b 100644 --- a/src/main/docker/consul.yml +++ b/src/main/docker/consul.yml @@ -1,15 +1,18 @@ -version: '2' +# This configuration is intended for development purpose, it's **your** responsibility to harden it for production +version: '3.8' services: consul: - image: consul:1.6.2 + image: consul:1.9.4 + # If you want to expose these ports outside your dev PC, + # remove the "127.0.0.1:" prefix ports: - - 8300:8300 - - 8500:8500 - - 8600:8600 - command: consul agent -dev -ui -client 0.0.0.0 + - 127.0.0.1:8300:8300 + - 127.0.0.1:8500:8500 + - 127.0.0.1:8600:8600 + command: consul agent -dev -ui -client 0.0.0.0 -log-level=INFO consul-config-loader: - image: jhipster/consul-config-loader:v0.3.0 + image: jhipster/consul-config-loader:v0.4.1 volumes: - ./central-server-config:/config environment: diff --git a/src/main/docker/jhipster-control-center.yml b/src/main/docker/jhipster-control-center.yml new file mode 100644 index 0000000..7d0d9a9 --- /dev/null +++ b/src/main/docker/jhipster-control-center.yml @@ -0,0 +1,54 @@ +## How to use JHCC docker compose +# To allow JHCC to reach JHipster application from a docker container note that we set the host as host.docker.internal +# To reach the application from a browser, you need to add '127.0.0.1 host.docker.internal' to your hosts file. +### Discovery mode +# JHCC support 3 kinds of discovery mode: Consul, Eureka and static +# In order to use one, please set SPRING_PROFILES_ACTIVE to one (and only one) of this values: consul,eureka,static +### Discovery properties +# According to the discovery mode choose as Spring profile, you have to set the right properties +# please note that current properties are set to run JHCC with default values, personalize them if needed +# and remove those from other modes. You can only have one mode active. +#### Eureka +# - EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE=http://admin:admin@host.docker.internal:8761/eureka/ +#### Consul +# - SPRING_CLOUD_CONSUL_HOST=host.docker.internal +# - SPRING_CLOUD_CONSUL_PORT=8500 +#### Static +# Add instances to "MyApp" +# - SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_MYAPP_0_URI=http://host.docker.internal:8081 +# - SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_MYAPP_1_URI=http://host.docker.internal:8082 +# Or add a new application named MyNewApp +# - SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_MYNEWAPP_0_URI=http://host.docker.internal:8080 +# This configuration is intended for development purpose, it's **your** responsibility to harden it for production + +#### IMPORTANT +# If you choose Consul or Eureka mode: +# Do not forget to remove the prefix "127.0.0.1" in front of their port in order to expose them. +# This is required because JHCC need to communicate with Consul or Eureka. +# - In Consul mode, the ports are in the consul.yml file. +# - In Eureka mode, the ports are in the jhipster-registry.yml file. + +version: '3.8' +services: + jhipster-control-center: + image: 'jhipster/jhipster-control-center:v0.4.1' + command: + - /bin/sh + - -c + # Patch /etc/hosts to support resolving host.docker.internal to the internal IP address used by the host in all OSes + - echo "`ip route | grep default | cut -d ' ' -f3` host.docker.internal" | tee -a /etc/hosts > /dev/null && java -jar /jhipster-control-center.jar + environment: + - _JAVA_OPTIONS=-Xmx512m -Xms256m + - SPRING_PROFILES_ACTIVE=prod,api-docs,consul,oauth2 + - JHIPSTER_SLEEP=30 # gives time for other services to boot before the application + # For keycloak to work, you need to add '127.0.0.1 keycloak' to your hosts file + - SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER_URI=http://keycloak:9080/auth/realms/jhipster + - SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_ID=jhipster-control-center + - SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_SECRET=jhipster-control-center + - SPRING_CLOUD_CONSUL_HOST=host.docker.internal + - SPRING_CLOUD_CONSUL_PORT=8500 + - LOGGING_FILE_NAME=/tmp/jhipster-control-center.log + # If you want to expose these ports outside your dev PC, + # remove the "127.0.0.1:" prefix + ports: + - 127.0.0.1:7419:7419 diff --git a/src/main/jib/entrypoint.sh b/src/main/docker/jib/entrypoint.sh similarity index 100% rename from src/main/jib/entrypoint.sh rename to src/main/docker/jib/entrypoint.sh diff --git a/src/main/docker/mariadb.yml b/src/main/docker/mariadb.yml index ea2b077..9a96c98 100644 --- a/src/main/docker/mariadb.yml +++ b/src/main/docker/mariadb.yml @@ -1,13 +1,16 @@ -version: '2' +# This configuration is intended for development purpose, it's **your** responsibility to harden it for production +version: '3.8' services: casemanagement-mariadb: - image: mariadb:10.4.10 + image: mariadb:10.5.9 # volumes: - # - ~/volumes/jhipster/caseManagement/mysql/:/var/lib/mysql/ + # - ~/volumes/jhipster/caseManagement/mysql/:/var/lib/mysql/ environment: - MYSQL_USER=root - MYSQL_ALLOW_EMPTY_PASSWORD=yes - MYSQL_DATABASE=casemanagement + # If you want to expose these ports outside your dev PC, + # remove the "127.0.0.1:" prefix ports: - - 3306:3306 + - 127.0.0.1:3306:3306 command: mysqld --lower_case_table_names=1 --skip-ssl --character_set_server=utf8mb4 --explicit_defaults_for_timestamp diff --git a/src/main/docker/monitoring.yml b/src/main/docker/monitoring.yml index c1ac16e..1c7c1e2 100644 --- a/src/main/docker/monitoring.yml +++ b/src/main/docker/monitoring.yml @@ -1,26 +1,31 @@ -version: '2' +# This configuration is intended for development purpose, it's **your** responsibility to harden it for production +version: '3.8' services: casemanagement-prometheus: - image: prom/prometheus:v2.14.0 + image: prom/prometheus:v2.25.0 volumes: - ./prometheus/:/etc/prometheus/ command: - '--config.file=/etc/prometheus/prometheus.yml' + # If you want to expose these ports outside your dev PC, + # remove the "127.0.0.1:" prefix ports: - - 9090:9090 + - 127.0.0.1:9090:9090 # On MacOS, remove next line and replace localhost by host.docker.internal in prometheus/prometheus.yml and # grafana/provisioning/datasources/datasource.yml network_mode: 'host' # to test locally running service casemanagement-grafana: - image: grafana/grafana:6.5.1 + image: grafana/grafana:7.4.3 volumes: - ./grafana/provisioning/:/etc/grafana/provisioning/ environment: - GF_SECURITY_ADMIN_PASSWORD=admin - GF_USERS_ALLOW_SIGN_UP=false - GF_INSTALL_PLUGINS=grafana-piechart-panel + # If you want to expose these ports outside your dev PC, + # remove the "127.0.0.1:" prefix ports: - - 3000:3000 + - 127.0.0.1:3000:3000 # On MacOS, remove next line and replace localhost by host.docker.internal in prometheus/prometheus.yml and # grafana/provisioning/datasources/datasource.yml network_mode: 'host' # to test locally running service diff --git a/src/main/docker/sonar.yml b/src/main/docker/sonar.yml index 622d796..8104715 100644 --- a/src/main/docker/sonar.yml +++ b/src/main/docker/sonar.yml @@ -1,7 +1,13 @@ -version: '2' +# This configuration is intended for development purpose, it's **your** responsibility to harden it for production +version: '3.8' services: casemanagement-sonar: - image: sonarqube:7.9.1-community + image: sonarqube:8.7.0-community + # Authentication is turned off for out of the box experience while trying out SonarQube + # For real use cases delete sonar.forceAuthentication variable or set sonar.forceAuthentication=true + environment: + - sonar.forceAuthentication=false + # If you want to expose these ports outside your dev PC, + # remove the "127.0.0.1:" prefix ports: - - 9001:9000 - - 9092:9092 + - 127.0.0.1:9001:9000 diff --git a/src/main/java/org/securityrat/casemanagement/ApplicationWebXml.java b/src/main/java/org/securityrat/casemanagement/ApplicationWebXml.java index 2346a2a..a0a82fd 100644 --- a/src/main/java/org/securityrat/casemanagement/ApplicationWebXml.java +++ b/src/main/java/org/securityrat/casemanagement/ApplicationWebXml.java @@ -1,9 +1,8 @@ package org.securityrat.casemanagement; -import io.github.jhipster.config.DefaultProfileUtil; - import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import tech.jhipster.config.DefaultProfileUtil; /** * This is a helper Java class that provides an alternative to creating a {@code web.xml}. @@ -13,9 +12,7 @@ public class ApplicationWebXml extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { - /** - * set a default to use when no profile is configured. - */ + // set a default to use when no profile is configured. DefaultProfileUtil.addDefaultProfile(application.application()); return application.sources(CaseManagementApp.class); } diff --git a/src/main/java/org/securityrat/casemanagement/CaseManagementApp.java b/src/main/java/org/securityrat/casemanagement/CaseManagementApp.java index 46f1405..60793e8 100644 --- a/src/main/java/org/securityrat/casemanagement/CaseManagementApp.java +++ b/src/main/java/org/securityrat/casemanagement/CaseManagementApp.java @@ -1,30 +1,26 @@ package org.securityrat.casemanagement; -import org.securityrat.casemanagement.config.ApplicationProperties; - -import io.github.jhipster.config.DefaultProfileUtil; -import io.github.jhipster.config.JHipsterConstants; - +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Optional; +import javax.annotation.PostConstruct; import org.apache.commons.lang3.StringUtils; +import org.securityrat.casemanagement.config.ApplicationProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.InitializingBean; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.core.env.Environment; - -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.Arrays; -import java.util.Collection; +import tech.jhipster.config.DefaultProfileUtil; +import tech.jhipster.config.JHipsterConstants; @SpringBootApplication -@EnableConfigurationProperties({LiquibaseProperties.class, ApplicationProperties.class}) -@EnableDiscoveryClient -public class CaseManagementApp implements InitializingBean { +@EnableConfigurationProperties({ LiquibaseProperties.class, ApplicationProperties.class }) +public class CaseManagementApp { private static final Logger log = LoggerFactory.getLogger(CaseManagementApp.class); @@ -41,16 +37,24 @@ public CaseManagementApp(Environment env) { *

* You can find more information on how profiles work with JHipster on https://www.jhipster.tech/profiles/. */ - @Override - public void afterPropertiesSet() throws Exception { + @PostConstruct + public void initApplication() { Collection activeProfiles = Arrays.asList(env.getActiveProfiles()); - if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) && activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_PRODUCTION)) { - log.error("You have misconfigured your application! It should not run " + - "with both the 'dev' and 'prod' profiles at the same time."); + if ( + activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) && + activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_PRODUCTION) + ) { + log.error( + "You have misconfigured your application! It should not run " + "with both the 'dev' and 'prod' profiles at the same time." + ); } - if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) && activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_CLOUD)) { - log.error("You have misconfigured your application! It should not " + - "run with both the 'dev' and 'cloud' profiles at the same time."); + if ( + activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) && + activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_CLOUD) + ) { + log.error( + "You have misconfigured your application! It should not " + "run with both the 'dev' and 'cloud' profiles at the same time." + ); } } @@ -67,26 +71,24 @@ public static void main(String[] args) { } private static void logApplicationStartup(Environment env) { - String protocol = "http"; - if (env.getProperty("server.ssl.key-store") != null) { - protocol = "https"; - } + String protocol = Optional.ofNullable(env.getProperty("server.ssl.key-store")).map(key -> "https").orElse("http"); String serverPort = env.getProperty("server.port"); - String contextPath = env.getProperty("server.servlet.context-path"); - if (StringUtils.isBlank(contextPath)) { - contextPath = "/"; - } + String contextPath = Optional + .ofNullable(env.getProperty("server.servlet.context-path")) + .filter(StringUtils::isNotBlank) + .orElse("/"); String hostAddress = "localhost"; try { hostAddress = InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { log.warn("The host name could not be determined, using `localhost` as fallback"); } - log.info("\n----------------------------------------------------------\n\t" + - "Application '{}' is running! Access URLs:\n\t" + - "Local: \t\t{}://localhost:{}{}\n\t" + - "External: \t{}://{}:{}{}\n\t" + - "Profile(s): \t{}\n----------------------------------------------------------", + log.info( + "\n----------------------------------------------------------\n\t" + + "Application '{}' is running! Access URLs:\n\t" + + "Local: \t\t{}://localhost:{}{}\n\t" + + "External: \t{}://{}:{}{}\n\t" + + "Profile(s): \t{}\n----------------------------------------------------------", env.getProperty("spring.application.name"), protocol, serverPort, @@ -95,13 +97,17 @@ private static void logApplicationStartup(Environment env) { hostAddress, serverPort, contextPath, - env.getActiveProfiles()); + env.getActiveProfiles() + ); String configServerStatus = env.getProperty("configserver.status"); if (configServerStatus == null) { configServerStatus = "Not found or not setup for this application"; } - log.info("\n----------------------------------------------------------\n\t" + - "Config Server: \t{}\n----------------------------------------------------------", configServerStatus); + log.info( + "\n----------------------------------------------------------\n\t" + + "Config Server: \t{}\n----------------------------------------------------------", + configServerStatus + ); } } diff --git a/src/main/java/org/securityrat/casemanagement/GeneratedByJHipster.java b/src/main/java/org/securityrat/casemanagement/GeneratedByJHipster.java new file mode 100644 index 0000000..2119628 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/GeneratedByJHipster.java @@ -0,0 +1,13 @@ +package org.securityrat.casemanagement; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.annotation.Generated; + +@Generated(value = "JHipster", comments = "Generated by JHipster 7.0.0") +@Retention(RetentionPolicy.SOURCE) +@Target({ ElementType.TYPE }) +public @interface GeneratedByJHipster { +} diff --git a/src/main/java/org/securityrat/casemanagement/aop/logging/LoggingAspect.java b/src/main/java/org/securityrat/casemanagement/aop/logging/LoggingAspect.java index e065893..b73510b 100644 --- a/src/main/java/org/securityrat/casemanagement/aop/logging/LoggingAspect.java +++ b/src/main/java/org/securityrat/casemanagement/aop/logging/LoggingAspect.java @@ -1,7 +1,6 @@ package org.securityrat.casemanagement.aop.logging; -import io.github.jhipster.config.JHipsterConstants; - +import java.util.Arrays; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.AfterThrowing; @@ -12,8 +11,7 @@ import org.slf4j.LoggerFactory; import org.springframework.core.env.Environment; import org.springframework.core.env.Profiles; - -import java.util.Arrays; +import tech.jhipster.config.JHipsterConstants; /** * Aspect for logging execution of service and repository Spring components. @@ -23,8 +21,6 @@ @Aspect public class LoggingAspect { - private final Logger log = LoggerFactory.getLogger(this.getClass()); - private final Environment env; public LoggingAspect(Environment env) { @@ -34,9 +30,11 @@ public LoggingAspect(Environment env) { /** * Pointcut that matches all repositories, services and Web REST endpoints. */ - @Pointcut("within(@org.springframework.stereotype.Repository *)" + + @Pointcut( + "within(@org.springframework.stereotype.Repository *)" + " || within(@org.springframework.stereotype.Service *)" + - " || within(@org.springframework.web.bind.annotation.RestController *)") + " || within(@org.springframework.web.bind.annotation.RestController *)" + ) public void springBeanPointcut() { // Method is empty as this is just a Pointcut, the implementations are in the advices. } @@ -44,13 +42,25 @@ public void springBeanPointcut() { /** * Pointcut that matches all Spring beans in the application's main packages. */ - @Pointcut("within(org.securityrat.casemanagement.repository..*)"+ - " || within(org.securityrat.casemanagement.service..*)"+ - " || within(org.securityrat.casemanagement.web.rest..*)") + @Pointcut( + "within(org.securityrat.casemanagement.repository..*)" + + " || within(org.securityrat.casemanagement.service..*)" + + " || within(org.securityrat.casemanagement.web.rest..*)" + ) public void applicationPackagePointcut() { // Method is empty as this is just a Pointcut, the implementations are in the advices. } + /** + * Retrieves the {@link Logger} associated to the given {@link JoinPoint}. + * + * @param joinPoint join point we want the logger for. + * @return {@link Logger} associated to the given {@link JoinPoint}. + */ + private Logger logger(JoinPoint joinPoint) { + return LoggerFactory.getLogger(joinPoint.getSignature().getDeclaringTypeName()); + } + /** * Advice that logs methods throwing exceptions. * @@ -60,12 +70,21 @@ public void applicationPackagePointcut() { @AfterThrowing(pointcut = "applicationPackagePointcut() && springBeanPointcut()", throwing = "e") public void logAfterThrowing(JoinPoint joinPoint, Throwable e) { if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT))) { - log.error("Exception in {}.{}() with cause = \'{}\' and exception = \'{}\'", joinPoint.getSignature().getDeclaringTypeName(), - joinPoint.getSignature().getName(), e.getCause() != null? e.getCause() : "NULL", e.getMessage(), e); - + logger(joinPoint) + .error( + "Exception in {}() with cause = \'{}\' and exception = \'{}\'", + joinPoint.getSignature().getName(), + e.getCause() != null ? e.getCause() : "NULL", + e.getMessage(), + e + ); } else { - log.error("Exception in {}.{}() with cause = {}", joinPoint.getSignature().getDeclaringTypeName(), - joinPoint.getSignature().getName(), e.getCause() != null? e.getCause() : "NULL"); + logger(joinPoint) + .error( + "Exception in {}() with cause = {}", + joinPoint.getSignature().getName(), + e.getCause() != null ? e.getCause() : "NULL" + ); } } @@ -78,21 +97,18 @@ public void logAfterThrowing(JoinPoint joinPoint, Throwable e) { */ @Around("applicationPackagePointcut() && springBeanPointcut()") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { + Logger log = logger(joinPoint); if (log.isDebugEnabled()) { - log.debug("Enter: {}.{}() with argument[s] = {}", joinPoint.getSignature().getDeclaringTypeName(), - joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs())); + log.debug("Enter: {}() with argument[s] = {}", joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs())); } try { Object result = joinPoint.proceed(); if (log.isDebugEnabled()) { - log.debug("Exit: {}.{}() with result = {}", joinPoint.getSignature().getDeclaringTypeName(), - joinPoint.getSignature().getName(), result); + log.debug("Exit: {}() with result = {}", joinPoint.getSignature().getName(), result); } return result; } catch (IllegalArgumentException e) { - log.error("Illegal argument: {} in {}.{}()", Arrays.toString(joinPoint.getArgs()), - joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName()); - + log.error("Illegal argument: {} in {}()", Arrays.toString(joinPoint.getArgs()), joinPoint.getSignature().getName()); throw e; } } diff --git a/src/main/java/org/securityrat/casemanagement/client/AuthorizedFeignClient.java b/src/main/java/org/securityrat/casemanagement/client/AuthorizedFeignClient.java index d589ae4..31d75f1 100644 --- a/src/main/java/org/securityrat/casemanagement/client/AuthorizedFeignClient.java +++ b/src/main/java/org/securityrat/casemanagement/client/AuthorizedFeignClient.java @@ -1,17 +1,15 @@ package org.securityrat.casemanagement.client; +import java.lang.annotation.*; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClientsConfiguration; import org.springframework.core.annotation.AliasFor; -import java.lang.annotation.*; - @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @FeignClient public @interface AuthorizedFeignClient { - @AliasFor(annotation = FeignClient.class, attribute = "name") String name() default ""; @@ -48,7 +46,7 @@ Class fallback() default void.class; /** - * Path prefix to be used by all method-level mappings. Can be used with or without {@code @RibbonClient}. + * Path prefix to be used by all method-level mappings. * @return the path prefix to be used by all method-level mappings. */ String path() default ""; diff --git a/src/main/java/org/securityrat/casemanagement/client/OAuth2InterceptedFeignConfiguration.java b/src/main/java/org/securityrat/casemanagement/client/OAuth2InterceptedFeignConfiguration.java index 659d076..f304ec9 100644 --- a/src/main/java/org/securityrat/casemanagement/client/OAuth2InterceptedFeignConfiguration.java +++ b/src/main/java/org/securityrat/casemanagement/client/OAuth2InterceptedFeignConfiguration.java @@ -1,10 +1,8 @@ package org.securityrat.casemanagement.client; -import org.springframework.context.annotation.Bean; - import feign.RequestInterceptor; - import org.securityrat.casemanagement.security.oauth2.AuthorizationHeaderUtil; +import org.springframework.context.annotation.Bean; public class OAuth2InterceptedFeignConfiguration { diff --git a/src/main/java/org/securityrat/casemanagement/client/TokenRelayRequestInterceptor.java b/src/main/java/org/securityrat/casemanagement/client/TokenRelayRequestInterceptor.java index 5104c0a..acff2ec 100644 --- a/src/main/java/org/securityrat/casemanagement/client/TokenRelayRequestInterceptor.java +++ b/src/main/java/org/securityrat/casemanagement/client/TokenRelayRequestInterceptor.java @@ -1,11 +1,9 @@ package org.securityrat.casemanagement.client; -import org.securityrat.casemanagement.security.oauth2.AuthorizationHeaderUtil; - import feign.RequestInterceptor; import feign.RequestTemplate; - import java.util.Optional; +import org.securityrat.casemanagement.security.oauth2.AuthorizationHeaderUtil; public class TokenRelayRequestInterceptor implements RequestInterceptor { diff --git a/src/main/java/org/securityrat/casemanagement/config/ApplicationProperties.java b/src/main/java/org/securityrat/casemanagement/config/ApplicationProperties.java index edac8d7..51dbea4 100644 --- a/src/main/java/org/securityrat/casemanagement/config/ApplicationProperties.java +++ b/src/main/java/org/securityrat/casemanagement/config/ApplicationProperties.java @@ -6,8 +6,7 @@ * Properties specific to Case Management. *

* Properties are configured in the {@code application.yml} file. - * See {@link io.github.jhipster.config.JHipsterProperties} for a good example. + * See {@link tech.jhipster.config.JHipsterProperties} for a good example. */ @ConfigurationProperties(prefix = "application", ignoreUnknownFields = false) -public class ApplicationProperties { -} +public class ApplicationProperties {} diff --git a/src/main/java/org/securityrat/casemanagement/config/AsyncConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/AsyncConfiguration.java index 30010be..119228b 100644 --- a/src/main/java/org/securityrat/casemanagement/config/AsyncConfiguration.java +++ b/src/main/java/org/securityrat/casemanagement/config/AsyncConfiguration.java @@ -1,6 +1,6 @@ package org.securityrat.casemanagement.config; -import io.github.jhipster.async.ExceptionHandlingAsyncTaskExecutor; +import java.util.concurrent.Executor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; @@ -12,8 +12,7 @@ import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; - -import java.util.concurrent.Executor; +import tech.jhipster.async.ExceptionHandlingAsyncTaskExecutor; @Configuration @EnableAsync diff --git a/src/main/java/org/securityrat/casemanagement/config/CloudDatabaseConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/CloudDatabaseConfiguration.java deleted file mode 100644 index 61bb763..0000000 --- a/src/main/java/org/securityrat/casemanagement/config/CloudDatabaseConfiguration.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.securityrat.casemanagement.config; - -import io.github.jhipster.config.JHipsterConstants; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.cloud.config.java.AbstractCloudConfig; -import org.springframework.context.annotation.*; - -import javax.sql.DataSource; -import org.springframework.boot.context.properties.ConfigurationProperties; - - -@Configuration -@Profile(JHipsterConstants.SPRING_PROFILE_CLOUD) -public class CloudDatabaseConfiguration extends AbstractCloudConfig { - - private final Logger log = LoggerFactory.getLogger(CloudDatabaseConfiguration.class); - - private static final String CLOUD_CONFIGURATION_HIKARI_PREFIX = "spring.datasource.hikari"; - - @Bean - @ConfigurationProperties(CLOUD_CONFIGURATION_HIKARI_PREFIX) - public DataSource dataSource() { - log.info("Configuring JDBC datasource from a cloud provider"); - return connectionFactory().dataSource(); - } -} diff --git a/src/main/java/org/securityrat/casemanagement/config/Constants.java b/src/main/java/org/securityrat/casemanagement/config/Constants.java index 8559954..2213540 100644 --- a/src/main/java/org/securityrat/casemanagement/config/Constants.java +++ b/src/main/java/org/securityrat/casemanagement/config/Constants.java @@ -6,12 +6,10 @@ public final class Constants { // Regex for acceptable logins - public static final String LOGIN_REGEX = "^[_.@A-Za-z0-9-]*$"; - - public static final String SYSTEM_ACCOUNT = "system"; + public static final String LOGIN_REGEX = "^(?>[a-zA-Z0-9!$&*+=?^_`{|}~.-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)|(?>[_.@A-Za-z0-9-]+)$"; + + public static final String SYSTEM = "system"; public static final String DEFAULT_LANGUAGE = "en"; - public static final String ANONYMOUS_USER = "anonymoususer"; - private Constants() { - } + private Constants() {} } diff --git a/src/main/java/org/securityrat/casemanagement/config/DatabaseConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/DatabaseConfiguration.java index 78fa2d3..1f6c240 100644 --- a/src/main/java/org/securityrat/casemanagement/config/DatabaseConfiguration.java +++ b/src/main/java/org/securityrat/casemanagement/config/DatabaseConfiguration.java @@ -1,19 +1,17 @@ package org.securityrat.casemanagement.config; -import io.github.jhipster.config.JHipsterConstants; -import io.github.jhipster.config.h2.H2ConfigurationHelper; +import java.sql.SQLException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; - import org.springframework.core.env.Environment; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.transaction.annotation.EnableTransactionManagement; - -import java.sql.SQLException; +import tech.jhipster.config.JHipsterConstants; +import tech.jhipster.config.h2.H2ConfigurationHelper; @Configuration @EnableJpaRepositories("org.securityrat.casemanagement.repository") diff --git a/src/main/java/org/securityrat/casemanagement/config/FeignConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/FeignConfiguration.java index 5dd7837..94cdd4d 100644 --- a/src/main/java/org/securityrat/casemanagement/config/FeignConfiguration.java +++ b/src/main/java/org/securityrat/casemanagement/config/FeignConfiguration.java @@ -18,5 +18,4 @@ public class FeignConfiguration { feign.Logger.Level feignLoggerLevel() { return feign.Logger.Level.BASIC; } - } diff --git a/src/main/java/org/securityrat/casemanagement/config/JacksonConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/JacksonConfiguration.java index a83db54..b11a73e 100644 --- a/src/main/java/org/securityrat/casemanagement/config/JacksonConfiguration.java +++ b/src/main/java/org/securityrat/casemanagement/config/JacksonConfiguration.java @@ -3,8 +3,6 @@ import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.fasterxml.jackson.module.afterburner.AfterburnerModule; - import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.zalando.problem.ProblemModule; @@ -35,19 +33,11 @@ public Hibernate5Module hibernate5Module() { return new Hibernate5Module(); } - /* - * Jackson Afterburner module to speed up serialization/deserialization. - */ - @Bean - public AfterburnerModule afterburnerModule() { - return new AfterburnerModule(); - } - /* * Module for serialization/deserialization of RFC7807 Problem. */ @Bean - ProblemModule problemModule() { + public ProblemModule problemModule() { return new ProblemModule(); } @@ -55,7 +45,7 @@ ProblemModule problemModule() { * Module for serialization/deserialization of ConstraintViolationProblem. */ @Bean - ConstraintViolationProblemModule constraintViolationProblemModule() { + public ConstraintViolationProblemModule constraintViolationProblemModule() { return new ConstraintViolationProblemModule(); } } diff --git a/src/main/java/org/securityrat/casemanagement/config/LiquibaseConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/LiquibaseConfiguration.java index 16d770f..ce7ce72 100644 --- a/src/main/java/org/securityrat/casemanagement/config/LiquibaseConfiguration.java +++ b/src/main/java/org/securityrat/casemanagement/config/LiquibaseConfiguration.java @@ -1,7 +1,7 @@ package org.securityrat.casemanagement.config; -import io.github.jhipster.config.JHipsterConstants; -import io.github.jhipster.config.liquibase.SpringLiquibaseUtil; +import java.util.concurrent.Executor; +import javax.sql.DataSource; import liquibase.integration.spring.SpringLiquibase; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,9 +14,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.core.env.Profiles; - -import javax.sql.DataSource; -import java.util.concurrent.Executor; +import tech.jhipster.config.JHipsterConstants; +import tech.jhipster.config.liquibase.SpringLiquibaseUtil; @Configuration public class LiquibaseConfiguration { @@ -30,13 +29,23 @@ public LiquibaseConfiguration(Environment env) { } @Bean - public SpringLiquibase liquibase(@Qualifier("taskExecutor") Executor executor, - @LiquibaseDataSource ObjectProvider liquibaseDataSource, LiquibaseProperties liquibaseProperties, - ObjectProvider dataSource, DataSourceProperties dataSourceProperties) { - + public SpringLiquibase liquibase( + @Qualifier("taskExecutor") Executor executor, + @LiquibaseDataSource ObjectProvider liquibaseDataSource, + LiquibaseProperties liquibaseProperties, + ObjectProvider dataSource, + DataSourceProperties dataSourceProperties + ) { // If you don't want Liquibase to start asynchronously, substitute by this: // SpringLiquibase liquibase = SpringLiquibaseUtil.createSpringLiquibase(liquibaseDataSource.getIfAvailable(), liquibaseProperties, dataSource.getIfUnique(), dataSourceProperties); - SpringLiquibase liquibase = SpringLiquibaseUtil.createAsyncSpringLiquibase(this.env, executor, liquibaseDataSource.getIfAvailable(), liquibaseProperties, dataSource.getIfUnique(), dataSourceProperties); + SpringLiquibase liquibase = SpringLiquibaseUtil.createAsyncSpringLiquibase( + this.env, + executor, + liquibaseDataSource.getIfAvailable(), + liquibaseProperties, + dataSource.getIfUnique(), + dataSourceProperties + ); liquibase.setChangeLog("classpath:config/liquibase/master.xml"); liquibase.setContexts(liquibaseProperties.getContexts()); liquibase.setDefaultSchema(liquibaseProperties.getDefaultSchema()); diff --git a/src/main/java/org/securityrat/casemanagement/config/LocaleConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/LocaleConfiguration.java index a29995e..bbac062 100644 --- a/src/main/java/org/securityrat/casemanagement/config/LocaleConfiguration.java +++ b/src/main/java/org/securityrat/casemanagement/config/LocaleConfiguration.java @@ -1,17 +1,16 @@ package org.securityrat.casemanagement.config; -import io.github.jhipster.config.locale.AngularCookieLocaleResolver; - import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.*; import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; +import tech.jhipster.config.locale.AngularCookieLocaleResolver; @Configuration public class LocaleConfiguration implements WebMvcConfigurer { - @Bean(name = "localeResolver") + @Bean public LocaleResolver localeResolver() { AngularCookieLocaleResolver cookieLocaleResolver = new AngularCookieLocaleResolver(); cookieLocaleResolver.setCookieName("NG_TRANSLATE_LANG_KEY"); diff --git a/src/main/java/org/securityrat/casemanagement/config/LoggingAspectConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/LoggingAspectConfiguration.java index 868191f..1d3b842 100644 --- a/src/main/java/org/securityrat/casemanagement/config/LoggingAspectConfiguration.java +++ b/src/main/java/org/securityrat/casemanagement/config/LoggingAspectConfiguration.java @@ -1,11 +1,9 @@ package org.securityrat.casemanagement.config; import org.securityrat.casemanagement.aop.logging.LoggingAspect; - -import io.github.jhipster.config.JHipsterConstants; - import org.springframework.context.annotation.*; import org.springframework.core.env.Environment; +import tech.jhipster.config.JHipsterConstants; @Configuration @EnableAspectJAutoProxy diff --git a/src/main/java/org/securityrat/casemanagement/config/LoggingConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/LoggingConfiguration.java index d315346..562ce6e 100644 --- a/src/main/java/org/securityrat/casemanagement/config/LoggingConfiguration.java +++ b/src/main/java/org/securityrat/casemanagement/config/LoggingConfiguration.java @@ -1,9 +1,12 @@ package org.securityrat.casemanagement.config; +import static tech.jhipster.config.logging.LoggingUtils.*; + import ch.qos.logback.classic.LoggerContext; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import io.github.jhipster.config.JHipsterProperties; +import java.util.HashMap; +import java.util.Map; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Value; @@ -11,11 +14,7 @@ import org.springframework.cloud.consul.serviceregistry.ConsulRegistration; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.context.annotation.Configuration; - -import java.util.HashMap; -import java.util.Map; - -import static io.github.jhipster.config.logging.LoggingUtils.*; +import tech.jhipster.config.JHipsterProperties; /* * Configures the console and Logstash log appenders from the app properties @@ -24,13 +23,14 @@ @RefreshScope public class LoggingConfiguration { - public LoggingConfiguration(@Value("${spring.application.name}") String appName, - @Value("${server.port}") String serverPort, - JHipsterProperties jHipsterProperties, - ObjectProvider consulRegistration, - ObjectProvider buildProperties, - ObjectMapper mapper) throws JsonProcessingException { - + public LoggingConfiguration( + @Value("${spring.application.name}") String appName, + @Value("${server.port}") String serverPort, + JHipsterProperties jHipsterProperties, + ObjectProvider consulRegistration, + ObjectProvider buildProperties, + ObjectMapper mapper + ) throws JsonProcessingException { LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); Map map = new HashMap<>(); @@ -52,8 +52,5 @@ public LoggingConfiguration(@Value("${spring.application.name}") String appName, if (loggingProperties.isUseJsonFormat() || logstashProperties.isEnabled()) { addContextListener(context, customFields, loggingProperties); } - if (jHipsterProperties.getMetrics().getLogs().isEnabled()) { - setMetricsMarkerLogbackFilter(context, loggingProperties.isUseJsonFormat()); - } } } diff --git a/src/main/java/org/securityrat/casemanagement/config/MethodSecurityConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/MethodSecurityConfiguration.java deleted file mode 100644 index 088d156..0000000 --- a/src/main/java/org/securityrat/casemanagement/config/MethodSecurityConfiguration.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.securityrat.casemanagement.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; -import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler; - -@Configuration -@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) -public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration { - @Override - protected MethodSecurityExpressionHandler createExpressionHandler() { - return new OAuth2MethodSecurityExpressionHandler(); - } -} diff --git a/src/main/java/org/securityrat/casemanagement/config/SecurityConfiguration.java b/src/main/java/org/securityrat/casemanagement/config/SecurityConfiguration.java index 64170e8..e1525b9 100644 --- a/src/main/java/org/securityrat/casemanagement/config/SecurityConfiguration.java +++ b/src/main/java/org/securityrat/casemanagement/config/SecurityConfiguration.java @@ -1,28 +1,32 @@ package org.securityrat.casemanagement.config; +import java.util.*; import org.securityrat.casemanagement.security.*; - -import io.github.jhipster.config.JHipsterProperties; +import org.securityrat.casemanagement.security.SecurityUtils; +import org.securityrat.casemanagement.security.oauth2.AudienceValidator; +import org.securityrat.casemanagement.security.oauth2.JwtGrantedAuthorityConverter; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.authentication.AbstractAuthenticationToken; +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.securityrat.casemanagement.security.oauth2.AudienceValidator; -import org.securityrat.casemanagement.security.SecurityUtils; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.jwt.*; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.core.GrantedAuthority; -import java.util.*; -import org.securityrat.casemanagement.security.oauth2.JwtAuthorityExtractor; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter; import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport; +import tech.jhipster.config.JHipsterProperties; @EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) @Import(SecurityProblemSupport.class) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @@ -30,18 +34,16 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { private String issuerUri; private final JHipsterProperties jHipsterProperties; - private final JwtAuthorityExtractor jwtAuthorityExtractor; private final SecurityProblemSupport problemSupport; - public SecurityConfiguration(JwtAuthorityExtractor jwtAuthorityExtractor, JHipsterProperties jHipsterProperties, SecurityProblemSupport problemSupport) { + public SecurityConfiguration(JHipsterProperties jHipsterProperties, SecurityProblemSupport problemSupport) { this.problemSupport = problemSupport; - this.jwtAuthorityExtractor = jwtAuthorityExtractor; this.jHipsterProperties = jHipsterProperties; } + @Override public void configure(WebSecurity web) { - web.ignoring() - .antMatchers("/h2-console/**"); + web.ignoring().antMatchers("/h2-console/**"); } @Override @@ -59,7 +61,7 @@ public void configure(HttpSecurity http) throws Exception { .and() .referrerPolicy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN) .and() - .featurePolicy("geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; speaker 'none'; fullscreen 'self'; payment 'none'") + .featurePolicy("geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; fullscreen 'self'; payment 'none'") .and() .frameOptions() .deny() @@ -69,26 +71,32 @@ public void configure(HttpSecurity http) throws Exception { .and() .authorizeRequests() .antMatchers("/api/auth-info").permitAll() + .antMatchers("/api/admin/**").hasAuthority(AuthoritiesConstants.ADMIN) .antMatchers("/api/**").authenticated() .antMatchers("/management/health").permitAll() + .antMatchers("/management/health/**").permitAll() .antMatchers("/management/info").permitAll() .antMatchers("/management/prometheus").permitAll() .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN) .and() .oauth2ResourceServer() .jwt() - .jwtAuthenticationConverter(jwtAuthorityExtractor) + .jwtAuthenticationConverter(authenticationConverter()) .and() .and() .oauth2Client(); // @formatter:on } + Converter authenticationConverter() { + JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); + jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new JwtGrantedAuthorityConverter()); + return jwtAuthenticationConverter; + } @Bean JwtDecoder jwtDecoder() { - NimbusJwtDecoderJwkSupport jwtDecoder = (NimbusJwtDecoderJwkSupport) - JwtDecoders.fromOidcIssuerLocation(issuerUri); + NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder) JwtDecoders.fromOidcIssuerLocation(issuerUri); OAuth2TokenValidator audienceValidator = new AudienceValidator(jHipsterProperties.getSecurity().getOauth2().getAudience()); OAuth2TokenValidator withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri); diff --git a/src/main/java/org/securityrat/casemanagement/config/WebConfigurer.java b/src/main/java/org/securityrat/casemanagement/config/WebConfigurer.java index 1abc415..3a652ec 100644 --- a/src/main/java/org/securityrat/casemanagement/config/WebConfigurer.java +++ b/src/main/java/org/securityrat/casemanagement/config/WebConfigurer.java @@ -1,8 +1,6 @@ package org.securityrat.casemanagement.config; -import io.github.jhipster.config.JHipsterConstants; -import io.github.jhipster.config.JHipsterProperties; -import io.github.jhipster.config.h2.H2ConfigurationHelper; +import javax.servlet.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.web.server.*; @@ -12,11 +10,13 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.core.env.Profiles; +import org.springframework.util.CollectionUtils; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; - -import javax.servlet.*; +import tech.jhipster.config.JHipsterConstants; +import tech.jhipster.config.JHipsterProperties; +import tech.jhipster.config.h2.H2ConfigurationHelper; /** * Configuration of web application with Servlet 3.0 APIs. @@ -40,6 +40,7 @@ public void onStartup(ServletContext servletContext) throws ServletException { if (env.getActiveProfiles().length != 0) { log.info("Web application configuration, using profiles: {}", (Object[]) env.getActiveProfiles()); } + if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT))) { initH2Console(servletContext); } @@ -50,11 +51,14 @@ public void onStartup(ServletContext servletContext) throws ServletException { public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = jHipsterProperties.getCors(); - if (config.getAllowedOrigins() != null && !config.getAllowedOrigins().isEmpty()) { + if (!CollectionUtils.isEmpty(config.getAllowedOrigins())) { log.debug("Registering CORS filter"); source.registerCorsConfiguration("/api/**", config); source.registerCorsConfiguration("/management/**", config); source.registerCorsConfiguration("/v2/api-docs", config); + source.registerCorsConfiguration("/v3/api-docs", config); + source.registerCorsConfiguration("/swagger-resources", config); + source.registerCorsConfiguration("/swagger-ui/**", config); } return new CorsFilter(source); } @@ -66,5 +70,4 @@ private void initH2Console(ServletContext servletContext) { log.debug("Initialize H2 console"); H2ConfigurationHelper.initH2Console(servletContext); } - } diff --git a/src/main/java/org/securityrat/casemanagement/config/audit/AuditEventConverter.java b/src/main/java/org/securityrat/casemanagement/config/audit/AuditEventConverter.java deleted file mode 100644 index a049c52..0000000 --- a/src/main/java/org/securityrat/casemanagement/config/audit/AuditEventConverter.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.securityrat.casemanagement.config.audit; - -import org.securityrat.casemanagement.domain.PersistentAuditEvent; - -import org.springframework.boot.actuate.audit.AuditEvent; -import org.springframework.security.web.authentication.WebAuthenticationDetails; -import org.springframework.stereotype.Component; - -import java.util.*; - -@Component -public class AuditEventConverter { - - /** - * Convert a list of {@link PersistentAuditEvent}s to a list of {@link AuditEvent}s. - * - * @param persistentAuditEvents the list to convert. - * @return the converted list. - */ - public List convertToAuditEvent(Iterable persistentAuditEvents) { - if (persistentAuditEvents == null) { - return Collections.emptyList(); - } - List auditEvents = new ArrayList<>(); - for (PersistentAuditEvent persistentAuditEvent : persistentAuditEvents) { - auditEvents.add(convertToAuditEvent(persistentAuditEvent)); - } - return auditEvents; - } - - /** - * Convert a {@link PersistentAuditEvent} to an {@link AuditEvent}. - * - * @param persistentAuditEvent the event to convert. - * @return the converted list. - */ - public AuditEvent convertToAuditEvent(PersistentAuditEvent persistentAuditEvent) { - if (persistentAuditEvent == null) { - return null; - } - return new AuditEvent(persistentAuditEvent.getAuditEventDate(), persistentAuditEvent.getPrincipal(), - persistentAuditEvent.getAuditEventType(), convertDataToObjects(persistentAuditEvent.getData())); - } - - /** - * Internal conversion. This is needed to support the current SpringBoot actuator {@code AuditEventRepository} interface. - * - * @param data the data to convert. - * @return a map of {@link String}, {@link Object}. - */ - public Map convertDataToObjects(Map data) { - Map results = new HashMap<>(); - - if (data != null) { - for (Map.Entry entry : data.entrySet()) { - results.put(entry.getKey(), entry.getValue()); - } - } - return results; - } - - /** - * Internal conversion. This method will allow to save additional data. - * By default, it will save the object as string. - * - * @param data the data to convert. - * @return a map of {@link String}, {@link String}. - */ - public Map convertDataToStrings(Map data) { - Map results = new HashMap<>(); - - if (data != null) { - for (Map.Entry entry : data.entrySet()) { - // Extract the data that will be saved. - if (entry.getValue() instanceof WebAuthenticationDetails) { - WebAuthenticationDetails authenticationDetails = (WebAuthenticationDetails) entry.getValue(); - results.put("remoteAddress", authenticationDetails.getRemoteAddress()); - results.put("sessionId", authenticationDetails.getSessionId()); - } else { - results.put(entry.getKey(), Objects.toString(entry.getValue())); - } - } - } - return results; - } -} diff --git a/src/main/java/org/securityrat/casemanagement/config/audit/package-info.java b/src/main/java/org/securityrat/casemanagement/config/audit/package-info.java deleted file mode 100644 index 033145f..0000000 --- a/src/main/java/org/securityrat/casemanagement/config/audit/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Audit specific code. - */ -package org.securityrat.casemanagement.config.audit; diff --git a/src/main/java/org/securityrat/casemanagement/domain/AbstractAuditingEntity.java b/src/main/java/org/securityrat/casemanagement/domain/AbstractAuditingEntity.java index 6a5e210..14f1725 100644 --- a/src/main/java/org/securityrat/casemanagement/domain/AbstractAuditingEntity.java +++ b/src/main/java/org/securityrat/casemanagement/domain/AbstractAuditingEntity.java @@ -1,21 +1,20 @@ package org.securityrat.casemanagement.domain; import com.fasterxml.jackson.annotation.JsonIgnore; -import org.springframework.data.annotation.CreatedBy; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.LastModifiedBy; -import org.springframework.data.annotation.LastModifiedDate; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; - import java.io.Serializable; import java.time.Instant; import javax.persistence.Column; import javax.persistence.EntityListeners; import javax.persistence.MappedSuperclass; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; /** - * Base abstract class for entities which will hold definitions for created, last modified by and created, - * last modified by date. + * Base abstract class for entities which will hold definitions for created, last modified, created by, + * last modified by attributes. */ @MappedSuperclass @EntityListeners(AuditingEntityListener.class) diff --git a/src/main/java/org/securityrat/casemanagement/domain/AccessToken.java b/src/main/java/org/securityrat/casemanagement/domain/AccessToken.java index dd5bbfc..c5fe969 100644 --- a/src/main/java/org/securityrat/casemanagement/domain/AccessToken.java +++ b/src/main/java/org/securityrat/casemanagement/domain/AccessToken.java @@ -1,11 +1,10 @@ package org.securityrat.casemanagement.domain; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; - -import javax.persistence.*; -import javax.validation.constraints.*; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.io.Serializable; import java.time.ZonedDateTime; +import javax.persistence.*; +import javax.validation.constraints.*; /** * A AccessToken. @@ -17,7 +16,8 @@ public class AccessToken implements Serializable { private static final long serialVersionUID = 1L; @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator") + @SequenceGenerator(name = "sequenceGenerator") private Long id; @NotNull @@ -35,14 +35,13 @@ public class AccessToken implements Serializable { private String refreshToken; @ManyToOne - @JsonIgnoreProperties("accessTokens") private User user; @ManyToOne - @JsonIgnoreProperties("accessTokens") + @JsonIgnoreProperties(value = { "accessTokens" }, allowSetters = true) private TicketSystemInstance ticketInstance; - // jhipster-needle-entity-add-field - JHipster will add fields here, do not remove + // jhipster-needle-entity-add-field - JHipster will add fields here public Long getId() { return id; } @@ -51,8 +50,13 @@ public void setId(Long id) { this.id = id; } + public AccessToken id(Long id) { + this.id = id; + return this; + } + public String getToken() { - return token; + return this.token; } public AccessToken token(String token) { @@ -65,7 +69,7 @@ public void setToken(String token) { } public ZonedDateTime getExpirationDate() { - return expirationDate; + return this.expirationDate; } public AccessToken expirationDate(ZonedDateTime expirationDate) { @@ -78,7 +82,7 @@ public void setExpirationDate(ZonedDateTime expirationDate) { } public String getSalt() { - return salt; + return this.salt; } public AccessToken salt(String salt) { @@ -91,7 +95,7 @@ public void setSalt(String salt) { } public String getRefreshToken() { - return refreshToken; + return this.refreshToken; } public AccessToken refreshToken(String refreshToken) { @@ -104,11 +108,11 @@ public void setRefreshToken(String refreshToken) { } public User getUser() { - return user; + return this.user; } public AccessToken user(User user) { - this.user = user; + this.setUser(user); return this; } @@ -117,18 +121,19 @@ public void setUser(User user) { } public TicketSystemInstance getTicketInstance() { - return ticketInstance; + return this.ticketInstance; } public AccessToken ticketInstance(TicketSystemInstance ticketSystemInstance) { - this.ticketInstance = ticketSystemInstance; + this.setTicketInstance(ticketSystemInstance); return this; } public void setTicketInstance(TicketSystemInstance ticketSystemInstance) { this.ticketInstance = ticketSystemInstance; } - // jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here, do not remove + + // jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here @Override public boolean equals(Object o) { @@ -143,9 +148,11 @@ public boolean equals(Object o) { @Override public int hashCode() { - return 31; + // see https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/ + return getClass().hashCode(); } + // prettier-ignore @Override public String toString() { return "AccessToken{" + diff --git a/src/main/java/org/securityrat/casemanagement/domain/Authority.java b/src/main/java/org/securityrat/casemanagement/domain/Authority.java index 4d7ada1..0d4cce0 100644 --- a/src/main/java/org/securityrat/casemanagement/domain/Authority.java +++ b/src/main/java/org/securityrat/casemanagement/domain/Authority.java @@ -1,13 +1,13 @@ package org.securityrat.casemanagement.domain; +import java.io.Serializable; +import java.util.Objects; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; -import javax.persistence.Column; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; -import java.io.Serializable; -import java.util.Objects; /** * An authority (a security role) used by Spring Security. @@ -48,6 +48,7 @@ public int hashCode() { return Objects.hashCode(name); } + // prettier-ignore @Override public String toString() { return "Authority{" + diff --git a/src/main/java/org/securityrat/casemanagement/domain/PersistentAuditEvent.java b/src/main/java/org/securityrat/casemanagement/domain/PersistentAuditEvent.java deleted file mode 100644 index bf997bf..0000000 --- a/src/main/java/org/securityrat/casemanagement/domain/PersistentAuditEvent.java +++ /dev/null @@ -1,106 +0,0 @@ -package org.securityrat.casemanagement.domain; - -import javax.persistence.*; -import javax.validation.constraints.NotNull; -import java.io.Serializable; -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; - -/** - * Persist AuditEvent managed by the Spring Boot actuator. - * - * @see org.springframework.boot.actuate.audit.AuditEvent - */ -@Entity -@Table(name = "jhi_persistent_audit_event") -public class PersistentAuditEvent implements Serializable { - - private static final long serialVersionUID = 1L; - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "event_id") - private Long id; - - @NotNull - @Column(nullable = false) - private String principal; - - @Column(name = "event_date") - private Instant auditEventDate; - - @Column(name = "event_type") - private String auditEventType; - - @ElementCollection - @MapKeyColumn(name = "name") - @Column(name = "value") - @CollectionTable(name = "jhi_persistent_audit_evt_data", joinColumns=@JoinColumn(name="event_id")) - private Map data = new HashMap<>(); - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getPrincipal() { - return principal; - } - - public void setPrincipal(String principal) { - this.principal = principal; - } - - public Instant getAuditEventDate() { - return auditEventDate; - } - - public void setAuditEventDate(Instant auditEventDate) { - this.auditEventDate = auditEventDate; - } - - public String getAuditEventType() { - return auditEventType; - } - - public void setAuditEventType(String auditEventType) { - this.auditEventType = auditEventType; - } - - public Map getData() { - return data; - } - - public void setData(Map data) { - this.data = data; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof PersistentAuditEvent)) { - return false; - } - return id != null && id.equals(((PersistentAuditEvent) o).id); - } - - @Override - public int hashCode() { - return 31; - } - - @Override - public String toString() { - return "PersistentAuditEvent{" + - "principal='" + principal + '\'' + - ", auditEventDate=" + auditEventDate + - ", auditEventType='" + auditEventType + '\'' + - '}'; - } -} diff --git a/src/main/java/org/securityrat/casemanagement/domain/TicketSystemInstance.java b/src/main/java/org/securityrat/casemanagement/domain/TicketSystemInstance.java index b66a101..0028fd6 100644 --- a/src/main/java/org/securityrat/casemanagement/domain/TicketSystemInstance.java +++ b/src/main/java/org/securityrat/casemanagement/domain/TicketSystemInstance.java @@ -1,12 +1,11 @@ package org.securityrat.casemanagement.domain; -import javax.persistence.*; -import javax.validation.constraints.*; - +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.io.Serializable; import java.util.HashSet; import java.util.Set; - +import javax.persistence.*; +import javax.validation.constraints.*; import org.securityrat.casemanagement.domain.enumeration.TicketSystem; /** @@ -19,7 +18,8 @@ public class TicketSystemInstance implements Serializable { private static final long serialVersionUID = 1L; @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator") + @SequenceGenerator(name = "sequenceGenerator") private Long id; @Column(name = "name") @@ -44,9 +44,10 @@ public class TicketSystemInstance implements Serializable { private String clientSecret; @OneToMany(mappedBy = "ticketInstance") + @JsonIgnoreProperties(value = { "user", "ticketInstance" }, allowSetters = true) private Set accessTokens = new HashSet<>(); - // jhipster-needle-entity-add-field - JHipster will add fields here, do not remove + // jhipster-needle-entity-add-field - JHipster will add fields here public Long getId() { return id; } @@ -55,8 +56,13 @@ public void setId(Long id) { this.id = id; } + public TicketSystemInstance id(Long id) { + this.id = id; + return this; + } + public String getName() { - return name; + return this.name; } public TicketSystemInstance name(String name) { @@ -69,7 +75,7 @@ public void setName(String name) { } public TicketSystem getType() { - return type; + return this.type; } public TicketSystemInstance type(TicketSystem type) { @@ -82,7 +88,7 @@ public void setType(TicketSystem type) { } public String getUrl() { - return url; + return this.url; } public TicketSystemInstance url(String url) { @@ -95,7 +101,7 @@ public void setUrl(String url) { } public String getConsumerKey() { - return consumerKey; + return this.consumerKey; } public TicketSystemInstance consumerKey(String consumerKey) { @@ -108,7 +114,7 @@ public void setConsumerKey(String consumerKey) { } public String getClientId() { - return clientId; + return this.clientId; } public TicketSystemInstance clientId(String clientId) { @@ -121,7 +127,7 @@ public void setClientId(String clientId) { } public String getClientSecret() { - return clientSecret; + return this.clientSecret; } public TicketSystemInstance clientSecret(String clientSecret) { @@ -134,11 +140,11 @@ public void setClientSecret(String clientSecret) { } public Set getAccessTokens() { - return accessTokens; + return this.accessTokens; } public TicketSystemInstance accessTokens(Set accessTokens) { - this.accessTokens = accessTokens; + this.setAccessTokens(accessTokens); return this; } @@ -155,9 +161,16 @@ public TicketSystemInstance removeAccessToken(AccessToken accessToken) { } public void setAccessTokens(Set accessTokens) { + if (this.accessTokens != null) { + this.accessTokens.forEach(i -> i.setTicketInstance(null)); + } + if (accessTokens != null) { + accessTokens.forEach(i -> i.setTicketInstance(this)); + } this.accessTokens = accessTokens; } - // jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here, do not remove + + // jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here @Override public boolean equals(Object o) { @@ -172,9 +185,11 @@ public boolean equals(Object o) { @Override public int hashCode() { - return 31; + // see https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/ + return getClass().hashCode(); } + // prettier-ignore @Override public String toString() { return "TicketSystemInstance{" + diff --git a/src/main/java/org/securityrat/casemanagement/domain/User.java b/src/main/java/org/securityrat/casemanagement/domain/User.java index 1ec91c3..b781fe4 100644 --- a/src/main/java/org/securityrat/casemanagement/domain/User.java +++ b/src/main/java/org/securityrat/casemanagement/domain/User.java @@ -1,20 +1,18 @@ package org.securityrat.casemanagement.domain; -import org.securityrat.casemanagement.config.Constants; - import com.fasterxml.jackson.annotation.JsonIgnore; -import org.apache.commons.lang3.StringUtils; -import org.hibernate.annotations.BatchSize; - +import java.io.Serializable; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; import javax.persistence.*; import javax.validation.constraints.Email; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; -import java.io.Serializable; -import java.util.HashSet; -import java.util.Locale; -import java.util.Set; +import org.apache.commons.lang3.StringUtils; +import org.hibernate.annotations.BatchSize; +import org.securityrat.casemanagement.config.Constants; /** * A user. @@ -63,9 +61,9 @@ public class User extends AbstractAuditingEntity implements Serializable { @ManyToMany @JoinTable( name = "jhi_user_authority", - joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")}, - inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "name")}) - + joinColumns = { @JoinColumn(name = "user_id", referencedColumnName = "id") }, + inverseJoinColumns = { @JoinColumn(name = "authority_name", referencedColumnName = "name") } + ) @BatchSize(size = 20) private Set authorities = new HashSet<>(); @@ -118,7 +116,7 @@ public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; } - public boolean getActivated() { + public boolean isActivated() { return activated; } @@ -155,9 +153,11 @@ public boolean equals(Object o) { @Override public int hashCode() { - return 31; + // see https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/ + return getClass().hashCode(); } + // prettier-ignore @Override public String toString() { return "User{" + diff --git a/src/main/java/org/securityrat/casemanagement/domain/enumeration/TicketSystem.java b/src/main/java/org/securityrat/casemanagement/domain/enumeration/TicketSystem.java index 3bd33ee..cb80891 100644 --- a/src/main/java/org/securityrat/casemanagement/domain/enumeration/TicketSystem.java +++ b/src/main/java/org/securityrat/casemanagement/domain/enumeration/TicketSystem.java @@ -4,5 +4,5 @@ * The TicketSystem enumeration. */ public enum TicketSystem { - JIRA + JIRA, } diff --git a/src/main/java/org/securityrat/casemanagement/repository/AccessTokenRepository.java b/src/main/java/org/securityrat/casemanagement/repository/AccessTokenRepository.java index 0a87416..ba81e06 100644 --- a/src/main/java/org/securityrat/casemanagement/repository/AccessTokenRepository.java +++ b/src/main/java/org/securityrat/casemanagement/repository/AccessTokenRepository.java @@ -1,19 +1,16 @@ package org.securityrat.casemanagement.repository; +import java.util.List; import org.securityrat.casemanagement.domain.AccessToken; import org.springframework.data.jpa.repository.*; import org.springframework.stereotype.Repository; -import java.util.List; - /** - * Spring Data repository for the AccessToken entity. + * Spring Data SQL repository for the AccessToken entity. */ @SuppressWarnings("unused") @Repository public interface AccessTokenRepository extends JpaRepository { - @Query("select accessToken from AccessToken accessToken where accessToken.user.login = ?#{principal.preferredUsername}") List findByUserIsCurrentUser(); - } diff --git a/src/main/java/org/securityrat/casemanagement/repository/AuthorityRepository.java b/src/main/java/org/securityrat/casemanagement/repository/AuthorityRepository.java index aa346eb..93d350c 100644 --- a/src/main/java/org/securityrat/casemanagement/repository/AuthorityRepository.java +++ b/src/main/java/org/securityrat/casemanagement/repository/AuthorityRepository.java @@ -1,11 +1,9 @@ package org.securityrat.casemanagement.repository; import org.securityrat.casemanagement.domain.Authority; - import org.springframework.data.jpa.repository.JpaRepository; /** * Spring Data JPA repository for the {@link Authority} entity. */ -public interface AuthorityRepository extends JpaRepository { -} +public interface AuthorityRepository extends JpaRepository {} diff --git a/src/main/java/org/securityrat/casemanagement/repository/CustomAuditEventRepository.java b/src/main/java/org/securityrat/casemanagement/repository/CustomAuditEventRepository.java deleted file mode 100644 index 3da6fc0..0000000 --- a/src/main/java/org/securityrat/casemanagement/repository/CustomAuditEventRepository.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.securityrat.casemanagement.repository; - -import org.securityrat.casemanagement.config.Constants; -import org.securityrat.casemanagement.config.audit.AuditEventConverter; -import org.securityrat.casemanagement.domain.PersistentAuditEvent; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.actuate.audit.AuditEvent; -import org.springframework.boot.actuate.audit.AuditEventRepository; -import org.springframework.stereotype.Repository; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; - -import java.time.Instant; -import java.util.*; - -/** - * An implementation of Spring Boot's {@link AuditEventRepository}. - */ -@Repository -public class CustomAuditEventRepository implements AuditEventRepository { - - private static final String AUTHORIZATION_FAILURE = "AUTHORIZATION_FAILURE"; - - /** - * Should be the same as in Liquibase migration. - */ - protected static final int EVENT_DATA_COLUMN_MAX_LENGTH = 255; - - private final PersistenceAuditEventRepository persistenceAuditEventRepository; - - private final AuditEventConverter auditEventConverter; - - private final Logger log = LoggerFactory.getLogger(getClass()); - - public CustomAuditEventRepository(PersistenceAuditEventRepository persistenceAuditEventRepository, - AuditEventConverter auditEventConverter) { - - this.persistenceAuditEventRepository = persistenceAuditEventRepository; - this.auditEventConverter = auditEventConverter; - } - - @Override - public List find(String principal, Instant after, String type) { - Iterable persistentAuditEvents = - persistenceAuditEventRepository.findByPrincipalAndAuditEventDateAfterAndAuditEventType(principal, after, type); - return auditEventConverter.convertToAuditEvent(persistentAuditEvents); - } - - @Override - @Transactional(propagation = Propagation.REQUIRES_NEW) - public void add(AuditEvent event) { - if (!AUTHORIZATION_FAILURE.equals(event.getType()) && - !Constants.ANONYMOUS_USER.equals(event.getPrincipal())) { - - PersistentAuditEvent persistentAuditEvent = new PersistentAuditEvent(); - persistentAuditEvent.setPrincipal(event.getPrincipal()); - persistentAuditEvent.setAuditEventType(event.getType()); - persistentAuditEvent.setAuditEventDate(event.getTimestamp()); - Map eventData = auditEventConverter.convertDataToStrings(event.getData()); - persistentAuditEvent.setData(truncate(eventData)); - persistenceAuditEventRepository.save(persistentAuditEvent); - } - } - - /** - * Truncate event data that might exceed column length. - */ - private Map truncate(Map data) { - Map results = new HashMap<>(); - - if (data != null) { - for (Map.Entry entry : data.entrySet()) { - String value = entry.getValue(); - if (value != null) { - int length = value.length(); - if (length > EVENT_DATA_COLUMN_MAX_LENGTH) { - value = value.substring(0, EVENT_DATA_COLUMN_MAX_LENGTH); - log.warn("Event data for {} too long ({}) has been truncated to {}. Consider increasing column width.", - entry.getKey(), length, EVENT_DATA_COLUMN_MAX_LENGTH); - } - } - results.put(entry.getKey(), value); - } - } - return results; - } -} diff --git a/src/main/java/org/securityrat/casemanagement/repository/PersistenceAuditEventRepository.java b/src/main/java/org/securityrat/casemanagement/repository/PersistenceAuditEventRepository.java deleted file mode 100644 index 702baaa..0000000 --- a/src/main/java/org/securityrat/casemanagement/repository/PersistenceAuditEventRepository.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.securityrat.casemanagement.repository; - -import org.securityrat.casemanagement.domain.PersistentAuditEvent; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.time.Instant; -import java.util.List; - -/** - * Spring Data JPA repository for the {@link PersistentAuditEvent} entity. - */ -public interface PersistenceAuditEventRepository extends JpaRepository { - - List findByPrincipal(String principal); - - List findByPrincipalAndAuditEventDateAfterAndAuditEventType(String principal, Instant after, String type); - - Page findAllByAuditEventDateBetween(Instant fromDate, Instant toDate, Pageable pageable); - - List findByAuditEventDateBefore(Instant before); -} diff --git a/src/main/java/org/securityrat/casemanagement/repository/TicketSystemInstanceRepository.java b/src/main/java/org/securityrat/casemanagement/repository/TicketSystemInstanceRepository.java index 0fbeb99..9973246 100644 --- a/src/main/java/org/securityrat/casemanagement/repository/TicketSystemInstanceRepository.java +++ b/src/main/java/org/securityrat/casemanagement/repository/TicketSystemInstanceRepository.java @@ -4,12 +4,9 @@ import org.springframework.data.jpa.repository.*; import org.springframework.stereotype.Repository; - /** - * Spring Data repository for the TicketSystemInstance entity. + * Spring Data SQL repository for the TicketSystemInstance entity. */ @SuppressWarnings("unused") @Repository -public interface TicketSystemInstanceRepository extends JpaRepository { - -} +public interface TicketSystemInstanceRepository extends JpaRepository {} diff --git a/src/main/java/org/securityrat/casemanagement/repository/UserRepository.java b/src/main/java/org/securityrat/casemanagement/repository/UserRepository.java index 0b10459..e5c28c7 100644 --- a/src/main/java/org/securityrat/casemanagement/repository/UserRepository.java +++ b/src/main/java/org/securityrat/casemanagement/repository/UserRepository.java @@ -1,36 +1,23 @@ package org.securityrat.casemanagement.repository; +import java.util.List; +import java.util.Optional; import org.securityrat.casemanagement.domain.User; - import org.springframework.data.domain.Page; - import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import java.util.List; -import java.util.Optional; -import java.time.Instant; - /** * Spring Data JPA repository for the {@link User} entity. */ @Repository public interface UserRepository extends JpaRepository { - - Optional findOneByEmailIgnoreCase(String email); - Optional findOneByLogin(String login); - @EntityGraph(attributePaths = "authorities") - Optional findOneWithAuthoritiesById(Long id); - @EntityGraph(attributePaths = "authorities") Optional findOneWithAuthoritiesByLogin(String login); - @EntityGraph(attributePaths = "authorities") - Optional findOneWithAuthoritiesByEmailIgnoreCase(String email); - - Page findAllByLoginNot(Pageable pageable, String login); + Page findAllByIdNotNullAndActivatedIsTrue(Pageable pageable); } diff --git a/src/main/java/org/securityrat/casemanagement/security/AuthoritiesConstants.java b/src/main/java/org/securityrat/casemanagement/security/AuthoritiesConstants.java index 924381d..a6a3a3c 100644 --- a/src/main/java/org/securityrat/casemanagement/security/AuthoritiesConstants.java +++ b/src/main/java/org/securityrat/casemanagement/security/AuthoritiesConstants.java @@ -11,6 +11,5 @@ public final class AuthoritiesConstants { public static final String ANONYMOUS = "ROLE_ANONYMOUS"; - private AuthoritiesConstants() { - } + private AuthoritiesConstants() {} } diff --git a/src/main/java/org/securityrat/casemanagement/security/SecurityUtils.java b/src/main/java/org/securityrat/casemanagement/security/SecurityUtils.java index 42b807c..e1f2f84 100644 --- a/src/main/java/org/securityrat/casemanagement/security/SecurityUtils.java +++ b/src/main/java/org/securityrat/casemanagement/security/SecurityUtils.java @@ -1,5 +1,8 @@ package org.securityrat.casemanagement.security; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -9,17 +12,12 @@ import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; - /** * Utility class for Spring Security. */ public final class SecurityUtils { - private SecurityUtils() { - } + private SecurityUtils() {} /** * Get the login of the current user. @@ -28,23 +26,26 @@ private SecurityUtils() { */ public static Optional getCurrentUserLogin() { SecurityContext securityContext = SecurityContextHolder.getContext(); - return Optional.ofNullable(securityContext.getAuthentication()) - .map(authentication -> { - if (authentication.getPrincipal() instanceof UserDetails) { - UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal(); - return springSecurityUser.getUsername(); - } else if (authentication instanceof JwtAuthenticationToken) { - return (String) ((JwtAuthenticationToken)authentication).getToken().getClaims().get("preferred_username"); - } else if (authentication.getPrincipal() instanceof DefaultOidcUser) { - Map attributes = ((DefaultOidcUser) authentication.getPrincipal()).getAttributes(); - if (attributes.containsKey("preferred_username")) { - return (String) attributes.get("preferred_username"); - } - } else if (authentication.getPrincipal() instanceof String) { - return (String) authentication.getPrincipal(); - } - return null; - }); + return Optional.ofNullable(extractPrincipal(securityContext.getAuthentication())); + } + + private static String extractPrincipal(Authentication authentication) { + if (authentication == null) { + return null; + } else if (authentication.getPrincipal() instanceof UserDetails) { + UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal(); + return springSecurityUser.getUsername(); + } else if (authentication instanceof JwtAuthenticationToken) { + return (String) ((JwtAuthenticationToken) authentication).getToken().getClaims().get("preferred_username"); + } else if (authentication.getPrincipal() instanceof DefaultOidcUser) { + Map attributes = ((DefaultOidcUser) authentication.getPrincipal()).getAttributes(); + if (attributes.containsKey("preferred_username")) { + return (String) attributes.get("preferred_username"); + } + } else if (authentication.getPrincipal() instanceof String) { + return (String) authentication.getPrincipal(); + } + return null; } /** @@ -54,47 +55,37 @@ public static Optional getCurrentUserLogin() { */ public static boolean isAuthenticated() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - return authentication != null && - getAuthorities(authentication).noneMatch(AuthoritiesConstants.ANONYMOUS::equals); + return authentication != null && getAuthorities(authentication).noneMatch(AuthoritiesConstants.ANONYMOUS::equals); } /** - * If the current user has a specific authority (security role). - *

- * The name of this method comes from the {@code isUserInRole()} method in the Servlet API. + * Checks if the current user has a specific authority. * * @param authority the authority to check. * @return true if the current user has the authority, false otherwise. */ - public static boolean isCurrentUserInRole(String authority) { + public static boolean hasCurrentUserThisAuthority(String authority) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - return authentication != null && - getAuthorities(authentication).anyMatch(authority::equals); + return authentication != null && getAuthorities(authentication).anyMatch(authority::equals); } private static Stream getAuthorities(Authentication authentication) { - Collection authorities = authentication instanceof JwtAuthenticationToken ? - extractAuthorityFromClaims(((JwtAuthenticationToken) authentication).getToken().getClaims()) + Collection authorities = authentication instanceof JwtAuthenticationToken + ? extractAuthorityFromClaims(((JwtAuthenticationToken) authentication).getToken().getClaims()) : authentication.getAuthorities(); - return authorities.stream() - .map(GrantedAuthority::getAuthority); + return authorities.stream().map(GrantedAuthority::getAuthority); } public static List extractAuthorityFromClaims(Map claims) { - return mapRolesToGrantedAuthorities( - getRolesFromClaims(claims)); + return mapRolesToGrantedAuthorities(getRolesFromClaims(claims)); } @SuppressWarnings("unchecked") private static Collection getRolesFromClaims(Map claims) { - return (Collection) claims.getOrDefault("groups", - claims.getOrDefault("roles", new ArrayList<>())); + return (Collection) claims.getOrDefault("groups", claims.getOrDefault("roles", new ArrayList<>())); } private static List mapRolesToGrantedAuthorities(Collection roles) { - return roles.stream() - .filter(role -> role.startsWith("ROLE_")) - .map(SimpleGrantedAuthority::new) - .collect(Collectors.toList()); + return roles.stream().filter(role -> role.startsWith("ROLE_")).map(SimpleGrantedAuthority::new).collect(Collectors.toList()); } } diff --git a/src/main/java/org/securityrat/casemanagement/security/SpringSecurityAuditorAware.java b/src/main/java/org/securityrat/casemanagement/security/SpringSecurityAuditorAware.java index 02b0056..c9e8f34 100644 --- a/src/main/java/org/securityrat/casemanagement/security/SpringSecurityAuditorAware.java +++ b/src/main/java/org/securityrat/casemanagement/security/SpringSecurityAuditorAware.java @@ -1,9 +1,7 @@ package org.securityrat.casemanagement.security; -import org.securityrat.casemanagement.config.Constants; - import java.util.Optional; - +import org.securityrat.casemanagement.config.Constants; import org.springframework.data.domain.AuditorAware; import org.springframework.stereotype.Component; @@ -15,6 +13,6 @@ public class SpringSecurityAuditorAware implements AuditorAware { @Override public Optional getCurrentAuditor() { - return Optional.of(SecurityUtils.getCurrentUserLogin().orElse(Constants.SYSTEM_ACCOUNT)); + return Optional.of(SecurityUtils.getCurrentUserLogin().orElse(Constants.SYSTEM)); } } diff --git a/src/main/java/org/securityrat/casemanagement/security/oauth2/AudienceValidator.java b/src/main/java/org/securityrat/casemanagement/security/oauth2/AudienceValidator.java index d403a6b..58c3d9b 100644 --- a/src/main/java/org/securityrat/casemanagement/security/oauth2/AudienceValidator.java +++ b/src/main/java/org/securityrat/casemanagement/security/oauth2/AudienceValidator.java @@ -1,5 +1,6 @@ package org.securityrat.casemanagement.security.oauth2; +import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.oauth2.core.OAuth2Error; @@ -8,11 +9,10 @@ import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.util.Assert; -import java.util.List; - public class AudienceValidator implements OAuth2TokenValidator { + private final Logger log = LoggerFactory.getLogger(AudienceValidator.class); - private OAuth2Error error = new OAuth2Error("invalid_token", "The required audience is missing", null); + private final OAuth2Error error = new OAuth2Error("invalid_token", "The required audience is missing", null); private final List allowedAudience; @@ -23,7 +23,7 @@ public AudienceValidator(List allowedAudience) { public OAuth2TokenValidatorResult validate(Jwt jwt) { List audience = jwt.getAudience(); - if(audience.stream().anyMatch(aud -> allowedAudience.contains(aud))) { + if (audience.stream().anyMatch(allowedAudience::contains)) { return OAuth2TokenValidatorResult.success(); } else { log.warn("Invalid audience: {}", audience); diff --git a/src/main/java/org/securityrat/casemanagement/security/oauth2/AuthorizationHeaderUtil.java b/src/main/java/org/securityrat/casemanagement/security/oauth2/AuthorizationHeaderUtil.java index 574386c..02a10c1 100644 --- a/src/main/java/org/securityrat/casemanagement/security/oauth2/AuthorizationHeaderUtil.java +++ b/src/main/java/org/securityrat/casemanagement/security/oauth2/AuthorizationHeaderUtil.java @@ -6,7 +6,8 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; - +import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.securityrat.casemanagement.security.oauth2.OAuthIdpTokenResponseDTO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,9 +32,6 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - @Component public class AuthorizationHeaderUtil { @@ -52,9 +50,7 @@ public Optional getAuthorizationHeader() { OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication; String name = oauthToken.getName(); String registrationId = oauthToken.getAuthorizedClientRegistrationId(); - OAuth2AuthorizedClient client = clientService.loadAuthorizedClient( - registrationId, - name); + OAuth2AuthorizedClient client = clientService.loadAuthorizedClient(registrationId, name); if (null == client) { throw new OAuth2AuthorizationException(new OAuth2Error("access_denied", "The token is expired", null)); @@ -75,7 +71,6 @@ public Optional getAuthorizationHeader() { String authorizationHeaderValue = String.format("%s %s", tokenType, accessTokenValue); return Optional.of(authorizationHeaderValue); } - } else if (authentication instanceof JwtAuthenticationToken) { JwtAuthenticationToken accessToken = (JwtAuthenticationToken) authentication; String tokenValue = accessToken.getToken().getTokenValue(); @@ -92,7 +87,7 @@ private String refreshToken(OAuth2AuthorizedClient client, OAuth2AuthenticationT return null; } - OAuth2RefreshToken refreshToken = atr.getRefreshToken() != null ? atr.getRefreshToken(): client.getRefreshToken(); + OAuth2RefreshToken refreshToken = atr.getRefreshToken() != null ? atr.getRefreshToken() : client.getRefreshToken(); OAuth2AuthorizedClient updatedClient = new OAuth2AuthorizedClient( client.getClientRegistration(), client.getPrincipalName(), @@ -105,7 +100,6 @@ private String refreshToken(OAuth2AuthorizedClient client, OAuth2AuthenticationT } private OAuth2AccessTokenResponse refreshTokenClient(OAuth2AuthorizedClient currentClient) { - MultiValueMap formParameters = new LinkedMultiValueMap<>(); formParameters.add(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.REFRESH_TOKEN.getValue()); formParameters.add(OAuth2ParameterNames.REFRESH_TOKEN, currentClient.getRefreshToken().getTokenValue()); @@ -115,7 +109,10 @@ private OAuth2AccessTokenResponse refreshTokenClient(OAuth2AuthorizedClient curr .contentType(MediaType.APPLICATION_FORM_URLENCODED) .body(formParameters); try { - RestTemplate r = restTemplate(currentClient.getClientRegistration().getClientId(), currentClient.getClientRegistration().getClientSecret()); + RestTemplate r = restTemplate( + currentClient.getClientRegistration().getClientId(), + currentClient.getClientRegistration().getClientSecret() + ); ResponseEntity responseEntity = r.exchange(requestEntity, OAuthIdpTokenResponseDTO.class); return toOAuth2AccessTokenResponse(responseEntity.getBody()); } catch (OAuth2AuthorizationException e) { @@ -130,7 +127,8 @@ private OAuth2AccessTokenResponse toOAuth2AccessTokenResponse(OAuthIdpTokenRespo additionalParameters.put("not-before-policy", oAuthIdpResponse.getNotBefore()); additionalParameters.put("refresh_expires_in", oAuthIdpResponse.getRefreshExpiresIn()); additionalParameters.put("session_state", oAuthIdpResponse.getSessionState()); - return OAuth2AccessTokenResponse.withToken(oAuthIdpResponse.getAccessToken()) + return OAuth2AccessTokenResponse + .withToken(oAuthIdpResponse.getAccessToken()) .expiresIn(oAuthIdpResponse.getExpiresIn()) .refreshToken(oAuthIdpResponse.getRefreshToken()) .scopes(Pattern.compile("\\s").splitAsStream(oAuthIdpResponse.getScope()).collect(Collectors.toSet())) @@ -141,9 +139,7 @@ private OAuth2AccessTokenResponse toOAuth2AccessTokenResponse(OAuthIdpTokenRespo private RestTemplate restTemplate(String clientId, String clientSecret) { return restTemplateBuilder - .additionalMessageConverters( - new FormHttpMessageConverter(), - new OAuth2AccessTokenResponseHttpMessageConverter()) + .additionalMessageConverters(new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter()) .errorHandler(new OAuth2ErrorResponseErrorHandler()) .basicAuthentication(clientId, clientSecret) .build(); diff --git a/src/main/java/org/securityrat/casemanagement/security/oauth2/JwtAuthorityExtractor.java b/src/main/java/org/securityrat/casemanagement/security/oauth2/JwtGrantedAuthorityConverter.java similarity index 58% rename from src/main/java/org/securityrat/casemanagement/security/oauth2/JwtAuthorityExtractor.java rename to src/main/java/org/securityrat/casemanagement/security/oauth2/JwtGrantedAuthorityConverter.java index 41cbc55..0d1225f 100644 --- a/src/main/java/org/securityrat/casemanagement/security/oauth2/JwtAuthorityExtractor.java +++ b/src/main/java/org/securityrat/casemanagement/security/oauth2/JwtGrantedAuthorityConverter.java @@ -1,21 +1,21 @@ package org.securityrat.casemanagement.security.oauth2; +import java.util.Collection; import org.securityrat.casemanagement.security.SecurityUtils; +import org.springframework.core.convert.converter.Converter; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.stereotype.Component; -import java.util.Collection; - @Component -public class JwtAuthorityExtractor extends JwtAuthenticationConverter { +public class JwtGrantedAuthorityConverter implements Converter> { - public JwtAuthorityExtractor() { + public JwtGrantedAuthorityConverter() { + // Bean extracting authority. } @Override - protected Collection extractAuthorities(Jwt jwt) { + public Collection convert(Jwt jwt) { return SecurityUtils.extractAuthorityFromClaims(jwt.getClaims()); } } diff --git a/src/main/java/org/securityrat/casemanagement/security/oauth2/OAuthIdpTokenResponseDTO.java b/src/main/java/org/securityrat/casemanagement/security/oauth2/OAuthIdpTokenResponseDTO.java index f6d1b4d..4dc0a9a 100644 --- a/src/main/java/org/securityrat/casemanagement/security/oauth2/OAuthIdpTokenResponseDTO.java +++ b/src/main/java/org/securityrat/casemanagement/security/oauth2/OAuthIdpTokenResponseDTO.java @@ -1,7 +1,6 @@ package org.securityrat.casemanagement.security.oauth2; import com.fasterxml.jackson.annotation.JsonProperty; - import java.io.Serializable; import java.util.UUID; @@ -41,7 +40,9 @@ public class OAuthIdpTokenResponseDTO implements Serializable { @JsonProperty("refresh_expires_in") private String refreshExpiresIn; - public OAuthIdpTokenResponseDTO() {} + public OAuthIdpTokenResponseDTO() { + // Empty constructor for serialization. + } public String getRefreshExpiresIn() { return refreshExpiresIn; diff --git a/src/main/java/org/securityrat/casemanagement/service/AuditEventService.java b/src/main/java/org/securityrat/casemanagement/service/AuditEventService.java deleted file mode 100644 index 4969ab2..0000000 --- a/src/main/java/org/securityrat/casemanagement/service/AuditEventService.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.securityrat.casemanagement.service; - -import io.github.jhipster.config.JHipsterProperties; -import org.securityrat.casemanagement.config.audit.AuditEventConverter; -import org.securityrat.casemanagement.repository.PersistenceAuditEventRepository; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.actuate.audit.AuditEvent; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Optional; - -/** - * Service for managing audit events. - *

- * This is the default implementation to support SpringBoot Actuator {@code AuditEventRepository}. - */ -@Service -@Transactional -public class AuditEventService { - - private final Logger log = LoggerFactory.getLogger(AuditEventService.class); - - private final JHipsterProperties jHipsterProperties; - - private final PersistenceAuditEventRepository persistenceAuditEventRepository; - - private final AuditEventConverter auditEventConverter; - - public AuditEventService( - PersistenceAuditEventRepository persistenceAuditEventRepository, - AuditEventConverter auditEventConverter, JHipsterProperties jhipsterProperties) { - - this.persistenceAuditEventRepository = persistenceAuditEventRepository; - this.auditEventConverter = auditEventConverter; - this.jHipsterProperties = jhipsterProperties; - } - - /** - * Old audit events should be automatically deleted after 30 days. - * - * This is scheduled to get fired at 12:00 (am). - */ - @Scheduled(cron = "0 0 12 * * ?") - public void removeOldAuditEvents() { - persistenceAuditEventRepository - .findByAuditEventDateBefore(Instant.now().minus(jHipsterProperties.getAuditEvents().getRetentionPeriod(), ChronoUnit.DAYS)) - .forEach(auditEvent -> { - log.debug("Deleting audit data {}", auditEvent); - persistenceAuditEventRepository.delete(auditEvent); - }); - } - - public Page findAll(Pageable pageable) { - return persistenceAuditEventRepository.findAll(pageable) - .map(auditEventConverter::convertToAuditEvent); - } - - public Page findByDates(Instant fromDate, Instant toDate, Pageable pageable) { - return persistenceAuditEventRepository.findAllByAuditEventDateBetween(fromDate, toDate, pageable) - .map(auditEventConverter::convertToAuditEvent); - } - - public Optional find(Long id) { - return persistenceAuditEventRepository.findById(id) - .map(auditEventConverter::convertToAuditEvent); - } -} diff --git a/src/main/java/org/securityrat/casemanagement/service/UserService.java b/src/main/java/org/securityrat/casemanagement/service/UserService.java index 3c92469..ed46403 100644 --- a/src/main/java/org/securityrat/casemanagement/service/UserService.java +++ b/src/main/java/org/securityrat/casemanagement/service/UserService.java @@ -1,13 +1,16 @@ package org.securityrat.casemanagement.service; +import java.time.Instant; +import java.util.*; +import java.util.stream.Collectors; import org.securityrat.casemanagement.config.Constants; import org.securityrat.casemanagement.domain.Authority; import org.securityrat.casemanagement.domain.User; import org.securityrat.casemanagement.repository.AuthorityRepository; import org.securityrat.casemanagement.repository.UserRepository; import org.securityrat.casemanagement.security.SecurityUtils; +import org.securityrat.casemanagement.service.dto.AdminUserDTO; import org.securityrat.casemanagement.service.dto.UserDTO; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.domain.Page; @@ -19,10 +22,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.Instant; -import java.util.*; -import java.util.stream.Collectors; - /** * Service class for managing users. */ @@ -51,64 +50,31 @@ public UserService(UserRepository userRepository, AuthorityRepository authorityR * @param imageUrl image URL of user. */ public void updateUser(String firstName, String lastName, String email, String langKey, String imageUrl) { - SecurityUtils.getCurrentUserLogin() + SecurityUtils + .getCurrentUserLogin() .flatMap(userRepository::findOneByLogin) - .ifPresent(user -> { - user.setFirstName(firstName); - user.setLastName(lastName); - if (email != null) { - user.setEmail(email.toLowerCase()); - } - user.setLangKey(langKey); - user.setImageUrl(imageUrl); - log.debug("Changed Information for User: {}", user); - }); - } - - /** - * Update all information for a specific user, and return the modified user. - * - * @param userDTO user to update. - * @return updated user. - */ - public Optional updateUser(UserDTO userDTO) { - return Optional.of(userRepository - .findById(userDTO.getId())) - .filter(Optional::isPresent) - .map(Optional::get) - .map(user -> { - user.setLogin(userDTO.getLogin().toLowerCase()); - user.setFirstName(userDTO.getFirstName()); - user.setLastName(userDTO.getLastName()); - if (userDTO.getEmail() != null) { - user.setEmail(userDTO.getEmail().toLowerCase()); + .ifPresent( + user -> { + user.setFirstName(firstName); + user.setLastName(lastName); + if (email != null) { + user.setEmail(email.toLowerCase()); + } + user.setLangKey(langKey); + user.setImageUrl(imageUrl); + log.debug("Changed Information for User: {}", user); } - user.setImageUrl(userDTO.getImageUrl()); - user.setActivated(userDTO.isActivated()); - user.setLangKey(userDTO.getLangKey()); - Set managedAuthorities = user.getAuthorities(); - managedAuthorities.clear(); - userDTO.getAuthorities().stream() - .map(authorityRepository::findById) - .filter(Optional::isPresent) - .map(Optional::get) - .forEach(managedAuthorities::add); - log.debug("Changed Information for User: {}", user); - return user; - }) - .map(UserDTO::new); + ); } - public void deleteUser(String login) { - userRepository.findOneByLogin(login).ifPresent(user -> { - userRepository.delete(user); - log.debug("Deleted User: {}", user); - }); + @Transactional(readOnly = true) + public Page getAllManagedUsers(Pageable pageable) { + return userRepository.findAll(pageable).map(AdminUserDTO::new); } @Transactional(readOnly = true) - public Page getAllManagedUsers(Pageable pageable) { - return userRepository.findAllByLoginNot(pageable, Constants.ANONYMOUS_USER).map(UserDTO::new); + public Page getAllPublicUsers(Pageable pageable) { + return userRepository.findAllByIdNotNullAndActivatedIsTrue(pageable).map(UserDTO::new); } @Transactional(readOnly = true) @@ -116,20 +82,11 @@ public Optional getUserWithAuthoritiesByLogin(String login) { return userRepository.findOneWithAuthoritiesByLogin(login); } - @Transactional(readOnly = true) - public Optional getUserWithAuthorities(Long id) { - return userRepository.findOneWithAuthoritiesById(id); - } - - @Transactional(readOnly = true) - public Optional getUserWithAuthorities() { - return SecurityUtils.getCurrentUserLogin().flatMap(userRepository::findOneWithAuthoritiesByLogin); - } - /** * Gets a list of all the authorities. * @return a list of all the authorities. */ + @Transactional(readOnly = true) public List getAuthorities() { return authorityRepository.findAll().stream().map(Authority::getName).collect(Collectors.toList()); } @@ -137,8 +94,7 @@ public List getAuthorities() { private User syncUserWithIdP(Map details, User user) { // save authorities in to sync user roles/groups between IdP and JHipster's local database Collection dbAuthorities = getAuthorities(); - Collection userAuthorities = - user.getAuthorities().stream().map(Authority::getName).collect(Collectors.toList()); + Collection userAuthorities = user.getAuthorities().stream().map(Authority::getName).collect(Collectors.toList()); for (String authority : userAuthorities) { if (!dbAuthorities.contains(authority)) { log.debug("Saving authority '{}' in local database", authority); @@ -153,17 +109,15 @@ private User syncUserWithIdP(Map details, User user) { // if IdP sends last updated information, use it to determine if an update should happen if (details.get("updated_at") != null) { Instant dbModifiedDate = existingUser.get().getLastModifiedDate(); - Instant idpModifiedDate = new Date(Long.valueOf((Integer) details.get("updated_at"))).toInstant(); + Instant idpModifiedDate = (Instant) details.get("updated_at"); if (idpModifiedDate.isAfter(dbModifiedDate)) { log.debug("Updating user '{}' in local database", user.getLogin()); - updateUser(user.getFirstName(), user.getLastName(), user.getEmail(), - user.getLangKey(), user.getImageUrl()); + updateUser(user.getFirstName(), user.getLastName(), user.getEmail(), user.getLangKey(), user.getImageUrl()); } // no last updated info, blindly update } else { log.debug("Updating user '{}' in local database", user.getLogin()); - updateUser(user.getFirstName(), user.getLastName(), user.getEmail(), - user.getLangKey(), user.getImageUrl()); + updateUser(user.getFirstName(), user.getLastName(), user.getEmail(), user.getLangKey(), user.getImageUrl()); } } else { log.debug("Saving user '{}' in local database", user.getLogin()); @@ -179,7 +133,8 @@ private User syncUserWithIdP(Map details, User user) { * @param authToken the authentication token. * @return the user from the authentication. */ - public UserDTO getUserFromAuthentication(AbstractAuthenticationToken authToken) { + @Transactional + public AdminUserDTO getUserFromAuthentication(AbstractAuthenticationToken authToken) { Map attributes; if (authToken instanceof OAuth2AuthenticationToken) { attributes = ((OAuth2AuthenticationToken) authToken).getPrincipal().getAttributes(); @@ -189,19 +144,26 @@ public UserDTO getUserFromAuthentication(AbstractAuthenticationToken authToken) throw new IllegalArgumentException("AuthenticationToken is not OAuth2 or JWT!"); } User user = getUser(attributes); - user.setAuthorities(authToken.getAuthorities().stream() - .map(GrantedAuthority::getAuthority) - .map(authority -> { - Authority auth = new Authority(); - auth.setName(authority); - return auth; - }) - .collect(Collectors.toSet())); - return new UserDTO(syncUserWithIdP(attributes, user)); + user.setAuthorities( + authToken + .getAuthorities() + .stream() + .map(GrantedAuthority::getAuthority) + .map( + authority -> { + Authority auth = new Authority(); + auth.setName(authority); + return auth; + } + ) + .collect(Collectors.toSet()) + ); + return new AdminUserDTO(syncUserWithIdP(attributes, user)); } private static User getUser(Map details) { User user = new User(); + Boolean activated = Boolean.TRUE; // handle resource server JWT, where sub claim is email and uid is ID if (details.get("uid") != null) { user.setId((String) details.get("uid")); @@ -221,7 +183,7 @@ private static User getUser(Map details) { user.setLastName((String) details.get("family_name")); } if (details.get("email_verified") != null) { - user.setActivated((Boolean) details.get("email_verified")); + activated = (Boolean) details.get("email_verified"); } if (details.get("email") != null) { user.setEmail(((String) details.get("email")).toLowerCase()); @@ -234,9 +196,9 @@ private static User getUser(Map details) { // trim off country code if it exists String locale = (String) details.get("locale"); if (locale.contains("_")) { - locale = locale.substring(0, locale.indexOf("_")); + locale = locale.substring(0, locale.indexOf('_')); } else if (locale.contains("-")) { - locale = locale.substring(0, locale.indexOf("-")); + locale = locale.substring(0, locale.indexOf('-')); } user.setLangKey(locale.toLowerCase()); } else { @@ -246,7 +208,7 @@ private static User getUser(Map details) { if (details.get("picture") != null) { user.setImageUrl((String) details.get("picture")); } - user.setActivated(true); + user.setActivated(activated); return user; } } diff --git a/src/main/java/org/securityrat/casemanagement/service/dto/AdminUserDTO.java b/src/main/java/org/securityrat/casemanagement/service/dto/AdminUserDTO.java new file mode 100644 index 0000000..ad289bd --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/service/dto/AdminUserDTO.java @@ -0,0 +1,193 @@ +package org.securityrat.casemanagement.service.dto; + +import java.time.Instant; +import java.util.Set; +import java.util.stream.Collectors; +import javax.validation.constraints.*; +import org.securityrat.casemanagement.config.Constants; +import org.securityrat.casemanagement.domain.Authority; +import org.securityrat.casemanagement.domain.User; + +/** + * A DTO representing a user, with his authorities. + */ +public class AdminUserDTO { + + private String id; + + @NotBlank + @Pattern(regexp = Constants.LOGIN_REGEX) + @Size(min = 1, max = 50) + private String login; + + @Size(max = 50) + private String firstName; + + @Size(max = 50) + private String lastName; + + @Email + @Size(min = 5, max = 254) + private String email; + + @Size(max = 256) + private String imageUrl; + + private boolean activated = false; + + @Size(min = 2, max = 10) + private String langKey; + + private String createdBy; + + private Instant createdDate; + + private String lastModifiedBy; + + private Instant lastModifiedDate; + + private Set authorities; + + public AdminUserDTO() { + // Empty constructor needed for Jackson. + } + + public AdminUserDTO(User user) { + this.id = user.getId(); + this.login = user.getLogin(); + this.firstName = user.getFirstName(); + this.lastName = user.getLastName(); + this.email = user.getEmail(); + this.activated = user.isActivated(); + this.imageUrl = user.getImageUrl(); + this.langKey = user.getLangKey(); + this.createdBy = user.getCreatedBy(); + this.createdDate = user.getCreatedDate(); + this.lastModifiedBy = user.getLastModifiedBy(); + this.lastModifiedDate = user.getLastModifiedDate(); + this.authorities = user.getAuthorities().stream().map(Authority::getName).collect(Collectors.toSet()); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public boolean isActivated() { + return activated; + } + + public void setActivated(boolean activated) { + this.activated = activated; + } + + public String getLangKey() { + return langKey; + } + + public void setLangKey(String langKey) { + this.langKey = langKey; + } + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + public Instant getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(Instant createdDate) { + this.createdDate = createdDate; + } + + public String getLastModifiedBy() { + return lastModifiedBy; + } + + public void setLastModifiedBy(String lastModifiedBy) { + this.lastModifiedBy = lastModifiedBy; + } + + public Instant getLastModifiedDate() { + return lastModifiedDate; + } + + public void setLastModifiedDate(Instant lastModifiedDate) { + this.lastModifiedDate = lastModifiedDate; + } + + public Set getAuthorities() { + return authorities; + } + + public void setAuthorities(Set authorities) { + this.authorities = authorities; + } + + // prettier-ignore + @Override + public String toString() { + return "AdminUserDTO{" + + "login='" + login + '\'' + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", email='" + email + '\'' + + ", imageUrl='" + imageUrl + '\'' + + ", activated=" + activated + + ", langKey='" + langKey + '\'' + + ", createdBy=" + createdBy + + ", createdDate=" + createdDate + + ", lastModifiedBy='" + lastModifiedBy + '\'' + + ", lastModifiedDate=" + lastModifiedDate + + ", authorities=" + authorities + + "}"; + } +} diff --git a/src/main/java/org/securityrat/casemanagement/service/dto/UserDTO.java b/src/main/java/org/securityrat/casemanagement/service/dto/UserDTO.java index 6a3a0a0..a337e78 100644 --- a/src/main/java/org/securityrat/casemanagement/service/dto/UserDTO.java +++ b/src/main/java/org/securityrat/casemanagement/service/dto/UserDTO.java @@ -1,75 +1,24 @@ package org.securityrat.casemanagement.service.dto; -import org.securityrat.casemanagement.config.Constants; - -import org.securityrat.casemanagement.domain.Authority; import org.securityrat.casemanagement.domain.User; -import javax.validation.constraints.*; -import java.time.Instant; -import java.util.Set; -import java.util.stream.Collectors; - /** - * A DTO representing a user, with his authorities. + * A DTO representing a user, with only the public attributes. */ public class UserDTO { private String id; - @NotBlank - @Pattern(regexp = Constants.LOGIN_REGEX) - @Size(min = 1, max = 50) private String login; - @Size(max = 50) - private String firstName; - - @Size(max = 50) - private String lastName; - - @Email - @Size(min = 5, max = 254) - private String email; - - @Size(max = 256) - private String imageUrl; - - private boolean activated = false; - - @Size(min = 2, max = 10) - private String langKey; - - private String createdBy; - - private Instant createdDate; - - private String lastModifiedBy; - - private Instant lastModifiedDate; - - private Set authorities; - public UserDTO() { // Empty constructor needed for Jackson. } public UserDTO(User user) { this.id = user.getId(); + // Customize it here if you need, or not, firstName/lastName/etc this.login = user.getLogin(); - this.firstName = user.getFirstName(); - this.lastName = user.getLastName(); - this.email = user.getEmail(); - this.activated = user.getActivated(); - this.imageUrl = user.getImageUrl(); - this.langKey = user.getLangKey(); - this.createdBy = user.getCreatedBy(); - this.createdDate = user.getCreatedDate(); - this.lastModifiedBy = user.getLastModifiedBy(); - this.lastModifiedDate = user.getLastModifiedDate(); - this.authorities = user.getAuthorities().stream() - .map(Authority::getName) - .collect(Collectors.toSet()); } public String getId() { @@ -88,109 +37,12 @@ public void setLogin(String login) { this.login = login; } - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getImageUrl() { - return imageUrl; - } - - public void setImageUrl(String imageUrl) { - this.imageUrl = imageUrl; - } - - public boolean isActivated() { - return activated; - } - - public void setActivated(boolean activated) { - this.activated = activated; - } - - public String getLangKey() { - return langKey; - } - - public void setLangKey(String langKey) { - this.langKey = langKey; - } - - public String getCreatedBy() { - return createdBy; - } - - public void setCreatedBy(String createdBy) { - this.createdBy = createdBy; - } - - public Instant getCreatedDate() { - return createdDate; - } - - public void setCreatedDate(Instant createdDate) { - this.createdDate = createdDate; - } - - public String getLastModifiedBy() { - return lastModifiedBy; - } - - public void setLastModifiedBy(String lastModifiedBy) { - this.lastModifiedBy = lastModifiedBy; - } - - public Instant getLastModifiedDate() { - return lastModifiedDate; - } - - public void setLastModifiedDate(Instant lastModifiedDate) { - this.lastModifiedDate = lastModifiedDate; - } - - public Set getAuthorities() { - return authorities; - } - - public void setAuthorities(Set authorities) { - this.authorities = authorities; - } - + // prettier-ignore @Override public String toString() { return "UserDTO{" + - "login='" + login + '\'' + - ", firstName='" + firstName + '\'' + - ", lastName='" + lastName + '\'' + - ", email='" + email + '\'' + - ", imageUrl='" + imageUrl + '\'' + - ", activated=" + activated + - ", langKey='" + langKey + '\'' + - ", createdBy=" + createdBy + - ", createdDate=" + createdDate + - ", lastModifiedBy='" + lastModifiedBy + '\'' + - ", lastModifiedDate=" + lastModifiedDate + - ", authorities=" + authorities + + "id='" + id + '\'' + + ", login='" + login + '\'' + "}"; } } diff --git a/src/main/java/org/securityrat/casemanagement/service/mapper/UserMapper.java b/src/main/java/org/securityrat/casemanagement/service/mapper/UserMapper.java index 53456ba..fcb92d4 100644 --- a/src/main/java/org/securityrat/casemanagement/service/mapper/UserMapper.java +++ b/src/main/java/org/securityrat/casemanagement/service/mapper/UserMapper.java @@ -1,14 +1,16 @@ package org.securityrat.casemanagement.service.mapper; +import java.util.*; +import java.util.stream.Collectors; +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapping; +import org.mapstruct.Named; import org.securityrat.casemanagement.domain.Authority; import org.securityrat.casemanagement.domain.User; +import org.securityrat.casemanagement.service.dto.AdminUserDTO; import org.securityrat.casemanagement.service.dto.UserDTO; - import org.springframework.stereotype.Service; -import java.util.*; -import java.util.stream.Collectors; - /** * Mapper for the entity {@link User} and its DTO called {@link UserDTO}. * @@ -19,24 +21,26 @@ public class UserMapper { public List usersToUserDTOs(List users) { - return users.stream() - .filter(Objects::nonNull) - .map(this::userToUserDTO) - .collect(Collectors.toList()); + return users.stream().filter(Objects::nonNull).map(this::userToUserDTO).collect(Collectors.toList()); } public UserDTO userToUserDTO(User user) { return new UserDTO(user); } - public List userDTOsToUsers(List userDTOs) { - return userDTOs.stream() - .filter(Objects::nonNull) - .map(this::userDTOToUser) - .collect(Collectors.toList()); + public List usersToAdminUserDTOs(List users) { + return users.stream().filter(Objects::nonNull).map(this::userToAdminUserDTO).collect(Collectors.toList()); + } + + public AdminUserDTO userToAdminUserDTO(User user) { + return new AdminUserDTO(user); } - public User userDTOToUser(UserDTO userDTO) { + public List userDTOsToUsers(List userDTOs) { + return userDTOs.stream().filter(Objects::nonNull).map(this::userDTOToUser).collect(Collectors.toList()); + } + + public User userDTOToUser(AdminUserDTO userDTO) { if (userDTO == null) { return null; } else { @@ -55,16 +59,21 @@ public User userDTOToUser(UserDTO userDTO) { } } - private Set authoritiesFromStrings(Set authoritiesAsString) { Set authorities = new HashSet<>(); - if(authoritiesAsString != null){ - authorities = authoritiesAsString.stream().map(string -> { - Authority auth = new Authority(); - auth.setName(string); - return auth; - }).collect(Collectors.toSet()); + if (authoritiesAsString != null) { + authorities = + authoritiesAsString + .stream() + .map( + string -> { + Authority auth = new Authority(); + auth.setName(string); + return auth; + } + ) + .collect(Collectors.toSet()); } return authorities; @@ -78,4 +87,63 @@ public User userFromId(String id) { user.setId(id); return user; } + + @Named("id") + @BeanMapping(ignoreByDefault = true) + @Mapping(target = "id", source = "id") + public UserDTO toDtoId(User user) { + if (user == null) { + return null; + } + UserDTO userDto = new UserDTO(); + userDto.setId(user.getId()); + return userDto; + } + + @Named("idSet") + @BeanMapping(ignoreByDefault = true) + @Mapping(target = "id", source = "id") + public Set toDtoIdSet(Set users) { + if (users == null) { + return null; + } + + Set userSet = new HashSet<>(); + for (User userEntity : users) { + userSet.add(this.toDtoId(userEntity)); + } + + return userSet; + } + + @Named("login") + @BeanMapping(ignoreByDefault = true) + @Mapping(target = "id", source = "id") + @Mapping(target = "login", source = "login") + public UserDTO toDtoLogin(User user) { + if (user == null) { + return null; + } + UserDTO userDto = new UserDTO(); + userDto.setId(user.getId()); + userDto.setLogin(user.getLogin()); + return userDto; + } + + @Named("loginSet") + @BeanMapping(ignoreByDefault = true) + @Mapping(target = "id", source = "id") + @Mapping(target = "login", source = "login") + public Set toDtoLoginSet(Set users) { + if (users == null) { + return null; + } + + Set userSet = new HashSet<>(); + for (User userEntity : users) { + userSet.add(this.toDtoLogin(userEntity)); + } + + return userSet; + } } diff --git a/src/main/java/org/securityrat/casemanagement/web/rest/AccessTokenResource.java b/src/main/java/org/securityrat/casemanagement/web/rest/AccessTokenResource.java index 1f686c6..5e9a86c 100644 --- a/src/main/java/org/securityrat/casemanagement/web/rest/AccessTokenResource.java +++ b/src/main/java/org/securityrat/casemanagement/web/rest/AccessTokenResource.java @@ -1,13 +1,16 @@ package org.securityrat.casemanagement.web.rest; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; import org.securityrat.casemanagement.domain.AccessToken; import org.securityrat.casemanagement.repository.AccessTokenRepository; import org.securityrat.casemanagement.repository.UserRepository; import org.securityrat.casemanagement.web.rest.errors.BadRequestAlertException; - -import io.github.jhipster.web.util.HeaderUtil; -import io.github.jhipster.web.util.PaginationUtil; -import io.github.jhipster.web.util.ResponseUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -15,16 +18,12 @@ import org.springframework.data.domain.Pageable; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; - -import javax.validation.Valid; -import java.net.URI; -import java.net.URISyntaxException; - -import java.util.List; -import java.util.Optional; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import tech.jhipster.web.util.HeaderUtil; +import tech.jhipster.web.util.PaginationUtil; +import tech.jhipster.web.util.ResponseUtil; /** * REST controller for managing {@link org.securityrat.casemanagement.domain.AccessToken}. @@ -62,49 +61,120 @@ public ResponseEntity createAccessToken(@Valid @RequestBody AccessT if (accessToken.getId() != null) { throw new BadRequestAlertException("A new accessToken cannot already have an ID", ENTITY_NAME, "idexists"); } - if (accessToken.getUser() != null) { // Save user in case it's new and only exists in gateway userRepository.save(accessToken.getUser()); } AccessToken result = accessTokenRepository.save(accessToken); - return ResponseEntity.created(new URI("/api/access-tokens/" + result.getId())) + return ResponseEntity + .created(new URI("/api/access-tokens/" + result.getId())) .headers(HeaderUtil.createEntityCreationAlert(applicationName, false, ENTITY_NAME, result.getId().toString())) .body(result); } /** - * {@code PUT /access-tokens} : Updates an existing accessToken. + * {@code PUT /access-tokens/:id} : Updates an existing accessToken. * + * @param id the id of the accessToken to save. * @param accessToken the accessToken to update. * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated accessToken, * or with status {@code 400 (Bad Request)} if the accessToken is not valid, * or with status {@code 500 (Internal Server Error)} if the accessToken couldn't be updated. * @throws URISyntaxException if the Location URI syntax is incorrect. */ - @PutMapping("/access-tokens") - public ResponseEntity updateAccessToken(@Valid @RequestBody AccessToken accessToken) throws URISyntaxException { - log.debug("REST request to update AccessToken : {}", accessToken); + @PutMapping("/access-tokens/{id}") + public ResponseEntity updateAccessToken( + @PathVariable(value = "id", required = false) final Long id, + @Valid @RequestBody AccessToken accessToken + ) throws URISyntaxException { + log.debug("REST request to update AccessToken : {}, {}", id, accessToken); if (accessToken.getId() == null) { throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull"); } + if (!Objects.equals(id, accessToken.getId())) { + throw new BadRequestAlertException("Invalid ID", ENTITY_NAME, "idinvalid"); + } + + if (!accessTokenRepository.existsById(id)) { + throw new BadRequestAlertException("Entity not found", ENTITY_NAME, "idnotfound"); + } if (accessToken.getUser() != null) { // Save user in case it's new and only exists in gateway userRepository.save(accessToken.getUser()); } AccessToken result = accessTokenRepository.save(accessToken); - return ResponseEntity.ok() + return ResponseEntity + .ok() .headers(HeaderUtil.createEntityUpdateAlert(applicationName, false, ENTITY_NAME, accessToken.getId().toString())) .body(result); } /** - * {@code GET /access-tokens} : get all the accessTokens. + * {@code PATCH /access-tokens/:id} : Partial updates given fields of an existing accessToken, field will ignore if it is null * + * @param id the id of the accessToken to save. + * @param accessToken the accessToken to update. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated accessToken, + * or with status {@code 400 (Bad Request)} if the accessToken is not valid, + * or with status {@code 404 (Not Found)} if the accessToken is not found, + * or with status {@code 500 (Internal Server Error)} if the accessToken couldn't be updated. + * @throws URISyntaxException if the Location URI syntax is incorrect. + */ + @PatchMapping(value = "/access-tokens/{id}", consumes = "application/merge-patch+json") + public ResponseEntity partialUpdateAccessToken( + @PathVariable(value = "id", required = false) final Long id, + @NotNull @RequestBody AccessToken accessToken + ) throws URISyntaxException { + log.debug("REST request to partial update AccessToken partially : {}, {}", id, accessToken); + if (accessToken.getId() == null) { + throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull"); + } + if (!Objects.equals(id, accessToken.getId())) { + throw new BadRequestAlertException("Invalid ID", ENTITY_NAME, "idinvalid"); + } - * @param pageable the pagination information. + if (!accessTokenRepository.existsById(id)) { + throw new BadRequestAlertException("Entity not found", ENTITY_NAME, "idnotfound"); + } + if (accessToken.getUser() != null) { + // Save user in case it's new and only exists in gateway + userRepository.save(accessToken.getUser()); + } + + Optional result = accessTokenRepository + .findById(accessToken.getId()) + .map( + existingAccessToken -> { + if (accessToken.getToken() != null) { + existingAccessToken.setToken(accessToken.getToken()); + } + if (accessToken.getExpirationDate() != null) { + existingAccessToken.setExpirationDate(accessToken.getExpirationDate()); + } + if (accessToken.getSalt() != null) { + existingAccessToken.setSalt(accessToken.getSalt()); + } + if (accessToken.getRefreshToken() != null) { + existingAccessToken.setRefreshToken(accessToken.getRefreshToken()); + } + + return existingAccessToken; + } + ) + .map(accessTokenRepository::save); + + return ResponseUtil.wrapOrNotFound( + result, + HeaderUtil.createEntityUpdateAlert(applicationName, false, ENTITY_NAME, accessToken.getId().toString()) + ); + } + + /** + * {@code GET /access-tokens} : get all the accessTokens. + * + * @param pageable the pagination information. * @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of accessTokens in body. */ @GetMapping("/access-tokens") @@ -138,6 +208,9 @@ public ResponseEntity getAccessToken(@PathVariable Long id) { public ResponseEntity deleteAccessToken(@PathVariable Long id) { log.debug("REST request to delete AccessToken : {}", id); accessTokenRepository.deleteById(id); - return ResponseEntity.noContent().headers(HeaderUtil.createEntityDeletionAlert(applicationName, false, ENTITY_NAME, id.toString())).build(); + return ResponseEntity + .noContent() + .headers(HeaderUtil.createEntityDeletionAlert(applicationName, false, ENTITY_NAME, id.toString())) + .build(); } } diff --git a/src/main/java/org/securityrat/casemanagement/web/rest/AuditResource.java b/src/main/java/org/securityrat/casemanagement/web/rest/AuditResource.java deleted file mode 100644 index 277e3e4..0000000 --- a/src/main/java/org/securityrat/casemanagement/web/rest/AuditResource.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.securityrat.casemanagement.web.rest; - -import org.securityrat.casemanagement.service.AuditEventService; - -import io.github.jhipster.web.util.PaginationUtil; -import io.github.jhipster.web.util.ResponseUtil; -import org.springframework.boot.actuate.audit.AuditEvent; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; - -import java.time.Instant; -import java.time.LocalDate; -import java.time.ZoneId; -import java.util.List; - -/** - * REST controller for getting the {@link AuditEvent}s. - */ -@RestController -@RequestMapping("/management/audits") -public class AuditResource { - - private final AuditEventService auditEventService; - - public AuditResource(AuditEventService auditEventService) { - this.auditEventService = auditEventService; - } - - /** - * {@code GET /audits} : get a page of {@link AuditEvent}s. - * - * @param pageable the pagination information. - * @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of {@link AuditEvent}s in body. - */ - @GetMapping - public ResponseEntity> getAll(Pageable pageable) { - Page page = auditEventService.findAll(pageable); - HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page); - return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK); - } - - /** - * {@code GET /audits} : get a page of {@link AuditEvent} between the {@code fromDate} and {@code toDate}. - * - * @param fromDate the start of the time period of {@link AuditEvent} to get. - * @param toDate the end of the time period of {@link AuditEvent} to get. - * @param pageable the pagination information. - * @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of {@link AuditEvent} in body. - */ - @GetMapping(params = {"fromDate", "toDate"}) - public ResponseEntity> getByDates( - @RequestParam(value = "fromDate") LocalDate fromDate, - @RequestParam(value = "toDate") LocalDate toDate, - Pageable pageable) { - - Instant from = fromDate.atStartOfDay(ZoneId.systemDefault()).toInstant(); - Instant to = toDate.atStartOfDay(ZoneId.systemDefault()).plusDays(1).toInstant(); - - Page page = auditEventService.findByDates(from, to, pageable); - HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page); - return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK); - } - - /** - * {@code GET /audits/:id} : get an {@link AuditEvent} by id. - * - * @param id the id of the entity to get. - * @return the {@link ResponseEntity} with status {@code 200 (OK)} and the {@link AuditEvent} in body, or status {@code 404 (Not Found)}. - */ - @GetMapping("/{id:.+}") - public ResponseEntity get(@PathVariable Long id) { - return ResponseUtil.wrapOrNotFound(auditEventService.find(id)); - } -} diff --git a/src/main/java/org/securityrat/casemanagement/web/rest/PublicUserResource.java b/src/main/java/org/securityrat/casemanagement/web/rest/PublicUserResource.java new file mode 100644 index 0000000..6c4ded6 --- /dev/null +++ b/src/main/java/org/securityrat/casemanagement/web/rest/PublicUserResource.java @@ -0,0 +1,52 @@ +package org.securityrat.casemanagement.web.rest; + +import java.util.*; +import org.securityrat.casemanagement.service.UserService; +import org.securityrat.casemanagement.service.dto.UserDTO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import tech.jhipster.web.util.PaginationUtil; + +@RestController +@RequestMapping("/api") +public class PublicUserResource { + + private final Logger log = LoggerFactory.getLogger(PublicUserResource.class); + + private final UserService userService; + + public PublicUserResource(UserService userService) { + this.userService = userService; + } + + /** + * {@code GET /users} : get all users with only the public informations - calling this are allowed for anyone. + * + * @param pageable the pagination information. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body all users. + */ + @GetMapping("/users") + public ResponseEntity> getAllPublicUsers(Pageable pageable) { + log.debug("REST request to get all public User names"); + + final Page page = userService.getAllPublicUsers(pageable); + HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page); + return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK); + } + + /** + * Gets a list of all roles. + * @return a string list of all roles. + */ + @GetMapping("/authorities") + public List getAuthorities() { + return userService.getAuthorities(); + } +} diff --git a/src/main/java/org/securityrat/casemanagement/web/rest/TicketSystemInstanceResource.java b/src/main/java/org/securityrat/casemanagement/web/rest/TicketSystemInstanceResource.java index c2c2b9a..c8fa0c0 100644 --- a/src/main/java/org/securityrat/casemanagement/web/rest/TicketSystemInstanceResource.java +++ b/src/main/java/org/securityrat/casemanagement/web/rest/TicketSystemInstanceResource.java @@ -1,12 +1,15 @@ package org.securityrat.casemanagement.web.rest; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; import org.securityrat.casemanagement.domain.TicketSystemInstance; import org.securityrat.casemanagement.repository.TicketSystemInstanceRepository; import org.securityrat.casemanagement.web.rest.errors.BadRequestAlertException; - -import io.github.jhipster.web.util.HeaderUtil; -import io.github.jhipster.web.util.PaginationUtil; -import io.github.jhipster.web.util.ResponseUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -14,17 +17,13 @@ import org.springframework.data.domain.Pageable; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import org.springframework.http.ResponseEntity; -import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; - -import javax.validation.Valid; -import java.net.URI; -import java.net.URISyntaxException; - -import java.util.List; -import java.util.Optional; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import tech.jhipster.web.util.HeaderUtil; +import tech.jhipster.web.util.PaginationUtil; +import tech.jhipster.web.util.ResponseUtil; /** * REST controller for managing {@link org.securityrat.casemanagement.domain.TicketSystemInstance}. @@ -55,44 +54,120 @@ public TicketSystemInstanceResource(TicketSystemInstanceRepository ticketSystemI * @throws URISyntaxException if the Location URI syntax is incorrect. */ @PostMapping("/ticket-system-instances") - public ResponseEntity createTicketSystemInstance(@Valid @RequestBody TicketSystemInstance ticketSystemInstance) throws URISyntaxException { + public ResponseEntity createTicketSystemInstance(@Valid @RequestBody TicketSystemInstance ticketSystemInstance) + throws URISyntaxException { log.debug("REST request to save TicketSystemInstance : {}", ticketSystemInstance); if (ticketSystemInstance.getId() != null) { throw new BadRequestAlertException("A new ticketSystemInstance cannot already have an ID", ENTITY_NAME, "idexists"); } TicketSystemInstance result = ticketSystemInstanceRepository.save(ticketSystemInstance); - return ResponseEntity.created(new URI("/api/ticket-system-instances/" + result.getId())) + return ResponseEntity + .created(new URI("/api/ticket-system-instances/" + result.getId())) .headers(HeaderUtil.createEntityCreationAlert(applicationName, false, ENTITY_NAME, result.getId().toString())) .body(result); } /** - * {@code PUT /ticket-system-instances} : Updates an existing ticketSystemInstance. + * {@code PUT /ticket-system-instances/:id} : Updates an existing ticketSystemInstance. * + * @param id the id of the ticketSystemInstance to save. * @param ticketSystemInstance the ticketSystemInstance to update. * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated ticketSystemInstance, * or with status {@code 400 (Bad Request)} if the ticketSystemInstance is not valid, * or with status {@code 500 (Internal Server Error)} if the ticketSystemInstance couldn't be updated. * @throws URISyntaxException if the Location URI syntax is incorrect. */ - @PutMapping("/ticket-system-instances") - public ResponseEntity updateTicketSystemInstance(@Valid @RequestBody TicketSystemInstance ticketSystemInstance) throws URISyntaxException { - log.debug("REST request to update TicketSystemInstance : {}", ticketSystemInstance); + @PutMapping("/ticket-system-instances/{id}") + public ResponseEntity updateTicketSystemInstance( + @PathVariable(value = "id", required = false) final Long id, + @Valid @RequestBody TicketSystemInstance ticketSystemInstance + ) throws URISyntaxException { + log.debug("REST request to update TicketSystemInstance : {}, {}", id, ticketSystemInstance); if (ticketSystemInstance.getId() == null) { throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull"); } + if (!Objects.equals(id, ticketSystemInstance.getId())) { + throw new BadRequestAlertException("Invalid ID", ENTITY_NAME, "idinvalid"); + } + + if (!ticketSystemInstanceRepository.existsById(id)) { + throw new BadRequestAlertException("Entity not found", ENTITY_NAME, "idnotfound"); + } + TicketSystemInstance result = ticketSystemInstanceRepository.save(ticketSystemInstance); - return ResponseEntity.ok() + return ResponseEntity + .ok() .headers(HeaderUtil.createEntityUpdateAlert(applicationName, false, ENTITY_NAME, ticketSystemInstance.getId().toString())) .body(result); } /** - * {@code GET /ticket-system-instances} : get all the ticketSystemInstances. + * {@code PATCH /ticket-system-instances/:id} : Partial updates given fields of an existing ticketSystemInstance, field will ignore if it is null * + * @param id the id of the ticketSystemInstance to save. + * @param ticketSystemInstance the ticketSystemInstance to update. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated ticketSystemInstance, + * or with status {@code 400 (Bad Request)} if the ticketSystemInstance is not valid, + * or with status {@code 404 (Not Found)} if the ticketSystemInstance is not found, + * or with status {@code 500 (Internal Server Error)} if the ticketSystemInstance couldn't be updated. + * @throws URISyntaxException if the Location URI syntax is incorrect. + */ + @PatchMapping(value = "/ticket-system-instances/{id}", consumes = "application/merge-patch+json") + public ResponseEntity partialUpdateTicketSystemInstance( + @PathVariable(value = "id", required = false) final Long id, + @NotNull @RequestBody TicketSystemInstance ticketSystemInstance + ) throws URISyntaxException { + log.debug("REST request to partial update TicketSystemInstance partially : {}, {}", id, ticketSystemInstance); + if (ticketSystemInstance.getId() == null) { + throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull"); + } + if (!Objects.equals(id, ticketSystemInstance.getId())) { + throw new BadRequestAlertException("Invalid ID", ENTITY_NAME, "idinvalid"); + } - * @param pageable the pagination information. + if (!ticketSystemInstanceRepository.existsById(id)) { + throw new BadRequestAlertException("Entity not found", ENTITY_NAME, "idnotfound"); + } + + Optional result = ticketSystemInstanceRepository + .findById(ticketSystemInstance.getId()) + .map( + existingTicketSystemInstance -> { + if (ticketSystemInstance.getName() != null) { + existingTicketSystemInstance.setName(ticketSystemInstance.getName()); + } + if (ticketSystemInstance.getType() != null) { + existingTicketSystemInstance.setType(ticketSystemInstance.getType()); + } + if (ticketSystemInstance.getUrl() != null) { + existingTicketSystemInstance.setUrl(ticketSystemInstance.getUrl()); + } + if (ticketSystemInstance.getConsumerKey() != null) { + existingTicketSystemInstance.setConsumerKey(ticketSystemInstance.getConsumerKey()); + } + if (ticketSystemInstance.getClientId() != null) { + existingTicketSystemInstance.setClientId(ticketSystemInstance.getClientId()); + } + if (ticketSystemInstance.getClientSecret() != null) { + existingTicketSystemInstance.setClientSecret(ticketSystemInstance.getClientSecret()); + } + + return existingTicketSystemInstance; + } + ) + .map(ticketSystemInstanceRepository::save) + .map(ticketSystemInstanceMapper::toDto); + + return ResponseUtil.wrapOrNotFound( + result, + HeaderUtil.createEntityUpdateAlert(applicationName, false, ENTITY_NAME, ticketSystemInstance.getId().toString()) + ); + } + /** + * {@code GET /ticket-system-instances} : get all the ticketSystemInstances. + * + * @param pageable the pagination information. * @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of ticketSystemInstances in body. */ @GetMapping("/ticket-system-instances") @@ -126,6 +201,9 @@ public ResponseEntity getTicketSystemInstance(@PathVariabl public ResponseEntity deleteTicketSystemInstance(@PathVariable Long id) { log.debug("REST request to delete TicketSystemInstance : {}", id); ticketSystemInstanceRepository.deleteById(id); - return ResponseEntity.noContent().headers(HeaderUtil.createEntityDeletionAlert(applicationName, false, ENTITY_NAME, id.toString())).build(); + return ResponseEntity + .noContent() + .headers(HeaderUtil.createEntityDeletionAlert(applicationName, false, ENTITY_NAME, id.toString())) + .build(); } } diff --git a/src/main/java/org/securityrat/casemanagement/web/rest/UserResource.java b/src/main/java/org/securityrat/casemanagement/web/rest/UserResource.java index 2679703..6d3d2dd 100644 --- a/src/main/java/org/securityrat/casemanagement/web/rest/UserResource.java +++ b/src/main/java/org/securityrat/casemanagement/web/rest/UserResource.java @@ -1,14 +1,11 @@ package org.securityrat.casemanagement.web.rest; +import java.util.*; +import javax.validation.constraints.Pattern; import org.securityrat.casemanagement.config.Constants; import org.securityrat.casemanagement.security.AuthoritiesConstants; import org.securityrat.casemanagement.service.UserService; -import org.securityrat.casemanagement.service.dto.UserDTO; - -import io.github.jhipster.web.util.HeaderUtil; -import io.github.jhipster.web.util.PaginationUtil; -import io.github.jhipster.web.util.ResponseUtil; - +import org.securityrat.casemanagement.service.dto.AdminUserDTO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -20,8 +17,9 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; - -import java.util.*; +import tech.jhipster.web.util.HeaderUtil; +import tech.jhipster.web.util.PaginationUtil; +import tech.jhipster.web.util.ResponseUtil; /** * REST controller for managing users. @@ -48,7 +46,7 @@ * Another option would be to have a specific JPA entity graph to handle this case. */ @RestController -@RequestMapping("/api") +@RequestMapping("/api/admin") public class UserResource { private final Logger log = LoggerFactory.getLogger(UserResource.class); @@ -59,44 +57,35 @@ public class UserResource { private final UserService userService; public UserResource(UserService userService) { - this.userService = userService; } /** - * {@code GET /users} : get all users. + * {@code GET /admin/users} : get all users with all the details - calling this are only allowed for the administrators. * * @param pageable the pagination information. * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body all users. */ @GetMapping("/users") - public ResponseEntity> getAllUsers(Pageable pageable) { - final Page page = userService.getAllManagedUsers(pageable); + @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") + public ResponseEntity> getAllUsers(Pageable pageable) { + log.debug("REST request to get all User for an admin"); + + final Page page = userService.getAllManagedUsers(pageable); HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page); return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK); } /** - * Gets a list of all roles. - * @return a string list of all roles. - */ - @GetMapping("/users/authorities") - @PreAuthorize("hasRole(\"" + AuthoritiesConstants.ADMIN + "\")") - public List getAuthorities() { - return userService.getAuthorities(); - } - - /** - * {@code GET /users/:login} : get the "login" user. + * {@code GET /admin/users/:login} : get the "login" user. * * @param login the login of the user to find. * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the "login" user, or with status {@code 404 (Not Found)}. */ - @GetMapping("/users/{login:" + Constants.LOGIN_REGEX + "}") - public ResponseEntity getUser(@PathVariable String login) { + @GetMapping("/users/{login}") + @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") + public ResponseEntity getUser(@PathVariable @Pattern(regexp = Constants.LOGIN_REGEX) String login) { log.debug("REST request to get User : {}", login); - return ResponseUtil.wrapOrNotFound( - userService.getUserWithAuthoritiesByLogin(login) - .map(UserDTO::new)); + return ResponseUtil.wrapOrNotFound(userService.getUserWithAuthoritiesByLogin(login).map(AdminUserDTO::new)); } } diff --git a/src/main/java/org/securityrat/casemanagement/web/rest/errors/BadRequestAlertException.java b/src/main/java/org/securityrat/casemanagement/web/rest/errors/BadRequestAlertException.java index 4494b21..403c9ab 100644 --- a/src/main/java/org/securityrat/casemanagement/web/rest/errors/BadRequestAlertException.java +++ b/src/main/java/org/securityrat/casemanagement/web/rest/errors/BadRequestAlertException.java @@ -1,11 +1,10 @@ package org.securityrat.casemanagement.web.rest.errors; -import org.zalando.problem.AbstractThrowableProblem; -import org.zalando.problem.Status; - import java.net.URI; import java.util.HashMap; import java.util.Map; +import org.zalando.problem.AbstractThrowableProblem; +import org.zalando.problem.Status; public class BadRequestAlertException extends AbstractThrowableProblem { diff --git a/src/main/java/org/securityrat/casemanagement/web/rest/errors/ErrorConstants.java b/src/main/java/org/securityrat/casemanagement/web/rest/errors/ErrorConstants.java index e85bd99..1218251 100644 --- a/src/main/java/org/securityrat/casemanagement/web/rest/errors/ErrorConstants.java +++ b/src/main/java/org/securityrat/casemanagement/web/rest/errors/ErrorConstants.java @@ -10,6 +10,5 @@ public final class ErrorConstants { public static final URI DEFAULT_TYPE = URI.create(PROBLEM_BASE_URL + "/problem-with-message"); public static final URI CONSTRAINT_VIOLATION_TYPE = URI.create(PROBLEM_BASE_URL + "/constraint-violation"); - private ErrorConstants() { - } + private ErrorConstants() {} } diff --git a/src/main/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslator.java b/src/main/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslator.java index ac9e551..dac61bf 100644 --- a/src/main/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslator.java +++ b/src/main/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslator.java @@ -1,10 +1,21 @@ package org.securityrat.casemanagement.web.rest.errors; -import io.github.jhipster.web.util.HeaderUtil; - +import java.net.URI; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.env.Environment; import org.springframework.dao.ConcurrencyFailureException; +import org.springframework.dao.DataAccessException; import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageConversionException; import org.springframework.validation.BindingResult; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; @@ -14,15 +25,12 @@ import org.zalando.problem.Problem; import org.zalando.problem.ProblemBuilder; import org.zalando.problem.Status; +import org.zalando.problem.StatusType; import org.zalando.problem.spring.web.advice.ProblemHandling; import org.zalando.problem.spring.web.advice.security.SecurityAdviceTrait; import org.zalando.problem.violations.ConstraintViolationProblem; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import java.util.List; -import java.util.stream.Collectors; +import tech.jhipster.config.JHipsterConstants; +import tech.jhipster.web.util.HeaderUtil; /** * Controller advice to translate the server side exceptions to client-friendly json structures. @@ -39,33 +47,40 @@ public class ExceptionTranslator implements ProblemHandling, SecurityAdviceTrait @Value("${jhipster.clientApp.name}") private String applicationName; + private final Environment env; + + public ExceptionTranslator(Environment env) { + this.env = env; + } + /** * Post-process the Problem payload to add the message key for the front-end if needed. */ @Override public ResponseEntity process(@Nullable ResponseEntity entity, NativeWebRequest request) { if (entity == null) { - return entity; + return null; } Problem problem = entity.getBody(); if (!(problem instanceof ConstraintViolationProblem || problem instanceof DefaultProblem)) { return entity; } - ProblemBuilder builder = Problem.builder() + + HttpServletRequest nativeRequest = request.getNativeRequest(HttpServletRequest.class); + String requestUri = nativeRequest != null ? nativeRequest.getRequestURI() : StringUtils.EMPTY; + ProblemBuilder builder = Problem + .builder() .withType(Problem.DEFAULT_TYPE.equals(problem.getType()) ? ErrorConstants.DEFAULT_TYPE : problem.getType()) .withStatus(problem.getStatus()) .withTitle(problem.getTitle()) - .with(PATH_KEY, request.getNativeRequest(HttpServletRequest.class).getRequestURI()); + .with(PATH_KEY, requestUri); if (problem instanceof ConstraintViolationProblem) { builder .with(VIOLATIONS_KEY, ((ConstraintViolationProblem) problem).getViolations()) .with(MESSAGE_KEY, ErrorConstants.ERR_VALIDATION); } else { - builder - .withCause(((DefaultProblem) problem).getCause()) - .withDetail(problem.getDetail()) - .withInstance(problem.getInstance()); + builder.withCause(((DefaultProblem) problem).getCause()).withDetail(problem.getDetail()).withInstance(problem.getInstance()); problem.getParameters().forEach(builder::with); if (!problem.getParameters().containsKey(MESSAGE_KEY) && problem.getStatus() != null) { builder.with(MESSAGE_KEY, "error.http." + problem.getStatus().getStatusCode()); @@ -77,11 +92,21 @@ public ResponseEntity process(@Nullable ResponseEntity entity, @Override public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, @Nonnull NativeWebRequest request) { BindingResult result = ex.getBindingResult(); - List fieldErrors = result.getFieldErrors().stream() - .map(f -> new FieldErrorVM(f.getObjectName().replaceFirst("DTO$", ""), f.getField(), f.getCode())) + List fieldErrors = result + .getFieldErrors() + .stream() + .map( + f -> + new FieldErrorVM( + f.getObjectName().replaceFirst("DTO$", ""), + f.getField(), + StringUtils.isNotBlank(f.getDefaultMessage()) ? f.getDefaultMessage() : f.getCode() + ) + ) .collect(Collectors.toList()); - Problem problem = Problem.builder() + Problem problem = Problem + .builder() .withType(ErrorConstants.CONSTRAINT_VIOLATION_TYPE) .withTitle("Method argument not valid") .withStatus(defaultConstraintViolationStatus()) @@ -93,15 +118,72 @@ public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotVal @ExceptionHandler public ResponseEntity handleBadRequestAlertException(BadRequestAlertException ex, NativeWebRequest request) { - return create(ex, request, HeaderUtil.createFailureAlert(applicationName, false, ex.getEntityName(), ex.getErrorKey(), ex.getMessage())); + return create( + ex, + request, + HeaderUtil.createFailureAlert(applicationName, false, ex.getEntityName(), ex.getErrorKey(), ex.getMessage()) + ); } @ExceptionHandler public ResponseEntity handleConcurrencyFailure(ConcurrencyFailureException ex, NativeWebRequest request) { - Problem problem = Problem.builder() - .withStatus(Status.CONFLICT) - .with(MESSAGE_KEY, ErrorConstants.ERR_CONCURRENCY_FAILURE) - .build(); + Problem problem = Problem.builder().withStatus(Status.CONFLICT).with(MESSAGE_KEY, ErrorConstants.ERR_CONCURRENCY_FAILURE).build(); return create(ex, problem, request); } + + @Override + public ProblemBuilder prepare(final Throwable throwable, final StatusType status, final URI type) { + Collection activeProfiles = Arrays.asList(env.getActiveProfiles()); + + if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_PRODUCTION)) { + if (throwable instanceof HttpMessageConversionException) { + return Problem + .builder() + .withType(type) + .withTitle(status.getReasonPhrase()) + .withStatus(status) + .withDetail("Unable to convert http message") + .withCause( + Optional.ofNullable(throwable.getCause()).filter(cause -> isCausalChainsEnabled()).map(this::toProblem).orElse(null) + ); + } + if (throwable instanceof DataAccessException) { + return Problem + .builder() + .withType(type) + .withTitle(status.getReasonPhrase()) + .withStatus(status) + .withDetail("Failure during data access") + .withCause( + Optional.ofNullable(throwable.getCause()).filter(cause -> isCausalChainsEnabled()).map(this::toProblem).orElse(null) + ); + } + if (containsPackageName(throwable.getMessage())) { + return Problem + .builder() + .withType(type) + .withTitle(status.getReasonPhrase()) + .withStatus(status) + .withDetail("Unexpected runtime exception") + .withCause( + Optional.ofNullable(throwable.getCause()).filter(cause -> isCausalChainsEnabled()).map(this::toProblem).orElse(null) + ); + } + } + + return Problem + .builder() + .withType(type) + .withTitle(status.getReasonPhrase()) + .withStatus(status) + .withDetail(throwable.getMessage()) + .withCause( + Optional.ofNullable(throwable.getCause()).filter(cause -> isCausalChainsEnabled()).map(this::toProblem).orElse(null) + ); + } + + private boolean containsPackageName(String message) { + // This list is for sure not complete + return StringUtils.containsAny(message, "org.", "java.", "net.", "javax.", "com.", "io.", "de.", "org.securityrat.casemanagement"); + } } diff --git a/src/main/java/org/securityrat/casemanagement/web/rest/errors/FieldErrorVM.java b/src/main/java/org/securityrat/casemanagement/web/rest/errors/FieldErrorVM.java index 71dd84d..229cc83 100644 --- a/src/main/java/org/securityrat/casemanagement/web/rest/errors/FieldErrorVM.java +++ b/src/main/java/org/securityrat/casemanagement/web/rest/errors/FieldErrorVM.java @@ -29,5 +29,4 @@ public String getField() { public String getMessage() { return message; } - } diff --git a/src/main/java/org/securityrat/casemanagement/web/rest/vm/ManagedUserVM.java b/src/main/java/org/securityrat/casemanagement/web/rest/vm/ManagedUserVM.java index b2ad38f..60c475c 100644 --- a/src/main/java/org/securityrat/casemanagement/web/rest/vm/ManagedUserVM.java +++ b/src/main/java/org/securityrat/casemanagement/web/rest/vm/ManagedUserVM.java @@ -1,16 +1,17 @@ package org.securityrat.casemanagement.web.rest.vm; -import org.securityrat.casemanagement.service.dto.UserDTO; +import org.securityrat.casemanagement.service.dto.AdminUserDTO; /** - * View Model extending the UserDTO, which is meant to be used in the user management UI. + * View Model extending the AdminUserDTO, which is meant to be used in the user management UI. */ -public class ManagedUserVM extends UserDTO { +public class ManagedUserVM extends AdminUserDTO { public ManagedUserVM() { // Empty constructor needed for Jackson. } + // prettier-ignore @Override public String toString() { return "ManagedUserVM{" + super.toString() + "} "; diff --git a/src/main/resources/.h2.server.properties b/src/main/resources/.h2.server.properties index 3c49b47..c5f790a 100644 --- a/src/main/resources/.h2.server.properties +++ b/src/main/resources/.h2.server.properties @@ -1,5 +1,5 @@ #H2 Server Properties 0=JHipster H2 (Disk)|org.h2.Driver|jdbc\:h2\:file\:./target/h2db/db/casemanagement|caseManagement webAllowOthers=true -webPort=8082 +webPort=8092 webSSL=false diff --git a/src/main/resources/config/application-dev.yml b/src/main/resources/config/application-dev.yml index d496665..75ccf1e 100644 --- a/src/main/resources/config/application-dev.yml +++ b/src/main/resources/config/application-dev.yml @@ -16,16 +16,11 @@ logging: level: ROOT: DEBUG - io.github.jhipster: DEBUG + tech.jhipster: DEBUG + org.hibernate.SQL: DEBUG org.securityrat.casemanagement: DEBUG spring: - profiles: - active: dev - include: - - swagger - # Uncomment to activate TLS for the dev profile - #- tls devtools: restart: enabled: true @@ -37,8 +32,18 @@ spring: indent-output: true cloud: consul: + config: + fail-fast: false # if not in "prod" profile, do not force to use Spring Cloud Config + format: yaml + profile-separator: '-' discovery: prefer-ip-address: true + tags: + - profile=${spring.profiles.active} + - version=#project.version# + - git-version=${git.commit.id.describe:} + - git-commit=${git.commit.id.abbrev:} + - git-branch=${git.branch:} host: localhost port: 8500 datasource: @@ -53,23 +58,10 @@ spring: console: enabled: false jpa: - database-platform: io.github.jhipster.domain.util.FixedH2Dialect - database: H2 - show-sql: true - properties: - hibernate.id.new_generator_mappings: true - hibernate.connection.provider_disables_autocommit: true - hibernate.cache.use_second_level_cache: false - hibernate.cache.use_query_cache: false - hibernate.generate_statistics: false + database-platform: tech.jhipster.domain.util.FixedH2Dialect liquibase: # Remove 'faker' if you do not want the sample data to be loaded automatically contexts: dev, faker - mail: - host: localhost - port: 25 - username: - password: messages: cache-duration: PT1S # 1 second, see the ISO 8601 standard thymeleaf: @@ -97,18 +89,12 @@ jhipster: # CORS is disabled by default on microservices, as you should access them through a gateway. # If you want to enable it, please uncomment the configuration below. # cors: - # allowed-origins: "*" - # allowed-methods: "*" - # allowed-headers: "*" - # exposed-headers: "Authorization,Link,X-Total-Count" - # allow-credentials: true - # max-age: 1800 - mail: # specific JHipster mail property, for standard properties see MailProperties - base-url: http://127.0.0.1:8082 - metrics: - logs: # Reports metrics in the logs - enabled: false - report-frequency: 60 # in seconds + # allowed-origins: "*" + # allowed-methods: "*" + # allowed-headers: "*" + # exposed-headers: "Authorization,Link,X-Total-Count" + # allow-credentials: true + # max-age: 1800 logging: use-json-format: false # By default, logs are not in Json format logstash: # Forward logs to logstash over a socket, used by LoggingConfiguration @@ -116,9 +102,6 @@ jhipster: host: localhost port: 5000 queue-size: 512 - audit-events: - retention-period: 30 # Number of days before audit events are deleted. - # =================================================================== # Application specific properties # Add your own application properties here, see the ApplicationProperties class diff --git a/src/main/resources/config/application-prod.yml b/src/main/resources/config/application-prod.yml index 2b0da31..d862db2 100644 --- a/src/main/resources/config/application-prod.yml +++ b/src/main/resources/config/application-prod.yml @@ -16,7 +16,7 @@ logging: level: ROOT: INFO - io.github.jhipster: INFO + tech.jhipster: INFO org.securityrat.casemanagement: INFO management: @@ -51,29 +51,9 @@ spring: prepStmtCacheSqlLimit: 2048 useServerPrepStmts: true jpa: - database-platform: org.hibernate.dialect.MariaDB103Dialect - database: MYSQL - show-sql: false - properties: - hibernate.id.new_generator_mappings: true - hibernate.connection.provider_disables_autocommit: true - hibernate.cache.use_second_level_cache: false - hibernate.cache.use_query_cache: false - hibernate.generate_statistics: false - # modify batch size as necessary - hibernate.jdbc.batch_size: 25 - hibernate.order_inserts: true - hibernate.order_updates: true - hibernate.query.fail_on_pagination_over_collection_fetch: true - hibernate.query.in_clause_parameter_padding: true # Replace by 'prod, faker' to add the faker context and have sample data loaded in production liquibase: contexts: prod - mail: - host: localhost - port: 25 - username: - password: thymeleaf: cache: true sleuth: @@ -96,17 +76,18 @@ spring: # Then, modify the server.ssl properties so your "server" configuration looks like: # # server: -# port: 443 -# ssl: -# key-store: classpath:config/tls/keystore.p12 -# key-store-password: password -# key-store-type: PKCS12 -# key-alias: casemanagement -# # The ciphers suite enforce the security by deactivating some old and deprecated SSL cipher, this list was tested against SSL Labs (https://www.ssllabs.com/ssltest/) -# ciphers: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ,TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 ,TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 ,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,TLS_RSA_WITH_CAMELLIA_256_CBC_SHA,TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA,TLS_RSA_WITH_CAMELLIA_128_CBC_SHA +# port: 443 +# ssl: +# key-store: classpath:config/tls/keystore.p12 +# key-store-password: password +# key-store-type: PKCS12 +# key-alias: selfsigned +# # The ciphers suite enforce the security by deactivating some old and deprecated SSL cipher, this list was tested against SSL Labs (https://www.ssllabs.com/ssltest/) +# ciphers: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ,TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 ,TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 ,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,TLS_RSA_WITH_CAMELLIA_256_CBC_SHA,TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA,TLS_RSA_WITH_CAMELLIA_128_CBC_SHA # =================================================================== server: port: 8082 + shutdown: graceful # see https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-graceful-shutdown compression: enabled: true mime-types: text/html,text/xml,text/plain,text/css, application/javascript, application/json @@ -122,12 +103,6 @@ jhipster: http: cache: # Used by the CachingHttpHeadersFilter timeToLiveInDays: 1461 - mail: # specific JHipster mail property, for standard properties see MailProperties - base-url: http://my-server-url-to-change # Modify according to your server's URL - metrics: - logs: # Reports metrics in the logs - enabled: false - report-frequency: 60 # in seconds logging: use-json-format: false # By default, logs are not in Json format logstash: # Forward logs to logstash over a socket, used by LoggingConfiguration @@ -135,9 +110,6 @@ jhipster: host: localhost port: 5000 queue-size: 512 - audit-events: - retention-period: 30 # Number of days before audit events are deleted. - # =================================================================== # Application specific properties # Add your own application properties here, see the ApplicationProperties class diff --git a/src/main/resources/config/application.yml b/src/main/resources/config/application.yml index 8a75c26..190b950 100644 --- a/src/main/resources/config/application.yml +++ b/src/main/resources/config/application.yml @@ -18,10 +18,10 @@ feign: hystrix: enabled: true # client: - # config: - # default: - # connectTimeout: 5000 - # readTimeout: 5000 + # config: + # default: + # connectTimeout: 5000 + # readTimeout: 5000 # See https://github.com/Netflix/Hystrix/wiki/Configuration hystrix: @@ -32,7 +32,7 @@ hystrix: strategy: SEMAPHORE # See https://github.com/spring-cloud/spring-cloud-netflix/issues/1330 # thread: - # timeoutInMilliseconds: 10000 + # timeoutInMilliseconds: 10000 shareSecurityContext: true management: @@ -40,17 +40,24 @@ management: web: base-path: /management exposure: - include: ['configprops', 'env', 'health', 'info', 'jhimetrics', 'logfile', 'loggers', 'prometheus', 'threaddump'] + include: ['configprops', 'env', 'health', 'info', 'jhimetrics', 'logfile', 'loggers', 'prometheus', 'threaddump', 'liquibase'] endpoint: health: - show-details: when-authorized + show-details: when_authorized roles: 'ROLE_ADMIN' + probes: + enabled: true jhimetrics: enabled: true info: git: mode: full health: + group: + liveness: + include: livenessState + readiness: + include: readinessState,datasource mail: enabled: false # When using the MailService, configure an SMTP server and set this to true metrics: @@ -74,7 +81,9 @@ management: application: ${spring.application.name} web: server: - auto-time-requests: true + request: + autotime: + enabled: true spring: application: @@ -88,6 +97,17 @@ spring: config: watch: enabled: false + profiles: + # The commented value for `active` can be replaced with valid Spring profiles to load. + # Otherwise, it will be filled in by maven when building the JAR file + # Either way, it can be overridden by `--spring.profiles.active` value passed in the commandline or `-Dspring.profiles.active` set in `JAVA_OPTS` + active: #spring.profiles.active# + group: + dev: + - dev + - api-docs + # Uncomment to activate TLS for the dev profile + #- tls jmx: enabled: false data: @@ -98,6 +118,17 @@ spring: open-in-view: false properties: hibernate.jdbc.time_zone: UTC + hibernate.id.new_generator_mappings: true + hibernate.connection.provider_disables_autocommit: true + hibernate.cache.use_second_level_cache: false + hibernate.cache.use_query_cache: false + hibernate.generate_statistics: false + # modify batch size as necessary + hibernate.jdbc.batch_size: 25 + hibernate.order_inserts: true + hibernate.order_updates: true + hibernate.query.fail_on_pagination_over_collection_fetch: true + hibernate.query.in_clause_parameter_padding: true hibernate: ddl-auto: none naming: @@ -107,9 +138,6 @@ spring: basename: i18n/messages main: allow-bean-definition-overriding: true - mvc: - favicon: - enabled: false task: execution: thread-name-prefix: case-management-task- @@ -136,6 +164,7 @@ spring: oidc: client-id: internal client-secret: internal + scope: openid,profile,email server: servlet: @@ -159,16 +188,17 @@ jhipster: name: 'caseManagementApp' # By default CORS is disabled. Uncomment to enable. # cors: - # allowed-origins: "*" - # allowed-methods: "*" - # allowed-headers: "*" - # exposed-headers: "Authorization,Link,X-Total-Count" - # allow-credentials: true - # max-age: 1800 + # allowed-origins: "*" + # allowed-methods: "*" + # allowed-headers: "*" + # exposed-headers: "Authorization,Link,X-Total-Count,X-${jhipster.clientApp.name}-alert,X-${jhipster.clientApp.name}-error,X-${jhipster.clientApp.name}-params" + # allow-credentials: true + # max-age: 1800 mail: from: caseManagement@localhost - swagger: - default-include-pattern: /api/.* + api-docs: + default-include-pattern: ${server.servlet.context-path:}/api/.* + management-include-pattern: ${server.servlet.context-path:}/management/.* title: caseManagement API description: caseManagement API documentation version: 0.0.1 @@ -176,7 +206,7 @@ jhipster: contact-name: contact-url: contact-email: - license: + license: unlicensed license-url: security: oauth2: diff --git a/src/main/resources/config/bootstrap.yml b/src/main/resources/config/bootstrap.yml index a4bdb71..86a1c73 100644 --- a/src/main/resources/config/bootstrap.yml +++ b/src/main/resources/config/bootstrap.yml @@ -24,5 +24,7 @@ spring: - git-version=${git.commit.id.describe:} - git-commit=${git.commit.id.abbrev:} - git-branch=${git.branch:} + - context-path=${server.servlet.context-path:} + host: localhost port: 8500 diff --git a/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml b/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml index b1ac55b..7468f74 100644 --- a/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml +++ b/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml @@ -3,10 +3,12 @@ xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd"> - + + + - + - + @@ -34,15 +32,12 @@ - + - + - - - - + + - diff --git a/src/main/resources/config/liquibase/changelog/20201108111723_added_entity_constraints_AccessToken.xml b/src/main/resources/config/liquibase/changelog/20201108111723_added_entity_constraints_AccessToken.xml index 52da1c6..a244886 100644 --- a/src/main/resources/config/liquibase/changelog/20201108111723_added_entity_constraints_AccessToken.xml +++ b/src/main/resources/config/liquibase/changelog/20201108111723_added_entity_constraints_AccessToken.xml @@ -2,7 +2,7 @@ + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd"> @@ -10,13 +10,13 @@ diff --git a/src/main/resources/config/liquibase/changelog/20201108112112_added_entity_TicketSystemInstance.xml b/src/main/resources/config/liquibase/changelog/20201108112112_added_entity_TicketSystemInstance.xml index b414c01..d690e47 100644 --- a/src/main/resources/config/liquibase/changelog/20201108112112_added_entity_TicketSystemInstance.xml +++ b/src/main/resources/config/liquibase/changelog/20201108112112_added_entity_TicketSystemInstance.xml @@ -3,17 +3,15 @@ xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd"> - - - + @@ -34,14 +32,11 @@ - + - - - - + + - diff --git a/src/main/resources/config/liquibase/fake-data/access_token.csv b/src/main/resources/config/liquibase/fake-data/access_token.csv index 04e0d8c..12a6179 100644 --- a/src/main/resources/config/liquibase/fake-data/access_token.csv +++ b/src/main/resources/config/liquibase/fake-data/access_token.csv @@ -1,11 +1,11 @@ id;token;expiration_date;salt;refresh_token -1;Operations;2020-11-07T17:45:42;e-services Toys;Forge -2;protocol;2020-11-07T11:29:33;Customer didactic;Cotton auxiliary web services -3;reboot;2020-11-07T18:33:57;harness matrix;Frozen -4;Credit Card Account;2020-11-08T02:18:42;firewall asymmetric payment;Data Awesome Granite Chips -5;firmware bypassing;2020-11-07T11:45:18;Nevada Gloves;utilize -6;Technician Alaska;2020-11-08T04:15:28;Hawaii;Analyst Music Health -7;bypass executive;2020-11-08T04:35:03;Bedfordshire Unbranded Cotton Salad Sleek;architecture -8;array hard drive;2020-11-07T17:22:21;Legacy quantify;Washington Handmade Soft Mouse -9;Small Oregon;2020-11-07T22:36:36;content;connecting -10;Data;2020-11-07T19:52:23;Configuration deposit Intranet;solution +1;Operations;2020-11-08T01:53:19;access violet;protocol +2;Customer stable;2020-11-08T01:43:10;Keyboard International harness;Music Soap drive +3;asymmetric Account;2020-11-07T15:56:08;Liechtenstein;firmware Frozen +4;China Tools;2020-11-07T20:25:12;payment 24/7 azure;Music Mills +5;executive;2020-11-07T18:57:42;Business-focused ubiquitous;Soap bricks-and-clicks +6;ubiquitous quantify Washington;2020-11-07T17:36:57;Hat Toys French;Keyboard bluetooth +7;quantifying Intranet solution;2020-11-08T06:28:06;Health application;Strategist Swaziland +8;USB CSS;2020-11-07T17:33:18;Executive;connect Pre-emptive Analyst +9;Interface Brand Market;2020-11-07T23:34:50;project purple Ergonomic;black Som Strategist +10;technologies Movies dot-com;2020-11-08T00:58:17;e-business;Frozen synergies Bosnia diff --git a/src/main/resources/config/liquibase/fake-data/ticket_system_instance.csv b/src/main/resources/config/liquibase/fake-data/ticket_system_instance.csv index bc5b78b..c13c852 100644 --- a/src/main/resources/config/liquibase/fake-data/ticket_system_instance.csv +++ b/src/main/resources/config/liquibase/fake-data/ticket_system_instance.csv @@ -1,11 +1,11 @@ id;name;type;url;consumer_key;client_id;client_secret -1;Indiana Intelligent Soft Bacon;JIRA;https://andreanne.net;Chips Grocery groupware;override Bermuda;Sausages bypass -2;transmit;JIRA;https://jalen.net;navigate tan;1080p Gorgeous Granite Tuna;extend e-services 24/7 -3;Jewelery;JIRA;https://emmanuel.org;Valleys Grocery New Mexico;Surinam Dollar Handmade Frozen Bacon;Forward cross-platform Guarani -4;Regional;JIRA;http://anabelle.com;knowledge base Administrator;e-commerce e-tailers;Borders Internal -5;JSON Intelligent;JIRA;https://caterina.biz;rich invoice;back up;Dynamic -6;Cambridgeshire utilize Haiti;JIRA;https://rhett.com;Plain Credit Card Account COM;New Caledonia cyan Venezuela;magnetic invoice Music -7;Tools;JIRA;http://aditya.com;infomediaries productivity;sticky Tuna;Buckinghamshire Enterprise-wide Automotive -8;Consultant;JIRA;https://luigi.name;Home protocol Handcrafted Metal Chicken;revolutionary;Music -9;COM deposit Steel;JIRA;https://elroy.net;Intelligent Rubber Shoes Swiss Franc;Nepalese Rupee experiences Legacy;solutions mobile -10;quantifying indigo;JIRA;http://sheila.org;New Israeli Sheqel Administrator functionalities;Awesome Granite Bacon Borders seamless;Pakistan Progressive +1;Indiana Cheese;JIRA;http://greyson.name;Oklahoma circuit;Grocery grow Hat;Sausages Small +2;Pants Versatile Sheqel;JIRA;http://johathan.info;protocol Niue extend;24/7;calculate +3;invoice;JIRA;http://tate.net;Lead Mexico;bypass Assistant Forward;Zloty Regional +4;killer Chips Car;JIRA;http://keenan.org;Massachusetts e-tailers;Cambridgeshire exploit;Bacon +5;Soft embrace;JIRA;http://johan.info;invoice back Dynamic;Coordinator Haiti;Borders up Plain +6;COM New EXE;JIRA;http://ila.name;Music Tools Practical;Books;Dalasi withdrawal +7;invoice;JIRA;http://darby.com;Virtual archive Investor;driver protocol;payment revolutionary Music +8;North Steel dynamic;JIRA;http://maxime.biz;Checking Missouri Salad;services connect;mobile +9;deposit open-source asynchronous;JIRA;http://amparo.org;SMTP Administrator Chief;Street Market;Pakistan Argentine +10;visualize open-source;JIRA;https://weldon.net;composite monetize;innovative Locks;invoice Reactive driver diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml index 93aded6..688f643 100644 --- a/src/main/resources/config/liquibase/master.xml +++ b/src/main/resources/config/liquibase/master.xml @@ -2,16 +2,17 @@ + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd"> - - + + + @@ -19,4 +20,5 @@ + diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties index 3d8dcf1..5fb5c54 100644 --- a/src/main/resources/i18n/messages.properties +++ b/src/main/resources/i18n/messages.properties @@ -4,18 +4,3 @@ error.subtitle=Sorry, an error has occurred. error.status=Status: error.message=Message: -# Activation email -email.activation.title=caseManagement account activation is required -email.activation.greeting=Dear {0} -email.activation.text1=Your caseManagement account has been created, please click on the URL below to activate it: -email.activation.text2=Regards, -email.signature=caseManagement Team. - -# Creation email -email.creation.text1=Your caseManagement account has been created, please click on the URL below to access it: - -# Reset email -email.reset.title=caseManagement password reset -email.reset.greeting=Dear {0} -email.reset.text1=For your caseManagement account a password reset was requested, please click on the URL below to reset it: -email.reset.text2=Regards, diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index 0377650..279be5e 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -59,6 +59,7 @@ + diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index 68659ba..6ff911e 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -1,103 +1,130 @@ - - - + + + JHipster microservice homepage - - -

-

Welcome, Java Hipster!

- -

This application is a microservice, which has been generated using JHipster.

- -
    -
  • It does not have a front-end. The front-end should be generated on a JHipster gateway
  • + + +
    +

    Welcome, Java Hipster!

    + +

    + This application is a microservice, which has been generated using + JHipster. +

    + +
      +
    • It does not have a front-end. The front-end should be generated on a JHipster gateway.
    • It is serving REST APIs, under the '/api' URLs.
    • -
    • Swagger documentation endpoint for those APIs is at /v2/api-docs, but if you want access to the full Swagger UI, you should use a JHipster gateway, which will serve as an API developer portal
    • -
    - -

    - If you have any question on JHipster: -

    - - - -

    - If you like JHipster, don't forget to give us a star on GitHub! -

    - -
    - +
  • + To manage this microservice, you will probably want to use the + JHipster Registry: +
      +
    • + To run the JHipster Registry locally, you can use Docker:
      docker-compose -f src/main/docker/jhipster-registry.yml up -d +
    • +
    • + Its default URL is http://localhost:8761/ and its default login/password is + admin/admin +
    • +
    +
  • +
  • + OpenAPI documentation endpoint for those APIs is at /v3/api-docs, but if you want access to the full + Swagger UI, you should use a JHipster gateway or a JHipster Registry, which will serve as API developer portals. +
  • +
+ +

If you have any question on JHipster:

+ + + +

+ If you like JHipster, don't forget to give us a star on + GitHub! +

+
+ diff --git a/src/main/resources/templates/error.html b/src/main/resources/templates/error.html index b703488..690e856 100644 --- a/src/main/resources/templates/error.html +++ b/src/main/resources/templates/error.html @@ -1,87 +1,92 @@ - - - - + + + + Your request cannot be processed - - -
-

Your request cannot be processed :(

+ + +
+

Your request cannot be processed :(

-

Sorry, an error has occurred.

+

Sorry, an error has occurred.

- Status:  ()
- - Message: 
-
-
- + Status:  ()
+ + Message: 
+
+
+ diff --git a/src/test/java/org/securityrat/casemanagement/ArchTest.java b/src/test/java/org/securityrat/casemanagement/ArchTest.java index 56c3f49..188d071 100644 --- a/src/test/java/org/securityrat/casemanagement/ArchTest.java +++ b/src/test/java/org/securityrat/casemanagement/ArchTest.java @@ -1,29 +1,29 @@ package org.securityrat.casemanagement; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; + import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.core.importer.ImportOption; import org.junit.jupiter.api.Test; -import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; - class ArchTest { @Test void servicesAndRepositoriesShouldNotDependOnWebLayer() { - JavaClasses importedClasses = new ClassFileImporter() .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) .importPackages("org.securityrat.casemanagement"); noClasses() .that() - .resideInAnyPackage("..service..") + .resideInAnyPackage("org.securityrat.casemanagement.service..") .or() - .resideInAnyPackage("..repository..") - .should().dependOnClassesThat() - .resideInAnyPackage("..org.securityrat.casemanagement.web..") - .because("Services and repositories should not depend on web layer") - .check(importedClasses); + .resideInAnyPackage("org.securityrat.casemanagement.repository..") + .should() + .dependOnClassesThat() + .resideInAnyPackage("..org.securityrat.casemanagement.web..") + .because("Services and repositories should not depend on web layer") + .check(importedClasses); } } diff --git a/src/test/java/org/securityrat/casemanagement/IntegrationTest.java b/src/test/java/org/securityrat/casemanagement/IntegrationTest.java new file mode 100644 index 0000000..11478e3 --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/IntegrationTest.java @@ -0,0 +1,18 @@ +package org.securityrat.casemanagement; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.securityrat.casemanagement.CaseManagementApp; +import org.securityrat.casemanagement.config.TestSecurityConfiguration; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * Base composite annotation for integration tests. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@SpringBootTest(classes = { CaseManagementApp.class, TestSecurityConfiguration.class }) +public @interface IntegrationTest { +} diff --git a/src/test/java/org/securityrat/casemanagement/config/TestSecurityConfiguration.java b/src/test/java/org/securityrat/casemanagement/config/TestSecurityConfiguration.java index 6d3cd7a..17381d8 100644 --- a/src/test/java/org/securityrat/casemanagement/config/TestSecurityConfiguration.java +++ b/src/test/java/org/securityrat/casemanagement/config/TestSecurityConfiguration.java @@ -1,9 +1,14 @@ package org.securityrat.casemanagement.config; +import static org.mockito.Mockito.mock; + +import java.util.HashMap; +import java.util.Map; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; @@ -13,16 +18,12 @@ import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.jwt.JwtDecoder; -import java.util.HashMap; -import java.util.Map; - -import static org.mockito.Mockito.mock; - /** * This class allows you to run unit and integration tests without an IdP. */ @TestConfiguration public class TestSecurityConfiguration { + private final ClientRegistration clientRegistration; public TestSecurityConfiguration() { @@ -38,8 +39,9 @@ private ClientRegistration.Builder clientRegistration() { Map metadata = new HashMap<>(); metadata.put("end_session_endpoint", "https://jhipster.org/logout"); - return ClientRegistration.withRegistrationId("oidc") - .redirectUriTemplate("{baseUrl}/{action}/oauth2/code/{registrationId}") + return ClientRegistration + .withRegistrationId("oidc") + .redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}") .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .scope("read:user") diff --git a/src/test/java/org/securityrat/casemanagement/config/WebConfigurerTest.java b/src/test/java/org/securityrat/casemanagement/config/WebConfigurerTest.java index 8205a83..de52aa9 100644 --- a/src/test/java/org/securityrat/casemanagement/config/WebConfigurerTest.java +++ b/src/test/java/org/securityrat/casemanagement/config/WebConfigurerTest.java @@ -1,7 +1,16 @@ package org.securityrat.casemanagement.config; -import io.github.jhipster.config.JHipsterConstants; -import io.github.jhipster.config.JHipsterProperties; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.*; +import javax.servlet.*; import org.h2.server.web.WebServlet; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -10,22 +19,13 @@ import org.springframework.mock.web.MockServletContext; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; - -import javax.servlet.*; -import java.util.*; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import tech.jhipster.config.JHipsterConstants; +import tech.jhipster.config.JHipsterProperties; /** * Unit tests for the {@link WebConfigurer} class. */ -public class WebConfigurerTest { +class WebConfigurerTest { private WebConfigurer webConfigurer; @@ -38,10 +38,8 @@ public class WebConfigurerTest { @BeforeEach public void setup() { servletContext = spy(new MockServletContext()); - doReturn(mock(FilterRegistration.Dynamic.class)) - .when(servletContext).addFilter(anyString(), any(Filter.class)); - doReturn(mock(ServletRegistration.Dynamic.class)) - .when(servletContext).addServlet(anyString(), any(Servlet.class)); + doReturn(mock(FilterRegistration.Dynamic.class)).when(servletContext).addFilter(anyString(), any(Filter.class)); + doReturn(mock(ServletRegistration.Dynamic.class)).when(servletContext).addServlet(anyString(), any(Servlet.class)); env = new MockEnvironment(); props = new JHipsterProperties(); @@ -50,37 +48,37 @@ public void setup() { } @Test - public void testStartUpProdServletContext() throws ServletException { + void shouldStartUpProdServletContext() throws ServletException { env.setActiveProfiles(JHipsterConstants.SPRING_PROFILE_PRODUCTION); - webConfigurer.onStartup(servletContext); + assertThatCode(() -> webConfigurer.onStartup(servletContext)).doesNotThrowAnyException(); verify(servletContext, never()).addServlet(eq("H2Console"), any(WebServlet.class)); } @Test - public void testStartUpDevServletContext() throws ServletException { + void shouldStartUpDevServletContext() throws ServletException { env.setActiveProfiles(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT); - webConfigurer.onStartup(servletContext); + assertThatCode(() -> webConfigurer.onStartup(servletContext)).doesNotThrowAnyException(); verify(servletContext).addServlet(eq("H2Console"), any(WebServlet.class)); } @Test - public void testCorsFilterOnApiPath() throws Exception { - props.getCors().setAllowedOrigins(Collections.singletonList("*")); + void shouldCorsFilterOnApiPath() throws Exception { + props.getCors().setAllowedOrigins(Collections.singletonList("other.domain.com")); props.getCors().setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE")); props.getCors().setAllowedHeaders(Collections.singletonList("*")); props.getCors().setMaxAge(1800L); props.getCors().setAllowCredentials(true); - MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new WebConfigurerTestController()) - .addFilters(webConfigurer.corsFilter()) - .build(); + MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new WebConfigurerTestController()).addFilters(webConfigurer.corsFilter()).build(); - mockMvc.perform( - options("/api/test-cors") - .header(HttpHeaders.ORIGIN, "other.domain.com") - .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "POST")) + mockMvc + .perform( + options("/api/test-cors") + .header(HttpHeaders.ORIGIN, "other.domain.com") + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "POST") + ) .andExpect(status().isOk()) .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "other.domain.com")) .andExpect(header().string(HttpHeaders.VARY, "Origin")) @@ -88,58 +86,48 @@ public void testCorsFilterOnApiPath() throws Exception { .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true")) .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "1800")); - mockMvc.perform( - get("/api/test-cors") - .header(HttpHeaders.ORIGIN, "other.domain.com")) + mockMvc + .perform(get("/api/test-cors").header(HttpHeaders.ORIGIN, "other.domain.com")) .andExpect(status().isOk()) .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "other.domain.com")); } @Test - public void testCorsFilterOnOtherPath() throws Exception { + void shouldCorsFilterOnOtherPath() throws Exception { props.getCors().setAllowedOrigins(Collections.singletonList("*")); props.getCors().setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE")); props.getCors().setAllowedHeaders(Collections.singletonList("*")); props.getCors().setMaxAge(1800L); props.getCors().setAllowCredentials(true); - MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new WebConfigurerTestController()) - .addFilters(webConfigurer.corsFilter()) - .build(); + MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new WebConfigurerTestController()).addFilters(webConfigurer.corsFilter()).build(); - mockMvc.perform( - get("/test/test-cors") - .header(HttpHeaders.ORIGIN, "other.domain.com")) + mockMvc + .perform(get("/test/test-cors").header(HttpHeaders.ORIGIN, "other.domain.com")) .andExpect(status().isOk()) .andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); } @Test - public void testCorsFilterDeactivated() throws Exception { + void shouldCorsFilterDeactivatedForNullAllowedOrigins() throws Exception { props.getCors().setAllowedOrigins(null); - MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new WebConfigurerTestController()) - .addFilters(webConfigurer.corsFilter()) - .build(); + MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new WebConfigurerTestController()).addFilters(webConfigurer.corsFilter()).build(); - mockMvc.perform( - get("/api/test-cors") - .header(HttpHeaders.ORIGIN, "other.domain.com")) + mockMvc + .perform(get("/api/test-cors").header(HttpHeaders.ORIGIN, "other.domain.com")) .andExpect(status().isOk()) .andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); } @Test - public void testCorsFilterDeactivated2() throws Exception { + void shouldCorsFilterDeactivatedForEmptyAllowedOrigins() throws Exception { props.getCors().setAllowedOrigins(new ArrayList<>()); - MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new WebConfigurerTestController()) - .addFilters(webConfigurer.corsFilter()) - .build(); + MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new WebConfigurerTestController()).addFilters(webConfigurer.corsFilter()).build(); - mockMvc.perform( - get("/api/test-cors") - .header(HttpHeaders.ORIGIN, "other.domain.com")) + mockMvc + .perform(get("/api/test-cors").header(HttpHeaders.ORIGIN, "other.domain.com")) .andExpect(status().isOk()) .andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); } diff --git a/src/test/java/org/securityrat/casemanagement/config/WebConfigurerTestController.java b/src/test/java/org/securityrat/casemanagement/config/WebConfigurerTestController.java index 54e2715..e5d7ea8 100644 --- a/src/test/java/org/securityrat/casemanagement/config/WebConfigurerTestController.java +++ b/src/test/java/org/securityrat/casemanagement/config/WebConfigurerTestController.java @@ -7,10 +7,8 @@ public class WebConfigurerTestController { @GetMapping("/api/test-cors") - public void testCorsOnApiPath() { - } + public void testCorsOnApiPath() {} @GetMapping("/test/test-cors") - public void testCorsOnOtherPath() { - } + public void testCorsOnOtherPath() {} } diff --git a/src/test/java/org/securityrat/casemanagement/config/timezone/HibernateTimeZoneIT.java b/src/test/java/org/securityrat/casemanagement/config/timezone/HibernateTimeZoneIT.java index 8aa6a63..8a3f378 100644 --- a/src/test/java/org/securityrat/casemanagement/config/timezone/HibernateTimeZoneIT.java +++ b/src/test/java/org/securityrat/casemanagement/config/timezone/HibernateTimeZoneIT.java @@ -1,34 +1,36 @@ package org.securityrat.casemanagement.config.timezone; -import org.securityrat.casemanagement.CaseManagementApp; -import org.securityrat.casemanagement.config.TestSecurityConfiguration; -import org.securityrat.casemanagement.repository.timezone.DateTimeWrapper; -import org.securityrat.casemanagement.repository.timezone.DateTimeWrapperRepository; +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.*; +import java.time.format.DateTimeFormatter; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.securityrat.casemanagement.IntegrationTest; +import org.securityrat.casemanagement.repository.timezone.DateTimeWrapper; +import org.securityrat.casemanagement.repository.timezone.DateTimeWrapperRepository; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.beans.factory.annotation.Value; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.rowset.SqlRowSet; import org.springframework.transaction.annotation.Transactional; -import java.time.*; -import java.time.format.DateTimeFormatter; - -import static java.lang.String.format; -import static org.assertj.core.api.Assertions.assertThat; - /** - * Integration tests for the UTC Hibernate configuration. + * Integration tests for the ZoneId Hibernate configuration. */ -@SpringBootTest(classes = {CaseManagementApp.class, TestSecurityConfiguration.class}) -public class HibernateTimeZoneIT { +@IntegrationTest +class HibernateTimeZoneIT { @Autowired private DateTimeWrapperRepository dateTimeWrapperRepository; + @Autowired private JdbcTemplate jdbcTemplate; + @Value("${spring.jpa.properties.hibernate.jdbc.time_zone:UTC}") + private String zoneId; + private DateTimeWrapper dateTimeWrapper; private DateTimeFormatter dateTimeFormatter; private DateTimeFormatter timeFormatter; @@ -45,21 +47,16 @@ public void setup() { dateTimeWrapper.setOffsetTime(OffsetTime.parse("14:30:00+02:00")); dateTimeWrapper.setLocalDate(LocalDate.parse("2016-09-10")); - dateTimeFormatter = DateTimeFormatter - .ofPattern("yyyy-MM-dd HH:mm:ss.S") - .withZone(ZoneId.of("UTC")); + dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S").withZone(ZoneId.of(zoneId)); - timeFormatter = DateTimeFormatter - .ofPattern("HH:mm:ss") - .withZone(ZoneId.of("UTC")); + timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss").withZone(ZoneId.of(zoneId)); - dateFormatter = DateTimeFormatter - .ofPattern("yyyy-MM-dd"); + dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); } @Test @Transactional - public void storeInstantWithUtcConfigShouldBeStoredOnGMTTimeZone() { + void storeInstantWithZoneIdConfigShouldBeStoredOnGMTTimeZone() { dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); String request = generateSqlRequest("instant", dateTimeWrapper.getId()); @@ -71,50 +68,43 @@ public void storeInstantWithUtcConfigShouldBeStoredOnGMTTimeZone() { @Test @Transactional - public void storeLocalDateTimeWithUtcConfigShouldBeStoredOnGMTTimeZone() { + void storeLocalDateTimeWithZoneIdConfigShouldBeStoredOnGMTTimeZone() { dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); String request = generateSqlRequest("local_date_time", dateTimeWrapper.getId()); SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); - String expectedValue = dateTimeWrapper - .getLocalDateTime() - .atZone(ZoneId.systemDefault()) - .format(dateTimeFormatter); + String expectedValue = dateTimeWrapper.getLocalDateTime().atZone(ZoneId.systemDefault()).format(dateTimeFormatter); assertThatDateStoredValueIsEqualToInsertDateValueOnGMTTimeZone(resultSet, expectedValue); } @Test @Transactional - public void storeOffsetDateTimeWithUtcConfigShouldBeStoredOnGMTTimeZone() { + void storeOffsetDateTimeWithZoneIdConfigShouldBeStoredOnGMTTimeZone() { dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); String request = generateSqlRequest("offset_date_time", dateTimeWrapper.getId()); SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); - String expectedValue = dateTimeWrapper - .getOffsetDateTime() - .format(dateTimeFormatter); + String expectedValue = dateTimeWrapper.getOffsetDateTime().format(dateTimeFormatter); assertThatDateStoredValueIsEqualToInsertDateValueOnGMTTimeZone(resultSet, expectedValue); } @Test @Transactional - public void storeZoneDateTimeWithUtcConfigShouldBeStoredOnGMTTimeZone() { + void storeZoneDateTimeWithZoneIdConfigShouldBeStoredOnGMTTimeZone() { dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); String request = generateSqlRequest("zoned_date_time", dateTimeWrapper.getId()); SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); - String expectedValue = dateTimeWrapper - .getZonedDateTime() - .format(dateTimeFormatter); + String expectedValue = dateTimeWrapper.getZonedDateTime().format(dateTimeFormatter); assertThatDateStoredValueIsEqualToInsertDateValueOnGMTTimeZone(resultSet, expectedValue); } @Test @Transactional - public void storeLocalTimeWithUtcConfigShouldBeStoredOnGMTTimeZoneAccordingToHis1stJan1970Value() { + void storeLocalTimeWithZoneIdConfigShouldBeStoredOnGMTTimeZoneAccordingToHis1stJan1970Value() { dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); String request = generateSqlRequest("local_time", dateTimeWrapper.getId()); @@ -130,7 +120,7 @@ public void storeLocalTimeWithUtcConfigShouldBeStoredOnGMTTimeZoneAccordingToHis @Test @Transactional - public void storeOffsetTimeWithUtcConfigShouldBeStoredOnGMTTimeZoneAccordingToHis1stJan1970Value() { + void storeOffsetTimeWithZoneIdConfigShouldBeStoredOnGMTTimeZoneAccordingToHis1stJan1970Value() { dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); String request = generateSqlRequest("offset_time", dateTimeWrapper.getId()); @@ -147,14 +137,12 @@ public void storeOffsetTimeWithUtcConfigShouldBeStoredOnGMTTimeZoneAccordingToHi @Test @Transactional - public void storeLocalDateWithUtcConfigShouldBeStoredWithoutTransformation() { + void storeLocalDateWithZoneIdConfigShouldBeStoredWithoutTransformation() { dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); String request = generateSqlRequest("local_date", dateTimeWrapper.getId()); SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); - String expectedValue = dateTimeWrapper - .getLocalDate() - .format(dateFormatter); + String expectedValue = dateTimeWrapper.getLocalDate().format(dateFormatter); assertThatDateStoredValueIsEqualToInsertDateValueOnGMTTimeZone(resultSet, expectedValue); } diff --git a/src/test/java/org/securityrat/casemanagement/domain/AccessTokenTest.java b/src/test/java/org/securityrat/casemanagement/domain/AccessTokenTest.java index a30fb99..dea261e 100644 --- a/src/test/java/org/securityrat/casemanagement/domain/AccessTokenTest.java +++ b/src/test/java/org/securityrat/casemanagement/domain/AccessTokenTest.java @@ -1,13 +1,14 @@ package org.securityrat.casemanagement.domain; -import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; import org.securityrat.casemanagement.web.rest.TestUtil; -public class AccessTokenTest { +class AccessTokenTest { @Test - public void equalsVerifier() throws Exception { + void equalsVerifier() throws Exception { TestUtil.equalsVerifier(AccessToken.class); AccessToken accessToken1 = new AccessToken(); accessToken1.setId(1L); diff --git a/src/test/java/org/securityrat/casemanagement/domain/TicketSystemInstanceTest.java b/src/test/java/org/securityrat/casemanagement/domain/TicketSystemInstanceTest.java index eb964c8..8d8b7e2 100644 --- a/src/test/java/org/securityrat/casemanagement/domain/TicketSystemInstanceTest.java +++ b/src/test/java/org/securityrat/casemanagement/domain/TicketSystemInstanceTest.java @@ -1,13 +1,14 @@ package org.securityrat.casemanagement.domain; -import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; import org.securityrat.casemanagement.web.rest.TestUtil; -public class TicketSystemInstanceTest { +class TicketSystemInstanceTest { @Test - public void equalsVerifier() throws Exception { + void equalsVerifier() throws Exception { TestUtil.equalsVerifier(TicketSystemInstance.class); TicketSystemInstance ticketSystemInstance1 = new TicketSystemInstance(); ticketSystemInstance1.setId(1L); diff --git a/src/test/java/org/securityrat/casemanagement/repository/CustomAuditEventRepositoryIT.java b/src/test/java/org/securityrat/casemanagement/repository/CustomAuditEventRepositoryIT.java deleted file mode 100644 index 785d95b..0000000 --- a/src/test/java/org/securityrat/casemanagement/repository/CustomAuditEventRepositoryIT.java +++ /dev/null @@ -1,164 +0,0 @@ -package org.securityrat.casemanagement.repository; - -import org.securityrat.casemanagement.CaseManagementApp; -import org.securityrat.casemanagement.config.Constants; -import org.securityrat.casemanagement.config.TestSecurityConfiguration; -import org.securityrat.casemanagement.config.audit.AuditEventConverter; -import org.securityrat.casemanagement.domain.PersistentAuditEvent; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.audit.AuditEvent; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.web.authentication.WebAuthenticationDetails; -import org.springframework.transaction.annotation.Transactional; - -import javax.servlet.http.HttpSession; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.securityrat.casemanagement.repository.CustomAuditEventRepository.EVENT_DATA_COLUMN_MAX_LENGTH; - -/** - * Integration tests for {@link CustomAuditEventRepository}. - */ -@SpringBootTest(classes = {CaseManagementApp.class, TestSecurityConfiguration.class}) -@Transactional -public class CustomAuditEventRepositoryIT { - - @Autowired - private PersistenceAuditEventRepository persistenceAuditEventRepository; - - @Autowired - private AuditEventConverter auditEventConverter; - - private CustomAuditEventRepository customAuditEventRepository; - - private PersistentAuditEvent testUserEvent; - - private PersistentAuditEvent testOtherUserEvent; - - private PersistentAuditEvent testOldUserEvent; - - @BeforeEach - public void setup() { - customAuditEventRepository = new CustomAuditEventRepository(persistenceAuditEventRepository, auditEventConverter); - persistenceAuditEventRepository.deleteAll(); - Instant oneHourAgo = Instant.now().minusSeconds(3600); - - testUserEvent = new PersistentAuditEvent(); - testUserEvent.setPrincipal("test-user"); - testUserEvent.setAuditEventType("test-type"); - testUserEvent.setAuditEventDate(oneHourAgo); - Map data = new HashMap<>(); - data.put("test-key", "test-value"); - testUserEvent.setData(data); - - testOldUserEvent = new PersistentAuditEvent(); - testOldUserEvent.setPrincipal("test-user"); - testOldUserEvent.setAuditEventType("test-type"); - testOldUserEvent.setAuditEventDate(oneHourAgo.minusSeconds(10000)); - - testOtherUserEvent = new PersistentAuditEvent(); - testOtherUserEvent.setPrincipal("other-test-user"); - testOtherUserEvent.setAuditEventType("test-type"); - testOtherUserEvent.setAuditEventDate(oneHourAgo); - } - - @Test - public void addAuditEvent() { - Map data = new HashMap<>(); - data.put("test-key", "test-value"); - AuditEvent event = new AuditEvent("test-user", "test-type", data); - customAuditEventRepository.add(event); - List persistentAuditEvents = persistenceAuditEventRepository.findAll(); - assertThat(persistentAuditEvents).hasSize(1); - PersistentAuditEvent persistentAuditEvent = persistentAuditEvents.get(0); - assertThat(persistentAuditEvent.getPrincipal()).isEqualTo(event.getPrincipal()); - assertThat(persistentAuditEvent.getAuditEventType()).isEqualTo(event.getType()); - assertThat(persistentAuditEvent.getData()).containsKey("test-key"); - assertThat(persistentAuditEvent.getData().get("test-key")).isEqualTo("test-value"); - assertThat(persistentAuditEvent.getAuditEventDate().truncatedTo(ChronoUnit.MILLIS)) - .isEqualTo(event.getTimestamp().truncatedTo(ChronoUnit.MILLIS)); - } - - @Test - public void addAuditEventTruncateLargeData() { - Map data = new HashMap<>(); - StringBuilder largeData = new StringBuilder(); - for (int i = 0; i < EVENT_DATA_COLUMN_MAX_LENGTH + 10; i++) { - largeData.append("a"); - } - data.put("test-key", largeData); - AuditEvent event = new AuditEvent("test-user", "test-type", data); - customAuditEventRepository.add(event); - List persistentAuditEvents = persistenceAuditEventRepository.findAll(); - assertThat(persistentAuditEvents).hasSize(1); - PersistentAuditEvent persistentAuditEvent = persistentAuditEvents.get(0); - assertThat(persistentAuditEvent.getPrincipal()).isEqualTo(event.getPrincipal()); - assertThat(persistentAuditEvent.getAuditEventType()).isEqualTo(event.getType()); - assertThat(persistentAuditEvent.getData()).containsKey("test-key"); - String actualData = persistentAuditEvent.getData().get("test-key"); - assertThat(actualData.length()).isEqualTo(EVENT_DATA_COLUMN_MAX_LENGTH); - assertThat(actualData).isSubstringOf(largeData); - assertThat(persistentAuditEvent.getAuditEventDate().truncatedTo(ChronoUnit.MILLIS)) - .isEqualTo(event.getTimestamp().truncatedTo(ChronoUnit.MILLIS)); - } - - @Test - public void testAddEventWithWebAuthenticationDetails() { - HttpSession session = new MockHttpSession(null, "test-session-id"); - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setSession(session); - request.setRemoteAddr("1.2.3.4"); - WebAuthenticationDetails details = new WebAuthenticationDetails(request); - Map data = new HashMap<>(); - data.put("test-key", details); - AuditEvent event = new AuditEvent("test-user", "test-type", data); - customAuditEventRepository.add(event); - List persistentAuditEvents = persistenceAuditEventRepository.findAll(); - assertThat(persistentAuditEvents).hasSize(1); - PersistentAuditEvent persistentAuditEvent = persistentAuditEvents.get(0); - assertThat(persistentAuditEvent.getData().get("remoteAddress")).isEqualTo("1.2.3.4"); - assertThat(persistentAuditEvent.getData().get("sessionId")).isEqualTo("test-session-id"); - } - - @Test - public void testAddEventWithNullData() { - Map data = new HashMap<>(); - data.put("test-key", null); - AuditEvent event = new AuditEvent("test-user", "test-type", data); - customAuditEventRepository.add(event); - List persistentAuditEvents = persistenceAuditEventRepository.findAll(); - assertThat(persistentAuditEvents).hasSize(1); - PersistentAuditEvent persistentAuditEvent = persistentAuditEvents.get(0); - assertThat(persistentAuditEvent.getData().get("test-key")).isEqualTo("null"); - } - - @Test - public void addAuditEventWithAnonymousUser() { - Map data = new HashMap<>(); - data.put("test-key", "test-value"); - AuditEvent event = new AuditEvent(Constants.ANONYMOUS_USER, "test-type", data); - customAuditEventRepository.add(event); - List persistentAuditEvents = persistenceAuditEventRepository.findAll(); - assertThat(persistentAuditEvents).hasSize(0); - } - - @Test - public void addAuditEventWithAuthorizationFailureType() { - Map data = new HashMap<>(); - data.put("test-key", "test-value"); - AuditEvent event = new AuditEvent("test-user", "AUTHORIZATION_FAILURE", data); - customAuditEventRepository.add(event); - List persistentAuditEvents = persistenceAuditEventRepository.findAll(); - assertThat(persistentAuditEvents).hasSize(0); - } - -} diff --git a/src/test/java/org/securityrat/casemanagement/repository/timezone/DateTimeWrapper.java b/src/test/java/org/securityrat/casemanagement/repository/timezone/DateTimeWrapper.java index 6dc59bf..f083c61 100644 --- a/src/test/java/org/securityrat/casemanagement/repository/timezone/DateTimeWrapper.java +++ b/src/test/java/org/securityrat/casemanagement/repository/timezone/DateTimeWrapper.java @@ -1,9 +1,9 @@ package org.securityrat.casemanagement.repository.timezone; -import javax.persistence.*; import java.io.Serializable; import java.time.*; import java.util.Objects; +import javax.persistence.*; @Entity @Table(name = "jhi_date_time_wrapper") @@ -12,7 +12,8 @@ public class DateTimeWrapper implements Serializable { private static final long serialVersionUID = 1L; @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator") + @SequenceGenerator(name = "sequenceGenerator") private Long id; @Column(name = "instant") @@ -118,6 +119,7 @@ public int hashCode() { return Objects.hashCode(getId()); } + // prettier-ignore @Override public String toString() { return "TimeZoneTest{" + diff --git a/src/test/java/org/securityrat/casemanagement/repository/timezone/DateTimeWrapperRepository.java b/src/test/java/org/securityrat/casemanagement/repository/timezone/DateTimeWrapperRepository.java index 699bbc3..14894b6 100644 --- a/src/test/java/org/securityrat/casemanagement/repository/timezone/DateTimeWrapperRepository.java +++ b/src/test/java/org/securityrat/casemanagement/repository/timezone/DateTimeWrapperRepository.java @@ -7,6 +7,4 @@ * Spring Data JPA repository for the {@link DateTimeWrapper} entity. */ @Repository -public interface DateTimeWrapperRepository extends JpaRepository { - -} +public interface DateTimeWrapperRepository extends JpaRepository {} diff --git a/src/test/java/org/securityrat/casemanagement/security/SecurityUtilsUnitTest.java b/src/test/java/org/securityrat/casemanagement/security/SecurityUtilsUnitTest.java index c2fdf89..80de7ad 100644 --- a/src/test/java/org/securityrat/casemanagement/security/SecurityUtilsUnitTest.java +++ b/src/test/java/org/securityrat/casemanagement/security/SecurityUtilsUnitTest.java @@ -1,5 +1,12 @@ package org.securityrat.casemanagement.security; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames.ID_TOKEN; + +import java.time.Instant; +import java.util.*; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; @@ -11,19 +18,19 @@ import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; import org.springframework.security.oauth2.core.oidc.user.OidcUser; -import java.time.Instant; -import java.util.*; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames.ID_TOKEN; - /** * Test class for the {@link SecurityUtils} utility class. */ -public class SecurityUtilsUnitTest { +class SecurityUtilsUnitTest { + + @BeforeEach + @AfterEach + void cleanup() { + SecurityContextHolder.clearContext(); + } @Test - public void testGetCurrentUserLogin() { + void testGetCurrentUserLogin() { SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("admin", "admin")); SecurityContextHolder.setContext(securityContext); @@ -32,19 +39,18 @@ public void testGetCurrentUserLogin() { } @Test - public void testGetCurrentUserLoginForOAuth2() { + void testGetCurrentUserLoginForOAuth2() { SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); Map claims = new HashMap<>(); - claims.put("groups", "ROLE_USER"); + claims.put("groups", AuthoritiesConstants.USER); claims.put("sub", 123); claims.put("preferred_username", "admin"); - OidcIdToken idToken = new OidcIdToken(ID_TOKEN, Instant.now(), - Instant.now().plusSeconds(60), claims); + OidcIdToken idToken = new OidcIdToken(ID_TOKEN, Instant.now(), Instant.now().plusSeconds(60), claims); Collection authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.USER)); OidcUser user = new DefaultOidcUser(authorities, idToken); - OAuth2AuthenticationToken bla = new OAuth2AuthenticationToken(user, authorities, "oidc"); - securityContext.setAuthentication(bla); + OAuth2AuthenticationToken auth2AuthenticationToken = new OAuth2AuthenticationToken(user, authorities, "oidc"); + securityContext.setAuthentication(auth2AuthenticationToken); SecurityContextHolder.setContext(securityContext); Optional login = SecurityUtils.getCurrentUserLogin(); @@ -53,7 +59,7 @@ public void testGetCurrentUserLoginForOAuth2() { } @Test - public void testIsAuthenticated() { + void testIsAuthenticated() { SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("admin", "admin")); SecurityContextHolder.setContext(securityContext); @@ -62,7 +68,7 @@ public void testIsAuthenticated() { } @Test - public void testAnonymousIsNotAuthenticated() { + void testAnonymousIsNotAuthenticated() { SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); Collection authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ANONYMOUS)); @@ -73,15 +79,14 @@ public void testAnonymousIsNotAuthenticated() { } @Test - public void testIsCurrentUserInRole() { + void testHasCurrentUserThisAuthority() { SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); Collection authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.USER)); securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("user", "user", authorities)); SecurityContextHolder.setContext(securityContext); - assertThat(SecurityUtils.isCurrentUserInRole(AuthoritiesConstants.USER)).isTrue(); - assertThat(SecurityUtils.isCurrentUserInRole(AuthoritiesConstants.ADMIN)).isFalse(); + assertThat(SecurityUtils.hasCurrentUserThisAuthority(AuthoritiesConstants.USER)).isTrue(); + assertThat(SecurityUtils.hasCurrentUserThisAuthority(AuthoritiesConstants.ADMIN)).isFalse(); } - } diff --git a/src/test/java/org/securityrat/casemanagement/security/oauth2/AudienceValidatorTest.java b/src/test/java/org/securityrat/casemanagement/security/oauth2/AudienceValidatorTest.java index efd25ae..782f67c 100644 --- a/src/test/java/org/securityrat/casemanagement/security/oauth2/AudienceValidatorTest.java +++ b/src/test/java/org/securityrat/casemanagement/security/oauth2/AudienceValidatorTest.java @@ -1,27 +1,26 @@ package org.securityrat.casemanagement.security.oauth2; -import org.junit.jupiter.api.Test; -import org.springframework.security.oauth2.jwt.Jwt; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.Test; +import org.springframework.security.oauth2.jwt.Jwt; /** * Test class for the {@link AudienceValidator} utility class. */ -public class AudienceValidatorTest { +class AudienceValidatorTest { - private AudienceValidator validator = new AudienceValidator(Arrays.asList("api://default")); + private final AudienceValidator validator = new AudienceValidator(Arrays.asList("api://default")); @Test @SuppressWarnings("unchecked") - public void testInvalidAudience() { + void testInvalidAudience() { Map claims = new HashMap<>(); claims.put("aud", "bar"); Jwt badJwt = mock(Jwt.class); @@ -31,7 +30,7 @@ public void testInvalidAudience() { @Test @SuppressWarnings("unchecked") - public void testValidAudience() { + void testValidAudience() { Map claims = new HashMap<>(); claims.put("aud", "api://default"); Jwt jwt = mock(Jwt.class); diff --git a/src/test/java/org/securityrat/casemanagement/security/oauth2/AuthorizationHeaderUtilTest.java b/src/test/java/org/securityrat/casemanagement/security/oauth2/AuthorizationHeaderUtilTest.java new file mode 100644 index 0000000..e3f367a --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/security/oauth2/AuthorizationHeaderUtilTest.java @@ -0,0 +1,226 @@ +package org.securityrat.casemanagement.security.oauth2; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.core.*; +import org.springframework.security.oauth2.core.oidc.OidcIdToken; +import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.RestTemplate; + +/** + * Test class for the {@link AuthorizationHeaderUtil} utility class. + */ +class AuthorizationHeaderUtilTest { + + public static final String VALID_REGISTRATION_ID = "OIDC"; + public static final String SUB_VALUE = "123456"; + + @Mock + private OAuth2AuthorizedClientService clientService; + + @Mock + private RestTemplateBuilder restTemplateBuilder; + + @Mock + private SecurityContext securityContext; + + @InjectMocks + private AuthorizationHeaderUtil authorizationHeaderUtil; + + @BeforeEach + public void setup() { + MockitoAnnotations.initMocks(this); + SecurityContextHolder.setContext(securityContext); + + doReturn(restTemplateBuilder).when(restTemplateBuilder).additionalMessageConverters(any(HttpMessageConverter.class)); + doReturn(restTemplateBuilder).when(restTemplateBuilder).errorHandler(any(ResponseErrorHandler.class)); + doReturn(restTemplateBuilder).when(restTemplateBuilder).basicAuthentication(anyString(), anyString()); + } + + @Test + void getAuthorizationHeader_Authentication() { + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken("principal", "credentials"); + doReturn(authenticationToken).when(securityContext).getAuthentication(); + + Optional header = authorizationHeaderUtil.getAuthorizationHeader(); + + Assertions.assertThat(header).isNotNull().isEmpty(); + } + + @Test + void getAuthorizationHeader_JwtAuthentication() { + JwtAuthenticationToken jwtToken = new JwtAuthenticationToken( + new Jwt("tokenVal", Instant.now(), Instant.now().plus(Duration.ofMinutes(3)), Map.of("alg", "HS256"), Map.of("sub", SUB_VALUE)) + ); + doReturn(jwtToken).when(securityContext).getAuthentication(); + + Optional header = authorizationHeaderUtil.getAuthorizationHeader(); + + Assertions.assertThat(header).isNotNull().isNotEmpty().get().isEqualTo("Bearer tokenVal"); + } + + @Test + void getAuthorizationHeader_OAuth2Authentication_InvalidClient() { + OAuth2AuthenticationToken oauth2Token = getTestOAuth2AuthenticationToken("INVALID"); + OAuth2AuthorizedClient authorizedClient = getTestOAuth2AuthorizedClient(); + + doReturn(oauth2Token).when(securityContext).getAuthentication(); + doReturn(authorizedClient).when(clientService).loadAuthorizedClient(eq(VALID_REGISTRATION_ID), eq(SUB_VALUE)); + + Assertions + .assertThatThrownBy( + () -> { + Optional header = authorizationHeaderUtil.getAuthorizationHeader(); + } + ) + .isInstanceOf(OAuth2AuthorizationException.class) + .hasMessageContaining("[access_denied] The token is expired"); + } + + @Test + void getAuthorizationHeader_OAuth2Authentication() { + OAuth2AuthenticationToken oauth2Token = getTestOAuth2AuthenticationToken(VALID_REGISTRATION_ID); + OAuth2AuthorizedClient authorizedClient = getTestOAuth2AuthorizedClient(); + + doReturn(oauth2Token).when(securityContext).getAuthentication(); + doReturn(authorizedClient).when(clientService).loadAuthorizedClient(eq(VALID_REGISTRATION_ID), eq(SUB_VALUE)); + + Optional header = authorizationHeaderUtil.getAuthorizationHeader(); + Assertions.assertThat(header).isNotNull().isNotEmpty().get().isEqualTo("Bearer tokenVal"); + } + + @Test + void getAuthorizationHeader_OAuth2Authentication_RefreshToken() { + OAuth2AuthenticationToken oauth2Token = getTestOAuth2AuthenticationToken(VALID_REGISTRATION_ID); + OAuth2AuthorizedClient authorizedClient = getTestOAuth2AuthorizedClient(true); + + doReturn(oauth2Token).when(securityContext).getAuthentication(); + doReturn(authorizedClient).when(clientService).loadAuthorizedClient(eq(VALID_REGISTRATION_ID), eq(SUB_VALUE)); + + RestTemplate restTemplate = mock(RestTemplate.class); + ResponseEntity refreshResponse = ResponseEntity.of(getTestOAuthIdpTokenResponseDTO(true)); + doReturn(refreshResponse).when(restTemplate).exchange(any(RequestEntity.class), eq(OAuthIdpTokenResponseDTO.class)); + doReturn(restTemplate).when(restTemplateBuilder).build(); + + Optional header = authorizationHeaderUtil.getAuthorizationHeader(); + Assertions.assertThat(header).isNotNull().isNotEmpty().get().isEqualTo("Bearer tokenVal"); + } + + @Test + void getAuthorizationHeader_OAuth2Authentication_RefreshToken_NoRefreshToken() { + OAuth2AuthenticationToken oauth2Token = getTestOAuth2AuthenticationToken(VALID_REGISTRATION_ID); + OAuth2AuthorizedClient authorizedClient = getTestOAuth2AuthorizedClient(true); + + doReturn(oauth2Token).when(securityContext).getAuthentication(); + doReturn(authorizedClient).when(clientService).loadAuthorizedClient(eq(VALID_REGISTRATION_ID), eq(SUB_VALUE)); + + RestTemplate restTemplate = mock(RestTemplate.class); + ResponseEntity refreshResponse = ResponseEntity.of(getTestOAuthIdpTokenResponseDTO(false)); + doReturn(refreshResponse).when(restTemplate).exchange(any(RequestEntity.class), eq(OAuthIdpTokenResponseDTO.class)); + doReturn(restTemplate).when(restTemplateBuilder).build(); + + Optional header = authorizationHeaderUtil.getAuthorizationHeader(); + Assertions.assertThat(header).isNotNull().isNotEmpty().get().isEqualTo("Bearer tokenVal"); + } + + @Test + void getAuthorizationHeader_OAuth2Authentication_RefreshTokenFails() { + OAuth2AuthenticationToken oauth2Token = getTestOAuth2AuthenticationToken(VALID_REGISTRATION_ID); + OAuth2AuthorizedClient authorizedClient = getTestOAuth2AuthorizedClient(true); + + doReturn(oauth2Token).when(securityContext).getAuthentication(); + doReturn(authorizedClient).when(clientService).loadAuthorizedClient(eq(VALID_REGISTRATION_ID), eq(SUB_VALUE)); + + RestTemplate restTemplate = mock(RestTemplate.class); + doThrow(new OAuth2AuthorizationException(new OAuth2Error("E"), "error")) + .when(restTemplate) + .exchange(any(RequestEntity.class), eq(OAuthIdpTokenResponseDTO.class)); + doReturn(restTemplate).when(restTemplateBuilder).build(); + + Assertions + .assertThatThrownBy( + () -> { + Optional header = authorizationHeaderUtil.getAuthorizationHeader(); + } + ) + .isInstanceOf(OAuth2AuthenticationException.class) + .hasMessageContaining("error"); + } + + private OAuth2AuthorizedClient getTestOAuth2AuthorizedClient() { + return getTestOAuth2AuthorizedClient(false); + } + + private OAuth2AuthorizedClient getTestOAuth2AuthorizedClient(boolean accessTokenExpired) { + return new OAuth2AuthorizedClient( + ClientRegistration + .withRegistrationId(VALID_REGISTRATION_ID) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .clientId("web-app") + .clientSecret("secret") + .redirectUriTemplate("/login/oauth2/code/oidc") + .authorizationUri("http://localhost:8080/auth/realms/master/protocol/openid-connect/auth") + .tokenUri("https://localhost:8080/auth/realms/master/protocol/openid-connect/token") + .build(), + "sub", + new OAuth2AccessToken( + OAuth2AccessToken.TokenType.BEARER, + "tokenVal", + Instant.now(), + accessTokenExpired ? Instant.now() : Instant.now().plus(Duration.ofMinutes(3)) + ), + new OAuth2RefreshToken("refreshVal", Instant.now()) + ); + } + + private OAuth2AuthenticationToken getTestOAuth2AuthenticationToken(String registrationId) { + return new OAuth2AuthenticationToken( + new DefaultOidcUser( + List.of(new SimpleGrantedAuthority("USER")), + OidcIdToken.withTokenValue("tokenVal").claim("sub", SUB_VALUE).build() + ), + List.of(new SimpleGrantedAuthority("USER")), + registrationId + ); + } + + private Optional getTestOAuthIdpTokenResponseDTO(boolean hasRefreshToken) { + OAuthIdpTokenResponseDTO dto = new OAuthIdpTokenResponseDTO(); + dto.setAccessToken("tokenVal"); + dto.setIdToken("tokenVal"); + dto.setNotBefore(0l); + dto.setRefreshExpiresIn("1800"); + dto.setSessionState("ccea4a55"); + dto.setExpiresIn(300l); + dto.setRefreshToken(hasRefreshToken ? "tokenVal" : null); + dto.setScope("openid email profile offline_access"); + return Optional.of(dto); + } +} diff --git a/src/test/java/org/securityrat/casemanagement/service/UserServiceIT.java b/src/test/java/org/securityrat/casemanagement/service/UserServiceIT.java index e0f4b72..f9dcb39 100644 --- a/src/test/java/org/securityrat/casemanagement/service/UserServiceIT.java +++ b/src/test/java/org/securityrat/casemanagement/service/UserServiceIT.java @@ -1,17 +1,20 @@ package org.securityrat.casemanagement.service; -import org.securityrat.casemanagement.CaseManagementApp; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.securityrat.casemanagement.IntegrationTest; import org.securityrat.casemanagement.config.Constants; -import org.securityrat.casemanagement.config.TestSecurityConfiguration; import org.securityrat.casemanagement.domain.User; import org.securityrat.casemanagement.repository.UserRepository; import org.securityrat.casemanagement.security.AuthoritiesConstants; -import org.securityrat.casemanagement.service.dto.UserDTO; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.securityrat.casemanagement.service.dto.AdminUserDTO; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -22,19 +25,12 @@ import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.transaction.annotation.Transactional; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - /** * Integration tests for {@link UserService}. */ -@SpringBootTest(classes = {CaseManagementApp.class, TestSecurityConfiguration.class}) +@IntegrationTest @Transactional -public class UserServiceIT { +class UserServiceIT { private static final String DEFAULT_LOGIN = "johndoe"; @@ -79,24 +75,9 @@ public void init() { @Test @Transactional - public void assertThatAnonymousUserIsNotGet() { - user.setId(Constants.ANONYMOUS_USER); - user.setLogin(Constants.ANONYMOUS_USER); - if (!userRepository.findOneByLogin(Constants.ANONYMOUS_USER).isPresent()) { - userRepository.saveAndFlush(user); - } - final PageRequest pageable = PageRequest.of(0, (int) userRepository.count()); - final Page allManagedUsers = userService.getAllManagedUsers(pageable); - assertThat(allManagedUsers.getContent().stream() - .noneMatch(user -> Constants.ANONYMOUS_USER.equals(user.getLogin()))) - .isTrue(); - } - - @Test - @Transactional - public void testDefaultUserDetails() { + void testDefaultUserDetails() { OAuth2AuthenticationToken authentication = createMockOAuth2AuthenticationToken(userDetails); - UserDTO userDTO = userService.getUserFromAuthentication(authentication); + AdminUserDTO userDTO = userService.getUserFromAuthentication(authentication); assertThat(userDTO.getLogin()).isEqualTo(DEFAULT_LOGIN); assertThat(userDTO.getFirstName()).isEqualTo(DEFAULT_FIRSTNAME); @@ -110,63 +91,67 @@ public void testDefaultUserDetails() { @Test @Transactional - public void testUserDetailsWithUsername() { + void testUserDetailsWithUsername() { userDetails.put("preferred_username", "TEST"); OAuth2AuthenticationToken authentication = createMockOAuth2AuthenticationToken(userDetails); - UserDTO userDTO = userService.getUserFromAuthentication(authentication); + AdminUserDTO userDTO = userService.getUserFromAuthentication(authentication); assertThat(userDTO.getLogin()).isEqualTo("test"); } @Test @Transactional - public void testUserDetailsWithLangKey() { + void testUserDetailsWithLangKey() { userDetails.put("langKey", DEFAULT_LANGKEY); userDetails.put("locale", "en-US"); OAuth2AuthenticationToken authentication = createMockOAuth2AuthenticationToken(userDetails); - UserDTO userDTO = userService.getUserFromAuthentication(authentication); + AdminUserDTO userDTO = userService.getUserFromAuthentication(authentication); assertThat(userDTO.getLangKey()).isEqualTo(DEFAULT_LANGKEY); } @Test @Transactional - public void testUserDetailsWithLocale() { + void testUserDetailsWithLocale() { userDetails.put("locale", "it-IT"); OAuth2AuthenticationToken authentication = createMockOAuth2AuthenticationToken(userDetails); - UserDTO userDTO = userService.getUserFromAuthentication(authentication); + AdminUserDTO userDTO = userService.getUserFromAuthentication(authentication); assertThat(userDTO.getLangKey()).isEqualTo("it"); } @Test @Transactional - public void testUserDetailsWithUSLocaleUnderscore() { + void testUserDetailsWithUSLocaleUnderscore() { userDetails.put("locale", "en_US"); OAuth2AuthenticationToken authentication = createMockOAuth2AuthenticationToken(userDetails); - UserDTO userDTO = userService.getUserFromAuthentication(authentication); + AdminUserDTO userDTO = userService.getUserFromAuthentication(authentication); assertThat(userDTO.getLangKey()).isEqualTo("en"); } @Test @Transactional - public void testUserDetailsWithUSLocaleDash() { + void testUserDetailsWithUSLocaleDash() { userDetails.put("locale", "en-US"); OAuth2AuthenticationToken authentication = createMockOAuth2AuthenticationToken(userDetails); - UserDTO userDTO = userService.getUserFromAuthentication(authentication); + AdminUserDTO userDTO = userService.getUserFromAuthentication(authentication); assertThat(userDTO.getLangKey()).isEqualTo("en"); } private OAuth2AuthenticationToken createMockOAuth2AuthenticationToken(Map userDetails) { Collection authorities = Collections.singletonList(new SimpleGrantedAuthority(AuthoritiesConstants.ANONYMOUS)); - UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(Constants.ANONYMOUS_USER, Constants.ANONYMOUS_USER, authorities); + UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken( + "anonymous", + "anonymous", + authorities + ); usernamePasswordAuthenticationToken.setDetails(userDetails); OAuth2User user = new DefaultOAuth2User(authorities, userDetails, "sub"); diff --git a/src/test/java/org/securityrat/casemanagement/service/mapper/UserMapperTest.java b/src/test/java/org/securityrat/casemanagement/service/mapper/UserMapperTest.java index b9f1b56..819f065 100644 --- a/src/test/java/org/securityrat/casemanagement/service/mapper/UserMapperTest.java +++ b/src/test/java/org/securityrat/casemanagement/service/mapper/UserMapperTest.java @@ -1,28 +1,28 @@ package org.securityrat.casemanagement.service.mapper; -import org.securityrat.casemanagement.domain.User; -import org.securityrat.casemanagement.service.dto.UserDTO; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.securityrat.casemanagement.domain.User; +import org.securityrat.casemanagement.service.dto.AdminUserDTO; +import org.securityrat.casemanagement.service.dto.UserDTO; /** * Unit tests for {@link UserMapper}. */ -public class UserMapperTest { +class UserMapperTest { private static final String DEFAULT_LOGIN = "johndoe"; private static final String DEFAULT_ID = "id1"; private UserMapper userMapper; private User user; - private UserDTO userDto; + private AdminUserDTO userDto; @BeforeEach public void init() { @@ -36,68 +36,64 @@ public void init() { user.setImageUrl("image_url"); user.setLangKey("en"); - userDto = new UserDTO(user); + userDto = new AdminUserDTO(user); } @Test - public void usersToUserDTOsShouldMapOnlyNonNullUsers() { + void usersToUserDTOsShouldMapOnlyNonNullUsers() { List users = new ArrayList<>(); users.add(user); users.add(null); List userDTOS = userMapper.usersToUserDTOs(users); - assertThat(userDTOS).isNotEmpty(); - assertThat(userDTOS).size().isEqualTo(1); + assertThat(userDTOS).isNotEmpty().size().isEqualTo(1); } @Test - public void userDTOsToUsersShouldMapOnlyNonNullUsers() { - List usersDto = new ArrayList<>(); + void userDTOsToUsersShouldMapOnlyNonNullUsers() { + List usersDto = new ArrayList<>(); usersDto.add(userDto); usersDto.add(null); List users = userMapper.userDTOsToUsers(usersDto); - assertThat(users).isNotEmpty(); - assertThat(users).size().isEqualTo(1); + assertThat(users).isNotEmpty().size().isEqualTo(1); } @Test - public void userDTOsToUsersWithAuthoritiesStringShouldMapToUsersWithAuthoritiesDomain() { + void userDTOsToUsersWithAuthoritiesStringShouldMapToUsersWithAuthoritiesDomain() { Set authoritiesAsString = new HashSet<>(); authoritiesAsString.add("ADMIN"); userDto.setAuthorities(authoritiesAsString); - List usersDto = new ArrayList<>(); + List usersDto = new ArrayList<>(); usersDto.add(userDto); List users = userMapper.userDTOsToUsers(usersDto); - assertThat(users).isNotEmpty(); - assertThat(users).size().isEqualTo(1); + assertThat(users).isNotEmpty().size().isEqualTo(1); assertThat(users.get(0).getAuthorities()).isNotNull(); assertThat(users.get(0).getAuthorities()).isNotEmpty(); assertThat(users.get(0).getAuthorities().iterator().next().getName()).isEqualTo("ADMIN"); } @Test - public void userDTOsToUsersMapWithNullAuthoritiesStringShouldReturnUserWithEmptyAuthorities() { + void userDTOsToUsersMapWithNullAuthoritiesStringShouldReturnUserWithEmptyAuthorities() { userDto.setAuthorities(null); - List usersDto = new ArrayList<>(); + List usersDto = new ArrayList<>(); usersDto.add(userDto); List users = userMapper.userDTOsToUsers(usersDto); - assertThat(users).isNotEmpty(); - assertThat(users).size().isEqualTo(1); + assertThat(users).isNotEmpty().size().isEqualTo(1); assertThat(users.get(0).getAuthorities()).isNotNull(); assertThat(users.get(0).getAuthorities()).isEmpty(); } @Test - public void userDTOToUserMapWithAuthoritiesStringShouldReturnUserWithAuthorities() { + void userDTOToUserMapWithAuthoritiesStringShouldReturnUserWithAuthorities() { Set authoritiesAsString = new HashSet<>(); authoritiesAsString.add("ADMIN"); userDto.setAuthorities(authoritiesAsString); @@ -111,7 +107,7 @@ public void userDTOToUserMapWithAuthoritiesStringShouldReturnUserWithAuthorities } @Test - public void userDTOToUserMapWithNullAuthoritiesStringShouldReturnUserWithEmptyAuthorities() { + void userDTOToUserMapWithNullAuthoritiesStringShouldReturnUserWithEmptyAuthorities() { userDto.setAuthorities(null); User user = userMapper.userDTOToUser(userDto); @@ -122,12 +118,12 @@ public void userDTOToUserMapWithNullAuthoritiesStringShouldReturnUserWithEmptyAu } @Test - public void userDTOToUserMapWithNullUserShouldReturnNull() { + void userDTOToUserMapWithNullUserShouldReturnNull() { assertThat(userMapper.userDTOToUser(null)).isNull(); } @Test - public void testUserFromId() { + void testUserFromId() { assertThat(userMapper.userFromId(DEFAULT_ID).getId()).isEqualTo(DEFAULT_ID); assertThat(userMapper.userFromId(null)).isNull(); } diff --git a/src/test/java/org/securityrat/casemanagement/web/rest/AccessTokenResourceIT.java b/src/test/java/org/securityrat/casemanagement/web/rest/AccessTokenResourceIT.java index e4f3061..887900d 100644 --- a/src/test/java/org/securityrat/casemanagement/web/rest/AccessTokenResourceIT.java +++ b/src/test/java/org/securityrat/casemanagement/web/rest/AccessTokenResourceIT.java @@ -1,44 +1,40 @@ package org.securityrat.casemanagement.web.rest; -import org.securityrat.casemanagement.CaseManagementApp; -import org.securityrat.casemanagement.config.TestSecurityConfiguration; -import org.securityrat.casemanagement.domain.AccessToken; -import org.securityrat.casemanagement.repository.UserRepository; -import org.securityrat.casemanagement.repository.AccessTokenRepository; -import org.securityrat.casemanagement.web.rest.errors.ExceptionTranslator; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.securityrat.casemanagement.web.rest.TestUtil.sameInstant; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Random; +import java.util.concurrent.atomic.AtomicLong; +import javax.persistence.EntityManager; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.MockitoAnnotations; +import org.securityrat.casemanagement.IntegrationTest; +import org.securityrat.casemanagement.domain.AccessToken; +import org.securityrat.casemanagement.repository.AccessTokenRepository; +import org.securityrat.casemanagement.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.web.PageableHandlerMethodArgumentResolver; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.http.MediaType; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.transaction.annotation.Transactional; -import org.springframework.validation.Validator; - -import javax.persistence.EntityManager; -import java.time.Instant; -import java.time.ZonedDateTime; -import java.time.ZoneOffset; -import java.time.ZoneId; -import java.util.List; - -import static org.securityrat.casemanagement.web.rest.TestUtil.sameInstant; -import static org.securityrat.casemanagement.web.rest.TestUtil.createFormattingConversionService; -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.hasItem; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; /** * Integration tests for the {@link AccessTokenResource} REST controller. */ -@SpringBootTest(classes = {CaseManagementApp.class, TestSecurityConfiguration.class}) -public class AccessTokenResourceIT { +@IntegrationTest +@AutoConfigureMockMvc +@WithMockUser +class AccessTokenResourceIT { private static final String DEFAULT_TOKEN = "AAAAAAAAAA"; private static final String UPDATED_TOKEN = "BBBBBBBBBB"; @@ -52,43 +48,26 @@ public class AccessTokenResourceIT { private static final String DEFAULT_REFRESH_TOKEN = "AAAAAAAAAA"; private static final String UPDATED_REFRESH_TOKEN = "BBBBBBBBBB"; - @Autowired - private AccessTokenRepository accessTokenRepository; + private static final String ENTITY_API_URL = "/api/access-tokens"; + private static final String ENTITY_API_URL_ID = ENTITY_API_URL + "/{id}"; - @Autowired - private UserRepository userRepository; - - @Autowired - private MappingJackson2HttpMessageConverter jacksonMessageConverter; + private static Random random = new Random(); + private static AtomicLong count = new AtomicLong(random.nextInt() + (2 * Integer.MAX_VALUE)); @Autowired - private PageableHandlerMethodArgumentResolver pageableArgumentResolver; + private AccessTokenRepository accessTokenRepository; @Autowired - private ExceptionTranslator exceptionTranslator; + private UserRepository userRepository; @Autowired private EntityManager em; @Autowired - private Validator validator; - private MockMvc restAccessTokenMockMvc; private AccessToken accessToken; - @BeforeEach - public void setup() { - MockitoAnnotations.initMocks(this); - final AccessTokenResource accessTokenResource = new AccessTokenResource(accessTokenRepository, userRepository); - this.restAccessTokenMockMvc = MockMvcBuilders.standaloneSetup(accessTokenResource) - .setCustomArgumentResolvers(pageableArgumentResolver) - .setControllerAdvice(exceptionTranslator) - .setConversionService(createFormattingConversionService()) - .setMessageConverters(jacksonMessageConverter) - .setValidator(validator).build(); - } - /** * Create an entity for this test. * @@ -103,6 +82,7 @@ public static AccessToken createEntity(EntityManager em) { .refreshToken(DEFAULT_REFRESH_TOKEN); return accessToken; } + /** * Create an updated entity for this test. * @@ -125,13 +105,16 @@ public void initTest() { @Test @Transactional - public void createAccessToken() throws Exception { + void createAccessToken() throws Exception { int databaseSizeBeforeCreate = accessTokenRepository.findAll().size(); - // Create the AccessToken - restAccessTokenMockMvc.perform(post("/api/access-tokens") - .contentType(TestUtil.APPLICATION_JSON_UTF8) - .content(TestUtil.convertObjectToJsonBytes(accessToken))) + restAccessTokenMockMvc + .perform( + post(ENTITY_API_URL) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(accessToken)) + ) .andExpect(status().isCreated()); // Validate the AccessToken in the database @@ -146,16 +129,20 @@ public void createAccessToken() throws Exception { @Test @Transactional - public void createAccessTokenWithExistingId() throws Exception { - int databaseSizeBeforeCreate = accessTokenRepository.findAll().size(); - + void createAccessTokenWithExistingId() throws Exception { // Create the AccessToken with an existing ID accessToken.setId(1L); + int databaseSizeBeforeCreate = accessTokenRepository.findAll().size(); + // An entity with an existing ID cannot be created, so this API call must fail - restAccessTokenMockMvc.perform(post("/api/access-tokens") - .contentType(TestUtil.APPLICATION_JSON_UTF8) - .content(TestUtil.convertObjectToJsonBytes(accessToken))) + restAccessTokenMockMvc + .perform( + post(ENTITY_API_URL) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(accessToken)) + ) .andExpect(status().isBadRequest()); // Validate the AccessToken in the database @@ -163,19 +150,22 @@ public void createAccessTokenWithExistingId() throws Exception { assertThat(accessTokenList).hasSize(databaseSizeBeforeCreate); } - @Test @Transactional - public void checkTokenIsRequired() throws Exception { + void checkTokenIsRequired() throws Exception { int databaseSizeBeforeTest = accessTokenRepository.findAll().size(); // set the field null accessToken.setToken(null); // Create the AccessToken, which fails. - restAccessTokenMockMvc.perform(post("/api/access-tokens") - .contentType(TestUtil.APPLICATION_JSON_UTF8) - .content(TestUtil.convertObjectToJsonBytes(accessToken))) + restAccessTokenMockMvc + .perform( + post(ENTITY_API_URL) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(accessToken)) + ) .andExpect(status().isBadRequest()); List accessTokenList = accessTokenRepository.findAll(); @@ -184,16 +174,20 @@ public void checkTokenIsRequired() throws Exception { @Test @Transactional - public void checkSaltIsRequired() throws Exception { + void checkSaltIsRequired() throws Exception { int databaseSizeBeforeTest = accessTokenRepository.findAll().size(); // set the field null accessToken.setSalt(null); // Create the AccessToken, which fails. - restAccessTokenMockMvc.perform(post("/api/access-tokens") - .contentType(TestUtil.APPLICATION_JSON_UTF8) - .content(TestUtil.convertObjectToJsonBytes(accessToken))) + restAccessTokenMockMvc + .perform( + post(ENTITY_API_URL) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(accessToken)) + ) .andExpect(status().isBadRequest()); List accessTokenList = accessTokenRepository.findAll(); @@ -202,31 +196,33 @@ public void checkSaltIsRequired() throws Exception { @Test @Transactional - public void getAllAccessTokens() throws Exception { + void getAllAccessTokens() throws Exception { // Initialize the database accessTokenRepository.saveAndFlush(accessToken); // Get all the accessTokenList - restAccessTokenMockMvc.perform(get("/api/access-tokens?sort=id,desc")) + restAccessTokenMockMvc + .perform(get(ENTITY_API_URL + "?sort=id,desc")) .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(jsonPath("$.[*].id").value(hasItem(accessToken.getId().intValue()))) .andExpect(jsonPath("$.[*].token").value(hasItem(DEFAULT_TOKEN))) .andExpect(jsonPath("$.[*].expirationDate").value(hasItem(sameInstant(DEFAULT_EXPIRATION_DATE)))) .andExpect(jsonPath("$.[*].salt").value(hasItem(DEFAULT_SALT))) .andExpect(jsonPath("$.[*].refreshToken").value(hasItem(DEFAULT_REFRESH_TOKEN))); } - + @Test @Transactional - public void getAccessToken() throws Exception { + void getAccessToken() throws Exception { // Initialize the database accessTokenRepository.saveAndFlush(accessToken); // Get the accessToken - restAccessTokenMockMvc.perform(get("/api/access-tokens/{id}", accessToken.getId())) + restAccessTokenMockMvc + .perform(get(ENTITY_API_URL_ID, accessToken.getId())) .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(jsonPath("$.id").value(accessToken.getId().intValue())) .andExpect(jsonPath("$.token").value(DEFAULT_TOKEN)) .andExpect(jsonPath("$.expirationDate").value(sameInstant(DEFAULT_EXPIRATION_DATE))) @@ -236,15 +232,14 @@ public void getAccessToken() throws Exception { @Test @Transactional - public void getNonExistingAccessToken() throws Exception { + void getNonExistingAccessToken() throws Exception { // Get the accessToken - restAccessTokenMockMvc.perform(get("/api/access-tokens/{id}", Long.MAX_VALUE)) - .andExpect(status().isNotFound()); + restAccessTokenMockMvc.perform(get(ENTITY_API_URL_ID, Long.MAX_VALUE)).andExpect(status().isNotFound()); } @Test @Transactional - public void updateAccessToken() throws Exception { + void putNewAccessToken() throws Exception { // Initialize the database accessTokenRepository.saveAndFlush(accessToken); @@ -260,9 +255,13 @@ public void updateAccessToken() throws Exception { .salt(UPDATED_SALT) .refreshToken(UPDATED_REFRESH_TOKEN); - restAccessTokenMockMvc.perform(put("/api/access-tokens") - .contentType(TestUtil.APPLICATION_JSON_UTF8) - .content(TestUtil.convertObjectToJsonBytes(updatedAccessToken))) + restAccessTokenMockMvc + .perform( + put(ENTITY_API_URL_ID, updatedAccessToken.getId()) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(updatedAccessToken)) + ) .andExpect(status().isOk()); // Validate the AccessToken in the database @@ -277,15 +276,172 @@ public void updateAccessToken() throws Exception { @Test @Transactional - public void updateNonExistingAccessToken() throws Exception { + void putNonExistingAccessToken() throws Exception { int databaseSizeBeforeUpdate = accessTokenRepository.findAll().size(); + accessToken.setId(count.incrementAndGet()); - // Create the AccessToken + // If the entity doesn't have an ID, it will throw BadRequestAlertException + restAccessTokenMockMvc + .perform( + put(ENTITY_API_URL_ID, accessToken.getId()) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(accessToken)) + ) + .andExpect(status().isBadRequest()); + + // Validate the AccessToken in the database + List accessTokenList = accessTokenRepository.findAll(); + assertThat(accessTokenList).hasSize(databaseSizeBeforeUpdate); + } + + @Test + @Transactional + void putWithIdMismatchAccessToken() throws Exception { + int databaseSizeBeforeUpdate = accessTokenRepository.findAll().size(); + accessToken.setId(count.incrementAndGet()); + + // If url ID doesn't match entity ID, it will throw BadRequestAlertException + restAccessTokenMockMvc + .perform( + put(ENTITY_API_URL_ID, count.incrementAndGet()) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(accessToken)) + ) + .andExpect(status().isBadRequest()); + + // Validate the AccessToken in the database + List accessTokenList = accessTokenRepository.findAll(); + assertThat(accessTokenList).hasSize(databaseSizeBeforeUpdate); + } + + @Test + @Transactional + void putWithMissingIdPathParamAccessToken() throws Exception { + int databaseSizeBeforeUpdate = accessTokenRepository.findAll().size(); + accessToken.setId(count.incrementAndGet()); + + // If url ID doesn't match entity ID, it will throw BadRequestAlertException + restAccessTokenMockMvc + .perform( + put(ENTITY_API_URL) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(accessToken)) + ) + .andExpect(status().isMethodNotAllowed()); + + // Validate the AccessToken in the database + List accessTokenList = accessTokenRepository.findAll(); + assertThat(accessTokenList).hasSize(databaseSizeBeforeUpdate); + } + + @Test + @Transactional + void partialUpdateAccessTokenWithPatch() throws Exception { + // Initialize the database + accessTokenRepository.saveAndFlush(accessToken); + + int databaseSizeBeforeUpdate = accessTokenRepository.findAll().size(); + + // Update the accessToken using partial update + AccessToken partialUpdatedAccessToken = new AccessToken(); + partialUpdatedAccessToken.setId(accessToken.getId()); + + partialUpdatedAccessToken.expirationDate(UPDATED_EXPIRATION_DATE).salt(UPDATED_SALT).refreshToken(UPDATED_REFRESH_TOKEN); + + restAccessTokenMockMvc + .perform( + patch(ENTITY_API_URL_ID, partialUpdatedAccessToken.getId()) + .with(csrf()) + .contentType("application/merge-patch+json") + .content(TestUtil.convertObjectToJsonBytes(partialUpdatedAccessToken)) + ) + .andExpect(status().isOk()); + + // Validate the AccessToken in the database + List accessTokenList = accessTokenRepository.findAll(); + assertThat(accessTokenList).hasSize(databaseSizeBeforeUpdate); + AccessToken testAccessToken = accessTokenList.get(accessTokenList.size() - 1); + assertThat(testAccessToken.getToken()).isEqualTo(DEFAULT_TOKEN); + assertThat(testAccessToken.getExpirationDate()).isEqualTo(UPDATED_EXPIRATION_DATE); + assertThat(testAccessToken.getSalt()).isEqualTo(UPDATED_SALT); + assertThat(testAccessToken.getRefreshToken()).isEqualTo(UPDATED_REFRESH_TOKEN); + } + + @Test + @Transactional + void fullUpdateAccessTokenWithPatch() throws Exception { + // Initialize the database + accessTokenRepository.saveAndFlush(accessToken); + + int databaseSizeBeforeUpdate = accessTokenRepository.findAll().size(); + + // Update the accessToken using partial update + AccessToken partialUpdatedAccessToken = new AccessToken(); + partialUpdatedAccessToken.setId(accessToken.getId()); + + partialUpdatedAccessToken + .token(UPDATED_TOKEN) + .expirationDate(UPDATED_EXPIRATION_DATE) + .salt(UPDATED_SALT) + .refreshToken(UPDATED_REFRESH_TOKEN); + + restAccessTokenMockMvc + .perform( + patch(ENTITY_API_URL_ID, partialUpdatedAccessToken.getId()) + .with(csrf()) + .contentType("application/merge-patch+json") + .content(TestUtil.convertObjectToJsonBytes(partialUpdatedAccessToken)) + ) + .andExpect(status().isOk()); + + // Validate the AccessToken in the database + List accessTokenList = accessTokenRepository.findAll(); + assertThat(accessTokenList).hasSize(databaseSizeBeforeUpdate); + AccessToken testAccessToken = accessTokenList.get(accessTokenList.size() - 1); + assertThat(testAccessToken.getToken()).isEqualTo(UPDATED_TOKEN); + assertThat(testAccessToken.getExpirationDate()).isEqualTo(UPDATED_EXPIRATION_DATE); + assertThat(testAccessToken.getSalt()).isEqualTo(UPDATED_SALT); + assertThat(testAccessToken.getRefreshToken()).isEqualTo(UPDATED_REFRESH_TOKEN); + } + + @Test + @Transactional + void patchNonExistingAccessToken() throws Exception { + int databaseSizeBeforeUpdate = accessTokenRepository.findAll().size(); + accessToken.setId(count.incrementAndGet()); // If the entity doesn't have an ID, it will throw BadRequestAlertException - restAccessTokenMockMvc.perform(put("/api/access-tokens") - .contentType(TestUtil.APPLICATION_JSON_UTF8) - .content(TestUtil.convertObjectToJsonBytes(accessToken))) + restAccessTokenMockMvc + .perform( + patch(ENTITY_API_URL_ID, accessToken.getId()) + .with(csrf()) + .contentType("application/merge-patch+json") + .content(TestUtil.convertObjectToJsonBytes(accessToken)) + ) + .andExpect(status().isBadRequest()); + + // Validate the AccessToken in the database + List accessTokenList = accessTokenRepository.findAll(); + assertThat(accessTokenList).hasSize(databaseSizeBeforeUpdate); + } + + @Test + @Transactional + void patchWithIdMismatchAccessToken() throws Exception { + int databaseSizeBeforeUpdate = accessTokenRepository.findAll().size(); + accessToken.setId(count.incrementAndGet()); + + // If url ID doesn't match entity ID, it will throw BadRequestAlertException + restAccessTokenMockMvc + .perform( + patch(ENTITY_API_URL_ID, count.incrementAndGet()) + .with(csrf()) + .contentType("application/merge-patch+json") + .content(TestUtil.convertObjectToJsonBytes(accessToken)) + ) .andExpect(status().isBadRequest()); // Validate the AccessToken in the database @@ -295,15 +451,36 @@ public void updateNonExistingAccessToken() throws Exception { @Test @Transactional - public void deleteAccessToken() throws Exception { + void patchWithMissingIdPathParamAccessToken() throws Exception { + int databaseSizeBeforeUpdate = accessTokenRepository.findAll().size(); + accessToken.setId(count.incrementAndGet()); + + // If url ID doesn't match entity ID, it will throw BadRequestAlertException + restAccessTokenMockMvc + .perform( + patch(ENTITY_API_URL) + .with(csrf()) + .contentType("application/merge-patch+json") + .content(TestUtil.convertObjectToJsonBytes(accessToken)) + ) + .andExpect(status().isMethodNotAllowed()); + + // Validate the AccessToken in the database + List accessTokenList = accessTokenRepository.findAll(); + assertThat(accessTokenList).hasSize(databaseSizeBeforeUpdate); + } + + @Test + @Transactional + void deleteAccessToken() throws Exception { // Initialize the database accessTokenRepository.saveAndFlush(accessToken); int databaseSizeBeforeDelete = accessTokenRepository.findAll().size(); // Delete the accessToken - restAccessTokenMockMvc.perform(delete("/api/access-tokens/{id}", accessToken.getId()) - .accept(TestUtil.APPLICATION_JSON_UTF8)) + restAccessTokenMockMvc + .perform(delete(ENTITY_API_URL_ID, accessToken.getId()).with(csrf()).accept(MediaType.APPLICATION_JSON)) .andExpect(status().isNoContent()); // Validate the database contains one less item diff --git a/src/test/java/org/securityrat/casemanagement/web/rest/AuditResourceIT.java b/src/test/java/org/securityrat/casemanagement/web/rest/AuditResourceIT.java deleted file mode 100644 index 555ae5b..0000000 --- a/src/test/java/org/securityrat/casemanagement/web/rest/AuditResourceIT.java +++ /dev/null @@ -1,165 +0,0 @@ -package org.securityrat.casemanagement.web.rest; - -import org.securityrat.casemanagement.CaseManagementApp; -import org.securityrat.casemanagement.config.TestSecurityConfiguration; -import io.github.jhipster.config.JHipsterProperties; -import org.securityrat.casemanagement.config.audit.AuditEventConverter; -import org.securityrat.casemanagement.domain.PersistentAuditEvent; -import org.securityrat.casemanagement.repository.PersistenceAuditEventRepository; - -import org.securityrat.casemanagement.service.AuditEventService; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.MockitoAnnotations; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.web.PageableHandlerMethodArgumentResolver; -import org.springframework.format.support.FormattingConversionService; -import org.springframework.http.MediaType; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.transaction.annotation.Transactional; - -import java.time.Instant; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.hamcrest.Matchers.hasItem; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -/** - * Integration tests for the {@link AuditResource} REST controller. - */ -@SpringBootTest(classes = {CaseManagementApp.class, TestSecurityConfiguration.class}) -@Transactional -public class AuditResourceIT { - - private static final String SAMPLE_PRINCIPAL = "SAMPLE_PRINCIPAL"; - private static final String SAMPLE_TYPE = "SAMPLE_TYPE"; - private static final Instant SAMPLE_TIMESTAMP = Instant.parse("2015-08-04T10:11:30Z"); - private static final long SECONDS_PER_DAY = 60 * 60 * 24; - - @Autowired - private PersistenceAuditEventRepository auditEventRepository; - - @Autowired - private AuditEventConverter auditEventConverter; - - @Autowired - private JHipsterProperties jhipsterProperties; - - @Autowired - private MappingJackson2HttpMessageConverter jacksonMessageConverter; - - @Autowired - @Qualifier("mvcConversionService") - private FormattingConversionService formattingConversionService; - - @Autowired - private PageableHandlerMethodArgumentResolver pageableArgumentResolver; - - private PersistentAuditEvent auditEvent; - - private MockMvc restAuditMockMvc; - - @BeforeEach - public void setup() { - MockitoAnnotations.initMocks(this); - AuditEventService auditEventService = - new AuditEventService(auditEventRepository, auditEventConverter, jhipsterProperties); - AuditResource auditResource = new AuditResource(auditEventService); - this.restAuditMockMvc = MockMvcBuilders.standaloneSetup(auditResource) - .setCustomArgumentResolvers(pageableArgumentResolver) - .setConversionService(formattingConversionService) - .setMessageConverters(jacksonMessageConverter).build(); - } - - @BeforeEach - public void initTest() { - auditEventRepository.deleteAll(); - auditEvent = new PersistentAuditEvent(); - auditEvent.setAuditEventType(SAMPLE_TYPE); - auditEvent.setPrincipal(SAMPLE_PRINCIPAL); - auditEvent.setAuditEventDate(SAMPLE_TIMESTAMP); - } - - @Test - public void getAllAudits() throws Exception { - // Initialize the database - auditEventRepository.save(auditEvent); - - // Get all the audits - restAuditMockMvc.perform(get("/management/audits")) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) - .andExpect(jsonPath("$.[*].principal").value(hasItem(SAMPLE_PRINCIPAL))); - } - - @Test - public void getAudit() throws Exception { - // Initialize the database - auditEventRepository.save(auditEvent); - - // Get the audit - restAuditMockMvc.perform(get("/management/audits/{id}", auditEvent.getId())) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) - .andExpect(jsonPath("$.principal").value(SAMPLE_PRINCIPAL)); - } - - @Test - public void getAuditsByDate() throws Exception { - // Initialize the database - auditEventRepository.save(auditEvent); - - // Generate dates for selecting audits by date, making sure the period will contain the audit - String fromDate = SAMPLE_TIMESTAMP.minusSeconds(SECONDS_PER_DAY).toString().substring(0, 10); - String toDate = SAMPLE_TIMESTAMP.plusSeconds(SECONDS_PER_DAY).toString().substring(0, 10); - - // Get the audit - restAuditMockMvc.perform(get("/management/audits?fromDate="+fromDate+"&toDate="+toDate)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) - .andExpect(jsonPath("$.[*].principal").value(hasItem(SAMPLE_PRINCIPAL))); - } - - @Test - public void getNonExistingAuditsByDate() throws Exception { - // Initialize the database - auditEventRepository.save(auditEvent); - - // Generate dates for selecting audits by date, making sure the period will not contain the sample audit - String fromDate = SAMPLE_TIMESTAMP.minusSeconds(2*SECONDS_PER_DAY).toString().substring(0, 10); - String toDate = SAMPLE_TIMESTAMP.minusSeconds(SECONDS_PER_DAY).toString().substring(0, 10); - - // Query audits but expect no results - restAuditMockMvc.perform(get("/management/audits?fromDate=" + fromDate + "&toDate=" + toDate)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) - .andExpect(header().string("X-Total-Count", "0")); - } - - @Test - public void getNonExistingAudit() throws Exception { - // Get the audit - restAuditMockMvc.perform(get("/management/audits/{id}", Long.MAX_VALUE)) - .andExpect(status().isNotFound()); - } - - @Test - @Transactional - public void testPersistentAuditEventEquals() throws Exception { - TestUtil.equalsVerifier(PersistentAuditEvent.class); - PersistentAuditEvent auditEvent1 = new PersistentAuditEvent(); - auditEvent1.setId(1L); - PersistentAuditEvent auditEvent2 = new PersistentAuditEvent(); - auditEvent2.setId(auditEvent1.getId()); - assertThat(auditEvent1).isEqualTo(auditEvent2); - auditEvent2.setId(2L); - assertThat(auditEvent1).isNotEqualTo(auditEvent2); - auditEvent1.setId(null); - assertThat(auditEvent1).isNotEqualTo(auditEvent2); - } -} diff --git a/src/test/java/org/securityrat/casemanagement/web/rest/PublicUserResourceIT.java b/src/test/java/org/securityrat/casemanagement/web/rest/PublicUserResourceIT.java new file mode 100644 index 0000000..aa8f568 --- /dev/null +++ b/src/test/java/org/securityrat/casemanagement/web/rest/PublicUserResourceIT.java @@ -0,0 +1,65 @@ +package org.securityrat.casemanagement.web.rest; + +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasItems; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import javax.persistence.EntityManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.securityrat.casemanagement.IntegrationTest; +import org.securityrat.casemanagement.config.TestSecurityConfiguration; +import org.securityrat.casemanagement.domain.User; +import org.securityrat.casemanagement.repository.UserRepository; +import org.securityrat.casemanagement.security.AuthoritiesConstants; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +/** + * Integration tests for the {@link UserResource} REST controller. + */ +@AutoConfigureMockMvc +@WithMockUser(authorities = AuthoritiesConstants.ADMIN) +@IntegrationTest +class PublicUserResourceIT { + + private static final String DEFAULT_LOGIN = "johndoe"; + + @Autowired + private UserRepository userRepository; + + @Autowired + private EntityManager em; + + @Autowired + private MockMvc restUserMockMvc; + + private User user; + + @BeforeEach + public void initTest() { + user = UserResourceIT.initTestUser(userRepository, em); + } + + @Test + @Transactional + void getAllPublicUsers() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + + // Get all the users + restUserMockMvc + .perform(get("/api/users?sort=id,desc").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(jsonPath("$.[*].login").value(hasItem(DEFAULT_LOGIN))) + .andExpect(jsonPath("$.[*].email").doesNotExist()) + .andExpect(jsonPath("$.[*].imageUrl").doesNotExist()) + .andExpect(jsonPath("$.[*].langKey").doesNotExist()); + } +} diff --git a/src/test/java/org/securityrat/casemanagement/web/rest/TestUtil.java b/src/test/java/org/securityrat/casemanagement/web/rest/TestUtil.java index 3568b2d..5e12ae4 100644 --- a/src/test/java/org/securityrat/casemanagement/web/rest/TestUtil.java +++ b/src/test/java/org/securityrat/casemanagement/web/rest/TestUtil.java @@ -1,27 +1,34 @@ package org.securityrat.casemanagement.web.rest; +import static org.assertj.core.api.Assertions.assertThat; + import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import org.hamcrest.Description; -import org.hamcrest.TypeSafeDiagnosingMatcher; -import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar; -import org.springframework.format.support.DefaultFormattingConversionService; -import org.springframework.format.support.FormattingConversionService; -import org.springframework.http.MediaType; - import java.io.IOException; +import java.math.BigDecimal; import java.time.ZonedDateTime; import java.time.format.DateTimeParseException; +import java.util.Collection; import java.util.List; - import javax.persistence.EntityManager; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; - -import static org.assertj.core.api.Assertions.assertThat; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; +import org.hamcrest.TypeSafeMatcher; +import org.securityrat.casemanagement.security.SecurityUtils; +import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar; +import org.springframework.format.support.DefaultFormattingConversionService; +import org.springframework.format.support.FormattingConversionService; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.core.oidc.OidcIdToken; +import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; /** * Utility class for testing REST controllers. @@ -30,11 +37,9 @@ public final class TestUtil { private static final ObjectMapper mapper = createObjectMapper(); - /** MediaType for JSON UTF8 */ - public static final MediaType APPLICATION_JSON_UTF8 = MediaType.APPLICATION_JSON_UTF8; - private static ObjectMapper createObjectMapper() { ObjectMapper mapper = new ObjectMapper(); + mapper.configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false); mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); mapper.registerModule(new JavaTimeModule()); return mapper; @@ -86,11 +91,9 @@ protected boolean matchesSafely(String item, Description mismatchDescription) { } return true; } catch (DateTimeParseException e) { - mismatchDescription.appendText("was ").appendValue(item) - .appendText(", which could not be parsed as a ZonedDateTime"); + mismatchDescription.appendText("was ").appendValue(item).appendText(", which could not be parsed as a ZonedDateTime"); return false; } - } @Override @@ -101,12 +104,64 @@ public void describeTo(Description description) { /** * Creates a matcher that matches when the examined string represents the same instant as the reference datetime. + * * @param date the reference datetime against which the examined string is checked. */ public static ZonedDateTimeMatcher sameInstant(ZonedDateTime date) { return new ZonedDateTimeMatcher(date); } + /** + * A matcher that tests that the examined number represents the same value - it can be Long, Double, etc - as the reference BigDecimal. + */ + public static class NumberMatcher extends TypeSafeMatcher { + + final BigDecimal value; + + public NumberMatcher(BigDecimal value) { + this.value = value; + } + + @Override + public void describeTo(Description description) { + description.appendText("a numeric value is ").appendValue(value); + } + + @Override + protected boolean matchesSafely(Number item) { + BigDecimal bigDecimal = asDecimal(item); + return bigDecimal != null && value.compareTo(bigDecimal) == 0; + } + + private static BigDecimal asDecimal(Number item) { + if (item == null) { + return null; + } + if (item instanceof BigDecimal) { + return (BigDecimal) item; + } else if (item instanceof Long) { + return BigDecimal.valueOf((Long) item); + } else if (item instanceof Integer) { + return BigDecimal.valueOf((Integer) item); + } else if (item instanceof Double) { + return BigDecimal.valueOf((Double) item); + } else if (item instanceof Float) { + return BigDecimal.valueOf((Float) item); + } else { + return BigDecimal.valueOf(item.doubleValue()); + } + } + } + + /** + * Creates a matcher that matches when the examined number represents the same value as the reference BigDecimal. + * + * @param number the reference BigDecimal against which the examined number is checked. + */ + public static NumberMatcher sameNumber(BigDecimal number) { + return new NumberMatcher(number); + } + /** * Verifies the equals/hashcode contract on the domain object. */ @@ -114,7 +169,7 @@ public static void equalsVerifier(Class clazz) throws Exception { T domainObject1 = clazz.getConstructor().newInstance(); assertThat(domainObject1.toString()).isNotNull(); assertThat(domainObject1).isEqualTo(domainObject1); - assertThat(domainObject1.hashCode()).isEqualTo(domainObject1.hashCode()); + assertThat(domainObject1).hasSameHashCodeAs(domainObject1); // Test with an instance of another class Object testOtherObject = new Object(); assertThat(domainObject1).isNotEqualTo(testOtherObject); @@ -123,7 +178,7 @@ public static void equalsVerifier(Class clazz) throws Exception { T domainObject2 = clazz.getConstructor().newInstance(); assertThat(domainObject1).isNotEqualTo(domainObject2); // HashCodes are equals because the objects are not persisted yet - assertThat(domainObject1.hashCode()).isEqualTo(domainObject2.hashCode()); + assertThat(domainObject1).hasSameHashCodeAs(domainObject2); } /** @@ -131,7 +186,7 @@ public static void equalsVerifier(Class clazz) throws Exception { * @return the {@link FormattingConversionService}. */ public static FormattingConversionService createFormattingConversionService() { - DefaultFormattingConversionService dfcs = new DefaultFormattingConversionService (); + DefaultFormattingConversionService dfcs = new DefaultFormattingConversionService(); DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar(); registrar.setUseIsoFormat(true); registrar.registerFormatters(dfcs); @@ -154,5 +209,18 @@ public static List findAll(EntityManager em, Class clss) { return allQuery.getResultList(); } + static final String ID_TOKEN = + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" + + ".eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsIm" + + "p0aSI6ImQzNWRmMTRkLTA5ZjYtNDhmZi04YTkzLTdjNmYwMzM5MzE1OSIsImlhdCI6MTU0M" + + "Tk3MTU4MywiZXhwIjoxNTQxOTc1MTgzfQ.QaQOarmV8xEUYV7yvWzX3cUE_4W1luMcWCwpr" + + "oqqUrg"; + + public static OAuth2AuthenticationToken authenticationToken(OidcIdToken idToken) { + Collection authorities = SecurityUtils.extractAuthorityFromClaims(idToken.getClaims()); + OidcUser user = new DefaultOidcUser(authorities, idToken); + return new OAuth2AuthenticationToken(user, authorities, "oidc"); + } + private TestUtil() {} } diff --git a/src/test/java/org/securityrat/casemanagement/web/rest/TicketSystemInstanceResourceIT.java b/src/test/java/org/securityrat/casemanagement/web/rest/TicketSystemInstanceResourceIT.java index 3468222..4b45a87 100644 --- a/src/test/java/org/securityrat/casemanagement/web/rest/TicketSystemInstanceResourceIT.java +++ b/src/test/java/org/securityrat/casemanagement/web/rest/TicketSystemInstanceResourceIT.java @@ -1,39 +1,35 @@ package org.securityrat.casemanagement.web.rest; -import org.securityrat.casemanagement.CaseManagementApp; -import org.securityrat.casemanagement.config.TestSecurityConfiguration; -import org.securityrat.casemanagement.domain.TicketSystemInstance; -import org.securityrat.casemanagement.repository.TicketSystemInstanceRepository; -import org.securityrat.casemanagement.web.rest.errors.ExceptionTranslator; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import java.util.List; +import java.util.Random; +import java.util.concurrent.atomic.AtomicLong; +import javax.persistence.EntityManager; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.MockitoAnnotations; +import org.securityrat.casemanagement.IntegrationTest; +import org.securityrat.casemanagement.domain.TicketSystemInstance; +import org.securityrat.casemanagement.domain.enumeration.TicketSystem; +import org.securityrat.casemanagement.repository.TicketSystemInstanceRepository; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.web.PageableHandlerMethodArgumentResolver; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.http.MediaType; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.transaction.annotation.Transactional; -import org.springframework.validation.Validator; - -import javax.persistence.EntityManager; -import java.util.List; -import static org.securityrat.casemanagement.web.rest.TestUtil.createFormattingConversionService; -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.hasItem; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -import org.securityrat.casemanagement.domain.enumeration.TicketSystem; /** * Integration tests for the {@link TicketSystemInstanceResource} REST controller. */ -@SpringBootTest(classes = {CaseManagementApp.class, TestSecurityConfiguration.class}) -public class TicketSystemInstanceResourceIT { +@IntegrationTest +@AutoConfigureMockMvc +@WithMockUser +class TicketSystemInstanceResourceIT { private static final String DEFAULT_NAME = "AAAAAAAAAA"; private static final String UPDATED_NAME = "BBBBBBBBBB"; @@ -53,40 +49,23 @@ public class TicketSystemInstanceResourceIT { private static final String DEFAULT_CLIENT_SECRET = "AAAAAAAAAA"; private static final String UPDATED_CLIENT_SECRET = "BBBBBBBBBB"; - @Autowired - private TicketSystemInstanceRepository ticketSystemInstanceRepository; + private static final String ENTITY_API_URL = "/api/ticket-system-instances"; + private static final String ENTITY_API_URL_ID = ENTITY_API_URL + "/{id}"; - @Autowired - private MappingJackson2HttpMessageConverter jacksonMessageConverter; + private static Random random = new Random(); + private static AtomicLong count = new AtomicLong(random.nextInt() + (2 * Integer.MAX_VALUE)); @Autowired - private PageableHandlerMethodArgumentResolver pageableArgumentResolver; - - @Autowired - private ExceptionTranslator exceptionTranslator; + private TicketSystemInstanceRepository ticketSystemInstanceRepository; @Autowired private EntityManager em; @Autowired - private Validator validator; - private MockMvc restTicketSystemInstanceMockMvc; private TicketSystemInstance ticketSystemInstance; - @BeforeEach - public void setup() { - MockitoAnnotations.initMocks(this); - final TicketSystemInstanceResource ticketSystemInstanceResource = new TicketSystemInstanceResource(ticketSystemInstanceRepository); - this.restTicketSystemInstanceMockMvc = MockMvcBuilders.standaloneSetup(ticketSystemInstanceResource) - .setCustomArgumentResolvers(pageableArgumentResolver) - .setControllerAdvice(exceptionTranslator) - .setConversionService(createFormattingConversionService()) - .setMessageConverters(jacksonMessageConverter) - .setValidator(validator).build(); - } - /** * Create an entity for this test. * @@ -103,6 +82,7 @@ public static TicketSystemInstance createEntity(EntityManager em) { .clientSecret(DEFAULT_CLIENT_SECRET); return ticketSystemInstance; } + /** * Create an updated entity for this test. * @@ -127,13 +107,16 @@ public void initTest() { @Test @Transactional - public void createTicketSystemInstance() throws Exception { + void createTicketSystemInstance() throws Exception { int databaseSizeBeforeCreate = ticketSystemInstanceRepository.findAll().size(); - // Create the TicketSystemInstance - restTicketSystemInstanceMockMvc.perform(post("/api/ticket-system-instances") - .contentType(TestUtil.APPLICATION_JSON_UTF8) - .content(TestUtil.convertObjectToJsonBytes(ticketSystemInstance))) + restTicketSystemInstanceMockMvc + .perform( + post(ENTITY_API_URL) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(ticketSystemInstance)) + ) .andExpect(status().isCreated()); // Validate the TicketSystemInstance in the database @@ -150,16 +133,20 @@ public void createTicketSystemInstance() throws Exception { @Test @Transactional - public void createTicketSystemInstanceWithExistingId() throws Exception { - int databaseSizeBeforeCreate = ticketSystemInstanceRepository.findAll().size(); - + void createTicketSystemInstanceWithExistingId() throws Exception { // Create the TicketSystemInstance with an existing ID ticketSystemInstance.setId(1L); + int databaseSizeBeforeCreate = ticketSystemInstanceRepository.findAll().size(); + // An entity with an existing ID cannot be created, so this API call must fail - restTicketSystemInstanceMockMvc.perform(post("/api/ticket-system-instances") - .contentType(TestUtil.APPLICATION_JSON_UTF8) - .content(TestUtil.convertObjectToJsonBytes(ticketSystemInstance))) + restTicketSystemInstanceMockMvc + .perform( + post(ENTITY_API_URL) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(ticketSystemInstance)) + ) .andExpect(status().isBadRequest()); // Validate the TicketSystemInstance in the database @@ -167,19 +154,22 @@ public void createTicketSystemInstanceWithExistingId() throws Exception { assertThat(ticketSystemInstanceList).hasSize(databaseSizeBeforeCreate); } - @Test @Transactional - public void checkTypeIsRequired() throws Exception { + void checkTypeIsRequired() throws Exception { int databaseSizeBeforeTest = ticketSystemInstanceRepository.findAll().size(); // set the field null ticketSystemInstance.setType(null); // Create the TicketSystemInstance, which fails. - restTicketSystemInstanceMockMvc.perform(post("/api/ticket-system-instances") - .contentType(TestUtil.APPLICATION_JSON_UTF8) - .content(TestUtil.convertObjectToJsonBytes(ticketSystemInstance))) + restTicketSystemInstanceMockMvc + .perform( + post(ENTITY_API_URL) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(ticketSystemInstance)) + ) .andExpect(status().isBadRequest()); List ticketSystemInstanceList = ticketSystemInstanceRepository.findAll(); @@ -188,16 +178,20 @@ public void checkTypeIsRequired() throws Exception { @Test @Transactional - public void checkUrlIsRequired() throws Exception { + void checkUrlIsRequired() throws Exception { int databaseSizeBeforeTest = ticketSystemInstanceRepository.findAll().size(); // set the field null ticketSystemInstance.setUrl(null); // Create the TicketSystemInstance, which fails. - restTicketSystemInstanceMockMvc.perform(post("/api/ticket-system-instances") - .contentType(TestUtil.APPLICATION_JSON_UTF8) - .content(TestUtil.convertObjectToJsonBytes(ticketSystemInstance))) + restTicketSystemInstanceMockMvc + .perform( + post(ENTITY_API_URL) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(ticketSystemInstance)) + ) .andExpect(status().isBadRequest()); List ticketSystemInstanceList = ticketSystemInstanceRepository.findAll(); @@ -206,14 +200,15 @@ public void checkUrlIsRequired() throws Exception { @Test @Transactional - public void getAllTicketSystemInstances() throws Exception { + void getAllTicketSystemInstances() throws Exception { // Initialize the database ticketSystemInstanceRepository.saveAndFlush(ticketSystemInstance); // Get all the ticketSystemInstanceList - restTicketSystemInstanceMockMvc.perform(get("/api/ticket-system-instances?sort=id,desc")) + restTicketSystemInstanceMockMvc + .perform(get(ENTITY_API_URL + "?sort=id,desc")) .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(jsonPath("$.[*].id").value(hasItem(ticketSystemInstance.getId().intValue()))) .andExpect(jsonPath("$.[*].name").value(hasItem(DEFAULT_NAME))) .andExpect(jsonPath("$.[*].type").value(hasItem(DEFAULT_TYPE.toString()))) @@ -222,17 +217,18 @@ public void getAllTicketSystemInstances() throws Exception { .andExpect(jsonPath("$.[*].clientId").value(hasItem(DEFAULT_CLIENT_ID))) .andExpect(jsonPath("$.[*].clientSecret").value(hasItem(DEFAULT_CLIENT_SECRET))); } - + @Test @Transactional - public void getTicketSystemInstance() throws Exception { + void getTicketSystemInstance() throws Exception { // Initialize the database ticketSystemInstanceRepository.saveAndFlush(ticketSystemInstance); // Get the ticketSystemInstance - restTicketSystemInstanceMockMvc.perform(get("/api/ticket-system-instances/{id}", ticketSystemInstance.getId())) + restTicketSystemInstanceMockMvc + .perform(get(ENTITY_API_URL_ID, ticketSystemInstance.getId())) .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(jsonPath("$.id").value(ticketSystemInstance.getId().intValue())) .andExpect(jsonPath("$.name").value(DEFAULT_NAME)) .andExpect(jsonPath("$.type").value(DEFAULT_TYPE.toString())) @@ -244,15 +240,14 @@ public void getTicketSystemInstance() throws Exception { @Test @Transactional - public void getNonExistingTicketSystemInstance() throws Exception { + void getNonExistingTicketSystemInstance() throws Exception { // Get the ticketSystemInstance - restTicketSystemInstanceMockMvc.perform(get("/api/ticket-system-instances/{id}", Long.MAX_VALUE)) - .andExpect(status().isNotFound()); + restTicketSystemInstanceMockMvc.perform(get(ENTITY_API_URL_ID, Long.MAX_VALUE)).andExpect(status().isNotFound()); } @Test @Transactional - public void updateTicketSystemInstance() throws Exception { + void putNewTicketSystemInstance() throws Exception { // Initialize the database ticketSystemInstanceRepository.saveAndFlush(ticketSystemInstance); @@ -270,9 +265,13 @@ public void updateTicketSystemInstance() throws Exception { .clientId(UPDATED_CLIENT_ID) .clientSecret(UPDATED_CLIENT_SECRET); - restTicketSystemInstanceMockMvc.perform(put("/api/ticket-system-instances") - .contentType(TestUtil.APPLICATION_JSON_UTF8) - .content(TestUtil.convertObjectToJsonBytes(updatedTicketSystemInstance))) + restTicketSystemInstanceMockMvc + .perform( + put(ENTITY_API_URL_ID, updatedTicketSystemInstance.getId()) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(updatedTicketSystemInstance)) + ) .andExpect(status().isOk()); // Validate the TicketSystemInstance in the database @@ -289,15 +288,157 @@ public void updateTicketSystemInstance() throws Exception { @Test @Transactional - public void updateNonExistingTicketSystemInstance() throws Exception { + void putNonExistingTicketSystemInstance() throws Exception { int databaseSizeBeforeUpdate = ticketSystemInstanceRepository.findAll().size(); + ticketSystemInstance.setId(count.incrementAndGet()); - // Create the TicketSystemInstance + // If the entity doesn't have an ID, it will throw BadRequestAlertException + restTicketSystemInstanceMockMvc + .perform( + put(ENTITY_API_URL_ID, ticketSystemInstance.getId()) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(ticketSystemInstance)) + ) + .andExpect(status().isBadRequest()); + + // Validate the TicketSystemInstance in the database + List ticketSystemInstanceList = ticketSystemInstanceRepository.findAll(); + assertThat(ticketSystemInstanceList).hasSize(databaseSizeBeforeUpdate); + } + + @Test + @Transactional + void putWithIdMismatchTicketSystemInstance() throws Exception { + int databaseSizeBeforeUpdate = ticketSystemInstanceRepository.findAll().size(); + ticketSystemInstance.setId(count.incrementAndGet()); + + // If url ID doesn't match entity ID, it will throw BadRequestAlertException + restTicketSystemInstanceMockMvc + .perform( + put(ENTITY_API_URL_ID, count.incrementAndGet()) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(ticketSystemInstance)) + ) + .andExpect(status().isBadRequest()); + + // Validate the TicketSystemInstance in the database + List ticketSystemInstanceList = ticketSystemInstanceRepository.findAll(); + assertThat(ticketSystemInstanceList).hasSize(databaseSizeBeforeUpdate); + } + + @Test + @Transactional + void putWithMissingIdPathParamTicketSystemInstance() throws Exception { + int databaseSizeBeforeUpdate = ticketSystemInstanceRepository.findAll().size(); + ticketSystemInstance.setId(count.incrementAndGet()); + + // If url ID doesn't match entity ID, it will throw BadRequestAlertException + restTicketSystemInstanceMockMvc + .perform( + put(ENTITY_API_URL) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(ticketSystemInstance)) + ) + .andExpect(status().isMethodNotAllowed()); + + // Validate the TicketSystemInstance in the database + List ticketSystemInstanceList = ticketSystemInstanceRepository.findAll(); + assertThat(ticketSystemInstanceList).hasSize(databaseSizeBeforeUpdate); + } + + @Test + @Transactional + void partialUpdateTicketSystemInstanceWithPatch() throws Exception { + // Initialize the database + ticketSystemInstanceRepository.saveAndFlush(ticketSystemInstance); + + int databaseSizeBeforeUpdate = ticketSystemInstanceRepository.findAll().size(); + + // Update the ticketSystemInstance using partial update + TicketSystemInstance partialUpdatedTicketSystemInstance = new TicketSystemInstance(); + partialUpdatedTicketSystemInstance.setId(ticketSystemInstance.getId()); + + partialUpdatedTicketSystemInstance.type(UPDATED_TYPE).clientSecret(UPDATED_CLIENT_SECRET); + + restTicketSystemInstanceMockMvc + .perform( + patch(ENTITY_API_URL_ID, partialUpdatedTicketSystemInstance.getId()) + .with(csrf()) + .contentType("application/merge-patch+json") + .content(TestUtil.convertObjectToJsonBytes(partialUpdatedTicketSystemInstance)) + ) + .andExpect(status().isOk()); + + // Validate the TicketSystemInstance in the database + List ticketSystemInstanceList = ticketSystemInstanceRepository.findAll(); + assertThat(ticketSystemInstanceList).hasSize(databaseSizeBeforeUpdate); + TicketSystemInstance testTicketSystemInstance = ticketSystemInstanceList.get(ticketSystemInstanceList.size() - 1); + assertThat(testTicketSystemInstance.getName()).isEqualTo(DEFAULT_NAME); + assertThat(testTicketSystemInstance.getType()).isEqualTo(UPDATED_TYPE); + assertThat(testTicketSystemInstance.getUrl()).isEqualTo(DEFAULT_URL); + assertThat(testTicketSystemInstance.getConsumerKey()).isEqualTo(DEFAULT_CONSUMER_KEY); + assertThat(testTicketSystemInstance.getClientId()).isEqualTo(DEFAULT_CLIENT_ID); + assertThat(testTicketSystemInstance.getClientSecret()).isEqualTo(UPDATED_CLIENT_SECRET); + } + + @Test + @Transactional + void fullUpdateTicketSystemInstanceWithPatch() throws Exception { + // Initialize the database + ticketSystemInstanceRepository.saveAndFlush(ticketSystemInstance); + + int databaseSizeBeforeUpdate = ticketSystemInstanceRepository.findAll().size(); + + // Update the ticketSystemInstance using partial update + TicketSystemInstance partialUpdatedTicketSystemInstance = new TicketSystemInstance(); + partialUpdatedTicketSystemInstance.setId(ticketSystemInstance.getId()); + + partialUpdatedTicketSystemInstance + .name(UPDATED_NAME) + .type(UPDATED_TYPE) + .url(UPDATED_URL) + .consumerKey(UPDATED_CONSUMER_KEY) + .clientId(UPDATED_CLIENT_ID) + .clientSecret(UPDATED_CLIENT_SECRET); + + restTicketSystemInstanceMockMvc + .perform( + patch(ENTITY_API_URL_ID, partialUpdatedTicketSystemInstance.getId()) + .with(csrf()) + .contentType("application/merge-patch+json") + .content(TestUtil.convertObjectToJsonBytes(partialUpdatedTicketSystemInstance)) + ) + .andExpect(status().isOk()); + + // Validate the TicketSystemInstance in the database + List ticketSystemInstanceList = ticketSystemInstanceRepository.findAll(); + assertThat(ticketSystemInstanceList).hasSize(databaseSizeBeforeUpdate); + TicketSystemInstance testTicketSystemInstance = ticketSystemInstanceList.get(ticketSystemInstanceList.size() - 1); + assertThat(testTicketSystemInstance.getName()).isEqualTo(UPDATED_NAME); + assertThat(testTicketSystemInstance.getType()).isEqualTo(UPDATED_TYPE); + assertThat(testTicketSystemInstance.getUrl()).isEqualTo(UPDATED_URL); + assertThat(testTicketSystemInstance.getConsumerKey()).isEqualTo(UPDATED_CONSUMER_KEY); + assertThat(testTicketSystemInstance.getClientId()).isEqualTo(UPDATED_CLIENT_ID); + assertThat(testTicketSystemInstance.getClientSecret()).isEqualTo(UPDATED_CLIENT_SECRET); + } + + @Test + @Transactional + void patchNonExistingTicketSystemInstance() throws Exception { + int databaseSizeBeforeUpdate = ticketSystemInstanceRepository.findAll().size(); + ticketSystemInstance.setId(count.incrementAndGet()); // If the entity doesn't have an ID, it will throw BadRequestAlertException - restTicketSystemInstanceMockMvc.perform(put("/api/ticket-system-instances") - .contentType(TestUtil.APPLICATION_JSON_UTF8) - .content(TestUtil.convertObjectToJsonBytes(ticketSystemInstance))) + restTicketSystemInstanceMockMvc + .perform( + patch(ENTITY_API_URL_ID, ticketSystemInstance.getId()) + .with(csrf()) + .contentType("application/merge-patch+json") + .content(TestUtil.convertObjectToJsonBytes(ticketSystemInstance)) + ) .andExpect(status().isBadRequest()); // Validate the TicketSystemInstance in the database @@ -307,15 +448,57 @@ public void updateNonExistingTicketSystemInstance() throws Exception { @Test @Transactional - public void deleteTicketSystemInstance() throws Exception { + void patchWithIdMismatchTicketSystemInstance() throws Exception { + int databaseSizeBeforeUpdate = ticketSystemInstanceRepository.findAll().size(); + ticketSystemInstance.setId(count.incrementAndGet()); + + // If url ID doesn't match entity ID, it will throw BadRequestAlertException + restTicketSystemInstanceMockMvc + .perform( + patch(ENTITY_API_URL_ID, count.incrementAndGet()) + .with(csrf()) + .contentType("application/merge-patch+json") + .content(TestUtil.convertObjectToJsonBytes(ticketSystemInstance)) + ) + .andExpect(status().isBadRequest()); + + // Validate the TicketSystemInstance in the database + List ticketSystemInstanceList = ticketSystemInstanceRepository.findAll(); + assertThat(ticketSystemInstanceList).hasSize(databaseSizeBeforeUpdate); + } + + @Test + @Transactional + void patchWithMissingIdPathParamTicketSystemInstance() throws Exception { + int databaseSizeBeforeUpdate = ticketSystemInstanceRepository.findAll().size(); + ticketSystemInstance.setId(count.incrementAndGet()); + + // If url ID doesn't match entity ID, it will throw BadRequestAlertException + restTicketSystemInstanceMockMvc + .perform( + patch(ENTITY_API_URL) + .with(csrf()) + .contentType("application/merge-patch+json") + .content(TestUtil.convertObjectToJsonBytes(ticketSystemInstance)) + ) + .andExpect(status().isMethodNotAllowed()); + + // Validate the TicketSystemInstance in the database + List ticketSystemInstanceList = ticketSystemInstanceRepository.findAll(); + assertThat(ticketSystemInstanceList).hasSize(databaseSizeBeforeUpdate); + } + + @Test + @Transactional + void deleteTicketSystemInstance() throws Exception { // Initialize the database ticketSystemInstanceRepository.saveAndFlush(ticketSystemInstance); int databaseSizeBeforeDelete = ticketSystemInstanceRepository.findAll().size(); // Delete the ticketSystemInstance - restTicketSystemInstanceMockMvc.perform(delete("/api/ticket-system-instances/{id}", ticketSystemInstance.getId()) - .accept(TestUtil.APPLICATION_JSON_UTF8)) + restTicketSystemInstanceMockMvc + .perform(delete(ENTITY_API_URL_ID, ticketSystemInstance.getId()).with(csrf()).accept(MediaType.APPLICATION_JSON)) .andExpect(status().isNoContent()); // Validate the database contains one less item diff --git a/src/test/java/org/securityrat/casemanagement/web/rest/UserResourceIT.java b/src/test/java/org/securityrat/casemanagement/web/rest/UserResourceIT.java index f6dbe5c..187ddc3 100644 --- a/src/test/java/org/securityrat/casemanagement/web/rest/UserResourceIT.java +++ b/src/test/java/org/securityrat/casemanagement/web/rest/UserResourceIT.java @@ -1,50 +1,46 @@ package org.securityrat.casemanagement.web.rest; -import org.securityrat.casemanagement.CaseManagementApp; -import org.securityrat.casemanagement.config.TestSecurityConfiguration; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasItems; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.time.Instant; +import java.util.*; +import java.util.function.Consumer; +import javax.persistence.EntityManager; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.securityrat.casemanagement.IntegrationTest; import org.securityrat.casemanagement.domain.Authority; import org.securityrat.casemanagement.domain.User; import org.securityrat.casemanagement.repository.UserRepository; import org.securityrat.casemanagement.security.AuthoritiesConstants; - -import org.securityrat.casemanagement.service.UserService; +import org.securityrat.casemanagement.service.dto.AdminUserDTO; import org.securityrat.casemanagement.service.dto.UserDTO; import org.securityrat.casemanagement.service.mapper.UserMapper; -import org.securityrat.casemanagement.web.rest.errors.ExceptionTranslator; -import org.apache.commons.lang3.RandomStringUtils; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.web.PageableHandlerMethodArgumentResolver; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.http.MediaType; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.transaction.annotation.Transactional; -import javax.persistence.EntityManager; -import java.time.Instant; -import java.util.*; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.hasItems; -import static org.hamcrest.Matchers.hasItem; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - /** * Integration tests for the {@link UserResource} REST controller. */ -@SpringBootTest(classes = {CaseManagementApp.class, TestSecurityConfiguration.class}) -public class UserResourceIT { +@AutoConfigureMockMvc +@WithMockUser(authorities = AuthoritiesConstants.ADMIN) +@IntegrationTest +class UserResourceIT { private static final String DEFAULT_LOGIN = "johndoe"; private static final String DEFAULT_ID = "id1"; - private static final String DEFAULT_PASSWORD = "passjohndoe"; - private static final String DEFAULT_EMAIL = "johndoe@localhost"; private static final String DEFAULT_FIRSTNAME = "john"; @@ -58,39 +54,17 @@ public class UserResourceIT { @Autowired private UserRepository userRepository; - @Autowired - private UserService userService; - @Autowired private UserMapper userMapper; - @Autowired - private MappingJackson2HttpMessageConverter jacksonMessageConverter; - - @Autowired - private PageableHandlerMethodArgumentResolver pageableArgumentResolver; - - @Autowired - private ExceptionTranslator exceptionTranslator; - @Autowired private EntityManager em; + @Autowired private MockMvc restUserMockMvc; private User user; - @BeforeEach - public void setup() { - UserResource userResource = new UserResource(userService); - - this.restUserMockMvc = MockMvcBuilders.standaloneSetup(userResource) - .setCustomArgumentResolvers(pageableArgumentResolver) - .setControllerAdvice(exceptionTranslator) - .setMessageConverters(jacksonMessageConverter) - .build(); - } - /** * Create a User. * @@ -110,24 +84,32 @@ public static User createEntity(EntityManager em) { return user; } - @BeforeEach - public void initTest() { - user = createEntity(em); + /** + * Setups the database with one user. + */ + public static User initTestUser(UserRepository userRepository, EntityManager em) { + User user = createEntity(em); user.setLogin(DEFAULT_LOGIN); user.setEmail(DEFAULT_EMAIL); + return user; + } + + @BeforeEach + public void initTest() { + user = initTestUser(userRepository, em); } @Test @Transactional - public void getAllUsers() throws Exception { + void getAllUsers() throws Exception { // Initialize the database userRepository.saveAndFlush(user); // Get all the users - restUserMockMvc.perform(get("/api/users?sort=id,desc") - .accept(MediaType.APPLICATION_JSON)) + restUserMockMvc + .perform(get("/api/admin/users?sort=id,desc").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(jsonPath("$.[*].login").value(hasItem(DEFAULT_LOGIN))) .andExpect(jsonPath("$.[*].firstName").value(hasItem(DEFAULT_FIRSTNAME))) .andExpect(jsonPath("$.[*].lastName").value(hasItem(DEFAULT_LASTNAME))) @@ -138,36 +120,34 @@ public void getAllUsers() throws Exception { @Test @Transactional - public void getUser() throws Exception { + void getUser() throws Exception { // Initialize the database userRepository.saveAndFlush(user); // Get the user - restUserMockMvc.perform(get("/api/users/{login}", user.getLogin())) + restUserMockMvc + .perform(get("/api/admin/users/{login}", user.getLogin())) .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(jsonPath("$.login").value(user.getLogin())) .andExpect(jsonPath("$.firstName").value(DEFAULT_FIRSTNAME)) .andExpect(jsonPath("$.lastName").value(DEFAULT_LASTNAME)) .andExpect(jsonPath("$.email").value(DEFAULT_EMAIL)) .andExpect(jsonPath("$.imageUrl").value(DEFAULT_IMAGEURL)) .andExpect(jsonPath("$.langKey").value(DEFAULT_LANGKEY)); - } @Test @Transactional - public void getNonExistingUser() throws Exception { - restUserMockMvc.perform(get("/api/users/unknown")) - .andExpect(status().isNotFound()); + void getNonExistingUser() throws Exception { + restUserMockMvc.perform(get("/api/admin/users/unknown")).andExpect(status().isNotFound()); } @Test - @Transactional - public void testUserEquals() throws Exception { + void testUserEquals() throws Exception { TestUtil.equalsVerifier(User.class); User user1 = new User(); - user1.setId("id1"); + user1.setId(DEFAULT_ID); User user2 = new User(); user2.setId(user1.getId()); assertThat(user1).isEqualTo(user2); @@ -178,8 +158,8 @@ public void testUserEquals() throws Exception { } @Test - public void testUserDTOtoUser() { - UserDTO userDTO = new UserDTO(); + void testUserDTOtoUser() { + AdminUserDTO userDTO = new AdminUserDTO(); userDTO.setId(DEFAULT_ID); userDTO.setLogin(DEFAULT_LOGIN); userDTO.setFirstName(DEFAULT_FIRSTNAME); @@ -198,7 +178,7 @@ public void testUserDTOtoUser() { assertThat(user.getFirstName()).isEqualTo(DEFAULT_FIRSTNAME); assertThat(user.getLastName()).isEqualTo(DEFAULT_LASTNAME); assertThat(user.getEmail()).isEqualTo(DEFAULT_EMAIL); - assertThat(user.getActivated()).isEqualTo(true); + assertThat(user.isActivated()).isTrue(); assertThat(user.getImageUrl()).isEqualTo(DEFAULT_IMAGEURL); assertThat(user.getLangKey()).isEqualTo(DEFAULT_LANGKEY); assertThat(user.getCreatedBy()).isNull(); @@ -209,7 +189,7 @@ public void testUserDTOtoUser() { } @Test - public void testUserToUserDTO() { + void testUserToUserDTO() { user.setId(DEFAULT_ID); user.setCreatedBy(DEFAULT_LOGIN); user.setCreatedDate(Instant.now()); @@ -221,14 +201,14 @@ public void testUserToUserDTO() { authorities.add(authority); user.setAuthorities(authorities); - UserDTO userDTO = userMapper.userToUserDTO(user); + AdminUserDTO userDTO = userMapper.userToAdminUserDTO(user); assertThat(userDTO.getId()).isEqualTo(DEFAULT_ID); assertThat(userDTO.getLogin()).isEqualTo(DEFAULT_LOGIN); assertThat(userDTO.getFirstName()).isEqualTo(DEFAULT_FIRSTNAME); assertThat(userDTO.getLastName()).isEqualTo(DEFAULT_LASTNAME); assertThat(userDTO.getEmail()).isEqualTo(DEFAULT_EMAIL); - assertThat(userDTO.isActivated()).isEqualTo(true); + assertThat(userDTO.isActivated()).isTrue(); assertThat(userDTO.getImageUrl()).isEqualTo(DEFAULT_IMAGEURL); assertThat(userDTO.getLangKey()).isEqualTo(DEFAULT_LANGKEY); assertThat(userDTO.getCreatedBy()).isEqualTo(DEFAULT_LOGIN); @@ -240,12 +220,10 @@ public void testUserToUserDTO() { } @Test - public void testAuthorityEquals() { + void testAuthorityEquals() { Authority authorityA = new Authority(); - assertThat(authorityA).isEqualTo(authorityA); - assertThat(authorityA).isNotEqualTo(null); - assertThat(authorityA).isNotEqualTo(new Object()); - assertThat(authorityA.hashCode()).isEqualTo(0); + assertThat(authorityA).isNotEqualTo(null).isNotEqualTo(new Object()); + assertThat(authorityA.hashCode()).isZero(); assertThat(authorityA.toString()).isNotNull(); Authority authorityB = new Authority(); @@ -258,7 +236,10 @@ public void testAuthorityEquals() { assertThat(authorityA).isNotEqualTo(authorityB); authorityB.setName(AuthoritiesConstants.USER); - assertThat(authorityA).isEqualTo(authorityB); - assertThat(authorityA.hashCode()).isEqualTo(authorityB.hashCode()); + assertThat(authorityA).isEqualTo(authorityB).hasSameHashCodeAs(authorityB); + } + + private void assertPersistedUsers(Consumer> userAssertion) { + userAssertion.accept(userRepository.findAll()); } } diff --git a/src/test/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslatorIT.java b/src/test/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslatorIT.java index d4bb7b2..0d48b64 100644 --- a/src/test/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslatorIT.java +++ b/src/test/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslatorIT.java @@ -1,85 +1,77 @@ package org.securityrat.casemanagement.web.rest.errors; -import org.securityrat.casemanagement.CaseManagementApp; -import org.securityrat.casemanagement.config.TestSecurityConfiguration; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; - +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; 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.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import org.junit.jupiter.api.Test; +import org.securityrat.casemanagement.IntegrationTest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; + /** * Integration tests {@link ExceptionTranslator} controller advice. */ -@SpringBootTest(classes = {CaseManagementApp.class, TestSecurityConfiguration.class}) -public class ExceptionTranslatorIT { - - @Autowired - private ExceptionTranslatorTestController controller; +@WithMockUser +@AutoConfigureMockMvc +@IntegrationTest +class ExceptionTranslatorIT { @Autowired - private ExceptionTranslator exceptionTranslator; - - @Autowired - private MappingJackson2HttpMessageConverter jacksonMessageConverter; - private MockMvc mockMvc; - @BeforeEach - public void setup() { - mockMvc = MockMvcBuilders.standaloneSetup(controller) - .setControllerAdvice(exceptionTranslator) - .setMessageConverters(jacksonMessageConverter) - .build(); - } - @Test - public void testConcurrencyFailure() throws Exception { - mockMvc.perform(get("/test/concurrency-failure")) + void testConcurrencyFailure() throws Exception { + mockMvc + .perform(get("/api/exception-translator-test/concurrency-failure").with(csrf())) .andExpect(status().isConflict()) .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) .andExpect(jsonPath("$.message").value(ErrorConstants.ERR_CONCURRENCY_FAILURE)); } @Test - public void testMethodArgumentNotValid() throws Exception { - mockMvc.perform(post("/test/method-argument").content("{}").contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()) - .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) - .andExpect(jsonPath("$.message").value(ErrorConstants.ERR_VALIDATION)) - .andExpect(jsonPath("$.fieldErrors.[0].objectName").value("test")) - .andExpect(jsonPath("$.fieldErrors.[0].field").value("test")) - .andExpect(jsonPath("$.fieldErrors.[0].message").value("NotNull")); + void testMethodArgumentNotValid() throws Exception { + mockMvc + .perform( + post("/api/exception-translator-test/method-argument").content("{}").contentType(MediaType.APPLICATION_JSON).with(csrf()) + ) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value(ErrorConstants.ERR_VALIDATION)) + .andExpect(jsonPath("$.fieldErrors.[0].objectName").value("test")) + .andExpect(jsonPath("$.fieldErrors.[0].field").value("test")) + .andExpect(jsonPath("$.fieldErrors.[0].message").value("must not be null")); } @Test - public void testMissingServletRequestPartException() throws Exception { - mockMvc.perform(get("/test/missing-servlet-request-part")) + void testMissingServletRequestPartException() throws Exception { + mockMvc + .perform(get("/api/exception-translator-test/missing-servlet-request-part").with(csrf())) .andExpect(status().isBadRequest()) .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) .andExpect(jsonPath("$.message").value("error.http.400")); } @Test - public void testMissingServletRequestParameterException() throws Exception { - mockMvc.perform(get("/test/missing-servlet-request-parameter")) + void testMissingServletRequestParameterException() throws Exception { + mockMvc + .perform(get("/api/exception-translator-test/missing-servlet-request-parameter").with(csrf())) .andExpect(status().isBadRequest()) .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) .andExpect(jsonPath("$.message").value("error.http.400")); } @Test - public void testAccessDenied() throws Exception { - mockMvc.perform(get("/test/access-denied")) + void testAccessDenied() throws Exception { + mockMvc + .perform(get("/api/exception-translator-test/access-denied").with(csrf())) .andExpect(status().isForbidden()) .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) .andExpect(jsonPath("$.message").value("error.http.403")) @@ -87,18 +79,20 @@ public void testAccessDenied() throws Exception { } @Test - public void testUnauthorized() throws Exception { - mockMvc.perform(get("/test/unauthorized")) + void testUnauthorized() throws Exception { + mockMvc + .perform(get("/api/exception-translator-test/unauthorized").with(csrf())) .andExpect(status().isUnauthorized()) .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) .andExpect(jsonPath("$.message").value("error.http.401")) - .andExpect(jsonPath("$.path").value("/test/unauthorized")) + .andExpect(jsonPath("$.path").value("/api/exception-translator-test/unauthorized")) .andExpect(jsonPath("$.detail").value("test authentication failed!")); } @Test - public void testMethodNotSupported() throws Exception { - mockMvc.perform(post("/test/access-denied")) + void testMethodNotSupported() throws Exception { + mockMvc + .perform(post("/api/exception-translator-test/access-denied").with(csrf())) .andExpect(status().isMethodNotAllowed()) .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) .andExpect(jsonPath("$.message").value("error.http.405")) @@ -106,8 +100,9 @@ public void testMethodNotSupported() throws Exception { } @Test - public void testExceptionWithResponseStatus() throws Exception { - mockMvc.perform(get("/test/response-status")) + void testExceptionWithResponseStatus() throws Exception { + mockMvc + .perform(get("/api/exception-translator-test/response-status").with(csrf())) .andExpect(status().isBadRequest()) .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) .andExpect(jsonPath("$.message").value("error.http.400")) @@ -115,12 +110,12 @@ public void testExceptionWithResponseStatus() throws Exception { } @Test - public void testInternalServerError() throws Exception { - mockMvc.perform(get("/test/internal-server-error")) + void testInternalServerError() throws Exception { + mockMvc + .perform(get("/api/exception-translator-test/internal-server-error").with(csrf())) .andExpect(status().isInternalServerError()) .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) .andExpect(jsonPath("$.message").value("error.http.500")) .andExpect(jsonPath("$.title").value("Internal Server Error")); } - } diff --git a/src/test/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslatorTestController.java b/src/test/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslatorTestController.java index 13ea32c..7ff9ab1 100644 --- a/src/test/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslatorTestController.java +++ b/src/test/java/org/securityrat/casemanagement/web/rest/errors/ExceptionTranslatorTestController.java @@ -1,50 +1,47 @@ package org.securityrat.casemanagement.web.rest.errors; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; import org.springframework.dao.ConcurrencyFailureException; import org.springframework.http.HttpStatus; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.web.bind.annotation.*; -import javax.validation.Valid; -import javax.validation.constraints.NotNull; - @RestController +@RequestMapping("/api/exception-translator-test") public class ExceptionTranslatorTestController { - @GetMapping("/test/concurrency-failure") + @GetMapping("/concurrency-failure") public void concurrencyFailure() { throw new ConcurrencyFailureException("test concurrency failure"); } - @PostMapping("/test/method-argument") - public void methodArgument(@Valid @RequestBody TestDTO testDTO) { - } + @PostMapping("/method-argument") + public void methodArgument(@Valid @RequestBody TestDTO testDTO) {} - @GetMapping("/test/missing-servlet-request-part") - public void missingServletRequestPartException(@RequestPart String part) { - } + @GetMapping("/missing-servlet-request-part") + public void missingServletRequestPartException(@RequestPart String part) {} - @GetMapping("/test/missing-servlet-request-parameter") - public void missingServletRequestParameterException(@RequestParam String param) { - } + @GetMapping("/missing-servlet-request-parameter") + public void missingServletRequestParameterException(@RequestParam String param) {} - @GetMapping("/test/access-denied") + @GetMapping("/access-denied") public void accessdenied() { throw new AccessDeniedException("test access denied!"); } - @GetMapping("/test/unauthorized") + @GetMapping("/unauthorized") public void unauthorized() { throw new BadCredentialsException("test authentication failed!"); } - @GetMapping("/test/response-status") + @GetMapping("/response-status") public void exceptionWithResponseStatus() { throw new TestResponseStatusException(); } - @GetMapping("/test/internal-server-error") + @GetMapping("/internal-server-error") public void internalServerError() { throw new RuntimeException(); } @@ -65,7 +62,5 @@ public void setTest(String test) { @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "test response status") @SuppressWarnings("serial") - public static class TestResponseStatusException extends RuntimeException { - } - + public static class TestResponseStatusException extends RuntimeException {} } diff --git a/src/test/resources/config/application-testcontainers.yml b/src/test/resources/config/application-testcontainers.yml new file mode 100644 index 0000000..b4c090b --- /dev/null +++ b/src/test/resources/config/application-testcontainers.yml @@ -0,0 +1,27 @@ +# =================================================================== +# Spring Boot configuration. +# +# This configuration is used for unit/integration tests with testcontainers database containers. +# +# To activate this configuration launch integration tests with the 'testcontainers' profile +# +# More information on database containers: https://www.testcontainers.org/modules/databases/ +# =================================================================== + +spring: + datasource: + type: com.zaxxer.hikari.HikariDataSource + driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver + url: jdbc:tc:mariadb:10.5.9:///caseManagement?TC_MY_CNF=testcontainers/mariadb&useLegacyDatetimeCode=false&serverTimezone=${user.timezone}&TC_TMPFS=/testtmpfs:rw + username: root + password: + hikari: + poolName: Hikari + auto-commit: false + data-source-properties: + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + jpa: + database-platform: org.hibernate.dialect.MariaDB103Dialect diff --git a/src/test/resources/config/application.yml b/src/test/resources/config/application.yml index a930a08..81de599 100644 --- a/src/test/resources/config/application.yml +++ b/src/test/resources/config/application.yml @@ -14,21 +14,33 @@ # =================================================================== spring: + profiles: + # Uncomment the following line to enable tests against production database type rather than H2, using Testcontainers + #active: testcontainers application: name: caseManagement + cloud: + consul: + discovery: + enabled: false + instanceId: ${spring.application.name}:${spring.application.instance-id:${random.value}} + config: + enabled: false + enabled: false datasource: type: com.zaxxer.hikari.HikariDataSource - url: jdbc:h2:mem:caseManagement;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE + url: jdbc:h2:mem:casemanagement;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE name: username: password: hikari: auto-commit: false + jackson: + serialization: + write-durations-as-timestamps: false jpa: - database-platform: io.github.jhipster.domain.util.FixedH2Dialect - database: H2 + database-platform: tech.jhipster.domain.util.FixedH2Dialect open-in-view: false - show-sql: false hibernate: ddl-auto: none naming: @@ -42,6 +54,7 @@ spring: hibernate.generate_statistics: false hibernate.hbm2ddl.auto: validate hibernate.jdbc.time_zone: UTC + hibernate.query.fail_on_pagination_over_collection_fetch: true liquibase: contexts: test mail: @@ -50,9 +63,6 @@ spring: allow-bean-definition-overriding: true messages: basename: i18n/messages - mvc: - favicon: - enabled: false task: execution: thread-name-prefix: case-management-task- @@ -89,7 +99,7 @@ jhipster: name: 'caseManagementApp' logging: # To test json console appender - use-json-format: true # By default, logs are in Json format + use-json-format: false logstash: enabled: false host: localhost @@ -98,11 +108,6 @@ jhipster: mail: from: test@localhost base-url: http://127.0.0.1:8080 - metrics: - logs: # Reports metrics in the logs - enabled: true - report-frequency: 60 # in seconds - # =================================================================== # Application specific properties # Add your own application properties here, see the ApplicationProperties class diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml index 111cd1d..b2fad23 100644 --- a/src/test/resources/logback.xml +++ b/src/test/resources/logback.xml @@ -6,7 +6,7 @@ - + @@ -15,9 +15,12 @@ + + + @@ -38,8 +41,10 @@ + - + + WARN diff --git a/src/test/resources/testcontainers/mariadb/my.cnf b/src/test/resources/testcontainers/mariadb/my.cnf new file mode 100644 index 0000000..194faef --- /dev/null +++ b/src/test/resources/testcontainers/mariadb/my.cnf @@ -0,0 +1,2 @@ +[mysqld] +character-set-server = utf8mb4