[
  {
    "path": ".travis.yml",
    "content": "language: go\ngo:\n  - \"1.13\"\n\nscript:\n  - go test -v ./...\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "The MIT License (MIT)\nCopyright (c)  The Plant https://theplant.jp\n\nPermission 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:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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.\n"
  },
  {
    "path": "README.md",
    "content": "# Roles\n\nRoles 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).\n\n[![GoDoc](https://godoc.org/github.com/qor/roles?status.svg)](https://godoc.org/github.com/qor/roles)\n[![Build Status](https://travis-ci.com/qor/roles.svg?branch=master)](https://travis-ci.com/qor/roles)\n\n## Usage\n\n### Permission Modes\n\nPermission 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):\n\n- roles.Read\n- roles.Update\n- roles.Create\n- roles.Delete\n- roles.CRUD   // CRUD means Read, Update, Create, Delete\n\nYou can use those permission modes, or create your own by [defining permissions](#define-permission).\n\n### Permission Behaviors and Interactions\n\n1. All roles in the Deny mapping for a permission mode are immediately denied without reference to the Allow mapping for that permission mode.\n\n    *E.g.*\n    ```go\n    roles.Deny(roles.Delete, roles.Anyone).Allow(roles.Delete, \"admin\")\n    ```\n     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.\n\n2. If there are **NO** roles in the Allow mapping for a permission mode, then `roles.Anyone` is allowed.\n\n    *E.g.*\n    ```go\n    roles.Deny(roles.CRUD, \"customer\")\n    ```\n    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.\n\n3. If even one (1) Allow mapping exists, then only roles on that list will be allowed through.\n\n    *E.g.*\n    ```go\n    roles.Allow(roles.READ, \"admin\")\n    ```\n    allows the `admin` role through and rejects **ALL** other roles.\n\nThe following is a flow diagram for a specific permission mode, *e.g.* `roles.READ`.\n\n``` flow\nst=>start: Input role\ndenied0=>end: Denied\nallowed0=>end: Allowed\ndenied1=>end: Denied\nallowed1=>end: Allowed\nop0=>operation: Exists in Deny map?\nop1=>operation: Allow map empty?\nop2=>operation: Exists in Allow map?\ncond0=>condition: Yes or No?\ncond1=>condition: Yes or No?\ncond2=>condition: Yes or No?\n\nst->op0->cond0\ncond0(no)->op1->cond1\ncond0(yes)->denied0\ncond1(yes)->allowed0\ncond1(no)->op2->cond2\ncond2(yes)->allowed1\ncond2(no)->denied1\n```\n\nPlease note that, when using [Roles](https://github.com/qor/roles) with [L10n](http://github.com/qor/l10n). The\n\n```go\n// allows the admin role through and rejects ALL other roles.\nroles.Allow(roles.READ, \"admin\")\n```\n\nmight 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\".\n\nSo 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.\n\n### Define Permission\n\n```go\nimport \"github.com/qor/roles\"\n\nfunc main() {\n  // Allow Permission\n  permission := roles.Allow(roles.Read, \"admin\") // `admin` has `Read` permission, `admin` is a role name\n\n  // Deny Permission\n  permission := roles.Deny(roles.Create, \"user\") // `user` has no `Create` permission\n\n  // Using Chain\n  permission := roles.Allow(roles.CRUD, \"admin\").Allow(roles.Read, \"visitor\") // `admin` has `CRUD` permissions, `visitor` only has `Read` permission\n  permission := roles.Allow(roles.CRUD, \"admin\").Deny(roles.Update, \"user\") // `admin` has `CRUD` permissions, `user` doesn't has `Update` permission\n\n  // roles `Anyone` means for anyone\n  permission := roles.Deny(roles.Update, roles.Anyone) // no one has update permission\n}\n```\n\n### Check Permission\n\n```go\nimport \"github.com/qor/roles\"\n\nfunc main() {\n  permission := roles.Allow(roles.CRUD, \"admin\").Deny(roles.Create, \"manager\").Allow(roles.Read, \"visitor\")\n\n  // check if role `admin` has the Read permission\n  permission.HasPermission(roles.Read, \"admin\")     // => true\n\n  // check if role `admin` has the Create permission\n  permission.HasPermission(roles.Create, \"admin\")     // => true\n\n  // check if role `user` has the Read permission\n  permission.HasPermission(roles.Read, \"user\")     // => true\n\n  // check if role `user` has the Create permission\n  permission.HasPermission(roles.Create, \"user\")     // => false\n\n  // check if role `visitor` has the Read permission\n  permission.HasPermission(roles.Read, \"user\")     // => true\n\n  // check if role `visitor` has the Create permission\n  permission.HasPermission(roles.Create, \"user\")     // => false\n\n  // Check with multiple roles\n  // check if role `admin` or `user` has the Create permission\n  permission.HasPermission(roles.Create, \"admin\", \"user\")     // => true\n}\n```\n\n### Register Roles\n\nWhen 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:\n\n```go\nimport \"github.com/qor/roles\"\n\nfunc main() {\n  // Register roles based on some conditions\n  roles.Register(\"admin\", func(req *http.Request, currentUser interface{}) bool {\n      return req.RemoteAddr == \"127.0.0.1\" || (currentUser.(*User) != nil && currentUser.(*User).Role == \"admin\")\n  })\n\n  roles.Register(\"user\", func(req *http.Request, currentUser interface{}) bool {\n    return currentUser.(*User) != nil\n  })\n\n  roles.Register(\"visitor\", func(req *http.Request, currentUser interface{}) bool {\n    return currentUser.(*User) == nil\n  })\n\n  // Get roles from a user\n  matchedRoles := roles.MatchedRoles(httpRequest, user) // []string{\"user\", \"admin\"}\n\n  // Check if role `user` or `admin` has Read permission\n  permission.HasPermission(roles.Read, matchedRoles...)\n}\n```\n\n## License\n\nReleased under the [MIT License](http://opensource.org/licenses/MIT).\n"
  },
  {
    "path": "global.go",
    "content": "package roles\n\nimport \"net/http\"\n\n// Global global role instance\nvar Global = &Role{}\n\n// Register register role with conditions\nfunc Register(name string, fc Checker) {\n\tGlobal.Register(name, fc)\n}\n\n// Allow allows permission mode for roles\nfunc Allow(mode PermissionMode, roles ...string) *Permission {\n\treturn Global.Allow(mode, roles...)\n}\n\n// Deny deny permission mode for roles\nfunc Deny(mode PermissionMode, roles ...string) *Permission {\n\treturn Global.Deny(mode, roles...)\n}\n\n// Get role defination\nfunc Get(name string) (Checker, bool) {\n\treturn Global.Get(name)\n}\n\n// Remove role definition from global role instance\nfunc Remove(name string) {\n\tGlobal.Remove(name)\n}\n\n// Reset role definitions from global role instance\nfunc Reset() {\n\tGlobal.Reset()\n}\n\n// MatchedRoles return defined roles from user\nfunc MatchedRoles(req *http.Request, user interface{}) []string {\n\treturn Global.MatchedRoles(req, user)\n}\n\n// HasRole check if current user has role\nfunc HasRole(req *http.Request, user interface{}, roles ...string) bool {\n\treturn Global.HasRole(req, user)\n}\n\n// NewPermission initialize a new permission for default role\nfunc NewPermission() *Permission {\n\treturn Global.NewPermission()\n}\n"
  },
  {
    "path": "permission.go",
    "content": "package roles\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n)\n\n// PermissionMode permission mode\ntype PermissionMode string\n\nconst (\n\t// Create predefined permission mode, create permission\n\tCreate PermissionMode = \"create\"\n\t// Read predefined permission mode, read permission\n\tRead PermissionMode = \"read\"\n\t// Update predefined permission mode, update permission\n\tUpdate PermissionMode = \"update\"\n\t// Delete predefined permission mode, deleted permission\n\tDelete PermissionMode = \"delete\"\n\t// CRUD predefined permission mode, create+read+update+delete permission\n\tCRUD PermissionMode = \"crud\"\n)\n\n// ErrPermissionDenied no permission error\nvar ErrPermissionDenied = errors.New(\"permission denied\")\n\n// Permission a struct contains permission definitions\ntype Permission struct {\n\tRole         *Role\n\tAllowedRoles map[PermissionMode][]string\n\tDeniedRoles  map[PermissionMode][]string\n}\n\nfunc includeRoles(roles []string, values []string) bool {\n\tfor _, role := range roles {\n\t\tif role == Anyone {\n\t\t\treturn true\n\t\t}\n\n\t\tfor _, value := range values {\n\t\t\tif value == role {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\n// Concat concat two permissions into a new one\nfunc (permission *Permission) Concat(newPermission *Permission) *Permission {\n\tvar result = Permission{\n\t\tRole:         Global,\n\t\tAllowedRoles: map[PermissionMode][]string{},\n\t\tDeniedRoles:  map[PermissionMode][]string{},\n\t}\n\n\tvar appendRoles = func(p *Permission) {\n\t\tif p != nil {\n\t\t\tresult.Role = p.Role\n\n\t\t\tfor mode, roles := range p.DeniedRoles {\n\t\t\t\tresult.DeniedRoles[mode] = append(result.DeniedRoles[mode], roles...)\n\t\t\t}\n\n\t\t\tfor mode, roles := range p.AllowedRoles {\n\t\t\t\tresult.AllowedRoles[mode] = append(result.AllowedRoles[mode], roles...)\n\t\t\t}\n\t\t}\n\t}\n\n\tappendRoles(newPermission)\n\tappendRoles(permission)\n\treturn &result\n}\n\n// Allow allows permission mode for roles\nfunc (permission *Permission) Allow(mode PermissionMode, roles ...string) *Permission {\n\tif mode == CRUD {\n\t\treturn permission.Allow(Create, roles...).Allow(Update, roles...).Allow(Read, roles...).Allow(Delete, roles...)\n\t}\n\n\tif permission.AllowedRoles[mode] == nil {\n\t\tpermission.AllowedRoles[mode] = []string{}\n\t}\n\tpermission.AllowedRoles[mode] = append(permission.AllowedRoles[mode], roles...)\n\treturn permission\n}\n\n// Deny deny permission mode for roles\nfunc (permission *Permission) Deny(mode PermissionMode, roles ...string) *Permission {\n\tif mode == CRUD {\n\t\treturn permission.Deny(Create, roles...).Deny(Update, roles...).Deny(Read, roles...).Deny(Delete, roles...)\n\t}\n\n\tif permission.DeniedRoles[mode] == nil {\n\t\tpermission.DeniedRoles[mode] = []string{}\n\t}\n\tpermission.DeniedRoles[mode] = append(permission.DeniedRoles[mode], roles...)\n\treturn permission\n}\n\n// HasPermission check roles has permission for mode or not\nfunc (permission Permission) HasPermission(mode PermissionMode, roles ...interface{}) bool {\n\tvar roleNames []string\n\tfor _, role := range roles {\n\t\tif r, ok := role.(string); ok {\n\t\t\troleNames = append(roleNames, r)\n\t\t} else if roler, ok := role.(Roler); ok {\n\t\t\troleNames = append(roleNames, roler.GetRoles()...)\n\t\t} else {\n\t\t\tfmt.Printf(\"invalid role %#v\\n\", role)\n\t\t\treturn false\n\t\t}\n\t}\n\n\tif len(permission.DeniedRoles) != 0 {\n\t\tif DeniedRoles := permission.DeniedRoles[mode]; DeniedRoles != nil {\n\t\t\tif includeRoles(DeniedRoles, roleNames) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\t// return true if haven't define allowed roles\n\tif len(permission.AllowedRoles) == 0 {\n\t\treturn true\n\t}\n\n\tif AllowedRoles := permission.AllowedRoles[mode]; AllowedRoles != nil {\n\t\tif includeRoles(AllowedRoles, roleNames) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "permissioner.go",
    "content": "package roles\n\n// Permissioner permissioner interface\ntype Permissioner interface {\n\tHasPermission(mode PermissionMode, roles ...interface{}) bool\n}\n\n// ConcatPermissioner concat permissioner\nfunc ConcatPermissioner(ps ...Permissioner) Permissioner {\n\tvar newPS []Permissioner\n\tfor _, p := range ps {\n\t\tif p != nil {\n\t\t\tnewPS = append(newPS, p)\n\t\t}\n\t}\n\treturn permissioners(newPS)\n}\n\ntype permissioners []Permissioner\n\n// HasPermission check has permission for permissioners or not\nfunc (ps permissioners) HasPermission(mode PermissionMode, roles ...interface{}) bool {\n\tfor _, p := range ps {\n\t\tif p != nil && !p.HasPermission(mode, roles) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n"
  },
  {
    "path": "role.go",
    "content": "package roles\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n)\n\nconst (\n\t// Anyone is a role for any one\n\tAnyone = \"*\"\n)\n\n// Checker check current request match this role or not\ntype Checker func(req *http.Request, user interface{}) bool\n\n// New initialize a new `Role`\nfunc New() *Role {\n\treturn &Role{}\n}\n\n// Role is a struct contains all roles definitions\ntype Role struct {\n\tdefinitions map[string]Checker\n}\n\n// Register register role with conditions\nfunc (role *Role) Register(name string, fc Checker) {\n\tif role.definitions == nil {\n\t\trole.definitions = map[string]Checker{}\n\t}\n\n\tdefinition := role.definitions[name]\n\tif definition != nil {\n\t\tfmt.Printf(\"Role `%v` already defined, overwrited it!\\n\", name)\n\t}\n\trole.definitions[name] = fc\n}\n\n// NewPermission initialize permission\nfunc (role *Role) NewPermission() *Permission {\n\treturn &Permission{\n\t\tRole:         role,\n\t\tAllowedRoles: map[PermissionMode][]string{},\n\t\tDeniedRoles:  map[PermissionMode][]string{},\n\t}\n}\n\n// Allow allows permission mode for roles\nfunc (role *Role) Allow(mode PermissionMode, roles ...string) *Permission {\n\treturn role.NewPermission().Allow(mode, roles...)\n}\n\n// Deny deny permission mode for roles\nfunc (role *Role) Deny(mode PermissionMode, roles ...string) *Permission {\n\treturn role.NewPermission().Deny(mode, roles...)\n}\n\n// Get role defination\nfunc (role *Role) Get(name string) (Checker, bool) {\n\tfc, ok := role.definitions[name]\n\treturn fc, ok\n}\n\n// Remove role definition\nfunc (role *Role) Remove(name string) {\n\tdelete(role.definitions, name)\n}\n\n// Reset role definitions\nfunc (role *Role) Reset() {\n\trole.definitions = map[string]Checker{}\n}\n\n// MatchedRoles return defined roles from user\nfunc (role *Role) MatchedRoles(req *http.Request, user interface{}) (roles []string) {\n\tif definitions := role.definitions; definitions != nil {\n\t\tfor name, definition := range definitions {\n\t\t\tif definition(req, user) {\n\t\t\t\troles = append(roles, name)\n\t\t\t}\n\t\t}\n\t}\n\treturn\n}\n\n// HasRole check if current user has role\nfunc (role *Role) HasRole(req *http.Request, user interface{}, roles ...string) bool {\n\tif definitions := role.definitions; definitions != nil {\n\t\tfor _, name := range roles {\n\t\t\tif definition, ok := definitions[name]; ok {\n\t\t\t\tif definition(req, user) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "role_manager.go",
    "content": "package roles\n\n// Roler Roler interface\ntype Roler interface {\n\tGetRoles() []string\n}\n"
  },
  {
    "path": "roles_test.go",
    "content": "package roles_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/qor/roles\"\n)\n\nfunc TestAllow(t *testing.T) {\n\tpermission := roles.Allow(roles.Read, \"api\")\n\n\tif !permission.HasPermission(roles.Read, \"api\") {\n\t\tt.Errorf(\"API should has permission to Read\")\n\t}\n\n\tif permission.HasPermission(roles.Update, \"api\") {\n\t\tt.Errorf(\"API should has no permission to Update\")\n\t}\n\n\tif permission.HasPermission(roles.Read, \"admin\") {\n\t\tt.Errorf(\"admin should has no permission to Read\")\n\t}\n\n\tif permission.HasPermission(roles.Update, \"admin\") {\n\t\tt.Errorf(\"admin should has no permission to Update\")\n\t}\n}\n\nfunc TestDeny(t *testing.T) {\n\tpermission := roles.Deny(roles.Create, \"api\")\n\n\tif !permission.HasPermission(roles.Read, \"api\") {\n\t\tt.Errorf(\"API should has permission to Read\")\n\t}\n\n\tif !permission.HasPermission(roles.Update, \"api\") {\n\t\tt.Errorf(\"API should has permission to Update\")\n\t}\n\n\tif permission.HasPermission(roles.Create, \"api\") {\n\t\tt.Errorf(\"API should has no permission to Update\")\n\t}\n\n\tif !permission.HasPermission(roles.Read, \"admin\") {\n\t\tt.Errorf(\"admin should has permission to Read\")\n\t}\n\n\tif !permission.HasPermission(roles.Create, \"admin\") {\n\t\tt.Errorf(\"admin should has permission to Update\")\n\t}\n}\n\nfunc TestCRUD(t *testing.T) {\n\tpermission := roles.Allow(roles.CRUD, \"admin\")\n\tif !permission.HasPermission(roles.Read, \"admin\") {\n\t\tt.Errorf(\"Admin should has permission to Read\")\n\t}\n\n\tif !permission.HasPermission(roles.Update, \"admin\") {\n\t\tt.Errorf(\"Admin should has permission to Update\")\n\t}\n\n\tif permission.HasPermission(roles.Read, \"api\") {\n\t\tt.Errorf(\"API should has no permission to Read\")\n\t}\n\n\tif permission.HasPermission(roles.Update, \"api\") {\n\t\tt.Errorf(\"API should has no permission to Update\")\n\t}\n}\n\nfunc TestAll(t *testing.T) {\n\tpermission := roles.Allow(roles.Update, roles.Anyone)\n\n\tif permission.HasPermission(roles.Read, \"api\") {\n\t\tt.Errorf(\"API should has no permission to Read\")\n\t}\n\n\tif !permission.HasPermission(roles.Update, \"api\") {\n\t\tt.Errorf(\"API should has permission to Update\")\n\t}\n\n\tpermission2 := roles.Deny(roles.Update, roles.Anyone)\n\n\tif !permission2.HasPermission(roles.Read, \"api\") {\n\t\tt.Errorf(\"API should has permission to Read\")\n\t}\n\n\tif permission2.HasPermission(roles.Update, \"api\") {\n\t\tt.Errorf(\"API should has no permission to Update\")\n\t}\n}\n\nfunc TestCustomizePermission(t *testing.T) {\n\tvar customized roles.PermissionMode = \"customized\"\n\tpermission := roles.Allow(customized, \"admin\")\n\n\tif !permission.HasPermission(customized, \"admin\") {\n\t\tt.Errorf(\"Admin should has customized permission\")\n\t}\n\n\tif permission.HasPermission(roles.Read, \"admin\") {\n\t\tt.Errorf(\"Admin should has no permission to Read\")\n\t}\n\n\tpermission2 := roles.Deny(customized, \"admin\")\n\n\tif permission2.HasPermission(customized, \"admin\") {\n\t\tt.Errorf(\"Admin should has customized permission\")\n\t}\n\n\tif !permission2.HasPermission(roles.Read, \"admin\") {\n\t\tt.Errorf(\"Admin should has no permission to Read\")\n\t}\n}\n"
  }
]