From 9b890d3e1b373bd3dfefd06199178134353dcb06 Mon Sep 17 00:00:00 2001 From: wayne <wayne8692wayne8692@gmail.com> Date: 星期二, 02 十一月 2021 17:44:06 +0800 Subject: [PATCH] TODO#129597,建立後臺Spring boot專案 --- pamapi/src/main/resources/templates/mail/activationEmail.html | 20 pamapi/src/test/java/com/pollex/pam/web/rest/PublicUserResourceIT.java | 99 pamapi/src/main/java/com/pollex/pam/service/MailService.java | 112 pamapi/src/main/docker/app.yml | 28 pamapi/src/main/java/com/pollex/pam/web/rest/errors/ErrorConstants.java | 17 pamapi/src/main/docker/sonar.yml | 13 pamapi/src/main/java/com/pollex/pam/aop/logging/LoggingAspect.java | 111 pamapi/src/main/java/com/pollex/pam/config/ApplicationProperties.java | 12 pamapi/src/test/java/com/pollex/pam/service/UserServiceIT.java | 185 pamapi/src/main/java/com/pollex/pam/PamapiApp.java | 103 pamapi/src/main/java/com/pollex/pam/config/LiquibaseConfiguration.java | 69 pamapi/src/main/resources/config/application-dev.yml | 114 pamapi/package-lock.json | 5216 +++++++++++++++ pamapi/.editorconfig | 23 pamapi/src/test/java/com/pollex/pam/repository/timezone/DateTimeWrapperRepository.java | 10 pamapi/src/main/java/com/pollex/pam/security/SecurityUtils.java | 100 pamapi/src/main/resources/i18n/messages_en.properties | 21 pamapi/src/test/java/com/pollex/pam/config/timezone/HibernateTimeZoneIT.java | 162 pamapi/src/test/java/com/pollex/pam/IntegrationTest.java | 17 pamapi/src/main/java/com/pollex/pam/web/websocket/dto/package-info.java | 4 pamapi/src/main/docker/postgresql.yml | 15 pamapi/src/main/java/com/pollex/pam/GeneratedByJHipster.java | 13 pamapi/src/main/java/com/pollex/pam/service/dto/UserDTO.java | 48 pamapi/src/test/java/com/pollex/pam/security/jwt/TokenProviderTest.java | 132 pamapi/src/test/java/com/pollex/pam/service/MailServiceIT.java | 245 pamapi/src/test/java/com/pollex/pam/service/mapper/UserMapperTest.java | 132 pamapi/src/main/docker/monitoring.yml | 31 pamapi/src/main/resources/config/application-prod.yml | 135 pamapi/src/test/java/com/pollex/pam/security/SecurityUtilsUnitTest.java | 101 pamapi/src/main/java/com/pollex/pam/config/WebsocketSecurityConfiguration.java | 40 pamapi/src/main/java/com/pollex/pam/web/rest/package-info.java | 4 pamapi/src/test/java/com/pollex/pam/repository/timezone/DateTimeWrapper.java | 133 pamapi/src/main/resources/config/liquibase/data/user.csv | 3 pamapi/src/main/java/com/pollex/pam/security/jwt/JWTFilter.java | 53 pamapi/src/test/resources/config/bootstrap.yml | 0 pamapi/src/test/java/com/pollex/pam/config/WebConfigurerTest.java | 131 pamapi/src/main/resources/config/liquibase/data/authority.csv | 3 pamapi/src/main/resources/templates/mail/passwordResetEmail.html | 22 pamapi/npmw | 25 pamapi/mvnw.cmd | 182 pamapi/sonar-project.properties | 28 pamapi/src/main/java/com/pollex/pam/security/SpringSecurityAuditorAware.java | 18 pamapi/src/main/resources/config/application-tls.yml | 19 pamapi/src/main/resources/templates/error.html | 92 pamapi/src/main/resources/config/liquibase/data/user_authority.csv | 4 pamapi/src/test/resources/i18n/messages_en.properties | 4 pamapi/src/test/java/com/pollex/pam/security/DomainUserDetailsServiceIT.java | 111 pamapi/src/test/resources/config/application-testcontainers.yml | 22 pamapi/.mvn/wrapper/MavenWrapperDownloader.java | 117 pamapi/.mvn/wrapper/maven-wrapper.properties | 2 pamapi/src/main/docker/grafana/provisioning/dashboards/JVM.json | 3778 +++++++++++ pamapi/src/main/resources/config/application.yml | 182 pamapi/src/main/docker/grafana/provisioning/datasources/datasource.yml | 50 pamapi/src/test/resources/config/application.yml | 112 pamapi/src/main/java/com/pollex/pam/web/rest/vm/LoginVM.java | 53 pamapi/src/main/resources/logback-spring.xml | 73 pamapi/src/main/java/com/pollex/pam/security/DomainUserDetailsService.java | 62 pamapi/src/main/docker/jhipster-control-center.yml | 52 pamapi/src/main/resources/config/bootstrap.yml | 14 pamapi/src/main/java/com/pollex/pam/web/rest/vm/package-info.java | 4 pamapi/src/test/java/com/pollex/pam/config/WebConfigurerTestController.java | 14 pamapi/src/test/java/com/pollex/pam/web/rest/errors/ExceptionTranslatorTestController.java | 66 pamapi/src/main/java/com/pollex/pam/repository/UserRepository.java | 42 pamapi/src/main/java/com/pollex/pam/service/dto/AdminUserDTO.java | 193 pamapi/src/main/java/com/pollex/pam/web/rest/errors/LoginAlreadyUsedException.java | 10 pamapi/src/test/java/com/pollex/pam/ArchTest.java | 29 pamapi/src/main/java/com/pollex/pam/web/rest/errors/BadRequestAlertException.java | 41 pamapi/src/test/resources/logback.xml | 49 pamapi/src/main/java/com/pollex/pam/service/mapper/UserMapper.java | 147 pamapi/src/main/java/com/pollex/pam/service/InvalidPasswordException.java | 10 pamapi/src/main/java/com/pollex/pam/web/rest/errors/FieldErrorVM.java | 32 pamapi/src/main/java/com/pollex/pam/web/rest/PublicUserResource.java | 65 pamapi/src/main/resources/config/bootstrap-prod.yml | 6 pamapi/src/main/resources/i18n/messages.properties | 21 pamapi/README.md | 129 pamapi/src/main/java/com/pollex/pam/config/Constants.java | 15 pamapi/src/main/java/com/pollex/pam/repository/AuthorityRepository.java | 9 pamapi/src/main/java/com/pollex/pam/security/UserNotActivatedException.java | 19 pamapi/src/main/java/com/pollex/pam/config/package-info.java | 4 pamapi/src/main/java/com/pollex/pam/config/JacksonConfiguration.java | 51 pamapi/src/main/java/com/pollex/pam/config/AsyncConfiguration.java | 46 pamapi/src/main/docker/jib/entrypoint.sh | 4 pamapi/src/main/docker/central-server-config/README.md | 1 pamapi/src/main/resources/banner.txt | 10 pamapi/src/main/java/com/pollex/pam/security/package-info.java | 4 pamapi/src/main/java/com/pollex/pam/service/UserService.java | 325 pamapi/src/test/java/com/pollex/pam/web/rest/TestUtil.java | 206 pamapi/src/main/java/com/pollex/pam/web/websocket/dto/ActivityDTO.java | 71 pamapi/src/main/java/com/pollex/pam/config/LocaleConfiguration.java | 26 pamapi/src/main/java/com/pollex/pam/ApplicationWebXml.java | 19 pamapi/.yo-rc-global.json | 8 pamapi/src/main/java/com/pollex/pam/config/WebsocketConfiguration.java | 90 pamapi/mvnw | 310 pamapi/src/main/java/com/pollex/pam/domain/Authority.java | 61 pamapi/src/test/java/com/pollex/pam/web/rest/UserJWTControllerIT.java | 98 pamapi/src/main/java/com/pollex/pam/web/rest/errors/EmailAlreadyUsedException.java | 10 pamapi/src/main/java/com/pollex/pam/web/websocket/package-info.java | 4 pamapi/src/main/java/com/pollex/pam/service/package-info.java | 4 pamapi/src/test/java/com/pollex/pam/config/NoOpMailConfiguration.java | 25 pamapi/src/main/java/com/pollex/pam/web/rest/errors/ExceptionTranslator.java | 222 pamapi/src/main/resources/i18n/messages_zh_TW.properties | 21 pamapi/src/main/resources/templates/mail/creationEmail.html | 20 pamapi/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml | 121 pamapi/src/main/resources/config/liquibase/master.xml | 21 pamapi/src/main/java/com/pollex/pam/config/CacheConfiguration.java | 78 pamapi/src/main/java/com/pollex/pam/security/AuthoritiesConstants.java | 15 pamapi/src/main/java/com/pollex/pam/web/rest/UserResource.java | 207 pamapi/src/main/resources/config/tls/keystore.p12 | 0 pamapi/.prettierrc | 18 pamapi/src/main/java/com/pollex/pam/config/LoggingAspectConfiguration.java | 17 pamapi/.prettierignore | 8 pamapi/src/main/java/com/pollex/pam/service/dto/package-info.java | 4 pamapi/src/main/java/com/pollex/pam/config/DatabaseConfiguration.java | 16 pamapi/src/test/resources/templates/mail/testEmail.html | 1 pamapi/checkstyle.xml | 20 pamapi/src/main/java/com/pollex/pam/web/rest/errors/InvalidPasswordException.java | 13 pamapi/.gitignore | 158 pamapi/src/main/java/com/pollex/pam/config/DateTimeFormatConfiguration.java | 20 pamapi/src/main/java/com/pollex/pam/service/dto/PasswordChangeDTO.java | 35 pamapi/src/main/java/com/pollex/pam/security/jwt/TokenProvider.java | 101 pamapi/src/main/java/com/pollex/pam/service/UsernameAlreadyUsedException.java | 10 pamapi/src/test/java/com/pollex/pam/web/rest/WithUnauthenticatedMockUser.java | 23 pamapi/src/main/java/com/pollex/pam/service/EmailAlreadyUsedException.java | 10 pamapi/src/main/java/com/pollex/pam/web/rest/vm/KeyAndPasswordVM.java | 27 pamapi/.gitattributes | 150 pamapi/package.json | 62 pamapi/npmw.cmd | 24 pamapi/src/main/java/com/pollex/pam/web/rest/errors/package-info.java | 6 pamapi/pom.xml | 1026 +++ pamapi/src/main/java/com/pollex/pam/config/WebConfigurer.java | 60 pamapi/src/main/java/com/pollex/pam/domain/AbstractAuditingEntity.java | 76 pamapi/src/main/java/com/pollex/pam/web/websocket/ActivityService.java | 46 pamapi/src/test/java/com/pollex/pam/web/rest/AccountResourceIT.java | 760 ++ pamapi/src/main/java/com/pollex/pam/repository/package-info.java | 4 pamapi/.mvn/wrapper/maven-wrapper.jar | 0 pamapi/src/main/docker/grafana/provisioning/dashboards/dashboard.yml | 11 pamapi/src/main/java/com/pollex/pam/service/mapper/package-info.java | 4 pamapi/src/main/java/com/pollex/pam/web/rest/UserJWTController.java | 68 pamapi/src/test/java/com/pollex/pam/security/jwt/JWTFilterTest.java | 112 pamapi/src/main/java/com/pollex/pam/domain/package-info.java | 4 pamapi/src/test/java/com/pollex/pam/web/rest/UserResourceIT.java | 583 + pamapi/src/main/java/com/pollex/pam/domain/User.java | 232 pamapi/src/main/java/com/pollex/pam/web/rest/AccountResource.java | 194 pamapi/src/main/java/com/pollex/pam/config/SecurityConfiguration.java | 104 pamapi/src/main/java/com/pollex/pam/web/rest/vm/ManagedUserVM.java | 35 pamapi/src/test/java/com/pollex/pam/web/rest/errors/ExceptionTranslatorIT.java | 118 pamapi/src/main/java/com/pollex/pam/config/LoggingConfiguration.java | 47 pamapi/.yo-rc.json | 48 pamapi/src/main/java/com/pollex/pam/security/jwt/JWTConfigurer.java | 21 pamapi/src/test/resources/i18n/messages_zh_TW.properties | 1 pamapi/src/main/docker/prometheus/prometheus.yml | 31 151 files changed, 19,968 insertions(+), 0 deletions(-) diff --git a/pamapi/.editorconfig b/pamapi/.editorconfig new file mode 100644 index 0000000..c2fa6a2 --- /dev/null +++ b/pamapi/.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,html,vue}] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/pamapi/.gitattributes b/pamapi/.gitattributes new file mode 100644 index 0000000..ca61722 --- /dev/null +++ b/pamapi/.gitattributes @@ -0,0 +1,150 @@ +# 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 +*.cmd text eol=crlf +*.ps1 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/pamapi/.gitignore b/pamapi/.gitignore new file mode 100644 index 0000000..4dd6fcb --- /dev/null +++ b/pamapi/.gitignore @@ -0,0 +1,158 @@ +###################### +# 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/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +###################### +# 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 + +###################### +# Code coverage +###################### +/coverage/ +/.nyc_output/ diff --git a/pamapi/.mvn/wrapper/MavenWrapperDownloader.java b/pamapi/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..e76d1f3 --- /dev/null +++ b/pamapi/.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 + * + * https://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.6"; + /** + * 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/pamapi/.mvn/wrapper/maven-wrapper.jar b/pamapi/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..2cc7d4a --- /dev/null +++ b/pamapi/.mvn/wrapper/maven-wrapper.jar Binary files differ diff --git a/pamapi/.mvn/wrapper/maven-wrapper.properties b/pamapi/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..a9f1ef8 --- /dev/null +++ b/pamapi/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.3/apache-maven-3.8.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/pamapi/.prettierignore b/pamapi/.prettierignore new file mode 100644 index 0000000..ab0567f --- /dev/null +++ b/pamapi/.prettierignore @@ -0,0 +1,8 @@ +node_modules +target +build +package-lock.json +.git +.mvn +gradle +.gradle diff --git a/pamapi/.prettierrc b/pamapi/.prettierrc new file mode 100644 index 0000000..f9b9911 --- /dev/null +++ b/pamapi/.prettierrc @@ -0,0 +1,18 @@ +# Prettier configuration + +printWidth: 140 +singleQuote: true +tabWidth: 2 +useTabs: false + +# js and ts rules: +arrowParens: avoid + +# jsx and tsx rules: +jsxBracketSameLine: false + +# java rules: +overrides: + - files: "*.java" + options: + tabWidth: 4 diff --git a/pamapi/.yo-rc-global.json b/pamapi/.yo-rc-global.json new file mode 100644 index 0000000..6fa35d8 --- /dev/null +++ b/pamapi/.yo-rc-global.json @@ -0,0 +1,8 @@ +{ + "generator-jhipster:7.3.0": { + "promptValues": { + "packageName": "com.pollex.pam", + "nativeLanguage": "zh-tw" + } + } +} diff --git a/pamapi/.yo-rc.json b/pamapi/.yo-rc.json new file mode 100644 index 0000000..3f73757 --- /dev/null +++ b/pamapi/.yo-rc.json @@ -0,0 +1,48 @@ +{ + "generator-jhipster": { + "skipClient": true, + "applicationType": "monolith", + "baseName": "pamapi", + "jhipsterVersion": "7.3.0", + "skipServer": false, + "skipUserManagement": false, + "skipCheckLengthOfIdentifier": false, + "skipFakeData": false, + "jhiPrefix": "jhi", + "entitySuffix": "", + "dtoSuffix": "DTO", + "testFrameworks": [], + "blueprints": [], + "otherModules": [], + "pages": [], + "creationTimestamp": 1635842756094, + "serviceDiscoveryType": "no", + "reactive": false, + "authenticationType": "jwt", + "packageName": "com.pollex.pam", + "serverPort": "8080", + "cacheProvider": "ehcache", + "enableHibernateCache": true, + "databaseType": "sql", + "devDatabaseType": "postgresql", + "prodDatabaseType": "postgresql", + "buildTool": "maven", + "serverSideOptions": ["websocket:spring-websocket"], + "websocket": "spring-websocket", + "searchEngine": false, + "messageBroker": false, + "enableSwaggerCodegen": false, + "enableTranslation": true, + "nativeLanguage": "zh-tw", + "monorepository": true, + "skipCommitHook": true, + "jwtSecretKey": "MjI3YWRiMzg1ZTY3ZmZkZDgxZmI5Yjc5YjVkOTIzMzc4MmI2OWM3NWVkZjFiOTNmNjg0YWFjZWQ4YzhlOTUzYzk3MGUzMzM5Y2U5MDdkZTMyN2Q0N2E0M2ZmM2FhYzkyNDY4MjBkYTY5OGM4YmIzMmYxODJhNWFkMmVhNmVhNTM=", + "languages": ["zh-tw", "en"], + "enableGradleEnterprise": false, + "clientFramework": "angularX", + "clientPackageManager": "npm", + "clientTheme": "none", + "clientThemeVariant": "", + "withAdminUi": true + } +} diff --git a/pamapi/README.md b/pamapi/README.md new file mode 100644 index 0000000..0994108 --- /dev/null +++ b/pamapi/README.md @@ -0,0 +1,129 @@ +# pamapi + +This application was generated using JHipster 7.3.0, you can find documentation and help at [https://www.jhipster.tech/documentation-archive/v7.3.0](https://www.jhipster.tech/documentation-archive/v7.3.0). + +## 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][]. + +### JHipster Control Center + +JHipster Control Center can help you manage and control your application(s). You can start a local control center server (accessible on http://localhost:7419) with: + +``` +docker-compose -f src/main/docker/jhipster-control-center.yml up +``` + +## Building for production + +### Packaging as jar + +To build the final jar and optimize the pamapi 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 +``` + +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: + +``` +./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 +``` + +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 postgresql database in a docker container, run: + +``` +docker-compose -f src/main/docker/postgresql.yml up -d +``` + +To stop it and remove the container, run: + +``` +docker-compose -f src/main/docker/postgresql.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 7.3.0 archive]: https://www.jhipster.tech/documentation-archive/v7.3.0 +[using jhipster in development]: https://www.jhipster.tech/documentation-archive/v7.3.0/development/ +[using docker and docker-compose]: https://www.jhipster.tech/documentation-archive/v7.3.0/docker-compose +[using jhipster in production]: https://www.jhipster.tech/documentation-archive/v7.3.0/production/ +[running tests page]: https://www.jhipster.tech/documentation-archive/v7.3.0/running-tests/ +[code quality page]: https://www.jhipster.tech/documentation-archive/v7.3.0/code-quality/ +[setting up continuous integration]: https://www.jhipster.tech/documentation-archive/v7.3.0/setting-up-ci/ +[node.js]: https://nodejs.org/ +[npm]: https://www.npmjs.com/ diff --git a/pamapi/checkstyle.xml b/pamapi/checkstyle.xml new file mode 100644 index 0000000..5d5ae65 --- /dev/null +++ b/pamapi/checkstyle.xml @@ -0,0 +1,20 @@ +<?xml version="1.0"?> +<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN" + "https://checkstyle.org/dtds/configuration_1_3.dtd"> +<module name="Checker"> + + <!-- Configure checker to use UTF-8 encoding --> + <property name="charset" value="UTF-8"/> + <!-- Configure checker to run on files with these extensions --> + <property name="fileExtensions" value=""/> + <!-- For detailed checkstyle configuration, see https://github.com/spring-io/nohttp/tree/master/nohttp-checkstyle --> + <module name="io.spring.nohttp.checkstyle.check.NoHttpCheck"> + <property name="allowlist" value="^\Qhttp://maven.apache.org/POM/4.0.0 ^\Qhttp://www.w3.org/2001/XMLSchema-instance"/> + </module> + <!-- Allow suppression with comments + // CHECKSTYLE:OFF + ... ignored content ... + // CHECKSTYLE:ON + --> + <module name="SuppressWithPlainTextCommentFilter"/> +</module> diff --git a/pamapi/mvnw b/pamapi/mvnw new file mode 100644 index 0000000..a16b543 --- /dev/null +++ b/pamapi/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 +# +# https://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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven 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.6/maven-wrapper-0.5.6.jar" + else + 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 ;; + 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/pamapi/mvnw.cmd b/pamapi/mvnw.cmd new file mode 100644 index 0000000..c8d4337 --- /dev/null +++ b/pamapi/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 https://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 Maven 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 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 +@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.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 +) + +@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.6/maven-wrapper-0.5.6.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/pamapi/npmw b/pamapi/npmw new file mode 100644 index 0000000..c9f12d7 --- /dev/null +++ b/pamapi/npmw @@ -0,0 +1,25 @@ +#!/bin/sh + +basedir=`dirname "$0"` + +if [ -f "$basedir/mvnw" ]; then + builddir="target/node" + installCommand="$basedir/mvnw frontend:install-node-and-npm@install-node-and-npm" +else + builddir=".gradle/npm" + installCommand="$basedir/gradlew npmSetup" +fi + +NPM_EXE="$basedir/$builddir/npm" + +if ! [ -x "$NPM_EXE" ]; then + $installCommand || true +fi + +if ! [ -x "$NPM_EXE" ]; then + echo "Using npm installed globally" + npm "$@" +else + echo "Using npm installed locally $($NPM_EXE --version)" + $NPM_EXE "$@" +fi diff --git a/pamapi/npmw.cmd b/pamapi/npmw.cmd new file mode 100644 index 0000000..f0c285c --- /dev/null +++ b/pamapi/npmw.cmd @@ -0,0 +1,24 @@ +@echo off + +@setlocal + +set NPMW_DIR=%~dp0 + +if exist "%NPMW_DIR%\mvnw.cmd" ( + set NPM_EXE=%NPMW_DIR%\target\node\npm.cmd + set INSTALL_NPM_COMMAND=%NPMW_DIR%\mvnw.cmd frontend:install-node-and-npm@install-node-and-npm +) else ( + set NPM_EXE=%NPMW_DIR%\.gradle\npm\npm.cmd + set INSTALL_NPM_COMMAND=%NPMW_DIR%\gradlew.bat npmSetup +) + +if not exist %NPM_EXE% ( + call %INSTALL_NPM_COMMAND% +) + +if not exist %NPM_EXE% goto globalNpm + +%NPM_EXE% %* + +:globalNpm +npm %* diff --git a/pamapi/package-lock.json b/pamapi/package-lock.json new file mode 100644 index 0000000..4d21df7 --- /dev/null +++ b/pamapi/package-lock.json @@ -0,0 +1,5216 @@ +{ + "name": "pamapi", + "version": "0.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", + "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.16.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true + }, + "@babel/highlight": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", + "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.15.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@chevrotain/types": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-9.1.0.tgz", + "integrity": "sha512-3hbCD1CThkv9gnaSIPq0GUXwKni68e0ph6jIHwCvcWiQ4JB2xi8bFxBain0RF04qHUWuDjgnZLj4rLgimuGO+g==", + "dev": true + }, + "@chevrotain/utils": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-9.1.0.tgz", + "integrity": "sha512-llLJZ8OAlZrjGlBvamm6Zdo/HmGAcCLq5gx7cSwUX8No+n/8ip+oaC4x33IdZIif8+Rh5dQUIZXmfbSghiOmNQ==", + "dev": true + }, + "@dabh/diagnostics": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz", + "integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==", + "dev": true, + "requires": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "@gar/promisify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.2.tgz", + "integrity": "sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw==", + "dev": true + }, + "@isaacs/string-locale-compare": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz", + "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==", + "dev": true + }, + "@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "dev": true, + "requires": { + "debug": "^4.1.1" + } + }, + "@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "dev": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@npmcli/arborist": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.10.0.tgz", + "integrity": "sha512-CLnD+zXG9oijEEzViimz8fbOoFVb7hoypiaf7p6giJhvYtrxLAyY3cZAMPIFQvsG731+02eMDp3LqVBNo7BaZA==", + "dev": true, + "requires": { + "@isaacs/string-locale-compare": "^1.0.1", + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/map-workspaces": "^1.0.2", + "@npmcli/metavuln-calculator": "^1.1.0", + "@npmcli/move-file": "^1.1.0", + "@npmcli/name-from-folder": "^1.0.1", + "@npmcli/node-gyp": "^1.0.1", + "@npmcli/package-json": "^1.0.1", + "@npmcli/run-script": "^1.8.2", + "bin-links": "^2.2.1", + "cacache": "^15.0.3", + "common-ancestor-path": "^1.0.1", + "json-parse-even-better-errors": "^2.3.1", + "json-stringify-nice": "^1.1.4", + "mkdirp": "^1.0.4", + "mkdirp-infer-owner": "^2.0.0", + "npm-install-checks": "^4.0.0", + "npm-package-arg": "^8.1.5", + "npm-pick-manifest": "^6.1.0", + "npm-registry-fetch": "^11.0.0", + "pacote": "^11.3.5", + "parse-conflict-json": "^1.1.1", + "proc-log": "^1.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^1.0.1", + "read-package-json-fast": "^2.0.2", + "readdir-scoped-modules": "^1.1.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "ssri": "^8.0.1", + "treeverse": "^1.0.4", + "walk-up-path": "^1.0.0" + } + }, + "@npmcli/fs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.0.0.tgz", + "integrity": "sha512-8ltnOpRR/oJbOp8vaGUnipOi3bqkcW+sLHFlyXIr08OGHmVJLB1Hn7QtGXbYcpVtH1gAYZTlmDXtE4YV0+AMMQ==", + "dev": true, + "requires": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "@npmcli/git": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-2.1.0.tgz", + "integrity": "sha512-/hBFX/QG1b+N7PZBFs0bi+evgRZcK9nWBxQKZkGoXUT5hJSwl5c4d7y8/hm+NQZRPhQ67RzFaj5UM9YeyKoryw==", + "dev": true, + "requires": { + "@npmcli/promise-spawn": "^1.3.2", + "lru-cache": "^6.0.0", + "mkdirp": "^1.0.4", + "npm-pick-manifest": "^6.1.1", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^2.0.2" + } + }, + "@npmcli/installed-package-contents": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz", + "integrity": "sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw==", + "dev": true, + "requires": { + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "@npmcli/map-workspaces": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-1.0.4.tgz", + "integrity": "sha512-wVR8QxhyXsFcD/cORtJwGQodeeaDf0OxcHie8ema4VgFeqwYkFsDPnSrIRSytX8xR6nKPAH89WnwTcaU608b/Q==", + "dev": true, + "requires": { + "@npmcli/name-from-folder": "^1.0.1", + "glob": "^7.1.6", + "minimatch": "^3.0.4", + "read-package-json-fast": "^2.0.1" + } + }, + "@npmcli/metavuln-calculator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-1.1.1.tgz", + "integrity": "sha512-9xe+ZZ1iGVaUovBVFI9h3qW+UuECUzhvZPxK9RaEA2mjU26o5D0JloGYWwLYvQELJNmBdQB6rrpuN8jni6LwzQ==", + "dev": true, + "requires": { + "cacache": "^15.0.5", + "pacote": "^11.1.11", + "semver": "^7.3.2" + } + }, + "@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "@npmcli/name-from-folder": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-1.0.1.tgz", + "integrity": "sha512-qq3oEfcLFwNfEYOQ8HLimRGKlD8WSeGEdtUa7hmzpR8Sa7haL1KVQrvgO6wqMjhWFFVjgtrh1gIxDz+P8sjUaA==", + "dev": true + }, + "@npmcli/node-gyp": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-1.0.3.tgz", + "integrity": "sha512-fnkhw+fmX65kiLqk6E3BFLXNC26rUhK90zVwe2yncPliVT/Qos3xjhTLE59Df8KnPlcwIERXKVlU1bXoUQ+liA==", + "dev": true + }, + "@npmcli/package-json": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-1.0.1.tgz", + "integrity": "sha512-y6jnu76E9C23osz8gEMBayZmaZ69vFOIk8vR1FJL/wbEJ54+9aVG9rLTjQKSXfgYZEr50nw1txBBFfBZZe+bYg==", + "dev": true, + "requires": { + "json-parse-even-better-errors": "^2.3.1" + } + }, + "@npmcli/promise-spawn": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-1.3.2.tgz", + "integrity": "sha512-QyAGYo/Fbj4MXeGdJcFzZ+FkDkomfRBrPM+9QYJSg+PxgAUL+LU3FneQk37rKR2/zjqkCV1BLHccX98wRXG3Sg==", + "dev": true, + "requires": { + "infer-owner": "^1.0.4" + } + }, + "@npmcli/run-script": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-1.8.6.tgz", + "integrity": "sha512-e42bVZnC6VluBZBAFEr3YrdqSspG3bgilyg4nSLBJ7TRGNCzxHa92XAHxQBLYg0BmgwO4b2mf3h/l5EkEWRn3g==", + "dev": true, + "requires": { + "@npmcli/node-gyp": "^1.0.2", + "@npmcli/promise-spawn": "^1.3.2", + "node-gyp": "^7.1.0", + "read-package-json-fast": "^2.0.1" + } + }, + "@octokit/auth-token": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", + "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", + "dev": true, + "requires": { + "@octokit/types": "^6.0.3" + } + }, + "@octokit/core": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz", + "integrity": "sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==", + "dev": true, + "requires": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.0", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "dev": true, + "requires": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + }, + "dependencies": { + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true + } + } + }, + "@octokit/graphql": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", + "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "dev": true, + "requires": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/openapi-types": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz", + "integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==", + "dev": true + }, + "@octokit/plugin-paginate-rest": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz", + "integrity": "sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw==", + "dev": true, + "requires": { + "@octokit/types": "^6.34.0" + } + }, + "@octokit/plugin-request-log": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", + "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", + "dev": true + }, + "@octokit/plugin-rest-endpoint-methods": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz", + "integrity": "sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA==", + "dev": true, + "requires": { + "@octokit/types": "^6.34.0", + "deprecation": "^2.3.1" + } + }, + "@octokit/request": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.2.tgz", + "integrity": "sha512-je66CvSEVf0jCpRISxkUcCa0UkxmFs6eGDRSbfJtAVwbLH5ceqF+YEyC8lj8ystKyZTy8adWr0qmkY52EfOeLA==", + "dev": true, + "requires": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.1", + "universal-user-agent": "^6.0.0" + }, + "dependencies": { + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true + } + } + }, + "@octokit/request-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "dev": true, + "requires": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "@octokit/rest": { + "version": "18.12.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz", + "integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==", + "dev": true, + "requires": { + "@octokit/core": "^3.5.1", + "@octokit/plugin-paginate-rest": "^2.16.8", + "@octokit/plugin-request-log": "^1.0.4", + "@octokit/plugin-rest-endpoint-methods": "^5.12.0" + } + }, + "@octokit/types": { + "version": "6.34.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz", + "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==", + "dev": true, + "requires": { + "@octokit/openapi-types": "^11.2.0" + } + }, + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true + }, + "@types/concat-stream": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.1.tgz", + "integrity": "sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/expect": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", + "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", + "dev": true + }, + "@types/form-data": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", + "integrity": "sha1-yayFsqX9GENbjIXZ7LUObWyJP/g=", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, + "@types/node": { + "version": "16.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz", + "integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==", + "dev": true + }, + "@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "dev": true + }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "@types/vinyl": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.6.tgz", + "integrity": "sha512-ayJ0iOCDNHnKpKTgBG6Q6JOnHTj9zFta+3j2b8Ejza0e4cvRyMn0ZoLEmbPrTHe5YYRlDYPvPWVdV4cTaRyH7g==", + "dev": true, + "requires": { + "@types/expect": "^1.20.4", + "@types/node": "*" + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "agentkeepalive": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.1.4.tgz", + "integrity": "sha512-+V/rGa3EuU74H6wR04plBb7Ks10FbtUQgRj/FQOG7uUIEuaINI+AiqJR1k6t3SVNs7o7ZjIdus6706qqzVq8jQ==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "8.6.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", + "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "requires": { + "ajv": "^8.0.0" + } + }, + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-differ": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", + "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", + "dev": true + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atomically": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz", + "integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==", + "dev": true + }, + "aws-sdk": { + "version": "2.999.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.999.0.tgz", + "integrity": "sha512-OcnD7m+HCZv2qDzmS7TgABGf26mVPfIyah0Dgz7hHAxBtx78qFWi/s9U6BDxVBKWLg7OKWVHf0opiMG4ujteqg==", + "dev": true, + "requires": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + }, + "dependencies": { + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + } + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "axios": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.22.0.tgz", + "integrity": "sha512-Z0U3uhqQeg1oNcihswf4ZD57O3NrR1+ZXhxaROaWpDmsDTx7T2HNBV2ulBtie2hwJptu8UvgnJoK+BIqdzh/1w==", + "dev": true, + "requires": { + "follow-redirects": "^1.14.4" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "before-after-hook": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", + "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==", + "dev": true + }, + "bin-links": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-2.3.0.tgz", + "integrity": "sha512-JzrOLHLwX2zMqKdyYZjkDgQGT+kHDkIhv2/IK2lJ00qLxV4TmFoHi8drDBb6H5Zrz1YfgHkai4e2MGPqnoUhqA==", + "dev": true, + "requires": { + "cmd-shim": "^4.0.1", + "mkdirp-infer-owner": "^2.0.0", + "npm-normalize-package-bin": "^1.0.0", + "read-cmd-shim": "^2.0.0", + "rimraf": "^3.0.0", + "write-file-atomic": "^3.0.3" + } + }, + "binaryextensions": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-4.18.0.tgz", + "integrity": "sha512-PQu3Kyv9dM4FnwB7XGj1+HucW+ShvJzJqjuw1JkKVs1mWdwOKVcRjOi+pV9X52A0tNvrPCsPkbFFQb+wE1EAXw==", + "dev": true + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "builtins": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", + "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", + "dev": true + }, + "cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "dev": true, + "requires": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "chevrotain": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-9.0.2.tgz", + "integrity": "sha512-6ZjgUdGvU4j1n1b2hTjb79Vr2V+qNtmP7f8FVt79+kdAYcUj2QfYNwI8ycCVsgHD/dIeO5Vr1hckkkfliVQTfg==", + "dev": true, + "requires": { + "@chevrotain/types": "^9.0.2", + "@chevrotain/utils": "^9.0.2", + "regexp-to-ast": "0.5.0" + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-spinners": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", + "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", + "dev": true + }, + "cli-table": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.6.tgz", + "integrity": "sha512-ZkNZbnZjKERTY5NwC2SeMeLeifSPq/pubeRoTpdr3WchLlnZg6hEgvHkK5zL7KNFdd9PmHN8lxrENUwI3cE8vQ==", + "dev": true, + "requires": { + "colors": "1.0.3" + }, + "dependencies": { + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true + } + } + }, + "cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "dev": true + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "cmd-shim": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-4.1.0.tgz", + "integrity": "sha512-lb9L7EM4I/ZRVuljLPEtUJOP+xiQVknZ4ZMpMgEp4JzNldPb27HU03hi6K1/6CoIuit/Zm/LQXySErFeXxDprw==", + "dev": true, + "requires": { + "mkdirp-infer-owner": "^2.0.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dev": true, + "requires": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + }, + "dependencies": { + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "color-string": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.6.0.tgz", + "integrity": "sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA==", + "dev": true, + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true + }, + "colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "dev": true, + "requires": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.2.0.tgz", + "integrity": "sha512-LLKxDvHeL91/8MIyTAD5BFMNtoIwztGPMiM/7Bl8rIPmHCZXRxmSWr91h57dpOpnQ6jIUqEWdXE/uBYMfiVZDA==", + "dev": true + }, + "common-ancestor-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", + "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "conf": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/conf/-/conf-10.0.3.tgz", + "integrity": "sha512-4gtQ/Q36qVxBzMe6B7gWOAfni1VdhuHkIzxydHkclnwGmgN+eW4bb6jj73vigCfr7d3WlmqawvhZrpCUCTPYxQ==", + "dev": true, + "requires": { + "ajv": "^8.6.3", + "ajv-formats": "^2.1.1", + "atomically": "^1.7.0", + "debounce-fn": "^4.0.0", + "dot-prop": "^6.0.1", + "env-paths": "^2.2.1", + "json-schema-typed": "^7.0.3", + "onetime": "^5.1.2", + "pkg-up": "^3.1.0", + "semver": "^7.3.5" + } + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "dargs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", + "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "dev": true + }, + "debounce-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz", + "integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==", + "dev": true, + "requires": { + "mimic-fn": "^3.0.0" + } + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "debuglog": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", + "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=", + "dev": true + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "dev": true + }, + "detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "dezalgo": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", + "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", + "dev": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "drange": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/drange/-/drange-1.1.1.tgz", + "integrity": "sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ejs": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz", + "integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==", + "dev": true, + "requires": { + "jake": "^10.6.1" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "dev": true + }, + "encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "requires": { + "iconv-lite": "^0.6.2" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true + }, + "err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true + }, + "error": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/error/-/error-10.4.0.tgz", + "integrity": "sha512-YxIFEJuhgcICugOUvRx5th0UM+ActZ9sjY0QJmeVwsQdvosZ7kYzc9QqS0Da3R5iUmgU5meGIxh0xBeZpMVeLw==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + } + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "dev": true + }, + "execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "faker": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/faker/-/faker-5.5.3.tgz", + "integrity": "sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-glob": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", + "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fecha": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.1.tgz", + "integrity": "sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q==", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "filelist": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz", + "integrity": "sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==", + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "find-yarn-workspace-root2": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root2/-/find-yarn-workspace-root2-1.2.16.tgz", + "integrity": "sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==", + "dev": true, + "requires": { + "micromatch": "^4.0.2", + "pkg-dir": "^4.2.0" + } + }, + "first-chunk-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz", + "integrity": "sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "dev": true + }, + "follow-redirects": { + "version": "1.14.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz", + "integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "generator-jhipster": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/generator-jhipster/-/generator-jhipster-7.3.0.tgz", + "integrity": "sha512-15GMc6FXr7ODpTEo/f19BeLJuXh+NlTKHsyvWQxt7Gq2thmMe3YOhiiGbDFtTt5V3luD3UCQkA4Wjsr1FHcVUQ==", + "dev": true, + "requires": { + "aws-sdk": "2.999.0", + "axios": "0.22.0", + "chalk": "4.1.2", + "chevrotain": "9.0.2", + "commander": "8.2.0", + "conf": "10.0.3", + "debug": "4.3.2", + "didyoumean": "1.2.2", + "ejs": "3.1.6", + "faker": "5.5.3", + "glob": "7.2.0", + "gulp-filter": "7.0.0", + "insight": "0.11.1", + "js-yaml": "4.1.0", + "lodash": "4.17.21", + "mem-fs-editor": "9.3.0", + "minimatch": "3.0.4", + "normalize-path": "3.0.0", + "os-locale": "5.0.0", + "p-queue": "6.6.2", + "parse-gitignore": "1.0.1", + "pluralize": "8.0.0", + "prettier": "2.4.1", + "prettier-plugin-java": "1.4.0", + "prettier-plugin-packagejson": "2.2.13", + "progress": "2.0.3", + "randexp": "0.5.3", + "semver": "7.3.5", + "shelljs": "0.8.4", + "simple-git": "2.46.0", + "then-request": "6.0.2", + "uuid": "8.3.2", + "winston": "3.3.3", + "yeoman-environment": "3.6.0", + "yeoman-generator": "5.4.2" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "git-hooks-list": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/git-hooks-list/-/git-hooks-list-1.0.3.tgz", + "integrity": "sha512-Y7wLWcrLUXwk2noSka166byGCvhMtDRpgHdzCno1UQv/n/Hegp++a2xBWJL1lJarnKD3SWaljD+0z1ztqxuKyQ==", + "dev": true + }, + "github-username": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/github-username/-/github-username-6.0.0.tgz", + "integrity": "sha512-7TTrRjxblSI5l6adk9zd+cV5d6i1OrJSo3Vr9xdGqFLBQo0mz5P9eIfKCDJ7eekVGGFLbce0qbPSnktXV2BjDQ==", + "dev": true, + "requires": { + "@octokit/rest": "^18.0.6" + } + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globby": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true + }, + "grouped-queue": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/grouped-queue/-/grouped-queue-2.0.0.tgz", + "integrity": "sha512-/PiFUa7WIsl48dUeCvhIHnwNmAAzlI/eHoJl0vu3nsFA366JleY7Ff8EVTplZu5kO0MIdZjKTTnzItL61ahbnw==", + "dev": true + }, + "gulp-filter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/gulp-filter/-/gulp-filter-7.0.0.tgz", + "integrity": "sha512-ZGWtJo0j1mHfP77tVuhyqem4MRA5NfNRjoVe6VAkLGeQQ/QGo2VsFwp7zfPTGDsd1rwzBmoDHhxpE6f5B3Zuaw==", + "dev": true, + "requires": { + "multimatch": "^5.0.0", + "plugin-error": "^1.0.1", + "streamfilter": "^3.0.0", + "to-absolute-glob": "^2.0.2" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "dev": true, + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + } + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true + }, + "hosted-git-info": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz", + "integrity": "sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "http-basic": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz", + "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==", + "dev": true, + "requires": { + "caseless": "^0.12.0", + "concat-stream": "^1.6.2", + "http-response-object": "^3.0.1", + "parse-cache-control": "^1.0.1" + } + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dev": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + } + }, + "http-response-object": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", + "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", + "dev": true, + "requires": { + "@types/node": "^10.0.3" + }, + "dependencies": { + "@types/node": { + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", + "dev": true + } + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true + }, + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true + }, + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + }, + "ignore-walk": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz", + "integrity": "sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==", + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "inquirer": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "insight": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/insight/-/insight-0.11.1.tgz", + "integrity": "sha512-TBcZ0qC9dgdmcxL93OoqkY/RZXJtIi0i07phX/QyYk2ysmJtZex59dgTj4Doq50N9CG9dLRe/RIudc/5CCoFNw==", + "dev": true, + "requires": { + "async": "^2.6.2", + "chalk": "^4.1.1", + "conf": "^10.0.1", + "inquirer": "^6.3.1", + "lodash.debounce": "^4.0.8", + "os-name": "^4.0.1", + "request": "^2.88.0", + "tough-cookie": "^4.0.0", + "uuid": "^8.3.2" + }, + "dependencies": { + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + } + } + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, + "invert-kv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-3.0.1.tgz", + "integrity": "sha512-CYdFeFexxhv/Bcny+Q0BfOV+ltRlJcd4BBZBYFX/O0u4npJrgZtIcjokegtiSMAvlMTJ+Koq0GBCc//3bueQxw==", + "dev": true + }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, + "is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "requires": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + } + }, + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "dev": true + }, + "is-core-module": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", + "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true + }, + "is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=", + "dev": true + }, + "is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "requires": { + "is-unc-path": "^1.0.0" + } + }, + "is-scoped": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-scoped/-/is-scoped-2.1.0.tgz", + "integrity": "sha512-Cv4OpPTHAK9kHYzkzCrof3VJh7H/PrG2MBUMvvJebaaUMbqhm0YAtXnvh0I3Hnj2tMZWwrRROWLSgfJrKqWmlQ==", + "dev": true, + "requires": { + "scoped-regex": "^2.0.0" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "requires": { + "unc-path-regex": "^0.1.2" + } + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isbinaryfile": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.8.tgz", + "integrity": "sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "jake": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", + "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", + "dev": true, + "requires": { + "async": "0.9.x", + "chalk": "^2.4.2", + "filelist": "^1.0.1", + "minimatch": "^3.0.4" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "java-parser": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/java-parser/-/java-parser-1.4.0.tgz", + "integrity": "sha512-5r1Ez6D8SRprarqwXTYzu6Am//jw37USu8ie8a5166KfgTc2yKa0wlz08xKX1HXYRo/jbnODUfGMgd7gPhHLog==", + "dev": true, + "requires": { + "chevrotain": "6.5.0", + "lodash": "4.17.21" + }, + "dependencies": { + "chevrotain": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-6.5.0.tgz", + "integrity": "sha512-BwqQ/AgmKJ8jcMEjaSnfMybnKMgGTrtDKowfTP3pX4jwVy0kNjRsT/AP6h+wC3+3NC+X8X15VWBnTCQlX+wQFg==", + "dev": true, + "requires": { + "regexp-to-ast": "0.4.0" + } + }, + "regexp-to-ast": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.4.0.tgz", + "integrity": "sha512-4qf/7IsIKfSNHQXSwial1IFmfM1Cc/whNBQqRwe0V2stPe7KmN1U0tWQiIx6JiirgSrisjE0eECdNf7Tav1Ntw==", + "dev": true + } + } + }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "json-schema-typed": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz", + "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==", + "dev": true + }, + "json-stringify-nice": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz", + "integrity": "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "just-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/just-diff/-/just-diff-3.1.1.tgz", + "integrity": "sha512-sdMWKjRq8qWZEjDcVA6llnUT8RDEBIfOiGpYFPYa9u+2c39JCsejktSP7mj5eRid5EIvTzIpQ2kDOCw1Nq9BjQ==", + "dev": true + }, + "just-diff-apply": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-3.1.2.tgz", + "integrity": "sha512-TCa7ZdxCeq6q3Rgms2JCRHTCfWAETPZ8SzYUbkYF6KR3I03sN29DaOIC+xyWboIcMvjAsD5iG2u/RWzHD8XpgQ==", + "dev": true + }, + "kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "dev": true + }, + "lcid": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-3.1.1.tgz", + "integrity": "sha512-M6T051+5QCGLBQb8id3hdvIW8+zeFV2FyBGFS9IEK5H9Wt4MueD4bW1eWikpHgZp+5xR3l5c8pZUkQsIA0BFZg==", + "dev": true, + "requires": { + "invert-kv": "^3.0.0" + } + }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "load-yaml-file": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/load-yaml-file/-/load-yaml-file-0.2.0.tgz", + "integrity": "sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.5", + "js-yaml": "^3.13.0", + "pify": "^4.0.1", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "logform": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.3.0.tgz", + "integrity": "sha512-graeoWUH2knKbGthMtuG1EfaSPMZFZBIrhuJHhkS5ZseFBrc7DupCzihOQAzsK/qIKPQaPJ/lFQFctILUY5ARQ==", + "dev": true, + "requires": { + "colors": "^1.2.1", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^1.1.0", + "triple-beam": "^1.3.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "macos-release": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.0.tgz", + "integrity": "sha512-EIgv+QZ9r+814gjJj0Bt5vSLJLzswGmSUbUpbi9AIr/fsN2IWFBl2NucV9PAiek+U1STK468tEkxmVYUtuAN3g==", + "dev": true + }, + "make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "dev": true, + "requires": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "mem": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/mem/-/mem-5.1.1.tgz", + "integrity": "sha512-qvwipnozMohxLXG1pOqoLiZKNkC4r4qqRucSoDwXowsNGDSULiqFTRUF05vcZWnwJSG22qTsynQhxbaMtnX9gw==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.3", + "mimic-fn": "^2.1.0", + "p-is-promise": "^2.1.0" + }, + "dependencies": { + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + } + } + }, + "mem-fs": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/mem-fs/-/mem-fs-2.2.1.tgz", + "integrity": "sha512-yiAivd4xFOH/WXlUi6v/nKopBh1QLzwjFi36NK88cGt/PRXI8WeBASqY+YSjIVWvQTx3hR8zHKDBMV6hWmglNA==", + "dev": true, + "requires": { + "@types/node": "^15.6.1", + "@types/vinyl": "^2.0.4", + "vinyl": "^2.0.1", + "vinyl-file": "^3.0.0" + }, + "dependencies": { + "@types/node": { + "version": "15.14.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.14.9.tgz", + "integrity": "sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==", + "dev": true + } + } + }, + "mem-fs-editor": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-9.3.0.tgz", + "integrity": "sha512-QKFbPwGCh1ypmc2H8BUYpbapwT/x2AOCYZQogzSui4rUNes7WVMagQXsirPIfp18EarX0SSY9Fpg426nSjew4Q==", + "dev": true, + "requires": { + "binaryextensions": "^4.16.0", + "commondir": "^1.0.1", + "deep-extend": "^0.6.0", + "ejs": "^3.1.6", + "globby": "^11.0.3", + "isbinaryfile": "^4.0.8", + "minimatch": "^3.0.4", + "multimatch": "^5.0.0", + "normalize-path": "^3.0.0", + "textextensions": "^5.13.0" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, + "mime-db": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", + "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==", + "dev": true + }, + "mime-types": { + "version": "2.1.33", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz", + "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", + "dev": true, + "requires": { + "mime-db": "1.50.0" + } + }, + "mimic-fn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "minipass": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz", + "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "dev": true, + "requires": { + "encoding": "^0.1.12", + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + } + }, + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-json-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", + "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "dev": true, + "requires": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "mkdirp-infer-owner": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz", + "integrity": "sha512-sdqtiFt3lkOaYvTXSRIUjkIdPTcxgv5+fgqYE/5qgwdw12cOrAuzzgzvVExIkH/ul1oeHN3bCLOWSG3XOqbKKw==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "infer-owner": "^1.0.4", + "mkdirp": "^1.0.3" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "multimatch": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", + "integrity": "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==", + "dev": true, + "requires": { + "@types/minimatch": "^3.0.3", + "array-differ": "^3.0.0", + "array-union": "^2.1.0", + "arrify": "^2.0.1", + "minimatch": "^3.0.4" + } + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true + }, + "node-fetch": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", + "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-gyp": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-7.1.2.tgz", + "integrity": "sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ==", + "dev": true, + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.3", + "nopt": "^5.0.0", + "npmlog": "^4.1.2", + "request": "^2.88.2", + "rimraf": "^3.0.2", + "semver": "^7.3.2", + "tar": "^6.0.2", + "which": "^2.0.2" + } + }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-bundled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", + "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", + "dev": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-install-checks": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-4.0.0.tgz", + "integrity": "sha512-09OmyDkNLYwqKPOnbI8exiOZU2GVVmQp7tgez2BPi5OZC8M82elDAps7sxC4l//uSUtotWqoEIDwjRvWH4qz8w==", + "dev": true, + "requires": { + "semver": "^7.1.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "dev": true + }, + "npm-package-arg": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.5.tgz", + "integrity": "sha512-LhgZrg0n0VgvzVdSm1oiZworPbTxYHUJCgtsJW8mGvlDpxTM1vSJc3m5QZeUkhAHIzbz3VCHd/R4osi1L1Tg/Q==", + "dev": true, + "requires": { + "hosted-git-info": "^4.0.1", + "semver": "^7.3.4", + "validate-npm-package-name": "^3.0.0" + } + }, + "npm-packlist": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-2.2.2.tgz", + "integrity": "sha512-Jt01acDvJRhJGthnUJVF/w6gumWOZxO7IkpY/lsX9//zqQgnF7OJaxgQXcerd4uQOLu7W5bkb4mChL9mdfm+Zg==", + "dev": true, + "requires": { + "glob": "^7.1.6", + "ignore-walk": "^3.0.3", + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-pick-manifest": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-6.1.1.tgz", + "integrity": "sha512-dBsdBtORT84S8V8UTad1WlUyKIY9iMsAmqxHbLdeEeBNMLQDlDWWra3wYUx9EBEIiG/YwAy0XyNHDd2goAsfuA==", + "dev": true, + "requires": { + "npm-install-checks": "^4.0.0", + "npm-normalize-package-bin": "^1.0.1", + "npm-package-arg": "^8.1.2", + "semver": "^7.3.4" + } + }, + "npm-registry-fetch": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-11.0.0.tgz", + "integrity": "sha512-jmlgSxoDNuhAtxUIG6pVwwtz840i994dL14FoNVZisrmZW5kWd63IUTNv1m/hyRSGSqWjCUp/YZlS1BJyNp9XA==", + "dev": true, + "requires": { + "make-fetch-happen": "^9.0.1", + "minipass": "^3.1.3", + "minipass-fetch": "^1.3.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.0.0", + "npm-package-arg": "^8.0.0" + } + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dev": true, + "requires": { + "fn.name": "1.x.x" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + }, + "dependencies": { + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + } + } + }, + "ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "requires": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "os-locale": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-5.0.0.tgz", + "integrity": "sha512-tqZcNEDAIZKBEPnHPlVDvKrp7NzgLi7jRmhKiUoa2NUmhl13FtkAGLUVR+ZsYvApBQdBfYm43A4tXXQ4IrYLBA==", + "dev": true, + "requires": { + "execa": "^4.0.0", + "lcid": "^3.0.0", + "mem": "^5.0.0" + } + }, + "os-name": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/os-name/-/os-name-4.0.1.tgz", + "integrity": "sha512-xl9MAoU97MH1Xt5K9ERft2YfCAoaO6msy1OBA0ozxEC0x0TmIoE6K3QvgJMMZA9yKGLmHXNY/YZoDbiGDj4zYw==", + "dev": true, + "requires": { + "macos-release": "^2.5.0", + "windows-release": "^4.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + } + }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, + "requires": { + "p-finally": "^1.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pacote": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-11.3.5.tgz", + "integrity": "sha512-fT375Yczn4zi+6Hkk2TBe1x1sP8FgFsEIZ2/iWaXY2r/NkhDJfxbcn5paz1+RTFCyNf+dPnaoBDJoAxXSU8Bkg==", + "dev": true, + "requires": { + "@npmcli/git": "^2.1.0", + "@npmcli/installed-package-contents": "^1.0.6", + "@npmcli/promise-spawn": "^1.2.0", + "@npmcli/run-script": "^1.8.2", + "cacache": "^15.0.5", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "infer-owner": "^1.0.4", + "minipass": "^3.1.3", + "mkdirp": "^1.0.3", + "npm-package-arg": "^8.0.1", + "npm-packlist": "^2.1.4", + "npm-pick-manifest": "^6.0.0", + "npm-registry-fetch": "^11.0.0", + "promise-retry": "^2.0.1", + "read-package-json-fast": "^2.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.1.0" + } + }, + "parse-cache-control": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", + "integrity": "sha1-juqz5U+laSD+Fro493+iGqzC104=", + "dev": true + }, + "parse-conflict-json": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-1.1.1.tgz", + "integrity": "sha512-4gySviBiW5TRl7XHvp1agcS7SOe0KZOjC//71dzZVWJrY9hCrgtvl5v3SyIxCZ4fZF47TxD9nfzmxcx76xmbUw==", + "dev": true, + "requires": { + "json-parse-even-better-errors": "^2.3.0", + "just-diff": "^3.0.1", + "just-diff-apply": "^3.0.0" + } + }, + "parse-gitignore": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-gitignore/-/parse-gitignore-1.0.1.tgz", + "integrity": "sha512-UGyowyjtx26n65kdAMWhm6/3uy5uSrpcuH7tt+QEVudiBoVS+eqHxD5kbi9oWVRwj7sCzXqwuM+rUGw7earl6A==", + "dev": true + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + } + } + }, + "pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + } + }, + "pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true + }, + "preferred-pm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/preferred-pm/-/preferred-pm-3.0.3.tgz", + "integrity": "sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==", + "dev": true, + "requires": { + "find-up": "^5.0.0", + "find-yarn-workspace-root2": "1.2.16", + "path-exists": "^4.0.0", + "which-pm": "2.0.0" + }, + "dependencies": { + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + } + } + }, + "prettier": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz", + "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==", + "dev": true + }, + "prettier-plugin-java": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-java/-/prettier-plugin-java-1.4.0.tgz", + "integrity": "sha512-Yie1yn4OdEF5Qey+3bChv8OuvMkzoMv07R6s/j1sn9HfZQxpw0eCSaXLOPyS7B6vVlaF1cr2TD1NsNSvxjVU1g==", + "dev": true, + "requires": { + "java-parser": "1.4.0", + "lodash": "4.17.21", + "prettier": "2.3.1" + }, + "dependencies": { + "prettier": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz", + "integrity": "sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==", + "dev": true + } + } + }, + "prettier-plugin-packagejson": { + "version": "2.2.13", + "resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.2.13.tgz", + "integrity": "sha512-AUsRlYHn7jjMck1X54wYTsKj6/E3wf0d0joPFSnSqY3Sxz/e2qqe2x7w0AiMdVeeQcRAkakjp7Qes/riT7J0zA==", + "dev": true, + "requires": { + "sort-package-json": "1.52.0" + } + }, + "pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true + }, + "proc-log": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-1.0.0.tgz", + "integrity": "sha512-aCk8AO51s+4JyuYGg3Q/a6gnrlDO09NpVWePtjp7xwphcoQ04x5WAfCyugcsbLooWcMJ87CLkD4+604IckEdhg==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "promise": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz", + "integrity": "sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==", + "dev": true, + "requires": { + "asap": "~2.0.6" + } + }, + "promise-all-reject-late": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz", + "integrity": "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==", + "dev": true + }, + "promise-call-limit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-1.0.1.tgz", + "integrity": "sha512-3+hgaa19jzCGLuSCbieeRsu5C2joKfYn8pY6JAuXFRVfF4IO+L7UPpFWNTeWT9pM7uhskvbPPd/oEOktCn317Q==", + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "requires": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + } + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "randexp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.5.3.tgz", + "integrity": "sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==", + "dev": true, + "requires": { + "drange": "^1.0.2", + "ret": "^0.2.0" + } + }, + "read-cmd-shim": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-2.0.0.tgz", + "integrity": "sha512-HJpV9bQpkl6KwjxlJcBoqu9Ba0PQg8TqSNIOrulGt54a0uup0HtevreFHzYzkm0lpnleRdNBzXznKrgxglEHQw==", + "dev": true + }, + "read-package-json-fast": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz", + "integrity": "sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ==", + "dev": true, + "requires": { + "json-parse-even-better-errors": "^2.3.0", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdir-scoped-modules": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", + "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", + "dev": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "regexp-to-ast": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", + "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==", + "dev": true + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "dev": true + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } + } + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + }, + "dependencies": { + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + } + } + }, + "ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "dev": true + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safe-stable-stringify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz", + "integrity": "sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=", + "dev": true + }, + "scoped-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scoped-regex/-/scoped-regex-2.1.0.tgz", + "integrity": "sha512-g3WxHrqSWCZHGHlSrF51VXFdjImhwvH8ZO/pryFH56Qi0cDsZfylQa/t0jCzVQFNbNvM00HfHjkDPEuarKDSWQ==", + "dev": true + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "shelljs": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", + "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "signal-exit": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", + "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==", + "dev": true + }, + "simple-git": { + "version": "2.46.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-2.46.0.tgz", + "integrity": "sha512-6eumII1vfP4NpRqxZcVWCcIT5xHH6dRyvBZSjkH4dJRDRpv+0f75hrN5ysp++y23Mfr3AbRC/dO2NDbfj1lJpQ==", + "dev": true, + "requires": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "debug": "^4.3.1" + } + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "dev": true, + "requires": { + "is-arrayish": "^0.3.1" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true + }, + "socks": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", + "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==", + "dev": true, + "requires": { + "ip": "^1.1.5", + "smart-buffer": "^4.1.0" + } + }, + "socks-proxy-agent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.0.tgz", + "integrity": "sha512-57e7lwCN4Tzt3mXz25VxOErJKXlPfXmkMLnk310v/jwW20jWRVcgsOit+xNkN3eIEdB47GwnfAEBLacZ/wVIKg==", + "dev": true, + "requires": { + "agent-base": "^6.0.2", + "debug": "^4.3.1", + "socks": "^2.6.1" + } + }, + "sort-object-keys": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-1.1.3.tgz", + "integrity": "sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg==", + "dev": true + }, + "sort-package-json": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-1.52.0.tgz", + "integrity": "sha512-TsKDXgH3kPsaSrjAszQgg+n2/FDYdPrBrXD4YxMxExpogsi8LCek0YzK/jZ70i5Gi53WcpV+mVzvb5CHB5LpZw==", + "dev": true, + "requires": { + "detect-indent": "^6.0.0", + "detect-newline": "3.1.0", + "git-hooks-list": "1.0.3", + "globby": "10.0.0", + "is-plain-obj": "2.1.0", + "sort-object-keys": "^1.1.3" + }, + "dependencies": { + "globby": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.0.tgz", + "integrity": "sha512-3LifW9M4joGZasyYPz2A1U74zbC/45fvpXUvO/9KbSa+VV0aGZarWkfdgKyR9sExNP0t0x0ss/UMJpNpcaTspw==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + } + } + } + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", + "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "dev": true, + "requires": { + "minipass": "^3.1.1" + } + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "dev": true + }, + "streamfilter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/streamfilter/-/streamfilter-3.0.0.tgz", + "integrity": "sha512-kvKNfXCmUyC8lAXSSHCIXBUlo/lhsLcCU/OmzACZYpRUdtKIH68xYhm/+HI15jFJYtNJGYtCgn2wmIiExY1VwA==", + "dev": true, + "requires": { + "readable-stream": "^3.0.6" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-bom-buf": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-buf/-/strip-bom-buf-1.0.0.tgz", + "integrity": "sha1-HLRar1dTD0yvhsf3UXnSyaUd1XI=", + "dev": true, + "requires": { + "is-utf8": "^0.2.1" + } + }, + "strip-bom-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz", + "integrity": "sha1-+H217yYT9paKpUWr/h7HKLaoKco=", + "dev": true, + "requires": { + "first-chunk-stream": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tar": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "dev": true + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "textextensions": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-5.14.0.tgz", + "integrity": "sha512-4cAYwNFNYlIAHBUo7p6zw8POUvWbZor+/R0Tanv+rIhsauEyV9QSrEXL40pI+GfTQxKX8k6Tyw6CmdSDSmASrg==", + "dev": true + }, + "then-request": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz", + "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==", + "dev": true, + "requires": { + "@types/concat-stream": "^1.6.0", + "@types/form-data": "0.0.33", + "@types/node": "^8.0.0", + "@types/qs": "^6.2.31", + "caseless": "~0.12.0", + "concat-stream": "^1.6.0", + "form-data": "^2.2.0", + "http-basic": "^8.1.1", + "http-response-object": "^3.0.1", + "promise": "^8.0.0", + "qs": "^6.4.0" + }, + "dependencies": { + "@types/node": { + "version": "8.10.66", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz", + "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==", + "dev": true + } + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "dev": true, + "requires": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + } + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "dev": true + }, + "treeverse": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/treeverse/-/treeverse-1.0.4.tgz", + "integrity": "sha512-whw60l7r+8ZU8Tu/Uc2yxtc4ZTZbR/PF3u1IPNKGQ6p8EICLb3Z2lAgoqw9bqYd8IkgnsaOcLzYHFckjqNsf0g==", + "dev": true + }, + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==", + "dev": true + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "dev": true + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", + "dev": true + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + } + } + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", + "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", + "dev": true, + "requires": { + "builtins": "^1.0.3" + } + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "dev": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + }, + "dependencies": { + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + } + } + }, + "vinyl-file": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-3.0.0.tgz", + "integrity": "sha1-sQTZ5ECf+jJfqt1SBkLQo7SIs2U=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.3.0", + "strip-bom-buf": "^1.0.0", + "strip-bom-stream": "^2.0.0", + "vinyl": "^2.0.1" + } + }, + "walk-up-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-1.0.0.tgz", + "integrity": "sha512-hwj/qMDUEjCU5h0xr90KGCf0tg0/LgJbmOWgrWKYlcJZM7XvquvUJZ0G/HMGr7F7OQMOUuPHWP9JpriinkAlkg==", + "dev": true + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-pm": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-pm/-/which-pm-2.0.0.tgz", + "integrity": "sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==", + "dev": true, + "requires": { + "load-yaml-file": "^0.2.0", + "path-exists": "^4.0.0" + }, + "dependencies": { + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + } + } + }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "windows-release": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-4.0.0.tgz", + "integrity": "sha512-OxmV4wzDKB1x7AZaZgXMVsdJ1qER1ed83ZrTYd5Bwq2HfJVg3DJS8nqlAG4sMoJ7mu8cuRmLEYyU13BKwctRAg==", + "dev": true, + "requires": { + "execa": "^4.0.2" + } + }, + "winston": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz", + "integrity": "sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==", + "dev": true, + "requires": { + "@dabh/diagnostics": "^2.0.2", + "async": "^3.1.0", + "is-stream": "^2.0.0", + "logform": "^2.2.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.4.0" + }, + "dependencies": { + "async": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.2.tgz", + "integrity": "sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==", + "dev": true + } + } + }, + "winston-transport": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz", + "integrity": "sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==", + "dev": true, + "requires": { + "readable-stream": "^2.3.7", + "triple-beam": "^1.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "dev": true, + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yeoman-environment": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-3.6.0.tgz", + "integrity": "sha512-X16N9lhzRdUKFT8MZrpwjLDKsdgAUqh4VPR2wAXeAqjJJaUxYBxCQGFxtZVTf3vbyNuIHXPunwOLtK60bpapbg==", + "dev": true, + "requires": { + "@npmcli/arborist": "^2.2.2", + "are-we-there-yet": "^1.1.5", + "arrify": "^2.0.1", + "binaryextensions": "^4.15.0", + "chalk": "^4.1.0", + "cli-table": "^0.3.1", + "commander": "7.1.0", + "dateformat": "^4.5.0", + "debug": "^4.1.1", + "diff": "^5.0.0", + "error": "^10.4.0", + "escape-string-regexp": "^4.0.0", + "execa": "^5.0.0", + "find-up": "^5.0.0", + "globby": "^11.0.1", + "grouped-queue": "^2.0.0", + "inquirer": "^8.0.0", + "is-scoped": "^2.1.0", + "lodash": "^4.17.10", + "log-symbols": "^4.0.0", + "mem-fs": "^1.2.0 || ^2.0.0", + "mem-fs-editor": "^8.1.2 || ^9.0.0", + "minimatch": "^3.0.4", + "npmlog": "^4.1.2", + "p-queue": "^6.6.2", + "pacote": "^11.2.6", + "preferred-pm": "^3.0.3", + "pretty-bytes": "^5.3.0", + "semver": "^7.1.3", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0", + "text-table": "^0.2.0", + "textextensions": "^5.12.0", + "untildify": "^4.0.0" + }, + "dependencies": { + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, + "commander": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.1.0.tgz", + "integrity": "sha512-pRxBna3MJe6HKnBGsDyMv8ETbptw3axEdYHoqNh7gu5oDcew8fs0xnivZGm06Ogk8zGAJ9VX+OPEr2GXEQK4dg==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + }, + "dependencies": { + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + } + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "inquirer": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.0.tgz", + "integrity": "sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.2.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "rxjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz", + "integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==", + "dev": true, + "requires": { + "tslib": "~2.1.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "tslib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", + "dev": true + } + } + }, + "yeoman-generator": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-5.4.2.tgz", + "integrity": "sha512-xgS3A4r5VoEYq3vPdk1fWPVZ30y5NHlT2hn0OEyhKG79xojCtPkPkfWcKQamgvC9QLhaotVGvambBxwxwBeDTg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "dargs": "^7.0.0", + "debug": "^4.1.1", + "execa": "^4.1.0", + "github-username": "^6.0.0", + "lodash": "^4.17.11", + "minimist": "^1.2.5", + "read-pkg-up": "^7.0.1", + "run-async": "^2.0.0", + "semver": "^7.2.1", + "shelljs": "^0.8.4", + "text-table": "^0.2.0" + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/pamapi/package.json b/pamapi/package.json new file mode 100644 index 0000000..1627aba --- /dev/null +++ b/pamapi/package.json @@ -0,0 +1,62 @@ +{ + "name": "pamapi", + "version": "0.0.0", + "private": true, + "description": "Description for pamapi", + "license": "UNLICENSED", + "scripts": { + "app:start": "./mvnw", + "backend:build-cache": "./mvnw dependency:go-offline", + "backend:debug": "./mvnw -Dspring-boot.run.jvmArguments=\"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000\"", + "backend:doc:test": "./mvnw -ntp javadoc:javadoc --batch-mode", + "backend:info": "./mvnw -ntp enforcer:display-info --batch-mode", + "backend:nohttp:test": "./mvnw -ntp checkstyle:check --batch-mode", + "backend:start": "./mvnw -P-webapp", + "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.com.pollex.pam=OFF -Dlogging.level.org.springframework=OFF -Dlogging.level.org.springframework.web=OFF -Dlogging.level.org.springframework.security=OFF", + "ci:backend:test": "npm run backend:info && npm run backend:doc:test && npm run backend:nohttp:test && npm run backend:unit:test", + "ci:e2e:package": "npm run java:$npm_package_config_packaging:$npm_package_config_default_environment -- -Pe2e -Denforcer.skip=true", + "ci:e2e:prepare": "npm run ci:e2e:prepare:docker", + "ci:e2e:prepare:docker": "npm run docker:db:up && npm run docker:others:up && docker ps -a", + "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.com.pollex.pam=OFF -Dlogging.level.org.springframework=OFF -Dlogging.level.org.springframework.web=OFF -Dlogging.level.org.springframework.security=OFF --logging.level.org.springframework.web=ERROR", + "ci:e2e:teardown": "npm run ci:e2e:teardown:docker", + "ci:e2e:teardown:docker": "npm run docker:db:down --if-present && npm run docker:others:down && docker ps -a", + "ci:server:package": "npm run java:$npm_package_config_packaging:$npm_package_config_default_environment", + "docker:app:up": "docker-compose -f src/main/docker/app.yml up -d", + "docker:db:down": "docker-compose -f src/main/docker/postgresql.yml down -v --remove-orphans", + "docker:db:up": "docker-compose -f src/main/docker/postgresql.yml up -d", + "docker:others:await": "", + "docker:others:down": "", + "predocker:others:up": "", + "docker:others:up": "", + "java:docker": "./mvnw -ntp verify -DskipTests jib:dockerBuild", + "java:docker:arm64": "npm run java:docker -- -Djib-maven-plugin.architecture=arm64", + "java:docker:dev": "npm run java:docker -- -Pdev,webapp", + "java:docker:prod": "npm run java:docker -- -Pprod", + "java:jar": "./mvnw -ntp verify -DskipTests --batch-mode", + "java:jar:dev": "npm run java:jar -- -Pdev,webapp", + "java:jar:prod": "npm run java:jar -- -Pprod", + "java:war": "./mvnw -ntp verify -DskipTests --batch-mode -Pwar", + "java:war:dev": "npm run java:war -- -Pdev,webapp", + "java:war:prod": "npm run java:war -- -Pprod", + "prettier:check": "prettier --check \"{,src/**/}*.{md,json,yml,html,java}\"", + "prettier:format": "prettier --write \"{,src/**/}*.{md,json,yml,html,java}\"" + }, + "config": { + "backend_port": "8080", + "default_environment": "prod", + "packaging": "jar" + }, + "devDependencies": { + "generator-jhipster": "7.3.0", + "prettier": "2.4.1", + "prettier-plugin-java": "1.4.0", + "prettier-plugin-packagejson": "2.2.13" + }, + "engines": { + "node": ">=14.17.6" + }, + "cacheDirectories": [ + "node_modules" + ] +} diff --git a/pamapi/pom.xml b/pamapi/pom.xml new file mode 100644 index 0000000..9804bb3 --- /dev/null +++ b/pamapi/pom.xml @@ -0,0 +1,1026 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 + https://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>com.pollex.pam</groupId> + <artifactId>pamapi</artifactId> + <version>0.0.1-SNAPSHOT</version> + <packaging>jar</packaging> + <name>Pamapi</name> + <description>Description for pamapi</description> + + <repositories> + <!-- jhipster-needle-maven-repository --> + </repositories> + + <pluginRepositories> + <!-- jhipster-needle-maven-plugin-repository --> + </pluginRepositories> + + <!-- jhipster-needle-distribution-management --> + + <properties> + <!-- Build properties --> + <maven.version>3.3.9</maven.version> + <java.version>11</java.version> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + <maven.build.timestamp.format>yyyyMMddHHmmss</maven.build.timestamp.format> + <maven.compiler.source>${java.version}</maven.compiler.source> + <maven.compiler.target>${java.version}</maven.compiler.target> + <start-class>com.pollex.pam.PamapiApp</start-class> + <argLine>-Djava.security.egd=file:/dev/./urandom -Xmx256m</argLine> + <m2e.apt.activation>jdt_apt</m2e.apt.activation> + <run.addResources>false</run.addResources> + <!-- These remain empty unless the corresponding profile is active --> + <profile.no-liquibase /> + <profile.api-docs /> + <profile.tls /> + + <!-- Dependency versions --> + <jhipster-dependencies.version>7.3.0</jhipster-dependencies.version> + <!-- The spring-boot version should match the one managed by + https://mvnrepository.com/artifact/tech.jhipster/jhipster-dependencies/${jhipster-dependencies.version} --> + <spring-boot.version>2.5.5</spring-boot.version> + <!-- The hibernate version should match the one managed by + https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-dependencies/${spring-boot.version} --> + <hibernate.version>5.4.32.Final</hibernate.version> + <!-- The javassist version should match the one managed by + https://mvnrepository.com/artifact/org.hibernate/hibernate-core/${hibernate.version} --> + <javassist.version>3.27.0-GA</javassist.version> + <!-- The liquibase version should match the one managed by + https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-dependencies/${spring-boot.version} --> + <liquibase.version>4.5.0</liquibase.version> + <liquibase-hibernate5.version>4.5.0</liquibase-hibernate5.version> + <validation-api.version>2.0.1.Final</validation-api.version> + <jaxb-runtime.version>2.3.3</jaxb-runtime.version> + <archunit-junit5.version>0.21.0</archunit-junit5.version> + <mapstruct.version>1.4.2.Final</mapstruct.version> + <!-- Plugin versions --> + <maven-clean-plugin.version>3.1.0</maven-clean-plugin.version> + <maven-site-plugin.version>3.9.1</maven-site-plugin.version> + <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version> + <maven-javadoc-plugin.version>3.3.1</maven-javadoc-plugin.version> + <maven-eclipse-plugin.version>2.10</maven-eclipse-plugin.version> + <maven-enforcer-plugin.version>3.0.0</maven-enforcer-plugin.version> + <maven-failsafe-plugin.version>3.0.0-M5</maven-failsafe-plugin.version> + <maven-idea-plugin.version>2.2.1</maven-idea-plugin.version> + <maven-resources-plugin.version>3.2.0</maven-resources-plugin.version> + <maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version> + <maven-war-plugin.version>3.3.1</maven-war-plugin.version> + <maven-checkstyle-plugin.version>3.1.2</maven-checkstyle-plugin.version> + <checkstyle.version>9.0</checkstyle.version> + <nohttp-checkstyle.version>0.0.9</nohttp-checkstyle.version> + <git-commit-id-plugin.version>5.0.0</git-commit-id-plugin.version> + <modernizer-maven-plugin.version>2.3.0</modernizer-maven-plugin.version> + <jacoco-maven-plugin.version>0.8.7</jacoco-maven-plugin.version> + <jib-maven-plugin.version>3.1.4</jib-maven-plugin.version> + <jib-maven-plugin.image>eclipse-temurin:11-jre-focal</jib-maven-plugin.image> + <jib-maven-plugin.architecture>amd64</jib-maven-plugin.architecture> + <lifecycle-mapping.version>1.0.0</lifecycle-mapping.version> + <properties-maven-plugin.version>1.0.0</properties-maven-plugin.version> + <sonar-maven-plugin.version>3.9.0.2155</sonar-maven-plugin.version> + <!-- jhipster-needle-maven-property --> + </properties> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>tech.jhipster</groupId> + <artifactId>jhipster-dependencies</artifactId> + <version>${jhipster-dependencies.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + <!-- jhipster-needle-maven-add-dependency-management --> + </dependencies> + </dependencyManagement> + + <dependencies> + <dependency> + <groupId>tech.jhipster</groupId> + <artifactId>jhipster-framework</artifactId> + </dependency> + <dependency> + <groupId>javax.annotation</groupId> + <artifactId>javax.annotation-api</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-cache</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.module</groupId> + <artifactId>jackson-module-jaxb-annotations</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.datatype</groupId> + <artifactId>jackson-datatype-hibernate5</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.datatype</groupId> + <artifactId>jackson-datatype-hppc</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.datatype</groupId> + <artifactId>jackson-datatype-jsr310</artifactId> + </dependency> + <dependency> + <groupId>com.h2database</groupId> + <artifactId>h2</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>io.springfox</groupId> + <artifactId>springfox-oas</artifactId> + </dependency> + <dependency> + <groupId>io.springfox</groupId> + <artifactId>springfox-swagger2</artifactId> + </dependency> + <dependency> + <groupId>io.springfox</groupId> + <artifactId>springfox-bean-validators</artifactId> + </dependency> + <dependency> + <groupId>com.zaxxer</groupId> + <artifactId>HikariCP</artifactId> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + </dependency> + <dependency> + <groupId>org.testcontainers</groupId> + <artifactId>postgresql</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>javax.cache</groupId> + <artifactId>cache-api</artifactId> + </dependency> + <dependency> + <groupId>org.ehcache</groupId> + <artifactId>ehcache</artifactId> + </dependency> + <dependency> + <groupId>org.hibernate</groupId> + <artifactId>hibernate-jcache</artifactId> + </dependency> + <dependency> + <groupId>org.hibernate</groupId> + <artifactId>hibernate-jpamodelgen</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.hibernate</groupId> + <artifactId>hibernate-core</artifactId> + </dependency> + <dependency> + <groupId>org.hibernate.validator</groupId> + <artifactId>hibernate-validator</artifactId> + </dependency> + <dependency> + <groupId>org.liquibase</groupId> + <artifactId>liquibase-core</artifactId> + <!-- Inherited version from Spring Boot can't be used because of regressions --> + <version>${liquibase.version}</version> + </dependency> + <dependency> + <groupId>org.postgresql</groupId> + <artifactId>postgresql</artifactId> + </dependency> + <dependency> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct</artifactId> + <version>${mapstruct.version}</version> + </dependency> + <dependency> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct-processor</artifactId> + <version>${mapstruct.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-configuration-processor</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-loader-tools</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-actuator</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-data-jpa</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-logging</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-mail</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-security</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-thymeleaf</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + <exclusions> + <exclusion> + <groupId>org.junit.vintage</groupId> + <artifactId>junit-vintage-engine</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-test</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.springframework.security</groupId> + <artifactId>spring-security-test</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.tngtech.archunit</groupId> + <artifactId>archunit-junit5-api</artifactId> + <version>${archunit-junit5.version}</version> + <scope>test</scope> + </dependency> + <!-- Adding the engine dependency to the surefire-plugin unfortunately does not work in the current version. --> + <!-- https://www.archunit.org/userguide/html/000_Index.html#_junit_5 --> + <dependency> + <groupId>com.tngtech.archunit</groupId> + <artifactId>archunit-junit5-engine</artifactId> + <version>${archunit-junit5.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.zalando</groupId> + <artifactId>problem-spring-web</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-websocket</artifactId> + </dependency> + <dependency> + <groupId>io.jsonwebtoken</groupId> + <artifactId>jjwt-api</artifactId> + </dependency> + <dependency> + <groupId>io.jsonwebtoken</groupId> + <artifactId>jjwt-impl</artifactId> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>io.jsonwebtoken</groupId> + <artifactId>jjwt-jackson</artifactId> + <scope>runtime</scope> + </dependency> + <!-- Spring Cloud --> + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-starter-bootstrap</artifactId> + </dependency> + + <dependency> + <groupId>org.springframework.security</groupId> + <artifactId>spring-security-data</artifactId> + </dependency> + <dependency> + <groupId>io.micrometer</groupId> + <artifactId>micrometer-registry-prometheus</artifactId> + </dependency> + <dependency> + <groupId>io.dropwizard.metrics</groupId> + <artifactId>metrics-core</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.security</groupId> + <artifactId>spring-security-messaging</artifactId> + </dependency> + <!-- jhipster-needle-maven-add-dependency --> + </dependencies> + + <build> + <defaultGoal>spring-boot:run</defaultGoal> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-checkstyle-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-eclipse-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-enforcer-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-failsafe-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-idea-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-resources-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.sonarsource.scanner.maven</groupId> + <artifactId>sonar-maven-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.liquibase</groupId> + <artifactId>liquibase-maven-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + </plugin> + <plugin> + <groupId>com.google.cloud.tools</groupId> + <artifactId>jib-maven-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>properties-maven-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.gaul</groupId> + <artifactId>modernizer-maven-plugin</artifactId> + </plugin> + <!-- jhipster-needle-maven-add-plugin --> + </plugins> + <pluginManagement> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-checkstyle-plugin</artifactId> + <version>${maven-checkstyle-plugin.version}</version> + <dependencies> + <dependency> + <groupId>com.puppycrawl.tools</groupId> + <artifactId>checkstyle</artifactId> + <version>${checkstyle.version}</version> + </dependency> + <dependency> + <groupId>io.spring.nohttp</groupId> + <artifactId>nohttp-checkstyle</artifactId> + <version>${nohttp-checkstyle.version}</version> + </dependency> + </dependencies> + <configuration> + <configLocation>checkstyle.xml</configLocation> + <includes>pom.xml,README.md</includes> + <excludes>.git/**/*,target/**/*,node_modules/**/*,node/**/*</excludes> + <sourceDirectories>./</sourceDirectories> + </configuration> + <executions> + <execution> + <goals> + <goal>check</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>${maven-compiler-plugin.version}</version> + <configuration> + <source>${java.version}</source> + <target>${java.version}</target> + <annotationProcessorPaths> + <path> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-configuration-processor</artifactId> + <version>${spring-boot.version}</version> + </path> + <path> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct-processor</artifactId> + <version>${mapstruct.version}</version> + </path> + <!-- For JPA static metamodel generation --> + <path> + <groupId>org.hibernate</groupId> + <artifactId>hibernate-jpamodelgen</artifactId> + <version>${hibernate.version}</version> + </path> + <path> + <groupId>org.glassfish.jaxb</groupId> + <artifactId>jaxb-runtime</artifactId> + <version>${jaxb-runtime.version}</version> + </path> + <!-- jhipster-needle-maven-add-annotation-processor --> + </annotationProcessorPaths> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <version>${maven-javadoc-plugin.version}</version> + <configuration> + <source>${maven.compiler.source}</source> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-war-plugin</artifactId> + <version>${maven-war-plugin.version}</version> + <executions> + <execution> + <id>default-war</id> + <goals> + <goal>war</goal> + </goals> + <phase>package</phase> + </execution> + </executions> + <configuration> + <warSourceIncludes>WEB-INF/**,META-INF/**</warSourceIncludes> + <failOnMissingWebXml>false</failOnMissingWebXml> + </configuration> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>properties-maven-plugin</artifactId> + <version>${properties-maven-plugin.version}</version> + <executions> + <execution> + <phase>initialize</phase> + <goals> + <goal>read-project-properties</goal> + </goals> + <configuration> + <files> + <file>sonar-project.properties</file> + </files> + </configuration> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>io.github.git-commit-id</groupId> + <artifactId>git-commit-id-maven-plugin</artifactId> + <version>${git-commit-id-plugin.version}</version> + <executions> + <execution> + <goals> + <goal>revision</goal> + </goals> + </execution> + </executions> + <configuration> + <failOnNoGitDirectory>false</failOnNoGitDirectory> + <failOnUnableToExtractRepoInfo>false</failOnUnableToExtractRepoInfo> + <generateGitPropertiesFile>true</generateGitPropertiesFile> + <includeOnlyProperties> + <includeOnlyProperty>^git.commit.id.abbrev$</includeOnlyProperty> + <includeOnlyProperty>^git.commit.id.describe$</includeOnlyProperty> + <includeOnlyProperty>^git.branch$</includeOnlyProperty> + </includeOnlyProperties> + </configuration> + </plugin> + <plugin> + <groupId>org.gaul</groupId> + <artifactId>modernizer-maven-plugin</artifactId> + <version>${modernizer-maven-plugin.version}</version> + <executions> + <execution> + <id>modernizer</id> + <phase>package</phase> + <goals> + <goal>modernizer</goal> + </goals> + </execution> + </executions> + <configuration> + <javaVersion>${java.version}</javaVersion> + </configuration> + </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <version>${jacoco-maven-plugin.version}</version> + <executions> + <execution> + <id>pre-unit-tests</id> + <goals> + <goal>prepare-agent</goal> + </goals> + </execution> + <!-- Ensures that the code coverage report for unit tests is created after unit tests have been run --> + <execution> + <id>post-unit-test</id> + <phase>test</phase> + <goals> + <goal>report</goal> + </goals> + </execution> + <execution> + <id>pre-integration-tests</id> + <goals> + <goal>prepare-agent-integration</goal> + </goals> + </execution> + <!-- Ensures that the code coverage report for integration tests is created after integration tests have been run --> + <execution> + <id>post-integration-tests</id> + <phase>post-integration-test</phase> + <goals> + <goal>report-integration</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>com.google.cloud.tools</groupId> + <artifactId>jib-maven-plugin</artifactId> + <version>${jib-maven-plugin.version}</version> + <configuration> + <from> + <image>${jib-maven-plugin.image}</image> + <platforms> + <platform> + <architecture>${jib-maven-plugin.architecture}</architecture> + <os>linux</os> + </platform> + </platforms> + </from> + <to> + <image>pamapi:latest</image> + </to> + <container> + <entrypoint> + <shell>bash</shell> + <option>-c</option> + <arg>/entrypoint.sh</arg> + </entrypoint> + <ports> + <port>8080</port> + </ports> + <environment> + <SPRING_OUTPUT_ANSI_ENABLED>ALWAYS</SPRING_OUTPUT_ANSI_ENABLED> + <JHIPSTER_SLEEP>0</JHIPSTER_SLEEP> + </environment> + <creationTime>USE_CURRENT_TIMESTAMP</creationTime> + <user>1000</user> + </container> + <extraDirectories> + <paths>src/main/docker/jib</paths> + <permissions> + <permission> + <file>/entrypoint.sh</file> + <mode>755</mode> + </permission> + </permissions> + </extraDirectories> + </configuration> + </plugin> + <plugin> + <groupId>org.liquibase</groupId> + <artifactId>liquibase-maven-plugin</artifactId> + <version>${liquibase.version}</version> + <configuration> + <changeLogFile>${project.basedir}/src/main/resources/config/liquibase/master.xml</changeLogFile> + <diffChangeLogFile>${project.basedir}/src/main/resources/config/liquibase/changelog/${maven.build.timestamp}_changelog.xml</diffChangeLogFile> + <driver>org.postgresql.Driver</driver> + <url>jdbc:postgresql://localhost:5432/pamapi</url> + <defaultSchemaName></defaultSchemaName> + <username>pamapi</username> + <password></password> + <referenceUrl>hibernate:spring:com.pollex.pam.domain?dialect=tech.jhipster.domain.util.FixedPostgreSQL10Dialect&hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy</referenceUrl> + <verbose>true</verbose> + <logging>debug</logging> + <contexts>!test</contexts> + </configuration> + <dependencies> + <dependency> + <groupId>org.liquibase</groupId> + <artifactId>liquibase-core</artifactId> + <version>${liquibase.version}</version> + </dependency> + <dependency> + <groupId>org.liquibase.ext</groupId> + <artifactId>liquibase-hibernate5</artifactId> + <version>${liquibase-hibernate5.version}</version> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-data-jpa</artifactId> + <version>${spring-boot.version}</version> + </dependency> + <dependency> + <groupId>javax.validation</groupId> + <artifactId>validation-api</artifactId> + <version>${validation-api.version}</version> + </dependency> + <dependency> + <groupId>org.javassist</groupId> + <artifactId>javassist</artifactId> + <version>${javassist.version}</version> + </dependency> + <dependency> + <groupId>tech.jhipster</groupId> + <artifactId>jhipster-framework</artifactId> + <version>${jhipster-dependencies.version}</version> + </dependency> + </dependencies> + </plugin> + <plugin> + <artifactId>maven-clean-plugin</artifactId> + <version>${maven-clean-plugin.version}</version> + </plugin> + <plugin> + <artifactId>maven-site-plugin</artifactId> + <version>${maven-site-plugin.version}</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-eclipse-plugin</artifactId> + <version>${maven-eclipse-plugin.version}</version> + <configuration> + <downloadSources>true</downloadSources> + <downloadJavadocs>true</downloadJavadocs> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-enforcer-plugin</artifactId> + <version>${maven-enforcer-plugin.version}</version> + <executions> + <execution> + <id>enforce-versions</id> + <goals> + <goal>enforce</goal> + </goals> + </execution> + <execution> + <id>enforce-dependencyConvergence</id> + <configuration> + <rules> + <DependencyConvergence /> + </rules> + <fail>false</fail> + </configuration> + <goals> + <goal>enforce</goal> + </goals> + </execution> + </executions> + <configuration> + <rules> + <requireMavenVersion> + <message>You are running an older version of Maven. JHipster requires at least Maven ${maven.version}</message> + <version>[${maven.version},)</version> + </requireMavenVersion> + <requireJavaVersion> + <message>You are running an incompatible version of Java. JHipster supports JDK 8 to 17.</message> + <version>[1.8,18)</version> + </requireJavaVersion> + </rules> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-idea-plugin</artifactId> + <version>${maven-idea-plugin.version}</version> + <configuration> + <exclude>node_modules</exclude> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-resources-plugin</artifactId> + <version>${maven-resources-plugin.version}</version> + <executions> + <execution> + <id>default-resources</id> + <phase>validate</phase> + <goals> + <goal>copy-resources</goal> + </goals> + <configuration> + <outputDirectory>${project.build.directory}/classes</outputDirectory> + <useDefaultDelimiters>false</useDefaultDelimiters> + <delimiters> + <delimiter>#</delimiter> + </delimiters> + <resources> + <resource> + <directory>src/main/resources/</directory> + <filtering>true</filtering> + <includes> + <include>config/*.yml</include> + </includes> + </resource> + <resource> + <directory>src/main/resources/</directory> + <filtering>false</filtering> + <excludes> + <exclude>config/*.yml</exclude> + </excludes> + </resource> + </resources> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <version>${maven-surefire-plugin.version}</version> + <configuration> + <!-- Force alphabetical order to have a reproducible build --> + <runOrder>alphabetical</runOrder> + <excludes> + <exclude>**/*IT*</exclude> + <exclude>**/*IntTest*</exclude> + </excludes> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-failsafe-plugin</artifactId> + <version>${maven-failsafe-plugin.version}</version> + <configuration> + <!-- Due to spring-boot repackage, without adding this property test classes are not found + See https://github.com/spring-projects/spring-boot/issues/6254 --> + <classesDirectory>${project.build.outputDirectory}</classesDirectory> + <!-- Force alphabetical order to have a reproducible build --> + <runOrder>alphabetical</runOrder> + <includes> + <include>**/*IT*</include> + <include>**/*IntTest*</include> + </includes> + </configuration> + <executions> + <execution> + <id>integration-test</id> + <goals> + <goal>integration-test</goal> + </goals> + </execution> + <execution> + <id>verify</id> + <goals> + <goal>verify</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.sonarsource.scanner.maven</groupId> + <artifactId>sonar-maven-plugin</artifactId> + <version>${sonar-maven-plugin.version}</version> + </plugin> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <version>${spring-boot.version}</version> + <executions> + <execution> + <goals> + <goal>repackage</goal> + </goals> + </execution> + </executions> + <configuration> + <mainClass>${start-class}</mainClass> + <fork>true</fork> + <!-- + Enable the line below to have remote debugging of your application on port 5005 + <jvmArguments>-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005</jvmArguments> + --> + </configuration> + + </plugin> + <!-- jhipster-needle-maven-add-plugin-management --> + </plugins> + </pluginManagement> + </build> + <profiles> + <profile> + <id>no-liquibase</id> + <properties> + <profile.no-liquibase>,no-liquibase</profile.no-liquibase> + </properties> + </profile> + <profile> + <id>api-docs</id> + <properties> + <profile.api-docs>,api-docs</profile.api-docs> + </properties> + </profile> + <profile> + <id>tls</id> + <properties> + <profile.tls>,tls</profile.tls> + </properties> + </profile> + <profile> + <id>dev</id> + <activation> + <activeByDefault>true</activeByDefault> + </activation> + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-undertow</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-devtools</artifactId> + <optional>true</optional> + </dependency> + </dependencies> + <properties> + <!-- default Spring profiles --> + <spring.profiles.active>dev${profile.tls}${profile.no-liquibase}</spring.profiles.active> + </properties> + </profile> + <profile> + <id>prod</id> + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-undertow</artifactId> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <artifactId>maven-clean-plugin</artifactId> + <configuration> + <filesets> + <fileset> + <directory>target/classes/static/</directory> + </fileset> + </filesets> + </configuration> + </plugin> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>build-info</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>io.github.git-commit-id</groupId> + <artifactId>git-commit-id-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + <properties> + <!-- default Spring profiles --> + <spring.profiles.active>prod${profile.api-docs}${profile.tls}${profile.no-liquibase}</spring.profiles.active> + </properties> + </profile> + <profile> + <id>war</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-war-plugin</artifactId> + </plugin> + </plugins> + </build> + </profile> + <profile> + <!-- + Profile for tracing requests with Zipkin. + --> + <id>zipkin</id> + <dependencies> + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-starter-zipkin</artifactId> + </dependency> + </dependencies> + </profile> + <profile> + <!-- + Profile for applying IDE-specific configuration. + At the moment it configures MapStruct and Hibernate JPA Metamodel Generator, which you need when working + with DTOs and entity filtering. + --> + <id>IDE</id> + <dependencies> + <dependency> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct-processor</artifactId> + <version>${mapstruct.version}</version> + </dependency> + <dependency> + <groupId>org.hibernate</groupId> + <artifactId>hibernate-jpamodelgen</artifactId> + </dependency> + </dependencies> + </profile> + <profile> + <!-- This is automatically activated when working in Eclipse --> + <id>eclipse</id> + <activation> + <property> + <name>m2e.version</name> + </property> + </activation> + <dependencies> + <!-- The following dependency is added due to issue #9175--> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-undertow</artifactId> + </dependency> + </dependencies> + <build> + <pluginManagement> + <plugins> + <!-- + This plugin's configuration is used to store Eclipse m2e settings only. + It has no influence on the Maven build itself. + Remove when the m2e plugin can correctly bind to Maven lifecycle + --> + <plugin> + <groupId>org.eclipse.m2e</groupId> + <artifactId>lifecycle-mapping</artifactId> + <version>${lifecycle-mapping.version}</version> + <configuration> + <lifecycleMappingMetadata> + <pluginExecutions> + <pluginExecution> + <pluginExecutionFilter> + <groupId>org.jacoco</groupId> + <artifactId> + jacoco-maven-plugin + </artifactId> + <versionRange> + ${jacoco-maven-plugin.version} + </versionRange> + <goals> + <goal>prepare-agent</goal> + </goals> + </pluginExecutionFilter> + <action> + <ignore/> + </action> + </pluginExecution> + </pluginExecutions> + </lifecycleMappingMetadata> + </configuration> + </plugin> + </plugins> + </pluginManagement> + </build> + </profile> + <!-- jhipster-needle-maven-add-profile --> + </profiles> +</project> diff --git a/pamapi/sonar-project.properties b/pamapi/sonar-project.properties new file mode 100644 index 0000000..354bdd2 --- /dev/null +++ b/pamapi/sonar-project.properties @@ -0,0 +1,28 @@ +sonar.projectKey=pamapi +sonar.projectName=pamapi 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/site/**/jacoco*.xml +sonar.java.codeCoveragePlugin=jacoco +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,S4502,S4684,UndocumentedApi +# 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://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://rules.sonarsource.com/java/RSPEC-4502 is ignored, as for JWT tokens we are not subject to CSRF attack +sonar.issue.ignore.multicriteria.S4502.resourceKey=src/main/java/**/* +sonar.issue.ignore.multicriteria.S4502.ruleKey=squid:S4502 +# Rule https://rules.sonarsource.com/java/RSPEC-4684 +sonar.issue.ignore.multicriteria.S4684.resourceKey=src/main/java/**/* +sonar.issue.ignore.multicriteria.S4684.ruleKey=java:S4684 diff --git a/pamapi/src/main/docker/app.yml b/pamapi/src/main/docker/app.yml new file mode 100644 index 0000000..829de96 --- /dev/null +++ b/pamapi/src/main/docker/app.yml @@ -0,0 +1,28 @@ +# This configuration is intended for development purpose, it's **your** responsibility to harden it for production +version: '3.8' +services: + pamapi-app: + image: pamapi + environment: + - _JAVA_OPTIONS=-Xmx512m -Xms256m + - SPRING_PROFILES_ACTIVE=prod,api-docs + - MANAGEMENT_METRICS_EXPORT_PROMETHEUS_ENABLED=true + - SPRING_DATASOURCE_URL=jdbc:postgresql://pamapi-postgresql:5432/pamapi + - SPRING_LIQUIBASE_URL=jdbc:postgresql://pamapi-postgresql:5432/pamapi + - JHIPSTER_SLEEP=30 # gives time for other services to boot before the application + # If you want to expose these ports outside your dev PC, + # remove the "127.0.0.1:" prefix + ports: + - 127.0.0.1:8080:8080 + pamapi-postgresql: + image: postgres:13.4 + # volumes: + # - ~/volumes/jhipster/pamapi/postgresql/:/var/lib/postgresql/data/ + environment: + - POSTGRES_USER=pamapi + - POSTGRES_PASSWORD= + - POSTGRES_HOST_AUTH_METHOD=trust + # If you want to expose these ports outside your dev PC, + # remove the "127.0.0.1:" prefix + ports: + - 127.0.0.1:5432:5432 diff --git a/pamapi/src/main/docker/central-server-config/README.md b/pamapi/src/main/docker/central-server-config/README.md new file mode 100644 index 0000000..c3e9cfd --- /dev/null +++ b/pamapi/src/main/docker/central-server-config/README.md @@ -0,0 +1 @@ +# Central configuration sources details diff --git a/pamapi/src/main/docker/grafana/provisioning/dashboards/JVM.json b/pamapi/src/main/docker/grafana/provisioning/dashboards/JVM.json new file mode 100644 index 0000000..5104abc --- /dev/null +++ b/pamapi/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/pamapi/src/main/docker/grafana/provisioning/dashboards/dashboard.yml b/pamapi/src/main/docker/grafana/provisioning/dashboards/dashboard.yml new file mode 100644 index 0000000..4817a83 --- /dev/null +++ b/pamapi/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/pamapi/src/main/docker/grafana/provisioning/datasources/datasource.yml b/pamapi/src/main/docker/grafana/provisioning/datasources/datasource.yml new file mode 100644 index 0000000..57b2bb3 --- /dev/null +++ b/pamapi/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: + # <string, required> name of the datasource. Required + - name: Prometheus + # <string, required> datasource type. Required + type: prometheus + # <string, required> access mode. direct or proxy. Required + access: proxy + # <int> org id. will default to orgId 1 if not specified + orgId: 1 + # <string> url + # On MacOS, replace localhost by host.docker.internal + url: http://localhost:9090 + # <string> database password, if used + password: + # <string> database user, if used + user: + # <string> database name, if used + database: + # <bool> enable/disable basic auth + basicAuth: false + # <string> basic auth username + basicAuthUser: admin + # <string> basic auth password + basicAuthPassword: admin + # <bool> enable/disable with credentials headers + withCredentials: + # <bool> mark as default datasource. Max one per org + isDefault: true + # <map> fields that will be converted to json and stored in json_data + jsonData: + graphiteVersion: '1.1' + tlsAuth: false + tlsAuthWithCACert: false + # <string> json object of data that will be encrypted. + secureJsonData: + tlsCACert: '...' + tlsClientCert: '...' + tlsClientKey: '...' + version: 1 + # <bool> allow users to edit datasources from the UI. + editable: true diff --git a/pamapi/src/main/docker/jhipster-control-center.yml b/pamapi/src/main/docker/jhipster-control-center.yml new file mode 100644 index 0000000..3b04680 --- /dev/null +++ b/pamapi/src/main/docker/jhipster-control-center.yml @@ -0,0 +1,52 @@ +## 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.5.0' + 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,no + - JHIPSTER_SLEEP=30 # gives time for other services to boot before the application + - SPRING_SECURITY_USER_PASSWORD=admin + # The token should have the same value than the one declared in you Spring configuration under the jhipster.security.authentication.jwt.base64-secret configuration's entry + - JHIPSTER_SECURITY_AUTHENTICATION_JWT_BASE64_SECRET=MjI3YWRiMzg1ZTY3ZmZkZDgxZmI5Yjc5YjVkOTIzMzc4MmI2OWM3NWVkZjFiOTNmNjg0YWFjZWQ4YzhlOTUzYzk3MGUzMzM5Y2U5MDdkZTMyN2Q0N2E0M2ZmM2FhYzkyNDY4MjBkYTY5OGM4YmIzMmYxODJhNWFkMmVhNmVhNTM= + - SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_PAMAPI_0_URI=http://host.docker.internal:8080 + - 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/pamapi/src/main/docker/jib/entrypoint.sh b/pamapi/src/main/docker/jib/entrypoint.sh new file mode 100644 index 0000000..df97251 --- /dev/null +++ b/pamapi/src/main/docker/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/* "com.pollex.pam.PamapiApp" "$@" diff --git a/pamapi/src/main/docker/monitoring.yml b/pamapi/src/main/docker/monitoring.yml new file mode 100644 index 0000000..00588e2 --- /dev/null +++ b/pamapi/src/main/docker/monitoring.yml @@ -0,0 +1,31 @@ +# This configuration is intended for development purpose, it's **your** responsibility to harden it for production +version: '3.8' +services: + pamapi-prometheus: + image: prom/prometheus:v2.29.2 + 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: + - 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 + pamapi-grafana: + image: grafana/grafana:8.1.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: + - 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/pamapi/src/main/docker/postgresql.yml b/pamapi/src/main/docker/postgresql.yml new file mode 100644 index 0000000..040ad45 --- /dev/null +++ b/pamapi/src/main/docker/postgresql.yml @@ -0,0 +1,15 @@ +# This configuration is intended for development purpose, it's **your** responsibility to harden it for production +version: '3.8' +services: + pamapi-postgresql: + image: postgres:13.4 + # volumes: + # - ~/volumes/jhipster/pamapi/postgresql/:/var/lib/postgresql/data/ + environment: + - POSTGRES_USER=pamapi + - POSTGRES_PASSWORD= + - POSTGRES_HOST_AUTH_METHOD=trust + # If you want to expose these ports outside your dev PC, + # remove the "127.0.0.1:" prefix + ports: + - 127.0.0.1:5432:5432 diff --git a/pamapi/src/main/docker/prometheus/prometheus.yml b/pamapi/src/main/docker/prometheus/prometheus.yml new file mode 100644 index 0000000..b370a2f --- /dev/null +++ b/pamapi/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=<job_name>` 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:8080 diff --git a/pamapi/src/main/docker/sonar.yml b/pamapi/src/main/docker/sonar.yml new file mode 100644 index 0000000..b4fdfbc --- /dev/null +++ b/pamapi/src/main/docker/sonar.yml @@ -0,0 +1,13 @@ +# This configuration is intended for development purpose, it's **your** responsibility to harden it for production +version: '3.8' +services: + pamapi-sonar: + image: sonarqube:8.9.2-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: + - 127.0.0.1:9001:9000 diff --git a/pamapi/src/main/java/com/pollex/pam/ApplicationWebXml.java b/pamapi/src/main/java/com/pollex/pam/ApplicationWebXml.java new file mode 100644 index 0000000..82d4d2d --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/ApplicationWebXml.java @@ -0,0 +1,19 @@ +package com.pollex.pam; + +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}. + * 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(PamapiApp.class); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/GeneratedByJHipster.java b/pamapi/src/main/java/com/pollex/pam/GeneratedByJHipster.java new file mode 100644 index 0000000..ba74dc4 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/GeneratedByJHipster.java @@ -0,0 +1,13 @@ +package com.pollex.pam; + +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.3.0") +@Retention(RetentionPolicy.SOURCE) +@Target({ ElementType.TYPE }) +public @interface GeneratedByJHipster { +} diff --git a/pamapi/src/main/java/com/pollex/pam/PamapiApp.java b/pamapi/src/main/java/com/pollex/pam/PamapiApp.java new file mode 100644 index 0000000..0182749 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/PamapiApp.java @@ -0,0 +1,103 @@ +package com.pollex.pam; + +import com.pollex.pam.config.ApplicationProperties; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; +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.core.env.Environment; +import tech.jhipster.config.DefaultProfileUtil; +import tech.jhipster.config.JHipsterConstants; + +@SpringBootApplication +@EnableConfigurationProperties({ LiquibaseProperties.class, ApplicationProperties.class }) +public class PamapiApp { + + private static final Logger log = LoggerFactory.getLogger(PamapiApp.class); + + private final Environment env; + + public PamapiApp(Environment env) { + this.env = env; + } + + /** + * Initializes pamapi. + * <p> + * Spring profiles can be configured with a program argument --spring.profiles.active=your-active-profile + * <p> + * You can find more information on how profiles work with JHipster on <a href="https://www.jhipster.tech/profiles/">https://www.jhipster.tech/profiles/</a>. + */ + @PostConstruct + public void initApplication() { + Collection<String> 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(PamapiApp.class); + DefaultProfileUtil.addDefaultProfile(app); + Environment env = app.run(args).getEnvironment(); + logApplicationStartup(env); + } + + private static void logApplicationStartup(Environment env) { + String protocol = Optional.ofNullable(env.getProperty("server.ssl.key-store")).map(key -> "https").orElse("http"); + String serverPort = env.getProperty("server.port"); + 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----------------------------------------------------------", + env.getProperty("spring.application.name"), + protocol, + serverPort, + contextPath, + protocol, + hostAddress, + serverPort, + contextPath, + env.getActiveProfiles().length == 0 ? env.getDefaultProfiles() : env.getActiveProfiles() + ); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/aop/logging/LoggingAspect.java b/pamapi/src/main/java/com/pollex/pam/aop/logging/LoggingAspect.java new file mode 100644 index 0000000..40b6006 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/aop/logging/LoggingAspect.java @@ -0,0 +1,111 @@ +package com.pollex.pam.aop.logging; + +import java.util.Arrays; +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 tech.jhipster.config.JHipsterConstants; + +/** + * 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 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(com.pollex.pam.repository..*)" + " || within(com.pollex.pam.service..*)" + " || within(com.pollex.pam.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. + * + * @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))) { + logger(joinPoint) + .error( + "Exception in {}() with cause = '{}' and exception = '{}'", + joinPoint.getSignature().getName(), + e.getCause() != null ? e.getCause() : "NULL", + e.getMessage(), + e + ); + } else { + logger(joinPoint) + .error( + "Exception in {}() with cause = {}", + 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 { + Logger log = logger(joinPoint); + if (log.isDebugEnabled()) { + 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().getName(), result); + } + return result; + } catch (IllegalArgumentException e) { + log.error("Illegal argument: {} in {}()", Arrays.toString(joinPoint.getArgs()), joinPoint.getSignature().getName()); + throw e; + } + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/config/ApplicationProperties.java b/pamapi/src/main/java/com/pollex/pam/config/ApplicationProperties.java new file mode 100644 index 0000000..cbe8982 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/config/ApplicationProperties.java @@ -0,0 +1,12 @@ +package com.pollex.pam.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Properties specific to Pamapi. + * <p> + * Properties are configured in the {@code application.yml} file. + * See {@link tech.jhipster.config.JHipsterProperties} for a good example. + */ +@ConfigurationProperties(prefix = "application", ignoreUnknownFields = false) +public class ApplicationProperties {} diff --git a/pamapi/src/main/java/com/pollex/pam/config/AsyncConfiguration.java b/pamapi/src/main/java/com/pollex/pam/config/AsyncConfiguration.java new file mode 100644 index 0000000..f1121f4 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/config/AsyncConfiguration.java @@ -0,0 +1,46 @@ +package com.pollex.pam.config; + +import java.util.concurrent.Executor; +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 tech.jhipster.async.ExceptionHandlingAsyncTaskExecutor; + +@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/pamapi/src/main/java/com/pollex/pam/config/CacheConfiguration.java b/pamapi/src/main/java/com/pollex/pam/config/CacheConfiguration.java new file mode 100644 index 0000000..980373c --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/config/CacheConfiguration.java @@ -0,0 +1,78 @@ +package com.pollex.pam.config; + +import java.time.Duration; +import org.ehcache.config.builders.*; +import org.ehcache.jsr107.Eh107Configuration; +import org.hibernate.cache.jcache.ConfigSettings; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer; +import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer; +import org.springframework.boot.info.BuildProperties; +import org.springframework.boot.info.GitProperties; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.interceptor.KeyGenerator; +import org.springframework.context.annotation.*; +import tech.jhipster.config.JHipsterProperties; +import tech.jhipster.config.cache.PrefixedKeyGenerator; + +@Configuration +@EnableCaching +public class CacheConfiguration { + + private GitProperties gitProperties; + private BuildProperties buildProperties; + private final javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration; + + public CacheConfiguration(JHipsterProperties jHipsterProperties) { + JHipsterProperties.Cache.Ehcache ehcache = jHipsterProperties.getCache().getEhcache(); + + jcacheConfiguration = + Eh107Configuration.fromEhcacheCacheConfiguration( + CacheConfigurationBuilder + .newCacheConfigurationBuilder(Object.class, Object.class, ResourcePoolsBuilder.heap(ehcache.getMaxEntries())) + .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(ehcache.getTimeToLiveSeconds()))) + .build() + ); + } + + @Bean + public HibernatePropertiesCustomizer hibernatePropertiesCustomizer(javax.cache.CacheManager cacheManager) { + return hibernateProperties -> hibernateProperties.put(ConfigSettings.CACHE_MANAGER, cacheManager); + } + + @Bean + public JCacheManagerCustomizer cacheManagerCustomizer() { + return cm -> { + createCache(cm, com.pollex.pam.repository.UserRepository.USERS_BY_LOGIN_CACHE); + createCache(cm, com.pollex.pam.repository.UserRepository.USERS_BY_EMAIL_CACHE); + createCache(cm, com.pollex.pam.domain.User.class.getName()); + createCache(cm, com.pollex.pam.domain.Authority.class.getName()); + createCache(cm, com.pollex.pam.domain.User.class.getName() + ".authorities"); + // jhipster-needle-ehcache-add-entry + }; + } + + private void createCache(javax.cache.CacheManager cm, String cacheName) { + javax.cache.Cache<Object, Object> cache = cm.getCache(cacheName); + if (cache != null) { + cache.clear(); + } else { + cm.createCache(cacheName, jcacheConfiguration); + } + } + + @Autowired(required = false) + public void setGitProperties(GitProperties gitProperties) { + this.gitProperties = gitProperties; + } + + @Autowired(required = false) + public void setBuildProperties(BuildProperties buildProperties) { + this.buildProperties = buildProperties; + } + + @Bean + public KeyGenerator keyGenerator() { + return new PrefixedKeyGenerator(this.gitProperties, this.buildProperties); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/config/Constants.java b/pamapi/src/main/java/com/pollex/pam/config/Constants.java new file mode 100644 index 0000000..ead5fc8 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/config/Constants.java @@ -0,0 +1,15 @@ +package com.pollex.pam.config; + +/** + * Application constants. + */ +public final class Constants { + + // Regex for acceptable logins + 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 = "zh-tw"; + + private Constants() {} +} diff --git a/pamapi/src/main/java/com/pollex/pam/config/DatabaseConfiguration.java b/pamapi/src/main/java/com/pollex/pam/config/DatabaseConfiguration.java new file mode 100644 index 0000000..02439f2 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/config/DatabaseConfiguration.java @@ -0,0 +1,16 @@ +package com.pollex.pam.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import tech.jhipster.config.JHipsterConstants; + +@Configuration +@EnableJpaRepositories({ "com.pollex.pam.repository" }) +@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware") +@EnableTransactionManagement +public class DatabaseConfiguration {} diff --git a/pamapi/src/main/java/com/pollex/pam/config/DateTimeFormatConfiguration.java b/pamapi/src/main/java/com/pollex/pam/config/DateTimeFormatConfiguration.java new file mode 100644 index 0000000..7767d12 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/config/DateTimeFormatConfiguration.java @@ -0,0 +1,20 @@ +package com.pollex.pam.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/pamapi/src/main/java/com/pollex/pam/config/JacksonConfiguration.java b/pamapi/src/main/java/com/pollex/pam/config/JacksonConfiguration.java new file mode 100644 index 0000000..e0c3b3e --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/config/JacksonConfiguration.java @@ -0,0 +1,51 @@ +package com.pollex.pam.config; + +import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.zalando.problem.jackson.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(); + } + + /* + * Module for serialization/deserialization of RFC7807 Problem. + */ + @Bean + public ProblemModule problemModule() { + return new ProblemModule(); + } + + /* + * Module for serialization/deserialization of ConstraintViolationProblem. + */ + @Bean + public ConstraintViolationProblemModule constraintViolationProblemModule() { + return new ConstraintViolationProblemModule(); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/config/LiquibaseConfiguration.java b/pamapi/src/main/java/com/pollex/pam/config/LiquibaseConfiguration.java new file mode 100644 index 0000000..0aca766 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/config/LiquibaseConfiguration.java @@ -0,0 +1,69 @@ +package com.pollex.pam.config; + +import java.util.concurrent.Executor; +import javax.sql.DataSource; +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 tech.jhipster.config.JHipsterConstants; +import tech.jhipster.config.liquibase.SpringLiquibaseUtil; + +@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<DataSource> liquibaseDataSource, + LiquibaseProperties liquibaseProperties, + ObjectProvider<DataSource> 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/pamapi/src/main/java/com/pollex/pam/config/LocaleConfiguration.java b/pamapi/src/main/java/com/pollex/pam/config/LocaleConfiguration.java new file mode 100644 index 0000000..02f60ed --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/config/LocaleConfiguration.java @@ -0,0 +1,26 @@ +package com.pollex.pam.config; + +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 + 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/pamapi/src/main/java/com/pollex/pam/config/LoggingAspectConfiguration.java b/pamapi/src/main/java/com/pollex/pam/config/LoggingAspectConfiguration.java new file mode 100644 index 0000000..dd3c950 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/config/LoggingAspectConfiguration.java @@ -0,0 +1,17 @@ +package com.pollex.pam.config; + +import com.pollex.pam.aop.logging.LoggingAspect; +import org.springframework.context.annotation.*; +import org.springframework.core.env.Environment; +import tech.jhipster.config.JHipsterConstants; + +@Configuration +@EnableAspectJAutoProxy +public class LoggingAspectConfiguration { + + @Bean + @Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) + public LoggingAspect loggingAspect(Environment env) { + return new LoggingAspect(env); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/config/LoggingConfiguration.java b/pamapi/src/main/java/com/pollex/pam/config/LoggingConfiguration.java new file mode 100644 index 0000000..a96e0af --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/config/LoggingConfiguration.java @@ -0,0 +1,47 @@ +package com.pollex.pam.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 java.util.HashMap; +import java.util.Map; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import tech.jhipster.config.JHipsterProperties; + +/* + * Configures the console and Logstash log appenders from the app properties + */ +@Configuration +public class LoggingConfiguration { + + public LoggingConfiguration( + @Value("${spring.application.name}") String appName, + @Value("${server.port}") String serverPort, + JHipsterProperties jHipsterProperties, + ObjectMapper mapper + ) throws JsonProcessingException { + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + + Map<String, String> map = new HashMap<>(); + map.put("app_name", appName); + map.put("app_port", serverPort); + 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); + } + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/config/SecurityConfiguration.java b/pamapi/src/main/java/com/pollex/pam/config/SecurityConfiguration.java new file mode 100644 index 0000000..3f9110b --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/config/SecurityConfiguration.java @@ -0,0 +1,104 @@ +package com.pollex.pam.config; + +import com.pollex.pam.security.*; +import com.pollex.pam.security.jwt.*; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter; +import org.springframework.web.filter.CorsFilter; +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 { + + private final JHipsterProperties jHipsterProperties; + + private final TokenProvider tokenProvider; + + private final CorsFilter corsFilter; + private final SecurityProblemSupport problemSupport; + + public SecurityConfiguration( + TokenProvider tokenProvider, + CorsFilter corsFilter, + JHipsterProperties jHipsterProperties, + SecurityProblemSupport problemSupport + ) { + this.tokenProvider = tokenProvider; + this.corsFilter = corsFilter; + this.problemSupport = problemSupport; + this.jHipsterProperties = jHipsterProperties; + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Override + public void configure(WebSecurity web) { + web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**").antMatchers("/swagger-ui/**").antMatchers("/test/**"); + } + + @Override + public void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .csrf() + .disable() + .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class) + .exceptionHandling() + .authenticationEntryPoint(problemSupport) + .accessDeniedHandler(problemSupport) + .and() + .headers() + .contentSecurityPolicy(jHipsterProperties.getSecurity().getContentSecurityPolicy()) + .and() + .referrerPolicy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN) + .and() + .permissionsPolicy().policy("camera=(), fullscreen=(self), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), sync-xhr=()") + .and() + .frameOptions() + .deny() + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authorizeRequests() + .antMatchers("/api/authenticate").permitAll() + .antMatchers("/api/register").permitAll() + .antMatchers("/api/activate").permitAll() + .antMatchers("/api/account/reset-password/init").permitAll() + .antMatchers("/api/account/reset-password/finish").permitAll() + .antMatchers("/api/admin/**").hasAuthority(AuthoritiesConstants.ADMIN) + .antMatchers("/api/**").authenticated() + .antMatchers("/websocket/**").authenticated() + .antMatchers("/management/health").permitAll() + .antMatchers("/management/health/**").permitAll() + .antMatchers("/management/info").permitAll() + .antMatchers("/management/prometheus").permitAll() + .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN) + .and() + .httpBasic() + .and() + .apply(securityConfigurerAdapter()); + // @formatter:on + } + + private JWTConfigurer securityConfigurerAdapter() { + return new JWTConfigurer(tokenProvider); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/config/WebConfigurer.java b/pamapi/src/main/java/com/pollex/pam/config/WebConfigurer.java new file mode 100644 index 0000000..8a48141 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/config/WebConfigurer.java @@ -0,0 +1,60 @@ +package com.pollex.pam.config; + +import javax.servlet.*; +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.util.CollectionUtils; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import tech.jhipster.config.JHipsterProperties; + +/** + * 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()); + } + + log.info("Web application fully configured"); + } + + @Bean + public CorsFilter corsFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration config = jHipsterProperties.getCors(); + if (!CollectionUtils.isEmpty(config.getAllowedOrigins()) || !CollectionUtils.isEmpty(config.getAllowedOriginPatterns())) { + 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); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/config/WebsocketConfiguration.java b/pamapi/src/main/java/com/pollex/pam/config/WebsocketConfiguration.java new file mode 100644 index 0000000..45e29b6 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/config/WebsocketConfiguration.java @@ -0,0 +1,90 @@ +package com.pollex.pam.config; + +import com.pollex.pam.security.AuthoritiesConstants; +import java.security.Principal; +import java.util.*; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.server.*; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.config.annotation.*; +import org.springframework.web.socket.server.HandshakeInterceptor; +import org.springframework.web.socket.server.support.DefaultHandshakeHandler; +import tech.jhipster.config.JHipsterProperties; + +@Configuration +@EnableWebSocketMessageBroker +public class WebsocketConfiguration implements WebSocketMessageBrokerConfigurer { + + public static final String IP_ADDRESS = "IP_ADDRESS"; + + private final JHipsterProperties jHipsterProperties; + + public WebsocketConfiguration(JHipsterProperties jHipsterProperties) { + this.jHipsterProperties = jHipsterProperties; + } + + @Override + public void configureMessageBroker(MessageBrokerRegistry config) { + config.enableSimpleBroker("/topic"); + } + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + String[] allowedOrigins = Optional + .ofNullable(jHipsterProperties.getCors().getAllowedOrigins()) + .map(origins -> origins.toArray(new String[0])) + .orElse(new String[0]); + registry + .addEndpoint("/websocket/tracker") + .setHandshakeHandler(defaultHandshakeHandler()) + .setAllowedOrigins(allowedOrigins) + .withSockJS() + .setInterceptors(httpSessionHandshakeInterceptor()); + } + + @Bean + public HandshakeInterceptor httpSessionHandshakeInterceptor() { + return new HandshakeInterceptor() { + @Override + public boolean beforeHandshake( + ServerHttpRequest request, + ServerHttpResponse response, + WebSocketHandler wsHandler, + Map<String, Object> attributes + ) throws Exception { + if (request instanceof ServletServerHttpRequest) { + ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request; + attributes.put(IP_ADDRESS, servletRequest.getRemoteAddress()); + } + return true; + } + + @Override + public void afterHandshake( + ServerHttpRequest request, + ServerHttpResponse response, + WebSocketHandler wsHandler, + Exception exception + ) {} + }; + } + + private DefaultHandshakeHandler defaultHandshakeHandler() { + return new DefaultHandshakeHandler() { + @Override + protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) { + Principal principal = request.getPrincipal(); + if (principal == null) { + Collection<SimpleGrantedAuthority> authorities = new ArrayList<>(); + authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ANONYMOUS)); + principal = new AnonymousAuthenticationToken("WebsocketConfiguration", "anonymous", authorities); + } + return principal; + } + }; + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/config/WebsocketSecurityConfiguration.java b/pamapi/src/main/java/com/pollex/pam/config/WebsocketSecurityConfiguration.java new file mode 100644 index 0000000..30afbb2 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/config/WebsocketSecurityConfiguration.java @@ -0,0 +1,40 @@ +package com.pollex.pam.config; + +import com.pollex.pam.security.AuthoritiesConstants; +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.SimpMessageType; +import org.springframework.security.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry; +import org.springframework.security.config.annotation.web.socket.AbstractSecurityWebSocketMessageBrokerConfigurer; + +@Configuration +public class WebsocketSecurityConfiguration extends AbstractSecurityWebSocketMessageBrokerConfigurer { + + @Override + protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { + messages + .nullDestMatcher() + .authenticated() + .simpDestMatchers("/topic/tracker") + .hasAuthority(AuthoritiesConstants.ADMIN) + // matches any destination that starts with /topic/ + // (i.e. cannot send messages directly to /topic/) + // (i.e. cannot subscribe to /topic/messages/* to get messages sent to + // /topic/messages-user<id>) + .simpDestMatchers("/topic/**") + .authenticated() + // message types other than MESSAGE and SUBSCRIBE + .simpTypeMatchers(SimpMessageType.MESSAGE, SimpMessageType.SUBSCRIBE) + .denyAll() + // catch all + .anyMessage() + .denyAll(); + } + + /** + * Disables CSRF for Websockets. + */ + @Override + protected boolean sameOriginDisabled() { + return true; + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/config/package-info.java b/pamapi/src/main/java/com/pollex/pam/config/package-info.java new file mode 100644 index 0000000..e96118b --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/config/package-info.java @@ -0,0 +1,4 @@ +/** + * Spring Framework configuration files. + */ +package com.pollex.pam.config; diff --git a/pamapi/src/main/java/com/pollex/pam/domain/AbstractAuditingEntity.java b/pamapi/src/main/java/com/pollex/pam/domain/AbstractAuditingEntity.java new file mode 100644 index 0000000..c89d1d2 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/domain/AbstractAuditingEntity.java @@ -0,0 +1,76 @@ +package com.pollex.pam.domain; + +import com.fasterxml.jackson.annotation.JsonIgnore; +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, created by, + * last modified by attributes. + */ +@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/pamapi/src/main/java/com/pollex/pam/domain/Authority.java b/pamapi/src/main/java/com/pollex/pam/domain/Authority.java new file mode 100644 index 0000000..c393e2b --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/domain/Authority.java @@ -0,0 +1,61 @@ +package com.pollex.pam.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.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +/** + * An authority (a security role) used by Spring Security. + */ +@Entity +@Table(name = "jhi_authority") +@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) +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); + } + + // prettier-ignore + @Override + public String toString() { + return "Authority{" + + "name='" + name + '\'' + + "}"; + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/domain/User.java b/pamapi/src/main/java/com/pollex/pam/domain/User.java new file mode 100644 index 0000000..3be5c1d --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/domain/User.java @@ -0,0 +1,232 @@ +package com.pollex.pam.domain; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.pollex.pam.config.Constants; +import java.io.Serializable; +import java.time.Instant; +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 org.apache.commons.lang3.StringUtils; +import org.hibernate.annotations.BatchSize; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +/** + * A user. + */ +@Entity +@Table(name = "jhi_user") +@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) +public class User extends AbstractAuditingEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator") + @SequenceGenerator(name = "sequenceGenerator") + private Long id; + + @NotNull + @Pattern(regexp = Constants.LOGIN_REGEX) + @Size(min = 1, max = 50) + @Column(length = 50, unique = true, nullable = false) + private String login; + + @JsonIgnore + @NotNull + @Size(min = 60, max = 60) + @Column(name = "password_hash", length = 60, nullable = false) + private String password; + + @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; + + @Size(max = 20) + @Column(name = "activation_key", length = 20) + @JsonIgnore + private String activationKey; + + @Size(max = 20) + @Column(name = "reset_key", length = 20) + @JsonIgnore + private String resetKey; + + @Column(name = "reset_date") + private Instant resetDate = null; + + @JsonIgnore + @ManyToMany + @JoinTable( + name = "jhi_user_authority", + joinColumns = { @JoinColumn(name = "user_id", referencedColumnName = "id") }, + inverseJoinColumns = { @JoinColumn(name = "authority_name", referencedColumnName = "name") } + ) + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) + @BatchSize(size = 20) + private Set<Authority> authorities = new HashSet<>(); + + public Long getId() { + return id; + } + + public void setId(Long 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 getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + 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 getActivationKey() { + return activationKey; + } + + public void setActivationKey(String activationKey) { + this.activationKey = activationKey; + } + + public String getResetKey() { + return resetKey; + } + + public void setResetKey(String resetKey) { + this.resetKey = resetKey; + } + + public Instant getResetDate() { + return resetDate; + } + + public void setResetDate(Instant resetDate) { + this.resetDate = resetDate; + } + + public String getLangKey() { + return langKey; + } + + public void setLangKey(String langKey) { + this.langKey = langKey; + } + + public Set<Authority> getAuthorities() { + return authorities; + } + + public void setAuthorities(Set<Authority> 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() { + // 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{" + + "login='" + login + '\'' + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", email='" + email + '\'' + + ", imageUrl='" + imageUrl + '\'' + + ", activated='" + activated + '\'' + + ", langKey='" + langKey + '\'' + + ", activationKey='" + activationKey + '\'' + + "}"; + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/domain/package-info.java b/pamapi/src/main/java/com/pollex/pam/domain/package-info.java new file mode 100644 index 0000000..bcfa3f9 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/domain/package-info.java @@ -0,0 +1,4 @@ +/** + * JPA domain objects. + */ +package com.pollex.pam.domain; diff --git a/pamapi/src/main/java/com/pollex/pam/repository/AuthorityRepository.java b/pamapi/src/main/java/com/pollex/pam/repository/AuthorityRepository.java new file mode 100644 index 0000000..111c063 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/repository/AuthorityRepository.java @@ -0,0 +1,9 @@ +package com.pollex.pam.repository; + +import com.pollex.pam.domain.Authority; +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * Spring Data JPA repository for the {@link Authority} entity. + */ +public interface AuthorityRepository extends JpaRepository<Authority, String> {} diff --git a/pamapi/src/main/java/com/pollex/pam/repository/UserRepository.java b/pamapi/src/main/java/com/pollex/pam/repository/UserRepository.java new file mode 100644 index 0000000..f4407cf --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/repository/UserRepository.java @@ -0,0 +1,42 @@ +package com.pollex.pam.repository; + +import com.pollex.pam.domain.User; +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import org.springframework.cache.annotation.Cacheable; +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; + +/** + * Spring Data JPA repository for the {@link User} entity. + */ +@Repository +public interface UserRepository extends JpaRepository<User, Long> { + String USERS_BY_LOGIN_CACHE = "usersByLogin"; + + String USERS_BY_EMAIL_CACHE = "usersByEmail"; + + Optional<User> findOneByActivationKey(String activationKey); + + List<User> findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(Instant dateTime); + + Optional<User> findOneByResetKey(String resetKey); + + Optional<User> findOneByEmailIgnoreCase(String email); + + Optional<User> findOneByLogin(String login); + + @EntityGraph(attributePaths = "authorities") + @Cacheable(cacheNames = USERS_BY_LOGIN_CACHE) + Optional<User> findOneWithAuthoritiesByLogin(String login); + + @EntityGraph(attributePaths = "authorities") + @Cacheable(cacheNames = USERS_BY_EMAIL_CACHE) + Optional<User> findOneWithAuthoritiesByEmailIgnoreCase(String email); + + Page<User> findAllByIdNotNullAndActivatedIsTrue(Pageable pageable); +} diff --git a/pamapi/src/main/java/com/pollex/pam/repository/package-info.java b/pamapi/src/main/java/com/pollex/pam/repository/package-info.java new file mode 100644 index 0000000..44ecf27 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/repository/package-info.java @@ -0,0 +1,4 @@ +/** + * Spring Data JPA repositories. + */ +package com.pollex.pam.repository; diff --git a/pamapi/src/main/java/com/pollex/pam/security/AuthoritiesConstants.java b/pamapi/src/main/java/com/pollex/pam/security/AuthoritiesConstants.java new file mode 100644 index 0000000..b67d92d --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/security/AuthoritiesConstants.java @@ -0,0 +1,15 @@ +package com.pollex.pam.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/pamapi/src/main/java/com/pollex/pam/security/DomainUserDetailsService.java b/pamapi/src/main/java/com/pollex/pam/security/DomainUserDetailsService.java new file mode 100644 index 0000000..e8a35fa --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/security/DomainUserDetailsService.java @@ -0,0 +1,62 @@ +package com.pollex.pam.security; + +import com.pollex.pam.domain.User; +import com.pollex.pam.repository.UserRepository; +import java.util.*; +import java.util.stream.Collectors; +import org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +/** + * Authenticate a user from the database. + */ +@Component("userDetailsService") +public class DomainUserDetailsService implements UserDetailsService { + + private final Logger log = LoggerFactory.getLogger(DomainUserDetailsService.class); + + private final UserRepository userRepository; + + public DomainUserDetailsService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + @Transactional + public UserDetails loadUserByUsername(final String login) { + log.debug("Authenticating {}", login); + + if (new EmailValidator().isValid(login, null)) { + return userRepository + .findOneWithAuthoritiesByEmailIgnoreCase(login) + .map(user -> createSpringSecurityUser(login, user)) + .orElseThrow(() -> new UsernameNotFoundException("User with email " + login + " was not found in the database")); + } + + String lowercaseLogin = login.toLowerCase(Locale.ENGLISH); + return userRepository + .findOneWithAuthoritiesByLogin(lowercaseLogin) + .map(user -> createSpringSecurityUser(lowercaseLogin, user)) + .orElseThrow(() -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the database")); + } + + private org.springframework.security.core.userdetails.User createSpringSecurityUser(String lowercaseLogin, User user) { + if (!user.isActivated()) { + throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated"); + } + List<GrantedAuthority> grantedAuthorities = user + .getAuthorities() + .stream() + .map(authority -> new SimpleGrantedAuthority(authority.getName())) + .collect(Collectors.toList()); + return new org.springframework.security.core.userdetails.User(user.getLogin(), user.getPassword(), grantedAuthorities); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/security/SecurityUtils.java b/pamapi/src/main/java/com/pollex/pam/security/SecurityUtils.java new file mode 100644 index 0000000..2e170c5 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/security/SecurityUtils.java @@ -0,0 +1,100 @@ +package com.pollex.pam.security; + +import java.util.Arrays; +import java.util.Optional; +import java.util.stream.Stream; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; + +/** + * 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<String> getCurrentUserLogin() { + SecurityContext securityContext = SecurityContextHolder.getContext(); + 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.getPrincipal() instanceof String) { + return (String) authentication.getPrincipal(); + } + return null; + } + + /** + * Get the JWT of the current user. + * + * @return the JWT of the current user. + */ + public static Optional<String> getCurrentUserJWT() { + SecurityContext securityContext = SecurityContextHolder.getContext(); + return Optional + .ofNullable(securityContext.getAuthentication()) + .filter(authentication -> authentication.getCredentials() instanceof String) + .map(authentication -> (String) authentication.getCredentials()); + } + + /** + * 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); + } + + /** + * Checks if the current user has any of the authorities. + * + * @param authorities the authorities to check. + * @return true if the current user has any of the authorities, false otherwise. + */ + public static boolean hasCurrentUserAnyOfAuthorities(String... authorities) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + return ( + authentication != null && getAuthorities(authentication).anyMatch(authority -> Arrays.asList(authorities).contains(authority)) + ); + } + + /** + * Checks if the current user has none of the authorities. + * + * @param authorities the authorities to check. + * @return true if the current user has none of the authorities, false otherwise. + */ + public static boolean hasCurrentUserNoneOfAuthorities(String... authorities) { + return !hasCurrentUserAnyOfAuthorities(authorities); + } + + /** + * 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 hasCurrentUserThisAuthority(String authority) { + return hasCurrentUserAnyOfAuthorities(authority); + } + + private static Stream<String> getAuthorities(Authentication authentication) { + return authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/security/SpringSecurityAuditorAware.java b/pamapi/src/main/java/com/pollex/pam/security/SpringSecurityAuditorAware.java new file mode 100644 index 0000000..bd4efb7 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/security/SpringSecurityAuditorAware.java @@ -0,0 +1,18 @@ +package com.pollex.pam.security; + +import com.pollex.pam.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<String> { + + @Override + public Optional<String> getCurrentAuditor() { + return Optional.of(SecurityUtils.getCurrentUserLogin().orElse(Constants.SYSTEM)); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/security/UserNotActivatedException.java b/pamapi/src/main/java/com/pollex/pam/security/UserNotActivatedException.java new file mode 100644 index 0000000..95b63dd --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/security/UserNotActivatedException.java @@ -0,0 +1,19 @@ +package com.pollex.pam.security; + +import org.springframework.security.core.AuthenticationException; + +/** + * This exception is thrown in case of a not activated user trying to authenticate. + */ +public class UserNotActivatedException extends AuthenticationException { + + private static final long serialVersionUID = 1L; + + public UserNotActivatedException(String message) { + super(message); + } + + public UserNotActivatedException(String message, Throwable t) { + super(message, t); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/security/jwt/JWTConfigurer.java b/pamapi/src/main/java/com/pollex/pam/security/jwt/JWTConfigurer.java new file mode 100644 index 0000000..755955b --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/security/jwt/JWTConfigurer.java @@ -0,0 +1,21 @@ +package com.pollex.pam.security.jwt; + +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.DefaultSecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +public class JWTConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> { + + private final TokenProvider tokenProvider; + + public JWTConfigurer(TokenProvider tokenProvider) { + this.tokenProvider = tokenProvider; + } + + @Override + public void configure(HttpSecurity http) { + JWTFilter customFilter = new JWTFilter(tokenProvider); + http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/security/jwt/JWTFilter.java b/pamapi/src/main/java/com/pollex/pam/security/jwt/JWTFilter.java new file mode 100644 index 0000000..a574659 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/security/jwt/JWTFilter.java @@ -0,0 +1,53 @@ +package com.pollex.pam.security.jwt; + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.GenericFilterBean; + +/** + * Filters incoming requests and installs a Spring Security principal if a header corresponding to a valid user is + * found. + */ +public class JWTFilter extends GenericFilterBean { + + public static final String AUTHORIZATION_HEADER = "Authorization"; + + public static final String AUTHORIZATION_TOKEN = "access_token"; + + private final TokenProvider tokenProvider; + + public JWTFilter(TokenProvider tokenProvider) { + this.tokenProvider = tokenProvider; + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; + String jwt = resolveToken(httpServletRequest); + if (StringUtils.hasText(jwt) && this.tokenProvider.validateToken(jwt)) { + Authentication authentication = this.tokenProvider.getAuthentication(jwt); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + filterChain.doFilter(servletRequest, servletResponse); + } + + private String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader(AUTHORIZATION_HEADER); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); + } + String jwt = request.getParameter(AUTHORIZATION_TOKEN); + if (StringUtils.hasText(jwt)) { + return jwt; + } + return null; + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/security/jwt/TokenProvider.java b/pamapi/src/main/java/com/pollex/pam/security/jwt/TokenProvider.java new file mode 100644 index 0000000..d17fca0 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/security/jwt/TokenProvider.java @@ -0,0 +1,101 @@ +package com.pollex.pam.security.jwt; + +import io.jsonwebtoken.*; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.util.*; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.stereotype.Component; +import org.springframework.util.ObjectUtils; +import tech.jhipster.config.JHipsterProperties; + +@Component +public class TokenProvider { + + private final Logger log = LoggerFactory.getLogger(TokenProvider.class); + + private static final String AUTHORITIES_KEY = "auth"; + + private final Key key; + + private final JwtParser jwtParser; + + private final long tokenValidityInMilliseconds; + + private final long tokenValidityInMillisecondsForRememberMe; + + public TokenProvider(JHipsterProperties jHipsterProperties) { + byte[] keyBytes; + String secret = jHipsterProperties.getSecurity().getAuthentication().getJwt().getBase64Secret(); + if (!ObjectUtils.isEmpty(secret)) { + log.debug("Using a Base64-encoded JWT secret key"); + keyBytes = Decoders.BASE64.decode(secret); + } else { + log.warn( + "Warning: the JWT key used is not Base64-encoded. " + + "We recommend using the `jhipster.security.authentication.jwt.base64-secret` key for optimum security." + ); + secret = jHipsterProperties.getSecurity().getAuthentication().getJwt().getSecret(); + keyBytes = secret.getBytes(StandardCharsets.UTF_8); + } + key = Keys.hmacShaKeyFor(keyBytes); + jwtParser = Jwts.parserBuilder().setSigningKey(key).build(); + this.tokenValidityInMilliseconds = 1000 * jHipsterProperties.getSecurity().getAuthentication().getJwt().getTokenValidityInSeconds(); + this.tokenValidityInMillisecondsForRememberMe = + 1000 * jHipsterProperties.getSecurity().getAuthentication().getJwt().getTokenValidityInSecondsForRememberMe(); + } + + public String createToken(Authentication authentication, boolean rememberMe) { + String authorities = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.joining(",")); + + long now = (new Date()).getTime(); + Date validity; + if (rememberMe) { + validity = new Date(now + this.tokenValidityInMillisecondsForRememberMe); + } else { + validity = new Date(now + this.tokenValidityInMilliseconds); + } + + return Jwts + .builder() + .setSubject(authentication.getName()) + .claim(AUTHORITIES_KEY, authorities) + .signWith(key, SignatureAlgorithm.HS512) + .setExpiration(validity) + .compact(); + } + + public Authentication getAuthentication(String token) { + Claims claims = jwtParser.parseClaimsJws(token).getBody(); + + Collection<? extends GrantedAuthority> authorities = Arrays + .stream(claims.get(AUTHORITIES_KEY).toString().split(",")) + .filter(auth -> !auth.trim().isEmpty()) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + + User principal = new User(claims.getSubject(), "", authorities); + + return new UsernamePasswordAuthenticationToken(principal, token, authorities); + } + + public boolean validateToken(String authToken) { + try { + jwtParser.parseClaimsJws(authToken); + return true; + } catch (JwtException | IllegalArgumentException e) { + log.info("Invalid JWT token."); + log.trace("Invalid JWT token trace.", e); + } + return false; + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/security/package-info.java b/pamapi/src/main/java/com/pollex/pam/security/package-info.java new file mode 100644 index 0000000..abd2a30 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/security/package-info.java @@ -0,0 +1,4 @@ +/** + * Spring Security configuration. + */ +package com.pollex.pam.security; diff --git a/pamapi/src/main/java/com/pollex/pam/service/EmailAlreadyUsedException.java b/pamapi/src/main/java/com/pollex/pam/service/EmailAlreadyUsedException.java new file mode 100644 index 0000000..427dc68 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/service/EmailAlreadyUsedException.java @@ -0,0 +1,10 @@ +package com.pollex.pam.service; + +public class EmailAlreadyUsedException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public EmailAlreadyUsedException() { + super("Email is already in use!"); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/service/InvalidPasswordException.java b/pamapi/src/main/java/com/pollex/pam/service/InvalidPasswordException.java new file mode 100644 index 0000000..24b28e5 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/service/InvalidPasswordException.java @@ -0,0 +1,10 @@ +package com.pollex.pam.service; + +public class InvalidPasswordException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public InvalidPasswordException() { + super("Incorrect password"); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/service/MailService.java b/pamapi/src/main/java/com/pollex/pam/service/MailService.java new file mode 100644 index 0000000..555edd5 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/service/MailService.java @@ -0,0 +1,112 @@ +package com.pollex.pam.service; + +import com.pollex.pam.domain.User; +import java.nio.charset.StandardCharsets; +import java.util.Locale; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.MessageSource; +import org.springframework.mail.MailException; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring5.SpringTemplateEngine; +import tech.jhipster.config.JHipsterProperties; + +/** + * Service for sending emails. + * <p> + * We use the {@link Async} annotation to send emails asynchronously. + */ +@Service +public class MailService { + + private final Logger log = LoggerFactory.getLogger(MailService.class); + + private static final String USER = "user"; + + private static final String BASE_URL = "baseUrl"; + + private final JHipsterProperties jHipsterProperties; + + private final JavaMailSender javaMailSender; + + private final MessageSource messageSource; + + private final SpringTemplateEngine templateEngine; + + public MailService( + JHipsterProperties jHipsterProperties, + JavaMailSender javaMailSender, + MessageSource messageSource, + SpringTemplateEngine templateEngine + ) { + this.jHipsterProperties = jHipsterProperties; + this.javaMailSender = javaMailSender; + this.messageSource = messageSource; + this.templateEngine = templateEngine; + } + + @Async + public void sendEmail(String to, String subject, String content, boolean isMultipart, boolean isHtml) { + log.debug( + "Send email[multipart '{}' and html '{}'] to '{}' with subject '{}' and content={}", + isMultipart, + isHtml, + to, + subject, + content + ); + + // Prepare message using a Spring helper + MimeMessage mimeMessage = javaMailSender.createMimeMessage(); + try { + MimeMessageHelper message = new MimeMessageHelper(mimeMessage, isMultipart, StandardCharsets.UTF_8.name()); + message.setTo(to); + message.setFrom(jHipsterProperties.getMail().getFrom()); + message.setSubject(subject); + message.setText(content, isHtml); + javaMailSender.send(mimeMessage); + log.debug("Sent email to User '{}'", to); + } catch (MailException | MessagingException e) { + log.warn("Email could not be sent to user '{}'", to, e); + } + } + + @Async + public void sendEmailFromTemplate(User user, String templateName, String titleKey) { + if (user.getEmail() == null) { + log.debug("Email doesn't exist for user '{}'", user.getLogin()); + return; + } + Locale locale = Locale.forLanguageTag(user.getLangKey()); + Context context = new Context(locale); + context.setVariable(USER, user); + context.setVariable(BASE_URL, jHipsterProperties.getMail().getBaseUrl()); + String content = templateEngine.process(templateName, context); + String subject = messageSource.getMessage(titleKey, null, locale); + sendEmail(user.getEmail(), subject, content, false, true); + } + + @Async + public void sendActivationEmail(User user) { + log.debug("Sending activation email to '{}'", user.getEmail()); + sendEmailFromTemplate(user, "mail/activationEmail", "email.activation.title"); + } + + @Async + public void sendCreationEmail(User user) { + log.debug("Sending creation email to '{}'", user.getEmail()); + sendEmailFromTemplate(user, "mail/creationEmail", "email.activation.title"); + } + + @Async + public void sendPasswordResetMail(User user) { + log.debug("Sending password reset email to '{}'", user.getEmail()); + sendEmailFromTemplate(user, "mail/passwordResetEmail", "email.reset.title"); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/service/UserService.java b/pamapi/src/main/java/com/pollex/pam/service/UserService.java new file mode 100644 index 0000000..fe9b32b --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/service/UserService.java @@ -0,0 +1,325 @@ +package com.pollex.pam.service; + +import com.pollex.pam.config.Constants; +import com.pollex.pam.domain.Authority; +import com.pollex.pam.domain.User; +import com.pollex.pam.repository.AuthorityRepository; +import com.pollex.pam.repository.UserRepository; +import com.pollex.pam.security.AuthoritiesConstants; +import com.pollex.pam.security.SecurityUtils; +import com.pollex.pam.service.dto.AdminUserDTO; +import com.pollex.pam.service.dto.UserDTO; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cache.CacheManager; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import tech.jhipster.security.RandomUtil; + +/** + * Service class for managing users. + */ +@Service +@Transactional +public class UserService { + + private final Logger log = LoggerFactory.getLogger(UserService.class); + + private final UserRepository userRepository; + + private final PasswordEncoder passwordEncoder; + + private final AuthorityRepository authorityRepository; + + private final CacheManager cacheManager; + + public UserService( + UserRepository userRepository, + PasswordEncoder passwordEncoder, + AuthorityRepository authorityRepository, + CacheManager cacheManager + ) { + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + this.authorityRepository = authorityRepository; + this.cacheManager = cacheManager; + } + + public Optional<User> activateRegistration(String key) { + log.debug("Activating user for activation key {}", key); + return userRepository + .findOneByActivationKey(key) + .map(user -> { + // activate given user for the registration key. + user.setActivated(true); + user.setActivationKey(null); + this.clearUserCaches(user); + log.debug("Activated user: {}", user); + return user; + }); + } + + public Optional<User> completePasswordReset(String newPassword, String key) { + log.debug("Reset user password for reset key {}", key); + return userRepository + .findOneByResetKey(key) + .filter(user -> user.getResetDate().isAfter(Instant.now().minus(1, ChronoUnit.DAYS))) + .map(user -> { + user.setPassword(passwordEncoder.encode(newPassword)); + user.setResetKey(null); + user.setResetDate(null); + this.clearUserCaches(user); + return user; + }); + } + + public Optional<User> requestPasswordReset(String mail) { + return userRepository + .findOneByEmailIgnoreCase(mail) + .filter(User::isActivated) + .map(user -> { + user.setResetKey(RandomUtil.generateResetKey()); + user.setResetDate(Instant.now()); + this.clearUserCaches(user); + return user; + }); + } + + public User registerUser(AdminUserDTO userDTO, String password) { + userRepository + .findOneByLogin(userDTO.getLogin().toLowerCase()) + .ifPresent(existingUser -> { + boolean removed = removeNonActivatedUser(existingUser); + if (!removed) { + throw new UsernameAlreadyUsedException(); + } + }); + userRepository + .findOneByEmailIgnoreCase(userDTO.getEmail()) + .ifPresent(existingUser -> { + boolean removed = removeNonActivatedUser(existingUser); + if (!removed) { + throw new EmailAlreadyUsedException(); + } + }); + User newUser = new User(); + String encryptedPassword = passwordEncoder.encode(password); + newUser.setLogin(userDTO.getLogin().toLowerCase()); + // new user gets initially a generated password + newUser.setPassword(encryptedPassword); + newUser.setFirstName(userDTO.getFirstName()); + newUser.setLastName(userDTO.getLastName()); + if (userDTO.getEmail() != null) { + newUser.setEmail(userDTO.getEmail().toLowerCase()); + } + newUser.setImageUrl(userDTO.getImageUrl()); + newUser.setLangKey(userDTO.getLangKey()); + // new user is not active + newUser.setActivated(false); + // new user gets registration key + newUser.setActivationKey(RandomUtil.generateActivationKey()); + Set<Authority> authorities = new HashSet<>(); + authorityRepository.findById(AuthoritiesConstants.USER).ifPresent(authorities::add); + newUser.setAuthorities(authorities); + userRepository.save(newUser); + this.clearUserCaches(newUser); + log.debug("Created Information for User: {}", newUser); + return newUser; + } + + private boolean removeNonActivatedUser(User existingUser) { + if (existingUser.isActivated()) { + return false; + } + userRepository.delete(existingUser); + userRepository.flush(); + this.clearUserCaches(existingUser); + return true; + } + + public User createUser(AdminUserDTO userDTO) { + User user = new 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()); + if (userDTO.getLangKey() == null) { + user.setLangKey(Constants.DEFAULT_LANGUAGE); // default language + } else { + user.setLangKey(userDTO.getLangKey()); + } + String encryptedPassword = passwordEncoder.encode(RandomUtil.generatePassword()); + user.setPassword(encryptedPassword); + user.setResetKey(RandomUtil.generateResetKey()); + user.setResetDate(Instant.now()); + user.setActivated(true); + if (userDTO.getAuthorities() != null) { + Set<Authority> authorities = userDTO + .getAuthorities() + .stream() + .map(authorityRepository::findById) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toSet()); + user.setAuthorities(authorities); + } + userRepository.save(user); + this.clearUserCaches(user); + log.debug("Created Information for User: {}", user); + return user; + } + + /** + * Update all information for a specific user, and return the modified user. + * + * @param userDTO user to update. + * @return updated user. + */ + public Optional<AdminUserDTO> updateUser(AdminUserDTO userDTO) { + return Optional + .of(userRepository.findById(userDTO.getId())) + .filter(Optional::isPresent) + .map(Optional::get) + .map(user -> { + this.clearUserCaches(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<Authority> managedAuthorities = user.getAuthorities(); + managedAuthorities.clear(); + userDTO + .getAuthorities() + .stream() + .map(authorityRepository::findById) + .filter(Optional::isPresent) + .map(Optional::get) + .forEach(managedAuthorities::add); + this.clearUserCaches(user); + log.debug("Changed Information for User: {}", user); + return user; + }) + .map(AdminUserDTO::new); + } + + public void deleteUser(String login) { + userRepository + .findOneByLogin(login) + .ifPresent(user -> { + userRepository.delete(user); + this.clearUserCaches(user); + log.debug("Deleted User: {}", user); + }); + } + + /** + * 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); + this.clearUserCaches(user); + log.debug("Changed Information for User: {}", user); + }); + } + + @Transactional + public void changePassword(String currentClearTextPassword, String newPassword) { + SecurityUtils + .getCurrentUserLogin() + .flatMap(userRepository::findOneByLogin) + .ifPresent(user -> { + String currentEncryptedPassword = user.getPassword(); + if (!passwordEncoder.matches(currentClearTextPassword, currentEncryptedPassword)) { + throw new InvalidPasswordException(); + } + String encryptedPassword = passwordEncoder.encode(newPassword); + user.setPassword(encryptedPassword); + this.clearUserCaches(user); + log.debug("Changed password for User: {}", user); + }); + } + + @Transactional(readOnly = true) + public Page<AdminUserDTO> getAllManagedUsers(Pageable pageable) { + return userRepository.findAll(pageable).map(AdminUserDTO::new); + } + + @Transactional(readOnly = true) + public Page<UserDTO> getAllPublicUsers(Pageable pageable) { + return userRepository.findAllByIdNotNullAndActivatedIsTrue(pageable).map(UserDTO::new); + } + + @Transactional(readOnly = true) + public Optional<User> getUserWithAuthoritiesByLogin(String login) { + return userRepository.findOneWithAuthoritiesByLogin(login); + } + + @Transactional(readOnly = true) + public Optional<User> getUserWithAuthorities() { + return SecurityUtils.getCurrentUserLogin().flatMap(userRepository::findOneWithAuthoritiesByLogin); + } + + /** + * Not activated users should be automatically deleted after 3 days. + * <p> + * This is scheduled to get fired everyday, at 01:00 (am). + */ + @Scheduled(cron = "0 0 1 * * ?") + public void removeNotActivatedUsers() { + userRepository + .findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(Instant.now().minus(3, ChronoUnit.DAYS)) + .forEach(user -> { + log.debug("Deleting not activated user {}", user.getLogin()); + userRepository.delete(user); + this.clearUserCaches(user); + }); + } + + /** + * Gets a list of all the authorities. + * @return a list of all the authorities. + */ + @Transactional(readOnly = true) + public List<String> getAuthorities() { + return authorityRepository.findAll().stream().map(Authority::getName).collect(Collectors.toList()); + } + + private void clearUserCaches(User user) { + Objects.requireNonNull(cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE)).evict(user.getLogin()); + if (user.getEmail() != null) { + Objects.requireNonNull(cacheManager.getCache(UserRepository.USERS_BY_EMAIL_CACHE)).evict(user.getEmail()); + } + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/service/UsernameAlreadyUsedException.java b/pamapi/src/main/java/com/pollex/pam/service/UsernameAlreadyUsedException.java new file mode 100644 index 0000000..3d011f8 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/service/UsernameAlreadyUsedException.java @@ -0,0 +1,10 @@ +package com.pollex.pam.service; + +public class UsernameAlreadyUsedException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public UsernameAlreadyUsedException() { + super("Login name already used!"); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/service/dto/AdminUserDTO.java b/pamapi/src/main/java/com/pollex/pam/service/dto/AdminUserDTO.java new file mode 100644 index 0000000..bcdafec --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/service/dto/AdminUserDTO.java @@ -0,0 +1,193 @@ +package com.pollex.pam.service.dto; + +import com.pollex.pam.config.Constants; +import com.pollex.pam.domain.Authority; +import com.pollex.pam.domain.User; +import java.time.Instant; +import java.util.Set; +import java.util.stream.Collectors; +import javax.validation.constraints.*; + +/** + * A DTO representing a user, with his authorities. + */ +public class AdminUserDTO { + + private Long 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<String> 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 Long getId() { + return id; + } + + public void setId(Long 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<String> getAuthorities() { + return authorities; + } + + public void setAuthorities(Set<String> 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/pamapi/src/main/java/com/pollex/pam/service/dto/PasswordChangeDTO.java b/pamapi/src/main/java/com/pollex/pam/service/dto/PasswordChangeDTO.java new file mode 100644 index 0000000..0ad177f --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/service/dto/PasswordChangeDTO.java @@ -0,0 +1,35 @@ +package com.pollex.pam.service.dto; + +/** + * A DTO representing a password change required data - current and new password. + */ +public class PasswordChangeDTO { + + private String currentPassword; + private String newPassword; + + public PasswordChangeDTO() { + // Empty constructor needed for Jackson. + } + + public PasswordChangeDTO(String currentPassword, String newPassword) { + this.currentPassword = currentPassword; + this.newPassword = newPassword; + } + + public String getCurrentPassword() { + return currentPassword; + } + + public void setCurrentPassword(String currentPassword) { + this.currentPassword = currentPassword; + } + + public String getNewPassword() { + return newPassword; + } + + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/service/dto/UserDTO.java b/pamapi/src/main/java/com/pollex/pam/service/dto/UserDTO.java new file mode 100644 index 0000000..dfcfacc --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/service/dto/UserDTO.java @@ -0,0 +1,48 @@ +package com.pollex.pam.service.dto; + +import com.pollex.pam.domain.User; + +/** + * A DTO representing a user, with only the public attributes. + */ +public class UserDTO { + + private Long id; + + private String login; + + 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(); + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + // prettier-ignore + @Override + public String toString() { + return "UserDTO{" + + "id='" + id + '\'' + + ", login='" + login + '\'' + + "}"; + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/service/dto/package-info.java b/pamapi/src/main/java/com/pollex/pam/service/dto/package-info.java new file mode 100644 index 0000000..44834d3 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/service/dto/package-info.java @@ -0,0 +1,4 @@ +/** + * Data Transfer Objects. + */ +package com.pollex.pam.service.dto; diff --git a/pamapi/src/main/java/com/pollex/pam/service/mapper/UserMapper.java b/pamapi/src/main/java/com/pollex/pam/service/mapper/UserMapper.java new file mode 100644 index 0000000..a5ccf3f --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/service/mapper/UserMapper.java @@ -0,0 +1,147 @@ +package com.pollex.pam.service.mapper; + +import com.pollex.pam.domain.Authority; +import com.pollex.pam.domain.User; +import com.pollex.pam.service.dto.AdminUserDTO; +import com.pollex.pam.service.dto.UserDTO; +import java.util.*; +import java.util.stream.Collectors; +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.springframework.stereotype.Service; + +/** + * 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<UserDTO> usersToUserDTOs(List<User> users) { + return users.stream().filter(Objects::nonNull).map(this::userToUserDTO).collect(Collectors.toList()); + } + + public UserDTO userToUserDTO(User user) { + return new UserDTO(user); + } + + public List<AdminUserDTO> usersToAdminUserDTOs(List<User> users) { + return users.stream().filter(Objects::nonNull).map(this::userToAdminUserDTO).collect(Collectors.toList()); + } + + public AdminUserDTO userToAdminUserDTO(User user) { + return new AdminUserDTO(user); + } + + public List<User> userDTOsToUsers(List<AdminUserDTO> userDTOs) { + return userDTOs.stream().filter(Objects::nonNull).map(this::userDTOToUser).collect(Collectors.toList()); + } + + public User userDTOToUser(AdminUserDTO 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<Authority> authorities = this.authoritiesFromStrings(userDTO.getAuthorities()); + user.setAuthorities(authorities); + return user; + } + } + + private Set<Authority> authoritiesFromStrings(Set<String> authoritiesAsString) { + Set<Authority> 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(Long id) { + if (id == null) { + return null; + } + User user = new User(); + 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<UserDTO> toDtoIdSet(Set<User> users) { + if (users == null) { + return Collections.emptySet(); + } + + Set<UserDTO> 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<UserDTO> toDtoLoginSet(Set<User> users) { + if (users == null) { + return Collections.emptySet(); + } + + Set<UserDTO> userSet = new HashSet<>(); + for (User userEntity : users) { + userSet.add(this.toDtoLogin(userEntity)); + } + + return userSet; + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/service/mapper/package-info.java b/pamapi/src/main/java/com/pollex/pam/service/mapper/package-info.java new file mode 100644 index 0000000..b91e8ed --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/service/mapper/package-info.java @@ -0,0 +1,4 @@ +/** + * MapStruct mappers for mapping domain objects and Data Transfer Objects. + */ +package com.pollex.pam.service.mapper; diff --git a/pamapi/src/main/java/com/pollex/pam/service/package-info.java b/pamapi/src/main/java/com/pollex/pam/service/package-info.java new file mode 100644 index 0000000..ee1f6cf --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/service/package-info.java @@ -0,0 +1,4 @@ +/** + * Service layer beans. + */ +package com.pollex.pam.service; diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/AccountResource.java b/pamapi/src/main/java/com/pollex/pam/web/rest/AccountResource.java new file mode 100644 index 0000000..fbb1b24 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/web/rest/AccountResource.java @@ -0,0 +1,194 @@ +package com.pollex.pam.web.rest; + +import com.pollex.pam.domain.User; +import com.pollex.pam.repository.UserRepository; +import com.pollex.pam.security.SecurityUtils; +import com.pollex.pam.service.MailService; +import com.pollex.pam.service.UserService; +import com.pollex.pam.service.dto.AdminUserDTO; +import com.pollex.pam.service.dto.PasswordChangeDTO; +import com.pollex.pam.web.rest.errors.*; +import com.pollex.pam.web.rest.vm.KeyAndPasswordVM; +import com.pollex.pam.web.rest.vm.ManagedUserVM; +import java.util.*; +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +/** + * REST controller for managing the current user's account. + */ +@RestController +@RequestMapping("/api") +public class AccountResource { + + private static class AccountResourceException extends RuntimeException { + + private AccountResourceException(String message) { + super(message); + } + } + + private final Logger log = LoggerFactory.getLogger(AccountResource.class); + + private final UserRepository userRepository; + + private final UserService userService; + + private final MailService mailService; + + public AccountResource(UserRepository userRepository, UserService userService, MailService mailService) { + this.userRepository = userRepository; + this.userService = userService; + this.mailService = mailService; + } + + /** + * {@code POST /register} : register the user. + * + * @param managedUserVM the managed user View Model. + * @throws InvalidPasswordException {@code 400 (Bad Request)} if the password is incorrect. + * @throws EmailAlreadyUsedException {@code 400 (Bad Request)} if the email is already used. + * @throws LoginAlreadyUsedException {@code 400 (Bad Request)} if the login is already used. + */ + @PostMapping("/register") + @ResponseStatus(HttpStatus.CREATED) + public void registerAccount(@Valid @RequestBody ManagedUserVM managedUserVM) { + if (isPasswordLengthInvalid(managedUserVM.getPassword())) { + throw new InvalidPasswordException(); + } + User user = userService.registerUser(managedUserVM, managedUserVM.getPassword()); + mailService.sendActivationEmail(user); + } + + /** + * {@code GET /activate} : activate the registered user. + * + * @param key the activation key. + * @throws RuntimeException {@code 500 (Internal Server Error)} if the user couldn't be activated. + */ + @GetMapping("/activate") + public void activateAccount(@RequestParam(value = "key") String key) { + Optional<User> user = userService.activateRegistration(key); + if (!user.isPresent()) { + throw new AccountResourceException("No user was found for this activation key"); + } + } + + /** + * {@code GET /authenticate} : check if the user is authenticated, and return its login. + * + * @param request the HTTP request. + * @return the login if the user is authenticated. + */ + @GetMapping("/authenticate") + public String isAuthenticated(HttpServletRequest request) { + log.debug("REST request to check if the current user is authenticated"); + return request.getRemoteUser(); + } + + /** + * {@code GET /account} : get the current user. + * + * @return the current user. + * @throws RuntimeException {@code 500 (Internal Server Error)} if the user couldn't be returned. + */ + @GetMapping("/account") + public AdminUserDTO getAccount() { + return userService + .getUserWithAuthorities() + .map(AdminUserDTO::new) + .orElseThrow(() -> new AccountResourceException("User could not be found")); + } + + /** + * {@code POST /account} : update the current user information. + * + * @param userDTO the current user information. + * @throws EmailAlreadyUsedException {@code 400 (Bad Request)} if the email is already used. + * @throws RuntimeException {@code 500 (Internal Server Error)} if the user login wasn't found. + */ + @PostMapping("/account") + public void saveAccount(@Valid @RequestBody AdminUserDTO userDTO) { + String userLogin = SecurityUtils + .getCurrentUserLogin() + .orElseThrow(() -> new AccountResourceException("Current user login not found")); + Optional<User> existingUser = userRepository.findOneByEmailIgnoreCase(userDTO.getEmail()); + if (existingUser.isPresent() && (!existingUser.get().getLogin().equalsIgnoreCase(userLogin))) { + throw new EmailAlreadyUsedException(); + } + Optional<User> user = userRepository.findOneByLogin(userLogin); + if (!user.isPresent()) { + throw new AccountResourceException("User could not be found"); + } + userService.updateUser( + userDTO.getFirstName(), + userDTO.getLastName(), + userDTO.getEmail(), + userDTO.getLangKey(), + userDTO.getImageUrl() + ); + } + + /** + * {@code POST /account/change-password} : changes the current user's password. + * + * @param passwordChangeDto current and new password. + * @throws InvalidPasswordException {@code 400 (Bad Request)} if the new password is incorrect. + */ + @PostMapping(path = "/account/change-password") + public void changePassword(@RequestBody PasswordChangeDTO passwordChangeDto) { + if (isPasswordLengthInvalid(passwordChangeDto.getNewPassword())) { + throw new InvalidPasswordException(); + } + userService.changePassword(passwordChangeDto.getCurrentPassword(), passwordChangeDto.getNewPassword()); + } + + /** + * {@code POST /account/reset-password/init} : Send an email to reset the password of the user. + * + * @param mail the mail of the user. + */ + @PostMapping(path = "/account/reset-password/init") + public void requestPasswordReset(@RequestBody String mail) { + Optional<User> user = userService.requestPasswordReset(mail); + if (user.isPresent()) { + mailService.sendPasswordResetMail(user.get()); + } else { + // Pretend the request has been successful to prevent checking which emails really exist + // but log that an invalid attempt has been made + log.warn("Password reset requested for non existing mail"); + } + } + + /** + * {@code POST /account/reset-password/finish} : Finish to reset the password of the user. + * + * @param keyAndPassword the generated key and the new password. + * @throws InvalidPasswordException {@code 400 (Bad Request)} if the password is incorrect. + * @throws RuntimeException {@code 500 (Internal Server Error)} if the password could not be reset. + */ + @PostMapping(path = "/account/reset-password/finish") + public void finishPasswordReset(@RequestBody KeyAndPasswordVM keyAndPassword) { + if (isPasswordLengthInvalid(keyAndPassword.getNewPassword())) { + throw new InvalidPasswordException(); + } + Optional<User> user = userService.completePasswordReset(keyAndPassword.getNewPassword(), keyAndPassword.getKey()); + + if (!user.isPresent()) { + throw new AccountResourceException("No user was found for this reset key"); + } + } + + private static boolean isPasswordLengthInvalid(String password) { + return ( + StringUtils.isEmpty(password) || + password.length() < ManagedUserVM.PASSWORD_MIN_LENGTH || + password.length() > ManagedUserVM.PASSWORD_MAX_LENGTH + ); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/PublicUserResource.java b/pamapi/src/main/java/com/pollex/pam/web/rest/PublicUserResource.java new file mode 100644 index 0000000..2b07888 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/web/rest/PublicUserResource.java @@ -0,0 +1,65 @@ +package com.pollex.pam.web.rest; + +import com.pollex.pam.service.UserService; +import com.pollex.pam.service.dto.UserDTO; +import java.util.*; +import java.util.Collections; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +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 static final List<String> ALLOWED_ORDERED_PROPERTIES = Collections.unmodifiableList( + Arrays.asList("id", "login", "firstName", "lastName", "email", "activated", "langKey") + ); + + 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<List<UserDTO>> getAllPublicUsers(Pageable pageable) { + log.debug("REST request to get all public User names"); + if (!onlyContainsAllowedProperties(pageable)) { + return ResponseEntity.badRequest().build(); + } + + final Page<UserDTO> page = userService.getAllPublicUsers(pageable); + HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page); + return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK); + } + + private boolean onlyContainsAllowedProperties(Pageable pageable) { + return pageable.getSort().stream().map(Sort.Order::getProperty).allMatch(ALLOWED_ORDERED_PROPERTIES::contains); + } + + /** + * Gets a list of all roles. + * @return a string list of all roles. + */ + @GetMapping("/authorities") + public List<String> getAuthorities() { + return userService.getAuthorities(); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/UserJWTController.java b/pamapi/src/main/java/com/pollex/pam/web/rest/UserJWTController.java new file mode 100644 index 0000000..4848e08 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/web/rest/UserJWTController.java @@ -0,0 +1,68 @@ +package com.pollex.pam.web.rest; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.pollex.pam.security.jwt.JWTFilter; +import com.pollex.pam.security.jwt.TokenProvider; +import com.pollex.pam.web.rest.vm.LoginVM; +import javax.validation.Valid; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.*; + +/** + * Controller to authenticate users. + */ +@RestController +@RequestMapping("/api") +public class UserJWTController { + + private final TokenProvider tokenProvider; + + private final AuthenticationManagerBuilder authenticationManagerBuilder; + + public UserJWTController(TokenProvider tokenProvider, AuthenticationManagerBuilder authenticationManagerBuilder) { + this.tokenProvider = tokenProvider; + this.authenticationManagerBuilder = authenticationManagerBuilder; + } + + @PostMapping("/authenticate") + public ResponseEntity<JWTToken> authorize(@Valid @RequestBody LoginVM loginVM) { + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( + loginVM.getUsername(), + loginVM.getPassword() + ); + + Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); + SecurityContextHolder.getContext().setAuthentication(authentication); + String jwt = tokenProvider.createToken(authentication, loginVM.isRememberMe()); + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer " + jwt); + return new ResponseEntity<>(new JWTToken(jwt), httpHeaders, HttpStatus.OK); + } + + /** + * Object to return as body in JWT Authentication. + */ + static class JWTToken { + + private String idToken; + + JWTToken(String idToken) { + this.idToken = idToken; + } + + @JsonProperty("id_token") + String getIdToken() { + return idToken; + } + + void setIdToken(String idToken) { + this.idToken = idToken; + } + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/UserResource.java b/pamapi/src/main/java/com/pollex/pam/web/rest/UserResource.java new file mode 100644 index 0000000..f7b7e17 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/web/rest/UserResource.java @@ -0,0 +1,207 @@ +package com.pollex.pam.web.rest; + +import com.pollex.pam.config.Constants; +import com.pollex.pam.domain.User; +import com.pollex.pam.repository.UserRepository; +import com.pollex.pam.security.AuthoritiesConstants; +import com.pollex.pam.service.MailService; +import com.pollex.pam.service.UserService; +import com.pollex.pam.service.dto.AdminUserDTO; +import com.pollex.pam.web.rest.errors.BadRequestAlertException; +import com.pollex.pam.web.rest.errors.EmailAlreadyUsedException; +import com.pollex.pam.web.rest.errors.LoginAlreadyUsedException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import java.util.Collections; +import javax.validation.Valid; +import javax.validation.constraints.Pattern; +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.data.domain.Sort; +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 tech.jhipster.web.util.HeaderUtil; +import tech.jhipster.web.util.PaginationUtil; +import tech.jhipster.web.util.ResponseUtil; + +/** + * REST controller for managing users. + * <p> + * This class accesses the {@link User} entity, and needs to fetch its collection of authorities. + * <p> + * 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. + * <p> + * We use a View Model and a DTO for 3 reasons: + * <ul> + * <li>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.</li> + * <li> 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).</li> + * <li> As this manages users, for security reasons, we'd rather have a DTO layer.</li> + * </ul> + * <p> + * Another option would be to have a specific JPA entity graph to handle this case. + */ +@RestController +@RequestMapping("/api/admin") +public class UserResource { + + private static final List<String> ALLOWED_ORDERED_PROPERTIES = Collections.unmodifiableList( + Arrays.asList( + "id", + "login", + "firstName", + "lastName", + "email", + "activated", + "langKey", + "createdBy", + "createdDate", + "lastModifiedBy", + "lastModifiedDate" + ) + ); + + private final Logger log = LoggerFactory.getLogger(UserResource.class); + + @Value("${jhipster.clientApp.name}") + private String applicationName; + + private final UserService userService; + + private final UserRepository userRepository; + + private final MailService mailService; + + public UserResource(UserService userService, UserRepository userRepository, MailService mailService) { + this.userService = userService; + this.userRepository = userRepository; + this.mailService = mailService; + } + + /** + * {@code POST /admin/users} : Creates a new user. + * <p> + * Creates a new user if the login and email are not already used, and sends an + * mail with an activation link. + * The user needs to be activated on creation. + * + * @param userDTO the user to create. + * @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new user, or with status {@code 400 (Bad Request)} if the login or email is already in use. + * @throws URISyntaxException if the Location URI syntax is incorrect. + * @throws BadRequestAlertException {@code 400 (Bad Request)} if the login or email is already in use. + */ + @PostMapping("/users") + @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") + public ResponseEntity<User> createUser(@Valid @RequestBody AdminUserDTO userDTO) throws URISyntaxException { + log.debug("REST request to save User : {}", userDTO); + + if (userDTO.getId() != null) { + throw new BadRequestAlertException("A new user cannot already have an ID", "userManagement", "idexists"); + // Lowercase the user login before comparing with database + } else if (userRepository.findOneByLogin(userDTO.getLogin().toLowerCase()).isPresent()) { + throw new LoginAlreadyUsedException(); + } else if (userRepository.findOneByEmailIgnoreCase(userDTO.getEmail()).isPresent()) { + throw new EmailAlreadyUsedException(); + } else { + User newUser = userService.createUser(userDTO); + mailService.sendCreationEmail(newUser); + return ResponseEntity + .created(new URI("/api/admin/users/" + newUser.getLogin())) + .headers(HeaderUtil.createAlert(applicationName, "userManagement.created", newUser.getLogin())) + .body(newUser); + } + } + + /** + * {@code PUT /admin/users} : Updates an existing User. + * + * @param userDTO the user to update. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated user. + * @throws EmailAlreadyUsedException {@code 400 (Bad Request)} if the email is already in use. + * @throws LoginAlreadyUsedException {@code 400 (Bad Request)} if the login is already in use. + */ + @PutMapping("/users") + @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") + public ResponseEntity<AdminUserDTO> updateUser(@Valid @RequestBody AdminUserDTO userDTO) { + log.debug("REST request to update User : {}", userDTO); + Optional<User> existingUser = userRepository.findOneByEmailIgnoreCase(userDTO.getEmail()); + if (existingUser.isPresent() && (!existingUser.get().getId().equals(userDTO.getId()))) { + throw new EmailAlreadyUsedException(); + } + existingUser = userRepository.findOneByLogin(userDTO.getLogin().toLowerCase()); + if (existingUser.isPresent() && (!existingUser.get().getId().equals(userDTO.getId()))) { + throw new LoginAlreadyUsedException(); + } + Optional<AdminUserDTO> updatedUser = userService.updateUser(userDTO); + + return ResponseUtil.wrapOrNotFound( + updatedUser, + HeaderUtil.createAlert(applicationName, "userManagement.updated", userDTO.getLogin()) + ); + } + + /** + * {@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") + @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") + public ResponseEntity<List<AdminUserDTO>> getAllUsers(Pageable pageable) { + log.debug("REST request to get all User for an admin"); + if (!onlyContainsAllowedProperties(pageable)) { + return ResponseEntity.badRequest().build(); + } + + final Page<AdminUserDTO> page = userService.getAllManagedUsers(pageable); + HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page); + return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK); + } + + private boolean onlyContainsAllowedProperties(Pageable pageable) { + return pageable.getSort().stream().map(Sort.Order::getProperty).allMatch(ALLOWED_ORDERED_PROPERTIES::contains); + } + + /** + * {@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}") + @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") + public ResponseEntity<AdminUserDTO> getUser(@PathVariable @Pattern(regexp = Constants.LOGIN_REGEX) String login) { + log.debug("REST request to get User : {}", login); + return ResponseUtil.wrapOrNotFound(userService.getUserWithAuthoritiesByLogin(login).map(AdminUserDTO::new)); + } + + /** + * {@code DELETE /admin/users/:login} : delete the "login" User. + * + * @param login the login of the user to delete. + * @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}. + */ + @DeleteMapping("/users/{login}") + @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") + public ResponseEntity<Void> deleteUser(@PathVariable @Pattern(regexp = Constants.LOGIN_REGEX) String login) { + log.debug("REST request to delete User: {}", login); + userService.deleteUser(login); + return ResponseEntity.noContent().headers(HeaderUtil.createAlert(applicationName, "userManagement.deleted", login)).build(); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/errors/BadRequestAlertException.java b/pamapi/src/main/java/com/pollex/pam/web/rest/errors/BadRequestAlertException.java new file mode 100644 index 0000000..5818e55 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/web/rest/errors/BadRequestAlertException.java @@ -0,0 +1,41 @@ +package com.pollex.pam.web.rest.errors; + +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 { + + 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<String, Object> getAlertParameters(String entityName, String errorKey) { + Map<String, Object> parameters = new HashMap<>(); + parameters.put("message", "error." + errorKey); + parameters.put("params", entityName); + return parameters; + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/errors/EmailAlreadyUsedException.java b/pamapi/src/main/java/com/pollex/pam/web/rest/errors/EmailAlreadyUsedException.java new file mode 100644 index 0000000..8d05d20 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/web/rest/errors/EmailAlreadyUsedException.java @@ -0,0 +1,10 @@ +package com.pollex.pam.web.rest.errors; + +public class EmailAlreadyUsedException extends BadRequestAlertException { + + private static final long serialVersionUID = 1L; + + public EmailAlreadyUsedException() { + super(ErrorConstants.EMAIL_ALREADY_USED_TYPE, "Email is already in use!", "userManagement", "emailexists"); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/errors/ErrorConstants.java b/pamapi/src/main/java/com/pollex/pam/web/rest/errors/ErrorConstants.java new file mode 100644 index 0000000..49e421c --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/web/rest/errors/ErrorConstants.java @@ -0,0 +1,17 @@ +package com.pollex.pam.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"); + public static final URI INVALID_PASSWORD_TYPE = URI.create(PROBLEM_BASE_URL + "/invalid-password"); + public static final URI EMAIL_ALREADY_USED_TYPE = URI.create(PROBLEM_BASE_URL + "/email-already-used"); + public static final URI LOGIN_ALREADY_USED_TYPE = URI.create(PROBLEM_BASE_URL + "/login-already-used"); + + private ErrorConstants() {} +} diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/errors/ExceptionTranslator.java b/pamapi/src/main/java/com/pollex/pam/web/rest/errors/ExceptionTranslator.java new file mode 100644 index 0000000..e59e428 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/web/rest/errors/ExceptionTranslator.java @@ -0,0 +1,222 @@ +package com.pollex.pam.web.rest.errors; + +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; +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.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 tech.jhipster.config.JHipsterConstants; +import tech.jhipster.web.util.HeaderUtil; + +/** + * 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; + + 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<Problem> process(@Nullable ResponseEntity<Problem> entity, NativeWebRequest request) { + if (entity == null) { + return null; + } + Problem problem = entity.getBody(); + if (!(problem instanceof ConstraintViolationProblem || problem instanceof DefaultProblem)) { + return entity; + } + + 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, 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()); + 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<Problem> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, @Nonnull NativeWebRequest request) { + BindingResult result = ex.getBindingResult(); + List<FieldErrorVM> 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() + .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<Problem> handleEmailAlreadyUsedException( + com.pollex.pam.service.EmailAlreadyUsedException ex, + NativeWebRequest request + ) { + EmailAlreadyUsedException problem = new EmailAlreadyUsedException(); + return create( + problem, + request, + HeaderUtil.createFailureAlert(applicationName, true, problem.getEntityName(), problem.getErrorKey(), problem.getMessage()) + ); + } + + @ExceptionHandler + public ResponseEntity<Problem> handleUsernameAlreadyUsedException( + com.pollex.pam.service.UsernameAlreadyUsedException ex, + NativeWebRequest request + ) { + LoginAlreadyUsedException problem = new LoginAlreadyUsedException(); + return create( + problem, + request, + HeaderUtil.createFailureAlert(applicationName, true, problem.getEntityName(), problem.getErrorKey(), problem.getMessage()) + ); + } + + @ExceptionHandler + public ResponseEntity<Problem> handleInvalidPasswordException( + com.pollex.pam.service.InvalidPasswordException ex, + NativeWebRequest request + ) { + return create(new InvalidPasswordException(), request); + } + + @ExceptionHandler + public ResponseEntity<Problem> handleBadRequestAlertException(BadRequestAlertException ex, NativeWebRequest request) { + return create( + ex, + request, + HeaderUtil.createFailureAlert(applicationName, true, ex.getEntityName(), ex.getErrorKey(), ex.getMessage()) + ); + } + + @ExceptionHandler + public ResponseEntity<Problem> 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); + } + + @Override + public ProblemBuilder prepare(final Throwable throwable, final StatusType status, final URI type) { + Collection<String> 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.", "com.pollex.pam"); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/errors/FieldErrorVM.java b/pamapi/src/main/java/com/pollex/pam/web/rest/errors/FieldErrorVM.java new file mode 100644 index 0000000..4028995 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/web/rest/errors/FieldErrorVM.java @@ -0,0 +1,32 @@ +package com.pollex.pam.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/pamapi/src/main/java/com/pollex/pam/web/rest/errors/InvalidPasswordException.java b/pamapi/src/main/java/com/pollex/pam/web/rest/errors/InvalidPasswordException.java new file mode 100644 index 0000000..dd3812f --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/web/rest/errors/InvalidPasswordException.java @@ -0,0 +1,13 @@ +package com.pollex.pam.web.rest.errors; + +import org.zalando.problem.AbstractThrowableProblem; +import org.zalando.problem.Status; + +public class InvalidPasswordException extends AbstractThrowableProblem { + + private static final long serialVersionUID = 1L; + + public InvalidPasswordException() { + super(ErrorConstants.INVALID_PASSWORD_TYPE, "Incorrect password", Status.BAD_REQUEST); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/errors/LoginAlreadyUsedException.java b/pamapi/src/main/java/com/pollex/pam/web/rest/errors/LoginAlreadyUsedException.java new file mode 100644 index 0000000..aab2f9a --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/web/rest/errors/LoginAlreadyUsedException.java @@ -0,0 +1,10 @@ +package com.pollex.pam.web.rest.errors; + +public class LoginAlreadyUsedException extends BadRequestAlertException { + + private static final long serialVersionUID = 1L; + + public LoginAlreadyUsedException() { + super(ErrorConstants.LOGIN_ALREADY_USED_TYPE, "Login name already used!", "userManagement", "userexists"); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/errors/package-info.java b/pamapi/src/main/java/com/pollex/pam/web/rest/errors/package-info.java new file mode 100644 index 0000000..b010ea3 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/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 com.pollex.pam.web.rest.errors; diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/package-info.java b/pamapi/src/main/java/com/pollex/pam/web/rest/package-info.java new file mode 100644 index 0000000..4380b97 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/web/rest/package-info.java @@ -0,0 +1,4 @@ +/** + * Spring MVC REST controllers. + */ +package com.pollex.pam.web.rest; diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/vm/KeyAndPasswordVM.java b/pamapi/src/main/java/com/pollex/pam/web/rest/vm/KeyAndPasswordVM.java new file mode 100644 index 0000000..7bb8309 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/web/rest/vm/KeyAndPasswordVM.java @@ -0,0 +1,27 @@ +package com.pollex.pam.web.rest.vm; + +/** + * View Model object for storing the user's key and password. + */ +public class KeyAndPasswordVM { + + private String key; + + private String newPassword; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getNewPassword() { + return newPassword; + } + + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/vm/LoginVM.java b/pamapi/src/main/java/com/pollex/pam/web/rest/vm/LoginVM.java new file mode 100644 index 0000000..97efc36 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/web/rest/vm/LoginVM.java @@ -0,0 +1,53 @@ +package com.pollex.pam.web.rest.vm; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +/** + * View Model object for storing a user's credentials. + */ +public class LoginVM { + + @NotNull + @Size(min = 1, max = 50) + private String username; + + @NotNull + @Size(min = 4, max = 100) + private String password; + + private boolean rememberMe; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public boolean isRememberMe() { + return rememberMe; + } + + public void setRememberMe(boolean rememberMe) { + this.rememberMe = rememberMe; + } + + // prettier-ignore + @Override + public String toString() { + return "LoginVM{" + + "username='" + username + '\'' + + ", rememberMe=" + rememberMe + + '}'; + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/vm/ManagedUserVM.java b/pamapi/src/main/java/com/pollex/pam/web/rest/vm/ManagedUserVM.java new file mode 100644 index 0000000..74605b5 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/web/rest/vm/ManagedUserVM.java @@ -0,0 +1,35 @@ +package com.pollex.pam.web.rest.vm; + +import com.pollex.pam.service.dto.AdminUserDTO; +import javax.validation.constraints.Size; + +/** + * View Model extending the AdminUserDTO, which is meant to be used in the user management UI. + */ +public class ManagedUserVM extends AdminUserDTO { + + public static final int PASSWORD_MIN_LENGTH = 4; + + public static final int PASSWORD_MAX_LENGTH = 100; + + @Size(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH) + private String password; + + public ManagedUserVM() { + // Empty constructor needed for Jackson. + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + // prettier-ignore + @Override + public String toString() { + return "ManagedUserVM{" + super.toString() + "} "; + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/vm/package-info.java b/pamapi/src/main/java/com/pollex/pam/web/rest/vm/package-info.java new file mode 100644 index 0000000..2a5048e --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/web/rest/vm/package-info.java @@ -0,0 +1,4 @@ +/** + * View Models used by Spring MVC REST controllers. + */ +package com.pollex.pam.web.rest.vm; diff --git a/pamapi/src/main/java/com/pollex/pam/web/websocket/ActivityService.java b/pamapi/src/main/java/com/pollex/pam/web/websocket/ActivityService.java new file mode 100644 index 0000000..9e3b480 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/web/websocket/ActivityService.java @@ -0,0 +1,46 @@ +package com.pollex.pam.web.websocket; + +import static com.pollex.pam.config.WebsocketConfiguration.IP_ADDRESS; + +import com.pollex.pam.web.websocket.dto.ActivityDTO; +import java.security.Principal; +import java.time.Instant; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationListener; +import org.springframework.messaging.handler.annotation.*; +import org.springframework.messaging.simp.SimpMessageSendingOperations; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.stereotype.Controller; +import org.springframework.web.socket.messaging.SessionDisconnectEvent; + +@Controller +public class ActivityService implements ApplicationListener<SessionDisconnectEvent> { + + private static final Logger log = LoggerFactory.getLogger(ActivityService.class); + + private final SimpMessageSendingOperations messagingTemplate; + + public ActivityService(SimpMessageSendingOperations messagingTemplate) { + this.messagingTemplate = messagingTemplate; + } + + @MessageMapping("/topic/activity") + @SendTo("/topic/tracker") + public ActivityDTO sendActivity(@Payload ActivityDTO activityDTO, StompHeaderAccessor stompHeaderAccessor, Principal principal) { + activityDTO.setUserLogin(principal.getName()); + activityDTO.setSessionId(stompHeaderAccessor.getSessionId()); + activityDTO.setIpAddress(stompHeaderAccessor.getSessionAttributes().get(IP_ADDRESS).toString()); + activityDTO.setTime(Instant.now()); + log.debug("Sending user tracking data {}", activityDTO); + return activityDTO; + } + + @Override + public void onApplicationEvent(SessionDisconnectEvent event) { + ActivityDTO activityDTO = new ActivityDTO(); + activityDTO.setSessionId(event.getSessionId()); + activityDTO.setPage("logout"); + messagingTemplate.convertAndSend("/topic/tracker", activityDTO); + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/web/websocket/dto/ActivityDTO.java b/pamapi/src/main/java/com/pollex/pam/web/websocket/dto/ActivityDTO.java new file mode 100644 index 0000000..c936ac0 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/web/websocket/dto/ActivityDTO.java @@ -0,0 +1,71 @@ +package com.pollex.pam.web.websocket.dto; + +import java.time.Instant; + +/** + * DTO for storing a user's activity. + */ +public class ActivityDTO { + + private String sessionId; + + private String userLogin; + + private String ipAddress; + + private String page; + + private Instant time; + + public String getSessionId() { + return sessionId; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + + public String getUserLogin() { + return userLogin; + } + + public void setUserLogin(String userLogin) { + this.userLogin = userLogin; + } + + public String getIpAddress() { + return ipAddress; + } + + public void setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + } + + public String getPage() { + return page; + } + + public void setPage(String page) { + this.page = page; + } + + public Instant getTime() { + return time; + } + + public void setTime(Instant time) { + this.time = time; + } + + // prettier-ignore + @Override + public String toString() { + return "ActivityDTO{" + + "sessionId='" + sessionId + '\'' + + ", userLogin='" + userLogin + '\'' + + ", ipAddress='" + ipAddress + '\'' + + ", page='" + page + '\'' + + ", time='" + time + '\'' + + '}'; + } +} diff --git a/pamapi/src/main/java/com/pollex/pam/web/websocket/dto/package-info.java b/pamapi/src/main/java/com/pollex/pam/web/websocket/dto/package-info.java new file mode 100644 index 0000000..e8658b4 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/web/websocket/dto/package-info.java @@ -0,0 +1,4 @@ +/** + * Data Access Objects used by WebSocket services. + */ +package com.pollex.pam.web.websocket.dto; diff --git a/pamapi/src/main/java/com/pollex/pam/web/websocket/package-info.java b/pamapi/src/main/java/com/pollex/pam/web/websocket/package-info.java new file mode 100644 index 0000000..0163b20 --- /dev/null +++ b/pamapi/src/main/java/com/pollex/pam/web/websocket/package-info.java @@ -0,0 +1,4 @@ +/** + * WebSocket services, using Spring Websocket. + */ +package com.pollex.pam.web.websocket; diff --git a/pamapi/src/main/resources/banner.txt b/pamapi/src/main/resources/banner.txt new file mode 100644 index 0000000..e0bc55a --- /dev/null +++ b/pamapi/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/pamapi/src/main/resources/config/application-dev.yml b/pamapi/src/main/resources/config/application-dev.yml new file mode 100644 index 0000000..e93ffa8 --- /dev/null +++ b/pamapi/src/main/resources/config/application-dev.yml @@ -0,0 +1,114 @@ +# =================================================================== +# 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 + tech.jhipster: DEBUG + org.hibernate.SQL: DEBUG + com.pollex.pam: DEBUG + +spring: + devtools: + restart: + enabled: true + additional-exclude: static/** + livereload: + enabled: false # we use Webpack dev server + BrowserSync for livereload + jackson: + serialization: + indent-output: true + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: jdbc:postgresql://localhost:5432/pamapi + username: pamapi + password: + hikari: + poolName: Hikari + auto-commit: false + jpa: + database-platform: tech.jhipster.domain.util.FixedPostgreSQL10Dialect + 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: 8080 + +# =================================================================== +# JHipster specific properties +# +# Full reference is available at: https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +jhipster: + cache: # Cache configuration + ehcache: # Ehcache configuration + time-to-live-seconds: 3600 # By default objects stay 1 hour in the cache + max-entries: 100 # Number of objects in each cache entry + # CORS is only enabled by default with the "dev" profile + cors: + # Allow Ionic for JHipster by default (* no longer allowed in Spring Boot 2.4+) + allowed-origins: 'http://localhost:8100,https://localhost:8100,http://localhost:9000,https://localhost:9000' + 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 + security: + authentication: + jwt: + # This token must be encoded using Base64 and be at least 256 bits long (you can type `openssl rand -base64 64` on your command line to generate a 512 bits one) + base64-secret: MjI3YWRiMzg1ZTY3ZmZkZDgxZmI5Yjc5YjVkOTIzMzc4MmI2OWM3NWVkZjFiOTNmNjg0YWFjZWQ4YzhlOTUzYzk3MGUzMzM5Y2U5MDdkZTMyN2Q0N2E0M2ZmM2FhYzkyNDY4MjBkYTY5OGM4YmIzMmYxODJhNWFkMmVhNmVhNTM= + # Token is valid 24 hours + token-validity-in-seconds: 86400 + token-validity-in-seconds-for-remember-me: 2592000 + mail: # specific JHipster mail property, for standard properties see MailProperties + base-url: http://127.0.0.1:8080 + 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 +# =================================================================== +# 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/pamapi/src/main/resources/config/application-prod.yml b/pamapi/src/main/resources/config/application-prod.yml new file mode 100644 index 0000000..40fd385 --- /dev/null +++ b/pamapi/src/main/resources/config/application-prod.yml @@ -0,0 +1,135 @@ +# =================================================================== +# 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 + tech.jhipster: INFO + com.pollex.pam: INFO + +management: + metrics: + export: + prometheus: + enabled: false + +spring: + devtools: + restart: + enabled: false + livereload: + enabled: false + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: jdbc:postgresql://localhost:5432/pamapi + username: pamapi + password: + hikari: + poolName: Hikari + auto-commit: false + jpa: + database-platform: tech.jhipster.domain.util.FixedPostgreSQL10Dialect + # 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 pamapi -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: 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: 8080 + 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,image/svg+xml + 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 + cache: # Cache configuration + ehcache: # Ehcache configuration + time-to-live-seconds: 3600 # By default objects stay 1 hour in the cache + max-entries: 1000 # Number of objects in each cache entry + security: + authentication: + jwt: + # This token must be encoded using Base64 and be at least 256 bits long (you can type `openssl rand -base64 64` on your command line to generate a 512 bits one) + # As this is the PRODUCTION configuration, you MUST change the default key, and store it securely: + # - In the JHipster Registry (which includes a Spring Cloud Config server) + # - In a separate `application-prod.yml` file, in the same folder as your executable JAR file + # - In the `JHIPSTER_SECURITY_AUTHENTICATION_JWT_BASE64_SECRET` environment variable + base64-secret: MjI3YWRiMzg1ZTY3ZmZkZDgxZmI5Yjc5YjVkOTIzMzc4MmI2OWM3NWVkZjFiOTNmNjg0YWFjZWQ4YzhlOTUzYzk3MGUzMzM5Y2U5MDdkZTMyN2Q0N2E0M2ZmM2FhYzkyNDY4MjBkYTY5OGM4YmIzMmYxODJhNWFkMmVhNmVhNTM= + # Token is valid 24 hours + token-validity-in-seconds: 86400 + token-validity-in-seconds-for-remember-me: 2592000 + 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 + 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 +# =================================================================== +# 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/pamapi/src/main/resources/config/application-tls.yml b/pamapi/src/main/resources/config/application-tls.yml new file mode 100644 index 0000000..039f6f4 --- /dev/null +++ b/pamapi/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_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 + enabled-protocols: TLSv1.2 + http2: + enabled: true diff --git a/pamapi/src/main/resources/config/application.yml b/pamapi/src/main/resources/config/application.yml new file mode 100644 index 0000000..0c738cb --- /dev/null +++ b/pamapi/src/main/resources/config/application.yml @@ -0,0 +1,182 @@ +# =================================================================== +# 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 +# =================================================================== + +management: + endpoints: + web: + base-path: /management + exposure: + include: + ['configprops', 'env', 'health', 'info', 'jhimetrics', 'logfile', 'loggers', 'prometheus', 'threaddump', 'caches', 'liquibase'] + endpoint: + health: + 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,db + 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: + request: + autotime: + enabled: true + +spring: + application: + name: pamapi + 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: + jpa: + repositories: + bootstrap-mode: deferred + jpa: + 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: true + 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: + 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 + task: + execution: + thread-name-prefix: pamapi-task- + pool: + core-size: 2 + max-size: 50 + queue-capacity: 10000 + scheduling: + thread-name-prefix: pamapi-scheduling- + pool: + size: 2 + thymeleaf: + mode: HTML + output: + ansi: + console-available: true + +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: 'pamapiApp' + # By default CORS is disabled. Uncomment to enable. + # cors: + # allowed-origins: "http://localhost:8100,http://localhost:9000" + # 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: pamapi@localhost + api-docs: + default-include-pattern: ${server.servlet.context-path:}/api/.* + management-include-pattern: ${server.servlet.context-path:}/management/.* + title: Pamapi API + description: Pamapi API documentation + version: 0.0.1 + terms-of-service-url: + contact-name: + contact-url: + contact-email: + license: unlicensed + license-url: + security: + content-security-policy: "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:" +# =================================================================== +# 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/pamapi/src/main/resources/config/bootstrap-prod.yml b/pamapi/src/main/resources/config/bootstrap-prod.yml new file mode 100644 index 0000000..1047df3 --- /dev/null +++ b/pamapi/src/main/resources/config/bootstrap-prod.yml @@ -0,0 +1,6 @@ +# =================================================================== +# Spring Cloud Config bootstrap configuration for the "prod" profile +# =================================================================== + +spring: + cloud: diff --git a/pamapi/src/main/resources/config/bootstrap.yml b/pamapi/src/main/resources/config/bootstrap.yml new file mode 100644 index 0000000..6236201 --- /dev/null +++ b/pamapi/src/main/resources/config/bootstrap.yml @@ -0,0 +1,14 @@ +# =================================================================== +# Spring Cloud 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: pamapi + 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: diff --git a/pamapi/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml b/pamapi/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml new file mode 100644 index 0000000..b514e45 --- /dev/null +++ b/pamapi/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml @@ -0,0 +1,121 @@ +<?xml version="1.0" encoding="utf-8"?> +<databaseChangeLog + 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-4.5.xsd + http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd"> + + <changeSet id="00000000000000" author="jhipster"> + <createSequence sequenceName="sequence_generator" startValue="1050" incrementBy="50"/> + </changeSet> + + <!-- + JHipster core tables. + The initial schema has the '00000000000001' id, so that it is over-written if we re-generate it. + --> + <changeSet id="00000000000001" author="jhipster"> + <createTable tableName="jhi_user"> + <column name="id" type="bigint"> + <constraints primaryKey="true" nullable="false"/> + </column> + <column name="login" type="varchar(50)"> + <constraints unique="true" nullable="false" uniqueConstraintName="ux_user_login"/> + </column> + <column name="password_hash" type="varchar(60)"/> + <column name="first_name" type="varchar(50)"/> + <column name="last_name" type="varchar(50)"/> + <column name="email" type="varchar(191)"> + <constraints unique="true" nullable="true" uniqueConstraintName="ux_user_email"/> + </column> + <column name="image_url" type="varchar(256)"/> + <column name="activated" type="boolean" valueBoolean="false"> + <constraints nullable="false" /> + </column> + <column name="lang_key" type="varchar(10)"/> + <column name="activation_key" type="varchar(20)"/> + <column name="reset_key" type="varchar(20)"/> + <column name="created_by" type="varchar(50)"> + <constraints nullable="false"/> + </column> + <column name="created_date" type="timestamp"/> + <column name="reset_date" type="timestamp"> + <constraints nullable="true"/> + </column> + <column name="last_modified_by" type="varchar(50)"/> + <column name="last_modified_date" type="timestamp"/> + </createTable> + + <createTable tableName="jhi_authority"> + <column name="name" type="varchar(50)"> + <constraints primaryKey="true" nullable="false"/> + </column> + </createTable> + + <createTable tableName="jhi_user_authority"> + <column name="user_id" type="bigint"> + <constraints nullable="false"/> + </column> + <column name="authority_name" type="varchar(50)"> + <constraints nullable="false"/> + </column> + </createTable> + + <addPrimaryKey columnNames="user_id, authority_name" tableName="jhi_user_authority"/> + + <addForeignKeyConstraint baseColumnNames="authority_name" + baseTableName="jhi_user_authority" + constraintName="fk_authority_name" + referencedColumnNames="name" + referencedTableName="jhi_authority"/> + + <addForeignKeyConstraint baseColumnNames="user_id" + baseTableName="jhi_user_authority" + constraintName="fk_user_id" + referencedColumnNames="id" + referencedTableName="jhi_user"/> + + <addNotNullConstraint columnName="password_hash" + columnDataType="varchar(60)" + tableName="jhi_user"/> + <loadData + file="config/liquibase/data/user.csv" + separator=";" + tableName="jhi_user" + usePreparedStatements="true"> + <column name="id" type="numeric"/> + <column name="activated" type="boolean"/> + <column name="created_date" type="timestamp"/> + </loadData> + <dropDefaultValue tableName="jhi_user" columnName="created_date" columnDataType="${datetimeType}"/> + <loadData + file="config/liquibase/data/authority.csv" + separator=";" + tableName="jhi_authority" + usePreparedStatements="true"> + <column name="name" type="string"/> + </loadData> + <loadData + file="config/liquibase/data/user_authority.csv" + separator=";" + tableName="jhi_user_authority" + usePreparedStatements="true"> + <column name="user_id" type="numeric"/> + </loadData> + </changeSet> + + <changeSet author="jhipster" id="00000000000002" context="test"> + <createTable tableName="jhi_date_time_wrapper"> + <column name="id" type="BIGINT"> + <constraints primaryKey="true" primaryKeyName="jhi_date_time_wrapperPK"/> + </column> + <column name="instant" type="timestamp"/> + <column name="local_date_time" type="timestamp"/> + <column name="offset_date_time" type="timestamp"/> + <column name="zoned_date_time" type="timestamp"/> + <column name="local_time" type="time"/> + <column name="offset_time" type="time"/> + <column name="local_date" type="date"/> + </createTable> + </changeSet> +</databaseChangeLog> diff --git a/pamapi/src/main/resources/config/liquibase/data/authority.csv b/pamapi/src/main/resources/config/liquibase/data/authority.csv new file mode 100644 index 0000000..af5c6df --- /dev/null +++ b/pamapi/src/main/resources/config/liquibase/data/authority.csv @@ -0,0 +1,3 @@ +name +ROLE_ADMIN +ROLE_USER diff --git a/pamapi/src/main/resources/config/liquibase/data/user.csv b/pamapi/src/main/resources/config/liquibase/data/user.csv new file mode 100644 index 0000000..4f612b0 --- /dev/null +++ b/pamapi/src/main/resources/config/liquibase/data/user.csv @@ -0,0 +1,3 @@ +id;login;password_hash;first_name;last_name;email;image_url;activated;lang_key;created_by;last_modified_by +1;admin;$2a$10$gSAhZrxMllrbgj/kkK9UceBPpChGWJA7SYIb1Mqo.n5aNLq1/oRrC;Administrator;Administrator;admin@localhost;;true;zh-tw;system;system +2;user;$2a$10$VEjxo0jq2YG9Rbk2HmX9S.k1uZBGYUHdUcid3g/vfiEl7lwWgOH/K;User;User;user@localhost;;true;zh-tw;system;system diff --git a/pamapi/src/main/resources/config/liquibase/data/user_authority.csv b/pamapi/src/main/resources/config/liquibase/data/user_authority.csv new file mode 100644 index 0000000..01dbdef --- /dev/null +++ b/pamapi/src/main/resources/config/liquibase/data/user_authority.csv @@ -0,0 +1,4 @@ +user_id;authority_name +1;ROLE_ADMIN +1;ROLE_USER +2;ROLE_USER diff --git a/pamapi/src/main/resources/config/liquibase/master.xml b/pamapi/src/main/resources/config/liquibase/master.xml new file mode 100644 index 0000000..92de5bb --- /dev/null +++ b/pamapi/src/main/resources/config/liquibase/master.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<databaseChangeLog + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + 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-4.5.xsd"> + + <property name="now" value="now()" dbms="h2"/> + <property name="now" value="current_timestamp" dbms="postgresql"/> + <property name="floatType" value="float4" dbms="postgresql, h2"/> + <property name="floatType" value="float" dbms="mysql, oracle, mssql, mariadb"/> + <property name="clobType" value="longvarchar" dbms="h2"/> + <property name="clobType" value="clob" dbms="mysql, oracle, mssql, mariadb, postgresql"/> + <property name="uuidType" value="uuid" dbms="h2, postgresql"/> + <property name="datetimeType" value="datetime(6)" dbms="mysql, mariadb"/> + <property name="datetimeType" value="datetime" dbms="oracle, mssql, postgresql, h2"/> + + <include file="config/liquibase/changelog/00000000000000_initial_schema.xml" relativeToChangelogFile="false"/> + <!-- jhipster-needle-liquibase-add-changelog - JHipster will add liquibase changelogs here --> + <!-- jhipster-needle-liquibase-add-constraints-changelog - JHipster will add liquibase constraints changelogs here --> + <!-- jhipster-needle-liquibase-add-incremental-changelog - JHipster will add incremental liquibase changelogs here --> +</databaseChangeLog> diff --git a/pamapi/src/main/resources/config/tls/keystore.p12 b/pamapi/src/main/resources/config/tls/keystore.p12 new file mode 100644 index 0000000..8d8c905 --- /dev/null +++ b/pamapi/src/main/resources/config/tls/keystore.p12 Binary files differ diff --git a/pamapi/src/main/resources/i18n/messages.properties b/pamapi/src/main/resources/i18n/messages.properties new file mode 100644 index 0000000..9cbf924 --- /dev/null +++ b/pamapi/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=pamapi account activation is required +email.activation.greeting=Dear {0} +email.activation.text1=Your pamapi account has been created, please click on the URL below to activate it: +email.activation.text2=Regards, +email.signature=pamapi Team. + +# Creation email +email.creation.text1=Your pamapi account has been created, please click on the URL below to access it: + +# Reset email +email.reset.title=pamapi password reset +email.reset.greeting=Dear {0} +email.reset.text1=For your pamapi account a password reset was requested, please click on the URL below to reset it: +email.reset.text2=Regards, diff --git a/pamapi/src/main/resources/i18n/messages_en.properties b/pamapi/src/main/resources/i18n/messages_en.properties new file mode 100644 index 0000000..ad4c1dd --- /dev/null +++ b/pamapi/src/main/resources/i18n/messages_en.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=pamapi account activation +email.activation.greeting=Dear {0} +email.activation.text1=Your pamapi account has been created, please click on the URL below to activate it: +email.activation.text2=Regards, +email.signature=pamapi Team. + +# Creation email +email.creation.text1=Your pamapi account has been created, please click on the URL below to access it: + +# Reset email +email.reset.title=pamapi password reset +email.reset.greeting=Dear {0} +email.reset.text1=For your pamapi account a password reset was requested, please click on the URL below to reset it: +email.reset.text2=Regards, diff --git a/pamapi/src/main/resources/i18n/messages_zh_TW.properties b/pamapi/src/main/resources/i18n/messages_zh_TW.properties new file mode 100644 index 0000000..f1bcd2b --- /dev/null +++ b/pamapi/src/main/resources/i18n/messages_zh_TW.properties @@ -0,0 +1,21 @@ +# Error page +error.title=\u7121\u6CD5\u8655\u7406\u60A8\u7684\u8981\u6C42 +error.subtitle=\u5F88\u62B1\u6B49\uFF0C\u6211\u5011\u9047\u5230\u4E86\u4E00\u4E9B\u554F\u984C\u3002 +error.status=\u72C0\u614B: +error.message=\u8A0A\u606F: + +# Activation email +email.activation.title=pamapi \u5E33\u865F\u555F\u7528 +email.activation.greeting=\u89AA\u611B\u7684 {0} +email.activation.text1=\u60A8\u7684 pamapi \u5E33\u865F\u5EFA\u7ACB\u6210\u529F\uFF0C\u8ACB\u9EDE\u4E0B\u5217\u7DB2\u5740\u555F\u7528\u5E33\u865F: +email.activation.text2=\u795D\u60A8\u4F7F\u7528\u6109\u5FEB\uFF0C +email.signature=pamapi \u5718\u968A\u3002 + +# Creation email +email.creation.text1=\u60A8\u7684 pamapi \u5E33\u865F\u5EFA\u7ACB\u6210\u529F\uFF0C\u8ACB\u9EDE\u4E0B\u5217\u7DB2\u5740\u958B\u59CB\u4F7F\u7528: + +# Reset email +email.reset.title=pamapi \u5BC6\u78BC\u91CD\u8A2D +email.reset.greeting=\u89AA\u611B\u7684 {0} +email.reset.text1=\u60A8\u7684 pamapi \u5E33\u865F\u88AB\u8981\u6C42\u91CD\u65B0\u8A2D\u5B9A\u5BC6\u78BC\uFF0C\u8ACB\u9EDE\u4E0B\u5217\u7DB2\u5740\u8A2D\u5B9A: +email.reset.text2=\u795D\u60A8\u4F7F\u7528\u6109\u5FEB\uFF0C diff --git a/pamapi/src/main/resources/logback-spring.xml b/pamapi/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..b2448bd --- /dev/null +++ b/pamapi/src/main/resources/logback-spring.xml @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE configuration> + +<configuration scan="true"> + <include resource="org/springframework/boot/logging/logback/base.xml"/> + +<!-- The FILE and ASYNC appenders are here as examples for a production configuration --> +<!-- + <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> + <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> + <fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern> + <maxHistory>90</maxHistory> + </rollingPolicy> + <encoder> + <charset>utf-8</charset> + <Pattern>%d %-5level [%thread] %logger{0}: %msg%n</Pattern> + </encoder> + </appender> + + <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender"> + <queueSize>512</queueSize> + <appender-ref ref="FILE"/> + </appender> + + <root level="${logging.level.root}"> + <appender-ref ref="ASYNC"/> + </root> +--> + + <logger name="javax.activation" level="WARN"/> + <logger name="javax.mail" level="WARN"/> + <logger name="javax.management.remote" level="WARN"/> + <logger name="javax.xml.bind" level="WARN"/> + <logger name="ch.qos.logback" level="WARN"/> + <logger name="com.ryantenney" level="WARN"/> + <logger name="com.sun" level="WARN"/> + <logger name="com.zaxxer" level="WARN"/> + <logger name="io.undertow" level="WARN"/> + <logger name="io.undertow.websockets.jsr" level="ERROR"/> + <logger name="org.ehcache" level="WARN"/> + <logger name="org.apache" level="WARN"/> + <logger name="org.apache.catalina.startup.DigesterFactory" level="OFF"/> + <logger name="org.bson" level="WARN"/> + <logger name="org.hibernate.validator" level="WARN"/> + <logger name="org.hibernate" level="WARN"/> + <logger name="org.hibernate.ejb.HibernatePersistence" level="OFF"/> + <logger name="org.postgresql" level="WARN"/> + <logger name="org.springframework" level="WARN"/> + <logger name="org.springframework.web" level="WARN"/> + <logger name="org.springframework.security" level="WARN"/> + <logger name="org.springframework.cache" level="WARN"/> + <logger name="org.thymeleaf" level="WARN"/> + <logger name="org.xnio" level="WARN"/> + <logger name="springfox" level="WARN"/> + <logger name="sun.rmi" level="WARN"/> + <logger name="liquibase" level="WARN"/> + <logger name="LiquibaseSchemaResolver" level="INFO"/> + <logger name="springfox.documentation.schema.property" level="ERROR"/> + <logger name="sun.rmi.transport" level="WARN"/> + <!-- See https://github.com/jhipster/generator-jhipster/issues/13835 --> + <logger name="Validator" level="INFO"/> + <!-- See https://github.com/jhipster/generator-jhipster/issues/14444 --> + <logger name="_org.springframework.web.servlet.HandlerMapping.Mappings" level="INFO"/> + <!-- jhipster-needle-logback-add-log - JHipster will add a new log with level --> + + <!-- https://logback.qos.ch/manual/configuration.html#shutdownHook and https://jira.qos.ch/browse/LOGBACK-1090 --> + <shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/> + + <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"> + <resetJUL>true</resetJUL> + </contextListener> + +</configuration> diff --git a/pamapi/src/main/resources/templates/error.html b/pamapi/src/main/resources/templates/error.html new file mode 100644 index 0000000..690e856 --- /dev/null +++ b/pamapi/src/main/resources/templates/error.html @@ -0,0 +1,92 @@ +<!DOCTYPE html> +<html + xmlns:th="http://www.thymeleaf.org" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.thymeleaf.org" + th:lang="${#locale.language}" + lang="en" +> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <link rel="icon" href="${baseUrl}/favicon.ico" /> + <title>Your request cannot be processed</title> + <style> + ::-moz-selection { + background: #b3d4fc; + text-shadow: none; + } + + ::selection { + background: #b3d4fc; + text-shadow: none; + } + + html { + padding: 30px 10px; + font-size: 20px; + line-height: 1.4; + color: #737373; + background: #3e8acc; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + } + + html, + input { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + } + + body { + max-width: 1000px; + _width: 500px; + padding: 30px 20px 50px; + border: 1px solid #b3b3b3; + border-radius: 4px; + margin: 0 auto; + box-shadow: 0 1px 10px #a7a7a7, inset 0 1px 0 #fff; + background: #fcfcfc; + } + + h1 { + margin: 0 10px; + font-size: 50px; + text-align: center; + } + + h1 span { + color: #bbb; + } + + h3 { + margin: 1.5em 0 0.5em; + } + + p { + margin: 1em 0; + } + + ul { + padding: 0 0 0 40px; + margin: 1em 0; + } + + .container { + max-width: 800px; + _width: 380px; + margin: 0 auto; + } + </style> + </head> + <body> + <div class="container"> + <h1 th:text="#{error.title}">Your request cannot be processed <span>:(</span></h1> + + <p th:text="#{error.subtitle}">Sorry, an error has occurred.</p> + + <span th:text="#{error.status}">Status:</span> <span th:text="${error}"></span> (<span th:text="${error}"></span>)<br /> + <span th:if="${!#strings.isEmpty(message)}"> + <span th:text="#{error.message}">Message:</span> <span th:text="${message}"></span><br /> + </span> + </div> + </body> +</html> diff --git a/pamapi/src/main/resources/templates/mail/activationEmail.html b/pamapi/src/main/resources/templates/mail/activationEmail.html new file mode 100644 index 0000000..0bb53a2 --- /dev/null +++ b/pamapi/src/main/resources/templates/mail/activationEmail.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html xmlns:th="http://www.thymeleaf.org" th:lang="${#locale.language}" lang="en"> + <head> + <title th:text="#{email.activation.title}">JHipster activation</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <link rel="icon" th:href="@{|${baseUrl}/favicon.ico|}" /> + </head> + <body> + <p th:text="#{email.activation.greeting(${user.login})}">Dear</p> + <p th:text="#{email.activation.text1}">Your JHipster account has been created, please click on the URL below to activate it:</p> + <p> + <a th:with="url=(@{|${baseUrl}/account/activate?key=${user.activationKey}|})" th:href="${url}" th:text="${url}">Activation link</a> + </p> + <p> + <span th:text="#{email.activation.text2}">Regards, </span> + <br /> + <em th:text="#{email.signature}">JHipster.</em> + </p> + </body> +</html> diff --git a/pamapi/src/main/resources/templates/mail/creationEmail.html b/pamapi/src/main/resources/templates/mail/creationEmail.html new file mode 100644 index 0000000..4e52898 --- /dev/null +++ b/pamapi/src/main/resources/templates/mail/creationEmail.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html xmlns:th="http://www.thymeleaf.org" th:lang="${#locale.language}" lang="en"> + <head> + <title th:text="#{email.activation.title}">JHipster creation</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <link rel="icon" th:href="@{|${baseUrl}/favicon.ico|}" /> + </head> + <body> + <p th:text="#{email.activation.greeting(${user.login})}">Dear</p> + <p th:text="#{email.creation.text1}">Your JHipster account has been created, please click on the URL below to access it:</p> + <p> + <a th:with="url=(@{|${baseUrl}/account/reset/finish?key=${user.resetKey}|})" th:href="${url}" th:text="${url}">Login link</a> + </p> + <p> + <span th:text="#{email.activation.text2}">Regards, </span> + <br /> + <em th:text="#{email.signature}">JHipster.</em> + </p> + </body> +</html> diff --git a/pamapi/src/main/resources/templates/mail/passwordResetEmail.html b/pamapi/src/main/resources/templates/mail/passwordResetEmail.html new file mode 100644 index 0000000..290ca6d --- /dev/null +++ b/pamapi/src/main/resources/templates/mail/passwordResetEmail.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html xmlns:th="http://www.thymeleaf.org" th:lang="${#locale.language}" lang="en"> + <head> + <title th:text="#{email.reset.title}">JHipster password reset</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <link rel="icon" th:href="@{|${baseUrl}/favicon.ico|}" /> + </head> + <body> + <p th:text="#{email.reset.greeting(${user.login})}">Dear</p> + <p th:text="#{email.reset.text1}"> + For your JHipster account a password reset was requested, please click on the URL below to reset it: + </p> + <p> + <a th:with="url=(@{|${baseUrl}/account/reset/finish?key=${user.resetKey}|})" th:href="${url}" th:text="${url}">Login link</a> + </p> + <p> + <span th:text="#{email.reset.text2}">Regards, </span> + <br /> + <em th:text="#{email.signature}">JHipster.</em> + </p> + </body> +</html> diff --git a/pamapi/src/test/java/com/pollex/pam/ArchTest.java b/pamapi/src/test/java/com/pollex/pam/ArchTest.java new file mode 100644 index 0000000..d98c800 --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/ArchTest.java @@ -0,0 +1,29 @@ +package com.pollex.pam; + +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; + +class ArchTest { + + @Test + void servicesAndRepositoriesShouldNotDependOnWebLayer() { + JavaClasses importedClasses = new ClassFileImporter() + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) + .importPackages("com.pollex.pam"); + + noClasses() + .that() + .resideInAnyPackage("com.pollex.pam.service..") + .or() + .resideInAnyPackage("com.pollex.pam.repository..") + .should() + .dependOnClassesThat() + .resideInAnyPackage("..com.pollex.pam.web..") + .because("Services and repositories should not depend on web layer") + .check(importedClasses); + } +} diff --git a/pamapi/src/test/java/com/pollex/pam/IntegrationTest.java b/pamapi/src/test/java/com/pollex/pam/IntegrationTest.java new file mode 100644 index 0000000..8a6cb1b --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/IntegrationTest.java @@ -0,0 +1,17 @@ +package com.pollex.pam; + +import com.pollex.pam.PamapiApp; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * Base composite annotation for integration tests. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@SpringBootTest(classes = PamapiApp.class) +public @interface IntegrationTest { +} diff --git a/pamapi/src/test/java/com/pollex/pam/config/NoOpMailConfiguration.java b/pamapi/src/test/java/com/pollex/pam/config/NoOpMailConfiguration.java new file mode 100644 index 0000000..cd88e1d --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/config/NoOpMailConfiguration.java @@ -0,0 +1,25 @@ +package com.pollex.pam.config; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; + +import com.pollex.pam.service.MailService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class NoOpMailConfiguration { + + private final MailService mockMailService; + + public NoOpMailConfiguration() { + mockMailService = mock(MailService.class); + doNothing().when(mockMailService).sendActivationEmail(any()); + } + + @Bean + public MailService mailService() { + return mockMailService; + } +} diff --git a/pamapi/src/test/java/com/pollex/pam/config/WebConfigurerTest.java b/pamapi/src/test/java/com/pollex/pam/config/WebConfigurerTest.java new file mode 100644 index 0000000..b48a331 --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/config/WebConfigurerTest.java @@ -0,0 +1,131 @@ +package com.pollex.pam.config; + +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.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 tech.jhipster.config.JHipsterConstants; +import tech.jhipster.config.JHipsterProperties; + +/** + * Unit tests for the {@link WebConfigurer} class. + */ +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 + void shouldStartUpProdServletContext() throws ServletException { + env.setActiveProfiles(JHipsterConstants.SPRING_PROFILE_PRODUCTION); + + assertThatCode(() -> webConfigurer.onStartup(servletContext)).doesNotThrowAnyException(); + } + + @Test + void shouldStartUpDevServletContext() throws ServletException { + env.setActiveProfiles(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT); + + assertThatCode(() -> webConfigurer.onStartup(servletContext)).doesNotThrowAnyException(); + } + + @Test + 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 + .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 + 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 + .perform(get("/test/test-cors").header(HttpHeaders.ORIGIN, "other.domain.com")) + .andExpect(status().isOk()) + .andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + } + + @Test + void shouldCorsFilterDeactivatedForNullAllowedOrigins() 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 + void shouldCorsFilterDeactivatedForEmptyAllowedOrigins() 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/pamapi/src/test/java/com/pollex/pam/config/WebConfigurerTestController.java b/pamapi/src/test/java/com/pollex/pam/config/WebConfigurerTestController.java new file mode 100644 index 0000000..d9b8f2f --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/config/WebConfigurerTestController.java @@ -0,0 +1,14 @@ +package com.pollex.pam.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/pamapi/src/test/java/com/pollex/pam/config/timezone/HibernateTimeZoneIT.java b/pamapi/src/test/java/com/pollex/pam/config/timezone/HibernateTimeZoneIT.java new file mode 100644 index 0000000..f0db7f2 --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/config/timezone/HibernateTimeZoneIT.java @@ -0,0 +1,162 @@ +package com.pollex.pam.config.timezone; + +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; + +import com.pollex.pam.IntegrationTest; +import com.pollex.pam.repository.timezone.DateTimeWrapper; +import com.pollex.pam.repository.timezone.DateTimeWrapperRepository; +import java.time.*; +import java.time.format.DateTimeFormatter; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +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; + +/** + * Integration tests for the ZoneId Hibernate configuration. + */ +@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; + 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(zoneId)); + + timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss").withZone(ZoneId.of(zoneId)); + + dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + } + + @Test + @Transactional + void storeInstantWithZoneIdConfigShouldBeStoredOnGMTTimeZone() { + 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 + 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); + + assertThatDateStoredValueIsEqualToInsertDateValueOnGMTTimeZone(resultSet, expectedValue); + } + + @Test + @Transactional + void storeOffsetDateTimeWithZoneIdConfigShouldBeStoredOnGMTTimeZone() { + 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 + void storeZoneDateTimeWithZoneIdConfigShouldBeStoredOnGMTTimeZone() { + 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 + void storeLocalTimeWithZoneIdConfigShouldBeStoredOnGMTTimeZoneAccordingToHis1stJan1970Value() { + 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 + void storeOffsetTimeWithZoneIdConfigShouldBeStoredOnGMTTimeZoneAccordingToHis1stJan1970Value() { + 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 + void storeLocalDateWithZoneIdConfigShouldBeStoredWithoutTransformation() { + 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/pamapi/src/test/java/com/pollex/pam/repository/timezone/DateTimeWrapper.java b/pamapi/src/test/java/com/pollex/pam/repository/timezone/DateTimeWrapper.java new file mode 100644 index 0000000..a6a6684 --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/repository/timezone/DateTimeWrapper.java @@ -0,0 +1,133 @@ +package com.pollex.pam.repository.timezone; + +import java.io.Serializable; +import java.time.*; +import java.util.Objects; +import javax.persistence.*; + +@Entity +@Table(name = "jhi_date_time_wrapper") +public class DateTimeWrapper implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator") + @SequenceGenerator(name = "sequenceGenerator") + 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()); + } + + // prettier-ignore + @Override + public String toString() { + return "TimeZoneTest{" + + "id=" + id + + ", instant=" + instant + + ", localDateTime=" + localDateTime + + ", offsetDateTime=" + offsetDateTime + + ", zonedDateTime=" + zonedDateTime + + '}'; + } +} diff --git a/pamapi/src/test/java/com/pollex/pam/repository/timezone/DateTimeWrapperRepository.java b/pamapi/src/test/java/com/pollex/pam/repository/timezone/DateTimeWrapperRepository.java new file mode 100644 index 0000000..2907925 --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/repository/timezone/DateTimeWrapperRepository.java @@ -0,0 +1,10 @@ +package com.pollex.pam.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<DateTimeWrapper, Long> {} diff --git a/pamapi/src/test/java/com/pollex/pam/security/DomainUserDetailsServiceIT.java b/pamapi/src/test/java/com/pollex/pam/security/DomainUserDetailsServiceIT.java new file mode 100644 index 0000000..bba00aa --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/security/DomainUserDetailsServiceIT.java @@ -0,0 +1,111 @@ +package com.pollex.pam.security; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import com.pollex.pam.IntegrationTest; +import com.pollex.pam.domain.User; +import com.pollex.pam.repository.UserRepository; +import java.util.Locale; +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.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.transaction.annotation.Transactional; + +/** + * Integrations tests for {@link DomainUserDetailsService}. + */ +@Transactional +@IntegrationTest +class DomainUserDetailsServiceIT { + + private static final String USER_ONE_LOGIN = "test-user-one"; + private static final String USER_ONE_EMAIL = "test-user-one@localhost"; + private static final String USER_TWO_LOGIN = "test-user-two"; + private static final String USER_TWO_EMAIL = "test-user-two@localhost"; + private static final String USER_THREE_LOGIN = "test-user-three"; + private static final String USER_THREE_EMAIL = "test-user-three@localhost"; + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserDetailsService domainUserDetailsService; + + @BeforeEach + public void init() { + User userOne = new User(); + userOne.setLogin(USER_ONE_LOGIN); + userOne.setPassword(RandomStringUtils.random(60)); + userOne.setActivated(true); + userOne.setEmail(USER_ONE_EMAIL); + userOne.setFirstName("userOne"); + userOne.setLastName("doe"); + userOne.setLangKey("en"); + userRepository.save(userOne); + + User userTwo = new User(); + userTwo.setLogin(USER_TWO_LOGIN); + userTwo.setPassword(RandomStringUtils.random(60)); + userTwo.setActivated(true); + userTwo.setEmail(USER_TWO_EMAIL); + userTwo.setFirstName("userTwo"); + userTwo.setLastName("doe"); + userTwo.setLangKey("en"); + userRepository.save(userTwo); + + User userThree = new User(); + userThree.setLogin(USER_THREE_LOGIN); + userThree.setPassword(RandomStringUtils.random(60)); + userThree.setActivated(false); + userThree.setEmail(USER_THREE_EMAIL); + userThree.setFirstName("userThree"); + userThree.setLastName("doe"); + userThree.setLangKey("en"); + userRepository.save(userThree); + } + + @Test + void assertThatUserCanBeFoundByLogin() { + UserDetails userDetails = domainUserDetailsService.loadUserByUsername(USER_ONE_LOGIN); + assertThat(userDetails).isNotNull(); + assertThat(userDetails.getUsername()).isEqualTo(USER_ONE_LOGIN); + } + + @Test + void assertThatUserCanBeFoundByLoginIgnoreCase() { + UserDetails userDetails = domainUserDetailsService.loadUserByUsername(USER_ONE_LOGIN.toUpperCase(Locale.ENGLISH)); + assertThat(userDetails).isNotNull(); + assertThat(userDetails.getUsername()).isEqualTo(USER_ONE_LOGIN); + } + + @Test + void assertThatUserCanBeFoundByEmail() { + UserDetails userDetails = domainUserDetailsService.loadUserByUsername(USER_TWO_EMAIL); + assertThat(userDetails).isNotNull(); + assertThat(userDetails.getUsername()).isEqualTo(USER_TWO_LOGIN); + } + + @Test + void assertThatUserCanBeFoundByEmailIgnoreCase() { + UserDetails userDetails = domainUserDetailsService.loadUserByUsername(USER_TWO_EMAIL.toUpperCase(Locale.ENGLISH)); + assertThat(userDetails).isNotNull(); + assertThat(userDetails.getUsername()).isEqualTo(USER_TWO_LOGIN); + } + + @Test + void assertThatEmailIsPrioritizedOverLogin() { + UserDetails userDetails = domainUserDetailsService.loadUserByUsername(USER_ONE_EMAIL); + assertThat(userDetails).isNotNull(); + assertThat(userDetails.getUsername()).isEqualTo(USER_ONE_LOGIN); + } + + @Test + void assertThatUserNotActivatedExceptionIsThrownForNotActivatedUsers() { + assertThatExceptionOfType(UserNotActivatedException.class) + .isThrownBy(() -> domainUserDetailsService.loadUserByUsername(USER_THREE_LOGIN)); + } +} diff --git a/pamapi/src/test/java/com/pollex/pam/security/SecurityUtilsUnitTest.java b/pamapi/src/test/java/com/pollex/pam/security/SecurityUtilsUnitTest.java new file mode 100644 index 0000000..202c393 --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/security/SecurityUtilsUnitTest.java @@ -0,0 +1,101 @@ +package com.pollex.pam.security; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Optional; +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; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +/** + * Test class for the {@link SecurityUtils} utility class. + */ +class SecurityUtilsUnitTest { + + @BeforeEach + @AfterEach + void cleanup() { + SecurityContextHolder.clearContext(); + } + + @Test + void testGetCurrentUserLogin() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("admin", "admin")); + SecurityContextHolder.setContext(securityContext); + Optional<String> login = SecurityUtils.getCurrentUserLogin(); + assertThat(login).contains("admin"); + } + + @Test + void testGetCurrentUserJWT() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("admin", "token")); + SecurityContextHolder.setContext(securityContext); + Optional<String> jwt = SecurityUtils.getCurrentUserJWT(); + assertThat(jwt).contains("token"); + } + + @Test + void testIsAuthenticated() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("admin", "admin")); + SecurityContextHolder.setContext(securityContext); + boolean isAuthenticated = SecurityUtils.isAuthenticated(); + assertThat(isAuthenticated).isTrue(); + } + + @Test + void testAnonymousIsNotAuthenticated() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + Collection<GrantedAuthority> 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 + void testHasCurrentUserThisAuthority() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + Collection<GrantedAuthority> authorities = new ArrayList<>(); + authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.USER)); + securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("user", "user", authorities)); + SecurityContextHolder.setContext(securityContext); + + assertThat(SecurityUtils.hasCurrentUserThisAuthority(AuthoritiesConstants.USER)).isTrue(); + assertThat(SecurityUtils.hasCurrentUserThisAuthority(AuthoritiesConstants.ADMIN)).isFalse(); + } + + @Test + void testHasCurrentUserAnyOfAuthorities() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + Collection<GrantedAuthority> authorities = new ArrayList<>(); + authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.USER)); + securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("user", "user", authorities)); + SecurityContextHolder.setContext(securityContext); + + assertThat(SecurityUtils.hasCurrentUserAnyOfAuthorities(AuthoritiesConstants.USER, AuthoritiesConstants.ADMIN)).isTrue(); + assertThat(SecurityUtils.hasCurrentUserAnyOfAuthorities(AuthoritiesConstants.ANONYMOUS, AuthoritiesConstants.ADMIN)).isFalse(); + } + + @Test + void testHasCurrentUserNoneOfAuthorities() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + Collection<GrantedAuthority> authorities = new ArrayList<>(); + authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.USER)); + securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("user", "user", authorities)); + SecurityContextHolder.setContext(securityContext); + + assertThat(SecurityUtils.hasCurrentUserNoneOfAuthorities(AuthoritiesConstants.USER, AuthoritiesConstants.ADMIN)).isFalse(); + assertThat(SecurityUtils.hasCurrentUserNoneOfAuthorities(AuthoritiesConstants.ANONYMOUS, AuthoritiesConstants.ADMIN)).isTrue(); + } +} diff --git a/pamapi/src/test/java/com/pollex/pam/security/jwt/JWTFilterTest.java b/pamapi/src/test/java/com/pollex/pam/security/jwt/JWTFilterTest.java new file mode 100644 index 0000000..d459104 --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/security/jwt/JWTFilterTest.java @@ -0,0 +1,112 @@ +package com.pollex.pam.security.jwt; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.pollex.pam.security.AuthoritiesConstants; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import java.util.Collections; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; +import org.springframework.mock.web.MockFilterChain; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.util.ReflectionTestUtils; +import tech.jhipster.config.JHipsterProperties; + +class JWTFilterTest { + + private TokenProvider tokenProvider; + + private JWTFilter jwtFilter; + + @BeforeEach + public void setup() { + JHipsterProperties jHipsterProperties = new JHipsterProperties(); + String base64Secret = "fd54a45s65fds737b9aafcb3412e07ed99b267f33413274720ddbb7f6c5e64e9f14075f2d7ed041592f0b7657baf8"; + jHipsterProperties.getSecurity().getAuthentication().getJwt().setBase64Secret(base64Secret); + tokenProvider = new TokenProvider(jHipsterProperties); + ReflectionTestUtils.setField(tokenProvider, "key", Keys.hmacShaKeyFor(Decoders.BASE64.decode(base64Secret))); + + ReflectionTestUtils.setField(tokenProvider, "tokenValidityInMilliseconds", 60000); + jwtFilter = new JWTFilter(tokenProvider); + SecurityContextHolder.getContext().setAuthentication(null); + } + + @Test + void testJWTFilter() throws Exception { + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( + "test-user", + "test-password", + Collections.singletonList(new SimpleGrantedAuthority(AuthoritiesConstants.USER)) + ); + String jwt = tokenProvider.createToken(authentication, false); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader(JWTFilter.AUTHORIZATION_HEADER, "Bearer " + jwt); + request.setRequestURI("/api/test"); + MockHttpServletResponse response = new MockHttpServletResponse(); + MockFilterChain filterChain = new MockFilterChain(); + jwtFilter.doFilter(request, response, filterChain); + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + assertThat(SecurityContextHolder.getContext().getAuthentication().getName()).isEqualTo("test-user"); + assertThat(SecurityContextHolder.getContext().getAuthentication().getCredentials()).hasToString(jwt); + } + + @Test + void testJWTFilterInvalidToken() throws Exception { + String jwt = "wrong_jwt"; + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader(JWTFilter.AUTHORIZATION_HEADER, "Bearer " + jwt); + request.setRequestURI("/api/test"); + MockHttpServletResponse response = new MockHttpServletResponse(); + MockFilterChain filterChain = new MockFilterChain(); + jwtFilter.doFilter(request, response, filterChain); + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); + } + + @Test + void testJWTFilterMissingAuthorization() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI("/api/test"); + MockHttpServletResponse response = new MockHttpServletResponse(); + MockFilterChain filterChain = new MockFilterChain(); + jwtFilter.doFilter(request, response, filterChain); + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); + } + + @Test + void testJWTFilterMissingToken() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader(JWTFilter.AUTHORIZATION_HEADER, "Bearer "); + request.setRequestURI("/api/test"); + MockHttpServletResponse response = new MockHttpServletResponse(); + MockFilterChain filterChain = new MockFilterChain(); + jwtFilter.doFilter(request, response, filterChain); + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); + } + + @Test + void testJWTFilterWrongScheme() throws Exception { + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( + "test-user", + "test-password", + Collections.singletonList(new SimpleGrantedAuthority(AuthoritiesConstants.USER)) + ); + String jwt = tokenProvider.createToken(authentication, false); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader(JWTFilter.AUTHORIZATION_HEADER, "Basic " + jwt); + request.setRequestURI("/api/test"); + MockHttpServletResponse response = new MockHttpServletResponse(); + MockFilterChain filterChain = new MockFilterChain(); + jwtFilter.doFilter(request, response, filterChain); + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); + } +} diff --git a/pamapi/src/test/java/com/pollex/pam/security/jwt/TokenProviderTest.java b/pamapi/src/test/java/com/pollex/pam/security/jwt/TokenProviderTest.java new file mode 100644 index 0000000..7dc9027 --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/security/jwt/TokenProviderTest.java @@ -0,0 +1,132 @@ +package com.pollex.pam.security.jwt; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.pollex.pam.security.AuthoritiesConstants; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.util.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.test.util.ReflectionTestUtils; +import tech.jhipster.config.JHipsterProperties; + +class TokenProviderTest { + + private static final long ONE_MINUTE = 60000; + + private Key key; + private TokenProvider tokenProvider; + + @BeforeEach + public void setup() { + JHipsterProperties jHipsterProperties = new JHipsterProperties(); + String base64Secret = "fd54a45s65fds737b9aafcb3412e07ed99b267f33413274720ddbb7f6c5e64e9f14075f2d7ed041592f0b7657baf8"; + jHipsterProperties.getSecurity().getAuthentication().getJwt().setBase64Secret(base64Secret); + tokenProvider = new TokenProvider(jHipsterProperties); + key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(base64Secret)); + + ReflectionTestUtils.setField(tokenProvider, "key", key); + ReflectionTestUtils.setField(tokenProvider, "tokenValidityInMilliseconds", ONE_MINUTE); + } + + @Test + void testReturnFalseWhenJWThasInvalidSignature() { + boolean isTokenValid = tokenProvider.validateToken(createTokenWithDifferentSignature()); + + assertThat(isTokenValid).isFalse(); + } + + @Test + void testReturnFalseWhenJWTisMalformed() { + Authentication authentication = createAuthentication(); + String token = tokenProvider.createToken(authentication, false); + String invalidToken = token.substring(1); + boolean isTokenValid = tokenProvider.validateToken(invalidToken); + + assertThat(isTokenValid).isFalse(); + } + + @Test + void testReturnFalseWhenJWTisExpired() { + ReflectionTestUtils.setField(tokenProvider, "tokenValidityInMilliseconds", -ONE_MINUTE); + + Authentication authentication = createAuthentication(); + String token = tokenProvider.createToken(authentication, false); + + boolean isTokenValid = tokenProvider.validateToken(token); + + assertThat(isTokenValid).isFalse(); + } + + @Test + void testReturnFalseWhenJWTisUnsupported() { + String unsupportedToken = createUnsupportedToken(); + + boolean isTokenValid = tokenProvider.validateToken(unsupportedToken); + + assertThat(isTokenValid).isFalse(); + } + + @Test + void testReturnFalseWhenJWTisInvalid() { + boolean isTokenValid = tokenProvider.validateToken(""); + + assertThat(isTokenValid).isFalse(); + } + + @Test + void testKeyIsSetFromSecretWhenSecretIsNotEmpty() { + final String secret = "NwskoUmKHZtzGRKJKVjsJF7BtQMMxNWi"; + JHipsterProperties jHipsterProperties = new JHipsterProperties(); + jHipsterProperties.getSecurity().getAuthentication().getJwt().setSecret(secret); + + TokenProvider tokenProvider = new TokenProvider(jHipsterProperties); + + Key key = (Key) ReflectionTestUtils.getField(tokenProvider, "key"); + assertThat(key).isNotNull().isEqualTo(Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8))); + } + + @Test + void testKeyIsSetFromBase64SecretWhenSecretIsEmpty() { + final String base64Secret = "fd54a45s65fds737b9aafcb3412e07ed99b267f33413274720ddbb7f6c5e64e9f14075f2d7ed041592f0b7657baf8"; + JHipsterProperties jHipsterProperties = new JHipsterProperties(); + jHipsterProperties.getSecurity().getAuthentication().getJwt().setBase64Secret(base64Secret); + + TokenProvider tokenProvider = new TokenProvider(jHipsterProperties); + + Key key = (Key) ReflectionTestUtils.getField(tokenProvider, "key"); + assertThat(key).isNotNull().isEqualTo(Keys.hmacShaKeyFor(Decoders.BASE64.decode(base64Secret))); + } + + private Authentication createAuthentication() { + Collection<GrantedAuthority> authorities = new ArrayList<>(); + authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ANONYMOUS)); + return new UsernamePasswordAuthenticationToken("anonymous", "anonymous", authorities); + } + + private String createUnsupportedToken() { + return Jwts.builder().setPayload("payload").signWith(key, SignatureAlgorithm.HS512).compact(); + } + + private String createTokenWithDifferentSignature() { + Key otherKey = Keys.hmacShaKeyFor( + Decoders.BASE64.decode("Xfd54a45s65fds737b9aafcb3412e07ed99b267f33413274720ddbb7f6c5e64e9f14075f2d7ed041592f0b7657baf8") + ); + + return Jwts + .builder() + .setSubject("anonymous") + .signWith(otherKey, SignatureAlgorithm.HS512) + .setExpiration(new Date(new Date().getTime() + ONE_MINUTE)) + .compact(); + } +} diff --git a/pamapi/src/test/java/com/pollex/pam/service/MailServiceIT.java b/pamapi/src/test/java/com/pollex/pam/service/MailServiceIT.java new file mode 100644 index 0000000..1b0b967 --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/service/MailServiceIT.java @@ -0,0 +1,245 @@ +package com.pollex.pam.service; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import com.pollex.pam.IntegrationTest; +import com.pollex.pam.config.Constants; +import com.pollex.pam.domain.User; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.net.URI; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.mail.Multipart; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.mail.MailSendException; +import org.springframework.mail.javamail.JavaMailSenderImpl; +import org.thymeleaf.spring5.SpringTemplateEngine; +import tech.jhipster.config.JHipsterProperties; + +/** + * Integration tests for {@link MailService}. + */ +@IntegrationTest +class MailServiceIT { + + private static final String[] languages = { + "zh-tw", + "en", + // jhipster-needle-i18n-language-constant - JHipster will add/remove languages in this array + }; + private static final Pattern PATTERN_LOCALE_3 = Pattern.compile("([a-z]{2})-([a-zA-Z]{4})-([a-z]{2})"); + private static final Pattern PATTERN_LOCALE_2 = Pattern.compile("([a-z]{2})-([a-z]{2})"); + + @Autowired + private JHipsterProperties jHipsterProperties; + + @Autowired + private MessageSource messageSource; + + @Autowired + private SpringTemplateEngine templateEngine; + + @Spy + private JavaMailSenderImpl javaMailSender; + + @Captor + private ArgumentCaptor<MimeMessage> messageCaptor; + + private MailService mailService; + + @BeforeEach + public void setup() { + MockitoAnnotations.initMocks(this); + doNothing().when(javaMailSender).send(any(MimeMessage.class)); + mailService = new MailService(jHipsterProperties, javaMailSender, messageSource, templateEngine); + } + + @Test + void testSendEmail() throws Exception { + mailService.sendEmail("john.doe@example.com", "testSubject", "testContent", false, false); + verify(javaMailSender).send(messageCaptor.capture()); + MimeMessage message = messageCaptor.getValue(); + assertThat(message.getSubject()).isEqualTo("testSubject"); + assertThat(message.getAllRecipients()[0]).hasToString("john.doe@example.com"); + assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); + assertThat(message.getContent()).isInstanceOf(String.class); + assertThat(message.getContent()).hasToString("testContent"); + assertThat(message.getDataHandler().getContentType()).isEqualTo("text/plain; charset=UTF-8"); + } + + @Test + void testSendHtmlEmail() throws Exception { + mailService.sendEmail("john.doe@example.com", "testSubject", "testContent", false, true); + verify(javaMailSender).send(messageCaptor.capture()); + MimeMessage message = messageCaptor.getValue(); + assertThat(message.getSubject()).isEqualTo("testSubject"); + assertThat(message.getAllRecipients()[0]).hasToString("john.doe@example.com"); + assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); + assertThat(message.getContent()).isInstanceOf(String.class); + assertThat(message.getContent()).hasToString("testContent"); + assertThat(message.getDataHandler().getContentType()).isEqualTo("text/html;charset=UTF-8"); + } + + @Test + void testSendMultipartEmail() throws Exception { + mailService.sendEmail("john.doe@example.com", "testSubject", "testContent", true, false); + verify(javaMailSender).send(messageCaptor.capture()); + MimeMessage message = messageCaptor.getValue(); + MimeMultipart mp = (MimeMultipart) message.getContent(); + MimeBodyPart part = (MimeBodyPart) ((MimeMultipart) mp.getBodyPart(0).getContent()).getBodyPart(0); + ByteArrayOutputStream aos = new ByteArrayOutputStream(); + part.writeTo(aos); + assertThat(message.getSubject()).isEqualTo("testSubject"); + assertThat(message.getAllRecipients()[0]).hasToString("john.doe@example.com"); + assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); + assertThat(message.getContent()).isInstanceOf(Multipart.class); + assertThat(aos).hasToString("\r\ntestContent"); + assertThat(part.getDataHandler().getContentType()).isEqualTo("text/plain; charset=UTF-8"); + } + + @Test + void testSendMultipartHtmlEmail() throws Exception { + mailService.sendEmail("john.doe@example.com", "testSubject", "testContent", true, true); + verify(javaMailSender).send(messageCaptor.capture()); + MimeMessage message = messageCaptor.getValue(); + MimeMultipart mp = (MimeMultipart) message.getContent(); + MimeBodyPart part = (MimeBodyPart) ((MimeMultipart) mp.getBodyPart(0).getContent()).getBodyPart(0); + ByteArrayOutputStream aos = new ByteArrayOutputStream(); + part.writeTo(aos); + assertThat(message.getSubject()).isEqualTo("testSubject"); + assertThat(message.getAllRecipients()[0]).hasToString("john.doe@example.com"); + assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); + assertThat(message.getContent()).isInstanceOf(Multipart.class); + assertThat(aos).hasToString("\r\ntestContent"); + assertThat(part.getDataHandler().getContentType()).isEqualTo("text/html;charset=UTF-8"); + } + + @Test + void testSendEmailFromTemplate() throws Exception { + User user = new User(); + user.setLogin("john"); + user.setEmail("john.doe@example.com"); + user.setLangKey("en"); + mailService.sendEmailFromTemplate(user, "mail/testEmail", "email.test.title"); + verify(javaMailSender).send(messageCaptor.capture()); + MimeMessage message = messageCaptor.getValue(); + assertThat(message.getSubject()).isEqualTo("test title"); + assertThat(message.getAllRecipients()[0]).hasToString(user.getEmail()); + assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); + assertThat(message.getContent().toString()).isEqualToNormalizingNewlines("<html>test title, http://127.0.0.1:8080, john</html>\n"); + assertThat(message.getDataHandler().getContentType()).isEqualTo("text/html;charset=UTF-8"); + } + + @Test + void testSendActivationEmail() throws Exception { + User user = new User(); + user.setLangKey(Constants.DEFAULT_LANGUAGE); + user.setLogin("john"); + user.setEmail("john.doe@example.com"); + mailService.sendActivationEmail(user); + verify(javaMailSender).send(messageCaptor.capture()); + MimeMessage message = messageCaptor.getValue(); + assertThat(message.getAllRecipients()[0]).hasToString(user.getEmail()); + assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); + assertThat(message.getContent().toString()).isNotEmpty(); + assertThat(message.getDataHandler().getContentType()).isEqualTo("text/html;charset=UTF-8"); + } + + @Test + void testCreationEmail() throws Exception { + User user = new User(); + user.setLangKey(Constants.DEFAULT_LANGUAGE); + user.setLogin("john"); + user.setEmail("john.doe@example.com"); + mailService.sendCreationEmail(user); + verify(javaMailSender).send(messageCaptor.capture()); + MimeMessage message = messageCaptor.getValue(); + assertThat(message.getAllRecipients()[0]).hasToString(user.getEmail()); + assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); + assertThat(message.getContent().toString()).isNotEmpty(); + assertThat(message.getDataHandler().getContentType()).isEqualTo("text/html;charset=UTF-8"); + } + + @Test + void testSendPasswordResetMail() throws Exception { + User user = new User(); + user.setLangKey(Constants.DEFAULT_LANGUAGE); + user.setLogin("john"); + user.setEmail("john.doe@example.com"); + mailService.sendPasswordResetMail(user); + verify(javaMailSender).send(messageCaptor.capture()); + MimeMessage message = messageCaptor.getValue(); + assertThat(message.getAllRecipients()[0]).hasToString(user.getEmail()); + assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); + assertThat(message.getContent().toString()).isNotEmpty(); + assertThat(message.getDataHandler().getContentType()).isEqualTo("text/html;charset=UTF-8"); + } + + @Test + void testSendEmailWithException() { + doThrow(MailSendException.class).when(javaMailSender).send(any(MimeMessage.class)); + try { + mailService.sendEmail("john.doe@example.com", "testSubject", "testContent", false, false); + } catch (Exception e) { + fail("Exception shouldn't have been thrown"); + } + } + + @Test + void testSendLocalizedEmailForAllSupportedLanguages() throws Exception { + User user = new User(); + user.setLogin("john"); + user.setEmail("john.doe@example.com"); + for (String langKey : languages) { + user.setLangKey(langKey); + mailService.sendEmailFromTemplate(user, "mail/testEmail", "email.test.title"); + verify(javaMailSender, atLeastOnce()).send(messageCaptor.capture()); + MimeMessage message = messageCaptor.getValue(); + + String propertyFilePath = "i18n/messages_" + getJavaLocale(langKey) + ".properties"; + URL resource = this.getClass().getClassLoader().getResource(propertyFilePath); + File file = new File(new URI(resource.getFile()).getPath()); + Properties properties = new Properties(); + properties.load(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8"))); + + String emailTitle = (String) properties.get("email.test.title"); + assertThat(message.getSubject()).isEqualTo(emailTitle); + assertThat(message.getContent().toString()) + .isEqualToNormalizingNewlines("<html>" + emailTitle + ", http://127.0.0.1:8080, john</html>\n"); + } + } + + /** + * Convert a lang key to the Java locale. + */ + private String getJavaLocale(String langKey) { + String javaLangKey = langKey; + Matcher matcher2 = PATTERN_LOCALE_2.matcher(langKey); + if (matcher2.matches()) { + javaLangKey = matcher2.group(1) + "_" + matcher2.group(2).toUpperCase(); + } + Matcher matcher3 = PATTERN_LOCALE_3.matcher(langKey); + if (matcher3.matches()) { + javaLangKey = matcher3.group(1) + "_" + matcher3.group(2) + "_" + matcher3.group(3).toUpperCase(); + } + return javaLangKey; + } +} diff --git a/pamapi/src/test/java/com/pollex/pam/service/UserServiceIT.java b/pamapi/src/test/java/com/pollex/pam/service/UserServiceIT.java new file mode 100644 index 0000000..9aa8c13 --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/service/UserServiceIT.java @@ -0,0 +1,185 @@ +package com.pollex.pam.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import com.pollex.pam.IntegrationTest; +import com.pollex.pam.config.Constants; +import com.pollex.pam.domain.User; +import com.pollex.pam.repository.UserRepository; +import com.pollex.pam.service.dto.AdminUserDTO; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Optional; +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.mock.mockito.MockBean; +import org.springframework.data.auditing.AuditingHandler; +import org.springframework.data.auditing.DateTimeProvider; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.transaction.annotation.Transactional; +import tech.jhipster.security.RandomUtil; + +/** + * Integration tests for {@link UserService}. + */ +@IntegrationTest +@Transactional +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; + + @Autowired + private AuditingHandler auditingHandler; + + @MockBean + private DateTimeProvider dateTimeProvider; + + private User user; + + @BeforeEach + public void init() { + user = new User(); + user.setLogin(DEFAULT_LOGIN); + user.setPassword(RandomStringUtils.random(60)); + user.setActivated(true); + user.setEmail(DEFAULT_EMAIL); + user.setFirstName(DEFAULT_FIRSTNAME); + user.setLastName(DEFAULT_LASTNAME); + user.setImageUrl(DEFAULT_IMAGEURL); + user.setLangKey(DEFAULT_LANGKEY); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.now())); + auditingHandler.setDateTimeProvider(dateTimeProvider); + } + + @Test + @Transactional + void assertThatUserMustExistToResetPassword() { + userRepository.saveAndFlush(user); + Optional<User> maybeUser = userService.requestPasswordReset("invalid.login@localhost"); + assertThat(maybeUser).isNotPresent(); + + maybeUser = userService.requestPasswordReset(user.getEmail()); + assertThat(maybeUser).isPresent(); + assertThat(maybeUser.orElse(null).getEmail()).isEqualTo(user.getEmail()); + assertThat(maybeUser.orElse(null).getResetDate()).isNotNull(); + assertThat(maybeUser.orElse(null).getResetKey()).isNotNull(); + } + + @Test + @Transactional + void assertThatOnlyActivatedUserCanRequestPasswordReset() { + user.setActivated(false); + userRepository.saveAndFlush(user); + + Optional<User> maybeUser = userService.requestPasswordReset(user.getLogin()); + assertThat(maybeUser).isNotPresent(); + userRepository.delete(user); + } + + @Test + @Transactional + void assertThatResetKeyMustNotBeOlderThan24Hours() { + Instant daysAgo = Instant.now().minus(25, ChronoUnit.HOURS); + String resetKey = RandomUtil.generateResetKey(); + user.setActivated(true); + user.setResetDate(daysAgo); + user.setResetKey(resetKey); + userRepository.saveAndFlush(user); + + Optional<User> maybeUser = userService.completePasswordReset("johndoe2", user.getResetKey()); + assertThat(maybeUser).isNotPresent(); + userRepository.delete(user); + } + + @Test + @Transactional + void assertThatResetKeyMustBeValid() { + Instant daysAgo = Instant.now().minus(25, ChronoUnit.HOURS); + user.setActivated(true); + user.setResetDate(daysAgo); + user.setResetKey("1234"); + userRepository.saveAndFlush(user); + + Optional<User> maybeUser = userService.completePasswordReset("johndoe2", user.getResetKey()); + assertThat(maybeUser).isNotPresent(); + userRepository.delete(user); + } + + @Test + @Transactional + void assertThatUserCanResetPassword() { + String oldPassword = user.getPassword(); + Instant daysAgo = Instant.now().minus(2, ChronoUnit.HOURS); + String resetKey = RandomUtil.generateResetKey(); + user.setActivated(true); + user.setResetDate(daysAgo); + user.setResetKey(resetKey); + userRepository.saveAndFlush(user); + + Optional<User> maybeUser = userService.completePasswordReset("johndoe2", user.getResetKey()); + assertThat(maybeUser).isPresent(); + assertThat(maybeUser.orElse(null).getResetDate()).isNull(); + assertThat(maybeUser.orElse(null).getResetKey()).isNull(); + assertThat(maybeUser.orElse(null).getPassword()).isNotEqualTo(oldPassword); + + userRepository.delete(user); + } + + @Test + @Transactional + void assertThatNotActivatedUsersWithNotNullActivationKeyCreatedBefore3DaysAreDeleted() { + Instant now = Instant.now(); + when(dateTimeProvider.getNow()).thenReturn(Optional.of(now.minus(4, ChronoUnit.DAYS))); + user.setActivated(false); + user.setActivationKey(RandomStringUtils.random(20)); + User dbUser = userRepository.saveAndFlush(user); + dbUser.setCreatedDate(now.minus(4, ChronoUnit.DAYS)); + userRepository.saveAndFlush(user); + Instant threeDaysAgo = now.minus(3, ChronoUnit.DAYS); + List<User> users = userRepository.findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(threeDaysAgo); + assertThat(users).isNotEmpty(); + userService.removeNotActivatedUsers(); + users = userRepository.findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(threeDaysAgo); + assertThat(users).isEmpty(); + } + + @Test + @Transactional + void assertThatNotActivatedUsersWithNullActivationKeyCreatedBefore3DaysAreNotDeleted() { + Instant now = Instant.now(); + when(dateTimeProvider.getNow()).thenReturn(Optional.of(now.minus(4, ChronoUnit.DAYS))); + user.setActivated(false); + User dbUser = userRepository.saveAndFlush(user); + dbUser.setCreatedDate(now.minus(4, ChronoUnit.DAYS)); + userRepository.saveAndFlush(user); + Instant threeDaysAgo = now.minus(3, ChronoUnit.DAYS); + List<User> users = userRepository.findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(threeDaysAgo); + assertThat(users).isEmpty(); + userService.removeNotActivatedUsers(); + Optional<User> maybeDbUser = userRepository.findById(dbUser.getId()); + assertThat(maybeDbUser).contains(dbUser); + } +} diff --git a/pamapi/src/test/java/com/pollex/pam/service/mapper/UserMapperTest.java b/pamapi/src/test/java/com/pollex/pam/service/mapper/UserMapperTest.java new file mode 100644 index 0000000..e4514c7 --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/service/mapper/UserMapperTest.java @@ -0,0 +1,132 @@ +package com.pollex.pam.service.mapper; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.pollex.pam.domain.User; +import com.pollex.pam.service.dto.AdminUserDTO; +import com.pollex.pam.service.dto.UserDTO; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link UserMapper}. + */ +class UserMapperTest { + + private static final String DEFAULT_LOGIN = "johndoe"; + private static final Long DEFAULT_ID = 1L; + + private UserMapper userMapper; + private User user; + private AdminUserDTO userDto; + + @BeforeEach + public void init() { + userMapper = new UserMapper(); + user = new User(); + user.setLogin(DEFAULT_LOGIN); + user.setPassword(RandomStringUtils.random(60)); + user.setActivated(true); + user.setEmail("johndoe@localhost"); + user.setFirstName("john"); + user.setLastName("doe"); + user.setImageUrl("image_url"); + user.setLangKey("en"); + + userDto = new AdminUserDTO(user); + } + + @Test + void usersToUserDTOsShouldMapOnlyNonNullUsers() { + List<User> users = new ArrayList<>(); + users.add(user); + users.add(null); + + List<UserDTO> userDTOS = userMapper.usersToUserDTOs(users); + + assertThat(userDTOS).isNotEmpty().size().isEqualTo(1); + } + + @Test + void userDTOsToUsersShouldMapOnlyNonNullUsers() { + List<AdminUserDTO> usersDto = new ArrayList<>(); + usersDto.add(userDto); + usersDto.add(null); + + List<User> users = userMapper.userDTOsToUsers(usersDto); + + assertThat(users).isNotEmpty().size().isEqualTo(1); + } + + @Test + void userDTOsToUsersWithAuthoritiesStringShouldMapToUsersWithAuthoritiesDomain() { + Set<String> authoritiesAsString = new HashSet<>(); + authoritiesAsString.add("ADMIN"); + userDto.setAuthorities(authoritiesAsString); + + List<AdminUserDTO> usersDto = new ArrayList<>(); + usersDto.add(userDto); + + List<User> users = userMapper.userDTOsToUsers(usersDto); + + 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 + void userDTOsToUsersMapWithNullAuthoritiesStringShouldReturnUserWithEmptyAuthorities() { + userDto.setAuthorities(null); + + List<AdminUserDTO> usersDto = new ArrayList<>(); + usersDto.add(userDto); + + List<User> users = userMapper.userDTOsToUsers(usersDto); + + assertThat(users).isNotEmpty().size().isEqualTo(1); + assertThat(users.get(0).getAuthorities()).isNotNull(); + assertThat(users.get(0).getAuthorities()).isEmpty(); + } + + @Test + void userDTOToUserMapWithAuthoritiesStringShouldReturnUserWithAuthorities() { + Set<String> 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 + void userDTOToUserMapWithNullAuthoritiesStringShouldReturnUserWithEmptyAuthorities() { + userDto.setAuthorities(null); + + User user = userMapper.userDTOToUser(userDto); + + assertThat(user).isNotNull(); + assertThat(user.getAuthorities()).isNotNull(); + assertThat(user.getAuthorities()).isEmpty(); + } + + @Test + void userDTOToUserMapWithNullUserShouldReturnNull() { + assertThat(userMapper.userDTOToUser(null)).isNull(); + } + + @Test + void testUserFromId() { + assertThat(userMapper.userFromId(DEFAULT_ID).getId()).isEqualTo(DEFAULT_ID); + assertThat(userMapper.userFromId(null)).isNull(); + } +} diff --git a/pamapi/src/test/java/com/pollex/pam/web/rest/AccountResourceIT.java b/pamapi/src/test/java/com/pollex/pam/web/rest/AccountResourceIT.java new file mode 100644 index 0000000..9ff6565 --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/web/rest/AccountResourceIT.java @@ -0,0 +1,760 @@ +package com.pollex.pam.web.rest; + +import static com.pollex.pam.web.rest.AccountResourceIT.TEST_USER_LOGIN; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import com.pollex.pam.IntegrationTest; +import com.pollex.pam.config.Constants; +import com.pollex.pam.domain.User; +import com.pollex.pam.repository.AuthorityRepository; +import com.pollex.pam.repository.UserRepository; +import com.pollex.pam.security.AuthoritiesConstants; +import com.pollex.pam.service.UserService; +import com.pollex.pam.service.dto.AdminUserDTO; +import com.pollex.pam.service.dto.PasswordChangeDTO; +import com.pollex.pam.service.dto.UserDTO; +import com.pollex.pam.web.rest.vm.KeyAndPasswordVM; +import com.pollex.pam.web.rest.vm.ManagedUserVM; +import java.time.Instant; +import java.util.*; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +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.crypto.password.PasswordEncoder; +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 AccountResource} REST controller. + */ +@AutoConfigureMockMvc +@WithMockUser(value = TEST_USER_LOGIN) +@IntegrationTest +class AccountResourceIT { + + static final String TEST_USER_LOGIN = "test"; + + @Autowired + private UserRepository userRepository; + + @Autowired + private AuthorityRepository authorityRepository; + + @Autowired + private UserService userService; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Autowired + private MockMvc restAccountMockMvc; + + @Test + @WithUnauthenticatedMockUser + void testNonAuthenticatedUser() throws Exception { + restAccountMockMvc + .perform(get("/api/authenticate").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().string("")); + } + + @Test + void testAuthenticatedUser() throws Exception { + restAccountMockMvc + .perform( + get("/api/authenticate") + .with(request -> { + request.setRemoteUser(TEST_USER_LOGIN); + return request; + }) + .accept(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(content().string(TEST_USER_LOGIN)); + } + + @Test + void testGetExistingAccount() throws Exception { + Set<String> authorities = new HashSet<>(); + authorities.add(AuthoritiesConstants.ADMIN); + + AdminUserDTO user = new AdminUserDTO(); + user.setLogin(TEST_USER_LOGIN); + user.setFirstName("john"); + user.setLastName("doe"); + user.setEmail("john.doe@jhipster.com"); + user.setImageUrl("http://placehold.it/50x50"); + user.setLangKey("en"); + user.setAuthorities(authorities); + userService.createUser(user); + + restAccountMockMvc + .perform(get("/api/account").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(jsonPath("$.login").value(TEST_USER_LOGIN)) + .andExpect(jsonPath("$.firstName").value("john")) + .andExpect(jsonPath("$.lastName").value("doe")) + .andExpect(jsonPath("$.email").value("john.doe@jhipster.com")) + .andExpect(jsonPath("$.imageUrl").value("http://placehold.it/50x50")) + .andExpect(jsonPath("$.langKey").value("en")) + .andExpect(jsonPath("$.authorities").value(AuthoritiesConstants.ADMIN)); + } + + @Test + void testGetUnknownAccount() throws Exception { + restAccountMockMvc + .perform(get("/api/account").accept(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(status().isInternalServerError()); + } + + @Test + @Transactional + void testRegisterValid() throws Exception { + ManagedUserVM validUser = new ManagedUserVM(); + validUser.setLogin("test-register-valid"); + validUser.setPassword("password"); + validUser.setFirstName("Alice"); + validUser.setLastName("Test"); + validUser.setEmail("test-register-valid@example.com"); + validUser.setImageUrl("http://placehold.it/50x50"); + validUser.setLangKey(Constants.DEFAULT_LANGUAGE); + validUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + assertThat(userRepository.findOneByLogin("test-register-valid")).isEmpty(); + + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(validUser))) + .andExpect(status().isCreated()); + + assertThat(userRepository.findOneByLogin("test-register-valid")).isPresent(); + } + + @Test + @Transactional + void testRegisterInvalidLogin() throws Exception { + ManagedUserVM invalidUser = new ManagedUserVM(); + invalidUser.setLogin("funky-log(n"); // <-- invalid + invalidUser.setPassword("password"); + invalidUser.setFirstName("Funky"); + invalidUser.setLastName("One"); + invalidUser.setEmail("funky@example.com"); + invalidUser.setActivated(true); + invalidUser.setImageUrl("http://placehold.it/50x50"); + invalidUser.setLangKey(Constants.DEFAULT_LANGUAGE); + invalidUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(invalidUser))) + .andExpect(status().isBadRequest()); + + Optional<User> user = userRepository.findOneByEmailIgnoreCase("funky@example.com"); + assertThat(user).isEmpty(); + } + + @Test + @Transactional + void testRegisterInvalidEmail() throws Exception { + ManagedUserVM invalidUser = new ManagedUserVM(); + invalidUser.setLogin("bob"); + invalidUser.setPassword("password"); + invalidUser.setFirstName("Bob"); + invalidUser.setLastName("Green"); + invalidUser.setEmail("invalid"); // <-- invalid + invalidUser.setActivated(true); + invalidUser.setImageUrl("http://placehold.it/50x50"); + invalidUser.setLangKey(Constants.DEFAULT_LANGUAGE); + invalidUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(invalidUser))) + .andExpect(status().isBadRequest()); + + Optional<User> user = userRepository.findOneByLogin("bob"); + assertThat(user).isEmpty(); + } + + @Test + @Transactional + void testRegisterInvalidPassword() throws Exception { + ManagedUserVM invalidUser = new ManagedUserVM(); + invalidUser.setLogin("bob"); + invalidUser.setPassword("123"); // password with only 3 digits + invalidUser.setFirstName("Bob"); + invalidUser.setLastName("Green"); + invalidUser.setEmail("bob@example.com"); + invalidUser.setActivated(true); + invalidUser.setImageUrl("http://placehold.it/50x50"); + invalidUser.setLangKey(Constants.DEFAULT_LANGUAGE); + invalidUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(invalidUser))) + .andExpect(status().isBadRequest()); + + Optional<User> user = userRepository.findOneByLogin("bob"); + assertThat(user).isEmpty(); + } + + @Test + @Transactional + void testRegisterNullPassword() throws Exception { + ManagedUserVM invalidUser = new ManagedUserVM(); + invalidUser.setLogin("bob"); + invalidUser.setPassword(null); // invalid null password + invalidUser.setFirstName("Bob"); + invalidUser.setLastName("Green"); + invalidUser.setEmail("bob@example.com"); + invalidUser.setActivated(true); + invalidUser.setImageUrl("http://placehold.it/50x50"); + invalidUser.setLangKey(Constants.DEFAULT_LANGUAGE); + invalidUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(invalidUser))) + .andExpect(status().isBadRequest()); + + Optional<User> user = userRepository.findOneByLogin("bob"); + assertThat(user).isEmpty(); + } + + @Test + @Transactional + void testRegisterDuplicateLogin() throws Exception { + // First registration + ManagedUserVM firstUser = new ManagedUserVM(); + firstUser.setLogin("alice"); + firstUser.setPassword("password"); + firstUser.setFirstName("Alice"); + firstUser.setLastName("Something"); + firstUser.setEmail("alice@example.com"); + firstUser.setImageUrl("http://placehold.it/50x50"); + firstUser.setLangKey(Constants.DEFAULT_LANGUAGE); + firstUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + // Duplicate login, different email + ManagedUserVM secondUser = new ManagedUserVM(); + secondUser.setLogin(firstUser.getLogin()); + secondUser.setPassword(firstUser.getPassword()); + secondUser.setFirstName(firstUser.getFirstName()); + secondUser.setLastName(firstUser.getLastName()); + secondUser.setEmail("alice2@example.com"); + secondUser.setImageUrl(firstUser.getImageUrl()); + secondUser.setLangKey(firstUser.getLangKey()); + secondUser.setCreatedBy(firstUser.getCreatedBy()); + secondUser.setCreatedDate(firstUser.getCreatedDate()); + secondUser.setLastModifiedBy(firstUser.getLastModifiedBy()); + secondUser.setLastModifiedDate(firstUser.getLastModifiedDate()); + secondUser.setAuthorities(new HashSet<>(firstUser.getAuthorities())); + + // First user + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(firstUser))) + .andExpect(status().isCreated()); + + // Second (non activated) user + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(secondUser))) + .andExpect(status().isCreated()); + + Optional<User> testUser = userRepository.findOneByEmailIgnoreCase("alice2@example.com"); + assertThat(testUser).isPresent(); + testUser.get().setActivated(true); + userRepository.save(testUser.get()); + + // Second (already activated) user + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(secondUser))) + .andExpect(status().is4xxClientError()); + } + + @Test + @Transactional + void testRegisterDuplicateEmail() throws Exception { + // First user + ManagedUserVM firstUser = new ManagedUserVM(); + firstUser.setLogin("test-register-duplicate-email"); + firstUser.setPassword("password"); + firstUser.setFirstName("Alice"); + firstUser.setLastName("Test"); + firstUser.setEmail("test-register-duplicate-email@example.com"); + firstUser.setImageUrl("http://placehold.it/50x50"); + firstUser.setLangKey(Constants.DEFAULT_LANGUAGE); + firstUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + // Register first user + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(firstUser))) + .andExpect(status().isCreated()); + + Optional<User> testUser1 = userRepository.findOneByLogin("test-register-duplicate-email"); + assertThat(testUser1).isPresent(); + + // Duplicate email, different login + ManagedUserVM secondUser = new ManagedUserVM(); + secondUser.setLogin("test-register-duplicate-email-2"); + secondUser.setPassword(firstUser.getPassword()); + secondUser.setFirstName(firstUser.getFirstName()); + secondUser.setLastName(firstUser.getLastName()); + secondUser.setEmail(firstUser.getEmail()); + secondUser.setImageUrl(firstUser.getImageUrl()); + secondUser.setLangKey(firstUser.getLangKey()); + secondUser.setAuthorities(new HashSet<>(firstUser.getAuthorities())); + + // Register second (non activated) user + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(secondUser))) + .andExpect(status().isCreated()); + + Optional<User> testUser2 = userRepository.findOneByLogin("test-register-duplicate-email"); + assertThat(testUser2).isEmpty(); + + Optional<User> testUser3 = userRepository.findOneByLogin("test-register-duplicate-email-2"); + assertThat(testUser3).isPresent(); + + // Duplicate email - with uppercase email address + ManagedUserVM userWithUpperCaseEmail = new ManagedUserVM(); + userWithUpperCaseEmail.setId(firstUser.getId()); + userWithUpperCaseEmail.setLogin("test-register-duplicate-email-3"); + userWithUpperCaseEmail.setPassword(firstUser.getPassword()); + userWithUpperCaseEmail.setFirstName(firstUser.getFirstName()); + userWithUpperCaseEmail.setLastName(firstUser.getLastName()); + userWithUpperCaseEmail.setEmail("TEST-register-duplicate-email@example.com"); + userWithUpperCaseEmail.setImageUrl(firstUser.getImageUrl()); + userWithUpperCaseEmail.setLangKey(firstUser.getLangKey()); + userWithUpperCaseEmail.setAuthorities(new HashSet<>(firstUser.getAuthorities())); + + // Register third (not activated) user + restAccountMockMvc + .perform( + post("/api/register") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(userWithUpperCaseEmail)) + ) + .andExpect(status().isCreated()); + + Optional<User> testUser4 = userRepository.findOneByLogin("test-register-duplicate-email-3"); + assertThat(testUser4).isPresent(); + assertThat(testUser4.get().getEmail()).isEqualTo("test-register-duplicate-email@example.com"); + + testUser4.get().setActivated(true); + userService.updateUser((new AdminUserDTO(testUser4.get()))); + + // Register 4th (already activated) user + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(secondUser))) + .andExpect(status().is4xxClientError()); + } + + @Test + @Transactional + void testRegisterAdminIsIgnored() throws Exception { + ManagedUserVM validUser = new ManagedUserVM(); + validUser.setLogin("badguy"); + validUser.setPassword("password"); + validUser.setFirstName("Bad"); + validUser.setLastName("Guy"); + validUser.setEmail("badguy@example.com"); + validUser.setActivated(true); + validUser.setImageUrl("http://placehold.it/50x50"); + validUser.setLangKey(Constants.DEFAULT_LANGUAGE); + validUser.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN)); + + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(validUser))) + .andExpect(status().isCreated()); + + Optional<User> userDup = userRepository.findOneWithAuthoritiesByLogin("badguy"); + assertThat(userDup).isPresent(); + assertThat(userDup.get().getAuthorities()) + .hasSize(1) + .containsExactly(authorityRepository.findById(AuthoritiesConstants.USER).get()); + } + + @Test + @Transactional + void testActivateAccount() throws Exception { + final String activationKey = "some activation key"; + User user = new User(); + user.setLogin("activate-account"); + user.setEmail("activate-account@example.com"); + user.setPassword(RandomStringUtils.random(60)); + user.setActivated(false); + user.setActivationKey(activationKey); + + userRepository.saveAndFlush(user); + + restAccountMockMvc.perform(get("/api/activate?key={activationKey}", activationKey)).andExpect(status().isOk()); + + user = userRepository.findOneByLogin(user.getLogin()).orElse(null); + assertThat(user.isActivated()).isTrue(); + } + + @Test + @Transactional + void testActivateAccountWithWrongKey() throws Exception { + restAccountMockMvc.perform(get("/api/activate?key=wrongActivationKey")).andExpect(status().isInternalServerError()); + } + + @Test + @Transactional + @WithMockUser("save-account") + void testSaveAccount() throws Exception { + User user = new User(); + user.setLogin("save-account"); + user.setEmail("save-account@example.com"); + user.setPassword(RandomStringUtils.random(60)); + user.setActivated(true); + userRepository.saveAndFlush(user); + + AdminUserDTO userDTO = new AdminUserDTO(); + userDTO.setLogin("not-used"); + userDTO.setFirstName("firstname"); + userDTO.setLastName("lastname"); + userDTO.setEmail("save-account@example.com"); + userDTO.setActivated(false); + userDTO.setImageUrl("http://placehold.it/50x50"); + userDTO.setLangKey(Constants.DEFAULT_LANGUAGE); + userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN)); + + restAccountMockMvc + .perform(post("/api/account").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(userDTO))) + .andExpect(status().isOk()); + + User updatedUser = userRepository.findOneWithAuthoritiesByLogin(user.getLogin()).orElse(null); + assertThat(updatedUser.getFirstName()).isEqualTo(userDTO.getFirstName()); + assertThat(updatedUser.getLastName()).isEqualTo(userDTO.getLastName()); + assertThat(updatedUser.getEmail()).isEqualTo(userDTO.getEmail()); + assertThat(updatedUser.getLangKey()).isEqualTo(userDTO.getLangKey()); + assertThat(updatedUser.getPassword()).isEqualTo(user.getPassword()); + assertThat(updatedUser.getImageUrl()).isEqualTo(userDTO.getImageUrl()); + assertThat(updatedUser.isActivated()).isTrue(); + assertThat(updatedUser.getAuthorities()).isEmpty(); + } + + @Test + @Transactional + @WithMockUser("save-invalid-email") + void testSaveInvalidEmail() throws Exception { + User user = new User(); + user.setLogin("save-invalid-email"); + user.setEmail("save-invalid-email@example.com"); + user.setPassword(RandomStringUtils.random(60)); + user.setActivated(true); + + userRepository.saveAndFlush(user); + + AdminUserDTO userDTO = new AdminUserDTO(); + userDTO.setLogin("not-used"); + userDTO.setFirstName("firstname"); + userDTO.setLastName("lastname"); + userDTO.setEmail("invalid email"); + userDTO.setActivated(false); + userDTO.setImageUrl("http://placehold.it/50x50"); + userDTO.setLangKey(Constants.DEFAULT_LANGUAGE); + userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN)); + + restAccountMockMvc + .perform(post("/api/account").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(userDTO))) + .andExpect(status().isBadRequest()); + + assertThat(userRepository.findOneByEmailIgnoreCase("invalid email")).isNotPresent(); + } + + @Test + @Transactional + @WithMockUser("save-existing-email") + void testSaveExistingEmail() throws Exception { + User user = new User(); + user.setLogin("save-existing-email"); + user.setEmail("save-existing-email@example.com"); + user.setPassword(RandomStringUtils.random(60)); + user.setActivated(true); + userRepository.saveAndFlush(user); + + User anotherUser = new User(); + anotherUser.setLogin("save-existing-email2"); + anotherUser.setEmail("save-existing-email2@example.com"); + anotherUser.setPassword(RandomStringUtils.random(60)); + anotherUser.setActivated(true); + + userRepository.saveAndFlush(anotherUser); + + AdminUserDTO userDTO = new AdminUserDTO(); + userDTO.setLogin("not-used"); + userDTO.setFirstName("firstname"); + userDTO.setLastName("lastname"); + userDTO.setEmail("save-existing-email2@example.com"); + userDTO.setActivated(false); + userDTO.setImageUrl("http://placehold.it/50x50"); + userDTO.setLangKey(Constants.DEFAULT_LANGUAGE); + userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN)); + + restAccountMockMvc + .perform(post("/api/account").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(userDTO))) + .andExpect(status().isBadRequest()); + + User updatedUser = userRepository.findOneByLogin("save-existing-email").orElse(null); + assertThat(updatedUser.getEmail()).isEqualTo("save-existing-email@example.com"); + } + + @Test + @Transactional + @WithMockUser("save-existing-email-and-login") + void testSaveExistingEmailAndLogin() throws Exception { + User user = new User(); + user.setLogin("save-existing-email-and-login"); + user.setEmail("save-existing-email-and-login@example.com"); + user.setPassword(RandomStringUtils.random(60)); + user.setActivated(true); + userRepository.saveAndFlush(user); + + AdminUserDTO userDTO = new AdminUserDTO(); + userDTO.setLogin("not-used"); + userDTO.setFirstName("firstname"); + userDTO.setLastName("lastname"); + userDTO.setEmail("save-existing-email-and-login@example.com"); + userDTO.setActivated(false); + userDTO.setImageUrl("http://placehold.it/50x50"); + userDTO.setLangKey(Constants.DEFAULT_LANGUAGE); + userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN)); + + restAccountMockMvc + .perform(post("/api/account").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(userDTO))) + .andExpect(status().isOk()); + + User updatedUser = userRepository.findOneByLogin("save-existing-email-and-login").orElse(null); + assertThat(updatedUser.getEmail()).isEqualTo("save-existing-email-and-login@example.com"); + } + + @Test + @Transactional + @WithMockUser("change-password-wrong-existing-password") + void testChangePasswordWrongExistingPassword() throws Exception { + User user = new User(); + String currentPassword = RandomStringUtils.random(60); + user.setPassword(passwordEncoder.encode(currentPassword)); + user.setLogin("change-password-wrong-existing-password"); + user.setEmail("change-password-wrong-existing-password@example.com"); + userRepository.saveAndFlush(user); + + restAccountMockMvc + .perform( + post("/api/account/change-password") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(new PasswordChangeDTO("1" + currentPassword, "new password"))) + ) + .andExpect(status().isBadRequest()); + + User updatedUser = userRepository.findOneByLogin("change-password-wrong-existing-password").orElse(null); + assertThat(passwordEncoder.matches("new password", updatedUser.getPassword())).isFalse(); + assertThat(passwordEncoder.matches(currentPassword, updatedUser.getPassword())).isTrue(); + } + + @Test + @Transactional + @WithMockUser("change-password") + void testChangePassword() throws Exception { + User user = new User(); + String currentPassword = RandomStringUtils.random(60); + user.setPassword(passwordEncoder.encode(currentPassword)); + user.setLogin("change-password"); + user.setEmail("change-password@example.com"); + userRepository.saveAndFlush(user); + + restAccountMockMvc + .perform( + post("/api/account/change-password") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(new PasswordChangeDTO(currentPassword, "new password"))) + ) + .andExpect(status().isOk()); + + User updatedUser = userRepository.findOneByLogin("change-password").orElse(null); + assertThat(passwordEncoder.matches("new password", updatedUser.getPassword())).isTrue(); + } + + @Test + @Transactional + @WithMockUser("change-password-too-small") + void testChangePasswordTooSmall() throws Exception { + User user = new User(); + String currentPassword = RandomStringUtils.random(60); + user.setPassword(passwordEncoder.encode(currentPassword)); + user.setLogin("change-password-too-small"); + user.setEmail("change-password-too-small@example.com"); + userRepository.saveAndFlush(user); + + String newPassword = RandomStringUtils.random(ManagedUserVM.PASSWORD_MIN_LENGTH - 1); + + restAccountMockMvc + .perform( + post("/api/account/change-password") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(new PasswordChangeDTO(currentPassword, newPassword))) + ) + .andExpect(status().isBadRequest()); + + User updatedUser = userRepository.findOneByLogin("change-password-too-small").orElse(null); + assertThat(updatedUser.getPassword()).isEqualTo(user.getPassword()); + } + + @Test + @Transactional + @WithMockUser("change-password-too-long") + void testChangePasswordTooLong() throws Exception { + User user = new User(); + String currentPassword = RandomStringUtils.random(60); + user.setPassword(passwordEncoder.encode(currentPassword)); + user.setLogin("change-password-too-long"); + user.setEmail("change-password-too-long@example.com"); + userRepository.saveAndFlush(user); + + String newPassword = RandomStringUtils.random(ManagedUserVM.PASSWORD_MAX_LENGTH + 1); + + restAccountMockMvc + .perform( + post("/api/account/change-password") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(new PasswordChangeDTO(currentPassword, newPassword))) + ) + .andExpect(status().isBadRequest()); + + User updatedUser = userRepository.findOneByLogin("change-password-too-long").orElse(null); + assertThat(updatedUser.getPassword()).isEqualTo(user.getPassword()); + } + + @Test + @Transactional + @WithMockUser("change-password-empty") + void testChangePasswordEmpty() throws Exception { + User user = new User(); + String currentPassword = RandomStringUtils.random(60); + user.setPassword(passwordEncoder.encode(currentPassword)); + user.setLogin("change-password-empty"); + user.setEmail("change-password-empty@example.com"); + userRepository.saveAndFlush(user); + + restAccountMockMvc + .perform( + post("/api/account/change-password") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(new PasswordChangeDTO(currentPassword, ""))) + ) + .andExpect(status().isBadRequest()); + + User updatedUser = userRepository.findOneByLogin("change-password-empty").orElse(null); + assertThat(updatedUser.getPassword()).isEqualTo(user.getPassword()); + } + + @Test + @Transactional + void testRequestPasswordReset() throws Exception { + User user = new User(); + user.setPassword(RandomStringUtils.random(60)); + user.setActivated(true); + user.setLogin("password-reset"); + user.setEmail("password-reset@example.com"); + userRepository.saveAndFlush(user); + + restAccountMockMvc + .perform(post("/api/account/reset-password/init").content("password-reset@example.com")) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void testRequestPasswordResetUpperCaseEmail() throws Exception { + User user = new User(); + user.setPassword(RandomStringUtils.random(60)); + user.setActivated(true); + user.setLogin("password-reset-upper-case"); + user.setEmail("password-reset-upper-case@example.com"); + userRepository.saveAndFlush(user); + + restAccountMockMvc + .perform(post("/api/account/reset-password/init").content("password-reset-upper-case@EXAMPLE.COM")) + .andExpect(status().isOk()); + } + + @Test + void testRequestPasswordResetWrongEmail() throws Exception { + restAccountMockMvc + .perform(post("/api/account/reset-password/init").content("password-reset-wrong-email@example.com")) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void testFinishPasswordReset() throws Exception { + User user = new User(); + user.setPassword(RandomStringUtils.random(60)); + user.setLogin("finish-password-reset"); + user.setEmail("finish-password-reset@example.com"); + user.setResetDate(Instant.now().plusSeconds(60)); + user.setResetKey("reset key"); + userRepository.saveAndFlush(user); + + KeyAndPasswordVM keyAndPassword = new KeyAndPasswordVM(); + keyAndPassword.setKey(user.getResetKey()); + keyAndPassword.setNewPassword("new password"); + + restAccountMockMvc + .perform( + post("/api/account/reset-password/finish") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(keyAndPassword)) + ) + .andExpect(status().isOk()); + + User updatedUser = userRepository.findOneByLogin(user.getLogin()).orElse(null); + assertThat(passwordEncoder.matches(keyAndPassword.getNewPassword(), updatedUser.getPassword())).isTrue(); + } + + @Test + @Transactional + void testFinishPasswordResetTooSmall() throws Exception { + User user = new User(); + user.setPassword(RandomStringUtils.random(60)); + user.setLogin("finish-password-reset-too-small"); + user.setEmail("finish-password-reset-too-small@example.com"); + user.setResetDate(Instant.now().plusSeconds(60)); + user.setResetKey("reset key too small"); + userRepository.saveAndFlush(user); + + KeyAndPasswordVM keyAndPassword = new KeyAndPasswordVM(); + keyAndPassword.setKey(user.getResetKey()); + keyAndPassword.setNewPassword("foo"); + + restAccountMockMvc + .perform( + post("/api/account/reset-password/finish") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(keyAndPassword)) + ) + .andExpect(status().isBadRequest()); + + User updatedUser = userRepository.findOneByLogin(user.getLogin()).orElse(null); + assertThat(passwordEncoder.matches(keyAndPassword.getNewPassword(), updatedUser.getPassword())).isFalse(); + } + + @Test + @Transactional + void testFinishPasswordResetWrongKey() throws Exception { + KeyAndPasswordVM keyAndPassword = new KeyAndPasswordVM(); + keyAndPassword.setKey("wrong reset key"); + keyAndPassword.setNewPassword("new password"); + + restAccountMockMvc + .perform( + post("/api/account/reset-password/finish") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(keyAndPassword)) + ) + .andExpect(status().isInternalServerError()); + } +} diff --git a/pamapi/src/test/java/com/pollex/pam/web/rest/PublicUserResourceIT.java b/pamapi/src/test/java/com/pollex/pam/web/rest/PublicUserResourceIT.java new file mode 100644 index 0000000..72ece3d --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/web/rest/PublicUserResourceIT.java @@ -0,0 +1,99 @@ +package com.pollex.pam.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 com.pollex.pam.IntegrationTest; +import com.pollex.pam.domain.User; +import com.pollex.pam.repository.UserRepository; +import com.pollex.pam.security.AuthoritiesConstants; +import javax.persistence.EntityManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.cache.CacheManager; +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 CacheManager cacheManager; + + @Autowired + private MockMvc restUserMockMvc; + + private User user; + + @BeforeEach + public void setup() { + cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE).clear(); + cacheManager.getCache(UserRepository.USERS_BY_EMAIL_CACHE).clear(); + } + + @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()); + } + + @Test + @Transactional + void getAllAuthorities() throws Exception { + restUserMockMvc + .perform(get("/api/authorities").accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$").value(hasItems(AuthoritiesConstants.USER, AuthoritiesConstants.ADMIN))); + } + + @Test + @Transactional + void getAllUsersSortedByParameters() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + + restUserMockMvc.perform(get("/api/users?sort=resetKey,desc").accept(MediaType.APPLICATION_JSON)).andExpect(status().isBadRequest()); + restUserMockMvc.perform(get("/api/users?sort=password,desc").accept(MediaType.APPLICATION_JSON)).andExpect(status().isBadRequest()); + restUserMockMvc + .perform(get("/api/users?sort=resetKey,id,desc").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + restUserMockMvc.perform(get("/api/users?sort=id,desc").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()); + } +} diff --git a/pamapi/src/test/java/com/pollex/pam/web/rest/TestUtil.java b/pamapi/src/test/java/com/pollex/pam/web/rest/TestUtil.java new file mode 100644 index 0000000..cb4437d --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/web/rest/TestUtil.java @@ -0,0 +1,206 @@ +package com.pollex.pam.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 java.io.IOException; +import java.math.BigDecimal; +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 org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; +import org.hamcrest.TypeSafeMatcher; +import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar; +import org.springframework.format.support.DefaultFormattingConversionService; +import org.springframework.format.support.FormattingConversionService; + +/** + * Utility class for testing REST controllers. + */ +public final class TestUtil { + + private static final ObjectMapper mapper = createObjectMapper(); + + 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; + } + + /** + * 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<String> { + + 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); + } + + /** + * 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<Number> { + + 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. + */ + public static <T> void equalsVerifier(Class<T> clazz) throws Exception { + T domainObject1 = clazz.getConstructor().newInstance(); + assertThat(domainObject1.toString()).isNotNull(); + assertThat(domainObject1).isEqualTo(domainObject1); + assertThat(domainObject1).hasSameHashCodeAs(domainObject1); + // 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).hasSameHashCodeAs(domainObject2); + } + + /** + * 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 <T> 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 <T> List<T> findAll(EntityManager em, Class<T> clss) { + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery<T> cq = cb.createQuery(clss); + Root<T> rootEntry = cq.from(clss); + CriteriaQuery<T> all = cq.select(rootEntry); + TypedQuery<T> allQuery = em.createQuery(all); + return allQuery.getResultList(); + } + + private TestUtil() {} +} diff --git a/pamapi/src/test/java/com/pollex/pam/web/rest/UserJWTControllerIT.java b/pamapi/src/test/java/com/pollex/pam/web/rest/UserJWTControllerIT.java new file mode 100644 index 0000000..094e9ce --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/web/rest/UserJWTControllerIT.java @@ -0,0 +1,98 @@ +package com.pollex.pam.web.rest; + +import static org.hamcrest.Matchers.emptyString; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.pollex.pam.IntegrationTest; +import com.pollex.pam.domain.User; +import com.pollex.pam.repository.UserRepository; +import com.pollex.pam.web.rest.vm.LoginVM; +import org.junit.jupiter.api.Test; +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.crypto.password.PasswordEncoder; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +/** + * Integration tests for the {@link UserJWTController} REST controller. + */ +@AutoConfigureMockMvc +@IntegrationTest +class UserJWTControllerIT { + + @Autowired + private UserRepository userRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Autowired + private MockMvc mockMvc; + + @Test + @Transactional + void testAuthorize() throws Exception { + User user = new User(); + user.setLogin("user-jwt-controller"); + user.setEmail("user-jwt-controller@example.com"); + user.setActivated(true); + user.setPassword(passwordEncoder.encode("test")); + + userRepository.saveAndFlush(user); + + LoginVM login = new LoginVM(); + login.setUsername("user-jwt-controller"); + login.setPassword("test"); + mockMvc + .perform(post("/api/authenticate").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(login))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id_token").isString()) + .andExpect(jsonPath("$.id_token").isNotEmpty()) + .andExpect(header().string("Authorization", not(nullValue()))) + .andExpect(header().string("Authorization", not(is(emptyString())))); + } + + @Test + @Transactional + void testAuthorizeWithRememberMe() throws Exception { + User user = new User(); + user.setLogin("user-jwt-controller-remember-me"); + user.setEmail("user-jwt-controller-remember-me@example.com"); + user.setActivated(true); + user.setPassword(passwordEncoder.encode("test")); + + userRepository.saveAndFlush(user); + + LoginVM login = new LoginVM(); + login.setUsername("user-jwt-controller-remember-me"); + login.setPassword("test"); + login.setRememberMe(true); + mockMvc + .perform(post("/api/authenticate").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(login))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id_token").isString()) + .andExpect(jsonPath("$.id_token").isNotEmpty()) + .andExpect(header().string("Authorization", not(nullValue()))) + .andExpect(header().string("Authorization", not(is(emptyString())))); + } + + @Test + void testAuthorizeFails() throws Exception { + LoginVM login = new LoginVM(); + login.setUsername("wrong-user"); + login.setPassword("wrong password"); + mockMvc + .perform(post("/api/authenticate").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(login))) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.id_token").doesNotExist()) + .andExpect(header().doesNotExist("Authorization")); + } +} diff --git a/pamapi/src/test/java/com/pollex/pam/web/rest/UserResourceIT.java b/pamapi/src/test/java/com/pollex/pam/web/rest/UserResourceIT.java new file mode 100644 index 0000000..069730b --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/web/rest/UserResourceIT.java @@ -0,0 +1,583 @@ +package com.pollex.pam.web.rest; + +import static org.assertj.core.api.Assertions.assertThat; +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 com.pollex.pam.IntegrationTest; +import com.pollex.pam.domain.Authority; +import com.pollex.pam.domain.User; +import com.pollex.pam.repository.UserRepository; +import com.pollex.pam.security.AuthoritiesConstants; +import com.pollex.pam.service.dto.AdminUserDTO; +import com.pollex.pam.service.dto.UserDTO; +import com.pollex.pam.service.mapper.UserMapper; +import com.pollex.pam.web.rest.vm.ManagedUserVM; +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.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.cache.CacheManager; +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 UserResourceIT { + + private static final String DEFAULT_LOGIN = "johndoe"; + private static final String UPDATED_LOGIN = "jhipster"; + + private static final Long DEFAULT_ID = 1L; + + private static final String DEFAULT_PASSWORD = "passjohndoe"; + private static final String UPDATED_PASSWORD = "passjhipster"; + + private static final String DEFAULT_EMAIL = "johndoe@localhost"; + private static final String UPDATED_EMAIL = "jhipster@localhost"; + + private static final String DEFAULT_FIRSTNAME = "john"; + private static final String UPDATED_FIRSTNAME = "jhipsterFirstName"; + + private static final String DEFAULT_LASTNAME = "doe"; + private static final String UPDATED_LASTNAME = "jhipsterLastName"; + + private static final String DEFAULT_IMAGEURL = "http://placehold.it/50x50"; + private static final String UPDATED_IMAGEURL = "http://placehold.it/40x40"; + + private static final String DEFAULT_LANGKEY = "en"; + private static final String UPDATED_LANGKEY = "fr"; + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserMapper userMapper; + + @Autowired + private EntityManager em; + + @Autowired + private CacheManager cacheManager; + + @Autowired + private MockMvc restUserMockMvc; + + private User user; + + @BeforeEach + public void setup() { + cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE).clear(); + cacheManager.getCache(UserRepository.USERS_BY_EMAIL_CACHE).clear(); + } + + /** + * 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.setLogin(DEFAULT_LOGIN + RandomStringUtils.randomAlphabetic(5)); + user.setPassword(RandomStringUtils.random(60)); + 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; + } + + /** + * 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 + void createUser() throws Exception { + int databaseSizeBeforeCreate = userRepository.findAll().size(); + + // Create the User + ManagedUserVM managedUserVM = new ManagedUserVM(); + managedUserVM.setLogin(DEFAULT_LOGIN); + managedUserVM.setPassword(DEFAULT_PASSWORD); + managedUserVM.setFirstName(DEFAULT_FIRSTNAME); + managedUserVM.setLastName(DEFAULT_LASTNAME); + managedUserVM.setEmail(DEFAULT_EMAIL); + managedUserVM.setActivated(true); + managedUserVM.setImageUrl(DEFAULT_IMAGEURL); + managedUserVM.setLangKey(DEFAULT_LANGKEY); + managedUserVM.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + restUserMockMvc + .perform( + post("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(managedUserVM)) + ) + .andExpect(status().isCreated()); + + // Validate the User in the database + assertPersistedUsers(users -> { + assertThat(users).hasSize(databaseSizeBeforeCreate + 1); + User testUser = users.get(users.size() - 1); + assertThat(testUser.getLogin()).isEqualTo(DEFAULT_LOGIN); + assertThat(testUser.getFirstName()).isEqualTo(DEFAULT_FIRSTNAME); + assertThat(testUser.getLastName()).isEqualTo(DEFAULT_LASTNAME); + assertThat(testUser.getEmail()).isEqualTo(DEFAULT_EMAIL); + assertThat(testUser.getImageUrl()).isEqualTo(DEFAULT_IMAGEURL); + assertThat(testUser.getLangKey()).isEqualTo(DEFAULT_LANGKEY); + }); + } + + @Test + @Transactional + void createUserWithExistingId() throws Exception { + int databaseSizeBeforeCreate = userRepository.findAll().size(); + + ManagedUserVM managedUserVM = new ManagedUserVM(); + managedUserVM.setId(DEFAULT_ID); + managedUserVM.setLogin(DEFAULT_LOGIN); + managedUserVM.setPassword(DEFAULT_PASSWORD); + managedUserVM.setFirstName(DEFAULT_FIRSTNAME); + managedUserVM.setLastName(DEFAULT_LASTNAME); + managedUserVM.setEmail(DEFAULT_EMAIL); + managedUserVM.setActivated(true); + managedUserVM.setImageUrl(DEFAULT_IMAGEURL); + managedUserVM.setLangKey(DEFAULT_LANGKEY); + managedUserVM.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + // An entity with an existing ID cannot be created, so this API call must fail + restUserMockMvc + .perform( + post("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(managedUserVM)) + ) + .andExpect(status().isBadRequest()); + + // Validate the User in the database + assertPersistedUsers(users -> assertThat(users).hasSize(databaseSizeBeforeCreate)); + } + + @Test + @Transactional + void createUserWithExistingLogin() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + int databaseSizeBeforeCreate = userRepository.findAll().size(); + + ManagedUserVM managedUserVM = new ManagedUserVM(); + managedUserVM.setLogin(DEFAULT_LOGIN); // this login should already be used + managedUserVM.setPassword(DEFAULT_PASSWORD); + managedUserVM.setFirstName(DEFAULT_FIRSTNAME); + managedUserVM.setLastName(DEFAULT_LASTNAME); + managedUserVM.setEmail("anothermail@localhost"); + managedUserVM.setActivated(true); + managedUserVM.setImageUrl(DEFAULT_IMAGEURL); + managedUserVM.setLangKey(DEFAULT_LANGKEY); + managedUserVM.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + // Create the User + restUserMockMvc + .perform( + post("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(managedUserVM)) + ) + .andExpect(status().isBadRequest()); + + // Validate the User in the database + assertPersistedUsers(users -> assertThat(users).hasSize(databaseSizeBeforeCreate)); + } + + @Test + @Transactional + void createUserWithExistingEmail() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + int databaseSizeBeforeCreate = userRepository.findAll().size(); + + ManagedUserVM managedUserVM = new ManagedUserVM(); + managedUserVM.setLogin("anotherlogin"); + managedUserVM.setPassword(DEFAULT_PASSWORD); + managedUserVM.setFirstName(DEFAULT_FIRSTNAME); + managedUserVM.setLastName(DEFAULT_LASTNAME); + managedUserVM.setEmail(DEFAULT_EMAIL); // this email should already be used + managedUserVM.setActivated(true); + managedUserVM.setImageUrl(DEFAULT_IMAGEURL); + managedUserVM.setLangKey(DEFAULT_LANGKEY); + managedUserVM.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + // Create the User + restUserMockMvc + .perform( + post("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(managedUserVM)) + ) + .andExpect(status().isBadRequest()); + + // Validate the User in the database + assertPersistedUsers(users -> assertThat(users).hasSize(databaseSizeBeforeCreate)); + } + + @Test + @Transactional + void getAllUsers() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + + // Get all the users + restUserMockMvc + .perform(get("/api/admin/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("$.[*].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 + void getUser() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + + assertThat(cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE).get(user.getLogin())).isNull(); + + // Get the user + restUserMockMvc + .perform(get("/api/admin/users/{login}", user.getLogin())) + .andExpect(status().isOk()) + .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)); + + assertThat(cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE).get(user.getLogin())).isNotNull(); + } + + @Test + @Transactional + void getNonExistingUser() throws Exception { + restUserMockMvc.perform(get("/api/admin/users/unknown")).andExpect(status().isNotFound()); + } + + @Test + @Transactional + void updateUser() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + int databaseSizeBeforeUpdate = userRepository.findAll().size(); + + // Update the user + User updatedUser = userRepository.findById(user.getId()).get(); + + ManagedUserVM managedUserVM = new ManagedUserVM(); + managedUserVM.setId(updatedUser.getId()); + managedUserVM.setLogin(updatedUser.getLogin()); + managedUserVM.setPassword(UPDATED_PASSWORD); + managedUserVM.setFirstName(UPDATED_FIRSTNAME); + managedUserVM.setLastName(UPDATED_LASTNAME); + managedUserVM.setEmail(UPDATED_EMAIL); + managedUserVM.setActivated(updatedUser.isActivated()); + managedUserVM.setImageUrl(UPDATED_IMAGEURL); + managedUserVM.setLangKey(UPDATED_LANGKEY); + managedUserVM.setCreatedBy(updatedUser.getCreatedBy()); + managedUserVM.setCreatedDate(updatedUser.getCreatedDate()); + managedUserVM.setLastModifiedBy(updatedUser.getLastModifiedBy()); + managedUserVM.setLastModifiedDate(updatedUser.getLastModifiedDate()); + managedUserVM.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + restUserMockMvc + .perform( + put("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(managedUserVM)) + ) + .andExpect(status().isOk()); + + // Validate the User in the database + assertPersistedUsers(users -> { + assertThat(users).hasSize(databaseSizeBeforeUpdate); + User testUser = users.stream().filter(usr -> usr.getId().equals(updatedUser.getId())).findFirst().get(); + assertThat(testUser.getFirstName()).isEqualTo(UPDATED_FIRSTNAME); + assertThat(testUser.getLastName()).isEqualTo(UPDATED_LASTNAME); + assertThat(testUser.getEmail()).isEqualTo(UPDATED_EMAIL); + assertThat(testUser.getImageUrl()).isEqualTo(UPDATED_IMAGEURL); + assertThat(testUser.getLangKey()).isEqualTo(UPDATED_LANGKEY); + }); + } + + @Test + @Transactional + void updateUserLogin() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + int databaseSizeBeforeUpdate = userRepository.findAll().size(); + + // Update the user + User updatedUser = userRepository.findById(user.getId()).get(); + + ManagedUserVM managedUserVM = new ManagedUserVM(); + managedUserVM.setId(updatedUser.getId()); + managedUserVM.setLogin(UPDATED_LOGIN); + managedUserVM.setPassword(UPDATED_PASSWORD); + managedUserVM.setFirstName(UPDATED_FIRSTNAME); + managedUserVM.setLastName(UPDATED_LASTNAME); + managedUserVM.setEmail(UPDATED_EMAIL); + managedUserVM.setActivated(updatedUser.isActivated()); + managedUserVM.setImageUrl(UPDATED_IMAGEURL); + managedUserVM.setLangKey(UPDATED_LANGKEY); + managedUserVM.setCreatedBy(updatedUser.getCreatedBy()); + managedUserVM.setCreatedDate(updatedUser.getCreatedDate()); + managedUserVM.setLastModifiedBy(updatedUser.getLastModifiedBy()); + managedUserVM.setLastModifiedDate(updatedUser.getLastModifiedDate()); + managedUserVM.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + restUserMockMvc + .perform( + put("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(managedUserVM)) + ) + .andExpect(status().isOk()); + + // Validate the User in the database + assertPersistedUsers(users -> { + assertThat(users).hasSize(databaseSizeBeforeUpdate); + User testUser = users.stream().filter(usr -> usr.getId().equals(updatedUser.getId())).findFirst().get(); + assertThat(testUser.getLogin()).isEqualTo(UPDATED_LOGIN); + assertThat(testUser.getFirstName()).isEqualTo(UPDATED_FIRSTNAME); + assertThat(testUser.getLastName()).isEqualTo(UPDATED_LASTNAME); + assertThat(testUser.getEmail()).isEqualTo(UPDATED_EMAIL); + assertThat(testUser.getImageUrl()).isEqualTo(UPDATED_IMAGEURL); + assertThat(testUser.getLangKey()).isEqualTo(UPDATED_LANGKEY); + }); + } + + @Test + @Transactional + void updateUserExistingEmail() throws Exception { + // Initialize the database with 2 users + userRepository.saveAndFlush(user); + + User anotherUser = new User(); + anotherUser.setLogin("jhipster"); + anotherUser.setPassword(RandomStringUtils.random(60)); + anotherUser.setActivated(true); + anotherUser.setEmail("jhipster@localhost"); + anotherUser.setFirstName("java"); + anotherUser.setLastName("hipster"); + anotherUser.setImageUrl(""); + anotherUser.setLangKey("en"); + userRepository.saveAndFlush(anotherUser); + + // Update the user + User updatedUser = userRepository.findById(user.getId()).get(); + + ManagedUserVM managedUserVM = new ManagedUserVM(); + managedUserVM.setId(updatedUser.getId()); + managedUserVM.setLogin(updatedUser.getLogin()); + managedUserVM.setPassword(updatedUser.getPassword()); + managedUserVM.setFirstName(updatedUser.getFirstName()); + managedUserVM.setLastName(updatedUser.getLastName()); + managedUserVM.setEmail("jhipster@localhost"); // this email should already be used by anotherUser + managedUserVM.setActivated(updatedUser.isActivated()); + managedUserVM.setImageUrl(updatedUser.getImageUrl()); + managedUserVM.setLangKey(updatedUser.getLangKey()); + managedUserVM.setCreatedBy(updatedUser.getCreatedBy()); + managedUserVM.setCreatedDate(updatedUser.getCreatedDate()); + managedUserVM.setLastModifiedBy(updatedUser.getLastModifiedBy()); + managedUserVM.setLastModifiedDate(updatedUser.getLastModifiedDate()); + managedUserVM.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + restUserMockMvc + .perform( + put("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(managedUserVM)) + ) + .andExpect(status().isBadRequest()); + } + + @Test + @Transactional + void updateUserExistingLogin() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + + User anotherUser = new User(); + anotherUser.setLogin("jhipster"); + anotherUser.setPassword(RandomStringUtils.random(60)); + anotherUser.setActivated(true); + anotherUser.setEmail("jhipster@localhost"); + anotherUser.setFirstName("java"); + anotherUser.setLastName("hipster"); + anotherUser.setImageUrl(""); + anotherUser.setLangKey("en"); + userRepository.saveAndFlush(anotherUser); + + // Update the user + User updatedUser = userRepository.findById(user.getId()).get(); + + ManagedUserVM managedUserVM = new ManagedUserVM(); + managedUserVM.setId(updatedUser.getId()); + managedUserVM.setLogin("jhipster"); // this login should already be used by anotherUser + managedUserVM.setPassword(updatedUser.getPassword()); + managedUserVM.setFirstName(updatedUser.getFirstName()); + managedUserVM.setLastName(updatedUser.getLastName()); + managedUserVM.setEmail(updatedUser.getEmail()); + managedUserVM.setActivated(updatedUser.isActivated()); + managedUserVM.setImageUrl(updatedUser.getImageUrl()); + managedUserVM.setLangKey(updatedUser.getLangKey()); + managedUserVM.setCreatedBy(updatedUser.getCreatedBy()); + managedUserVM.setCreatedDate(updatedUser.getCreatedDate()); + managedUserVM.setLastModifiedBy(updatedUser.getLastModifiedBy()); + managedUserVM.setLastModifiedDate(updatedUser.getLastModifiedDate()); + managedUserVM.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + restUserMockMvc + .perform( + put("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(managedUserVM)) + ) + .andExpect(status().isBadRequest()); + } + + @Test + @Transactional + void deleteUser() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + int databaseSizeBeforeDelete = userRepository.findAll().size(); + + // Delete the user + restUserMockMvc + .perform(delete("/api/admin/users/{login}", user.getLogin()).accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()); + + assertThat(cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE).get(user.getLogin())).isNull(); + + // Validate the database is empty + assertPersistedUsers(users -> assertThat(users).hasSize(databaseSizeBeforeDelete - 1)); + } + + @Test + void testUserEquals() throws Exception { + TestUtil.equalsVerifier(User.class); + User user1 = new User(); + user1.setId(DEFAULT_ID); + User user2 = new User(); + user2.setId(user1.getId()); + assertThat(user1).isEqualTo(user2); + user2.setId(2L); + assertThat(user1).isNotEqualTo(user2); + user1.setId(null); + assertThat(user1).isNotEqualTo(user2); + } + + @Test + void testUserDTOtoUser() { + AdminUserDTO userDTO = new AdminUserDTO(); + 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.isActivated()).isTrue(); + 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 + void testUserToUserDTO() { + user.setId(DEFAULT_ID); + user.setCreatedBy(DEFAULT_LOGIN); + user.setCreatedDate(Instant.now()); + user.setLastModifiedBy(DEFAULT_LOGIN); + user.setLastModifiedDate(Instant.now()); + Set<Authority> authorities = new HashSet<>(); + Authority authority = new Authority(); + authority.setName(AuthoritiesConstants.USER); + authorities.add(authority); + user.setAuthorities(authorities); + + 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()).isTrue(); + 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 + void testAuthorityEquals() { + Authority authorityA = new Authority(); + assertThat(authorityA).isNotEqualTo(null).isNotEqualTo(new Object()); + assertThat(authorityA.hashCode()).isZero(); + 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).hasSameHashCodeAs(authorityB); + } + + private void assertPersistedUsers(Consumer<List<User>> userAssertion) { + userAssertion.accept(userRepository.findAll()); + } +} diff --git a/pamapi/src/test/java/com/pollex/pam/web/rest/WithUnauthenticatedMockUser.java b/pamapi/src/test/java/com/pollex/pam/web/rest/WithUnauthenticatedMockUser.java new file mode 100644 index 0000000..6c0ecf5 --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/web/rest/WithUnauthenticatedMockUser.java @@ -0,0 +1,23 @@ +package com.pollex.pam.web.rest; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.test.context.support.WithSecurityContext; +import org.springframework.security.test.context.support.WithSecurityContextFactory; + +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@WithSecurityContext(factory = WithUnauthenticatedMockUser.Factory.class) +public @interface WithUnauthenticatedMockUser { + class Factory implements WithSecurityContextFactory<WithUnauthenticatedMockUser> { + + @Override + public SecurityContext createSecurityContext(WithUnauthenticatedMockUser annotation) { + return SecurityContextHolder.createEmptyContext(); + } + } +} diff --git a/pamapi/src/test/java/com/pollex/pam/web/rest/errors/ExceptionTranslatorIT.java b/pamapi/src/test/java/com/pollex/pam/web/rest/errors/ExceptionTranslatorIT.java new file mode 100644 index 0000000..057057c --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/web/rest/errors/ExceptionTranslatorIT.java @@ -0,0 +1,118 @@ +package com.pollex.pam.web.rest.errors; + +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 com.pollex.pam.IntegrationTest; +import org.junit.jupiter.api.Test; +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. + */ +@WithMockUser +@AutoConfigureMockMvc +@IntegrationTest +class ExceptionTranslatorIT { + + @Autowired + private MockMvc mockMvc; + + @Test + void testConcurrencyFailure() throws Exception { + mockMvc + .perform(get("/api/exception-translator-test/concurrency-failure")) + .andExpect(status().isConflict()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value(ErrorConstants.ERR_CONCURRENCY_FAILURE)); + } + + @Test + void testMethodArgumentNotValid() throws Exception { + mockMvc + .perform(post("/api/exception-translator-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("must not be null")); + } + + @Test + void testMissingServletRequestPartException() throws Exception { + mockMvc + .perform(get("/api/exception-translator-test/missing-servlet-request-part")) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value("error.http.400")); + } + + @Test + void testMissingServletRequestParameterException() throws Exception { + mockMvc + .perform(get("/api/exception-translator-test/missing-servlet-request-parameter")) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value("error.http.400")); + } + + @Test + void testAccessDenied() throws Exception { + mockMvc + .perform(get("/api/exception-translator-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 + void testUnauthorized() throws Exception { + mockMvc + .perform(get("/api/exception-translator-test/unauthorized")) + .andExpect(status().isUnauthorized()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value("error.http.401")) + .andExpect(jsonPath("$.path").value("/api/exception-translator-test/unauthorized")) + .andExpect(jsonPath("$.detail").value("test authentication failed!")); + } + + @Test + void testMethodNotSupported() throws Exception { + mockMvc + .perform(post("/api/exception-translator-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 + void testExceptionWithResponseStatus() throws Exception { + mockMvc + .perform(get("/api/exception-translator-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 + void testInternalServerError() throws Exception { + mockMvc + .perform(get("/api/exception-translator-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/pamapi/src/test/java/com/pollex/pam/web/rest/errors/ExceptionTranslatorTestController.java b/pamapi/src/test/java/com/pollex/pam/web/rest/errors/ExceptionTranslatorTestController.java new file mode 100644 index 0000000..131a99d --- /dev/null +++ b/pamapi/src/test/java/com/pollex/pam/web/rest/errors/ExceptionTranslatorTestController.java @@ -0,0 +1,66 @@ +package com.pollex.pam.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.*; + +@RestController +@RequestMapping("/api/exception-translator-test") +public class ExceptionTranslatorTestController { + + @GetMapping("/concurrency-failure") + public void concurrencyFailure() { + throw new ConcurrencyFailureException("test concurrency failure"); + } + + @PostMapping("/method-argument") + public void methodArgument(@Valid @RequestBody TestDTO testDTO) {} + + @GetMapping("/missing-servlet-request-part") + public void missingServletRequestPartException(@RequestPart String part) {} + + @GetMapping("/missing-servlet-request-parameter") + public void missingServletRequestParameterException(@RequestParam String param) {} + + @GetMapping("/access-denied") + public void accessdenied() { + throw new AccessDeniedException("test access denied!"); + } + + @GetMapping("/unauthorized") + public void unauthorized() { + throw new BadCredentialsException("test authentication failed!"); + } + + @GetMapping("/response-status") + public void exceptionWithResponseStatus() { + throw new TestResponseStatusException(); + } + + @GetMapping("/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/pamapi/src/test/resources/config/application-testcontainers.yml b/pamapi/src/test/resources/config/application-testcontainers.yml new file mode 100644 index 0000000..34aab4b --- /dev/null +++ b/pamapi/src/test/resources/config/application-testcontainers.yml @@ -0,0 +1,22 @@ +# =================================================================== +# 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:postgresql:13.4:///pamapi?TC_TMPFS=/testtmpfs:rw + username: pamapi + password: + hikari: + poolName: Hikari + auto-commit: false + jpa: + database-platform: tech.jhipster.domain.util.FixedPostgreSQL10Dialect diff --git a/pamapi/src/test/resources/config/application.yml b/pamapi/src/test/resources/config/application.yml new file mode 100644 index 0000000..019443f --- /dev/null +++ b/pamapi/src/test/resources/config/application.yml @@ -0,0 +1,112 @@ +# =================================================================== +# 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: + profiles: + # Uncomment the following line to enable tests against production database type rather than H2, using Testcontainers + #active: testcontainers + application: + name: pamapi + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: jdbc:h2:mem:pamapi;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: tech.jhipster.domain.util.FixedH2Dialect + open-in-view: 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 + hibernate.query.fail_on_pagination_over_collection_fetch: true + liquibase: + contexts: test + mail: + host: localhost + main: + allow-bean-definition-overriding: true + messages: + basename: i18n/messages + task: + execution: + thread-name-prefix: pamapi-task- + pool: + core-size: 1 + max-size: 50 + queue-capacity: 10000 + scheduling: + thread-name-prefix: pamapi-scheduling- + pool: + size: 1 + thymeleaf: + mode: HTML + +server: + port: 10344 + address: localhost + +# =================================================================== +# JHipster specific properties +# +# Full reference is available at: https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +jhipster: + clientApp: + name: 'pamapiApp' + logging: + # To test json console appender + use-json-format: false + logstash: + enabled: false + host: localhost + port: 5000 + queue-size: 512 + mail: + from: test@localhost + base-url: http://127.0.0.1:8080 + security: + authentication: + jwt: + # This token must be encoded using Base64 (you can type `echo 'secret-key'|base64` on your command line) + base64-secret: MjI3YWRiMzg1ZTY3ZmZkZDgxZmI5Yjc5YjVkOTIzMzc4MmI2OWM3NWVkZjFiOTNmNjg0YWFjZWQ4YzhlOTUzYzk3MGUzMzM5Y2U5MDdkZTMyN2Q0N2E0M2ZmM2FhYzkyNDY4MjBkYTY5OGM4YmIzMmYxODJhNWFkMmVhNmVhNTM= + # Token is valid 24 hours + token-validity-in-seconds: 86400 +# =================================================================== +# 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/pamapi/src/test/resources/config/bootstrap.yml b/pamapi/src/test/resources/config/bootstrap.yml new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/pamapi/src/test/resources/config/bootstrap.yml diff --git a/pamapi/src/test/resources/i18n/messages_en.properties b/pamapi/src/test/resources/i18n/messages_en.properties new file mode 100644 index 0000000..dd4aa31 --- /dev/null +++ b/pamapi/src/test/resources/i18n/messages_en.properties @@ -0,0 +1,4 @@ +email.test.title=test title +# Value used for English locale unit test in MailServiceIT +# as this file is loaded instead of real file +email.activation.title=pamapi account activation diff --git a/pamapi/src/test/resources/i18n/messages_zh_TW.properties b/pamapi/src/test/resources/i18n/messages_zh_TW.properties new file mode 100644 index 0000000..fbfd926 --- /dev/null +++ b/pamapi/src/test/resources/i18n/messages_zh_TW.properties @@ -0,0 +1 @@ +email.test.title=\u5E33\u865F\u555F\u7528 diff --git a/pamapi/src/test/resources/logback.xml b/pamapi/src/test/resources/logback.xml new file mode 100644 index 0000000..258974c --- /dev/null +++ b/pamapi/src/test/resources/logback.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE configuration> + +<configuration scan="true"> + <include resource="org/springframework/boot/logging/logback/base.xml"/> + + <logger name="com.pollex.pam" level="INFO"/> + + <logger name="tech.jhipster" level="WARN"/> + + <logger name="javax.activation" level="WARN"/> + <logger name="javax.mail" level="WARN"/> + <logger name="javax.xml.bind" level="WARN"/> + <logger name="ch.qos.logback" level="WARN"/> + <logger name="com.jayway.jsonpath" level="WARN"/> + <logger name="com.ryantenney" level="WARN"/> + <logger name="com.sun" level="WARN"/> + <logger name="com.zaxxer" level="WARN"/> + <logger name="com.github.dockerjava" level="WARN"/> + <logger name="org.testcontainers" level="WARN"/> + <logger name="io.undertow" level="WARN"/> + <logger name="io.undertow.websockets.jsr" level="ERROR"/> + <logger name="org.ehcache" level="WARN"/> + <logger name="org.apache" level="WARN"/> + <logger name="org.apache.catalina.startup.DigesterFactory" level="OFF"/> + <logger name="org.bson" level="WARN"/> + <logger name="org.hibernate.validator" level="WARN"/> + <logger name="org.hibernate" level="WARN"/> + <logger name="org.hibernate.ejb.HibernatePersistence" level="OFF"/> + <logger name="org.postgresql.jdbc" level="WARN"/> + <logger name="org.springframework" level="WARN"/> + <logger name="org.springframework.web" level="WARN"/> + <logger name="org.springframework.security" level="WARN"/> + <logger name="org.springframework.cache" level="WARN"/> + <logger name="org.thymeleaf" level="WARN"/> + <logger name="org.xnio" level="WARN"/> + <logger name="springfox" level="WARN"/> + <logger name="sun.rmi" level="WARN"/> + <logger name="liquibase" level="WARN"/> + <logger name="LiquibaseSchemaResolver" level="INFO"/> + <logger name="sun.rmi.transport" level="WARN"/> + <logger name="com.tngtech.archunit.core.importer" level="ERROR"/> + + <root> + <level>WARN</level> + <appender-ref ref="CONSOLE"/> + </root> + +</configuration> diff --git a/pamapi/src/test/resources/templates/mail/testEmail.html b/pamapi/src/test/resources/templates/mail/testEmail.html new file mode 100644 index 0000000..a4ca16a --- /dev/null +++ b/pamapi/src/test/resources/templates/mail/testEmail.html @@ -0,0 +1 @@ +<html xmlns:th="http://www.thymeleaf.org" th:text="|#{email.test.title}, ${baseUrl}, ${user.login}|"></html> -- Gitblit v1.8.0