Skip to content

Commit

Permalink
Small testing
Browse files Browse the repository at this point in the history
  • Loading branch information
Gijsdeman committed Aug 21, 2023
1 parent a26f9e8 commit 0e0f52e
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 158 deletions.
27 changes: 11 additions & 16 deletions src/dovecotAPI.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import axios, { AxiosInstance } from 'axios';
import {
DoveadmExchangeResult,
DovecotData,
DovecotRequestData,
DovecotResponseExchange,
DovecotPermissions,
ActiveDirectoryPermissions,
} from './types';
Expand All @@ -12,7 +11,6 @@ let dovecotClient: AxiosInstance;

export async function initializeDovecotAPI(): Promise<void> {
dovecotClient = axios.create({
// baseURL: `${config.DOVEADM_API_HOST}/doveadm/v1`,
baseURL: 'http://172.22.1.250:9000/doveadm/v1',
headers: {
'Content-Type': 'text/plain',
Expand All @@ -25,9 +23,8 @@ export async function initializeDovecotAPI(): Promise<void> {
* Get all mailboxes of an email
* @param email - email to get all inboxes from
*/
async function getMailboxes(email: string): Promise<string[]> {
// Get all mailboxes
const response = (await dovecotClient.post(
async function getMailboxSubFolders(email: string): Promise<string[]> {
const mailboxData: DovecotData[] = ((await dovecotClient.post(
'',
[[
'mailboxList',
Expand All @@ -36,16 +33,15 @@ async function getMailboxes(email: string): Promise<string[]> {
},
`mailboxList_${email}`,
]],
)).data as DovecotResponseExchange;
)).data)[0][1];

// Convert response to array of mailboxes
const mailboxObjects: DoveadmExchangeResult[] = response[0][1];
let subFolders: string[] = [];
for (let subFolder of mailboxData) {
if (subFolder.mailbox.startsWith('Shared')) continue;
subFolders.push(subFolder.mailbox);
}

return mailboxObjects.filter(function (item) {
return !item.mailbox.startsWith('Shared');
}).map((item: DoveadmExchangeResult) => {
return item.mailbox;
});
return subFolders;
}

/**
Expand Down Expand Up @@ -74,7 +70,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';
}

Expand Down Expand Up @@ -103,7 +99,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,
Expand Down
104 changes: 48 additions & 56 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Client } from 'ldapts';
import {
activityUserDB,
addUserDB,
updateLocalUserActivity,
createLocalUser,
getLocalUser,
createSOBDB,
getChangedSOBDB,
editLocalUserPermissions,
getUpdateSOBLocalUsers,
getUncheckedLocalActiveUsers,
initializeLocalUserDatabase,
updateLocalUserPermissions,
Expand All @@ -15,11 +15,10 @@ import {
} from 'replace-in-file';
import fs, { PathLike } from 'fs';
import path from 'path';
import { SearchResult } from 'ldapts/Client';
import {
addUserAPI,
createMailcowUser,
getMailcowUser,
editUserAPI,
editMailcowUser,
initializeMailcowAPI,
} from './mailcowAPI';
import {
Expand All @@ -40,7 +39,6 @@ import {
initializeMailcowDatabase,
} from './mailcowDatabase';

// Set all default variables
export const containerConfig: ContainerConfig = {
LDAP_URI: '',
LDAP_BIND_DN: '',
Expand All @@ -58,14 +56,13 @@ export const containerConfig: ContainerConfig = {
DOVEADM_API_KEY: '',
DOVEADM_API_HOST: '',
};
const consoleLogLine = '-'.repeat(process.stdout.columns);
const consoleLogLine: string = '-'.repeat(30);
export const sessionTime: number = new Date().getTime();

let activeDirectoryConnector: Client;
let activeDirectoryUsers: ActiveDirectoryUser[] = [];

export async function getActiveDirectoryDisplayName(mail: string) : Promise<string> {
// TODO can the filter be generalized to top level?
const activeDirectoryUser: ActiveDirectoryUser[] = (await activeDirectoryConnector.search(containerConfig.LDAP_BASE_DN, {
scope: 'sub',
filter: `(&(objectClass=user)(objectCategory=person)(mail=${mail})`,
Expand Down Expand Up @@ -98,12 +95,12 @@ async function getActiveDirectoryMails(users: string[], skipUser: ActiveDirector
* @param permission - type of permission being considered
*/
async function synchronizeUserACL(activeDirectoryUser: ActiveDirectoryUser, permission: ActiveDirectoryPermissions): Promise<void> {
const activeDirectoryGroup = (await activeDirectoryConnector.search(activeDirectoryUser[permission], {
const activeDirectoryPermissionGroup = (await activeDirectoryConnector.search(activeDirectoryUser[permission], {
scope: 'sub',
attributes: ['memberFlattened'],
})).searchEntries[0] as unknown as ActiveDirectoryUser;

await updateLocalUserPermissions(activeDirectoryUser.mail, activeDirectoryGroup.memberFlattened, permission)
await updateLocalUserPermissions(activeDirectoryUser.mail, activeDirectoryPermissionGroup.memberFlattened, permission)
.then(async (changedUsers: ChangedUsers) => {
if (changedUsers.newUsers.length != 0) {
changedUsers.newUsers = await getActiveDirectoryMails(changedUsers.newUsers, activeDirectoryUser);
Expand All @@ -122,27 +119,25 @@ async function synchronizeUserACL(activeDirectoryUser: ActiveDirectoryUser, perm
);
}

async function synchronizeUserSOB(activeDirectoryUser: ActiveDirectoryUser): Promise<void> {
const activeDirectoryGroup: ActiveDirectoryUser[] = (await activeDirectoryConnector.search(activeDirectoryUser[ActiveDirectoryPermissions.mailPermSOB], {
async function synchronizeUserSOB(activeDirectoryGroup: ActiveDirectoryUser): Promise<void> {
// Should always be one entry
const activeDirectoryPermissionGroup: ActiveDirectoryUser = ((await activeDirectoryConnector.search(activeDirectoryGroup[ActiveDirectoryPermissions.mailPermSOB], {
scope: 'sub',
attributes: ['memberFlattened'],
})).searchEntries as unknown as ActiveDirectoryUser[];
})).searchEntries)[0] as unknown as ActiveDirectoryUser;

// 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
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, {
scope: 'sub',
attributes: ['mail'],
})).searchEntries as unknown as ActiveDirectoryUser[];
await createSOBDB(memberResults[0].mail, activeDirectoryUser.mail);
}
// Singular entries are possible, so turn them into an array
if (!Array.isArray(activeDirectoryPermissionGroup.memberFlattened))
activeDirectoryPermissionGroup.memberFlattened = [activeDirectoryPermissionGroup.memberFlattened];

// All users are given as DN, so we have to get their mail first
for (const activeDirectoryUserDN of activeDirectoryPermissionGroup.memberFlattened) {
const activeDirectoryUserMail: ActiveDirectoryUser = ((await activeDirectoryConnector.search(activeDirectoryUserDN, {
scope: 'sub',
attributes: ['mail'],
})).searchEntries)[0] as unknown as ActiveDirectoryUser;
// Own group has to be skipped to prevent clashing permissions
await editLocalUserPermissions(activeDirectoryUserMail.mail, activeDirectoryGroup.mail);
}
}

Expand Down Expand Up @@ -299,7 +294,7 @@ async function getUserDataFromActiveDirectory(): Promise<void> {
})).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');
}


Expand All @@ -320,38 +315,39 @@ async function synchronizeUsersWithActiveDirectory(): Promise<void> {

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;
}

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);
Expand All @@ -363,28 +359,29 @@ async function synchronizeUsersWithActiveDirectory(): Promise<void> {

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<void> {
for (const activeDirectoryUser of activeDirectoryUsers) {
try {
// Check if current user has corresponding permissions
// Sometimes, the mail considered is a personal account, but it can also be a shared mailbox
// (in principle this does not matter though, personal mails _could_ in principle also be shared if wanted)
if (activeDirectoryUser[ActiveDirectoryPermissions.mailPermROInbox].length != 0)
await synchronizeUserACL(activeDirectoryUser, ActiveDirectoryPermissions.mailPermROInbox);
if (activeDirectoryUser[ActiveDirectoryPermissions.mailPermROSent].length != 0)
Expand All @@ -396,22 +393,22 @@ async function synchronizePermissionsWithActiveDirectory(): Promise<void> {
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');
}

/**
Expand Down Expand Up @@ -449,7 +446,7 @@ async function initializeSync(): Promise<void> {
.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);
Expand All @@ -459,26 +456,21 @@ async function initializeSync(): Promise<void> {
console.warn('One or more config files have been changed, please restart dovecot-mailcow and sogo-mailcow.');
console.log('Successfully applied all config files \n\n');

// Start 'connection' with database
console.log(consoleLogLine + '\n INITIALIZING DATABASES AND API CLIENTS \n' + consoleLogLine);
await initializeLocalUserDatabase();
await initializeMailcowDatabase();
await initializeMailcowAPI();
await initializeDovecotAPI();
console.log('Successfully initialized all databases and API clients \n\n');

// Start sync loop every interval milliseconds
console.log(consoleLogLine + '\n GETTING USERS FROM LDAP \n' + consoleLogLine);
console.log(consoleLogLine + '\nGETTING USERS FROM ACTIVE DIRECTORY\n' + consoleLogLine);
await getUserDataFromActiveDirectory();

console.log(consoleLogLine + '\n SYNCING ALL USERS \n' + consoleLogLine);
await synchronizeUsersWithActiveDirectory();

console.log(consoleLogLine + '\n SYNCING ALL PERMISSIONS \n' + consoleLogLine);
await synchronizePermissionsWithActiveDirectory();

console.log(consoleLogLine + '\n CHECKING INACTIVE USERS \n' + consoleLogLine);
await synchronize();
}

/**
Expand Down
Loading

0 comments on commit 0e0f52e

Please sign in to comment.