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

ocrvs-7698 / ocrvs-7691 Stops local sys admin from creating national level staff #7996

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
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: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

### Breaking changes

- **Dashboard:** Changes made to the dashboard configuration will reset after upgrading OpenCRVS.
- **Dashboard:** Changes made to the dashboard configuration will reset after upgrading OpenCRVS.

## Improvements

Expand Down Expand Up @@ -37,6 +37,13 @@
- Only show items with values in review [#5192](https://github.com/opencrvs/opencrvs-core/pull/5192)
- Fix prefix text overlap issue in form text inputs

## 1.6.1

## Bug fixes

- Stops local sys admins creating national level users. [#7698](https://github.com/opencrvs/opencrvs-core/issues/7698)
- Stops sys admins deactivating themselves by mistake. [#7691](https://github.com/opencrvs/opencrvs-core/issues/7691)

## 1.6.0 Release candidate

## Improvements
Expand Down
13 changes: 9 additions & 4 deletions packages/client/src/views/SysAdmin/Team/user/UserList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ import { SysAdminContentWrapper } from '@client/views/SysAdmin/SysAdminContentWr
import {
getAddressName,
getUserRoleIntlKey,
UserStatus
UserStatus,
canDeactivateUser
} from '@client/views/SysAdmin/Team/utils'
import { LinkButton } from '@opencrvs/components/lib/buttons'
import { Button } from '@opencrvs/components/lib/Button'
Expand Down Expand Up @@ -396,7 +397,7 @@ function UserListComponent(props: IProps) {
)

const getMenuItems = useCallback(
function getMenuItems(user: User) {
function getMenuItems(user: User, userDetails: UserDetails | null) {
const menuItems = [
{
label: intl.formatMessage(messages.editUserDetailsTitle),
Expand Down Expand Up @@ -432,7 +433,11 @@ function UserListComponent(props: IProps) {
})
}

if (user.status === 'active') {
if (
userDetails &&
user.status === 'active' &&
canDeactivateUser(user.id, userDetails)
) {
menuItems.push({
label: intl.formatMessage(messages.deactivate),
handler: () => toggleUserActivationModal(user)
Expand Down Expand Up @@ -530,7 +535,7 @@ function UserListComponent(props: IProps) {
toggleButton={
<Icon name="DotsThreeVertical" color="primary" size="large" />
}
menuItems={getMenuItems(user)}
menuItems={getMenuItems(user, userDetails)}
/>
)}
</Stack>
Expand Down
5 changes: 5 additions & 0 deletions packages/client/src/views/SysAdmin/Team/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { IntlShape, MessageDescriptor } from 'react-intl'
import { messages } from '@client/i18n/messages/views/userSetup'
import { SystemRoleType } from '@client/utils/gateway'
import { ILocation, IOfflineData } from '@client/offline/reducer'
import { UserDetails } from '@client/utils/userUtils'

export enum UserStatus {
ACTIVE,
Expand Down Expand Up @@ -112,3 +113,7 @@ export function getUserSystemRole(
export const getUserRoleIntlKey = (_roleId: string) => {
return `role.${_roleId}`
}

export const canDeactivateUser = (id: string, userDetails: UserDetails) => {
return id !== userDetails.id ? true : false
}
Comment on lines +117 to +119
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/opencrvs/opencrvs-core/blob/develop/packages/gateway/src/features/user/root-resolvers.ts#L500-L511

Should we add the same condition to the backend endpoint? Or maybe it's enough just to prevent it from happening in the UI? Guess it's not that big of a deal

11 changes: 9 additions & 2 deletions packages/client/src/views/UserAudit/UserAudit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ import { AvatarSmall } from '@client/components/Avatar'
import styled from 'styled-components'
import { ToggleMenu } from '@opencrvs/components/lib/ToggleMenu'
import { Button } from '@opencrvs/components/lib/Button'
import { getUserRoleIntlKey } from '@client/views/SysAdmin//Team/utils'
import {
getUserRoleIntlKey,
canDeactivateUser
} from '@client/views/SysAdmin/Team/utils'
import { EMPTY_STRING, LANG_EN } from '@client/utils/constants'
import { Loader } from '@opencrvs/components/lib/Loader'
import { messages as userSetupMessages } from '@client/i18n/messages/views/userSetup'
Expand Down Expand Up @@ -246,7 +249,11 @@ export const UserAudit = () => {
)
}

if (status === 'active') {
if (
status === 'active' &&
userDetails &&
canDeactivateUser(userId, userDetails)
) {
menuItems.push({
label: intl.formatMessage(sysMessages.deactivate),
handler: () => toggleUserActivationModal()
Expand Down
10 changes: 10 additions & 0 deletions packages/user-mgnt/src/features/createUser/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import * as fetchMock from 'jest-fetch-mock'
import * as jwt from 'jsonwebtoken'
import * as mockingoose from 'mockingoose'
import { Types } from 'mongoose'
import { dummyRoleListNatlSysAdmin } from '@user-mgnt/features/getRoles/handler.test'
import SystemRole from '@user-mgnt/model/systemRole'

const fetch = fetchMock as fetchMock.FetchMock

Expand Down Expand Up @@ -71,10 +73,18 @@ describe('createUser handler', () => {
mockingoose.resetAll()
server = await createServer()
fetch.resetMocks()
SystemRole.find = jest.fn().mockReturnValue(dummyRoleListNatlSysAdmin)
SystemRole.find().sort = jest
.fn()
.mockReturnValue(dummyRoleListNatlSysAdmin)
SystemRole.find().populate = jest
.fn()
.mockReturnValue(dummyRoleListNatlSysAdmin)
})

it('creates and saves fhir resources and adds user using mongoose', async () => {
fetch.mockResponses(
['', { status: 201, headers: { Location: 'Practitioner/123' } }],
['', { status: 201, headers: { Location: 'Practitioner/123' } }],
['', { status: 201, headers: { Location: 'PractitionerRole/123' } }],
['', { status: 200 }]
Expand Down
11 changes: 9 additions & 2 deletions packages/user-mgnt/src/features/createUser/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ import {
generateSaltedHash,
generateRandomPassword
} from '@user-mgnt/utils/hash'
import { statuses, hasDemoScope, getUserId } from '@user-mgnt/utils/userUtils'
import {
statuses,
hasDemoScope,
getUserId,
getValidRoles
} from '@user-mgnt/utils/userUtils'
import { userRoleScopes } from '@opencrvs/commons/authentication'
import { QA_ENV } from '@user-mgnt/constants'
import * as Hapi from '@hapi/hapi'
Expand All @@ -36,7 +41,9 @@ export default async function createUser(
) {
const user = request.payload as IUser & { password?: string }
const token = request.headers.authorization

if (!getValidRoles({}, 'asc', 'creationDate', token, user.systemRole)) {
return h.response().code(400)
}
Comment on lines +44 to +46
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's have the access management in gateway where we have some parts of it already

// construct Practitioner resource and save them
let practitionerId = null
let roleId = null
Expand Down
195 changes: 185 additions & 10 deletions packages/user-mgnt/src/features/getRoles/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ beforeEach(async () => {
server = await createServer()
})

const token = jwt.sign(
const localSysAdmintoken = jwt.sign(
{ scope: ['sysadmin'] },
readFileSync('./test/cert.key'),
{
Expand All @@ -28,7 +28,16 @@ const token = jwt.sign(
audience: 'opencrvs:user-mgnt-user'
}
)
const dummyRoleList = [
const nationalSysAdmintoken = jwt.sign(
{ scope: ['natlsysadmin', 'sysadmin'] },
readFileSync('./test/cert.key'),
{
algorithm: 'RS256',
issuer: 'opencrvs:auth-service',
audience: 'opencrvs:user-mgnt-user'
}
)
export const dummyRoleListNatlSysAdmin = [
{
_id: '63a06b979538ca7ab52f9759',
active: true,
Expand Down Expand Up @@ -207,24 +216,169 @@ const dummyRoleList = [
}
]

const dummyRoleListLocalSysAdmin = [
{
_id: '63a06b979538ca7ab52f9759',
active: true,
value: 'FIELD_AGENT',
creationDate: 1671457687106,
roles: [
{
labels: [
{
lang: 'en',
label: 'Healthcare Worker'
},
{
lang: 'fr',
label: 'Professionnel de Santé'
}
]
},
{
labels: [
{
lang: 'en',
label: 'Police Officer'
},
{
lang: 'fr',
label: 'Agent de Police'
}
]
},
{
labels: [
{
lang: 'en',
label: 'Social Worker'
},
{
lang: 'fr',
label: 'Travailleur Social'
}
]
},
{
labels: [
{
lang: 'en',
label: 'Local Leader'
},
{
lang: 'fr',
label: 'Leader Local'
}
]
}
]
},
{
_id: '63a06b979538ca7ab52f975a',
active: true,
value: 'REGISTRATION_AGENT',
creationDate: 1671457687107,
roles: [
{
labels: [
{
lang: 'en',
label: 'Registration Agent'
},
{
lang: 'fr',
label: "Agent d'enregistrement"
}
]
}
]
},
{
_id: '63a06b979538ca7ab52f975b',
active: true,
value: 'LOCAL_REGISTRAR',
creationDate: 1671457687107,
roles: [
{
labels: [
{
lang: 'en',
label: 'Local Registrar'
},
{
lang: 'fr',
label: 'Registraire local'
}
]
}
]
},
{
_id: '63a06b979538ca7ab52f975c',
active: true,
value: 'LOCAL_SYSTEM_ADMIN',
creationDate: 1671457687107,
roles: [
{
labels: [
{
lang: 'en',
label: 'Local System_admin'
},
{
lang: 'fr',
label: 'Administrateur système local'
}
]
}
]
},
{
_id: '63a06b979538ca7ab52f975e',
active: true,
value: 'PERFORMANCE_MANAGEMENT',
creationDate: 1671457687107,
roles: [
{
labels: [
{
lang: 'en',
label: 'Performance Management'
},
{
lang: 'fr',
label: 'Gestion des performances'
}
]
}
]
}
]

describe('getSystemRoles tests', () => {
it('Successfully returns full role list', async () => {
SystemRole.find = jest.fn().mockReturnValue(dummyRoleList)
SystemRole.find().sort = jest.fn().mockReturnValue(dummyRoleList)
SystemRole.find().populate = jest.fn().mockReturnValue(dummyRoleList)
SystemRole.find = jest.fn().mockReturnValue(dummyRoleListNatlSysAdmin)
SystemRole.find().sort = jest
.fn()
.mockReturnValue(dummyRoleListNatlSysAdmin)
SystemRole.find().populate = jest
.fn()
.mockReturnValue(dummyRoleListNatlSysAdmin)

const res = await server.server.inject({
method: 'POST',
url: '/getSystemRoles',
payload: {},
headers: {
Authorization: `Bearer ${token}`
Authorization: `Bearer ${nationalSysAdmintoken}`
}
})
expect(res.result).toEqual(dummyRoleList)
expect(JSON.stringify(res.result)).toEqual(
JSON.stringify(dummyRoleListNatlSysAdmin)
)
})
it('Successfully returns filtered user list', async () => {
const filteredList = [dummyRoleList[2]]
const filteredList = [dummyRoleListNatlSysAdmin[2]]
SystemRole.find = jest.fn().mockReturnValue(filteredList)
SystemRole.find().sort = jest.fn().mockReturnValue(filteredList)
SystemRole.find().populate = jest.fn().mockReturnValue(filteredList)
Expand All @@ -240,10 +394,31 @@ describe('getSystemRoles tests', () => {
active: true
},
headers: {
Authorization: `Bearer ${token}`
Authorization: `Bearer ${nationalSysAdmintoken}`
}
})

expect(res.result).toEqual(filteredList)
expect(JSON.stringify(res.result)).toEqual(JSON.stringify(filteredList))
})
it('Successfully returns local sys admin role list minus national roles', async () => {
SystemRole.find = jest.fn().mockReturnValue(dummyRoleListNatlSysAdmin)
SystemRole.find().sort = jest
.fn()
.mockReturnValue(dummyRoleListNatlSysAdmin)
SystemRole.find().populate = jest
.fn()
.mockReturnValue(dummyRoleListNatlSysAdmin)

const res = await server.server.inject({
method: 'POST',
url: '/getSystemRoles',
payload: {},
headers: {
Authorization: `Bearer ${localSysAdmintoken}`
}
})
expect(JSON.stringify(res.result)).toEqual(
JSON.stringify(dummyRoleListLocalSysAdmin)
)
})
})
Loading
Loading