blob: 865f9cd1da4ceda06d82ac3e35f9e877a76342f8 [file] [log] [blame]
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Licensed 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 com.google.enterprise.adaptor.ad;
import com.google.enterprise.adaptor.AbstractAdaptor;
import com.google.enterprise.adaptor.AdaptorContext;
import com.google.enterprise.adaptor.Config;
import com.google.enterprise.adaptor.DocIdPusher;
import com.google.enterprise.adaptor.GroupPrincipal;
import com.google.enterprise.adaptor.Principal;
import com.google.enterprise.adaptor.Request;
import com.google.enterprise.adaptor.Response;
import com.google.enterprise.adaptor.UserPrincipal;
import java.io.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.InterruptedNamingException;
/** Adaptor for Active Directory. */
public class AdAdaptor extends AbstractAdaptor {
private static final Logger log
= Logger.getLogger(AdAdaptor.class.getName());
// private static final Charset ENCODING = Charset.forName("UTF-8");
private static final boolean CASE_SENSITIVITY = false;
private String namespace;
private String domain;
private List<AdServer> servers = new ArrayList<AdServer>();
@Override
public void initConfig(Config config) {
config.addKey("ad.servers", null);
config.addKey("ad.namespace", "Default");
// TODO: pull domain from AD
config.addKey("ad.domain", null);
}
@Override
public void init(AdaptorContext context) throws Exception {
namespace = context.getConfig().getValue("ad.namespace");
log.config("common namespace: " + namespace);
domain = context.getConfig().getValue("ad.domain");
log.config("common domain: " + domain);
List<Map<String, String>> serverConfigs
= context.getConfig().getListOfConfigs("ad.servers");
for (Map<String, String> singleServerConfig : serverConfigs) {
String host = singleServerConfig.get("host");
int port = Integer.parseInt(singleServerConfig.get("port"));
Method method = null;
if ("ssl".equals(singleServerConfig.get("method").toLowerCase())) {
method = Method.SSL;
} else {
method = Method.STANDARD;
}
String principal = singleServerConfig.get("user");
String passwd = singleServerConfig.get("password");
AdServer adServer = new AdServer(method, host, port, principal, passwd);
servers.add(adServer);
Map<String, String> dup = new TreeMap<String, String>(singleServerConfig);
dup.put("password", "XXXXXX");
log.config("AD server spec: " + dup);
}
}
/** This adaptor does not serve documents. */
@Override
public void getDocContent(Request req, Response resp) throws IOException {
resp.respondNotFound();
}
/** Call default main for adaptors. */
public static void main(String[] args) {
AbstractAdaptor.main(new AdAdaptor(), args);
}
/** Pushes all groups from all AdServers. */
@Override
public void getDocIds(DocIdPusher pusher) throws InterruptedException {
// TODO: implement well known groups
// TODO: implement built in groups
for (AdServer server : servers) {
server.initialize();
try {
GroupCatalog catalog = new GroupCatalog();
catalog.readFrom(server);
pusher.pushGroupDefinitions(catalog.makeDefs(), CASE_SENSITIVITY);
} catch (InterruptedNamingException ine) {
String host = server.getHostName();
log.log(Level.WARNING, "skipping " + host, ine);
}
}
}
// Space for all group info, organized in different ways
private class GroupCatalog {
Set<AdEntity> entities = new HashSet<AdEntity>();
Map<AdEntity, Set<String>> members = new HashMap<AdEntity, Set<String>>();
Map<String, AdEntity> bySid = new HashMap<String, AdEntity>();
Map<String, AdEntity> byDn = new HashMap<String, AdEntity>();
void readFrom(AdServer server) throws InterruptedNamingException {
entities = server.search(AdConstants.LDAP_QUERY, /*deleted=*/ false,
new String[] {
AdConstants.ATTR_USNCHANGED,
AdConstants.ATTR_SAMACCOUNTNAME,
AdConstants.ATTR_OBJECTSID,
AdConstants.ATTR_OBJECTGUID,
AdConstants.ATTR_UPN,
AdConstants.ATTR_PRIMARYGROUPID,
AdConstants.ATTR_MEMBER}
);
int n = entities.size();
log.log(Level.FINE, "received " + n + " entities from server");
for (AdEntity e : entities) {
bySid.put(e.getSid(), e);
byDn.put(e.getDn(), e);
}
resolvePrimaryGroups();
resolveForeignSecurityPrincipals();
}
private void resolvePrimaryGroups() {
for (AdEntity e : entities) {
if (e.isGroup()) {
continue;
}
AdEntity user = e;
AdEntity primaryGroup = bySid.get(user.getPrimaryGroupSid());
if (!members.containsKey(primaryGroup)) {
members.put(primaryGroup, new TreeSet<String>());
}
members.get(primaryGroup).add(user.getDn());
}
}
private void resolveForeignSecurityPrincipals() {
for (AdEntity entity : entities) {
if (!entity.isGroup()) {
continue;
}
if (!members.containsKey(entity)) {
continue;
}
Set<String> resolvedMembers = new HashSet<String>();
for (String member : members.get(entity)) {
String sid = AdEntity.parseForeignSecurityPrincipal(member);
if (null == sid) {
resolvedMembers.add(member);
} else {
AdEntity resolved = bySid.get(sid);
if (null == resolved) {
log.info("unable to resolve foreign principal ["
+ member + "]; member of [" + entity.getDn());
} else {
resolvedMembers.add(resolved.getDn());
}
}
}
members.put(entity, resolvedMembers);
}
}
private Map<GroupPrincipal, List<Principal>> makeDefs() {
Map<GroupPrincipal, List<Principal>> groups
= new HashMap<GroupPrincipal, List<Principal>>();
for (AdEntity entity : entities) {
if (!entity.isGroup()) {
continue;
}
// TODO: get domain from entity
GroupPrincipal group = new GroupPrincipal(
entity.getSAMAccountName() + "@" + domain, namespace);
List<Principal> def = new ArrayList<Principal>();
if (members.containsKey(entity)) {
for (String memberDn : members.get(entity)) {
AdEntity member = byDn.get(memberDn);
if (member == null) {
log.info("Unknown member [" + memberDn + "] of group ["
+ entity.getDn());
continue;
}
Principal p;
if (member.isGroup()) {
// TODO: get domain from entity
p = new GroupPrincipal(
member.getSAMAccountName() + "@" + domain, namespace);
} else {
// TODO: get domain from entity
p = new UserPrincipal(
member.getSAMAccountName() + "@" + domain, namespace);
}
def.add(p);
}
}
groups.put(group, def);
}
if (log.isLoggable(Level.FINE)) {
log.fine("number of groups defined: " + groups.keySet().size());
}
if (log.isLoggable(Level.FINER)) {
int numGroups = groups.keySet().size();
int totalMembers = 0;
for (List<Principal> def : groups.values()) {
totalMembers += def.size();
}
if (0 != numGroups) {
double mean = ((double) totalMembers) / numGroups;
log.finer("mean size of defined group: " + mean);
}
}
return groups;
}
}
}