View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.core.provisioning.java.propagation;
20  
21  import java.util.Base64;
22  import java.util.Optional;
23  import java.util.Set;
24  import javax.xml.bind.DatatypeConverter;
25  import org.apache.syncope.common.lib.types.AnyTypeKind;
26  import org.apache.syncope.common.lib.types.CipherAlgorithm;
27  import org.apache.syncope.core.persistence.api.dao.UserDAO;
28  import org.apache.syncope.core.persistence.api.entity.ConnInstance;
29  import org.apache.syncope.core.persistence.api.entity.user.User;
30  import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
31  import org.apache.syncope.core.provisioning.api.propagation.PropagationManager;
32  import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
33  import org.apache.syncope.core.spring.implementation.InstanceScope;
34  import org.apache.syncope.core.spring.implementation.SyncopeImplementation;
35  import org.identityconnectors.common.security.GuardedString;
36  import org.identityconnectors.framework.common.objects.Attribute;
37  import org.identityconnectors.framework.common.objects.AttributeBuilder;
38  import org.identityconnectors.framework.common.objects.AttributeUtil;
39  import org.identityconnectors.framework.common.objects.OperationalAttributes;
40  import org.springframework.beans.factory.annotation.Autowired;
41  import org.springframework.transaction.annotation.Transactional;
42  import org.springframework.util.CollectionUtils;
43  
44  /**
45   * Propagate a non-cleartext password out to a resource, if the PropagationManager has not already
46   * added a password. The CipherAlgorithm associated with the password must match the password
47   * hash algorithm property of the LDAP Connector.
48   */
49  @SyncopeImplementation(scope = InstanceScope.PER_CONTEXT)
50  public class LDAPPasswordPropagationActions implements PropagationActions {
51  
52      protected static final String CLEARTEXT = "CLEARTEXT";
53  
54      @Autowired
55      protected UserDAO userDAO;
56  
57      @Transactional(readOnly = true)
58      @Override
59      public void before(final PropagationTaskInfo taskInfo) {
60          if (AnyTypeKind.USER == taskInfo.getAnyTypeKind()) {
61              User user = userDAO.find(taskInfo.getEntityKey());
62              if (user == null || user.getPassword() == null) {
63                  return;
64              }
65  
66              Set<Attribute> attrs = taskInfo.getPropagationData().getAttributes();
67  
68              String cipherAlgorithm = getCipherAlgorithm(taskInfo.getResource().getConnector());
69              Optional.ofNullable(AttributeUtil.find(PropagationManager.MANDATORY_MISSING_ATTR_NAME, attrs)).
70                      filter(missing -> !CollectionUtils.isEmpty(missing.getValue())
71                      && OperationalAttributes.PASSWORD_NAME.equals(missing.getValue().get(0))
72                      && cipherAlgorithmMatches(cipherAlgorithm, user.getCipherAlgorithm())).
73                      ifPresent(missing -> {
74                          attrs.remove(missing);
75  
76                          byte[] decodedPassword = DatatypeConverter.parseHexBinary(user.getPassword().toLowerCase());
77                          String base64EncodedPassword = Base64.getEncoder().encodeToString(decodedPassword);
78  
79                          String cipherPlusPassword = '{' + cipherAlgorithm + '}' + base64EncodedPassword;
80  
81                          attrs.add(AttributeBuilder.buildPassword(new GuardedString(cipherPlusPassword.toCharArray())));
82                      });
83          }
84      }
85  
86      protected String getCipherAlgorithm(final ConnInstance connInstance) {
87          return connInstance.getConf().stream().
88                  filter(property -> "passwordHashAlgorithm".equals(property.getSchema().getName())
89                  && !property.getValues().isEmpty()).findFirst().
90                  map(cipherAlgorithm -> cipherAlgorithm.getValues().get(0).toString()).
91                  orElse(CLEARTEXT);
92      }
93  
94      protected boolean cipherAlgorithmMatches(final String connectorAlgo, final CipherAlgorithm userAlgo) {
95          if (userAlgo == null) {
96              return false;
97          }
98  
99          if (connectorAlgo.equals(userAlgo.name())) {
100             return true;
101         }
102 
103         // Special check for "SHA" and "SSHA" (user pulled from LDAP)
104         return ("SHA".equals(connectorAlgo) && userAlgo.name().startsWith("SHA"))
105                 || ("SSHA".equals(connectorAlgo) && userAlgo.name().startsWith("SSHA"));
106     }
107 }