blob: acf653ac6634d88f326facafbd3cff1b0a3c399f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.syncope.core.logic.oidc;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.syncope.common.lib.AnyOperations;
import org.apache.syncope.common.lib.Attr;
import org.apache.syncope.common.lib.EntityTOUtils;
import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.common.lib.request.UserCR;
import org.apache.syncope.common.lib.request.UserUR;
import org.apache.syncope.common.lib.oidc.OIDCLoginResponse;
import org.apache.syncope.common.lib.to.PropagationStatus;
import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.entity.user.User;
import org.apache.syncope.core.provisioning.api.IntAttrName;
import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
import org.apache.syncope.core.provisioning.api.data.ItemTransformer;
import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
import org.apache.syncope.core.provisioning.java.pushpull.InboundMatcher;
import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
import org.apache.syncope.core.provisioning.java.utils.TemplateUtils;
import org.apache.syncope.core.spring.ImplementationManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.apache.syncope.core.provisioning.api.OIDCC4UIProviderActions;
import org.apache.syncope.core.persistence.api.entity.OIDCC4UIProvider;
import org.apache.syncope.core.persistence.api.entity.OIDCC4UIProviderItem;
public class OIDCUserManager {
protected static final Logger LOG = LoggerFactory.getLogger(OIDCUserManager.class);
protected static final String OIDC_CLIENT_CONTEXT = "ODIC Client";
protected final InboundMatcher inboundMatcher;
protected final UserDAO userDAO;
protected final IntAttrNameParser intAttrNameParser;
protected final TemplateUtils templateUtils;
protected final UserProvisioningManager provisioningManager;
protected final UserDataBinder binder;
public OIDCUserManager(
final InboundMatcher inboundMatcher,
final UserDAO userDAO,
final IntAttrNameParser intAttrNameParser,
final TemplateUtils templateUtils,
final UserProvisioningManager provisioningManager,
final UserDataBinder binder) {
this.inboundMatcher = inboundMatcher;
this.userDAO = userDAO;
this.intAttrNameParser = intAttrNameParser;
this.templateUtils = templateUtils;
this.provisioningManager = provisioningManager;
this.binder = binder;
}
@Transactional(readOnly = true)
public List<String> findMatchingUser(
final String connObjectKeyValue,
final OIDCC4UIProviderItem connObjectKeyItem) {
return inboundMatcher.matchByConnObjectKeyValue(
connObjectKeyItem, connObjectKeyValue, AnyTypeKind.USER, false, null).stream().
filter(match -> match.getAny() != null).
map(match -> ((User) match.getAny()).getUsername()).
collect(Collectors.toList());
}
protected List<OIDCC4UIProviderActions> getActions(final OIDCC4UIProvider op) {
List<OIDCC4UIProviderActions> actions = new ArrayList<>();
op.getActions().forEach(impl -> {
try {
actions.add(ImplementationManager.build(impl));
} catch (Exception e) {
LOG.warn("While building {}", impl, e);
}
});
return actions;
}
public void fill(final OIDCC4UIProvider op, final OIDCLoginResponse loginResponse, final UserTO userTO) {
op.getItems().forEach(item -> {
List<String> values = new ArrayList<>();
Optional<Attr> oidcAttr = loginResponse.getAttr(item.getExtAttrName());
if (oidcAttr.isPresent() && !oidcAttr.get().getValues().isEmpty()) {
values.addAll(oidcAttr.get().getValues());
List<Object> transformed = new ArrayList<>(values);
for (ItemTransformer transformer : MappingUtils.getItemTransformers(item)) {
transformed = transformer.beforePull(null, userTO, transformed);
}
values.clear();
for (Object value : transformed) {
if (value != null) {
values.add(value.toString());
}
}
}
IntAttrName intAttrName = null;
try {
intAttrName = intAttrNameParser.parse(item.getIntAttrName(), AnyTypeKind.USER);
} catch (ParseException e) {
LOG.error("Invalid intAttrName '{}' specified, ignoring", item.getIntAttrName(), e);
}
if (intAttrName != null && intAttrName.getField() != null) {
switch (intAttrName.getField()) {
case "username":
if (!values.isEmpty()) {
userTO.setUsername(values.get(0));
}
break;
default:
LOG.warn("Unsupported: {}", intAttrName.getField());
}
} else if (intAttrName != null && intAttrName.getSchemaType() != null) {
switch (intAttrName.getSchemaType()) {
case PLAIN:
Optional<Attr> attr = userTO.getPlainAttr(intAttrName.getSchema().getKey());
if (attr.isPresent()) {
attr.get().getValues().clear();
} else {
attr = Optional.of(new Attr.Builder(intAttrName.getSchema().getKey()).build());
userTO.getPlainAttrs().add(attr.get());
}
attr.get().getValues().addAll(values);
break;
default:
LOG.warn("Unsupported: {} {}", intAttrName.getSchemaType(), intAttrName.getSchema().getKey());
}
}
});
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public String create(final OIDCC4UIProvider op, final OIDCLoginResponse responseTO, final String defaultUsername) {
UserCR userCR = new UserCR();
userCR.setStorePassword(false);
if (op.getUserTemplate() != null && op.getUserTemplate().get() != null) {
templateUtils.apply(userCR, op.getUserTemplate().get());
}
List<OIDCC4UIProviderActions> actions = getActions(op);
for (OIDCC4UIProviderActions action : actions) {
userCR = action.beforeCreate(userCR, responseTO);
}
UserTO userTO = new UserTO();
fill(op, responseTO, userTO);
EntityTOUtils.toAnyCR(userTO, userCR);
if (userCR.getRealm() == null) {
userCR.setRealm(SyncopeConstants.ROOT_REALM);
}
if (userCR.getUsername() == null) {
userCR.setUsername(defaultUsername);
}
Pair<String, List<PropagationStatus>> created =
provisioningManager.create(userCR, false, userCR.getUsername(), OIDC_CLIENT_CONTEXT);
userTO = binder.getUserTO(created.getKey());
for (OIDCC4UIProviderActions action : actions) {
userTO = action.afterCreate(userTO, responseTO);
}
return userTO.getUsername();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public String update(final String username, final OIDCC4UIProvider op, final OIDCLoginResponse responseTO) {
UserTO userTO = binder.getUserTO(userDAO.findKey(username));
UserTO original = SerializationUtils.clone(userTO);
fill(op, responseTO, userTO);
UserUR userUR = AnyOperations.diff(userTO, original, true);
List<OIDCC4UIProviderActions> actions = getActions(op);
for (OIDCC4UIProviderActions action : actions) {
userUR = action.beforeUpdate(userUR, responseTO);
}
Pair<UserUR, List<PropagationStatus>> updated =
provisioningManager.update(userUR, false, userTO.getUsername(), OIDC_CLIENT_CONTEXT);
userTO = binder.getUserTO(updated.getLeft().getKey());
for (OIDCC4UIProviderActions action : actions) {
userTO = action.afterUpdate(userTO, responseTO);
}
return userTO.getUsername();
}
}