Skip to content

Commit c31df78

Browse files
authored
Merge pull request #44 from aservo/feature/saml-directory
Allow setting authentication IDP and SSO config
2 parents 3cdd2e3 + dbeb992 commit c31df78

File tree

7 files changed

+535
-1
lines changed

7 files changed

+535
-1
lines changed

pom.xml

+7-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<modelVersion>4.0.0</modelVersion>
1313

1414
<artifactId>confapi-jira-plugin</artifactId>
15-
<version>0.0.7-SNAPSHOT</version>
15+
<version>0.1.0-SNAPSHOT</version>
1616
<packaging>atlassian-plugin</packaging>
1717

1818
<name>ConfAPI for Jira</name>
@@ -221,6 +221,12 @@
221221
<scope>provided</scope>
222222
</dependency>
223223

224+
<dependency>
225+
<groupId>com.atlassian.plugins.authentication</groupId>
226+
<artifactId>atlassian-authentication-plugin</artifactId>
227+
<scope>provided</scope>
228+
</dependency>
229+
224230
<dependency>
225231
<groupId>com.atlassian.plugin</groupId>
226232
<artifactId>atlassian-spring-scanner-annotation</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package de.aservo.confapi.jira.model.util;
2+
3+
import com.atlassian.plugins.authentication.api.config.IdpConfig;
4+
import com.atlassian.plugins.authentication.api.config.SsoType;
5+
import com.atlassian.plugins.authentication.api.config.oidc.OidcConfig;
6+
import com.atlassian.plugins.authentication.api.config.saml.SamlConfig;
7+
import de.aservo.confapi.commons.exception.BadRequestException;
8+
import de.aservo.confapi.commons.exception.InternalServerErrorException;
9+
import de.aservo.confapi.commons.model.AbstractAuthenticationIdpBean;
10+
import de.aservo.confapi.commons.model.AuthenticationIdpOidcBean;
11+
import de.aservo.confapi.commons.model.AuthenticationIdpSamlBean;
12+
13+
public class AuthenticationIdpBeanUtil {
14+
15+
public static IdpConfig toIdpConfig(
16+
final AbstractAuthenticationIdpBean authenticationIdpBean) {
17+
18+
return toIdpConfig(authenticationIdpBean, null);
19+
}
20+
21+
public static IdpConfig toIdpConfig(
22+
final AbstractAuthenticationIdpBean authenticationIdpBean,
23+
final IdpConfig existingIdpConfig) {
24+
25+
if (authenticationIdpBean instanceof AuthenticationIdpOidcBean) {
26+
return toOidcConfig((AuthenticationIdpOidcBean) authenticationIdpBean, existingIdpConfig);
27+
}
28+
29+
throw new BadRequestException("IDP types other than OIDC are not (yet) supported");
30+
}
31+
32+
private static OidcConfig toOidcConfig(
33+
final AuthenticationIdpOidcBean authenticationIdpOidcBean,
34+
final IdpConfig existingIdpConfig) {
35+
36+
final OidcConfig.Builder oidcConfigBuilder;
37+
38+
if (existingIdpConfig == null) {
39+
oidcConfigBuilder = OidcConfig.builder();
40+
} else {
41+
verifyIdAndType(authenticationIdpOidcBean, existingIdpConfig, OidcConfig.class);
42+
oidcConfigBuilder = OidcConfig.builder((OidcConfig) existingIdpConfig);
43+
}
44+
45+
if (authenticationIdpOidcBean.getId() != null) {
46+
oidcConfigBuilder.setId(authenticationIdpOidcBean.getId());
47+
}
48+
if (authenticationIdpOidcBean.getName() != null) {
49+
oidcConfigBuilder.setName(authenticationIdpOidcBean.getName());
50+
}
51+
if (authenticationIdpOidcBean.getEnabled() != null) {
52+
oidcConfigBuilder.setEnabled(authenticationIdpOidcBean.getEnabled());
53+
}
54+
if (authenticationIdpOidcBean.getUrl() != null) {
55+
oidcConfigBuilder.setIssuer(authenticationIdpOidcBean.getUrl());
56+
}
57+
if (authenticationIdpOidcBean.getEnableRememberMe() != null) {
58+
oidcConfigBuilder.setEnableRememberMe(authenticationIdpOidcBean.getEnableRememberMe());
59+
}
60+
if (authenticationIdpOidcBean.getButtonText() != null) {
61+
oidcConfigBuilder.setButtonText(authenticationIdpOidcBean.getButtonText());
62+
}
63+
if (authenticationIdpOidcBean.getClientId() != null) {
64+
oidcConfigBuilder.setClientId(authenticationIdpOidcBean.getClientId());
65+
}
66+
if (authenticationIdpOidcBean.getClientSecret() != null) {
67+
oidcConfigBuilder.setClientSecret(authenticationIdpOidcBean.getClientSecret());
68+
}
69+
if (authenticationIdpOidcBean.getUsernameClaim() != null) {
70+
oidcConfigBuilder.setUsernameClaim(authenticationIdpOidcBean.getUsernameClaim());
71+
}
72+
if (authenticationIdpOidcBean.getAdditionalScopes() != null) {
73+
oidcConfigBuilder.setAdditionalScopes(authenticationIdpOidcBean.getAdditionalScopes());
74+
}
75+
if (authenticationIdpOidcBean.getDiscoveryEnabled() != null) {
76+
oidcConfigBuilder.setDiscoveryEnabled(authenticationIdpOidcBean.getDiscoveryEnabled());
77+
}
78+
if (authenticationIdpOidcBean.getAuthorizationEndpoint() != null) {
79+
oidcConfigBuilder.setAuthorizationEndpoint(authenticationIdpOidcBean.getAuthorizationEndpoint());
80+
}
81+
if (authenticationIdpOidcBean.getTokenEndpoint() != null) {
82+
oidcConfigBuilder.setTokenEndpoint(authenticationIdpOidcBean.getTokenEndpoint());
83+
}
84+
if (authenticationIdpOidcBean.getUserInfoEndpoint() != null) {
85+
oidcConfigBuilder.setUserInfoEndpoint(authenticationIdpOidcBean.getUserInfoEndpoint());
86+
}
87+
88+
return oidcConfigBuilder.build();
89+
}
90+
91+
public static AbstractAuthenticationIdpBean toAuthenticationIdpBean(
92+
final IdpConfig idpConfig) {
93+
94+
if (idpConfig.getSsoType().equals(SsoType.OIDC)) {
95+
return toAuthenticationIdpOidcBean(idpConfig);
96+
} else if (idpConfig.getSsoType().equals(SsoType.SAML)) {
97+
return toAuthenticationIdpSamlBean(idpConfig);
98+
}
99+
100+
throw new UnsupportedOperationException("The IDP type cannot be NONE");
101+
}
102+
103+
private static AuthenticationIdpOidcBean toAuthenticationIdpOidcBean(
104+
final IdpConfig idpConfig) {
105+
106+
if (!(idpConfig instanceof OidcConfig)) {
107+
throw new InternalServerErrorException("The class of the IDP config is not OIDC");
108+
}
109+
110+
final OidcConfig oidcConfig = (OidcConfig) idpConfig;
111+
112+
final AuthenticationIdpOidcBean authenticationIdpOidcBean = new AuthenticationIdpOidcBean();
113+
authenticationIdpOidcBean.setId(oidcConfig.getId());
114+
authenticationIdpOidcBean.setName(oidcConfig.getName());
115+
authenticationIdpOidcBean.setEnabled(oidcConfig.isEnabled());
116+
authenticationIdpOidcBean.setUrl(oidcConfig.getIssuer());
117+
authenticationIdpOidcBean.setEnableRememberMe(oidcConfig.isEnableRememberMe());
118+
authenticationIdpOidcBean.setButtonText(oidcConfig.getButtonText());
119+
authenticationIdpOidcBean.setClientId(oidcConfig.getClientId());
120+
authenticationIdpOidcBean.setUsernameClaim(oidcConfig.getUsernameClaim());
121+
authenticationIdpOidcBean.setAdditionalScopes(oidcConfig.getAdditionalScopes());
122+
authenticationIdpOidcBean.setDiscoveryEnabled(oidcConfig.isDiscoveryEnabled());
123+
authenticationIdpOidcBean.setAuthorizationEndpoint(oidcConfig.getAuthorizationEndpoint());
124+
authenticationIdpOidcBean.setTokenEndpoint(oidcConfig.getTokenEndpoint());
125+
authenticationIdpOidcBean.setUserInfoEndpoint(oidcConfig.getUserInfoEndpoint());
126+
127+
return authenticationIdpOidcBean;
128+
}
129+
130+
private static AuthenticationIdpSamlBean toAuthenticationIdpSamlBean(
131+
final IdpConfig idpConfig) {
132+
133+
if (!(idpConfig instanceof SamlConfig)) {
134+
throw new InternalServerErrorException("The class of the IDP config is not SAML");
135+
}
136+
137+
final SamlConfig samlConfig = (SamlConfig) idpConfig;
138+
139+
final AuthenticationIdpSamlBean authenticationIdpSamlBean = new AuthenticationIdpSamlBean();
140+
authenticationIdpSamlBean.setId(samlConfig.getId());
141+
authenticationIdpSamlBean.setName(samlConfig.getName());
142+
authenticationIdpSamlBean.setEnabled(samlConfig.isEnabled());
143+
authenticationIdpSamlBean.setUrl(samlConfig.getIssuer());
144+
authenticationIdpSamlBean.setEnableRememberMe(samlConfig.isEnableRememberMe());
145+
authenticationIdpSamlBean.setButtonText(samlConfig.getButtonText());
146+
// is it wanted to return the certificate here?
147+
authenticationIdpSamlBean.setUsernameAttribute(samlConfig.getUsernameAttribute());
148+
149+
return authenticationIdpSamlBean;
150+
}
151+
152+
private static void verifyIdAndType(
153+
final AbstractAuthenticationIdpBean authenticationIdpBean,
154+
final IdpConfig existingIdpConfig,
155+
final Class<? extends IdpConfig> clazz) {
156+
157+
if (authenticationIdpBean.getId() != null && !authenticationIdpBean.getId().equals(existingIdpConfig.getId())) {
158+
throw new BadRequestException("An ID has been passed but it does not match the ID of the existing IDP with the same name");
159+
}
160+
161+
if (!clazz.isAssignableFrom(existingIdpConfig.getClass())) {
162+
throw new BadRequestException("The existing IDP config with the same name is not of type OIDC");
163+
}
164+
}
165+
166+
private AuthenticationIdpBeanUtil() {
167+
}
168+
169+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package de.aservo.confapi.jira.model.util;
2+
3+
import com.atlassian.plugins.authentication.api.config.ImmutableSsoConfig;
4+
import com.atlassian.plugins.authentication.api.config.SsoConfig;
5+
import de.aservo.confapi.commons.model.AuthenticationSsoBean;
6+
7+
public class AuthenticationSsoBeanUtil {
8+
9+
public static SsoConfig toSsoConfig(
10+
final AuthenticationSsoBean authenticationSsoBean) {
11+
12+
return toSsoConfig(authenticationSsoBean, null);
13+
}
14+
15+
public static SsoConfig toSsoConfig(
16+
final AuthenticationSsoBean authenticationSsoBean,
17+
final SsoConfig existingSsoConfig) {
18+
19+
final ImmutableSsoConfig.Builder ssoConfigBuilder;
20+
21+
if (existingSsoConfig != null) {
22+
ssoConfigBuilder = ImmutableSsoConfig.toBuilder(existingSsoConfig);
23+
} else {
24+
ssoConfigBuilder = ImmutableSsoConfig.builder();
25+
}
26+
27+
if (authenticationSsoBean.getShowOnLogin() != null) {
28+
ssoConfigBuilder.setShowLoginForm(authenticationSsoBean.getShowOnLogin());
29+
}
30+
31+
return ssoConfigBuilder.build();
32+
}
33+
34+
public static AuthenticationSsoBean toAuthenticationSsoBean(
35+
final SsoConfig ssoConfig) {
36+
37+
final AuthenticationSsoBean authenticationSsoBean = new AuthenticationSsoBean();
38+
authenticationSsoBean.setShowOnLogin(ssoConfig.getShowLoginForm());
39+
40+
return authenticationSsoBean;
41+
}
42+
43+
private AuthenticationSsoBeanUtil() {
44+
}
45+
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package de.aservo.confapi.jira.rest;
2+
3+
import com.sun.jersey.spi.container.ResourceFilters;
4+
import de.aservo.confapi.commons.constants.ConfAPI;
5+
import de.aservo.confapi.commons.rest.AbstractAuthenticationResourceImpl;
6+
import de.aservo.confapi.commons.service.api.AuthenticationService;
7+
import de.aservo.confapi.jira.filter.SysadminOnlyResourceFilter;
8+
import org.springframework.stereotype.Component;
9+
10+
import javax.inject.Inject;
11+
import javax.ws.rs.Path;
12+
13+
@Path(ConfAPI.AUTHENTICATION)
14+
@ResourceFilters(SysadminOnlyResourceFilter.class)
15+
@Component
16+
public class AuthenticationResourceImpl extends AbstractAuthenticationResourceImpl {
17+
18+
@Inject
19+
public AuthenticationResourceImpl(AuthenticationService authenticationService) {
20+
super(authenticationService);
21+
}
22+
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package de.aservo.confapi.jira.service;
2+
3+
import com.atlassian.plugin.spring.scanner.annotation.export.ExportAsService;
4+
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
5+
import com.atlassian.plugins.authentication.api.config.IdpConfig;
6+
import com.atlassian.plugins.authentication.api.config.IdpConfigService;
7+
import com.atlassian.plugins.authentication.api.config.SsoConfig;
8+
import com.atlassian.plugins.authentication.api.config.SsoConfigService;
9+
import de.aservo.confapi.commons.exception.BadRequestException;
10+
import de.aservo.confapi.commons.model.AbstractAuthenticationIdpBean;
11+
import de.aservo.confapi.commons.model.AuthenticationIdpsBean;
12+
import de.aservo.confapi.commons.model.AuthenticationSsoBean;
13+
import de.aservo.confapi.commons.service.api.AuthenticationService;
14+
import de.aservo.confapi.jira.model.util.AuthenticationIdpBeanUtil;
15+
import de.aservo.confapi.jira.model.util.AuthenticationSsoBeanUtil;
16+
import org.springframework.stereotype.Component;
17+
18+
import java.util.Comparator;
19+
import java.util.Map;
20+
import java.util.function.Function;
21+
import java.util.stream.Collectors;
22+
23+
@Component
24+
@ExportAsService(AuthenticationService.class)
25+
public class AuthenticationServiceImpl implements AuthenticationService {
26+
27+
@ComponentImport
28+
private final IdpConfigService idpConfigService;
29+
30+
@ComponentImport
31+
private final SsoConfigService ssoConfigService;
32+
33+
public AuthenticationServiceImpl(
34+
final IdpConfigService idpConfigService,
35+
final SsoConfigService ssoConfigService) {
36+
37+
this.idpConfigService = idpConfigService;
38+
this.ssoConfigService = ssoConfigService;
39+
}
40+
41+
@Override
42+
public AuthenticationIdpsBean getAuthenticationIdps() {
43+
return new AuthenticationIdpsBean(idpConfigService.getIdpConfigs().stream()
44+
.map(AuthenticationIdpBeanUtil::toAuthenticationIdpBean)
45+
.sorted(authenticationIdpBeanComparator)
46+
.collect(Collectors.toList()));
47+
}
48+
49+
@Override
50+
public AuthenticationIdpsBean setAuthenticationIdps(
51+
final AuthenticationIdpsBean authenticationIdpsBean) {
52+
53+
return new AuthenticationIdpsBean(authenticationIdpsBean.getAuthenticationIdpBeans().stream()
54+
.map(this::setAuthenticationIdp)
55+
.sorted(authenticationIdpBeanComparator)
56+
.collect(Collectors.toList()));
57+
}
58+
59+
public AbstractAuthenticationIdpBean setAuthenticationIdp(
60+
final AbstractAuthenticationIdpBean authenticationIdpBean) {
61+
62+
if (authenticationIdpBean.getName() == null || authenticationIdpBean.getName().trim().isEmpty()) {
63+
throw new BadRequestException("The name cannot be empty");
64+
}
65+
66+
final IdpConfig existingIdpConfig = findIdpConfigByName(authenticationIdpBean.getName());
67+
68+
if (existingIdpConfig == null) {
69+
final IdpConfig idpConfig = AuthenticationIdpBeanUtil.toIdpConfig(authenticationIdpBean);
70+
final IdpConfig addedIdpConfig = idpConfigService.addIdpConfig(idpConfig);
71+
return AuthenticationIdpBeanUtil.toAuthenticationIdpBean(addedIdpConfig);
72+
}
73+
74+
final IdpConfig idpConfig = AuthenticationIdpBeanUtil.toIdpConfig(authenticationIdpBean, existingIdpConfig);
75+
final IdpConfig updatedIdpConfig = idpConfigService.updateIdpConfig(idpConfig);
76+
return AuthenticationIdpBeanUtil.toAuthenticationIdpBean(updatedIdpConfig);
77+
}
78+
79+
@Override
80+
public AuthenticationSsoBean getAuthenticationSso() {
81+
return AuthenticationSsoBeanUtil.toAuthenticationSsoBean(ssoConfigService.getSsoConfig());
82+
}
83+
84+
@Override
85+
public AuthenticationSsoBean setAuthenticationSso(AuthenticationSsoBean authenticationSsoBean) {
86+
final SsoConfig existingSsoConfig = ssoConfigService.getSsoConfig();
87+
final SsoConfig ssoConfig = AuthenticationSsoBeanUtil.toSsoConfig(authenticationSsoBean, existingSsoConfig);
88+
return AuthenticationSsoBeanUtil.toAuthenticationSsoBean(ssoConfigService.updateSsoConfig(ssoConfig));
89+
}
90+
91+
IdpConfig findIdpConfigByName(
92+
final String name) {
93+
94+
final Map<String, IdpConfig> idpConfigsByName = idpConfigService.getIdpConfigs().stream().collect(Collectors.toMap(
95+
IdpConfig::getName, Function.identity(), (existing, replacement) -> {
96+
throw new IllegalStateException("Duplicate name key found: " + existing.getName());
97+
}
98+
));
99+
100+
return idpConfigsByName.get(name);
101+
}
102+
103+
static Comparator<AbstractAuthenticationIdpBean> authenticationIdpBeanComparator = (a1, a2) -> a1.getName().compareToIgnoreCase(a2.getName());
104+
105+
}

0 commit comments

Comments
 (0)