New user provider for nextcloud migration (#337)

This commit is contained in:
Anton Shepilov
2024-01-22 21:32:32 +03:00
committed by GitHub
parent 18ca9444ca
commit 184b9acbe3
10 changed files with 160 additions and 63 deletions
@@ -1,6 +1,7 @@
import express, { Express, Request, Response } from "express";
import { NextcloudMigration, NextcloudMigrationConfiguration } from './nextcloud_migration.js';
import { logger } from "./logger"
import { UserProviderType } from "./user/user_privider";
const app: Express = express();
const port = process.env.SERVER_PORT || 3000;
@@ -13,6 +14,10 @@ const config: NextcloudMigrationConfiguration = {
baseDn: process.env.LDAP_BASE!,
url: process.env.LDAP_URL!,
},
lemon: {
url: process.env.LEMON_USERS_URL!,
auth: process.env.LEMON_USERS_AUTH!,
},
tmpDir: process.env.TMP_DIR || '/tmp',
nextcloudUrl: process.env.NEXTCLOUD_URL!,
drive: {
@@ -21,7 +26,8 @@ const config: NextcloudMigrationConfiguration = {
appId: process.env.TWAKE_DRIVE_APP_ID!,
secret: process.env.TWAKE_DRIVE_SECRET!,
}
}
},
userProvider: process.env.USER_PROVIDER! as UserProviderType
}
if (!config.ldap.baseDn) {
@@ -1,17 +1,21 @@
import { exec } from 'child_process';
// @ts-ignore
import fs from 'fs';
import { LdapUser } from './shell_ldap_user';
import { User } from './ldap_user.js';
import { ShellLdapUserProvider } from './user/shell_ldap_user';
import { TwakeDriveClient, TwakeDriveUser } from './twake_client';
import path from 'path';
import { logger } from "./logger"
import { User, UserProvider, UserProviderFactory, UserProviderType } from "./user/user_privider";
export type NextcloudMigrationConfiguration = {
ldap: {
export interface NextcloudMigrationConfiguration {
shell: {
baseDn: string,
url: string,
},
lemon: {
url: string,
auth: string,
}
drive: {
url: string,
credentials: {
@@ -21,19 +25,20 @@ export type NextcloudMigrationConfiguration = {
},
tmpDir: string,
nextcloudUrl: string
userProvider: UserProviderType
}
export class NextcloudMigration {
private config: NextcloudMigrationConfiguration;
private ldap: LdapUser;
private userProvider: UserProvider;
driveClient: TwakeDriveClient;
constructor(config: NextcloudMigrationConfiguration) {
this.config = config;
this.ldap = new LdapUser(config.ldap);
this.userProvider = (new UserProviderFactory()).get(config.userProvider, config[config.userProvider]);
this.driveClient = new TwakeDriveClient(this.config.drive);
}
@@ -79,7 +84,7 @@ export class NextcloudMigration {
}
async getLDAPUser(username: string): Promise<User> {
const user = await this.ldap.find(username);
const user = await this.userProvider.find(username);
if (!user.email) {
throw new Error(`User ${username} not found`);
}
@@ -1,9 +1,9 @@
import axios from 'axios';
import { User } from './ldap_user';
import FormData from 'form-data';
// @ts-ignore
import fs from 'fs';
import { logger } from './logger';
import { User } from "./user/user_privider";
type TwakeClientConfiguration = {
url: string,
@@ -15,7 +15,7 @@ type TwakeClientConfiguration = {
export class TwakeDriveClient {
private config: TwakeClientConfiguration;
private readonly config: TwakeClientConfiguration;
constructor(config: TwakeClientConfiguration) {
this.config = config;
@@ -1,20 +1,14 @@
import ldap, { SearchEntry, SearchOptions } from 'ldapjs';
import { logger } from "./logger"
import ldap, { SearchOptions } from "ldapjs";
import { logger } from "../logger";
import { User, UserProvider } from "./user_privider";
export type LdapConfiguration = {
url: string,
baseDn: string,
}
export type User = {
firstName: string,
lastName: string,
email: string,
uid: string
}
// Doesn't work, fix it later, somehow none of the events is called for the search request
export class LdapUser {
export class LdapUserProvider implements UserProvider {
private config: LdapConfiguration;
@@ -24,38 +18,6 @@ export class LdapUser {
this.config = config;
}
async auth(username: string, password: string) {
return new Promise((resolve, reject) => {
this.client?.bind(username, password, (error) => {
if (error) {
reject(new Error("Authentication error"));
} else {
logger.info("Successfully authenticated in LDAP")
resolve(this.client);
}
});
});
}
async connect() {
return new Promise((resolve, reject) => {
if (!this.client) {
this.client = ldap.createClient({
url: this.config.url,
reconnect: true
});
this.client.on('connect', (res) => {
logger.info("Connected to LDAP")
resolve(this.auth("", ""));
})
this.client.on('connectionError', (error) => {
logger.info("Error connecting to LDAP")
reject(error);
});
}
});
}
async find(username: string): Promise<User> {
const search = await this.search(username);
return new Promise<User>((resolve, reject) => {
@@ -0,0 +1,45 @@
import { User, UserProvider } from "./user_privider";
import axios, { AxiosInstance } from "axios";
export interface LemonLdapUserProviderConfig {
url: string,
auth: string,
}
export class LemonLdapUserProvider implements UserProvider {
private client: AxiosInstance;
constructor(private config: LemonLdapUserProviderConfig) {
this.client = axios.create({
baseURL: config.url,
headers: {
Authorization: `Bearer ${config.auth}`,
},
});
}
async find(username: string): Promise<User> {
const response = await this.client.post("", [username]);
if (response.status == 200 && response.data) {
const u: any = response.data[username];
if (u && u.length > 0) {
if (u) {
const user = {
firstName: u[0].filter(f => f[0] === "givenName").map(f => f[1])[0],
lastName: u[0].filter(f => f[0] === "sn").map(f => f[1])[0],
email: u[0].filter(f => f[0] === "mail").map(f => f[1])[0],
uid: u[0].filter(f => f[0] === "uid").map(f => f[1])[0]
}
if (user.uid && !user.email) {
user.email = `${user.uid}@cnb.linagora.com`;
}
return user;
}
}
}
return {} as User;
}
}
@@ -1,9 +1,10 @@
import { LdapConfiguration, User } from './ldap_user';
import { LdapConfiguration } from './ldap_user';
import { exec } from 'child_process';
import ldif from 'ldif';
import { logger } from "./logger"
import { logger } from "../logger"
import { User, UserProvider } from "./user_privider";
export class LdapUser {
export class ShellLdapUserProvider implements UserProvider {
private config: LdapConfiguration;
@@ -12,7 +13,7 @@ export class LdapUser {
}
async find(username: string): Promise<User> {
return new Promise((resolve, reject) => {
return new Promise<User>((resolve, reject) => {
let cmd = `ldapsearch -x -H ${this.config.url} -b '${this.config.baseDn}' '(uid=${username})'`;
logger.info("Executing command to get data from LDAP for " + username);
exec(cmd, (error, stdout, stderr) => {
@@ -0,0 +1,33 @@
import { ShellLdapUserProvider } from "./shell_ldap_user";
import { LemonLdapUserProvider } from "./lemon_ldap_user_provider";
import { LdapUserProvider } from "./ldap_user";
export type User = {
firstName: string,
lastName: string,
email: string,
uid: string
}
export type UserProviderType = "lemon" | "ldap" | "shell";
export interface UserProvider {
find(username: string): Promise<User>
}
export class UserProviderFactory {
get(type: UserProviderType, config: any) {
switch (type) {
case "shell":
return new ShellLdapUserProvider(config)
case "ldap":
return new LdapUserProvider(config);
case "lemon":
return new LemonLdapUserProvider(config);
default:
return new ShellLdapUserProvider(config);
}
}
}
@@ -0,0 +1,40 @@
import {describe, expect, test} from '@jest/globals';
import { LemonLdapUserProvider, LemonLdapUserProviderConfig } from "../src/user/lemon_ldap_user_provider";
//FOR LOCAL DEBUG PURPOSE ONLY, ITS NOT A TEST
describe.skip('Lemon LDAP User Provider', () => {
test('ldap returns user info', async () => {
const ldap = new LemonLdapUserProvider({
url: "https://auth.avocat.fr/_getldapentries",
auth: "isPnOTuBwjqAlIdCFGNY",
});
const user = await ldap.find("999248");
expect(user.firstName).toBe("Xavier");
expect(user.lastName).toBe("GUIMARD");
expect(user.email).toBe("999248@cnb.linagora.com");
expect(user.uid).toBe("999248");
});
test('ldap returns user info with email', async () => {
const ldap = new LemonLdapUserProvider({
url: "https://auth.avocat.fr/_getldapentries",
auth: "isPnOTuBwjqAlIdCFGNY",
});
const user = await ldap.find("999253");
expect(user.firstName).toBe("Utilisateur4");
expect(user.lastName).toBe("ONBOARDING4");
expect(user.email).toBe("utilisateur4.onboarding4@avocat.fr");
expect(user.uid).toBe("999253");
});
test('ldap returns nothing', async () => {
const ldap = new LemonLdapUserProvider({
url: "https://auth.avocat.fr/_getldapentries",
auth: "isPnOTuBwjqAlIdCFGNY",
});
const user = await ldap.find("NOTHING_HERE");
expect(user.email).toBeUndefined();
});
});
@@ -14,7 +14,12 @@ describe.skip('nextcloud migration module', () => {
appId: "tdrive_onlyoffice",
secret: "c1cc66db78e1d3bb4713c55d5ab2"
}
}
},
userProvider: "lemon",
lemon: {
url: "url",
auth: "key"
},
} as NextcloudMigrationConfiguration);
let user = await subj.driveClient.createUser({firstName: "DWHO", lastName: "DWHO", email: "dwho@example.com", uid: "test"});
@@ -1,24 +1,24 @@
import {describe, expect, test} from '@jest/globals';
import { LdapUser } from '../src/shell_ldap_user';
import { LdapConfiguration } from '../src/ldap_user';
import { ShellLdapUserProvider } from '../src/user/shell_ldap_user';
import { LdapConfiguration } from '../src/user/ldap_user';
//FOR LOCAL DEBUG PURPOSE ONLY, ITS NOT A TEST
describe.skip('ldap module', () => {
describe.skip('Shell LDAP User Provider', () => {
test('ldap returns user info', async () => {
const ldap = new LdapUser({
const ldap = new ShellLdapUserProvider({
url: "ldap://auth.poc-mail-avocat.fr:389",
baseDn: "ou=users,dc=example,dc=com",
} as LdapConfiguration);
const user = await ldap.find("999248");
expect(user.firstName).toBe("Xavier");
expect(user.lastName).toBe("GUIMARD");
expect(user.email).toBe("xguimard@avocat.fr");
expect(user.email).toBe("xguimard@preprod-avocat.fr");
expect(user.uid).toBe("999248");
});
test('ldap returns nothing', async () => {
const ldap = new LdapUser({
const ldap = new ShellLdapUserProvider({
url: "ldap://auth.poc-mail-avocat.fr:389",
baseDn: "ou=users,dc=example,dc=com",
} as LdapConfiguration);