1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.wa.bootstrap;
20
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.Map;
24 import java.util.Objects;
25 import java.util.Set;
26 import java.util.TreeMap;
27 import java.util.stream.Collectors;
28 import java.util.stream.Stream;
29 import org.apache.commons.lang3.ArrayUtils;
30 import org.apache.commons.lang3.StringUtils;
31 import org.apache.commons.lang3.tuple.Pair;
32 import org.apache.syncope.client.lib.SyncopeClient;
33 import org.apache.syncope.common.lib.OIDCScopeConstants;
34 import org.apache.syncope.common.lib.to.OIDCRPClientAppTO;
35 import org.apache.syncope.common.rest.api.service.AttrRepoService;
36 import org.apache.syncope.common.rest.api.service.AuthModuleService;
37 import org.apache.syncope.common.rest.api.service.wa.WAClientAppService;
38 import org.apache.syncope.common.rest.api.service.wa.WAConfigService;
39 import org.apache.syncope.wa.bootstrap.mapping.AttrReleaseMapper;
40 import org.apache.syncope.wa.bootstrap.mapping.AttrRepoPropertySourceMapper;
41 import org.apache.syncope.wa.bootstrap.mapping.AuthModulePropertySourceMapper;
42 import org.apereo.cas.configuration.model.support.oidc.OidcDiscoveryProperties;
43 import org.apereo.cas.oidc.claims.OidcAddressScopeAttributeReleasePolicy;
44 import org.apereo.cas.oidc.claims.OidcEmailScopeAttributeReleasePolicy;
45 import org.apereo.cas.oidc.claims.OidcPhoneScopeAttributeReleasePolicy;
46 import org.apereo.cas.oidc.claims.OidcProfileScopeAttributeReleasePolicy;
47 import org.apereo.cas.services.BaseMappedAttributeReleasePolicy;
48 import org.apereo.cas.services.ChainingAttributeReleasePolicy;
49 import org.apereo.cas.services.RegisteredServiceAttributeReleasePolicy;
50 import org.apereo.cas.services.ReturnAllowedAttributeReleasePolicy;
51 import org.apereo.cas.util.crypto.CipherExecutor;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54 import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
55 import org.springframework.core.annotation.Order;
56 import org.springframework.core.env.Environment;
57 import org.springframework.core.env.MapPropertySource;
58 import org.springframework.core.env.PropertySource;
59
60 @Order
61 public class WAPropertySourceLocator implements PropertySourceLocator {
62
63 protected static final Logger LOG = LoggerFactory.getLogger(WAPropertySourceLocator.class);
64
65 protected final WARestClient waRestClient;
66
67 protected final AuthModulePropertySourceMapper authModulePropertySourceMapper;
68
69 protected final AttrRepoPropertySourceMapper attrRepoPropertySourceMapper;
70
71 protected final AttrReleaseMapper attrReleaseMapper;
72
73 protected final CipherExecutor<String, String> configurationCipher;
74
75 public WAPropertySourceLocator(
76 final WARestClient waRestClient,
77 final AuthModulePropertySourceMapper authModulePropertySourceMapper,
78 final AttrRepoPropertySourceMapper attrRepoPropertySourceMapper,
79 final AttrReleaseMapper attrReleaseMapper,
80 final CipherExecutor<String, String> configurationCipher) {
81
82 this.waRestClient = waRestClient;
83 this.authModulePropertySourceMapper = authModulePropertySourceMapper;
84 this.attrRepoPropertySourceMapper = attrRepoPropertySourceMapper;
85 this.attrReleaseMapper = attrReleaseMapper;
86 this.configurationCipher = configurationCipher;
87 }
88
89 protected Map<String, Object> index(final Map<String, Object> map, final Map<String, Integer> prefixes) {
90 Map<String, Object> indexed = map;
91
92 if (!map.isEmpty()) {
93 String prefix = map.keySet().iterator().next();
94 if (prefix.contains("[]")) {
95 prefix = StringUtils.substringBefore(prefix, "[]");
96 Integer index = prefixes.getOrDefault(prefix, 0);
97
98 indexed = map.entrySet().stream().
99 map(e -> Pair.of(e.getKey().replace("[]", "[" + index + "]"), e.getValue())).
100 collect(Collectors.toMap(Pair::getKey, Pair::getValue));
101
102 prefixes.put(prefix, index + 1);
103 }
104 }
105
106 return indexed;
107 }
108
109 @Override
110 public PropertySource<?> locate(final Environment environment) {
111 SyncopeClient syncopeClient = waRestClient.getSyncopeClient();
112 if (syncopeClient == null) {
113 LOG.warn("Application context is not ready to bootstrap WA configuration");
114 return null;
115 }
116
117 LOG.info("Bootstrapping WA configuration");
118 Map<String, Object> properties = new TreeMap<>();
119 Map<String, Integer> prefixes = new HashMap<>();
120
121 syncopeClient.getService(AuthModuleService.class).list().forEach(authModuleTO -> {
122 LOG.debug("Mapping auth module {} ", authModuleTO.getKey());
123
124 Map<String, Object> map = authModuleTO.getConf().map(authModuleTO, authModulePropertySourceMapper);
125 properties.putAll(index(map, prefixes));
126 });
127
128 syncopeClient.getService(AttrRepoService.class).list().forEach(attrRepoTO -> {
129 LOG.debug("Mapping attr repo {} ", attrRepoTO.getKey());
130
131 Map<String, Object> map = attrRepoTO.getConf().map(attrRepoTO, attrRepoPropertySourceMapper);
132 properties.putAll(index(map, prefixes));
133 });
134
135 Set<String> customClaims = syncopeClient.getService(WAClientAppService.class).list().stream().
136 filter(clientApp -> clientApp.getAttrReleasePolicy() != null
137 && clientApp.getClientAppTO() instanceof OIDCRPClientAppTO).
138 flatMap(clientApp -> {
139 OIDCRPClientAppTO rp = OIDCRPClientAppTO.class.cast(clientApp.getClientAppTO());
140
141 RegisteredServiceAttributeReleasePolicy attributeReleasePolicy =
142 attrReleaseMapper.build(clientApp.getAttrReleasePolicy());
143
144 Set<String> claims = new HashSet<>();
145 if (attributeReleasePolicy instanceof BaseMappedAttributeReleasePolicy) {
146 claims.addAll(((BaseMappedAttributeReleasePolicy) attributeReleasePolicy).
147 getAllowedAttributes().values().stream().
148 map(Objects::toString).collect(Collectors.toSet()));
149 } else if (attributeReleasePolicy instanceof ReturnAllowedAttributeReleasePolicy) {
150 claims.addAll(((ReturnAllowedAttributeReleasePolicy) attributeReleasePolicy).
151 getAllowedAttributes().stream().collect(Collectors.toSet()));
152 } else if (attributeReleasePolicy instanceof ChainingAttributeReleasePolicy) {
153 ((ChainingAttributeReleasePolicy) attributeReleasePolicy).getPolicies().stream().
154 filter(ReturnAllowedAttributeReleasePolicy.class::isInstance).
155 findFirst().map(ReturnAllowedAttributeReleasePolicy.class::cast).
156 map(p -> p.getAllowedAttributes().stream().collect(Collectors.toSet())).
157 ifPresent(claims::addAll);
158 }
159 if (rp.getScopes().contains(OIDCScopeConstants.PROFILE)) {
160 claims.removeAll(OidcProfileScopeAttributeReleasePolicy.ALLOWED_CLAIMS);
161 }
162 if (rp.getScopes().contains(OIDCScopeConstants.ADDRESS)) {
163 claims.removeAll(OidcAddressScopeAttributeReleasePolicy.ALLOWED_CLAIMS);
164 }
165 if (rp.getScopes().contains(OIDCScopeConstants.EMAIL)) {
166 claims.removeAll(OidcEmailScopeAttributeReleasePolicy.ALLOWED_CLAIMS);
167 }
168 if (rp.getScopes().contains(OIDCScopeConstants.PHONE)) {
169 claims.removeAll(OidcPhoneScopeAttributeReleasePolicy.ALLOWED_CLAIMS);
170 }
171
172 return claims.stream();
173 }).collect(Collectors.toSet());
174 if (!customClaims.isEmpty()) {
175 Stream.concat(new OidcDiscoveryProperties().getClaims().stream(), customClaims.stream()).
176 collect(Collectors.joining(","));
177
178 properties.put("cas.authn.oidc.discovery.claims",
179 Stream.concat(new OidcDiscoveryProperties().getClaims().stream(), customClaims.stream()).
180 collect(Collectors.joining(",")));
181 properties.put("cas.authn.oidc.core.user-defined-scopes.syncope",
182 customClaims.stream().collect(Collectors.joining(",")));
183 }
184
185 syncopeClient.getService(WAConfigService.class).list().forEach(attr -> properties.put(
186 attr.getSchema(), attr.getValues().stream().collect(Collectors.joining(","))));
187
188 LOG.debug("Collected WA properties: {}", properties);
189 Map<String, Object> decodedProperties = configurationCipher.decode(properties, ArrayUtils.EMPTY_OBJECT_ARRAY);
190 LOG.debug("Decoded WA properties: {}", decodedProperties);
191 return new MapPropertySource(getClass().getName(), decodedProperties);
192 }
193 }