1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.core.logic.saml2;
20
21 import java.text.ParseException;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Objects;
27 import java.util.Optional;
28 import java.util.concurrent.ConcurrentHashMap;
29 import java.util.stream.Collectors;
30 import org.apache.commons.lang3.SerializationUtils;
31 import org.apache.commons.lang3.tuple.Pair;
32 import org.apache.syncope.common.lib.AnyOperations;
33 import org.apache.syncope.common.lib.Attr;
34 import org.apache.syncope.common.lib.SyncopeConstants;
35 import org.apache.syncope.common.lib.request.UserCR;
36 import org.apache.syncope.common.lib.request.UserUR;
37 import org.apache.syncope.common.lib.saml2.SAML2LoginResponse;
38 import org.apache.syncope.common.lib.to.Item;
39 import org.apache.syncope.common.lib.to.PropagationStatus;
40 import org.apache.syncope.common.lib.to.UserTO;
41 import org.apache.syncope.common.lib.types.AnyTypeKind;
42 import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
43 import org.apache.syncope.core.persistence.api.dao.SAML2SP4UIIdPDAO;
44 import org.apache.syncope.core.persistence.api.dao.UserDAO;
45 import org.apache.syncope.core.persistence.api.entity.Implementation;
46 import org.apache.syncope.core.persistence.api.entity.SAML2SP4UIIdP;
47 import org.apache.syncope.core.persistence.api.entity.user.User;
48 import org.apache.syncope.core.provisioning.api.IntAttrName;
49 import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
50 import org.apache.syncope.core.provisioning.api.SAML2SP4UIIdPActions;
51 import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
52 import org.apache.syncope.core.provisioning.api.data.ItemTransformer;
53 import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
54 import org.apache.syncope.core.provisioning.java.pushpull.InboundMatcher;
55 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
56 import org.apache.syncope.core.provisioning.java.utils.TemplateUtils;
57 import org.apache.syncope.core.spring.implementation.ImplementationManager;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
60 import org.springframework.transaction.annotation.Propagation;
61 import org.springframework.transaction.annotation.Transactional;
62
63 public class SAML2SP4UIUserManager {
64
65 private static final Logger LOG = LoggerFactory.getLogger(SAML2SP4UIUserManager.class);
66
67 private static final String SAML2SP_CONTEXT = "SAML 2.0 SP";
68
69 protected final SAML2SP4UIIdPDAO idpDAO;
70
71 protected final InboundMatcher inboundMatcher;
72
73 protected final UserDAO userDAO;
74
75 protected final ImplementationDAO implementationDAO;
76
77 protected final IntAttrNameParser intAttrNameParser;
78
79 protected final TemplateUtils templateUtils;
80
81 protected final UserProvisioningManager provisioningManager;
82
83 protected final UserDataBinder binder;
84
85 protected final Map<String, SAML2SP4UIIdPActions> perContextActions = new ConcurrentHashMap<>();
86
87 public SAML2SP4UIUserManager(
88 final SAML2SP4UIIdPDAO idpDAO,
89 final InboundMatcher inboundMatcher,
90 final UserDAO userDAO,
91 final ImplementationDAO implementationDAO,
92 final IntAttrNameParser intAttrNameParser,
93 final TemplateUtils templateUtils,
94 final UserProvisioningManager provisioningManager,
95 final UserDataBinder binder) {
96
97 this.idpDAO = idpDAO;
98 this.inboundMatcher = inboundMatcher;
99 this.userDAO = userDAO;
100 this.implementationDAO = implementationDAO;
101 this.intAttrNameParser = intAttrNameParser;
102 this.templateUtils = templateUtils;
103 this.provisioningManager = provisioningManager;
104 this.binder = binder;
105 }
106
107 @Transactional(readOnly = true)
108 public List<String> findMatchingUser(final String connObjectKeyValue, final String idpKey) {
109 SAML2SP4UIIdP idp = idpDAO.find(idpKey);
110 if (idp == null) {
111 LOG.warn("Invalid IdP: {}", idpKey);
112 return List.of();
113 }
114 if (idp.getConnObjectKeyItem().isEmpty()) {
115 LOG.warn("Unable to determine conn object key item for IdP: {}", idpKey);
116 return Collections.emptyList();
117 }
118 return inboundMatcher.matchByConnObjectKeyValue(
119 idp.getConnObjectKeyItem().get(), connObjectKeyValue, AnyTypeKind.USER, false, null).stream().
120 filter(match -> match.getAny() != null).
121 map(match -> ((User) match.getAny()).getUsername()).
122 collect(Collectors.toList());
123 }
124
125 protected List<SAML2SP4UIIdPActions> getActions(final SAML2SP4UIIdP idp) {
126 List<SAML2SP4UIIdPActions> result = new ArrayList<>();
127
128 idp.getActions().forEach(impl -> {
129 try {
130 result.add(ImplementationManager.build(
131 impl,
132 () -> perContextActions.get(impl.getKey()),
133 instance -> perContextActions.put(impl.getKey(), instance)));
134 } catch (Exception e) {
135 LOG.warn("While building {}", impl, e);
136 }
137 });
138
139 return result;
140 }
141
142 protected List<Implementation> getTransformers(final Item item) {
143 return item.getTransformers().stream().
144 map(implementationDAO::find).
145 filter(Objects::nonNull).
146 collect(Collectors.toList());
147 }
148
149 public void fill(final String idpKey, final SAML2LoginResponse loginResponse, final UserTO userTO) {
150 SAML2SP4UIIdP idp = idpDAO.find(idpKey);
151 if (idp == null) {
152 LOG.warn("Invalid IdP: {}", idpKey);
153 return;
154 }
155
156 idp.getItems().forEach(item -> {
157 List<String> values = List.of();
158 Optional<Attr> samlAttr = loginResponse.getAttr(item.getExtAttrName());
159 if (samlAttr.isPresent() && !samlAttr.get().getValues().isEmpty()) {
160 values = samlAttr.get().getValues();
161
162 List<Object> transformed = new ArrayList<>(values);
163 for (ItemTransformer transformer : MappingUtils.getItemTransformers(item, getTransformers(item))) {
164 transformed = transformer.beforePull(null, userTO, transformed);
165 }
166 values.clear();
167 for (Object value : transformed) {
168 values.add(value.toString());
169 }
170 }
171
172 IntAttrName intAttrName = null;
173 try {
174 intAttrName = intAttrNameParser.parse(item.getIntAttrName(), AnyTypeKind.USER);
175 } catch (ParseException e) {
176 LOG.error("Invalid intAttrName '{}' specified, ignoring", item.getIntAttrName(), e);
177 }
178
179 if (intAttrName != null && intAttrName.getField() != null) {
180 switch (intAttrName.getField()) {
181 case "username":
182 if (!values.isEmpty()) {
183 userTO.setUsername(values.get(0));
184 }
185 break;
186
187 default:
188 LOG.warn("Unsupported: {}", intAttrName.getField());
189 }
190 } else if (intAttrName != null && intAttrName.getSchemaType() != null) {
191 switch (intAttrName.getSchemaType()) {
192 case PLAIN:
193 Optional<Attr> attr = userTO.getPlainAttr(intAttrName.getSchema().getKey());
194 if (attr.isPresent()) {
195 attr.get().getValues().clear();
196 } else {
197 attr = Optional.of(new Attr.Builder(intAttrName.getSchema().getKey()).build());
198 userTO.getPlainAttrs().add(attr.get());
199 }
200 attr.get().getValues().addAll(values);
201 break;
202
203 default:
204 LOG.warn("Unsupported: {} {}", intAttrName.getSchemaType(), intAttrName.getSchema().getKey());
205 }
206 }
207 });
208 }
209
210 @Transactional(propagation = Propagation.REQUIRES_NEW)
211 public String create(final SAML2SP4UIIdP idp, final SAML2LoginResponse loginResponse, final String nameID) {
212 UserCR userCR = new UserCR();
213 userCR.setStorePassword(false);
214
215 if (idp.getUserTemplate() != null) {
216 templateUtils.apply(userCR, idp.getUserTemplate().get());
217 }
218
219 UserTO userTO = new UserTO();
220 fill(idp.getKey(), loginResponse, userTO);
221
222 Optional.ofNullable(userTO.getUsername()).ifPresent(userCR::setUsername);
223 userCR.getPlainAttrs().addAll(userTO.getPlainAttrs());
224
225 if (userCR.getRealm() == null) {
226 userCR.setRealm(SyncopeConstants.ROOT_REALM);
227 }
228 if (userCR.getUsername() == null) {
229 userCR.setUsername(nameID);
230 }
231
232 List<SAML2SP4UIIdPActions> actions = getActions(idp);
233 for (SAML2SP4UIIdPActions action : actions) {
234 userCR = action.beforeCreate(userCR, loginResponse);
235 }
236
237 Pair<String, List<PropagationStatus>> created =
238 provisioningManager.create(userCR, false, userCR.getUsername(), SAML2SP_CONTEXT);
239 userTO = binder.getUserTO(created.getKey());
240
241 for (SAML2SP4UIIdPActions action : actions) {
242 userTO = action.afterCreate(userTO, loginResponse);
243 }
244
245 return userTO.getUsername();
246 }
247
248 @Transactional(propagation = Propagation.REQUIRES_NEW)
249 public String update(final String username, final SAML2SP4UIIdP idp, final SAML2LoginResponse loginResponse) {
250 UserTO userTO = binder.getUserTO(userDAO.findKey(username));
251 UserTO original = SerializationUtils.clone(userTO);
252
253 fill(idp.getKey(), loginResponse, userTO);
254
255 UserUR userUR = AnyOperations.diff(userTO, original, true);
256
257 List<SAML2SP4UIIdPActions> actions = getActions(idp);
258 for (SAML2SP4UIIdPActions action : actions) {
259 userUR = action.beforeUpdate(userUR, loginResponse);
260 }
261
262 Pair<UserUR, List<PropagationStatus>> updated =
263 provisioningManager.update(userUR, false, userTO.getUsername(), SAML2SP_CONTEXT);
264 userTO = binder.getUserTO(updated.getLeft().getKey());
265
266 for (SAML2SP4UIIdPActions action : actions) {
267 userTO = action.afterUpdate(userTO, loginResponse);
268 }
269
270 return userTO.getUsername();
271 }
272 }