commit
8c4852b13b
No known key found for this signature in database
GPG Key ID: B4C3BF012D9B26BE
6 changed files with 474 additions and 0 deletions
-
6.gitignore
-
2ldap-sync.sh
-
119pom.xml
-
101src/main/java/de/hsfulda/informatik/AccountSource.java
-
135src/main/java/de/hsfulda/informatik/LdapSync.java
-
111src/test/java/de/hsfulda/informatik/LdapSyncTest.java
@ -0,0 +1,6 @@ |
|||
*.iws |
|||
*.iml |
|||
*.ipr |
|||
target/ |
|||
.DS_Store |
|||
.idea/ |
@ -0,0 +1,2 @@ |
|||
#!/bin/bash |
|||
java -jar target/ldap-sync-1.0-SNAPSHOT-jar-with-dependencies.jar |
@ -0,0 +1,119 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
|||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
|||
<modelVersion>4.0.0</modelVersion> |
|||
|
|||
<groupId>de.hsfulda.informatik</groupId> |
|||
<artifactId>ldap-sync</artifactId> |
|||
<version>1.0-SNAPSHOT</version> |
|||
|
|||
<name>ldap-sync</name> |
|||
<!-- FIXME change it to the project's website --> |
|||
<url>http://www.example.com</url> |
|||
|
|||
<properties> |
|||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
|||
<maven.compiler.source>1.7</maven.compiler.source> |
|||
<maven.compiler.target>1.7</maven.compiler.target> |
|||
</properties> |
|||
|
|||
<dependencies> |
|||
<dependency> |
|||
<groupId>com.unboundid</groupId> |
|||
<artifactId>unboundid-ldapsdk</artifactId> |
|||
<version>6.0.1</version> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>com.google.guava</groupId> |
|||
<artifactId>guava</artifactId> |
|||
<version>30.1.1-jre</version> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>junit</groupId> |
|||
<artifactId>junit</artifactId> |
|||
<version>4.11</version> |
|||
<scope>test</scope> |
|||
</dependency> |
|||
</dependencies> |
|||
|
|||
<build> |
|||
<plugins> |
|||
<plugin> |
|||
<artifactId>maven-assembly-plugin</artifactId> |
|||
<executions> |
|||
<execution> |
|||
<id>foobar</id> |
|||
<phase>package</phase> |
|||
<goals> |
|||
<goal>single</goal> |
|||
</goals> |
|||
</execution> |
|||
</executions> |
|||
<configuration> |
|||
<descriptorRefs> |
|||
<descriptorRef>jar-with-dependencies</descriptorRef> |
|||
</descriptorRefs> |
|||
<archive> |
|||
<manifest> |
|||
<mainClass>de.hsfulda.informatik.LdapSync</mainClass> |
|||
</manifest> |
|||
</archive> |
|||
</configuration> |
|||
</plugin> |
|||
<plugin> |
|||
<groupId>org.apache.maven.plugins</groupId> |
|||
<artifactId>maven-compiler-plugin</artifactId> |
|||
<configuration> |
|||
<source>8</source> |
|||
<target>8</target> |
|||
</configuration> |
|||
</plugin> |
|||
</plugins> |
|||
|
|||
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> |
|||
<plugins> |
|||
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle --> |
|||
<plugin> |
|||
<artifactId>maven-clean-plugin</artifactId> |
|||
<version>3.1.0</version> |
|||
</plugin> |
|||
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging --> |
|||
<plugin> |
|||
<artifactId>maven-resources-plugin</artifactId> |
|||
<version>3.0.2</version> |
|||
</plugin> |
|||
<plugin> |
|||
<artifactId>maven-compiler-plugin</artifactId> |
|||
<version>3.8.0</version> |
|||
</plugin> |
|||
<plugin> |
|||
<artifactId>maven-surefire-plugin</artifactId> |
|||
<version>2.22.1</version> |
|||
</plugin> |
|||
<plugin> |
|||
<artifactId>maven-jar-plugin</artifactId> |
|||
<version>3.0.2</version> |
|||
</plugin> |
|||
<plugin> |
|||
<artifactId>maven-install-plugin</artifactId> |
|||
<version>2.5.2</version> |
|||
</plugin> |
|||
<plugin> |
|||
<artifactId>maven-deploy-plugin</artifactId> |
|||
<version>2.8.2</version> |
|||
</plugin> |
|||
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle --> |
|||
<plugin> |
|||
<artifactId>maven-site-plugin</artifactId> |
|||
<version>3.7.1</version> |
|||
</plugin> |
|||
<plugin> |
|||
<artifactId>maven-project-info-reports-plugin</artifactId> |
|||
<version>3.0.0</version> |
|||
</plugin> |
|||
</plugins> |
|||
</pluginManagement> |
|||
</build> |
|||
|
|||
</project> |
@ -0,0 +1,101 @@ |
|||
package de.hsfulda.informatik; |
|||
|
|||
import com.unboundid.ldap.sdk.Entry; |
|||
import com.unboundid.ldap.sdk.ExtendedResult; |
|||
import com.unboundid.ldap.sdk.LDAPConnection; |
|||
import com.unboundid.ldap.sdk.LDAPException; |
|||
import com.unboundid.ldap.sdk.LDAPResult; |
|||
import com.unboundid.ldap.sdk.ResultCode; |
|||
import com.unboundid.ldap.sdk.SearchResult; |
|||
import com.unboundid.ldap.sdk.SearchResultEntry; |
|||
import com.unboundid.ldap.sdk.SearchScope; |
|||
import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest; |
|||
import com.unboundid.util.ssl.SSLUtil; |
|||
import com.unboundid.util.ssl.TrustAllTrustManager; |
|||
|
|||
import java.security.GeneralSecurityException; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
import java.util.Set; |
|||
import java.util.stream.Collectors; |
|||
|
|||
public class AccountSource { |
|||
final LDAPConnection connection; |
|||
final List<SearchResultEntry> searchResultEntryList; |
|||
final Set<String> users; |
|||
final String baseDN; |
|||
|
|||
AccountSource(final String baseDN, final List<SearchResultEntry> searchResultEntryList) { |
|||
this.baseDN = baseDN; |
|||
this.searchResultEntryList = searchResultEntryList; |
|||
this.users = getEntries().stream() |
|||
.map(e -> e.getAttribute("cn").getValue().toLowerCase()) |
|||
.filter(e -> e.startsWith("fd")) |
|||
.collect(Collectors.toSet()); |
|||
connection = null; |
|||
} |
|||
|
|||
public AccountSource(final String host, final int port, final String bindDN, final String password, final String baseDN, final String filter, final String... attributes) throws LDAPException, GeneralSecurityException { |
|||
this.baseDN = baseDN; |
|||
final SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager()); |
|||
this.connection = new LDAPConnection(host, port); |
|||
final ExtendedResult extendedResult = connection.processExtendedOperation(new StartTLSExtendedRequest(sslUtil.createSSLContext())); |
|||
this.connection.bind(bindDN, password); |
|||
|
|||
if (extendedResult.getResultCode() != ResultCode.SUCCESS) { |
|||
throw new LDAPException(extendedResult.getResultCode()); |
|||
} |
|||
|
|||
final SearchResult searchResult = connection.search(baseDN, SearchScope.SUB, filter, attributes); |
|||
this.searchResultEntryList = searchResult.getSearchEntries(); |
|||
this.users = getEntries().stream() |
|||
.map(e -> e.getAttribute("cn").getValue().toLowerCase()) |
|||
.filter(e -> e.startsWith("fd")) |
|||
.collect(Collectors.toSet()); |
|||
} |
|||
|
|||
public List<SearchResultEntry> getEntries() { |
|||
return Collections.unmodifiableList(this.searchResultEntryList); |
|||
} |
|||
|
|||
public Set<String> getUsers() { |
|||
return Collections.unmodifiableSet(this.users); |
|||
} |
|||
|
|||
public void add(final List<Entry> usersToBeAdded) { |
|||
for (final Entry entry : usersToBeAdded) { |
|||
System.out.print("-> Füge Benutzer " + entry.getDN() + "..."); |
|||
final LDAPResult ldapResult; |
|||
try { |
|||
ldapResult = connection.add(entry); |
|||
|
|||
if (ldapResult.getResultCode().intValue() == 0) { |
|||
System.out.print("Ok\n"); |
|||
} else { |
|||
System.out.print("Failed (" + ldapResult.getResultCode() + ")\n"); |
|||
} |
|||
} catch (LDAPException e) { |
|||
System.out.print("Failed (" + e.getMessage() + ")\n"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void del(final Set<String> usersToBeDeleted) { |
|||
for (final String user : usersToBeDeleted) { |
|||
System.out.print("-> Lösche Benutzer cn=" + user + "," + this.baseDN + "..."); |
|||
final LDAPResult ldapResult; |
|||
|
|||
try { |
|||
ldapResult = connection.delete("cn=" + user + "," + this.baseDN); |
|||
|
|||
if (ldapResult.getResultCode().intValue() == 0) { |
|||
System.out.print("Ok\n"); |
|||
} else { |
|||
System.out.print("Failed (" + ldapResult.getResultCode() + ")\n"); |
|||
} |
|||
} catch (LDAPException e) { |
|||
System.out.print("Failed (" + e.getMessage() + ")\n"); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,135 @@ |
|||
package de.hsfulda.informatik; |
|||
|
|||
import com.unboundid.ldap.sdk.Attribute; |
|||
import com.unboundid.ldap.sdk.Entry; |
|||
import com.unboundid.ldap.sdk.LDAPException; |
|||
|
|||
import java.io.FileNotFoundException; |
|||
import java.io.FileReader; |
|||
import java.io.IOException; |
|||
import java.security.GeneralSecurityException; |
|||
import java.util.List; |
|||
import java.util.Properties; |
|||
import java.util.Set; |
|||
import java.util.TreeSet; |
|||
import java.util.regex.Matcher; |
|||
import java.util.regex.Pattern; |
|||
import java.util.stream.Collectors; |
|||
|
|||
/** |
|||
* Ldap sync utility |
|||
*/ |
|||
public class LdapSync { |
|||
final Properties properties = new Properties(); |
|||
final static Pattern pattern = Pattern.compile("^fd([a-z][a-z])?([0-9]*?)$"); |
|||
|
|||
public LdapSync() throws IOException, LDAPException, GeneralSecurityException { |
|||
// lade Konfiguration |
|||
properties.load(new FileReader("ldap-sync.properties")); |
|||
System.out.print("Abfrage der Benutzer im eDirectory..."); |
|||
|
|||
// lade Daten des Remote-Systems |
|||
final AccountSource remote = new AccountSource( |
|||
properties.getProperty("sync.src.host"), |
|||
Integer.parseInt(properties.getProperty("sync.src.port")), |
|||
properties.getProperty("sync.src.binddn"), |
|||
properties.getProperty("sync.src.bindpw"), |
|||
properties.getProperty("sync.src.basedn"), |
|||
properties.getProperty("sync.src.filter"), |
|||
new String[]{"cn", "givenname", "sn", "uid"} |
|||
); |
|||
|
|||
System.out.print("Ok\nAbfrage der Benutzer im OpenLDAP..."); |
|||
|
|||
// lade Daten des lokalen Systems |
|||
final AccountSource local = new AccountSource( |
|||
properties.getProperty("sync.dst.host"), |
|||
Integer.parseInt(properties.getProperty("sync.dst.port")), |
|||
properties.getProperty("sync.dst.binddn"), |
|||
properties.getProperty("sync.dst.bindpw"), |
|||
properties.getProperty("sync.dst.basedn"), |
|||
properties.getProperty("sync.dst.filter"), |
|||
new String[]{} |
|||
); |
|||
|
|||
System.out.print("Ok\n"); |
|||
|
|||
sync(remote, local); |
|||
} |
|||
|
|||
LdapSync(final AccountSource remote, final AccountSource local) throws IOException, LDAPException, GeneralSecurityException { |
|||
sync(remote, local); |
|||
} |
|||
|
|||
private void sync(final AccountSource remote, final AccountSource local) throws LDAPException { |
|||
Set<String> usersToBeAdded = new TreeSet<>(remote.getUsers()); |
|||
usersToBeAdded.removeAll(local.getUsers()); |
|||
Set<String> usersToBeDeleted = new TreeSet<>(local.getUsers()); |
|||
usersToBeDeleted.removeAll(remote.getUsers()); |
|||
|
|||
final List<Entry> entriesToBeAdded = remote.getEntries().stream() |
|||
.filter(s -> usersToBeAdded.contains(s.getAttributeValue("cn").toLowerCase())) |
|||
.map(s -> { |
|||
final String cn = s.getAttributeValue("cn").toLowerCase(); |
|||
final String dn = "cn=" + cn + "," + local.baseDN; |
|||
final Entry e = new Entry(dn); |
|||
e.addAttribute(new Attribute("objectClass", "inetOrgPerson")); |
|||
e.addAttribute(new Attribute("objectClass", "shadowAccount")); |
|||
e.addAttribute(new Attribute("objectClass", "top")); |
|||
e.addAttribute(new Attribute("objectClass", "posixAccount")); |
|||
e.addAttribute(new Attribute("cn", cn)); |
|||
e.addAttribute(new Attribute("sn", s.getAttributeValue("sn"))); |
|||
e.addAttribute(new Attribute("givenname", s.getAttributeValue("givenName"))); |
|||
e.addAttribute(new Attribute("uid", cn)); |
|||
e.addAttribute(new Attribute("uidNumber", String.valueOf(computeUid(cn)))); |
|||
e.addAttribute(new Attribute("gidNumber", "20")); |
|||
e.addAttribute(new Attribute("loginShell", "/bin/zsh")); |
|||
e.addAttribute(new Attribute("homeDirectory", "/Users/" + cn)); |
|||
e.addAttribute(new Attribute("userPassword", "{SASL}" + cn)); |
|||
|
|||
return e; |
|||
}).collect(Collectors.toList()); |
|||
|
|||
System.out.print("Zu erzeugende Benutzer: " + entriesToBeAdded.size() + ", Zu löschende Benutzer: " + usersToBeDeleted.size() + "\n"); |
|||
|
|||
local.del(usersToBeDeleted); |
|||
local.add(entriesToBeAdded); |
|||
} |
|||
|
|||
static Integer computeUid(final String cn) { |
|||
final Matcher m = pattern.matcher(cn); |
|||
|
|||
if (m.find()) { |
|||
int a = 0; |
|||
int b = 0; |
|||
|
|||
if (m.group(1) != null) { |
|||
a = m.group(1).charAt(0) - 96; |
|||
b = m.group(1).charAt(1) - 96; |
|||
} |
|||
|
|||
final int s = m.group(2).length(); |
|||
final int z = Integer.parseInt(m.group(2)); |
|||
|
|||
final int uid = (a * 1000 + b * 10 + s) * 10000 + z; |
|||
|
|||
return uid; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public static void main(String[] args) { |
|||
try { |
|||
final LdapSync ldapSync = new LdapSync(); |
|||
} catch (FileNotFoundException e) { |
|||
e.printStackTrace(); |
|||
} catch (LDAPException e) { |
|||
e.printStackTrace(); |
|||
} catch (GeneralSecurityException e) { |
|||
e.printStackTrace(); |
|||
} catch (IOException e) { |
|||
e.printStackTrace(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,111 @@ |
|||
package de.hsfulda.informatik; |
|||
|
|||
import com.unboundid.ldap.sdk.Attribute; |
|||
import com.unboundid.ldap.sdk.Entry; |
|||
import com.unboundid.ldap.sdk.LDAPException; |
|||
import com.unboundid.ldap.sdk.SearchResultEntry; |
|||
import org.junit.Test; |
|||
|
|||
import java.io.IOException; |
|||
import java.security.GeneralSecurityException; |
|||
import java.util.ArrayList; |
|||
import java.util.Arrays; |
|||
import java.util.List; |
|||
import java.util.Set; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertTrue; |
|||
|
|||
public class LdapSyncTest { |
|||
private List<Entry> addList; |
|||
private Set<String> delSet; |
|||
|
|||
@Test |
|||
public void testSync() throws LDAPException, GeneralSecurityException, IOException { |
|||
final List<SearchResultEntry> remoteEntries = new ArrayList<>(); |
|||
remoteEntries.add(searchResultEntry("dn=Fd1234,dc=remote,dc=de", "fD1234", "Mustermann", "Mustermann")); |
|||
remoteEntries.add(searchResultEntry("dn=fDai1235,dc=remote,dc=de", "FDai1235", "Schuster", "Schuster")); |
|||
remoteEntries.add(searchResultEntry("dn=fdaI1236,dc=remote,dc=de", "fdAI1236", "Bunsen", "Bunsen")); |
|||
remoteEntries.add(searchResultEntry("dn=fDeT123,dc=remote,dc=de", "FDET123", "Bauer", "Bauer")); |
|||
remoteEntries.add(searchResultEntry("dn=fdXx9999,dc=remote,dc=de", "fdxX9999", "Schmidt", "Schmidt")); |
|||
final AccountSource remote = new AccountSource("dc=remote,dc=de", remoteEntries); |
|||
|
|||
final List<SearchResultEntry> localEntries = new ArrayList<>(); |
|||
localEntries.add(searchResultEntry("dn=Fdai1236,dc=local,dc=de", "fDai1236", "Bunsen", "Bunsen")); |
|||
localEntries.add(searchResultEntry("dn=fDai1237,dc=local,dc=de", "fdAi1237", "Beaker", "Beaker")); |
|||
localEntries.add(searchResultEntry("dn=fdAi1238,dc=local,dc=de", "fdaI1238", "Hopper", "Hopper")); |
|||
final AccountSource local = new AccountSource("dc=local,dc=de", localEntries) { |
|||
@Override |
|||
public void add(List<Entry> usersToBeAdded) { |
|||
addList = usersToBeAdded; |
|||
} |
|||
|
|||
@Override |
|||
public void del(Set<String> usersToBeDeleted) { |
|||
delSet = usersToBeDeleted; |
|||
} |
|||
}; |
|||
|
|||
final LdapSync ldapSync = new LdapSync(remote, local); |
|||
|
|||
assertEquals(delSet.size(), 2); |
|||
assertTrue(delSet.contains("fdai1237")); |
|||
assertTrue(delSet.contains("fdai1238")); |
|||
|
|||
assertEquals(addList.size(), 4); |
|||
|
|||
for (final Entry entry : addList) { |
|||
assertTrue(entry.getDN().endsWith(",dc=local,dc=de")); |
|||
|
|||
final List<String> objectClasses = Arrays.asList(entry.getObjectClassValues()); |
|||
assertTrue(objectClasses.contains("shadowAccount")); |
|||
assertTrue(objectClasses.contains("posixAccount")); |
|||
assertTrue(objectClasses.contains("top")); |
|||
assertTrue(objectClasses.contains("inetOrgPerson")); |
|||
|
|||
final List<String> attributes = entry.getAttributes().stream().map(a -> a.getName()).collect(Collectors.toList()); |
|||
assertTrue(attributes.contains("uid")); |
|||
assertTrue(attributes.contains("cn")); |
|||
assertTrue(attributes.contains("givenname")); |
|||
assertTrue(attributes.contains("sn")); |
|||
assertTrue(attributes.contains("uidNumber")); |
|||
assertTrue(attributes.contains("gidNumber")); |
|||
assertTrue(attributes.contains("homeDirectory")); |
|||
assertTrue(attributes.contains("loginShell")); |
|||
|
|||
assertEquals(entry.getAttributeValue("cn").toLowerCase(), entry.getAttributeValue("cn")); |
|||
assertEquals(entry.getAttributeValue("uid").toLowerCase(), entry.getAttributeValue("uid")); |
|||
assertEquals(entry.getDN().toLowerCase(), entry.getDN()); |
|||
|
|||
System.out.println(); |
|||
|
|||
for (final String string : entry.toLDIF()) { |
|||
System.out.println(string); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Test |
|||
public void testUidNumber() { |
|||
assertEquals(41000, (int) LdapSync.computeUid("fd1000")); |
|||
assertEquals(49999, (int) LdapSync.computeUid("fd9999")); |
|||
assertEquals(10141000, (int) LdapSync.computeUid("fdaa1000")); |
|||
assertEquals(10149999, (int) LdapSync.computeUid("fdaa9999")); |
|||
assertEquals(262641000, (int) LdapSync.computeUid("fdzz1000")); |
|||
assertEquals(262649999, (int) LdapSync.computeUid("fdzz9999")); |
|||
} |
|||
|
|||
private SearchResultEntry searchResultEntry(final String dn, final String cn, final String sn, final String givenname) { |
|||
final Attribute[] attributes = { |
|||
new Attribute("cn", cn), |
|||
new Attribute("uid", cn), |
|||
new Attribute("sn", sn), |
|||
new Attribute("givenname", givenname), |
|||
new Attribute("objectClass", "inetOrgPerson"), |
|||
new Attribute("objectClass", "top"), |
|||
}; |
|||
|
|||
return new SearchResultEntry(dn, attributes); |
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue