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

test: Add permissions and roles e2e tests #4172

Draft
wants to merge 56 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
c512014
test: Add permissions and roles e2e tests
novakzaballa Jun 12, 2024
cbdb48e
Update project-permissio-test
novakzaballa Jun 13, 2024
e13a27a
Update project and environments permissions
novakzaballa Jun 14, 2024
801e406
Add data-test property
novakzaballa Jun 18, 2024
10d2538
Role test
novakzaballa Jun 18, 2024
24e881d
Update the role test, and include permission and role test in the wor…
novakzaballa Jun 19, 2024
4a44fec
solve merge issues
novakzaballa Aug 12, 2024
6bb85fe
Merge branch 'main' into test/add-permissions-and-roles-e2e-tests
novakzaballa Aug 12, 2024
8db66c4
Merge branch 'main' into test/add-permissions-and-roles-e2e-tests
novakzaballa Aug 13, 2024
0cbc289
Solve roles test
novakzaballa Aug 15, 2024
83b3b2e
Add tag based permissions
kyle-ssg Sep 18, 2024
e838de4
Merge branch 'refs/heads/main' into test/add-permissions-and-roles-e2…
matthewelwell Oct 7, 2024
e188231
Post merge additions
matthewelwell Oct 7, 2024
c8d4532
Merge branch 'refs/heads/main' into feat/tag-based-permissions
kyle-ssg Oct 8, 2024
58e6478
Merge
kyle-ssg Oct 8, 2024
dcef5ae
Tag based permissions flag
kyle-ssg Nov 5, 2024
82519f2
Add tag based permissions ui
kyle-ssg Nov 5, 2024
716706b
tag based role ui
kyle-ssg Nov 6, 2024
2c71cfa
Add tag_based_permissions logic to frontend
kyle-ssg Nov 6, 2024
3634609
Merge branch 'refs/heads/main' into feat/tag-based-permissions-valida…
kyle-ssg Nov 12, 2024
985cceb
Adjust tag base permissions data structure
kyle-ssg Nov 12, 2024
c09d39b
Adjust tag base permissions data structure
kyle-ssg Nov 12, 2024
a940e9a
Better permissions view for create feature
kyle-ssg Nov 12, 2024
750054b
Merge branch 'refs/heads/main' into feat/tag-based-permissions
kyle-ssg Nov 12, 2024
1837651
Merge branch 'refs/heads/main' into feat/tag-based-permissions
kyle-ssg Nov 12, 2024
48d54c5
Remove static tag_based variable
kyle-ssg Nov 12, 2024
f962c1c
Remove incorrect function call
kyle-ssg Nov 12, 2024
243c7a4
Adjust doc wording
kyle-ssg Nov 12, 2024
9ad625f
Merge branch 'refs/heads/main' into feat/tag-based-permissions
kyle-ssg Nov 13, 2024
bf5d177
Permissions v2
kyle-ssg Nov 13, 2024
72e5d8c
Tag based permissions UI
kyle-ssg Nov 19, 2024
195c400
Merge branch 'refs/heads/main' into feat/tag-based-permissions-v2
kyle-ssg Nov 19, 2024
d2b7011
Interop edit permissions / role-based permissions
kyle-ssg Nov 19, 2024
8482f89
Adjust UI for limited permissions
kyle-ssg Nov 19, 2024
29051be
LIMITED > GRANTED_FOR_TAGS
kyle-ssg Nov 19, 2024
23f5d18
Merge branch 'refs/heads/main' into feat/tag-based-permissions-v2
kyle-ssg Nov 27, 2024
7b2f458
Fix
kyle-ssg Nov 27, 2024
050f305
Integrate new API
kyle-ssg Dec 3, 2024
ae40fbc
Merge branch 'refs/heads/main' into feat/tag-based-permissions-v2
kyle-ssg Dec 3, 2024
f347559
Update docs
kyle-ssg Dec 3, 2024
6d986f3
Merge remote-tracking branch 'refs/remotes/origin/main' into test/add…
kyle-ssg Dec 3, 2024
f3e6082
Update tests
kyle-ssg Dec 3, 2024
2753ab8
Merge remote-tracking branch 'refs/remotes/origin/feat/tag-based-perm…
kyle-ssg Dec 3, 2024
db27e30
Merge tag based roles
kyle-ssg Dec 3, 2024
1422ab9
Merge tag based roles
kyle-ssg Dec 3, 2024
af13ff7
add tests
kyle-ssg Dec 3, 2024
244a675
Add tests
kyle-ssg Dec 4, 2024
f450416
Merge branch 'refs/heads/main' into test/add-permissions-and-roles-e2…
kyle-ssg Dec 4, 2024
0b7b8da
more tests
kyle-ssg Dec 4, 2024
ec813ab
setuser permissions helper
kyle-ssg Dec 6, 2024
3c1a3f7
Merge branch 'refs/heads/main' into test/add-permissions-and-roles-e2…
kyle-ssg Dec 10, 2024
a7be3f7
Remove debugger
kyle-ssg Dec 10, 2024
bc71237
Fix type and tags copy
kyle-ssg Dec 10, 2024
08b5795
Add tests for all project permissions
kyle-ssg Dec 10, 2024
44246b5
re-add tests
kyle-ssg Dec 10, 2024
00f935b
re-add tests
kyle-ssg Dec 10, 2024
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
2 changes: 2 additions & 0 deletions .github/workflows/platform-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,5 @@ jobs:
concurrency: 2
- tests: versioning
concurrency: 1
- tests: organisation-permission environment-permission project-permission roles
concurrency: 1
45 changes: 26 additions & 19 deletions docs/docs/system-administration/rbac.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ Assigning roles to groups has several benefits over assigning permissions direct

Permissions can be assigned at four levels: user group, organisation, project, and environment.

## Tagged Permissions

When creating a role, some permissions allow you to grant access when features have specific tags. For example, you can
configure a role to create change requests only for features tagged with "marketing".

### User group

| Permission | Ability |
Expand All @@ -149,25 +154,27 @@ Permissions can be assigned at four levels: user group, organisation, project, a

### Project

| Permission | Ability |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Administrator | Grants full read and write access to all environments, features and segments. |
| View Project | Allows viewing this project. The project is hidden from users without this permission. |
| Create Environment | Allows creating new environments in this project. Users are automatically granted Administrator permissions on any environments they create. |
| Create Feature | Allows creating new features in all environments. |
| Delete Feature | Allows deleting features from all environments. |
| Manage Segments | Grants write access to segments in this project. |
| View audit log | Allows viewing all audit log entries for this project. |
### Project

| Permission | Ability | Supports Tags |
kyle-ssg marked this conversation as resolved.
Show resolved Hide resolved
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| Administrator | Grants full read and write access to all environments, features, and segments. | |
| View Project | Allows viewing this project. The project is hidden from users without this permission. | |
| Create Environment | Allows creating new environments in this project. Users are automatically granted Administrator permissions on any environments they create. | |
| Create Feature | Allows creating new features in all environments. | |
| Delete Feature | Allows deleting features from all environments. | Yes |
| Manage Segments | Grants write access to segments in this project. | |
| View audit log | Allows viewing all audit log entries for this project. | |

### Environment

| Permission | Ability |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
| Administrator | Grants full read and write access to all feature states, overrides, identities and change requests in this environment. |
| View Environment | Allows viewing this environment. The environment is hidden from users without this permission. |
| Update Feature State | Allows updating updating any feature state or values in this environment. |
| Manage Identities | Grants read and write access to identities in this environment. |
| Manage Segment Overrides | Grants write access to segment overrides in this environment. |
| Create Change Request | Allows creating change requests for features in this environment. |
| Approve Change Request | Allows approving or denying change requests in this environment. |
| View Identities | Grants read-only access to identities in this environment. |
| Permission | Ability | Supports Tags |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------ | ------------- |
| Administrator | Grants full read and write access to all feature states, overrides, identities, and change requests in this environment. | |
| View Environment | Allows viewing this environment. The environment is hidden from users without this permission. | |
| Update Feature State | Allows updating any feature state or values in this environment. | Yes |
| Manage Identities | Grants read and write access to identities in this environment. | |
| Manage Segment Overrides | Grants write access to segment overrides in this environment. | |
| Create Change Request | Allows creating change requests for features in this environment. | Yes |
| Approve Change Request | Allows approving or denying change requests in this environment. | Yes |
| View Identities | Grants read-only access to identities in this environment. | |
4 changes: 4 additions & 0 deletions frontend/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ app.get('/config/project-overrides', (req, res) => {
name: 'githubAppURL',
value: process.env.GITHUB_APP_URL,
},
{
name: 'e2eToken',
value: process.env.E2E_TEST_TOKEN || '',
},
]
let output = values.map(getVariable).join('')
let dynatrace = ''
Expand Down
33 changes: 27 additions & 6 deletions frontend/common/providers/Permission.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import React, { FC, ReactNode } from 'react'
import React, { FC, ReactNode, useMemo } from 'react'
import { useGetPermissionQuery } from 'common/services/usePermission'
import { PermissionLevel } from 'common/types/requests'
import AccountStore from 'common/stores/account-store' // we need this to make JSX compile
import AccountStore from 'common/stores/account-store'
import intersection from 'lodash/intersection'
import { add } from 'ionicons/icons';
import { cloneDeep } from 'lodash'; // we need this to make JSX compile

type PermissionType = {
id: any
permission: string
tags?: number[]
level: PermissionLevel
children: (data: { permission: boolean; isLoading: boolean }) => ReactNode
}
Expand All @@ -14,11 +18,26 @@ export const useHasPermission = ({
id,
level,
permission,
tags,
}: Omit<PermissionType, 'children'>) => {
const { data, isLoading, isSuccess } = useGetPermissionQuery(
{ id: `${id}`, level },
{ skip: !id || !level },
)
const {
data: permissionsData,
isLoading,
isSuccess,
} = useGetPermissionQuery({ id: `${id}`, level }, { skip: !id || !level })
const data = useMemo(() => {
if (!tags?.length || !permissionsData?.tag_based_permissions)
return permissionsData
const addedPermissions = cloneDeep(permissionsData)
permissionsData.tag_based_permissions.forEach((tagBasedPermission) => {
if (intersection(tagBasedPermission.tags, tags).length) {
tagBasedPermission.permissions.forEach((key) => {
addedPermissions[key] = true
})
}
})
return addedPermissions
}, [permissionsData, tags])
const hasPermission = !!data?.[permission] || !!data?.ADMIN
return {
isLoading,
Expand All @@ -32,11 +51,13 @@ const Permission: FC<PermissionType> = ({
id,
level,
permission,
tags,
}) => {
const { isLoading, permission: hasPermission } = useHasPermission({
id,
level,
permission,
tags,
})
return (
<>
Expand Down
17 changes: 13 additions & 4 deletions frontend/common/services/usePermission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,25 @@ export const permissionService = service
query: ({ id, level }: Req['getPermission']) => ({
url: `${level}s/${id}/my-permissions/`,
}),
transformResponse(baseQueryReturnValue: {
admin: boolean
permissions: string[]
}) {
transformResponse(
baseQueryReturnValue: {
admin: boolean
permissions: string[]
tag_based_permissions?: Res['permission']['tag_based_permissions']
},
_,
) {
const res: Res['permission'] = {
ADMIN: baseQueryReturnValue.admin,
}
if (baseQueryReturnValue.tag_based_permissions) {
res.tag_based_permissions =
baseQueryReturnValue.tag_based_permissions
}
baseQueryReturnValue.permissions.forEach((v) => {
res[v] = true
})

return res
},
}),
Expand Down
2 changes: 1 addition & 1 deletion frontend/common/services/useProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const projectService = service
query: (data) => ({
url: `projects/?organisation=${data.organisationId}`,
}),
transformResponse: (res) => sortBy(res, 'name'),
transformResponse: (res) => sortBy(res, (v) => v.name.toLowerCase()),
}),
// END OF ENDPOINTS
}),
Expand Down
6 changes: 5 additions & 1 deletion frontend/common/services/useRole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const roleService = service
}),
}),
getRole: builder.query<Res['role'], Req['getRole']>({
providesTags: (res) => [{ id: res?.id, type: 'Role' }],
query: (query: Req['getRole']) => ({
url: `organisations/${query.organisation_id}/roles/${query.role_id}/`,
}),
Expand All @@ -33,7 +34,10 @@ export const roleService = service
}),
}),
updateRole: builder.mutation<Res['roles'], Req['updateRole']>({
invalidatesTags: (res) => [{ id: 'LIST', type: 'Role' }],
invalidatesTags: (res, _, req) => [
{ id: 'LIST', type: 'Role' },
{ id: req.role_id, type: 'Role' },
],
query: (query: Req['updateRole']) => ({
body: query.body,
method: 'PUT',
Expand Down
10 changes: 2 additions & 8 deletions frontend/common/services/useRolePermission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,15 +121,12 @@ export async function getRoleProjectPermissions(
typeof rolePermissionService.endpoints.getRoleProjectPermissions.initiate
>[1],
) {
store.dispatch(
return store.dispatch(
rolePermissionService.endpoints.getRoleProjectPermissions.initiate(
data,
options,
),
)
return Promise.all(
store.dispatch(rolePermissionService.util.getRunningQueriesThunk()),
)
}

export async function getRoleEnvironmentPermissions(
Expand All @@ -139,15 +136,12 @@ export async function getRoleEnvironmentPermissions(
typeof rolePermissionService.endpoints.getRoleEnvironmentPermissions.initiate
>[1],
) {
store.dispatch(
return store.dispatch(
rolePermissionService.endpoints.getRoleEnvironmentPermissions.initiate(
data,
options,
),
)
return Promise.all(
store.dispatch(rolePermissionService.util.getRunningQueriesThunk()),
)
}

export async function createRolePermissions(
Expand Down
3 changes: 3 additions & 0 deletions frontend/common/stores/account-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import Constants from 'common/constants'
import dataRelay from 'data-relay'
import { sortBy } from 'lodash'
import Project from 'common/project'
import { getStore } from 'common/store'
import { service } from "common/service";

const controller = {
acceptInvite: (id) => {
Expand Down Expand Up @@ -341,6 +343,7 @@ const controller = {
API.reset().finally(() => {
store.model = user
store.organisation = null
getStore().dispatch(service.util.resetApiState())
store.trigger('logout')
})
})
Expand Down
6 changes: 5 additions & 1 deletion frontend/common/stores/organisation-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ const controller = {
)
: ['Development', 'Production']
data
.post(`${Project.api}projects/`, { name, organisation: store.id })
.post(
`${Project.api}projects/`,
{ name, organisation: store.id },
E2E ? { 'X-E2E-Test-Auth-Token': Project.e2eToken } : {},
)
.then((project) => {
Promise.all(
defaultEnvironmentNames.map((envName) => {
Expand Down
6 changes: 4 additions & 2 deletions frontend/common/types/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
UserGroup,
AttributeName,
Identity,
Role,
RolePermission,
} from './responses'

export type PagedRequest<T> = T & {
Expand Down Expand Up @@ -158,7 +160,7 @@ export type Req = {
updateRole: {
organisation_id: number
role_id: number
body: { description: string | null; name: string }
body: Role
}
deleteRole: { organisation_id: number; role_id: number }
getRolePermissionEnvironment: {
Expand All @@ -179,7 +181,7 @@ export type Req = {
level: PermissionLevel
body: {
admin?: boolean
permissions: string[]
permissions: RolePermission['permissions']
project: number
environment: number
}
Expand Down
14 changes: 11 additions & 3 deletions frontend/common/types/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,12 @@ export type UserPermission = {
id: number
role?: number
}

export type RolePermission = Omit<UserPermission, 'permissions'> & {
permissions: { permission_key: string; tags: number[] }[]
}
export type GroupPermission = Omit<UserPermission, 'user'> & {
group: UserGroup
group: UserGroupSummary
}

export type AuditLogItem = {
Expand Down Expand Up @@ -328,6 +332,7 @@ export type Identity = {
export type AvailablePermission = {
key: string
description: string
supports_tag: boolean
}

export type APIKey = {
Expand Down Expand Up @@ -657,7 +662,10 @@ export type Res = {
}
identity: { id: string } //todo: we don't consider this until we migrate identity-store
identities: EdgePagedResponse<Identity>
permission: Record<string, boolean>
permission: Record<string, boolean> & {
ADMIN: boolean
tag_based_permissions?: { permissions: string[]; tags: number[] }[]
}
availablePermissions: AvailablePermission[]
tag: Tag
tags: Tag[]
Expand Down Expand Up @@ -695,7 +703,7 @@ export type Res = {
versionFeatureState: FeatureState[]
role: Role
roles: PagedResponse<Role>
rolePermission: PagedResponse<UserPermission>
rolePermission: PagedResponse<RolePermission>
projectFlags: PagedResponse<ProjectFlag>
projectFlag: ProjectFlag
identityFeatureStatesAll: IdentityFeatureState[]
Expand Down
30 changes: 0 additions & 30 deletions frontend/common/utils/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -341,36 +341,6 @@ const Utils = Object.assign({}, require('./base/_utils'), {
return `/organisation/${orgId}/projects`
},

getPermissionList(
isAdmin: boolean,
permissions: string[] | undefined | null,
numberToTruncate = 3,
): {
items: string[]
truncatedItems: string[]
} {
if (isAdmin) {
return {
items: ['Administrator'],
truncatedItems: [],
}
}
if (!permissions) return { items: [], truncatedItems: [] }

const items =
permissions && permissions.length
? permissions
.slice(0, numberToTruncate)
.map((item) => `${Format.enumeration.get(item)}`)
: []

return {
items,
truncatedItems: (permissions || [])
.slice(numberToTruncate)
.map((item) => `${Format.enumeration.get(item)}`),
}
},
getPlanName: (plan: string) => {
if (plan && plan.includes('free')) {
return planNames.free
Expand Down
6 changes: 6 additions & 0 deletions frontend/e2e/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
const E2E_EMAIL_DOMAIN = 'flagsmithe2etestdomain.io'
export const E2E_SIGN_UP_USER = `e2e_signup_user@${E2E_EMAIL_DOMAIN}`
export const E2E_USER = `e2e_user@${E2E_EMAIL_DOMAIN}`
export const E2E_NON_ADMIN_USER = `e2e_user@${E2E_EMAIL_DOMAIN}`
export const E2E_NON_ADMIN_USER_WITH_ORG_PERMISSIONS = `e2e_non_admin_user_with_org_permissions@${E2E_EMAIL_DOMAIN}`
export const E2E_NON_ADMIN_USER_WITH_PROJECT_PERMISSIONS = `e2e_non_admin_user_with_project_permissions@${E2E_EMAIL_DOMAIN}`
export const E2E_NON_ADMIN_USER_WITH_PROJECT_READ_PERMISSIONS = `e2e_non_admin_user_with_project_read_permissions@${E2E_EMAIL_DOMAIN}`
export const E2E_NON_ADMIN_USER_WITH_ENV_PERMISSIONS = `e2e_non_admin_user_with_env_permissions@${E2E_EMAIL_DOMAIN}`
export const E2E_NON_ADMIN_USER_WITH_A_ROLE = `e2e_non_admin_user_with_a_role@${E2E_EMAIL_DOMAIN}`
export const E2E_CHANGE_MAIL = `e2e_change_email@${E2E_EMAIL_DOMAIN}`
export const PASSWORD = 'Str0ngp4ssw0rd!'
Loading
Loading