/**
* License Agreement for OpenSearchServer
*
* Copyright (C) 2014-2015 Emmanuel Keller / Jaeksoft
*
* http://www.open-search-server.com
*
* This file is part of OpenSearchServer.
*
* OpenSearchServer is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* OpenSearchServer 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 General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* OpenSearchServer. If not, see <http://www.gnu.org/licenses/>.
**/
package com.jaeksoft.searchlib.util;
import java.io.Closeable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import org.apache.commons.collections.CollectionUtils;
import com.jaeksoft.searchlib.Logging;
public class ActiveDirectory implements Closeable {
private final DirContext dirContext;
private final String domainSearchName;
public ActiveDirectory(String serverName, String username, String password,
String domain) throws NamingException {
if (StringUtils.isEmpty(domain))
throw new NamingException("The domain is empty");
Properties properties = new Properties();
properties.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
domainSearchName = getDomainSearch(domain);
String login = StringUtils.fastConcat(username, "@", domain);
if (serverName != null) {
properties.put(Context.PROVIDER_URL,
StringUtils.fastConcat("ldap://", serverName, ":389"));
}
properties.put(Context.SECURITY_PRINCIPAL, login);
properties.put(Context.SECURITY_CREDENTIALS, password);
properties.put(Context.REFERRAL, "follow");
properties.put("java.naming.ldap.attributes.binary", "objectSID");
dirContext = new InitialLdapContext(properties, null);
}
public final static String ATTR_CN = "cn";
public final static String ATTR_MAIL = "mail";
public final static String ATTR_GIVENNAME = "givenName";
public final static String ATTR_MEMBEROF = "memberOf";
public final static String ATTR_OBJECTSID = "objectSid";
public final static String ATTR_SAMACCOUNTNAME = "sAMAccountName";
public final static String ATTR_DN = "DistinguishedName";
private NamingEnumeration<SearchResult> find(String filterExpr,
String... returningAttributes) throws NamingException {
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setReturningAttributes(returningAttributes);
return dirContext.search(domainSearchName, filterExpr, searchControls);
}
public static final Attributes getAttributes(
NamingEnumeration<SearchResult> result) throws NamingException {
if (result == null)
return null;
if (!result.hasMore())
return null;
SearchResult rs = (SearchResult) result.next();
return rs.getAttributes();
}
public NamingEnumeration<SearchResult> findUser(String username)
throws NamingException {
return find(
StringUtils.fastConcat(
"(&(objectCategory=person)(objectClass=user)(samAccountType=805306368)(sAMAccountName=",
username, "))"), ATTR_CN, ATTR_MAIL, ATTR_GIVENNAME,
ATTR_OBJECTSID, ATTR_SAMACCOUNTNAME, ATTR_MEMBEROF, ATTR_DN);
}
private NamingEnumeration<SearchResult> findGroup(String group)
throws NamingException {
return find(
StringUtils.fastConcat(
"(&(objectCategory=group)(objectClass=group)(samAccountType=268435456)(sAMAccountName=",
group, "))"), ATTR_CN, ATTR_MAIL, ATTR_GIVENNAME,
ATTR_OBJECTSID, ATTR_SAMACCOUNTNAME, ATTR_MEMBEROF, ATTR_DN);
}
private void findGroups(Collection<ADGroup> groups,
Collection<ADGroup> collector, Set<String> searchedGroups)
throws NamingException {
if (CollectionUtils.isEmpty(groups))
return;
List<ADGroup> newGroups = new ArrayList<ADGroup>();
for (ADGroup group : groups) {
if (searchedGroups.contains(group.cn1))
continue;
collector.add(group);
searchedGroups.add(group.cn1);
NamingEnumeration<SearchResult> result = findGroup(group.cn1);
Attributes attrs = getAttributes(result);
if (attrs == null)
continue;
collectMemberOf(attrs, newGroups);
}
findGroups(newGroups, collector, searchedGroups);
}
public void findUserGroups(Attributes userAttrs,
Collection<ADGroup> collector) throws NamingException {
List<ADGroup> groups = new ArrayList<ADGroup>();
ActiveDirectory.collectMemberOf(userAttrs, groups);
TreeSet<String> searchedGroups = new TreeSet<String>();
findGroups(groups, collector, searchedGroups);
}
public void findUserGroup(String userDN, Collection<ADGroup> collector)
throws NamingException {
String filter = StringUtils.fastConcat(
"(member:1.2.840.113556.1.4.1941:=", userDN, ')');
NamingEnumeration<SearchResult> results = find(filter, ATTR_OBJECTSID,
ATTR_DN);
while (results.hasMore()) {
SearchResult searchResult = results.next();
Attributes groupAttrs = searchResult.getAttributes();
Logging.info("ATTRS: " + groupAttrs.toString());
ADGroup adGroup = new ADGroup(getObjectSID(groupAttrs),
getStringAttribute(groupAttrs, ATTR_DN));
collector.add(adGroup);
}
}
@Override
public void close() {
try {
if (dirContext != null)
dirContext.close();
} catch (NamingException e) {
Logging.warn(e);
}
}
private static String getDomainName(String domain) {
String[] dcs = StringUtils.split(domain, '.');
return dcs != null && dcs.length > 0 ? dcs[0] : null;
}
final public static String getDisplayString(String domain, String user) {
StringBuilder sb = new StringBuilder();
String domainName = getDomainName(domain);
if (domainName != null)
sb.append(domainName);
if (user != null) {
if (sb.length() > 0)
sb.append('\\');
sb.append(user);
}
return sb.toString().toLowerCase();
}
public static void collectMemberOf(Attributes attrs,
Collection<ADGroup> groups) throws NamingException {
Attribute tga = attrs.get("memberOf");
if (tga == null)
return;
NamingEnumeration<?> membersOf = tga.getAll();
while (membersOf.hasMore()) {
Object memberObject = membersOf.next();
groups.add(new ADGroup(getObjectSID(attrs), memberObject.toString()));
}
membersOf.close();
}
public static class ADGroup {
public final String sid;
public final String cn1;
public final String cn2;
public final String dc;
private ADGroup(final String sid, final String memberOf) {
this.sid = sid;
String[] parts = StringUtils.split(memberOf, ',');
String lcn1 = null;
String lcn2 = null;
String ldc = null;
for (String part : parts) {
String[] pair = StringUtils.split(part, "=");
if (pair == null || pair.length != 2)
continue;
if ("cn".equalsIgnoreCase(pair[0])) {
if (lcn1 == null)
lcn1 = pair[1];
else if (lcn2 == null)
lcn2 = pair[1];
}
if (ldc == null && "dc".equalsIgnoreCase(pair[0]))
ldc = pair[1];
}
this.cn1 = lcn1;
this.cn2 = lcn2;
this.dc = ldc;
}
}
public static String[] toArray(Collection<ADGroup> groups,
String... additionalGroups) {
TreeSet<String> groupSet = new TreeSet<String>();
for (ADGroup group : groups) {
if ("builtin".equalsIgnoreCase(group.cn2))
groupSet.add(group.cn1.toLowerCase());
else
groupSet.add(StringUtils.fastConcat(group.dc, '\\', group.cn1)
.toLowerCase());
groupSet.add(group.sid);
}
if (additionalGroups != null)
for (String additionalGroup : additionalGroups)
groupSet.add(additionalGroup);
return groupSet.toArray(new String[groupSet.size()]);
}
public static void main(String[] args) throws NamingException {
System.out.println(getDisplayString("sp.int.fr", "01234"));
System.out
.println(new ADGroup(
null,
"CN=GG-TEST-TEST-TEST,OU=Groupes Ressource,OU=Groupes,OU=DSCP,DC=sp,DC=pn,DC=int"));
}
private static String getDomainSearch(String domain) {
String[] dcs = StringUtils.split(domain, '.');
StringBuilder sb = new StringBuilder();
boolean first = true;
for (String dc : dcs) {
if (!first)
sb.append(',');
else
first = false;
sb.append("dc=");
sb.append(dc);
}
return sb.toString();
}
public static String getStringAttribute(Attributes attrs, String name) {
Attribute attr = attrs.get(name);
if (attr == null)
return null;
String s = attr.toString();
if (StringUtils.isEmpty(s))
return s;
int i = s.indexOf(':');
if (i == -1)
throw new IllegalArgumentException(StringUtils.fastConcat(
"Wrong returned value: ", s));
return s.substring(i + 1).trim();
}
public static String getObjectSID(Attributes attrs) throws NamingException {
Attribute attr = attrs.get("objectsid");
if (attr == null)
throw new NamingException("No ObjectSID attribute");
Object attrObject = attr.get();
if (attrObject == null)
throw new NamingException("ObjectSID is empty");
if (attrObject instanceof String) {
String attrString = (String) attrObject;
if (attrString.startsWith("S-"))
return attrString;
return decodeSID(attrString.getBytes());
} else if (attrObject instanceof byte[]) {
return decodeSID((byte[]) attrObject);
} else
throw new NamingException("Unknown attribute type: "
+ attrObject.getClass().getName());
}
/**
* The binary data is in the form: byte[0] - revision level byte[1] - count
* of sub-authorities byte[2-7] - 48 bit authority (big-endian) and then
* count x 32 bit sub authorities (little-endian)
*
* The String value is: S-Revision-Authority-SubAuthority[n]...
*
* Based on code from here -
* http://forums.oracle.com/forums/thread.jspa?threadID=1155740&tstart=0
*/
public static String decodeSID(byte[] sid) {
final StringBuilder strSid = new StringBuilder("S-");
// get version
final int revision = sid[0];
strSid.append(Integer.toString(revision));
// next byte is the count of sub-authorities
final int countSubAuths = sid[1] & 0xFF;
// get the authority
long authority = 0;
// String rid = "";
for (int i = 2; i <= 7; i++) {
authority |= ((long) sid[i]) << (8 * (5 - (i - 2)));
}
strSid.append("-");
strSid.append(Long.toHexString(authority));
// iterate all the sub-auths
int offset = 8;
int size = 4; // 4 bytes for each sub auth
for (int j = 0; j < countSubAuths; j++) {
long subAuthority = 0;
for (int k = 0; k < size; k++) {
subAuthority |= (long) (sid[offset + k] & 0xFF) << (8 * k);
}
strSid.append("-");
strSid.append(subAuthority);
offset += size;
}
return strSid.toString();
}
}