blob: 511bf0d402465d2061f21cfad378c83eccc68f77 [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.ldap;
import static org.apache.openmeetings.util.OpenmeetingsVariables.CONFIG_DEFAULT_GROUP_ID;
import static org.apache.openmeetings.util.OpenmeetingsVariables.webAppRootKey;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
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.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.exception.LdapInvalidDnException;
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.api.ldap.model.name.Rdn;
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.OrganisationDao;
import org.apache.openmeetings.db.dao.user.StateDao;
import org.apache.openmeetings.db.dao.user.UserDao;
import org.apache.openmeetings.db.entity.server.LdapConfig;
import org.apache.openmeetings.db.entity.user.Organisation;
import org.apache.openmeetings.db.entity.user.Organisation_Users;
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);
// Config constants
private static final String CONFIGKEY_LDAP_HOST = "ldap_conn_host";
private static final String CONFIGKEY_LDAP_PORT = "ldap_conn_port";
private static final String CONFIGKEY_LDAP_SECURE = "ldap_conn_secure";
private static final String CONFIGKEY_LDAP_ADMIN_DN = "ldap_admin_dn";
private static final String CONFIGKEY_LDAP_ADMIN_PASSWD = "ldap_passwd";
private static final String CONFIGKEY_LDAP_AUTH_TYPE = "ldap_auth_type";
private static final String CONFIGKEY_LDAP_PROV_TYPE = "ldap_provisionning";
private static final String CONFIGKEY_LDAP_SYNC_PASSWD_OM = "ldap_sync_password_to_om"; // 'true' or 'false'
private static final String CONFIGKEY_LDAP_USE_LOWER_CASE = "ldap_use_lower_case";
private static final String CONFIGKEY_LDAP_TIMEZONE_NAME = "ldap_user_timezone";
private static final String CONFIGKEY_LDAP_SEARCH_BASE = "ldap_search_base";
private static final String CONFIGKEY_LDAP_SEARCH_QUERY = "ldap_search_query";
private static final String CONFIGKEY_LDAP_SEARCH_SCOPE = "ldap_search_scope";
private static final String CONFIGKEY_LDAP_USERDN_FORMAT = "ldap_userdn_format";
private static final String CONFIGKEY_LDAP_USE_ADMIN_4ATTRS = "ldap_use_admin_to_get_attrs";
private static final String CONFIGKEY_LDAP_DEREF_MODE = "ldap_deref_mode";
private static final String CONFIGKEY_LDAP_GROUP_MODE = "ldap_group_mode";
private static final String CONFIGKEY_LDAP_GROUP_QUERY = "ldap_group_query";
// LDAP custom attribute mapping keys
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";
private 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_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 StateDao stateDao;
@Autowired
private LdapConfigDao ldapConfigDao;
@Autowired
private UserDao userDao;
@Autowired
private OrganisationDao orgDao;
@Autowired
private TimezoneUtil timezoneUtil;
private Dn getUserDn(Properties config, String user) throws LdapInvalidDnException {
return new Dn(String.format(config.getProperty(CONFIGKEY_LDAP_USERDN_FORMAT, "%s"), user));
}
private void bindAdmin(LdapConnection conn, String admin, String pass) throws LdapException {
if (!Strings.isEmpty(admin)) {
conn.bind(admin, pass);
} else {
conn.bind();
}
}
private String 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.getString();
}
/**
* 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;
}
Properties config = new Properties();
Reader r = null;
try {
LdapConfig ldapConfig = ldapConfigDao.get(domainId);
r = new InputStreamReader(new FileInputStream(new File(OmFileHelper.getConfDir(), ldapConfig.getConfigFileName())), "UTF-8");
config.load(r);
} catch (Exception e) {
log.error("Error on LdapLogin : Configurationdata couldn't be retrieved!");
return null;
} finally {
if (r != null) {
try {
r.close();
} catch (IOException e) {
log.error("Error while closing ldap config file");
return null;
}
}
}
if (config.isEmpty()) {
log.error("Error on LdapLogin : Configurationdata couldnt be retrieved!");
return null;
}
String ldap_use_lower_case = config.getProperty(CONFIGKEY_LDAP_USE_LOWER_CASE, "false");
if ("true".equals(ldap_use_lower_case)) {
login = login.toLowerCase();
}
String ldap_auth_type = config.getProperty(CONFIGKEY_LDAP_AUTH_TYPE, "");
AuthType type = AuthType.SIMPLEBIND;
try {
type = AuthType.valueOf(ldap_auth_type);
} catch (Exception e) {
log.error(String.format("ConfigKey in Ldap Config contains invalid auth type : '%s' -> Defaulting to %s", ldap_auth_type, type));
}
String ldap_prov_type = config.getProperty(CONFIGKEY_LDAP_PROV_TYPE, "");
Provisionning prov = Provisionning.AUTOCREATE;
try {
prov = Provisionning.valueOf(ldap_prov_type);
} catch (Exception e) {
log.error(String.format("ConfigKey in Ldap Config contains invalid provisionning type : '%s' -> Defaulting to %s", ldap_prov_type, prov));
}
String ldap_deref_mode = config.getProperty(CONFIGKEY_LDAP_DEREF_MODE, "");
AliasDerefMode derefMode = AliasDerefMode.DEREF_ALWAYS;
try {
derefMode = AliasDerefMode.getDerefMode(ldap_deref_mode);
} catch (Exception e) {
log.error(String.format("ConfigKey in Ldap Config contains invalid deref mode : '%s' -> Defaulting to %s", ldap_deref_mode, derefMode));
}
if (AuthType.NONE == type && Provisionning.NONE == prov) {
log.error("Both AuthType and Provisionning are NONE!");
return null;
}
boolean useAdminForAttrs = true;
try {
useAdminForAttrs = "true".equals(config.getProperty(CONFIGKEY_LDAP_USE_ADMIN_4ATTRS, ""));
} catch (Exception e) {
//no-op
}
GroupMode groupMode = GroupMode.NONE;
try {
groupMode = GroupMode.valueOf(config.getProperty(CONFIGKEY_LDAP_GROUP_MODE, "NONE"));
} catch (Exception e) {
//no-op
}
if (AuthType.NONE == type && !useAdminForAttrs) {
log.error("Unable to get Attributes, please change Auth type and/or Use Admin to get attributes");
return null;
}
// Connection URL
String ldap_host = config.getProperty(CONFIGKEY_LDAP_HOST);
int ldap_port = Integer.parseInt(config.getProperty(CONFIGKEY_LDAP_PORT, "389"));
boolean ldap_secure = "true".equals(config.getProperty(CONFIGKEY_LDAP_SECURE, "false"));
// Username for LDAP SERVER himself
String ldap_admin_dn = config.getProperty(CONFIGKEY_LDAP_ADMIN_DN);
// Password for LDAP SERVER himself
String ldap_admin_passwd = config.getProperty(CONFIGKEY_LDAP_ADMIN_PASSWD);
User u = null;
LdapConnection conn = null;
try {
boolean authenticated = true;
conn = new LdapNetworkConnection(ldap_host, ldap_port, ldap_secure);
Dn userDn = null;
Entry entry = null;
switch (type) {
case SEARCHANDBIND:
{
bindAdmin(conn, ldap_admin_dn, ldap_admin_passwd);
Dn baseDn = new Dn(config.getProperty(CONFIGKEY_LDAP_SEARCH_BASE, ""));
String searchQ = String.format(config.getProperty(CONFIGKEY_LDAP_SEARCH_QUERY, "%s"), login);
SearchScope scope = SearchScope.valueOf(config.getProperty(CONFIGKEY_LDAP_SEARCH_SCOPE, SearchScope.ONELEVEL.name()));
EntryCursor cursor = new EntryCursorImpl(conn.search(
new SearchRequestImpl()
.setBase(baseDn)
.setFilter(searchQ)
.setScope(scope)
.addAttributes("*")
.setDerefAliases(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 (useAdminForAttrs) {
entry = e;
}
} catch (CursorLdapReferralException cle) {
log.warn("Referral LDAP entry found, ignore it");
}
}
cursor.close();
if (userDn == null) {
log.error("NONE users found in LDAP");
throw new OmException(-11L);
}
conn.bind(userDn, passwd);
}
break;
case SIMPLEBIND:
{
userDn = getUserDn(config, login);
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 != prov) {
log.error("User not found in OM DB and Provisionning.AUTOCREATE was not set");
throw new OmException(-11L);
}
if (authenticated && entry == null) {
if (useAdminForAttrs) {
bindAdmin(conn, ldap_admin_dn, ldap_admin_passwd);
}
entry = conn.lookup(userDn);
}
switch (prov) {
case AUTOUPDATE:
case AUTOCREATE:
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);
u.getOrganisation_users().add(new Organisation_Users(orgDao.get(cfgDao.getConfValue(CONFIG_DEFAULT_GROUP_ID, Long.class, "-1"))));
u.setLogin(login);
u.setShowContactDataToContacts(true);
}
if ("true".equals(config.getProperty(CONFIGKEY_LDAP_SYNC_PASSWD_OM, ""))) {
u.updatePassword(cfgDao, passwd);
}
u.setLastname(getAttr(config, entry, CONFIGKEY_LDAP_KEY_LASTNAME, LDAP_KEY_LASTNAME));
u.setFirstname(getAttr(config, entry, CONFIGKEY_LDAP_KEY_FIRSTNAME, LDAP_KEY_FIRSTNAME));
u.getAdresses().setEmail(getAttr(config, entry, CONFIGKEY_LDAP_KEY_MAIL, LDAP_KEY_MAIL));
u.getAdresses().setStreet(getAttr(config, entry, CONFIGKEY_LDAP_KEY_STREET, LDAP_KEY_STREET));
u.getAdresses().setAdditionalname(getAttr(config, entry, CONFIGKEY_LDAP_KEY_ADDITIONAL_NAME, LDAP_KEY_ADDITIONAL_NAME));
u.getAdresses().setFax(getAttr(config, entry, CONFIGKEY_LDAP_KEY_FAX, LDAP_KEY_FAX));
u.getAdresses().setZip(getAttr(config, entry, CONFIGKEY_LDAP_KEY_ZIP, LDAP_KEY_ZIP));
u.getAdresses().setStates(stateDao.getStateByName(getAttr(config, entry, CONFIGKEY_LDAP_KEY_COUNTRY, LDAP_KEY_COUNTRY)));
u.getAdresses().setTown(getAttr(config, entry, CONFIGKEY_LDAP_KEY_TOWN, LDAP_KEY_TOWN));
u.getAdresses().setPhone(getAttr(config, entry, CONFIGKEY_LDAP_KEY_PHONE, LDAP_KEY_PHONE));
String tz = getAttr(config, entry, CONFIGKEY_LDAP_TIMEZONE_NAME, LDAP_KEY_TIMEZONE);
if (tz == null) {
tz = config.getProperty(CONFIGKEY_LDAP_TIMEZONE_NAME, null);
}
u.setTimeZoneId(timezoneUtil.getTimeZone(tz).getID());
String picture = getAttr(config, entry, CONFIGKEY_LDAP_KEY_PICTURE_URI, LDAP_KEY_PICTURE_URI);
if (picture == null) {
picture = config.getProperty(CONFIGKEY_LDAP_KEY_PICTURE_URI, null);
}
u.setPictureuri(picture);
u = userDao.update(u, null);
List<Dn> groups = new ArrayList<>();
if (GroupMode.ATTRIBUTE == groupMode) {
Attribute a = entry.get(config.getProperty(CONFIGKEY_LDAP_KEY_GROUP, LDAP_KEY_GROUP));
String attr = a == null ? "" : a.getString();
for (String g : attr.split("|")) {
groups.add(new Dn(g));
}
} else if (GroupMode.QUERY == groupMode) {
Dn baseDn = new Dn(config.getProperty(CONFIGKEY_LDAP_SEARCH_BASE, ""));
String searchQ = String.format(config.getProperty(CONFIGKEY_LDAP_GROUP_QUERY, "%s"), login);
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");
}
}
cursor.close();
}
for (Dn g : groups) {
Rdn namer = g.getRdn();
String name = namer.getValue().toString();
if (!Strings.isEmpty(name)) {
Organisation o = orgDao.get(name);
boolean found = false;
if (o == null) {
o = new Organisation();
o.setName(name);
o = orgDao.update(o, u.getUser_id());
} else {
for (Organisation_Users ou : u.getOrganisation_users()) {
if (ou.getOrganisation().getName().equals(name)) {
found = true;
break;
}
}
}
if (!found) {
u.getOrganisation_users().add(new Organisation_Users(o));
userDao.update(u, u.getUser_id());
log.debug("Going to add user to group:: " + name);
}
}
}
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);
} finally {
if (conn != null) {
try {
conn.unBind();
conn.close();
} catch (Exception e) {
log.error("Unexpected exception.", e);
throw new OmException(e);
}
}
}
return u;
}
}