├── test
├── rails_app
│ ├── log
│ │ └── .keep
│ ├── app
│ │ ├── mailers
│ │ │ └── .keep
│ │ ├── models
│ │ │ ├── .keep
│ │ │ ├── concerns
│ │ │ │ └── .keep
│ │ │ ├── client.rb
│ │ │ ├── admin
│ │ │ │ └── report.rb
│ │ │ ├── application_record.rb
│ │ │ ├── image.rb
│ │ │ ├── user.rb
│ │ │ ├── document.rb
│ │ │ ├── admin.rb
│ │ │ └── membership.rb
│ │ ├── assets
│ │ │ ├── images
│ │ │ │ └── .keep
│ │ │ ├── javascripts
│ │ │ │ └── application.js
│ │ │ └── stylesheets
│ │ │ │ └── application.css
│ │ ├── controllers
│ │ │ ├── concerns
│ │ │ │ └── .keep
│ │ │ ├── admin
│ │ │ │ ├── comm_reports_controller.rb
│ │ │ │ └── reports_controller.rb
│ │ │ ├── images_controller.rb
│ │ │ ├── application_controller.rb
│ │ │ └── documents_controller.rb
│ │ ├── helpers
│ │ │ └── application_helper.rb
│ │ └── views
│ │ │ └── layouts
│ │ │ └── application.html.erb
│ ├── lib
│ │ ├── assets
│ │ │ └── .keep
│ │ └── tasks
│ │ │ └── .keep
│ ├── public
│ │ ├── favicon.ico
│ │ ├── robots.txt
│ │ ├── 500.html
│ │ ├── 422.html
│ │ └── 404.html
│ ├── test
│ │ ├── helpers
│ │ │ └── .keep
│ │ ├── mailers
│ │ │ └── .keep
│ │ ├── models
│ │ │ ├── .keep
│ │ │ ├── image_test.rb
│ │ │ ├── user_test.rb
│ │ │ ├── client_test.rb
│ │ │ ├── document_test.rb
│ │ │ ├── membership_test.rb
│ │ │ └── admin
│ │ │ │ └── report_test.rb
│ │ ├── controllers
│ │ │ └── .keep
│ │ ├── fixtures
│ │ │ ├── .keep
│ │ │ ├── clients.yml
│ │ │ ├── users.yml
│ │ │ ├── images.yml
│ │ │ ├── admin
│ │ │ │ └── reports.yml
│ │ │ ├── documents.yml
│ │ │ ├── admins.yml
│ │ │ └── memberships.yml
│ │ ├── integration
│ │ │ └── .keep
│ │ └── test_helper.rb
│ ├── vendor
│ │ └── assets
│ │ │ ├── javascripts
│ │ │ └── .keep
│ │ │ └── stylesheets
│ │ │ └── .keep
│ ├── bin
│ │ ├── bundle
│ │ ├── rake
│ │ ├── rails
│ │ └── spring
│ ├── config
│ │ ├── initializers
│ │ │ ├── cookies_serializer.rb
│ │ │ ├── session_store.rb
│ │ │ ├── mime_types.rb
│ │ │ ├── filter_parameter_logging.rb
│ │ │ ├── assets.rb
│ │ │ ├── backtrace_silencers.rb
│ │ │ ├── wrap_parameters.rb
│ │ │ └── inflections.rb
│ │ ├── environment.rb
│ │ ├── routes.rb
│ │ ├── boot.rb
│ │ ├── application.rb
│ │ ├── database.yml
│ │ ├── locales
│ │ │ └── en.yml
│ │ ├── secrets.yml
│ │ └── environments
│ │ │ ├── development.rb
│ │ │ ├── test.rb
│ │ │ └── production.rb
│ ├── config.ru
│ ├── db
│ │ ├── migrate
│ │ │ ├── 20141016183619_create_users.rb
│ │ │ ├── 20190711184853_create_clients.rb
│ │ │ ├── 20160823220959_create_images.rb
│ │ │ ├── 20141016142638_create_admins.rb
│ │ │ ├── 20141016183642_create_documents.rb
│ │ │ ├── 20141017140833_create_admin_reports.rb
│ │ │ └── 20141016183633_create_memberships.rb
│ │ ├── seeds.rb
│ │ └── schema.rb
│ ├── Rakefile
│ ├── .gitignore
│ └── README.rdoc
├── simon_says_test.rb
├── controllers
│ ├── images_controller_test.rb
│ ├── documents_controller_test.rb
│ └── admin
│ │ └── reports_controller_test.rb
├── test_helper.rb
└── simon_says
│ ├── authorizer_denied_test.rb
│ ├── roleable_test.rb
│ └── authorizer_test.rb
├── .gitpublish
├── SimonSays.png
├── lib
├── simon_says
│ ├── version.rb
│ ├── roleable.rb
│ └── authorizer.rb
├── generators
│ └── active_record
│ │ ├── templates
│ │ └── migration.rb
│ │ └── simon_says_generator.rb
└── simon_says.rb
├── bin
├── setup
└── console
├── Gemfile
├── .gitignore
├── .travis.yml
├── Rakefile
├── Guardfile
├── config
└── locales
│ └── en.yml
├── LICENSE.txt
├── simon_says.gemspec
├── CODE_OF_CONDUCT.md
└── README.md
/test/rails_app/log/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/rails_app/app/mailers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/rails_app/app/models/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/rails_app/lib/assets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/rails_app/lib/tasks/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/rails_app/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/rails_app/test/helpers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/rails_app/test/mailers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/rails_app/test/models/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/rails_app/app/assets/images/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/rails_app/test/controllers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/rails_app/test/fixtures/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/rails_app/test/integration/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/rails_app/app/models/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/rails_app/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/rails_app/vendor/assets/javascripts/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/rails_app/vendor/assets/stylesheets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/rails_app/test/fixtures/clients.yml:
--------------------------------------------------------------------------------
1 | alice: {}
2 |
--------------------------------------------------------------------------------
/test/rails_app/test/fixtures/users.yml:
--------------------------------------------------------------------------------
1 | bob: {}
2 | jim: {}
3 |
--------------------------------------------------------------------------------
/.gitpublish:
--------------------------------------------------------------------------------
1 | export_dir=docs/
2 | remote=origin
3 | branch=gh-pages
4 |
--------------------------------------------------------------------------------
/test/rails_app/test/fixtures/images.yml:
--------------------------------------------------------------------------------
1 | image_one:
2 | token: abcdef
3 |
--------------------------------------------------------------------------------
/SimonSays.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimplyBuilt/SimonSays/HEAD/SimonSays.png
--------------------------------------------------------------------------------
/test/rails_app/app/models/client.rb:
--------------------------------------------------------------------------------
1 | class Client < ApplicationRecord
2 | end
3 |
--------------------------------------------------------------------------------
/lib/simon_says/version.rb:
--------------------------------------------------------------------------------
1 | module SimonSays
2 | VERSION = '0.3.0.alpha.7'
3 | end
4 |
--------------------------------------------------------------------------------
/test/rails_app/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/test/rails_app/test/fixtures/admin/reports.yml:
--------------------------------------------------------------------------------
1 | report_one:
2 | title: Some Title
3 |
--------------------------------------------------------------------------------
/test/rails_app/app/models/admin/report.rb:
--------------------------------------------------------------------------------
1 | class Admin::Report < ApplicationRecord
2 | end
3 |
--------------------------------------------------------------------------------
/test/rails_app/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | self.abstract_class = true
3 | end
4 |
--------------------------------------------------------------------------------
/test/rails_app/test/fixtures/documents.yml:
--------------------------------------------------------------------------------
1 | alpha:
2 | title: Alpha
3 |
4 | beta:
5 | title: Beta
6 |
7 | gamma:
8 | title: Gamma
9 |
--------------------------------------------------------------------------------
/test/rails_app/app/models/image.rb:
--------------------------------------------------------------------------------
1 | class Image < ApplicationRecord
2 | has_secure_token
3 |
4 | def to_param
5 | token
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/test/rails_app/app/models/user.rb:
--------------------------------------------------------------------------------
1 | class User < ApplicationRecord
2 | has_many :memberships
3 | has_many :documents, through: :memberships
4 | end
5 |
--------------------------------------------------------------------------------
/test/rails_app/app/models/document.rb:
--------------------------------------------------------------------------------
1 | class Document < ApplicationRecord
2 | has_many :memberships
3 | has_many :users, through: :memberships
4 | end
5 |
--------------------------------------------------------------------------------
/test/rails_app/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 | set -vx
5 |
6 | bundle install
7 |
8 | # Do any other automated setup that you need to do here
9 |
--------------------------------------------------------------------------------
/test/rails_app/app/models/admin.rb:
--------------------------------------------------------------------------------
1 | class Admin < ApplicationRecord
2 | include SimonSays::Roleable
3 |
4 | has_roles :support, :content, :marketing, as: :access
5 | end
6 |
--------------------------------------------------------------------------------
/test/rails_app/test/models/image_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class ImageTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/rails_app/test/models/user_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class UserTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/rails_app/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.action_dispatch.cookies_serializer = :json
--------------------------------------------------------------------------------
/test/rails_app/test/models/client_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class ClientTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/rails_app/test/models/document_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class DocumentTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/rails_app/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run Rails.application
5 |
--------------------------------------------------------------------------------
/test/rails_app/test/models/membership_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class MembershipTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/simon_says_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class TestSimonSays < ActiveSupport::TestCase
4 | test "has version number" do
5 | refute_nil SimonSays::VERSION
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/test/rails_app/test/models/admin/report_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Admin::ReportTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/rails_app/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/test/rails_app/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.session_store :cookie_store, key: '_rails_app_session'
4 |
--------------------------------------------------------------------------------
/test/rails_app/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | namespace :admin do
3 | resources :reports
4 | end
5 |
6 | resources :documents
7 | resources :images, only: :show
8 | end
9 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gemspec
4 |
5 | group :development do
6 | gem 'sqlite3'
7 |
8 | gem 'rdoc'
9 |
10 | gem 'guard'
11 | gem 'guard-minitest', "2.3.2"
12 | end
13 |
--------------------------------------------------------------------------------
/test/rails_app/config/boot.rb:
--------------------------------------------------------------------------------
1 | # Set up gems listed in the Gemfile.
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 |
4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
5 |
--------------------------------------------------------------------------------
/test/rails_app/db/migrate/20141016183619_create_users.rb:
--------------------------------------------------------------------------------
1 | class CreateUsers < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :users do |t|
4 | t.timestamps
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/test/rails_app/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path("../spring", __FILE__)
4 | rescue LoadError
5 | end
6 | require_relative '../config/boot'
7 | require 'rake'
8 | Rake.application.run
9 |
--------------------------------------------------------------------------------
/test/rails_app/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/test/rails_app/app/models/membership.rb:
--------------------------------------------------------------------------------
1 | class Membership < ApplicationRecord
2 | include SimonSays::Roleable
3 |
4 | belongs_to :user
5 | belongs_to :document
6 |
7 | has_roles :download, :fork, :edit, :delete
8 | end
9 |
--------------------------------------------------------------------------------
/test/rails_app/db/migrate/20190711184853_create_clients.rb:
--------------------------------------------------------------------------------
1 | class CreateClients < ActiveRecord::Migration[5.1]
2 | def change
3 | create_table :clients, primary_key: :client_id do |t|
4 | t.timestamps
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/test/rails_app/db/migrate/20160823220959_create_images.rb:
--------------------------------------------------------------------------------
1 | class CreateImages < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :images do |t|
4 | t.string :token
5 |
6 | t.timestamps
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/test/rails_app/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/test/rails_app/test/fixtures/admins.yml:
--------------------------------------------------------------------------------
1 | support:
2 | access_mask: 1
3 |
4 | content:
5 | access_mask: 2
6 |
7 | marketing:
8 | access_mask: 4
9 |
10 | all:
11 | access_mask: <%= (0..Admin::ACCESS.size - 1).map { |n| 2 ** n }.sum %>
12 |
--------------------------------------------------------------------------------
/test/rails_app/test/fixtures/memberships.yml:
--------------------------------------------------------------------------------
1 | mb1:
2 | user: bob
3 | document: alpha
4 | roles_mask: <%= (0..Membership::ROLES.size - 1).map { |n| 2 ** n }.sum %>
5 |
6 | mb2:
7 | user: bob
8 | document: beta
9 | roles_mask: 1
10 |
11 |
--------------------------------------------------------------------------------
/test/rails_app/db/migrate/20141016142638_create_admins.rb:
--------------------------------------------------------------------------------
1 | class CreateAdmins < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :admins do |t|
4 | t.integer :access_mask
5 |
6 | t.timestamps
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/test/rails_app/db/migrate/20141016183642_create_documents.rb:
--------------------------------------------------------------------------------
1 | class CreateDocuments < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :documents do |t|
4 | t.string :title
5 |
6 | t.timestamps
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/test/rails_app/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /Gemfile.lock
4 | /_yardoc/
5 | /coverage/
6 | /docs/
7 | /pkg/
8 | /spec/reports/
9 | /tmp/
10 | *.bundle
11 | *.so
12 | *.o
13 | *.a
14 | mkmf.log
15 | .ruby-version
16 |
17 | *.gem
18 |
19 | /test/rails_app/db/*.sqlite3
20 |
--------------------------------------------------------------------------------
/test/rails_app/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path("../spring", __FILE__)
4 | rescue LoadError
5 | end
6 | APP_PATH = File.expand_path('../../config/application', __FILE__)
7 | require_relative '../config/boot'
8 | require 'rails/commands'
9 |
--------------------------------------------------------------------------------
/test/rails_app/db/migrate/20141017140833_create_admin_reports.rb:
--------------------------------------------------------------------------------
1 | class CreateAdminReports < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :admin_reports do |t|
4 | t.string :title
5 |
6 | t.timestamps
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | cache: bundler
3 | before_install:
4 | - gem update --system
5 | - gem install bundler
6 | install: bundle install --jobs=3 --retry=3
7 | rvm:
8 | - "2.6.3"
9 | - "2.5.5"
10 | - "2.4.6"
11 | script:
12 | - bundle exec rake test
13 |
--------------------------------------------------------------------------------
/lib/generators/active_record/templates/migration.rb:
--------------------------------------------------------------------------------
1 | class SimonSaysAddTo<%= table_name.camelize %> < ActiveRecord::Migration<%= migration_version %>
2 | def change
3 | add_column :<%= table_name %>, :<%= role_attribute_name %>_mask, :integer, default: 0, null: false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/test/rails_app/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require File.expand_path('../config/application', __FILE__)
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/test/rails_app/app/controllers/admin/comm_reports_controller.rb:
--------------------------------------------------------------------------------
1 | class Admin::CommReportsController < ApplicationController
2 | find_and_authorize :report, :comms, with: :admin, namespace: :admin
3 |
4 | respond_to :json
5 |
6 | def show
7 | respond_with @report
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/test/rails_app/app/controllers/images_controller.rb:
--------------------------------------------------------------------------------
1 | class ImagesController < ApplicationController
2 | respond_to :json
3 |
4 | authenticate :user
5 |
6 | find_resource :image, find_attribute: :token, only: :show # any role
7 |
8 | def show
9 | respond_with @image
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/test/rails_app/db/migrate/20141016183633_create_memberships.rb:
--------------------------------------------------------------------------------
1 | class CreateMemberships < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :memberships do |t|
4 | t.references :user
5 | t.references :document
6 |
7 | t.integer :roles_mask, default: 0
8 |
9 | t.timestamps
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/test/rails_app/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # This file should contain all the record creation needed to seed the database with its default values.
2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
3 | #
4 | # Examples:
5 | #
6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
7 | # Mayor.create(name: 'Emanuel', city: cities.first)
8 |
--------------------------------------------------------------------------------
/test/rails_app/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | ENV['RAILS_ENV'] ||= 'test'
2 | require File.expand_path('../../config/environment', __FILE__)
3 | require 'rails/test_help'
4 |
5 | class ActiveSupport::TestCase
6 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
7 | fixtures :all
8 |
9 | # Add more helper methods to be used by all tests here...
10 | end
11 |
--------------------------------------------------------------------------------
/test/rails_app/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | RailsApp
5 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
6 | <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
7 | <%= csrf_meta_tags %>
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/rails_app/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = '1.0'
5 |
6 | # Precompile additional assets.
7 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
8 | # Rails.application.config.assets.precompile += %w( search.js )
9 |
--------------------------------------------------------------------------------
/lib/simon_says.rb:
--------------------------------------------------------------------------------
1 | require 'active_support'
2 | require 'active_support/core_ext'
3 |
4 | require "simon_says/version"
5 | require "simon_says/roleable"
6 | require "simon_says/authorizer"
7 |
8 | begin
9 | require 'i18n' unless defined? I18n
10 |
11 | if defined? I18n
12 | I18n.load_path += Dir[File.join(
13 | File.expand_path(File.join('..', 'config', 'locales'), __dir__), '*.yml'
14 | )]
15 | end
16 | rescue LoadError
17 | # do nothing
18 | end
19 |
--------------------------------------------------------------------------------
/test/rails_app/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require 'bundler/setup'
5 |
6 | $: << File.expand_path('../lib', __dir__) if ENV['DEBUG'] == 'true'
7 | require 'simon_says'
8 |
9 | # You can add fixtures and/or initialization code here to make experimenting
10 | # with your gem easier. You can also use a different console, if you like.
11 |
12 | # (If you use this, don't forget to add pry to your Gemfile!)
13 | # require "pry"
14 | # Pry.start
15 |
16 | require 'irb'
17 | IRB.start(__FILE__)
18 |
--------------------------------------------------------------------------------
/test/rails_app/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | require 'rails/all'
4 | require 'responders'
5 |
6 | # Require the gems listed in Gemfile, including any gems
7 | # you've limited to :test, :development, or :production.
8 | Bundler.require(*Rails.groups)
9 |
10 | module RailsApp
11 | class Application < Rails::Application
12 | end
13 | end
14 |
15 | unless defined? SimonSays
16 | $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)
17 |
18 | require 'simon_says'
19 | end
20 |
--------------------------------------------------------------------------------
/test/rails_app/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2 | #
3 | # If you find yourself ignoring temporary files generated by your text editor
4 | # or operating system, you probably want to add a global ignore instead:
5 | # git config --global core.excludesfile '~/.gitignore_global'
6 |
7 | # Ignore bundler config.
8 | /.bundle
9 |
10 | # Ignore the default SQLite database.
11 | /db/*.sqlite3
12 | /db/*.sqlite3-journal
13 |
14 | # Ignore all logfiles and tempfiles.
15 | /log/*.log
16 | /tmp
17 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 | require "rake/testtask"
3 | require 'rdoc/task'
4 |
5 | Rake::TestTask.new :test do |t|
6 | t.libs << 'test'
7 | t.pattern = 'test/**/*_test.rb'
8 | t.verbose = false
9 | end
10 |
11 | namespace :docs do
12 | RDoc::Task.new :generate do |rdoc|
13 | rdoc.title = "SimonSays RDOC"
14 | rdoc.main = "README.md"
15 |
16 | rdoc.rdoc_dir = "docs"
17 | rdoc.rdoc_files.include("README.md", "lib/**/*.rb")
18 |
19 | rdoc.options << "--all"
20 | end
21 | end
22 |
23 |
24 | task :default => :test
25 |
--------------------------------------------------------------------------------
/Guardfile:
--------------------------------------------------------------------------------
1 | # A sample Guardfile
2 | # More info at https://github.com/guard/guard#readme
3 |
4 | guard :minitest do
5 | # with Minitest::Unit
6 | watch(%r{^test/(.*)\/?(.*)_test\.rb$})
7 | watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}#{m[2]}_test.rb" }
8 | watch(%r{^test/test_helper\.rb$}) { 'test' }
9 |
10 | # watch test Rails app
11 | watch(%r{^test/rails_app/app/models/(.*)\.rb$}) { |m| "test/models/#{m[1]}_test.rb" }
12 | watch(%r{^test/rails_app/app/controllers/(.*)\.rb$}) { |m| "test/controllers/#{m[1]}_test.rb" }
13 | end
14 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | simon_says:
3 | denied:
4 | warning: 'Access denied:'
5 | array_connector:
6 | two_words_connector: ' or '
7 | last_word_connector: ', or '
8 |
9 | required:
10 | one: '%{list} is required'
11 | other: '%{list} are required'
12 |
13 | explanation:
14 | zero: '%{warning} %{required}; however, you have no %{role_attr} set'
15 | one: '%{warning} %{required}; however, you have %{actual} %{role_attr} set'
16 | other: '%{warning} %{required}; however, you have %{actual} %{role_attr} set'
17 |
--------------------------------------------------------------------------------
/test/rails_app/bin/spring:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # This file loads spring without using Bundler, in order to be fast
4 | # It gets overwritten when you run the `spring binstub` command
5 |
6 | unless defined?(Spring)
7 | require "rubygems"
8 | require "bundler"
9 |
10 | if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ spring \((.*?)\)$.*?^$/m)
11 | ENV["GEM_PATH"] = ([Bundler.bundle_path.to_s] + Gem.path).join(File::PATH_SEPARATOR)
12 | ENV["GEM_HOME"] = ""
13 | Gem.paths = ENV
14 |
15 | gem "spring", match[1]
16 | require "spring/binstub"
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/test/controllers/images_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class ImagesControllerTest < ActionController::TestCase
4 | setup do
5 | @image = images(:image_one)
6 |
7 | @controller.current_admin = users(:bob)
8 | end
9 |
10 | test 'get show with correct id parameter' do
11 | get :show, params: { id: @image.token }, format: :json
12 |
13 | assert_response :success
14 | end
15 |
16 | test 'get show with incorrect id parameter' do
17 | assert_raises ActiveRecord::RecordNotFound do
18 | get :show, params: { id: @image.id }, format: :json
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/test/rails_app/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/test/rails_app/README.rdoc:
--------------------------------------------------------------------------------
1 | == README
2 |
3 | This README would normally document whatever steps are necessary to get the
4 | application up and running.
5 |
6 | Things you may want to cover:
7 |
8 | * Ruby version
9 |
10 | * System dependencies
11 |
12 | * Configuration
13 |
14 | * Database creation
15 |
16 | * Database initialization
17 |
18 | * How to run the test suite
19 |
20 | * Services (job queues, cache servers, search engines, etc.)
21 |
22 | * Deployment instructions
23 |
24 | * ...
25 |
26 |
27 | Please feel free to use a different markup language if you do not plan to run
28 | rake doc:app.
29 |
--------------------------------------------------------------------------------
/test/rails_app/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | include SimonSays::Authorizer
3 |
4 | self.default_authorization_scope = :current_user
5 |
6 | # Prevent CSRF attacks by raising an exception.
7 | # For APIs, you may want to use :null_session instead.
8 | protect_from_forgery with: :exception
9 |
10 | # This would be provided by some authentication
11 | # system, such as Devise.
12 | attr_accessor :current_user, :current_admin
13 |
14 | def authenticate_admin!
15 | end # dummy method
16 |
17 | def authenticate_user!
18 | end # dummy method
19 | end
20 |
--------------------------------------------------------------------------------
/test/rails_app/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite version 3.x
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem 'sqlite3'
6 | #
7 | default: &default
8 | adapter: sqlite3
9 | pool: 5
10 | timeout: 5000
11 |
12 | development:
13 | <<: *default
14 | database: db/development.sqlite3
15 |
16 | # Warning: The database defined as "test" will be erased and
17 | # re-generated from your development database when you run "rake".
18 | # Do not set this db to the same as development or production.
19 | test:
20 | <<: *default
21 | database: db/test.sqlite3
22 |
23 | production:
24 | <<: *default
25 | database: db/production.sqlite3
26 |
--------------------------------------------------------------------------------
/lib/generators/active_record/simon_says_generator.rb:
--------------------------------------------------------------------------------
1 | require 'rails/generators/active_record'
2 |
3 | module ActiveRecord
4 | module Generators
5 | class SimonSaysGenerator < ActiveRecord::Generators::Base
6 | source_root File.expand_path("../templates", __FILE__)
7 |
8 | def copy_simon_says_migration
9 | migration_template "migration.rb", "db/migrate/simon_says_add_to_#{table_name}.rb"
10 | end
11 |
12 | private
13 |
14 | def migration_version
15 | "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]" if Rails.version >= '5.0.0'
16 | end
17 |
18 | def role_attribute_name
19 | args.first || 'roles'
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/test/rails_app/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/test/rails_app/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # To learn more, please read the Rails Internationalization guide
20 | # available at http://guides.rubyonrails.org/i18n.html.
21 |
22 | en:
23 | hello: "Hello world"
24 |
--------------------------------------------------------------------------------
/test/rails_app/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file.
9 | //
10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require jquery
14 | //= require jquery_ujs
15 | //= require turbolinks
16 | //= require_tree .
17 |
--------------------------------------------------------------------------------
/test/rails_app/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any styles
10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11 | * file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | */
16 |
--------------------------------------------------------------------------------
/test/rails_app/app/controllers/admin/reports_controller.rb:
--------------------------------------------------------------------------------
1 | class Admin::ReportsController < ApplicationController
2 | respond_to :json
3 |
4 | self.default_authorization_scope = :current_admin
5 |
6 | authorize :support
7 | find_resource :report, namespace: :admin, except: [:index, :new, :create]
8 |
9 | def index
10 | @reports = Admin::Report.all
11 |
12 | respond_with @reports
13 | end
14 |
15 | def create
16 | @report = Admin::Report.create(report_params)
17 |
18 | respond_with @report
19 | end
20 |
21 | def show
22 | respond_with @report
23 | end
24 |
25 | def update
26 | @report.update report_params
27 |
28 | respond_with @report
29 | end
30 |
31 | def destroy
32 | @report.destroy
33 |
34 | respond_with @report
35 | end
36 |
37 | protected
38 |
39 | def report_params
40 | params.require(:report).permit(:title)
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/test/rails_app/config/secrets.yml:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rake secret` to generate a secure secret key.
9 |
10 | # Make sure the secrets in this file are kept private
11 | # if you're sharing your code publicly.
12 |
13 | development:
14 | secret_key_base: a34174313988759e4ff645ff356d9ae608b20f5667647b27291ce0d41192eb8d7ffc2b9fecce349b505f1404ab4f3865e4c8df8a5cfbab197ce15c9a6ad07411
15 |
16 | test:
17 | secret_key_base: ea9a236945f8bcce36867b44e3d68793758e78b03a5a0826b234c057d3ab59bf2b4de1ff01f13c15c7cba4da4dfce304fcf1b692a322b07908a1c26ec18a0323
18 |
19 | # Do not keep production secrets in the repository,
20 | # instead read values from the environment.
21 | production:
22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
23 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Michael Coyne
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/test/rails_app/app/controllers/documents_controller.rb:
--------------------------------------------------------------------------------
1 | class DocumentsController < ApplicationController
2 | respond_to :json
3 |
4 | authenticate :user
5 |
6 | find_and_authorize :document, through: :memberships, only: :show # any role
7 | find_and_authorize :document, :edit, through: :memberships, only: [:edit, :update]
8 | find_and_authorize :document, :delete, through: :memberships, only: :destroy
9 | find_and_authorize :document, :download, through: :memberships, only: :send_file
10 |
11 | def index
12 | @documents = current_user.documents
13 |
14 | respond_with @documents
15 | end
16 |
17 | def create
18 | @document = current_user.documents.create(document_params)
19 |
20 | respond_with @document
21 | end
22 |
23 | def show
24 | respond_with @document
25 | end
26 |
27 | def update
28 | @document.update document_params
29 |
30 | respond_with @document
31 | end
32 |
33 | def destroy
34 | @document.destroy
35 |
36 | respond_with @document
37 | end
38 |
39 | def send_file
40 | send_data @document.title, filename: 'doc.txt'
41 | end
42 |
43 | protected
44 |
45 | def document_params
46 | params.require(:document).permit(:title)
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/simon_says.gemspec:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | lib = File.expand_path('../lib', __FILE__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'simon_says/version'
5 |
6 | Gem::Specification.new do |spec|
7 | spec.name = "simon_says"
8 | spec.version = SimonSays::VERSION
9 | spec.authors = ["Michael Coyne", "Cameron Craig", "SimplyBuilt"]
10 | spec.email = ["mikeycgto@gmail.com"]
11 | spec.summary = %q{Light-weight, declarative authorization and access control for Rails}
12 | spec.description = %q{This gem is a simple, easy-to-use declarative role-based access control system for Rails}
13 | spec.homepage = "https://github.com/SimplyBuilt/SimonSays"
14 | spec.license = "MIT"
15 |
16 | spec.files = `git ls-files -z`.split("\x0")
17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19 | spec.require_paths = ["lib"]
20 |
21 | spec.add_dependency "activesupport", ">= 4.0"
22 |
23 | spec.add_development_dependency "bundler", "~> 2.0"
24 | spec.add_development_dependency "rake", "~> 10.0"
25 | spec.add_development_dependency "rails", ">= 4.0", "< 5.2"
26 | spec.add_development_dependency "responders", "~> 2.0"
27 | spec.add_development_dependency "mocha", "~> 1.1"
28 | end
29 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | require "minitest/autorun"
2 | require 'mocha/minitest'
3 |
4 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
5 | require 'simon_says' # HELLO SIMON
6 |
7 | # Load test/rails_app
8 | ENV["RAILS_ENV"] = "test"
9 |
10 | require File.expand_path("../rails_app/config/environment.rb", __FILE__)
11 | require "rails/test_help"
12 |
13 | Rails.backtrace_cleaner.remove_silencers!
14 |
15 | # Load support files
16 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
17 |
18 | # Load fixtures from the engine
19 | ActiveSupport::TestCase.fixture_path = File.expand_path("../rails_app/test/fixtures", __FILE__)
20 | ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
21 |
22 | # Make ActiveRecord happy
23 | ActiveRecord::Base.logger = Logger.new(nil)
24 | ActiveRecord::Migration.verbose = false
25 |
26 | class ActiveSupport::TestCase
27 | include ActiveRecord::TestFixtures
28 |
29 | fixtures :all
30 |
31 | def create_test_table(name, &block)
32 | with_migration { |m| m.create_table name, &block }
33 | end
34 |
35 | def drop_test_table(name, opts = {})
36 | with_migration { |m| m.drop_table name, opts }
37 | end
38 |
39 | protected
40 |
41 | def with_migration
42 | ActiveRecord::Migration.tap do |m|
43 | m.verbose = false
44 | yield m
45 | m.verbose = true
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/test/rails_app/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports and disable caching.
13 | config.consider_all_requests_local = true
14 | config.action_controller.perform_caching = false
15 |
16 | # Don't care if the mailer can't send.
17 | config.action_mailer.raise_delivery_errors = false
18 |
19 | # Print deprecation notices to the Rails logger.
20 | config.active_support.deprecation = :log
21 |
22 | # Raise an error on page load if there are pending migrations.
23 | config.active_record.migration_error = :page_load
24 |
25 | # Debug mode disables concatenation and preprocessing of assets.
26 | # This option may cause significant delays in view rendering with a large
27 | # number of complex assets.
28 | config.assets.debug = true
29 |
30 | # Adds additional error checking when serving assets at runtime.
31 | # Checks for improperly declared sprockets dependencies.
32 | # Raises helpful error messages.
33 | config.assets.raise_runtime_errors = true
34 |
35 | # Raises error for missing translations
36 | # config.action_view.raise_on_missing_translations = true
37 | end
38 |
--------------------------------------------------------------------------------
/test/rails_app/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/test/rails_app/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/test/rails_app/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/test/rails_app/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = true
14 |
15 | # Configure static asset server for tests with Cache-Control for performance.
16 | config.public_file_server.enabled = true
17 | config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=3600' }
18 |
19 | config.active_support.test_order = :random
20 |
21 | # Show full error reports and disable caching.
22 | config.consider_all_requests_local = true
23 | config.action_controller.perform_caching = false
24 |
25 | # Raise exceptions instead of rendering exception templates.
26 | config.action_dispatch.show_exceptions = false
27 |
28 | # Disable request forgery protection in test environment.
29 | config.action_controller.allow_forgery_protection = false
30 |
31 | # Tell Action Mailer not to deliver emails to the real world.
32 | # The :test delivery method accumulates sent emails in the
33 | # ActionMailer::Base.deliveries array.
34 | config.action_mailer.delivery_method = :test
35 |
36 | # Print deprecation notices to the stderr.
37 | config.active_support.deprecation = :stderr
38 |
39 | # Raises error for missing translations
40 | # config.action_view.raise_on_missing_translations = true
41 |
42 | # Use transactional fixtures
43 | config.use_transactional_fixtures = true
44 |
45 | end
46 |
--------------------------------------------------------------------------------
/test/controllers/documents_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class DocumentsControllerTest < ActionController::TestCase
4 | setup do
5 | @alpha = documents(:alpha)
6 | @beta = documents(:beta)
7 |
8 | @bob = users(:bob)
9 | @jim = users(:jim)
10 | end
11 |
12 | def as_bob!
13 | @controller.current_user = @bob
14 | end
15 |
16 | def as_jim!
17 | @controller.current_user = @jim
18 | end
19 |
20 | test "show" do
21 | as_bob!
22 |
23 | get :show, params: { id: @alpha.id }, format: :json
24 |
25 | assert_response :success
26 | end
27 |
28 | test "show without through relationship" do
29 | as_jim!
30 |
31 | assert_raises ActiveRecord::RecordNotFound do
32 | get :show, params: { id: @alpha.id }, format: :json
33 | end
34 | end
35 |
36 | test "update with access" do
37 | as_bob!
38 |
39 | patch :update, params: { id: @alpha.id, document: { title: 'Test' } }, format: :json
40 |
41 | assert_response :success
42 | end
43 |
44 | test "update without access" do
45 | as_bob!
46 |
47 | assert_raises SimonSays::Authorizer::Denied do
48 | patch :update, params: { id: @beta.id, document: { title: 'Test' } }, format: :json
49 | end
50 | end
51 |
52 | test "update without through relationship" do
53 | as_jim!
54 |
55 | assert_raises ActiveRecord::RecordNotFound do
56 | patch :update, params: { id: @alpha.id, document: { title: 'Test' } }, format: :json
57 | end
58 | end
59 |
60 | test "destroy with access" do
61 | as_bob!
62 |
63 | assert_difference 'Document.count', -1 do
64 | delete :destroy, params: { id: @alpha.id }, format: :json
65 | end
66 | end
67 |
68 | test "destroy without access" do
69 | as_bob!
70 |
71 | assert_raises SimonSays::Authorizer::Denied do
72 | delete :destroy, params: { id: @beta.id }, format: :json
73 | end
74 | end
75 |
76 | test "destroy without through relationship" do
77 | as_jim!
78 |
79 | assert_raises ActiveRecord::RecordNotFound do
80 | delete :destroy, params: { id: @beta.id }, format: :json
81 | end
82 | end
83 | end
84 |
--------------------------------------------------------------------------------
/test/rails_app/db/schema.rb:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from the current state of the database. Instead
2 | # of editing this file, please use the migrations feature of Active Record to
3 | # incrementally modify your database, and then regenerate this schema definition.
4 | #
5 | # Note that this schema.rb definition is the authoritative source for your
6 | # database schema. If you need to create the application database on another
7 | # system, you should be using db:schema:load, not running all the migrations
8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9 | # you'll amass, the slower it'll run and the greater likelihood for issues).
10 | #
11 | # It's strongly recommended that you check this file into your version control system.
12 |
13 | ActiveRecord::Schema.define(version: 20190711184853) do
14 |
15 | create_table "admin_reports", force: :cascade do |t|
16 | t.string "title"
17 | t.datetime "created_at", null: false
18 | t.datetime "updated_at", null: false
19 | end
20 |
21 | create_table "admins", force: :cascade do |t|
22 | t.integer "access_mask"
23 | t.datetime "created_at", null: false
24 | t.datetime "updated_at", null: false
25 | end
26 |
27 | create_table "clients", primary_key: "client_id", force: :cascade do |t|
28 | t.datetime "created_at", null: false
29 | t.datetime "updated_at", null: false
30 | end
31 |
32 | create_table "documents", force: :cascade do |t|
33 | t.string "title"
34 | t.datetime "created_at", null: false
35 | t.datetime "updated_at", null: false
36 | end
37 |
38 | create_table "images", force: :cascade do |t|
39 | t.string "token"
40 | t.datetime "created_at", null: false
41 | t.datetime "updated_at", null: false
42 | end
43 |
44 | create_table "memberships", force: :cascade do |t|
45 | t.integer "user_id"
46 | t.integer "document_id"
47 | t.integer "roles_mask", default: 0
48 | t.datetime "created_at", null: false
49 | t.datetime "updated_at", null: false
50 | t.index ["document_id"], name: "index_memberships_on_document_id"
51 | t.index ["user_id"], name: "index_memberships_on_user_id"
52 | end
53 |
54 | create_table "users", force: :cascade do |t|
55 | t.datetime "created_at", null: false
56 | t.datetime "updated_at", null: false
57 | end
58 |
59 | end
60 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Code of Conduct
2 |
3 | As contributors and maintainers of this project, and in the interest of
4 | fostering an open and welcoming community, we pledge to respect all people who
5 | contribute through reporting issues, posting feature requests, updating
6 | documentation, submitting pull requests or patches, and other activities.
7 |
8 | We are committed to making participation in this project a harassment-free
9 | experience for everyone, regardless of level of experience, gender, gender
10 | identity and expression, sexual orientation, disability, personal appearance,
11 | body size, race, ethnicity, age, religion, or nationality.
12 |
13 | Examples of unacceptable behavior by participants include:
14 |
15 | * The use of sexualized language or imagery
16 | * Personal attacks
17 | * Trolling or insulting/derogatory comments
18 | * Public or private harassment
19 | * Publishing other's private information, such as physical or electronic
20 | addresses, without explicit permission
21 | * Other unethical or unprofessional conduct
22 |
23 | Project maintainers have the right and responsibility to remove, edit, or
24 | reject comments, commits, code, wiki edits, issues, and other contributions
25 | that are not aligned to this Code of Conduct, or to ban temporarily or
26 | permanently any contributor for other behaviors that they deem inappropriate,
27 | threatening, offensive, or harmful.
28 |
29 | By adopting this Code of Conduct, project maintainers commit themselves to
30 | fairly and consistently applying these principles to every aspect of managing
31 | this project. Project maintainers who do not follow or enforce the Code of
32 | Conduct may be permanently removed from the project team.
33 |
34 | This code of conduct applies both within project spaces and in public spaces
35 | when an individual is representing the project or its community.
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
38 | reported by contacting a project maintainer at mikeycgto@gmail.com. All
39 | complaints will be reviewed and investigated and will result in a response that
40 | is deemed necessary and appropriate to the circumstances. Maintainers are
41 | obligated to maintain confidentiality with regard to the reporter of an
42 | incident.
43 |
44 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45 | version 1.3.0, available at
46 | [http://contributor-covenant.org/version/1/3/0/][version]
47 |
48 | [homepage]: http://contributor-covenant.org
49 | [version]: http://contributor-covenant.org/version/1/3/0/
--------------------------------------------------------------------------------
/test/controllers/admin/reports_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Admin::ReportsControllerTest < ActionController::TestCase
4 | setup do
5 | @support = admins(:support)
6 | @marketing = admins(:marketing)
7 | end
8 |
9 | test "index with access" do
10 | @controller.current_admin = @support
11 |
12 | get :index, params: { format: :json }
13 |
14 | assert_response :success
15 | end
16 |
17 | test "index without access" do
18 | @controller.current_admin = @marketing
19 |
20 | assert_raises SimonSays::Authorizer::Denied do
21 | get :index, params: { format: :json }
22 | end
23 | end
24 |
25 | test "create with access" do
26 | @controller.current_admin = @support
27 |
28 | assert_difference 'Admin::Report.count' do
29 | post :create, params: { report: { title: 'Test' } }, format: :json
30 | end
31 | end
32 |
33 | test "create without access" do
34 | @controller.current_admin = @marketing
35 |
36 | assert_raises SimonSays::Authorizer::Denied do
37 | post :create, params: { report: { title: 'Test' } }, format: :json
38 | end
39 | end
40 |
41 | test "show with access" do
42 | @controller.current_admin = @support
43 |
44 | get :show, params: { id: admin_reports(:report_one) }, format: :json
45 |
46 | assert_response :success
47 | end
48 |
49 | test "show without access" do
50 | @controller.current_admin = @marketing
51 |
52 | assert_raises SimonSays::Authorizer::Denied do
53 | get :show, params: { id: admin_reports(:report_one) }, format: :json
54 | end
55 | end
56 |
57 | test "update with access" do
58 | @controller.current_admin = @support
59 |
60 | patch :show, params: { id: admin_reports(:report_one), report: { title: 'Test' } }, format: :json
61 |
62 | assert_response :success
63 | end
64 |
65 | test "update without access" do
66 | @controller.current_admin = @marketing
67 |
68 | assert_raises SimonSays::Authorizer::Denied do
69 | patch :show, params: { id: admin_reports(:report_one), report: { title: 'Test' } }, format: :json
70 | end
71 | end
72 |
73 | test "destroy with access" do
74 | @controller.current_admin = @support
75 |
76 | assert_difference 'Admin::Report.count', -1 do
77 | delete :destroy, params: { id: admin_reports(:report_one) }, format: :json
78 | end
79 | end
80 |
81 | test "destroy without access" do
82 | @controller.current_admin = @marketing
83 |
84 | assert_raises SimonSays::Authorizer::Denied do
85 | delete :destroy, params: { id: admin_reports(:report_one) }, format: :json
86 | end
87 | end
88 | end
89 |
--------------------------------------------------------------------------------
/test/simon_says/authorizer_denied_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class AuthorizerAccessDeniedTest < ActiveSupport::TestCase
4 | test 'one required and none set' do
5 | err = SimonSays::Authorizer::Denied.new(:roles, %w(foo), [])
6 |
7 | assert_equal 'Access denied: foo is required; however, you have no roles set', err.message
8 | end
9 |
10 | test 'two required and none set' do
11 | err = SimonSays::Authorizer::Denied.new(:roles, %w(foo bar), [])
12 |
13 | assert_equal 'Access denied: foo or bar are required; however, you have no roles set', err.message
14 | end
15 |
16 | test 'three required and none set' do
17 | err = SimonSays::Authorizer::Denied.new(:roles, %w(foo bar baz), [])
18 |
19 | assert_equal 'Access denied: foo, bar, or baz are required; however, you have no roles set', err.message
20 | end
21 |
22 | test 'one required and one set' do
23 | err = SimonSays::Authorizer::Denied.new(:roles, %w(foo), %w(qux))
24 |
25 | assert_equal 'Access denied: foo is required; however, you have qux roles set', err.message
26 | end
27 |
28 | test 'two required and one set' do
29 | err = SimonSays::Authorizer::Denied.new(:roles, %w(foo bar), %w(qux))
30 |
31 | assert_equal 'Access denied: foo or bar are required; however, you have qux roles set', err.message
32 | end
33 |
34 | test 'three required and one set' do
35 | err = SimonSays::Authorizer::Denied.new(:roles, %w(foo bar baz), %w(qux))
36 |
37 | assert_equal 'Access denied: foo, bar, or baz are required; however, you have qux roles set', err.message
38 | end
39 |
40 | test 'one required and two set' do
41 | err = SimonSays::Authorizer::Denied.new(:roles, %w(foo), %w(qux quux))
42 |
43 | assert_equal 'Access denied: foo is required; however, you have qux and quux roles set', err.message
44 | end
45 |
46 | test 'two required and two set' do
47 | err = SimonSays::Authorizer::Denied.new(:roles, %w(foo bar), %w(qux quux))
48 |
49 | assert_equal 'Access denied: foo or bar are required; however, you have qux and quux roles set', err.message
50 | end
51 |
52 | test 'three required and two set' do
53 | err = SimonSays::Authorizer::Denied.new(:roles, %w(foo bar baz), %w(qux quux))
54 |
55 | assert_equal 'Access denied: foo, bar, or baz are required; however, you have qux and quux roles set', err.message
56 | end
57 |
58 | test 'one required and three set' do
59 | err = SimonSays::Authorizer::Denied.new(:roles, %w(foo), %w(qux quux quuz))
60 |
61 | assert_equal 'Access denied: foo is required; however, you have qux, quux, and quuz roles set', err.message
62 | end
63 |
64 | test 'two required and three set' do
65 | err = SimonSays::Authorizer::Denied.new(:roles, %w(foo bar), %w(qux quux quuz))
66 |
67 | assert_equal 'Access denied: foo or bar are required; however, you have qux, quux, and quuz roles set', err.message
68 | end
69 |
70 | test 'three required and three set' do
71 | err = SimonSays::Authorizer::Denied.new(:roles, %w(foo bar baz), %w(qux quux quuz))
72 |
73 | assert_equal 'Access denied: foo, bar, or baz are required; however, you have qux, quux, and quuz roles set', err.message
74 | end
75 | end
76 |
--------------------------------------------------------------------------------
/test/rails_app/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both threaded web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application
18 | # Add `rack-cache` to your Gemfile before enabling this.
19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid.
20 | # config.action_dispatch.rack_cache = true
21 |
22 | # Disable Rails's static asset server (Apache or nginx will already do this).
23 | config.serve_static_assets = false
24 |
25 | # Compress JavaScripts and CSS.
26 | config.assets.js_compressor = :uglifier
27 | # config.assets.css_compressor = :sass
28 |
29 | # Do not fallback to assets pipeline if a precompiled asset is missed.
30 | config.assets.compile = false
31 |
32 | # Generate digests for assets URLs.
33 | config.assets.digest = true
34 |
35 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
36 |
37 | # Specifies the header that your server uses for sending files.
38 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
39 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
40 |
41 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
42 | # config.force_ssl = true
43 |
44 | # Set to :debug to see everything in the log.
45 | config.log_level = :info
46 |
47 | # Prepend all log lines with the following tags.
48 | # config.log_tags = [ :subdomain, :uuid ]
49 |
50 | # Use a different logger for distributed setups.
51 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
52 |
53 | # Use a different cache store in production.
54 | # config.cache_store = :mem_cache_store
55 |
56 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
57 | # config.action_controller.asset_host = "http://assets.example.com"
58 |
59 | # Ignore bad email addresses and do not raise email delivery errors.
60 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
61 | # config.action_mailer.raise_delivery_errors = false
62 |
63 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
64 | # the I18n.default_locale when a translation cannot be found).
65 | config.i18n.fallbacks = true
66 |
67 | # Send deprecation notices to registered listeners.
68 | config.active_support.deprecation = :notify
69 |
70 | # Disable automatic flushing of the log to improve performance.
71 | # config.autoflush_log = false
72 |
73 | # Use default logging formatter so that PID and timestamp are not suppressed.
74 | config.log_formatter = ::Logger::Formatter.new
75 |
76 | # Do not dump schema after migrations.
77 | config.active_record.dump_schema_after_migration = false
78 | end
79 |
--------------------------------------------------------------------------------
/lib/simon_says/roleable.rb:
--------------------------------------------------------------------------------
1 | module SimonSays
2 | module Roleable
3 | extend ActiveSupport::Concern
4 |
5 | module ClassMethods
6 | # Provides a declarative method to introduce role based
7 | # access controller through a give integer mask.
8 | #
9 | # By default it'll use an attributed named +role_mask+. You can
10 | # use the +:as+ option to change the prefix for the +_mask+
11 | # attribute. This will also alter the names of the dynamically
12 | # generated methods.
13 | #
14 | # Several methods are dynamically genreated when calling +has_roles+.
15 | # The methods generated include a setter, a getter and a predicate
16 | # method
17 | #
18 | # @param [Array] roles array of role symbols or strings
19 | # @param [Hash] opts options hash
20 | # @param opts [Symbol] :as alternative prefix name instead of "role"
21 | #
22 | # @example Detailed example:
23 | # class User < ActiveRecord::Base
24 | # include SimonSays::Roleable
25 | #
26 | # has_roles :read, :write, :delete
27 | # end
28 | #
29 | # class Editor < ActiveRecord::Base
30 | # include SimonSays::Roleable
31 | #
32 | # has_roles :create, :update, :publish, as: :access
33 | # end
34 | #
35 | # User.new.roles
36 | # => []
37 | #
38 | # User.new(roles: :read).roles
39 | # => [:read]
40 | #
41 | # User.new.tap { |u| u.roles = :write, :read }.roles
42 | # => [:read, :write]
43 | #
44 | # User.new(roles: [:read, :write]).has_roles? :read, :write
45 | # => true
46 | #
47 | # User.new(roles: :read).has_role? :read
48 | # => true
49 | #
50 | # Editor.new(access: %w[create update publish]).access
51 | # => [:create, :update, :publish]
52 | #
53 | # Editor.new(access: :publish).has_access? :create
54 | # => false
55 | #
56 | def has_roles *roles
57 | options = roles.extract_options!
58 |
59 | name = (options[:as] || :roles).to_s
60 | singular = name.singularize
61 | const = name.upcase
62 |
63 | roles.map!(&:to_sym)
64 |
65 | class_eval <<-RUBY_EVAL, __FILE__, __LINE__
66 | #{const} = %i[#{roles * ' '}]
67 |
68 | def #{name}=(args)
69 | args = [args] unless Array === args
70 |
71 | args.compact!
72 | args.map!(&:to_sym)
73 |
74 | self.#{name}_mask = (args & #{const}).map { |i| 2 ** #{const}.index(i) }.sum
75 | end
76 |
77 | def #{name}
78 | #{const}.reject { |i| ((#{name}_mask || 0) & 2 ** #{const}.index(i)).zero? }.tap(&:freeze)
79 | end
80 |
81 | def has_#{name}?(*args)
82 | (#{name} & args).size > 0
83 | end
84 |
85 | def self.role_attribute_name
86 | :#{name}
87 | end
88 | RUBY_EVAL
89 |
90 | if name != singular
91 | class_eval <<-RUBY_EVAL
92 | alias has_#{singular}? has_#{name}?
93 | RUBY_EVAL
94 | end
95 |
96 | Roleable.define_orm_scope self, "with_#{name}" do |*args|
97 | clause = "#{name}_mask & ?"
98 | values = Roleable.cast_roles_to_ints(roles, *args)
99 |
100 | query = where(clause, values.shift)
101 | query = query.or(where(clause, values.shift)) until values.empty?
102 | query
103 | end
104 |
105 | Roleable.define_orm_scope self, "with_all_#{name}" do |*args|
106 | clause = "#{name}_mask & ?"
107 | values = Roleable.cast_roles_to_ints(roles, *args)
108 |
109 | query = where(clause, values.shift)
110 | query = query.where(clause, values.shift) until values.empty?
111 | query
112 | end
113 | end
114 | end
115 |
116 | def self.cast_roles_to_ints(defined_roles, *args)
117 | values = args.map do |arg|
118 | index = defined_roles.index(arg)
119 | index ? 2 ** index : nil
120 | end
121 |
122 | values.tap(&:flatten!)
123 | end
124 |
125 | def self.define_orm_scope(model, name, &block)
126 | if defined? ActiveRecord
127 | model.scope name, block
128 | elsif defined? Sequel
129 | model.dataset_module { subset name, &block }
130 | end
131 | end
132 | end
133 | end
134 |
--------------------------------------------------------------------------------
/test/simon_says/roleable_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class RoleableTest < ActiveSupport::TestCase
4 | test "adds constant" do
5 | assert_equal [:download, :fork, :edit, :delete], Membership::ROLES
6 | end
7 |
8 | test "adds constant with :as option" do
9 | assert_equal [:support, :content, :marketing], Admin::ACCESS
10 | end
11 |
12 | test "adds roles method" do
13 | assert_equal Membership::ROLES, memberships(:mb1).roles
14 | end
15 |
16 | test "adds reader method with :as option" do
17 | assert_equal Admin::ACCESS, admins(:all).access
18 | end
19 |
20 | test "set roles with multiple symbols" do
21 | mbr = memberships(:mb2)
22 | mbr.roles = :download, :fork
23 |
24 | assert_equal [:download, :fork], mbr.roles
25 | end
26 |
27 | test "set roles with multiple symbols with :as option" do
28 | adm = admins(:support)
29 | adm.access = :support, :marketing
30 |
31 | assert_equal [:support, :marketing], adm.access
32 | end
33 |
34 | test "set roles with single symbol" do
35 | mbr = memberships(:mb2)
36 | mbr.roles = :download
37 |
38 | assert_equal [:download], mbr.roles
39 | end
40 |
41 | test "set roles with single symbol with :as option" do
42 | adm = admins(:support)
43 | adm.access = :marketing
44 |
45 | assert_equal [:marketing], adm.access
46 | end
47 |
48 | test "set roles with single string" do
49 | mbr = memberships(:mb2)
50 | mbr.roles = 'download'
51 |
52 | assert_equal [:download], mbr.roles
53 | end
54 |
55 | test "set roles with single string with :as option" do
56 | adm = admins(:support)
57 | adm.access = 'marketing'
58 |
59 | assert_equal [:marketing], adm.access
60 | end
61 |
62 | test "set roles with multiple strings" do
63 | mbr = memberships(:mb2)
64 | mbr.roles = 'download', 'fork'
65 |
66 | assert_equal [:download, :fork], mbr.roles
67 | end
68 |
69 | test "set roles with multiples strings with :as option" do
70 | adm = admins(:support)
71 | adm.access = 'marketing', 'content'
72 |
73 | assert_equal [:content, :marketing], adm.access
74 | end
75 |
76 | test 'ignores unknown roles' do
77 | mbr = memberships(:mb2)
78 | mbr.roles = :download, :unknown
79 |
80 | assert_equal [:download], mbr.roles
81 | end
82 |
83 | test 'handles out of order roles' do
84 | mbr = memberships(:mb2)
85 | mbr.roles = Membership::ROLES.reverse
86 |
87 | assert_equal Membership::ROLES, mbr.roles
88 | end
89 |
90 | test "has_roles? without any roles" do
91 | mbr = memberships(:mb1)
92 | mbr.roles = nil
93 |
94 | assert_equal false, mbr.has_roles?(:download)
95 | end
96 |
97 | test "has_roles? with one role" do
98 | mbr = memberships(:mb1)
99 | mbr.roles = :download
100 |
101 | assert_equal true, mbr.has_roles?(:download)
102 | end
103 |
104 | test "has_roles? with multiple role" do
105 | mbr = memberships(:mb1)
106 | mbr.roles = :download, :fork, :edit
107 |
108 | assert_equal true, mbr.has_roles?(:download, :fork, :edit)
109 | end
110 |
111 | test "has_access? without any roles" do
112 | adm = admins(:support)
113 | adm.access = nil
114 |
115 | assert_equal false, adm.has_access?(:support)
116 | end
117 |
118 | test "has_access? with one role" do
119 | adm = admins(:support)
120 | adm.access = :marketing
121 |
122 | assert_equal true, adm.has_access?(:marketing)
123 | end
124 |
125 | test "has_access? with multiple role" do
126 | adm = admins(:support)
127 | adm.access = :support, :content, :marketing
128 |
129 | assert_equal true, adm.has_access?(:support, :content, :marketing)
130 | end
131 |
132 | test "named scope with_roles" do
133 | assert_equal [2, 1], [
134 | Membership.with_roles(:download).count,
135 | Membership.with_roles(:edit, :delete).count,
136 | ]
137 | end
138 |
139 | test "named scope with_access" do
140 | assert_equal [2, 3, 4], [
141 | Admin.with_access(:content).count,
142 | Admin.with_access(:content, :marketing).count,
143 | Admin.with_access(:content, :marketing, :support).count
144 | ]
145 | end
146 |
147 | test "named scope with_all_roles" do
148 | memberships(:mb1).update roles: %i[download fork edit]
149 | memberships(:mb2).update roles: %i[download fork]
150 |
151 | assert_equal [2, 0], [
152 | Membership.with_all_roles(:download, :fork).count,
153 | Membership.with_all_roles(:download, :fork, :edit, :delete).count
154 | ]
155 | end
156 |
157 | test "named scope with_all_access" do
158 | Admin.create(access: %i[content marketing])
159 |
160 | assert_equal [2, 1], [
161 | Admin.with_all_access(:content, :marketing).count,
162 | Admin.with_all_access(:content, :marketing, :support).count
163 | ]
164 | end
165 |
166 | test "Membership defines role_attribute_name" do
167 | assert_equal :roles, Membership.role_attribute_name
168 | end
169 |
170 | test "Admin defines role_attribute_name" do
171 | assert_equal :access, Admin.role_attribute_name
172 | end
173 | end
174 |
--------------------------------------------------------------------------------
/test/simon_says/authorizer_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class AuthorizerTest < ActiveSupport::TestCase
4 | setup do
5 | @controller = Class.new(ApplicationController) do
6 | # These would be defined by Devise or some authenication library
7 | attr_accessor :current_user, :current_admin, :sites
8 | attr_reader :params
9 |
10 | def params=(params)
11 | @params = params.with_indifferent_access
12 | end
13 |
14 | # shortcut to read instance variables
15 | def [](ivar_name)
16 | instance_variable_get :"@#{ivar_name}"
17 | end
18 |
19 | def authenticate_admin! # dummy method
20 | end
21 |
22 | def authenticate_user! # dummy method
23 | end
24 |
25 | include SimonSays::Authorizer
26 | end.new
27 |
28 | @controller.current_user = users(:bob)
29 | @controller.params = { id: documents(:alpha).id }
30 | end
31 |
32 | def with_params(params)
33 | default_params = @controller.params
34 | @controller.params = params
35 |
36 | yield
37 |
38 | ensure
39 | @controller.params = default_params
40 | end
41 |
42 | def with_default_find_attribute(callalbe)
43 | @controller.class.default_find_attribute = callalbe
44 |
45 | yield
46 |
47 | ensure
48 | @controller.class.default_find_attribute = nil
49 | end
50 |
51 | test "find_resource" do
52 | @controller.find_resource :document
53 |
54 | assert_equal documents(:alpha), @controller[:document]
55 | end
56 |
57 | test "find_resource with class_name" do
58 | @controller.find_resource :document, class_name: 'document'
59 |
60 | assert_equal documents(:alpha), @controller[:document]
61 | end
62 |
63 | test "find_resource with default scope and through" do
64 | @controller.class.default_authorization_scope = :current_user
65 | @controller.current_user = users(:bob)
66 |
67 | @controller.find_resource :document, through: :memberships
68 |
69 | assert_equal documents(:alpha), @controller[:document]
70 | end
71 |
72 | test "find_resource with from" do
73 | @controller.instance_variable_set :@user, users(:bob)
74 |
75 | @controller.find_resource :document, from: :user
76 |
77 | assert_equal documents(:alpha), @controller[:document]
78 | end
79 |
80 | test "find_resource with namespace" do
81 | @controller.current_admin = admins(:support)
82 | @controller.params = { id: admin_reports(:report_one).id }
83 |
84 | @controller.find_resource :report, namespace: :admin
85 |
86 | assert_equal admin_reports(:report_one), @controller[:report]
87 | end
88 |
89 | test "find_resource raises RecordNotFound" do
90 | assert_raises ActiveRecord::RecordNotFound do
91 | @controller.params = { id: -1 }
92 | @controller.find_resource :document
93 | end
94 | end
95 |
96 | test "find_resource raises RecordNotFound with default scope and through" do
97 | @controller.class.default_authorization_scope = :current_user
98 | @controller.current_user = users(:bob)
99 |
100 | assert_raises ActiveRecord::RecordNotFound do
101 | @controller.params = { id: -1 }
102 | @controller.find_resource :document, through: :memberships
103 | end
104 | end
105 |
106 | test "find_resource raises RecordNotFound with from" do
107 | @controller.instance_variable_set :@user, users(:bob)
108 |
109 | assert_raises ActiveRecord::RecordNotFound do
110 | @controller.params = { id: -1 }
111 | @controller.find_resource :document, from: :user
112 | end
113 | end
114 |
115 | test "authorize with membership role" do
116 | @controller.instance_variable_set :@membership, documents(:alpha).memberships.first
117 |
118 | assert @controller.authorize(:fork, with: :membership)
119 | end
120 |
121 | test "authorize with current_admin" do
122 | @controller.current_admin = admins(:support)
123 |
124 | assert @controller.authorize(:support, with: :admin)
125 | end
126 |
127 | test "authorize with multiple roles" do
128 | @controller.instance_variable_set :@membership, documents(:alpha).memberships.first
129 |
130 | assert @controller.authorize([:update, :delete], with: :membership)
131 | end
132 |
133 | test "authorize with through" do
134 | @controller.instance_variable_set :@membership, documents(:alpha).memberships.first
135 |
136 | assert @controller.authorize(:delete, through: :membership)
137 | end
138 |
139 | test "authorize invokes authentication_admin" do
140 | @controller.current_admin = admins(:marketing)
141 |
142 | @controller.expects(:authenticate_admin!).once
143 | @controller.authorize(:marketing, with: :admin)
144 | end
145 |
146 | test "authorization failure single role" do
147 | assert_raises SimonSays::Authorizer::Denied do
148 | @controller.instance_variable_set :@membership, documents(:beta).memberships.first
149 |
150 | @controller.authorize(:delete, with: :membership)
151 | end
152 | end
153 |
154 | test "authorization failire multi roles" do
155 | @controller.instance_variable_set :@membership, documents(:beta).memberships.first
156 |
157 | assert_raises SimonSays::Authorizer::Denied do
158 | @controller.authorize([:update, :delete], with: :membership)
159 | end
160 | end
161 |
162 | test 'Authorizer.default_find_attribute proc' do
163 | with_default_find_attribute ->(resource) { :"#{resource}_id" } do
164 | with_params id: clients(:alice).client_id do
165 | @controller.find_resource :client
166 | end
167 | end
168 |
169 | assert_equal clients(:alice), @controller[:client]
170 | end
171 | end
172 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SimonSays!
2 |
3 | 
5 |
6 | This gem is a simple, declarative, role-based access control system for
7 | Rails that works great with devise!
8 |
9 | [](https://travis-ci.org/SimplyBuilt/SimonSays)
10 | [](https://badge.fury.io/rb/simon_says)
11 | [](./LICENSE)
12 |
13 | ### Installation
14 |
15 | SimonSays can be installed via your Gemfile.
16 |
17 | ```ruby
18 | gem 'simon_says'
19 | ```
20 |
21 | ### Usage
22 |
23 | SimonSays consists of two parts:
24 |
25 | 1. A [Roleable](#roleable) module mixin which provides a way to define
26 | roles on User models or on join through models.
27 | 2. An [Authorizer](#authorizer) module mixin which provides a
28 | declarative API to controllers for finding and authorizing resources.
29 |
30 | #### Roleable
31 |
32 | First, we need to define some roles on a model. Roles are stored as an
33 | integer and [bitmasking](https://en.wikipedia.org/wiki/Mask_(computing))
34 | is used to determine the roles assigned for given record. SimonSays
35 | provides a generator for creating a new migration for this required
36 | attribute:
37 |
38 | ```bash
39 | rails g model User # if and only if this model does not yet exist
40 | rails g active_record:simon_says User
41 | rails db:migrate
42 | ```
43 |
44 | Now we can define some roles in our User model. For example:
45 |
46 | ```ruby
47 | class User < ActiveRecord::Base
48 | include SimonSays::Roleable
49 |
50 | has_roles :add, :edit, :delete
51 | end
52 |
53 | # > User.new.roles
54 | # => []
55 |
56 | # > u = User.create(roles: %i[add edit])
57 | # => #
58 | # > u.roles
59 | # => [:add, :edit]
60 | # > u.has_add?
61 | # => true
62 | # > u.has_delete?
63 | # => false
64 | # > u.update roles: %i[delete add edit]
65 | # > u.save # save record with roles_mask of 7
66 | ```
67 |
68 | The attribute name can be customized by using the `:as` option as seen
69 | here in the Admin model:
70 |
71 | ```ruby
72 | class Admin < ActiveRecord::Base
73 | include SimonSays::Roleable
74 |
75 | has_roles :design, :support, :moderator, as: :access
76 | end
77 |
78 | # > Admin.new.access
79 | # => []
80 |
81 | # > Admin.new(access: :support).access
82 | # => [:support]
83 | ```
84 |
85 | Make sure to generate a migration using the correct attribute name if
86 | `:as` is used. For example:
87 |
88 | ```bash
89 | rails g active_record:simon_says Admin access
90 | ```
91 |
92 | We can also use `has_roles` to define roles on a join through model
93 | which is used to associate a User with a resource.
94 |
95 | ```ruby
96 |
97 | class Permission < ActiveRecord::Base
98 | include SimonSays::Roleable
99 |
100 | belongs_to :user
101 | belongs_to :document
102 |
103 | has_roles :download, :edit, :delete,
104 | end
105 |
106 | # > Permission.new(roles: Permission::ROLES).roles
107 | # => [:download, :edit, :delete]
108 | ```
109 |
110 | `Roleable` also creates two scopes that can be used to find records that
111 | have a given set roles. Using the default attribute name, the two scopes
112 | generated would be `with_roles` and `with_all_roles`. Both methods
113 | accept one or more role symbols as its arguments. The first scope,
114 | `with_roles`, will find any record with one or more the supplied roles.
115 | The second scope, `with_all_roles` will only find record that have all
116 | of the supplied roles.
117 |
118 | It is useful to note the various dynamically generated methods as well
119 | the `ROLES` constant, which is used in the Permission example. Take a
120 | look at the `Roleable`
121 | [source code](https://github.com/SimplyBuilt/SimonSays/blob/master/lib/simon_says/roleable.rb)
122 | to see how methods and scopes are dynamically generated with
123 | `has_roles`.
124 |
125 | #### Authorizer
126 |
127 | The `Authorizer` concern provides several methods that can be used within
128 | your controllers in a declarative manner.
129 |
130 | Please note, certain assumptions are made with `Authorizer`. Building
131 | upon the above User and Admin model examples, `Authorizer` would assume
132 | there is a `current_user` and `current_admin` method. If these models
133 | correspond to devise scopes this would be the case by default.
134 | Additionally there would need to be an `authenticate_user!` and
135 | `authenticate_admin!` methods, which devise provides as well.
136 |
137 | Eventually, we would like to see better customization around the
138 | authentication aspects. This library is intended to solve the problem of
139 | authorization and access control. It is not an authentication library.
140 |
141 | In general, the `Authorizer` concern provides four core declarative methods
142 | to be used in controllers. All of these methods accept the `:only` and
143 | `:except` options which end up being used in a `before_action` callback.
144 |
145 | - `authenticate(scope, opts): Declarative convenience method to setup
146 | authenticate `before_action`
147 | - `find_resource(resource, opts)`: Declarative method to find a resource
148 | and assign it to an instance variable
149 | - `authorize_resource(resource, *roles)`: Authorize resource for given
150 | roles
151 | - `find_and_authorize(resource, *roles)`: Find a resource and then try
152 | authorize it for the given roles. If successful, the resource is
153 | assigned to an instance variable
154 |
155 | When find resources, the `default_authorization_scope` is used. It can
156 | be customized on a per-controller basis. For example:
157 |
158 | ```ruby
159 | class ApplicationController < ActionController::Base
160 | include SimonSays::Authorizer
161 |
162 | self.default_authorization_scope = :current_user
163 | end
164 | ```
165 |
166 | To authorize resources against a given role, we use either `authorize`
167 | or `find_and_authorize`. For example, consider this
168 | `DocumentsController` which uses an authenticated `User` resource and a
169 | `Permission` through model:
170 |
171 | ```ruby
172 | class DocumentsController < ApplicationController
173 | authenticate :user
174 |
175 | find_and_authorize :document, :edit, through: :permissions, only: [:edit, :update]
176 | find_and_authorize :document, :delete, through: :permissions, only: :destroy
177 | end
178 | ```
179 |
180 | This controller will find a Document resource and assign it to the
181 | `@document` instance variable. For the `:edit` and `:update` actions,
182 | it'll require a permission with an `:edit` role. For the `:destroy`
183 | method, a permission with the `:delete` role is required. Since the
184 | `:through` option is used, a `@permission` instance variable will also
185 | be created.
186 |
187 | The `find_resource` method may raise an `ActiveRecord::RecordNotFound`
188 | exception. The `authorize` method may raise a
189 | `SimonSays::Authorizer::Denied` exception if there is insufficient role
190 | access. As a result, the `find_and_authorize` method may raise either
191 | exception.
192 |
193 | We can also use a different authorization scope with the `:from`
194 | option for `find_resource` and `find_and_authorize`. For example:
195 |
196 | ```ruby
197 | class ReportsController < ApplicationController
198 | authorize_resource :admin, :support
199 |
200 | find_resource :report, from: :current_admin, except: [:index, :new, :create]
201 | end
202 | ```
203 |
204 | Please refer to the
205 | [docs](http://www.rubydoc.info/github/SimplyBuilt/SimonSays/SimonSays/Authorizer/ClassMethods)
206 | for more information on the various declarative methods provided by the
207 | `Authorizer`.
208 |
209 | ## Contributing
210 |
211 | 1. Fork it ( https://github.com/SimplyBuilt/SimonSays/fork )
212 | 2. Create your feature branch (`git checkout -b my-new-feature`)
213 | 3. Commit your changes (`git commit -am 'Add some feature'`)
214 | 4. Push to the branch (`git push origin my-new-feature`)
215 | 5. Create a new Pull Request
216 |
--------------------------------------------------------------------------------
/lib/simon_says/authorizer.rb:
--------------------------------------------------------------------------------
1 | module SimonSays
2 | module Authorizer
3 | extend ActiveSupport::Concern
4 |
5 | class Denied < StandardError
6 | attr_reader :role_attr
7 |
8 | # @private
9 | def initialize(role_attr, required, actual)
10 | @role_attr = role_attr
11 |
12 | if defined? I18n
13 | required_text = I18n.translate(
14 | 'simon_says.denied.required',
15 | count: required.size,
16 | list: required.to_sentence(I18n.translate('simon_says.denied.array_connector'))
17 | )
18 |
19 | super I18n.translate(
20 | 'simon_says.denied.explanation',
21 | count: actual.size,
22 | role_attr: @role_attr,
23 | warning: I18n.translate('simon_says.denied.warning'),
24 | actual: actual.to_sentence,
25 | required: required_text
26 | )
27 | else
28 | super "Access denied; required=#{required.inspect} current_access=#{actual.inspect}"
29 | end
30 | end
31 | end
32 |
33 | included do
34 | class_attribute :default_authorization_scope
35 | class_attribute :default_find_attribute
36 | end
37 |
38 | module ClassMethods
39 | # Authentication convenience method (to keep things declarative).
40 | # This method just setups a +before_action+
41 | #
42 | # @param [Symbol, String] scope corresponds to some sort of authentication
43 | # scope (ie: +authenticate_user!+)
44 | # @param [Hash] opts before_action options
45 | #
46 | # @example Authentication user scope
47 | # authenticate :user, expect: :index
48 | def authenticate(scope, opts = {})
49 | before_action :"authenticate_#{scope}!", action_options(opts)
50 | end
51 |
52 | # Find and authorize a resource.
53 | #
54 | # @param [Symbol, String] resource name of resource to find
55 | # @param [Array] roles one or more role symbols or strings
56 | # @param [Hash] opts before_action and finder options
57 | # @param opts [Symbol] :from corresponds to an instance variable or method that
58 | # returns an ActiveRecord scope or model instance. If the object +respond_to?+
59 | # to the pluralized resource name it is called and used as the finder scope. This
60 | # makes it easy to handle finding resource through associations.
61 | # @param opts [Symbol] :find_attribute attribute resource is found by; by
62 | # default, +:id+ is used
63 | # @param opts [Symbol] :param_key params key for resource query; by default,
64 | # +:id+ is used
65 | # @param opts [Symbol] :through through model to use when finding and
66 | # authorizing the resource. Mutually exclusive with the :with option.
67 | # @param opts [Symbol] :with what resource to authorize with. Mutually
68 | # exclusive with the :through option.
69 | # @param opts [Symbol] :namespace resource namespace
70 | #
71 | # @see #find_resource for finder option examples
72 | def find_and_authorize(resource, *roles)
73 | opts = roles.extract_options!
74 |
75 | before_action(action_options(opts)) do
76 | find_resource resource, opts
77 |
78 | authorize roles, opts unless roles.empty?
79 | end
80 | end
81 |
82 | # Find a resource
83 | #
84 | # @param [Symbol, String] resource name of resource to find
85 | # @param [Hash] opts before_action and finder options
86 | # @param opts [Symbol] :from corresponds to an instance variable or method that
87 | # returns an ActiveRecord scope or model instance. If the object +respond_to?+
88 | # to the pluralized resource name it is called and used as the finder scope. This
89 | # makes it easy to handle finding resource through associations.
90 | # @param opts [Symbol] :find_attribute attribute resource is found by; by
91 | # default, +:id+ is used
92 | # @param opts [Symbol] :param_key params key for resource query; by default,
93 | # +:id+ is used
94 | # @param opts [Symbol] :through through model to use when finding resource
95 | # @param opts [Symbol] :namespace resource namespace
96 | #
97 | # @example Find with a +:through+ option
98 | # find_and_authorize :document, :create, :update :publish, through: :memberships
99 | # @example Find and authorize with a +:from+ option
100 | # # +@site.pages+ would be finder scope and is treated like an association
101 | # find_and_authorize :page, from: :site
102 | # @example Find resource with a +:find_attribute+ option
103 | # # the where clause is now +where(token: params[:id])+
104 | # find_resource :image, find_attribute: :token
105 | # @example Find a resource using a namespace
106 | # # Admin::Report is the class and query scope used
107 | # find_resource :report, namespace: :admin
108 | def find_resource(resource, opts = {})
109 | before_action action_options(opts) do
110 | find_resource resource, opts
111 | end
112 | end
113 |
114 | # Authorize against a given resource. This resource should be an instance
115 | # that includes Roleable.
116 | #
117 | # @param [Symbol, String] resource name of resource to find
118 | # @param [Array] roles one or more role symbols or strings
119 | # @param [Hash] opts before_action options
120 | #
121 | # @example Authorize resource
122 | # authorize_with :admin, :support
123 | def authorize_with(resource, *roles)
124 | opts = roles.extract_options!
125 |
126 | before_action action_options(opts) do
127 | authorize roles, { with: resource }
128 | end
129 | end
130 |
131 | # Authorize with the +default_authorization_scope+. The instance returned
132 | # by the +default_authorization_scope+ should include Roleable.
133 | #
134 | # @param [Array] roles one or more role symbols or strings
135 | # @param [Hash] opts before_action options
136 | #
137 | # @example Authorize "content" and "marketing" using the current Admin
138 | # self.default_authorization_scope = :current_admin
139 | #
140 | # authorize :content, :marketing
141 | def authorize(*roles)
142 | authorize_with(default_authorization_scope, *roles)
143 | end
144 |
145 | # Extract before_action options from Hash
146 | #
147 | # @private
148 | # @param [Hash] options input options hash
149 | # @param options [Symbol] :expect before_action expect option
150 | # @param options [Symbol] :only before_action only option
151 | # @param options [Symbol] :prepend before_action prepend option
152 | def action_options(options)
153 | { except: options.delete(:except), only: options.delete(:only), prepend: options.delete(:prepend) }
154 | end
155 | end
156 |
157 | # Internal find_resource instance method
158 | #
159 | # @private
160 | # @param [Symbol, String] resource name of resource to find
161 | # @param [Hash] options finder options
162 | def find_resource(resource, options = {})
163 | resource = resource.to_s
164 |
165 | scope, query = resource_scope_and_query(resource, options)
166 | through = options[:through] ? options[:through].to_s : nil
167 |
168 | assoc_name = through || (options[:from] ? resource.pluralize : nil)
169 | assoc = Authorizer.association_method(assoc_name)
170 | scope = scope.send(assoc) if assoc && scope.respond_to?(assoc)
171 |
172 | record = scope.where(query).first!
173 |
174 | if through
175 | instance_variable_set "@#{through.singularize}", record
176 | record = record.send(resource)
177 | end
178 |
179 | instance_variable_set "@#{resource}", record
180 | end
181 |
182 | # Internal authorize instance method
183 | #
184 | # @private
185 | # @param [Symbol, String] one or more required roles
186 | # @param [Hash] options authorizer options
187 | def authorize(required = nil, options)
188 | if options.key? :through
189 | name = options[:through].to_s.singularize.to_sym
190 | elsif options.key? :with
191 | name = options[:with].to_s.singularize.to_sym
192 | else
193 | raise ArgumentError, 'find_and_authorize must be called with either '\
194 | ':through or :with option. The resource referenced by the value '\
195 | 'of this option should be an instance of a class that includes '\
196 | 'Roleable.'
197 | end
198 |
199 | record = instance_variable_get("@#{name}")
200 |
201 | if record.nil? # must be devise scope
202 | record = send("current_#{name}")
203 | send "authenticate_#{name}!"
204 | end
205 |
206 | role_attr = record.class.role_attribute_name
207 | actual = record.send(role_attr)
208 |
209 | required ||= options[role_attr]
210 | required = [required] unless Array === required
211 |
212 | # actual roles must have at least
213 | # one required role (array intersection)
214 | ((required & actual).size > 0).tap do |res|
215 | raise Denied.new(role_attr, required, actual) unless res
216 | end
217 | end
218 |
219 | def self.association_method(assoc)
220 | return if assoc.nil?
221 | return :"#{assoc}_dataset" if defined? Sequel
222 |
223 | assoc.to_sym
224 | end
225 |
226 | private
227 |
228 | # @private
229 | def resource_scope_and_query(resource, options)
230 | if options[:through]
231 | field = :"#{resource}_id"
232 |
233 | query = { field => params[field] } if params[field]
234 | scope = send(self.class.default_authorization_scope)
235 |
236 | elsif options[:from]
237 | scope = instance_variable_get("@#{options[:from]}") || send(options[:from])
238 |
239 | else
240 | klass = (options[:class_name] || resource).to_s
241 | klass = "#{options[:namespace]}/#{klass}" if options[:namespace]
242 |
243 | scope = klass.classify.constantize
244 | end
245 |
246 | field ||= options.fetch(:find_attribute) do
247 | self.class.default_find_attribute&.call(resource) || :id
248 | end
249 |
250 | query ||= { field => params[options.fetch(:param_key, :id)] }
251 |
252 | return scope, query
253 | end
254 | end
255 | end
256 |
--------------------------------------------------------------------------------