3
0

upd(system): add Permissions API

This commit is contained in:
Mitja Zivkovic 2019-02-20 22:14:58 +01:00
parent cbfdb34db0
commit 8cc4551efc
58 changed files with 1608 additions and 1691 deletions

View File

@ -151,7 +151,7 @@ mocks: $(GOMOCK)
$(MOCKGEN) -package service -source messaging/service/channel.go -destination messaging/service/channel_mock_test.go
$(MOCKGEN) -package service -source messaging/service/message.go -destination messaging/service/message_mock_test.go
$(MOCKGEN) -package service -source system/service/organisation.go -destination system/service/organisation_mock_test.go
$(MOCKGEN) -package service -source system/service/team.go -destination system/service/team_mock_test.go
$(MOCKGEN) -package service -source system/service/role.go -destination system/service/role_mock_test.go
$(MOCKGEN) -package service -source system/service/user.go -destination system/service/user_mock_test.go
$(MOCKGEN) -package mail -source internal/mail/mail.go -destination internal/mail/mail_mock_test.go

View File

@ -679,95 +679,5 @@
}
}
]
},
{
"title": "Permissions",
"parameters": {},
"entrypoint": "permissions",
"path": "/permissions",
"authentication": [
"Client ID",
"Session ID"
],
"struct": [
{
"imports": [
"github.com/crusttech/crust/internal/rules"
]
}
],
"apis": [
{
"name": "list",
"path": "/",
"method": "GET",
"title": "List default permissions",
"parameters": {}
},
{
"name": "get",
"path": "/{teamID}",
"method": "GET",
"title": "Retrieve current permission settings",
"parameters": {
"path": [
{
"name": "teamID",
"type": "uint64",
"required": true,
"title": "Team ID"
}
],
"get": [
{
"name": "resource",
"type": "string",
"required": true,
"title": "Permissions resource"
}
]
}
},
{
"name": "set",
"path": "/{teamID}",
"method": "POST",
"title": "Update permission settings",
"parameters": {
"path": [
{
"name": "teamID",
"type": "uint64",
"required": true,
"title": "Team ID"
}
],
"post": [
{
"name": "permissions",
"type": "[]rules.Rules",
"required": true,
"title": "List of rules to set"
}
]
}
},
{
"name": "scopes",
"path": "/scopes/{scope}",
"method": "GET",
"title": "List resources for given scope",
"parameters": {
"path": [
{
"name": "scope",
"type": "string",
"required": true,
"title": "Scope"
}
]
}
}
]
}
]

View File

@ -160,10 +160,10 @@
]
},
{
"title": "Teams",
"description": "An organisation may have many teams. Teams may have many channels available. Access to channels may be shared between teams.",
"path": "/teams",
"entrypoint": "team",
"title": "Roles",
"description": "An organisation may have many roles. Roles may have many channels available. Access to channels may be shared between roles.",
"path": "/roles",
"entrypoint": "role",
"authentication": [
"Client ID",
"Session ID"
@ -172,7 +172,7 @@
{
"name": "list",
"method": "GET",
"title": "List teams",
"title": "List roles",
"path": "/",
"parameters": {
"get": [
@ -188,7 +188,7 @@
{
"name": "create",
"method": "POST",
"title": "Update team details",
"title": "Update role details",
"path": "/",
"parameters": {
"post": [
@ -196,13 +196,13 @@
"type": "string",
"name": "name",
"required": true,
"title": "Name of Team"
"title": "Name of Role"
},
{
"type": "[]uint64",
"name": "members",
"required": false,
"title": "Team member IDs"
"title": "Role member IDs"
}
]
}
@ -210,15 +210,15 @@
{
"name": "update",
"method": "PUT",
"title": "Update team details",
"path": "/{teamID}",
"title": "Update role details",
"path": "/{roleID}",
"parameters": {
"path": [
{
"type": "uint64",
"name": "teamID",
"name": "roleID",
"required": true,
"title": "Team ID"
"title": "Role ID"
}
],
"post": [
@ -226,13 +226,13 @@
"type": "string",
"name": "name",
"required": false,
"title": "Name of Team"
"title": "Name of Role"
},
{
"type": "[]uint64",
"name": "members",
"required": false,
"title": "Team member IDs"
"title": "Role member IDs"
}
]
}
@ -240,15 +240,15 @@
{
"name": "read",
"method": "GET",
"title": "Read team details and memberships",
"path": "/{teamID}",
"title": "Read role details and memberships",
"path": "/{roleID}",
"parameters": {
"path": [
{
"type": "uint64",
"name": "teamID",
"name": "roleID",
"required": true,
"title": "Team ID"
"title": "Role ID"
}
]
}
@ -256,15 +256,15 @@
{
"name": "remove",
"method": "DELETE",
"title": "Remove team",
"path": "/{teamID}",
"title": "Remove role",
"path": "/{roleID}",
"parameters": {
"path": [
{
"type": "uint64",
"name": "teamID",
"name": "roleID",
"required": true,
"title": "Team ID"
"title": "Role ID"
}
]
}
@ -272,15 +272,15 @@
{
"name": "archive",
"method": "POST",
"title": "Archive team",
"path": "/{teamID}/archive",
"title": "Archive role",
"path": "/{roleID}/archive",
"parameters": {
"path": [
{
"type": "uint64",
"name": "teamID",
"name": "roleID",
"required": true,
"title": "Team ID"
"title": "Role ID"
}
]
}
@ -288,15 +288,15 @@
{
"name": "move",
"method": "POST",
"title": "Move team to different organisation",
"path": "/{teamID}/move",
"title": "Move role to different organisation",
"path": "/{roleID}/move",
"parameters": {
"path": [
{
"type": "uint64",
"name": "teamID",
"name": "roleID",
"required": true,
"title": "Team ID"
"title": "Role ID"
}
],
"post": [
@ -304,7 +304,7 @@
"type": "uint64",
"name": "organisationID",
"required": true,
"title": "Team ID"
"title": "Role ID"
}
]
}
@ -312,15 +312,15 @@
{
"name": "merge",
"method": "POST",
"title": "Merge one team into another",
"path": "/{teamID}/merge",
"title": "Merge one role into another",
"path": "/{roleID}/merge",
"parameters": {
"path": [
{
"type": "uint64",
"name": "teamID",
"name": "roleID",
"required": true,
"title": "Source Team ID"
"title": "Source Role ID"
}
],
"post": [
@ -328,7 +328,7 @@
"type": "uint64",
"name": "destination",
"required": true,
"title": "Destination Team ID"
"title": "Destination Role ID"
}
]
}
@ -336,15 +336,15 @@
{
"name": "memberAdd",
"method": "POST",
"title": "Add member to a team",
"path": "/{teamID}/memberAdd",
"title": "Add member to a role",
"path": "/{roleID}/memberAdd",
"parameters": {
"path": [
{
"type": "uint64",
"name": "teamID",
"name": "roleID",
"required": true,
"title": "Source Team ID"
"title": "Source Role ID"
}
],
"post": [
@ -360,15 +360,15 @@
{
"name": "memberRemove",
"method": "POST",
"title": "Remove member from a team",
"path": "/{teamID}/memberRemove",
"title": "Remove member from a role",
"path": "/{roleID}/memberRemove",
"parameters": {
"path": [
{
"type": "uint64",
"name": "teamID",
"name": "roleID",
"required": true,
"title": "Source Team ID"
"title": "Source Role ID"
}
],
"post": [
@ -561,5 +561,80 @@
}
}
]
},
{
"title": "Permissions",
"parameters": {},
"entrypoint": "permissions",
"path": "/permissions",
"authentication": [
"Client ID",
"Session ID"
],
"struct": [
{
"imports": [
"github.com/crusttech/crust/internal/rules"
]
}
],
"apis": [
{
"name": "get",
"path": "/{roleID}/rules",
"method": "GET",
"title": "Retrieve role permissions",
"parameters": {
"path": [
{
"name": "roleID",
"type": "uint64",
"required": true,
"title": "Role ID"
}
]
}
},
{
"name": "delete",
"path": "/{roleID}/rules",
"method": "DELETE",
"title": "Remove all defined role permissions",
"parameters": {
"path": [
{
"name": "roleID",
"type": "uint64",
"required": true,
"title": "Role ID"
}
]
}
},
{
"name": "update",
"path": "/{roleID}/rules",
"method": "PATCH",
"title": "Update permission settings",
"parameters": {
"path": [
{
"name": "roleID",
"type": "uint64",
"required": true,
"title": "Role ID"
}
],
"post": [
{
"name": "permissions",
"type": "[]rules.Rule",
"required": true,
"title": "List of permissions to set"
}
]
}
}
]
}
]

View File

@ -16,48 +16,49 @@
],
"Path": "/permissions",
"APIs": [
{
"Name": "list",
"Method": "GET",
"Title": "List default permissions",
"Path": "/",
"Parameters": {}
},
{
"Name": "get",
"Method": "GET",
"Title": "Retrieve current permission settings",
"Path": "/{teamID}",
"Title": "Retrieve role permissions",
"Path": "/{roleID}/rules",
"Parameters": {
"get": [
{
"name": "resource",
"required": true,
"title": "Permissions resource",
"type": "string"
}
],
"path": [
{
"name": "teamID",
"name": "roleID",
"required": true,
"title": "Team ID",
"title": "Role ID",
"type": "uint64"
}
]
}
},
{
"Name": "set",
"Method": "POST",
"Title": "Update permission settings",
"Path": "/{teamID}",
"Name": "delete",
"Method": "DELETE",
"Title": "Remove all defined role permissions",
"Path": "/{roleID}/rules",
"Parameters": {
"path": [
{
"name": "teamID",
"name": "roleID",
"required": true,
"title": "Team ID",
"title": "Role ID",
"type": "uint64"
}
]
}
},
{
"Name": "update",
"Method": "PATCH",
"Title": "Update permission settings",
"Path": "/{roleID}/rules",
"Parameters": {
"path": [
{
"name": "roleID",
"required": true,
"title": "Role ID",
"type": "uint64"
}
],
@ -65,24 +66,8 @@
{
"name": "permissions",
"required": true,
"title": "List of rules to set",
"type": "[]rules.Rules"
}
]
}
},
{
"Name": "scopes",
"Method": "GET",
"Title": "List resources for given scope",
"Path": "/scopes/{scope}",
"Parameters": {
"path": [
{
"name": "scope",
"required": true,
"title": "Scope",
"type": "string"
"title": "List of permissions to set",
"type": "[]rules.Rule"
}
]
}

View File

@ -1,7 +1,7 @@
{
"Title": "Teams",
"Description": "An organisation may have many teams. Teams may have many channels available. Access to channels may be shared between teams.",
"Interface": "Team",
"Title": "Roles",
"Description": "An organisation may have many roles. Roles may have many channels available. Access to channels may be shared between roles.",
"Interface": "Role",
"Struct": null,
"Parameters": null,
"Protocol": "",
@ -9,12 +9,12 @@
"Client ID",
"Session ID"
],
"Path": "/teams",
"Path": "/roles",
"APIs": [
{
"Name": "list",
"Method": "GET",
"Title": "List teams",
"Title": "List roles",
"Path": "/",
"Parameters": {
"get": [
@ -30,20 +30,20 @@
{
"Name": "create",
"Method": "POST",
"Title": "Update team details",
"Title": "Update role details",
"Path": "/",
"Parameters": {
"post": [
{
"name": "name",
"required": true,
"title": "Name of Team",
"title": "Name of Role",
"type": "string"
},
{
"name": "members",
"required": false,
"title": "Team member IDs",
"title": "Role member IDs",
"type": "[]uint64"
}
]
@ -52,14 +52,14 @@
{
"Name": "update",
"Method": "PUT",
"Title": "Update team details",
"Path": "/{teamID}",
"Title": "Update role details",
"Path": "/{roleID}",
"Parameters": {
"path": [
{
"name": "teamID",
"name": "roleID",
"required": true,
"title": "Team ID",
"title": "Role ID",
"type": "uint64"
}
],
@ -67,13 +67,13 @@
{
"name": "name",
"required": false,
"title": "Name of Team",
"title": "Name of Role",
"type": "string"
},
{
"name": "members",
"required": false,
"title": "Team member IDs",
"title": "Role member IDs",
"type": "[]uint64"
}
]
@ -82,14 +82,14 @@
{
"Name": "read",
"Method": "GET",
"Title": "Read team details and memberships",
"Path": "/{teamID}",
"Title": "Read role details and memberships",
"Path": "/{roleID}",
"Parameters": {
"path": [
{
"name": "teamID",
"name": "roleID",
"required": true,
"title": "Team ID",
"title": "Role ID",
"type": "uint64"
}
]
@ -98,14 +98,14 @@
{
"Name": "remove",
"Method": "DELETE",
"Title": "Remove team",
"Path": "/{teamID}",
"Title": "Remove role",
"Path": "/{roleID}",
"Parameters": {
"path": [
{
"name": "teamID",
"name": "roleID",
"required": true,
"title": "Team ID",
"title": "Role ID",
"type": "uint64"
}
]
@ -114,14 +114,14 @@
{
"Name": "archive",
"Method": "POST",
"Title": "Archive team",
"Path": "/{teamID}/archive",
"Title": "Archive role",
"Path": "/{roleID}/archive",
"Parameters": {
"path": [
{
"name": "teamID",
"name": "roleID",
"required": true,
"title": "Team ID",
"title": "Role ID",
"type": "uint64"
}
]
@ -130,14 +130,14 @@
{
"Name": "move",
"Method": "POST",
"Title": "Move team to different organisation",
"Path": "/{teamID}/move",
"Title": "Move role to different organisation",
"Path": "/{roleID}/move",
"Parameters": {
"path": [
{
"name": "teamID",
"name": "roleID",
"required": true,
"title": "Team ID",
"title": "Role ID",
"type": "uint64"
}
],
@ -145,7 +145,7 @@
{
"name": "organisationID",
"required": true,
"title": "Team ID",
"title": "Role ID",
"type": "uint64"
}
]
@ -154,14 +154,14 @@
{
"Name": "merge",
"Method": "POST",
"Title": "Merge one team into another",
"Path": "/{teamID}/merge",
"Title": "Merge one role into another",
"Path": "/{roleID}/merge",
"Parameters": {
"path": [
{
"name": "teamID",
"name": "roleID",
"required": true,
"title": "Source Team ID",
"title": "Source Role ID",
"type": "uint64"
}
],
@ -169,7 +169,7 @@
{
"name": "destination",
"required": true,
"title": "Destination Team ID",
"title": "Destination Role ID",
"type": "uint64"
}
]
@ -178,14 +178,14 @@
{
"Name": "memberAdd",
"Method": "POST",
"Title": "Add member to a team",
"Path": "/{teamID}/memberAdd",
"Title": "Add member to a role",
"Path": "/{roleID}/memberAdd",
"Parameters": {
"path": [
{
"name": "teamID",
"name": "roleID",
"required": true,
"title": "Source Team ID",
"title": "Source Role ID",
"type": "uint64"
}
],
@ -202,14 +202,14 @@
{
"Name": "memberRemove",
"Method": "POST",
"Title": "Remove member from a team",
"Path": "/{teamID}/memberRemove",
"Title": "Remove member from a role",
"Path": "/{roleID}/memberRemove",
"Parameters": {
"path": [
{
"name": "teamID",
"name": "roleID",
"required": true,
"title": "Source Team ID",
"title": "Source Role ID",
"type": "uint64"
}
],

View File

@ -23,7 +23,7 @@ function permissions {
fi
./build/gen-permissions -package types -object-name Organisation -input messaging/types/permissions/1-organisation.json -output messaging/types/organisation.perms.gen.go
./build/gen-permissions -package types -object-name Team -input messaging/types/permissions/2-team.json -output messaging/types/team.perms.gen.go
./build/gen-permissions -package types -object-name Role -input messaging/types/permissions/2-role.json -output messaging/types/role.perms.gen.go
./build/gen-permissions -package types -object-name Channel -input messaging/types/permissions/3-channel.json -output messaging/types/channel.perms.gen.go
green "OK"
@ -52,7 +52,7 @@ function types {
./build/gen-type-set --with-primary-key=false --types Unread --output messaging/types/unread.gen.go
./build/gen-type-set --types User --output system/types/user.gen.go
./build/gen-type-set --with-resources=true --resource-type "rules.Resource" --imports "github.com/crusttech/crust/internal/rules" --types Team --output system/types/team.gen.go
./build/gen-type-set --with-resources=true --resource-type "rules.Resource" --imports "github.com/crusttech/crust/internal/rules" --types Role --output system/types/role.gen.go
./build/gen-type-set --with-resources=true --resource-type "rules.Resource" --imports "github.com/crusttech/crust/internal/rules" --types Organisation --output system/types/organisation.gen.go
./build/gen-type-set --types Credentials --output system/types/credentials.gen.go
green "OK"

View File

@ -17,7 +17,7 @@ foreach ($apis as $api) {
$tpl->assign("name", $name);
$tpl->assign("api", $api);
$tpl->assign("apis", $apis);
$tpl->assign("self", strtolower(substr($name, 0, 1)));
$tpl->assign("self", strtolower(substr($name, 0, 2)));
$tpl->assign("structs", $api['struct']);
$imports = imports($api);
$tpl->assign("imports", $imports);

View File

@ -448,68 +448,6 @@ The following event types may be sent with a message event:
# Permissions
## List default permissions
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/permissions/` | HTTP/S | GET | Client ID, Session ID |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
## Retrieve current permission settings
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/permissions/{teamID}` | HTTP/S | GET | Client ID, Session ID |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| resource | string | GET | Permissions resource | N/A | YES |
| teamID | uint64 | PATH | Team ID | N/A | YES |
## Update permission settings
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/permissions/{teamID}` | HTTP/S | POST | Client ID, Session ID |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| teamID | uint64 | PATH | Team ID | N/A | YES |
| permissions | []rules.Rules | POST | List of rules to set | N/A | YES |
## List resources for given scope
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/permissions/scopes/{scope}` | HTTP/S | GET | Client ID, Session ID |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| scope | string | PATH | Scope | N/A | YES |
# Search entry point
## Search for messages

View File

@ -136,17 +136,65 @@ Organisations represent a top-level grouping entity. There may be many organisat
# Teams
# Permissions
An organisation may have many teams. Teams may have many channels available. Access to channels may be shared between teams.
## List teams
## Retrieve role permissions
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/teams/` | HTTP/S | GET | Client ID, Session ID |
| `/permissions/{roleID}/rules` | HTTP/S | GET | Client ID, Session ID |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| roleID | uint64 | PATH | Role ID | N/A | YES |
## Remove all defined role permissions
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/permissions/{roleID}/rules` | HTTP/S | DELETE | Client ID, Session ID |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| roleID | uint64 | PATH | Role ID | N/A | YES |
## Update permission settings
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/permissions/{roleID}/rules` | HTTP/S | PATCH | Client ID, Session ID |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| roleID | uint64 | PATH | Role ID | N/A | YES |
| permissions | []rules.Rule | POST | List of permissions to set | N/A | YES |
# Roles
An organisation may have many roles. Roles may have many channels available. Access to channels may be shared between roles.
## List roles
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/roles/` | HTTP/S | GET | Client ID, Session ID |
#### Request parameters
@ -154,137 +202,137 @@ An organisation may have many teams. Teams may have many channels available. Acc
| --------- | ---- | ------ | ----------- | ------- | --------- |
| query | string | GET | Search query | N/A | NO |
## Update team details
## Update role details
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/teams/` | HTTP/S | POST | Client ID, Session ID |
| `/roles/` | HTTP/S | POST | Client ID, Session ID |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| name | string | POST | Name of Team | N/A | YES |
| members | []uint64 | POST | Team member IDs | N/A | NO |
| name | string | POST | Name of Role | N/A | YES |
| members | []uint64 | POST | Role member IDs | N/A | NO |
## Update team details
## Update role details
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/teams/{teamID}` | HTTP/S | PUT | Client ID, Session ID |
| `/roles/{roleID}` | HTTP/S | PUT | Client ID, Session ID |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| teamID | uint64 | PATH | Team ID | N/A | YES |
| name | string | POST | Name of Team | N/A | NO |
| members | []uint64 | POST | Team member IDs | N/A | NO |
| roleID | uint64 | PATH | Role ID | N/A | YES |
| name | string | POST | Name of Role | N/A | NO |
| members | []uint64 | POST | Role member IDs | N/A | NO |
## Read team details and memberships
## Read role details and memberships
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/teams/{teamID}` | HTTP/S | GET | Client ID, Session ID |
| `/roles/{roleID}` | HTTP/S | GET | Client ID, Session ID |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| teamID | uint64 | PATH | Team ID | N/A | YES |
| roleID | uint64 | PATH | Role ID | N/A | YES |
## Remove team
## Remove role
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/teams/{teamID}` | HTTP/S | DELETE | Client ID, Session ID |
| `/roles/{roleID}` | HTTP/S | DELETE | Client ID, Session ID |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| teamID | uint64 | PATH | Team ID | N/A | YES |
| roleID | uint64 | PATH | Role ID | N/A | YES |
## Archive team
## Archive role
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/teams/{teamID}/archive` | HTTP/S | POST | Client ID, Session ID |
| `/roles/{roleID}/archive` | HTTP/S | POST | Client ID, Session ID |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| teamID | uint64 | PATH | Team ID | N/A | YES |
| roleID | uint64 | PATH | Role ID | N/A | YES |
## Move team to different organisation
## Move role to different organisation
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/teams/{teamID}/move` | HTTP/S | POST | Client ID, Session ID |
| `/roles/{roleID}/move` | HTTP/S | POST | Client ID, Session ID |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| teamID | uint64 | PATH | Team ID | N/A | YES |
| organisationID | uint64 | POST | Team ID | N/A | YES |
| roleID | uint64 | PATH | Role ID | N/A | YES |
| organisationID | uint64 | POST | Role ID | N/A | YES |
## Merge one team into another
## Merge one role into another
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/teams/{teamID}/merge` | HTTP/S | POST | Client ID, Session ID |
| `/roles/{roleID}/merge` | HTTP/S | POST | Client ID, Session ID |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| teamID | uint64 | PATH | Source Team ID | N/A | YES |
| destination | uint64 | POST | Destination Team ID | N/A | YES |
| roleID | uint64 | PATH | Source Role ID | N/A | YES |
| destination | uint64 | POST | Destination Role ID | N/A | YES |
## Add member to a team
## Add member to a role
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/teams/{teamID}/memberAdd` | HTTP/S | POST | Client ID, Session ID |
| `/roles/{roleID}/memberAdd` | HTTP/S | POST | Client ID, Session ID |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| teamID | uint64 | PATH | Source Team ID | N/A | YES |
| roleID | uint64 | PATH | Source Role ID | N/A | YES |
| userID | uint64 | POST | User ID | N/A | YES |
## Remove member from a team
## Remove member from a role
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/teams/{teamID}/memberRemove` | HTTP/S | POST | Client ID, Session ID |
| `/roles/{roleID}/memberRemove` | HTTP/S | POST | Client ID, Session ID |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| teamID | uint64 | PATH | Source Team ID | N/A | YES |
| roleID | uint64 | PATH | Source Role ID | N/A | YES |
| userID | uint64 | POST | User ID | N/A | YES |

View File

@ -11,6 +11,10 @@ type ResourcesInterface interface {
IsAllowed(resource string, operation string) Access
Grant(teamID uint64, resource string, operations []string, value Access) error
ListGrants(teamID uint64, resource string) ([]Rules, error)
GrantByResource(roleID uint64, resource string, operations []string, value Access) error
ListByResource(roleID uint64, resource string) ([]Rule, error)
Grant(roleID uint64, rules []Rule) error
List(roleID uint64) ([]Rule, error)
Delete(roleID uint64) error
}

View File

@ -7,27 +7,29 @@ import (
)
type Resource struct {
ID uint64 `json:"id,string"`
Name string `json:"name"`
Scope string `json:"scope"`
ID uint64 `json:"id,string"`
Name string `json:"name"`
Scope string `json:"scope"`
Service string `json:"service"`
}
type ResourceJSON struct {
ID uint64 `json:"id,string"`
Name string `json:"name"`
Scope string `json:"scope"`
Service string `json:"service"`
ResourceID string `json:"resource"`
}
func (r Resource) String() string {
if r.ID > 0 {
return fmt.Sprintf("%s:%d", r.Scope, r.ID)
return fmt.Sprintf("%s:%s:%d", r.Service, r.Scope, r.ID)
}
return ""
}
func (r Resource) All() string {
return fmt.Sprintf("%s:*", r.Scope)
return fmt.Sprintf("%s:%s:*", r.Service, r.Scope)
}
func (r Resource) MarshalJSON() ([]byte, error) {
@ -35,6 +37,7 @@ func (r Resource) MarshalJSON() ([]byte, error) {
r.ID,
r.Name,
r.Scope,
r.Service,
r.String(),
})
}

View File

@ -13,8 +13,8 @@ func TestResource(t *testing.T) {
var (
assert = test.Assert
)
r := Resource{123, "Test name", "team"}
assert(t, r.String() == "team:123", "Resource ID doesn't match, team:123 != '%s'", r.String())
r := Resource{123, "Test name", "channel", "messaging"}
assert(t, r.String() == "messaging:channel:123", "Resource ID doesn't match, messaging:channel:123 != '%s'", r.String())
b, _ := json.Marshal(r)
fmt.Println(string(b))
@ -22,13 +22,13 @@ func TestResource(t *testing.T) {
{
r := ResourceJSON{}
json.Unmarshal(b, &r)
assert(t, r.ResourceID == "team:123", "Decoded full-json resource ID doesn't match, team:123 != '%s'", r.ResourceID)
assert(t, r.ResourceID == "messaging:channel:123", "Decoded full-json resource ID doesn't match, messaging:channel:123 != '%s'", r.ResourceID)
}
{
r := Resource{}
json.Unmarshal(b, &r)
assert(t, r.String() == "team:123", "Decoded full-json resource ID doesn't match, team:123 != '%s'", r.String())
assert(t, r.String() == "messaging:channel:123", "Decoded full-json resource ID doesn't match, messaging:channel:123 != '%s'", r.String())
}
{

View File

@ -43,7 +43,7 @@ func (r *resources) checkAccessMulti(resource string, operation string) Access {
// select rules
"select r.value from sys_rules r",
// join members
"inner join sys_team_member m on (m.rel_team = r.rel_team and m.rel_user=?)",
"inner join sys_role_member m on (m.rel_role = r.rel_role and m.rel_user=?)",
// add conditions
"where r.resource LIKE ? and r.operation=?",
}
@ -75,7 +75,7 @@ func (r *resources) checkAccess(resource string, operation string) Access {
// select rules
"select r.value from sys_rules r",
// join members
"inner join sys_team_member m on (m.rel_team = r.rel_team and m.rel_user=?)",
"inner join sys_role_member m on (m.rel_role = r.rel_role and m.rel_user=?)",
// add conditions
"where r.resource=? and r.operation=?",
}
@ -99,35 +99,75 @@ func (r *resources) checkAccess(resource string, operation string) Access {
return Inherit
}
func (r *resources) Grant(teamID uint64, resource string, operations []string, value Access) error {
row := Rules{
TeamID: teamID,
Resource: resource,
Value: value,
}
func (r *resources) GrantByResource(roleID uint64, resource string, operations []string, value Access) error {
return r.db.Transaction(func() error {
row := Rule{
RoleID: roleID,
Resource: resource,
Value: value,
}
var err error
for _, operation := range operations {
row.Operation = operation
switch value {
case Inherit:
_, err = r.db.NamedExec("delete from sys_rules where rel_team=:rel_team and resource=:resource and operation=:operation", row)
default:
err = r.db.Replace("sys_rules", row)
var err error
for _, operation := range operations {
row.Operation = operation
switch value {
case Inherit:
_, err = r.db.NamedExec("delete from sys_rules where rel_role=:rel_role and resource=:resource and operation=:operation", row)
default:
err = r.db.Replace("sys_rules", row)
}
if err != nil {
return err
}
}
if err != nil {
break
}
}
return err
return nil
})
}
func (r *resources) ListGrants(teamID uint64, resource string) ([]Rules, error) {
result := []Rules{}
func (r *resources) ListByResource(roleID uint64, resource string) ([]Rule, error) {
result := []Rule{}
query := "select * from sys_rules where rel_team = ? and resource = ?"
if err := r.db.Select(&result, query, teamID, resource); err != nil {
query := "select * from sys_rules where rel_role = ? and resource = ?"
if err := r.db.Select(&result, query, roleID, resource); err != nil {
return nil, err
}
return result, nil
}
func (r *resources) Grant(roleID uint64, rules []Rule) error {
return r.db.Transaction(func() error {
var err error
for _, rule := range rules {
rule.RoleID = roleID
switch rule.Value {
case Inherit:
_, err = r.db.NamedExec("delete from sys_rules where rel_role=:rel_role and resource=:resource and operation=:operation", rule)
default:
err = r.db.Replace("sys_rules", rule)
}
if err != nil {
return err
}
}
return nil
})
}
func (r *resources) List(roleID uint64) ([]Rule, error) {
result := []Rule{}
query := "select * from sys_rules where rel_role = ?"
if err := r.db.Select(&result, query, roleID); err != nil {
return nil, err
}
return result, nil
}
func (r *resources) Delete(roleID uint64) error {
query := "delete from sys_rules where rel_role = ?"
if _, err := r.db.Exec(query, roleID); err != nil {
return err
}
return nil
}

View File

@ -2,7 +2,6 @@ package rules_test
import (
"context"
"errors"
"fmt"
"testing"
@ -21,62 +20,101 @@ func TestRules(t *testing.T) {
db := factory.Database.MustGet()
Error(t, db.Transaction(func() error {
db.Insert("sys_user", user)
var i uint64 = 0
for i < 5 {
db.Insert("sys_team", types.Team{ID: i, Name: fmt.Sprintf("Team %d", i)})
i++
}
db.Insert("sys_team_member", types.TeamMember{TeamID: 1, UserID: user.ID})
db.Insert("sys_team_member", types.TeamMember{TeamID: 2, UserID: user.ID})
db.Insert("sys_user", user)
var i uint64 = 0
for i < 5 {
db.Insert("sys_role", types.Role{ID: i, Name: fmt.Sprintf("Role %d", i)})
i++
}
db.Insert("sys_role_member", types.RoleMember{RoleID: 1, UserID: user.ID})
db.Insert("sys_role_member", types.RoleMember{RoleID: 2, UserID: user.ID})
Expect := func(expected rules.Access, actual rules.Access, format string, params ...interface{}) {
Assert(t, expected == actual, format, params...)
}
Expect := func(expected rules.Access, actual rules.Access, format string, params ...interface{}) {
Assert(t, expected == actual, format, params...)
}
resources := rules.NewResources(ctx, db)
resources := rules.NewResources(ctx, db)
// default (unset=deny)
{
Expect(rules.Inherit, resources.IsAllowed("channel:1", "edit"), "expected inherit")
Expect(rules.Inherit, resources.IsAllowed("channel:*", "edit"), "expected inherit")
}
// default (unset=deny)
{
Expect(rules.Inherit, resources.IsAllowed("channel:1", "edit"), "expected inherit")
Expect(rules.Inherit, resources.IsAllowed("channel:*", "edit"), "expected inherit")
}
// allow channel:2 group:2 (default deny, multi=allow)
{
resources.Grant(2, "channel:2", []string{"edit", "delete"}, rules.Allow)
Expect(rules.Inherit, resources.IsAllowed("channel:1", "edit"), "expected error, got nil")
Expect(rules.Allow, resources.IsAllowed("channel:2", "edit"), "channel:2 edit, expected no error")
Expect(rules.Allow, resources.IsAllowed("channel:*", "edit"), "channel:* edit, expected no error")
}
// allow channel:2 group:2 (default deny, multi=allow)
{
resources.GrantByResource(2, "channel:2", []string{"edit", "delete"}, rules.Allow)
Expect(rules.Inherit, resources.IsAllowed("channel:1", "edit"), "expected error, got nil")
Expect(rules.Allow, resources.IsAllowed("channel:2", "edit"), "channel:2 edit, expected no error")
Expect(rules.Allow, resources.IsAllowed("channel:*", "edit"), "channel:* edit, expected no error")
}
// list grants for team
{
grants, err := resources.ListGrants(2, "channel:2")
NoError(t, err, "expect no error")
Assert(t, len(grants) == 2, "expected 2 grants")
Assert(t, grants[0].TeamID == 2, "expected TeamID == 2, got %v", grants[0].TeamID)
Assert(t, grants[0].Resource == "channel:2", "expected Resource == channel:2, got %s", grants[0].Resource)
Assert(t, grants[0].Operation == "delete", "expected Operation == delete, got %s", grants[0].Operation)
Assert(t, grants[0].Value == rules.Allow, "expected Value == Allow, got %s", grants[0].Value)
}
// list grants for role
{
grants, err := resources.ListByResource(2, "channel:2")
NoError(t, err, "expect no error")
Assert(t, len(grants) == 2, "expected 2 grants")
Assert(t, grants[0].RoleID == 2, "expected RoleID == 2, got %v", grants[0].RoleID)
Assert(t, grants[0].Resource == "channel:2", "expected Resource == channel:2, got %s", grants[0].Resource)
Assert(t, grants[0].Operation == "delete", "expected Operation == delete, got %s", grants[0].Operation)
Assert(t, grants[0].Value == rules.Allow, "expected Value == Allow, got %s", grants[0].Value)
}
// deny channel:1 group:1 (explicit deny, multi=deny)
{
resources.Grant(1, "channel:1", []string{"edit"}, rules.Deny)
Expect(rules.Deny, resources.IsAllowed("channel:1", "edit"), "expected error, got nil")
Expect(rules.Allow, resources.IsAllowed("channel:2", "edit"), "channel:2 edit, expected no error")
Expect(rules.Deny, resources.IsAllowed("channel:*", "edit"), "expected error, got nil")
}
// list all by role
{
grants, err := resources.List(2)
NoError(t, err, "expected no error")
Assert(t, len(grants) == 2, "expected grants == 2, got %v", len(grants))
}
// reset (unset=deny)
{
resources.Grant(2, "channel:2", []string{"edit", "delete"}, rules.Inherit)
resources.Grant(1, "channel:1", []string{"edit", "delete"}, rules.Inherit)
Expect(rules.Inherit, resources.IsAllowed("channel:1", "edit"), "expected error, got nil")
Expect(rules.Inherit, resources.IsAllowed("channel:*", "edit"), "expected error, got nil")
// deny channel:1 group:1 (explicit deny, multi=deny)
{
resources.GrantByResource(1, "channel:1", []string{"edit"}, rules.Deny)
Expect(rules.Deny, resources.IsAllowed("channel:1", "edit"), "expected error, got nil")
Expect(rules.Allow, resources.IsAllowed("channel:2", "edit"), "channel:2 edit, expected no error")
Expect(rules.Deny, resources.IsAllowed("channel:*", "edit"), "expected error, got nil")
}
// reset (unset=deny)
{
resources.GrantByResource(2, "channel:2", []string{"edit", "delete"}, rules.Inherit)
resources.GrantByResource(1, "channel:1", []string{"edit", "delete"}, rules.Inherit)
Expect(rules.Inherit, resources.IsAllowed("channel:1", "edit"), "expected error, got nil")
Expect(rules.Inherit, resources.IsAllowed("channel:*", "edit"), "expected error, got nil")
}
// Grant by roleID
{
list := []rules.Rule{
rules.Rule{Resource: "channel:*", Operation: "update", Value: rules.Allow},
rules.Rule{Resource: "channel:1", Operation: "update", Value: rules.Deny},
rules.Rule{Resource: "channel:2", Operation: "update"},
rules.Rule{Resource: "system", Operation: "organisation.create", Value: rules.Allow},
}
return errors.New("Rollback")
}), "Expected rollback error, got nil")
err := resources.Grant(2, list)
NoError(t, err, "expected no error")
}
// list all by roleID
{
grants, err := resources.List(2)
fmt.Println(grants)
NoError(t, err, "expected no error")
Assert(t, len(grants) == 3, "expected grants == 3, got %v", len(grants))
}
// delete all by role
{
err := resources.Delete(2)
NoError(t, err, "expected no error")
}
// list all by role
{
grants, err := resources.List(2)
NoError(t, err, "expected no error")
Assert(t, len(grants) == 0, "expected grants == 0, got %v", len(grants))
}
}

View File

@ -13,8 +13,8 @@ const (
Inherit = 0
)
type Rules struct {
TeamID uint64 `db:"rel_team"`
type Rule struct {
RoleID uint64 `db:"rel_role"`
Resource string `db:"resource"`
Operation string `db:"operation"`
Value Access `db:"value"`

View File

@ -1,37 +0,0 @@
package rest
import (
"context"
"github.com/crusttech/crust/messaging/rest/request"
"github.com/crusttech/crust/messaging/service"
_ "github.com/crusttech/crust/messaging/types"
)
type Permissions struct {
svc struct {
perms service.PermissionsService
}
}
func (Permissions) New() *Permissions {
ctrl := &Permissions{}
ctrl.svc.perms = service.DefaultPermissions
return ctrl
}
func (ctrl *Permissions) List(ctx context.Context, r *request.PermissionsList) (interface{}, error) {
return ctrl.svc.perms.List()
}
func (ctrl *Permissions) Get(ctx context.Context, r *request.PermissionsGet) (interface{}, error) {
return ctrl.svc.perms.Get(r.TeamID, r.Resource)
}
func (ctrl *Permissions) Set(ctx context.Context, r *request.PermissionsSet) (interface{}, error) {
return ctrl.svc.perms.Set(r.TeamID, r.Permissions)
}
func (ctrl *Permissions) Scopes(ctx context.Context, r *request.PermissionsScopes) (interface{}, error) {
return ctrl.svc.perms.Scopes(r.Scope)
}

View File

@ -22,7 +22,6 @@ func MountRoutes() func(chi.Router) {
handlers.NewChannel(Channel{}.New()).MountRoutes(r)
handlers.NewMessage(Message{}.New()).MountRoutes(r)
handlers.NewSearch(Search{}.New()).MountRoutes(r)
handlers.NewPermissions(Permissions{}.New()).MountRoutes(r)
})
}
}

View File

@ -49,7 +49,7 @@ func TestMain(m *testing.M) {
// clean up tables
{
for _, name := range []string{"sys_user", "sys_team", "sys_team_member", "sys_organisation", "sys_rules"} {
for _, name := range []string{"sys_user", "sys_role", "sys_role_member", "sys_organisation", "sys_rules"} {
_, err := db.Exec("truncate " + name)
if err != nil {
panic("Error when clearing " + name + ": " + err.Error())

View File

@ -1,99 +0,0 @@
package service
import (
"context"
"github.com/pkg/errors"
"github.com/crusttech/crust/internal/organization"
internalRules "github.com/crusttech/crust/internal/rules"
"github.com/crusttech/crust/messaging/repository"
"github.com/crusttech/crust/messaging/types"
systemRepository "github.com/crusttech/crust/system/repository"
)
type (
permissions struct {
db db
ctx context.Context
team systemRepository.TeamRepository
channel repository.ChannelRepository
scopes internalRules.ScopeInterface
resources internalRules.ResourcesInterface
}
PermissionsService interface {
With(ctx context.Context) PermissionsService
List() (interface{}, error)
Get(teamID uint64, resource string) (interface{}, error)
Set(teamID uint64, rules []internalRules.Rules) (interface{}, error)
Scopes(scope string) (interface{}, error)
}
)
func Permissions(scopes internalRules.ScopeInterface) PermissionsService {
return (&permissions{
scopes: scopes,
}).With(context.Background())
}
func (p *permissions) With(ctx context.Context) PermissionsService {
db := repository.DB(ctx)
return &permissions{
db: db,
ctx: ctx,
team: systemRepository.Team(ctx, db),
channel: repository.Channel(ctx, db),
scopes: p.scopes,
resources: internalRules.NewResources(ctx, db),
}
}
func (p *permissions) List() (interface{}, error) {
return p.scopes.List(), nil
}
func (p *permissions) Get(teamID uint64, resource string) (interface{}, error) {
return p.resources.ListGrants(teamID, resource)
}
func (p *permissions) Set(teamID uint64, rules []internalRules.Rules) (interface{}, error) {
var err error
for _, rule := range rules {
err = p.resources.Grant(
teamID,
rule.Resource,
[]string{rule.Operation},
rule.Value,
)
if err != nil {
break
}
}
return nil, err
}
func (p *permissions) Scopes(scope string) (interface{}, error) {
switch scope {
case "organization":
// @todo organizations from DB once multi-org
// return p.organizaion.Find(nil)
orgs := []types.Organisation{
types.Organisation{
organization.Crust(),
},
}
return orgs, nil
case "team":
return p.team.Find(nil)
case "channel":
return p.channel.FindChannels(nil)
}
return nil, errors.New("no scope defined")
}

View File

@ -1,119 +0,0 @@
package service
import (
"context"
"testing"
"github.com/titpetric/factory"
"github.com/crusttech/crust/internal/auth"
internalRules "github.com/crusttech/crust/internal/rules"
. "github.com/crusttech/crust/internal/test"
"github.com/crusttech/crust/messaging/types"
systemRepos "github.com/crusttech/crust/system/repository"
systemTypes "github.com/crusttech/crust/system/types"
)
func TestPermissions(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
return
}
ctx := context.TODO()
// Create user for test.
userRepo := systemRepos.User(ctx, factory.Database.MustGet())
user := &systemTypes.User{
Name: "John Doe",
Username: "johndoe",
SatosaID: "1234",
}
err := user.GeneratePassword("johndoe")
NoError(t, err, "expected no error generating password, got %v", err)
_, err = userRepo.Create(user)
NoError(t, err, "expected no error creating user, got %v", err)
// Create team for test and add user
teamRepo := systemRepos.Team(ctx, factory.Database.MustGet())
team := &systemTypes.Team{
Name: "Test team v1",
}
_, err = teamRepo.Create(team)
NoError(t, err, "expected no error creating team, got %v", err)
err = teamRepo.MemberAddByID(team.ID, user.ID)
NoError(t, err, "expected no error adding user to team, got %v", err)
// Set Identity.
ctx = auth.SetIdentityToContext(ctx, user)
// Create scopes.
scopes := internalRules.NewScope()
scopes.Add(&types.Organisation{})
scopes.Add(&types.Team{})
scopes.Add(&types.Channel{})
permissionsSvc := Permissions(scopes).With(ctx)
// Get all available scopes and items
list, err := permissionsSvc.List()
scopeItems := list.([]internalRules.ScopeItem)
NoError(t, err, "expected no error, receiving scopes")
Assert(t, len(scopeItems) == 3, "expected 3 scopes, got %v", len(scopeItems))
// Setup nothing for organization
organisationScope := scopeItems[0]
Assert(t, organisationScope.Scope == "organisation", "expected scope 'organisation', got %s", organisationScope.Scope)
// Setup everything allow for team
teamScope := scopeItems[1]
Assert(t, teamScope.Scope == "team", "expected scope 'team', got %s", teamScope.Scope)
rules := make([]internalRules.Rules, 0)
for _, group := range teamScope.Permissions {
for _, op := range group.Operations {
r := internalRules.Rules{
TeamID: team.ID,
Resource: "team:1",
Operation: op.Key,
Value: internalRules.Allow,
}
rules = append(rules, r)
}
}
_, err = permissionsSvc.Set(team.ID, rules)
NoError(t, err, "expected no error, setting rules")
// Deny all permissions for scope channel:1
channelScope := scopeItems[2]
Assert(t, channelScope.Scope == "channel", "expected scope 'channel', got %s", channelScope.Scope)
rules = make([]internalRules.Rules, 0)
for _, group := range channelScope.Permissions {
for _, op := range group.Operations {
r := internalRules.Rules{
TeamID: team.ID,
Resource: "channel:1",
Operation: op.Key,
Value: internalRules.Deny,
}
rules = append(rules, r)
}
}
_, err = permissionsSvc.Set(team.ID, rules)
NoError(t, err, "expected no error, setting rules")
// Check permission on channel and team level to test inheritance.
rls := Rules().With(ctx)
canManageChannels := rls.canManageChannels()
Assert(t, canManageChannels == false, "expected canManageChannels == false, got %v", canManageChannels)
canSendMessage := rls.canSendMessages(&types.Channel{ID: 1})
Assert(t, canSendMessage == false, "expected canSendMessage == false, got %v", canSendMessage)
canSendMessage = rls.canSendMessages(&types.Channel{ID: 2})
Assert(t, canSendMessage == true, "expected canSendMessage == true, got %v", canSendMessage)
}

View File

@ -16,7 +16,7 @@ type (
// identity is passed with context
resources internalRules.ResourcesInterface
team *systemTypes.Team
role *systemTypes.Role
org *types.Organisation
}
@ -31,7 +31,6 @@ type (
canManageRoles() bool
canManageChannels() bool
// types.Team derived from identity uses a wildcard match
canManageWebhooks(ch *types.Channel) bool
// Messaging rules
@ -46,7 +45,7 @@ type (
func Rules() RulesService {
return (&rules{
team: &systemTypes.Team{},
role: &systemTypes.Role{},
}).With(context.Background())
}
@ -57,7 +56,7 @@ func (r *rules) With(ctx context.Context) RulesService {
db: db,
ctx: ctx,
org: org,
team: r.team,
role: r.role,
resources: internalRules.NewResources(ctx, db),
}
@ -87,37 +86,37 @@ func (r *rules) canManageChannels() bool {
func (r *rules) canManageWebhooks(ch *types.Channel) bool {
op := "manage.webhooks"
return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String(), r.team.Resource().All(), ch.Resource().String())
return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String(), r.role.Resource().All(), ch.Resource().String())
}
func (r *rules) canSendMessages(ch *types.Channel) bool {
op := "message.send"
return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String(), r.team.Resource().All(), ch.Resource().String())
return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String(), r.role.Resource().All(), ch.Resource().String())
}
func (r *rules) canEmbedLinks(ch *types.Channel) bool {
op := "message.embed"
return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String(), r.team.Resource().All(), ch.Resource().String())
return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String(), r.role.Resource().All(), ch.Resource().String())
}
func (r *rules) canAttachFiles(ch *types.Channel) bool {
op := "message.attach"
return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String(), r.team.Resource().All(), ch.Resource().String())
return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String(), r.role.Resource().All(), ch.Resource().String())
}
func (r *rules) canUpdateOwnMessages(ch *types.Channel) bool {
op := "message.update_own"
return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String(), r.team.Resource().All(), ch.Resource().String())
return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String(), r.role.Resource().All(), ch.Resource().String())
}
func (r *rules) canUpdateMessages(ch *types.Channel) bool {
op := "message.update_all"
return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String(), r.team.Resource().All(), ch.Resource().String())
return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String(), r.role.Resource().All(), ch.Resource().String())
}
func (r *rules) canReact(ch *types.Channel) bool {
op := "message.react"
return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String(), r.team.Resource().All(), ch.Resource().String())
return r.hasAccess(op, r.org.PermissionDefault(op), r.org.Resource().String(), r.role.Resource().All(), ch.Resource().String())
}
func (r *rules) hasAccess(operation string, value internalRules.Access, scopes ...string) bool {

View File

@ -18,13 +18,12 @@ type (
)
var (
o sync.Once
DefaultAttachment AttachmentService
DefaultChannel ChannelService
DefaultMessage MessageService
DefaultPermissions PermissionsService
DefaultPubSub *pubSub
DefaultEvent EventService
o sync.Once
DefaultAttachment AttachmentService
DefaultChannel ChannelService
DefaultMessage MessageService
DefaultPubSub *pubSub
DefaultEvent EventService
)
func Init() {
@ -36,13 +35,12 @@ func Init() {
scopes := internalRules.NewScope()
scopes.Add(&types.Organisation{})
scopes.Add(&types.Team{})
scopes.Add(&types.Role{})
scopes.Add(&types.Channel{})
DefaultEvent = Event()
DefaultAttachment = Attachment(fs)
DefaultMessage = Message()
DefaultPermissions = Permissions(scopes)
DefaultChannel = Channel()
DefaultPubSub = PubSub()
})

View File

@ -12,5 +12,5 @@ type (
// These entities create resources in RBAC
var _ ResourceProvider = &Organisation{}
var _ ResourceProvider = &Team{}
var _ ResourceProvider = &Role{}
var _ ResourceProvider = &Channel{}

View File

@ -5,7 +5,7 @@ import (
)
type (
Team struct {
types.Team
Role struct {
types.Role
}
)

View File

@ -2,9 +2,9 @@ package types
import "github.com/crusttech/crust/internal/rules"
/* File is generated from messaging/types/permissions/2-team.json with permissions.go */
/* File is generated from messaging/types/permissions/2-role.json with permissions.go */
func (*Team) Permissions() []rules.OperationGroup {
func (*Role) Permissions() []rules.OperationGroup {
return []rules.OperationGroup{
rules.OperationGroup{
Title: "General permissions",
@ -62,15 +62,15 @@ func (*Team) Permissions() []rules.OperationGroup {
}
}
func (*Team) PermissionDefault(key string) rules.Access {
func (*Role) PermissionDefault(key string) rules.Access {
values := map[string]rules.Access{
"manage.webhooks": rules.Inherit,
"message.send": rules.Inherit,
"message.embed": rules.Inherit,
"message.attach": rules.Inherit,
"message.update_own": rules.Inherit,
"message.update_all": rules.Inherit,
"message.react": rules.Inherit,
"manage.webhooks": rules.Inherit,
"message.send": rules.Inherit,
}
if value, ok := values[key]; ok {
return value

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,5 @@
ALTER TABLE sys_team RENAME TO sys_role;
ALTER TABLE sys_team_member RENAME TO sys_role_member;
ALTER TABLE `sys_role_member` CHANGE COLUMN `rel_team` `rel_role` BIGINT UNSIGNED NOT NULL;
ALTER TABLE `sys_rules` CHANGE COLUMN `rel_team` `rel_role` BIGINT UNSIGNED NOT NULL;

View File

@ -45,7 +45,7 @@ func TestMain(m *testing.M) {
// clean up tables
{
for _, name := range []string{"sys_user", "sys_team", "sys_team_member", "sys_organisation", "settings"} {
for _, name := range []string{"sys_user", "sys_role", "sys_role_member", "sys_organisation", "settings"} {
_, err := db.Exec("truncate " + name)
if err != nil {
panic("Error when clearing " + name + ": " + err.Error())

157
system/repository/role.go Normal file
View File

@ -0,0 +1,157 @@
package repository
import (
"context"
"time"
"github.com/titpetric/factory"
"github.com/crusttech/crust/system/types"
)
type (
RoleRepository interface {
With(ctx context.Context, db *factory.DB) RoleRepository
FindByID(id uint64) (*types.Role, error)
FindByMemberID(userID uint64) ([]*types.Role, error)
Find(filter *types.RoleFilter) ([]*types.Role, error)
Create(mod *types.Role) (*types.Role, error)
Update(mod *types.Role) (*types.Role, error)
ArchiveByID(id uint64) error
UnarchiveByID(id uint64) error
DeleteByID(id uint64) error
MergeByID(id, targetRoleID uint64) error
MoveByID(id, targetOrganisationID uint64) error
MemberAddByID(id, userID uint64) error
MemberRemoveByID(id, userID uint64) error
}
role struct {
*repository
// sql table reference
roles string
members string
}
)
const (
sqlRoleScope = "deleted_at IS NULL AND archived_at IS NULL"
ErrRoleNotFound = repositoryError("RoleNotFound")
)
func Role(ctx context.Context, db *factory.DB) RoleRepository {
return (&role{}).With(ctx, db)
}
func (r *role) With(ctx context.Context, db *factory.DB) RoleRepository {
return &role{
repository: r.repository.With(ctx, db),
roles: "sys_role",
members: "sys_role_member",
}
}
func (r *role) FindByID(id uint64) (*types.Role, error) {
sql := "SELECT * FROM " + r.roles + " WHERE id = ? AND " + sqlRoleScope
mod := &types.Role{}
return mod, isFound(r.db().Get(mod, sql, id), mod.ID > 0, ErrRoleNotFound)
}
func (r *role) FindByMemberID(userID uint64) ([]*types.Role, error) {
ids := make([]uint64, 0)
params := make([]interface{}, 0)
sql := "SELECT DISTINCT rel_role FROM " + r.members + " "
sql += "WHERE rel_user = ?"
params = append(params, userID)
if err := r.db().Select(&ids, sql, params...); err != nil {
return nil, err
}
rval := make([]*types.Role, 0)
for _, id := range ids {
mod, err := r.FindByID(id)
if err != nil {
return nil, err
}
rval = append(rval, mod)
}
return rval, nil
}
func (r *role) Find(filter *types.RoleFilter) ([]*types.Role, error) {
rval := make([]*types.Role, 0)
params := make([]interface{}, 0)
sql := "SELECT * FROM " + r.roles + " WHERE " + sqlRoleScope
if filter != nil {
if filter.Query != "" {
sql += " AND name LIKE ?"
params = append(params, filter.Query+"%")
}
}
sql += " ORDER BY name ASC"
return rval, r.db().Select(&rval, sql, params...)
}
func (r *role) Create(mod *types.Role) (*types.Role, error) {
mod.ID = factory.Sonyflake.NextID()
mod.CreatedAt = time.Now()
return mod, r.db().Insert(r.roles, mod)
}
func (r *role) Update(mod *types.Role) (*types.Role, error) {
mod.UpdatedAt = timeNowPtr()
return mod, r.db().Replace(r.roles, mod)
}
func (r *role) ArchiveByID(id uint64) error {
return r.updateColumnByID(r.roles, "archived_at", time.Now(), id)
}
func (r *role) UnarchiveByID(id uint64) error {
return r.updateColumnByID(r.roles, "archived_at", nil, id)
}
func (r *role) DeleteByID(id uint64) error {
return r.updateColumnByID(r.roles, "deleted_at", time.Now(), id)
}
func (r *role) MergeByID(id, targetRoleID uint64) error {
return ErrNotImplemented
}
func (r *role) MoveByID(id, targetOrganisationID uint64) error {
return ErrNotImplemented
}
func (r *role) MemberAddByID(id, userID uint64) error {
mod := &types.RoleMember{
RoleID: id,
UserID: userID,
}
return r.db().Replace(r.members, mod)
}
func (r *role) MemberRemoveByID(id, userID uint64) error {
mod := &types.RoleMember{
RoleID: id,
UserID: userID,
}
return r.db().Delete(r.members, mod, "rel_role", "rel_user")
}

View File

@ -0,0 +1,103 @@
package repository
import (
"context"
"github.com/titpetric/factory"
"testing"
"github.com/crusttech/crust/system/types"
)
func TestRole(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
return
}
userRepo := User(context.Background(), factory.Database.MustGet())
user := &types.User{
Name: "John Doe",
Username: "johndoe",
}
user.GeneratePassword("johndoe")
{
u1, err := userRepo.Create(user)
assert(t, err == nil, "Owner.Create error: %+v", err)
assert(t, user.ID == u1.ID, "Changes were not stored")
}
roleRepo := Role(context.Background(), factory.Database.MustGet())
role := &types.Role{
Name: "Test role v1",
}
{
t1, err := roleRepo.Create(role)
assert(t, err == nil, "Role.Create error: %+v", err)
assert(t, role.Name == t1.Name, "Changes were not stored")
}
{
role.Name = "Test role v2"
t1, err := roleRepo.Update(role)
assert(t, err == nil, "Role.Update error: %+v", err)
assert(t, role.Name == t1.Name, "Changes were not stored")
}
{
t1, err := roleRepo.FindByID(role.ID)
assert(t, err == nil, "Role.FindByID error: %+v", err)
assert(t, role.Name == t1.Name, "Changes were not stored")
}
{
aa, err := roleRepo.Find(&types.RoleFilter{Query: role.Name})
assert(t, err == nil, "Role.Find error: %+v", err)
assert(t, len(aa) > 0, "No results found")
}
{
err := roleRepo.ArchiveByID(role.ID)
assert(t, err == nil, "Role.ArchiveByID error: %+v", err)
}
{
err := roleRepo.UnarchiveByID(role.ID)
assert(t, err == nil, "Role.UnarchiveByID error: %+v", err)
}
{
err := roleRepo.MemberAddByID(role.ID, user.ID)
assert(t, err == nil, "Role.MemberAddByID error: %+v", err)
}
{
roles, err := roleRepo.FindByMemberID(user.ID)
assert(t, err == nil, "Role.FindByMemberID error: %+v", err)
assert(t, len(roles) > 0, "No results found")
}
{
roles, err := roleRepo.FindByMemberID(0)
assert(t, err == nil, "Role.FindByMemberID error: %+v", err)
assert(t, len(roles) == 0, "Results found")
}
{
err := roleRepo.MemberRemoveByID(role.ID, user.ID)
assert(t, err == nil, "Role.MemberRemoveByID error: %+v", err)
}
{
err := roleRepo.DeleteByID(role.ID)
assert(t, err == nil, "Role.DeleteByID error: %+v", err)
}
{
err := userRepo.DeleteByID(user.ID)
assert(t, err == nil, "Owner.DeleteByID error: %+v", err)
}
}

View File

@ -1,157 +0,0 @@
package repository
import (
"context"
"time"
"github.com/titpetric/factory"
"github.com/crusttech/crust/system/types"
)
type (
TeamRepository interface {
With(ctx context.Context, db *factory.DB) TeamRepository
FindByID(id uint64) (*types.Team, error)
FindByMemberID(userID uint64) ([]*types.Team, error)
Find(filter *types.TeamFilter) ([]*types.Team, error)
Create(mod *types.Team) (*types.Team, error)
Update(mod *types.Team) (*types.Team, error)
ArchiveByID(id uint64) error
UnarchiveByID(id uint64) error
DeleteByID(id uint64) error
MergeByID(id, targetTeamID uint64) error
MoveByID(id, targetOrganisationID uint64) error
MemberAddByID(id, userID uint64) error
MemberRemoveByID(id, userID uint64) error
}
team struct {
*repository
// sql table reference
teams string
members string
}
)
const (
sqlTeamScope = "deleted_at IS NULL AND archived_at IS NULL"
ErrTeamNotFound = repositoryError("TeamNotFound")
)
func Team(ctx context.Context, db *factory.DB) TeamRepository {
return (&team{}).With(ctx, db)
}
func (r *team) With(ctx context.Context, db *factory.DB) TeamRepository {
return &team{
repository: r.repository.With(ctx, db),
teams: "sys_team",
members: "sys_team_member",
}
}
func (r *team) FindByID(id uint64) (*types.Team, error) {
sql := "SELECT * FROM " + r.teams + " WHERE id = ? AND " + sqlTeamScope
mod := &types.Team{}
return mod, isFound(r.db().Get(mod, sql, id), mod.ID > 0, ErrTeamNotFound)
}
func (r *team) FindByMemberID(userID uint64) ([]*types.Team, error) {
ids := make([]uint64, 0)
params := make([]interface{}, 0)
sql := "SELECT DISTINCT rel_team FROM " + r.members + " "
sql += "WHERE rel_user = ?"
params = append(params, userID)
if err := r.db().Select(&ids, sql, params...); err != nil {
return nil, err
}
rval := make([]*types.Team, 0)
for _, id := range ids {
mod, err := r.FindByID(id)
if err != nil {
return nil, err
}
rval = append(rval, mod)
}
return rval, nil
}
func (r *team) Find(filter *types.TeamFilter) ([]*types.Team, error) {
rval := make([]*types.Team, 0)
params := make([]interface{}, 0)
sql := "SELECT * FROM " + r.teams + " WHERE " + sqlTeamScope
if filter != nil {
if filter.Query != "" {
sql += " AND name LIKE ?"
params = append(params, filter.Query+"%")
}
}
sql += " ORDER BY name ASC"
return rval, r.db().Select(&rval, sql, params...)
}
func (r *team) Create(mod *types.Team) (*types.Team, error) {
mod.ID = factory.Sonyflake.NextID()
mod.CreatedAt = time.Now()
return mod, r.db().Insert(r.teams, mod)
}
func (r *team) Update(mod *types.Team) (*types.Team, error) {
mod.UpdatedAt = timeNowPtr()
return mod, r.db().Replace(r.teams, mod)
}
func (r *team) ArchiveByID(id uint64) error {
return r.updateColumnByID(r.teams, "archived_at", time.Now(), id)
}
func (r *team) UnarchiveByID(id uint64) error {
return r.updateColumnByID(r.teams, "archived_at", nil, id)
}
func (r *team) DeleteByID(id uint64) error {
return r.updateColumnByID(r.teams, "deleted_at", time.Now(), id)
}
func (r *team) MergeByID(id, targetTeamID uint64) error {
return ErrNotImplemented
}
func (r *team) MoveByID(id, targetOrganisationID uint64) error {
return ErrNotImplemented
}
func (r *team) MemberAddByID(id, userID uint64) error {
mod := &types.TeamMember{
TeamID: id,
UserID: userID,
}
return r.db().Replace(r.members, mod)
}
func (r *team) MemberRemoveByID(id, userID uint64) error {
mod := &types.TeamMember{
TeamID: id,
UserID: userID,
}
return r.db().Delete(r.members, mod, "rel_team", "rel_user")
}

View File

@ -1,103 +0,0 @@
package repository
import (
"context"
"github.com/titpetric/factory"
"testing"
"github.com/crusttech/crust/system/types"
)
func TestTeam(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
return
}
userRepo := User(context.Background(), factory.Database.MustGet())
user := &types.User{
Name: "John Doe",
Username: "johndoe",
}
user.GeneratePassword("johndoe")
{
u1, err := userRepo.Create(user)
assert(t, err == nil, "Owner.Create error: %+v", err)
assert(t, user.ID == u1.ID, "Changes were not stored")
}
teamRepo := Team(context.Background(), factory.Database.MustGet())
team := &types.Team{
Name: "Test team v1",
}
{
t1, err := teamRepo.Create(team)
assert(t, err == nil, "Team.Create error: %+v", err)
assert(t, team.Name == t1.Name, "Changes were not stored")
}
{
team.Name = "Test team v2"
t1, err := teamRepo.Update(team)
assert(t, err == nil, "Team.Update error: %+v", err)
assert(t, team.Name == t1.Name, "Changes were not stored")
}
{
t1, err := teamRepo.FindByID(team.ID)
assert(t, err == nil, "Team.FindByID error: %+v", err)
assert(t, team.Name == t1.Name, "Changes were not stored")
}
{
aa, err := teamRepo.Find(&types.TeamFilter{Query: team.Name})
assert(t, err == nil, "Team.Find error: %+v", err)
assert(t, len(aa) > 0, "No results found")
}
{
err := teamRepo.ArchiveByID(team.ID)
assert(t, err == nil, "Team.ArchiveByID error: %+v", err)
}
{
err := teamRepo.UnarchiveByID(team.ID)
assert(t, err == nil, "Team.UnarchiveByID error: %+v", err)
}
{
err := teamRepo.MemberAddByID(team.ID, user.ID)
assert(t, err == nil, "Team.MemberAddByID error: %+v", err)
}
{
teams, err := teamRepo.FindByMemberID(user.ID)
assert(t, err == nil, "Team.FindByMemberID error: %+v", err)
assert(t, len(teams) > 0, "No results found")
}
{
teams, err := teamRepo.FindByMemberID(0)
assert(t, err == nil, "Team.FindByMemberID error: %+v", err)
assert(t, len(teams) == 0, "Results found")
}
{
err := teamRepo.MemberRemoveByID(team.ID, user.ID)
assert(t, err == nil, "Team.MemberRemoveByID error: %+v", err)
}
{
err := teamRepo.DeleteByID(team.ID)
assert(t, err == nil, "Team.DeleteByID error: %+v", err)
}
{
err := userRepo.DeleteByID(user.ID)
assert(t, err == nil, "Owner.DeleteByID error: %+v", err)
}
}

View File

@ -86,7 +86,7 @@ func (r *user) FindByID(id uint64) (*types.User, error) {
if err := isFound(r.db().Get(mod, sql, id), mod.ID > 0, ErrUserNotFound); err != nil {
return nil, err
}
return mod, r.prepare(mod, "teams")
return mod, r.prepare(mod, "roles")
}
func (r *user) FindByIDs(IDs ...uint64) (uu types.UserSet, err error) {
@ -142,7 +142,7 @@ func (r *user) Find(filter *types.UserFilter) ([]*types.User, error) {
if err := r.db().Select(&rval, sql, params...); err != nil {
return nil, err
}
if err := r.prepareAll(rval, "teams"); err != nil {
if err := r.prepareAll(rval, "roles"); err != nil {
return nil, err
}
@ -182,16 +182,16 @@ func (r *user) prepareAll(users []*types.User, fields ...string) error {
}
func (r *user) prepare(user *types.User, fields ...string) (err error) {
api := Team(r.Context(), r.db())
api := Role(r.Context(), r.db())
for _, field := range fields {
switch field {
case "teams":
case "roles":
if user.ID > 0 {
teams, err := api.FindByMemberID(user.ID)
roles, err := api.FindByMemberID(user.ID)
if err != nil {
return err
}
user.Teams = teams
user.Roles = roles
}
default:
}

View File

@ -29,30 +29,30 @@ func TestUser(t *testing.T) {
assert(t, user.ID == uu.ID, "Changes were not stored")
}
teamRepo := Team(context.Background(), factory.Database.MustGet())
team := &types.Team{
Name: "Test team v1",
roleRepo := Role(context.Background(), factory.Database.MustGet())
role := &types.Role{
Name: "Test role v1",
}
{
t1, err := teamRepo.Create(team)
assert(t, err == nil, "Team.Create error: %+v", err)
assert(t, team.Name == t1.Name, "Changes were not stored")
t1, err := roleRepo.Create(role)
assert(t, err == nil, "Role.Create error: %+v", err)
assert(t, role.Name == t1.Name, "Changes were not stored")
err = teamRepo.MemberAddByID(t1.ID, user.ID)
assert(t, err == nil, "Team.MemberAddByID error: %+v", err)
err = roleRepo.MemberAddByID(t1.ID, user.ID)
assert(t, err == nil, "Role.MemberAddByID error: %+v", err)
}
{
uu, err := userRepo.FindByID(user.ID)
assert(t, err == nil, "Owner.FindByID error: %+v", err)
assert(t, len(uu.Teams) == 1, "Expected 1 team, got %d", len(uu.Teams))
assert(t, len(uu.Roles) == 1, "Expected 1 role, got %d", len(uu.Roles))
}
{
users, err := userRepo.Find(&types.UserFilter{Query: ""})
assert(t, err == nil, "Owner.Find error: %+v", err)
assert(t, len(users) == 1, "Owner.Find: expected 1 user, got %d", len(users))
assert(t, len(users[0].Teams) == 1, "Owner.Find: expected 1 team, got %d", len(users[0].Teams))
assert(t, len(users[0].Roles) == 1, "Owner.Find: expected 1 role, got %d", len(users[0].Roles))
}
}

View File

@ -22,34 +22,25 @@ import (
"github.com/titpetric/factory/resputil"
"github.com/crusttech/crust/messaging/rest/request"
"github.com/crusttech/crust/system/rest/request"
)
// Internal API interface
type PermissionsAPI interface {
List(context.Context, *request.PermissionsList) (interface{}, error)
Get(context.Context, *request.PermissionsGet) (interface{}, error)
Set(context.Context, *request.PermissionsSet) (interface{}, error)
Scopes(context.Context, *request.PermissionsScopes) (interface{}, error)
Delete(context.Context, *request.PermissionsDelete) (interface{}, error)
Update(context.Context, *request.PermissionsUpdate) (interface{}, error)
}
// HTTP API interface
type Permissions struct {
List func(http.ResponseWriter, *http.Request)
Get func(http.ResponseWriter, *http.Request)
Set func(http.ResponseWriter, *http.Request)
Scopes func(http.ResponseWriter, *http.Request)
Delete func(http.ResponseWriter, *http.Request)
Update func(http.ResponseWriter, *http.Request)
}
func NewPermissions(ph PermissionsAPI) *Permissions {
return &Permissions{
List: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewPermissionsList()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return ph.List(r.Context(), params)
})
},
Get: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewPermissionsGet()
@ -57,18 +48,18 @@ func NewPermissions(ph PermissionsAPI) *Permissions {
return ph.Get(r.Context(), params)
})
},
Set: func(w http.ResponseWriter, r *http.Request) {
Delete: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewPermissionsSet()
params := request.NewPermissionsDelete()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return ph.Set(r.Context(), params)
return ph.Delete(r.Context(), params)
})
},
Scopes: func(w http.ResponseWriter, r *http.Request) {
Update: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewPermissionsScopes()
params := request.NewPermissionsUpdate()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return ph.Scopes(r.Context(), params)
return ph.Update(r.Context(), params)
})
},
}
@ -78,10 +69,9 @@ func (ph *Permissions) MountRoutes(r chi.Router, middlewares ...func(http.Handle
r.Group(func(r chi.Router) {
r.Use(middlewares...)
r.Route("/permissions", func(r chi.Router) {
r.Get("/", ph.List)
r.Get("/{teamID}", ph.Get)
r.Post("/{teamID}", ph.Set)
r.Get("/scopes/{scope}", ph.Scopes)
r.Get("/{roleID}/rules", ph.Get)
r.Delete("/{roleID}/rules", ph.Delete)
r.Patch("/{roleID}/rules", ph.Update)
})
})
}

View File

@ -10,8 +10,8 @@ package handlers
1. run [spec](https://github.com/titpetric/spec) in the same folder,
2. run `./_gen.php` in this folder.
You may edit `team.go`, `team.util.go` or `team_test.go` to
implement your API calls, helper functions and tests. The file `team.go`
You may edit `role.go`, `role.util.go` or `role_test.go` to
implement your API calls, helper functions and tests. The file `role.go`
is only generated the first time, and will not be overwritten if it exists.
*/
@ -26,21 +26,21 @@ import (
)
// Internal API interface
type TeamAPI interface {
List(context.Context, *request.TeamList) (interface{}, error)
Create(context.Context, *request.TeamCreate) (interface{}, error)
Update(context.Context, *request.TeamUpdate) (interface{}, error)
Read(context.Context, *request.TeamRead) (interface{}, error)
Remove(context.Context, *request.TeamRemove) (interface{}, error)
Archive(context.Context, *request.TeamArchive) (interface{}, error)
Move(context.Context, *request.TeamMove) (interface{}, error)
Merge(context.Context, *request.TeamMerge) (interface{}, error)
MemberAdd(context.Context, *request.TeamMemberAdd) (interface{}, error)
MemberRemove(context.Context, *request.TeamMemberRemove) (interface{}, error)
type RoleAPI interface {
List(context.Context, *request.RoleList) (interface{}, error)
Create(context.Context, *request.RoleCreate) (interface{}, error)
Update(context.Context, *request.RoleUpdate) (interface{}, error)
Read(context.Context, *request.RoleRead) (interface{}, error)
Remove(context.Context, *request.RoleRemove) (interface{}, error)
Archive(context.Context, *request.RoleArchive) (interface{}, error)
Move(context.Context, *request.RoleMove) (interface{}, error)
Merge(context.Context, *request.RoleMerge) (interface{}, error)
MemberAdd(context.Context, *request.RoleMemberAdd) (interface{}, error)
MemberRemove(context.Context, *request.RoleMemberRemove) (interface{}, error)
}
// HTTP API interface
type Team struct {
type Role struct {
List func(http.ResponseWriter, *http.Request)
Create func(http.ResponseWriter, *http.Request)
Update func(http.ResponseWriter, *http.Request)
@ -53,95 +53,95 @@ type Team struct {
MemberRemove func(http.ResponseWriter, *http.Request)
}
func NewTeam(th TeamAPI) *Team {
return &Team{
func NewRole(rh RoleAPI) *Role {
return &Role{
List: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewTeamList()
params := request.NewRoleList()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return th.List(r.Context(), params)
return rh.List(r.Context(), params)
})
},
Create: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewTeamCreate()
params := request.NewRoleCreate()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return th.Create(r.Context(), params)
return rh.Create(r.Context(), params)
})
},
Update: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewTeamUpdate()
params := request.NewRoleUpdate()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return th.Update(r.Context(), params)
return rh.Update(r.Context(), params)
})
},
Read: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewTeamRead()
params := request.NewRoleRead()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return th.Read(r.Context(), params)
return rh.Read(r.Context(), params)
})
},
Remove: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewTeamRemove()
params := request.NewRoleRemove()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return th.Remove(r.Context(), params)
return rh.Remove(r.Context(), params)
})
},
Archive: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewTeamArchive()
params := request.NewRoleArchive()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return th.Archive(r.Context(), params)
return rh.Archive(r.Context(), params)
})
},
Move: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewTeamMove()
params := request.NewRoleMove()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return th.Move(r.Context(), params)
return rh.Move(r.Context(), params)
})
},
Merge: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewTeamMerge()
params := request.NewRoleMerge()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return th.Merge(r.Context(), params)
return rh.Merge(r.Context(), params)
})
},
MemberAdd: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewTeamMemberAdd()
params := request.NewRoleMemberAdd()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return th.MemberAdd(r.Context(), params)
return rh.MemberAdd(r.Context(), params)
})
},
MemberRemove: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewTeamMemberRemove()
params := request.NewRoleMemberRemove()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return th.MemberRemove(r.Context(), params)
return rh.MemberRemove(r.Context(), params)
})
},
}
}
func (th *Team) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
func (rh *Role) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
r.Group(func(r chi.Router) {
r.Use(middlewares...)
r.Route("/teams", func(r chi.Router) {
r.Get("/", th.List)
r.Post("/", th.Create)
r.Put("/{teamID}", th.Update)
r.Get("/{teamID}", th.Read)
r.Delete("/{teamID}", th.Remove)
r.Post("/{teamID}/archive", th.Archive)
r.Post("/{teamID}/move", th.Move)
r.Post("/{teamID}/merge", th.Merge)
r.Post("/{teamID}/memberAdd", th.MemberAdd)
r.Post("/{teamID}/memberRemove", th.MemberRemove)
r.Route("/roles", func(r chi.Router) {
r.Get("/", rh.List)
r.Post("/", rh.Create)
r.Put("/{roleID}", rh.Update)
r.Get("/{roleID}", rh.Read)
r.Delete("/{roleID}", rh.Remove)
r.Post("/{roleID}/archive", rh.Archive)
r.Post("/{roleID}/move", rh.Move)
r.Post("/{roleID}/merge", rh.Merge)
r.Post("/{roleID}/memberAdd", rh.MemberAdd)
r.Post("/{roleID}/memberRemove", rh.MemberRemove)
})
})
}

View File

@ -0,0 +1,36 @@
package rest
import (
"context"
"github.com/pkg/errors"
"github.com/crusttech/crust/system/rest/request"
"github.com/crusttech/crust/system/service"
)
var _ = errors.Wrap
type (
Permissions struct {
svc struct {
perm service.PermissionService
}
}
)
func (Permissions) New() *Permissions {
return &Permissions{}
}
func (ctrl *Permissions) Get(ctx context.Context, r *request.PermissionsGet) (interface{}, error) {
return ctrl.svc.perm.Get(r.RoleID)
}
func (ctrl *Permissions) Delete(ctx context.Context, r *request.PermissionsDelete) (interface{}, error) {
return ctrl.svc.perm.Delete(r.RoleID)
}
func (ctrl *Permissions) Update(ctx context.Context, r *request.PermissionsUpdate) (interface{}, error) {
return ctrl.svc.perm.Update(r.RoleID, r.Permissions)
}

View File

@ -37,9 +37,9 @@ func NewAuthCheck() *AuthCheck {
return &AuthCheck{}
}
func (a *AuthCheck) Fill(r *http.Request) (err error) {
func (au *AuthCheck) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(a)
err = json.NewDecoder(r.Body).Decode(au)
switch {
case err == io.EOF:
@ -79,9 +79,9 @@ func NewAuthLogin() *AuthLogin {
return &AuthLogin{}
}
func (a *AuthLogin) Fill(r *http.Request) (err error) {
func (au *AuthLogin) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(a)
err = json.NewDecoder(r.Body).Decode(au)
switch {
case err == io.EOF:
@ -108,11 +108,11 @@ func (a *AuthLogin) Fill(r *http.Request) (err error) {
if val, ok := post["username"]; ok {
a.Username = val
au.Username = val
}
if val, ok := post["password"]; ok {
a.Password = val
au.Password = val
}
return err
@ -128,9 +128,9 @@ func NewAuthLogout() *AuthLogout {
return &AuthLogout{}
}
func (a *AuthLogout) Fill(r *http.Request) (err error) {
func (au *AuthLogout) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(a)
err = json.NewDecoder(r.Body).Decode(au)
switch {
case err == io.EOF:

View File

@ -38,9 +38,9 @@ func NewOrganisationList() *OrganisationList {
return &OrganisationList{}
}
func (o *OrganisationList) Fill(r *http.Request) (err error) {
func (or *OrganisationList) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(o)
err = json.NewDecoder(r.Body).Decode(or)
switch {
case err == io.EOF:
@ -67,7 +67,7 @@ func (o *OrganisationList) Fill(r *http.Request) (err error) {
if val, ok := get["query"]; ok {
o.Query = val
or.Query = val
}
return err
@ -84,9 +84,9 @@ func NewOrganisationCreate() *OrganisationCreate {
return &OrganisationCreate{}
}
func (o *OrganisationCreate) Fill(r *http.Request) (err error) {
func (or *OrganisationCreate) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(o)
err = json.NewDecoder(r.Body).Decode(or)
switch {
case err == io.EOF:
@ -113,7 +113,7 @@ func (o *OrganisationCreate) Fill(r *http.Request) (err error) {
if val, ok := post["name"]; ok {
o.Name = val
or.Name = val
}
return err
@ -131,9 +131,9 @@ func NewOrganisationUpdate() *OrganisationUpdate {
return &OrganisationUpdate{}
}
func (o *OrganisationUpdate) Fill(r *http.Request) (err error) {
func (or *OrganisationUpdate) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(o)
err = json.NewDecoder(r.Body).Decode(or)
switch {
case err == io.EOF:
@ -158,10 +158,10 @@ func (o *OrganisationUpdate) Fill(r *http.Request) (err error) {
post[name] = string(param[0])
}
o.ID = parseUInt64(chi.URLParam(r, "id"))
or.ID = parseUInt64(chi.URLParam(r, "id"))
if val, ok := post["name"]; ok {
o.Name = val
or.Name = val
}
return err
@ -178,9 +178,9 @@ func NewOrganisationRemove() *OrganisationRemove {
return &OrganisationRemove{}
}
func (o *OrganisationRemove) Fill(r *http.Request) (err error) {
func (or *OrganisationRemove) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(o)
err = json.NewDecoder(r.Body).Decode(or)
switch {
case err == io.EOF:
@ -205,7 +205,7 @@ func (o *OrganisationRemove) Fill(r *http.Request) (err error) {
post[name] = string(param[0])
}
o.ID = parseUInt64(chi.URLParam(r, "id"))
or.ID = parseUInt64(chi.URLParam(r, "id"))
return err
}
@ -221,9 +221,9 @@ func NewOrganisationRead() *OrganisationRead {
return &OrganisationRead{}
}
func (o *OrganisationRead) Fill(r *http.Request) (err error) {
func (or *OrganisationRead) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(o)
err = json.NewDecoder(r.Body).Decode(or)
switch {
case err == io.EOF:
@ -250,7 +250,7 @@ func (o *OrganisationRead) Fill(r *http.Request) (err error) {
if val, ok := get["id"]; ok {
o.ID = parseUInt64(val)
or.ID = parseUInt64(val)
}
return err
@ -267,9 +267,9 @@ func NewOrganisationArchive() *OrganisationArchive {
return &OrganisationArchive{}
}
func (o *OrganisationArchive) Fill(r *http.Request) (err error) {
func (or *OrganisationArchive) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(o)
err = json.NewDecoder(r.Body).Decode(or)
switch {
case err == io.EOF:
@ -294,7 +294,7 @@ func (o *OrganisationArchive) Fill(r *http.Request) (err error) {
post[name] = string(param[0])
}
o.ID = parseUInt64(chi.URLParam(r, "id"))
or.ID = parseUInt64(chi.URLParam(r, "id"))
return err
}

View File

@ -31,59 +31,18 @@ import (
var _ = chi.URLParam
var _ = multipart.FileHeader{}
// Permissions list request parameters
type PermissionsList struct {
}
func NewPermissionsList() *PermissionsList {
return &PermissionsList{}
}
func (p *PermissionsList) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(p)
switch {
case err == io.EOF:
err = nil
case err != nil:
return errors.Wrap(err, "error parsing http request body")
}
}
if err = r.ParseForm(); err != nil {
return err
}
get := map[string]string{}
post := map[string]string{}
urlQuery := r.URL.Query()
for name, param := range urlQuery {
get[name] = string(param[0])
}
postVars := r.Form
for name, param := range postVars {
post[name] = string(param[0])
}
return err
}
var _ RequestFiller = NewPermissionsList()
// Permissions get request parameters
type PermissionsGet struct {
Resource string
TeamID uint64 `json:",string"`
RoleID uint64 `json:",string"`
}
func NewPermissionsGet() *PermissionsGet {
return &PermissionsGet{}
}
func (p *PermissionsGet) Fill(r *http.Request) (err error) {
func (pe *PermissionsGet) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(p)
err = json.NewDecoder(r.Body).Decode(pe)
switch {
case err == io.EOF:
@ -108,30 +67,25 @@ func (p *PermissionsGet) Fill(r *http.Request) (err error) {
post[name] = string(param[0])
}
if val, ok := get["resource"]; ok {
p.Resource = val
}
p.TeamID = parseUInt64(chi.URLParam(r, "teamID"))
pe.RoleID = parseUInt64(chi.URLParam(r, "roleID"))
return err
}
var _ RequestFiller = NewPermissionsGet()
// Permissions set request parameters
type PermissionsSet struct {
TeamID uint64 `json:",string"`
Permissions []rules.Rules
// Permissions delete request parameters
type PermissionsDelete struct {
RoleID uint64 `json:",string"`
}
func NewPermissionsSet() *PermissionsSet {
return &PermissionsSet{}
func NewPermissionsDelete() *PermissionsDelete {
return &PermissionsDelete{}
}
func (p *PermissionsSet) Fill(r *http.Request) (err error) {
func (pe *PermissionsDelete) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(p)
err = json.NewDecoder(r.Body).Decode(pe)
switch {
case err == io.EOF:
@ -156,25 +110,26 @@ func (p *PermissionsSet) Fill(r *http.Request) (err error) {
post[name] = string(param[0])
}
p.TeamID = parseUInt64(chi.URLParam(r, "teamID"))
pe.RoleID = parseUInt64(chi.URLParam(r, "roleID"))
return err
}
var _ RequestFiller = NewPermissionsSet()
var _ RequestFiller = NewPermissionsDelete()
// Permissions scopes request parameters
type PermissionsScopes struct {
Scope string
// Permissions update request parameters
type PermissionsUpdate struct {
RoleID uint64 `json:",string"`
Permissions []rules.Rule
}
func NewPermissionsScopes() *PermissionsScopes {
return &PermissionsScopes{}
func NewPermissionsUpdate() *PermissionsUpdate {
return &PermissionsUpdate{}
}
func (p *PermissionsScopes) Fill(r *http.Request) (err error) {
func (pe *PermissionsUpdate) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(p)
err = json.NewDecoder(r.Body).Decode(pe)
switch {
case err == io.EOF:
@ -199,9 +154,9 @@ func (p *PermissionsScopes) Fill(r *http.Request) (err error) {
post[name] = string(param[0])
}
p.Scope = chi.URLParam(r, "scope")
pe.RoleID = parseUInt64(chi.URLParam(r, "roleID"))
return err
}
var _ RequestFiller = NewPermissionsScopes()
var _ RequestFiller = NewPermissionsUpdate()

View File

@ -10,8 +10,8 @@ package request
1. run [spec](https://github.com/titpetric/spec) in the same folder,
2. run `./_gen.php` in this folder.
You may edit `team.go`, `team.util.go` or `team_test.go` to
implement your API calls, helper functions and tests. The file `team.go`
You may edit `role.go`, `role.util.go` or `role_test.go` to
implement your API calls, helper functions and tests. The file `role.go`
is only generated the first time, and will not be overwritten if it exists.
*/
@ -29,18 +29,18 @@ import (
var _ = chi.URLParam
var _ = multipart.FileHeader{}
// Team list request parameters
type TeamList struct {
// Role list request parameters
type RoleList struct {
Query string
}
func NewTeamList() *TeamList {
return &TeamList{}
func NewRoleList() *RoleList {
return &RoleList{}
}
func (t *TeamList) Fill(r *http.Request) (err error) {
func (ro *RoleList) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(t)
err = json.NewDecoder(r.Body).Decode(ro)
switch {
case err == io.EOF:
@ -67,27 +67,27 @@ func (t *TeamList) Fill(r *http.Request) (err error) {
if val, ok := get["query"]; ok {
t.Query = val
ro.Query = val
}
return err
}
var _ RequestFiller = NewTeamList()
var _ RequestFiller = NewRoleList()
// Team create request parameters
type TeamCreate struct {
// Role create request parameters
type RoleCreate struct {
Name string
Members []uint64 `json:",string"`
}
func NewTeamCreate() *TeamCreate {
return &TeamCreate{}
func NewRoleCreate() *RoleCreate {
return &RoleCreate{}
}
func (t *TeamCreate) Fill(r *http.Request) (err error) {
func (ro *RoleCreate) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(t)
err = json.NewDecoder(r.Body).Decode(ro)
switch {
case err == io.EOF:
@ -114,29 +114,29 @@ func (t *TeamCreate) Fill(r *http.Request) (err error) {
if val, ok := post["name"]; ok {
t.Name = val
ro.Name = val
}
t.Members = parseUInt64A(r.Form["members"])
ro.Members = parseUInt64A(r.Form["members"])
return err
}
var _ RequestFiller = NewTeamCreate()
var _ RequestFiller = NewRoleCreate()
// Team update request parameters
type TeamUpdate struct {
TeamID uint64 `json:",string"`
// Role update request parameters
type RoleUpdate struct {
RoleID uint64 `json:",string"`
Name string
Members []uint64 `json:",string"`
}
func NewTeamUpdate() *TeamUpdate {
return &TeamUpdate{}
func NewRoleUpdate() *RoleUpdate {
return &RoleUpdate{}
}
func (t *TeamUpdate) Fill(r *http.Request) (err error) {
func (ro *RoleUpdate) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(t)
err = json.NewDecoder(r.Body).Decode(ro)
switch {
case err == io.EOF:
@ -161,30 +161,30 @@ func (t *TeamUpdate) Fill(r *http.Request) (err error) {
post[name] = string(param[0])
}
t.TeamID = parseUInt64(chi.URLParam(r, "teamID"))
ro.RoleID = parseUInt64(chi.URLParam(r, "roleID"))
if val, ok := post["name"]; ok {
t.Name = val
ro.Name = val
}
t.Members = parseUInt64A(r.Form["members"])
ro.Members = parseUInt64A(r.Form["members"])
return err
}
var _ RequestFiller = NewTeamUpdate()
var _ RequestFiller = NewRoleUpdate()
// Team read request parameters
type TeamRead struct {
TeamID uint64 `json:",string"`
// Role read request parameters
type RoleRead struct {
RoleID uint64 `json:",string"`
}
func NewTeamRead() *TeamRead {
return &TeamRead{}
func NewRoleRead() *RoleRead {
return &RoleRead{}
}
func (t *TeamRead) Fill(r *http.Request) (err error) {
func (ro *RoleRead) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(t)
err = json.NewDecoder(r.Body).Decode(ro)
switch {
case err == io.EOF:
@ -209,25 +209,25 @@ func (t *TeamRead) Fill(r *http.Request) (err error) {
post[name] = string(param[0])
}
t.TeamID = parseUInt64(chi.URLParam(r, "teamID"))
ro.RoleID = parseUInt64(chi.URLParam(r, "roleID"))
return err
}
var _ RequestFiller = NewTeamRead()
var _ RequestFiller = NewRoleRead()
// Team remove request parameters
type TeamRemove struct {
TeamID uint64 `json:",string"`
// Role remove request parameters
type RoleRemove struct {
RoleID uint64 `json:",string"`
}
func NewTeamRemove() *TeamRemove {
return &TeamRemove{}
func NewRoleRemove() *RoleRemove {
return &RoleRemove{}
}
func (t *TeamRemove) Fill(r *http.Request) (err error) {
func (ro *RoleRemove) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(t)
err = json.NewDecoder(r.Body).Decode(ro)
switch {
case err == io.EOF:
@ -252,25 +252,25 @@ func (t *TeamRemove) Fill(r *http.Request) (err error) {
post[name] = string(param[0])
}
t.TeamID = parseUInt64(chi.URLParam(r, "teamID"))
ro.RoleID = parseUInt64(chi.URLParam(r, "roleID"))
return err
}
var _ RequestFiller = NewTeamRemove()
var _ RequestFiller = NewRoleRemove()
// Team archive request parameters
type TeamArchive struct {
TeamID uint64 `json:",string"`
// Role archive request parameters
type RoleArchive struct {
RoleID uint64 `json:",string"`
}
func NewTeamArchive() *TeamArchive {
return &TeamArchive{}
func NewRoleArchive() *RoleArchive {
return &RoleArchive{}
}
func (t *TeamArchive) Fill(r *http.Request) (err error) {
func (ro *RoleArchive) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(t)
err = json.NewDecoder(r.Body).Decode(ro)
switch {
case err == io.EOF:
@ -295,26 +295,26 @@ func (t *TeamArchive) Fill(r *http.Request) (err error) {
post[name] = string(param[0])
}
t.TeamID = parseUInt64(chi.URLParam(r, "teamID"))
ro.RoleID = parseUInt64(chi.URLParam(r, "roleID"))
return err
}
var _ RequestFiller = NewTeamArchive()
var _ RequestFiller = NewRoleArchive()
// Team move request parameters
type TeamMove struct {
TeamID uint64 `json:",string"`
// Role move request parameters
type RoleMove struct {
RoleID uint64 `json:",string"`
OrganisationID uint64 `json:",string"`
}
func NewTeamMove() *TeamMove {
return &TeamMove{}
func NewRoleMove() *RoleMove {
return &RoleMove{}
}
func (t *TeamMove) Fill(r *http.Request) (err error) {
func (ro *RoleMove) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(t)
err = json.NewDecoder(r.Body).Decode(ro)
switch {
case err == io.EOF:
@ -339,30 +339,30 @@ func (t *TeamMove) Fill(r *http.Request) (err error) {
post[name] = string(param[0])
}
t.TeamID = parseUInt64(chi.URLParam(r, "teamID"))
ro.RoleID = parseUInt64(chi.URLParam(r, "roleID"))
if val, ok := post["organisationID"]; ok {
t.OrganisationID = parseUInt64(val)
ro.OrganisationID = parseUInt64(val)
}
return err
}
var _ RequestFiller = NewTeamMove()
var _ RequestFiller = NewRoleMove()
// Team merge request parameters
type TeamMerge struct {
TeamID uint64 `json:",string"`
// Role merge request parameters
type RoleMerge struct {
RoleID uint64 `json:",string"`
Destination uint64 `json:",string"`
}
func NewTeamMerge() *TeamMerge {
return &TeamMerge{}
func NewRoleMerge() *RoleMerge {
return &RoleMerge{}
}
func (t *TeamMerge) Fill(r *http.Request) (err error) {
func (ro *RoleMerge) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(t)
err = json.NewDecoder(r.Body).Decode(ro)
switch {
case err == io.EOF:
@ -387,30 +387,30 @@ func (t *TeamMerge) Fill(r *http.Request) (err error) {
post[name] = string(param[0])
}
t.TeamID = parseUInt64(chi.URLParam(r, "teamID"))
ro.RoleID = parseUInt64(chi.URLParam(r, "roleID"))
if val, ok := post["destination"]; ok {
t.Destination = parseUInt64(val)
ro.Destination = parseUInt64(val)
}
return err
}
var _ RequestFiller = NewTeamMerge()
var _ RequestFiller = NewRoleMerge()
// Team memberAdd request parameters
type TeamMemberAdd struct {
TeamID uint64 `json:",string"`
// Role memberAdd request parameters
type RoleMemberAdd struct {
RoleID uint64 `json:",string"`
UserID uint64 `json:",string"`
}
func NewTeamMemberAdd() *TeamMemberAdd {
return &TeamMemberAdd{}
func NewRoleMemberAdd() *RoleMemberAdd {
return &RoleMemberAdd{}
}
func (t *TeamMemberAdd) Fill(r *http.Request) (err error) {
func (ro *RoleMemberAdd) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(t)
err = json.NewDecoder(r.Body).Decode(ro)
switch {
case err == io.EOF:
@ -435,30 +435,30 @@ func (t *TeamMemberAdd) Fill(r *http.Request) (err error) {
post[name] = string(param[0])
}
t.TeamID = parseUInt64(chi.URLParam(r, "teamID"))
ro.RoleID = parseUInt64(chi.URLParam(r, "roleID"))
if val, ok := post["userID"]; ok {
t.UserID = parseUInt64(val)
ro.UserID = parseUInt64(val)
}
return err
}
var _ RequestFiller = NewTeamMemberAdd()
var _ RequestFiller = NewRoleMemberAdd()
// Team memberRemove request parameters
type TeamMemberRemove struct {
TeamID uint64 `json:",string"`
// Role memberRemove request parameters
type RoleMemberRemove struct {
RoleID uint64 `json:",string"`
UserID uint64 `json:",string"`
}
func NewTeamMemberRemove() *TeamMemberRemove {
return &TeamMemberRemove{}
func NewRoleMemberRemove() *RoleMemberRemove {
return &RoleMemberRemove{}
}
func (t *TeamMemberRemove) Fill(r *http.Request) (err error) {
func (ro *RoleMemberRemove) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(t)
err = json.NewDecoder(r.Body).Decode(ro)
switch {
case err == io.EOF:
@ -483,13 +483,13 @@ func (t *TeamMemberRemove) Fill(r *http.Request) (err error) {
post[name] = string(param[0])
}
t.TeamID = parseUInt64(chi.URLParam(r, "teamID"))
ro.RoleID = parseUInt64(chi.URLParam(r, "roleID"))
if val, ok := post["userID"]; ok {
t.UserID = parseUInt64(val)
ro.UserID = parseUInt64(val)
}
return err
}
var _ RequestFiller = NewTeamMemberRemove()
var _ RequestFiller = NewRoleMemberRemove()

View File

@ -40,9 +40,9 @@ func NewUserList() *UserList {
return &UserList{}
}
func (u *UserList) Fill(r *http.Request) (err error) {
func (us *UserList) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(u)
err = json.NewDecoder(r.Body).Decode(us)
switch {
case err == io.EOF:
@ -69,15 +69,15 @@ func (u *UserList) Fill(r *http.Request) (err error) {
if val, ok := get["query"]; ok {
u.Query = val
us.Query = val
}
if val, ok := get["username"]; ok {
u.Username = val
us.Username = val
}
if val, ok := get["email"]; ok {
u.Email = val
us.Email = val
}
return err
@ -97,9 +97,9 @@ func NewUserCreate() *UserCreate {
return &UserCreate{}
}
func (u *UserCreate) Fill(r *http.Request) (err error) {
func (us *UserCreate) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(u)
err = json.NewDecoder(r.Body).Decode(us)
switch {
case err == io.EOF:
@ -126,19 +126,19 @@ func (u *UserCreate) Fill(r *http.Request) (err error) {
if val, ok := post["email"]; ok {
u.Email = val
us.Email = val
}
if val, ok := post["name"]; ok {
u.Name = val
us.Name = val
}
if val, ok := post["handle"]; ok {
u.Handle = val
us.Handle = val
}
if val, ok := post["kind"]; ok {
u.Kind = val
us.Kind = val
}
return err
@ -159,9 +159,9 @@ func NewUserUpdate() *UserUpdate {
return &UserUpdate{}
}
func (u *UserUpdate) Fill(r *http.Request) (err error) {
func (us *UserUpdate) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(u)
err = json.NewDecoder(r.Body).Decode(us)
switch {
case err == io.EOF:
@ -186,22 +186,22 @@ func (u *UserUpdate) Fill(r *http.Request) (err error) {
post[name] = string(param[0])
}
u.UserID = parseUInt64(chi.URLParam(r, "userID"))
us.UserID = parseUInt64(chi.URLParam(r, "userID"))
if val, ok := post["email"]; ok {
u.Email = val
us.Email = val
}
if val, ok := post["name"]; ok {
u.Name = val
us.Name = val
}
if val, ok := post["handle"]; ok {
u.Handle = val
us.Handle = val
}
if val, ok := post["kind"]; ok {
u.Kind = val
us.Kind = val
}
return err
@ -218,9 +218,9 @@ func NewUserRead() *UserRead {
return &UserRead{}
}
func (u *UserRead) Fill(r *http.Request) (err error) {
func (us *UserRead) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(u)
err = json.NewDecoder(r.Body).Decode(us)
switch {
case err == io.EOF:
@ -245,7 +245,7 @@ func (u *UserRead) Fill(r *http.Request) (err error) {
post[name] = string(param[0])
}
u.UserID = parseUInt64(chi.URLParam(r, "userID"))
us.UserID = parseUInt64(chi.URLParam(r, "userID"))
return err
}
@ -261,9 +261,9 @@ func NewUserRemove() *UserRemove {
return &UserRemove{}
}
func (u *UserRemove) Fill(r *http.Request) (err error) {
func (us *UserRemove) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(u)
err = json.NewDecoder(r.Body).Decode(us)
switch {
case err == io.EOF:
@ -288,7 +288,7 @@ func (u *UserRemove) Fill(r *http.Request) (err error) {
post[name] = string(param[0])
}
u.UserID = parseUInt64(chi.URLParam(r, "userID"))
us.UserID = parseUInt64(chi.URLParam(r, "userID"))
return err
}
@ -304,9 +304,9 @@ func NewUserSuspend() *UserSuspend {
return &UserSuspend{}
}
func (u *UserSuspend) Fill(r *http.Request) (err error) {
func (us *UserSuspend) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(u)
err = json.NewDecoder(r.Body).Decode(us)
switch {
case err == io.EOF:
@ -331,7 +331,7 @@ func (u *UserSuspend) Fill(r *http.Request) (err error) {
post[name] = string(param[0])
}
u.UserID = parseUInt64(chi.URLParam(r, "userID"))
us.UserID = parseUInt64(chi.URLParam(r, "userID"))
return err
}
@ -347,9 +347,9 @@ func NewUserUnsuspend() *UserUnsuspend {
return &UserUnsuspend{}
}
func (u *UserUnsuspend) Fill(r *http.Request) (err error) {
func (us *UserUnsuspend) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(u)
err = json.NewDecoder(r.Body).Decode(us)
switch {
case err == io.EOF:
@ -374,7 +374,7 @@ func (u *UserUnsuspend) Fill(r *http.Request) (err error) {
post[name] = string(param[0])
}
u.UserID = parseUInt64(chi.URLParam(r, "userID"))
us.UserID = parseUInt64(chi.URLParam(r, "userID"))
return err
}

76
system/rest/role.go Normal file
View File

@ -0,0 +1,76 @@
package rest
import (
"context"
"github.com/pkg/errors"
"github.com/crusttech/crust/system/rest/request"
"github.com/crusttech/crust/system/service"
"github.com/crusttech/crust/system/types"
)
var _ = errors.Wrap
type (
Role struct {
svc struct {
role service.RoleService
}
}
)
func (Role) New() *Role {
ctrl := &Role{}
ctrl.svc.role = service.DefaultRole
return ctrl
}
func (ctrl *Role) Read(ctx context.Context, r *request.RoleRead) (interface{}, error) {
return ctrl.svc.role.With(ctx).FindByID(r.RoleID)
}
func (ctrl *Role) List(ctx context.Context, r *request.RoleList) (interface{}, error) {
return ctrl.svc.role.With(ctx).Find(&types.RoleFilter{Query: r.Query})
}
func (ctrl *Role) Create(ctx context.Context, r *request.RoleCreate) (interface{}, error) {
org := &types.Role{
Name: r.Name,
}
return ctrl.svc.role.With(ctx).Create(org)
}
func (ctrl *Role) Update(ctx context.Context, r *request.RoleUpdate) (interface{}, error) {
org := &types.Role{
ID: r.RoleID,
Name: r.Name,
}
return ctrl.svc.role.With(ctx).Update(org)
}
func (ctrl *Role) Remove(ctx context.Context, r *request.RoleRemove) (interface{}, error) {
return nil, ctrl.svc.role.With(ctx).Delete(r.RoleID)
}
func (ctrl *Role) Archive(ctx context.Context, r *request.RoleArchive) (interface{}, error) {
return nil, ctrl.svc.role.With(ctx).Archive(r.RoleID)
}
func (ctrl *Role) Merge(ctx context.Context, r *request.RoleMerge) (interface{}, error) {
return nil, ctrl.svc.role.With(ctx).Merge(r.RoleID, r.Destination)
}
func (ctrl *Role) Move(ctx context.Context, r *request.RoleMove) (interface{}, error) {
return nil, ctrl.svc.role.With(ctx).Move(r.RoleID, r.OrganisationID)
}
func (ctrl *Role) MemberAdd(ctx context.Context, r *request.RoleMemberAdd) (interface{}, error) {
return nil, ctrl.svc.role.With(ctx).MemberAdd(r.RoleID, r.UserID)
}
func (ctrl *Role) MemberRemove(ctx context.Context, r *request.RoleMemberRemove) (interface{}, error) {
return nil, ctrl.svc.role.With(ctx).MemberRemove(r.RoleID, r.UserID)
}

View File

@ -47,8 +47,9 @@ func MountRoutes(oidcConfig *config.OIDC, socialConfig *config.Social, jwtEncode
r.Use(auth.MiddlewareValidOnly)
handlers.NewUser(User{}.New()).MountRoutes(r)
handlers.NewTeam(Team{}.New()).MountRoutes(r)
handlers.NewRole(Role{}.New()).MountRoutes(r)
handlers.NewOrganisation(Organisation{}.New()).MountRoutes(r)
handlers.NewPermissions(Permissions{}.New()).MountRoutes(r)
})
}
}

View File

@ -1,76 +0,0 @@
package rest
import (
"context"
"github.com/pkg/errors"
"github.com/crusttech/crust/system/rest/request"
"github.com/crusttech/crust/system/service"
"github.com/crusttech/crust/system/types"
)
var _ = errors.Wrap
type (
Team struct {
svc struct {
team service.TeamService
}
}
)
func (Team) New() *Team {
ctrl := &Team{}
ctrl.svc.team = service.DefaultTeam
return ctrl
}
func (ctrl *Team) Read(ctx context.Context, r *request.TeamRead) (interface{}, error) {
return ctrl.svc.team.With(ctx).FindByID(r.TeamID)
}
func (ctrl *Team) List(ctx context.Context, r *request.TeamList) (interface{}, error) {
return ctrl.svc.team.With(ctx).Find(&types.TeamFilter{Query: r.Query})
}
func (ctrl *Team) Create(ctx context.Context, r *request.TeamCreate) (interface{}, error) {
org := &types.Team{
Name: r.Name,
}
return ctrl.svc.team.With(ctx).Create(org)
}
func (ctrl *Team) Update(ctx context.Context, r *request.TeamUpdate) (interface{}, error) {
org := &types.Team{
ID: r.TeamID,
Name: r.Name,
}
return ctrl.svc.team.With(ctx).Update(org)
}
func (ctrl *Team) Remove(ctx context.Context, r *request.TeamRemove) (interface{}, error) {
return nil, ctrl.svc.team.With(ctx).Delete(r.TeamID)
}
func (ctrl *Team) Archive(ctx context.Context, r *request.TeamArchive) (interface{}, error) {
return nil, ctrl.svc.team.With(ctx).Archive(r.TeamID)
}
func (ctrl *Team) Merge(ctx context.Context, r *request.TeamMerge) (interface{}, error) {
return nil, ctrl.svc.team.With(ctx).Merge(r.TeamID, r.Destination)
}
func (ctrl *Team) Move(ctx context.Context, r *request.TeamMove) (interface{}, error) {
return nil, ctrl.svc.team.With(ctx).Move(r.TeamID, r.OrganisationID)
}
func (ctrl *Team) MemberAdd(ctx context.Context, r *request.TeamMemberAdd) (interface{}, error) {
return nil, ctrl.svc.team.With(ctx).MemberAdd(r.TeamID, r.UserID)
}
func (ctrl *Team) MemberRemove(ctx context.Context, r *request.TeamMemberRemove) (interface{}, error) {
return nil, ctrl.svc.team.With(ctx).MemberRemove(r.TeamID, r.UserID)
}

View File

@ -0,0 +1,51 @@
package service
import (
"context"
"github.com/crusttech/crust/internal/rules"
"github.com/crusttech/crust/system/repository"
)
type (
permission struct {
db db
ctx context.Context
resources rules.ResourcesInterface
}
PermissionService interface {
With(ctx context.Context) PermissionService
Get(roleID uint64) (interface{}, error)
Update(roleID uint64, rules []rules.Rule) (interface{}, error)
Delete(roleID uint64) (interface{}, error)
}
)
func Permission() PermissionService {
return (&permission{}).With(context.Background())
}
func (p *permission) With(ctx context.Context) PermissionService {
db := repository.DB(ctx)
return &permission{
db: db,
ctx: ctx,
resources: rules.NewResources(ctx, db),
}
}
func (p *permission) Get(roleID uint64) (interface{}, error) {
return p.resources.List(roleID)
}
func (p *permission) Update(roleID uint64, rules []rules.Rule) (interface{}, error) {
return nil, p.resources.Grant(roleID, rules)
}
func (p *permission) Delete(roleID uint64) (interface{}, error) {
return nil, p.resources.Delete(roleID)
}

View File

@ -0,0 +1,95 @@
package service
import (
"context"
"testing"
"github.com/titpetric/factory"
internalAuth "github.com/crusttech/crust/internal/auth"
"github.com/crusttech/crust/internal/rules"
. "github.com/crusttech/crust/internal/test"
systemRepos "github.com/crusttech/crust/system/repository"
systemTypes "github.com/crusttech/crust/system/types"
)
func TestPermissions(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
return
}
ctx := context.TODO()
// Create user for test.
userRepo := systemRepos.User(ctx, factory.Database.MustGet())
user := &systemTypes.User{
Name: "John Doe",
Username: "johndoe",
SatosaID: "1234",
}
err := user.GeneratePassword("johndoe")
NoError(t, err, "expected no error generating password, got %v", err)
_, err = userRepo.Create(user)
NoError(t, err, "expected no error creating user, got %v", err)
// Create role for test and add user
roleRepo := systemRepos.Role(ctx, factory.Database.MustGet())
role := &systemTypes.Role{
Name: "Test role v1",
}
_, err = roleRepo.Create(role)
NoError(t, err, "expected no error creating role, got %v", err)
err = roleRepo.MemberAddByID(role.ID, user.ID)
NoError(t, err, "expected no error adding user to role, got %v", err)
// Set Identity.
ctx = internalAuth.SetIdentityToContext(ctx, user)
// Create permission service.
permissionSvc := Permission().With(ctx)
// Setup rules for test role.
{
rules := []rules.Rule{
rules.Rule{Resource: "messaging:channel:*", Operation: "update", Value: rules.Allow},
rules.Rule{Resource: "messaging:channel:1", Operation: "update", Value: rules.Deny},
rules.Rule{Resource: "messaging:channel:2", Operation: "update"},
rules.Rule{Resource: "system", Operation: "organisation.create", Value: rules.Allow},
rules.Rule{Resource: "system:organisation:*", Operation: "update", Value: rules.Allow},
rules.Rule{Resource: "system:organisation:*", Operation: "delete", Value: rules.Allow},
rules.Rule{Resource: "messaging:channel:*", Operation: "update.name", Value: rules.Allow},
rules.Rule{Resource: "messaging:channel:*", Operation: "update.topic", Value: rules.Allow},
rules.Rule{Resource: "messaging:channel:*", Operation: "members.manage", Value: rules.Allow},
}
_, err := permissionSvc.Update(role.ID, rules)
NoError(t, err, "expected no error, setting rules")
}
// List rules for test role.
{
ret, err := permissionSvc.Get(role.ID)
NoError(t, err, "expected no error, setting rules")
rules := ret.([]rules.Rule)
Assert(t, len(rules) == 8, "expected len(rules) == 8, got %v", len(rules))
}
// Delete rules for test role.
{
_, err := permissionSvc.Delete(role.ID)
NoError(t, err, "expected no error, setting rules")
}
// List rules for test role.
{
ret, err := permissionSvc.Get(role.ID)
NoError(t, err, "expected no error, setting rules")
rules := ret.([]rules.Rule)
Assert(t, len(rules) == 0, "expected len(rules) == 0, got %v", len(rules))
}
}

131
system/service/role.go Normal file
View File

@ -0,0 +1,131 @@
package service
import (
"context"
"github.com/titpetric/factory"
"github.com/crusttech/crust/system/repository"
"github.com/crusttech/crust/system/types"
)
type (
role struct {
db *factory.DB
ctx context.Context
role repository.RoleRepository
}
RoleService interface {
With(ctx context.Context) RoleService
FindByID(roleID uint64) (*types.Role, error)
Find(filter *types.RoleFilter) ([]*types.Role, error)
Create(role *types.Role) (*types.Role, error)
Update(role *types.Role) (*types.Role, error)
Merge(roleID, targetroleID uint64) error
Move(roleID, organisationID uint64) error
Archive(ID uint64) error
Unarchive(ID uint64) error
Delete(ID uint64) error
MemberAdd(roleID, userID uint64) error
MemberRemove(roleID, userID uint64) error
}
)
func Role() RoleService {
return (&role{}).With(context.Background())
}
func (svc *role) With(ctx context.Context) RoleService {
db := repository.DB(ctx)
return &role{
db: db,
ctx: ctx,
role: repository.Role(ctx, db),
}
}
func (svc *role) FindByID(id uint64) (*types.Role, error) {
// @todo: permission check if current user has access to this role
return svc.role.FindByID(id)
}
func (svc *role) Find(filter *types.RoleFilter) ([]*types.Role, error) {
// @todo: permission check to return only roles that current user has access to
return svc.role.Find(filter)
}
func (svc *role) Create(mod *types.Role) (*types.Role, error) {
// @todo: permission check if current user can add/edit role
return svc.role.Create(mod)
}
func (svc *role) Update(mod *types.Role) (t *types.Role, err error) {
// @todo: permission check if current user can add/edit role
// @todo: make sure archived & deleted entries can not be edited
return t, svc.db.Transaction(func() (err error) {
if t, err = svc.role.FindByID(mod.ID); err != nil {
return
}
// Assign changed values
t.Name = mod.Name
t.Handle = mod.Handle
if t, err = svc.role.Update(t); err != nil {
return err
}
return nil
})
}
func (svc *role) Delete(id uint64) error {
// @todo: make history unavailable
// @todo: notify users that role has been removed (remove from web UI)
// @todo: permissions check if current user can remove role
return svc.role.DeleteByID(id)
}
func (svc *role) Archive(id uint64) error {
// @todo: make history unavailable
// @todo: notify users that role has been removed (remove from web UI)
// @todo: permissions check if current user can remove role
return svc.role.ArchiveByID(id)
}
func (svc *role) Unarchive(id uint64) error {
// @todo: permissions check if current user can unarchive role
// @todo: make history accessible
// @todo: notify users that role has been unarchived
return svc.role.UnarchiveByID(id)
}
func (svc *role) Merge(id, targetroleID uint64) error {
// @todo: permission check if current user can merge role
return svc.role.MergeByID(id, targetroleID)
}
func (svc *role) Move(id, targetOrganisationID uint64) error {
// @todo: permission check if current user can move role to another organisation
return svc.role.MoveByID(id, targetOrganisationID)
}
func (svc *role) MemberAdd(id, userID uint64) error {
// @todo: permission check if current user can add user in to a role
return svc.role.MemberAddByID(id, userID)
}
func (svc *role) MemberRemove(id, userID uint64) error {
// @todo: permission check if current user can remove user from a role
return svc.role.MemberRemoveByID(id, userID)
}
var _ RoleService = &role{}

View File

@ -0,0 +1,183 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: system/service/role.go
// Package service is a generated GoMock package.
package service
import (
context "context"
types "github.com/crusttech/crust/system/types"
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockRoleService is a mock of RoleService interface
type MockRoleService struct {
ctrl *gomock.Controller
recorder *MockRoleServiceMockRecorder
}
// MockRoleServiceMockRecorder is the mock recorder for MockRoleService
type MockRoleServiceMockRecorder struct {
mock *MockRoleService
}
// NewMockRoleService creates a new mock instance
func NewMockRoleService(ctrl *gomock.Controller) *MockRoleService {
mock := &MockRoleService{ctrl: ctrl}
mock.recorder = &MockRoleServiceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockRoleService) EXPECT() *MockRoleServiceMockRecorder {
return m.recorder
}
// With mocks base method
func (m *MockRoleService) With(ctx context.Context) RoleService {
ret := m.ctrl.Call(m, "With", ctx)
ret0, _ := ret[0].(RoleService)
return ret0
}
// With indicates an expected call of With
func (mr *MockRoleServiceMockRecorder) With(ctx interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "With", reflect.TypeOf((*MockRoleService)(nil).With), ctx)
}
// FindByID mocks base method
func (m *MockRoleService) FindByID(roleID uint64) (*types.Role, error) {
ret := m.ctrl.Call(m, "FindByID", roleID)
ret0, _ := ret[0].(*types.Role)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindByID indicates an expected call of FindByID
func (mr *MockRoleServiceMockRecorder) FindByID(roleID interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByID", reflect.TypeOf((*MockRoleService)(nil).FindByID), roleID)
}
// Find mocks base method
func (m *MockRoleService) Find(filter *types.RoleFilter) ([]*types.Role, error) {
ret := m.ctrl.Call(m, "Find", filter)
ret0, _ := ret[0].([]*types.Role)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Find indicates an expected call of Find
func (mr *MockRoleServiceMockRecorder) Find(filter interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Find", reflect.TypeOf((*MockRoleService)(nil).Find), filter)
}
// Create mocks base method
func (m *MockRoleService) Create(role *types.Role) (*types.Role, error) {
ret := m.ctrl.Call(m, "Create", role)
ret0, _ := ret[0].(*types.Role)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Create indicates an expected call of Create
func (mr *MockRoleServiceMockRecorder) Create(role interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockRoleService)(nil).Create), role)
}
// Update mocks base method
func (m *MockRoleService) Update(role *types.Role) (*types.Role, error) {
ret := m.ctrl.Call(m, "Update", role)
ret0, _ := ret[0].(*types.Role)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Update indicates an expected call of Update
func (mr *MockRoleServiceMockRecorder) Update(role interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockRoleService)(nil).Update), role)
}
// Merge mocks base method
func (m *MockRoleService) Merge(roleID, targetroleID uint64) error {
ret := m.ctrl.Call(m, "Merge", roleID, targetroleID)
ret0, _ := ret[0].(error)
return ret0
}
// Merge indicates an expected call of Merge
func (mr *MockRoleServiceMockRecorder) Merge(roleID, targetroleID interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Merge", reflect.TypeOf((*MockRoleService)(nil).Merge), roleID, targetroleID)
}
// Move mocks base method
func (m *MockRoleService) Move(roleID, organisationID uint64) error {
ret := m.ctrl.Call(m, "Move", roleID, organisationID)
ret0, _ := ret[0].(error)
return ret0
}
// Move indicates an expected call of Move
func (mr *MockRoleServiceMockRecorder) Move(roleID, organisationID interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Move", reflect.TypeOf((*MockRoleService)(nil).Move), roleID, organisationID)
}
// Archive mocks base method
func (m *MockRoleService) Archive(ID uint64) error {
ret := m.ctrl.Call(m, "Archive", ID)
ret0, _ := ret[0].(error)
return ret0
}
// Archive indicates an expected call of Archive
func (mr *MockRoleServiceMockRecorder) Archive(ID interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Archive", reflect.TypeOf((*MockRoleService)(nil).Archive), ID)
}
// Unarchive mocks base method
func (m *MockRoleService) Unarchive(ID uint64) error {
ret := m.ctrl.Call(m, "Unarchive", ID)
ret0, _ := ret[0].(error)
return ret0
}
// Unarchive indicates an expected call of Unarchive
func (mr *MockRoleServiceMockRecorder) Unarchive(ID interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unarchive", reflect.TypeOf((*MockRoleService)(nil).Unarchive), ID)
}
// Delete mocks base method
func (m *MockRoleService) Delete(ID uint64) error {
ret := m.ctrl.Call(m, "Delete", ID)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete
func (mr *MockRoleServiceMockRecorder) Delete(ID interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockRoleService)(nil).Delete), ID)
}
// MemberAdd mocks base method
func (m *MockRoleService) MemberAdd(roleID, userID uint64) error {
ret := m.ctrl.Call(m, "MemberAdd", roleID, userID)
ret0, _ := ret[0].(error)
return ret0
}
// MemberAdd indicates an expected call of MemberAdd
func (mr *MockRoleServiceMockRecorder) MemberAdd(roleID, userID interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MemberAdd", reflect.TypeOf((*MockRoleService)(nil).MemberAdd), roleID, userID)
}
// MemberRemove mocks base method
func (m *MockRoleService) MemberRemove(roleID, userID uint64) error {
ret := m.ctrl.Call(m, "MemberRemove", roleID, userID)
ret0, _ := ret[0].(error)
return ret0
}
// MemberRemove indicates an expected call of MemberRemove
func (mr *MockRoleServiceMockRecorder) MemberRemove(roleID, userID interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MemberRemove", reflect.TypeOf((*MockRoleService)(nil).MemberRemove), roleID, userID)
}

View File

@ -14,7 +14,8 @@ var (
o sync.Once
DefaultAuth AuthService
DefaultUser UserService
DefaultTeam TeamService
DefaultRole RoleService
DefaultPermission PermissionService
DefaultOrganisation OrganisationService
)
@ -22,7 +23,8 @@ func Init() {
o.Do(func() {
DefaultAuth = Auth()
DefaultUser = User()
DefaultTeam = Team()
DefaultRole = Role()
DefaultPermission = Permission()
DefaultOrganisation = Organisation()
})
}

View File

@ -1,131 +0,0 @@
package service
import (
"context"
"github.com/titpetric/factory"
"github.com/crusttech/crust/system/repository"
"github.com/crusttech/crust/system/types"
)
type (
team struct {
db *factory.DB
ctx context.Context
team repository.TeamRepository
}
TeamService interface {
With(ctx context.Context) TeamService
FindByID(teamID uint64) (*types.Team, error)
Find(filter *types.TeamFilter) ([]*types.Team, error)
Create(team *types.Team) (*types.Team, error)
Update(team *types.Team) (*types.Team, error)
Merge(teamID, targetTeamID uint64) error
Move(teamID, organisationID uint64) error
Archive(ID uint64) error
Unarchive(ID uint64) error
Delete(ID uint64) error
MemberAdd(teamID, userID uint64) error
MemberRemove(teamID, userID uint64) error
}
)
func Team() TeamService {
return (&team{}).With(context.Background())
}
func (svc *team) With(ctx context.Context) TeamService {
db := repository.DB(ctx)
return &team{
db: db,
ctx: ctx,
team: repository.Team(ctx, db),
}
}
func (svc *team) FindByID(id uint64) (*types.Team, error) {
// @todo: permission check if current user has access to this team
return svc.team.FindByID(id)
}
func (svc *team) Find(filter *types.TeamFilter) ([]*types.Team, error) {
// @todo: permission check to return only teams that current user has access to
return svc.team.Find(filter)
}
func (svc *team) Create(mod *types.Team) (*types.Team, error) {
// @todo: permission check if current user can add/edit team
return svc.team.Create(mod)
}
func (svc *team) Update(mod *types.Team) (t *types.Team, err error) {
// @todo: permission check if current user can add/edit team
// @todo: make sure archived & deleted entries can not be edited
return t, svc.db.Transaction(func() (err error) {
if t, err = svc.team.FindByID(mod.ID); err != nil {
return
}
// Assign changed values
t.Name = mod.Name
t.Handle = mod.Handle
if t, err = svc.team.Update(t); err != nil {
return err
}
return nil
})
}
func (svc *team) Delete(id uint64) error {
// @todo: make history unavailable
// @todo: notify users that team has been removed (remove from web UI)
// @todo: permissions check if current user can remove team
return svc.team.DeleteByID(id)
}
func (svc *team) Archive(id uint64) error {
// @todo: make history unavailable
// @todo: notify users that team has been removed (remove from web UI)
// @todo: permissions check if current user can remove team
return svc.team.ArchiveByID(id)
}
func (svc *team) Unarchive(id uint64) error {
// @todo: permissions check if current user can unarchive team
// @todo: make history accessible
// @todo: notify users that team has been unarchived
return svc.team.UnarchiveByID(id)
}
func (svc *team) Merge(id, targetTeamID uint64) error {
// @todo: permission check if current user can merge team
return svc.team.MergeByID(id, targetTeamID)
}
func (svc *team) Move(id, targetOrganisationID uint64) error {
// @todo: permission check if current user can move team to another organisation
return svc.team.MoveByID(id, targetOrganisationID)
}
func (svc *team) MemberAdd(id, userID uint64) error {
// @todo: permission check if current user can add user in to a team
return svc.team.MemberAddByID(id, userID)
}
func (svc *team) MemberRemove(id, userID uint64) error {
// @todo: permission check if current user can remove user from a team
return svc.team.MemberRemoveByID(id, userID)
}
var _ TeamService = &team{}

View File

@ -1,183 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: system/service/team.go
// Package service is a generated GoMock package.
package service
import (
context "context"
types "github.com/crusttech/crust/system/types"
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockTeamService is a mock of TeamService interface
type MockTeamService struct {
ctrl *gomock.Controller
recorder *MockTeamServiceMockRecorder
}
// MockTeamServiceMockRecorder is the mock recorder for MockTeamService
type MockTeamServiceMockRecorder struct {
mock *MockTeamService
}
// NewMockTeamService creates a new mock instance
func NewMockTeamService(ctrl *gomock.Controller) *MockTeamService {
mock := &MockTeamService{ctrl: ctrl}
mock.recorder = &MockTeamServiceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockTeamService) EXPECT() *MockTeamServiceMockRecorder {
return m.recorder
}
// With mocks base method
func (m *MockTeamService) With(ctx context.Context) TeamService {
ret := m.ctrl.Call(m, "With", ctx)
ret0, _ := ret[0].(TeamService)
return ret0
}
// With indicates an expected call of With
func (mr *MockTeamServiceMockRecorder) With(ctx interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "With", reflect.TypeOf((*MockTeamService)(nil).With), ctx)
}
// FindByID mocks base method
func (m *MockTeamService) FindByID(teamID uint64) (*types.Team, error) {
ret := m.ctrl.Call(m, "FindByID", teamID)
ret0, _ := ret[0].(*types.Team)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindByID indicates an expected call of FindByID
func (mr *MockTeamServiceMockRecorder) FindByID(teamID interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByID", reflect.TypeOf((*MockTeamService)(nil).FindByID), teamID)
}
// Find mocks base method
func (m *MockTeamService) Find(filter *types.TeamFilter) ([]*types.Team, error) {
ret := m.ctrl.Call(m, "Find", filter)
ret0, _ := ret[0].([]*types.Team)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Find indicates an expected call of Find
func (mr *MockTeamServiceMockRecorder) Find(filter interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Find", reflect.TypeOf((*MockTeamService)(nil).Find), filter)
}
// Create mocks base method
func (m *MockTeamService) Create(team *types.Team) (*types.Team, error) {
ret := m.ctrl.Call(m, "Create", team)
ret0, _ := ret[0].(*types.Team)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Create indicates an expected call of Create
func (mr *MockTeamServiceMockRecorder) Create(team interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockTeamService)(nil).Create), team)
}
// Update mocks base method
func (m *MockTeamService) Update(team *types.Team) (*types.Team, error) {
ret := m.ctrl.Call(m, "Update", team)
ret0, _ := ret[0].(*types.Team)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Update indicates an expected call of Update
func (mr *MockTeamServiceMockRecorder) Update(team interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockTeamService)(nil).Update), team)
}
// Merge mocks base method
func (m *MockTeamService) Merge(teamID, targetTeamID uint64) error {
ret := m.ctrl.Call(m, "Merge", teamID, targetTeamID)
ret0, _ := ret[0].(error)
return ret0
}
// Merge indicates an expected call of Merge
func (mr *MockTeamServiceMockRecorder) Merge(teamID, targetTeamID interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Merge", reflect.TypeOf((*MockTeamService)(nil).Merge), teamID, targetTeamID)
}
// Move mocks base method
func (m *MockTeamService) Move(teamID, organisationID uint64) error {
ret := m.ctrl.Call(m, "Move", teamID, organisationID)
ret0, _ := ret[0].(error)
return ret0
}
// Move indicates an expected call of Move
func (mr *MockTeamServiceMockRecorder) Move(teamID, organisationID interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Move", reflect.TypeOf((*MockTeamService)(nil).Move), teamID, organisationID)
}
// Archive mocks base method
func (m *MockTeamService) Archive(ID uint64) error {
ret := m.ctrl.Call(m, "Archive", ID)
ret0, _ := ret[0].(error)
return ret0
}
// Archive indicates an expected call of Archive
func (mr *MockTeamServiceMockRecorder) Archive(ID interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Archive", reflect.TypeOf((*MockTeamService)(nil).Archive), ID)
}
// Unarchive mocks base method
func (m *MockTeamService) Unarchive(ID uint64) error {
ret := m.ctrl.Call(m, "Unarchive", ID)
ret0, _ := ret[0].(error)
return ret0
}
// Unarchive indicates an expected call of Unarchive
func (mr *MockTeamServiceMockRecorder) Unarchive(ID interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unarchive", reflect.TypeOf((*MockTeamService)(nil).Unarchive), ID)
}
// Delete mocks base method
func (m *MockTeamService) Delete(ID uint64) error {
ret := m.ctrl.Call(m, "Delete", ID)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete
func (mr *MockTeamServiceMockRecorder) Delete(ID interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockTeamService)(nil).Delete), ID)
}
// MemberAdd mocks base method
func (m *MockTeamService) MemberAdd(teamID, userID uint64) error {
ret := m.ctrl.Call(m, "MemberAdd", teamID, userID)
ret0, _ := ret[0].(error)
return ret0
}
// MemberAdd indicates an expected call of MemberAdd
func (mr *MockTeamServiceMockRecorder) MemberAdd(teamID, userID interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MemberAdd", reflect.TypeOf((*MockTeamService)(nil).MemberAdd), teamID, userID)
}
// MemberRemove mocks base method
func (m *MockTeamService) MemberRemove(teamID, userID uint64) error {
ret := m.ctrl.Call(m, "MemberRemove", teamID, userID)
ret0, _ := ret[0].(error)
return ret0
}
// MemberRemove indicates an expected call of MemberRemove
func (mr *MockTeamServiceMockRecorder) MemberRemove(teamID, userID interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MemberRemove", reflect.TypeOf((*MockTeamService)(nil).MemberRemove), teamID, userID)
}

View File

@ -8,16 +8,16 @@ import (
type (
// TeamSet slice of Team
// RoleSet slice of Role
//
// This type is auto-generated.
TeamSet []*Team
RoleSet []*Role
)
// Walk iterates through every slice item and calls w(Team) err
// Walk iterates through every slice item and calls w(Role) err
//
// This function is auto-generated.
func (set TeamSet) Walk(w func(*Team) error) (err error) {
func (set RoleSet) Walk(w func(*Role) error) (err error) {
for i := range set {
if err = w(set[i]); err != nil {
return
@ -27,12 +27,12 @@ func (set TeamSet) Walk(w func(*Team) error) (err error) {
return
}
// Filter iterates through every slice item, calls f(Team) (bool, err) and return filtered slice
// Filter iterates through every slice item, calls f(Role) (bool, err) and return filtered slice
//
// This function is auto-generated.
func (set TeamSet) Filter(f func(*Team) (bool, error)) (out TeamSet, err error) {
func (set RoleSet) Filter(f func(*Role) (bool, error)) (out RoleSet, err error) {
var ok bool
out = TeamSet{}
out = RoleSet{}
for i := range set {
if ok, err = f(set[i]); err != nil {
return
@ -47,7 +47,7 @@ func (set TeamSet) Filter(f func(*Team) (bool, error)) (out TeamSet, err error)
// FindByID finds items from slice by its ID property
//
// This function is auto-generated.
func (set TeamSet) FindByID(ID uint64) *Team {
func (set RoleSet) FindByID(ID uint64) *Role {
for i := range set {
if set[i].ID == ID {
return set[i]
@ -60,7 +60,7 @@ func (set TeamSet) FindByID(ID uint64) *Team {
// IDs returns a slice of uint64s from all items in the set
//
// This function is auto-generated.
func (set TeamSet) IDs() (IDs []uint64) {
func (set RoleSet) IDs() (IDs []uint64) {
IDs = make([]uint64, len(set))
for i := range set {
@ -73,7 +73,7 @@ func (set TeamSet) IDs() (IDs []uint64) {
// Resources returns a slice of types.Resource from all items in the set
//
// This function is auto-generated.
func (set TeamSet) Resources() (Resources []rules.Resource) {
func (set RoleSet) Resources() (Resources []rules.Resource) {
Resources = make([]rules.Resource, len(set))
for i := range set {

View File

@ -7,9 +7,9 @@ import (
)
type (
// Teams - An organisation may have many teams. Teams may have many channels available. Access to channels may be shared between teams.
Team struct {
ID uint64 `json:"teamID,string" db:"id"`
// Role - An organisation may have many roles. Roles may have many channels available. Access to channels may be shared between roles.
Role struct {
ID uint64 `json:"roleID,string" db:"id"`
Name string `json:"name" db:"name"`
Handle string `json:"handle" db:"handle"`
CreatedAt time.Time `json:"createdAt,omitempty" db:"created_at"`
@ -18,13 +18,13 @@ type (
DeletedAt *time.Time `json:"deletedAt,omitempty" db:"deleted_at"`
}
TeamFilter struct {
RoleFilter struct {
Query string
}
)
// Resource returns a system resource ID for this type
func (r *Team) Resource() rules.Resource {
func (r *Role) Resource() rules.Resource {
resource := rules.Resource{
Scope: "team",
}

View File

@ -0,0 +1,12 @@
package types
type (
RoleMember struct {
RoleID uint64 `db:"rel_role"`
UserID uint64 `db:"rel_user"`
}
RoleMemberFilter struct {
Query string
}
)

View File

@ -1,12 +0,0 @@
package types
type (
TeamMember struct {
TeamID uint64 `db:"rel_team"`
UserID uint64 `db:"rel_user"`
}
TeamMemberFilter struct {
Query string
}
)

View File

@ -29,7 +29,7 @@ type (
SuspendedAt *time.Time `json:"suspendedAt,omitempty" db:"suspended_at"`
DeletedAt *time.Time `json:"deletedAt,omitempty" db:"deleted_at"`
Teams []*Team `json:"teams,omitempty" db:"-"`
Roles []*Role `json:"roles,omitempty" db:"-"`
}
UserFilter struct {