Repository: qor/roles Branch: master Commit: dcaf8a4646d8 Files: 9 Total size: 17.9 KB Directory structure: gitextract_2mbiqyvs/ ├── .travis.yml ├── LICENSE.txt ├── README.md ├── global.go ├── permission.go ├── permissioner.go ├── role.go ├── role_manager.go └── roles_test.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .travis.yml ================================================ language: go go: - "1.13" script: - go test -v ./... ================================================ FILE: LICENSE.txt ================================================ The MIT License (MIT) Copyright (c) The Plant https://theplant.jp Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Roles Roles is an [authorization](https://en.wikipedia.org/wiki/Authorization) library for [Golang](http://golang.org/), it also integrates nicely with [QOR Admin](http://github.com/qor/admin). [![GoDoc](https://godoc.org/github.com/qor/roles?status.svg)](https://godoc.org/github.com/qor/roles) [![Build Status](https://travis-ci.com/qor/roles.svg?branch=master)](https://travis-ci.com/qor/roles) ## Usage ### Permission Modes Permission modes are really the *roles* in [Roles](https://github.com/qor/roles). [Roles](https://github.com/qor/roles) has [5 default permission modes](https://github.com/qor/roles/blob/master/permission.go#L8-L12): - roles.Read - roles.Update - roles.Create - roles.Delete - roles.CRUD // CRUD means Read, Update, Create, Delete You can use those permission modes, or create your own by [defining permissions](#define-permission). ### Permission Behaviors and Interactions 1. All roles in the Deny mapping for a permission mode are immediately denied without reference to the Allow mapping for that permission mode. *E.g.* ```go roles.Deny(roles.Delete, roles.Anyone).Allow(roles.Delete, "admin") ``` will deny access to `admin` for the permission mode `roles.Delete`, despite the chained call to `Allow()`. *I.e.* `Allow()` has **NO** effect in this chain. 2. If there are **NO** roles in the Allow mapping for a permission mode, then `roles.Anyone` is allowed. *E.g.* ```go roles.Deny(roles.CRUD, "customer") ``` will allow access for permission mode `roles.CRUD` to **any** role that is not a `customer` because the Allow mapping is empty and the blanket allow rule is in force. 3. If even one (1) Allow mapping exists, then only roles on that list will be allowed through. *E.g.* ```go roles.Allow(roles.READ, "admin") ``` allows the `admin` role through and rejects **ALL** other roles. The following is a flow diagram for a specific permission mode, *e.g.* `roles.READ`. ``` flow st=>start: Input role denied0=>end: Denied allowed0=>end: Allowed denied1=>end: Denied allowed1=>end: Allowed op0=>operation: Exists in Deny map? op1=>operation: Allow map empty? op2=>operation: Exists in Allow map? cond0=>condition: Yes or No? cond1=>condition: Yes or No? cond2=>condition: Yes or No? st->op0->cond0 cond0(no)->op1->cond1 cond0(yes)->denied0 cond1(yes)->allowed0 cond1(no)->op2->cond2 cond2(yes)->allowed1 cond2(no)->denied1 ``` Please note that, when using [Roles](https://github.com/qor/roles) with [L10n](http://github.com/qor/l10n). The ```go // allows the admin role through and rejects ALL other roles. roles.Allow(roles.READ, "admin") ``` might be invalid because [L10n](http://github.com/qor/l10n) defined a [permission system](http://github.com/qor/l10n#editable-locales) that applys new roles to the current user. For example, There is a user with role "manager", the `EditableLocales` in the [L10n](http://github.com/qor/l10n) permission system returns true in current locale. Then this user actually has two roles "manager" and "locale_admin". because [L10n](http://github.com/qor/l10n) set `resource.Permission.Allow(roles.CRUD, "locale_admin")` to the resource. So the user could access this resource by the role "locale\_admin". So you either use `Deny` instead which means swtich "white list" to "black list" or make the `EditableLocales` always return blank array which means disabled [L10n](http://github.com/qor/l10n) permission system. ### Define Permission ```go import "github.com/qor/roles" func main() { // Allow Permission permission := roles.Allow(roles.Read, "admin") // `admin` has `Read` permission, `admin` is a role name // Deny Permission permission := roles.Deny(roles.Create, "user") // `user` has no `Create` permission // Using Chain permission := roles.Allow(roles.CRUD, "admin").Allow(roles.Read, "visitor") // `admin` has `CRUD` permissions, `visitor` only has `Read` permission permission := roles.Allow(roles.CRUD, "admin").Deny(roles.Update, "user") // `admin` has `CRUD` permissions, `user` doesn't has `Update` permission // roles `Anyone` means for anyone permission := roles.Deny(roles.Update, roles.Anyone) // no one has update permission } ``` ### Check Permission ```go import "github.com/qor/roles" func main() { permission := roles.Allow(roles.CRUD, "admin").Deny(roles.Create, "manager").Allow(roles.Read, "visitor") // check if role `admin` has the Read permission permission.HasPermission(roles.Read, "admin") // => true // check if role `admin` has the Create permission permission.HasPermission(roles.Create, "admin") // => true // check if role `user` has the Read permission permission.HasPermission(roles.Read, "user") // => true // check if role `user` has the Create permission permission.HasPermission(roles.Create, "user") // => false // check if role `visitor` has the Read permission permission.HasPermission(roles.Read, "user") // => true // check if role `visitor` has the Create permission permission.HasPermission(roles.Create, "user") // => false // Check with multiple roles // check if role `admin` or `user` has the Create permission permission.HasPermission(roles.Create, "admin", "user") // => true } ``` ### Register Roles When checking permissions, you will need to know current user's *roles* first. This could quickly get out of hand if you have defined many *roles* based on lots of conditions - so [Roles](https://github.com/qor/roles) provides some helper methods to make it easier: ```go import "github.com/qor/roles" func main() { // Register roles based on some conditions roles.Register("admin", func(req *http.Request, currentUser interface{}) bool { return req.RemoteAddr == "127.0.0.1" || (currentUser.(*User) != nil && currentUser.(*User).Role == "admin") }) roles.Register("user", func(req *http.Request, currentUser interface{}) bool { return currentUser.(*User) != nil }) roles.Register("visitor", func(req *http.Request, currentUser interface{}) bool { return currentUser.(*User) == nil }) // Get roles from a user matchedRoles := roles.MatchedRoles(httpRequest, user) // []string{"user", "admin"} // Check if role `user` or `admin` has Read permission permission.HasPermission(roles.Read, matchedRoles...) } ``` ## License Released under the [MIT License](http://opensource.org/licenses/MIT). ================================================ FILE: global.go ================================================ package roles import "net/http" // Global global role instance var Global = &Role{} // Register register role with conditions func Register(name string, fc Checker) { Global.Register(name, fc) } // Allow allows permission mode for roles func Allow(mode PermissionMode, roles ...string) *Permission { return Global.Allow(mode, roles...) } // Deny deny permission mode for roles func Deny(mode PermissionMode, roles ...string) *Permission { return Global.Deny(mode, roles...) } // Get role defination func Get(name string) (Checker, bool) { return Global.Get(name) } // Remove role definition from global role instance func Remove(name string) { Global.Remove(name) } // Reset role definitions from global role instance func Reset() { Global.Reset() } // MatchedRoles return defined roles from user func MatchedRoles(req *http.Request, user interface{}) []string { return Global.MatchedRoles(req, user) } // HasRole check if current user has role func HasRole(req *http.Request, user interface{}, roles ...string) bool { return Global.HasRole(req, user) } // NewPermission initialize a new permission for default role func NewPermission() *Permission { return Global.NewPermission() } ================================================ FILE: permission.go ================================================ package roles import ( "errors" "fmt" ) // PermissionMode permission mode type PermissionMode string const ( // Create predefined permission mode, create permission Create PermissionMode = "create" // Read predefined permission mode, read permission Read PermissionMode = "read" // Update predefined permission mode, update permission Update PermissionMode = "update" // Delete predefined permission mode, deleted permission Delete PermissionMode = "delete" // CRUD predefined permission mode, create+read+update+delete permission CRUD PermissionMode = "crud" ) // ErrPermissionDenied no permission error var ErrPermissionDenied = errors.New("permission denied") // Permission a struct contains permission definitions type Permission struct { Role *Role AllowedRoles map[PermissionMode][]string DeniedRoles map[PermissionMode][]string } func includeRoles(roles []string, values []string) bool { for _, role := range roles { if role == Anyone { return true } for _, value := range values { if value == role { return true } } } return false } // Concat concat two permissions into a new one func (permission *Permission) Concat(newPermission *Permission) *Permission { var result = Permission{ Role: Global, AllowedRoles: map[PermissionMode][]string{}, DeniedRoles: map[PermissionMode][]string{}, } var appendRoles = func(p *Permission) { if p != nil { result.Role = p.Role for mode, roles := range p.DeniedRoles { result.DeniedRoles[mode] = append(result.DeniedRoles[mode], roles...) } for mode, roles := range p.AllowedRoles { result.AllowedRoles[mode] = append(result.AllowedRoles[mode], roles...) } } } appendRoles(newPermission) appendRoles(permission) return &result } // Allow allows permission mode for roles func (permission *Permission) Allow(mode PermissionMode, roles ...string) *Permission { if mode == CRUD { return permission.Allow(Create, roles...).Allow(Update, roles...).Allow(Read, roles...).Allow(Delete, roles...) } if permission.AllowedRoles[mode] == nil { permission.AllowedRoles[mode] = []string{} } permission.AllowedRoles[mode] = append(permission.AllowedRoles[mode], roles...) return permission } // Deny deny permission mode for roles func (permission *Permission) Deny(mode PermissionMode, roles ...string) *Permission { if mode == CRUD { return permission.Deny(Create, roles...).Deny(Update, roles...).Deny(Read, roles...).Deny(Delete, roles...) } if permission.DeniedRoles[mode] == nil { permission.DeniedRoles[mode] = []string{} } permission.DeniedRoles[mode] = append(permission.DeniedRoles[mode], roles...) return permission } // HasPermission check roles has permission for mode or not func (permission Permission) HasPermission(mode PermissionMode, roles ...interface{}) bool { var roleNames []string for _, role := range roles { if r, ok := role.(string); ok { roleNames = append(roleNames, r) } else if roler, ok := role.(Roler); ok { roleNames = append(roleNames, roler.GetRoles()...) } else { fmt.Printf("invalid role %#v\n", role) return false } } if len(permission.DeniedRoles) != 0 { if DeniedRoles := permission.DeniedRoles[mode]; DeniedRoles != nil { if includeRoles(DeniedRoles, roleNames) { return false } } } // return true if haven't define allowed roles if len(permission.AllowedRoles) == 0 { return true } if AllowedRoles := permission.AllowedRoles[mode]; AllowedRoles != nil { if includeRoles(AllowedRoles, roleNames) { return true } } return false } ================================================ FILE: permissioner.go ================================================ package roles // Permissioner permissioner interface type Permissioner interface { HasPermission(mode PermissionMode, roles ...interface{}) bool } // ConcatPermissioner concat permissioner func ConcatPermissioner(ps ...Permissioner) Permissioner { var newPS []Permissioner for _, p := range ps { if p != nil { newPS = append(newPS, p) } } return permissioners(newPS) } type permissioners []Permissioner // HasPermission check has permission for permissioners or not func (ps permissioners) HasPermission(mode PermissionMode, roles ...interface{}) bool { for _, p := range ps { if p != nil && !p.HasPermission(mode, roles) { return false } } return true } ================================================ FILE: role.go ================================================ package roles import ( "fmt" "net/http" ) const ( // Anyone is a role for any one Anyone = "*" ) // Checker check current request match this role or not type Checker func(req *http.Request, user interface{}) bool // New initialize a new `Role` func New() *Role { return &Role{} } // Role is a struct contains all roles definitions type Role struct { definitions map[string]Checker } // Register register role with conditions func (role *Role) Register(name string, fc Checker) { if role.definitions == nil { role.definitions = map[string]Checker{} } definition := role.definitions[name] if definition != nil { fmt.Printf("Role `%v` already defined, overwrited it!\n", name) } role.definitions[name] = fc } // NewPermission initialize permission func (role *Role) NewPermission() *Permission { return &Permission{ Role: role, AllowedRoles: map[PermissionMode][]string{}, DeniedRoles: map[PermissionMode][]string{}, } } // Allow allows permission mode for roles func (role *Role) Allow(mode PermissionMode, roles ...string) *Permission { return role.NewPermission().Allow(mode, roles...) } // Deny deny permission mode for roles func (role *Role) Deny(mode PermissionMode, roles ...string) *Permission { return role.NewPermission().Deny(mode, roles...) } // Get role defination func (role *Role) Get(name string) (Checker, bool) { fc, ok := role.definitions[name] return fc, ok } // Remove role definition func (role *Role) Remove(name string) { delete(role.definitions, name) } // Reset role definitions func (role *Role) Reset() { role.definitions = map[string]Checker{} } // MatchedRoles return defined roles from user func (role *Role) MatchedRoles(req *http.Request, user interface{}) (roles []string) { if definitions := role.definitions; definitions != nil { for name, definition := range definitions { if definition(req, user) { roles = append(roles, name) } } } return } // HasRole check if current user has role func (role *Role) HasRole(req *http.Request, user interface{}, roles ...string) bool { if definitions := role.definitions; definitions != nil { for _, name := range roles { if definition, ok := definitions[name]; ok { if definition(req, user) { return true } } } } return false } ================================================ FILE: role_manager.go ================================================ package roles // Roler Roler interface type Roler interface { GetRoles() []string } ================================================ FILE: roles_test.go ================================================ package roles_test import ( "testing" "github.com/qor/roles" ) func TestAllow(t *testing.T) { permission := roles.Allow(roles.Read, "api") if !permission.HasPermission(roles.Read, "api") { t.Errorf("API should has permission to Read") } if permission.HasPermission(roles.Update, "api") { t.Errorf("API should has no permission to Update") } if permission.HasPermission(roles.Read, "admin") { t.Errorf("admin should has no permission to Read") } if permission.HasPermission(roles.Update, "admin") { t.Errorf("admin should has no permission to Update") } } func TestDeny(t *testing.T) { permission := roles.Deny(roles.Create, "api") if !permission.HasPermission(roles.Read, "api") { t.Errorf("API should has permission to Read") } if !permission.HasPermission(roles.Update, "api") { t.Errorf("API should has permission to Update") } if permission.HasPermission(roles.Create, "api") { t.Errorf("API should has no permission to Update") } if !permission.HasPermission(roles.Read, "admin") { t.Errorf("admin should has permission to Read") } if !permission.HasPermission(roles.Create, "admin") { t.Errorf("admin should has permission to Update") } } func TestCRUD(t *testing.T) { permission := roles.Allow(roles.CRUD, "admin") if !permission.HasPermission(roles.Read, "admin") { t.Errorf("Admin should has permission to Read") } if !permission.HasPermission(roles.Update, "admin") { t.Errorf("Admin should has permission to Update") } if permission.HasPermission(roles.Read, "api") { t.Errorf("API should has no permission to Read") } if permission.HasPermission(roles.Update, "api") { t.Errorf("API should has no permission to Update") } } func TestAll(t *testing.T) { permission := roles.Allow(roles.Update, roles.Anyone) if permission.HasPermission(roles.Read, "api") { t.Errorf("API should has no permission to Read") } if !permission.HasPermission(roles.Update, "api") { t.Errorf("API should has permission to Update") } permission2 := roles.Deny(roles.Update, roles.Anyone) if !permission2.HasPermission(roles.Read, "api") { t.Errorf("API should has permission to Read") } if permission2.HasPermission(roles.Update, "api") { t.Errorf("API should has no permission to Update") } } func TestCustomizePermission(t *testing.T) { var customized roles.PermissionMode = "customized" permission := roles.Allow(customized, "admin") if !permission.HasPermission(customized, "admin") { t.Errorf("Admin should has customized permission") } if permission.HasPermission(roles.Read, "admin") { t.Errorf("Admin should has no permission to Read") } permission2 := roles.Deny(customized, "admin") if permission2.HasPermission(customized, "admin") { t.Errorf("Admin should has customized permission") } if !permission2.HasPermission(roles.Read, "admin") { t.Errorf("Admin should has no permission to Read") } }