Skip to content

Commit be33923

Browse files
authored
Setup CRUD permissions check (#74)
* Create IsAuthorOf permission group struct * Create story permission groups * Create IsSelf permission group struct * Create user permission groups * Create delete operation permission groups * Fix imports post-refactor * Update CheckPermissions signature * Create permission groups for listing of resources * Add permissions check to stories controllers * Add permissions check to user controllers * Fix role getter in usergroups middleware * Implement user role authorization checker
1 parent ab71347 commit be33923

File tree

14 files changed

+252
-14
lines changed

14 files changed

+252
-14
lines changed

controller/stories/delete.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import (
88
"github.com/go-chi/chi/v5"
99
"github.com/sirupsen/logrus"
1010
"github.com/source-academy/stories-backend/controller"
11+
"github.com/source-academy/stories-backend/internal/auth"
1112
"github.com/source-academy/stories-backend/internal/database"
1213
apierrors "github.com/source-academy/stories-backend/internal/errors"
14+
storypermissiongroups "github.com/source-academy/stories-backend/internal/permissiongroups/stories"
1315
"github.com/source-academy/stories-backend/model"
1416
storyviews "github.com/source-academy/stories-backend/view/stories"
1517
)
@@ -23,6 +25,14 @@ func HandleDelete(w http.ResponseWriter, r *http.Request) error {
2325
}
2426
}
2527

28+
err = auth.CheckPermissions(r, storypermissiongroups.Delete(uint(storyID)))
29+
if err != nil {
30+
logrus.Error(err)
31+
return apierrors.ClientForbiddenError{
32+
Message: fmt.Sprintf("Error deleting story: %v", err),
33+
}
34+
}
35+
2636
// TODO: Prevents cross-tenant story viewing
2737
// when user is a member of multiple stories groups.
2838
// Not implemented yet as deletion is protected with more

controller/stories/stories.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,25 @@ import (
1111
"github.com/sirupsen/logrus"
1212

1313
"github.com/source-academy/stories-backend/controller"
14+
"github.com/source-academy/stories-backend/internal/auth"
1415
"github.com/source-academy/stories-backend/internal/database"
1516
apierrors "github.com/source-academy/stories-backend/internal/errors"
17+
storypermissiongroups "github.com/source-academy/stories-backend/internal/permissiongroups/stories"
1618
"github.com/source-academy/stories-backend/internal/usergroups"
1719
"github.com/source-academy/stories-backend/model"
1820
storyparams "github.com/source-academy/stories-backend/params/stories"
1921
storyviews "github.com/source-academy/stories-backend/view/stories"
2022
)
2123

2224
func HandleList(w http.ResponseWriter, r *http.Request) error {
25+
err := auth.CheckPermissions(r, storypermissiongroups.List())
26+
if err != nil {
27+
logrus.Error(err)
28+
return apierrors.ClientForbiddenError{
29+
Message: fmt.Sprintf("Error listing stories: %v", err),
30+
}
31+
}
32+
2333
// Get DB instance
2434
db, err := database.GetDBFrom(r)
2535
if err != nil {
@@ -53,6 +63,14 @@ func HandleRead(w http.ResponseWriter, r *http.Request) error {
5363
}
5464
}
5565

66+
err = auth.CheckPermissions(r, storypermissiongroups.Read())
67+
if err != nil {
68+
logrus.Error(err)
69+
return apierrors.ClientForbiddenError{
70+
Message: fmt.Sprintf("Error reading story: %v", err),
71+
}
72+
}
73+
5674
// Get DB instance
5775
db, err := database.GetDBFrom(r)
5876
if err != nil {
@@ -90,6 +108,14 @@ func HandleRead(w http.ResponseWriter, r *http.Request) error {
90108
}
91109

92110
func HandleCreate(w http.ResponseWriter, r *http.Request) error {
111+
err := auth.CheckPermissions(r, storypermissiongroups.Create())
112+
if err != nil {
113+
logrus.Error(err)
114+
return apierrors.ClientForbiddenError{
115+
Message: fmt.Sprintf("Error creating story: %v", err),
116+
}
117+
}
118+
93119
var params storyparams.Create
94120
if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
95121
e, ok := err.(*json.UnmarshalTypeError)
@@ -106,7 +132,7 @@ func HandleCreate(w http.ResponseWriter, r *http.Request) error {
106132
}
107133
}
108134

109-
err := params.Validate()
135+
err = params.Validate()
110136
if err != nil {
111137
logrus.Error(err)
112138
return apierrors.ClientUnprocessableEntityError{

controller/stories/update.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ import (
99
"github.com/go-chi/chi/v5"
1010
"github.com/sirupsen/logrus"
1111
"github.com/source-academy/stories-backend/controller"
12+
"github.com/source-academy/stories-backend/internal/auth"
1213
"github.com/source-academy/stories-backend/internal/database"
1314
apierrors "github.com/source-academy/stories-backend/internal/errors"
15+
storypermissiongroups "github.com/source-academy/stories-backend/internal/permissiongroups/stories"
1416
"github.com/source-academy/stories-backend/model"
1517
storyparams "github.com/source-academy/stories-backend/params/stories"
1618
storyviews "github.com/source-academy/stories-backend/view/stories"
@@ -25,6 +27,14 @@ func HandleUpdate(w http.ResponseWriter, r *http.Request) error {
2527
}
2628
}
2729

30+
err = auth.CheckPermissions(r, storypermissiongroups.Update(uint(storyID)))
31+
if err != nil {
32+
logrus.Error(err)
33+
return apierrors.ClientForbiddenError{
34+
Message: fmt.Sprintf("Error updating story: %v", err),
35+
}
36+
}
37+
2838
// Extra params won't do anything, e.g. authorID can't be changed.
2939
// TODO: Error on extra params?
3040
var params storyparams.Update

controller/users/create.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,24 @@ import (
77

88
"github.com/sirupsen/logrus"
99
"github.com/source-academy/stories-backend/controller"
10+
"github.com/source-academy/stories-backend/internal/auth"
1011
"github.com/source-academy/stories-backend/internal/database"
1112
apierrors "github.com/source-academy/stories-backend/internal/errors"
13+
userpermissiongroups "github.com/source-academy/stories-backend/internal/permissiongroups/users"
1214
"github.com/source-academy/stories-backend/model"
1315
userparams "github.com/source-academy/stories-backend/params/users"
1416
userviews "github.com/source-academy/stories-backend/view/users"
1517
)
1618

1719
func HandleCreate(w http.ResponseWriter, r *http.Request) error {
20+
err := auth.CheckPermissions(r, userpermissiongroups.Create())
21+
if err != nil {
22+
logrus.Error(err)
23+
return apierrors.ClientForbiddenError{
24+
Message: fmt.Sprintf("Error creating user: %v", err),
25+
}
26+
}
27+
1828
var params userparams.Create
1929
if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
2030
e, ok := err.(*json.UnmarshalTypeError)
@@ -31,7 +41,7 @@ func HandleCreate(w http.ResponseWriter, r *http.Request) error {
3141
}
3242
}
3343

34-
err := params.Validate()
44+
err = params.Validate()
3545
if err != nil {
3646
logrus.Error(err)
3747
return apierrors.ClientUnprocessableEntityError{
@@ -60,6 +70,14 @@ func HandleCreate(w http.ResponseWriter, r *http.Request) error {
6070
}
6171

6272
func HandleBatchCreate(w http.ResponseWriter, r *http.Request) error {
73+
err := auth.CheckPermissions(r, userpermissiongroups.Create())
74+
if err != nil {
75+
logrus.Error(err)
76+
return apierrors.ClientForbiddenError{
77+
Message: fmt.Sprintf("Error batch creating users: %v", err),
78+
}
79+
}
80+
6381
var usersparams userparams.BatchCreate
6482
if err := json.NewDecoder(r.Body).Decode(&usersparams); err != nil {
6583
e, ok := err.(*json.UnmarshalTypeError)

controller/users/delete.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,23 @@ import (
88
"github.com/go-chi/chi/v5"
99
"github.com/sirupsen/logrus"
1010
"github.com/source-academy/stories-backend/controller"
11+
"github.com/source-academy/stories-backend/internal/auth"
1112
"github.com/source-academy/stories-backend/internal/database"
1213
apierrors "github.com/source-academy/stories-backend/internal/errors"
14+
userpermissiongroups "github.com/source-academy/stories-backend/internal/permissiongroups/users"
1315
"github.com/source-academy/stories-backend/model"
1416
userviews "github.com/source-academy/stories-backend/view/users"
1517
)
1618

1719
func HandleDelete(w http.ResponseWriter, r *http.Request) error {
20+
err := auth.CheckPermissions(r, userpermissiongroups.Delete())
21+
if err != nil {
22+
logrus.Error(err)
23+
return apierrors.ClientForbiddenError{
24+
Message: fmt.Sprintf("Error deleting user: %v", err),
25+
}
26+
}
27+
1828
userIDStr := chi.URLParam(r, "userID")
1929
userID, err := strconv.Atoi(userIDStr)
2030
if err != nil {

controller/users/list.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,28 @@
11
package users
22

33
import (
4+
"fmt"
45
"net/http"
56

67
"github.com/sirupsen/logrus"
78
"github.com/source-academy/stories-backend/controller"
9+
"github.com/source-academy/stories-backend/internal/auth"
810
"github.com/source-academy/stories-backend/internal/database"
11+
apierrors "github.com/source-academy/stories-backend/internal/errors"
12+
userpermissiongroups "github.com/source-academy/stories-backend/internal/permissiongroups/users"
913
"github.com/source-academy/stories-backend/model"
1014
userviews "github.com/source-academy/stories-backend/view/users"
1115
)
1216

1317
func HandleList(w http.ResponseWriter, r *http.Request) error {
18+
err := auth.CheckPermissions(r, userpermissiongroups.List())
19+
if err != nil {
20+
logrus.Error(err)
21+
return apierrors.ClientForbiddenError{
22+
Message: fmt.Sprintf("Error listing users: %v", err),
23+
}
24+
}
25+
1426
// Get DB instance
1527
db, err := database.GetDBFrom(r)
1628
if err != nil {

controller/users/read.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/source-academy/stories-backend/internal/auth"
1212
"github.com/source-academy/stories-backend/internal/database"
1313
apierrors "github.com/source-academy/stories-backend/internal/errors"
14+
userpermissiongroups "github.com/source-academy/stories-backend/internal/permissiongroups/users"
1415
"github.com/source-academy/stories-backend/model"
1516
userviews "github.com/source-academy/stories-backend/view/users"
1617
)
@@ -24,6 +25,14 @@ func HandleRead(w http.ResponseWriter, r *http.Request) error {
2425
}
2526
}
2627

28+
err = auth.CheckPermissions(r, userpermissiongroups.Read(uint(userID)))
29+
if err != nil {
30+
logrus.Error(err)
31+
return apierrors.ClientForbiddenError{
32+
Message: fmt.Sprintf("Error reading user: %v", err),
33+
}
34+
}
35+
2736
// Get DB instance
2837
db, err := database.GetDBFrom(r)
2938
if err != nil {
@@ -47,6 +56,14 @@ func HandleReadSelf(w http.ResponseWriter, r *http.Request) error {
4756
return err
4857
}
4958

59+
err = auth.CheckPermissions(r, userpermissiongroups.Read(uint(*userID)))
60+
if err != nil {
61+
logrus.Error(err)
62+
return apierrors.ClientForbiddenError{
63+
Message: fmt.Sprintf("Error reading user: %v", err),
64+
}
65+
}
66+
5067
// Get DB instance
5168
db, err := database.GetDBFrom(r)
5269
if err != nil {

internal/auth/permission.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
package auth
22

33
import (
4+
"errors"
45
"net/http"
56

67
"github.com/source-academy/stories-backend/internal/permissions"
78
)
89

9-
func CheckPermissions(r *http.Request, requestedActionPermissions ...permissions.PermissionGroup) (bool, error) {
10+
func CheckPermissions(r *http.Request, requestedActionPermissions ...permissions.PermissionGroup) error {
1011
requiredPermissions := permissions.AllOf{
1112
Groups: requestedActionPermissions,
1213
}
13-
return requiredPermissions.IsAuthorized(r), nil
14+
isAuthorized := requiredPermissions.IsAuthorized(r)
15+
if !isAuthorized {
16+
return errors.New("You are not authorized to perform this action.")
17+
}
18+
return nil
1419
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package storypermissiongroups
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/source-academy/stories-backend/internal/auth"
7+
"github.com/source-academy/stories-backend/internal/database"
8+
"github.com/source-academy/stories-backend/model"
9+
)
10+
11+
type IsAuthorOf struct {
12+
StoryID uint
13+
}
14+
15+
func (p IsAuthorOf) IsAuthorized(r *http.Request) bool {
16+
userID, err := auth.GetUserIDFrom(r)
17+
if err != nil {
18+
return false
19+
}
20+
21+
db, err := database.GetDBFrom(r)
22+
if err != nil {
23+
return false
24+
}
25+
26+
story, err := model.GetStoryByID(db, int(p.StoryID))
27+
if err != nil {
28+
return false
29+
}
30+
31+
return story.AuthorID == uint(*userID)
32+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package storypermissiongroups
2+
3+
import (
4+
"github.com/source-academy/stories-backend/internal/permissions"
5+
userpermissions "github.com/source-academy/stories-backend/internal/permissions/users"
6+
)
7+
8+
func List() permissions.PermissionGroup {
9+
return userpermissions.
10+
GetRolePermission(userpermissions.CanReadStories)
11+
}
12+
13+
func Create() permissions.PermissionGroup {
14+
return userpermissions.
15+
GetRolePermission(userpermissions.CanCreateStories)
16+
}
17+
18+
func Read() permissions.PermissionGroup {
19+
return userpermissions.
20+
GetRolePermission(userpermissions.CanReadStories)
21+
}
22+
23+
func Update(storyID uint) permissions.PermissionGroup {
24+
return permissions.AnyOf{
25+
Groups: []permissions.PermissionGroup{
26+
userpermissions.
27+
GetRolePermission(userpermissions.CanUpdateStories),
28+
IsAuthorOf{StoryID: storyID},
29+
},
30+
}
31+
}
32+
33+
func Delete(storyID uint) permissions.PermissionGroup {
34+
return permissions.AnyOf{
35+
Groups: []permissions.PermissionGroup{
36+
userpermissions.
37+
GetRolePermission(userpermissions.CanDeleteStories),
38+
IsAuthorOf{StoryID: storyID},
39+
},
40+
}
41+
}

0 commit comments

Comments
 (0)