Skip to content

Commit fa0b0d7

Browse files
committed
manage user permissions
1 parent 4a82064 commit fa0b0d7

File tree

19 files changed

+469
-44
lines changed

19 files changed

+469
-44
lines changed

backend/pkg/constants.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ type DatabaseRepositoryType string
1010
type InstallationVerificationStatus string
1111
type InstallationQuotaStatus string
1212
type UserRole string
13+
type Permission string
1314

1415
const (
1516
ResourceListPageSize int = 20
@@ -54,4 +55,7 @@ const (
5455

5556
UserRoleUser UserRole = "user"
5657
UserRoleAdmin UserRole = "admin"
58+
59+
PermissionManageSources Permission = "manage_sources"
60+
PermissionRead Permission = "read"
5761
)

backend/pkg/database/gorm_common.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,104 @@ func (gr *GormRepository) GetUsers(ctx context.Context) ([]models.User, error) {
192192
return sanitizedUsers, result.Error
193193
}
194194

195+
func (gr *GormRepository) GetUser(ctx context.Context, userID uuid.UUID) (*models.FrontendUser, error) {
196+
var dbUser models.User
197+
var user models.FrontendUser
198+
result := gr.GormClient.WithContext(ctx).First(&dbUser, userID)
199+
if result.Error != nil {
200+
return nil, result.Error
201+
}
202+
203+
user.ID = dbUser.ID
204+
user.FullName = dbUser.FullName
205+
user.Email = dbUser.Email
206+
user.Username = dbUser.Username
207+
user.Role = dbUser.Role
208+
209+
// Populate ACLs for the user
210+
var acls []models.UserPermission
211+
if err := gr.GormClient.WithContext(ctx).
212+
Where("user_id = ?", user.ID).
213+
Find(&acls).Error; err != nil {
214+
return nil, err
215+
}
216+
user.Permissions = make(map[string]map[string]bool)
217+
218+
for _, acl := range acls {
219+
if _, exists := user.Permissions[acl.TargetUserID.String()]; !exists {
220+
user.Permissions[acl.TargetUserID.String()] = make(map[string]bool)
221+
}
222+
user.Permissions[acl.TargetUserID.String()][string(acl.Permission)] = true
223+
}
224+
225+
return &user, nil
226+
}
227+
228+
func (gr *GormRepository) UpdateUserAndPermissions(ctx context.Context, user models.FrontendUser) error {
229+
// Lookup user from the db
230+
var dbUser models.User
231+
result := gr.GormClient.WithContext(ctx).First(&dbUser, user.ID)
232+
if result.Error != nil {
233+
return result.Error
234+
}
235+
// Update fields on User
236+
result = gr.GormClient.WithContext(ctx).Model(dbUser).Updates(map[string]interface{}{"full_name": user.FullName, "username": user.Username, "email": user.Email, "role": user.Role})
237+
if result.Error != nil {
238+
return result.Error
239+
}
240+
// Update User Permissions
241+
var existingPermissions []models.UserPermission
242+
if err := gr.GormClient.WithContext(ctx).
243+
Where("user_id = ?", user.ID).
244+
Find(&existingPermissions).Error; err != nil {
245+
return err
246+
}
247+
for targetUserId, permissions := range user.Permissions {
248+
for permission, value := range permissions {
249+
if !value {
250+
continue
251+
}
252+
// Check if the permission already exists
253+
exists := false
254+
for _, existingPermission := range existingPermissions {
255+
if existingPermission.TargetUserID.String() == targetUserId && string(existingPermission.Permission) == permission {
256+
exists = true
257+
break
258+
}
259+
}
260+
if !exists {
261+
// Add new permission
262+
p := models.UserPermission{
263+
UserID: user.ID,
264+
TargetUserID: uuid.Must(uuid.Parse(targetUserId)),
265+
Permission: pkg.Permission(permission),
266+
}
267+
err := gr.GormClient.WithContext(ctx).Create(&p).Error
268+
if err != nil {
269+
return err
270+
}
271+
}
272+
}
273+
}
274+
275+
// Remove permissions that are no longer in user.Permissions
276+
for _, existingPermission := range existingPermissions {
277+
targetUserId := existingPermission.TargetUserID.String()
278+
permission := string(existingPermission.Permission)
279+
280+
// Check if the permission still exists in the new user.Permissions
281+
if _, exists := user.Permissions[targetUserId]; !exists || !user.Permissions[targetUserId][permission] {
282+
// Permission no longer exists, so delete it
283+
err := gr.GormClient.WithContext(ctx).Delete(&existingPermission).Error
284+
if err != nil {
285+
return err
286+
}
287+
}
288+
}
289+
290+
return nil
291+
}
292+
195293
//</editor-fold>
196294

197295
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

backend/pkg/database/gorm_repository_migrations.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
_20240114103850 "github.com/fastenhealth/fasten-onprem/backend/pkg/database/migrations/20240114103850"
1212
_20240208112210 "github.com/fastenhealth/fasten-onprem/backend/pkg/database/migrations/20240208112210"
1313
_20240813222836 "github.com/fastenhealth/fasten-onprem/backend/pkg/database/migrations/20240813222836"
14+
_20240827214347 "github.com/fastenhealth/fasten-onprem/backend/pkg/database/migrations/20240827214347"
1415
"github.com/fastenhealth/fasten-onprem/backend/pkg/models"
1516
databaseModel "github.com/fastenhealth/fasten-onprem/backend/pkg/models/database"
1617
sourceCatalog "github.com/fastenhealth/fasten-sources/catalog"
@@ -225,6 +226,20 @@ func (gr *GormRepository) Migrate() error {
225226
return nil
226227
},
227228
},
229+
{
230+
ID: "20240827214347", // add UserPermission model
231+
Migrate: func(tx *gorm.DB) error {
232+
233+
err := tx.AutoMigrate(
234+
&_20240827214347.UserPermission{},
235+
)
236+
if err != nil {
237+
return err
238+
}
239+
240+
return nil
241+
},
242+
},
228243
})
229244

230245
// run when database is empty

backend/pkg/database/interface.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ type DatabaseRepository interface {
2020
GetCurrentUser(ctx context.Context) (*models.User, error)
2121
DeleteCurrentUser(ctx context.Context) error
2222
GetUsers(ctx context.Context) ([]models.User, error)
23+
GetUser(ctx context.Context, userId uuid.UUID) (*models.FrontendUser, error)
24+
UpdateUserAndPermissions(ctx context.Context, user models.FrontendUser) error
2325

2426
GetSummary(ctx context.Context) (*models.Summary, error)
2527

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package _20240827214347
2+
3+
import (
4+
"github.com/fastenhealth/fasten-onprem/backend/pkg/models"
5+
"github.com/google/uuid"
6+
)
7+
8+
type Permission string
9+
10+
const (
11+
PermissionManageSources Permission = "manage_sources"
12+
PermissionRead Permission = "read"
13+
)
14+
15+
type UserPermission struct {
16+
models.ModelBase
17+
UserID uuid.UUID `json:"user_id" gorm:"type:uuid"`
18+
TargetUserID uuid.UUID `json:"target_user_id" gorm:"type:uuid"`
19+
Permission Permission `json:"permission"`
20+
}

backend/pkg/database/mock/mock_database.go

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/pkg/models/user.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,19 @@ import (
1111

1212
type User struct {
1313
ModelBase
14-
FullName string `json:"full_name"`
15-
Username string `json:"username" gorm:"unique"`
16-
Password string `json:"password"`
14+
FullName string `json:"full_name"`
15+
Username string `json:"username" gorm:"unique"`
16+
Password string `json:"password"`
17+
Picture string `json:"picture"`
18+
Email string `json:"email"`
19+
Role pkg.UserRole `json:"role"`
20+
}
1721

18-
//additional optional metadata that Fasten stores with users
19-
Picture string `json:"picture"`
20-
Email string `json:"email"`
21-
Role pkg.UserRole `json:"role"`
22+
// FrontendUser is User with the addition of Permissions arranged
23+
// as we want for sending to and from the frontend
24+
type FrontendUser struct {
25+
User
26+
Permissions map[string]map[string]bool `json:"permissions"`
2227
}
2328

2429
func (user *User) HashPassword(password string) error {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package models
2+
3+
import (
4+
"github.com/fastenhealth/fasten-onprem/backend/pkg"
5+
"github.com/google/uuid"
6+
)
7+
8+
type UserPermission struct {
9+
ModelBase
10+
UserID uuid.UUID `json:"user_id" gorm:"type:uuid"`
11+
TargetUserID uuid.UUID `json:"target_user_id" gorm:"type:uuid"`
12+
Permission pkg.Permission `json:"permission"`
13+
}

backend/pkg/web/handler/users.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/fastenhealth/fasten-onprem/backend/pkg/database"
99
"github.com/fastenhealth/fasten-onprem/backend/pkg/models"
1010
"github.com/gin-gonic/gin"
11+
"github.com/google/uuid"
1112
"gorm.io/gorm"
1213
)
1314

@@ -54,3 +55,43 @@ func CreateUser(c *gin.Context) {
5455

5556
c.JSON(http.StatusOK, gin.H{"success": true, "data": newUser})
5657
}
58+
59+
func GetUser(c *gin.Context) {
60+
if !IsAdmin(c) {
61+
c.JSON(http.StatusUnauthorized, gin.H{"success": false, "error": "Unauthorized"})
62+
return
63+
}
64+
65+
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
66+
67+
user, err := databaseRepo.GetUser(c, uuid.MustParse(c.Param("userId")))
68+
if err != nil {
69+
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
70+
return
71+
}
72+
73+
c.JSON(200, gin.H{"success": true, "data": user})
74+
}
75+
76+
func UpdateUser(c *gin.Context) {
77+
if !IsAdmin(c) {
78+
c.JSON(http.StatusUnauthorized, gin.H{"success": false, "error": "Unauthorized"})
79+
return
80+
}
81+
82+
databaseRepo := c.MustGet(pkg.ContextKeyTypeDatabase).(database.DatabaseRepository)
83+
84+
var user models.FrontendUser
85+
if err := c.ShouldBindJSON(&user); err != nil {
86+
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": err.Error()})
87+
return
88+
}
89+
90+
err := databaseRepo.UpdateUserAndPermissions(c, user)
91+
if err != nil {
92+
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
93+
return
94+
}
95+
96+
c.JSON(200, gin.H{"success": true, "data": user})
97+
}

backend/pkg/web/server.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ func (ae *AppEngine) Setup() (*gin.RouterGroup, *gin.Engine) {
128128

129129
secure.GET("/users", handler.GetUsers)
130130
secure.POST("/users", handler.CreateUser)
131+
secure.GET("/users/:userId", handler.GetUser)
132+
secure.POST("/users/:userId", handler.UpdateUser)
131133

132134
//server-side-events handler (only supported on mac/linux)
133135
// TODO: causes deadlock on Windows

0 commit comments

Comments
 (0)