| /* |
| * 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.openmeetings.core.ldap; |
| |
| import static org.apache.openmeetings.util.OpenmeetingsVariables.CONFIG_DEFAULT_GROUP_ID; |
| import static org.apache.openmeetings.util.OpenmeetingsVariables.webAppRootKey; |
| |
| import java.io.Closeable; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.Reader; |
| import java.nio.charset.StandardCharsets; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Properties; |
| |
| import org.apache.directory.api.ldap.model.cursor.CursorException; |
| import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException; |
| import org.apache.directory.api.ldap.model.cursor.EntryCursor; |
| import org.apache.directory.api.ldap.model.entry.Attribute; |
| import org.apache.directory.api.ldap.model.entry.Entry; |
| import org.apache.directory.api.ldap.model.entry.Value; |
| import org.apache.directory.api.ldap.model.exception.LdapAuthenticationException; |
| import org.apache.directory.api.ldap.model.exception.LdapException; |
| import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; |
| import org.apache.directory.api.ldap.model.message.AliasDerefMode; |
| import org.apache.directory.api.ldap.model.message.SearchRequestImpl; |
| import org.apache.directory.api.ldap.model.message.SearchScope; |
| import org.apache.directory.api.ldap.model.name.Dn; |
| import org.apache.directory.ldap.client.api.EntryCursorImpl; |
| import org.apache.directory.ldap.client.api.LdapConnection; |
| import org.apache.directory.ldap.client.api.LdapNetworkConnection; |
| import org.apache.openmeetings.db.dao.basic.ConfigurationDao; |
| import org.apache.openmeetings.db.dao.server.LdapConfigDao; |
| import org.apache.openmeetings.db.dao.user.GroupDao; |
| import org.apache.openmeetings.db.dao.user.UserDao; |
| import org.apache.openmeetings.db.entity.server.LdapConfig; |
| import org.apache.openmeetings.db.entity.user.Address; |
| import org.apache.openmeetings.db.entity.user.Group; |
| import org.apache.openmeetings.db.entity.user.GroupUser; |
| import org.apache.openmeetings.db.entity.user.User; |
| import org.apache.openmeetings.db.entity.user.User.Right; |
| import org.apache.openmeetings.db.entity.user.User.Type; |
| import org.apache.openmeetings.db.util.TimezoneUtil; |
| import org.apache.openmeetings.util.OmException; |
| import org.apache.openmeetings.util.OmFileHelper; |
| import org.apache.wicket.util.string.Strings; |
| import org.red5.logging.Red5LoggerFactory; |
| import org.slf4j.Logger; |
| import org.springframework.beans.factory.annotation.Autowired; |
| |
| /** |
| * Management of optional LDAP Login |
| * |
| * @author o.becherer |
| * |
| */ |
| public class LdapLoginManagement { |
| private static final Logger log = Red5LoggerFactory.getLogger(LdapLoginManagement.class, webAppRootKey); |
| // LDAP custom attribute mapping keys |
| private static final String CONFIGKEY_LDAP_KEY_LOGIN = "ldap_user_attr_login"; |
| private static final String CONFIGKEY_LDAP_KEY_LASTNAME = "ldap_user_attr_lastname"; |
| private static final String CONFIGKEY_LDAP_KEY_FIRSTNAME = "ldap_user_attr_firstname"; |
| private static final String CONFIGKEY_LDAP_KEY_MAIL = "ldap_user_attr_mail"; |
| private static final String CONFIGKEY_LDAP_KEY_STREET = "ldap_user_attr_street"; |
| private static final String CONFIGKEY_LDAP_KEY_ADDITIONAL_NAME = "ldap_user_attr_additionalname"; |
| private static final String CONFIGKEY_LDAP_KEY_FAX = "ldap_user_attr_fax"; |
| private static final String CONFIGKEY_LDAP_KEY_ZIP = "ldap_user_attr_zip"; |
| private static final String CONFIGKEY_LDAP_KEY_COUNTRY = "ldap_user_attr_country"; |
| private static final String CONFIGKEY_LDAP_KEY_TOWN = "ldap_user_attr_town"; |
| private static final String CONFIGKEY_LDAP_KEY_PHONE = "ldap_user_attr_phone"; |
| static final String CONFIGKEY_LDAP_KEY_PICTURE_URI = "ldap_user_picture_uri"; |
| private static final String CONFIGKEY_LDAP_KEY_GROUP = "ldap_group_attr"; |
| |
| // LDAP default attributes mapping |
| private static final String LDAP_KEY_LOGIN = "uid"; |
| private static final String LDAP_KEY_LASTNAME = "sn"; |
| private static final String LDAP_KEY_FIRSTNAME = "givenName"; |
| private static final String LDAP_KEY_MAIL = "mail"; |
| private static final String LDAP_KEY_STREET = "streetAddress"; |
| private static final String LDAP_KEY_ADDITIONAL_NAME = "description"; |
| private static final String LDAP_KEY_FAX = "facsimileTelephoneNumber"; |
| private static final String LDAP_KEY_ZIP = "postalCode"; |
| private static final String LDAP_KEY_COUNTRY = "co"; |
| private static final String LDAP_KEY_TOWN = "l"; |
| private static final String LDAP_KEY_PHONE = "telephoneNumber"; |
| private static final String LDAP_KEY_TIMEZONE = "timezone"; |
| private static final String LDAP_KEY_PICTURE_URI = "pictureUri"; |
| private static final String LDAP_KEY_GROUP = "memberOf"; |
| |
| public enum AuthType { |
| NONE |
| , SEARCHANDBIND |
| , SIMPLEBIND |
| } |
| |
| public enum Provisionning { |
| NONE |
| , AUTOUPDATE |
| , AUTOCREATE |
| } |
| |
| public enum GroupMode { |
| NONE |
| , ATTRIBUTE |
| , QUERY |
| } |
| @Autowired |
| private ConfigurationDao cfgDao; |
| @Autowired |
| private LdapConfigDao ldapConfigDao; |
| @Autowired |
| private UserDao userDao; |
| @Autowired |
| private GroupDao groupDao; |
| @Autowired |
| private TimezoneUtil timezoneUtil; |
| |
| private static void bindAdmin(LdapConnection conn, LdapOptions options) throws LdapException { |
| if (!Strings.isEmpty(options.adminDn)) { |
| conn.bind(options.adminDn, options.adminPasswd); |
| } else { |
| conn.bind(); |
| } |
| } |
| |
| private static Attribute getAttr(Properties config, Entry entry, String aliasCode, String defaultAlias) throws LdapInvalidAttributeValueException { |
| String alias = config.getProperty(aliasCode, ""); |
| Attribute a = entry.get(Strings.isEmpty(alias) ? defaultAlias : alias); |
| return a == null ? null : a; |
| } |
| |
| private static String getStringAttr(Properties config, Entry entry, String aliasCode, String defaultAlias) throws LdapInvalidAttributeValueException { |
| Attribute a = getAttr(config, entry, aliasCode, defaultAlias); |
| return a == null ? null : a.getString(); |
| } |
| |
| private static String getLogin(Properties config, Entry entry) throws LdapInvalidAttributeValueException { |
| return getStringAttr(config, entry, CONFIGKEY_LDAP_KEY_LOGIN, LDAP_KEY_LOGIN); |
| } |
| |
| /** |
| * Ldap Login |
| * |
| * Connection Data is retrieved from ConfigurationFile |
| * |
| */ |
| public User login(String login, String passwd, Long domainId) throws OmException { |
| log.debug("LdapLoginmanagement.doLdapLogin"); |
| if (!userDao.validLogin(login)) { |
| log.error("Invalid login provided"); |
| return null; |
| } |
| |
| User u = null; |
| try (LdapWorker w = new LdapWorker(domainId)) { |
| if (w.options.useLowerCase) { |
| login = login.toLowerCase(); |
| } |
| |
| boolean authenticated = true; |
| Dn userDn = null; |
| Entry entry = null; |
| switch (w.options.type) { |
| case SEARCHANDBIND: |
| { |
| bindAdmin(w.conn, w.options); |
| Dn baseDn = new Dn(w.options.searchBase); |
| String searchQ = String.format(w.options.searchQuery, login); |
| |
| try (EntryCursor cursor = new EntryCursorImpl(w.conn.search( |
| new SearchRequestImpl() |
| .setBase(baseDn) |
| .setFilter(searchQ) |
| .setScope(w.options.scope) |
| .addAttributes("*") |
| .setDerefAliases(w.options.derefMode)))) |
| { |
| while (cursor.next()) { |
| try { |
| Entry e = cursor.get(); |
| if (userDn != null) { |
| log.error("more than 1 user found in LDAP"); |
| throw new OmException(-1L); |
| } |
| userDn = e.getDn(); |
| if (w.options.useAdminForAttrs) { |
| entry = e; |
| } |
| } catch (CursorLdapReferralException cle) { |
| log.warn("Referral LDAP entry found, ignore it"); |
| } |
| } |
| } |
| if (userDn == null) { |
| log.error("NONE users found in LDAP"); |
| throw new OmException(-11L); |
| } |
| w.conn.bind(userDn, passwd); |
| } |
| break; |
| case SIMPLEBIND: |
| { |
| userDn = new Dn(String.format(w.options.userDn, login)); |
| w.conn.bind(userDn, passwd); |
| } |
| break; |
| case NONE: |
| default: |
| authenticated = false; |
| break; |
| } |
| u = authenticated ? userDao.getByLogin(login, Type.ldap, domainId) : userDao.login(login, passwd); |
| if (u == null && Provisionning.AUTOCREATE != w.options.prov) { |
| log.error("User not found in OM DB and Provisionning.AUTOCREATE was not set"); |
| throw new OmException(-11L); |
| } |
| if (authenticated && entry == null) { |
| if (w.options.useAdminForAttrs) { |
| bindAdmin(w.conn, w.options); |
| } |
| entry = w.conn.lookup(userDn); |
| } |
| switch (w.options.prov) { |
| case AUTOUPDATE: |
| case AUTOCREATE: |
| u = w.getUser(entry, u); |
| if (w.options.syncPasswd) { |
| u.updatePassword(cfgDao, passwd); |
| } |
| u = userDao.update(u, null); |
| break; |
| case NONE: |
| default: |
| break; |
| } |
| } catch (LdapAuthenticationException ae) { |
| log.error("Not authenticated.", ae); |
| throw new OmException(-11L); |
| } catch (OmException e) { |
| throw e; |
| } catch (Exception e) { |
| log.error("Unexpected exception.", e); |
| throw new OmException(e); |
| } |
| return u; |
| } |
| |
| public void importUsers(Long domainId, boolean print) throws OmException { |
| try (LdapWorker w = new LdapWorker(domainId)) { |
| bindAdmin(w.conn, w.options); |
| Dn baseDn = new Dn(w.options.searchBase); |
| |
| try (EntryCursor cursor = new EntryCursorImpl(w.conn.search( |
| new SearchRequestImpl() |
| .setBase(baseDn) |
| .setFilter(w.options.importQuery) |
| .setScope(w.options.scope) |
| .addAttributes("*") |
| .setDerefAliases(w.options.derefMode)))) |
| { |
| while (cursor.next()) { |
| try { |
| Entry e = cursor.get(); |
| User u = userDao.getByLogin(getLogin(w.config, e), Type.ldap, domainId); |
| u = w.getUser(e, u); |
| if (print) { |
| log.info("Going to import user: {}", u); |
| } else { |
| userDao.update(u, null); |
| log.info("User {}, was imported", u); |
| } |
| } catch (CursorLdapReferralException cle) { |
| log.warn("Referral LDAP entry found, ignore it"); |
| } |
| } |
| } |
| } catch (LdapAuthenticationException ae) { |
| log.error("Not authenticated.", ae); |
| throw new OmException(-11L); |
| } catch (OmException e) { |
| throw e; |
| } catch (Exception e) { |
| log.error("Unexpected exception.", e); |
| throw new OmException(e); |
| } |
| } |
| |
| private class LdapWorker implements Closeable { |
| LdapConnection conn = null; |
| Properties config = new Properties(); |
| LdapOptions options = null; |
| Long domainId = null; |
| |
| public LdapWorker(Long domainId) throws Exception { |
| this.domainId = domainId; |
| LdapConfig ldapConfig = ldapConfigDao.get(domainId); |
| try (InputStream is = new FileInputStream(new File(OmFileHelper.getConfDir(), ldapConfig.getConfigFileName())); |
| Reader r = new InputStreamReader(is, StandardCharsets.UTF_8)) |
| { |
| config.load(r); |
| if (config.isEmpty()) { |
| throw new RuntimeException("Error on LdapLogin : Configurationdata couldnt be retrieved!"); |
| } |
| options = new LdapOptions(config); |
| } catch (Exception e) { |
| log.error("Error on LdapLogin : Configurationdata couldn't be retrieved!"); |
| throw e; |
| } |
| |
| conn = new LdapNetworkConnection(options.host, options.port, options.secure); |
| } |
| |
| public User getUser(Entry entry, User u) throws LdapException, CursorException, OmException, IOException { |
| if (entry == null) { |
| log.error("LDAP entry is null, search or lookup by Dn failed"); |
| throw new OmException(-11L); |
| } |
| if (u == null) { |
| u = userDao.getNewUserInstance(null); |
| u.setType(Type.ldap); |
| u.getRights().remove(Right.Login); |
| u.setDomainId(domainId); |
| Group g = groupDao.get(cfgDao.getConfValue(CONFIG_DEFAULT_GROUP_ID, Long.class, "-1")); |
| if (g != null) { |
| u.getGroupUsers().add(new GroupUser(g, u)); |
| } |
| u.setLogin(getLogin(config, entry)); |
| u.setShowContactDataToContacts(true); |
| u.setAddress(new Address()); |
| } |
| u.setLastname(getStringAttr(config, entry, CONFIGKEY_LDAP_KEY_LASTNAME, LDAP_KEY_LASTNAME)); |
| u.setFirstname(getStringAttr(config, entry, CONFIGKEY_LDAP_KEY_FIRSTNAME, LDAP_KEY_FIRSTNAME)); |
| u.getAddress().setEmail(getStringAttr(config, entry, CONFIGKEY_LDAP_KEY_MAIL, LDAP_KEY_MAIL)); |
| u.getAddress().setStreet(getStringAttr(config, entry, CONFIGKEY_LDAP_KEY_STREET, LDAP_KEY_STREET)); |
| u.getAddress().setAdditionalname(getStringAttr(config, entry, CONFIGKEY_LDAP_KEY_ADDITIONAL_NAME, LDAP_KEY_ADDITIONAL_NAME)); |
| u.getAddress().setFax(getStringAttr(config, entry, CONFIGKEY_LDAP_KEY_FAX, LDAP_KEY_FAX)); |
| u.getAddress().setZip(getStringAttr(config, entry, CONFIGKEY_LDAP_KEY_ZIP, LDAP_KEY_ZIP)); |
| u.getAddress().setCountry(getStringAttr(config, entry, CONFIGKEY_LDAP_KEY_COUNTRY, LDAP_KEY_COUNTRY)); |
| u.getAddress().setTown(getStringAttr(config, entry, CONFIGKEY_LDAP_KEY_TOWN, LDAP_KEY_TOWN)); |
| u.getAddress().setPhone(getStringAttr(config, entry, CONFIGKEY_LDAP_KEY_PHONE, LDAP_KEY_PHONE)); |
| String tz = getStringAttr(config, entry, LdapOptions.CONFIGKEY_LDAP_TIMEZONE_NAME, LDAP_KEY_TIMEZONE); |
| if (tz == null) { |
| tz = options.tz; |
| } |
| u.setTimeZoneId(timezoneUtil.getTimeZone(tz).getID()); |
| String picture = getStringAttr(config, entry, CONFIGKEY_LDAP_KEY_PICTURE_URI, LDAP_KEY_PICTURE_URI); |
| if (picture == null) { |
| picture = options.pictureUri; |
| } |
| u.setPictureuri(picture); |
| |
| List<Dn> groups = new ArrayList<>(); |
| if (GroupMode.ATTRIBUTE == options.groupMode) { |
| Attribute attr = getAttr(config, entry, CONFIGKEY_LDAP_KEY_GROUP, LDAP_KEY_GROUP); |
| if (attr != null) { |
| for (Value<?> v : attr) { |
| groups.add(new Dn(v.getString())); |
| } |
| } |
| } else if (GroupMode.QUERY == options.groupMode) { |
| Dn baseDn = new Dn(options.searchBase); |
| String searchQ = String.format(options.groupQuery, u.getLogin()); |
| |
| try (EntryCursor cursor = new EntryCursorImpl(conn.search( |
| new SearchRequestImpl() |
| .setBase(baseDn) |
| .setFilter(searchQ) |
| .setScope(SearchScope.SUBTREE) |
| .addAttributes("*") |
| .setDerefAliases(AliasDerefMode.DEREF_ALWAYS)))) |
| { |
| while (cursor.next()) { |
| try { |
| Entry e = cursor.get(); |
| groups.add(e.getDn()); |
| } catch (CursorLdapReferralException cle) { |
| log.warn("Referral LDAP entry found, ignore it"); |
| } |
| } |
| } |
| } |
| for (Dn g : groups) { |
| String name = g.getRdn().getValue(); |
| if (!Strings.isEmpty(name)) { |
| Group o = groupDao.get(name); |
| boolean found = false; |
| if (o == null) { |
| o = new Group(); |
| o.setName(name); |
| o = groupDao.update(o, u.getId()); |
| } else { |
| for (GroupUser ou : u.getGroupUsers()) { |
| if (ou.getGroup().getName().equals(name)) { |
| found = true; |
| break; |
| } |
| } |
| } |
| if (!found) { |
| u.getGroupUsers().add(new GroupUser(o, u)); |
| log.debug("Going to add user to group:: " + name); |
| } |
| } |
| } |
| return u; |
| } |
| |
| @Override |
| public void close() throws IOException { |
| if (conn != null) { |
| conn.close(); |
| } |
| } |
| } |
| } |