@contember/engine-tenant-api 1.3.0-rc.3 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/model/authorization/AclSchemaAccessNodeFactory.d.ts.map +1 -1
- package/dist/src/model/authorization/AclSchemaAccessNodeFactory.js +4 -0
- package/dist/src/model/authorization/AclSchemaAccessNodeFactory.js.map +1 -1
- package/dist/src/model/authorization/PermissionActions.d.ts +5 -0
- package/dist/src/model/authorization/PermissionActions.d.ts.map +1 -1
- package/dist/src/model/authorization/PermissionActions.js +1 -0
- package/dist/src/model/authorization/PermissionActions.js.map +1 -1
- package/dist/src/model/authorization/PermissionsFactory.d.ts.map +1 -1
- package/dist/src/model/authorization/PermissionsFactory.js +1 -0
- package/dist/src/model/authorization/PermissionsFactory.js.map +1 -1
- package/dist/src/model/queries/membership/ProjectMembersQuery.d.ts.map +1 -1
- package/dist/src/model/queries/membership/ProjectMembersQuery.js +14 -10
- package/dist/src/model/queries/membership/ProjectMembersQuery.js.map +1 -1
- package/dist/src/model/service/InviteManager.d.ts.map +1 -1
- package/dist/src/model/service/InviteManager.js +4 -0
- package/dist/src/model/service/InviteManager.js.map +1 -1
- package/dist/src/model/service/SignUpManager.d.ts.map +1 -1
- package/dist/src/model/service/SignUpManager.js +4 -0
- package/dist/src/model/service/SignUpManager.js.map +1 -1
- package/dist/src/model/utils/email.d.ts +1 -0
- package/dist/src/model/utils/email.d.ts.map +1 -1
- package/dist/src/model/utils/email.js +7 -1
- package/dist/src/model/utils/email.js.map +1 -1
- package/dist/src/resolvers/query/IDPQueryResolver.d.ts.map +1 -1
- package/dist/src/resolvers/query/IDPQueryResolver.js +4 -0
- package/dist/src/resolvers/query/IDPQueryResolver.js.map +1 -1
- package/dist/src/resolvers/types/ProjectTypeResolver.d.ts.map +1 -1
- package/dist/src/resolvers/types/ProjectTypeResolver.js +5 -2
- package/dist/src/resolvers/types/ProjectTypeResolver.js.map +1 -1
- package/dist/src/schema/index.d.ts +8 -6
- package/dist/src/schema/index.d.ts.map +1 -1
- package/dist/src/schema/tenant.graphql.d.ts.map +1 -1
- package/dist/src/schema/tenant.graphql.js +5 -1
- package/dist/src/schema/tenant.graphql.js.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/tests/cases/integration/mocked/listProjectMembersQuery.test.d.ts +2 -0
- package/dist/tests/cases/integration/mocked/listProjectMembersQuery.test.d.ts.map +1 -0
- package/dist/tests/cases/integration/mocked/listProjectMembersQuery.test.js +82 -0
- package/dist/tests/cases/integration/mocked/listProjectMembersQuery.test.js.map +1 -0
- package/dist/tests/cases/unit/authorization/membershipAwareAccessNode.test.js +19 -1
- package/dist/tests/cases/unit/authorization/membershipAwareAccessNode.test.js.map +1 -1
- package/dist/tests/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -12
- package/src/model/authorization/AclSchemaAccessNodeFactory.ts +6 -0
- package/src/model/authorization/PermissionActions.ts +1 -0
- package/src/model/authorization/PermissionsFactory.ts +1 -0
- package/src/model/queries/membership/ProjectMembersQuery.ts +17 -9
- package/src/model/service/InviteManager.ts +6 -1
- package/src/model/service/SignUpManager.ts +4 -0
- package/src/model/utils/email.ts +6 -2
- package/src/resolvers/query/IDPQueryResolver.ts +6 -1
- package/src/resolvers/types/ProjectTypeResolver.ts +3 -0
- package/src/schema/index.ts +9 -5
- package/src/schema/tenant.graphql.ts +5 -1
- package/tests/cases/integration/mocked/listProjectMembersQuery.test.ts +83 -0
- package/tests/cases/unit/authorization/membershipAwareAccessNode.test.ts +25 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contember/engine-tenant-api",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"typings": "dist/src/index.d.ts",
|
|
@@ -13,16 +13,16 @@
|
|
|
13
13
|
"test": "vitest --dir ./tests/cases --no-threads"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@contember/authorization": "1.3.
|
|
17
|
-
"@contember/config-loader": "1.3.
|
|
18
|
-
"@contember/database": "1.3.
|
|
19
|
-
"@contember/database-migrations": "1.3.
|
|
20
|
-
"@contember/dic": "1.3.
|
|
21
|
-
"@contember/graphql-utils": "1.3.
|
|
22
|
-
"@contember/logger": "1.3.
|
|
23
|
-
"@contember/schema": "1.3.
|
|
24
|
-
"@contember/schema-utils": "1.3.
|
|
25
|
-
"@contember/typesafe": "1.3.
|
|
16
|
+
"@contember/authorization": "1.3.1",
|
|
17
|
+
"@contember/config-loader": "1.3.1",
|
|
18
|
+
"@contember/database": "1.3.1",
|
|
19
|
+
"@contember/database-migrations": "1.3.1",
|
|
20
|
+
"@contember/dic": "1.3.1",
|
|
21
|
+
"@contember/graphql-utils": "1.3.1",
|
|
22
|
+
"@contember/logger": "1.3.1",
|
|
23
|
+
"@contember/schema": "1.3.1",
|
|
24
|
+
"@contember/schema-utils": "1.3.1",
|
|
25
|
+
"@contember/typesafe": "1.3.1",
|
|
26
26
|
"@graphql-tools/utils": "^8.6.13",
|
|
27
27
|
"graphql-tag": "^2.12.5",
|
|
28
28
|
"jose": "^4.10",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"otpauth": "^7.0.6"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@contember/database-tester": "1.3.
|
|
35
|
+
"@contember/database-tester": "1.3.1",
|
|
36
36
|
"@graphql-codegen/cli": "^2.6.2",
|
|
37
37
|
"@graphql-codegen/typescript": "^2.5.1",
|
|
38
38
|
"@graphql-codegen/typescript-operations": "^2.4.2",
|
|
@@ -38,6 +38,12 @@ export class AclSchemaAccessNodeFactory {
|
|
|
38
38
|
node.allow(PermissionActions.PROJECT_UPDATE_MEMBER([]), verifier)
|
|
39
39
|
node.allow(PermissionActions.PROJECT_REMOVE_MEMBER([]), verifier)
|
|
40
40
|
}
|
|
41
|
+
|
|
42
|
+
if (membershipRoles.some(it => it.tenant?.view)) {
|
|
43
|
+
const verifier = createVerifier(it => it.view ?? {})
|
|
44
|
+
node.allow(PermissionActions.PROJECT_VIEW_MEMBER([]), verifier)
|
|
45
|
+
}
|
|
46
|
+
|
|
41
47
|
return node
|
|
42
48
|
}
|
|
43
49
|
}
|
|
@@ -57,6 +57,7 @@ namespace PermissionActions {
|
|
|
57
57
|
export const IDP_UPDATE = Authorizator.createAction(Resources.idp, 'update')
|
|
58
58
|
export const IDP_DISABLE = Authorizator.createAction(Resources.idp, 'disable')
|
|
59
59
|
export const IDP_ENABLE = Authorizator.createAction(Resources.idp, 'enable')
|
|
60
|
+
export const IDP_LIST = Authorizator.createAction(Resources.idp, 'list')
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
export { PermissionActions }
|
|
@@ -57,6 +57,7 @@ class PermissionsFactory {
|
|
|
57
57
|
permissions.allow(TenantRole.PROJECT_ADMIN, PermissionActions.IDP_UPDATE)
|
|
58
58
|
permissions.allow(TenantRole.PROJECT_ADMIN, PermissionActions.IDP_DISABLE)
|
|
59
59
|
permissions.allow(TenantRole.PROJECT_ADMIN, PermissionActions.IDP_ENABLE)
|
|
60
|
+
permissions.allow(TenantRole.PROJECT_ADMIN, PermissionActions.IDP_LIST)
|
|
60
61
|
|
|
61
62
|
permissions.allow(TenantRole.PROJECT_ADMIN, PermissionActions.ENTRYPOINT_DEPLOY)
|
|
62
63
|
|
|
@@ -23,22 +23,30 @@ export class ProjectMembersQuery extends DatabaseQuery<ProjectMembersQueryResult
|
|
|
23
23
|
.where({ project_id: this.projectId }),
|
|
24
24
|
))
|
|
25
25
|
.match(qb => {
|
|
26
|
-
const
|
|
27
|
-
|
|
26
|
+
const identityId = this.projectMemberInput.filter?.identityId
|
|
27
|
+
return identityId ? qb.where(it => it.in(['identity', 'id'], identityId)) : qb
|
|
28
|
+
})
|
|
29
|
+
.match(qb => {
|
|
30
|
+
const { email, memberType, personId } = this.projectMemberInput.filter ?? {}
|
|
31
|
+
if ((email || personId) && memberType === 'API_KEY') {
|
|
28
32
|
throw new ImplementationException()
|
|
29
33
|
}
|
|
34
|
+
|
|
30
35
|
const personQuery = SelectBuilder.create()
|
|
31
36
|
.from('person')
|
|
32
37
|
.where(expr => expr.columnsEq(['person', 'identity_id'], ['identity', 'id']))
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
.match(qb => email ? qb.where(it => it.in('email', email)) : qb)
|
|
39
|
+
.match(qb => personId ? qb.where(it => it.in(['person', 'id'], personId)) : qb)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
if (email || personId || memberType === 'PERSON') {
|
|
43
|
+
return qb.where(it => it.exists(personQuery))
|
|
35
44
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
case 'PERSON':
|
|
40
|
-
return qb.where(it => it.exists(personQuery))
|
|
45
|
+
|
|
46
|
+
if (memberType === 'API_KEY') {
|
|
47
|
+
return qb.where(it => it.not(it => it.exists(personQuery)))
|
|
41
48
|
}
|
|
49
|
+
|
|
42
50
|
return qb
|
|
43
51
|
})
|
|
44
52
|
.limit(this.projectMemberInput.limit ?? undefined, this.projectMemberInput.offset ?? undefined)
|
|
@@ -12,10 +12,11 @@ import { InviteErrorCode, InviteMethod } from '../../schema'
|
|
|
12
12
|
import { TenantRole } from '../authorization'
|
|
13
13
|
import { UserMailer } from '../mailing'
|
|
14
14
|
import { createAppendMembershipVariables } from './membershipUtils'
|
|
15
|
-
import { Response, ResponseOk } from '../utils/Response'
|
|
15
|
+
import { Response, ResponseError, ResponseOk } from '../utils/Response'
|
|
16
16
|
import { DatabaseContext, TokenHash } from '../utils'
|
|
17
17
|
import { NoPassword, PasswordPlain } from '../dtos'
|
|
18
18
|
import { Acl } from '@contember/schema'
|
|
19
|
+
import { validateEmail } from '../utils/email'
|
|
19
20
|
|
|
20
21
|
export interface InviteData {
|
|
21
22
|
email: string
|
|
@@ -52,6 +53,10 @@ export class InviteManager {
|
|
|
52
53
|
passwordResetTokenHash,
|
|
53
54
|
}: InviteData,
|
|
54
55
|
): Promise<InviteResponse> {
|
|
56
|
+
if (!validateEmail(email.trim())) {
|
|
57
|
+
return new ResponseError('INVALID_EMAIL_FORMAT', 'E-mail address is not in a valid format')
|
|
58
|
+
}
|
|
59
|
+
|
|
55
60
|
return await dbContext.transaction(async trx => {
|
|
56
61
|
let person: Omit<PersonRow, 'roles'> | null = await trx.queryHandler.fetch(PersonQuery.byEmail(email))
|
|
57
62
|
const isNew = !person
|
|
@@ -6,6 +6,7 @@ import { getPasswordWeaknessMessage } from '../utils/password'
|
|
|
6
6
|
import { Response, ResponseError, ResponseOk } from '../utils/Response'
|
|
7
7
|
import { DatabaseContext } from '../utils'
|
|
8
8
|
import { MaybePassword } from '../dtos'
|
|
9
|
+
import { validateEmail } from '../utils/email'
|
|
9
10
|
|
|
10
11
|
type SignUpUser = {
|
|
11
12
|
email: string
|
|
@@ -16,6 +17,9 @@ type SignUpUser = {
|
|
|
16
17
|
|
|
17
18
|
export class SignUpManager {
|
|
18
19
|
async signUp(dbContext: DatabaseContext, { email, password, roles = [] }: SignUpUser): Promise<SignUpResponse> {
|
|
20
|
+
if (!validateEmail(email.trim())) {
|
|
21
|
+
return new ResponseError('INVALID_EMAIL_FORMAT', 'E-mail address is not in a valid format')
|
|
22
|
+
}
|
|
19
23
|
if (await this.isEmailAlreadyUsed(dbContext, email)) {
|
|
20
24
|
return new ResponseError('EMAIL_ALREADY_EXISTS', `User with email ${email} already exists`)
|
|
21
25
|
}
|
package/src/model/utils/email.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import { Literal } from '@contember/database'
|
|
2
|
-
|
|
3
1
|
export const normalizeEmail = (email: string) => {
|
|
4
2
|
return email.replaceAll(/\s/g, '').toLocaleLowerCase()
|
|
5
3
|
}
|
|
4
|
+
|
|
5
|
+
export const validateEmail = (email: string): boolean => {
|
|
6
|
+
return email.length >= 3
|
|
7
|
+
&& email.includes('@')
|
|
8
|
+
&& !!email.match(/^[^\s@]+@[\w_-]+(\.[\w_-]+)*$/)
|
|
9
|
+
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { IdentityProvider, QueryResolvers } from '../../schema'
|
|
2
|
-
import { IdentityProvidersQuery } from '../../model'
|
|
2
|
+
import { IdentityProvidersQuery, PermissionActions } from '../../model'
|
|
3
3
|
import { TenantResolverContext } from '../TenantResolverContext'
|
|
4
4
|
|
|
5
5
|
export class IDPQueryResolver implements QueryResolvers {
|
|
6
6
|
async identityProviders(parent: unknown, args: unknown, context: TenantResolverContext): Promise<IdentityProvider[]> {
|
|
7
|
+
await context.requireAccess({
|
|
8
|
+
action: PermissionActions.IDP_LIST,
|
|
9
|
+
message: 'You are not allowed to list IDP',
|
|
10
|
+
})
|
|
11
|
+
|
|
7
12
|
return await context.db.queryHandler.fetch(new IdentityProvidersQuery())
|
|
8
13
|
}
|
|
9
14
|
}
|
|
@@ -24,6 +24,9 @@ export class ProjectTypeResolver implements ProjectResolvers {
|
|
|
24
24
|
if (args.input?.filter?.email && args.input.filter.memberType === 'API_KEY') {
|
|
25
25
|
throw new UserInputError(`Cannot use email filter for ApiKey member type.`)
|
|
26
26
|
}
|
|
27
|
+
if (args.input?.filter?.personId && args.input.filter.memberType === 'API_KEY') {
|
|
28
|
+
throw new UserInputError(`Cannot use personId filter for ApiKey member type.`)
|
|
29
|
+
}
|
|
27
30
|
if (args.memberType && args.input) {
|
|
28
31
|
throw new UserInputError(`Cannot use both deprecated memberType parameter and new input parameter`)
|
|
29
32
|
}
|
package/src/schema/index.ts
CHANGED
|
@@ -275,9 +275,9 @@ export type CreateSessionTokenError = {
|
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
export type CreateSessionTokenErrorCode =
|
|
278
|
+
| 'PERSON_DISABLED'
|
|
278
279
|
| 'UNKNOWN_EMAIL'
|
|
279
280
|
| 'UNKNOWN_PERSON_ID'
|
|
280
|
-
| 'PERSON_DISABLED'
|
|
281
281
|
|
|
282
282
|
export type CreateSessionTokenResponse = {
|
|
283
283
|
readonly __typename?: 'CreateSessionTokenResponse'
|
|
@@ -352,7 +352,7 @@ export type DisablePersonError = {
|
|
|
352
352
|
}
|
|
353
353
|
|
|
354
354
|
export type DisablePersonErrorCode =
|
|
355
|
-
'PERSON_ALREADY_DISABLED'
|
|
355
|
+
| 'PERSON_ALREADY_DISABLED'
|
|
356
356
|
| 'PERSON_NOT_FOUND'
|
|
357
357
|
|
|
358
358
|
export type DisablePersonResponse = {
|
|
@@ -461,6 +461,7 @@ export type InviteError = {
|
|
|
461
461
|
|
|
462
462
|
export type InviteErrorCode =
|
|
463
463
|
| 'ALREADY_MEMBER'
|
|
464
|
+
| 'INVALID_EMAIL_FORMAT'
|
|
464
465
|
| 'INVALID_MEMBERSHIP'
|
|
465
466
|
| 'PROJECT_NOT_FOUND'
|
|
466
467
|
| 'ROLE_NOT_FOUND'
|
|
@@ -852,8 +853,10 @@ export type ProjectIdentityRelation = {
|
|
|
852
853
|
}
|
|
853
854
|
|
|
854
855
|
export type ProjectMembersFilter = {
|
|
855
|
-
readonly email?: InputMaybe<Scalars['String']
|
|
856
|
+
readonly email?: InputMaybe<ReadonlyArray<Scalars['String']>>
|
|
857
|
+
readonly identityId?: InputMaybe<ReadonlyArray<Scalars['String']>>
|
|
856
858
|
readonly memberType?: InputMaybe<MemberType>
|
|
859
|
+
readonly personId?: InputMaybe<ReadonlyArray<Scalars['String']>>
|
|
857
860
|
}
|
|
858
861
|
|
|
859
862
|
export type ProjectMembersInput = {
|
|
@@ -1031,8 +1034,8 @@ export type SignInErrorCode =
|
|
|
1031
1034
|
| 'INVALID_PASSWORD'
|
|
1032
1035
|
| 'NO_PASSWORD_SET'
|
|
1033
1036
|
| 'OTP_REQUIRED'
|
|
1034
|
-
| 'UNKNOWN_EMAIL'
|
|
1035
1037
|
| 'PERSON_DISABLED'
|
|
1038
|
+
| 'UNKNOWN_EMAIL'
|
|
1036
1039
|
|
|
1037
1040
|
export type SignInIdpError = {
|
|
1038
1041
|
readonly __typename?: 'SignInIDPError'
|
|
@@ -1046,8 +1049,8 @@ export type SignInIdpErrorCode =
|
|
|
1046
1049
|
| 'IDP_VALIDATION_FAILED'
|
|
1047
1050
|
| 'INVALID_IDP_RESPONSE'
|
|
1048
1051
|
| 'PERSON_ALREADY_EXISTS'
|
|
1049
|
-
| 'PERSON_NOT_FOUND'
|
|
1050
1052
|
| 'PERSON_DISABLED'
|
|
1053
|
+
| 'PERSON_NOT_FOUND'
|
|
1051
1054
|
|
|
1052
1055
|
export type SignInIdpResponse = {
|
|
1053
1056
|
readonly __typename?: 'SignInIDPResponse'
|
|
@@ -1109,6 +1112,7 @@ export type SignUpError = {
|
|
|
1109
1112
|
|
|
1110
1113
|
export type SignUpErrorCode =
|
|
1111
1114
|
| 'EMAIL_ALREADY_EXISTS'
|
|
1115
|
+
| 'INVALID_EMAIL_FORMAT'
|
|
1112
1116
|
| 'TOO_WEAK'
|
|
1113
1117
|
|
|
1114
1118
|
export type SignUpResponse = {
|
|
@@ -116,6 +116,7 @@ const schema: DocumentNode = gql`
|
|
|
116
116
|
|
|
117
117
|
enum SignUpErrorCode {
|
|
118
118
|
EMAIL_ALREADY_EXISTS
|
|
119
|
+
INVALID_EMAIL_FORMAT
|
|
119
120
|
TOO_WEAK
|
|
120
121
|
}
|
|
121
122
|
|
|
@@ -393,6 +394,7 @@ const schema: DocumentNode = gql`
|
|
|
393
394
|
PROJECT_NOT_FOUND
|
|
394
395
|
ALREADY_MEMBER
|
|
395
396
|
INVALID_MEMBERSHIP
|
|
397
|
+
INVALID_EMAIL_FORMAT
|
|
396
398
|
|
|
397
399
|
ROLE_NOT_FOUND @deprecated
|
|
398
400
|
VARIABLE_NOT_FOUND @deprecated
|
|
@@ -684,7 +686,9 @@ const schema: DocumentNode = gql`
|
|
|
684
686
|
|
|
685
687
|
input ProjectMembersFilter {
|
|
686
688
|
memberType: MemberType
|
|
687
|
-
email: String
|
|
689
|
+
email: [String!]
|
|
690
|
+
identityId: [String!]
|
|
691
|
+
personId: [String!]
|
|
688
692
|
}
|
|
689
693
|
|
|
690
694
|
enum MemberType {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { executeTenantTest } from '../../../src/testTenant'
|
|
2
|
+
import { GQL } from '../../../src/tags'
|
|
3
|
+
import { testUuid } from '../../../src/testUuid'
|
|
4
|
+
import { test } from 'vitest'
|
|
5
|
+
import { sqlTransaction } from './sql/sqlTransaction'
|
|
6
|
+
|
|
7
|
+
test('list project members by email query', async () => {
|
|
8
|
+
await executeTenantTest({
|
|
9
|
+
query: {
|
|
10
|
+
query: GQL`
|
|
11
|
+
query {
|
|
12
|
+
projectBySlug(slug: "sandbox") {
|
|
13
|
+
members(
|
|
14
|
+
input: { filter: { email: ["foo@localhost", "bar@localhost"], memberType: PERSON } }
|
|
15
|
+
) {
|
|
16
|
+
identity {
|
|
17
|
+
id
|
|
18
|
+
}
|
|
19
|
+
memberships {
|
|
20
|
+
role
|
|
21
|
+
variables {
|
|
22
|
+
name
|
|
23
|
+
values
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}`,
|
|
29
|
+
variables: {},
|
|
30
|
+
},
|
|
31
|
+
executes: [
|
|
32
|
+
{
|
|
33
|
+
sql: `SELECT "id", "name", "slug", "config", "updated_at" AS "updatedAt"
|
|
34
|
+
FROM "tenant"."project"
|
|
35
|
+
WHERE "slug" = ?`,
|
|
36
|
+
parameters: ['sandbox'],
|
|
37
|
+
response: { rows: [{ id: testUuid(1), name: 'sandbox', slug: 'sandbox', config: {}, updatedAt: new Date() }] },
|
|
38
|
+
},
|
|
39
|
+
...sqlTransaction(
|
|
40
|
+
{
|
|
41
|
+
sql: `select "id", "description" from "tenant"."identity"
|
|
42
|
+
where exists (select ?::int from "tenant"."project_membership"
|
|
43
|
+
where "project_membership"."identity_id" = "identity"."id" and "project_id" = ?)
|
|
44
|
+
and exists (select ?::int from "tenant"."person" where "person"."identity_id" = "identity"."id" and "email" in (?, ?))`,
|
|
45
|
+
parameters: [1, testUuid(1), 1, 'foo@localhost', 'bar@localhost'],
|
|
46
|
+
response: {
|
|
47
|
+
rows: [
|
|
48
|
+
{ id: testUuid(2), description: 'foobar' },
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
sql: `with
|
|
54
|
+
"memberships" as (select "project_membership"."id", "project_membership"."role", "project_membership"."identity_id" from "tenant"."project_membership" where "identity_id" in (?) and "project_id" = ?),
|
|
55
|
+
"variables" as (select "membership_id", json_agg(json_build_object('name', variable, 'values', value)) as "variables" from "tenant"."project_membership_variable" inner join "memberships" on "project_membership_variable"."membership_id" = "memberships"."id" group by "membership_id")
|
|
56
|
+
select "role", coalesce(variables, '[]'::json) as "variables", "identity_id" as "identityId" from "memberships" left join "variables" on "memberships"."id" = "variables"."membership_id"`,
|
|
57
|
+
parameters: [testUuid(2), testUuid(1)],
|
|
58
|
+
response: {
|
|
59
|
+
rows: [
|
|
60
|
+
{
|
|
61
|
+
role: 'foo',
|
|
62
|
+
variables: [{ values: ['x', 'y'], name: 'xyz' }],
|
|
63
|
+
identityId: testUuid(2),
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
),
|
|
69
|
+
],
|
|
70
|
+
return: {
|
|
71
|
+
data: {
|
|
72
|
+
projectBySlug: {
|
|
73
|
+
members: [
|
|
74
|
+
{
|
|
75
|
+
identity: { id: testUuid(2) },
|
|
76
|
+
memberships: [{ role: 'foo', variables: [{ name: 'xyz', values: ['x', 'y'] }] }],
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
})
|
|
@@ -68,7 +68,7 @@ const aclSchemaForInviteOnly: Acl.Schema = {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
|
|
71
|
-
test('admin can invite public.
|
|
71
|
+
test('admin can invite public. cannot manage public, cannot invite lorem', async () => {
|
|
72
72
|
const node = new AclSchemaAccessNodeFactory().create(aclSchemaForInviteOnly, [{ role: 'admin', variables: [] }])
|
|
73
73
|
|
|
74
74
|
assert.ok(await node.isAllowed(aclEvaluator, PermissionActions.PERSON_INVITE([{ role: 'public', variables: [] }])))
|
|
@@ -77,3 +77,27 @@ test('admin can invite public. cannat manage public, cannot invite lorem', async
|
|
|
77
77
|
|
|
78
78
|
assert.notOk(await node.isAllowed(aclEvaluator, PermissionActions.PROJECT_REMOVE_MEMBER([{ role: 'public', variables: [] }])))
|
|
79
79
|
})
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
const aclSchemaForViewOnly: Acl.Schema = {
|
|
83
|
+
roles: {
|
|
84
|
+
admin: {
|
|
85
|
+
entities: {},
|
|
86
|
+
variables: {},
|
|
87
|
+
tenant: {
|
|
88
|
+
view: {
|
|
89
|
+
lorem: true,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
test('admin can view lorem, but cannot manage it', async () => {
|
|
98
|
+
const node = new AclSchemaAccessNodeFactory().create(aclSchemaForViewOnly, [{ role: 'admin', variables: [] }])
|
|
99
|
+
|
|
100
|
+
assert.ok(await node.isAllowed(aclEvaluator, PermissionActions.PROJECT_VIEW_MEMBER([{ role: 'lorem', variables: [] }])))
|
|
101
|
+
|
|
102
|
+
assert.notOk(await node.isAllowed(aclEvaluator, PermissionActions.PROJECT_REMOVE_MEMBER([{ role: 'lorem', variables: [] }])))
|
|
103
|
+
})
|