{"id":15288807,"url":"https://github.com/skorks/ingress","last_synced_at":"2025-10-08T18:18:01.520Z","repository":{"id":54384547,"uuid":"59110225","full_name":"skorks/ingress","owner":"skorks","description":"Simple role based authorization for Ruby applications","archived":false,"fork":false,"pushed_at":"2025-05-20T03:33:51.000Z","size":34,"stargazers_count":7,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-09-24T15:41:11.782Z","etag":null,"topics":["ruby","ruby-gem","ruby-on-rails"],"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/skorks.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-05-18T11:40:26.000Z","updated_at":"2025-06-03T01:53:10.000Z","dependencies_parsed_at":"2022-08-13T14:01:05.236Z","dependency_job_id":null,"html_url":"https://github.com/skorks/ingress","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/skorks/ingress","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skorks%2Fingress","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skorks%2Fingress/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skorks%2Fingress/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skorks%2Fingress/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/skorks","download_url":"https://codeload.github.com/skorks/ingress/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skorks%2Fingress/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278188749,"owners_count":25945002,"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-10-03T02:00:06.070Z","response_time":53,"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":["ruby","ruby-gem","ruby-on-rails"],"created_at":"2024-09-30T15:53:16.590Z","updated_at":"2025-10-03T16:11:45.982Z","avatar_url":"https://github.com/skorks.png","language":"Ruby","readme":"# Ingress\n\nA simple role based authorization framework inspired by CanCan (similar syntax) with a nicer interface for defining the permissions for the roles in your system.\n\nThe biggest problem I had with CanCan was the fact that it mostly forced you define the permissions for all the roles in one class (really one method). And when the set of permissions in your system grew very large, you had to bend over backwards to allow you to break things down.\n\nIn the OO world we're used to being able to break down functionality into multiple smaller classes which we can them compose into a greater whole. This is the main idea behind this gem, keep the nice syntax that CanCan had, but allow composing the main permission object in your system from many smaller classes.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'ingress'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install ingress\n\n## Usage\n\nLet's say you have a user object in your system and the user can have multiple roles. Our set of roles will be `guest`, `member`, `admin`.\n\nFirst we create the main permission object in our system, let's call it `UserPermissions`:\n\n```ruby\nclass UserPermissions \u003c Ingress::Permissions\n  def user_role_identifiers\n    user.roles.map do |role|\n      role.name.to_sym\n    end\n  end\nend\n```\n\nA couple of things of note is that it inherits from `Ingress::Permissions`, from which it inherits the initializer:\n\n```ruby\nattr_reader :user\n\ndef initialize(user)\n  @user = user\nend\n```\n\nSo this object is always instantiated with a user. The second thing to note is that we have to provide a method called\n`user_role_identifiers` which needs to return a list of role identifier that this particular user has. Above we make the\nassumptions that a user has many roles and that a role has a name. So we iterate over the roles collect the symbolized names\nand return them. This is essentially what ties everything together. We haven't defined any permissions just yet, but we\ncan already do the following:\n\n```ruby\nuser_permissions = UserPermissions.new(user)\nuser_permissions.can?(:do, :stuff) # returns false\n```\n\nSo now we have an object that we can instantiate anywhere, and ask it if our user has a particular permission.\nLet us now define some permissions for a role. We'll start with the guest role. First, let's update our `UserPermissions`\nobject:\n\n```ruby\nclass UserPermissions \u003c Ingress::Permissions\n  define_role_permissions :guest, GuestPermissions\n\n  def user_role_identifiers\n    user.roles.map do |role|\n      role.name.to_sym\n    end\n  end\nend\n```\n\nWe've now said that the permission for the role with the `guest` identifier live in the `GuestPermissions` class. Let's create\nit:\n\n```ruby\nclass GuestPermissions \u003c Ingress::Permissions\n  define_role_permissions do\n    can :view, :non_sensitive_info\n    can [:create], :session\n  end\nend\n```\n\nIt's pretty self explanatory, the class again inherits from `Ingress::Permissions` as that's where the simple DSL for defining\npermissions lives. The thing to note is that we called the class `GuestPermissions`, but it could be called anything, the permissions\nwe define here are not attached to any role. They only get attached to the role via the `define_role_permissions :guest, GuestPermissions`\nline in the `UserPermissions` class. The syntax for defining permissions is:\n\n```\ncan 'action', 'subject'\nor\ncannot 'action', 'subject'\n```\n\nSimilar to CanCan, the `action` can be any string, symbol or array of strings or symbols. The `subject` can also be a string or symbol, or\nit can be a class constant. Let's define permissions for the next role in our system, `member` which is more complex. Firstly, update our `UserPermissions`.\n\n```ruby\nclass UserPermissions \u003c Ingress::Permissions\n  define_role_permissions :guest, GuestPermissions\n  define_role_permissions :member, MemberPermissions\n\n  def user_role_identifiers\n    user.roles.map do |role|\n      role.name.to_sym\n    end\n  end\nend\n```\n\nSimple, next `MemberPermissions` class:\n\n```ruby\nclass MemberPermissions \u003c Ingress::Permissions\n  define_role_permissions do\n    can [:show, :update, :destroy], :session\n    can :accept, :terms\n    can [:view, :create], Post\n    can [:update, :destroy], Post, if: -\u003e(user, post) do\n      user.id == post.user_id\n    end\n  end\nend\n```\n\nIt's a little bit more complex, but still fairly self explanatory. As you can see, we have a `Post` object in our system. So we allow\nuser with a `member` role to view and create posts, and they can update and destroy posts that they own. So we could do:\n\n```ruby\nuser_permissions = UserPermissions.new(user)\nuser_permissions.can?(:create, Post) # returns true\npost = user.posts.first # assume we can get the list of posts form the user object\nuser_permissions.can?(:update, post) # returns true\n```\n\nThe condition lambda always takes two parameters, the `user` and an `object`, the object is whatever we supply to the `can?` method,\nwhen we check permissions.\n\nLet's add our admin role:\n\n```ruby\nclass UserPermissions \u003c Ingress::Permissions\n  define_role_permissions :guest, GuestPermissions\n  define_role_permissions :member, MemberPermissions\n  define_role_permissions :admin, AdminPermissions\n\n  def user_role_identifiers\n    user.roles.map do |role|\n      role.name.to_sym\n    end\n  end\nend\n```\n\nAnd the class:\n\n```ruby\nclass AdminPermissions \u003c Ingress::Permissions\n  define_role_permissions do\n    can \"*\", \"*\" # you can also use can_do_anything\n  end\nend\n```\n\nAs you can see both `action` and `subject` can be wildcards, so in this case an admin would be able to do anything in the system, i.e.\nany call to `can?` will always return `true`.\n\nSo what else can we do? Well let's say we wanted another role called `limited_admin` which would be similar to admin, but can't destroy\ncomments:\n\n```ruby\nclass UserPermissions \u003c Ingress::Permissions\n  define_role_permissions :guest, GuestPermissions\n  define_role_permissions :member, MemberPermissions\n  define_role_permissions :admin, AdminPermissions\n  define_role_permissions :limited_admin, LimitedAdminPermissions\n\n  def user_role_identifiers\n    user.roles.map do |role|\n      role.name.to_sym\n    end\n  end\nend\n```\n\nAnd the class:\n\n```ruby\nclass LimitedAdminPermissions \u003c Ingress::Permissions\n  inherits AdminPermissions\n\n  define_role_permissions do\n    cannot :destroy, Comment\n  end\nend\n```\n\nSo basically, we can inherit permissions that are defined in other classes, and either switch off some or add others. Let's create\nsome sort of `super_member` role, which can do everything a member can do, but can also update anything in the system:\n\n```ruby\nclass UserPermissions \u003c Ingress::Permissions\n  define_role_permissions :guest, GuestPermissions\n  define_role_permissions :member, MemberPermissions\n  define_role_permissions :admin, AdminPermissions\n  define_role_permissions :limited_admin, LimitedAdminPermissions\n  define_role_permissions :super_member, SuperMemberPermissions\n\n  def user_role_identifiers\n    user.roles.map do |role|\n      role.name.to_sym\n    end\n  end\nend\n```\n\nAnd the class:\n\n```ruby\nclass SuperMemberPermissions \u003c Ingress::Permissions\n  inherits MemberPermissions\n\n  define_role_permissions do\n    can :update, \"*\"\n  end\nend\n```\n\nWe can inherit permissions, and we use a wildcard subject, to allow a user with the `super_member` role to be able to update anything. We\ncan even define a common set of permissions which we want multiple roles to share and have the permission class for each of those roles\ninherit from the common set. Let's say we want a `financial_officer` role and a `reporting_officer` role both of which should have the ability to do anything with a `Transaction` object in our system (for whatever reason):\n\n```ruby\nclass UserPermissions \u003c Ingress::Permissions\n  define_role_permissions :guest, GuestPermissions\n  define_role_permissions :member, MemberPermissions\n  define_role_permissions :admin, AdminPermissions\n  define_role_permissions :limited_admin, LimitedAdminPermissions\n  define_role_permissions :super_member, SuperMemberPermissions\n  define_role_permissions :financial_officer, FinancialOfficerPermissions\n  define_role_permissions :reporting_officer, ReportingOfficerPermissions\n\n  def user_role_identifiers\n    user.roles.map do |role|\n      role.name.to_sym\n    end\n  end\nend\n```\n\nAnd the classes:\n\n```ruby\nclass CommonPermissions \u003c Ingress::Permissions\n  define_role_permissions do\n    can \"*\", Transaction\n  end\nend\n\nclass FinancialOfficerPermissions \u003c Ingress::Permissions\n  inherits CommonPermissions\nend\n\nclass ReportingOfficerPermissions \u003c Ingress::Permissions\n  inherits CommonPermissions\nend\n```\n\nNow we wildcard the action, so we can do anything to `Transaction` objects. And we have to other sets of permission inherit from\nthe `CommonPermissions` class.\n\nI hope it's relatively clear that it's pretty flexible, you can almost endlessly decompose the permission definitions into smaller classes\nthen combine via `inherits` and assign the final permission set to a role identifier via `define_role_permissions` on the main\n`UserPermissions` class.\n\nSo now the authorization in your system can be defined in a much more OO way, without nasty and complex tricks. And you can still enjoy a nice syntax very similar to CanCan.\n\nThis framework has no hooks into Rails (these would be trivial to write if necessary, e.g. you can instantiate the `user_permissions` object on your `ApplicationController` and then do the `can?` checks anywhere you want) and can therefore be used with any web framework, or even outside of the context of a web framework (if such a use case makes sense).\n\n### Conditional Lambda\n\nIngress by default does not know about the subject that is given in the conditional lambda. The given subject can be a Class or an Object and it depends on the user to define the correct lambda to handle the given subject.\n\nFor example, given the following `UserPermissions`:\n\n```\nclass UserPermissions \u003c Ingress::Permissions\n  define_role_permissions do\n    can :read, Post, if: -\u003e(user, given_subject) do\n      case [user, given_subject]\n      in [_, Post]\n        user.id == given_subject.user_id\n      in [_, Class]\n        true\n      else\n        false\n    end\n  end\nend\n```\n\nThis is the result of the defined permissions:\n\n```\nuser_permissions = UserPermissions.new(user)\nuser_permissions.can?(:read, Post) # returns true\npost = user.posts.first # assume we can get the list of posts form the user object\nuser_permissions.can?(:read, post) # returns true\n```\n\nIngress provides 2 convenient interfaces to apply conditional lambda on a Class or an Instance:\n\n* if_subject_is_an_instance\n* if_subject_is_a_class\n\nThese conditional lambdas always take 3 parameters: the `user`, the `subject` (this is either a Class or an Instance), and the `options` (this is additional attributes that may be needed to do the check).\n\nThey can be chained together like following:\n\n```\nclass UserPermissions \u003c Ingress::Permissions\n  define_role_permissions do\n    can :read, Post,\n      if: -\u003e(user, _post) { !user.id.nil? },\n      if_subject_is_an_instance: -\u003e(user, post, _options) { user.id == post.user_id },\n      if_subject_is_a_class: -\u003e(_user, klass, _options) { klass == Post }\n  end\nend\n```\n\nThis is the result of the defined permissions:\n\n```\nuser_permissions = UserPermissions.new(user)\nuser_permissions.can?(:read, Post) # returns true\npost = user.posts.first # assume we can get the list of posts form the user object\nuser_permissions.can?(:read, post) # returns true\n```\n\n## Development\n\nAfter checking out the repo, run `script/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `script/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/skorks/ingress.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskorks%2Fingress","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fskorks%2Fingress","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskorks%2Fingress/lists"}