Skip to content

Instantly share code, notes, and snippets.

@hmt
Last active March 14, 2026 10:36
Show Gist options
  • Select an option

  • Save hmt/fc84b4c6f5ee101c2ddd152696f05025 to your computer and use it in GitHub Desktop.

Select an option

Save hmt/fc84b4c6f5ee101c2ddd152696f05025 to your computer and use it in GitHub Desktop.
BSD 3-Clause License
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/**
* dieses Script holt sich die Schüler aus der Schild2-DB und aus UCS, vergleicht und erstellt daraus die neuen Benutzer, bzw löscht sie
**/
/** Achung, dies ist ein privates Script zur Nutzung der Bibliothek und nicht sehr komfortabel. Es macht, was es machen soll. **/
Dieses Modul erzeugt Benutzernamen und Passwörter. Hier nicht mit dabei
// import { Helfer } from "./helfer.ts";
import { Schild } from "./schild.ts";
import { UCS } from "./ucs.ts";
// zwei Config-Dateien für die Zugangsdaten zu Schild-DB und UCS
import CONFIG from './ucsimport.config.json' with { type: 'json' };
import SCHILDCONFIG from './schildimport.config.json' with { type: 'json' };
const { bk } = CONFIG;
const cb = new UCS(bk);
const okb = await cb.init();
if ((okb === false))
throw Error('Initialisierung fehlgeschlagen');
const exportUser = await cb.getSchueler();
// console.log(exportUser)
// const exportKlassen = await cb.getGroups();
const setKlassen = new Set<string>();
const setSchuelerID = new Set<string>();
const mapSchueler: Map<string, {username: string, dn: string}> = new Map();
// Hier werden die existierenden Benutzer analysiert und in den Sets festgehalten
for (const u of exportUser) {
const s = {
username: u.id,
dn: u.dn,
}
const groups = u.properties.groups;
let klasse = null;
for (const g of groups) {
const split = g.split(",")
for (const cn of split) {
if (cn === "cn=klassen") {
klasse = split[0].slice(3);
break
}
}
}
mapSchueler.set(s.username, s);
if (klasse !== null)
setKlassen.add(klasse);
setSchuelerID.add(s.username);
}
// Nun die Schülerdaten aus Schild holen, wir verwenden zwei Datenbanken, diese müssen dann später zusammengefühtr werden
const schild_kbk2 = new Schild(SCHILDCONFIG.kbk2);
await schild_kbk2.open();
const res2 = await schild_kbk2.getSchueler();
// const resLehrer2 = await schild_kbk2.getLehrer();
await schild_kbk2.close();
const schild_kbk = new Schild(SCHILDCONFIG.kbk);
await schild_kbk.open();
const res = await schild_kbk.getSchueler();
// wenn man das hier braucht, unkommentieren
// const resLehrer = await schild_kbk.getLehrer();
await schild_kbk.close();
// dieses Modul erzeugt die Nutzernamen und Passwörter
const importUser = Helfer.updater(res2.concat(res));
const setKlassenNeu = new Set<string>();
const setSchuelerIDNeu = new Set<string>();
const mapImport = new Map();
for (const u of importUser) {
u.domain = "example.com";
if (u.Klasse !== undefined && u.Klasse !== null)
setKlassenNeu.add(u.Klasse);
setSchuelerIDNeu.add(u.username!);
mapImport.set(u.username, u);
}
Hier werden nun die diffs zwischen alten und neuen Nutzern ermittelt
const addKlassen = setKlassenNeu.difference(setKlassen);
const delKlassen = setKlassen.difference(setKlassenNeu);
const addSchueler = setSchuelerIDNeu.difference(setSchuelerID);
const delSchueler = setSchuelerID.difference(setSchuelerIDNeu);
const modSchueler = setSchuelerID.intersection(setSchuelerIDNeu);
console.log("Klassen nur alt: ", delKlassen);
console.log("Klassen nur neu: ", addKlassen);
console.log("Schüler nur alt: ", delSchueler);
console.log("Schüler nur neu: ", addSchueler);
console.log("Schüler zu modifizieren: ", modSchueler.size);
Wenn man nur die Ausgabe haben und nichts in UCS einspielen möchte, hier mittels throw unterbrechen, ansonsten kommentieren und ausführen lassen
throw Error();
// Lösche nicht benötigte Klassen
// wenn man das will, unkommentieren
// for (const k of delKlassen) {
// cs.delGroup()
// }
// Ergänze noch unbekannte Klassen
for (const k of addKlassen) {
// unkommentieren bei Bedarf
// await cb.addGroup(k);
}
console.log("Alle neuen Klassen erstellt: ", addKlassen.size);
// lösche unbekannte Nutzer
for (const s of delSchueler) {
const schueler = mapSchueler.get(s);
if (schueler === undefined)
continue;
console.log("lösche: ", schueler.username)
await cb.delUser(schueler.dn);
}
console.log("Alle unbekannten Schüler entfernt: ", delSchueler.size);
// erzeuge neue Nutzer
for (const s of addSchueler) {
const schueler = mapImport.get(s);
if (schueler === undefined)
continue;
console.log("add: ", schueler.username)
await cb.addSchueler(schueler);
}
console.log("Alle neuen Schüler angelegt: ", addSchueler.size);
// wenn man modifizieren möchte, dies hier wieder unkommentieren
// modifiziere alte Nutzer
// for (const s of modSchueler) {
// const schuelerAlt = mapSchueler.get(s);
// const schuelerNeu = mapImport.get(s);
// if (schuelerAlt === undefined || schuelerNeu === undefined)
// continue;
// console.log("modifiziere: ", schuelerAlt.username)
// // console.log(await cb.modSchueler(schuelerAlt.dn, schuelerNeu));
// }
console.log("Alle bekannten Schüler modifiziert: ", modSchueler.size);
import mysql from "mysql2/promise";
/**
* Standardbenutzer aus Schild
*/
export type SchildUser = {
Name: string,
Vorname: string,
Klasse?: string,
Geburtsdatum?: string | Date,
ID?: number,
GU_ID?: string,
Kuerzel?: string,
username?: string,
Geschlecht?: string,
password?: string,
domain?: string,
dn?: string,
}
/**
* Diese Klasse kann mit einer Schild-Datenbank kommunizieren
*/
export class Schild {
/** Die Verbindungsdaten zum Server */
private _connectionConfig: Partial<mysql.ConnectionConfig>;
/** Die Verbindung zum Server */
private _connection: mysql.Connection | null = null;
public constructor(connection: Partial<mysql.ConnectionConfig> = {}) {
const config: Partial<mysql.ConnectionConfig> = {};
config.host = connection.host ?? 'localhost';
config.database = connection.database ?? 'schild';
config.port = connection.port ?? 3306;
config.user = connection.user ?? '';
config.password = connection.password ?? ''
this._connectionConfig = config;
}
/**
* Öffnet eine Verbindung zur Schild DB
*
* @returns die Verbindung
*/
public async open(): Promise<mysql.Connection> {
this._connection = await mysql.createConnection(this._connectionConfig);
return this._connection;
}
/**
* Schließt die Verbindung zur Datenbank
*
* @returns die geschlossene Verbindung oder null bei Misserfolg
*/
public async close() {
return await this.connection.end() ?? null;
}
/**
* Gibt die Verbindung zur Schild-DB zurück
*/
get connection(): mysql.Connection {
if (this._connection === null)
throw Error('Keine Verbindung');
return this._connection;
}
/**
* Gibt alle aktiven Schüler aus der DB zurück
*
* @returns alle aktiven Schüler aus Schild
*/
public async getSchueler(): Promise<SchildUser[]> {
const [results] = await this.connection.query(`SELECT s.ID, Name, Vorname, Klasse, Geburtsdatum, GU_ID
FROM schueler s
WHERE s.Status = 2 AND s.Geloescht = "-" AND s.Gesperrt = "-"
ORDER BY Klasse, Name ASC`);
return results as SchildUser[];
}
/**
* Gibt alle aktuellen Lehrer aus Schild zurück
*
* @returns alle aktiven Lehrer aus Schild
*/
public async getLehrer(): Promise<SchildUser[]> {
const [results] = await this.connection.query(`SELECT ID, Nachname, Vorname, Kuerzel, GU_ID
FROM k_lehrer WHERE Sichtbar="+"
ORDER BY Nachname ASC`);
return results as SchildUser[];
}
}
import type { SchildUser } from "./schild.ts";
/**
* Diese Klasse kann zum Lesen und Schreiben des UCS-Servers verwendet werden.
* Dazu wird die UDM-Rest-Api verwendet.
* Hier werden jedoch nur Methoden angeboten, die zum Bearbeiten der Nutzerkonten benötigt werden und ein gewisses Schema voraussetzen,
* User sind aufgeteilt in zwei Gruppen, Lehrer und Schüler, liegen auch in zwei CN.
*/
export class UCS {
private _server: string;
private _headers = new Headers();
private _userBase: string;
private _groupBase: string = "";
constructor(connection: { user: string, password: string, server: string, userBase: string }) {
this._server = connection.server;
this._userBase = connection.userBase;
this._headers.append('Accept', 'application/json');
this._headers.append('Content-Type', 'application/json');
this._headers.append("Accept-Language", "en-US,en;q=0.5");
this._headers.append('Authorization', `Basic ${btoa(connection.user + ':' + connection.password)}`);
}
/**
* Die Methode wird von fast allen anderen Methoden für die Fetches verwendet
*
* @param url die URL
* @param method die zu verwendende Fetch-Methoden
* @param body Der Body, der bei Bedarf mitgeschickt wird
* @returns gibt den Inhalt als JSON zurück
*/
public async useFetch(url: URL, method="GET", body?: string) {
const headers = this._headers;
const res = await fetch(url, { headers, method, body });
if (res.ok && method !== 'DELETE') {
return await res.json();
}
}
/**
* Diese Methode holt alle Lehrer aus dem LDAP
*
* @returns gibt alle Lehrer als LDAP-Objekte zurück
*/
public async getLehrer() {
const queryAll = new URL(`${this._server}/users/user/?position=cn=lehrer,${this._userBase}&hidden=1&property=&query[]=*`);
const res = await this.useFetch(queryAll);
return res._embedded["udm:object"] ?? [];
}
/**
* Diese Methode holt alle Schüler aus dem LDAP
*
* @returns gibt alle Schüler als LDAP-Objekte zurück
*/
public async getSchueler() {
const queryAll = new URL(`${this._server}/users/user/?position=cn=schueler,${this._userBase}&hidden=1&property=&query[]=*`);
const res = await this.useFetch(queryAll);
return res._embedded["udm:object"] ?? [];
}
/**
* Diese Methode holt alle Benutzer aus dem LDAP
*
* @returns gibt alle Benutzer als LDAP-Objekte zurück
*/
public async getUsers() {
const queryAll = new URL(`${this._server}/users/user/?property=&query[]=*`);
const res = await this.useFetch(queryAll);
return res._embedded["udm:object"] ?? [];
}
/**
* Diese Methode holt einen Benutzer aus dem LDAP
*
* @param user übergebe entweder DN oder URL mit der DN
* @returns gibt einen Benutzer als LDAP-Objekt zurück
*/
public async getUser(user: URL): Promise<any>;
public async getUser(user: string): Promise<any>;
public async getUser(user: string | URL): Promise<any> {
let url: URL;
if (typeof user === 'string')
url = new URL(`${this._server}/users/user/${user}`);
else
url = user;
return await this.useFetch(url);
}
/**
* Holt das User-Template des Servers
*
* @returns gibt das User-Template zurück
*/
public async getUserTemplate() {
const url = new URL(`${this._server}/users/user/add`);
const res = await this.useFetch(url);
if (res === false)
throw new Error("Es konnte kein User-Template geladen werden");
this._userBase = res.position;
return res;
}
/**
* Ergänzt einen Schüler im LDAP
*
* @param schueler Die Daten eines einzupflegenden Schülers
* @returns Gibt die JSON-Response zurück
*/
public async addSchueler(schueler: SchildUser) {
const template = await this.getUserTemplate();
template.position = 'cn=schueler,'+template.position;
template.properties.groups.push(`cn=${schueler.Klasse},cn=klassen,${this._groupBase}`);
template.properties.groups.push(`cn=schueler,${this._groupBase}`);
template.properties.password = schueler.password;
template.properties.lastname = schueler.Name;
template.properties.firstname = schueler.Vorname;
template.properties.username = schueler.username;
template.properties.birthday = schueler.Geburtsdatum;
template.properties["e-mail"].push(`${schueler.username}@${schueler.domain}`);
const create = new URL(`${this._server}/users/user/`);
return await this.useFetch(create, 'POST', JSON.stringify(template));
}
/**
* Ergänzt einen Lehrer im LDAP
*
* @param lehrer Die Daten eines einzupflegenden Lehrers
* @returns Gibt die JSON-Response zurück
*/
public async addLehrer(lehrer: SchildUser) {
const template = await this.getUserTemplate();
template.position = 'cn=lehrer,'+template.position;
template.properties.groups.push(`cn=lehrer,${this._groupBase}`);
template.properties.password = lehrer.password;
template.properties.lastname = lehrer.Name;
template.properties.firstname = lehrer.Vorname;
template.properties.username = lehrer.username;
template.properties["e-mail"].push(`${lehrer.username}@${lehrer.domain}`);
const create = new URL(`${this._server}/users/user/`);
return await this.useFetch(create, 'POST', JSON.stringify(template));
}
/**
* Modifiziert einen Schüler im LDAP
*
* @param dn Die DN eines Schülers
* @param schueler Die Daten des Schülers
* @returns Gibt an, ob die Operation erfolgreich war
*/
public async modSchueler(dn: string, schueler: SchildUser) {
const url = new URL(`${this._server}/users/user/${dn}`);
const headers = new Headers([...this._headers]);
const resGet = await fetch(url, { headers });
const etag = resGet.headers.get('etag');
const template = await resGet.json();
template.properties.groups = [];
template.properties.groups.push(`cn=${schueler.Klasse},cn=klassen,${this._groupBase}`);
template.properties.groups.push(`cn=schueler,${this._groupBase}`);
template.properties.groups.push(template.properties.primaryGroup);
template.properties.password = schueler.password;
template.properties.lastname = schueler.Name;
template.properties.firstname = schueler.Vorname;
template.properties.username = schueler.username;
template.properties.birthday = schueler.Geburtsdatum;
template.properties["e-mail"] = [`${schueler.username}@${schueler.domain}`];
const mod = new URL(`${this._server}/users/user/${dn}`);
if (etag !== null)
headers.append('If-Match', etag);
const resPut = await fetch(mod, { headers, method: 'PUT', body: JSON.stringify(template) });
return resPut.ok;
// return await this.useFetch(create, 'PUT', JSON.stringify(template));
}
/**
* Modifiziert die Mailadresse eines Lehrers
*
* @param dn die DN des Lehrers
* @param lehrer Die Daten des Lehrers
* @returns Gibt an, ob die Operation erfolgreich war
*/
public async modUserMPA(dn: string, lehrer: { mailPrimaryAddress: string }) {
const url = new URL(`${this._server}/users/user/${dn}`);
const headers = new Headers([...this._headers]);
const resGet = await fetch(url, { headers });
const etag = resGet.headers.get('etag');
const template = await resGet.json();
template.properties.mailPrimaryAddress = lehrer.mailPrimaryAddress;
const mod = new URL(`${this._server}/users/user/${dn}`);
if (etag !== null)
headers.append('If-Match', etag);
const resPut = await fetch(mod, { headers, method: 'PUT', body: JSON.stringify(template) });
return resPut.ok;
}
/**
* Ergänzt MS365 bei einem Schüler
*
* @param dn die DN des Schülers
* @returns Gibt an, ob die Operation erfolgreich war
*/
public async modUserMS365(dn: string) {
const url = new URL(`${this._server}/users/user/${dn}`);
const headers = new Headers([...this._headers]);
const resGet = await fetch(url, { headers });
const etag = resGet.headers.get('etag');
const template = await resGet.json();
template.properties.mailPrimaryAddress ??= template.properties["e-mail"][0];
template.properties.UniventionOffice365Enabled = true;
template.properties.UniventionOffice365ADConnections = [
{
AADConnection: "defaultADconnection",
userPrincipalName: template.properties.mailPrimaryAddress,
}
];
const mod = new URL(`${this._server}/users/user/${dn}`);
if (etag !== null)
headers.append('If-Match', etag);
const resPut = await fetch(mod, { headers, method: 'PUT', body: JSON.stringify(template) });
console.log(resPut, await resPut.text())
return resPut.ok;
}
/**
* Entfernt einen Benutzer
*
* @param user DN oder URL eines Benutzers
*/
public async delUser(user: URL): Promise<any>;
public async delUser(user: string): Promise<any>;
public async delUser(user: string | URL): Promise<any> {
let del: URL;
if (typeof user === 'string')
del = new URL(`${this._server}/users/user/${user}`);
else
del = user;
return await this.useFetch(del, 'DELETE');
}
/**
* Holt ein Gruppen-Template des Servers
*
* @returns ein Gruppen-Template
*/
public async getGroupTemplate() {
const url = new URL(`${this._server}/groups/group/add`);
const res = await this.useFetch(url);
if (res === false)
throw new Error("Es konnte kein Group-Template geladen werden");
this._groupBase = res.position;
return res;
}
/**
* Holt alle Gruppen des Servers
*
* @returns alle Gruppen des Servers
*/
public async getGroups() {
const queryAll = new URL(`${this._server}/groups/group/?property=&query[]=*`);
const res = await this.useFetch(queryAll);
return res._embedded["udm:object"] ?? [];
}
/**
* Holt eine Grupe vom Server
*
* @param group DN oder URL einer Gruppe
*/
public async getGroup(group: URL): Promise<any>;
public async getGroup(group: string): Promise<any>;
public async getGroup(group: string | URL): Promise<any> {
let url: URL;
if (typeof group === 'string')
url = new URL(`${this._server}/groups/group/${group}`);
else
url = group;
return await this.useFetch(url);
}
/**
* Erzeugt eine Gruppe auf dem LDAP
*
* @param cn Name der Gruppe
* @returns Gibt ein JSON-Response der Operation zurück
*/
public async addGroup(cn: string) {
const template = await this.getGroupTemplate();
template.position = 'cn=klassen,'+template.position;
template.properties.name = cn;
const create = new URL(`${this._server}/groups/group/`);
return await this.useFetch(create, 'POST', JSON.stringify(template));
}
/**
* Löscht eine Gruppe auf dem LDAP
*
* @param group Name der Gruppe
*/
public async delGroup(group: URL): Promise<any>;
public async delGroup(group: string): Promise<any>;
public async delGroup(group: string | URL): Promise<any> {
let del: URL;
if (typeof group === 'string')
del = new URL(`${this._server}/groups/group/${group}`);
else
del = group;
return await this.useFetch(del, 'DELETE');
}
/**
* Initialisiert die Instanz und holt die Templates vom Server
*
* @returns gibt an, ob die Operation erfolgreich war
*/
public async init() {
const g = await this.getGroupTemplate();
const u = await this.getUserTemplate();
return (g !== false) && (u !== false);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment