From 792fda60f1d88b6d245021ebdd10eab06ec05bcf Mon Sep 17 00:00:00 2001 From: Gijs de Man Date: Mon, 21 Aug 2023 14:35:44 +0200 Subject: [PATCH] Small testing --- src/dovecotAPI.ts | 26 +++++++---------- src/index.ts | 60 +++++++++++++++++++-------------------- src/localUserDatabase.ts | 61 +++++++++++++++++++--------------------- src/mailcowAPI.ts | 47 ++++++------------------------- src/mailcowDatabase.ts | 18 ++++++------ src/types.ts | 6 ++-- 6 files changed, 89 insertions(+), 129 deletions(-) diff --git a/src/dovecotAPI.ts b/src/dovecotAPI.ts index ddf225b..7de99bf 100644 --- a/src/dovecotAPI.ts +++ b/src/dovecotAPI.ts @@ -1,8 +1,7 @@ import axios, { AxiosInstance } from 'axios'; import { - DoveadmExchangeResult, + DovecotData, DovecotRequestData, - DovecotResponseExchange, DovecotPermissions, ActiveDirectoryPermissions, } from './types'; @@ -12,7 +11,6 @@ let dovecotClient: AxiosInstance; export async function initializeDovecotAPI(): Promise { dovecotClient = axios.create({ - // baseURL: `${config.DOVEADM_API_HOST}/doveadm/v1`, baseURL: 'http://172.22.1.250:9000/doveadm/v1', headers: { 'Content-Type': 'text/plain', @@ -25,9 +23,8 @@ export async function initializeDovecotAPI(): Promise { * Get all mailboxes of an email * @param email - email to get all inboxes from */ -async function getMailboxes(email: string): Promise { - // Get all mailboxes - const response = (await dovecotClient.post( +async function getMailboxSubfolders(email: string): Promise { + const subFolders: DovecotData[] = ((await dovecotClient.post( '', [[ 'mailboxList', @@ -36,15 +33,13 @@ async function getMailboxes(email: string): Promise { }, `mailboxList_${email}`, ]], - )).data as DovecotResponseExchange; + )).data)[0][1]; - // Convert response to array of mailboxes - const mailboxObjects: DoveadmExchangeResult[] = response[0][1]; - - return mailboxObjects.filter(function (item) { - return !item.mailbox.startsWith('Shared'); - }).map((item: DoveadmExchangeResult) => { - return item.mailbox; + // TODO change to reduce + return subFolders.filter(function (subFolder: DovecotData) { + return !subFolder.mailbox.startsWith('Shared'); + }).map((subFolder: DovecotData) => { + return subFolder.mailbox; }); } @@ -74,7 +69,7 @@ export async function setDovecotPermissions(mail: string, users: string[], permi } if (permission == ActiveDirectoryPermissions.mailPermRO || ActiveDirectoryPermissions.mailPermRW) { - mailboxSubFolders = await getMailboxes(mail); + mailboxSubFolders = await getMailboxSubfolders(mail); permissionTag = 'PermRO'; } @@ -103,7 +98,6 @@ export async function setDovecotPermissions(mail: string, users: string[], permi } const dovecotRequest: DovecotRequestData = [ - // Check if users should be removed or added removePermission ? 'aclRemove' : 'aclSet', { 'user': mail, diff --git a/src/index.ts b/src/index.ts index a3f63b8..2dbe853 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,10 @@ import { Client } from 'ldapts'; import { - activityUserDB, - addUserDB, + updateLocalUserActivity, + createLocalUser, getLocalUser, createSOBDB, - getChangedSOBDB, + getUpdateSOBLocalUsers, getUncheckedLocalActiveUsers, initializeLocalUserDatabase, updateLocalUserPermissions, @@ -17,9 +17,9 @@ import fs, { PathLike } from 'fs'; import path from 'path'; import { SearchResult } from 'ldapts/Client'; import { - addUserAPI, + createMailcowUser, getMailcowUser, - editUserAPI, + editMailcowUser, initializeMailcowAPI, } from './mailcowAPI'; import { @@ -131,17 +131,18 @@ async function synchronizeUserSOB(activeDirectoryUser: ActiveDirectoryUser): Pro // Construct list in database with DN of all committees they are in // Get existing list of committees, add new DN as string // TODO check, I think this can be the same as syncACL where you just pick first entry instead of looping + console.log(activeDirectoryGroup.length); for (const members of activeDirectoryGroup) { // Singular entries are possible, so turn them into an array if (!Array.isArray(members.memberFlattened)) members.memberFlattened = [members.memberFlattened]; for (const member of members.memberFlattened) { - const memberResults: ActiveDirectoryUser[] = (await activeDirectoryConnector.search(member, { + const memberResults: ActiveDirectoryUser = ((await activeDirectoryConnector.search(member, { scope: 'sub', attributes: ['mail'], - })).searchEntries as unknown as ActiveDirectoryUser[]; - await createSOBDB(memberResults[0].mail, activeDirectoryUser.mail); + })).searchEntries)[0] as unknown as ActiveDirectoryUser; + await createSOBDB(memberResults.mail, activeDirectoryUser.mail); } } } @@ -299,7 +300,7 @@ async function getUserDataFromActiveDirectory(): Promise { })).searchEntries as unknown as ActiveDirectoryUser[]; } if (retryCount === maxRetryCount) throw new Error('Ran into an issue when getting users from Active Directory.'); - console.log('Successfully got all users from Active Directory'); + console.log('Successfully got all users from Active Directory. \n\n'); } @@ -320,14 +321,14 @@ async function synchronizeUsersWithActiveDirectory(): Promise { if (!localUser.exists) { console.log(`Adding local user ${mail} (active: ${isActive})`); - await addUserDB(mail, displayName, isActive); + await createLocalUser(mail, displayName, isActive); localUser.exists = true; localUser.isActive = isActive; } if (!mailcowUser.exists) { console.log(`Adding Mailcow user ${mail} (active: ${isActive})`); - await addUserAPI(mail, displayName, isActive, 256); + await createMailcowUser(mail, displayName, isActive, 256); mailcowUser.exists = true; mailcowUser.isActive = isActive; mailcowUser.displayName = displayName; @@ -335,23 +336,24 @@ async function synchronizeUsersWithActiveDirectory(): Promise { if (localUser.isActive !== isActive) { console.log(`Set ${mail} to active state ${isActive} in local user database`); - await activityUserDB(mail, isActive, 0); + await updateLocalUserActivity(mail, isActive, 0); } if (mailcowUser.isActive !== isActive) { console.log(`Set ${mail} to active state ${isActive} in Mailcow`); - await editUserAPI(mail, { active: isActive }); + await editMailcowUser(mail, { active: isActive }); } if (mailcowUser.displayName !== displayName) { console.log(`Changed displayname for ${mail} to ${displayName} in Mailcow`); - await editUserAPI(mail, { name: displayName }); + await editMailcowUser(mail, { name: displayName }); } } catch (error) { - console.error(`Ran into an issue when syncing user ${activeDirectoryUser.mail}: ${error}`); + console.error(`Ran into an issue when syncing user ${activeDirectoryUser.mail}. \n\n ${error}`); } } + // Users that were not checked might have to be removed from mailcow for (const user of await getUncheckedLocalActiveUsers()) { try { const mailcowUserData: MailcowUserData = await getMailcowUser(user.email); @@ -363,22 +365,21 @@ async function synchronizeUsersWithActiveDirectory(): Promise { if (inactiveCount > maxInactiveCount) { console.log(`Deactivated user ${user.email} in local user database, not found in LDAP`); - await activityUserDB(user.email, 0, 255); + await updateLocalUserActivity(user.email, 0, 255); } else { console.log(`Increased inactive count to ${inactiveCount + 1} for ${user.email}`); - await activityUserDB(user.email, 2, inactiveCount + 1); + await updateLocalUserActivity(user.email, 2, inactiveCount + 1); } - // Check if user is still active, if so, deactivate user if (mailcowUserData.isActive && localUserData.isActive === 0) { - console.log(`Deactivated user ${user.email} in Mailcow, not found in LDAP`); - await editUserAPI(user.email, { active: 0 }); + console.log(`Deactivated user ${user.email} in Mailcow, not found in Active Directory`); + await editMailcowUser(user.email, { active: 0 }); } } catch (error) { - console.log(`Exception throw during handling of ${user}: ${error}`); + console.log(`Ran into an issue when checking inactivity of ${user.email}. \n\n ${error}`); } } - console.log('Successfully synced all users with Active Directory'); + console.log('Successfully synced all users with Active Directory. \n\n'); } async function synchronizePermissionsWithActiveDirectory(): Promise { @@ -396,22 +397,22 @@ async function synchronizePermissionsWithActiveDirectory(): Promise { if (activeDirectoryUser[ActiveDirectoryPermissions.mailPermSOB].length != 0) await synchronizeUserSOB(activeDirectoryUser); } catch (error) { - console.log(`Ran into an issue when syncing permissions of ${activeDirectoryUser.mail}: ${error}`); + console.log(`Ran into an issue when syncing permissions of ${activeDirectoryUser.mail}. \n\n ${error}`); } } - for (const activeDirectoryUser of await getChangedSOBDB()) { + for (const activeDirectoryUser of await getUpdateSOBLocalUsers()) { try { console.log(`Changing SOB of ${activeDirectoryUser.email}`); const SOBs: string[] = activeDirectoryUser.mailPermSOB.split(';'); - await editUserAPI(activeDirectoryUser.email, { sender_acl: SOBs }); + await editMailcowUser(activeDirectoryUser.email, { sender_acl: SOBs }); await editUserSignature(activeDirectoryUser, SOBs); } catch (error) { - console.log(`Exception throw during handling of ${activeDirectoryUser}: ${error}`); + console.log(`Ran into an issue when syncing send on behalf of ${activeDirectoryUser.email}. \n\n ${error}`); } } - console.log('Successfully synced all permissions with Active Directory'); + console.log('Successfully synced all permissions with Active Directory. \n\n'); } /** @@ -449,7 +450,7 @@ async function initializeSync(): Promise { .catch((error) => { throw new Error('Ran into an issue when reading extra.conf. \n\n' + error); }); - console.log('Successfully adjusted all template files.'); + console.log('Successfully adjusted all template files. \n\n'); console.log(consoleLogLine + '\n APPLYING CONFIG FILES \n' + consoleLogLine); const passDBConfigChanged: boolean = applyConfig('./conf/dovecot/ldap/passdb.conf', passDBConfig); @@ -476,9 +477,6 @@ async function initializeSync(): Promise { console.log(consoleLogLine + '\n SYNCING ALL PERMISSIONS \n' + consoleLogLine); await synchronizePermissionsWithActiveDirectory(); - - console.log(consoleLogLine + '\n CHECKING INACTIVE USERS \n' + consoleLogLine); - await synchronize(); } /** diff --git a/src/localUserDatabase.ts b/src/localUserDatabase.ts index df25830..0d25105 100644 --- a/src/localUserDatabase.ts +++ b/src/localUserDatabase.ts @@ -4,17 +4,8 @@ import fs from 'fs'; import { ActiveDirectoryPermissions, ChangedUsers, ActiveUserSetting, LocalUserData } from './types'; import { sessionTime } from './index'; -// Connection options for the DB -const dataSource = new DataSource({ - type: 'sqlite', - database: './db/ldap-mailcow.sqlite3', - entities: [ - Users, - ], - synchronize: true, -}); - let localUserRepository: Repository; +let dataSource: DataSource; /** * Initialize database connection. Setup database if it does not yet exist @@ -22,6 +13,16 @@ let localUserRepository: Repository; export async function initializeLocalUserDatabase(): Promise { if (!fs.existsSync('./db/ldap-mailcow.sqlite3')) fs.writeFileSync('./db/ldap-mailcow.sqlite3', ''); + + dataSource = new DataSource({ + type: 'sqlite', + database: './db/ldap-mailcow.sqlite3', + entities: [ + Users, + ], + synchronize: true, + }); + dataSource.initialize().catch((error) => console.log(error)); localUserRepository = dataSource.getRepository(Users); } @@ -41,13 +42,13 @@ export async function getUncheckedLocalActiveUsers(): Promise { /** * Add a user to the DB - * @param email - mail entry in the database + * @param mail - mail entry in the database * @param displayName * @param active - whether user is active */ -export async function addUserDB(email: string, displayName: string, active: ActiveUserSetting): Promise { +export async function createLocalUser(mail: string, displayName: string, active: ActiveUserSetting): Promise { const user: Users = Object.assign(new Users(), { - email: email, + email: mail, active: active, displayName: displayName, inactiveCount: 0, @@ -68,9 +69,9 @@ export async function addUserDB(email: string, displayName: string, active: Acti /** * Get a user data from database - * @param email - mail from to be retrieved user + * @param mail - mail from to be retrieved user */ -export async function getLocalUser(email: string): Promise { +export async function getLocalUser(mail: string): Promise { const localUserData: LocalUserData = { exists: false, isActive: 0, @@ -79,7 +80,7 @@ export async function getLocalUser(email: string): Promise { const localUser: Users | null = await localUserRepository.findOne({ where: { - email: email, + email: mail, }, }); @@ -98,18 +99,16 @@ export async function getLocalUser(email: string): Promise { /** * Change user activity status in the DB - * @param email - email of user + * @param mail - email of user * @param active - activity of user * @param inactiveCount - number of times user has been inactive */ -export async function activityUserDB(email: string, active: ActiveUserSetting, inactiveCount: number): Promise { - // Retrieve user with email +export async function updateLocalUserActivity(mail: string, active: ActiveUserSetting, inactiveCount: number): Promise { const user: Users = await localUserRepository.findOneOrFail({ where: { - email: email, + email: mail, }, }); - // Set new activity of user user.active = active; user.inactiveCount = inactiveCount; await localUserRepository.update(user.email, user); @@ -117,19 +116,18 @@ export async function activityUserDB(email: string, active: ActiveUserSetting, i /** * Update user's SOB - * @param email - email of user + * @param mail - email of user * @param SOBEmail - acl to check */ -export async function createSOBDB(email: string, SOBEmail: string): Promise { - // Retrieve user with email +export async function createSOBDB(mail: string, SOBEmail: string): Promise { const user: Users = await localUserRepository.findOneOrFail({ where: { - email: email, + email: mail, }, }); // Check if permissions for ACL are set - const SOB = !user.newMailPermSOB ? [] : user.newMailPermSOB.split(';'); + const SOB: string[] = !user.newMailPermSOB ? [] : user.newMailPermSOB.split(';'); // Check if sob mail is in list (it should not be, but checking does not hurt) if (SOB.indexOf(SOBEmail) === -1) { @@ -139,14 +137,13 @@ export async function createSOBDB(email: string, SOBEmail: string): Promise { - // First, check all users that actually have changed - const users = await localUserRepository.find(); +export async function getUpdateSOBLocalUsers(): Promise { + const users: Users[] = await localUserRepository.find(); const changedUsers : Users[] = []; for (const user of users) { if (user.newMailPermSOB != user.mailPermSOB) { - console.log(`SOB of ${user.email} changed from ${user.mailPermSOB} to ${user.newMailPermSOB}`); + console.log(`SOB of ${user.email} changed from ${user.mailPermSOB} to ${user.newMailPermSOB}.`); user.mailPermSOB = user.newMailPermSOB; changedUsers.push(user); } @@ -182,8 +179,8 @@ export async function updateLocalUserPermissions(mail: string, newUsers: string[ // Filter for users, also filter empty entries const removedUsers : string[] = !user ? [] : user[permission].split(';'); - changedUsers.newUsers = newUsers.filter(x => !removedUsers.includes(x) && x != ''); - changedUsers.removedUsers = removedUsers.filter(x => !newUsers.includes(x) && x != ''); + changedUsers.newUsers = newUsers.filter((innerUser: string) => !removedUsers.includes(innerUser) && innerUser != ''); + changedUsers.removedUsers = removedUsers.filter((innerUser: string) => !newUsers.includes(innerUser) && innerUser != ''); user[permission] = newUsers.join(';'); await localUserRepository.update(user.email, user); diff --git a/src/mailcowAPI.ts b/src/mailcowAPI.ts index 5fc7ae0..3678323 100644 --- a/src/mailcowAPI.ts +++ b/src/mailcowAPI.ts @@ -1,7 +1,6 @@ import MailCowClient from 'ts-mailcow-api'; import { ACLEditRequest, - MailboxDeleteRequest, MailboxEditRequest, MailboxPostRequest, } from 'ts-mailcow-api/src/types'; @@ -13,8 +12,8 @@ import { } from 'ts-mailcow-api/dist/types'; import { containerConfig } from './index'; +const passwordLength: number = 32; let mailcowClient: MailCowClient; -const passwordLength = 32; /** * Initialize database connection. Setup database if it does not yet exist @@ -36,10 +35,10 @@ export async function initializeMailcowAPI(): Promise { * @param length - length of random password */ function generatePassword(length: number): string { - let result = ''; - const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let result: string = ''; + const characters: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const charactersLength: number = characters.length; - for (let i = 0; i < length; i++) + for (let i: number = 0; i < length; i++) result += characters.charAt(Math.floor(Math.random() * charactersLength)); return result; } @@ -51,11 +50,9 @@ function generatePassword(length: number): string { * @param active - activity of the new user * @param quotum - mailbox size of the new user */ -export async function addUserAPI(email: string, name: string, active: number, quotum: number): Promise { - // Generate password +export async function createMailcowUser(email: string, name: string, active: number, quotum: number): Promise { const password: string = generatePassword(passwordLength); - // Set details of the net mailbox const mailboxData: MailboxPostRequest = { // Active: 0 = no incoming mail/no login, 1 = allow both, 2 = custom state: allow incoming mail/no login 'active': active, @@ -70,33 +67,22 @@ export async function addUserAPI(email: string, name: string, active: number, qu 'tls_enforce_out': false, }; - // Create mailbox await mailcowClient.mailbox.create(mailboxData); - // Set ACL data of new mailbox const aclData: ACLEditRequest = { 'items': email, 'attr': { 'user_acl': [ 'spam_alias', - //"tls_policy", 'spam_score', 'spam_policy', 'delimiter_action', - // "syncjobs", - // "eas_reset", - // "sogo_profile_reset", 'quarantine', - // "quarantine_attachments", 'quarantine_notification', - // "quarantine_category", - // "app_passwds", - // "pushover" ], }, }; - // Adjust ACL data of new mailbox await mailcowClient.mailbox.editUserACL(aclData); } @@ -105,24 +91,13 @@ export async function addUserAPI(email: string, name: string, active: number, qu * @param email - email of user to be edited * @param options - options to be edited */ -// Todo add send from ACLs -export async function editUserAPI(email: string, options: Partial): Promise { +export async function editMailcowUser(email: string, options: Partial): Promise { const mailboxData: MailboxEditRequest = { 'items': [email], 'attr': options, }; - await mailcowClient.mailbox.edit(mailboxData); -} -/** - * Delete user from Mailcow - * @param email - */ -export async function deleteUserAPI(email: string): Promise { - const mailboxData: MailboxDeleteRequest = { - 'mailboxes': [email], - }; - await mailcowClient.mailbox.delete(mailboxData); + await mailcowClient.mailbox.edit(mailboxData); } /** @@ -135,13 +110,9 @@ export async function getMailcowUser(email: string): Promise { isActive: 0, }; - // Get mailbox data from user with email - const mailboxData: Mailbox = (await mailcowClient.mailbox.get(email) - .catch(e => { - throw new Error(e); - }))[0]; + // Should only find one user + const mailboxData: Mailbox = (await mailcowClient.mailbox.get(email))[0]; - // If no data, return immediately, otherwise return response data if (!(Object.keys(mailboxData).length === 0 && mailboxData.constructor === Object)) { userData.exists = true; userData.isActive = mailboxData.active_int; diff --git a/src/mailcowDatabase.ts b/src/mailcowDatabase.ts index 9d2cff9..d3b8cf4 100644 --- a/src/mailcowDatabase.ts +++ b/src/mailcowDatabase.ts @@ -43,24 +43,24 @@ export async function initializeMailcowDatabase(): Promise { export async function editUserSignature(user: Users, SOBs: string[]): Promise { console.log(`Changing signatures for ${user.email}`); - let profile = await SogoUserProfileRepository.findOneOrFail({ + let userProfile: SogoUserProfile = await SogoUserProfileRepository.findOneOrFail({ where: { c_uid: user.email, }, }); - let cDefaults : Defaults = JSON.parse(profile.c_defaults); - let newIdentities : SOGoMailIdentity[] = []; + let defaultSettings: Defaults = JSON.parse(userProfile.c_defaults); + let newIdentities: SOGoMailIdentity[] = []; - for (let identity of cDefaults.SOGoMailIdentities) { + for (let identity of defaultSettings.SOGoMailIdentities) { if (identity.signature.indexOf('class="autogenerated"') === -1) { newIdentities.push(identity); } } for (let identityMail of SOBs) { - const committeeDisplayName = await getActiveDirectoryDisplayName(identityMail); - let signature : string = (await axios.get(`https://signature.gewis.nl/${identityMail}`)).data; + const committeeDisplayName: string = await getActiveDirectoryDisplayName(identityMail); + let signature: string = (await axios.get(`https://signature.gewis.nl/${identityMail}`)).data; signature = signature.replace('{{displayName}}', user.displayName); signature = signature.replaceAll('{{committeeDisplayName}}', committeeDisplayName); signature = signature.replaceAll('{{identityMail}}', identityMail); @@ -76,8 +76,8 @@ export async function editUserSignature(user: Users, SOBs: string[]): Promise