blob: f39f8397120717850b987649661749717be34142 [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;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.apache.commons.jexl3.MapContext;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
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.AttrPatch;
import org.apache.syncope.common.lib.request.GroupCR;
import org.apache.syncope.common.lib.request.GroupUR;
import org.apache.syncope.common.lib.request.PasswordPatch;
import org.apache.syncope.common.lib.request.StatusR;
import org.apache.syncope.common.lib.request.StringReplacePatchItem;
import org.apache.syncope.common.lib.request.UserCR;
import org.apache.syncope.common.lib.request.UserUR;
import org.apache.syncope.common.lib.scim.SCIMComplexConf;
import org.apache.syncope.common.lib.scim.SCIMConf;
import org.apache.syncope.common.lib.scim.SCIMEnterpriseUserConf;
import org.apache.syncope.common.lib.scim.SCIMManagerConf;
import org.apache.syncope.common.lib.scim.SCIMUserAddressConf;
import org.apache.syncope.common.lib.to.GroupTO;
import org.apache.syncope.common.lib.to.MembershipTO;
import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.common.lib.types.PatchOperation;
import org.apache.syncope.common.lib.types.StatusRType;
import org.apache.syncope.core.logic.scim.SCIMConfManager;
import org.apache.syncope.core.persistence.api.dao.AnyDAO;
import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
import org.apache.syncope.core.spring.security.AuthDataAccessor;
import org.apache.syncope.ext.scimv2.api.BadRequestException;
import org.apache.syncope.ext.scimv2.api.data.Group;
import org.apache.syncope.ext.scimv2.api.data.Member;
import org.apache.syncope.ext.scimv2.api.data.Meta;
import org.apache.syncope.ext.scimv2.api.data.SCIMComplexValue;
import org.apache.syncope.ext.scimv2.api.data.SCIMEnterpriseInfo;
import org.apache.syncope.ext.scimv2.api.data.SCIMGroup;
import org.apache.syncope.ext.scimv2.api.data.SCIMPatchOperation;
import org.apache.syncope.ext.scimv2.api.data.SCIMUser;
import org.apache.syncope.ext.scimv2.api.data.SCIMUserAddress;
import org.apache.syncope.ext.scimv2.api.data.SCIMUserManager;
import org.apache.syncope.ext.scimv2.api.data.SCIMUserName;
import org.apache.syncope.ext.scimv2.api.data.Value;
import org.apache.syncope.ext.scimv2.api.type.ErrorType;
import org.apache.syncope.ext.scimv2.api.type.Function;
import org.apache.syncope.ext.scimv2.api.type.PatchOp;
import org.apache.syncope.ext.scimv2.api.type.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.PageRequest;
import org.springframework.util.CollectionUtils;
public class SCIMDataBinder {
protected static final Logger LOG = LoggerFactory.getLogger(SCIMDataBinder.class);
protected static final List<String> USER_SCHEMAS = List.of(Resource.User.schema());
protected static final List<String> ENTERPRISE_USER_SCHEMAS =
List.of(Resource.User.schema(), Resource.EnterpriseUser.schema());
protected static final List<String> GROUP_SCHEMAS = List.of(Resource.Group.schema());
/**
* Translates the given SCIM filter into the equivalent JEXL expression.
*
* @param filter SCIM filter according to https://www.rfc-editor.org/rfc/rfc7644#section-3.4.2.2
* @return translated JEXL expression; see https://commons.apache.org/proper/commons-jexl/reference/syntax.html
* */
public static String filter2JexlExpression(final String filter) {
String jexlExpression = filter.
replace(" co ", " =~ ").
replace(" sw ", " =^ ").
replace(" ew ", " =$ ");
boolean endsWithPR = jexlExpression.endsWith(" pr");
int pr = endsWithPR ? jexlExpression.indexOf(" pr") : jexlExpression.indexOf(" pr ");
while (pr != -1) {
String before = jexlExpression.substring(0, pr);
int start = before.indexOf(' ') == -1 ? 0 : jexlExpression.substring(0, pr).lastIndexOf(' ', pr) + 1;
String literal = jexlExpression.substring(start, pr);
endsWithPR = jexlExpression.endsWith(" pr");
jexlExpression = jexlExpression.replace(
literal + " pr" + (endsWithPR ? "" : " "),
"not(empty(" + literal + "))" + (endsWithPR ? "" : " "));
pr = endsWithPR ? jexlExpression.indexOf(" pr") : jexlExpression.indexOf(" pr ");
}
return jexlExpression;
}
protected final SCIMConfManager confManager;
protected final UserLogic userLogic;
protected final AuthDataAccessor authDataAccessor;
public SCIMDataBinder(
final SCIMConfManager confManager,
final UserLogic userLogic,
final AuthDataAccessor authDataAccessor) {
this.confManager = confManager;
this.userLogic = userLogic;
this.authDataAccessor = authDataAccessor;
}
protected <E extends Enum<?>> void fill(
final Map<String, Attr> attrs,
final List<SCIMComplexConf<E>> confs,
final List<SCIMComplexValue> values) {
confs.forEach(conf -> {
SCIMComplexValue value = new SCIMComplexValue();
if (conf.getValue() != null && attrs.containsKey(conf.getValue())) {
value.setValue(attrs.get(conf.getValue()).getValues().get(0));
}
if (conf.getDisplay() != null && attrs.containsKey(conf.getDisplay())) {
value.setDisplay(attrs.get(conf.getDisplay()).getValues().get(0));
}
if (conf.getType() != null) {
value.setType(conf.getType().name());
}
value.setPrimary(conf.isPrimary());
if (!value.isEmpty()) {
values.add(value);
}
});
}
protected boolean output(
final List<String> attributes,
final List<String> excludedAttributes,
final String schema) {
return (attributes.isEmpty() || attributes.contains(schema))
&& (excludedAttributes.isEmpty() || !excludedAttributes.contains(schema));
}
protected <T> T output(
final List<String> attributes,
final List<String> excludedAttributes,
final String schema,
final T value) {
return output(attributes, excludedAttributes, schema)
? value
: null;
}
public SCIMUser toSCIMUser(
final UserTO userTO,
final String location,
final List<String> attributes,
final List<String> excludedAttributes) {
SCIMConf conf = confManager.get();
List<String> schemas = new ArrayList<>();
schemas.add(Resource.User.schema());
if (conf.getEnterpriseUserConf() != null) {
schemas.add(Resource.EnterpriseUser.schema());
}
SCIMUser user = new SCIMUser(
userTO.getKey(),
schemas,
new Meta(
Resource.User,
userTO.getCreationDate(),
Optional.ofNullable(userTO.getLastChangeDate()).orElse(userTO.getCreationDate()),
userTO.getETagValue(),
location),
output(attributes, excludedAttributes, "userName", userTO.getUsername()),
!userTO.isSuspended());
Map<String, Attr> attrs = new HashMap<>();
attrs.putAll(EntityTOUtils.buildAttrMap(userTO.getPlainAttrs()));
attrs.putAll(EntityTOUtils.buildAttrMap(userTO.getDerAttrs()));
attrs.putAll(EntityTOUtils.buildAttrMap(userTO.getVirAttrs()));
attrs.put("username", new Attr.Builder("username").value(userTO.getUsername()).build());
if (conf.getUserConf() != null) {
if (output(attributes, excludedAttributes, "externalId")
&& conf.getUserConf().getExternalId() != null
&& attrs.containsKey(conf.getUserConf().getExternalId())) {
user.setExternalId(attrs.get(conf.getUserConf().getExternalId()).getValues().get(0));
}
if (output(attributes, excludedAttributes, "name") && conf.getUserConf().getName() != null) {
SCIMUserName name = new SCIMUserName();
if (conf.getUserConf().getName().getFamilyName() != null
&& attrs.containsKey(conf.getUserConf().getName().getFamilyName())) {
name.setFamilyName(attrs.get(conf.getUserConf().getName().getFamilyName()).getValues().get(0));
}
if (conf.getUserConf().getName().getFormatted() != null
&& attrs.containsKey(conf.getUserConf().getName().getFormatted())) {
name.setFormatted(attrs.get(conf.getUserConf().getName().getFormatted()).getValues().get(0));
}
if (conf.getUserConf().getName().getGivenName() != null
&& attrs.containsKey(conf.getUserConf().getName().getGivenName())) {
name.setGivenName(attrs.get(conf.getUserConf().getName().getGivenName()).getValues().get(0));
}
if (conf.getUserConf().getName().getHonorificPrefix() != null
&& attrs.containsKey(conf.getUserConf().getName().getHonorificPrefix())) {
name.setHonorificPrefix(
attrs.get(conf.getUserConf().getName().getHonorificPrefix()).getValues().get(0));
}
if (conf.getUserConf().getName().getHonorificSuffix() != null
&& attrs.containsKey(conf.getUserConf().getName().getHonorificSuffix())) {
name.setHonorificSuffix(
attrs.get(conf.getUserConf().getName().getHonorificSuffix()).getValues().get(0));
}
if (conf.getUserConf().getName().getMiddleName() != null
&& attrs.containsKey(conf.getUserConf().getName().getMiddleName())) {
name.setMiddleName(attrs.get(conf.getUserConf().getName().getMiddleName()).getValues().get(0));
}
if (!name.isEmpty()) {
user.setName(name);
}
}
if (output(attributes, excludedAttributes, "displayName")
&& conf.getUserConf().getDisplayName() != null
&& attrs.containsKey(conf.getUserConf().getDisplayName())) {
user.setDisplayName(attrs.get(conf.getUserConf().getDisplayName()).getValues().get(0));
}
if (output(attributes, excludedAttributes, "nickName")
&& conf.getUserConf().getNickName() != null
&& attrs.containsKey(conf.getUserConf().getNickName())) {
user.setNickName(attrs.get(conf.getUserConf().getNickName()).getValues().get(0));
}
if (output(attributes, excludedAttributes, "profileUrl")
&& conf.getUserConf().getProfileUrl() != null
&& attrs.containsKey(conf.getUserConf().getProfileUrl())) {
user.setProfileUrl(attrs.get(conf.getUserConf().getProfileUrl()).getValues().get(0));
}
if (output(attributes, excludedAttributes, "title")
&& conf.getUserConf().getTitle() != null
&& attrs.containsKey(conf.getUserConf().getTitle())) {
user.setTitle(attrs.get(conf.getUserConf().getTitle()).getValues().get(0));
}
if (output(attributes, excludedAttributes, "userType")
&& conf.getUserConf().getUserType() != null
&& attrs.containsKey(conf.getUserConf().getUserType())) {
user.setUserType(attrs.get(conf.getUserConf().getUserType()).getValues().get(0));
}
if (output(attributes, excludedAttributes, "preferredLanguage")
&& conf.getUserConf().getPreferredLanguage() != null
&& attrs.containsKey(conf.getUserConf().getPreferredLanguage())) {
user.setPreferredLanguage(attrs.get(conf.getUserConf().getPreferredLanguage()).getValues().get(0));
}
if (output(attributes, excludedAttributes, "locale")
&& conf.getUserConf().getLocale() != null
&& attrs.containsKey(conf.getUserConf().getLocale())) {
user.setLocale(attrs.get(conf.getUserConf().getLocale()).getValues().get(0));
}
if (output(attributes, excludedAttributes, "timezone")
&& conf.getUserConf().getTimezone() != null
&& attrs.containsKey(conf.getUserConf().getTimezone())) {
user.setTimezone(attrs.get(conf.getUserConf().getTimezone()).getValues().get(0));
}
if (output(attributes, excludedAttributes, "emails")) {
fill(attrs, conf.getUserConf().getEmails(), user.getEmails());
}
if (output(attributes, excludedAttributes, "phoneNumbers")) {
fill(attrs, conf.getUserConf().getPhoneNumbers(), user.getPhoneNumbers());
}
if (output(attributes, excludedAttributes, "ims")) {
fill(attrs, conf.getUserConf().getIms(), user.getIms());
}
if (output(attributes, excludedAttributes, "photos")) {
fill(attrs, conf.getUserConf().getPhotos(), user.getPhotos());
}
if (output(attributes, excludedAttributes, "addresses")) {
conf.getUserConf().getAddresses().forEach(addressConf -> {
SCIMUserAddress address = new SCIMUserAddress();
if (addressConf.getFormatted() != null && attrs.containsKey(addressConf.getFormatted())) {
address.setFormatted(attrs.get(addressConf.getFormatted()).getValues().get(0));
}
if (addressConf.getStreetAddress() != null && attrs.containsKey(addressConf.getStreetAddress())) {
address.setStreetAddress(attrs.get(addressConf.getStreetAddress()).getValues().get(0));
}
if (addressConf.getLocality() != null && attrs.containsKey(addressConf.getLocality())) {
address.setLocality(attrs.get(addressConf.getLocality()).getValues().get(0));
}
if (addressConf.getRegion() != null && attrs.containsKey(addressConf.getRegion())) {
address.setRegion(attrs.get(addressConf.getRegion()).getValues().get(0));
}
if (addressConf.getCountry() != null && attrs.containsKey(addressConf.getCountry())) {
address.setCountry(attrs.get(addressConf.getCountry()).getValues().get(0));
}
if (addressConf.getType() != null) {
address.setType(addressConf.getType().name());
}
if (addressConf.isPrimary()) {
address.setPrimary(true);
}
if (!address.isEmpty()) {
user.getAddresses().add(address);
}
});
}
if (output(attributes, excludedAttributes, "x509Certificates")) {
conf.getUserConf().getX509Certificates().stream().filter(attrs::containsKey).
forEach(cert -> user.getX509Certificates().add(new Value(attrs.get(cert).getValues().get(0))));
}
}
if (conf.getEnterpriseUserConf() != null) {
SCIMEnterpriseInfo enterpriseInfo = new SCIMEnterpriseInfo();
if (output(attributes, excludedAttributes, "employeeNumber")
&& conf.getEnterpriseUserConf().getEmployeeNumber() != null
&& attrs.containsKey(conf.getEnterpriseUserConf().getEmployeeNumber())) {
enterpriseInfo.setEmployeeNumber(
attrs.get(conf.getEnterpriseUserConf().getEmployeeNumber()).getValues().get(0));
}
if (output(attributes, excludedAttributes, "costCenter")
&& conf.getEnterpriseUserConf().getCostCenter() != null
&& attrs.containsKey(conf.getEnterpriseUserConf().getCostCenter())) {
enterpriseInfo.setCostCenter(
attrs.get(conf.getEnterpriseUserConf().getCostCenter()).getValues().get(0));
}
if (output(attributes, excludedAttributes, "organization")
&& conf.getEnterpriseUserConf().getOrganization() != null
&& attrs.containsKey(conf.getEnterpriseUserConf().getOrganization())) {
enterpriseInfo.setOrganization(
attrs.get(conf.getEnterpriseUserConf().getOrganization()).getValues().get(0));
}
if (output(attributes, excludedAttributes, "division")
&& conf.getEnterpriseUserConf().getDivision() != null
&& attrs.containsKey(conf.getEnterpriseUserConf().getDivision())) {
enterpriseInfo.setDivision(
attrs.get(conf.getEnterpriseUserConf().getDivision()).getValues().get(0));
}
if (output(attributes, excludedAttributes, "department")
&& conf.getEnterpriseUserConf().getDepartment() != null
&& attrs.containsKey(conf.getEnterpriseUserConf().getDepartment())) {
enterpriseInfo.setDepartment(
attrs.get(conf.getEnterpriseUserConf().getDepartment()).getValues().get(0));
}
if (output(attributes, excludedAttributes, "manager")
&& conf.getEnterpriseUserConf().getManager() != null) {
SCIMUserManager manager = new SCIMUserManager();
if (conf.getEnterpriseUserConf().getManager().getKey() != null
&& attrs.containsKey(conf.getEnterpriseUserConf().getManager().getKey())) {
try {
UserTO userManager = userLogic.read(attrs.get(
conf.getEnterpriseUserConf().getManager().getKey()).getValues().get(0));
manager.setValue(userManager.getKey());
manager.setRef(
StringUtils.substringBefore(location, "/Users") + "/Users/" + userManager.getKey());
if (conf.getEnterpriseUserConf().getManager().getDisplayName() != null) {
Attr displayName = userManager.getPlainAttr(
conf.getEnterpriseUserConf().getManager().getDisplayName()).orElse(null);
if (displayName == null) {
displayName = userManager.getDerAttr(
conf.getEnterpriseUserConf().getManager().getDisplayName()).orElse(null);
}
if (displayName == null) {
displayName = userManager.getVirAttr(
conf.getEnterpriseUserConf().getManager().getDisplayName()).orElse(null);
}
if (displayName != null) {
manager.setDisplayName(displayName.getValues().get(0));
}
}
} catch (Exception e) {
LOG.error("Could not read user {}", conf.getEnterpriseUserConf().getManager().getKey(), e);
}
}
if (!manager.isEmpty()) {
enterpriseInfo.setManager(manager);
}
}
if (!enterpriseInfo.isEmpty()) {
user.setEnterpriseInfo(enterpriseInfo);
}
}
if (output(attributes, excludedAttributes, "groups")) {
userTO.getMemberships().forEach(membership -> user.getGroups().add(new Group(
membership.getGroupKey(),
StringUtils.substringBefore(location, "/Users") + "/Groups/" + membership.getGroupKey(),
membership.getGroupName(),
Function.direct)));
userTO.getDynMemberships().forEach(membership -> user.getGroups().add(new Group(
membership.getGroupKey(),
StringUtils.substringBefore(location, "/Users") + "/Groups/" + membership.getGroupKey(),
membership.getGroupName(),
Function.indirect)));
}
if (output(attributes, excludedAttributes, "entitlements")) {
authDataAccessor.getAuthorities(userTO.getUsername(), null).forEach(authority -> user.getEntitlements().
add(new Value(authority.getAuthority() + " on Realm(s) " + authority.getRealms())));
}
if (output(attributes, excludedAttributes, "roles")) {
userTO.getRoles().forEach(role -> user.getRoles().add(new Value(role)));
}
return user;
}
protected void setAttribute(
final UserTO userTO,
final String schema,
final String value) {
if (schema == null || value == null) {
return;
}
switch (schema) {
case "username" ->
userTO.setUsername(value);
default ->
userTO.getPlainAttrs().add(new Attr.Builder(schema).value(value).build());
}
}
protected <E extends Enum<?>> void setAttribute(
final Set<Attr> attrs,
final List<SCIMComplexConf<E>> confs,
final List<SCIMComplexValue> values) {
values.stream().filter(value -> value.getType() != null).forEach(value -> confs.stream().
filter(object -> value.getType().equals(object.getType().name())).findFirst().
ifPresent(conf -> attrs.add(
new Attr.Builder(conf.getValue()).value(value.getValue()).build())));
}
public UserTO toUserTO(final SCIMUser user, final boolean checkSchemas) {
if (checkSchemas
&& !USER_SCHEMAS.equals(user.getSchemas())
&& !ENTERPRISE_USER_SCHEMAS.equals(user.getSchemas())) {
throw new BadRequestException(ErrorType.invalidValue);
}
UserTO userTO = new UserTO();
userTO.setRealm(SyncopeConstants.ROOT_REALM);
userTO.setKey(user.getId());
userTO.setPassword(user.getPassword());
userTO.setUsername(user.getUserName());
SCIMConf conf = confManager.get();
if (conf.getUserConf() != null) {
setAttribute(
userTO,
conf.getUserConf().getExternalId(),
user.getExternalId());
if (conf.getUserConf().getName() != null && user.getName() != null) {
setAttribute(
userTO,
conf.getUserConf().getName().getFamilyName(),
user.getName().getFamilyName());
setAttribute(
userTO,
conf.getUserConf().getName().getFormatted(),
user.getName().getFormatted());
setAttribute(
userTO,
conf.getUserConf().getName().getGivenName(),
user.getName().getGivenName());
setAttribute(
userTO,
conf.getUserConf().getName().getHonorificPrefix(),
user.getName().getHonorificPrefix());
setAttribute(
userTO,
conf.getUserConf().getName().getHonorificSuffix(),
user.getName().getHonorificSuffix());
setAttribute(
userTO,
conf.getUserConf().getName().getMiddleName(),
user.getName().getMiddleName());
}
setAttribute(
userTO,
conf.getUserConf().getDisplayName(),
user.getDisplayName());
setAttribute(
userTO,
conf.getUserConf().getNickName(),
user.getNickName());
setAttribute(
userTO,
conf.getUserConf().getProfileUrl(),
user.getProfileUrl());
setAttribute(
userTO,
conf.getUserConf().getTitle(),
user.getTitle());
setAttribute(
userTO,
conf.getUserConf().getUserType(),
user.getUserType());
setAttribute(
userTO,
conf.getUserConf().getPreferredLanguage(),
user.getPreferredLanguage());
setAttribute(
userTO,
conf.getUserConf().getLocale(),
user.getLocale());
setAttribute(
userTO,
conf.getUserConf().getTimezone(),
user.getTimezone());
setAttribute(userTO.getPlainAttrs(), conf.getUserConf().getEmails(), user.getEmails());
setAttribute(userTO.getPlainAttrs(), conf.getUserConf().getPhoneNumbers(), user.getPhoneNumbers());
setAttribute(userTO.getPlainAttrs(), conf.getUserConf().getIms(), user.getIms());
setAttribute(userTO.getPlainAttrs(), conf.getUserConf().getPhotos(), user.getPhotos());
user.getAddresses().stream().filter(address -> address.getType() != null).
forEach(address -> conf.getUserConf().getAddresses().stream().
filter(object -> address.getType().equals(object.getType().name())).findFirst().
ifPresent(addressConf -> {
setAttribute(
userTO,
addressConf.getFormatted(),
address.getFormatted());
setAttribute(
userTO,
addressConf.getStreetAddress(),
address.getStreetAddress());
setAttribute(
userTO,
addressConf.getLocality(),
address.getLocality());
setAttribute(
userTO,
addressConf.getRegion(),
address.getRegion());
setAttribute(
userTO,
addressConf.getPostalCode(),
address.getPostalCode());
setAttribute(
userTO,
addressConf.getCountry(),
address.getCountry());
}));
for (int i = 0; i < user.getX509Certificates().size(); i++) {
Value certificate = user.getX509Certificates().get(i);
if (conf.getUserConf().getX509Certificates().size() > i) {
setAttribute(
userTO,
conf.getUserConf().getX509Certificates().get(i),
certificate.getValue());
}
}
}
if (conf.getEnterpriseUserConf() != null && user.getEnterpriseInfo() != null) {
setAttribute(
userTO,
conf.getEnterpriseUserConf().getEmployeeNumber(),
user.getEnterpriseInfo().getEmployeeNumber());
setAttribute(
userTO,
conf.getEnterpriseUserConf().getCostCenter(),
user.getEnterpriseInfo().getCostCenter());
setAttribute(
userTO,
conf.getEnterpriseUserConf().getOrganization(),
user.getEnterpriseInfo().getOrganization());
setAttribute(
userTO,
conf.getEnterpriseUserConf().getDivision(),
user.getEnterpriseInfo().getDivision());
setAttribute(
userTO,
conf.getEnterpriseUserConf().getDepartment(),
user.getEnterpriseInfo().getDepartment());
setAttribute(
userTO,
Optional.ofNullable(conf.getEnterpriseUserConf().getManager()).
map(SCIMManagerConf::getKey).orElse(null),
Optional.ofNullable(user.getEnterpriseInfo().getManager()).
map(SCIMUserManager::getValue).orElse(null));
}
userTO.getMemberships().addAll(user.getGroups().stream().
map(group -> new MembershipTO.Builder(group.getValue()).build()).
toList());
userTO.getRoles().addAll(user.getRoles().stream().
map(Value::getValue).
toList());
return userTO;
}
public UserCR toUserCR(final SCIMUser user) {
UserTO userTO = toUserTO(user, true);
UserCR userCR = new UserCR();
EntityTOUtils.toAnyCR(userTO, userCR);
return userCR;
}
protected void setAttribute(final Set<AttrPatch> attrs, final String schema, final SCIMPatchOperation op) {
Optional.ofNullable(schema).ifPresent(a -> {
Attr.Builder attr = new Attr.Builder(a);
if (!CollectionUtils.isEmpty(op.getValue())) {
attr.value(op.getValue().get(0).toString());
}
attrs.add(new AttrPatch.Builder(attr.build()).
operation(op.getOp() == PatchOp.remove ? PatchOperation.DELETE : PatchOperation.ADD_REPLACE).
build());
});
}
protected <E extends Enum<?>> void setAttribute(
final Set<AttrPatch> attrs,
final List<SCIMComplexConf<E>> confs,
final SCIMPatchOperation op) {
confs.stream().
filter(conf -> BooleanUtils.toBoolean(JexlUtils.evaluateExpr(
filter2JexlExpression(op.getPath().getFilter()),
new MapContext(Map.of("type", conf.getType().name()))).toString())).findFirst().
ifPresent(conf -> {
if (op.getPath().getSub() == null || "display".equals(op.getPath().getSub())) {
setAttribute(attrs, conf.getDisplay(), op);
}
if (op.getPath().getSub() == null || "value".equals(op.getPath().getSub())) {
setAttribute(attrs, conf.getValue(), op);
}
});
}
protected <E extends Enum<?>> void setAttribute(
final Set<AttrPatch> attrs,
final List<SCIMComplexConf<E>> confs,
final List<SCIMComplexValue> values,
final PatchOp patchOp) {
values.stream().
filter(value -> value.getType() != null).forEach(value -> confs.stream().
filter(conf -> value.getType().equals(conf.getType().name())).findFirst().
ifPresent(conf -> attrs.add(new AttrPatch.Builder(
new Attr.Builder(conf.getValue()).value(value.getValue()).build()).
operation(patchOp == PatchOp.remove ? PatchOperation.DELETE : PatchOperation.ADD_REPLACE).
build())));
}
protected void setAttribute(
final Set<AttrPatch> attrs,
final SCIMUserAddressConf conf,
final SCIMPatchOperation op) {
if (op.getPath().getSub() == null || "formatted".equals(op.getPath().getSub())) {
setAttribute(attrs, conf.getFormatted(), op);
}
if (op.getPath().getSub() == null || "streetAddress".equals(op.getPath().getSub())) {
setAttribute(attrs, conf.getStreetAddress(), op);
}
if (op.getPath().getSub() == null || "locality".equals(op.getPath().getSub())) {
setAttribute(attrs, conf.getLocality(), op);
}
if (op.getPath().getSub() == null || "region".equals(op.getPath().getSub())) {
setAttribute(attrs, conf.getRegion(), op);
}
if (op.getPath().getSub() == null || "postalCode".equals(op.getPath().getSub())) {
setAttribute(attrs, conf.getPostalCode(), op);
}
if (op.getPath().getSub() == null || "country".equals(op.getPath().getSub())) {
setAttribute(attrs, conf.getCountry(), op);
}
}
public Pair<UserUR, StatusR> toUserUpdate(
final UserTO before,
final Collection<String> resources,
final SCIMPatchOperation op) {
StatusR statusR = null;
if (op.getPath() == null && op.getOp() != PatchOp.remove
&& !CollectionUtils.isEmpty(op.getValue()) && op.getValue().get(0) instanceof SCIMUser) {
SCIMUser after = (SCIMUser) op.getValue().get(0);
if (after.getActive() != null && before.isSuspended() == after.isActive()) {
statusR = new StatusR.Builder(
before.getKey(),
after.isActive() ? StatusRType.REACTIVATE : StatusRType.SUSPEND).
resources(resources).
build();
}
UserTO updated = toUserTO(after, false);
updated.setKey(before.getKey());
return Pair.of(AnyOperations.diff(updated, before, true), statusR);
}
UserUR userUR = new UserUR.Builder(before.getKey()).build();
SCIMConf conf = confManager.get();
if (conf == null) {
return Pair.of(userUR, statusR);
}
switch (op.getPath().getAttribute()) {
case "externalId" ->
setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getExternalId(), op);
case "userName" -> {
if (op.getOp() != PatchOp.remove && !CollectionUtils.isEmpty(op.getValue())) {
userUR.setUsername(new StringReplacePatchItem.Builder().
value(op.getValue().get(0).toString()).build());
}
}
case "password" -> {
if (op.getOp() != PatchOp.remove && !CollectionUtils.isEmpty(op.getValue())) {
userUR.setPassword(new PasswordPatch.Builder().
value(op.getValue().get(0).toString()).build());
}
}
case "active" -> {
if (!CollectionUtils.isEmpty(op.getValue())) {
// Workaround for Microsoft Entra being not SCIM compliant on PATCH requests
if (op.getValue().get(0) instanceof String a) {
op.setValue(List.of(BooleanUtils.toBoolean(a)));
}
statusR = new StatusR.Builder(
before.getKey(),
(boolean) op.getValue().get(0) ? StatusRType.REACTIVATE : StatusRType.SUSPEND).
resources(resources).
build();
}
}
case "name" -> {
if (conf.getUserConf().getName() != null) {
if (op.getPath().getSub() == null || "familyName".equals(op.getPath().getSub())) {
setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getName().getFamilyName(), op);
}
if (op.getPath().getSub() == null || "formatted".equals(op.getPath().getSub())) {
setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getName().getFormatted(), op);
}
if (op.getPath().getSub() == null || "givenName".equals(op.getPath().getSub())) {
setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getName().getGivenName(), op);
}
if (op.getPath().getSub() == null || "honorificPrefix".equals(op.getPath().getSub())) {
setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getName().getHonorificPrefix(), op);
}
if (op.getPath().getSub() == null || "honorificSuffix".equals(op.getPath().getSub())) {
setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getName().getHonorificSuffix(), op);
}
if (op.getPath().getSub() == null || "middleName".equals(op.getPath().getSub())) {
setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getName().getMiddleName(), op);
}
}
}
case "displayName" ->
setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getDisplayName(), op);
case "nickName" ->
setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getNickName(), op);
case "profileUrl" ->
setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getProfileUrl(), op);
case "title" ->
setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getTitle(), op);
case "userType" ->
setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getUserType(), op);
case "preferredLanguage" ->
setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getPreferredLanguage(), op);
case "locale" ->
setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getLocale(), op);
case "timezone" ->
setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getTimezone(), op);
case "emails" -> {
if (!CollectionUtils.isEmpty(op.getValue()) && op.getValue().get(0) instanceof SCIMUser) {
setAttribute(
userUR.getPlainAttrs(),
conf.getUserConf().getEmails(),
((SCIMUser) op.getValue().get(0)).getEmails(),
op.getOp());
} else if (op.getPath().getFilter() != null) {
setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getEmails(), op);
}
}
case "phoneNumbers" -> {
if (!CollectionUtils.isEmpty(op.getValue()) && op.getValue().get(0) instanceof SCIMUser) {
setAttribute(
userUR.getPlainAttrs(),
conf.getUserConf().getPhoneNumbers(),
((SCIMUser) op.getValue().get(0)).getPhoneNumbers(),
op.getOp());
} else if (op.getPath().getFilter() != null) {
setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getPhoneNumbers(), op);
}
}
case "ims" -> {
if (!CollectionUtils.isEmpty(op.getValue()) && op.getValue().get(0) instanceof SCIMUser) {
setAttribute(
userUR.getPlainAttrs(),
conf.getUserConf().getIms(),
((SCIMUser) op.getValue().get(0)).getIms(),
op.getOp());
} else if (op.getPath().getFilter() != null) {
setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getIms(), op);
}
}
case "photos" -> {
if (!CollectionUtils.isEmpty(op.getValue()) && op.getValue().get(0) instanceof SCIMUser) {
setAttribute(
userUR.getPlainAttrs(),
conf.getUserConf().getPhotos(),
((SCIMUser) op.getValue().get(0)).getPhotos(),
op.getOp());
} else if (op.getPath().getFilter() != null) {
setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getPhotos(), op);
}
}
case "addresses" -> {
if (!CollectionUtils.isEmpty(op.getValue()) && op.getValue().get(0) instanceof SCIMUser) {
SCIMUser after = (SCIMUser) op.getValue().get(0);
after.getAddresses().stream().filter(address -> address.getType() != null).
forEach(address -> conf.getUserConf().getAddresses().stream().
filter(object -> address.getType().equals(object.getType().name())).findFirst().
ifPresent(addressConf -> setAttribute(userUR.getPlainAttrs(), addressConf, op)));
} else if (op.getPath().getFilter() != null) {
conf.getUserConf().getAddresses().stream().
filter(addressConf -> BooleanUtils.toBoolean(JexlUtils.evaluateExpr(
filter2JexlExpression(op.getPath().getFilter()),
new MapContext(Map.of("type", addressConf.getType().name()))).toString())).findFirst().
ifPresent(addressConf -> setAttribute(userUR.getPlainAttrs(), addressConf, op));
}
}
case "employeeNumber" ->
setAttribute(userUR.getPlainAttrs(), Optional.ofNullable(conf.getEnterpriseUserConf()).
map(SCIMEnterpriseUserConf::getEmployeeNumber).orElse(null), op);
case "costCenter" ->
setAttribute(userUR.getPlainAttrs(), Optional.ofNullable(conf.getEnterpriseUserConf()).
map(SCIMEnterpriseUserConf::getCostCenter).orElse(null), op);
case "organization" ->
setAttribute(userUR.getPlainAttrs(), Optional.ofNullable(conf.getEnterpriseUserConf()).
map(SCIMEnterpriseUserConf::getOrganization).orElse(null), op);
case "division" ->
setAttribute(userUR.getPlainAttrs(), Optional.ofNullable(conf.getEnterpriseUserConf()).
map(SCIMEnterpriseUserConf::getDivision).orElse(null), op);
case "department" ->
setAttribute(userUR.getPlainAttrs(), Optional.ofNullable(conf.getEnterpriseUserConf()).
map(SCIMEnterpriseUserConf::getDepartment).orElse(null), op);
case "manager" ->
setAttribute(userUR.getPlainAttrs(), Optional.ofNullable(conf.getEnterpriseUserConf()).
map(SCIMEnterpriseUserConf::getManager).map(SCIMManagerConf::getKey).orElse(null), op);
default -> {
}
}
return Pair.of(userUR, statusR);
}
public SCIMGroup toSCIMGroup(
final GroupTO groupTO,
final String location,
final List<String> attributes,
final List<String> excludedAttributes) {
SCIMGroup group = new SCIMGroup(
groupTO.getKey(),
new Meta(
Resource.Group,
groupTO.getCreationDate(),
Optional.ofNullable(groupTO.getLastChangeDate()).orElse(groupTO.getCreationDate()),
groupTO.getETagValue(),
location),
output(attributes, excludedAttributes, "displayName", groupTO.getName()));
SCIMConf conf = confManager.get();
Map<String, Attr> attrs = new HashMap<>();
attrs.putAll(EntityTOUtils.buildAttrMap(groupTO.getPlainAttrs()));
attrs.putAll(EntityTOUtils.buildAttrMap(groupTO.getDerAttrs()));
attrs.putAll(EntityTOUtils.buildAttrMap(groupTO.getVirAttrs()));
if (output(attributes, excludedAttributes, "externalId")
&& conf.getGroupConf() != null
&& conf.getGroupConf().getExternalId() != null
&& attrs.containsKey(conf.getGroupConf().getExternalId())) {
group.setExternalId(attrs.get(conf.getGroupConf().getExternalId()).getValues().get(0));
}
MembershipCond membCond = new MembershipCond();
membCond.setGroup(groupTO.getKey());
SearchCond searchCond = SearchCond.getLeaf(membCond);
if (output(attributes, excludedAttributes, "members")) {
long count = userLogic.search(
searchCond, PageRequest.of(0, 1), SyncopeConstants.ROOT_REALM, true, false).getTotalElements();
for (int page = 0; page <= (count / AnyDAO.DEFAULT_PAGE_SIZE); page++) {
List<UserTO> users = userLogic.search(
searchCond,
PageRequest.of(page, AnyDAO.DEFAULT_PAGE_SIZE),
SyncopeConstants.ROOT_REALM,
true,
false).
getContent();
users.forEach(userTO -> group.getMembers().add(new Member(
userTO.getKey(),
StringUtils.substringBefore(location, "/Groups") + "/Users/" + userTO.getKey(),
userTO.getUsername())));
}
}
return group;
}
public GroupTO toGroupTO(final SCIMGroup group, final boolean checkSchemas) {
if (checkSchemas && !GROUP_SCHEMAS.equals(group.getSchemas())) {
throw new BadRequestException(ErrorType.invalidValue);
}
GroupTO groupTO = new GroupTO();
groupTO.setRealm(SyncopeConstants.ROOT_REALM);
groupTO.setKey(group.getId());
groupTO.setName(group.getDisplayName());
SCIMConf conf = confManager.get();
if (conf.getGroupConf() != null
&& conf.getGroupConf().getExternalId() != null && group.getExternalId() != null) {
groupTO.getPlainAttrs().add(
new Attr.Builder(conf.getGroupConf().getExternalId()).
value(group.getExternalId()).build());
}
return groupTO;
}
public GroupCR toGroupCR(final SCIMGroup group) {
GroupTO groupTO = toGroupTO(group, true);
GroupCR groupCR = new GroupCR();
EntityTOUtils.toAnyCR(groupTO, groupCR);
return groupCR;
}
public GroupUR toGroupUR(final GroupTO before, final SCIMPatchOperation op) {
if (op.getPath() == null) {
throw new UnsupportedOperationException("Empty path not supported for Groups");
}
GroupUR groupUR = new GroupUR.Builder(before.getKey()).build();
if ("displayName".equals(op.getPath().getAttribute())) {
StringReplacePatchItem.Builder name = new StringReplacePatchItem.Builder().
operation(op.getOp() == PatchOp.remove ? PatchOperation.DELETE : PatchOperation.ADD_REPLACE);
if (!CollectionUtils.isEmpty(op.getValue())) {
name.value(op.getValue().get(0).toString());
}
groupUR.setName(name.build());
} else {
SCIMConf conf = confManager.get();
if (conf.getGroupConf() != null) {
setAttribute(groupUR.getPlainAttrs(), conf.getGroupConf().getExternalId(), op);
}
}
return groupUR;
}
}