diff --git a/pom.xml b/pom.xml
index 63705b7..1d64c5f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@
confapi-commons
- 0.4.1-SNAPSHOT
+ 0.5.0-SNAPSHOT
jar
ConfAPI Commons
diff --git a/src/main/java/de/aservo/confapi/commons/constants/ConfAPI.java b/src/main/java/de/aservo/confapi/commons/constants/ConfAPI.java
index 9da103e..3a3d3d4 100644
--- a/src/main/java/de/aservo/confapi/commons/constants/ConfAPI.java
+++ b/src/main/java/de/aservo/confapi/commons/constants/ConfAPI.java
@@ -7,6 +7,12 @@ public class ConfAPI {
public static final String APPLICATIONS = "applications";
public static final String APPLICATION_LINK = "application-link";
public static final String APPLICATION_LINKS = "application-links";
+ public static final String AUTHENTICATION = "authentication";
+ public static final String AUTHENTICATION_IDP = "idp";
+ public static final String AUTHENTICATION_IDPS = "idps";
+ public static final String AUTHENTICATION_IDP_OIDC = "oidc";
+ public static final String AUTHENTICATION_IDP_SAML = "saml";
+ public static final String AUTHENTICATION_SSO = "sso";
public static final String BACKUP = "backup";
public static final String BACKUP_EXPORT = "export";
public static final String BACKUP_IMPORT = "import";
diff --git a/src/main/java/de/aservo/confapi/commons/model/AbstractAuthenticationIdpBean.java b/src/main/java/de/aservo/confapi/commons/model/AbstractAuthenticationIdpBean.java
new file mode 100644
index 0000000..a679f84
--- /dev/null
+++ b/src/main/java/de/aservo/confapi/commons/model/AbstractAuthenticationIdpBean.java
@@ -0,0 +1,45 @@
+package de.aservo.confapi.commons.model;
+
+import de.aservo.confapi.commons.constants.ConfAPI;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.codehaus.jackson.annotate.JsonSubTypes;
+import org.codehaus.jackson.annotate.JsonTypeInfo;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@Data
+@NoArgsConstructor
+@XmlRootElement
+@JsonTypeInfo(
+ use = JsonTypeInfo.Id.NAME,
+ include = JsonTypeInfo.As.PROPERTY,
+ property = "type"
+)
+// Note: New subtypes must also be registered in AuthenticationIdpsBean.java to be considered in the REST API documentation
+@JsonSubTypes({
+ @JsonSubTypes.Type(value = AuthenticationIdpOidcBean.class, name = ConfAPI.AUTHENTICATION_IDP_OIDC),
+ @JsonSubTypes.Type(value = AuthenticationIdpSamlBean.class, name = ConfAPI.AUTHENTICATION_IDP_SAML),
+})
+public abstract class AbstractAuthenticationIdpBean {
+
+ @XmlElement
+ private Long id;
+
+ @XmlElement
+ private String name;
+
+ @XmlElement
+ private Boolean enabled;
+
+ @XmlElement
+ private String url;
+
+ @XmlElement
+ private Boolean enableRememberMe;
+
+ @XmlElement
+ private String buttonText;
+
+}
diff --git a/src/main/java/de/aservo/confapi/commons/model/AuthenticationIdpOidcBean.java b/src/main/java/de/aservo/confapi/commons/model/AuthenticationIdpOidcBean.java
new file mode 100644
index 0000000..e905b63
--- /dev/null
+++ b/src/main/java/de/aservo/confapi/commons/model/AuthenticationIdpOidcBean.java
@@ -0,0 +1,67 @@
+package de.aservo.confapi.commons.model;
+
+import de.aservo.confapi.commons.constants.ConfAPI;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import java.util.Collections;
+import java.util.List;
+
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@XmlRootElement(name = ConfAPI.AUTHENTICATION + "-" + ConfAPI.AUTHENTICATION_IDP + "-" + ConfAPI.AUTHENTICATION_IDP_OIDC)
+public class AuthenticationIdpOidcBean extends AbstractAuthenticationIdpBean {
+
+ @XmlElement
+ private String clientId;
+
+ @XmlElement
+ private String clientSecret;
+
+ @XmlElement
+ private String usernameClaim;
+
+ @XmlElement
+ private List additionalScopes;
+
+ @XmlElement
+ private Boolean discoveryEnabled;
+
+ @XmlElement
+ private String authorizationEndpoint;
+
+ @XmlElement
+ private String tokenEndpoint;
+
+ @XmlElement
+ private String userInfoEndpoint;
+
+ // Just-in-time user provisioning is not implemented yet
+
+ // Example instances for documentation and tests
+
+ public static final AuthenticationIdpOidcBean EXAMPLE_1;
+
+ static {
+ EXAMPLE_1 = new AuthenticationIdpOidcBean();
+ EXAMPLE_1.setId(1L);
+ EXAMPLE_1.setName("OIDC");
+ EXAMPLE_1.setEnabled(true);
+ EXAMPLE_1.setUrl("https://oidc.example.com");
+ EXAMPLE_1.setEnableRememberMe(true);
+ EXAMPLE_1.setButtonText("Login with OIDC");
+ EXAMPLE_1.setClientId("oidc");
+ EXAMPLE_1.setClientSecret("s3cr3t");
+ EXAMPLE_1.setUsernameClaim("userName");
+ EXAMPLE_1.setAdditionalScopes(Collections.emptyList());
+ EXAMPLE_1.setDiscoveryEnabled(false);
+ EXAMPLE_1.setAuthorizationEndpoint("https://oidc.example.com/authorization");
+ EXAMPLE_1.setTokenEndpoint("https://oidc.example.com/token");
+ EXAMPLE_1.setUserInfoEndpoint("https://oidc.example.com/userinfo");
+ }
+
+}
diff --git a/src/main/java/de/aservo/confapi/commons/model/AuthenticationIdpSamlBean.java b/src/main/java/de/aservo/confapi/commons/model/AuthenticationIdpSamlBean.java
new file mode 100644
index 0000000..e428fdf
--- /dev/null
+++ b/src/main/java/de/aservo/confapi/commons/model/AuthenticationIdpSamlBean.java
@@ -0,0 +1,41 @@
+package de.aservo.confapi.commons.model;
+
+import de.aservo.confapi.commons.constants.ConfAPI;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+@XmlRootElement(name = ConfAPI.AUTHENTICATION + "-" + ConfAPI.AUTHENTICATION_IDP + "-" + ConfAPI.AUTHENTICATION_IDP_SAML)
+public class AuthenticationIdpSamlBean extends AbstractAuthenticationIdpBean {
+
+ @XmlElement
+ private String certificate;
+
+ @XmlElement
+ private String usernameAttribute;
+
+ // Just-in-time user provisioning is not implemented yet
+
+ // Example instances for documentation and tests
+
+ public static final AuthenticationIdpSamlBean EXAMPLE_1;
+
+ static {
+ EXAMPLE_1 = new AuthenticationIdpSamlBean();
+ EXAMPLE_1.setId(1L);
+ EXAMPLE_1.setName("SAML");
+ EXAMPLE_1.setEnabled(true);
+ EXAMPLE_1.setUrl("https://saml.example.com");
+ EXAMPLE_1.setEnableRememberMe(true);
+ EXAMPLE_1.setButtonText("Login with SAML");
+ EXAMPLE_1.setCertificate("certificate");
+ EXAMPLE_1.setUsernameAttribute("username");
+ }
+
+}
diff --git a/src/main/java/de/aservo/confapi/commons/model/AuthenticationIdpsBean.java b/src/main/java/de/aservo/confapi/commons/model/AuthenticationIdpsBean.java
new file mode 100644
index 0000000..6386361
--- /dev/null
+++ b/src/main/java/de/aservo/confapi/commons/model/AuthenticationIdpsBean.java
@@ -0,0 +1,25 @@
+package de.aservo.confapi.commons.model;
+
+import de.aservo.confapi.commons.constants.ConfAPI;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import java.util.Collection;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@XmlRootElement(name = ConfAPI.AUTHENTICATION + "-" + ConfAPI.AUTHENTICATION_IDPS)
+public class AuthenticationIdpsBean {
+
+ @XmlElement
+ @Schema(anyOf = {
+ AuthenticationIdpOidcBean.class,
+ })
+ private Collection authenticationIdpBeans;
+
+}
diff --git a/src/main/java/de/aservo/confapi/commons/model/AuthenticationSsoBean.java b/src/main/java/de/aservo/confapi/commons/model/AuthenticationSsoBean.java
new file mode 100644
index 0000000..385f5e9
--- /dev/null
+++ b/src/main/java/de/aservo/confapi/commons/model/AuthenticationSsoBean.java
@@ -0,0 +1,27 @@
+package de.aservo.confapi.commons.model;
+
+import de.aservo.confapi.commons.constants.ConfAPI;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@Data
+@NoArgsConstructor
+@XmlRootElement(name = ConfAPI.AUTHENTICATION + "-" + ConfAPI.AUTHENTICATION_SSO)
+public class AuthenticationSsoBean {
+
+ @XmlElement
+ private Boolean showOnLogin;
+
+ // Example instances for documentation and tests
+
+ public static final AuthenticationSsoBean EXAMPLE_1;
+
+ static {
+ EXAMPLE_1 = new AuthenticationSsoBean();
+ EXAMPLE_1.setShowOnLogin(true);
+ }
+
+}
diff --git a/src/main/java/de/aservo/confapi/commons/rest/AbstractAuthenticationResourceImpl.java b/src/main/java/de/aservo/confapi/commons/rest/AbstractAuthenticationResourceImpl.java
new file mode 100644
index 0000000..b294046
--- /dev/null
+++ b/src/main/java/de/aservo/confapi/commons/rest/AbstractAuthenticationResourceImpl.java
@@ -0,0 +1,48 @@
+package de.aservo.confapi.commons.rest;
+
+import de.aservo.confapi.commons.model.AuthenticationIdpsBean;
+import de.aservo.confapi.commons.model.AuthenticationSsoBean;
+import de.aservo.confapi.commons.rest.api.AuthenticationResource;
+import de.aservo.confapi.commons.service.api.AuthenticationService;
+
+import javax.ws.rs.core.Response;
+
+public abstract class AbstractAuthenticationResourceImpl implements AuthenticationResource {
+
+ private final AuthenticationService authenticationService;
+
+ protected AbstractAuthenticationResourceImpl(
+ final AuthenticationService authenticationService) {
+
+ this.authenticationService = authenticationService;
+ }
+
+ @Override
+ public Response getAuthenticationIdps() {
+ final AuthenticationIdpsBean resultAuthenticationIdpsBean = authenticationService.getAuthenticationIdps();
+ return Response.ok(resultAuthenticationIdpsBean).build();
+ }
+
+ @Override
+ public Response setAuthenticationIdps(
+ final AuthenticationIdpsBean authenticationIdpsBean) {
+
+ final AuthenticationIdpsBean resultAuthenticationIdpsBean = authenticationService.setAuthenticationIdps(authenticationIdpsBean);
+ return Response.ok(resultAuthenticationIdpsBean).build();
+ }
+
+ @Override
+ public Response getAuthenticationSso() {
+ final AuthenticationSsoBean resultAuthenticationSsoBean = authenticationService.getAuthenticationSso();
+ return Response.ok(resultAuthenticationSsoBean).build();
+ }
+
+ @Override
+ public Response setAuthenticationSso(
+ final AuthenticationSsoBean authenticationSsoBean) {
+
+ final AuthenticationSsoBean resultAuthenticationSsoBean = authenticationService.setAuthenticationSso(authenticationSsoBean);
+ return Response.ok(resultAuthenticationSsoBean).build();
+ }
+
+}
diff --git a/src/main/java/de/aservo/confapi/commons/rest/api/AuthenticationResource.java b/src/main/java/de/aservo/confapi/commons/rest/api/AuthenticationResource.java
new file mode 100644
index 0000000..af33e10
--- /dev/null
+++ b/src/main/java/de/aservo/confapi/commons/rest/api/AuthenticationResource.java
@@ -0,0 +1,98 @@
+package de.aservo.confapi.commons.rest.api;
+
+import de.aservo.confapi.commons.constants.ConfAPI;
+import de.aservo.confapi.commons.http.PATCH;
+import de.aservo.confapi.commons.model.AuthenticationIdpsBean;
+import de.aservo.confapi.commons.model.AuthenticationSsoBean;
+import de.aservo.confapi.commons.model.ErrorCollection;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+public interface AuthenticationResource {
+
+ @GET
+ @Path(ConfAPI.AUTHENTICATION_IDPS)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Operation(
+ tags = { ConfAPI.AUTHENTICATION },
+ summary = "Get all authentication identity providers",
+ responses = {
+ @ApiResponse(
+ responseCode = "200", content = @Content(schema = @Schema(implementation = AuthenticationIdpsBean.class)),
+ description = "Returns all authentication identity providers."),
+ @ApiResponse(
+ content = @Content(schema = @Schema(implementation = ErrorCollection.class)),
+ description = "Returns a list of error messages."
+ )
+ }
+ )
+ Response getAuthenticationIdps();
+
+ @PATCH
+ @Path(ConfAPI.AUTHENTICATION_IDPS)
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Operation(
+ tags = { ConfAPI.AUTHENTICATION },
+ summary = "Set all authentication identity providers",
+ responses = {
+ @ApiResponse(
+ responseCode = "200", content = @Content(schema = @Schema(implementation = AuthenticationIdpsBean.class)),
+ description = "Returns the set authentication identity providers."),
+ @ApiResponse(
+ content = @Content(schema = @Schema(implementation = ErrorCollection.class)),
+ description = "Returns a list of error messages."
+ )
+ }
+ )
+ Response setAuthenticationIdps(
+ final AuthenticationIdpsBean authenticationIdpsBean);
+
+ @GET
+ @Path(ConfAPI.AUTHENTICATION_SSO)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Operation(
+ tags = { ConfAPI.AUTHENTICATION },
+ summary = "Get authentication SSO configuration",
+ responses = {
+ @ApiResponse(
+ responseCode = "200", content = @Content(schema = @Schema(implementation = AuthenticationSsoBean.class)),
+ description = "Returns the authentication SSO configuration."),
+ @ApiResponse(
+ content = @Content(schema = @Schema(implementation = ErrorCollection.class)),
+ description = "Returns a list of error messages."
+ )
+ }
+ )
+ Response getAuthenticationSso();
+
+ @PATCH
+ @Path(ConfAPI.AUTHENTICATION_SSO)
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Operation(
+ tags = { ConfAPI.AUTHENTICATION },
+ summary = "Set authentication SSO configuration",
+ responses = {
+ @ApiResponse(
+ responseCode = "200", content = @Content(schema = @Schema(implementation = AuthenticationSsoBean.class)),
+ description = "Returns the set authentication SSO configuration."),
+ @ApiResponse(
+ content = @Content(schema = @Schema(implementation = ErrorCollection.class)),
+ description = "Returns a list of error messages."
+ )
+ }
+ )
+ Response setAuthenticationSso(
+ final AuthenticationSsoBean authenticationSsoBean);
+
+}
diff --git a/src/main/java/de/aservo/confapi/commons/service/api/AuthenticationService.java b/src/main/java/de/aservo/confapi/commons/service/api/AuthenticationService.java
new file mode 100644
index 0000000..a099c9c
--- /dev/null
+++ b/src/main/java/de/aservo/confapi/commons/service/api/AuthenticationService.java
@@ -0,0 +1,22 @@
+package de.aservo.confapi.commons.service.api;
+
+import de.aservo.confapi.commons.model.AbstractAuthenticationIdpBean;
+import de.aservo.confapi.commons.model.AuthenticationIdpsBean;
+import de.aservo.confapi.commons.model.AuthenticationSsoBean;
+
+public interface AuthenticationService {
+
+ AuthenticationIdpsBean getAuthenticationIdps();
+
+ AuthenticationIdpsBean setAuthenticationIdps(
+ AuthenticationIdpsBean authenticationIdpsBean);
+
+ AbstractAuthenticationIdpBean setAuthenticationIdp(
+ AbstractAuthenticationIdpBean authenticationIdpBean);
+
+ AuthenticationSsoBean getAuthenticationSso();
+
+ AuthenticationSsoBean setAuthenticationSso(
+ AuthenticationSsoBean authenticationSsoBean);
+
+}
diff --git a/src/test/java/de/aservo/confapi/commons/rest/AuthenticationResourceTest.java b/src/test/java/de/aservo/confapi/commons/rest/AuthenticationResourceTest.java
new file mode 100644
index 0000000..440c04f
--- /dev/null
+++ b/src/test/java/de/aservo/confapi/commons/rest/AuthenticationResourceTest.java
@@ -0,0 +1,88 @@
+package de.aservo.confapi.commons.rest;
+
+import de.aservo.confapi.commons.model.*;
+import de.aservo.confapi.commons.rest.impl.TestAuthenticationResourceImpl;
+import de.aservo.confapi.commons.service.api.AuthenticationService;
+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 javax.ws.rs.core.Response;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.doReturn;
+
+@ExtendWith(MockitoExtension.class)
+class AuthenticationResourceTest {
+
+ @Mock
+ private AuthenticationService authenticationService;
+
+ private TestAuthenticationResourceImpl resource;
+
+ @BeforeEach
+ public void setup() {
+ resource = new TestAuthenticationResourceImpl(authenticationService);
+ }
+
+ @Test
+ void testGetAuthenticationIdps() {
+ final Collection authenticationIdpBeans = Arrays.asList(
+ AuthenticationIdpOidcBean.EXAMPLE_1,
+ AuthenticationIdpSamlBean.EXAMPLE_1
+ );
+ final AuthenticationIdpsBean authenticationIdpsBean = new AuthenticationIdpsBean(authenticationIdpBeans);
+ doReturn(authenticationIdpsBean).when(authenticationService).getAuthenticationIdps();
+
+ final Response response = resource.getAuthenticationIdps();
+ assertEquals(200, response.getStatus());
+
+ final AuthenticationIdpsBean authenticationIdpsBeanResponse = (AuthenticationIdpsBean) response.getEntity();
+ assertEquals(authenticationIdpsBean, authenticationIdpsBeanResponse);
+ }
+
+ @Test
+ void testSetAuthenticationIdps() {
+ final Collection authenticationIdpBeans = Arrays.asList(
+ AuthenticationIdpOidcBean.EXAMPLE_1,
+ AuthenticationIdpSamlBean.EXAMPLE_1
+ );
+ final AuthenticationIdpsBean authenticationIdpsBean = new AuthenticationIdpsBean(authenticationIdpBeans);
+ doReturn(authenticationIdpsBean).when(authenticationService).setAuthenticationIdps(authenticationIdpsBean);
+
+ final Response response = resource.setAuthenticationIdps(authenticationIdpsBean);
+ assertEquals(200, response.getStatus());
+
+ final AuthenticationIdpsBean authenticationIdpsBeanResponse = (AuthenticationIdpsBean) response.getEntity();
+ assertEquals(authenticationIdpsBean, authenticationIdpsBeanResponse);
+ }
+
+ @Test
+ void testGetAuthenticationSso() {
+ final AuthenticationSsoBean authenticationSsoBean = AuthenticationSsoBean.EXAMPLE_1;
+ doReturn(authenticationSsoBean).when(authenticationService).getAuthenticationSso();
+
+ final Response response = resource.getAuthenticationSso();
+ assertEquals(200, response.getStatus());
+
+ final AuthenticationSsoBean authenticationSsoBeanResponse = (AuthenticationSsoBean) response.getEntity();
+ assertEquals(authenticationSsoBean, authenticationSsoBeanResponse);
+ }
+
+ @Test
+ void testSetAuthenticationSso() {
+ final AuthenticationSsoBean authenticationSsoBean = AuthenticationSsoBean.EXAMPLE_1;
+ doReturn(authenticationSsoBean).when(authenticationService).setAuthenticationSso(authenticationSsoBean);
+
+ final Response response = resource.setAuthenticationSso(authenticationSsoBean);
+ assertEquals(200, response.getStatus());
+
+ final AuthenticationSsoBean authenticationSsoBeanResponse = (AuthenticationSsoBean) response.getEntity();
+ assertEquals(authenticationSsoBean, authenticationSsoBeanResponse);
+ }
+
+}
diff --git a/src/test/java/de/aservo/confapi/commons/rest/impl/TestAuthenticationResourceImpl.java b/src/test/java/de/aservo/confapi/commons/rest/impl/TestAuthenticationResourceImpl.java
new file mode 100644
index 0000000..53869ea
--- /dev/null
+++ b/src/test/java/de/aservo/confapi/commons/rest/impl/TestAuthenticationResourceImpl.java
@@ -0,0 +1,12 @@
+package de.aservo.confapi.commons.rest.impl;
+
+import de.aservo.confapi.commons.rest.AbstractAuthenticationResourceImpl;
+import de.aservo.confapi.commons.service.api.AuthenticationService;
+
+public class TestAuthenticationResourceImpl extends AbstractAuthenticationResourceImpl {
+
+ public TestAuthenticationResourceImpl(AuthenticationService authenticationService) {
+ super(authenticationService);
+ }
+
+}