/*
* eXist Open Source Native XML Database
* Copyright (C) 2010 The eXist Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
package org.exist.security.realm.ldap;
import java.lang.reflect.Field;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
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.LdapContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.exist.EXistException;
import org.exist.config.Configuration;
import org.exist.config.annotation.*;
import org.exist.security.AXSchemaType;
import org.exist.security.AbstractAccount;
import org.exist.security.AbstractRealm;
import org.exist.security.Account;
import org.exist.security.AuthenticationException;
import org.exist.security.Group;
import org.exist.security.PermissionDeniedException;
import org.exist.security.SchemaType;
import org.exist.security.Subject;
import org.exist.security.internal.SecurityManagerImpl;
import org.exist.security.internal.SubjectAccreditedImpl;
import org.exist.security.internal.aider.GroupAider;
import org.exist.security.internal.aider.UserAider;
import org.exist.security.realm.ldap.AbstractLDAPSearchPrincipal.LDAPSearchAttributeKey;
import org.exist.storage.DBBroker;
/**
* @author <a href="mailto:shabanovd@gmail.com">Dmitriy Shabanov</a>
* @author Adam Retter <adam@exist-db.org>
*/
@ConfigurationClass("realm") //TODO: id = LDAP
public class LDAPRealm extends AbstractRealm {
private final static Logger LOG = LogManager.getLogger(LDAPRealm.class);
@ConfigurationFieldAsAttribute("id")
public static String ID = "LDAP";
@ConfigurationFieldAsAttribute("version")
public final static String version = "1.0";
@ConfigurationFieldAsAttribute("principals-are-case-insensitive")
private boolean principalsAreCaseInsensitive;
@ConfigurationFieldAsElement("context")
protected LdapContextFactory ldapContextFactory;
public LDAPRealm(SecurityManagerImpl sm, Configuration config) {
super(sm, config);
}
protected LdapContextFactory ensureContextFactory() {
if(this.ldapContextFactory == null) {
if(LOG.isDebugEnabled()) {
LOG.debug("No LdapContextFactory specified - creating a default instance.");
}
LdapContextFactory factory = new LdapContextFactory(configuration);
this.ldapContextFactory = factory;
}
return this.ldapContextFactory;
}
@Override
public String getId() {
return ID;
}
@Override
public void start(DBBroker broker) throws EXistException {
super.start(broker);
}
private String ensureCase(final String username) {
if(username == null){
return null;
}
if (principalsAreCaseInsensitive) {
return username.toLowerCase();
}
return username;
}
@Override
public Subject authenticate(final String username, final Object credentials) throws AuthenticationException {
final String name = ensureCase(username);
// Binds using the username and password provided by the user.
LdapContext ctx = null;
try {
ctx = ensureContextFactory().getLdapContext(name, String.valueOf(credentials));
final AbstractAccount account = (AbstractAccount) getAccount(ctx, name);
if (account == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Account '"+name+"' can not be found.");
}
throw new AuthenticationException(
AuthenticationException.ACCOUNT_NOT_FOUND,
"Account '"+name+"' can not be found.");
}
return new AuthenticatedLdapSubjectAccreditedImpl(account, ctx, String.valueOf(credentials));
} catch(final NamingException e) {
LOG.debug(e.getMessage(), e);
if(e instanceof javax.naming.AuthenticationException) {
throw new AuthenticationException(AuthenticationException.ACCOUNT_NOT_FOUND, e.getMessage());
} else {
throw new AuthenticationException(AuthenticationException.UNNOWN_EXCEPTION, e.getMessage());
}
} finally {
LdapUtils.closeContext(ctx);
}
}
private List<Group> getGroupMembershipForLdapUser(final LdapContext ctx, final DBBroker broker, final SearchResult ldapUser) throws NamingException {
final List<Group> memberOf_groups = new ArrayList<>();
final LDAPSearchContext search = ensureContextFactory().getSearch();
final String userDistinguishedName = (String)ldapUser.getAttributes().get(search.getSearchAccount().getSearchAttribute(LDAPSearchAttributeKey.DN)).get();
final List<String> memberOf_groupNames = findGroupnamesForUserDistinguishedName(ctx, userDistinguishedName);
for(final String memberOf_groupName : memberOf_groupNames) {
memberOf_groups.add(getGroup(ctx, broker, memberOf_groupName));
}
//TODO expand to a general method that rewrites the useraider based on the realTransformation
if(ensureContextFactory().getTransformationContext() != null){
final List<String> additionalGroupNames = ensureContextFactory().getTransformationContext().getAdditionalGroups();
if(additionalGroupNames != null) {
for(final String additionalGroupName : additionalGroupNames) {
final Group additionalGroup = getSecurityManager().getGroup(additionalGroupName);
if(additionalGroup != null) {
memberOf_groups.add(additionalGroup);
}
}
}
}
return memberOf_groups;
}
private List<SimpleEntry<AXSchemaType, String>> getMetadataForLdapUser(final SearchResult ldapUser) throws NamingException {
final List<SimpleEntry<AXSchemaType, String>> metadata = new ArrayList<SimpleEntry<AXSchemaType, String>>();
final LDAPSearchAccount searchAccount = ensureContextFactory().getSearch().getSearchAccount();
final Attributes userAttributes = ldapUser.getAttributes();
//store any requested metadata
for(final AXSchemaType axSchemaType : searchAccount.getMetadataSearchAttributeKeys()) {
final String searchAttribute = searchAccount.getMetadataSearchAttribute(axSchemaType);
if(userAttributes != null) {
final Attribute userAttribute = userAttributes.get(searchAttribute);
if(userAttribute != null) {
final String attributeValue = userAttribute.get().toString();
metadata.add(new SimpleEntry<AXSchemaType, String>(axSchemaType, attributeValue));
}
}
}
return metadata;
}
public Account refreshAccountFromLdap(final Account account) throws PermissionDeniedException, AuthenticationException{
final int UPDATE_NONE = 0;
final int UPDATE_GROUP = 1;
final int UPDATE_METADATA = 2;
final Subject invokingUser = getSecurityManager().getCurrentSubject();
if(!invokingUser.hasDbaRole() && invokingUser.getId() != account.getId()) {
throw new PermissionDeniedException("You do not have permission to modify the account");
}
try {
final LdapContext ctx = getContext(invokingUser);
final SearchResult ldapUser = findAccountByAccountName(ctx, account.getName());
if(ldapUser == null) {
throw new AuthenticationException(AuthenticationException.ACCOUNT_NOT_FOUND, "Could not find the account in the LDAP");
}
return executeAsSystemUser(ctx, new Unit<Account>(){
@Override
public Account execute(LdapContext ctx, DBBroker broker) throws EXistException, PermissionDeniedException, NamingException {
int update = UPDATE_NONE;
//1) get the ldap group membership
final List<Group> memberOf_groups = getGroupMembershipForLdapUser(ctx, broker, ldapUser);
//2) get the ldap primary group
final String primaryGroup = findGroupBySID(ctx, getPrimaryGroupSID(ldapUser));
//append the ldap primaryGroup to the head of the ldap group list, and compare
//to the account group list
memberOf_groups.add(0, getGroup(ctx, broker, primaryGroup));
final String accountGroups[] = account.getGroups();
if(!accountGroups[0].equals(ensureCase(primaryGroup))) {
update |= UPDATE_GROUP;
} else {
if(accountGroups.length != memberOf_groups.size()) {
update |= UPDATE_GROUP;
} else {
for(int i = 0; i < accountGroups.length; i++) {
boolean found = false;
for(Group memberOf_group : memberOf_groups) {
if(accountGroups[i].equals(ensureCase(memberOf_group.getName()))) {
found = true;
break;
}
}
if(!found) {
update |= UPDATE_GROUP;
break;
}
}
}
}
//3) check metadata
final List<SimpleEntry<AXSchemaType, String>> ldapMetadatas = getMetadataForLdapUser(ldapUser);
final Set<SchemaType> accountMetadataKeys = account.getMetadataKeys();
if(accountMetadataKeys.size() != ldapMetadatas.size()) {
update |= UPDATE_METADATA;
} else {
for(SchemaType accountMetadataKey : accountMetadataKeys) {
final String accountMetadataValue = account.getMetadataValue(accountMetadataKey);
boolean found = false;
for(SimpleEntry<AXSchemaType, String> ldapMetadata : ldapMetadatas) {
if(accountMetadataKey.equals(ldapMetadata.getKey()) && accountMetadataValue.equals(ldapMetadata.getValue())) {
found = true;
break;
}
}
if(!found) {
update |= UPDATE_METADATA;
break;
}
}
}
//update the groups?
if((update & UPDATE_GROUP) == UPDATE_GROUP) {
try {
Field fld = account.getClass().getSuperclass().getDeclaredField("groups");
fld.setAccessible(true);
fld.set(account, memberOf_groups);
} catch(NoSuchFieldException nsfe) {
throw new EXistException(nsfe.getMessage(), nsfe);
} catch(IllegalAccessException iae) {
throw new EXistException(iae.getMessage(), iae);
}
}
//update the metdata?
if((update & UPDATE_METADATA) == UPDATE_METADATA) {
account.clearMetadata();
for(SimpleEntry<AXSchemaType, String> ldapMetadata : ldapMetadatas) {
account.setMetadataValue(ldapMetadata.getKey(), ldapMetadata.getValue());
}
}
if(update != UPDATE_NONE) {
boolean updated = getSecurityManager().updateAccount(account);
if(!updated) {
LOG.error("Could not update account");
}
}
return account;
}
});
} catch(final NamingException ne) {
throw new AuthenticationException(AuthenticationException.UNNOWN_EXCEPTION, ne.getMessage(), ne);
} catch(final EXistException ee) {
throw new AuthenticationException(AuthenticationException.UNNOWN_EXCEPTION, ee.getMessage(), ee);
}
}
private Account createAccountInDatabase(final LdapContext ctx, final String username, final SearchResult ldapUser, final String primaryGroupName) throws AuthenticationException {
final LDAPSearchAccount searchAccount = ensureContextFactory().getSearch().getSearchAccount();
try {
return executeAsSystemUser(ctx, new Unit<Account>(){
@Override
public Account execute(final LdapContext ctx, final DBBroker broker) throws EXistException, PermissionDeniedException, NamingException {
if(LOG.isDebugEnabled()) {
LOG.debug("Saving account '"+username+"'.");
}
//get (or create) the primary group if it doesnt exist
final Group primaryGroup = getGroup(ctx, broker, primaryGroupName);
//get (or create) member groups
/*LDAPSearchContext search = ensureContextFactory().getSearch();
String userDistinguishedName = (String)ldapUser.getAttributes().get(search.getSearchAccount().getSearchAttribute(LDAPSearchAttributeKey.DN)).get();
List<String> memberOf_groupNames = findGroupnamesForUserDistinguishedName(invokingUser, userDistinguishedName);
List<Group> memberOf_groups = new ArrayList<Group>();
for(String memberOf_groupName : memberOf_groupNames) {
memberOf_groups.add(getGroup(invokingUser, memberOf_groupName));
}*/
//create the user account
final UserAider userAider = new UserAider(ID, username, primaryGroup);
//add the member groups
for(final Group memberOf_group : getGroupMembershipForLdapUser(ctx, broker, ldapUser)) {
userAider.addGroup(memberOf_group);
}
//store any requested metadata
for(final SimpleEntry<AXSchemaType, String> metadata : getMetadataForLdapUser(ldapUser)) {
userAider.setMetadataValue(metadata.getKey(), metadata.getValue());
}
final Account account = getSecurityManager().addAccount(userAider);
//LDAPAccountImpl account = sm.addAccount(instantiateAccount(ID, username));
//TODO expand to a general method that rewrites the useraider based on the realTransformation
/*
boolean updatedAccount = false;
if(ensureContextFactory().getTransformationContext() != null){
List<String> additionalGroupNames = ensureContextFactory().getTransformationContext().getAdditionalGroups();
if(additionalGroupNames != null) {
for(String additionalGroupName : additionalGroupNames) {
Group additionalGroup = getSecurityManager().getGroup(invokingUser, ensureCase(additionalGroupName));
if(additionalGroup != null) {
account.addGroup(additionalGroup);
updatedAccount = true;
}
}
}
}
if(updatedAccount) {
boolean updated = getSecurityManager().updateAccount(invokingUser, account);
if(!updated) {
LOG.error("Could not update account");
}
}*/
return account;
}
});
} catch(final Exception e) {
LOG.debug(e);
throw new AuthenticationException(AuthenticationException.UNNOWN_EXCEPTION, e.getMessage(), e);
}
}
private interface Unit<R> {
public R execute(LdapContext ctx, DBBroker broker) throws EXistException, PermissionDeniedException, NamingException;
}
private <R> R executeAsSystemUser(final LdapContext ctx, final Unit<R> unit) throws EXistException, PermissionDeniedException, NamingException {
try(final DBBroker broker = getDatabase().get(Optional.of(getSecurityManager().getSystemSubject()))) {
//perform as SYSTEM user
return unit.execute(ctx, broker);
}
}
private Group createGroupInDatabase(final DBBroker broker, final String groupname) throws AuthenticationException {
try {
//return sm.addGroup(instantiateGroup(this, groupname));
return getSecurityManager().addGroup(broker, new GroupAider(ID, groupname));
} catch(Exception e) {
throw new AuthenticationException(AuthenticationException.UNNOWN_EXCEPTION, e.getMessage(), e);
}
}
private LdapContext getContext(final Subject invokingUser) throws NamingException {
final LdapContextFactory ctxFactory = ensureContextFactory();
final LdapContext ctx;
if(invokingUser != null && invokingUser instanceof AuthenticatedLdapSubjectAccreditedImpl) {
//use the provided credentials for the lookup
ctx = ctxFactory.getLdapContext(invokingUser.getUsername(), ((AuthenticatedLdapSubjectAccreditedImpl) invokingUser).getAuthenticatedCredentials(), null);
} else {
//use the default credentials for lookup
LDAPSearchContext searchCtx = ctxFactory.getSearch();
ctx = ctxFactory.getLdapContext(searchCtx.getDefaultUsername(), searchCtx.getDefaultPassword(), null);
}
return ctx;
}
@Override
public final synchronized Account getAccount(String name) {
name = ensureCase(name);
//first attempt to get the cached account
final Account acct = super.getAccount(name);
if(acct != null) {
return acct;
} else {
LdapContext ctx = null;
try {
ctx = getContext(getSecurityManager().getDatabase().getActiveBroker().getCurrentSubject());
return getAccount(ctx, name);
} catch(final NamingException ne) {
LOG.debug(ne.getMessage(), ne);
LOG.error(new AuthenticationException(AuthenticationException.UNNOWN_EXCEPTION, ne.getMessage()));
return null;
} finally {
if(ctx != null){
LdapUtils.closeContext(ctx);
}
}
}
}
public final synchronized Account getAccount(final LdapContext ctx, String name) {
name = ensureCase(name);
if (LOG.isDebugEnabled()) {
LOG.debug("Get request for account '"+name+"'.");
}
//first attempt to get the cached account
final Account acct = super.getAccount(name);
if(acct != null) {
LOG.debug("Cached used.");
//XXX: synchronize with LDAP
return acct;
} else {
//if the account is not cached, we should try and find it in LDAP and cache it if it exists
try{
//do the lookup
final SearchResult ldapUser = findAccountByAccountName(ctx, name);
LOG.debug("LDAP search return '"+ldapUser+"'.");
if(ldapUser == null) {
return null;
} else {
//found a user from ldap so cache them and return
try {
final String primaryGroupSID = getPrimaryGroupSID(ldapUser);
final String primaryGroup = findGroupBySID(ctx, primaryGroupSID);
if (LOG.isDebugEnabled()) {
LOG.debug("LDAP search for primary group by SID '" + primaryGroupSID + "', found '" + primaryGroup + "'.");
}
if (primaryGroup == null) {
//or exception?
return null;
}
return createAccountInDatabase(ctx, name, ldapUser, ensureCase(primaryGroup));
//registerAccount(acct); //TODO do we need this
} catch(final AuthenticationException ae) {
LOG.error(ae.getMessage(), ae);
return null;
}
}
} catch(final NamingException ne) {
LOG.debug(ne.getMessage(), ne);
//LOG.error(new AuthenticationException(AuthenticationException.UNNOWN_EXCEPTION, ne.getMessage()));
return null;
} finally {
if(ctx != null){
LdapUtils.closeContext(ctx);
}
}
}
}
/**
* The binary data is in 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]...
*
* http://forums.oracle.com/forums/thread.jspa?threadID=1155740&tstart=0
*/
private static String decodeSID(final 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();
}
private String getPrimaryGroupSID(final SearchResult ldapUser) throws NamingException {
final LDAPSearchContext search = ensureContextFactory().getSearch();
final Object objSID = ldapUser.getAttributes().get(search.getSearchAccount().getSearchAttribute(LDAPSearchAttributeKey.OBJECT_SID)).get();
final String strObjectSid;
if (objSID instanceof String) {
strObjectSid = objSID.toString();
} else {
strObjectSid = decodeSID((byte[])objSID);
}
final String strPrimaryGroupID = (String)ldapUser.getAttributes().get(search.getSearchAccount().getSearchAttribute(LDAPSearchAttributeKey.PRIMARY_GROUP_ID)).get();
return strObjectSid.substring(0, strObjectSid.lastIndexOf('-') + 1) + strPrimaryGroupID;
}
public final synchronized Group getGroup(final Subject invokingUser, final DBBroker broker, String name) {
name = ensureCase(name);
final Group grp = getGroup(name);
if(grp != null) {
return grp;
} else {
//if the group is not cached, we should try and find it in LDAP and cache it if it exists
LdapContext ctx = null;
try {
ctx = getContext(invokingUser);
return getGroup(ctx, broker, name);
} catch(final NamingException ne) {
LOG.error(new AuthenticationException(AuthenticationException.UNNOWN_EXCEPTION, ne.getMessage()));
return null;
} finally {
if(ctx != null) {
LdapUtils.closeContext(ctx);
}
}
}
}
public final synchronized Group getGroup(final LdapContext ctx, final DBBroker broker, final String name) {
if(name == null){
return null;
}
final String gName = ensureCase(name);
final Group grp = getGroup(gName);
if(grp != null) {
return grp;
} else {
//if the group is not cached, we should try and find it in LDAP and cache it if it exists
try {
//do the lookup
final SearchResult ldapGroup = findGroupByGroupName(ctx, removeDomainPostfix(gName));
if(ldapGroup == null) {
return null;
} else {
//found a group from ldap so cache them and return
try {
return createGroupInDatabase(broker, gName);
//registerGroup(grp); //TODO do we need to do this?
} catch(final AuthenticationException ae) {
LOG.error(ae.getMessage(), ae);
return null;
}
}
} catch(final NamingException ne) {
LOG.error(new AuthenticationException(AuthenticationException.UNNOWN_EXCEPTION, ne.getMessage()));
return null;
} finally {
if(ctx != null) {
LdapUtils.closeContext(ctx);
}
}
}
}
private String addDomainPostfix(final String principalName) {
String name = principalName;
if(name.indexOf("@") == -1){
name += '@' + ensureContextFactory().getDomain();
}
return name;
}
private String removeDomainPostfix(final String principalName) {
String name = principalName;
if(name.indexOf('@') > -1 && name.endsWith(ensureContextFactory().getDomain())) {
name = name.substring(0, name.indexOf('@'));
}
return name;
}
private boolean checkAccountRestrictionList(final String accountname) {
final LDAPSearchContext search = ensureContextFactory().getSearch();
return checkPrincipalRestrictionList(accountname, search.getSearchAccount());
}
private boolean checkGroupRestrictionList(final String groupname) {
final LDAPSearchContext search = ensureContextFactory().getSearch();
return checkPrincipalRestrictionList(groupname, search.getSearchGroup());
}
private boolean checkPrincipalRestrictionList(final String principalName, final AbstractLDAPSearchPrincipal searchPrinciple) {
String name = ensureCase(principalName);
if(name.indexOf('@') > -1) {
name = name.substring(0, name.indexOf('@'));
}
List<String> blackList = null;
if(searchPrinciple.getBlackList() != null) {
blackList = searchPrinciple.getBlackList().getPrincipals();
}
List<String> whiteList = null;
if(searchPrinciple.getWhiteList() != null) {
whiteList = searchPrinciple.getWhiteList().getPrincipals();
}
if(blackList != null) {
for(String blackEntry : blackList) {
if(ensureCase(blackEntry).equals(name)) {
return false;
}
}
}
if(whiteList != null && whiteList.size() > 0) {
for(String whiteEntry : whiteList) {
if(ensureCase(whiteEntry).equals(name)) {
return true;
}
}
return false;
}
return true;
}
private SearchResult findAccountByAccountName(final DirContext ctx, final String accountName) throws NamingException {
if(!checkAccountRestrictionList(accountName)) {
return null;
}
final String userName = removeDomainPostfix(accountName);
final LDAPSearchContext search = ensureContextFactory().getSearch();
final SearchAttribute sa = new SearchAttribute(search.getSearchAccount().getSearchAttribute(LDAPSearchAttributeKey.NAME), userName);
final String searchFilter = buildSearchFilter(search.getSearchAccount().getSearchFilterPrefix(), sa);
final SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
final NamingEnumeration<SearchResult> results = ctx.search(search.getBase(), searchFilter, searchControls);
SearchResult searchResult = null;
if(results.hasMoreElements()) {
searchResult = (SearchResult) results.nextElement();
//make sure there is not another item available, there should be only 1 match
if(results.hasMoreElements()) {
LOG.error("Matched multiple users for the accountName: " + accountName);
}
}
return searchResult;
}
private String findGroupBySID(final DirContext ctx, final String sid) throws NamingException {
final LDAPSearchContext search = ensureContextFactory().getSearch();
final SearchAttribute sa = new SearchAttribute(search.getSearchGroup().getSearchAttribute(LDAPSearchAttributeKey.OBJECT_SID), sid);
final String searchFilter = buildSearchFilter(search.getSearchGroup().getSearchFilterPrefix(), sa);
final SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
final NamingEnumeration<SearchResult> results = ctx.search(search.getAbsoluteBase(), searchFilter, searchControls);
if(results.hasMoreElements()) {
SearchResult searchResult = (SearchResult) results.nextElement();
//make sure there is not another item available, there should be only 1 match
if(results.hasMoreElements()) {
LOG.error("Matched multiple groups for the group with SID: " + sid);
return null;
} else {
return addDomainPostfix((String)searchResult.getAttributes().get(search.getSearchGroup().getSearchAttribute(LDAPSearchAttributeKey.NAME)).get());
}
}
LOG.error("Matched no group with SID: " + sid);
return null;
}
private SearchResult findGroupByGroupName(final DirContext ctx, final String groupName) throws NamingException {
if(!checkGroupRestrictionList(groupName)) {
return null;
}
final LDAPSearchContext search = ensureContextFactory().getSearch();
final SearchAttribute sa = new SearchAttribute(search.getSearchGroup().getSearchAttribute(LDAPSearchAttributeKey.NAME), groupName);
final String searchFilter = buildSearchFilter(search.getSearchGroup().getSearchFilterPrefix(), sa);
final SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
final NamingEnumeration<SearchResult> results = ctx.search(search.getAbsoluteBase(), searchFilter, searchControls);
if(results.hasMoreElements()) {
final SearchResult searchResult = (SearchResult) results.nextElement();
//make sure there is not another item available, there should be only 1 match
if(results.hasMoreElements()) {
LOG.error("Matched multiple groups for the groupName: " + groupName);
return null;
} else {
return searchResult;
}
}
LOG.error("Matched no groups for the groupName: " + groupName);
return null;
}
// configurable methods
@Override
public boolean isConfigured() {
return (configuration != null);
}
@Override
public Configuration getConfiguration() {
return configuration;
}
@Override
public boolean updateAccount(final Account account) throws PermissionDeniedException, EXistException {
return super.updateAccount(account);
}
@Override
public boolean deleteAccount(final Account account) throws PermissionDeniedException, EXistException {
// TODO we dont support writting to LDAP
//XXX: delete local cache?
return false;
}
@Override
public boolean updateGroup(final Group group) throws PermissionDeniedException, EXistException {
return super.updateGroup(group);
}
@Override
public boolean deleteGroup(final Group group) throws PermissionDeniedException, EXistException {
//XXX: delete local cache?
return false;
}
private class SearchAttribute {
private final String name;
private final String value;
public SearchAttribute(final String name, final String value) {
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public String getValue() {
return value;
}
}
private String buildSearchFilter(final String searchPrefix, final SearchAttribute sa) {
final StringBuilder builder = new StringBuilder();
builder.append("(");
builder.append(buildSearchCriteria(searchPrefix));
if(sa.getName() != null && sa.getValue() != null) {
builder.append("(");
builder.append(sa.getName());
builder.append("=");
builder.append(sa.getValue());
builder.append(")");
}
builder.append(")");
return builder.toString();
}
private String buildSearchFilterUnion(final String searchPrefix, final List<SearchAttribute> searchAttributes) {
final StringBuilder builder = new StringBuilder();
builder.append("(");
builder.append(buildSearchCriteria(searchPrefix));
if(!searchAttributes.isEmpty()) {
builder.append("(|");
for(SearchAttribute sa : searchAttributes) {
builder.append("(");
builder.append(sa.getName());
builder.append("=");
builder.append(sa.getValue());
builder.append(")");
}
builder.append(")");
}
builder.append(")");
return builder.toString();
}
private String buildSearchCriteria(String searchPrefix) {
return "&(" + searchPrefix + ")";
}
@Override
public List<String> findUsernamesWhereNameStarts(String startsWith) {
startsWith = ensureCase(startsWith);
final List<String> usernames = new ArrayList<String>();
LdapContext ctx = null;
try {
ctx = getContext(getSecurityManager().getCurrentSubject());
final LDAPSearchContext search = ensureContextFactory().getSearch();
final SearchAttribute sa = new SearchAttribute(search.getSearchAccount().getMetadataSearchAttribute(AXSchemaType.FULLNAME), startsWith + "*");
final String searchFilter = buildSearchFilter(search.getSearchAccount().getSearchFilterPrefix(), sa);
final SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setReturningAttributes(new String[] { search.getSearchAccount().getSearchAttribute(LDAPSearchAttributeKey.NAME) });
final NamingEnumeration<SearchResult> results = ctx.search(search.getBase(), searchFilter, searchControls);
while(results.hasMoreElements()) {
final SearchResult searchResult = (SearchResult) results.nextElement();
final String username = ensureCase(addDomainPostfix((String)searchResult.getAttributes().get(search.getSearchAccount().getSearchAttribute(LDAPSearchAttributeKey.NAME)).get()));
if(checkAccountRestrictionList(username)) {
usernames.add(username);
}
}
} catch(final NamingException ne) {
LOG.error(new AuthenticationException(AuthenticationException.UNNOWN_EXCEPTION, ne.getMessage()));
} finally {
if(ctx != null) {
LdapUtils.closeContext(ctx);
}
}
return usernames;
}
@Override
public List<String> findUsernamesWhereNamePartStarts(final String startsWith) {
final String sWith = ensureCase(startsWith);
final List<String> usernames = new ArrayList<String>();
LdapContext ctx = null;
try {
ctx = getContext(getSecurityManager().getCurrentSubject());
final LDAPSearchContext search = ensureContextFactory().getSearch();
final SearchAttribute firstNameSa = new SearchAttribute(search.getSearchAccount().getMetadataSearchAttribute(AXSchemaType.FIRSTNAME), sWith + "*");
final SearchAttribute lastNameSa = new SearchAttribute(search.getSearchAccount().getMetadataSearchAttribute(AXSchemaType.LASTNAME), sWith + "*");
final List<SearchAttribute> sas = new ArrayList<SearchAttribute>();
sas.add(firstNameSa);
sas.add(lastNameSa);
final String searchFilter = buildSearchFilterUnion(search.getSearchAccount().getSearchFilterPrefix(), sas);
final SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setReturningAttributes(new String[] { search.getSearchAccount().getSearchAttribute(LDAPSearchAttributeKey.NAME) });
final NamingEnumeration<SearchResult> results = ctx.search(search.getBase(), searchFilter, searchControls);
while(results.hasMoreElements()) {
final SearchResult searchResult = (SearchResult) results.nextElement();
final String username = ensureCase(addDomainPostfix((String)searchResult.getAttributes().get(search.getSearchAccount().getSearchAttribute(LDAPSearchAttributeKey.NAME)).get()));
if(checkAccountRestrictionList(username)) {
usernames.add(username);
}
}
} catch(final NamingException ne) {
LOG.error(new AuthenticationException(AuthenticationException.UNNOWN_EXCEPTION, ne.getMessage()));
} finally {
if(ctx != null) {
LdapUtils.closeContext(ctx);
}
}
return usernames;
}
@Override
public List<String> findUsernamesWhereUsernameStarts(final String startsWith) {
final String sWith = ensureCase(startsWith);
final List<String> usernames = new ArrayList<String>();
LdapContext ctx = null;
try {
ctx = getContext(getSecurityManager().getCurrentSubject());
final LDAPSearchContext search = ensureContextFactory().getSearch();
final SearchAttribute sa = new SearchAttribute(search.getSearchAccount().getSearchAttribute(LDAPSearchAttributeKey.NAME), sWith + "*");
final String searchFilter = buildSearchFilter(search.getSearchAccount().getSearchFilterPrefix(), sa);
final SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setReturningAttributes(new String[] { search.getSearchAccount().getSearchAttribute(LDAPSearchAttributeKey.NAME) });
final NamingEnumeration<SearchResult> results = ctx.search(search.getBase(), searchFilter, searchControls);
while(results.hasMoreElements()) {
final SearchResult searchResult = (SearchResult) results.nextElement();
final String username = ensureCase(addDomainPostfix((String)searchResult.getAttributes().get(search.getSearchAccount().getSearchAttribute(LDAPSearchAttributeKey.NAME)).get()));
if(checkAccountRestrictionList(username)) {
usernames.add(username);
}
}
} catch(final NamingException ne) {
LOG.error(new AuthenticationException(AuthenticationException.UNNOWN_EXCEPTION, ne.getMessage()));
} finally {
if(ctx != null) {
LdapUtils.closeContext(ctx);
}
}
return usernames;
}
private List<String> findGroupnamesForUserDistinguishedName(final LdapContext ctx, final String userDistinguishedName) {
final List<String> groupnames = new ArrayList<String>();
try {
final LDAPSearchContext search = ensureContextFactory().getSearch();
final SearchAttribute sa = new SearchAttribute(search.getSearchGroup().getSearchAttribute(LDAPSearchAttributeKey.MEMBER), userDistinguishedName);
final String searchFilter = buildSearchFilter(search.getSearchGroup().getSearchFilterPrefix(), sa);
final SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setReturningAttributes(new String[] { search.getSearchGroup().getSearchAttribute(LDAPSearchAttributeKey.NAME) });
final NamingEnumeration<SearchResult> results = ctx.search(search.getAbsoluteBase(), searchFilter, searchControls);
while(results.hasMoreElements()) {
final SearchResult searchResult = (SearchResult) results.nextElement();
final String groupname = ensureCase(addDomainPostfix((String)searchResult.getAttributes().get(search.getSearchGroup().getSearchAttribute(LDAPSearchAttributeKey.NAME)).get()));
if(checkGroupRestrictionList(groupname)) {
groupnames.add(groupname);
}
}
} catch(final NamingException ne) {
LOG.error(new AuthenticationException(AuthenticationException.UNNOWN_EXCEPTION, ne.getMessage()));
} finally {
if(ctx != null) {
LdapUtils.closeContext(ctx);
}
}
return groupnames;
}
@Override
public List<String> findGroupnamesWhereGroupnameStarts(final String startsWith) {
final String sWith = ensureCase(startsWith);
final List<String> groupnames = new ArrayList<String>();
LdapContext ctx = null;
try {
ctx = getContext(getSecurityManager().getCurrentSubject());
final LDAPSearchContext search = ensureContextFactory().getSearch();
final SearchAttribute sa = new SearchAttribute(search.getSearchGroup().getSearchAttribute(LDAPSearchAttributeKey.NAME), sWith + "*");
final String searchFilter = buildSearchFilter(search.getSearchGroup().getSearchFilterPrefix(), sa);
final SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setReturningAttributes(new String[] { search.getSearchGroup().getSearchAttribute(LDAPSearchAttributeKey.NAME) });
final NamingEnumeration<SearchResult> results = ctx.search(search.getBase(), searchFilter, searchControls);
while(results.hasMoreElements()) {
final SearchResult searchResult = (SearchResult) results.nextElement();
final String groupname = ensureCase(addDomainPostfix((String)searchResult.getAttributes().get(search.getSearchGroup().getSearchAttribute(LDAPSearchAttributeKey.NAME)).get()));
if(checkGroupRestrictionList(groupname)) {
groupnames.add(groupname);
}
}
} catch(final NamingException ne) {
LOG.error(new AuthenticationException(AuthenticationException.UNNOWN_EXCEPTION, ne.getMessage()));
} finally {
if(ctx != null) {
LdapUtils.closeContext(ctx);
}
}
return groupnames;
}
@Override
public List<String> findGroupnamesWhereGroupnameContains(final String fragment) {
final String part = ensureCase(fragment);
final List<String> groupnames = new ArrayList<String>();
LdapContext ctx = null;
try {
ctx = getContext(getSecurityManager().getCurrentSubject());
final LDAPSearchContext search = ensureContextFactory().getSearch();
final SearchAttribute sa = new SearchAttribute(search.getSearchGroup().getSearchAttribute(LDAPSearchAttributeKey.NAME), "*" + part + "*");
final String searchFilter = buildSearchFilter(search.getSearchGroup().getSearchFilterPrefix(), sa);
final SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setReturningAttributes(new String[] { search.getSearchGroup().getSearchAttribute(LDAPSearchAttributeKey.NAME) });
final NamingEnumeration<SearchResult> results = ctx.search(search.getBase(), searchFilter, searchControls);
while(results.hasMoreElements()) {
final SearchResult searchResult = (SearchResult) results.nextElement();
final String groupname = ensureCase(addDomainPostfix((String)searchResult.getAttributes().get(search.getSearchGroup().getSearchAttribute(LDAPSearchAttributeKey.NAME)).get()));
if(checkGroupRestrictionList(groupname)) {
groupnames.add(groupname);
}
}
} catch(final NamingException ne) {
LOG.error(ne);
} finally {
if(ctx != null) {
LdapUtils.closeContext(ctx);
}
}
return groupnames;
}
@Override
public List<String> findAllGroupNames() {
final List<String> groupnames = new ArrayList<String>();
LdapContext ctx = null;
try {
ctx = getContext(getSecurityManager().getCurrentSubject());
final LDAPSearchContext search = ensureContextFactory().getSearch();
final SearchAttribute sa = new SearchAttribute(null, null);
final String searchFilter = buildSearchFilter(search.getSearchGroup().getSearchFilterPrefix(), sa);
final SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setReturningAttributes(new String[] { search.getSearchGroup().getSearchAttribute(LDAPSearchAttributeKey.NAME) });
final NamingEnumeration<SearchResult> results = ctx.search(search.getBase(), searchFilter, searchControls);
while(results.hasMoreElements()) {
final SearchResult searchResult = (SearchResult) results.nextElement();
final String groupname = ensureCase(addDomainPostfix((String)searchResult.getAttributes().get(search.getSearchGroup().getSearchAttribute(LDAPSearchAttributeKey.NAME)).get()));
if(checkGroupRestrictionList(groupname)) {
groupnames.add(groupname);
}
}
} catch(final NamingException ne) {
LOG.error(new AuthenticationException(AuthenticationException.UNNOWN_EXCEPTION, ne.getMessage()));
} finally {
if(ctx != null) {
LdapUtils.closeContext(ctx);
}
}
return groupnames;
}
@Override
public List<String> findAllUserNames() {
final List<String> usernames = new ArrayList<String>();
LdapContext ctx = null;
try {
ctx = getContext(getSecurityManager().getCurrentSubject());
final LDAPSearchContext search = ensureContextFactory().getSearch();
final SearchAttribute sa = new SearchAttribute(null, null);
final String searchFilter = buildSearchFilter(search.getSearchAccount().getSearchFilterPrefix(), sa);
final SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setReturningAttributes(new String[] { search.getSearchAccount().getSearchAttribute(LDAPSearchAttributeKey.NAME) });
final NamingEnumeration<SearchResult> results = ctx.search(search.getBase(), searchFilter, searchControls);
while(results.hasMoreElements()) {
final SearchResult searchResult = (SearchResult) results.nextElement();
final String accountname = ensureCase(addDomainPostfix((String)searchResult.getAttributes().get(search.getSearchAccount().getSearchAttribute(LDAPSearchAttributeKey.NAME)).get()));
if(checkAccountRestrictionList(accountname)) {
usernames.add(accountname);
}
}
} catch(final NamingException ne) {
LOG.error(new AuthenticationException(AuthenticationException.UNNOWN_EXCEPTION, ne.getMessage()));
} finally {
if(ctx != null) {
LdapUtils.closeContext(ctx);
}
}
return usernames;
}
@Override
public List<String> findAllGroupMembers(final String groupName) {
final String name = ensureCase(groupName);
final List<String> groupMembers = new ArrayList<String>();
if(!checkGroupRestrictionList(name)) {
return groupMembers;
}
LdapContext ctx = null;
try {
ctx = getContext(getSecurityManager().getCurrentSubject());
//find the dn of the group
SearchResult searchResult = findGroupByGroupName(ctx, removeDomainPostfix(name));
final LDAPSearchContext search = ensureContextFactory().getSearch();
final String dnGroup = (String)searchResult.getAttributes().get(search.getSearchGroup().getSearchAttribute(LDAPSearchAttributeKey.DN)).get();
//find all accounts that are a member of the group
final SearchAttribute sa = new SearchAttribute(search.getSearchAccount().getSearchAttribute(LDAPSearchAttributeKey.MEMBER_OF), dnGroup);
final String searchFilter = buildSearchFilter(search.getSearchAccount().getSearchFilterPrefix(), sa);
final SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setReturningAttributes(new String[] { search.getSearchAccount().getSearchAttribute(LDAPSearchAttributeKey.NAME) });
final NamingEnumeration<SearchResult> results = ctx.search(search.getBase(), searchFilter, searchControls);
while(results.hasMoreElements()) {
searchResult = (SearchResult) results.nextElement();
final String member = ensureCase(addDomainPostfix((String)searchResult.getAttributes().get(search.getSearchAccount().getSearchAttribute(LDAPSearchAttributeKey.NAME)).get()));
if(checkAccountRestrictionList(member)) {
groupMembers.add(member);
}
}
} catch(final NamingException ne) {
LOG.error(new AuthenticationException(AuthenticationException.UNNOWN_EXCEPTION, ne.getMessage()));
} finally {
if(ctx != null) {
LdapUtils.closeContext(ctx);
}
}
return groupMembers;
}
private final class AuthenticatedLdapSubjectAccreditedImpl extends SubjectAccreditedImpl {
private final String authenticatedCredentials;
private AuthenticatedLdapSubjectAccreditedImpl(final AbstractAccount account, final LdapContext ctx, final String authenticatedCredentials) {
super(account, ctx);
this.authenticatedCredentials = authenticatedCredentials;
}
private String getAuthenticatedCredentials() {
return authenticatedCredentials;
}
}
}