CVE-2024-29194: Insufficient Information (NVD-CWE-noinfo)
value: OneUptime is a solution for monitoring and managing online services. The vulnerability lies in the improper validation of client-side stored data within the web application. Specifically, the is_master_admin key, stored in the local storage of the browser, can be manipulated by an attacker. By changing this key from false to true, the application grants administrative privileges to the user, without proper server-side validation.  This has been patched in 7.0.1815.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-03-24T19:15Z
Repository: https://github.com/OneUptime/oneuptime
Language: TypeScript
Files changed: CommonServer/Types/Database/ModelPermission.ts, Model/Models/BillingInvoice.ts, Model/Models/OnCallDutyPolicyEscalationRule.ts, Model/Models/OnCallDutyPolicyEscalationRuleSchedule.ts, Model/Models/OnCallDutyPolicyEscalationRuleTeam.ts, Model/Models/OnCallDutyPolicyEscalationRuleUser.ts, Model/Models/OnCallDutyPolicyExecutionLog.ts, Model/Models/OnCallDutyPolicyExecutionLogTimeline.ts, Model/Models/OnCallDutyPolicySchedule.ts, Model/Models/OnCallDutyPolicyScheduleLayer.ts, Model/Models/OnCallDutyPolicyScheduleLayerUser.ts, Model/Models/User.ts
Commit date: 2024-03-22 10:28:33 +0000
Commit message: Add deletedByUserId property to several models

Diff: diff --git a/CommonServer/Types/Database/ModelPermission.ts b/CommonServer/Types/Database/ModelPermission.ts
index 2a30cef0dc..5303b68438 100644
--- a/CommonServer/Types/Database/ModelPermission.ts
+++ b/CommonServer/Types/Database/ModelPermission.ts
@@ -37,10 +37,11 @@ import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan';
 import NotAuthenticatedException from 'Common/Types/Exception/NotAuthenticatedException';
 import UserType from 'Common/Types/UserType';
 import ColumnBillingAccessControl from 'Common/Types/BaseDatabase/ColumnBillingAccessControl';
 import DatabaseCommonInteractionPropsUtil from 'Common/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil';
 import Includes from 'Common/Types/BaseDatabase/Includes';
+import UserModel from 'Common/Models/UserModel';
 
 export interface CheckReadPermissionType<TBaseModel extends BaseModel> {
     query: Query<TBaseModel>;
     select: Select<TBaseModel> | null;
     relationSelect: RelationSelect<TBaseModel> | null;
@@ -293,10 +294,14 @@ export default class ModelPermission {
                 query,
                 select,
                 props
             );
 
+            // add user scope if any
+
+            query = await this.addUserScopeToQuery(modelType, query, props);
+
             if (!props.isMultiTenantRequest) {
                 // We will check for this permission in recursive function.
 
                 // check model level permissions.
                 this.checkModelLevelPermissions(
@@ -852,10 +857,30 @@ export default class ModelPermission {
         }
 
         return query;
     }
 
+    private static async addUserScopeToQuery<TBaseModel extends BaseModel>(
+        modelType: { new (): TBaseModel },
+        query: Query<TBaseModel>,
+        props: DatabaseCommonInteractionProps
+    ): Promise<Query<TBaseModel>> {
+        const model: BaseModel = new modelType();
+
+        if (model instanceof UserModel) {
+            if (props.userId) {
+                (query as any)['_id'] = props.userId;
+            } else if (!props.isRoot && !props.isMasterAdmin) {
+                throw new NotAuthorizedException(
+                    `You do not have permissions to query on - ${model.singularName}.`
+                );
+            }
+        }
+
+        return query;
+    }
+
     private static async addTenantScopeToQuery<TBaseModel extends BaseModel>(
         modelType: { new (): TBaseModel },
         query: Query<TBaseModel>,
         select: Select<TBaseModel> | null,
         props: DatabaseCommonInteractionProps
diff --git a/Model/Models/BillingInvoice.ts b/Model/Models/BillingInvoice.ts
index d005ce6e64..425b949e2e 100644
--- a/Model/Models/BillingInvoice.ts
+++ b/Model/Models/BillingInvoice.ts
@@ -190,10 +190,11 @@ export default class BillingInvoice extends BaseModel {
         }
     )
     @JoinColumn({ name: 'deletedByUserId' })
     public deletedByUser?: User = undefined;
 
+
     @ColumnAccessControl({
         create: [],
         read: [
             Permission.ProjectOwner,
             Permission.ProjectAdmin,
diff --git a/Model/Models/OnCallDutyPolicyEscalationRule.ts b/Model/Models/OnCallDutyPolicyEscalationRule.ts
index 9702d5ffb2..ad45d51026 100644
--- a/Model/Models/OnCallDutyPolicyEscalationRule.ts
+++ b/Model/Models/OnCallDutyPolicyEscalationRule.ts
@@ -353,10 +353,29 @@ export default class OnCallDutyPolicyEscalationRule extends BaseModel {
         }
     )
     @JoinColumn({ name: 'deletedByUserId' })
     public deletedByUser?: User = undefined;
 
+    @ColumnAccessControl({
+        create: [],
+        read: [],
+        update: [],
+    })
+    @TableColumn({
+        type: TableColumnType.ObjectID,
+        title: 'Deleted by User ID',
+        description:
+            'User ID who deleted this object (if this object was deleted by a User)',
+    })
+    @Column({
+        type: ColumnType.ObjectID,
+        nullable: true,
+        transformer: ObjectID.getDatabaseTransformer(),
+    })
+    public deletedByUserId?: ObjectID = undefined;
+
+
     @ColumnAccessControl({
         create: [
             Permission.ProjectOwner,
             Permission.ProjectAdmin,
             Permission.ProjectMember,
diff --git a/Model/Models/OnCallDutyPolicyEscalationRuleSchedule.ts b/Model/Models/OnCallDutyPolicyEscalationRuleSchedule.ts
index 65a9c19aa1..aa2e84c414 100644
--- a/Model/Models/OnCallDutyPolicyEscalationRuleSchedule.ts
+++ b/Model/Models/OnCallDutyPolicyEscalationRuleSchedule.ts
@@ -418,6 +418,25 @@ export default class OnCallDutyPolicyEscalationRuleSchedule extends BaseModel {
             orphanedRowAction: 'nullify',
         }
     )
     @JoinColumn({ name: 'deletedByUserId' })
     public deletedByUser?: User = undefined;
+
+
+    @ColumnAccessControl({
+        create: [],
+        read: [],
+        update: [],
+    })
+    @TableColumn({
+        type: TableColumnType.ObjectID,
+        title: 'Deleted by User ID',
+        description:
+            'User ID who deleted this object (if this object was deleted by a User)',
+    })
+    @Column({
+        type: ColumnType.ObjectID,
+        nullable: true,
+        transformer: ObjectID.getDatabaseTransformer(),
+    })
+    public deletedByUserId?: ObjectID = undefined;
 }
diff --git a/Model/Models/OnCallDutyPolicyEscalationRuleTeam.ts b/Model/Models/OnCallDutyPolicyEscalationRuleTeam.ts
index 006d1794ae..aefb023f9d 100644
--- a/Model/Models/OnCallDutyPolicyEscalationRuleTeam.ts
+++ b/Model/Models/OnCallDutyPolicyEscalationRuleTeam.ts
@@ -416,6 +416,24 @@ export default class OnCallDutyPolicyEscalationRuleTeam extends BaseModel {
             orphanedRowAction: 'nullify',
         }
     )
     @JoinColumn({ name: 'deletedByUserId' })
     public deletedByUser?: User = undefined;
+
+    @ColumnAccessControl({
+        create: [],
+        read: [],
+        update: [],
+    })
+    @TableColumn({
+        type: TableColumnType.ObjectID,
+        title: 'Deleted by User ID',
+        description:
+            'User ID who deleted this object (if this object was deleted by a User)',
+    })
+    @Column({
+        type: ColumnType.ObjectID,
+        nullable: true,
+        transformer: ObjectID.getDatabaseTransformer(),
+    })
+    public deletedByUserId?: ObjectID = undefined;
 }
diff --git a/Model/Models/OnCallDutyPolicyEscalationRuleUser.ts b/Model/Models/OnCallDutyPolicyEscalationRuleUser.ts
index 88cfaf1e3b..5a5984421b 100644
--- a/Model/Models/OnCallDutyPolicyEscalationRuleUser.ts
+++ b/Model/Models/OnCallDutyPolicyEscalationRuleUser.ts
@@ -415,6 +415,24 @@ export default class OnCallDutyPolicyEscalationRuleUser extends BaseModel {
             orphanedRowAction: 'nullify',
         }
     )
     @JoinColumn({ name: 'deletedByUserId' })
     public deletedByUser?: User = undefined;
+
+    @ColumnAccessControl({
+        create: [],
+        read: [],
+        update: [],
+    })
+    @TableColumn({
+        type: TableColumnType.ObjectID,
+        title: 'Deleted by User ID',
+        description:
+            'User ID who deleted this object (if this object was deleted by a User)',
+    })
+    @Column({
+        type: ColumnType.ObjectID,
+        nullable: true,
+        transformer: ObjectID.getDatabaseTransformer(),
+    })
+    public deletedByUserId?: ObjectID = undefined;
 }
diff --git a/Model/Models/OnCallDutyPolicyExecutionLog.ts b/Model/Models/OnCallDutyPolicyExecutionLog.ts
index 780ee6c26e..6c20019cef 100644
--- a/Model/Models/OnCallDutyPolicyExecutionLog.ts
+++ b/Model/Models/OnCallDutyPolicyExecutionLog.ts
@@ -662,6 +662,8 @@ export default class OnCallDutyPolicyExecutionLog extends BaseModel {
         type: ColumnType.Number,
         nullable: false,
         default: 1,
     })
     public onCallPolicyExecutionRepeatCount?: number = undefined;
+
+    
 }
diff --git a/Model/Models/OnCallDutyPolicyExecutionLogTimeline.ts b/Model/Models/OnCallDutyPolicyExecutionLogTimeline.ts
index 9d95c4bcb9..7d08b198ce 100644
--- a/Model/Models/OnCallDutyPolicyExecutionLogTimeline.ts
+++ b/Model/Models/OnCallDutyPolicyExecutionLogTimeline.ts
@@ -706,6 +706,24 @@ export default class OnCallDutyPolicyExecutionLogTimeline extends BaseModel {
         type: ColumnType.Date,
         nullable: true,
         unique: false,
     })
     public acknowledgedAt?: Date = undefined;
+
+    @ColumnAccessControl({
+        create: [],
+        read: [],
+        update: [],
+    })
+    @TableColumn({
+        type: TableColumnType.ObjectID,
+        title: 'Deleted by User ID',
+        description:
+            'User ID who deleted this object (if this object was deleted by a User)',
+    })
+    @Column({
+        type: ColumnType.ObjectID,
+        nullable: true,
+        transformer: ObjectID.getDatabaseTransformer(),
+    })
+    public deletedByUserId?: ObjectID = undefined;
 }
diff --git a/Model/Models/OnCallDutyPolicySchedule.ts b/Model/Models/OnCallDutyPolicySchedule.ts
index a8ee613665..76392ab96b 100644
--- a/Model/Models/OnCallDutyPolicySchedule.ts
+++ b/Model/Models/OnCallDutyPolicySchedule.ts
@@ -400,6 +400,8 @@ export default class OnCallDutyPolicySchedule extends BaseModel {
         type: ColumnType.ObjectID,
         nullable: true,
         transformer: ObjectID.getDatabaseTransformer(),
     })
     public deletedByUserId?: ObjectID = undefined;
+
+    
 }
diff --git a/Model/Models/OnCallDutyPolicyScheduleLayer.ts b/Model/Models/OnCallDutyPolicyScheduleLayer.ts
index d527593bdf..c70ebbc6d1 100644
--- a/Model/Models/OnCallDutyPolicyScheduleLayer.ts
+++ b/Model/Models/OnCallDutyPolicyScheduleLayer.ts
@@ -554,6 +554,8 @@ export default class OnCallDutyPolicyScheduleLayer extends BaseModel {
         type: ColumnType.JSON,
         default: RestrictionTimes.getDefault(),
         transformer: RestrictionTimes.getDatabaseTransformer(),
     })
     public restrictionTimes?: RestrictionTimes = undefined;
+
+    
 }
diff --git a/Model/Models/OnCallDutyPolicyScheduleLayerUser.ts b/Model/Models/OnCallDutyPolicyScheduleLayerUser.ts
index 088709fa8f..2f2ecad1e2 100644
--- a/Model/Models/OnCallDutyPolicyScheduleLayerUser.ts
+++ b/Model/Models/OnCallDutyPolicyScheduleLayerUser.ts
@@ -492,6 +492,7 @@ export default class OnCallDutyPolicyScheduleLayerUser extends BaseModel {
         type: ColumnType.ObjectID,
         nullable: false,
         transformer: ObjectID.getDatabaseTransformer(),
     })
     public userId?: ObjectID = undefined;
+    
 }
diff --git a/Model/Models/User.ts b/Model/Models/User.ts
index 0de11e88be..dab362900a 100644
--- a/Model/Models/User.ts
+++ b/Model/Models/User.ts
@@ -30,15 +30,11 @@ import EnableDocumentation from 'Common/Types/Database/EnableDocumentation';
     isMasterAdminApiDocs: true,
 })
 @AllowAccessIfSubscriptionIsUnpaid()
 @TableAccessControl({
     create: [Permission.Public],
-    read: [
-        Permission.CurrentUser,
-        Permission.ProjectAdmin,
-        Permission.ProjectOwner,
-    ],
+    read: [Permission.CurrentUser],
     delete: [Permission.CurrentUser],
     update: [Permission.CurrentUser],
 })
 @CrudApiEndpoint(new Route('/user'))
 @SlugifyColumn('name', 'slug')
@@ -54,15 +50,11 @@ import EnableDocumentation from 'Common/Types/Database/EnableDocumentation';
 })
 @CurrentUserCanAccessRecordBy('_id')
 class User extends UserModel {
     @ColumnAccessControl({
         create: [Permission.Public],
-        read: [
-            Permission.CurrentUser,
-            Permission.ProjectAdmin,
-            Permission.ProjectOwner,
-        ],
+        read: [Permission.CurrentUser],
         update: [Permission.CurrentUser],
     })
     @TableColumn({ type: TableColumnType.Name, canReadOnRelationQuery: true })
     @Column({
         type: ColumnType.Name,
@@ -73,15 +65,11 @@ class User extends UserModel {
     })
     public name?: Name = undefined;
 
     @ColumnAccessControl({
         create: [Permission.Public],
-        read: [
-            Permission.CurrentUser,
-            Permission.ProjectAdmin,
-            Permission.ProjectOwner,
-        ],
+        read: [Permission.CurrentUser],
 
         update: [Permission.CurrentUser],
     })
     @TableColumn({
         title: 'Email',
@@ -245,15 +233,11 @@ class User extends UserModel {
     })
     public companyPhoneNumber?: Phone = undefined;
 
     @ColumnAccessControl({
         create: [],
-        read: [
-            Permission.CurrentUser,
-            Permission.ProjectAdmin,
-            Permission.ProjectOwner,
-        ],
+        read: [Permission.CurrentUser],
 
         update: [Permission.CurrentUser],
     })
     @TableColumn({
         manyToOneRelationColumn: 'profilePictureId',
@@ -274,15 +258,11 @@ class User extends UserModel {
     @JoinColumn({ name: 'profilePictureId' })
     public profilePictureFile?: File = undefined;
 
     @ColumnAccessControl({
         create: [],
-        read: [
-            Permission.CurrentUser,
-            Permission.ProjectAdmin,
-            Permission.ProjectOwner,
-        ],
+        read: [Permission.CurrentUser],
 
         update: [Permission.CurrentUser],
     })
     @TableColumn({
         type: TableColumnType.ObjectID,
@@ -295,22 +275,19 @@ class User extends UserModel {
     })
     public profilePictureId?: ObjectID = undefined;
 
     @ColumnAccessControl({
         create: [],
-        read: [
-            Permission.CurrentUser,
-            Permission.ProjectAdmin,
-            Permission.ProjectOwner,
-        ],
+        read: [Permission.CurrentUser],
 
         update: [Permission.CurrentUser],
     })
     @TableColumn({
         isDefaultValueColumn: true,
         required: true,
         type: TableColumnType.Boolean,
+        canReadOnRelationQuery: true,
     })
     @Column({
         type: ColumnType.Boolean,
         default: false,
         nullable: false,
@@ -422,15 +399,11 @@ class User extends UserModel {
     })
     public resetPasswordExpires?: Date = undefined;
 
     @ColumnAccessControl({
         create: [],
-        read: [
-            Permission.CurrentUser,
-            Permission.ProjectAdmin,
-            Permission.ProjectOwner,
-        ],
+        read: [Permission.CurrentUser],
 
         update: [Permission.CurrentUser],
     })
     @TableColumn({ type: TableColumnType.ShortText })
     @Column({
@@ -541,15 +514,11 @@ class User extends UserModel {
     })
     public isBlocked?: boolean = undefined;
 
     @ColumnAccessControl({
         create: [],
-        read: [
-            Permission.CurrentUser,
-            Permission.ProjectAdmin,
-            Permission.ProjectOwner,
-        ],
+        read: [Permission.CurrentUser],
 
         update: [Permission.CurrentUser],
     })
     @TableColumn({ type: TableColumnType.Phone })
     @Column({

Vulnerable code:     read: [
        Permission.CurrentUser,
        Permission.ProjectAdmin,
        Permission.ProjectOwner,
    ],
        read: [
            Permission.CurrentUser,
            Permission.ProjectAdmin,
            Permission.ProjectOwner,
        ],
        read: [
            Permission.CurrentUser,
            Permission.ProjectAdmin,
            Permission.ProjectOwner,
        ],
        read: [
            Permission.CurrentUser,
            Permission.ProjectAdmin,
            Permission.ProjectOwner,
        ],
        read: [
            Permission.CurrentUser,
            Permission.ProjectAdmin,
            Permission.ProjectOwner,
        ],
        read: [
            Permission.CurrentUser,
            Permission.ProjectAdmin,
            Permission.ProjectOwner,
        ],
        read: [
            Permission.CurrentUser,
            Permission.ProjectAdmin,
            Permission.ProjectOwner,
        ],
        read: [
            Permission.CurrentUser,
            Permission.ProjectAdmin,
            Permission.ProjectOwner,
        ],
Fixed code: import UserModel from 'Common/Models/UserModel';
            // add user scope if any

            query = await this.addUserScopeToQuery(modelType, query, props);

    private static async addUserScopeToQuery<TBaseModel extends BaseModel>(
        modelType: { new (): TBaseModel },
        query: Query<TBaseModel>,
        props: DatabaseCommonInteractionProps
    ): Promise<Query<TBaseModel>> {
        const model: BaseModel = new modelType();

        if (model instanceof UserModel) {
            if (props.userId) {
                (query as any)['_id'] = props.userId;
            } else if (!props.isRoot && !props.isMasterAdmin) {
                throw new NotAuthorizedException(
                    `You do not have permissions to query on - ${model.singularName}.`
                );
            }
        }

        return query;
    }


    @ColumnAccessControl({
        create: [],
        read: [],
        update: [],
    })
    @TableColumn({
        type: TableColumnType.ObjectID,
        title: 'Deleted by User ID',
        description:
            'User ID who deleted this object (if this object was deleted by a User)',
    })
    @Column({
        type: ColumnType.ObjectID,
        nullable: true,
        transformer: ObjectID.getDatabaseTransformer(),
    })
    public deletedByUserId?: ObjectID = undefined;




    @ColumnAccessControl({
        create: [],
        read: [],
        update: [],
    })
    @TableColumn({
        type: TableColumnType.ObjectID,
        title: 'Deleted by User ID',
        description:
            'User ID who deleted this object (if this object was deleted by a User)',
    })
    @Column({
        type: ColumnType.ObjectID,
        nullable: true,
        transformer: ObjectID.getDatabaseTransformer(),
    })
    public deletedByUserId?: ObjectID = undefined;

    @ColumnAccessControl({
        create: [],
        read: [],
        update: [],
    })
    @TableColumn({
        type: TableColumnType.ObjectID,
        title: 'Deleted by User ID',
        description:
            'User ID who deleted this object (if this object was deleted by a User)',
    })
    @Column({
        type: ColumnType.ObjectID,
        nullable: true,
        transformer: ObjectID.getDatabaseTransformer(),
    })
    public deletedByUserId?: ObjectID = undefined;

    @ColumnAccessControl({
        create: [],
        read: [],
        update: [],
    })
    @TableColumn({
        type: TableColumnType.ObjectID,
        title: 'Deleted by User ID',
        description:
            'User ID who deleted this object (if this object was deleted by a User)',
    })
    @Column({
        type: ColumnType.ObjectID,
        nullable: true,
        transformer: ObjectID.getDatabaseTransformer(),
    })
    public deletedByUserId?: ObjectID = undefined;

    

    @ColumnAccessControl({
        create: [],
        read: [],
        update: [],
    })
    @TableColumn({
        type: TableColumnType.ObjectID,
        title: 'Deleted by User ID',
        description:
            'User ID who deleted this object (if this object was deleted by a User)',
    })
    @Column({
        type: ColumnType.ObjectID,
        nullable: true,
        transformer: ObjectID.getDatabaseTransformer(),
    })
    public deletedByUserId?: ObjectID = undefined;

    

    
    
    read: [Permission.CurrentUser],
        read: [Permission.CurrentUser],
        read: [Permission.CurrentUser],
        read: [Permission.CurrentUser],
        read: [Permission.CurrentUser],
        read: [Permission.CurrentUser],
        canReadOnRelationQuery: true,
        read: [Permission.CurrentUser],
        read: [Permission.CurrentUser],