blob: 46eca52bd2bd2039aa0ef5159e67aebaae5ae987 [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.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();
}
}
}
}