├── .gitignore ├── .gitpublish ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── Gemfile ├── Guardfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── SimonSays.png ├── bin ├── console └── setup ├── config └── locales │ └── en.yml ├── lib ├── generators │ └── active_record │ │ ├── simon_says_generator.rb │ │ └── templates │ │ └── migration.rb ├── simon_says.rb └── simon_says │ ├── authorizer.rb │ ├── roleable.rb │ └── version.rb ├── simon_says.gemspec └── test ├── controllers ├── admin │ └── reports_controller_test.rb ├── documents_controller_test.rb └── images_controller_test.rb ├── rails_app ├── .gitignore ├── README.rdoc ├── Rakefile ├── app │ ├── assets │ │ ├── images │ │ │ └── .keep │ │ ├── javascripts │ │ │ └── application.js │ │ └── stylesheets │ │ │ └── application.css │ ├── controllers │ │ ├── admin │ │ │ ├── comm_reports_controller.rb │ │ │ └── reports_controller.rb │ │ ├── application_controller.rb │ │ ├── concerns │ │ │ └── .keep │ │ ├── documents_controller.rb │ │ └── images_controller.rb │ ├── helpers │ │ └── application_helper.rb │ ├── mailers │ │ └── .keep │ ├── models │ │ ├── .keep │ │ ├── admin.rb │ │ ├── admin │ │ │ └── report.rb │ │ ├── application_record.rb │ │ ├── client.rb │ │ ├── concerns │ │ │ └── .keep │ │ ├── document.rb │ │ ├── image.rb │ │ ├── membership.rb │ │ └── user.rb │ └── views │ │ └── layouts │ │ └── application.html.erb ├── bin │ ├── bundle │ ├── rails │ ├── rake │ └── spring ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── assets.rb │ │ ├── backtrace_silencers.rb │ │ ├── cookies_serializer.rb │ │ ├── filter_parameter_logging.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── session_store.rb │ │ └── wrap_parameters.rb │ ├── locales │ │ └── en.yml │ ├── routes.rb │ └── secrets.yml ├── db │ ├── migrate │ │ ├── 20141016142638_create_admins.rb │ │ ├── 20141016183619_create_users.rb │ │ ├── 20141016183633_create_memberships.rb │ │ ├── 20141016183642_create_documents.rb │ │ ├── 20141017140833_create_admin_reports.rb │ │ ├── 20160823220959_create_images.rb │ │ └── 20190711184853_create_clients.rb │ ├── schema.rb │ └── seeds.rb ├── lib │ ├── assets │ │ └── .keep │ └── tasks │ │ └── .keep ├── log │ └── .keep ├── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ ├── favicon.ico │ └── robots.txt ├── test │ ├── controllers │ │ └── .keep │ ├── fixtures │ │ ├── .keep │ │ ├── admin │ │ │ └── reports.yml │ │ ├── admins.yml │ │ ├── clients.yml │ │ ├── documents.yml │ │ ├── images.yml │ │ ├── memberships.yml │ │ └── users.yml │ ├── helpers │ │ └── .keep │ ├── integration │ │ └── .keep │ ├── mailers │ │ └── .keep │ ├── models │ │ ├── .keep │ │ ├── admin │ │ │ └── report_test.rb │ │ ├── client_test.rb │ │ ├── document_test.rb │ │ ├── image_test.rb │ │ ├── membership_test.rb │ │ └── user_test.rb │ └── test_helper.rb └── vendor │ └── assets │ ├── javascripts │ └── .keep │ └── stylesheets │ └── .keep ├── simon_says ├── authorizer_denied_test.rb ├── authorizer_test.rb └── roleable_test.rb ├── simon_says_test.rb └── test_helper.rb /.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 | -------------------------------------------------------------------------------- /.gitpublish: -------------------------------------------------------------------------------- 1 | export_dir=docs/ 2 | remote=origin 3 | branch=gh-pages 4 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/ -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SimonSays! 2 | 3 | ![SimonSays 4 | Logo](https://raw.githubusercontent.com/SimplyBuilt/SimonSays/master/SimonSays.png) 5 | 6 | This gem is a simple, declarative, role-based access control system for 7 | Rails that works great with devise! 8 | 9 | [![Travis Build Status](https://travis-ci.org/SimplyBuilt/SimonSays.svg)](https://travis-ci.org/SimplyBuilt/SimonSays) 10 | [![Gem Version](https://badge.fury.io/rb/simon_says.svg)](https://badge.fury.io/rb/simon_says) 11 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /SimonSays.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplyBuilt/SimonSays/34700053157c9cd489cf6ec91c9f020131759c89/SimonSays.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/simon_says/version.rb: -------------------------------------------------------------------------------- 1 | module SimonSays 2 | VERSION = '0.3.0.alpha.7' 3 | end 4 | -------------------------------------------------------------------------------- /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/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/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/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/.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 | -------------------------------------------------------------------------------- /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/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/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplyBuilt/SimonSays/34700053157c9cd489cf6ec91c9f020131759c89/test/rails_app/app/assets/images/.keep -------------------------------------------------------------------------------- /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/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/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/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/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplyBuilt/SimonSays/34700053157c9cd489cf6ec91c9f020131759c89/test/rails_app/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/rails_app/app/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplyBuilt/SimonSays/34700053157c9cd489cf6ec91c9f020131759c89/test/rails_app/app/mailers/.keep -------------------------------------------------------------------------------- /test/rails_app/app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplyBuilt/SimonSays/34700053157c9cd489cf6ec91c9f020131759c89/test/rails_app/app/models/.keep -------------------------------------------------------------------------------- /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/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/app/models/client.rb: -------------------------------------------------------------------------------- 1 | class Client < ApplicationRecord 2 | end 3 | -------------------------------------------------------------------------------- /test/rails_app/app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplyBuilt/SimonSays/34700053157c9cd489cf6ec91c9f020131759c89/test/rails_app/app/models/concerns/.keep -------------------------------------------------------------------------------- /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/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/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/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/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/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplyBuilt/SimonSays/34700053157c9cd489cf6ec91c9f020131759c89/test/rails_app/lib/assets/.keep -------------------------------------------------------------------------------- /test/rails_app/lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplyBuilt/SimonSays/34700053157c9cd489cf6ec91c9f020131759c89/test/rails_app/lib/tasks/.keep -------------------------------------------------------------------------------- /test/rails_app/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplyBuilt/SimonSays/34700053157c9cd489cf6ec91c9f020131759c89/test/rails_app/log/.keep -------------------------------------------------------------------------------- /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/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/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/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplyBuilt/SimonSays/34700053157c9cd489cf6ec91c9f020131759c89/test/rails_app/public/favicon.ico -------------------------------------------------------------------------------- /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/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplyBuilt/SimonSays/34700053157c9cd489cf6ec91c9f020131759c89/test/rails_app/test/controllers/.keep -------------------------------------------------------------------------------- /test/rails_app/test/fixtures/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplyBuilt/SimonSays/34700053157c9cd489cf6ec91c9f020131759c89/test/rails_app/test/fixtures/.keep -------------------------------------------------------------------------------- /test/rails_app/test/fixtures/admin/reports.yml: -------------------------------------------------------------------------------- 1 | report_one: 2 | title: Some Title 3 | -------------------------------------------------------------------------------- /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/clients.yml: -------------------------------------------------------------------------------- 1 | alice: {} 2 | -------------------------------------------------------------------------------- /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/test/fixtures/images.yml: -------------------------------------------------------------------------------- 1 | image_one: 2 | token: abcdef 3 | -------------------------------------------------------------------------------- /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/test/fixtures/users.yml: -------------------------------------------------------------------------------- 1 | bob: {} 2 | jim: {} 3 | -------------------------------------------------------------------------------- /test/rails_app/test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplyBuilt/SimonSays/34700053157c9cd489cf6ec91c9f020131759c89/test/rails_app/test/helpers/.keep -------------------------------------------------------------------------------- /test/rails_app/test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplyBuilt/SimonSays/34700053157c9cd489cf6ec91c9f020131759c89/test/rails_app/test/integration/.keep -------------------------------------------------------------------------------- /test/rails_app/test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplyBuilt/SimonSays/34700053157c9cd489cf6ec91c9f020131759c89/test/rails_app/test/mailers/.keep -------------------------------------------------------------------------------- /test/rails_app/test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplyBuilt/SimonSays/34700053157c9cd489cf6ec91c9f020131759c89/test/rails_app/test/models/.keep -------------------------------------------------------------------------------- /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/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/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/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/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/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/vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplyBuilt/SimonSays/34700053157c9cd489cf6ec91c9f020131759c89/test/rails_app/vendor/assets/javascripts/.keep -------------------------------------------------------------------------------- /test/rails_app/vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplyBuilt/SimonSays/34700053157c9cd489cf6ec91c9f020131759c89/test/rails_app/vendor/assets/stylesheets/.keep -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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_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/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 | --------------------------------------------------------------------------------