Skip to content

Commit

Permalink
Filter User roles based on user that's requesting
Browse files Browse the repository at this point in the history
This is avoid users with lower roles creating or updating other users with higher roles

#7698
  • Loading branch information
Siyasanga committed Nov 29, 2024
1 parent fb400bd commit b46c67e
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 1,913 deletions.
16 changes: 14 additions & 2 deletions packages/gateway/src/features/role/root-resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ import { GQLResolver } from '@gateway/graphql/schema'
import fetch from '@gateway/fetch'
import { USER_MANAGEMENT_URL } from '@gateway/constants'
import { IRoleSearchPayload } from '@gateway/features/role/type-resolvers'
import { transformMongoComparisonObject } from '@gateway/features/role/utils'
import {
getAccessibleRolesForScope,
SystemRole,
transformMongoComparisonObject
} from '@gateway/features/role/utils'
import { hasScope } from '@gateway/features/user/utils'
import { getTokenPayload } from '@opencrvs/commons/authentication'

export const resolvers: GQLResolver = {
Query: {
Expand Down Expand Up @@ -51,6 +56,7 @@ export const resolvers: GQLResolver = {
if (active !== null) {
payload = { ...payload, active }
}

const res = await fetch(`${USER_MANAGEMENT_URL}getSystemRoles`, {
method: 'POST',
body: JSON.stringify(payload),
Expand All @@ -59,7 +65,13 @@ export const resolvers: GQLResolver = {
...authHeader
}
})
return await res.json()

const { scope } = getTokenPayload(authHeader.Authorization.split(' ')[1])
const accessibleSysAdminRoles = getAccessibleRolesForScope(scope)
const allSysAdminRoles = (await res.json()) as SystemRole[]
return allSysAdminRoles.filter((sysAdminRole) =>
accessibleSysAdminRoles?.includes(sysAdminRole.value)
)
}
},
Mutation: {
Expand Down
65 changes: 65 additions & 0 deletions packages/gateway/src/features/role/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Scope } from '@opencrvs/commons/authentication'

/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
Expand All @@ -8,6 +10,56 @@
*
* Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS.
*/

export const SYSTEM_ROLE_KEYS = [
'FIELD_AGENT',
'LOCAL_REGISTRAR',
'LOCAL_SYSTEM_ADMIN',
'NATIONAL_REGISTRAR',
'NATIONAL_SYSTEM_ADMIN',
'PERFORMANCE_MANAGEMENT',
'REGISTRATION_AGENT'
] as const

// Derive the type from SYSTEM_ROLE_KEYS
type SystemRoleKeyType = (typeof SYSTEM_ROLE_KEYS)[number]

export const SysAdminAccessMap: Partial<
Record<SystemRoleKeyType, SystemRoleKeyType[]>
> = {
LOCAL_SYSTEM_ADMIN: [
'FIELD_AGENT',
'LOCAL_REGISTRAR',
'LOCAL_SYSTEM_ADMIN',
'PERFORMANCE_MANAGEMENT',
'REGISTRATION_AGENT'
],
NATIONAL_SYSTEM_ADMIN: [
'FIELD_AGENT',
'LOCAL_REGISTRAR',
'LOCAL_SYSTEM_ADMIN',
'NATIONAL_REGISTRAR',
'NATIONAL_SYSTEM_ADMIN',
'PERFORMANCE_MANAGEMENT',
'REGISTRATION_AGENT'
]
}

type UserRole = {
labels: Label[]
}

type Label = {
lang: string
label: string
}

export type SystemRole = {
value: SystemRoleKeyType
roles: UserRole[]
active: boolean
creationDate: number
}
export interface IComparisonObject {
eq?: string
gt?: string
Expand Down Expand Up @@ -46,3 +98,16 @@ export function transformMongoComparisonObject(
{}
)
}

export function getAccessibleRolesForScope(scope: Scope[]) {
let roleFilter: keyof typeof SysAdminAccessMap
if (scope.includes('natlsysadmin')) {
roleFilter = 'NATIONAL_SYSTEM_ADMIN'
} else if (scope.includes('sysadmin')) {
roleFilter = 'LOCAL_SYSTEM_ADMIN'
} else {
throw Error('Create user is only allowed for sysadmin/natlsysadmin')
}

return SysAdminAccessMap[roleFilter]
}
56 changes: 2 additions & 54 deletions packages/gateway/src/features/user/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import * as decode from 'jwt-decode'
import fetch from '@gateway/fetch'
import { Scope } from '@opencrvs/commons/authentication'
import { GQLUserInput } from '@gateway/graphql/schema'
import { SysAdminAccessMap } from '@gateway/features/role/utils'

export interface ITokenPayload {
sub: string
Expand All @@ -35,60 +36,6 @@ export type scopeType =
| 'sysadmin'
| 'performance'

type RoleSearchPayload = {
title?: string
value?: MongoComparisonObject
role?: string
active?: boolean
sortBy?: string
sortOrder?: string
}

type MongoComparisonObject = {
$eq?: string
$gt?: string
$lt?: string
$gte?: string
$lte?: string
$in?: string[]
$ne?: string
$nin?: string[]
}

const SYSTEM_ROLE_TYPES = [
'FIELD_AGENT',
'LOCAL_REGISTRAR',
'LOCAL_SYSTEM_ADMIN',
'NATIONAL_REGISTRAR',
'NATIONAL_SYSTEM_ADMIN',
'PERFORMANCE_MANAGEMENT',
'REGISTRATION_AGENT'
] as const

// Derive the type from SYSTEM_ROLE_TYPES
type SystemRoleType = (typeof SYSTEM_ROLE_TYPES)[number]

export const SysAdminAccessMap: Partial<
Record<SystemRoleType, SystemRoleType[]>
> = {
LOCAL_SYSTEM_ADMIN: [
'FIELD_AGENT',
'LOCAL_REGISTRAR',
'LOCAL_SYSTEM_ADMIN',
'PERFORMANCE_MANAGEMENT',
'REGISTRATION_AGENT'
],
NATIONAL_SYSTEM_ADMIN: [
'FIELD_AGENT',
'LOCAL_REGISTRAR',
'LOCAL_SYSTEM_ADMIN',
'NATIONAL_REGISTRAR',
'NATIONAL_SYSTEM_ADMIN',
'PERFORMANCE_MANAGEMENT',
'REGISTRATION_AGENT'
]
}

export async function getUser(
body: { [key: string]: string | undefined },
authHeader: IAuthHeader
Expand All @@ -103,6 +50,7 @@ export async function getUser(
})
return await res.json()
}

export function canAssignRole(
loggedInUserScope: Scope[],
userToSave: GQLUserInput
Expand Down
148 changes: 74 additions & 74 deletions packages/gateway/src/graphql/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1040,45 +1040,6 @@ export interface GQLOIDPUserInfo {
updated_at?: number
}

export interface GQLPersonInput {
_fhirID?: string
identifier?: Array<GQLIdentityInput | null>
name?: Array<GQLHumanNameInput | null>
telecom?: Array<GQLContactPointInput | null>
gender?: GQLGender
birthDate?: GQLPlainDate
age?: number
maritalStatus?: string
occupation?: string
detailsExist?: boolean
reasonNotApplying?: string
dateOfMarriage?: GQLPlainDate
multipleBirth?: number
address?: Array<GQLAddressInput | null>
photo?: Array<GQLAttachmentInput>
deceased?: GQLDeceasedInput
nationality?: Array<string | null>
educationalAttainment?: string
ageOfIndividualInYears?: number
}

export interface GQLLocationInput {
_fhirID?: string
identifier?: Array<string | null>
status?: string
name?: string
alias?: Array<string | null>
description?: string
partOf?: string
type?: string
telecom?: Array<GQLContactPointInput | null>
address?: GQLAddressInput
longitude?: number
latitude?: number
altitude?: number
geoData?: string
}

export interface GQLAttachmentInput {
_fhirID?: string
contentType?: string
Expand Down Expand Up @@ -1109,6 +1070,23 @@ export interface GQLCorrectionValueInput {
newValue: GQLFieldValue
}

export interface GQLLocationInput {
_fhirID?: string
identifier?: Array<string | null>
status?: string
name?: string
alias?: Array<string | null>
description?: string
partOf?: string
type?: string
telecom?: Array<GQLContactPointInput | null>
address?: GQLAddressInput
longitude?: number
latitude?: number
altitude?: number
geoData?: string
}

export interface GQLFHIRIDMap {
composition?: string
encounter?: string
Expand Down Expand Up @@ -1145,6 +1123,28 @@ export interface GQLRegistrationInput {
changedValues?: Array<GQLCorrectionValueInput>
}

export interface GQLPersonInput {
_fhirID?: string
identifier?: Array<GQLIdentityInput | null>
name?: Array<GQLHumanNameInput | null>
telecom?: Array<GQLContactPointInput | null>
gender?: GQLGender
birthDate?: GQLPlainDate
age?: number
maritalStatus?: string
occupation?: string
detailsExist?: boolean
reasonNotApplying?: string
dateOfMarriage?: GQLPlainDate
multipleBirth?: number
address?: Array<GQLAddressInput | null>
photo?: Array<GQLAttachmentInput>
deceased?: GQLDeceasedInput
nationality?: Array<string | null>
educationalAttainment?: string
ageOfIndividualInYears?: number
}

export interface GQLRelatedPersonInput {
id?: string
_fhirID?: string
Expand Down Expand Up @@ -1523,26 +1523,30 @@ export interface GQLOIDPUserAddress {
country?: string
}

export interface GQLIdentityInput {
id?: string
type?: string
otherType?: string
fieldsModifiedByIdentity?: Array<string | null>
export const enum GQLAttachmentInputStatus {
approved = 'approved',
validated = 'validated',
deleted = 'deleted'
}

export const enum GQLPaymentType {
MANUAL = 'MANUAL'
}

export const enum GQLPaymentOutcomeType {
COMPLETED = 'COMPLETED',
ERROR = 'ERROR',
PARTIAL = 'PARTIAL'
}

export type GQLFieldValue = any

export interface GQLContactPointInput {
system?: GQLTelecomSystem
value?: string
use?: GQLTelecomUse
}

export const enum GQLGender {
male = 'male',
female = 'female',
other = 'other',
unknown = 'unknown'
}

export interface GQLAddressInput {
use?: GQLAddressUse
type?: GQLAddressType
Expand All @@ -1558,29 +1562,6 @@ export interface GQLAddressInput {
partOf?: string
}

export interface GQLDeceasedInput {
deceased?: boolean
deathDate?: GQLPlainDate
}

export const enum GQLAttachmentInputStatus {
approved = 'approved',
validated = 'validated',
deleted = 'deleted'
}

export const enum GQLPaymentType {
MANUAL = 'MANUAL'
}

export const enum GQLPaymentOutcomeType {
COMPLETED = 'COMPLETED',
ERROR = 'ERROR',
PARTIAL = 'PARTIAL'
}

export type GQLFieldValue = any

export interface GQLObservationFHIRIDS {
maleDependentsOfDeceased?: string
femaleDependentsOfDeceased?: string
Expand Down Expand Up @@ -1615,6 +1596,25 @@ export interface GQLCertificateInput {
data?: string
}

export interface GQLIdentityInput {
id?: string
type?: string
otherType?: string
fieldsModifiedByIdentity?: Array<string | null>
}

export const enum GQLGender {
male = 'male',
female = 'female',
other = 'other',
unknown = 'unknown'
}

export interface GQLDeceasedInput {
deceased?: boolean
deathDate?: GQLPlainDate
}

export interface GQLLabelInput {
lang: string
label: string
Expand Down
Loading

0 comments on commit b46c67e

Please sign in to comment.