Permalink
Browse files

XX-10130: Multi domain LDAP authentication and user import support

-added domain field in LDAP configuration page
-added domain setting in edit user page
-pick ldap authenticator that belongs to user domain
-parsing mechanism of login user@domain domain\user entries
-fix ldap import to be able to corectly save aliases
-if alias collision, do not save alias for that user, show warning in status page, log warning
-fixed/improved tests
  • Loading branch information...
mirceac committed May 3, 2012
1 parent 27ae3b2 commit 6e0a0674c9e3d1b0443395be33603488c6316fc0
Showing with 297 additions and 24 deletions.
  1. +49 −0 sipXcommons/src/main/java/org/sipfoundry/commons/security/Util.java
  2. +4 −0 sipXconfig/etc/sipxpbx/commserver/user-settings.properties
  3. +7 −0 sipXconfig/etc/sipxpbx/commserver/user-settings.xml
  4. +8 −1 sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/bulk/RowInserter.java
  5. +43 −3 sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/bulk/ldap/LdapConnectionParams.java
  6. +1 −0 sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/bulk/ldap/LdapImportManagerImpl.java
  7. +23 −2 sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/bulk/ldap/LdapRowInserter.java
  8. +18 −5 sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/bulk/ldap/UserMapper.java
  9. +1 −1 sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/bulk/ldap/ldap.hbm.xml
  10. +7 −0 sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/common/AbstractUser.java
  11. +2 −0 sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/common/CoreContext.java
  12. +20 −0 sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/common/CoreContextImpl.java
  13. +32 −7 ...config/neoconf/src/org/sipfoundry/sipxconfig/security/ConfigurableLdapAuthenticationProvider.java
  14. +12 −3 sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/security/DaoAuthenticationProvider.java
  15. +6 −0 sipXconfig/neoconf/src/org/sipfoundry/sipxconfig/security/UserDetailsImpl.java
  16. +13 −0 sipXconfig/neoconf/test/org/sipfoundry/sipxconfig/bulk/ldap/LdapRowInserterTest.java
  17. +3 −2 sipXconfig/neoconf/test/org/sipfoundry/sipxconfig/bulk/ldap/UserMapperTest.java
  18. +41 −0 sipXconfig/neoconf/test/org/sipfoundry/sipxconfig/bulk/ldap/UsernameDomainTest.java
  19. +5 −0 sipXconfig/web/context/WEB-INF/admin/ldap/LdapConnectionPanel.html
  20. +2 −0 sipXconfig/web/context/WEB-INF/admin/ldap/LdapConnectionPanel.properties
@@ -0,0 +1,49 @@
+/**
+ *
+ *
+ * Copyright (c) 2012 eZuce, Inc. All rights reserved.
+ * Contributed to SIPfoundry under a Contributor Agreement
+ *
+ * This software is free software; you can redistribute it and/or modify it under
+ * the terms of the Affero General Public License (AGPL) as published by the
+ * Free Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+ * details.
+ */
+package org.sipfoundry.commons.security;
+
+import static org.apache.commons.lang.StringUtils.split;
+
+public class Util {
+ public static String retrieveUsername(String loginEntry) {
+ String[] userArray = split(loginEntry, '@');
+ String userLoginName = loginEntry;
+ if (userArray.length == 2) {
+ userLoginName = userArray[0];
+ } else {
+ userArray = split(loginEntry, '\\');
+ if (userArray.length == 2) {
+ userLoginName = userArray[1];
+ }
+ }
+ return userLoginName;
+ }
+
+ public static String retrieveDomain(String loginEntry) {
+ String[] userArray = split(loginEntry, '@');
+ String domain = null;
+ if (userArray.length == 2) {
+ domain = userArray[1];
+ } else {
+ userArray = split(loginEntry, '\\');
+ if (userArray.length == 2) {
+ domain = userArray[0];
+ }
+ }
+ return domain;
+ }
+}
@@ -145,6 +145,10 @@ im_notification.leaveMsgEndIM.description=Send me an Instant Message when a call
callfwd.label=Call Forwarding
callfwd.description=
+user-domain.label=Domain
+user-domain.domain.label=Domain
+user-domain.domain.description=Specify user domain.
+
callfwd.timer.label=Initial delay
callfwd.timer.description=The interval (in seconds) that the user's phone is ringing before call forwarding is activated. \
You can configure default value for group here. Individual users can adjust it when setting up call forwarding. \
@@ -221,6 +221,13 @@
</type>
</setting>
</group>
+ <group name="user-domain">
+ <setting name="domain">
+ <type>
+ <string/>
+ </type>
+ </setting>
+ </group>
<group name="voicemail">
<group name="fax" hidden="yes">
<setting name="extension">
@@ -26,7 +26,7 @@
public abstract class RowInserter<T> extends HibernateDaoSupport implements Closure {
public enum RowStatus {
- FAILURE, SUCCESS, WARNING_PIN_RESET;
+ FAILURE, SUCCESS, WARNING_PIN_RESET, WARNING_ALIAS_COLLISION;
}
public static final Log LOG = LogFactory.getLog(CsvRowInserter.class);
@@ -134,6 +134,13 @@ protected void doInTransactionWithoutResult(TransactionStatus status_) {
LOG.warn(warnMessage);
m_jobContext.warning(m_id, warnMessage);
break;
+ case WARNING_ALIAS_COLLISION:
+ insertRow(m_input);
+ warnMessage = "Alias collision - skip alias for: " + dataToString(m_input);
+ LOG.warn(warnMessage);
+ m_jobContext.warning(m_id, warnMessage);
+ afterInsert();
+ break;
default:
throw new IllegalArgumentException("Need to handle all status cases.");
}
@@ -10,14 +10,19 @@
package org.sipfoundry.sipxconfig.bulk.ldap;
+import static org.apache.commons.lang.StringUtils.EMPTY;
+import static org.apache.commons.lang.StringUtils.defaultString;
+import static org.apache.commons.lang.StringUtils.isBlank;
+import static org.apache.commons.lang.StringUtils.join;
+import static org.apache.commons.lang.StringUtils.split;
+
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.naming.Context;
-import org.apache.commons.lang.StringUtils;
import org.sipfoundry.sipxconfig.cfgmgt.DeployConfigOnEdit;
import org.sipfoundry.sipxconfig.common.BeanWithId;
import org.sipfoundry.sipxconfig.common.CronSchedule;
@@ -32,6 +37,8 @@
private static final int DEFAULT_SSL_PORT = 636;
private String m_host;
+ private String m_fullHost;
+ private String m_domain;
private Integer m_port;
private String m_principal;
private String m_secret;
@@ -50,6 +57,15 @@ public String getHost() {
public void setHost(String host) {
m_host = host;
+ m_fullHost = getFullHostValue();
+ }
+
+ private String getFullHostValue() {
+ if (!isBlank(m_domain) && !isBlank(m_host)) {
+ return join(new String [] {m_host, " ", m_domain});
+ } else {
+ return m_host;
+ }
}
public Integer getPort() {
@@ -113,8 +129,8 @@ public void setReferral(String referral) {
}
public void applyToContext(LdapContextSource config) {
- config.setUserName(StringUtils.defaultString(m_principal, StringUtils.EMPTY));
- config.setPassword(StringUtils.defaultString(m_secret, StringUtils.EMPTY));
+ config.setUserName(defaultString(m_principal, EMPTY));
+ config.setPassword(defaultString(m_secret, EMPTY));
config.setUrl(getUrl());
Map<String, String> otherParams = new HashMap<String, String>();
otherParams.put(Context.REFERRAL, m_referral);
@@ -125,4 +141,28 @@ public void applyToContext(LdapContextSource config) {
public Collection<Feature> getAffectedFeaturesOnChange() {
return Collections.singleton((Feature) LdapManager.FEATURE);
}
+
+ public String getDomain() {
+ return m_domain;
+ }
+
+ public void setDomain(String domain) {
+ m_domain = domain;
+ m_fullHost = getFullHostValue();
+ }
+
+ public String getFullHost() {
+ return m_fullHost;
+ }
+
+ public void setFullHost(String fullHost) {
+ m_fullHost = fullHost;
+ String[] host = split(m_fullHost);
+ if (host.length == 2) {
+ m_host = host[0];
+ m_domain = host[1];
+ } else {
+ m_host = fullHost;
+ }
+ }
}
@@ -48,6 +48,7 @@ public void setUserMapper(UserMapper userMapper) {
public void insert(int connectionId) {
m_rowInserter.setAttrMap(m_ldapManager.getAttrMap(connectionId));
+ m_rowInserter.setDomain(m_ldapManager.getConnectionParams(connectionId).getDomain());
m_rowInserter.beforeInserting();
NameClassPairCallbackHandler handler = new NameClassPairMapperClosureAdapter(
m_rowInserter);
@@ -44,6 +44,8 @@
private Set<String> m_existingUserNames;
private UserMapper m_userMapper;
private AttrMap m_attrMap;
+ private String m_domain;
+ private Set<String> m_aliases;
private boolean m_preserveMissingUsers;
@@ -87,6 +89,8 @@ void insertRow(SearchResult searchResult, Attributes attrs) {
m_existingUserNames.remove(userName);
m_userMapper.setUserProperties(user, attrs);
+ m_userMapper.setAliasesSet(m_aliases, user);
+
String pin = m_userMapper.getPin(attrs, newUser);
if (pin != null) {
user.setPin(pin, m_coreContext.getAuthorizationRealm());
@@ -115,6 +119,8 @@ void insertRow(SearchResult searchResult, Attributes attrs) {
user.addGroup(userGroup);
}
+ user.setSettingValue(User.DOMAIN_SETTING, m_domain);
+
if (newUser) {
// Execute the automatic assignments for the user.
GroupAutoAssign groupAutoAssign = new GroupAutoAssign(m_conferenceBridgeContext, m_coreContext,
@@ -141,16 +147,27 @@ protected RowStatus checkRowData(SearchResult sr) {
if (attrs.get(idAttrName) == null) {
return RowStatus.FAILURE;
}
- // check username
+ RowStatus status = RowStatus.SUCCESS;
try {
String userName = m_userMapper.getUserName(attrs);
+ // check username
if (!UserValidationUtils.isValidUserName(userName)) {
return RowStatus.FAILURE;
}
+ Set<String> aliases = m_userMapper.getAliasesSet(attrs);
+ if (aliases != null) {
+ for (String alias : aliases) {
+ if (m_coreContext.isAliasInUseForOthers(alias, userName)) {
+ aliases.remove(alias);
+ status = RowStatus.WARNING_ALIAS_COLLISION;
+ }
+ }
+ }
+ m_aliases = aliases;
} catch (NamingException e) {
return RowStatus.FAILURE;
}
- return RowStatus.SUCCESS;
+ return status;
}
public void setAttrMap(AttrMap attrMap) {
@@ -188,4 +205,8 @@ public void setLdapManager(LdapManager ldapManager) {
private AttrMap getAttrMap() {
return m_attrMap;
}
+
+ public void setDomain(String domain) {
+ m_domain = domain;
+ }
}
@@ -47,7 +47,7 @@ public Object mapFromNameClassPair(NameClassPair nameClass) throws NamingExcepti
List<String> groupNames = new ArrayList<String>(getGroupNames(searchResult));
setUserProperties(user, attrs);
-
+ setAliasesSet(getAliasesSet(attrs), user);
UserPreview preview = new UserPreview(user, groupNames);
return preview;
}
@@ -87,13 +87,20 @@ void setUserProperties(User user, Attributes attrs) throws NamingException {
setProperty(user, attrs, Index.OFFICE_STATE);
setProperty(user, attrs, Index.OFFICE_COUNTRY);
setProperty(user, attrs, Index.OFFICE_ZIP);
+ }
- Set<String> aliases = getValues(attrs, Index.ALIAS);
+ public void setAliasesSet(Set<String> aliases, User user) {
if (aliases != null) {
user.copyAliases(deleteWhitespace(aliases));
+ } else {
+ user.setAliasesString(null);
}
}
+ public Set<String> getAliasesSet(Attributes attrs) throws NamingException {
+ return getValues(attrs, Index.ALIAS);
+ }
+
public Collection<String> getGroupNames(SearchResult sr) throws NamingException {
Set<String> groupNames = new HashSet<String>();
// group names in the current entry
@@ -156,9 +163,7 @@ void setUserProperties(User user, Attributes attrs) throws NamingException {
private void setProperty(User user, Attributes attrs, Index index) throws NamingException {
try {
String value = getValue(attrs, index);
- if (value != null) {
- BeanUtils.setProperty(user, index.getName(), value);
- }
+ BeanUtils.setProperty(user, index.getName(), value);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
@@ -242,6 +247,14 @@ public String getPin(Attributes attrs, boolean newUser) throws NamingException {
return pin;
}
+ public String getAliases(Attributes attrs) throws NamingException {
+ return getValue(attrs, Index.ALIAS);
+ }
+
+ public String getImId(Attributes attrs) throws NamingException {
+ return getValue(attrs, Index.IM_ID);
+ }
+
public String getUserName(Attributes attrs) throws NamingException {
String attrName = getAttrMap().getIdentityAttributeName();
String userName = getValue(attrs, attrName);
@@ -16,7 +16,7 @@
<id name="id" column="ldap_connection_id" type="int" unsaved-value="-1">
<generator class="native" />
</id>
- <property name="host" />
+ <property name="fullHost" column="host" />
<property name="port" />
<property name="principal" />
<property name="secret" />
@@ -68,6 +68,7 @@
public static final String EMPTY_STRING = "";
public static final String FAX_EXTENSION_SETTING = "voicemail/fax/extension";
public static final String DID_SETTING = "voicemail/fax/did";
+ public static final String DOMAIN_SETTING = "user-domain/domain";
public static final String CALLFWD_TIMER = "callfwd/timer";
public static final String OPERATOR_SETTING = "personal-attendant/operator";
public static final String DEFAULT_VM_OPTION = "personal-attendant/default-vm-option";
@@ -456,6 +457,12 @@ public String getFaxDid() {
.getTypedValue());
}
+ public String getUserDomain() {
+ Setting setting = null == getSettings() ? null : getSettings().getSetting(DOMAIN_SETTING);
+ return null == setting ? EMPTY_STRING : (setting.getTypedValue() == null ? EMPTY_STRING : (String) setting
+ .getTypedValue());
+ }
+
public void setFaxDid(String did) {
setSettingValue(DID_SETTING, did);
}
@@ -217,4 +217,6 @@
Collection<Integer> getBranchMembersByPage(int bid, int first, int pageSize);
+ public boolean isAliasInUseForOthers(String alias, String username);
+
}
@@ -60,6 +60,16 @@
+ "where u.user_name= :alias or alias.alias= :alias or abe.im_id= :alias "
+ "or (sv.path='voicemail/fax/did' and sv.value= :alias) "
+ "or (sv.path='voicemail/fax/extension' and sv.value= :alias) ";
+ private static final String SQL_QUERY_USER_IDS_BY_NAME_OR_ALIAS_OR_IM_ID_EXCEPT_THIS =
+ "select distinct u.user_id from users "
+ + "u left outer join user_alias alias "
+ + "on u.user_id=alias.user_id left "
+ + "outer join address_book_entry abe on u.address_book_entry_id=abe.address_book_entry_id left "
+ + "outer join value_storage vs on vs.value_storage_id=u.value_storage_id left "
+ + "outer join setting_value sv on sv.value_storage_id=vs.value_storage_id "
+ + "where (u.user_name= :alias or alias.alias= :alias or abe.im_id = :alias "
+ + "or (sv.path='voicemail/fax/did' and sv.value = :alias) "
+ + "or (sv.path='voicemail/fax/extension' and sv.value = :alias)) and u.user_name != :username";
private static final String ALIAS = "alias";
private static final String QUERY_USER = "from AbstractUser";
private static final String QUERY_PARAM_GROUP_ID = "groupId";
@@ -773,6 +783,16 @@ public boolean isAliasInUse(String alias) {
return SipxCollectionUtils.safeSize(userIds) > 0;
}
+ @Override
+ public boolean isAliasInUseForOthers(String alias, String username) {
+ Query q = getHibernateTemplate().getSessionFactory().getCurrentSession()
+ .createSQLQuery(SQL_QUERY_USER_IDS_BY_NAME_OR_ALIAS_OR_IM_ID_EXCEPT_THIS).addScalar(USER_ID, Hibernate.INTEGER);
+ q.setString(ALIAS, alias);
+ q.setString("username", username);
+ List<Integer> userIds = q.list();
+ return SipxCollectionUtils.safeSize(userIds) > 0;
+ }
+
@Override
public Collection getBeanIdsOfObjectsWithAlias(String alias) {
Query q = getHibernateTemplate().getSessionFactory().getCurrentSession()
Oops, something went wrong.

0 comments on commit 6e0a067

Please sign in to comment.