From beac2bf1245668a910f9610bbf6450b910ca1369 Mon Sep 17 00:00:00 2001
From: Caetan Tojeiro Carpente <caetan.tojeiro.carpente@cern.ch>
Date: Mon, 23 May 2022 18:38:40 +0200
Subject: [PATCH] [#158] Audit: add endpoint to download external notification
 audit

---
 src/controllers/notifications/controller.ts   | 21 ++++++++++
 src/log/auditing.ts                           | 42 +++++++++++++------
 .../impl/notifications-service-impl.ts        |  5 +++
 .../get-notification-audit-by-id.ts           | 26 ++++++++++++
 src/services/notifications-service.ts         |  2 +
 5 files changed, 83 insertions(+), 13 deletions(-)
 create mode 100644 src/services/impl/notifications/get-notification-audit-by-id.ts

diff --git a/src/controllers/notifications/controller.ts b/src/controllers/notifications/controller.ts
index 8e03ef07..87e1f284 100644
--- a/src/controllers/notifications/controller.ts
+++ b/src/controllers/notifications/controller.ts
@@ -16,6 +16,7 @@ import { GetNotificationResponse, SendNotificationRequest } from './dto';
 import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 import { API_KEY_ACCESS_ROLE } from '../../middleware/authorizationChecker';
 import { StatusCodeDescriptions, StatusCodes } from '../../utils/status-codes';
+import { JSONSchema } from 'class-validator-jsonschema';
 
 @JsonController('/notifications')
 export class NotificationsController {
@@ -40,6 +41,26 @@ export class NotificationsController {
     return this.notificationsService.getById(notificationId, req.authorizationBag);
   }
 
+  @Authorized([process.env.INTERNAL_ROLE, process.env.SUPPORTER_ROLE])
+  @OpenAPI({
+    summary: 'Returns the audit of the Notification with the provided notification id.',
+    description:
+      'This endpoint returns the audit of the notification with the provided notification id, if requester has access.',
+    operationId: 'getNotificationAuditById',
+    security: [{ oauth2: [] }],
+    responses: {
+      [StatusCodes.OK]: StatusCodeDescriptions[StatusCodes.OK],
+      [StatusCodes.NotFound]: StatusCodeDescriptions[StatusCodes.NotFound],
+      [StatusCodes.Forbidden]: StatusCodeDescriptions[StatusCodes.Forbidden],
+      [StatusCodes.Unauthorized]: StatusCodeDescriptions[StatusCodes.Unauthorized],
+    },
+  })
+  @ResponseSchema(JSONSchema, { description: 'Audit Notification requested.' })
+  @Get('/:id/audit')
+  getNotificationAuditById(@Req() req, @Param('id') notificationId: string): Promise<JSON> {
+    return this.notificationsService.getNotificationAuditById(notificationId, req.authorizationBag);
+  }
+
   @Authorized([process.env.INTERNAL_ROLE, process.env.VIEWER_ROLE, API_KEY_ACCESS_ROLE])
   @OpenAPI({
     summary: 'Send a Notification.',
diff --git a/src/log/auditing.ts b/src/log/auditing.ts
index 6ead5baa..84f9d8b1 100644
--- a/src/log/auditing.ts
+++ b/src/log/auditing.ts
@@ -1,6 +1,7 @@
 import { Etcd3, SortTarget, SortOrder } from 'etcd3';
 import { v4 as uuidv4 } from 'uuid';
 import * as moment from 'moment';
+import { NotFoundError } from 'routing-controllers';
 
 class BaseEtcd {
   private baseClient = new Etcd3({
@@ -70,28 +71,31 @@ class BaseEtcd {
     return allFValues;
   }
 
-  public async getValuesAsJson(prefix) {
+  public async getValuesAsJson(prefix): Promise<JSON> {
     if (!this.namespaceClient) {
       console.debug('etcd getValues namespaceClient is null');
       return;
     }
 
     const allFValues = await this.getValues(prefix);
+    if (!allFValues) {
+      throw new NotFoundError('Audit does not exist.');
+    }
 
-    var ret = {};
+    const ret: JSON = {} as JSON;
     Object.keys(allFValues).forEach(function (key) {
       const value = allFValues[key];
-      const splittedKeys = key.split('/');
+      const splitKeys = key.split('/');
       let tmpret = ret;
-      for (var j = 0; j < splittedKeys.length - 1; j++) {
-        if (splittedKeys[j] === '') continue;
-        if (!tmpret[splittedKeys[j]]) tmpret[splittedKeys[j]] = {};
-        tmpret = tmpret[splittedKeys[j]];
+      for (let j = 0; j < splitKeys.length - 1; j++) {
+        if (splitKeys[j] === '') continue;
+        if (!tmpret[splitKeys[j]]) tmpret[splitKeys[j]] = {};
+        tmpret = tmpret[splitKeys[j]];
       }
       try {
-        tmpret[splittedKeys[splittedKeys.length - 1]] = JSON.parse(value);
+        tmpret[splitKeys[splitKeys.length - 1]] = JSON.parse(value);
       } catch {
-        tmpret[splittedKeys[splittedKeys.length - 1]] = value;
+        tmpret[splitKeys[splitKeys.length - 1]] = value;
       }
     });
 
@@ -119,7 +123,19 @@ class AuditChannels extends BaseEtcd {
   }
 }
 
-const i1 = AuditNotifications.Instance;
-export { i1 as AuditNotifications };
-const i2 = AuditChannels.Instance;
-export { i2 as AuditChannels };
+class AuditExternal extends BaseEtcd {
+  private static _instance: AuditExternal;
+  constructor() {
+    super('external/notifications');
+  }
+  public static get Instance() {
+    return this._instance || (this._instance = new this());
+  }
+}
+
+const auditNotifications = AuditNotifications.Instance;
+export { auditNotifications as AuditNotifications };
+const auditChannels = AuditChannels.Instance;
+export { auditChannels as AuditChannels };
+const auditExternal = AuditExternal.Instance;
+export { auditExternal as AuditExternal };
diff --git a/src/services/impl/notifications-service-impl.ts b/src/services/impl/notifications-service-impl.ts
index 9fa1c0a6..743bf9bc 100644
--- a/src/services/impl/notifications-service-impl.ts
+++ b/src/services/impl/notifications-service-impl.ts
@@ -2,6 +2,7 @@ import { NotificationsService } from '../notifications-service';
 import { SendNotification } from './notifications/send-notification';
 import { FindAllNotifications } from './notifications/find-all-notifications';
 import { GetById } from './notifications/get-by-id';
+import { GetNotificationAuditById } from './notifications/get-notification-audit-by-id';
 import { AbstractService } from './abstract-service';
 import { UserNotification } from '../../models/user-notification';
 import { UpdateUserNotification } from './notifications/update-user-notification';
@@ -37,4 +38,8 @@ export class NotificationsServiceImpl extends AbstractService implements Notific
   updateUserNotification(notification: UserNotification, authorizationBag: AuthorizationBag): Promise<any> {
     return this.commandExecutor.execute(new UpdateUserNotification(notification, authorizationBag));
   }
+
+  getNotificationAuditById(notificationId: string, authorizationBag: AuthorizationBag): Promise<JSON> {
+    return this.commandExecutor.execute(new GetNotificationAuditById(notificationId, authorizationBag));
+  }
 }
diff --git a/src/services/impl/notifications/get-notification-audit-by-id.ts b/src/services/impl/notifications/get-notification-audit-by-id.ts
new file mode 100644
index 00000000..a53be1cb
--- /dev/null
+++ b/src/services/impl/notifications/get-notification-audit-by-id.ts
@@ -0,0 +1,26 @@
+import { Command } from '../command';
+import { AuthorizationBag } from '../../../models/authorization-bag';
+import { AuditExternal } from '../../../log/auditing';
+import { EntityManager } from 'typeorm';
+import { ForbiddenError, NotFoundError } from 'routing-controllers';
+import { Notification } from '../../../models/notification';
+
+export class GetNotificationAuditById implements Command {
+  constructor(private notificationId: string, private authorizationBag: AuthorizationBag) {}
+
+  async execute(transactionManager: EntityManager): Promise<JSON> {
+    const notification = await transactionManager.findOne(Notification, {
+      relations: ['target', 'target.owner', 'target.adminGroup'],
+      where: {
+        id: this.notificationId,
+      },
+    });
+
+    if (!notification) throw new NotFoundError('Notification does not exist');
+
+    if (!(await notification.target.isAdmin(this.authorizationBag)))
+      throw new ForbiddenError("You don't have the rights to edit this channel.");
+
+    return AuditExternal.getValuesAsJson(this.notificationId);
+  }
+}
diff --git a/src/services/notifications-service.ts b/src/services/notifications-service.ts
index f1a58880..d59819e4 100644
--- a/src/services/notifications-service.ts
+++ b/src/services/notifications-service.ts
@@ -20,4 +20,6 @@ export interface NotificationsService {
   getById(notificationId: string, authorizationBag: AuthorizationBag): Promise<GetNotificationResponse>;
 
   updateUserNotification(notification: UserNotification, authorizationBag: AuthorizationBag): Promise<any>;
+
+  getNotificationAuditById(notificationId: string, authorizationBag: AuthorizationBag): Promise<JSON>;
 }
-- 
GitLab