Skip to content

Commit

Permalink
add synchronization to new ad structure
Browse files Browse the repository at this point in the history
  • Loading branch information
mateusz.uzarek committed Oct 3, 2024
1 parent f758d11 commit cf45a92
Show file tree
Hide file tree
Showing 18 changed files with 579 additions and 110 deletions.
2 changes: 0 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,9 @@ ldap.security.principal= (Active directory username)
ldap.security.credentials= (Active directory password)
ldap.provider.url= (Active directory url)
ad.containers.main= (Main container)
ad.groups.users= (Main user group)
ad.groups.ec= (Group for employees)
ad.groups.b2b= (Group for associates)
ad.groups.admin= (Group for admins)
ad.identifiers.team=Team,Guild (Postfixes for group names. Here we have distinction for teams and guilds)
azure.ad.clientId= (Client Id from azure AD registration)
azure.ad.tenantId= (Tenant Id from azure AD registration)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,12 @@ private Set<TeamInfo> pickTeamsInfo(User user) {
Set<TeamInfo> teams = new HashSet<>();
for (var team : user.getTeams()) {
var teamName = team.getName();
var allUsersLeader = userService.getAllUsersLeader();
var leader = user.equals(team.getLeader()) ? allUsersLeader : team.getLeader();

if (leader != null) {
var leaderName = leader.getFirstName() + " " + leader.getLastName();
var teamInfo = new TeamInfo(teamName, leaderName);
teams.add(teamInfo);
}
var leader = userService.getTeamLeader(user, team);
var leaderName = leader != null
? leader.getFirstName() + " " + leader.getLastName()
: "-";
var teamInfo = new TeamInfo(teamName, leaderName);
teams.add(teamInfo);
}
return teams;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
package info.fingo.urlopia.config.ad;

public enum ActiveDirectoryObjectClass {
Person, Group
PERSON("person"),
GROUP("group"),
ORGANIZATIONAL_UNIT("organizationalUnit");

private final String key;

ActiveDirectoryObjectClass(String key) {
this.key = key;
}

public String getKey() {
return key;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

@ConditionalOnProperty(name = "ad.configuration.enabled", havingValue = "true", matchIfMissing = true)
public class ActiveDirectorySearcher {
Expand All @@ -28,7 +29,15 @@ public ActiveDirectorySearcher(String mainContainer,
}

public ActiveDirectorySearcher objectClass(ActiveDirectoryObjectClass objectClass) {
var value = String.format("(objectClass=%s)", objectClass.name());
var value = String.format("(objectClass=%s)", objectClass.getKey());
filter.append(value);
return this;
}

public ActiveDirectorySearcher objectClasses(List<ActiveDirectoryObjectClass> objectClasses) {
var value = objectClasses.stream()
.map(objClass -> String.format("(objectClass=%s)", objClass.getKey()))
.collect(Collectors.joining("", "(|", ")"));
filter.append(value);
return this;
}
Expand Down Expand Up @@ -63,6 +72,12 @@ public ActiveDirectorySearcher distinguishedName(String distinguishedName) {
return this;
}

public ActiveDirectorySearcher excludeDistinguishedName(String distinguishedName) {
var value = String.format("(!(distinguishedName=%s))", distinguishedName);
filter.append(value);
return this;
}

public ActiveDirectorySearcher isDisabled(){
var builder = new StringBuilder("(|");
for (var disableKey: ActiveDirectoryUtils.DISABLED_STATUS){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import javax.naming.directory.SearchResult;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

Expand Down Expand Up @@ -43,4 +44,39 @@ public static boolean isDisabled(SearchResult searchResult){
return DISABLED_STATUS.contains(accountStatus);
}

public static String getRelativeDN(String distinguishedName,
String base) {
if (distinguishedName.equals(base)) {
return "";
} else if(distinguishedName.endsWith(base) && !base.isBlank()) {
return distinguishedName.substring(0, distinguishedName.length() - base.length() - 1); // -1 for comma
} else {
return distinguishedName;
}
}

public static String getParentDN(String distinguishedName) {
var dnParts = Arrays.stream(distinguishedName.split(",")).toList();
if (dnParts.size() == 1) {
return "";
} else {
return String.join(",", dnParts.subList(1, dnParts.size()));
}
}

public static boolean isOU(SearchResult object) {
return isObjectClass(object, ActiveDirectoryObjectClass.ORGANIZATIONAL_UNIT);
}

public static boolean isPerson(SearchResult object) {
return isObjectClass(object, ActiveDirectoryObjectClass.PERSON);
}

public static boolean isObjectClass(SearchResult object, ActiveDirectoryObjectClass objectClass) {
var objectClasses = pickAttribute(object, info.fingo.urlopia.config.ad.Attribute.OBJECT_CLASS);
return Arrays.stream(objectClasses.split(","))
.map(String::trim)
.anyMatch(oc -> oc.equalsIgnoreCase(objectClass.getKey()));
}

}
3 changes: 2 additions & 1 deletion src/main/java/info/fingo/urlopia/config/ad/Attribute.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ public enum Attribute {
CREATED_TIME("whenCreated"),
CHANGED_TIME("whenChanged"),
USER_ACCOUNT_CONTROL("userAccountControl"),
ACCOUNT_NAME("sAMAccountName");
ACCOUNT_NAME("sAMAccountName"),
OBJECT_CLASS("objectClass");

private String key;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package info.fingo.urlopia.config.ad.tree;

import info.fingo.urlopia.config.ad.ActiveDirectoryUtils;
import info.fingo.urlopia.config.ad.Attribute;

import javax.naming.directory.SearchResult;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class ActiveDirectoryNode {

private final String relativeDN;
private final SearchResult object;
private final Map<String, ActiveDirectoryNode> children;

protected ActiveDirectoryNode(SearchResult object) {
this.relativeDN = getRDN(object);
this.object = object;
this.children = new HashMap<>();
}

protected ActiveDirectoryNode(String distinguishedName) {
this.relativeDN = getRDN(distinguishedName);
this.object = null;
this.children = new HashMap<>();
}

public void add(ActiveDirectoryNode child) {
children.put(child.relativeDN, child);
}

public Optional<ActiveDirectoryNode> getChild(String childRelativeDistinguishedName) {
return Optional.ofNullable(children.get(childRelativeDistinguishedName));
}

private static String getRDN(SearchResult object) {
var distinguishedName = ActiveDirectoryUtils.pickAttribute(object, Attribute.DISTINGUISHED_NAME);
return getRDN(distinguishedName);
}

private static String getRDN(String distinguishedName) {
return distinguishedName.split(",", 2)[0];
}

public List<SearchResult> getDirectChildrenObjects() {
return children.values().stream()
.map(child -> child.object)
.toList();
}

public SearchResult getObject() {
return object;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package info.fingo.urlopia.config.ad.tree;

import info.fingo.urlopia.config.ad.ActiveDirectoryUtils;
import info.fingo.urlopia.config.ad.Attribute;

import javax.naming.directory.SearchResult;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class ActiveDirectoryTree {

private final String mainContainerDn;
private final ActiveDirectoryNode root;

public ActiveDirectoryTree(String mainContainerDn) {
this.mainContainerDn = mainContainerDn;
this.root = new ActiveDirectoryNode(mainContainerDn);
}

public void put(SearchResult object) {
var childDn = ActiveDirectoryUtils.pickAttribute(object, Attribute.DISTINGUISHED_NAME);
var parentDn = ActiveDirectoryUtils.getParentDN(childDn);
var parentNode = searchNode(parentDn);
parentNode.ifPresentOrElse(
pNode -> {
var childNode = new ActiveDirectoryNode(object);
pNode.add(childNode);
},
() -> {
throw ActiveDirectoryTreeException.missingParent(childDn);
}
);
}

public List<SearchResult> searchDirectChildrenObjectsOf(String distinguishedName) {
return searchNode(distinguishedName)
.map(ActiveDirectoryNode::getDirectChildrenObjects)
.orElse(List.of());
}

public Optional<SearchResult> search(String distinguishedName) {
return searchNode(distinguishedName).map(ActiveDirectoryNode::getObject);
}

private Optional<ActiveDirectoryNode> searchNode(String distinguishedName) {
var relativeDn = ActiveDirectoryUtils.getRelativeDN(distinguishedName, mainContainerDn);
if (relativeDn.isBlank()) {
return Optional.of(root);
}
var dnParts = Arrays.stream(relativeDn.split(",")).toList();
return searchNode(root, dnParts);
}

private Optional<ActiveDirectoryNode> searchNode(ActiveDirectoryNode node,
List<String> dnParts) {
if (dnParts.isEmpty()) {
return Optional.of(node);
}
var topDnPartIdx = dnParts.size() - 1;
var topDnPart = dnParts.get(topDnPartIdx);
var topNode = node.getChild(topDnPart);
var bottomDnParts = dnParts.stream()
.limit(topDnPartIdx)
.toList();
return topNode.flatMap(n -> searchNode(n, bottomDnParts));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package info.fingo.urlopia.config.ad.tree;

public class ActiveDirectoryTreeException extends RuntimeException {

private static final String MISSING_PARENT = "Missing parent node for object %s";

private ActiveDirectoryTreeException(String message) {
super(message);
}

public static ActiveDirectoryTreeException missingParent(String child) {
return new ActiveDirectoryTreeException(MISSING_PARENT.formatted(child));
}

}
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
package info.fingo.urlopia.team;

import info.fingo.urlopia.config.ad.ActiveDirectory;
import info.fingo.urlopia.config.ad.ActiveDirectoryObjectClass;
import info.fingo.urlopia.config.ad.ActiveDirectoryUtils;
import info.fingo.urlopia.config.ad.Attribute;
import info.fingo.urlopia.user.User;
import info.fingo.urlopia.user.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

Expand All @@ -16,23 +10,9 @@
@ConditionalOnProperty(name = "ad.configuration.enabled", havingValue = "true", matchIfMissing = true)
public class ActiveDirectoryAllUsersLeaderProvider implements AllUsersLeaderProvider{

@Value("${ad.groups.users}")
private String usersGroup;
private final ActiveDirectory activeDirectory;
private final UserRepository userRepository;


@Override
public User getAllUsersLeader() {
var groups = activeDirectory.newSearch()
.objectClass(ActiveDirectoryObjectClass.Group)
.distinguishedName(usersGroup)
.search();

return groups.stream()
.map(group -> ActiveDirectoryUtils.pickAttribute(group, Attribute.MANAGED_BY))
.findFirst()
.flatMap(userRepository::findFirstByAdName)
.orElse(null);
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package info.fingo.urlopia.team;

import info.fingo.urlopia.config.ad.ActiveDirectoryUtils;
import info.fingo.urlopia.config.ad.Attribute;
import info.fingo.urlopia.config.ad.tree.ActiveDirectoryTree;
import info.fingo.urlopia.user.User;
import info.fingo.urlopia.user.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

import javax.naming.directory.SearchResult;
import java.util.Optional;

@Component
@RequiredArgsConstructor
@ConditionalOnProperty(name = "ad.configuration.enabled", havingValue = "true", matchIfMissing = true)
public class ActiveDirectoryTeamLeaderProvider {

private final UserRepository userRepository;

public Optional<User> getTeamLeader(String adTeamDN,
ActiveDirectoryTree adTeamsTree) {
return adTeamsTree.search(adTeamDN)
.flatMap(adTeam -> {
var managedBy = getManagedBy(adTeam);
return managedBy
.map(this::getUser)
.orElseGet(() -> checkParentTeam(adTeamDN, adTeamsTree));
});
}

private Optional<String> getManagedBy(SearchResult adTeam) {
return Optional.ofNullable(ActiveDirectoryUtils.pickAttribute(adTeam, Attribute.MANAGED_BY))
.filter(managedBy -> !managedBy.isBlank());
}

private Optional<User> checkParentTeam(String adTeamDN,
ActiveDirectoryTree adTeamsTree) {
var parentTeamDN = ActiveDirectoryUtils.getParentDN(adTeamDN);
return getTeamLeader(parentTeamDN, adTeamsTree);
}

private Optional<User> getUser(String userDN) {
System.out.println("repo:" + userDN);
return userRepository.findFirstByAdName(userDN);
}
}
Loading

0 comments on commit cf45a92

Please sign in to comment.