/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other 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.keycloak.storage.ldap.idm.model;
import javax.naming.ldap.Rdn;
import java.util.Collection;
import java.util.Deque;
import java.util.LinkedList;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LDAPDn {
private final Deque<Entry> entries;
private LDAPDn() {
this.entries = new LinkedList<>();
}
private LDAPDn(Deque<Entry> entries) {
this.entries = entries;
}
public static LDAPDn fromString(String dnString) {
LDAPDn dn = new LDAPDn();
// In certain OpenLDAP implementations the uniqueMember attribute is mandatory
// Thus, if a new group is created, it will contain an empty uniqueMember attribute
// Later on, when adding members, this empty attribute will be kept
// Keycloak must be able to process it, properly, w/o throwing an ArrayIndexOutOfBoundsException
if(dnString.trim().isEmpty())
return dn;
String[] rdns = dnString.split("(?<!\\\\),");
for (String entryStr : rdns) {
String[] rdn = entryStr.split("(?<!\\\\)=");
dn.addLast(rdn[0].trim(), rdn[1].trim());
}
return dn;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof LDAPDn)) {
return false;
}
return toString().equals(obj.toString());
}
@Override
public int hashCode() {
return toString().hashCode();
}
@Override
public String toString() {
return toString(entries);
}
private static String toString(Collection<Entry> entries) {
StringBuilder builder = new StringBuilder();
boolean first = true;
for (Entry rdn : entries) {
if (first) {
first = false;
} else {
builder.append(",");
}
builder.append(rdn.attrName).append("=").append(rdn.attrValue);
}
return builder.toString();
}
/**
* @return string like "uid=joe" from the DN like "uid=joe,dc=something,dc=org"
*/
public String getFirstRdn() {
Entry firstEntry = entries.getFirst();
return firstEntry.attrName + "=" + unescapeValue(firstEntry.attrValue);
}
/**
* @return string attribute name like "uid" from the DN like "uid=joe,dc=something,dc=org"
*/
public String getFirstRdnAttrName() {
Entry firstEntry = entries.getFirst();
return firstEntry.attrName;
}
/**
* @return string attribute value like "joe" from the DN like "uid=joe,dc=something,dc=org"
*/
public String getFirstRdnAttrValue() {
Entry firstEntry = entries.getFirst();
String dnEscaped = firstEntry.attrValue;
return unescapeValue(dnEscaped);
}
private String unescapeValue(String escaped) {
// Something needed to handle non-String types?
return Rdn.unescapeValue(escaped).toString();
}
/**
*
* @return DN like "dc=something,dc=org" from the DN like "uid=joe,dc=something,dc=org".
* Returned DN will be new clone not related to the original DN instance.
*
*/
public LDAPDn getParentDn() {
LinkedList<Entry> parentDnEntries = new LinkedList<>(entries);
parentDnEntries.remove();
return new LDAPDn(parentDnEntries);
}
public boolean isDescendantOf(LDAPDn expectedParentDn) {
int parentEntriesCount = expectedParentDn.entries.size();
Deque<Entry> myEntries = new LinkedList<>(this.entries);
boolean someRemoved = false;
while (myEntries.size() > parentEntriesCount) {
myEntries.removeFirst();
someRemoved = true;
}
String myEntriesParentStr = toString(myEntries).toLowerCase();
String expectedParentDnStr = expectedParentDn.toString().toLowerCase();
return someRemoved && myEntriesParentStr.equals(expectedParentDnStr);
}
public void addFirst(String rdnName, String rdnValue) {
rdnValue = Rdn.escapeValue(rdnValue);
entries.addFirst(new Entry(rdnName, rdnValue));
}
private void addLast(String rdnName, String rdnValue) {
entries.addLast(new Entry(rdnName, rdnValue));
}
private static class Entry {
private final String attrName;
private final String attrValue;
private Entry(String attrName, String attrValue) {
this.attrName = attrName;
this.attrValue = attrValue;
}
}
}