Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow setting authentication IDP and SSO config #44

Merged
merged 1 commit into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<modelVersion>4.0.0</modelVersion>

<artifactId>confapi-jira-plugin</artifactId>
<version>0.0.7-SNAPSHOT</version>
<version>0.1.0-SNAPSHOT</version>
<packaging>atlassian-plugin</packaging>

<name>ConfAPI for Jira</name>
Expand Down Expand Up @@ -221,6 +221,12 @@
<scope>provided</scope>
</dependency>

<dependency>
<groupId>com.atlassian.plugins.authentication</groupId>
<artifactId>atlassian-authentication-plugin</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>com.atlassian.plugin</groupId>
<artifactId>atlassian-spring-scanner-annotation</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package de.aservo.confapi.jira.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<? extends IdpConfig> 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() {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package de.aservo.confapi.jira.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() {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package de.aservo.confapi.jira.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.jira.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);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package de.aservo.confapi.jira.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.jira.model.util.AuthenticationIdpBeanUtil;
import de.aservo.confapi.jira.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<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 = (a1, a2) -> a1.getName().compareToIgnoreCase(a2.getName());

}
Loading