{"id":24460903,"url":"https://github.com/jacklin10/canner","last_synced_at":"2025-10-05T01:43:39.964Z","repository":{"id":18700709,"uuid":"21910938","full_name":"jacklin10/canner","owner":"jacklin10","description":"Full featured rails authorization without all the magic.","archived":false,"fork":false,"pushed_at":"2018-04-07T16:23:41.000Z","size":67,"stargazers_count":16,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-26T04:25:30.942Z","etag":null,"topics":["authorization","pundit","rails","ruby"],"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/jacklin10.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-07-16T17:49:39.000Z","updated_at":"2025-01-12T00:22:47.000Z","dependencies_parsed_at":"2022-09-24T17:13:10.466Z","dependency_job_id":null,"html_url":"https://github.com/jacklin10/canner","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/jacklin10/canner","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacklin10%2Fcanner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacklin10%2Fcanner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacklin10%2Fcanner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacklin10%2Fcanner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jacklin10","download_url":"https://codeload.github.com/jacklin10/canner/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacklin10%2Fcanner/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278399610,"owners_count":25980330,"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-04T02:00:05.491Z","response_time":63,"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":["authorization","pundit","rails","ruby"],"created_at":"2025-01-21T04:15:54.829Z","updated_at":"2025-10-05T01:43:39.923Z","avatar_url":"https://github.com/jacklin10.png","language":"Ruby","readme":"## Canner\n\n[![Code Climate](https://codeclimate.com/github/jacklin10/canner/badges/gpa.svg)](https://codeclimate.com/github/jacklin10/canner)\n[![Build Status](https://travis-ci.org/jacklin10/canner.svg?branch=master)](https://travis-ci.org/jacklin10/canner)\n[![Gem Version](https://badge.fury.io/rb/canner.svg)](http://badge.fury.io/rb/canner)\n\nCanner is an authorization gem heavily modeled after Pundit.  \n\nCanner's intention is to provide you a framework for authorization that has little to no magic.  \nYour canner policies can be as simple or as complicated as your app requires.\n\nWho needs another auth gem?  There's a bunch of very good ones out there.  \nPundit, cancan, cancancan and Declarative Authorization to name a few alternatives.  \n\nUnfortunately for me, none of those solutions had built in support for a requirement I had.\n\nI needed to authorize a user by more than just a role, I needed to authorize a user by role and location.  \nThe other auth libraries out there don't support this requirement out of the box. \n\nWith canner you can ask:  Can *joe* having the role *manager* access the reports for the *pittsburgh* store.  \n\nYou just have to let canner know the branch you are currently operating on.  \n\nFor details see the wiki page [Authorize with Branches ( Store Locations )](https://github.com/jacklin10/canner/wiki/Authorize-with-Branches)\n\nAlso note that canner works fine if you don't need this particular feature, its just there if you do.\n\n## Compatability \nCanner works with rails 4.x and 5.x.\n\n## Installation\n\nI've only tested Canner on rails 4 but it should work fine in rails 3.2 apps.\n\nTo install Canner just put the following in your gem file.\n``` ruby\ngem \"canner\"\n```\n\nThen run\n\n``` ruby\nbundle install\n```\n\nNow include canner in your application_controller.rb\n\n``` ruby\ninclude Canner\n```\n\nYou'll then need to create a Policy class for the models you'd like to authorize.\n\n``` ruby\nrails g canner:policy user\n```\n\nIf your app gets roles from a user in a way other than ```@current_user.roles``` then you'll\nneed to override the fetch_roles policy method.\n\n```ruby\nrails g canner:fetch_roles\n```\n\nMore details are available in the wiki:\n[Overriding the Fetching of Roles](https://github.com/jacklin10/canner/wiki/Feed-Roles)\n\n## Policies\n\nAs mentioned Canner is strongly influenced by Pundit and is also based on Policy objects.\nYour policy objects should be named using the following pattern:   \n```\n  \u003cmodel_name\u003ePolicy.rb\n  i.e:  User.rb / UserPolicy.rb, Customer.rb / CustomerPolicy.rb\n```\n\n#### Generator\n\nYou can also use our generator to create the policy for you: \n\n``` rails g canner:policy \u003cmodel name\u003e ```\n\nYour policy models need to implement 2 methods:\n\n``` ruby\ndef canner_scope\nend\n\ndef can?\nend\n```\n\n### canner_scope\n\nYou'll want to implement this method for each of your model policies that extend from base_policy.rb.\n\nThe canner_scope method is used to scope the authorized models consistently in your app.\n\nFor example in my app the Customers controller uses the canner_scope to\nensure only Users from the current_company are displayed.\n\n``` ruby\nclass CustomersController \u003c ApplicationController\n  respond_to :html, :json\n  before_action :authenticate_user!\n\n  def index\n    @customers = canner_scope(:index, :customer)\n\n    can?(:index, :customer)\n  end\nend\n```\n\nand the policy is:\n\n``` ruby\nclass CustomerPolicy \u003c BasePolicy\n\n  def canner_scope\n    case @method\n    when :index\n      User.where(company_id: @current_branch.company_id)\n    else\n      User.none\n    end\n  end\n\n  def can?\n    case @method\n    when :new, :index, :create, :update, :edit\n      has_role?(:admin)\n    else\n      false\n    end\n  end\n\nend\n```\n\nNow you don't really need to think about the auth logic when fetching a list of customers.\nJust make sure you use the policy and you'll only show the users what is intended.\n\nAlso if your policy changes at some point its a one place fix.\n\n### can?\n\nYou use the can? method to determine if the current_user is able to access an action or resource.\n\nThe example above uses a straightforward case statement to determine if the current_user can\naccess the current action or resource.\n\nThe symbols in the when portion of the case match your typical actions in the example but they\ncan be whatever you want really.\n\n``` ruby\ncase @method\nwhen :something_random\n  has_role?(:admin)\nelse\n  false\nend\n```\nThen in controller do:\n```can?(:something_random, :customer)```\n\nIn english the can method is saying:\n\nCan the currently signed in user access the something_random action?  Oh, and by the way please\nuse the CustomerPolicy's can? method to do the checking.\n\n`can?(:something_random, :user)` would use the ... you guessed it UserPolicy's can? method.\n\nIf you want to deny access by default across all model policies you could do something as simple as:\n\n``` ruby\ndef can?\n  false\nend\n```\n\nin your base_policy's `can?` method\n\n### instance_can?\n\nYou use the instance_can? method to determine if the current_user is able to modify a particular instance\nof an object.  \n\nFor example, if a user wants to edit a particular item they may end up here:  \n\n```\n/items/3/edit\n```\n\nThe user changes the item price and moves on with their day.\n\nNow we have another user who decides they want to see what happens if they tinker with the url to potential edit other items.\nThey enter the following:\n\n```\n/items/13/edit\n```\n\nMaybe item 13 belongs to a different company, or is in a cateogory that this user isn't supposed to see.\nIf you don't defend against this situation a clever user can gain access to any item in the system.\n\nThe **instance_can?** method helps in these situations.  \n\nIn your items controller for the **edit, update and destroy** methods add something like:  \n\n``` ruby\n@item = Item.find params[:id]\ninstance_can? :manage, :item, @item\n```\n\nYour item_policy.rb will have something like:  \n\n``` ruby\ndef instance_can?(item)\n  case @method\n  when :manage\n    return @current_user.item_categories.include?(item.category)\n  else\n    false\n  end\nend\n```\n\nNow an access denied message will be shown to any users attempting to access an item in a category they don't belong to.\n\nYour policy can be more complex if needed.  Canner is just a framework so you can get as creative as you want\njust so long as you eventually return true or false.  \n\nFor example, maybe your admin user is allowed to edit any items?  You could do something like this:  \n``` ruby\ndef instance_can?(item)\n  case @method\n  when :manage\n    return has_role?(:admin) ? true : @current_user.company == item.company\n  else\n    false\n  end\nend\n```\n\nYou can enforce that your methods check for this just like you can for ```canner_scope``` or ```can?```.  \nThe next section shows you how.\n\n### Forcing Controller Authorization\n\nYou are able to force the use of controller authorization with canner.  \nI recommend you do this so you don't forget to wrap authorization about some of your resources.\n\nTo make sure your controller actions are using the can? method add this near the top of your\n`application_controller.rb`.  Use the `unless:` option for ensuring we ignore controllers\nrelated to authentication.\n\n``` ruby\nafter_action :ensure_auth\n\n# using devise?\nafter_action :ensure_auth, unless: :devise_controller?\n\n# using CASino?\nafter_action :ensure_auth, unless: -\u003e { self.is_a? CASino::SessionsController }\n```\n\nAnd to make sure you are using the ```canner_scope``` do the following:\n``` ruby\nafter_action :ensure_scope, only: :index\n```\n\nNote the use of only here.  You usually won't need the canner_scope on anything except\nfor the index to be strictly enforced.\n\nAnd finally, if you want to enforce that you are using instance_can? use something like:\n``` ruby\nafter_action :ensure_instance_checking, only: [:edit, :destroy, :update]\n```\n\nIf you would like to skip one of the enforcements for a specific controller add one or all of these:\n\n``` ruby\nskip_filter :ensure_scope\nskip_filter :ensure_auth\nskip_filter :ensure_instance_checking\n```\n\n### Handle Canner Authorization Failures\n\nWhen a user does stumble onto something they don't have access to you'll want to politely\ntell them about it and direct the app flow as you see fit.\n\nTo accomplish this in your application_controller.rb add\n\n``` ruby\nrescue_from Canner::NotAuthorizedError, with: :user_not_authorized\n```\n\nYou can name your method whatever you want.  Mine is user_not_authorized and looks like this:\n\n``` ruby\nprivate\n\ndef user_not_authorized(exception)\n  flash[:error] = exception.message\n  redirect_to(request.referrer || root_path)\nend\n```\n\n### Using can? in views\n\nYou'll likely want to show or hide on screen items based on a users' role.\nThis is done in canner like this:\n\n``` ruby\n  = link_to 'Create Customer', new_customer_path if canner_policy(:new, :customer).can?\n```\n\nThis will look in the CustomerPolicy's can? method implemention and follow whatever rules\nyou have for the :new symbol.\n\nSo assuming the CustomerPolicy can? method provided below the currently signed in user\nwould only be able to see the create customer link if they had an admin role.\n\n``` ruby\n  def can?\n    case @method\n    when :new\n      has_role?(:admin)\n    else\n      false\n    end\n  end\n```\n\n## Testing\n\nSee the wiki for some testing tips\n[Testing](https://github.com/jacklin10/canner/wiki/Testing)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjacklin10%2Fcanner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjacklin10%2Fcanner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjacklin10%2Fcanner/lists"}