{"id":13879351,"url":"https://github.com/neiljohari/scram","last_synced_at":"2025-04-24T00:41:25.267Z","repository":{"id":56894508,"uuid":"81262676","full_name":"neiljohari/scram","owner":"neiljohari","description":"🔐A highly flexible authorization gem that gives granular control over permissions.","archived":false,"fork":false,"pushed_at":"2018-02-25T20:23:26.000Z","size":134,"stargazers_count":5,"open_issues_count":1,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-23T19:18:29.129Z","etag":null,"topics":["authorization","mongodb","mongoid","rails","ruby-gem"],"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/neiljohari.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}},"created_at":"2017-02-07T22:45:46.000Z","updated_at":"2020-09-17T22:44:05.000Z","dependencies_parsed_at":"2022-08-20T17:10:21.063Z","dependency_job_id":null,"html_url":"https://github.com/neiljohari/scram","commit_stats":null,"previous_names":["skreem/scram"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neiljohari%2Fscram","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neiljohari%2Fscram/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neiljohari%2Fscram/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neiljohari%2Fscram/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/neiljohari","download_url":"https://codeload.github.com/neiljohari/scram/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250540925,"owners_count":21447426,"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":["authorization","mongodb","mongoid","rails","ruby-gem"],"created_at":"2024-08-06T08:02:18.037Z","updated_at":"2025-04-24T00:41:25.238Z","avatar_url":"https://github.com/neiljohari.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# Scram [![Build Status](https://travis-ci.org/neiljohari/scram.svg?branch=master)](https://travis-ci.org/neiljohari/scram) [![Coverage Status](https://coveralls.io/repos/github/neiljohari/scram/badge.svg?branch=master)](https://coveralls.io/github/neiljohari/scram?branch=master)\nAn awesome authorization system\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'scram'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install scram\n\n## Usage\n\n[Click here to see YARD Documentation](http://www.rubydoc.info/github/skreem/scram/master)\n\n### Quick Overview of Scram\n- Holder\n  - Scram doesn't force you to use a specific group system. Instead, just include `Holder` into any class which can hold `Policies`.\n  - Scram provides a class for objects like Groups through an `AggregateHolder`. This is a class which should be included in anything which holds policies through other holders.\n  - In most cases, your `AggregateHolder` will be a `User` model. Your `Group` model will be a `Holder`. If you don't want to use a group system, then your `User` model will likely be a `Holder`.\n- Policy\n  - Policies are used to bundle together permissions.\n  - There are 2 kinds of `Policies`: Those for a specific model, and \"global\" `Policies` for permissions that aren't bound to a specific model.\n- Target\n  - `Targets` are a way to declare what actions are allowed in a `Policy`.\n  - `Targets` have a list of actions and conditions.\n    - Actions are anything a user can do to an object. For example: `:update, :view, :create, :destroy`.\n    - Conditions are used to refine what instances a target applies to. They support basic comparisons to attributes, but can be used to support more complex logic with the DSL.\n\n### Example Usage\nIf you choose to implement Holder into your user class directly, it may look something like the following.\n```ruby\nclass User\n  ...\n  include Scram::Holder\n\n  # Will automatically implement the needed #policies method for Holder.\n  has_many :policies, class: \"Scram::Policy\"\n\n  def scram_compare_value\n    self.id\n  end\nend\n```\nThis sort of system would not include a group system at all (for simplicity). If you want a Group system, have your user include `Scram::AggregateHolder` and then implement `#aggregates` to return your groups (which should be `Holder`s themselves).\n\nWe will be providing a full fledge example application of Scram shortly which will include a Group and membership system, and will clarify how the `AggregateHolder` system works. For now, lets see how Scram works in its simplest usage (a user who stores policies just for themselves).\n\n#### Adding a String Permission\nNow lets add a String permission to display a statistics bar for users like admins. We want to call `user.can? :view, \"peek_bar\"` and have it return true for admins.\n\nTo do this, we'll need to define a non-model Policy (because our object is a string, \"peek_bar\").\n```ruby\nuser = ...\npolicy = Scram::Policy.new\npolicy.name = \"global-strings-policy\" # Note that we're setting name, and we will leave context nil.\npolicy.context = nil # This would be nil by default as well. By not setting this to anything, we let this Policy handle String permissions, and not be bound to a model.\npolicy.save\nuser.policies \u003c\u003c policy\nuser.save\n```\n\nNow we want to add a target that represents the ability to `view \"peek_bar\"`.\n```ruby\ntarget = Target.new\ntarget.conditions = {:equals =\u003e { :'*target_name' =\u003e  \"peek_bar\"}}\ntarget.actions \u003c\u003c \"view\"\npolicy.targets \u003c\u003c target\npolicy.save\n```\n\nThis code creates a target which permits viewing if the `*target_name` equals \"peek_bar\".\n\nScram automatically replaces `*target_name` with the action being compared. For example, in `can? :view, \"something_else\"` Scram would check if `\"something_else\" == \"peek_bar\"`.\n\nAnd now we're done! :tada:\n\n#### Adding a Model Permission\nNow lets add something a bit more complex. Say we're developing a Forum application. We want to add the ability for a user to edit their own `Posts` using Scram.\n\nHere's our simple `Post` model:\n```ruby\nclass Post\n  ...\n  belongs_to :user\nend\n```\n\nLets make a Policy that handles post related permissions.\n```ruby\nuser = ...\npolicy = Scram::Policy.new\npolicy.name = \"Post Stuff\" # This name is just for organizational/display purposes\npolicy.context = Post.to_s # Note: By setting context, we bind this policy to the model \"Post\"\npolicy.save\nuser.policies \u003c\u003c policy\nuser.save\n```\n\nNow we need a Target in our Policy to let users edit their own Posts.\n```ruby\ntarget = Target.new\ntarget.conditions = {:equals =\u003e {:user =\u003e \"*holder\"}}\ntarget.actions \u003c\u003c \"edit\"\npolicy.save\n```\nWhat is `*holder`? Well, Scram replaces this special variable with the current user being compared. In `User#scram_compare_value` we return the User's ObjectId, and this is exactly what Scram replaced `*holder` with.\n\nSo now this Target reads \"allow a holder to edit a Post if the user of that Post is the holder\". Pretty neat, huh?\n\nAnd now we're done! Go ahead and call `holder.can? :edit, @post` on a post which they own, and you'll see that Scram allows it! :tada:\n\n#### Using conditions\nIn our last example, we let Scram directly compare an attribute of the model. What if we need more complex checking behavior? Luckily, Scram provides a DSL for models to easily define conditions which can be referenced in the database in place of attributes.\n\nLets revisit the `Post` example, but this time we'll define how to get the owner using a condition DSL, instead of the attribute.\n\n```ruby\nclass Post\n  include Scram::DSL::ModelConditions\n  ...\n  belongs_to :user\n\n  scram_define do\n    condition :owner do |post|\n      post.user\n    end\n  end\nend\n```\n\nNow we no longer need to directly tell our Target to access the user field. Here's what the equivalent Target would look like from our previous example, now using the new condition:\n\n```ruby\n...\ntarget.conditions = {:equals =\u003e {:'*owner' =\u003e \"*holder\"}}\n...\n```\n\nScram is smart enough to realize that any key starting with an `*`, like `*owner`, is a manually defined condition. Now, calling `user.can? :edit, @post` will compare the value returned by the `condition` block to the hash value (which in this case is the Holder).\n\n#### Defining a New Comparator\nYou may have noticed from the previous examples that the keys of our Target conditions were things like `equals` and `less_than`. These come from our Comparator definitions (see `Scram::DSL::Definitions::COMPARATORS`).\n\nThese comparators are defined using the DSL for comparators. We provide a basic set of comparing operators, but you may need to add your own. To do this, we recommend creating an initializer file and then calling something like the following:\n\n```ruby\nbuilder = Scram::DSL::Builders::ComparatorBuilder.new do\n  comparator :asdf do |a,b|\n    true\n  end\nend\nScram::DSL::Definitions.add_comparators(builder)\n```\nNow your targets can use `asdf` as a conditions key, and Scram will use your method of comparison to determine if something is true or not. In this case, `asdf` returns true regardless of the two objects being compared.\n\n#### Gotchas\nHaving trouble trying to use a holder check on a relation? Easy fix! The issue you are experiencing is just that the holder's scram_compare_value will probably be an ObjectId of some sort, but if you are comparing it against the relation... you are trying to compare the current holder's ObjectId to a document. The fix to this is just defining a condition within the model with the user you are trying to compare, and returning the object id of that.\n\nExample of the issue:\nLets say your `Post` model `belongs_to :user`. If you tried setting up a condition which checks something like this: `:equals =\u003e { :'user' =\u003e \"*holder\" }` it will never work because of the above description. To fix it, define a condition which returns an ObjectId.\n\n```ruby\nscram_define do\n  condition :owner do |post|\n    post.user.scram_compare_value # we could also have done post.user.id\n  end\nend\n```\n\nNow update your Target to have the following condition: `:equals =\u003e { :'*owner' =\u003e \"*holder\" }`. Voila! It will all work now, because you are correctly comparing the right data types.\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/skreem/scram.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneiljohari%2Fscram","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fneiljohari%2Fscram","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneiljohari%2Fscram/lists"}