Skip to content

Commit

Permalink
feat: add run-user-template-monitoring command, clean up UserGroups a…
Browse files Browse the repository at this point in the history
…nd minor fixes to user group monitoring files.
  • Loading branch information
nshandra committed Sep 4, 2024
1 parent 25d3159 commit 14080ed
Show file tree
Hide file tree
Showing 16 changed files with 482 additions and 48 deletions.
2 changes: 2 additions & 0 deletions src/data/externalConfig/Namespaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ export const Namespace = {
TWO_FACTOR_MONITORING: "two-factor-monitoring",
AUTHORITIES_MONITOR: "authorities-monitor",
USER_GROUPS_MONITORING: "user-groups-monitoring",
USER_TEMPLATE_MONITORING: "user-template-monitoring",
} as const;

export const NamespaceProperties: Record<string, string[]> = {
[Namespace.PERMISSION_FIXER]: [],
[Namespace.TWO_FACTOR_MONITORING]: [],
[Namespace.AUTHORITIES_MONITOR]: [],
[Namespace.USER_GROUPS_MONITORING]: [],
[Namespace.USER_TEMPLATE_MONITORING]: [],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Username } from "domain/entities/Base";
import { Async } from "domain/entities/Async";
import { User } from "domain/entities/user-monitoring/user-template-monitoring/Users";
import { UserRepository } from "domain/repositories/user-monitoring/user-template-monitoring/UserRepository";
import { D2Api, SelectedPick, D2UserSchema } from "@eyeseetea/d2-api/2.36";

export class UserD2Repository implements UserRepository {
constructor(private api: D2Api) {}

async getByUsername(usernames: Username[]): Async<User[]> {
const users = await this.api.models.users
.get({
fields: userFields,
filter: { username: { in: usernames } },
})
.getData();

return users.objects.map((user: D2User) => {
return {
...user,
id: user.id,
username: user.username,
userCredentials: {
...user.userCredentials,
},
} as User;
});
}
}

const userFields = {
$all: true,
username: true,
userRoles: { id: true, name: true },
userGroups: { id: true, name: true },
userCredentials: {
access: true,
accountExpiry: true,
attributeValues: true,
catDimensionConstraints: true,
code: true,
cogsDimensionConstraints: true,
created: true,
createdBy: true,
disabled: true,
displayName: true,
externalAccess: true,
externalAuth: true,
favorite: true,
favorites: true,
href: true,
id: true,
invitation: true,
lastLogin: true,
lastUpdated: true,
lastUpdatedBy: true,
ldapId: true,
name: true,
openId: true,
password: true,
passwordLastUpdated: true,
publicAccess: true,
selfRegistered: true,
sharing: true,
translations: true,
twoFA: true,
user: true,
userAccesses: true,
userGroupAccesses: true,
userInfo: true,
username: true,
userRoles: false,
},
} as const;

type D2User = {
username?: string;
} & Partial<SelectedPick<D2UserSchema, typeof userFields>>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import _ from "lodash";

import log from "utils/log";
import { D2Api } from "@eyeseetea/d2-api/2.36";
import { Async } from "domain/entities/Async";
import { getObject } from "../common/GetDataStoreObjectByKey";
import { d2ToolsNamespace, Namespace } from "data/externalConfig/Namespaces";
import { UserTemplatesMonitoringOptions } from "domain/entities/user-monitoring/user-template-monitoring/UserTemplatesMonitoringOptions";
import { UserTemplatesMonitoringConfigRepository } from "domain/repositories/user-monitoring/user-template-monitoring/UserTemplatesMonitoringConfigRepository";

export class UserTemplatesMonitoringConfigD2Repository implements UserTemplatesMonitoringConfigRepository {
private api: D2Api;

constructor(api: D2Api) {
this.api = api;
}

public async get(): Async<UserTemplatesMonitoringOptions> {
const config = await getObject<UserTemplatesMonitoringOptions>(
this.api,
d2ToolsNamespace,
Namespace.USER_TEMPLATE_MONITORING
);

if (!config) {
log.warn("Error loading config from datastore");
throw new Error("Error loading config from datastore");
}

return config;
}

public async save(config: UserTemplatesMonitoringOptions): Promise<void> {
await this.api.dataStore(d2ToolsNamespace).save(Namespace.USER_TEMPLATE_MONITORING, config).getData();
}
}
Original file line number Diff line number Diff line change
@@ -1,44 +1,5 @@
import { D2UserGroup } from "@eyeseetea/d2-api/2.36";
import { Access, Id, NamedRef, Ref } from "domain/entities/Base";
import { U } from "vitest/dist/types-198fd1d9";

// export declare type D2UserGroup = {
// access: D2Access;
// attributeValues: D2AttributeValue[];
// code: Id;
// created: string;
// createdBy: D2User;
// displayName: string;
// externalAccess: boolean;
// favorite: boolean;
// favorites: string[];
// href: string;
// id: Id;
// lastUpdated: string;
// lastUpdatedBy: D2User;
// managedByGroups: D2UserGroup[];
// managedGroups: D2UserGroup[];
// name: string;
// publicAccess: string;
// sharing: Sharing;
// translations: D2Translation[];
// user: D2User;
// userAccesses: D2UserAccess[];
// userGroupAccesses: D2UserGroupAccess[];
// users: D2User[];
// };
// export declare type D2UserGroupAccess = {
// access: string;
// displayName: string;
// id: string;
// userGroupUid: string;
// };

// declare type Access = string;
// interface IdAccess {
// id: Id;
// access: Access;
// }
export interface Sharing {
owner: Id;
public: Access;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Username } from "domain/entities/Base";
import { User } from "./Users";

export type UserTemplatesMonitoringOptions = {
templatesToMonitor: Username[];
lastExecution: string;
monitoredUserTemplates: User[];
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { D2User, D2UserCredentials } from "@eyeseetea/d2-api/2.36";
import { Id, Username, NamedRef } from "domain/entities/Base";

type UserCredentials = Partial<Omit<D2UserCredentials, "userRoles">>;

export type User = {
id: Id;
username: Username;
userCredentials?: UserCredentials;
} & Partial<D2User>;

type MembershipChanges = {
userRoles_Lost: NamedRef[];
userRoles_Added: NamedRef[];
userGroups_Lost: NamedRef[];
userGroups_Added: NamedRef[];
};

export type UserTemplateDiff = {
id: Id;
username: Username;
newProps: Partial<User>;
changedPropsLost: Partial<User>;
changedPropsAdded: Partial<User>;
membershipChanges: MembershipChanges;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Username } from "domain/entities/Base";
import { Async } from "domain/entities/Async";
import { User } from "domain/entities/user-monitoring/user-template-monitoring/Users";

export interface UserRepository {
getByUsername(usernames: Username[]): Async<User[]>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { UserTemplatesMonitoringOptions } from "domain/entities/user-monitoring/user-template-monitoring/UserTemplatesMonitoringOptions";

export interface UserTemplatesMonitoringConfigRepository {
get(): Promise<UserTemplatesMonitoringOptions>;
save(config: UserTemplatesMonitoringOptions): Promise<void>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class CompareUserGroupsUseCase {

_.forOwn(oldUserGroup, (value, key) => {
if (_.has(newUserGroup, key)) {
const newValue = _.get(newUserGroup, key, undefined);
const newValue: any = _.get(newUserGroup, key, undefined);

if (!_.isEqual(value, newValue)) {
if (key === "users") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import _ from "lodash";
import log from "utils/log";

import { Id } from "domain/entities/Base";
import { Async } from "domain/entities/Async";
import { UserGroup, UserGroupDiff } from "domain/entities/user-monitoring/user-group-monitoring/UserGroups";
import { UserGroupsMonitoringOptions } from "domain/entities/user-monitoring/user-group-monitoring/UserGroupsMonitoringOptions";
Expand All @@ -10,10 +9,10 @@ import { MessageRepository } from "domain/repositories/user-monitoring/common/Me
import { UserGroupRepository } from "domain/repositories/user-monitoring/user-group-monitoring/UserGroupRepository";
import { UserGroupsMonitoringConfigRepository } from "domain/repositories/user-monitoring/user-group-monitoring/UserGroupsMonitoringConfigRepository";

import { GetUserGroupsMonitoringConfigUseCase } from "./GetUserGroupsMonitoringConfigUseCase";
import { GetUserGroupsUseCase } from "./GetUserGroupsUseCase";
import { SaveUserGroupsMonitoringConfigUseCase } from "./SaveUserGroupsMonitoringConfigUseCase";
import { CompareUserGroupsUseCase } from "./CompareUserGroupsUseCase";
import { GetUserGroupsMonitoringConfigUseCase } from "./GetUserGroupsMonitoringConfigUseCase";
import { SaveUserGroupsMonitoringConfigUseCase } from "./SaveUserGroupsMonitoringConfigUseCase";

export class MonitorUserGroupsUseCase {
constructor(
Expand Down Expand Up @@ -79,7 +78,7 @@ export class MonitorUserGroupsUseCase {
const compareUserGroupsUseCase = new CompareUserGroupsUseCase();

const userGroups: UserGroup[] = await getGroupsUseCase.execute(options.groupsToMonitor);
log.info("Retrieved user groups:");
log.info(`Retrieved user groups: ${userGroups.map(g => g.id).join(", ")}`);

if (!setDataStore) {
const userGroupsChanges = userGroups.flatMap(group => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import _ from "lodash";
import { User, UserTemplateDiff } from "domain/entities/user-monitoring/user-template-monitoring/Users";

export class CompareUserTemplatesUseCase {
constructor() {}

private membershipKeys = ["userRoles", "userGroups"];

private getNewProps(oldUserTemplate: User, newUserTemplate: User): Partial<User> {
let newProps: Partial<User> = {};

_.forOwn(newUserTemplate, (value, key) => {
if (this.membershipKeys.includes(key)) {
return;
}

if (!_.has(oldUserTemplate, key)) {
newProps = _.set(newProps, key, value);
} else {
const oldValue = _.get(oldUserTemplate, key, undefined);
if (!_.isEqual(value, oldValue)) {
if ((_.isObjectLike(oldValue) || _.isArrayLike(oldValue)) && _.isEmpty(oldValue)) {
newProps = _.set(newProps, key, value);
}
}
}
});

return newProps;
}

private compareMembershipProperties(oldUserTemplate: User, newUserTemplate: User): UserTemplateDiff {
let changedPropsLost: Partial<User> = {};
let changedPropsAdded: Partial<User> = {};
let membershipChanges = {
userRoles_Lost: [],
userRoles_Added: [],
userGroups_Lost: [],
userGroups_Added: [],
};

_.forOwn(oldUserTemplate, (value, key) => {
if (_.has(newUserTemplate, key)) {
const newValue: any = _.get(newUserTemplate, key, undefined);

if (!_.isEqual(value, newValue)) {
if (this.membershipKeys.includes(key)) {
membershipChanges = _.set(
membershipChanges,
`${key}_Lost`,
_.differenceBy(value as any, newValue, "id")
);
membershipChanges = _.set(
membershipChanges,
`${key}_Added`,
_.differenceBy(newValue, value as any, "id")
);
} else {
changedPropsLost = _.set(changedPropsLost, key, value);
changedPropsAdded = _.set(changedPropsAdded, key, newValue);
}
}
}
});

const newProps = this.getNewProps(oldUserTemplate, newUserTemplate);

return {
id: oldUserTemplate.id,
username: oldUserTemplate.username,
changedPropsLost: changedPropsLost,
changedPropsAdded: changedPropsAdded,
membershipChanges: membershipChanges,
newProps: newProps,
};
}

execute(oldUserTemplate: User, newUserTemplate: User): UserTemplateDiff {
return this.compareMembershipProperties(oldUserTemplate, newUserTemplate);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { UserTemplatesMonitoringConfigRepository } from "domain/repositories/user-monitoring/user-template-monitoring/UserTemplatesMonitoringConfigRepository";

export class GetUserTemplatesMonitoringConfigUseCase {
constructor(private configRepository: UserTemplatesMonitoringConfigRepository) {}

async execute() {
return this.configRepository.get();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Username } from "domain/entities/Base";
import { Async } from "domain/entities/Async";
import { User } from "domain/entities/user-monitoring/user-template-monitoring/Users";
import { UserRepository } from "domain/repositories/user-monitoring/user-template-monitoring/UserRepository";

export class GetUserTemplatesUseCase {
constructor(private userGroupRepository: UserRepository) {}

async execute(usernames: Username[]): Async<User[]> {
return this.userGroupRepository.getByUsername(usernames);
}
}
Loading

0 comments on commit 14080ed

Please sign in to comment.