{"id":16710741,"url":"https://github.com/overminddl1/permission_ex","last_synced_at":"2025-03-21T20:33:11.110Z","repository":{"id":57530976,"uuid":"60804359","full_name":"OvermindDL1/permission_ex","owner":"OvermindDL1","description":null,"archived":false,"fork":false,"pushed_at":"2019-09-19T23:19:32.000Z","size":26,"stargazers_count":24,"open_issues_count":0,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-03-14T23:07:20.856Z","etag":null,"topics":["elixir","elixir-lang","elixir-library"],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/OvermindDL1.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-06-09T20:17:52.000Z","updated_at":"2023-09-01T08:51:34.000Z","dependencies_parsed_at":"2022-09-05T10:50:24.437Z","dependency_job_id":null,"html_url":"https://github.com/OvermindDL1/permission_ex","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OvermindDL1%2Fpermission_ex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OvermindDL1%2Fpermission_ex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OvermindDL1%2Fpermission_ex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OvermindDL1%2Fpermission_ex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/OvermindDL1","download_url":"https://codeload.github.com/OvermindDL1/permission_ex/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244866202,"owners_count":20523479,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elixir","elixir-lang","elixir-library"],"created_at":"2024-10-12T20:09:26.961Z","updated_at":"2025-03-21T20:33:10.777Z","avatar_url":"https://github.com/OvermindDL1.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PermissionEx\n\nA simple Struct-based Permission system for Elixir.  Created to be used with\nPhoenix but has no requirement or any real integration with it as this is\ndesigned to be entirely generic.\n\n## Installation\n\n[Available](https://hex.pm/packages/permission_ex) on Hex, the package can be\ninstalled by adding `permission_ex` to the list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [{:permission_ex, \"~\u003e 0.6.0\"}]\nend\n```\n\n## Features\n\nThis is the current feature set of what is done, planned, and thought about.  If\nany feature is not done yet or if any feature wants to be added that is not on\nthis list then please open an issue and/or pull request to have it get done\nfaster.\n\n- [x] Permission Matcher to test permissions against a requirement.\n- [x] Admin Permission Matcher to pre-authorize before testing normal permissions.\n- [ ] This currently works well with the `canada` library, but is there anything that can be done to make it even more simple?\n- [ ] Maybe add some more Permission specialties, such as maybe a `{:range, lower, upper}` test, maybe a function call test?\n- [ ] Maybe add helpers for serializing the structs to/from json by using Poison.\n- [ ] Maybe add helpers to serialize the structs in other ways?  If so then into what ways?\n- [ ] Maybe a deny Permission Matcher to hard deny before admin is tested.\n- [ ] Maybe add support to take a list of requirements and test each so all must have a match.\n- [ ] Maybe Create plugs to test permissions and either set a variable or kill/redirect the plug chain.\n\n## Usage\n\nGeneral usage will usually be something like reading either a tagged map or a\nspecific permission set from, say, a database or elsewhere, then comparing it to\na specific requirement.\n\nFor example, say you have this phoenix controller method:\n```elixir\ndef show(conn, _params) do\n  conn\n  |\u003e render(\"index.html\")\nend\n```\n\nAnd if you have a permission set from the logged in user (or you can pre-fill an\nanonymous user permission set, or leave empty if anon should have no access to\nanything), say you have it on `conn.assigns.perms` and it is a tagged map, then\nyou could test it like this:\n```elixir\ndef show(conn, _params) do\n  if PermissionEx.test_tagged_permissions(MyApp.Perms.IndexPage{action: :show}, conn.assigns.perms) do\n    conn\n    |\u003e render(\"index.html\")\n  else\n    conn\n    |\u003e render(\"unauthorized.html\")\n  end\nend\n```\n\n## Examples\n\nPlease see `PermissionEx` for detailed examples.\n\nAll of the examples use these as the example structs:\n\n```elixir\ndefmodule PermissionEx.Test.Structs.User do\n  @moduledoc false\n  @derive [Poison.Encoder]\n  defstruct name: nil\nend\n\ndefmodule PermissionEx.Test.Structs.Page do\n  @moduledoc false\n  @derive [Poison.Encoder]\n  defstruct action: nil\nend\n\ndefmodule PermissionEx.Test.Structs.PageReq do\n  @moduledoc false\n  @derive [Poison.Encoder]\n  defstruct action: nil\nend\n\ndefmodule PermissionEx.Test.Structs.PagePerm do\n  @moduledoc false\n  @derive [Poison.Encoder]\n  defstruct action: nil\nend\n```\n\n### Testing a specific permission:  `PermissionEx.test_permission/2`\n\nThe required permission is the first argument, the allowed permission is on the\nright.\n\n* Normal usage would be something like:\n\n    ```elixir\n    permissions = [:show, :edit] # From somewhere else\n    PermissionEx.test_permission(:show, permissions) # =\u003e true\n    PermissionEx.test_permission(:admin, permissions) # =\u003e true\n    ```\n\n* Identical things match:\n\n    ```elixir\n    PermissionEx.test_permission(:anything_identical, :anything_identical) # =\u003e true\n    PermissionEx.test_permission(\"anything identical\", \"anything identical\") # =\u003e true\n    ```\n\n* Anything matches the atom `:_`:\n  \n    ```elixir\n    PermissionEx.test_permission(:_, :_) # =\u003e true\n    PermissionEx.test_permission(:_, :anything) # =\u003e true\n    PermissionEx.test_permission(:anything, :_) # =\u003e true\n    ```\n\n* If the permission is a `[:any | permissions]` then each permission in the\n  list will be tested individually for if they match the requirement, and if\n  any test true then this will be true:\n  \n    ```elixir\n    PermissionEx.test_permission(:show, [:any]) # =\u003e false\n    PermissionEx.test_permission(:show, [:any, :show]) # =\u003e true\n    PermissionEx.test_permission(:show, [:any, :show, :edit]) # =\u003e true\n    PermissionEx.test_permission(:show, [:any, :edit, :show]) # =\u003e true\n    PermissionEx.test_permission(:show, [:any, :edit, :otherwise]) # =\u003e false\n    ```\n\n* If an atom and binary fail to match, they will be tested again with the\n  required atom being `to_string`'d, good for if loading from JSON or so, such\n  as in:\n  \n    ```elixir\n    PermissionEx.test_permission(:show, [\"any\", :edit, :show]) # =\u003e true\n    PermissionEx.test_permission(:show, [\"any\", \"edit\", \"show\"]) # =\u003e true\n    PermissionEx.test_permission(:show, \"show\") # =\u003e true\n    ```\n\n### Testing a permission set against a requirement struct: `PermissionEx.test_permissions/2`\n\nYou can test a struct requirement against a permission map or list or maps or\neven against override values such as in:\n\n* Via an override, where `true` or `:_` allows the entire requirement,\n  and where `false`, `nil`, an empty list `[]`, or an empty map or struct `%{}`\n  return `false`:\n  \n    ```elixir\n    PermissionEx.test_permissions(%PermissionEx.Test.Structs.Page{action: :show}, :_) # =\u003e true\n    PermissionEx.test_permissions(%PermissionEx.Test.Structs.Page{action: :show}, true) # =\u003e true\n    PermissionEx.test_permissions(%PermissionEx.Test.Structs.Page{action: :show}, false) # =\u003e false\n    PermissionEx.test_permissions(%PermissionEx.Test.Structs.Page{action: :show}, nil) # =\u003e false\n    PermissionEx.test_permissions(%PermissionEx.Test.Structs.Page{action: :show}, []) # =\u003e false\n    PermissionEx.test_permissions(%PermissionEx.Test.Structs.Page{action: :show}, %{}) # =\u003e false\n    ```\n\n* Or via a map or struct, structs tend to be better if the default values\n  for the struct align with the needs better, if anything in a map is\n  missing that a requirement tests for then it will return false:\n  \n    ```elixir\n    PermissionEx.test_permissions(%PermissionEx.Test.Structs.Page{action: :show}, %{action: :_}) # =\u003e true\n    PermissionEx.test_permissions(%PermissionEx.Test.Structs.Page{action: :show}, %{action: true}) # =\u003e false\n    PermissionEx.test_permissions(%PermissionEx.Test.Structs.Page{action: :show}, %{action: [:any, :edit, :show]}) # =\u003e true\n    PermissionEx.test_permissions(%PermissionEx.Test.Structs.Page{action: :show}, %PermissionEx.Test.Structs.Page{}) # =\u003e false\n    PermissionEx.test_permissions(%PermissionEx.Test.Structs.Page{action: :show}, %PermissionEx.Test.Structs.Page{action: :edit}) # =\u003e false\n    PermissionEx.test_permissions(%PermissionEx.Test.Structs.Page{action: :show}, %PermissionEx.Test.Structs.Page{action: :show}) # =\u003e true\n    ```\n\n* Or a list of any of the above, any overrides, maps, or structs:\n\n    ```elixir\n    PermissionEx.test_permissions(%PermissionEx.Test.Structs.Page{action: :show}, [true]) # =\u003e true\n    PermissionEx.test_permissions(%PermissionEx.Test.Structs.Page{action: :show}, [false]) # =\u003e false\n    PermissionEx.test_permissions(%PermissionEx.Test.Structs.Page{action: :show}, [%{action: :edit}]) # =\u003e false\n    PermissionEx.test_permissions(%PermissionEx.Test.Structs.Page{action: :show}, [%{action: :show}]) # =\u003e true\n    PermissionEx.test_permissions(%PermissionEx.Test.Structs.Page{action: :show}, [%PermissionEx.Test.Structs.Page{action: :edit}]) # =\u003e false\n    PermissionEx.test_permissions(%PermissionEx.Test.Structs.Page{action: :show}, [%PermissionEx.Test.Structs.Page{action: :show}]) # =\u003e true\n    ```\n\n### Testing a tagged permission set against a requirement struct: `PermissionEx.test_tagged_permissions/2`\n\nYou can test a struct requirement against a map of permissions keyed on the\nrequirement structs `:__struct__` value.\n\nThere is also an override key of `:admin`, this is another tagged permission map\nor an override that is tested before the main permissions are tested.\n\n* The permission map is just a map of the permission sets, so for example:\n\n    ```elixir\n    PermissionEx.test_tagged_permissions(%PermissionEx.Test.Structs.Page{action: :show}, %{PermissionEx.Test.Structs.Page =\u003e %{}}) # =\u003e false\n    PermissionEx.test_tagged_permissions(%PermissionEx.Test.Structs.Page{action: :show}, %{PermissionEx.Test.Structs.Page =\u003e %PermissionEx.Test.Structs.Page{action: :show}}) # =\u003etrue\n    PermissionEx.test_tagged_permissions(%PermissionEx.Test.Structs.Page{action: :show}, %{PermissionEx.Test.Structs.Page =\u003e %PermissionEx.Test.Structs.Page{action: :_}}) # =\u003e true\n    PermissionEx.test_tagged_permissions(%PermissionEx.Test.Structs.Page{action: :show}, %{PermissionEx.Test.Structs.Page =\u003e %PermissionEx.Test.Structs.Page{action: nil}}) # =\u003e false\n    ```\n\n* Do note, the permission map is keyed on the requirement struct, not on the\n  struct of its value, this allows you to define a different struct for the\n  permission side that could have certain default values to be set to what you\n  want, such as in:\n  \n    ```elixir\n    PermissionEx.test_tagged_permissions(%PermissionEx.Test.Structs.PageReq{action: :show}, %{PermissionEx.Test.Structs.PageReq =\u003e [%PermissionEx.Test.Structs.PagePerm{action: :_}]}) # =\u003e true\n    PermissionEx.test_tagged_permissions(%PermissionEx.Test.Structs.PageReq{action: :show}, %{PermissionEx.Test.Structs.PagePerm =\u003e [%PermissionEx.Test.Structs.PagePerm{action: :_}]}) # =\u003e false\n    ```\n\n* If there is an `:admin` key on the struct, then it is checked first, this\n  allows you to set up easy overrides for all or specific matches:\n    \n    ```elixir\n    # Can override and allow absolutely everything by just setting admin: true\n    PermissionEx.test_tagged_permissions(%PermissionEx.Test.Structs.Page{}, %{admin: true}) # =\u003e true\n\n    # Or can set it on a specific struct, it will not affect others then:\n    PermissionEx.test_tagged_permissions(%PermissionEx.Test.Structs.Page{}, %{admin: %{PermissionEx.Test.Structs.Page =\u003e true}}) # =\u003e true\n\n    PermissionEx.test_tagged_permissions(%PermissionEx.Test.Structs.User{}, %{admin: %{PermissionEx.Test.Structs.Page =\u003e true}}) # =\u003e false\n\n    # Can do fine-tuned matching as well if an override is needed, it will not allow non-matches\n    PermissionEx.test_tagged_permissions(%PermissionEx.Test.Structs.Page{action: :show}, %{admin: %{PermissionEx.Test.Structs.Page =\u003e %{action: :show}}}) # =\u003e true\n    PermissionEx.test_tagged_permissions(%PermissionEx.Test.Structs.Page{action: :edit}, %{admin: %{PermissionEx.Test.Structs.Page =\u003e %{action: :show}}}) # =\u003e false\n    ```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foverminddl1%2Fpermission_ex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foverminddl1%2Fpermission_ex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foverminddl1%2Fpermission_ex/lists"}