diff --git a/index.adoc b/index.adoc index afc4dad..0b8b735 100644 --- a/index.adoc +++ b/index.adoc @@ -686,6 +686,332 @@ ifdef::internal-generation[] endif::internal-generation[] +[.Authentication] +=== Authentication + + +[.getAuthenticationIdps] +==== getAuthenticationIdps + +`GET /authentication/idps` + +Get all authentication identity providers + +===== Description + + + + +// markup not found, no include::{specDir}authentication/idps/GET/spec.adoc[opts=optional] + + + +===== Parameters + + + + + + + +===== Return Type + +<> + + +===== Content Type + +* application/json + +===== Responses + +.HTTP Response Codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 200 +| Returns all authentication identity providers. +| <> + + +| 0 +| Returns a list of error messages. +| <> + +|=== + +===== Samples + + +// markup not found, no include::{snippetDir}authentication/idps/GET/http-request.adoc[opts=optional] + + +// markup not found, no include::{snippetDir}authentication/idps/GET/http-response.adoc[opts=optional] + + + +// file not found, no * wiremock data link :authentication/idps/GET/GET.json[] + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}authentication/idps/GET/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + +[.getAuthenticationSso] +==== getAuthenticationSso + +`GET /authentication/sso` + +Get authentication SSO configuration + +===== Description + + + + +// markup not found, no include::{specDir}authentication/sso/GET/spec.adoc[opts=optional] + + + +===== Parameters + + + + + + + +===== Return Type + +<> + + +===== Content Type + +* application/json + +===== Responses + +.HTTP Response Codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 200 +| Returns the authentication SSO configuration. +| <> + + +| 0 +| Returns a list of error messages. +| <> + +|=== + +===== Samples + + +// markup not found, no include::{snippetDir}authentication/sso/GET/http-request.adoc[opts=optional] + + +// markup not found, no include::{snippetDir}authentication/sso/GET/http-response.adoc[opts=optional] + + + +// file not found, no * wiremock data link :authentication/sso/GET/GET.json[] + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}authentication/sso/GET/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + +[.setAuthenticationIdps] +==== setAuthenticationIdps + +`PATCH /authentication/idps` + +Set all authentication identity providers + +===== Description + + + + +// markup not found, no include::{specDir}authentication/idps/PATCH/spec.adoc[opts=optional] + + + +===== Parameters + + +====== Body Parameter + +[cols="2,3,1,1,1"] +|=== +|Name| Description| Required| Default| Pattern + +| AuthenticationIdpsBean +| <> +| - +| +| + +|=== + + + + + +===== Return Type + +<> + + +===== Content Type + +* application/json + +===== Responses + +.HTTP Response Codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 200 +| Returns the set authentication identity providers. +| <> + + +| 0 +| Returns a list of error messages. +| <> + +|=== + +===== Samples + + +// markup not found, no include::{snippetDir}authentication/idps/PATCH/http-request.adoc[opts=optional] + + +// markup not found, no include::{snippetDir}authentication/idps/PATCH/http-response.adoc[opts=optional] + + + +// file not found, no * wiremock data link :authentication/idps/PATCH/PATCH.json[] + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}authentication/idps/PATCH/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + +[.setAuthenticationSso] +==== setAuthenticationSso + +`PATCH /authentication/sso` + +Set authentication SSO configuration + +===== Description + + + + +// markup not found, no include::{specDir}authentication/sso/PATCH/spec.adoc[opts=optional] + + + +===== Parameters + + +====== Body Parameter + +[cols="2,3,1,1,1"] +|=== +|Name| Description| Required| Default| Pattern + +| AuthenticationSsoBean +| <> +| - +| +| + +|=== + + + + + +===== Return Type + +<> + + +===== Content Type + +* application/json + +===== Responses + +.HTTP Response Codes +[cols="2,3,1"] +|=== +| Code | Message | Datatype + + +| 200 +| Returns the set authentication SSO configuration. +| <> + + +| 0 +| Returns a list of error messages. +| <> + +|=== + +===== Samples + + +// markup not found, no include::{snippetDir}authentication/sso/PATCH/http-request.adoc[opts=optional] + + +// markup not found, no include::{snippetDir}authentication/sso/PATCH/http-response.adoc[opts=optional] + + + +// file not found, no * wiremock data link :authentication/sso/PATCH/PATCH.json[] + + +ifdef::internal-generation[] +===== Implementation + +// markup not found, no include::{specDir}authentication/sso/PATCH/implementation.adoc[opts=optional] + + +endif::internal-generation[] + + [.Cache] === Cache @@ -4126,6 +4452,141 @@ endif::internal-generation[] |=== +[#AuthenticationIdpOidcBean] +=== _AuthenticationIdpOidcBean_ + + + +[.fields-AuthenticationIdpOidcBean] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| id +| +| Long +| +| int64 + +| name +| +| String +| +| + +| enabled +| +| Boolean +| +| + +| url +| +| String +| +| + +| enableRememberMe +| +| Boolean +| +| + +| buttonText +| +| String +| +| + +| clientId +| +| String +| +| + +| clientSecret +| +| String +| +| + +| usernameClaim +| +| String +| +| + +| additionalScopes +| +| List of <> +| +| + +| discoveryEnabled +| +| Boolean +| +| + +| authorizationEndpoint +| +| String +| +| + +| tokenEndpoint +| +| String +| +| + +| userInfoEndpoint +| +| String +| +| + +|=== + + +[#AuthenticationIdpsBean] +=== _AuthenticationIdpsBean_ + + + +[.fields-AuthenticationIdpsBean] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| authenticationIdpBeans +| +| List of <> +| +| + +|=== + + +[#AuthenticationSsoBean] +=== _AuthenticationSsoBean_ + + + +[.fields-AuthenticationSsoBean] +[cols="2,1,2,4,1"] +|=== +| Field Name| Required| Type| Description| Format + +| showOnLogin +| +| Boolean +| +| + +|=== + + [#CacheBean] === _CacheBean_ diff --git a/pom.xml b/pom.xml index c1a43fd..c58ee1c 100644 --- a/pom.xml +++ b/pom.xml @@ -68,7 +68,7 @@ 8.1.4 ${project.groupId}.${project.artifactId} 2.2.4 - 0.4.0-SNAPSHOT + 0.5.0 2.0.2 2.3.1 2.1.1 @@ -245,6 +245,13 @@ + + com.atlassian.plugins.authentication + atlassian-authentication-plugin + 4.3.8 + provided + + io.swagger.core.v3 swagger-annotations diff --git a/src/main/java/de/aservo/confapi/confluence/model/util/AuthenticationIdpBeanUtil.java b/src/main/java/de/aservo/confapi/confluence/model/util/AuthenticationIdpBeanUtil.java new file mode 100644 index 0000000..7c38329 --- /dev/null +++ b/src/main/java/de/aservo/confapi/confluence/model/util/AuthenticationIdpBeanUtil.java @@ -0,0 +1,169 @@ +package de.aservo.confapi.confluence.model.util; + +import com.atlassian.plugins.authentication.api.config.IdpConfig; +import com.atlassian.plugins.authentication.api.config.SsoType; +import com.atlassian.plugins.authentication.api.config.oidc.OidcConfig; +import com.atlassian.plugins.authentication.api.config.saml.SamlConfig; +import de.aservo.confapi.commons.exception.BadRequestException; +import de.aservo.confapi.commons.exception.InternalServerErrorException; +import de.aservo.confapi.commons.model.AbstractAuthenticationIdpBean; +import de.aservo.confapi.commons.model.AuthenticationIdpOidcBean; +import de.aservo.confapi.commons.model.AuthenticationIdpSamlBean; + +public class AuthenticationIdpBeanUtil { + + public static IdpConfig toIdpConfig( + final AbstractAuthenticationIdpBean authenticationIdpBean) { + + return toIdpConfig(authenticationIdpBean, null); + } + + public static IdpConfig toIdpConfig( + final AbstractAuthenticationIdpBean authenticationIdpBean, + final IdpConfig existingIdpConfig) { + + if (authenticationIdpBean instanceof AuthenticationIdpOidcBean) { + return toOidcConfig((AuthenticationIdpOidcBean) authenticationIdpBean, existingIdpConfig); + } + + throw new BadRequestException("IDP types other than OIDC are not (yet) supported"); + } + + private static OidcConfig toOidcConfig( + final AuthenticationIdpOidcBean authenticationIdpOidcBean, + final IdpConfig existingIdpConfig) { + + final OidcConfig.Builder oidcConfigBuilder; + + if (existingIdpConfig == null) { + oidcConfigBuilder = OidcConfig.builder(); + } else { + verifyIdAndType(authenticationIdpOidcBean, existingIdpConfig, OidcConfig.class); + oidcConfigBuilder = OidcConfig.builder((OidcConfig) existingIdpConfig); + } + + if (authenticationIdpOidcBean.getId() != null) { + oidcConfigBuilder.setId(authenticationIdpOidcBean.getId()); + } + if (authenticationIdpOidcBean.getName() != null) { + oidcConfigBuilder.setName(authenticationIdpOidcBean.getName()); + } + if (authenticationIdpOidcBean.getEnabled() != null) { + oidcConfigBuilder.setEnabled(authenticationIdpOidcBean.getEnabled()); + } + if (authenticationIdpOidcBean.getUrl() != null) { + oidcConfigBuilder.setIssuer(authenticationIdpOidcBean.getUrl()); + } + if (authenticationIdpOidcBean.getEnableRememberMe() != null) { + oidcConfigBuilder.setEnableRememberMe(authenticationIdpOidcBean.getEnableRememberMe()); + } + if (authenticationIdpOidcBean.getButtonText() != null) { + oidcConfigBuilder.setButtonText(authenticationIdpOidcBean.getButtonText()); + } + if (authenticationIdpOidcBean.getClientId() != null) { + oidcConfigBuilder.setClientId(authenticationIdpOidcBean.getClientId()); + } + if (authenticationIdpOidcBean.getClientSecret() != null) { + oidcConfigBuilder.setClientSecret(authenticationIdpOidcBean.getClientSecret()); + } + if (authenticationIdpOidcBean.getUsernameClaim() != null) { + oidcConfigBuilder.setUsernameClaim(authenticationIdpOidcBean.getUsernameClaim()); + } + if (authenticationIdpOidcBean.getAdditionalScopes() != null) { + oidcConfigBuilder.setAdditionalScopes(authenticationIdpOidcBean.getAdditionalScopes()); + } + if (authenticationIdpOidcBean.getDiscoveryEnabled() != null) { + oidcConfigBuilder.setDiscoveryEnabled(authenticationIdpOidcBean.getDiscoveryEnabled()); + } + if (authenticationIdpOidcBean.getAuthorizationEndpoint() != null) { + oidcConfigBuilder.setAuthorizationEndpoint(authenticationIdpOidcBean.getAuthorizationEndpoint()); + } + if (authenticationIdpOidcBean.getTokenEndpoint() != null) { + oidcConfigBuilder.setTokenEndpoint(authenticationIdpOidcBean.getTokenEndpoint()); + } + if (authenticationIdpOidcBean.getUserInfoEndpoint() != null) { + oidcConfigBuilder.setUserInfoEndpoint(authenticationIdpOidcBean.getUserInfoEndpoint()); + } + + return oidcConfigBuilder.build(); + } + + public static AbstractAuthenticationIdpBean toAuthenticationIdpBean( + final IdpConfig idpConfig) { + + if (idpConfig.getSsoType().equals(SsoType.OIDC)) { + return toAuthenticationIdpOidcBean(idpConfig); + } else if (idpConfig.getSsoType().equals(SsoType.SAML)) { + return toAuthenticationIdpSamlBean(idpConfig); + } + + throw new UnsupportedOperationException("The IDP type cannot be NONE"); + } + + private static AuthenticationIdpOidcBean toAuthenticationIdpOidcBean( + final IdpConfig idpConfig) { + + if (!(idpConfig instanceof OidcConfig)) { + throw new InternalServerErrorException("The class of the IDP config is not OIDC"); + } + + final OidcConfig oidcConfig = (OidcConfig) idpConfig; + + final AuthenticationIdpOidcBean authenticationIdpOidcBean = new AuthenticationIdpOidcBean(); + authenticationIdpOidcBean.setId(oidcConfig.getId()); + authenticationIdpOidcBean.setName(oidcConfig.getName()); + authenticationIdpOidcBean.setEnabled(oidcConfig.isEnabled()); + authenticationIdpOidcBean.setUrl(oidcConfig.getIssuer()); + authenticationIdpOidcBean.setEnableRememberMe(oidcConfig.isEnableRememberMe()); + authenticationIdpOidcBean.setButtonText(oidcConfig.getButtonText()); + authenticationIdpOidcBean.setClientId(oidcConfig.getClientId()); + authenticationIdpOidcBean.setUsernameClaim(oidcConfig.getUsernameClaim()); + authenticationIdpOidcBean.setAdditionalScopes(oidcConfig.getAdditionalScopes()); + authenticationIdpOidcBean.setDiscoveryEnabled(oidcConfig.isDiscoveryEnabled()); + authenticationIdpOidcBean.setAuthorizationEndpoint(oidcConfig.getAuthorizationEndpoint()); + authenticationIdpOidcBean.setTokenEndpoint(oidcConfig.getTokenEndpoint()); + authenticationIdpOidcBean.setUserInfoEndpoint(oidcConfig.getUserInfoEndpoint()); + + return authenticationIdpOidcBean; + } + + private static AuthenticationIdpSamlBean toAuthenticationIdpSamlBean( + final IdpConfig idpConfig) { + + if (!(idpConfig instanceof SamlConfig)) { + throw new InternalServerErrorException("The class of the IDP config is not SAML"); + } + + final SamlConfig samlConfig = (SamlConfig) idpConfig; + + final AuthenticationIdpSamlBean authenticationIdpSamlBean = new AuthenticationIdpSamlBean(); + authenticationIdpSamlBean.setId(samlConfig.getId()); + authenticationIdpSamlBean.setName(samlConfig.getName()); + authenticationIdpSamlBean.setEnabled(samlConfig.isEnabled()); + authenticationIdpSamlBean.setUrl(samlConfig.getIssuer()); + authenticationIdpSamlBean.setEnableRememberMe(samlConfig.isEnableRememberMe()); + authenticationIdpSamlBean.setButtonText(samlConfig.getButtonText()); + // is it wanted to return the certificate here? + authenticationIdpSamlBean.setUsernameAttribute(samlConfig.getUsernameAttribute()); + + return authenticationIdpSamlBean; + } + + private static void verifyIdAndType( + final AbstractAuthenticationIdpBean authenticationIdpBean, + final IdpConfig existingIdpConfig, + final Class clazz) { + + if (authenticationIdpBean.getId() != null && !authenticationIdpBean.getId().equals(existingIdpConfig.getId())) { + throw new BadRequestException("An ID has been passed but it does not match the ID of the existing IDP with the same name"); + } + + if (!clazz.isAssignableFrom(existingIdpConfig.getClass())) { + throw new BadRequestException("The existing IDP config with the same name is not of type OIDC"); + } + } + + private AuthenticationIdpBeanUtil() { + } + +} diff --git a/src/main/java/de/aservo/confapi/confluence/model/util/AuthenticationSsoBeanUtil.java b/src/main/java/de/aservo/confapi/confluence/model/util/AuthenticationSsoBeanUtil.java new file mode 100644 index 0000000..e6a5d20 --- /dev/null +++ b/src/main/java/de/aservo/confapi/confluence/model/util/AuthenticationSsoBeanUtil.java @@ -0,0 +1,46 @@ +package de.aservo.confapi.confluence.model.util; + +import com.atlassian.plugins.authentication.api.config.ImmutableSsoConfig; +import com.atlassian.plugins.authentication.api.config.SsoConfig; +import de.aservo.confapi.commons.model.AuthenticationSsoBean; + +public class AuthenticationSsoBeanUtil { + + public static SsoConfig toSsoConfig( + final AuthenticationSsoBean authenticationSsoBean) { + + return toSsoConfig(authenticationSsoBean, null); + } + + public static SsoConfig toSsoConfig( + final AuthenticationSsoBean authenticationSsoBean, + final SsoConfig existingSsoConfig) { + + final ImmutableSsoConfig.Builder ssoConfigBuilder; + + if (existingSsoConfig != null) { + ssoConfigBuilder = ImmutableSsoConfig.toBuilder(existingSsoConfig); + } else { + ssoConfigBuilder = ImmutableSsoConfig.builder(); + } + + if (authenticationSsoBean.getShowOnLogin() != null) { + ssoConfigBuilder.setShowLoginForm(authenticationSsoBean.getShowOnLogin()); + } + + return ssoConfigBuilder.build(); + } + + public static AuthenticationSsoBean toAuthenticationSsoBean( + final SsoConfig ssoConfig) { + + final AuthenticationSsoBean authenticationSsoBean = new AuthenticationSsoBean(); + authenticationSsoBean.setShowOnLogin(ssoConfig.getShowLoginForm()); + + return authenticationSsoBean; + } + + private AuthenticationSsoBeanUtil() { + } + +} diff --git a/src/main/java/de/aservo/confapi/confluence/rest/AuthenticationResourceImpl.java b/src/main/java/de/aservo/confapi/confluence/rest/AuthenticationResourceImpl.java new file mode 100644 index 0000000..5ddc6ac --- /dev/null +++ b/src/main/java/de/aservo/confapi/confluence/rest/AuthenticationResourceImpl.java @@ -0,0 +1,23 @@ +package de.aservo.confapi.confluence.rest; + +import com.sun.jersey.spi.container.ResourceFilters; +import de.aservo.confapi.commons.constants.ConfAPI; +import de.aservo.confapi.commons.rest.AbstractAuthenticationResourceImpl; +import de.aservo.confapi.commons.service.api.AuthenticationService; +import de.aservo.confapi.confluence.filter.SysAdminOnlyResourceFilter; +import org.springframework.stereotype.Component; + +import javax.inject.Inject; +import javax.ws.rs.Path; + +@Path(ConfAPI.AUTHENTICATION) +@ResourceFilters(SysAdminOnlyResourceFilter.class) +@Component +public class AuthenticationResourceImpl extends AbstractAuthenticationResourceImpl { + + @Inject + public AuthenticationResourceImpl(AuthenticationService authenticationService) { + super(authenticationService); + } + +} diff --git a/src/main/java/de/aservo/confapi/confluence/service/AuthenticationServiceImpl.java b/src/main/java/de/aservo/confapi/confluence/service/AuthenticationServiceImpl.java new file mode 100644 index 0000000..0ecdc9a --- /dev/null +++ b/src/main/java/de/aservo/confapi/confluence/service/AuthenticationServiceImpl.java @@ -0,0 +1,105 @@ +package de.aservo.confapi.confluence.service; + +import com.atlassian.plugin.spring.scanner.annotation.export.ExportAsService; +import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport; +import com.atlassian.plugins.authentication.api.config.IdpConfig; +import com.atlassian.plugins.authentication.api.config.IdpConfigService; +import com.atlassian.plugins.authentication.api.config.SsoConfig; +import com.atlassian.plugins.authentication.api.config.SsoConfigService; +import de.aservo.confapi.commons.exception.BadRequestException; +import de.aservo.confapi.commons.model.AbstractAuthenticationIdpBean; +import de.aservo.confapi.commons.model.AuthenticationIdpsBean; +import de.aservo.confapi.commons.model.AuthenticationSsoBean; +import de.aservo.confapi.commons.service.api.AuthenticationService; +import de.aservo.confapi.confluence.model.util.AuthenticationIdpBeanUtil; +import de.aservo.confapi.confluence.model.util.AuthenticationSsoBeanUtil; +import org.springframework.stereotype.Component; + +import java.util.Comparator; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Component +@ExportAsService(AuthenticationService.class) +public class AuthenticationServiceImpl implements AuthenticationService { + + @ComponentImport + private final IdpConfigService idpConfigService; + + @ComponentImport + private final SsoConfigService ssoConfigService; + + public AuthenticationServiceImpl( + final IdpConfigService idpConfigService, + final SsoConfigService ssoConfigService) { + + this.idpConfigService = idpConfigService; + this.ssoConfigService = ssoConfigService; + } + + @Override + public AuthenticationIdpsBean getAuthenticationIdps() { + return new AuthenticationIdpsBean(idpConfigService.getIdpConfigs().stream() + .map(AuthenticationIdpBeanUtil::toAuthenticationIdpBean) + .sorted(authenticationIdpBeanComparator) + .collect(Collectors.toList())); + } + + @Override + public AuthenticationIdpsBean setAuthenticationIdps( + final AuthenticationIdpsBean authenticationIdpsBean) { + + return new AuthenticationIdpsBean(authenticationIdpsBean.getAuthenticationIdpBeans().stream() + .map(this::setAuthenticationIdp) + .sorted(authenticationIdpBeanComparator) + .collect(Collectors.toList())); + } + + public AbstractAuthenticationIdpBean setAuthenticationIdp( + final AbstractAuthenticationIdpBean authenticationIdpBean) { + + if (authenticationIdpBean.getName() == null || authenticationIdpBean.getName().trim().isEmpty()) { + throw new BadRequestException("The name cannot be empty"); + } + + final IdpConfig existingIdpConfig = findIdpConfigByName(authenticationIdpBean.getName()); + + if (existingIdpConfig == null) { + final IdpConfig idpConfig = AuthenticationIdpBeanUtil.toIdpConfig(authenticationIdpBean); + final IdpConfig addedIdpConfig = idpConfigService.addIdpConfig(idpConfig); + return AuthenticationIdpBeanUtil.toAuthenticationIdpBean(addedIdpConfig); + } + + final IdpConfig idpConfig = AuthenticationIdpBeanUtil.toIdpConfig(authenticationIdpBean, existingIdpConfig); + final IdpConfig updatedIdpConfig = idpConfigService.updateIdpConfig(idpConfig); + return AuthenticationIdpBeanUtil.toAuthenticationIdpBean(updatedIdpConfig); + } + + @Override + public AuthenticationSsoBean getAuthenticationSso() { + return AuthenticationSsoBeanUtil.toAuthenticationSsoBean(ssoConfigService.getSsoConfig()); + } + + @Override + public AuthenticationSsoBean setAuthenticationSso(AuthenticationSsoBean authenticationSsoBean) { + final SsoConfig existingSsoConfig = ssoConfigService.getSsoConfig(); + final SsoConfig ssoConfig = AuthenticationSsoBeanUtil.toSsoConfig(authenticationSsoBean, existingSsoConfig); + return AuthenticationSsoBeanUtil.toAuthenticationSsoBean(ssoConfigService.updateSsoConfig(ssoConfig)); + } + + IdpConfig findIdpConfigByName( + final String name) { + + final Map idpConfigsByName = idpConfigService.getIdpConfigs().stream().collect(Collectors.toMap( + IdpConfig::getName, Function.identity(), (existing, replacement) -> { + throw new IllegalStateException("Duplicate name key found: " + existing.getName()); + } + )); + + return idpConfigsByName.get(name); + } + + static Comparator authenticationIdpBeanComparator = (a1, a2) -> a1.getName().compareToIgnoreCase(a2.getName()); + +} diff --git a/src/test/java/de/aservo/confapi/confluence/model/util/AuthenticationIdpBeanUtilTest.java b/src/test/java/de/aservo/confapi/confluence/model/util/AuthenticationIdpBeanUtilTest.java new file mode 100644 index 0000000..8fbfb0f --- /dev/null +++ b/src/test/java/de/aservo/confapi/confluence/model/util/AuthenticationIdpBeanUtilTest.java @@ -0,0 +1,61 @@ +package de.aservo.confapi.confluence.model.util; + +import com.atlassian.plugins.authentication.api.config.IdpConfig; +import com.atlassian.plugins.authentication.api.config.SsoType; +import com.atlassian.plugins.authentication.api.config.oidc.OidcConfig; +import com.atlassian.plugins.authentication.api.config.saml.SamlConfig; +import de.aservo.confapi.commons.exception.BadRequestException; +import de.aservo.confapi.commons.model.AbstractAuthenticationIdpBean; +import de.aservo.confapi.commons.model.AuthenticationIdpOidcBean; +import de.aservo.confapi.commons.model.AuthenticationIdpSamlBean; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(MockitoExtension.class) +class AuthenticationIdpBeanUtilTest { + + @Test + void testToIdpConfigOidc() { + final AuthenticationIdpOidcBean authenticationIdpOidcBean = AuthenticationIdpOidcBean.EXAMPLE_1; + final IdpConfig idpConfig = AuthenticationIdpBeanUtil.toIdpConfig(authenticationIdpOidcBean); + assertEquals(SsoType.OIDC, idpConfig.getSsoType()); + } + + @Test + void testToIdpConfigOidcExistingIdDoesNotMatch() { + final AuthenticationIdpOidcBean authenticationIdpOidcBean = AuthenticationIdpOidcBean.EXAMPLE_1; + final IdpConfig existingIdpConfig = OidcConfig.builder().setId(authenticationIdpOidcBean.getId() + 1).build(); + + assertThrows(BadRequestException.class, () -> { + AuthenticationIdpBeanUtil.toIdpConfig(authenticationIdpOidcBean, existingIdpConfig); + }); + } + + @Test + void testToIdpConfigOidcExistingTypeDoesNotMatch() { + final AuthenticationIdpOidcBean authenticationIdpOidcBean = AuthenticationIdpOidcBean.EXAMPLE_1; + final IdpConfig existingIdpConfig = SamlConfig.builder().build(); + + assertThrows(BadRequestException.class, () -> { + AuthenticationIdpBeanUtil.toIdpConfig(authenticationIdpOidcBean, existingIdpConfig); + }); + } + + @Test + void testToAuthenticationIdpBeanOidcType() { + final IdpConfig idpConfig = OidcConfig.builder().setName("oidc").build(); + final AbstractAuthenticationIdpBean authenticationIdpBean = AuthenticationIdpBeanUtil.toAuthenticationIdpBean(idpConfig); + assertTrue(authenticationIdpBean.getClass().isAssignableFrom(AuthenticationIdpOidcBean.class)); + } + + @Test + void testToAuthenticationIdpBeanSamlType() { + final IdpConfig idpConfig = SamlConfig.builder().setName("saml").build(); + final AbstractAuthenticationIdpBean authenticationIdpBean = AuthenticationIdpBeanUtil.toAuthenticationIdpBean(idpConfig); + assertTrue(authenticationIdpBean.getClass().isAssignableFrom(AuthenticationIdpSamlBean.class)); + } + +} diff --git a/src/test/java/de/aservo/confapi/confluence/service/AuthenticationServiceTest.java b/src/test/java/de/aservo/confapi/confluence/service/AuthenticationServiceTest.java new file mode 100644 index 0000000..f7df6e9 --- /dev/null +++ b/src/test/java/de/aservo/confapi/confluence/service/AuthenticationServiceTest.java @@ -0,0 +1,124 @@ +package de.aservo.confapi.confluence.service; + +import com.atlassian.plugins.authentication.api.config.*; +import com.atlassian.plugins.authentication.api.config.oidc.OidcConfig; +import com.atlassian.plugins.authentication.api.config.saml.SamlConfig; +import de.aservo.confapi.commons.exception.BadRequestException; +import de.aservo.confapi.commons.model.AbstractAuthenticationIdpBean; +import de.aservo.confapi.commons.model.AuthenticationIdpOidcBean; +import de.aservo.confapi.commons.model.AuthenticationIdpsBean; +import de.aservo.confapi.commons.model.AuthenticationSsoBean; +import de.aservo.confapi.confluence.model.util.AuthenticationIdpBeanUtil; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class AuthenticationServiceTest { + + @Mock + private IdpConfigService idpConfigService; + + @Mock + private SsoConfigService ssoConfigService; + + private AuthenticationServiceImpl authenticationService; + + @BeforeEach + public void setup() { + authenticationService = new AuthenticationServiceImpl(idpConfigService, ssoConfigService); + } + + @Test + void testGetAuthenticationIdps() { + final OidcConfig oidcConfig = OidcConfig.builder().setName("oidc").build(); + final SamlConfig samlConfig = SamlConfig.builder().setName("saml").build(); + doReturn(Arrays.asList(oidcConfig, samlConfig)).when(idpConfigService).getIdpConfigs(); + + final AuthenticationIdpsBean authenticationIdpsBean = authenticationService.getAuthenticationIdps(); + final List names = authenticationIdpsBean.getAuthenticationIdpBeans().stream() + .map(AbstractAuthenticationIdpBean::getName) + .collect(Collectors.toList()); + assertTrue(names.contains(oidcConfig.getName())); + assertTrue(names.contains(samlConfig.getName())); + } + + @Test + void testSetAuthenticationIdpsWithCreate() { + final AuthenticationIdpOidcBean authenticationIdpOidcBean = AuthenticationIdpOidcBean.EXAMPLE_1; + final AuthenticationIdpsBean authenticationIdpsBean = new AuthenticationIdpsBean(Collections.singletonList(authenticationIdpOidcBean)); + doAnswer(invocation -> invocation.getArgument(0)).when(idpConfigService).addIdpConfig(any()); + + final AuthenticationIdpsBean resultAuthenticationIdpsBean = authenticationService.setAuthenticationIdps(authenticationIdpsBean); + verify(idpConfigService, times(1)).addIdpConfig(any()); + assertEquals(authenticationIdpOidcBean.getId(), resultAuthenticationIdpsBean.getAuthenticationIdpBeans().iterator().next().getId()); + assertEquals(authenticationIdpOidcBean.getName(), resultAuthenticationIdpsBean.getAuthenticationIdpBeans().iterator().next().getName()); + } + + @Test + void testSetAuthenticationIdpsWithUpdate() { + final AuthenticationIdpOidcBean authenticationIdpOidcBean = AuthenticationIdpOidcBean.EXAMPLE_1; + final AuthenticationIdpsBean authenticationIdpsBean = new AuthenticationIdpsBean(Collections.singletonList(authenticationIdpOidcBean)); + final IdpConfig idpConfig = AuthenticationIdpBeanUtil.toIdpConfig(authenticationIdpOidcBean); + doReturn(Collections.singletonList(idpConfig)).when(idpConfigService).getIdpConfigs(); + doAnswer(invocation -> invocation.getArgument(0)).when(idpConfigService).updateIdpConfig(any()); + + final AuthenticationIdpsBean resultAuthenticationIdpsBean = authenticationService.setAuthenticationIdps(authenticationIdpsBean); + verify(idpConfigService, times(1)).updateIdpConfig(any()); + assertEquals(authenticationIdpOidcBean.getId(), resultAuthenticationIdpsBean.getAuthenticationIdpBeans().iterator().next().getId()); + assertEquals(authenticationIdpOidcBean.getName(), resultAuthenticationIdpsBean.getAuthenticationIdpBeans().iterator().next().getName()); + } + + @Test + void testSetAuthenticationIdpsNameNull() { + final AuthenticationIdpOidcBean authenticationIdpOidcBean = new AuthenticationIdpOidcBean(); + final AuthenticationIdpsBean authenticationIdpsBean = new AuthenticationIdpsBean(Collections.singletonList(authenticationIdpOidcBean)); + + assertThrows(BadRequestException.class, () -> { + authenticationService.setAuthenticationIdps(authenticationIdpsBean); + }); + } + + @Test + void testSetAuthenticationIdpsNameEmpty() { + final AuthenticationIdpOidcBean authenticationIdpOidcBean = new AuthenticationIdpOidcBean(); + authenticationIdpOidcBean.setName(""); + final AuthenticationIdpsBean authenticationIdpsBean = new AuthenticationIdpsBean(Collections.singletonList(authenticationIdpOidcBean)); + + assertThrows(BadRequestException.class, () -> { + authenticationService.setAuthenticationIdps(authenticationIdpsBean); + }); + } + + @Test + void testGetAuthenticationSso() { + final SsoConfig ssoConfig = ImmutableSsoConfig.builder().setShowLoginForm(true).build(); + doReturn(ssoConfig).when(ssoConfigService).getSsoConfig(); + + final AuthenticationSsoBean authenticationSsoBean = authenticationService.getAuthenticationSso(); + assertEquals(ssoConfig.getShowLoginForm(), authenticationSsoBean.getShowOnLogin()); + } + + @Test + void testSetAuthenticationSso() { + final AuthenticationSsoBean authenticationSsoBean = AuthenticationSsoBean.EXAMPLE_1; + final SsoConfig ssoConfig = ImmutableSsoConfig.builder().setShowLoginForm(authenticationSsoBean.getShowOnLogin()).build(); + doReturn(ssoConfig).when(ssoConfigService).updateSsoConfig(ssoConfig); + + final AuthenticationSsoBean resultAuthenticationSsoBean = authenticationService.setAuthenticationSso(authenticationSsoBean); + verify(ssoConfigService, times(1)).updateSsoConfig(ssoConfig); + assertEquals(authenticationSsoBean.getShowOnLogin(), resultAuthenticationSsoBean.getShowOnLogin()); + } + +}