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