From c596f1afef33e350398184bfba965efe2cc37cc0 Mon Sep 17 00:00:00 2001
From: Patrick Hobusch <patrick@hobusch.net>
Date: Fri, 1 Mar 2024 15:14:51 +0800
Subject: [PATCH] WIP

---
 index.adoc                                    | 461 ++++++++++++++++++
 pom.xml                                       |   9 +-
 .../model/util/AuthenticationIdpBeanUtil.java | 135 +++++
 .../model/util/AuthenticationSsoBeanUtil.java |  46 ++
 .../rest/AuthenticationResourceImpl.java      |  23 +
 .../service/AuthenticationServiceImpl.java    | 119 +++++
 6 files changed, 792 insertions(+), 1 deletion(-)
 create mode 100644 src/main/java/de/aservo/confapi/confluence/model/util/AuthenticationIdpBeanUtil.java
 create mode 100644 src/main/java/de/aservo/confapi/confluence/model/util/AuthenticationSsoBeanUtil.java
 create mode 100644 src/main/java/de/aservo/confapi/confluence/rest/AuthenticationResourceImpl.java
 create mode 100644 src/main/java/de/aservo/confapi/confluence/service/AuthenticationServiceImpl.java

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
+
+<<AuthenticationIdpsBean>>
+
+
+===== Content Type
+
+* application/json
+
+===== Responses
+
+.HTTP Response Codes
+[cols="2,3,1"]
+|===
+| Code | Message | Datatype
+
+
+| 200
+| Returns all authentication identity providers.
+|  <<AuthenticationIdpsBean>>
+
+
+| 0
+| Returns a list of error messages.
+|  <<ErrorCollection>>
+
+|===
+
+===== 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
+
+<<AuthenticationSsoBean>>
+
+
+===== Content Type
+
+* application/json
+
+===== Responses
+
+.HTTP Response Codes
+[cols="2,3,1"]
+|===
+| Code | Message | Datatype
+
+
+| 200
+| Returns the authentication SSO configuration.
+|  <<AuthenticationSsoBean>>
+
+
+| 0
+| Returns a list of error messages.
+|  <<ErrorCollection>>
+
+|===
+
+===== 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
+|  <<AuthenticationIdpsBean>>
+| -
+| 
+| 
+
+|===
+
+
+
+
+
+===== Return Type
+
+<<AuthenticationIdpsBean>>
+
+
+===== Content Type
+
+* application/json
+
+===== Responses
+
+.HTTP Response Codes
+[cols="2,3,1"]
+|===
+| Code | Message | Datatype
+
+
+| 200
+| Returns the set authentication identity providers.
+|  <<AuthenticationIdpsBean>>
+
+
+| 0
+| Returns a list of error messages.
+|  <<ErrorCollection>>
+
+|===
+
+===== 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
+|  <<AuthenticationSsoBean>>
+| -
+| 
+| 
+
+|===
+
+
+
+
+
+===== Return Type
+
+<<AuthenticationSsoBean>>
+
+
+===== Content Type
+
+* application/json
+
+===== Responses
+
+.HTTP Response Codes
+[cols="2,3,1"]
+|===
+| Code | Message | Datatype
+
+
+| 200
+| Returns the set authentication SSO configuration.
+|  <<AuthenticationSsoBean>>
+
+
+| 0
+| Returns a list of error messages.
+|  <<ErrorCollection>>
+
+|===
+
+===== 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 <<string>>
+| 
+|  
+
+| 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 <<AuthenticationIdpOidcBean>>
+| 
+|  
+
+|===
+
+
+[#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 @@
         <atlassian.gadgets.version>8.1.4</atlassian.gadgets.version>
         <atlassian.plugin.key>${project.groupId}.${project.artifactId}</atlassian.plugin.key>
         <atlassian.spring.scanner.version>2.2.4</atlassian.spring.scanner.version>
-        <confapi-commons.version>0.4.0-SNAPSHOT</confapi-commons.version>
+        <confapi-commons.version>0.5.0</confapi-commons.version>
         <custom-favicon-api.version>2.0.2</custom-favicon-api.version>
         <javax.jaxb-api.version>2.3.1</javax.jaxb-api.version>
         <javax.ws.rs-api.version>2.1.1</javax.ws.rs-api.version>
@@ -245,6 +245,13 @@
             </exclusions>
         </dependency>
 
+        <dependency>
+            <groupId>com.atlassian.plugins.authentication</groupId>
+            <artifactId>atlassian-authentication-plugin</artifactId>
+            <version>4.3.8</version>
+            <scope>provided</scope>
+        </dependency>
+
         <dependency>
             <groupId>io.swagger.core.v3</groupId>
             <artifactId>swagger-annotations</artifactId>
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..96bf365
--- /dev/null
+++ b/src/main/java/de/aservo/confapi/confluence/model/util/AuthenticationIdpBeanUtil.java
@@ -0,0 +1,135 @@
+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 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;
+
+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.getClass().isAssignableFrom(AuthenticationIdpOidcBean.class)) {
+            return toOidcConfig((AuthenticationIdpOidcBean) authenticationIdpBean, existingIdpConfig);
+        }
+
+        throw new UnsupportedIdpTypeException();
+    }
+
+    private static OidcConfig toOidcConfig(
+            final AuthenticationIdpOidcBean authenticationIdpOidcBean,
+            final IdpConfig existingIdpConfig) {
+
+        final OidcConfig.Builder oidcConfigBuilder;
+
+        if (existingIdpConfig != null) {
+            if (!existingIdpConfig.getClass().isAssignableFrom(OidcConfig.class)) {
+                throw new BadRequestException("The existing IDP config with the same name is not of type OIDC");
+            }
+
+            oidcConfigBuilder = OidcConfig.builder((OidcConfig) existingIdpConfig);
+        } else {
+            oidcConfigBuilder = OidcConfig.builder();
+        }
+
+        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);
+        }
+
+        throw new UnsupportedIdpTypeException();
+    }
+
+    public static AuthenticationIdpOidcBean toAuthenticationIdpOidcBean(
+            final IdpConfig idpConfig) {
+
+        if (!idpConfig.getClass().isAssignableFrom(OidcConfig.class)) {
+            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 AuthenticationIdpBeanUtil() {
+    }
+
+    static class UnsupportedIdpTypeException extends UnsupportedOperationException {
+        public UnsupportedIdpTypeException() {
+            super("IDP types other than OIDC are not (yet) supported");
+        }
+    }
+
+}
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..317a5e9
--- /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..5478f10
--- /dev/null
+++ b/src/main/java/de/aservo/confapi/confluence/service/AuthenticationServiceImpl.java
@@ -0,0 +1,119 @@
+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 com.atlassian.plugins.authentication.api.config.oidc.OidcConfig;
+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);
+        }
+
+        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 (!OidcConfig.class.isAssignableFrom(existingIdpConfig.getClass())) {
+            throw new BadRequestException("IDP types other than OIDC are not (yet) supported");
+        }
+
+        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<String, IdpConfig> 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<AbstractAuthenticationIdpBean> authenticationIdpBeanComparator = new Comparator<AbstractAuthenticationIdpBean>() {
+        @Override
+        public int compare(AbstractAuthenticationIdpBean a1, AbstractAuthenticationIdpBean a2) {
+            return a1.getName().compareToIgnoreCase(a2.getName());
+        }
+    };
+
+}