{"id":14955978,"url":"https://github.com/alphamarket/rails-acu","last_synced_at":"2025-11-11T18:38:10.598Z","repository":{"id":32418418,"uuid":"86563971","full_name":"alphamarket/rails-acu","owner":"alphamarket","description":"ACU is the acronym for Access Control Unit, and it's designed to give the 100% control over permissions on multiple levels of rails application's structure.","archived":false,"fork":false,"pushed_at":"2023-03-08T17:24:34.000Z","size":201,"stargazers_count":4,"open_issues_count":11,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-13T21:24:43.749Z","etag":null,"topics":["authentication","gem","rails","rails5"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alphamarket.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"MIT-LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2017-03-29T09:34:12.000Z","updated_at":"2024-12-03T08:22:44.000Z","dependencies_parsed_at":"2023-07-15T14:05:04.127Z","dependency_job_id":null,"html_url":"https://github.com/alphamarket/rails-acu","commit_stats":null,"previous_names":["noise2/rails-acu"],"tags_count":17,"template":false,"template_full_name":null,"purl":"pkg:github/alphamarket/rails-acu","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alphamarket%2Frails-acu","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alphamarket%2Frails-acu/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alphamarket%2Frails-acu/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alphamarket%2Frails-acu/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alphamarket","download_url":"https://codeload.github.com/alphamarket/rails-acu/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alphamarket%2Frails-acu/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":283910127,"owners_count":26915128,"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","status":"online","status_checked_at":"2025-11-11T02:00:06.610Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["authentication","gem","rails","rails5"],"created_at":"2024-09-24T13:12:06.892Z","updated_at":"2025-11-11T18:38:10.566Z","avatar_url":"https://github.com/alphamarket.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/alphamarket/rails-acu.svg?branch=master)](https://travis-ci.org/alphamarket/rails-acu)\n\n\u003e ### Note:\n\u003e This branch is maintained for **Rails v6.*** and **Ruby v2.6.*** for *Rails v5.** please use `rails-5` branch.\n\n# ACU\nACU is the acronym for **A**ccess **C**ontrol **U**nit, and it's designed to give the 100% control over permissions on multiple levels of rails application's structure.\nThe software engineering of this gem tends to make it much faster and simple. All you have to do is to define the **entities** of your authentications (i.e `what is who?`)\nand write the rules for them based on `allow`/`deny` binary logic, and everything else will be done automatically.\n\n## Installation\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'rails-acu'\n```\n\nAnd then execute:\n```bash\n$ bundle\n```\n\nOr install it yourself as:\n```bash\n$ gem install rails-acu\n```\n\nThen install it in you app using:\n\n```bash\n$ rails generate acu:install\n```\n\n## Usage\nAfter installation using `rails generate acu:install`  two files will be created:\n\n```bash\ncreate  config/initializers/acu_setup.rb\ncreate  config/initializers/acu_rules.rb\n```\nThe file `acu_setup.rb` is the configuration of ACU gem, you can leave it alone and use the default configurations or customize it as desired,\nwe will talk about the configuration later.\n\nThe other hand the `acu_rules.rb` is where you put your access rules there, access rules are binary, _either an entity can access a resource or not_ -\nin this gem, resource means any of `namespace`, `controller` and `action`. here as an example `acu_rules.rb` and we explain its components in the following:\n\n```ruby\n# config/initializers/acu_rules.rb\nAcu::Rules.define do\n  # anyone makes a request could be count as everyone!\n  whois(:everyone) { true }\n\n  whois(:admin, args: [:user]) { |c| c and c.user_type == :ADMIN.to_s }\n\n  whois(:client, args: [:user]) { |c| c and c.user_type == :PUBLIC.to_s }\n\n  # admin can access to everywhere\n  allow :admin\n\n  # the default namespace\n  namespace do  \n    # assume anyone can access, your default namespace\n    allow :everyone\n    controller :home, :shop do\n      allow :admin, :client, on: [:some_secret_action1, :some_secret_action2]\n      # OR\n      # action :some_secret_action1, :some_secret_action2 do\n      #  allow :admin, :client\n      # end\n    end\n  end\n\n  # allow every get access to public controller in 3 [default(the `nil`), admin]\n  namespace nil, :admin do\n    controller :public do\n      allow :everyone\n    end\n  end\n\n  # the admin namespace\n  namespace :admin do\n\n    controller :contact, only: [:send_message] do\n      allow :everyone\n    end\n\n    controller :contact do\n      action(:support) {\n        allow :client\n      }\n    end\n  end\n\n  # nested namespace (since v3.0.0)\n  namespace :admin do\n    namespace :chat do\n      allow :client\n    end\n  end\n\n  # negated entities (since v3.0.4)\n  namespace do\n    controller :profile do\n      # only owners can edit the profile page\n      deny :not_owner, on: [:edit]\n    end\n  end\nend\n```\n\nAs we define our rules at the first line, we have to say who are the entities? _to whom we call who?_ for this purpose I have come up with a simple entity definition `whois`, it takes three arguments (1 of them is optional: `args`), first the label of the entity, in this example they are `:everyone, :admin` and `:client`, the second argument (which is optional) is the variables that are going to be used to determining if the current request has been initiated by the entity or not, and the final argument is a block which its job is to determine who is the defined entity!\n\nOnce we defined our entities we can set their binary access permissions at namespace/controller/action levels using `allow` and `deny` helpers. **that is it, we are done tutorialing; from now on is just tiny details. :)**\n\n\u003e **Scenario:** We have a *public* site which serves to its client's; we have 2 namespaces on this site, one is the _default_ namespace with _home_ controller in it, and the second namespace belongs to the _admin_ of site which has many controllers and also a _contact_ controller.\u003cbr /\u003e\nWe want to grant access to everyone for all of _home_ controller actions in _default_ namespace **except** the `some_secret_action1` and `some_secret_action2`; but these `some_secret_action*` can be accessed via the `:admin` and `:client` entities. By default only `:admin` can access to everywhere, but in namespace `admin` we made an exception for 2 actions in the `Admin::ContactController` which everyone can `send_message` to the admin and only clients can ask for `support`. Finally we want to grant access to everyone for _public_ controllers in our 2 namespaces _the default_ and _admin_. Also clients can access to everything in namespace _chat_.\u003cbr /\u003e\nIf you back trace it in the above example you can easily find this scenario in the rules, plain and simple.\n\n### Gaurding the requests\nFor gaurding you application using ACU, you to need to call it in `before_action` callbacks (preferably in you **base controller**). And also occasionally there is some situation that you need to pass the some argument in the entities to be able to determine the entity (i.e you cannot get it from `session`, `global variables/function` or directly from `database`) for such situations you can pass the arguments as you are calling `Acu::Monitor.gaurd` in your `before_action` as below:\n\n```ruby\nclass ApplicationController \u003c ActionController::Base\n  protect_from_forgery with: :exception\n\n  before_action { Acu::Monitor.gaurd by: { user: some_way_to_fetch_it } }\nend\n```\nThe method `Acu::Monitor.gaurd` accepts a hashed list of agruments named `by`, please note that the keys should be identical to the entities' `args` argument.\n\n### Some handy helpers\nAlthough you can define a binary allow/deny access rule in the `acu_rules.rb` file but there will be some gray area that neither you can allow _full access_ to the resource nor _no access_.\u003cbr /\u003e\nFor those situations you allow the entities to get access but limits their operations in the action/view/layout with the `acu_is?`, `acu_as` and `acu_except` helpers, here is some usage example of them:\n\n```ruby\n# return true if the entity `:admin`'s block in `whois(:admin)` return true, otherwise false\nacu_is? :admin\n# returns true if any of the given entity's block return true; if none of the was valid, returns false.\nacu_is? [:admin, :client]\n\n# executes the block if current user identified as an admin by `whois(:admin)`\nacu_as :admin do\n  puts 'You are identified as an `admin`'\nend\n# executes the block if current user identified as either `:admin` or `:client`\nacu_as [:admin, :client] do\n  puts 'You are either `admin` or `client`'\nend\n\n# DO NOT execute the block if current user identified as `:guest`\nacu_except [:guest] do\n  puts 'Except `:guest`s anyone else can execute this code'\nend\n\n# [since version v4.1.0]\n# alias checking:\n#   passing dynamic params to check if the passed params identify as an entity or not!\n#   NOTE: the passed arguments should match the entity definition arguments  \n\n# checks if the given user is an `admin` entity or not?\nacu_is? :admin, user: User.find_by_username('username')\n# checks if the given user is an `admin` OR a `client` entity or not?\nacu_is? [:admin, :client], user: User.find_by_username('username')\n# execute the block if the passed user is an `admin`\nacu_as :admin, user: User.find_by_username('username') do\n    puts 'The `username` is an `admin`'\nend\n# DO NOT execute the block if passed user identified as `:guest`\nacu_except [:guest], user: User.find_by_username('username') do\n  puts 'Except `:guest`s anyone else can execute this code'\nend\n```\n\n### Configurations\nOne of the files that `acu:install` command will generate is `acu_setup.rb` which contains the configuration for the gem, the default configurations are as following:\n\n```ruby\nAcu.setup do |config|\n  # to tighten the security this is enabled by default\n  # i.e if it checked to be true, then if a request didn't match to any of rules, it will get passed through\n  # otherwise the requests which don't fit into any of rules, the request is denied by default\n  config.allow_by_default = false\n\n  # the audit log file, to log how the requests handles, good for production\n  # leave it black for nil to disable the logging\n  config.audit_log_file   = \"\"\n\n  # cache the rules to make rule matching much faster\n  # it's not recommended to use it in developement/test evn.\n  config.use_cache = false\n\n  # the caching namespace\n  config.cache_namespace = 'acu'\n\n  # define the expiration of cached entries\n  config.cache_expires_in = nil\n\n  # the race condition ttl\n  config.cache_race_condition_ttl = nil\n\n  # more details about cache options:\n  # http://guides.rubyonrails.org/caching_with_rails.html\nend\n```\n\nHere are the details of the configurations:\n\n| Name | Default | Description |\n| ----- |-------| ------ |\n| allow_by_default | `false` | Set it `true` if you want to grant access to requests that doesn't fit to any rules you have defined (**Warning:** please be advised, setting it `true` may cause a security hole in your website if you don't cover the rules perfectly!). |\n| audit_log_file |  | The audit log file, useful for rules debugging! |\n| use_cache | `false` | ACU can utilize the `Rails.cache` to make the rules matching much faster by caching them, but if caching is enabled and you change the please make user you have cleared the ACU caches by `Acu::Monitor.clear_cache`. |\n| cache_* | 'acu' or `nil` | See rails [caching options](http://guides.rubyonrails.org/caching_with_rails.html#activesupport-cache-store) for details. |\n\n### API\nHere are the list of APIs that didn't mentioned above:\n\n| API | Arguments | Alias | Description |\n| ----- | :-------: | :------: | ---- |\n| `Acu::Configs.get` | `name` | N/A | Get the value of the `name`ed config |\n| `Acu::Monitor.args` | `kwargs` | N/A | Set the arguments demaned by blocks in `whois` |\n| `Acu::Monitor.clear_cache` | None | N/A | Clears the ACU's rule matching cache |\n| `Acu::Monitor.clear_args` | None | N/A | Clears the argument set by `Acu::Monitor.args` and `Acu::Monitor.gaurd` |\n| `Acu::Monitor.valid_for?` | `entity` | `acu_is?` | Check if the current request is come from the entity or not |\n| `Acu::Monitor.gaurd` | `by` | N/A | Validates the current request, considering the arguments demaned by blocks in `whois` |\n| `Acu::Rules.define` | `\u0026block` | N/A | Get a block of rules, **Note** that there could be mutliple `Acu::Rules.define` in your project, the rules will all merge together as a one, so you can have mutliple `acu_rule*.rb` file in your `config/initialize` and they will merge together |\n| `Acu::Rules.reset` | None | N/A | Resets everything in the `Acu::Rules` |\n| `Acu::Rule.lock` | None | N/A | Freezes the rules, you can set it at the _end of the last_ `acu_rule*.rb` file. |\n\n\n### Exceptions\nHere are the list of exceptions defined in ACU gem:\n\n```ruby\nclass Acu::Errors::AccessDenied \u003c StandardError\n\nclass Acu::Errors::UncheckedPermissions \u003c StandardError\n\nclass Acu::Errors::InvalidSyntax \u003c StandardError\n\nclass Acu::Errors::AmbiguousRule \u003c StandardError\n\nclass Acu::Errors::InvalidData \u003c StandardError\n\nclass Acu::Errors::MissingData \u003c InvalidData\n\nclass Acu::Errors::MissingEntity \u003c MissingData\n\nclass Acu::Errors::MissingUser \u003c MissingData\n\nclass Acu::Errors::MissingAction \u003c MissingData\n\nclass Acu::Errors::MissingController \u003c MissingData\n\nclass Acu::Errors::MissingNamespace \u003c MissingData\n```\n\n## Known contributions subjects to work on\n\n### Implementing to overriding the rules in inner loops:\nConsider we have to give the everyone to access the default namespace except `:profile` controller which will only allow by signed in users, although there are tools provided\nfor this purpose, such as `except` and `only` tags on `controller` and `namespace` but it would be nice if there are such a command like `override` which its skeleton has been\ndefined in the `Acu::Rules.override` which enables the previously defined rule to be overrided, the following pseudo-example removes the `allow :everyone` rule from the controller\n`profile`:\n\n```ruby\n  # config/initializers/acu_rules.rb\n  [...]\n  namespace do\n    allow :everyone\n    controller :profiles do\n      override :everyone\n      allow :signed_in\n    end\n  end\n  [...]\n```\n\n## Change Logs\n\n### v4.0\n* Moved to Rails 6 \u0026 Ruby 2.6.\n* More effective \u0026 robust permission caching \u0026 checking.\n\n### v3.0\n* Nested namespace support\n\n### Before `v3.0`\n* Core functionalities implemented and stabilized\n\n\n## Contributing\nIn order contributing to this project:\n1. Fork\n2. Make changes/upgrades/fixes etc\n3. Write a through tests\n4. Make a pull request to the `develop` branch\n\n## License\nThe gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falphamarket%2Frails-acu","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falphamarket%2Frails-acu","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falphamarket%2Frails-acu/lists"}