org.wildfly.security.auth.realm.ldap.LdapSecurityRealm Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote Jakarta Enterprise Beans and Jakarta Messaging, including
all dependencies. It is intended for use by those not using maven, maven users should just import the Jakarta Enterprise Beans and
Jakarta Messaging BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
/*
* JBoss, Home of Professional Open Source
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* 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 org.wildfly.security.auth.realm.ldap;
import static org.wildfly.security.auth.realm.ldap.ElytronMessages.log;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.security.Provider;
import java.security.spec.AlgorithmParameterSpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import javax.naming.Binding;
import javax.naming.InvalidNameException;
import javax.naming.NameNotFoundException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.ReferralException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.event.EventContext;
import javax.naming.event.NamespaceChangeListener;
import javax.naming.event.NamingEvent;
import javax.naming.event.NamingExceptionEvent;
import javax.naming.event.ObjectChangeListener;
import javax.naming.ldap.Control;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.PagedResultsControl;
import javax.naming.ldap.PagedResultsResponseControl;
import javax.naming.ldap.Rdn;
import org.wildfly.common.Assert;
import org.wildfly.common.function.ExceptionSupplier;
import org.wildfly.security.auth.principal.NamePrincipal;
import org.wildfly.security.auth.realm.CacheableSecurityRealm;
import org.wildfly.security.auth.realm.IdentitySharedExclusiveLock;
import org.wildfly.security.auth.realm.IdentitySharedExclusiveLock.IdentityLock;
import org.wildfly.security.auth.server.ModifiableRealmIdentityIterator;
import org.wildfly.security.auth.server.ModifiableRealmIdentity;
import org.wildfly.security.auth.server.ModifiableSecurityRealm;
import org.wildfly.security.auth.server.NameRewriter;
import org.wildfly.security.auth.server.RealmIdentity;
import org.wildfly.security.auth.server.RealmUnavailableException;
import org.wildfly.security.auth.SupportLevel;
import org.wildfly.security.authz.AuthorizationIdentity;
import org.wildfly.security.authz.MapAttributes;
import org.wildfly.security.credential.AlgorithmCredential;
import org.wildfly.security.credential.Credential;
import org.wildfly.security.evidence.AlgorithmEvidence;
import org.wildfly.security.evidence.Evidence;
import org.wildfly.security.password.spec.Encoding;
/**
* Security realm implementation backed by LDAP.
*
* @author Darran Lofthouse
* @author Jan Kalina
*/
class LdapSecurityRealm implements ModifiableSecurityRealm, CacheableSecurityRealm {
private final String ENV_BINARY_ATTRIBUTES = "java.naming.ldap.attributes.binary";
private final Supplier providers;
private final ExceptionSupplier dirContextSupplier;
private final NameRewriter nameRewriter;
private final IdentityMapping identityMapping;
private final int pageSize;
private final Charset hashCharset;
private final Encoding hashEncoding;
private final List credentialLoaders;
private final List credentialPersisters;
private final List evidenceVerifiers;
private final ConcurrentHashMap realmIdentityLocks = new ConcurrentHashMap<>();
private Set> listenersPendingRegistration = new LinkedHashSet>();
LdapSecurityRealm(final Supplier providers,
final ExceptionSupplier dirContextSupplier,
final NameRewriter nameRewriter,
final IdentityMapping identityMapping,
final List credentialLoaders,
final List credentialPersisters,
final List evidenceVerifiers,
final int pageSize,
final Charset hashCharset,
final Encoding hashEncoding) {
this.providers = providers;
this.dirContextSupplier = dirContextSupplier;
this.nameRewriter = nameRewriter;
this.identityMapping = identityMapping;
this.pageSize = pageSize;
this.hashCharset = hashCharset != null ? hashCharset : StandardCharsets.UTF_8;
this.hashEncoding = hashEncoding != null ? hashEncoding : Encoding.BASE64;
this.credentialLoaders = credentialLoaders;
this.credentialPersisters = credentialPersisters;
this.evidenceVerifiers = evidenceVerifiers;
}
@Override
public RealmIdentity getRealmIdentity(final Principal principal) {
return getRealmIdentity(principal, false);
}
@Override
public ModifiableRealmIdentity getRealmIdentityForUpdate(final Principal principal) {
return getRealmIdentity(principal, true);
}
@Override
public void registerIdentityChangeListener(Consumer listener) {
synchronized (this.listenersPendingRegistration) {
DirContext dirContext = null;
try {
dirContext = obtainContext();
registerIdentityChangeListener(dirContext, listener);
} catch (Exception cause) {
// either connection died or realm not available during boot
// we need to wait, lets store
listenersPendingRegistration.add(listener);
log.ldapRealmDeferRegistration();
if (log.isDebugEnabled()) {
log.debug("Listener registration failure: ", cause);
}
} finally {
if (dirContext != null) {
closeContext(dirContext);
}
}
}
}
private void registerIdentityChangeListener(final DirContext dirContext, final Consumer listener) throws NamingException {
EventContext eventContext = (EventContext) dirContext.lookup("");
eventContext.addNamingListener("", EventContext.SUBTREE_SCOPE, new ServerNotificationListener(listener));
}
private ModifiableRealmIdentity getRealmIdentity(final Principal principal, final boolean exclusive) {
if (! (principal instanceof NamePrincipal)) {
return ModifiableRealmIdentity.NON_EXISTENT;
}
String name = nameRewriter.rewriteName(principal.getName());
if (name == null) {
throw log.invalidName();
}
// Acquire the appropriate lock for the realm identity
log.debugf("Obtaining lock for identity [%s]...", name);
IdentitySharedExclusiveLock realmIdentityLock = getRealmIdentityLockForName(name);
IdentityLock lock;
if (exclusive) {
lock = realmIdentityLock.lockExclusive();
} else {
lock = realmIdentityLock.lockShared();
}
log.debugf("Obtained lock for identity [%s].", name);
return new LdapRealmIdentity(name, lock, hashCharset, hashEncoding);
}
private DirContext obtainContext() throws RealmUnavailableException {
try {
DirContext ctx = dirContextSupplier.get();
synchronized (this.listenersPendingRegistration) {
// we got ctx, this means connection is up
// add & remove in case we are ok, take into account network failure.
final Iterator> it = this.listenersPendingRegistration.iterator();
while(it.hasNext()) {
registerIdentityChangeListener(ctx, it.next());
it.remove();
}
return ctx;
}
} catch (NamingException e) {
throw log.ldapRealmFailedToObtainContext(e);
}
}
private void closeContext(DirContext dirContext) {
try {
dirContext.close();
} catch (NamingException e) {
log.debug("LdapSecurityRealm failed to close DirContext", e);
}
}
@Override
public ModifiableRealmIdentityIterator getRealmIdentityIterator() throws RealmUnavailableException {
if (identityMapping.iteratorFilter == null) {
throw log.ldapRealmNotConfiguredToSupportIteratingOverIdentities();
}
final DirContext dirContext = obtainContext();
final Stream resultStream;
final LdapSearch ldapSearch = new LdapSearch(identityMapping.searchDn, identityMapping.searchRecursive, pageSize, identityMapping.iteratorFilter);
ldapSearch.setReturningAttributes(Collections.singleton(identityMapping.rdnIdentifier));
resultStream = ldapSearch.search(dirContext);
Iterator iterator = resultStream.map(entry -> {
try {
return (String) entry.getAttributes().get(identityMapping.rdnIdentifier).get();
} catch (NamingException e) {
throw new RuntimeException(log.ldapRealmIdentitySearchFailed(e));
}
}).distinct().iterator(); // distinct to prevent deadlock on identity locking when one identity found twice
return new ModifiableRealmIdentityIterator() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public ModifiableRealmIdentity next() {
String name = iterator.next();
return getRealmIdentityForUpdate(new NamePrincipal(name));
}
@Override
public void close() throws RealmUnavailableException {
resultStream.close();
closeContext(dirContext);
}
};
}
@Override
public SupportLevel getCredentialAcquireSupport(final Class extends Credential> credentialType, final String algorithmName, final AlgorithmParameterSpec parameterSpec) throws RealmUnavailableException {
Assert.checkNotNullParam("credentialType", credentialType);
SupportLevel response = SupportLevel.UNSUPPORTED;
for (CredentialLoader loader : credentialLoaders) {
SupportLevel support = loader.getCredentialAcquireSupport(credentialType, algorithmName, parameterSpec);
if (support.isDefinitelySupported()) {
// One claiming it is definitely supported is enough!
return support;
}
if (response.compareTo(support) < 0) {
response = support;
}
}
return response;
}
@Override
public SupportLevel getEvidenceVerifySupport(final Class extends Evidence> evidenceType, final String algorithmName) throws RealmUnavailableException {
Assert.checkNotNullParam("evidenceType", evidenceType);
SupportLevel response = SupportLevel.UNSUPPORTED;
for (EvidenceVerifier verifier : evidenceVerifiers) {
SupportLevel support = verifier.getEvidenceVerifySupport(evidenceType, algorithmName);
if (support.isDefinitelySupported()) {
// One claiming it is definitely supported is enough!
return support;
}
if (response.compareTo(support) < 0) {
response = support;
}
}
return response;
}
private IdentitySharedExclusiveLock getRealmIdentityLockForName(final String name) {
IdentitySharedExclusiveLock realmIdentityLock = realmIdentityLocks.get(name);
if (realmIdentityLock == null) {
final IdentitySharedExclusiveLock newRealmIdentityLock = new IdentitySharedExclusiveLock();
realmIdentityLock = realmIdentityLocks.putIfAbsent(name, newRealmIdentityLock);
if (realmIdentityLock == null) {
realmIdentityLock = newRealmIdentityLock;
}
}
return realmIdentityLock;
}
private class LdapRealmIdentity implements ModifiableRealmIdentity {
private final String name;
private IdentityLock lock;
private final Charset hashCharset;
private final Encoding hashEncoding;
LdapRealmIdentity(final String name, final IdentityLock lock, final Charset hashCharset, final Encoding hashEncoding) {
this.name = name;
this.lock = lock;
this.hashCharset = hashCharset;
this.hashEncoding = hashEncoding;
}
public Principal getRealmIdentityPrincipal() {
return new NamePrincipal(name);
}
public Charset getHashCharset() {
return this.hashCharset;
}
@Override
public SupportLevel getCredentialAcquireSupport(final Class extends Credential> credentialType, final String algorithmName, final AlgorithmParameterSpec parameterSpec) throws RealmUnavailableException {
Assert.checkNotNullParam("credentialType", credentialType);
if (LdapSecurityRealm.this.getCredentialAcquireSupport(credentialType, algorithmName, parameterSpec) == SupportLevel.UNSUPPORTED) {
// If not supported in general then definitely not supported for a specific principal.
return SupportLevel.UNSUPPORTED;
}
DirContext dirContext = obtainContext();
try {
Set attributes = new HashSet<>();
Set binaryAttributes = new HashSet<>();
for (CredentialLoader loader : credentialLoaders) {
loader.addRequiredIdentityAttributes(attributes);
loader.addBinaryIdentityAttributes(binaryAttributes);
}
LdapIdentity identity = getIdentity(dirContext, attributes, binaryAttributes);
if (identity == null) {
return SupportLevel.UNSUPPORTED;
}
SupportLevel support = SupportLevel.UNSUPPORTED;
for (CredentialLoader loader : credentialLoaders) {
if (loader.getCredentialAcquireSupport(credentialType, algorithmName, parameterSpec).mayBeSupported()) {
IdentityCredentialLoader icl = loader.forIdentity(identity.getDirContext(), identity.getDistinguishedName(), identity.getEntry().getAttributes(), hashEncoding);
SupportLevel temp = icl.getCredentialAcquireSupport(credentialType, algorithmName, parameterSpec, providers);
if (temp != null && temp.isDefinitelySupported()) {
// As soon as one claims definite support we know it is supported.
return temp;
}
if (temp != null && support.compareTo(temp) < 0) {
support = temp;
}
}
}
return support;
} finally {
closeContext(dirContext);
}
}
@Override
public C getCredential(final Class credentialType) throws RealmUnavailableException {
return getCredential(credentialType, null);
}
@Override
public C getCredential(final Class credentialType, final String algorithmName) throws RealmUnavailableException {
return getCredential(credentialType, algorithmName, null);
}
@Override
public C getCredential(final Class credentialType, final String algorithmName, final AlgorithmParameterSpec parameterSpec) throws RealmUnavailableException {
Assert.checkNotNullParam("credentialType", credentialType);
if (LdapSecurityRealm.this.getCredentialAcquireSupport(credentialType, algorithmName, parameterSpec) == SupportLevel.UNSUPPORTED) {
// If not supported in general then definitely not supported for a specific principal.
return null;
}
DirContext dirContext = obtainContext();
try {
Set attributes = new HashSet<>();
Set binaryAttributes = new HashSet<>();
for (CredentialLoader loader : credentialLoaders) {
loader.addRequiredIdentityAttributes(attributes);
loader.addBinaryIdentityAttributes(binaryAttributes);
}
LdapIdentity identity = getIdentity(dirContext, attributes, binaryAttributes);
if (identity == null) {
return null;
}
for (CredentialLoader loader : credentialLoaders) {
if (loader.getCredentialAcquireSupport(credentialType, algorithmName, parameterSpec).mayBeSupported()) {
IdentityCredentialLoader icl = loader.forIdentity(identity.getDirContext(), identity.getDistinguishedName(), identity.getEntry().getAttributes(), hashEncoding);
Credential credential = icl.getCredential(credentialType, algorithmName, parameterSpec, providers);
if (credentialType.isInstance(credential)) {
return credentialType.cast(credential);
}
}
}
} finally {
closeContext(dirContext);
}
return null;
}
@Override
public void setCredentials(final Collection extends Credential> credentials) throws RealmUnavailableException {
Assert.checkNotNullParam("credentials", credentials);
DirContext dirContext = obtainContext();
try {
Set attributes = new HashSet<>();
Set binaryAttributes = new HashSet<>();
for (CredentialPersister persister : credentialPersisters) {
persister.addRequiredIdentityAttributes(attributes);
persister.addBinaryIdentityAttributes(binaryAttributes);
}
LdapIdentity identity = getIdentity(dirContext, attributes, binaryAttributes);
if (identity == null) {
throw log.ldapRealmIdentityNotExists(name);
}
// verify support
for (Credential credential : credentials) {
final Class extends Credential> credentialType = credential.getClass();
final String algorithmName = credential instanceof AlgorithmCredential ? ((AlgorithmCredential) credential).getAlgorithm() : null;
final AlgorithmParameterSpec parameterSpec = credential instanceof AlgorithmCredential ? ((AlgorithmCredential) credential).getParameters() : null;
boolean supported = false;
for (CredentialPersister persister : credentialPersisters) {
IdentityCredentialPersister icp = persister.forIdentity(identity.getDirContext(), identity.getDistinguishedName(), identity.getEntry().getAttributes());
if (icp.getCredentialPersistSupport(credentialType, algorithmName, parameterSpec)) {
supported = true;
}
}
if (!supported) {
throw log.ldapRealmsPersisterNotSupported();
}
}
// clear
for (CredentialPersister persister : credentialPersisters) {
IdentityCredentialPersister icp = persister.forIdentity(identity.getDirContext(), identity.getDistinguishedName(), identity.getEntry().getAttributes());
icp.clearCredentials();
}
// set
for (Credential credential : credentials) {
final Class extends Credential> credentialType = credential.getClass();
final String algorithmName = credential instanceof AlgorithmCredential ? ((AlgorithmCredential) credential).getAlgorithm() : null;
final AlgorithmParameterSpec parameterSpec = credential instanceof AlgorithmCredential ? ((AlgorithmCredential) credential).getParameters() : null;
for (CredentialPersister persister : credentialPersisters) {
IdentityCredentialPersister icp = persister.forIdentity(identity.getDirContext(), identity.getDistinguishedName(), identity.getEntry().getAttributes());
if (icp.getCredentialPersistSupport(credentialType, algorithmName, parameterSpec)) {
icp.persistCredential(credential);
// next credential
break;
}
}
}
} finally {
closeContext(dirContext);
}
}
@Override
public void dispose() {
// Release the lock for this realm identity
IdentityLock identityLock = lock;
lock = null;
if (identityLock != null) {
identityLock.release();
}
}
@Override
public AuthorizationIdentity getAuthorizationIdentity() throws RealmUnavailableException {
return AuthorizationIdentity.basicIdentity(getAttributes());
}
@Override
public org.wildfly.security.authz.Attributes getAttributes() throws RealmUnavailableException {
DirContext context = obtainContext();
try {
LdapIdentity identity = getIdentity(context,
identityMapping.attributes.stream()
.map(AttributeMapping::getIdentityLdapName)
.filter(Objects::nonNull)
.collect(Collectors.toSet()),
null);
SearchResult entry = identity != null ? identity.getEntry() : null;
DirContext identityContext = identity != null ? identity.getDirContext() : null;
MapAttributes attributes = new MapAttributes();
attributes.addAll(extractSimpleAttributes(entry));
attributes.addAll(extractFilteredAttributes(entry, context, identityContext));
if (log.isDebugEnabled()) {
log.debugf("Obtaining authorization identity attributes for principal [%s]:", name);
if (attributes.isEmpty()) {
log.debugf("Identity [%s] does not have any attributes.", name);
} else {
log.debugf("Identity [%s] attributes are:", name);
attributes.keySet().forEach(key -> {
org.wildfly.security.authz.Attributes.Entry values = attributes.get(key);
values.forEach(value -> log.debugf(" Attribute [%s] value [%s].", key, value));
});
}
}
return attributes.asReadOnly();
} finally {
closeContext(context);
}
}
@Override
public SupportLevel getEvidenceVerifySupport(final Class extends Evidence> evidenceType, final String algorithmName) throws RealmUnavailableException {
Assert.checkNotNullParam("evidenceType", evidenceType);
DirContext dirContext = obtainContext();
try {
Set attributes = new HashSet<>();
Set binaryAttributes = new HashSet<>();
for (EvidenceVerifier verifier : evidenceVerifiers) {
verifier.addRequiredIdentityAttributes(attributes);
verifier.addBinaryIdentityAttributes(binaryAttributes);
}
LdapIdentity identity = getIdentity(dirContext, attributes, binaryAttributes);
if (identity == null) {
return SupportLevel.UNSUPPORTED;
}
SupportLevel response = SupportLevel.UNSUPPORTED;
for (EvidenceVerifier verifier : evidenceVerifiers) {
if (verifier.getEvidenceVerifySupport(evidenceType, algorithmName).mayBeSupported()) {
final IdentityEvidenceVerifier iev = verifier.forIdentity(identity.getDirContext(), identity.getDistinguishedName(), identity.getUrl(), identity.getEntry().getAttributes(), hashEncoding);
final SupportLevel support = iev.getEvidenceVerifySupport(evidenceType, algorithmName, providers);
if (support != null && support.isDefinitelySupported()) {
// As soon as one claims definite support we know it is supported.
return support;
}
if (support != null && response.compareTo(support) < 0) {
response = support;
}
}
}
return response;
} finally {
closeContext(dirContext);
}
}
@Override
public boolean verifyEvidence(final Evidence evidence) throws RealmUnavailableException {
Assert.checkNotNullParam("evidence", evidence);
final Class extends Evidence> evidenceType = evidence.getClass();
final String algorithmName = evidence instanceof AlgorithmEvidence ? ((AlgorithmEvidence) evidence).getAlgorithm() : null;
if (LdapSecurityRealm.this.getEvidenceVerifySupport(evidenceType, algorithmName) == SupportLevel.UNSUPPORTED) {
// If not supported in general then definitely not supported for a specific principal.
return false;
}
DirContext dirContext = obtainContext();
try {
Set attributes = new HashSet<>();
Set binaryAttributes = new HashSet<>();
for (EvidenceVerifier verifier : evidenceVerifiers) {
verifier.addRequiredIdentityAttributes(attributes);
verifier.addBinaryIdentityAttributes(binaryAttributes);
}
LdapIdentity identity = getIdentity(dirContext, attributes, binaryAttributes);
if (identity == null) {
return false;
}
for (EvidenceVerifier verifier : evidenceVerifiers) {
if (verifier.getEvidenceVerifySupport(evidenceType, algorithmName).mayBeSupported()) {
IdentityEvidenceVerifier iev = verifier.forIdentity(identity.getDirContext(), identity.getDistinguishedName(), identity.getUrl(), identity.getEntry().getAttributes(), hashEncoding);
if (iev.verifyEvidence(evidence, providers, hashCharset)) {
return true;
}
}
}
} finally {
closeContext(dirContext);
}
return false;
}
@Override
public boolean exists() throws RealmUnavailableException {
DirContext dirContext = obtainContext();
try {
LdapIdentity identity = getIdentity(dirContext);
boolean exists = identity != null;
if (!exists) {
log.debugf("Principal [%s] does not exists.", name);
}
return exists;
} finally {
closeContext(dirContext);
}
}
private LdapSearch createLdapSearchByDn() {
if ( ! name.regionMatches(true, 0, identityMapping.rdnIdentifier, 0, identityMapping.rdnIdentifier.length())) {
return null;
} // equal sign not checked here as whitespaces can be between yet
try {
LdapName ldapName = new LdapName(name);
int rdnPosition = ldapName.size() - 1;
Rdn rdnIdentifier = ldapName.getRdn(rdnPosition);
if ( ! rdnIdentifier.getType().equalsIgnoreCase(identityMapping.rdnIdentifier)) { // uid=...
log.tracef("Getting identity [%s] by DN skipped - RDN does not match [%s]", name, identityMapping.rdnIdentifier);
return null;
}
if (identityMapping.searchDn != null) {
List expectedStart = new LdapName(identityMapping.searchDn).getRdns();
if ( ! ldapName.startsWith(expectedStart)) { // ...,search-dn
log.tracef("Getting identity [%s] by DN skipped - DN not in search-dn [%s]", name, identityMapping.searchDn);
return null;
}
if ( ! identityMapping.searchRecursive && ldapName.size() != expectedStart.size() + 1) {
log.tracef("Getting identity [%s] by DN skipped - DN not directly in search-dn and recursive search not enabled [%s]", name, identityMapping.searchDn);
return null;
}
}
return new LdapSearch(ldapName.toString(), SearchControls.OBJECT_SCOPE, 0, identityMapping.filterName, rdnIdentifier.getValue().toString());
} catch (InvalidNameException e) {
log.tracef(e, "Getting identity [%s] by DN failed - will continue by name", name);
}
return null;
}
private LdapIdentity getIdentity(DirContext dirContext) throws RealmUnavailableException {
return getIdentity(dirContext, null, null);
}
private LdapIdentity getIdentity(DirContext dirContext, Collection returningAttributes, Collection binaryAttributes) throws RealmUnavailableException {
log.debugf("Trying to create identity for principal [%s].", name);
LdapSearch ldapSearch = createLdapSearchByDn();
if (ldapSearch == null) { // name is not a valid DN, search by name
if (identityMapping.searchDn != null) {
ldapSearch = new LdapSearch(identityMapping.searchDn, identityMapping.searchRecursive, 0, identityMapping.filterName, name);
} else {
log.debugf("Identity for principal [%s] not found. The name is not a valid DN and the search base DN is null", name);
return null;
}
}
ldapSearch.setReturningAttributes(returningAttributes);
ldapSearch.setBinaryAttributes(binaryAttributes);
final LdapSearch ldapSearchFinal = ldapSearch;
try (Stream resultsStream = ldapSearch.search(dirContext)) {
SearchResult result = resultsStream.findFirst().orElse(null);
if (result != null) {
LdapIdentity identity = new LdapIdentity(name, ldapSearchFinal.getContext(), result.getNameInNamespace(), result.isRelative() ? null : result.getName(), result);
log.debugf("Identity for principal [%s] found at [%s].", name, identity.getDistinguishedName());
return identity;
} else {
log.debugf("Identity for principal [%s] not found.", name);
return null;
}
}
}
private String extractRdn(AttributeMapping mapping, final String dn) {
String valueRdn = mapping.getRdn();
try {
for (Rdn rdn : new LdapName(dn).getRdns()) {
if (rdn.getType().equalsIgnoreCase(valueRdn)) {
return rdn.getValue().toString();
}
}
} catch (Exception cause) {
throw log.ldapRealmInvalidRdnForAttribute(mapping.getName(), dn, valueRdn, cause);
}
return null;
}
/**
* Obtains attribute value by mapping from given entry and put it into given collection.
*
* @param entry LDAP entry, from which should be values obtained
* @param mapping attribute mapping defining attribute, whose values should be obtained
* @param outputCollection output collection for obtained values
* @return {@code true} if {@code outputCollection} was changed.
*/
private boolean valuesFromAttribute(SearchResult entry, AttributeMapping mapping, Collection outputCollection) throws NamingException {
if (mapping.getLdapName() == null) {
String value = entry.getNameInNamespace();
if (mapping.getRdn() != null) {
value = extractRdn(mapping, value);
}
return outputCollection.add(value);
} else {
Attributes entryAttributes = entry.getAttributes();
javax.naming.directory.Attribute ldapAttribute = entryAttributes.get(mapping.getLdapName());
if (ldapAttribute == null) return false;
NamingEnumeration> attributesEnum = null;
try {
attributesEnum = ldapAttribute.getAll();
Stream values = Collections.list(attributesEnum).stream().map(Object::toString);
if (mapping.getRdn() != null) {
values = values.map(val -> extractRdn(mapping, val)).filter(Objects::nonNull);
}
return values.map(outputCollection::add).filter(changed -> changed).count() != 0;
} finally {
if (attributesEnum != null) {
try {
attributesEnum.close();
} catch (NamingException ignore) {
}
}
}
}
}
private Map> extractFilteredAttributes(SearchResult identityEntry, DirContext context, DirContext identityContext) {
return extractAttributes(AttributeMapping::isFilteredOrReference, mapping -> {
Collection values = mapping.getRoleRecursionDepth() == 0 ? new ArrayList<>() : new HashSet<>();
final String searchDn = mapping.getSearchDn() != null ? mapping.getSearchDn() : identityMapping.searchDn;
List toSearch = new LinkedList<>();
toSearch.add(identityEntry);
for (int depth = 0; depth <= mapping.getRoleRecursionDepth() && ! toSearch.isEmpty(); depth++) {
List toSearchInNextLevel = new LinkedList<>();
for(SearchResult entry : toSearch) {
final String entryDn = entry != null ? entry.getNameInNamespace() : null;
if (mapping.getReference() != null && entry != null) { // reference
forEachAttributeValue(entry, mapping.getReference(), value -> {
LdapSearch search = new LdapSearch(value);
extractFilteredAttributesFromSearch(search, entry, mapping, context, identityContext, values, toSearchInNextLevel);
});
} else if (mapping.getReference() == null) { // filter
if (depth == 0) { // roles of identity
LdapSearch search = new LdapSearch(searchDn, mapping.getRecursiveSearch(), 0, mapping.getFilter(), name, entryDn);
extractFilteredAttributesFromSearch(search, entry, mapping, context, identityContext, values, toSearchInNextLevel);
} else if (entry != null) { // roles of role
forEachAttributeValue(entry, mapping.getRoleRecursionName(), roleName -> {
LdapSearch search = new LdapSearch(searchDn, mapping.getRecursiveSearch(), 0, mapping.getFilter(), roleName, entryDn);
extractFilteredAttributesFromSearch(search, entry, mapping, context, identityContext, values, toSearchInNextLevel);
});
}
}
}
toSearch = toSearchInNextLevel;
}
return values;
});
}
private void extractFilteredAttributesFromSearch(LdapSearch search, SearchResult referencedEntry, AttributeMapping mapping, DirContext context, DirContext identityContext, Collection identityAttributeValues, Collection toSearchInNextLevel) {
String referencedDn = referencedEntry != null ? referencedEntry.getNameInNamespace() : null;
Set attributes = new HashSet<>();
attributes.add(mapping.getLdapName());
attributes.add(mapping.getReference());
attributes.add(mapping.getRoleRecursionName());
search.setReturningAttributes(attributes);
try (Stream entries = search.search(mapping.searchInIdentityContext() ? identityContext : context)) {
entries.forEach(entry -> {
try {
if (valuesFromAttribute(entry, mapping, identityAttributeValues)) {
toSearchInNextLevel.add(entry);
}
} catch (Exception cause) {
throw ElytronMessages.log.ldapRealmFailedObtainAttributes(referencedDn, cause);
}
});
} catch (Exception cause) {
throw ElytronMessages.log.ldapRealmFailedObtainAttributes(referencedDn, cause);
}
}
private Map> extractSimpleAttributes(SearchResult identityEntry) {
if (identityEntry == null) return Collections.emptyMap();
return extractAttributes(mapping -> !mapping.isFilteredOrReference(), mapping -> {
Collection identityAttributeValues = new ArrayList<>();
try {
valuesFromAttribute(identityEntry, mapping, identityAttributeValues);
} catch (Exception cause) {
throw ElytronMessages.log.ldapRealmFailedObtainAttributes(identityEntry.getNameInNamespace(), cause);
}
return identityAttributeValues;
});
}
private Map> extractAttributes(Predicate filter, Function> valueFunction) {
return identityMapping.attributes.stream()
.filter(filter)
.collect(Collectors.toMap(AttributeMapping::getName, valueFunction, (values1, values2) -> {
List merged = new ArrayList<>(values1);
merged.addAll(values2);
return merged;
}));
}
private void forEachAttributeValue(SearchResult entry, String attrId, Consumer action) {
NamingEnumeration> attributesEnum = null;
try {
Attribute attribute = entry.getAttributes().get(attrId);
if (attribute == null) return;
attributesEnum = attribute.getAll();
Collections.list(attributesEnum).stream().map(Object::toString).forEach(action);
} catch (NamingException e) {
throw ElytronMessages.log.ldapRealmFailedObtainAttributes(entry.getNameInNamespace(), e);
} finally {
if (attributesEnum != null) {
try {
attributesEnum.close();
} catch (NamingException e) {
log.trace("Unable to close attributesEnum", e);
}
}
}
}
@Override
public void delete() throws RealmUnavailableException {
DirContext context = obtainContext();
try {
LdapIdentity identity = getIdentity(context);
if (identity == null) {
throw log.noSuchIdentity();
}
log.debugf("Removing identity [%s] with DN [%s] from LDAP", name, identity.getDistinguishedName());
identity.getDirContext().destroySubcontext(new LdapName(identity.getDistinguishedName()));
} catch (NamingException e) {
throw log.ldapRealmFailedDeleteIdentityFromServer(e);
} finally {
closeContext(context);
}
}
@Override
public void create() throws RealmUnavailableException {
if (identityMapping.newIdentityParent == null || identityMapping.newIdentityAttributes == null) {
throw log.ldapRealmNotConfiguredToSupportCreatingIdentities();
}
DirContext context = obtainContext();
try {
LdapName distinguishName = (LdapName) identityMapping.newIdentityParent.clone();
distinguishName.add(new Rdn(identityMapping.rdnIdentifier, name));
log.debugf("Creating identity [%s] with DN [%s] in LDAP", name, distinguishName.toString());
context.createSubcontext(distinguishName, identityMapping.newIdentityAttributes);
} catch (NamingException e) {
throw log.ldapRealmFailedCreateIdentityOnServer(e);
} finally {
closeContext(context);
}
}
@Override public void setAttributes(org.wildfly.security.authz.Attributes attributes) throws RealmUnavailableException {
log.debugf("Trying to set attributes for principal [%s].", name);
DirContext context = obtainContext();
try {
LdapIdentity identity = getIdentity(context);
if (identity == null) {
throw log.noSuchIdentity();
}
List modItems = new LinkedList<>();
LdapName identityLdapName = new LdapName(identity.getDistinguishedName());
String renameTo = null;
for(AttributeMapping mapping : identityMapping.attributes) {
if (mapping.getFilter() != null || mapping.getReference() != null || mapping.getRdn() != null) { // read-only mapping
if (attributes.size(mapping.getName()) != 0) {
log.ldapRealmDoesNotSupportSettingFilteredAttribute(mapping.getName(), name);
}
} else if (identityMapping.rdnIdentifier.equalsIgnoreCase(mapping.getLdapName())) { // entry rename
if (attributes.size(mapping.getName()) == 1) {
renameTo = attributes.get(mapping.getName(), 0);
} else {
throw log.ldapRealmRequiresExactlyOneRdnAttribute(mapping.getName(), name);
}
} else { // standard ldap attributes
if (attributes.size(mapping.getName()) == 0) {
BasicAttribute attribute = new BasicAttribute(mapping.getLdapName());
modItems.add(new ModificationItem(DirContext.REMOVE_ATTRIBUTE, attribute));
} else {
BasicAttribute attribute = new BasicAttribute(mapping.getLdapName());
attributes.get(mapping.getName()).forEach(attribute::add);
modItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attribute));
}
}
}
for(org.wildfly.security.authz.Attributes.Entry entry : attributes.entries()) {
if (identityMapping.attributes.stream().filter(mp -> mp.getName().equals(entry.getKey())).count() == 0) {
throw log.ldapRealmCannotSetAttributeWithoutMapping(entry.getKey(), name);
}
}
ModificationItem[] modItemsArray = modItems.toArray(new ModificationItem[modItems.size()]);
identity.getDirContext().modifyAttributes(identityLdapName, modItemsArray);
if (renameTo != null && ! renameTo.equals(identityLdapName.getRdn(identityLdapName.size()-1).getValue())) {
LdapName newLdapName = new LdapName(identityLdapName.getRdns().subList(0, identityLdapName.size()-1));
newLdapName.add(new Rdn(identityMapping.rdnIdentifier, renameTo));
identity.getDirContext().rename(identityLdapName, newLdapName);
}
} catch (Exception e) {
throw log.ldapRealmAttributesSettingFailed(name, e);
} finally {
closeContext(context);
}
}
private class LdapIdentity {
private final String name;
private final DirContext dirContext;
private final String distinguishedName;
private final String url;
private final SearchResult entry;
LdapIdentity(String name, DirContext dirContext, String distinguishedName, String url, SearchResult entry) {
this.name = name;
this.dirContext = dirContext;
this.distinguishedName = distinguishedName;
this.url = url;
this.entry = entry;
}
String getName() {
return this.name;
}
DirContext getDirContext() {
return this.dirContext;
}
String getDistinguishedName() {
return this.distinguishedName;
}
String getUrl() {
return this.url;
}
SearchResult getEntry() {
return this.entry;
}
}
}
private class LdapSearch {
private final String NO_FILTER = "(objectclass=*)";
private final String searchDn;
private final int searchScope;
private final int pageSize;
private final String filter;
private final String[] filterArgs;
private Collection returningAttributes;
private Collection binaryAttributes;
private DirContext context;
private NamingEnumeration result;
private byte[] cookie;
private ReferralException referralException;
public LdapSearch(String searchDn, boolean searchRecursive, int pageSize, String filter, String... filterArgs) {
this(searchDn, searchRecursive ? SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE, pageSize, filter, filterArgs);
}
public LdapSearch(String searchDn, int searchScope, int pageSize, String filter, String... filterArgs) {
this.searchDn = searchDn;
this.searchScope = searchScope;
this.pageSize = pageSize;
this.filter = filter;
this.filterArgs = filterArgs;
}
public LdapSearch(String searchDn) {
this.searchDn = searchDn;
this.searchScope = SearchControls.OBJECT_SCOPE;
this.pageSize = 0;
this.filter = NO_FILTER;
this.filterArgs = null;
}
public Stream search(DirContext ctx) throws RealmUnavailableException {
if (log.isDebugEnabled()) {
log.debugf("Executing search [%s] in context [%s] with arguments [%s]. Returning attributes are [%s]. Binary attributes are [%s].",
filter, searchDn,
filterArgs == null ? null : String.join(", ", filterArgs),
returningAttributes == null ? null : String.join(", ", returningAttributes),
binaryAttributes == null ? null : String.join(", ", binaryAttributes)
);
}
context = ctx;
cookie = null;
try {
try {
result = searchWithPagination();
} catch (ReferralException e) {
referralException = e;
}
return StreamSupport.stream(new Spliterators.AbstractSpliterator(Long.MAX_VALUE, Spliterator.NONNULL) {
boolean finished = false;
Set
© 2015 - 2025 Weber Informatics LLC | Privacy Policy