Skip to content

Commit 70a6664

Browse files
Merge pull request #61 from topcoder-platform/usergrouproles
Introduce member roles
2 parents 8cc7a42 + 8392049 commit 70a6664

File tree

12 files changed

+742
-72
lines changed

12 files changed

+742
-72
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,5 +94,6 @@ The GroupContains relation contains these fields:
9494

9595
- id: the relationship UUID
9696
- type: the relationship type, 'group' or 'user'
97+
- roles: the roles of the user in the group
9798
- createdAt: the created at date string
9899
- createdBy: the created by user id

app-constants.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,13 @@ const GroupStatus = {
2020
InActive: 'inactive'
2121
}
2222

23+
const GroupRoleName = ['groupManager', 'groupAdmin']
24+
2325
module.exports = {
2426
UserRoles,
2527
MembershipTypes,
2628
EVENT_ORIGINATOR,
2729
EVENT_MIME_TYPE,
28-
GroupStatus
30+
GroupStatus,
31+
GroupRoleName
2932
}

config/default.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ module.exports = {
3939
KAFKA_GROUP_MEMBER_DELETE_TOPIC: process.env.KAFKA_GROUP_MEMBER_DELETE_TOPIC || 'groups.notification.member.delete',
4040
KAFKA_GROUP_UNIVERSAL_MEMBER_ADD_TOPIC: process.env.KAFKA_GROUP_UNIVERSAL_MEMBER_ADD_TOPIC || 'groups.notification.universalmember.add',
4141
KAFKA_GROUP_UNIVERSAL_MEMBER_DELETE_TOPIC: process.env.KAFKA_GROUP_UNIVERSAL_MEMBER_DELETE_TOPIC || 'groups.notification.universalmember.delete',
42+
KAFKA_GROUP_MEMBER_ROLE_ADD_TOPIC: process.env.KAFKA_GROUP_MEMBER_ROLE_ADD_TOPIC || 'groups.notification.create',
43+
KAFKA_GROUP_MEMBER_ROLE_DELETE_TOPIC: process.env.KAFKA_GROUP_MEMBER_ROLE_DELETE_TOPIC || 'groups.notification.create',
44+
KAFKA_SUBGROUP_CREATE_TOPIC: process.env.KAFKA_SUBGROUP_CREATE_TOPIC || 'groups.notification.create',
45+
KAFKA_SUBGROUP_DELETE_TOPIC: process.env.KAFKA_SUBGROUP_DELETE_TOPIC || 'groups.notification.create',
4246

4347
USER_ROLES: {
4448
Admin: 'Administrator',

docs/swagger.yml

Lines changed: 179 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ paths:
4444
description: |
4545
Add a member to the specified group
4646
47-
If the group is private, the user needs to be a member of the group, or an admin.
47+
If the group is private, the user needs to be a member of the group, or an admin or the user making the request is having the role of groupManager or groupAdmin.
4848
tags:
4949
- group membership
5050
security:
@@ -92,7 +92,7 @@ paths:
9292
description: |
9393
Remove a member from specified group
9494
95-
The user has to have admin role and the group allows self registration.
95+
The user has to have admin role or the role of groupManager or groupAdmin and the group allows self registration.
9696
tags:
9797
- group membership
9898
security:
@@ -365,6 +365,131 @@ paths:
365365
$ref: "#/components/responses/NotFound"
366366
500:
367367
$ref: "#/components/responses/InternalServerError"
368+
369+
/groups/{groupId}/subGroup:
370+
post:
371+
description: |
372+
Creation of a new sub group
373+
374+
The user has to have admin role or have a "groupAdmin" role for that group.
375+
tags:
376+
- sub groups
377+
security:
378+
- bearer: []
379+
parameters:
380+
- $ref: '#/components/parameters/groupId'
381+
requestBody:
382+
$ref: '#/components/requestBodies/NewGroupBodyParam'
383+
responses:
384+
200:
385+
$ref: "#/components/responses/GroupResponse"
386+
400:
387+
$ref: "#/components/responses/BadRequest"
388+
401:
389+
$ref: "#/components/responses/Unauthenticated"
390+
403:
391+
$ref: "#/components/responses/Forbidden"
392+
500:
393+
$ref: "#/components/responses/InternalServerError"
394+
395+
/groups/{groupId}/subGroup/{subGroupId}:
396+
delete:
397+
description: |
398+
Creation of a new sub group
399+
400+
The user has to have admin role or have a "groupAdmin" role for that group.
401+
tags:
402+
- sub groups
403+
security:
404+
- bearer: []
405+
parameters:
406+
- $ref: '#/components/parameters/groupId'
407+
- $ref: '#/components/parameters/subGroupId'
408+
responses:
409+
204:
410+
description: The resource was deleted successfully.
411+
400:
412+
$ref: "#/components/responses/BadRequest"
413+
401:
414+
$ref: "#/components/responses/Unauthenticated"
415+
403:
416+
$ref: "#/components/responses/Forbidden"
417+
500:
418+
$ref: "#/components/responses/InternalServerError"
419+
420+
/groupRoles/users/{memberId}:
421+
get:
422+
description: |
423+
Returns the groups and roles of the user identified by memberId.
424+
425+
The user has to have admin role.
426+
tags:
427+
- group roles
428+
security:
429+
- bearer: []
430+
parameters:
431+
- $ref: '#/components/parameters/memberId'
432+
- $ref: '#/components/parameters/page'
433+
- $ref: '#/components/parameters/perPage'
434+
responses:
435+
200:
436+
$ref: '#/components/responses/GroupMemberRoleResponse'
437+
400:
438+
$ref: "#/components/responses/BadRequest"
439+
401:
440+
$ref: "#/components/responses/Unauthenticated"
441+
403:
442+
$ref: "#/components/responses/Forbidden"
443+
500:
444+
$ref: "#/components/responses/InternalServerError"
445+
post:
446+
description: |
447+
Creation of new group role for a user
448+
449+
The user has to have admin role.
450+
tags:
451+
- group roles
452+
security:
453+
- bearer: []
454+
parameters:
455+
- $ref: '#/components/parameters/memberId'
456+
requestBody:
457+
$ref: '#/components/requestBodies/GroupRoleBodyParam'
458+
responses:
459+
201:
460+
description: CREATED
461+
400:
462+
$ref: "#/components/responses/BadRequest"
463+
401:
464+
$ref: "#/components/responses/Unauthenticated"
465+
403:
466+
$ref: "#/components/responses/Forbidden"
467+
500:
468+
$ref: "#/components/responses/InternalServerError"
469+
delete:
470+
description: |
471+
Delete a group role
472+
473+
The user has to have admin role.
474+
tags:
475+
- group roles
476+
security:
477+
- bearer: []
478+
parameters:
479+
- $ref: '#/components/parameters/memberId'
480+
requestBody:
481+
$ref: '#/components/requestBodies/GroupRoleBodyParam'
482+
responses:
483+
204:
484+
description: The resource was deleted successfully.
485+
400:
486+
$ref: "#/components/responses/BadRequest"
487+
401:
488+
$ref: "#/components/responses/Unauthenticated"
489+
403:
490+
$ref: "#/components/responses/Forbidden"
491+
500:
492+
$ref: "#/components/responses/InternalServerError"
368493

369494
/groups/health:
370495
get:
@@ -400,6 +525,14 @@ components:
400525
schema:
401526
type: string
402527
example: '10ba038e-48da-123b-96e8-8d3b99b6d18a'
528+
subGroupId:
529+
name: subGroupId
530+
in: path
531+
description: The sub group id.
532+
required: true
533+
schema:
534+
type: string
535+
example: '10ba038e-48da-123b-96e8-8d3b99b6d18a'
403536
oldId:
404537
name: oldId
405538
in: path
@@ -617,10 +750,15 @@ components:
617750
content:
618751
application/json:
619752
schema:
620-
type: object
621-
properties:
622-
result:
623-
$ref: '#/components/schemas/Group'
753+
$ref: '#/components/schemas/Group'
754+
GroupMemberRoleResponse:
755+
description: The group role response
756+
content:
757+
application/json:
758+
schema:
759+
type: array
760+
items:
761+
$ref: '#/components/schemas/GroupRole'
624762

625763
schemas:
626764
GroupMembership:
@@ -695,6 +833,24 @@ components:
695833
type: array
696834
items:
697835
$ref: '#/components/schemas/Group'
836+
837+
GroupRole:
838+
description: The group role entity
839+
properties:
840+
groupId:
841+
type: string
842+
description: The group id
843+
role:
844+
type: string
845+
enum: ['groupManager', 'groupAdmin']
846+
description: The group role
847+
createdAt:
848+
type: string
849+
format: date-time
850+
description: The time the group role created at
851+
createdBy:
852+
type: string
853+
description: The id of the user who created the group role
698854

699855
requestBodies:
700856
NewGroupMembershipBodyParam:
@@ -738,4 +894,20 @@ components:
738894
status:
739895
type: string
740896
enum: ['active', 'inactive']
741-
description: Value indicating the status of the group
897+
description: Value indicating the status of the group
898+
GroupRoleBodyParam:
899+
description: A JSON object containing group role body information
900+
required: true
901+
content:
902+
application/json:
903+
schema:
904+
type: object
905+
properties:
906+
groupId:
907+
type: string
908+
description: The group id
909+
example: '10ba038e-48da-123b-96e8-8d3b99b6d18a'
910+
role:
911+
type: string
912+
enum: ['groupManager', 'groupAdmin']
913+
description: The group role

src/common/helper.js

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ const busApi = require('tc-bus-api-wrapper')
77
const config = require('config')
88
const neo4j = require('neo4j-driver')
99
const querystring = require('querystring')
10+
const uuid = require('uuid/v4')
1011
let validate = require('uuid-validate')
1112

1213
const errors = require('./errors')
14+
const logger = require('./logger')
1315
const constants = require('../../app-constants')
1416

1517
// Bus API Client
@@ -133,6 +135,26 @@ async function ensureGroupMember(session, groupId, userId) {
133135
}
134136
}
135137

138+
/**
139+
* Return whether the user has one of the group roles or not.
140+
* @param {Object} session the db session
141+
* @param {String} groupId the group id
142+
* @param {String} userId the user id
143+
* @param {Array} roles an array of group roles
144+
* @returns {Boolean} true if user has one of the group roles
145+
*/
146+
async function hasGroupRole (session, groupId, userId, roles) {
147+
const memberCheckRes = await session.run(
148+
'MATCH (g:Group {id: {groupId}})-[r:GroupContains {type: {membershipType}}]->(u:User {id: {userId}}) RETURN r',
149+
{ groupId, membershipType: config.MEMBERSHIP_TYPES.User, userId }
150+
)
151+
if (memberCheckRes.records.length === 0) {
152+
return false
153+
}
154+
const existRoles = memberCheckRes.records[0]._fields[0].properties.roles || []
155+
return _.some(existRoles, r => _.some(roles, role => JSON.parse(r).role === role))
156+
}
157+
136158
/**
137159
* Get child groups.
138160
* @param {Object} session the db session
@@ -305,6 +327,73 @@ async function postBusEvent(topic, payload) {
305327
})
306328
}
307329

330+
async function createGroup (tx, data, currentUser) {
331+
// check whether group name is already used
332+
const nameCheckRes = await tx.run('MATCH (g:Group {name: {name}}) RETURN g LIMIT 1', {
333+
name: data.name
334+
})
335+
if (nameCheckRes.records.length > 0) {
336+
throw new errors.ConflictError(`The group name ${data.name} is already used`)
337+
}
338+
339+
// create group
340+
const groupData = data
341+
342+
// generate next group id
343+
groupData.id = uuid()
344+
groupData.createdAt = new Date().toISOString()
345+
groupData.createdBy = currentUser === 'M2M' ? '00000000' : currentUser.userId
346+
groupData.domain = groupData.domain ? groupData.domain : ''
347+
groupData.ssoId = groupData.ssoId ? groupData.ssoId : ''
348+
groupData.organizationId = groupData.organizationId ? groupData.organizationId : ''
349+
350+
const createRes = await tx.run(
351+
'CREATE (group:Group {id: {id}, name: {name}, description: {description}, privateGroup: {privateGroup}, selfRegister: {selfRegister}, createdAt: {createdAt}, createdBy: {createdBy}, domain: {domain}, ssoId: {ssoId}, organizationId: {organizationId}, status: {status}}) RETURN group',
352+
groupData
353+
)
354+
355+
return createRes.records[0]._fields[0].properties
356+
}
357+
358+
async function deleteGroup (tx, group) {
359+
const groupsToDelete = [group]
360+
let index = 0
361+
while (index < groupsToDelete.length) {
362+
const g = groupsToDelete[index]
363+
index += 1
364+
365+
const childGroups = await getChildGroups(tx, g.id)
366+
for (let i = 0; i < childGroups.length; i += 1) {
367+
const child = childGroups[i]
368+
if (_.find(groupsToDelete, (gtd) => gtd.id === child.id)) {
369+
// the child was checked, ignore duplicate processing
370+
continue
371+
}
372+
// delete child if it doesn't belong to other group
373+
const parents = await getParentGroups(tx, child.id)
374+
if (parents.length <= 1) {
375+
groupsToDelete.push(child)
376+
}
377+
}
378+
}
379+
380+
logger.debug(`Groups to delete ${JSON.stringify(groupsToDelete)}`)
381+
382+
for (let i = 0; i < groupsToDelete.length; i += 1) {
383+
const id = groupsToDelete[i].id
384+
// delete group's relationships
385+
await tx.run('MATCH (g:Group {id: {groupId}})-[r]-() DELETE r', {
386+
groupId: id
387+
})
388+
389+
// delete group
390+
await tx.run('MATCH (g:Group {id: {groupId}}) DELETE g', {
391+
groupId: id
392+
})
393+
}
394+
return groupsToDelete
395+
}
396+
308397
module.exports = {
309398
wrapExpress,
310399
autoWrapExpress,
@@ -316,5 +405,8 @@ module.exports = {
316405
setResHeaders,
317406
checkIfExists,
318407
hasAdminRole,
319-
postBusEvent
408+
hasGroupRole,
409+
postBusEvent,
410+
createGroup,
411+
deleteGroup
320412
}

0 commit comments

Comments
 (0)