Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Enforce keycloak UUIDs as user identifiers - take 2 #118

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions qg-api-service/api-commons-lib/src/input-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const maxIdPostgresSerial4 = 2147483647

const numberSchema = z.number().int().positive().max(maxIdPostgresSerial4)
const stringSchema = z.string().trim().min(1)
const uuidSchema = z.string().trim().uuid()

const dateSchema = z.coerce.date()

Expand All @@ -27,6 +28,14 @@ export function validateId(receivedId: any): void {
}
}

export function validateUUID(receivedUUID: any): void {
try {
uuidSchema.parse(receivedUUID)
} catch (err) {
throw new BadRequestException(fromZodError(err).message, { cause: err })
}
}

export function validateName(receivedName: any): void {
try {
stringSchema.parse(receivedName)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// SPDX-FileCopyrightText: 2024 grow platform GmbH
//
// SPDX-License-Identifier: MIT

import { MigrationInterface, QueryRunner } from 'typeorm'

export class Database1732714208822 implements MigrationInterface {
name = 'Database1732714208822'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "release" ALTER COLUMN "createdBy" SET DATA TYPE uuid USING "createdBy"::uuid`,
)
await queryRunner.query(
`ALTER TABLE "release" ALTER COLUMN "lastModifiedBy" SET DATA TYPE uuid USING "lastModifiedBy"::uuid`,
)
await queryRunner.query(
`ALTER TABLE "long_running_token" ALTER COLUMN "kcuid" SET DATA TYPE uuid USING "kcuid"::uuid`,
)
await queryRunner.query(
`ALTER TABLE "long_running_token" ALTER COLUMN "createdBy" SET DATA TYPE uuid USING "createdBy"::uuid`,
)
await queryRunner.query(
`ALTER TABLE "long_running_token" ALTER COLUMN "lastModifiedBy" SET DATA TYPE uuid USING "lastModifiedBy"::uuid`,
)
await queryRunner.query(
`ALTER TABLE "user_profile" ALTER COLUMN "id" SET DATA TYPE uuid USING "id"::uuid`,
)
await queryRunner.query(
`ALTER TABLE "findings" ALTER COLUMN "resolver" SET DATA TYPE uuid USING "resolver"::uuid`,
)
await queryRunner.query(
`ALTER TABLE "comment" ALTER COLUMN "createdBy" SET DATA TYPE uuid USING "createdBy"::uuid`,
)
await queryRunner.query(
`ALTER TABLE "comment" ALTER COLUMN "lastModifiedBy" SET DATA TYPE uuid USING "lastModifiedBy"::uuid`,
)
await queryRunner.query(
`ALTER TABLE "approval" ALTER COLUMN "approver" SET DATA TYPE uuid USING "approver"::uuid`,
)
await queryRunner.query(
`ALTER TABLE "approval" ALTER COLUMN "createdBy" SET DATA TYPE uuid USING "createdBy"::uuid`,
)
await queryRunner.query(
`ALTER TABLE "approval" ALTER COLUMN "lastModifiedBy" SET DATA TYPE uuid USING "lastModifiedBy"::uuid`,
)
await queryRunner.query(
`ALTER TABLE "override" ALTER COLUMN "createdBy" SET DATA TYPE uuid USING "createdBy"::uuid`,
)
await queryRunner.query(
`ALTER TABLE "override" ALTER COLUMN "lastModifiedBy" SET DATA TYPE uuid USING "lastModifiedBy"::uuid`,
)
await queryRunner.query(
`ALTER TABLE "task" ALTER COLUMN "createdBy" SET DATA TYPE uuid USING "createdBy"::uuid`,
)
await queryRunner.query(
`ALTER TABLE "task" ALTER COLUMN "lastModifiedBy" SET DATA TYPE uuid USING "lastModifiedBy"::uuid`,
)
await queryRunner.query(
`ALTER TABLE "task" ALTER COLUMN "assignees" SET DATA TYPE uuid[] USING "assignees"::uuid[]`,
)
await queryRunner.query(
`ALTER TABLE "subscriptions" ALTER COLUMN "userId" SET DATA TYPE uuid USING "userId"::uuid`,
)
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "subscriptions" ALTER COLUMN "userId" SET DATA TYPE varchar`,
)
await queryRunner.query(
`ALTER TABLE "task" ALTER COLUMN "assignees" SET DATA TYPE text[]`,
)
await queryRunner.query(
`ALTER TABLE "task" ALTER COLUMN "lastModifiedBy" SET DATA TYPE varchar`,
)
await queryRunner.query(
`ALTER TABLE "task" ALTER COLUMN "createdBy" SET DATA TYPE varchar`,
)
await queryRunner.query(
`ALTER TABLE "override" ALTER COLUMN "lastModifiedBy" SET DATA TYPE varchar`,
)
await queryRunner.query(
`ALTER TABLE "override" ALTER COLUMN "createdBy" SET DATA TYPE varchar`,
)
await queryRunner.query(
`ALTER TABLE "approval" ALTER COLUMN "lastModifiedBy" SET DATA TYPE varchar`,
)
await queryRunner.query(
`ALTER TABLE "approval" ALTER COLUMN "createdBy" SET DATA TYPE varchar`,
)
await queryRunner.query(
`ALTER TABLE "approval" ALTER COLUMN "approver" SET DATA TYPE varchar`,
)
await queryRunner.query(
`ALTER TABLE "comment" ALTER COLUMN "lastModifiedBy" SET DATA TYPE varchar`,
)
await queryRunner.query(
`ALTER TABLE "comment" ALTER COLUMN "createdBy" SET DATA TYPE varchar`,
)
await queryRunner.query(
`ALTER TABLE "findings" ALTER COLUMN "resolver" SET DATA TYPE varchar(100)`,
)
await queryRunner.query(
`ALTER TABLE "user_profile" ALTER COLUMN "id" SET DATA TYPE varchar`,
)
await queryRunner.query(
`ALTER TABLE "long_running_token" ALTER COLUMN "lastModifiedBy" SET DATA TYPE varchar`,
)
await queryRunner.query(
`ALTER TABLE "long_running_token" ALTER COLUMN "createdBy" SET DATA TYPE varchar`,
)
await queryRunner.query(
`ALTER TABLE "long_running_token" ALTER COLUMN "kcuid" SET DATA TYPE varchar`,
)
await queryRunner.query(
`ALTER TABLE "release" ALTER COLUMN "lastModifiedBy" SET DATA TYPE varchar`,
)
await queryRunner.query(
`ALTER TABLE "release" ALTER COLUMN "createdBy" SET DATA TYPE varchar`,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export class Finding {
@Column({ type: 'timestamptz', nullable: true })
resolvedDate?: string

@Column({ type: 'varchar', length: 100, nullable: true })
@Column({ type: 'uuid', nullable: true })
resolver: string

@CreateDateColumn({ type: 'timestamptz' })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ export class ApprovalEntity {
namespace: Namespace
@ManyToOne(() => ReleaseEntity)
release: ReleaseEntity
@Column({ nullable: false })
@Column({ type: 'uuid', nullable: false })
approver: string
@Column({ type: 'enum', enum: approvalStates, nullable: false })
approvalState: ApprovalState
@Column({ nullable: false })
@Column({ type: 'uuid', nullable: false })
createdBy: string
@Column({ nullable: false })
@Column({ type: 'uuid', nullable: false })
lastModifiedBy: string
@CreateDateColumn({ type: 'timestamptz', nullable: false })
creationTime: Date
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import { z } from 'zod'
export class AddApproverDto {
@ApiProperty({
description: 'User id of the user to be added as an approver',
example: '[email protected]',
example: '221609bd-f023-406d-a9b8-7b15709758b9',
})
user: string
}
export const addApproverDtoSchema = z
.object({
user: z.string().trim().min(1),
user: z.string().trim().uuid(),
})
.strict()

Expand All @@ -42,8 +42,7 @@ export class ApprovalDto {
id: number

@ApiProperty({
description: 'User id of the approver',
example: '[email protected]',
description: 'Approver details from namespace',
})
user: UserInNamespaceDto

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ export class CommentEntity {
todo: boolean
@Column({ type: 'enum', enum: commentStatuses, nullable: false })
status: CommentStatus
@Column({ nullable: false })
@Column({ type: 'uuid', nullable: false })
createdBy: string
@Column({ nullable: false })
@Column({ type: 'uuid', nullable: false })
lastModifiedBy: string
@Column({ type: 'timestamptz', nullable: false })
creationTime: Date
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ export class OverrideEntity {
@Column()
check: string

@Column()
@Column({ type: 'uuid' })
createdBy: string

@Column()
@Column({ type: 'uuid' })
lastModifiedBy: string

@Column({ type: 'timestamptz' })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ export class ReleaseEntity {
approvalMode: ApprovalMode
@ManyToOne(() => ConfigEntity, { nullable: false })
config: ConfigEntity
@Column({ nullable: false })
@Column({ type: 'uuid', nullable: false })
createdBy: string
@Column({ nullable: false })
@Column({ type: 'uuid', nullable: false })
lastModifiedBy: string
@Column({ type: 'timestamptz', nullable: false })
plannedDate: Date
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,17 @@ export class TaskEntity {
title: string
@Column({ nullable: true })
description: string
@Column({ nullable: false })
@Column({ type: 'uuid', nullable: false })
createdBy: string
@Column({ nullable: false })
@Column({ type: 'uuid', nullable: false })
lastModifiedBy: string
@Column({ type: 'timestamptz', nullable: false })
creationTime: Date
@Column({ type: 'timestamptz', nullable: false })
lastModificationTime: Date
@Column({ nullable: false, default: false })
closed: boolean
@Column('text', { nullable: true, array: true })
@Column('uuid', { nullable: true, array: true })
assignees: string[]

DeepCopy(): TaskEntity {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,16 +378,17 @@ export const updateTaskDtoSchema = z

export class AddRemoveAssigneesDto {
@ApiProperty({
description: 'Username of the user to assign',
example: '["user1", "user2"]',
description: 'User id of the user to assign',
example:
'["ed8a6d88-42e2-4e51-88b8-95ff9f59736a", "b5c3e4ea-7e39-4121-860d-f214939d79dd"]',
type: [String],
})
assignees: string[]
}

export const addRemoveAssigneesDtoSchema = z
.object({
assignees: z.array(z.string().trim().min(1)),
assignees: z.array(z.string().trim().uuid()),
})
.strict()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {

@Entity({ name: 'subscriptions' })
export class SubscriptionEntity {
@PrimaryColumn({ type: 'varchar' })
@PrimaryColumn({ type: 'uuid' })
userId: string

@PrimaryColumn({ type: 'integer' })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
HttpException,
HttpStatus,
} from '@nestjs/common'
import { validateBody } from '@B-S-F/api-commons-lib'
import { validateBody, validateId, validateUUID } from '@B-S-F/api-commons-lib'
import { SubscriptionService } from './subscription.service'
import { z } from 'zod'
import { KeyCloakUser } from '@B-S-F/api-keycloak-auth-lib'
Expand Down Expand Up @@ -134,6 +134,8 @@ export class SubscriptionController {
@Param('userId') userId: string,
@Param('releaseId') releaseId: number,
): Promise<SubscriptionDto> {
validateId(releaseId)
validateUUID(userId)
try {
return this.subscriptionService.getSubscriptionStatus(userId, releaseId)
} catch (err) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import { SYSTEM_REQUEST_USER } from '../module.utils'

const CACHE_TIMEOUT = 1000 * 60 * 5 // 5 minutes
export const SYSTEM_USER = new UserInNamespaceDto()
SYSTEM_USER.id = 'SYSTEM_ACTOR'
SYSTEM_USER.id = '00000000-0000-0000-0000-000000000000'
SYSTEM_USER.username = 'SYSTEM_ACTOR'
SYSTEM_USER.displayName = 'SYSTEM_ACTOR'
SYSTEM_USER.displayName = 'Sytem actor (machine user)'

export const DELETED_USER = new UserInNamespaceDto()
DELETED_USER.id = 'DELETED_USER'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class LongRunningTokenEntity {
@Column()
description: string

@Column()
@Column({ type: 'uuid' })
kcuid: string

@Column()
Expand All @@ -34,10 +34,10 @@ export class LongRunningTokenEntity {
@Column({ type: 'enum', enum: statuses })
status: STATUS

@Column()
@Column({ type: 'uuid' })
createdBy: string

@Column()
@Column({ type: 'uuid' })
lastModifiedBy: string

@Column({ type: 'timestamptz' })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { EditorType } from './utils/types'

@Entity()
export class UserProfile {
@PrimaryColumn()
@PrimaryColumn({ type: 'uuid' })
id: string // Actual kc_id of the user

@Column({ default: true })
Expand Down
Loading