Skip to content

Commit

Permalink
Merge branch 'release-v1.6.1' into release-v1.6.2
Browse files Browse the repository at this point in the history
  • Loading branch information
rikukissa committed Dec 10, 2024
2 parents 595c3c9 + 9a8831c commit 364fb9f
Show file tree
Hide file tree
Showing 17 changed files with 339 additions and 170 deletions.
40 changes: 32 additions & 8 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,37 @@ jobs:
- name: Get list of services
id: get-services
run: |
services=$(grep "^ [^ ]" docker-compose.yml | grep -v '#' | awk -F: '{print $1}' | sed -e 's/^ *//')
services=$(grep "^ [^ ]" docker-compose.yml | grep -v base| grep -v '#' | awk -F: '{print $1}' | sed -e 's/^ *//')
services_json=$(echo $services | tr '\n' ',' | sed 's/,$//' | jq -R 'split(" ")' | tr -d '\n')
# Set the list of service names as an output variable
echo "services=$services_json" >> $GITHUB_OUTPUT
echo "services=$services_json"
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Build and push base image
uses: docker/build-push-action@v6
with:
file: packages/Dockerfile.base
context: .
push: true
tags: |
opencrvs/ocrvs-base:${{ steps.set-version.outputs.version }}
cache-from: type=registry,ref=opencrvs/ocrvs-base:${{ steps.set-version.outputs.version }}
cache-to: type=inline

outputs:
services: ${{ steps.get-services.outputs.services }}
version: ${{ steps.set-version.outputs.version }}

build:
needs: base
strategy:
fail-fast: false
matrix:
service: ${{ fromJSON(needs.base.outputs.services) }}
runs-on: ubuntu-22.04
Expand All @@ -74,17 +92,23 @@ jobs:
if: github.event_name == 'push'

- name: Login to DockerHub
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Build ${{ matrix.service }}
run: |
echo ${{ matrix.service }}
export VERSION=${{ github.event.inputs.release_version }}
docker compose build ${{ matrix.service }}
docker compose push ${{ matrix.service }}
- name: Build and push
uses: docker/build-push-action@v6
with:
file: packages/${{ matrix.service }}/Dockerfile
build-args: |
VERSION=${{ needs.base.outputs.version }}
push: true
context: .
tags: |
opencrvs/ocrvs-${{ matrix.service }}:${{ needs.base.outputs.version }}
cache-from: type=registry,ref=opencrvs/ocrvs-${{ matrix.service }}:${{ needs.base.outputs.version}}
cache-to: type=inline

security-scans:
needs: [base, build]
Expand Down
185 changes: 106 additions & 79 deletions CHANGELOG.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/client/src/views/RegisterForm/RegisterForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1115,6 +1115,7 @@ class RegisterFormView extends React.Component<FullProps, State> {
type="tertiary"
size="small"
onClick={this.props.goBack}
disabled={!canContinue}
>
<Icon name="ArrowLeft" size="medium" />
{intl.formatMessage(buttonMessages.back)}
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
}
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
2 changes: 1 addition & 1 deletion packages/components/src/Headings/Headings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ export const Heading2 = styled.h2`
export const Heading3 = styled.h3`
${({ theme }) => theme.fonts.h3};
color: ${({ theme }) => theme.colors.grey600};
padding: 8px 0px;
padding: 18px 0px 8px 0px;
border-top: 1px solid ${({ theme }) => theme.colors.grey200};
`
31 changes: 28 additions & 3 deletions packages/gateway/src/features/role/root-resolvers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,17 +202,42 @@ describe('Role root resolvers', () => {
}
]
it('returns full role list', async () => {
const sysAdminToken = jwt.sign(
{ scope: ['natlsysadmin'] },
readFileSync('./test/cert.key'),
{
subject: 'ba7022f0ff4822',
algorithm: 'RS256',
issuer: 'opencrvs:auth-service',
audience: 'opencrvs:gateway-user'
}
)
const authHeaderSysAdmin = {
Authorization: `Bearer ${sysAdminToken}`
}
fetch.mockResponseOnce(JSON.stringify(dummyRoleList))

const response = await resolvers.Query!.getSystemRoles(
{},
{},
{ headers: undefined }
{ headers: authHeaderSysAdmin }
)

expect(response).toEqual(dummyRoleList)
})
it('returns filtered role list', async () => {
const sysAdminToken = jwt.sign(
{ scope: ['sysadmin'] },
readFileSync('./test/cert.key'),
{
subject: 'ba7022f0ff4822',
algorithm: 'RS256',
issuer: 'opencrvs:auth-service',
audience: 'opencrvs:gateway-user'
}
)
const authHeaderSysAdmin = {
Authorization: `Bearer ${sysAdminToken}`
}
fetch.mockResponseOnce(JSON.stringify([dummyRoleList[2]]))

const response = await resolvers.Query!.getSystemRoles(
Expand All @@ -225,7 +250,7 @@ describe('Role root resolvers', () => {
type: 'Mayor',
active: true
},
{ headers: undefined }
{ headers: authHeaderSysAdmin }
)
expect(response).toEqual([dummyRoleList[2]])
})
Expand Down
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
Expand Up @@ -8,6 +8,58 @@
*
* Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS.
*/

import { Scope } from '@opencrvs/commons/authentication'

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]
}
12 changes: 11 additions & 1 deletion packages/gateway/src/features/user/root-resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import {
hasScope,
inScope,
isTokenOwner,
getUserId
getUserId,
canAssignRole
} from '@gateway/features/user/utils'
import {
GQLHumanNameInput,
Expand All @@ -37,6 +38,7 @@ import { validateAttachments } from '@gateway/utils/validators'
import { postMetrics } from '@gateway/features/metrics/service'
import { uploadBase64ToMinio } from '@gateway/features/documents/service'
import { rateLimitedResolver } from '@gateway/rate-limit'
import { getTokenPayload } from '@opencrvs/commons/authentication'

export const resolvers: GQLResolver = {
Query: {
Expand Down Expand Up @@ -272,6 +274,14 @@ export const resolvers: GQLResolver = {
)
}

const { scope: loggedInUserScope } = getTokenPayload(
authHeader.Authorization.split(' ')[1]
)

if (!canAssignRole(loggedInUserScope, user)) {
throw Error('Create user is only allowed for sysadmin/natlsysadmin')
}

try {
if (user.signature) {
await validateAttachments([user.signature])
Expand Down
Loading

0 comments on commit 364fb9f

Please sign in to comment.