Skip to content

Commit

Permalink
add multiple roles support
Browse files Browse the repository at this point in the history
  • Loading branch information
harranali committed May 19, 2021
1 parent 31b9777 commit a43436d
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 46 deletions.
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@ Role Based Access Control (RBAC) Go package with database persistence
# Features
- Create Roles
- Create Permissions
- Assign Permissions To Roles
- Assign Roles To Users
- Assign Permissions to Roles
- Assign Multiple Roles to Users
- Check User's Roles
- Check User's Permissions
- Check Role's Permissions
- Revoke User's Roles
- Revoke User's Permissions
- Revoke Role's permissions
- List Roles
- List Permissions
- List User's Roles
- List All Roles
- List All Permissions
- Delete Roles
- Delete Permissions

Expand Down Expand Up @@ -132,7 +133,7 @@ err := auth.AssignPermissions("role-1", []string{


### func (a *Authority) AssignRole(userID uint, roleName string) error
AssignRole assigns a given role to a user. the first parameter is the user id, the second parameter is the role name. if the role name doesn't have a matching record in the data base an error is returned. if the user have already a role assigned to him an error is returned.
AssignRole assigns a given role to a user, you can assign multiple roles to a user, the first parameter is the user id, the second parameter is the role name. if the role name doesn't have a matching record in the database an error is returned.
```go
// assign a role to user (user id)
err = auth.AssignRole(1, "role-a")
Expand All @@ -147,7 +148,7 @@ ok, err := auth.CheckRole(1, "role-a")
```

### func (a *Authority) CheckPermission(userID uint, permName string) (bool, error)
CheckPermission checks if a permission is assigned to a user. it accepts the user id as the first parameter. the permission as the second parameter. it returns an error if the user donesn't have a rols assigned. it returns an error if the user's role doesn't have the permission assigned. it returns an error if the permission is not present in the database
CheckPermission checks if a permission is assigned to the role that's assigned to the user. it accepts the user id as the first parameter. the permission as the second parameter. it returns an error if the permission is not present in the database
```go
// check if a user have a given permission
ok, err := auth.CheckPermission(1, "permission-d")
Expand Down Expand Up @@ -186,6 +187,12 @@ GetRoles returns all stored roles
roles, err := auth.GetRoles()
```

### (a *Authority) GetUserRoles(userID uint) ([]string, error)
GetUserRoles returns user assigned roles
```go
roles, err := auth.GetUserRoles(1)
```

### func (a *Authority) GetPermissions() ([]string, error)
GetPermissions retuns all stored permissions
```go
Expand Down
77 changes: 49 additions & 28 deletions authority.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ func (a *Authority) AssignPermissions(roleName string, permNames []string) error
// if the role name doesn't have a matching record in the data base an error is returned
// if the user have already a role assigned to him an error is returned
func (a *Authority) AssignRole(userID uint, roleName string) error {
// find the role
// make sure the role exist
var role Role
res := a.DB.Where("name = ?", roleName).First(&role)
if res.Error != nil {
Expand All @@ -135,21 +135,21 @@ func (a *Authority) AssignRole(userID uint, roleName string) error {
}
}

// check if the role is already assigned
var userRole UserRole
res = a.DB.Where("user_id = ?", userID).Where("role_id = ?", role.ID).First(&userRole)
if res.Error != nil {
if errors.Is(res.Error, gorm.ErrRecordNotFound) {
// create
cRes := a.DB.Create(&UserRole{UserID: userID, RoleID: role.ID})
if cRes.Error != nil {
return errors.New("error assigning user, " + cRes.Error.Error())
}
return nil
}
if res.Error == nil {
//found a record, this role is already assigned to the same user
return errors.New("this role is already assinged to the user")
}

// assign the role
cRes := a.DB.Create(&UserRole{UserID: userID, RoleID: role.ID})
if cRes.Error != nil {
return errors.New("error assigning user, " + cRes.Error.Error())
}

return errors.New("user have a role assgined")
return nil
}

// CheckRole checks if a role is assigned to a user
Expand Down Expand Up @@ -180,24 +180,27 @@ func (a *Authority) CheckRole(userID uint, roleName string) (bool, error) {
return true, nil
}

// CheckPermission checks if a permission is assigned to a user
// CheckPermission checks if a permission is assigned to the role that's assigned to the user.
// it accepts the user id as the first parameter
// the permission as the second parameter
// it returns an error if the user donesn't have a rols assigned
// it returns an error if the user's role doesn't have the permission assigned
// it returns an error if the permission is not present in the database
func (a *Authority) CheckPermission(userID uint, permName string) (bool, error) {
// the user role
var userRole UserRole
res := a.DB.Where("user_id = ?", userID).First(&userRole)
var userRoles []UserRole
res := a.DB.Where("user_id = ?", userID).Find(&userRoles)
if res.Error != nil {
if errors.Is(res.Error, gorm.ErrRecordNotFound) {
return false, errors.New("user doesn't have a role assigned")
return false, nil
}
}

//prepare an array of role ids
var roleIDs []uint
for _, r := range userRoles {
roleIDs = append(roleIDs, r.RoleID)
}

// fin the permission
// find the permission
var perm Permission
res = a.DB.Where("name = ?", permName).First(&perm)
if res.Error != nil {
Expand All @@ -209,12 +212,9 @@ func (a *Authority) CheckPermission(userID uint, permName string) (bool, error)

// find the role permission
var rolePermission RolePermission
res = a.DB.Where("role_id = ?", userRole.RoleID).Where("permission_id = ?", perm.ID).First(&rolePermission)
res = a.DB.Where("role_id IN (?)", roleIDs).Where("permission_id = ?", perm.ID).First(&rolePermission)
if res.Error != nil {
if errors.Is(res.Error, gorm.ErrRecordNotFound) {
return false, nil
}

return false, nil
}

return true, nil
Expand Down Expand Up @@ -284,9 +284,10 @@ func (a *Authority) RevokeRole(userID uint, roleName string) error {
// RevokePermission revokes a permission from the user's assigned role
// it returns an error in case of any
func (a *Authority) RevokePermission(userID uint, permName string) error {
// find the user role
var userRole UserRole
res := a.DB.Where("user_id = ?", userID).First(&userRole)
// revoke the permission from all roles of the user
// find the user roles
var userRoles []UserRole
res := a.DB.Where("user_id = ?", userID).Find(&userRoles)
if res.Error != nil {
if errors.Is(res.Error, gorm.ErrRecordNotFound) {
return errors.New("user doesn't have a role assgined")
Expand All @@ -304,8 +305,10 @@ func (a *Authority) RevokePermission(userID uint, permName string) error {

}

// revoke the permission
a.DB.Where("role_id = ?", userRole.RoleID).Where("permission_id = ?", perm.ID).Delete(RolePermission{})
for _, r := range userRoles {
// revoke the permission
a.DB.Where("role_id = ?", r.RoleID).Where("permission_id = ?", perm.ID).Delete(RolePermission{})
}

return nil
}
Expand Down Expand Up @@ -352,6 +355,24 @@ func (a *Authority) GetRoles() ([]string, error) {
return result, nil
}

// GetUserRoles returns all user assigned roles
func (a *Authority) GetUserRoles(userID uint) ([]string, error) {
var result []string
var userRoles []UserRole
a.DB.Where("user_id = ?", userID).Find(&userRoles)

for _, r := range userRoles {
var role Role
// for every user role get the role name
res := a.DB.Where("id = ?", r.RoleID).Find(&role)
if res.Error == nil {
result = append(result, role.Name)
}
}

return result, nil
}

// GetPermissions returns all stored permissions
func (a *Authority) GetPermissions() ([]string, error) {
var result []string
Expand Down
82 changes: 70 additions & 12 deletions authority_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,12 +179,19 @@ func TestAssignRole(t *testing.T) {
t.Error("unexpected error while assigning role.", err)
}

// double assign user
// double assign the role
err = auth.AssignRole(1, "role-a")
if err == nil {
t.Error("expecting an error when assign a role to user more than one time")
}

// assign a second role
auth.CreateRole("role-b")
err = auth.AssignRole(1, "role-b")
if err != nil {
t.Error("un expected error when assigning a second role. ", err)
}

// assign missing role
err = auth.AssignRole(1, "role-aa")
if err == nil {
Expand All @@ -199,9 +206,10 @@ func TestAssignRole(t *testing.T) {
t.Error("failed assigning roles to permission")
}

// clean up
db.Where("role_id = ?", r.ID).Delete(authority.UserRole{})
//clean up
db.Where("user_id = ?", 1).Delete(authority.UserRole{})
db.Where("name = ?", "role-a").Delete(authority.Role{})
db.Where("name = ?", "role-b").Delete(authority.Role{})
}

func TestCheckRole(t *testing.T) {
Expand Down Expand Up @@ -277,24 +285,34 @@ func TestCheckPermission(t *testing.T) {
t.Error("unexpected error while assigning permissions.", err)
}

// test when no role is a ssigned
ok, err := auth.CheckPermission(1, "permission-a")
if err != nil {
t.Error("expecting error to be nil when no role is assigned")
}
if ok {
t.Error("expecting false to be returned when no role is assigned")
}

// assign the role
err = auth.AssignRole(1, "role-a")
if err != nil {
t.Error("unexpected error while assigning role.", err)
}

// test
ok, err := auth.CheckPermission(1, "permission-a")
// test a permission of an assigned role
ok, err = auth.CheckPermission(1, "permission-a")
if err != nil {
t.Error("unexpected error while checking permission of a user.", err)
}
if !ok {
t.Error("failed to assert checking permission of a user")
t.Error("expecting true to be returned")
}
// test assigning to missing user
_, err = auth.CheckPermission(11, "permission-a")
if err == nil {
t.Error("expecting an error when checking permission of missing user")

// check when user does not have roles
ok, _ = auth.CheckPermission(11, "permission-a")
if ok {
t.Error("expecting an false when checking permission of not assigned user")
}

// test assigning missing permission
Expand All @@ -303,14 +321,14 @@ func TestCheckPermission(t *testing.T) {
t.Error("expecting an error when checking a missing permission")
}

// test for checking not assigned permission
// check for an exist but not assigned permission
auth.CreatePermission("permission-c")
ok, _ = auth.CheckPermission(1, "permission-c")
if ok {
t.Error("expecting false when checking for not assigned permissions")
}

//clean up
// clean up
var r authority.Role
db.Where("name = ?", "role-a").First(&r)
db.Where("role_id = ?", r.ID).Delete(authority.UserRole{})
Expand Down Expand Up @@ -681,3 +699,43 @@ func TestDeletePermission(t *testing.T) {
// clean up
auth.DeleteRole("role-a")
}

func TestGetUserRoles(t *testing.T) {
auth := authority.New(authority.Options{
TablesPrefix: "authority_",
DB: db,
})

// first create a role
auth.CreateRole("role-a")
auth.CreateRole("role-b")
auth.AssignRole(1, "role-a")
auth.AssignRole(1, "role-b")

roles, _ := auth.GetUserRoles(1)
if len(roles) != 2 {
t.Error("expeting two roles to be returned")
}

if !sliceHasString(roles, "role-a") {
t.Error("missing role in returned roles")
}

if !sliceHasString(roles, "role-b") {
t.Error("missing role in returned roles")
}

db.Where("user_id = ?", 1).Delete(authority.UserRole{})
db.Where("name = ?", "role-a").Delete(authority.Role{})
db.Where("name = ?", "role-b").Delete(authority.Role{})
}

func sliceHasString(s []string, val string) bool {
for _, v := range s {
if v == val {
return true
}
}

return false
}

0 comments on commit a43436d

Please sign in to comment.