pax_global_header00006660000000000000000000000064134551637010014517gustar00rootroot0000000000000052 comment=61bfbc15eeb6303029e00b44e1f7e5bbb9ddad42 cancancan-3.0.1/000077500000000000000000000000001345516370100134255ustar00rootroot00000000000000cancancan-3.0.1/.codeclimate.yml000066400000000000000000000001061345516370100164740ustar00rootroot00000000000000version: "2" checks: argument-count: config: threshold: 5 cancancan-3.0.1/.github/000077500000000000000000000000001345516370100147655ustar00rootroot00000000000000cancancan-3.0.1/.github/issue_template.md000066400000000000000000000010011345516370100203220ustar00rootroot00000000000000### Steps to reproduce Please describe accurately how to reproduce the issue and post all necessary code. If you provide a Gist to reproduce the issue this will make the debug much easier. Please copy the following Gist and edit it to reproduce your issue: https://gist.github.com/coorasse/3f00f536563249125a37e15a1652648c ### Expected behavior Tell us what should happen ### Actual behavior Tell us what happens instead ### System configuration **Rails version**: **Ruby version**: **CanCanCan version** cancancan-3.0.1/.gitignore000066400000000000000000000002041345516370100154110ustar00rootroot00000000000000.DS_Store .idea/* *.swp **/*.swp *.gem .bundle gemfiles/*.lock Gemfile.lock .rvmrc .rbenv-version .ruby-version .ruby-gemset /tmp cancancan-3.0.1/.rspec000066400000000000000000000000101345516370100145310ustar00rootroot00000000000000--color cancancan-3.0.1/.rubocop.yml000066400000000000000000000013131345516370100156750ustar00rootroot00000000000000Style/Documentation: Enabled: false Style/NonNilCheck: IncludeSemanticChanges: true Style/EmptyMethod: Enabled: false Style/ClassAndModuleChildren: Enabled: false Metrics/LineLength: Max: 120 Metrics/BlockLength: Exclude: - 'lib/cancan/matchers.rb' - '**/*_spec.rb' # TODO # Offense count: 2 # Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist. # NamePrefix: is_, has_, have_ # NamePrefixBlacklist: is_, has_, have_ # NameWhitelist: is_a? Naming/PredicateName: Exclude: - 'spec/**/*' - 'lib/cancan/ability.rb' # TODO Lint/AmbiguousBlockAssociation: Enabled: false AllCops: TargetRubyVersion: 2.2.0 Exclude: - 'gemfiles/**/*' - 'Appraisals' cancancan-3.0.1/.travis.yml000066400000000000000000000023321345516370100155360ustar00rootroot00000000000000language: ruby cache: bundler sudo: false addons: postgresql: "9.6" rvm: - 2.3.5 - 2.4.2 - 2.5.1 - 2.6.0 - ruby-head - jruby-9.1.9.0 - jruby-9.2.5.0 - jruby-head gemfile: - gemfiles/activerecord_4.2.0.gemfile - gemfiles/activerecord_5.0.2.gemfile - gemfiles/activerecord_5.1.0.gemfile - gemfiles/activerecord_5.2.2.gemfile - gemfiles/activerecord_6.0.0.gemfile env: - DB=sqlite - DB=postgres matrix: fast_finish: true exclude: - rvm: 2.2.6 gemfile: gemfiles/activerecord_6.0.0.gemfile - rvm: 2.3.5 gemfile: gemfiles/activerecord_6.0.0.gemfile - rvm: 2.4.2 gemfile: gemfiles/activerecord_6.0.0.gemfile - rvm: jruby-9.1.9.0 gemfile: gemfiles/activerecord_5.0.2.gemfile - rvm: jruby-9.1.9.0 gemfile: gemfiles/activerecord_6.0.0.gemfile - rvm: jruby-9.2.5.0 gemfile: gemfiles/activerecord_5.0.2.gemfile - rvm: jruby-9.2.5.0 gemfile: gemfiles/activerecord_6.0.0.gemfile allow_failures: - rvm: ruby-head - rvm: jruby-head notifications: email: recipients: - alessandro.rodi@renuo.ch on_success: change on_failure: change before_install: - gem update --system - gem install bundler script: - bundle exec rubocop && bundle exec rake cancancan-3.0.1/Appraisals000066400000000000000000000044461345516370100154570ustar00rootroot00000000000000appraise 'activerecord_4.2.0' do gem 'activerecord', '~> 4.2.0', require: 'active_record' gem 'activesupport', '~> 4.2.0', require: 'active_support/all' gem 'actionpack', '~> 4.2.0', require: 'action_pack' gem 'nokogiri', '~> 1.6.8', require: 'nokogiri' # TODO: fix for ruby 2.0.0 gemfile.platforms :jruby do gem 'activerecord-jdbcsqlite3-adapter', '~> 1.3.24' gem 'jdbc-sqlite3' gem 'jdbc-postgres' end gemfile.platforms :ruby, :mswin, :mingw do gem 'sqlite3', '~> 1.3.0' gem 'pg', '~> 0.21' end end appraise 'activerecord_5.0.2' do gem 'activerecord', '~> 5.0.2', require: 'active_record' gem 'activesupport', '~> 5.0.2', require: 'active_support/all' gem 'actionpack', '~> 5.0.2', require: 'action_pack' gemfile.platforms :jruby do gem 'activerecord-jdbcsqlite3-adapter' gem 'jdbc-sqlite3' gem 'jdbc-postgres' end gemfile.platforms :ruby, :mswin, :mingw do gem 'sqlite3', '~> 1.3.0' gem 'pg', '~> 0.21' end end appraise 'activerecord_5.1.0' do gem 'activerecord', '~> 5.1.0', require: 'active_record' gem 'activesupport', '~> 5.1.0', require: 'active_support/all' gem 'actionpack', '~> 5.1.0', require: 'action_pack' gemfile.platforms :jruby do gem 'activerecord-jdbcsqlite3-adapter' gem 'jdbc-sqlite3' gem 'jdbc-postgres' end gemfile.platforms :ruby, :mswin, :mingw do gem 'sqlite3', '~> 1.3.0' gem 'pg', '~> 0.21' end end appraise 'activerecord_5.2.2' do gem 'activerecord', '~> 5.2.2', require: 'active_record' gem 'activesupport', '~> 5.2.2', require: 'active_support/all' gem 'actionpack', '~> 5.2.2', require: 'action_pack' gemfile.platforms :jruby do gem 'activerecord-jdbcsqlite3-adapter' gem 'jdbc-sqlite3' gem 'jdbc-postgres' end gemfile.platforms :ruby, :mswin, :mingw do gem 'sqlite3', '~> 1.3.0' gem 'pg', '~> 0.21' end end appraise 'activerecord_6.0.0' do gem 'actionpack', '~> 6.0.0.beta3', require: 'action_pack' gem 'activerecord', '~> 6.0.0.beta3', require: 'active_record' gem 'activesupport', '~> 6.0.0.beta3', require: 'active_support/all' platforms :jruby do gem 'activerecord-jdbcsqlite3-adapter' gem 'jdbc-sqlite3' gem 'jdbc-postgres' end platforms :ruby, :mswin, :mingw do gem 'pg', '~> 1.1.4' gem 'sqlite3', '~> 1.3.0' end end cancancan-3.0.1/CHANGELOG.md000066400000000000000000000604611345516370100152450ustar00rootroot00000000000000## 3.0.1 * [#583](https://github.com/CanCanCommunity/cancancan/pull/583): Fix regression when using a method reference block. ([@coorasse][]) ## 3.0.0 Please read the [guide on migrating from CanCanCan 2.x to 3.0](https://github.com/CanCanCommunity/cancancan/wiki/Migrating-from-CanCanCan-2.x-to-3.0) * [#560](https://github.com/CanCanCommunity/cancancan/pull/560): Add support for Rails 6.0. ([@coorasse][]) * [#489](https://github.com/CanCanCommunity/cancancan/pull/489): Drop support for actions without a subject. ([@andrew-aladev][]) * [#474](https://github.com/CanCanCommunity/cancancan/pull/474): Allow to add attribute-level rules. ([@phaedryx][]) * [#512](https://github.com/CanCanCommunity/cancancan/pull/512): Removed automatic eager loading of associations for ActiveRecord >= 5.0. ([@kaspernj][]) * [#575](https://github.com/CanCanCommunity/cancancan/pull/575): Use the rules compressor when generating joins in accessible_by. ([@coorasse][]) * [#444](https://github.com/CanCanCommunity/cancancan/issues/444): Allow to use symbols when defining conditions over enums. ([@s-mage][]) * [#538](https://github.com/CanCanCommunity/cancancan/issues/538): Merge alias actions when merging abilities. ([@Jcambass][]) * [#462](https://github.com/CanCanCommunity/cancancan/issues/462): Add support to translate the model name in messages. ([@nyamadori][]) * [#567](https://github.com/CanCanCommunity/cancancan/issues/567): Extensively run tests on different databases (sqlite and postgres). ([@coorasse][]) * [#566](https://github.com/CanCanCommunity/cancancan/issues/566): Avoid queries on session dumps (speed up error pages). ([@coorasse][]) * [#568](https://github.com/CanCanCommunity/cancancan/issues/568): Automatically freeze strings in all files. ([@coorasse][]) * [#577](https://github.com/CanCanCommunity/cancancan/pull/577): Normalise rules traversing associations to reduce the number of joins. ([@coorasse][]) ## 2.3.0 (Sep 16th, 2018) * [#528](https://github.com/CanCanCommunity/cancancan/issues/528): Compress irrelevant rules before generating a query to optimize performances. ([@coorasse][]) * [#529](https://github.com/CanCanCommunity/cancancan/issues/529): Remove ruby 2.2 from Travis and add ruby 2.5.1. ([@coorasse][]) * [#530](https://github.com/CanCanCommunity/cancancan/issues/530): Predict associations names to support multiple references to the same table. ([@coorasse][]) * [#530](https://github.com/CanCanCommunity/cancancan/issues/530): Raise a specific exception when using a wrong association name in rules definition. ([@coorasse][]) ## 2.2.0 (Apr 13th, 2018) * [#482](https://github.com/CanCanCommunity/cancancan/issues/482): Include conditions passed to authorize! in AccessDenied exception. ([@kraflab][]) * Removed support for dynamic finders. ([@coorasse][]) * [#479](https://github.com/CanCanCommunity/cancancan/issues/479): Support Rails 5.2. ([@lizzyaustad][]) * Use ActiveSupport standard loader. ([@BookOfGreg][]) ## 2.1.4 (Apr 09th, 2018) * Inject cancancan in ActionController::API and ActionController::Base when they are both defined. ([@arturoherrero][]) ## 2.1.3 (Jan 16th, 2018) * Fix compatibility with Rails 5 API. ([@Eric-Guo][]) ## 2.1.2 (Nov 22th, 2017) * Various bugfixes on version 2.1.0. ([@coorasse][]) ## 2.1.0 (Nov 10th, 2017) * Adds support for Rails Api applications. ([@ajgon][]) * Controller subclasses inherit skip_load_resource from superclass. ([@jpmckinney][]) * Fix instance variable not initialized warnings. ([@sethcharles][]) * Fix build_resource when model name is Action. ([@anilmaurya][]) * Smaller performance improvements. ([@DNNX][]) * Fix i18n lookup for unauthorized message. ([@clemens][]) ## 2.0.0 (May 18th, 2017) * Drop support for Rails < 4.2. ([@oliverklee][]) * Drop support for ruby < 2.2. ([@coorasse][]) * Drop support for InheritedResource. ([@coorasse][]) * Drop support for Sequel. ([@coorasse][]) * Drop support for Mongoid. ([@coorasse][]) * Add ability to rspec matcher to take array of abilities. ([@gingray][]) * [#204](https://github.com/CanCanCommunity/cancancan/pull/204): Increase Performance. ([@timraymond][]) * Removed controller methods: skip_authorization, unauthorized!. ([@coorasse][]) * Removed options: nested, name, resource. ([@coorasse][]) ## 1.17.0 (March 26th, 2017) * Improve performance for the Mongoid Adapter. ## 1.16.0 (February 2nd, 2017) * Introduce rubocop and fixes most of the issues ([@coorasse][]). ## 1.15.0 (June 13th, 2016) * Add support for Rails 5 (craig1410). ## 1.14.0 (May 14th, 2016) * Use cover for ranges. * Add support for rails 4 enum's (markpmitchell). ## 1.13.1 (Oct 8th, 2015) * Fix #merge with empty Ability (jhawthorn). ## 1.13.0 (Oct 7th, 2015) * Significantly improve rule lookup time (amarshall). * Removed deprecation warnings for RSpec 3.2 (NekoNova). * Drop support for REE and Ruby 1.x and so Rails 2 (Richard Wilson). ## 1.12.0 (June 28th, 2015) * Add a permissions method to Ability (devaroop). ## 1.11.0 (June 15th, 2015) * Complete cancancan#115 - Specify authorization action for parent resources. (phallguy). ## 1.10.1 (January 13th, 2015) * Fix cancancan#168 - A bug with ActiveRecord 4.2 support causing ProtocolViolation due to named parameters not being passed in. ## 1.10.0 (January 7th, 2015) * Fix i18n issue for Ruby < 1.9.3 ([@bryanrite][]). * Fix cancancan#149 - Fix an issue loading namespaced models (darthjee). * Fix cancancan#160 - Support for Rails 4.2 (marshall-lee). * Fix cancancan#153 - More useful output in ability spec matchers (jondkinney). ## 1.9.2 (August 8th, 2014) * Fix cancancan#77, 78 - Fix an issue with associations for namespaced models (jjp). ## 1.9.1 (July 21st, 2014) * Fix cancancan#101 - Fixes an issue where overjealous use of references would cause issues with scopes when loading associations ([@bryanrite][]). ## 1.9.0 (July 20th, 2014) * Fix cancancan#59 - Parameters are automatically detected and santitized for all actions, not just create and update ([@bryanrite][]). * Fix cancancan#97, 72, 40, 39, 26 - Support Active Record 4 properly with references on nested permissions (scpike, tdg5, Crystark). ## 1.8.4 (June 24th, 2014) * Fix cancancan#86 - Fixes previous RSpec 3 update as there was a bug in the fix for RSpec 2.99 ([@bryanrite][]). ## 1.8.3 (June 24th, 2014) * Fix cancancan#85 - Remove deprecation notices for RSpec 3 and continue backwards compatibility (andypike, bryanrite, porteta). ## 1.8.2 (June 5th, 2014) * Fix cancancan#75 - More specific hash-like object check. ([@bryanrite][]). ## 1.8.1 (May 27th, 2014) * Fix cancancan#67 - Sequel tests are run properly for JRuby. ([@bryanrite][]). * Fix cancancan#68 - Checks for hash-like objects in subject better. ([@bryanrite][]). ## 1.8.0 (May 8th, 2014) * Feature cancan#884 - Add a Sequel model adapter (szetobo). * Feature cancancan#3 - Permit "can?" check multiple subjects (cefigueiredo). * Feature cancancan#29 - Add ability to use a String that will get instance_eval'd or a Proc that will get called as the parameter method option for strong_parameter santization (svoop). * Feature cancancan#48 - Define a CanCanCan module. Even though it is not used, it is standard practice to define the module, and helpful for determining between CanCanCan and CanCan for external libraries. ## 1.7.1 (March 19th, 2014) * Fix ryanb/cancan#992 - Remove Rails 4 deprecations for scoped (thejchap & hitendrasingh). * Fix cancancan#16 - RSpec expectations are not explicitly required in RSpec > 2.13 (justinaiken & bryanrite). ## 1.7.0 (February 19th, 2014) * Feature #988 Adds support for strong_parameters ([@bryanrite][]). * Fix #726 - Allow multiple abilities with associations (elabs-dev). * Fix #864 - Fix id_param in shallow routes (francocatena). * Fix #871 - Fixes nested ability conditions (ricec). * Fix #935 - Reduce unnecessary object allocations (grosser). * Fix #966 - Fixes a variable name collision in nested conditions (knoopx). * Fix #971 - Does not execute "empty?" scope when checking class rule (matt-glover). * Fix #974 - Avoid unnecessary sql execution (inkstak). ## 1.6.10 (May 7, 2013) * Fix matches_conditons_hash for string values on 1.8 ([@rrosen][]). * Work around SQL injection vulnerability in older Rails versions ([@steerio][]) - issue #800. * Add support for nested join conditions ([@yuszuv][]) - issue #806. * Fix load_resource "find_by" in mongoid resources ([@albertobajo][]) - issue #705. * Fix namespace split behavior ([@xinuc][]) - issue #668. ## 1.6.9 (February 4, 2013) * Fix inserting AND (NULL) to end of SQL queries (jonsgreen) - issue #687. * Fix merge_joins for nested association hashes (DavidMikeSimon) - issues #655, #560. * Raise error on recursive alias_action (fl00r) - issue #660. * Fix namespace controllers not loading params (andhapp) - issues #670, #664. ## 1.6.8 (June 25, 2012) * Improved support for namespaced controllers and models. * Pass :if and :unless options for load and authorize resource (mauriciozaffari). * Travis CI badge (plentz). * Adding Ability#merge for combining multiple abilities (rogercampos). * Support for multiple MetaWhere rules (andhapp). * Various fixes for DataMapper, Mongoid, and Inherited Resource integration. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.6.7...1.6.8]. ## 1.6.7 (October 4, 2011) * Fixing nested resource problem caused by namespace addition - issue #482. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.6.6...1.6.7]. ## 1.6.6 (September 28, 2011) * Correct "return cant jump across threads" error when using check_authorization (codeprimate) - issues #463, #469. * Fixing tests in development by specifying with_model version (kirkconnell) - issue #476. * Added travis.yml file for TravisCI support (bai) - issue #427. * Better support for namespaced models (whilefalse) - issues #424. * Adding :id_param option to load_and_authorize_resource (skhisma) - issue #425. * Make default unauthorized message translatable text (nhocki) - issue #409. * Improving DataMapper behavior (psanford, maxsum-corin) - issue #410, #373. * Allow :find_by option to be full find method name - issue #335. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.6.5...1.6.6]. ## 1.6.5 (May 18, 2011) * [#366](https://github.com/CanCanCommunity/cancancan/issues/366): Pass action and subject through AccessDenied exception when :through isn't found. * Many Mongoid adapter improvements (rahearn, cardagin) - issues #363, #352, #343. * [#360](https://github.com/CanCanCommunity/cancancan/issues/360): Allow :through option to work with private controller methods. * [#359](https://github.com/CanCanCommunity/cancancan/issues/359): Ensure Mongoid::Document is defined before loading Mongoid adapter. * [#355](https://github.com/CanCanCommunity/cancancan/issues/355): Many DataMapper adapter improvements ([@emmanuel][]). * [#330](https://github.com/CanCanCommunity/cancancan/issues/330): Handle checking nil attributes through associations ([@thatothermitch][]). * Improve scope merging - issue #328. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.6.4...1.6.5]. ## 1.6.4 (March 29, 2011) * Fixed mongoid 'or' error - see issue #322. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.6.3...1.6.4]. ## 1.6.3 (March 25, 2011) * Make sure ActiveRecord::Relation is defined before checking conditions against it so Rails 2 is supported again - see issue #312. * Return subject passed to authorize! - see issue #314. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.6.2...1.6.3]. ## 1.6.2 (March 18, 2011) * Fixed instance loading when :singleton option is used - see issue #310. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.6.1...1.6.2]. ## 1.6.1 (March 15, 2011) * Use Item.new instead of build_item for singleton resource so it doesn't effect database - see issue #304. * Made accessible_by action default to :index and parent action default to :show instead of :read - see issue #302. * Reverted Inherited Resources "collection" override since it doesn't seem to be working - see issue #305. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.6.0...1.6.1]. ## 1.6.0 (March 11, 2011) * Added MetaWhere support - see issue #194 and #261. * Allow Active Record scopes in Ability conditions - see issue #257. * Added :if and :unless options to check_authorization - see issue #284. * Several Inherited Resources fixes (aq1018, tanordheim and stefanoverna). * Pass action name to accessible_by call when loading a collection ([@amw][]). * Added :prepend option to load_and_authorize_resource to load before other filters - see issue #290. * Fixed spacing issue in I18n message for multi-word model names - see issue #292. * Load resource collection for any action which doesn't have an "id" parameter - see issue #296. * Raise an exception when trying to make a Ability condition with both a hash of conditions and a block - see issue #269. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.5.1...1.6.0]. ## 1.5.1 (January 20, 2011) * Fixing deeply nested conditions in Active Record adapter - see issue #246. * Improving Mongoid support for multiple can and cannot definitions ([@stellard][]) - see issue #239. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.5.0...1.5.1]. ## 1.5.0 (January 11, 2011) * Added an Ability generator - see issue #170. * Added DataMapper support ([@natemueller][]). * Added Mongoid support ([@bowsersenior][]). * Added skip_load_and_authorize_resource methods to controller class - see issue #164. * Added support for uncountable resources in index action - see issue #193. * Cleaned up README and added spec/README. * Internal: renamed CanDefinition to Rule. * Internal: added a model adapter layer for easily supporting more ORMs. * Internal: added .rvmrc to auto-switch to 1.8.7 with gemset - see issue #231. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.4.1...1.5.0]. ## 1.4.1 (November 12, 2010) * Renaming skip_authorization to skip_authorization_check - see issue #169. * Adding :through_association option to load_resource ([@hunterae][]) - see issue #171. * The :shallow option now works with the :singleton option ([@nandalopes][]) - see issue #187. * Play nicely with quick_scopes gem ([@ramontayag][]) - see issue #183. * Fix odd behavior when "cache_classes = false" ([@mphalliday][]) - see issue #174. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.4.0...1.4.1]. ## 1.4.0 (October 5, 2010) * Adding Gemfile; to get specs running just +bundle+ and +rake+ - see issue #163. * Stop at 'cannot' definition when there are no conditions - see issue #161. * The :through option will now call a method with that name if instance variable doesn't exist - see issue #146. * Adding :shallow option to load_resource to bring back old behavior of fetching a child without a parent. * Raise AccessDenied error when loading a child and parent resource isn't found. * Abilities defined on a module will apply to anything that includes that module - see issue #150 and #152. * Abilities can be defined with a string of SQL in addition to a block so accessible_by works with a block - see issue #150. * Adding better support for InheritedResource - see issue #23. * Loading the collection instance variable (for index action) using accessible_by - see issue #137. * Adding action and subject variables to I18n unauthorized message - closes #142. * Adding check_authorization and skip_authorization controller class methods to ensure authorization is performed ([@justinko][]) - see issue #135. * Setting initial attributes based on ability conditions in new/create actions - see issue #114. * Check parent attributes for nested association in index action - see issue #121. * Supporting nesting in can? method using hash - see issue #121. * Adding I18n support for Access Denied messages ([@EppO][]) - see issue #103. * Passing no arguments to +can+ definition will pass action, class, and object to block - see issue #129. * Don't pass action to block in +can+ definition when using :+manage+ option - see issue #129. * No longer calling block in +can+ definition when checking on class - see issue #116. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.3.4...1.4.0]. ## 1.3.4 (August 31, 2010) * Don't stop at +cannot+ with hash conditions when checking class ([@tamoya][]) - see issue #131. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.3.3...1.3.4]. ## 1.3.3 (August 20, 2010) * Switching to Rspec namespace to remove deprecation warning in Rspec 2 - see issue #119. * Pluralize nested associations for conditions in accessible_by ([@mlooney][]) - see issue #123. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.3.2...1.3.3]. ## 1.3.2 (August 7, 2010) * Fixing slice error when passing in custom resource name - see issue #112. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.3.1...1.3.2]. ## 1.3.1 (August 6, 2010) * Fixing protected sanitize_sql error - see issue #111. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.3.0...1.3.1]. ## 1.3.0 (August 6, 2010) * Adding :find_by option to load_resource - see issue #19. * Adding :singleton option to load_resource - see issue #93. * Supporting multiple resources in :through option for polymorphic associations - see issue #73. * Supporting Single Table Inheritance for "can" comparisons - see issue #55. * Adding :instance_name option to load/authorize_resource - see issue #44. * Don't pass nil to "new" to keep MongoMapper happy - see issue #63. * Parent resources are now authorized with :read action. * Changing :resource option in load/authorize_resource back to :class with ability to pass false. * Removing :nested option in favor of :through option with separate load/authorize call. * Moving internal logic from ResourceAuthorization to ControllerResource class. * Supporting multiple "can" and "cannot" calls with accessible_by (funny-falcon) - see issue #71. * Supporting deeply nested aliases - see issue #98. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.2.0...1.3.0]. ## 1.2.0 (July 16, 2010) * Load nested parent resources on collection actions such as "index" (dohzya). * Adding :name option to load_and_authorize_resource if it does not match controller - see issue #65. * Fixing issue when using accessible_by with nil can conditions (jrallison) - see issue #66. * Pluralize table name for belongs_to associations in can conditions hash (logandk) - see issue #62. * Support has_many association or arrays in can conditions hash. * Adding joins clause to accessible_by when conditions are across associations. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.1.1...1.2.0]. ## 1.1.1 (April 17, 2010) * Fixing behavior in Rails 3 by properly initializing ResourceAuthorization. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.1...1.1.1]. ## 1.1.0 (April 17, 2010) * Supporting arrays, ranges, and nested hashes in ability conditions. * Removing "unauthorized!" method in favor of "authorize!" in controllers. * Adding action, subject and default_message abilities to AccessDenied exception - see issue #40. * Adding caching to current_ability controller method, if you're overriding this be sure to add caching too. * Adding "accessible_by" method to Active Record for fetching records matching a specific ability. * Adding conditions behavior to Ability#can and fetch with Ability#conditions - see issue #53. * Renaming :class option to :resource for load_and_authorize_resource which now supports a symbol for non models - see issue #45. * Properly handle Admin::AbilitiesController in params[:controller] - see issue #46. * Adding be_able_to RSpec matcher (dchelimsky), requires Ruby 1.8.7 or higher - see issue #54. * Support additional arguments to can? which get passed to the block - see issue #48. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.0.2...1.1]. ## 1.0.2 (Dec 30, 2009) * Adding clear_aliased_actions to Ability which removes previously defined actions including defaults - see issue #20. * Append aliased actions (don't overwrite them) - see issue #20. * Adding custom message argument to unauthorized! method (tjwallace) - see issue #18. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.0.1...1.0.2]. ## 1.0.1 (Dec 14, 2009) * Adding :class option to load_resource so one can customize which class to use for the model - see issue #17. * Don't fetch parent of nested resource if *_id parameter is missing so it works with shallow nested routes - see issue #14. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/1.0.0...1.0.1]. ## 1.0.0 (Dec 13, 2009) * Don't set resource instance variable if it has been set already - see issue #13. * Allowing :nested option to accept an array for deep nesting. * Adding :nested option to load resource method - see issue #10. * Pass :only and :except options to before filters for load/authorize resource methods. * Adding :collection and :new options to load_resource method so we can specify behavior of additional actions if needed. * BACKWARDS INCOMPATIBLE: turning load and authorize resource methods into class methods which set up the before filter so they can accept additional arguments. * {see the full list of changes}[https://github.com/CanCanCommunity/cancancan/compare/0.2.1...1.0.0]. ## 0.2.1 (Nov 26, 2009) * Many internal refactorings - see issues #11 and #12. * Adding "cannot" method to define which abilities cannot be done - see issue #7. * Support custom objects (usually symbols) in can definition - see issue #8. * See the full list of changes [https://github.com/CanCanCommunity/cancancan/compare/0.2.0...0.2.1]. ## 0.2.0 (Nov 17, 2009) * Fix behavior of load_and_authorize_resource for namespaced controllers - see issue #3. * Support arrays being passed to "can" to specify multiple actions or classes - see issue #2. * Adding "cannot?" method to ability, controller, and view which is inverse of "can?" - see issue #1. * BACKWARDS INCOMPATIBLE: use Ability#initialize instead of 'prepare' to set up abilities - see issue #4. * See the full list of changes [https://github.com/CanCanCommunity/cancancan/compare/0.1.0...0.2.0]. ## 0.1.0 (Nov 16, 2009) * Initial release. [@coorasse]: https://github.com/coorasse [@kraflab]: https://github.com/kraflab [@lizzyaustad]: https://github.com/lizzyaustad [@kevintyll]: https://github.com/kevintyll [@BookOfGreg]: https://github.com/BookOfGreg [@arturoherrero]: https://github.com/arturoherrero [@Eric-Guo]: https://github.com/Eric-Guo [@ajgon]: https://github.com/ajgon [@jpmckinney]: https://github.com/jpmckinney [@sethcharles]: https://github.com/sethcharles [@anilmaurya]: https://github.com/anilmaurya [@DNNX]: https://github.com/DNNX [@clemens]: https://github.com/clemens [@bryanrite]: https://github.com/bryanrite [@emmanuel]: https://github.com/emmanuel [@thatothermitch]: https://github.com/thatothermitch [@amw]: https://github.com/amw [@stellard]: https://github.com/stellard [@natemueller]: https://github.com/natemueller [@bowsersenior]: https://github.com/bowsersenior [@hunterae]: https://github.com/hunterae [@nandalopes]: https://github.com/nandalopes [@ramontayag]: https://github.com/ramontayag [@mphalliday]: https://github.com/mphalliday [@justinko]: https://github.com/justinko [@EppO]: https://github.com/EppO [@tamoya]: https://github.com/tamoya [@mlooney]: https://github.com/mlooney [@rrosen]: https://github.com/rrosen [@steerio]: https://github.com/steerio [@yuszuv]: https://github.com/yuszuv [@albertobajo]: https://github.com/albertobajo [@xinuc]: https://github.com/xinuc [@oliverklee]: https://github.com/oliverklee [@gingray]: https://github.com/gingray [@timraymond]: https://github.com/timraymond [@s-mage]: https://github.com/s-mage [@Jcambass]: https://github.com/Jcambass [@nyamadori]: https://github.com/nyamadori [@andrew-aladev]: https://github.com/andrew-aladev [@phaedryx]: https://github.com/phaedryx [@kaspernj]: https://github.com/kaspernj cancancan-3.0.1/CONTRIBUTING.md000066400000000000000000000027151345516370100156630ustar00rootroot00000000000000## Contributing to CanCanCan ### Reporting an Issue 1. If you have any questions about CanCanCan, search the [Wiki](https://github.com/cancancommunity/cancancan/wiki) or use [Stack Overflow](http://stackoverflow.com/questions/tagged/cancancan). Do not post questions here. 1. If you find a security bug, **DO NOT** submit an issue here. Please send an e-mail to the [current maintainer](https://github.com/coorasse) instead. 1. Do a small search on the issues tracker before submitting your issue to see if it was already reported / fixed. 1. Create your report including Rails and CanCanCan versions. If you are getting exceptions, please include the full backtrace. Use the [following gist](https://gist.github.com/coorasse/3f00f536563249125a37e15a1652648c) as a base to reproduce your bug. That's it! The more information you give, the more easy it becomes for us to track it down and fix it. Ideal scenario would be adding the issue to CanCanCan test suite or to a sample application. ### Adding new Features or Bugfixes CanCanCan uses a [git-flow](http://nvie.com/posts/a-successful-git-branching-model/) development model. The latest "released" version of CanCanCan, the latest gem version, can always be found on `master`, while the next version or nightly is on `develop`. Please make sure you have test coverage for anything you add or fix! Please add a CHANGELOG entry with any relevant tags for issues, pull-requests, and authors. Thanks for you contribution! cancancan-3.0.1/Gemfile000066400000000000000000000001061345516370100147150ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gemspec cancancan-3.0.1/LICENSE000066400000000000000000000020531345516370100144320ustar00rootroot00000000000000Copyright (c) 2011 Ryan Bates MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cancancan-3.0.1/README.md000066400000000000000000000236471345516370100147200ustar00rootroot00000000000000# CanCanCan ![CanCanCan Logo](/logo/cancancan.jpg) [![Gem Version](https://badge.fury.io/rb/cancancan.svg)](http://badge.fury.io/rb/cancancan) [![Travis badge](https://travis-ci.org/CanCanCommunity/cancancan.svg?branch=develop)](https://travis-ci.org/CanCanCommunity/cancancan) [![Code Climate Badge](https://codeclimate.com/github/CanCanCommunity/cancancan.svg)](https://codeclimate.com/github/CanCanCommunity/cancancan) [Wiki](https://github.com/CanCanCommunity/cancancan/wiki) | [RDocs](http://rdoc.info/projects/CanCanCommunity/cancancan) | [Screencast 1](http://railscasts.com/episodes/192-authorization-with-cancan) | [Screencast 2](https://www.youtube.com/watch?v=cTYu-OjUgDw) CanCanCan is an authorization library for Ruby >= 2.2.0 and Ruby on Rails >= 4.2 which restricts what resources a given user is allowed to access. All permissions can be defined in one or multiple ability files and not duplicated across controllers, views, and database queries, keeping your permissions logic in one place for easy maintenance and testing. It consists of two main parts: 1. **Authorizations library** that allows you to define the rules to access different objects, and provides helpers to check for those permissions. 2. **Rails helpers** to simplify the code in Rails Controllers by performing the loading and checking of permissions of models automatically and reduce duplicated code. ## Installation Add this to your Gemfile: gem 'cancancan' and run the `bundle install` command. ## Define Abilities User permissions are defined in an `Ability` class. rails g cancan:ability Here follows an example of rules defined to read a Post model. ```ruby class Ability include CanCan::Ability def initialize(user) can :read, Post, public: true if user.present? # additional permissions for logged in users (they can read their own posts) can :read, Post, user_id: user.id if user.admin? # additional permissions for administrators can :read, post end end end end ``` See [Defining Abilities](https://github.com/CanCanCommunity/cancancan/wiki/defining-abilities) for details on how to define your rules. ## Check Abilities The current user's permissions can then be checked using the `can?` and `cannot?` methods in views and controllers. ```erb <% if can? :read, @post %> <%= link_to "View", @post %> <% end %> ``` See [Checking Abilities](https://github.com/CanCanCommunity/cancancan/wiki/checking-abilities) for more information on how you can use these helpers. ## Fetching records One of the key features of CanCanCan, compared to other authorization libraries, is the possibility to retrieve all the objects that the user is authorized to access. The following: ```ruby Post.accessible_by(current_ability) ``` will use your rules to ensure that the user retrieves only a list of posts that can be read. See [Fetching records](https://github.com/CanCanCommunity/cancancan/wiki/Fetching-Records) for details. ## Controller helpers CanCanCan expects a `current_user` method to exist in the controller. First, set up some authentication (such as [Devise](https://github.com/plataformatec/devise) or [Authlogic](https://github.com/binarylogic/authlogic)). See [Changing Defaults](https://github.com/CanCanCommunity/cancancan/wiki/changing-defaults) if you need a different behavior. ### 3.1 Authorizations The `authorize!` method in the controller will raise an exception if the user is not able to perform the given action. ```ruby def show @post = Post.find(params[:id]) authorize! :read, @post end ``` ### 3.2 Loaders Setting this for every action can be tedious, therefore the `load_and_authorize_resource` method is provided to automatically authorize all actions in a RESTful style resource controller. It will use a before action to load the resource into an instance variable and authorize it for every action. ```ruby class PostsController < ApplicationController load_and_authorize_resource def show # @post is already loaded and authorized end def index # @posts is already loaded with all posts the user is authorized to read end end ``` See [Authorizing Controller Actions](https://github.com/CanCanCommunity/cancancan/wiki/authorizing-controller-actions) for more information. ### 3.3 Strong Parameters You have to sanitize inputs before saving the record, in actions such as `:create` and `:update`. For the `:update` action, CanCanCan will load and authorize the resource but *not* change it automatically, so the typical usage would be something like: ```ruby def update if @post.update(post_params) # hurray else render :edit end end ... def post_params params.require(:post).permit(:body) end ``` For the `:create` action, CanCanCan will try to initialize a new instance with sanitized input by seeing if your controller will respond to the following methods (in order): 1. `create_params` 2. `_params` such as `post_params` (this is the default convention in rails for naming your param method) 3. `resource_params` (a generically named method you could specify in each controller) Additionally, `load_and_authorize_resource` can now take a `param_method` option to specify a custom method in the controller to run to sanitize input. You can associate the `param_method` option with a symbol corresponding to the name of a method that will get called: ```ruby class PostsController < ApplicationController load_and_authorize_resource param_method: :my_sanitizer def create if @post.save # hurray else render :new end end private def my_sanitizer params.require(:post).permit(:name) end end ``` You can also use a string that will be evaluated in the context of the controller using `instance_eval` and needs to contain valid Ruby code. load_and_authorize_resource param_method: 'permitted_params.post' Finally, it's possible to associate `param_method` with a Proc object which will be called with the controller as the only argument: load_and_authorize_resource param_method: Proc.new { |c| c.params.require(:post).permit(:name) } See [Strong Parameters](https://github.com/CanCanCommunity/cancancan/wiki/Strong-Parameters) for more information. ## Handle Unauthorized Access If the user authorization fails, a `CanCan::AccessDenied` exception will be raised. You can catch this and modify its behavior in the `ApplicationController`. ```ruby class ApplicationController < ActionController::Base rescue_from CanCan::AccessDenied do |exception| respond_to do |format| format.json { head :forbidden, content_type: 'text/html' } format.html { redirect_to main_app.root_url, notice: exception.message } format.js { head :forbidden, content_type: 'text/html' } end end end ``` See [Exception Handling](https://github.com/CanCanCommunity/cancancan/wiki/exception-handling) for more information. ## Lock It Down If you want to ensure authorization happens on every action in your application, add `check_authorization` to your `ApplicationController`. ```ruby class ApplicationController < ActionController::Base check_authorization end ``` This will raise an exception if authorization is not performed in an action. If you want to skip this, add `skip_authorization_check` to a controller subclass. See [Ensure Authorization](https://github.com/CanCanCommunity/cancancan/wiki/Ensure-Authorization) for more information. ## Wiki Docs * [Defining Abilities](https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities) * [Checking Abilities](https://github.com/CanCanCommunity/cancancan/wiki/Checking-Abilities) * [Authorizing Controller Actions](https://github.com/CanCanCommunity/cancancan/wiki/Authorizing-Controller-Actions) * [Exception Handling](https://github.com/CanCanCommunity/cancancan/wiki/Exception-Handling) * [Changing Defaults](https://github.com/CanCanCommunity/cancancan/wiki/Changing-Defaults) * [See more](https://github.com/CanCanCommunity/cancancan/wiki) ## Mission This repo is a continuation of the dead [CanCan](https://github.com/ryanb/cancan) project. Our mission is to keep CanCan alive and moving forward, with maintenance fixes and new features. Pull Requests are welcome! Any help is greatly appreciated, feel free to submit pull-requests or open issues. ## Questions? If you have any question or doubt regarding CanCanCan which you cannot find the solution to in the [documentation](https://github.com/CanCanCommunity/cancancan/wiki) or our [mailing list](http://groups.google.com/group/cancancan), please [open a question on Stackoverflow](http://stackoverflow.com/questions/ask?tags=cancancan) with tag [cancancan](http://stackoverflow.com/questions/tagged/cancancan) ## Bugs? If you find a bug please add an [issue on GitHub](https://github.com/CanCanCommunity/cancancan/issues) or fork the project and send a pull request. ## Development CanCanCan uses [appraisals](https://github.com/thoughtbot/appraisal) to test the code base against multiple versions of Rails, as well as the different model adapters. When first developing, you need to run `bundle install` and then `appraisal install`, to install the different sets. You can then run all appraisal files (like CI does), with `appraisal rake` or just run a specific set `appraisal activerecord_5.0 rake`. See the [CONTRIBUTING](https://github.com/CanCanCommunity/cancancan/blob/develop/CONTRIBUTING.md) and [spec/README](https://github.com/CanCanCommunity/cancancan/blob/master/spec/README.rdoc) for more information. ## Special Thanks [![Renuo AG](/logo/renuo.png)](https://www.renuo.ch) Thanks to [Renuo AG](https://www.renuo.ch) for currently maintaining and supporting the project. Also many thanks to the [CanCanCan contributors](https://github.com/CanCanCommunity/cancancan/contributors). See the [CHANGELOG](https://github.com/CanCanCommunity/cancancan/blob/master/CHANGELOG.md) for the full list. CanCanCan was inspired by [declarative_authorization](https://github.com/stffn/declarative_authorization/) and [aegis](https://github.com/makandra/aegis). cancancan-3.0.1/Rakefile000066400000000000000000000002721345516370100150730ustar00rootroot00000000000000# frozen_string_literal: true require 'bundler/gem_tasks' require 'rspec/core/rake_task' desc 'Run RSpec' RSpec::Core::RakeTask.new do |t| t.verbose = false end task default: :spec cancancan-3.0.1/cancancan.gemspec000066400000000000000000000021711345516370100167000ustar00rootroot00000000000000# frozen_string_literal: true lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'cancan/version' Gem::Specification.new do |s| s.name = 'cancancan' s.version = CanCan::VERSION s.authors = ['Alessandro Rodi (Renuo AG)', 'Bryan Rite', 'Ryan Bates', 'Richard Wilson'] s.email = 'alessandro.rodi@renuo.ch' s.homepage = 'https://github.com/CanCanCommunity/cancancan' s.summary = 'Simple authorization solution for Rails.' s.description = 'Simple authorization solution for Rails. All permissions are stored in a single location.' s.platform = Gem::Platform::RUBY s.license = 'MIT' s.files = `git ls-files lib init.rb cancancan.gemspec`.split($INPUT_RECORD_SEPARATOR) s.require_paths = ['lib'] s.required_ruby_version = '>= 2.2.0' s.add_development_dependency 'appraisal', '~> 2.0', '>= 2.0.0' s.add_development_dependency 'bundler', '~> 2.0' s.add_development_dependency 'rake', '~> 10.1', '>= 10.1.1' s.add_development_dependency 'rspec', '~> 3.2', '>= 3.2.0' s.add_development_dependency 'rubocop', '~> 0.63.1' end cancancan-3.0.1/gemfiles/000077500000000000000000000000001345516370100152205ustar00rootroot00000000000000cancancan-3.0.1/gemfiles/activerecord_4.2.0.gemfile000066400000000000000000000010151345516370100217420ustar00rootroot00000000000000# This file was generated by Appraisal source "https://rubygems.org" gem "activerecord", "~> 4.2.0", require: "active_record" gem "activesupport", "~> 4.2.0", require: "active_support/all" gem "actionpack", "~> 4.2.0", require: "action_pack" gem "nokogiri", "~> 1.6.8", require: "nokogiri" platforms :jruby do gem "activerecord-jdbcsqlite3-adapter", "~> 1.3.24" gem "jdbc-sqlite3" gem "jdbc-postgres" end platforms :ruby, :mswin, :mingw do gem "sqlite3", "~> 1.3.0" gem "pg", "~> 0.21" end gemspec path: "../" cancancan-3.0.1/gemfiles/activerecord_5.0.2.gemfile000066400000000000000000000007201345516370100217450ustar00rootroot00000000000000# This file was generated by Appraisal source "https://rubygems.org" gem "activerecord", "~> 5.0.2", require: "active_record" gem "activesupport", "~> 5.0.2", require: "active_support/all" gem "actionpack", "~> 5.0.2", require: "action_pack" platforms :jruby do gem "activerecord-jdbcsqlite3-adapter" gem "jdbc-sqlite3" gem "jdbc-postgres" end platforms :ruby, :mswin, :mingw do gem "sqlite3", "~> 1.3.0" gem "pg", "~> 0.21" end gemspec path: "../" cancancan-3.0.1/gemfiles/activerecord_5.1.0.gemfile000066400000000000000000000007201345516370100217440ustar00rootroot00000000000000# This file was generated by Appraisal source "https://rubygems.org" gem "activerecord", "~> 5.1.0", require: "active_record" gem "activesupport", "~> 5.1.0", require: "active_support/all" gem "actionpack", "~> 5.1.0", require: "action_pack" platforms :jruby do gem "activerecord-jdbcsqlite3-adapter" gem "jdbc-sqlite3" gem "jdbc-postgres" end platforms :ruby, :mswin, :mingw do gem "sqlite3", "~> 1.3.0" gem "pg", "~> 0.21" end gemspec path: "../" cancancan-3.0.1/gemfiles/activerecord_5.2.2.gemfile000066400000000000000000000007201345516370100217470ustar00rootroot00000000000000# This file was generated by Appraisal source "https://rubygems.org" gem "activerecord", "~> 5.2.2", require: "active_record" gem "activesupport", "~> 5.2.2", require: "active_support/all" gem "actionpack", "~> 5.2.2", require: "action_pack" platforms :jruby do gem "activerecord-jdbcsqlite3-adapter" gem "jdbc-sqlite3" gem "jdbc-postgres" end platforms :ruby, :mswin, :mingw do gem "sqlite3", "~> 1.3.0" gem "pg", "~> 0.21" end gemspec path: "../" cancancan-3.0.1/gemfiles/activerecord_6.0.0.gemfile000066400000000000000000000007431345516370100217510ustar00rootroot00000000000000# This file was generated by Appraisal source "https://rubygems.org" gem "actionpack", "~> 6.0.0.beta3", require: "action_pack" gem "activerecord", "~> 6.0.0.beta3", require: "active_record" gem "activesupport", "~> 6.0.0.beta3", require: "active_support/all" platforms :jruby do gem "activerecord-jdbcsqlite3-adapter" gem "jdbc-sqlite3" gem "jdbc-postgres" end platforms :ruby, :mswin, :mingw do gem "pg", "~> 1.1.4" gem "sqlite3", "~> 1.3.0" end gemspec path: "../" cancancan-3.0.1/init.rb000066400000000000000000000000601345516370100147110ustar00rootroot00000000000000# frozen_string_literal: true require 'cancan' cancancan-3.0.1/lib/000077500000000000000000000000001345516370100141735ustar00rootroot00000000000000cancancan-3.0.1/lib/cancan.rb000066400000000000000000000013221345516370100157410ustar00rootroot00000000000000# frozen_string_literal: true require 'cancan/version' require 'cancan/parameter_validators' require 'cancan/ability' require 'cancan/rule' require 'cancan/controller_resource' require 'cancan/controller_additions' require 'cancan/model_additions' require 'cancan/exceptions' require 'cancan/model_adapters/abstract_adapter' require 'cancan/model_adapters/default_adapter' require 'cancan/rules_compressor' if defined? ActiveRecord require 'cancan/model_adapters/conditions_extractor' require 'cancan/model_adapters/conditions_normalizer' require 'cancan/model_adapters/active_record_adapter' require 'cancan/model_adapters/active_record_4_adapter' require 'cancan/model_adapters/active_record_5_adapter' end cancancan-3.0.1/lib/cancan/000077500000000000000000000000001345516370100154165ustar00rootroot00000000000000cancancan-3.0.1/lib/cancan/ability.rb000066400000000000000000000250461345516370100174070ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'ability/rules.rb' require_relative 'ability/actions.rb' require_relative 'unauthorized_message_resolver.rb' require_relative 'ability/strong_parameter_support' module CanCan # This module is designed to be included into an Ability class. This will # provide the "can" methods for defining and checking abilities. # # class Ability # include CanCan::Ability # # def initialize(user) # if user.admin? # can :manage, :all # else # can :read, :all # end # end # end # module Ability include CanCan::Ability::Rules include CanCan::Ability::Actions include CanCan::UnauthorizedMessageResolver include StrongParameterSupport # Check if the user has permission to perform a given action on an object. # # can? :destroy, @project # # You can also pass the class instead of an instance (if you don't have one handy). # # can? :create, Project # # Nested resources can be passed through a hash, this way conditions which are # dependent upon the association will work when using a class. # # can? :create, @category => Project # # You can also pass multiple objects to check. You only need to pass a hash # following the pattern { :any => [many subjects] }. The behaviour is check if # there is a permission on any of the given objects. # # can? :create, {:any => [Project, Rule]} # # # Any additional arguments will be passed into the "can" block definition. This # can be used to pass more information about the user's request for example. # # can? :create, Project, request.remote_ip # # can :create, Project do |project, remote_ip| # # ... # end # # Not only can you use the can? method in the controller and view (see ControllerAdditions), # but you can also call it directly on an ability instance. # # ability.can? :destroy, @project # # This makes testing a user's abilities very easy. # # def test "user can only destroy projects which he owns" # user = User.new # ability = Ability.new(user) # assert ability.can?(:destroy, Project.new(:user => user)) # assert ability.cannot?(:destroy, Project.new) # end # # Also see the RSpec Matchers to aid in testing. def can?(action, subject, attribute = nil, *extra_args) match = extract_subjects(subject).lazy.map do |a_subject| relevant_rules_for_match(action, a_subject).detect do |rule| rule.matches_conditions?(action, a_subject, attribute, *extra_args) && rule.matches_attributes?(attribute) end end.reject(&:nil?).first match ? match.base_behavior : false end # Convenience method which works the same as "can?" but returns the opposite value. # # cannot? :destroy, @project # def cannot?(*args) !can?(*args) end # Defines which abilities are allowed using two arguments. The first one is the action # you're setting the permission for, the second one is the class of object you're setting it on. # # can :update, Article # # You can pass an array for either of these parameters to match any one. # Here the user has the ability to update or destroy both articles and comments. # # can [:update, :destroy], [Article, Comment] # # You can pass :all to match any object and :manage to match any action. Here are some examples. # # can :manage, :all # can :update, :all # can :manage, Project # # You can pass a hash of conditions as the third argument. Here the user can only see active projects which he owns. # # can :read, Project, :active => true, :user_id => user.id # # See ActiveRecordAdditions#accessible_by for how to use this in database queries. These conditions # are also used for initial attributes when building a record in ControllerAdditions#load_resource. # # If the conditions hash does not give you enough control over defining abilities, you can use a block # along with any Ruby code you want. # # can :update, Project do |project| # project.groups.include?(user.group) # end # # If the block returns true then the user has that :update ability for that project, otherwise he # will be denied access. The downside to using a block is that it cannot be used to generate # conditions for database queries. # # You can pass custom objects into this "can" method, this is usually done with a symbol # and is useful if a class isn't available to define permissions on. # # can :read, :stats # can? :read, :stats # => true # # IMPORTANT: Neither a hash of conditions nor a block will be used when checking permission on a class. # # can :update, Project, :priority => 3 # can? :update, Project # => true # # If you pass no arguments to +can+, the action, class, and object will be passed to the block and the # block will always be executed. This allows you to override the full behavior if the permissions are # defined in an external source such as the database. # # can do |action, object_class, object| # # check the database and return true/false # end # def can(action = nil, subject = nil, *attributes_and_conditions, &block) add_rule(Rule.new(true, action, subject, *attributes_and_conditions, &block)) end # Defines an ability which cannot be done. Accepts the same arguments as "can". # # can :read, :all # cannot :read, Comment # # A block can be passed just like "can", however if the logic is complex it is recommended # to use the "can" method. # # cannot :read, Product do |product| # product.invisible? # end # def cannot(action = nil, subject = nil, *attributes_and_conditions, &block) add_rule(Rule.new(false, action, subject, *attributes_and_conditions, &block)) end # User shouldn't specify targets with names of real actions or it will cause Seg fault def validate_target(target) error_message = "You can't specify target (#{target}) as alias because it is real action name" raise Error, error_message if aliased_actions.values.flatten.include? target end def model_adapter(model_class, action) adapter_class = ModelAdapters::AbstractAdapter.adapter_class(model_class) adapter_class.new(model_class, relevant_rules_for_query(action, model_class)) end # See ControllerAdditions#authorize! for documentation. def authorize!(action, subject, *args) message = args.last.is_a?(Hash) && args.last.key?(:message) ? args.pop[:message] : nil if cannot?(action, subject, *args) message ||= unauthorized_message(action, subject) raise AccessDenied.new(message, action, subject, args) end subject end def attributes_for(action, subject) attributes = {} relevant_rules(action, subject).map do |rule| attributes.merge!(rule.attributes_from_conditions) if rule.base_behavior end attributes end def has_block?(action, subject) relevant_rules(action, subject).any?(&:only_block?) end def has_raw_sql?(action, subject) relevant_rules(action, subject).any?(&:only_raw_sql?) end # Copies all rules and aliased actions of the given +CanCan::Ability+ and adds them to +self+. # class ReadAbility # include CanCan::Ability # # def initialize # can :read, User # alias_action :show, :index, to: :see # end # end # # class WritingAbility # include CanCan::Ability # # def initialize # can :edit, User # alias_action :create, :update, to: :modify # end # end # # read_ability = ReadAbility.new # read_ability.can? :edit, User.new #=> false # read_ability.merge(WritingAbility.new) # read_ability.can? :edit, User.new #=> true # read_ability.aliased_actions #=> [:see => [:show, :index], :modify => [:create, :update]] # # If there are collisions when merging the +aliased_actions+, the actions on +self+ will be # overwritten. # # class ReadAbility # include CanCan::Ability # # def initialize # alias_action :show, :index, to: :see # end # end # # class ShowAbility # include CanCan::Ability # # def initialize # alias_action :show, to: :see # end # end # # read_ability = ReadAbility.new # read_ability.merge(ShowAbility) # read_ability.aliased_actions #=> [:see => [:show]] def merge(ability) ability.rules.each do |rule| add_rule(rule.dup) end @aliased_actions = aliased_actions.merge(ability.aliased_actions) self end # Return a hash of permissions for the user in the format of: # { # can: can_hash, # cannot: cannot_hash # } # # Where can_hash and cannot_hash are formatted thusly: # { # action: { subject: [attributes] } # } def permissions permissions_list = { can: Hash.new { |actions, k1| actions[k1] = Hash.new { |subjects, k2| subjects[k2] = [] } }, cannot: Hash.new { |actions, k1| actions[k1] = Hash.new { |subjects, k2| subjects[k2] = [] } } } rules.each { |rule| extract_rule_in_permissions(permissions_list, rule) } permissions_list end def extract_rule_in_permissions(permissions_list, rule) expand_actions(rule.actions).each do |action| container = rule.base_behavior ? :can : :cannot rule.subjects.each do |subject| permissions_list[container][action][subject.to_s] += rule.attributes end end end private def unauthorized_message_keys(action, subject) subject = (subject.class == Class ? subject : subject.class).name.underscore unless subject.is_a? Symbol aliases = aliases_for_action(action) [subject, :all].product([*aliases, :manage]).map do |try_subject, try_action| :"#{try_action}.#{try_subject}" end end # It translates to an array the subject or the hash with multiple subjects given to can?. def extract_subjects(subject) if subject.is_a?(Hash) && subject.key?(:any) subject[:any] else [subject] end end def alternative_subjects(subject) subject = subject.class unless subject.is_a?(Module) [:all, *subject.ancestors, subject.class.to_s] end end end cancancan-3.0.1/lib/cancan/ability/000077500000000000000000000000001345516370100170535ustar00rootroot00000000000000cancancan-3.0.1/lib/cancan/ability/actions.rb000066400000000000000000000056071345516370100210500ustar00rootroot00000000000000# frozen_string_literal: true module CanCan module Ability module Actions # Alias one or more actions into another one. # # alias_action :update, :destroy, :to => :modify # can :modify, Comment # # Then :modify permission will apply to both :update and :destroy requests. # # can? :update, Comment # => true # can? :destroy, Comment # => true # # This only works in one direction. Passing the aliased action into the "can?" call # will not work because aliases are meant to generate more generic actions. # # alias_action :update, :destroy, :to => :modify # can :update, Comment # can? :modify, Comment # => false # # Unless that exact alias is used. # # can :modify, Comment # can? :modify, Comment # => true # # The following aliases are added by default for conveniently mapping common controller actions. # # alias_action :index, :show, :to => :read # alias_action :new, :to => :create # alias_action :edit, :to => :update # # This way one can use params[:action] in the controller to determine the permission. def alias_action(*args) target = args.pop[:to] validate_target(target) aliased_actions[target] ||= [] aliased_actions[target] += args end # Returns a hash of aliased actions. The key is the target and the value is an array of actions aliasing the key. def aliased_actions @aliased_actions ||= default_alias_actions end # Removes previously aliased actions including the defaults. def clear_aliased_actions @aliased_actions = {} end private def default_alias_actions { read: %i[index show], create: [:new], update: [:edit] } end # Given an action, it will try to find all of the actions which are aliased to it. # This does the opposite kind of lookup as expand_actions. def aliases_for_action(action) results = [action] aliased_actions.each do |aliased_action, actions| results += aliases_for_action(aliased_action) if actions.include? action end results end def expanded_actions @expanded_actions ||= {} end # Accepts an array of actions and returns an array of actions which match. # This should be called before "matches?" and other checking methods since they # rely on the actions to be expanded. def expand_actions(actions) expanded_actions[actions] ||= begin expanded = [] actions.each do |action| expanded << action if (aliases = aliased_actions[action]) expanded += expand_actions(aliases) end end expanded end end end end end cancancan-3.0.1/lib/cancan/ability/rules.rb000066400000000000000000000056401345516370100205370ustar00rootroot00000000000000# frozen_string_literal: true module CanCan module Ability module Rules protected # Must be protected as an ability can merge with other abilities. # This means that an ability must expose their rules with another ability. def rules @rules ||= [] end private def add_rule(rule) rules << rule add_rule_to_index(rule, rules.size - 1) end def add_rule_to_index(rule, position) @rules_index ||= Hash.new { |h, k| h[k] = [] } subjects = rule.subjects.compact subjects << :all if subjects.empty? subjects.each do |subject| @rules_index[subject] << position end end # Returns an array of Rule instances which match the action and subject # This does not take into consideration any hash conditions or block statements def relevant_rules(action, subject) return [] unless @rules relevant = possible_relevant_rules(subject).select do |rule| rule.expanded_actions = expand_actions(rule.actions) rule.relevant? action, subject end relevant.reverse!.uniq! optimize_order! relevant relevant end def possible_relevant_rules(subject) if subject.is_a?(Hash) rules else positions = @rules_index.values_at(subject, *alternative_subjects(subject)) positions.flatten!.sort! positions.map { |i| @rules[i] } end end def relevant_rules_for_match(action, subject) relevant_rules(action, subject).each do |rule| next unless rule.only_raw_sql? raise Error, "The can? and cannot? call cannot be used with a raw sql 'can' definition."\ " The checking code cannot be determined for #{action.inspect} #{subject.inspect}" end end def relevant_rules_for_query(action, subject) rules = relevant_rules(action, subject).reject do |rule| # reject 'cannot' rules with attributes when doing queries rule.base_behavior == false && rule.attributes.present? end if rules.any?(&:only_block?) raise Error, "The accessible_by call cannot be used with a block 'can' definition."\ "The SQL cannot be determined for #{action.inspect} #{subject.inspect}" end rules end # Optimizes the order of the rules, so that rules with the :all subject are evaluated first. def optimize_order!(rules) first_can_in_group = -1 rules.each_with_index do |rule, i| (first_can_in_group = -1) && next unless rule.base_behavior (first_can_in_group = i) && next if first_can_in_group == -1 next unless rule.subjects == [:all] rules[i] = rules[first_can_in_group] rules[first_can_in_group] = rule first_can_in_group += 1 end end end end end cancancan-3.0.1/lib/cancan/ability/strong_parameter_support.rb000066400000000000000000000026751345516370100245620ustar00rootroot00000000000000# frozen_string_literal: true module CanCan module Ability module StrongParameterSupport # Returns an array of attributes suitable for use with strong parameters # # Note: reversing the relevant rules is important. Normal order means that 'cannot' # rules will come before 'can' rules. However, you can't remove attributes before # they are added. The 'reverse' is so that attributes will be added before the # 'cannot' rules remove them. def permitted_attributes(action, subject) relevant_rules(action, subject) .reverse .select { |rule| rule.matches_conditions? action, subject } .each_with_object(Set.new) do |rule, set| attributes = get_attributes(rule, subject) # add attributes for 'can', remove them for 'cannot' rule.base_behavior ? set.merge(attributes) : set.subtract(attributes) end.to_a end private def subject_class?(subject) klass = (subject.is_a?(Hash) ? subject.values.first : subject).class [Class, Module].include? klass end def get_attributes(rule, subject) klass = subject_class?(subject) ? subject : subject.class # empty attributes is an 'all' if rule.attributes.empty? && klass < ActiveRecord::Base klass.column_names.map(&:to_sym) - Array(klass.primary_key) else rule.attributes end end end end end cancancan-3.0.1/lib/cancan/conditions_matcher.rb000066400000000000000000000064201345516370100216210ustar00rootroot00000000000000# frozen_string_literal: true module CanCan module ConditionsMatcher # Matches the block or conditions hash def matches_conditions?(action, subject, attribute = nil, *extra_args) return call_block_with_all(action, subject, extra_args) if @match_all return matches_block_conditions(subject, attribute, *extra_args) if @block return matches_non_block_conditions(subject) unless conditions_empty? true end private def subject_class?(subject) klass = (subject.is_a?(Hash) ? subject.values.first : subject).class [Class, Module].include? klass end def matches_block_conditions(subject, *extra_args) return @base_behavior if subject_class?(subject) @block.call(subject, *extra_args.compact) end def matches_non_block_conditions(subject) return nested_subject_matches_conditions?(subject) if subject.class == Hash return matches_conditions_hash?(subject) unless subject_class?(subject) # Don't stop at "cannot" definitions when there are conditions. @base_behavior end def nested_subject_matches_conditions?(subject_hash) parent, _child = subject_hash.first matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym] || {}) end # Checks if the given subject matches the given conditions hash. # This behavior can be overriden by a model adapter by defining two class methods: # override_matching_for_conditions?(subject, conditions) and # matches_conditions_hash?(subject, conditions) def matches_conditions_hash?(subject, conditions = @conditions) return true if conditions.empty? adapter = model_adapter(subject) if adapter.override_conditions_hash_matching?(subject, conditions) return adapter.matches_conditions_hash?(subject, conditions) end matches_all_conditions?(adapter, conditions, subject) end def matches_all_conditions?(adapter, conditions, subject) conditions.all? do |name, value| if adapter.override_condition_matching?(subject, name, value) adapter.matches_condition?(subject, name, value) else condition_match?(subject.send(name), value) end end end def condition_match?(attribute, value) case value when Hash hash_condition_match?(attribute, value) when Range value.cover?(attribute) when Enumerable value.include?(attribute) else attribute == value end end def hash_condition_match?(attribute, value) if attribute.is_a?(Array) || (defined?(ActiveRecord) && attribute.is_a?(ActiveRecord::Relation)) attribute.any? { |element| matches_conditions_hash?(element, value) } else attribute && matches_conditions_hash?(attribute, value) end end def call_block_with_all(action, subject, *extra_args) if subject.class == Class @block.call(action, subject, nil, *extra_args) else @block.call(action, subject.class, subject, *extra_args) end end def model_adapter(subject) CanCan::ModelAdapters::AbstractAdapter.adapter_class(subject_class?(subject) ? subject : subject.class) end def conditions_empty? @conditions == {} || @conditions.nil? end end end cancancan-3.0.1/lib/cancan/controller_additions.rb000066400000000000000000000367711345516370100222020ustar00rootroot00000000000000# frozen_string_literal: true module CanCan # This module is automatically included into all controllers. # It also makes the "can?" and "cannot?" methods available to all views. module ControllerAdditions module ClassMethods # Sets up a before filter which loads and authorizes the current resource. This performs both # load_resource and authorize_resource and accepts the same arguments. See those methods for details. # # class BooksController < ApplicationController # load_and_authorize_resource # end # def load_and_authorize_resource(*args) cancan_resource_class.add_before_action(self, :load_and_authorize_resource, *args) end # Sets up a before filter which loads the model resource into an instance variable. # For example, given an ArticlesController it will load the current article into the @article # instance variable. It does this by either calling Article.find(params[:id]) or # Article.new(params[:article]) depending upon the action. The index action will # automatically set @articles to Article.accessible_by(current_ability). # # If a conditions hash is used in the Ability, the +new+ and +create+ actions will set # the initial attributes based on these conditions. This way these actions will satisfy # the ability restrictions. # # Call this method directly on the controller class. # # class BooksController < ApplicationController # load_resource # end # # A resource is not loaded if the instance variable is already set. This makes it easy to override # the behavior through a before_action on certain actions. # # class BooksController < ApplicationController # before_action :find_book_by_permalink, :only => :show # load_resource # # private # # def find_book_by_permalink # @book = Book.find_by_permalink!(params[:id]) # end # end # # If a name is provided which does not match the controller it assumes it is a parent resource. Child # resources can then be loaded through it. # # class BooksController < ApplicationController # load_resource :author # load_resource :book, :through => :author # end # # Here the author resource will be loaded before each action using params[:author_id]. The book resource # will then be loaded through the @author instance variable. # # That first argument is optional and will default to the singular name of the controller. # A hash of options (see below) can also be passed to this method to further customize it. # # See load_and_authorize_resource to automatically authorize the resource too. # # Options: # [:+only+] # Only applies before filter to given actions. # # [:+except+] # Does not apply before filter to given actions. # # [:+through+] # Load this resource through another one. This should match the name of the parent instance variable or method. # # [:+through_association+] # The name of the association to fetch the child records through the parent resource. # This is normally not needed because it defaults to the pluralized resource name. # # [:+shallow+] # Pass +true+ to allow this resource to be loaded directly when parent is +nil+. Defaults to +false+. # # [:+singleton+] # Pass +true+ if this is a singleton resource through a +has_one+ association. # # [:+parent+] # True or false depending on if the resource is considered a parent resource. # This defaults to +true+ if a resource name is given which does not match the controller. # # [:+class+] # The class to use for the model (string or constant). # # [:+instance_name+] # The name of the instance variable to load the resource into. # # [:+find_by+] # Find using a different attribute other than id. For example. # # load_resource :find_by => :permalink # will use find_by_permalink!(params[:id]) # # [:+id_param+] # Find using a param key other than :id. For example: # # load_resource :id_param => :url # will use find(params[:url]) # # [:+collection+] # Specify which actions are resource collection actions in addition to :+index+. This # is usually not necessary because it will try to guess depending on if the id param is present. # # load_resource :collection => [:sort, :list] # # [:+new+] # Specify which actions are new resource actions in addition to :+new+ and :+create+. # Pass an action name into here if you would like to build a new resource instead of # fetch one. # # load_resource :new => :build # # [:+prepend+] # Passing +true+ will use prepend_before_action instead of a normal before_action. # def load_resource(*args) cancan_resource_class.add_before_action(self, :load_resource, *args) end # Sets up a before filter which authorizes the resource using the instance variable. # For example, if you have an ArticlesController it will check the @article instance variable # and ensure the user can perform the current action on it. Under the hood it is doing # something like the following. # # authorize!(params[:action].to_sym, @article || Article) # # Call this method directly on the controller class. # # class BooksController < ApplicationController # authorize_resource # end # # If you pass in the name of a resource which does not match the controller it will assume # it is a parent resource. # # class BooksController < ApplicationController # authorize_resource :author # authorize_resource :book # end # # Here it will authorize :+show+, @+author+ on every action before authorizing the book. # # That first argument is optional and will default to the singular name of the controller. # A hash of options (see below) can also be passed to this method to further customize it. # # See load_and_authorize_resource to automatically load the resource too. # # Options: # [:+only+] # Only applies before filter to given actions. # # [:+except+] # Does not apply before filter to given actions. # # [:+singleton+] # Pass +true+ if this is a singleton resource through a +has_one+ association. # # [:+parent+] # True or false depending on if the resource is considered a parent resource. # This defaults to +true+ if a resource name is given which does not match the controller. # # [:+class+] # The class to use for the model (string or constant). This passed in when the instance variable is not set. # Pass +false+ if there is no associated class for this resource and it will use a symbol of the resource name. # # [:+instance_name+] # The name of the instance variable for this resource. # # [:+through+] # Authorize conditions on this parent resource when instance isn't available. # # [:+prepend+] # Passing +true+ will use prepend_before_action instead of a normal before_action. # def authorize_resource(*args) cancan_resource_class.add_before_action(self, :authorize_resource, *args) end # Skip both the loading and authorization behavior of CanCan for this given controller. This is primarily # useful to skip the behavior of a superclass. You can pass :only and :except options to specify which actions # to skip the effects on. It will apply to all actions by default. # # class ProjectsController < SomeOtherController # skip_load_and_authorize_resource :only => :index # end # # You can also pass the resource name as the first argument to skip that resource. def skip_load_and_authorize_resource(*args) skip_load_resource(*args) skip_authorize_resource(*args) end # Skip the loading behavior of CanCan. This is useful when using +load_and_authorize_resource+ but want to # only do authorization on certain actions. You can pass :only and :except options to specify which actions to # skip the effects on. It will apply to all actions by default. # # class ProjectsController < ApplicationController # load_and_authorize_resource # skip_load_resource :only => :index # end # # You can also pass the resource name as the first argument to skip that resource. def skip_load_resource(*args) options = args.extract_options! name = args.first cancan_skipper[:load][name] = options end # Skip the authorization behavior of CanCan. This is useful when using +load_and_authorize_resource+ but want to # only do loading on certain actions. You can pass :only and :except options to specify which actions to # skip the effects on. It will apply to all actions by default. # # class ProjectsController < ApplicationController # load_and_authorize_resource # skip_authorize_resource :only => :index # end # # You can also pass the resource name as the first argument to skip that resource. def skip_authorize_resource(*args) options = args.extract_options! name = args.first cancan_skipper[:authorize][name] = options end # Add this to a controller to ensure it performs authorization through +authorize+! or +authorize_resource+ call. # If neither of these authorization methods are called, # a CanCan::AuthorizationNotPerformed exception will be raised. # This is normally added to the ApplicationController to ensure all controller actions do authorization. # # class ApplicationController < ActionController::Base # check_authorization # end # # See skip_authorization_check to bypass this check on specific controller actions. # # Options: # [:+only+] # Only applies to given actions. # # [:+except+] # Does not apply to given actions. # # [:+if+] # Supply the name of a controller method to be called. # The authorization check only takes place if this returns true. # # check_authorization :if => :admin_controller? # # [:+unless+] # Supply the name of a controller method to be called. # The authorization check only takes place if this returns false. # # check_authorization :unless => :devise_controller? # def check_authorization(options = {}) block = proc do |controller| next if controller.instance_variable_defined?(:@_authorized) next if options[:if] && !controller.send(options[:if]) next if options[:unless] && controller.send(options[:unless]) raise AuthorizationNotPerformed, 'This action failed the check_authorization because it does not authorize_resource. '\ 'Add skip_authorization_check to bypass this check.' end send(:after_action, options.slice(:only, :except), &block) end # Call this in the class of a controller to skip the check_authorization behavior on the actions. # # class HomeController < ApplicationController # skip_authorization_check :only => :index # end # # Any arguments are passed to the +before_action+ it triggers. def skip_authorization_check(*args) block = proc { |controller| controller.instance_variable_set(:@_authorized, true) } send(:before_action, *args, &block) end def cancan_resource_class ControllerResource end def cancan_skipper self._cancan_skipper ||= { authorize: {}, load: {} } end end def self.included(base) base.extend ClassMethods base.helper_method :can?, :cannot?, :current_ability if base.respond_to? :helper_method base.class_attribute :_cancan_skipper end # Raises a CanCan::AccessDenied exception if the current_ability cannot # perform the given action. This is usually called in a controller action or # before filter to perform the authorization. # # def show # @article = Article.find(params[:id]) # authorize! :read, @article # end # # A :message option can be passed to specify a different message. # # authorize! :read, @article, :message => "Not authorized to read #{@article.name}" # # You can also use I18n to customize the message. Action aliases defined in Ability work here. # # en: # unauthorized: # manage: # all: "Not authorized to %{action} %{subject}." # user: "Not allowed to manage other user accounts." # update: # project: "Not allowed to update this project." # # You can rescue from the exception in the controller to customize how unauthorized # access is displayed to the user. # # class ApplicationController < ActionController::Base # rescue_from CanCan::AccessDenied do |exception| # redirect_to root_url, :alert => exception.message # end # end # # See the CanCan::AccessDenied exception for more details on working with the exception. # # See the load_and_authorize_resource method to automatically add the authorize! behavior # to the default RESTful actions. def authorize!(*args) @_authorized = true current_ability.authorize!(*args) end # Creates and returns the current user's ability and caches it. If you # want to override how the Ability is defined then this is the place. # Just define the method in the controller to change behavior. # # def current_ability # # instead of Ability.new(current_user) # @current_ability ||= UserAbility.new(current_account) # end # # Notice it is important to cache the ability object so it is not # recreated every time. def current_ability @current_ability ||= ::Ability.new(current_user) end # Use in the controller or view to check the user's permission for a given action # and object. # # can? :destroy, @project # # You can also pass the class instead of an instance (if you don't have one handy). # # <% if can? :create, Project %> # <%= link_to "New Project", new_project_path %> # <% end %> # # If it's a nested resource, you can pass the parent instance in a hash. This way it will # check conditions which reach through that association. # # <% if can? :create, @category => Project %> # <%= link_to "New Project", new_project_path %> # <% end %> # # This simply calls "can?" on the current_ability. See Ability#can?. def can?(*args) current_ability.can?(*args) end # Convenience method which works the same as "can?" but returns the opposite value. # # cannot? :destroy, @project # def cannot?(*args) current_ability.cannot?(*args) end end end if defined? ActiveSupport ActiveSupport.on_load(:action_controller) do include CanCan::ControllerAdditions end end cancancan-3.0.1/lib/cancan/controller_resource.rb000066400000000000000000000102011345516370100220270ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'controller_resource_loader.rb' module CanCan # Handle the load and authorization controller logic # so we don't clutter up all controllers with non-interface methods. # This class is used internally, so you do not need to call methods directly on it. class ControllerResource # :nodoc: include ControllerResourceLoader def self.add_before_action(controller_class, method, *args) options = args.extract_options! resource_name = args.first before_action_method = before_callback_name(options) controller_class.send(before_action_method, options.slice(:only, :except, :if, :unless)) do |controller| controller.class.cancan_resource_class .new(controller, resource_name, options.except(:only, :except, :if, :unless)).send(method) end end def self.before_callback_name(options) options.delete(:prepend) ? :prepend_before_action : :before_action end def initialize(controller, *args) @controller = controller @params = controller.params @options = args.extract_options! @name = args.first end def load_and_authorize_resource load_resource authorize_resource end def authorize_resource return if skip?(:authorize) @controller.authorize!(authorization_action, resource_instance || resource_class_with_parent) end def parent? @options.key?(:parent) ? @options[:parent] : @name && @name != name_from_controller.to_sym end def skip?(behavior) return false unless (options = @controller.class.cancan_skipper[behavior][@name]) options == {} || options[:except] && !action_exists_in?(options[:except]) || action_exists_in?(options[:only]) end protected # Returns the class used for this resource. This can be overriden by the :class option. # If +false+ is passed in it will use the resource name as a symbol in which case it should # only be used for authorization, not loading since there's no class to load through. def resource_class case @options[:class] when false name.to_sym when nil namespaced_name.to_s.camelize.constantize when String @options[:class].constantize else @options[:class] end end def load_instance? parent? || member_action? end def load_collection? resource_base.respond_to?(:accessible_by) && !current_ability.has_block?(authorization_action, resource_class) end def member_action? new_actions.include?(@params[:action].to_sym) || @options[:singleton] || ((@params[:id] || @params[@options[:id_param]]) && !collection_actions.include?(@params[:action].to_sym)) end def resource_class_with_parent parent_resource ? { parent_resource => resource_class } : resource_class end def resource_instance=(instance) @controller.instance_variable_set("@#{instance_name}", instance) end def resource_instance return unless load_instance? && @controller.instance_variable_defined?("@#{instance_name}") @controller.instance_variable_get("@#{instance_name}") end def collection_instance=(instance) @controller.instance_variable_set("@#{instance_name.to_s.pluralize}", instance) end def collection_instance return unless @controller.instance_variable_defined?("@#{instance_name.to_s.pluralize}") @controller.instance_variable_get("@#{instance_name.to_s.pluralize}") end def parameters_require_sanitizing? save_actions.include?(@params[:action].to_sym) || resource_params_by_namespaced_name.present? end def instance_name @options[:instance_name] || name end def collection_actions [:index] + Array(@options[:collection]) end def save_actions %i[create update] end private def action_exists_in?(options) Array(options).include?(@params[:action].to_sym) end def adapter ModelAdapters::AbstractAdapter.adapter_class(resource_class) end def current_ability @controller.send(:current_ability) end end end cancancan-3.0.1/lib/cancan/controller_resource_builder.rb000066400000000000000000000012771345516370100235520ustar00rootroot00000000000000# frozen_string_literal: true module CanCan module ControllerResourceBuilder protected def build_resource resource = resource_base.new(resource_params || {}) assign_attributes(resource) end def assign_attributes(resource) resource.send("#{parent_name}=", parent_resource) if @options[:singleton] && parent_resource initial_attributes.each do |attr_name, value| resource.send("#{attr_name}=", value) end resource end def initial_attributes current_ability.attributes_for(@params[:action].to_sym, resource_class).delete_if do |key, _value| resource_params && resource_params.include?(key) end end end end cancancan-3.0.1/lib/cancan/controller_resource_finder.rb000066400000000000000000000021001345516370100233550ustar00rootroot00000000000000# frozen_string_literal: true module CanCan module ControllerResourceFinder protected def find_resource if @options[:singleton] && parent_resource.respond_to?(name) parent_resource.send(name) elsif @options[:find_by] find_resource_using_find_by else adapter.find(resource_base, id_param) end end def find_resource_using_find_by find_by_dynamic_finder || find_by_find_by_finder || resource_base.send(@options[:find_by], id_param) end def find_by_dynamic_finder method_name = "find_by_#{@options[:find_by]}!" resource_base.send(method_name, id_param) if resource_base.respond_to? method_name end def find_by_find_by_finder resource_base.find_by(@options[:find_by].to_sym => id_param) if resource_base.respond_to? :find_by end def id_param @params[id_param_key].to_s if @params[id_param_key].present? end def id_param_key if @options[:id_param] @options[:id_param] else parent? ? :"#{name}_id" : :id end end end end cancancan-3.0.1/lib/cancan/controller_resource_loader.rb000066400000000000000000000066331345516370100233730ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'controller_resource_finder.rb' require_relative 'controller_resource_name_finder.rb' require_relative 'controller_resource_builder.rb' require_relative 'controller_resource_sanitizer.rb' module CanCan module ControllerResourceLoader include CanCan::ControllerResourceNameFinder include CanCan::ControllerResourceFinder include CanCan::ControllerResourceBuilder include CanCan::ControllerResourceSanitizer def load_resource return if skip?(:load) if load_instance? self.resource_instance ||= load_resource_instance elsif load_collection? self.collection_instance ||= load_collection end end protected def new_actions %i[new create] + Array(@options[:new]) end def resource_params_by_key(key) return unless @options[key] && @params.key?(extract_key(@options[key])) @params[extract_key(@options[key])] end def resource_params_by_namespaced_name resource_params_by_key(:instance_name) || resource_params_by_key(:class) || ( params = @params[extract_key(namespaced_name)] params.respond_to?(:to_h) ? params : nil) end def resource_params if parameters_require_sanitizing? && params_method.present? sanitize_parameters else resource_params_by_namespaced_name end end def fetch_parent(name) if @controller.instance_variable_defined? "@#{name}" @controller.instance_variable_get("@#{name}") elsif @controller.respond_to?(name, true) @controller.send(name) end end # The object to load this resource through. def parent_resource parent_name && fetch_parent(parent_name) end def parent_name @options[:through] && [@options[:through]].flatten.detect { |i| fetch_parent(i) } end def resource_base_through_parent_resource if @options[:singleton] resource_class else parent_resource.send(@options[:through_association] || name.to_s.pluralize) end end def resource_base_through if parent_resource resource_base_through_parent_resource elsif @options[:shallow] resource_class else # maybe this should be a record not found error instead? raise AccessDenied.new(nil, authorization_action, resource_class) end end # The object that methods (such as "find", "new" or "build") are called on. # If the :through option is passed it will go through an association on that instance. # If the :shallow option is passed it will use the resource_class if there's no parent # If the :singleton option is passed it won't use the association because it needs to be handled later. def resource_base @options[:through] ? resource_base_through : resource_class end def parent_authorization_action @options[:parent_action] || :show end def authorization_action parent? ? parent_authorization_action : @params[:action].to_sym end def load_collection resource_base.accessible_by(current_ability, authorization_action) end def load_resource_instance if !parent? && new_actions.include?(@params[:action].to_sym) build_resource elsif id_param || @options[:singleton] find_resource end end private def extract_key(value) value.to_s.underscore.tr('/', '_') end end end cancancan-3.0.1/lib/cancan/controller_resource_name_finder.rb000066400000000000000000000006731345516370100243720ustar00rootroot00000000000000# frozen_string_literal: true module CanCan module ControllerResourceNameFinder protected def name_from_controller @params[:controller].split('/').last.singularize end def namespaced_name [namespace, name].join('/').singularize.camelize.safe_constantize || name end def name @name || name_from_controller end def namespace @params[:controller].split('/')[0..-2] end end end cancancan-3.0.1/lib/cancan/controller_resource_sanitizer.rb000066400000000000000000000015021345516370100241230ustar00rootroot00000000000000# frozen_string_literal: true module CanCan module ControllerResourceSanitizer protected def sanitize_parameters case params_method when Symbol @controller.send(params_method) when String @controller.instance_eval(params_method) when Proc params_method.call(@controller) end end def params_methods methods = ["#{@params[:action]}_params".to_sym, "#{name}_params".to_sym, :resource_params] methods.unshift(@options[:param_method]) if @options[:param_method].present? methods end def params_method params_methods.each do |method| return method if (method.is_a?(Symbol) && @controller.respond_to?(method, true)) || method.is_a?(String) || method.is_a?(Proc) end nil end end end cancancan-3.0.1/lib/cancan/exceptions.rb000066400000000000000000000042741345516370100201330ustar00rootroot00000000000000# frozen_string_literal: true module CanCan # A general CanCan exception class Error < StandardError; end # Raised when behavior is not implemented, usually used in an abstract class. class NotImplemented < Error; end # Raised when removed code is called, an alternative solution is provided in message. class ImplementationRemoved < Error; end # Raised when using check_authorization without calling authorize! class AuthorizationNotPerformed < Error; end # Raised when a rule is created with both a block and a hash of conditions class BlockAndConditionsError < Error; end # Raised when an unexpected argument is passed as an attribute class AttributeArgumentError < Error; end # Raised when using a wrong association name class WrongAssociationName < Error; end # This error is raised when a user isn't allowed to access a given controller action. # This usually happens within a call to ControllerAdditions#authorize! but can be # raised manually. # # raise CanCan::AccessDenied.new("Not authorized!", :read, Article) # # The passed message, action, and subject are optional and can later be retrieved when # rescuing from the exception. # # exception.message # => "Not authorized!" # exception.action # => :read # exception.subject # => Article # # If the message is not specified (or is nil) it will default to "You are not authorized # to access this page." This default can be overridden by setting default_message. # # exception.default_message = "Default error message" # exception.message # => "Default error message" # # See ControllerAdditions#authorize! for more information on rescuing from this exception # and customizing the message using I18n. class AccessDenied < Error attr_reader :action, :subject, :conditions attr_writer :default_message def initialize(message = nil, action = nil, subject = nil, conditions = nil) @message = message @action = action @subject = subject @conditions = conditions @default_message = I18n.t(:"unauthorized.default", default: 'You are not authorized to access this page.') end def to_s @message || @default_message end end end cancancan-3.0.1/lib/cancan/matchers.rb000066400000000000000000000027521345516370100175570ustar00rootroot00000000000000# frozen_string_literal: true rspec_module = defined?(RSpec::Core) ? 'RSpec' : 'Spec' # RSpec 1 compatability if rspec_module == 'RSpec' require 'rspec/core' require 'rspec/expectations' else ActiveSupport::Deprecation.warn('RSpec < 3 will not be supported in the CanCanCan >= 2.0.0') end Kernel.const_get(rspec_module)::Matchers.define :be_able_to do |*args| match do |ability| actions = args.first if actions.is_a? Array break false if actions.empty? actions.all? { |action| ability.can?(action, *args[1..-1]) } else ability.can?(*args) end end # Check that RSpec is < 2.99 if !respond_to?(:failure_message) && respond_to?(:failure_message_for_should) alias_method :failure_message, :failure_message_for_should end if !respond_to?(:failure_message_when_negated) && respond_to?(:failure_message_for_should_not) alias_method :failure_message_when_negated, :failure_message_for_should_not end failure_message do resource = args[1] if resource.instance_of?(Class) "expected to be able to #{args.map(&:to_s).join(' ')}" else "expected to be able to #{args.map(&:inspect).join(' ')}" end end failure_message_when_negated do resource = args[1] if resource.instance_of?(Class) "expected not to be able to #{args.map(&:to_s).join(' ')}" else "expected not to be able to #{args.map(&:inspect).join(' ')}" end end description do "be able to #{args.map(&:to_s).join(' ')}" end end cancancan-3.0.1/lib/cancan/model_adapters/000077500000000000000000000000001345516370100204015ustar00rootroot00000000000000cancancan-3.0.1/lib/cancan/model_adapters/abstract_adapter.rb000066400000000000000000000040651345516370100242360ustar00rootroot00000000000000# frozen_string_literal: true module CanCan module ModelAdapters class AbstractAdapter def self.inherited(subclass) @subclasses ||= [] @subclasses << subclass end def self.adapter_class(model_class) @subclasses.detect { |subclass| subclass.for_class?(model_class) } || DefaultAdapter end # Used to determine if the given adapter should be used for the passed in class. def self.for_class?(_member_class) false # override in subclass end # Override if you need custom find behavior def self.find(model_class, id) model_class.find(id) end # Used to determine if this model adapter will override the matching behavior for a hash of conditions. # If this returns true then matches_conditions_hash? will be called. See Rule#matches_conditions_hash def self.override_conditions_hash_matching?(_subject, _conditions) false end # Override if override_conditions_hash_matching? returns true def self.matches_conditions_hash?(_subject, _conditions) raise NotImplemented, 'This model adapter does not support matching on a conditions hash.' end # Used to determine if this model adapter will override the matching behavior for a specific condition. # If this returns true then matches_condition? will be called. See Rule#matches_conditions_hash def self.override_condition_matching?(_subject, _name, _value) false end # Override if override_condition_matching? returns true def self.matches_condition?(_subject, _name, _value) raise NotImplemented, 'This model adapter does not support matching on a specific condition.' end def initialize(model_class, rules) @model_class = model_class @rules = rules end def database_records # This should be overridden in a subclass to return records which match @rules raise NotImplemented, 'This model adapter does not support fetching records from the database.' end end end end cancancan-3.0.1/lib/cancan/model_adapters/active_record_4_adapter.rb000066400000000000000000000043231345516370100254640ustar00rootroot00000000000000# frozen_string_literal: true module CanCan module ModelAdapters class ActiveRecord4Adapter < ActiveRecordAdapter AbstractAdapter.inherited(self) class << self def for_class?(model_class) version_lower?('5.0.0') && model_class <= ActiveRecord::Base end def override_condition_matching?(subject, name, _value) subject.class.defined_enums.include?(name.to_s) end def matches_condition?(subject, name, value) # Get the mapping from enum strings to values. enum = subject.class.send(name.to_s.pluralize) # Get the value of the attribute as an integer. attribute = enum[subject.send(name)] # Check to see if the value matches the condition. if value.is_a?(Enumerable) value.include? attribute else attribute == value end end end private # As of rails 4, `includes()` no longer causes active record to # look inside the where clause to decide to outer join tables # you're using in the where. Instead, `references()` is required # in addition to `includes()` to force the outer join. def build_relation(*where_conditions) relation = @model_class.where(*where_conditions) relation = relation.includes(joins).references(joins) if joins.present? relation end # Rails 4.2 deprecates `sanitize_sql_hash_for_conditions` def sanitize_sql(conditions) if self.class.version_greater_or_equal?('4.2.0') && conditions.is_a?(Hash) sanitize_sql_activerecord4(conditions) else @model_class.send(:sanitize_sql, conditions) end end def sanitize_sql_activerecord4(conditions) table = Arel::Table.new(@model_class.send(:table_name)) conditions = ActiveRecord::PredicateBuilder.resolve_column_aliases @model_class, conditions conditions = @model_class.send(:expand_hash_conditions_for_aggregates, conditions) ActiveRecord::PredicateBuilder.build_from_hash(@model_class, conditions, table).map do |b| @model_class.send(:connection).visitor.compile b end.join(' AND ') end end end end cancancan-3.0.1/lib/cancan/model_adapters/active_record_5_adapter.rb000066400000000000000000000042371345516370100254710ustar00rootroot00000000000000# frozen_string_literal: true module CanCan module ModelAdapters class ActiveRecord5Adapter < ActiveRecord4Adapter AbstractAdapter.inherited(self) def self.for_class?(model_class) version_greater_or_equal?('5.0.0') && model_class <= ActiveRecord::Base end # rails 5 is capable of using strings in enum # but often people use symbols in rules def self.matches_condition?(subject, name, value) return super if Array.wrap(value).all? { |x| x.is_a? Integer } attribute = subject.send(name) raw_attribute = subject.class.send(name.to_s.pluralize)[attribute] !(Array(value).map(&:to_s) & [attribute, raw_attribute]).empty? end private def build_relation(*where_conditions) relation = @model_class.where(*where_conditions) relation = relation.left_joins(joins).distinct if joins.present? relation end # Rails 4.2 deprecates `sanitize_sql_hash_for_conditions` def sanitize_sql(conditions) if conditions.is_a?(Hash) sanitize_sql_activerecord5(conditions) else @model_class.send(:sanitize_sql, conditions) end end def sanitize_sql_activerecord5(conditions) table = @model_class.send(:arel_table) table_metadata = ActiveRecord::TableMetadata.new(@model_class, table) predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata) conditions = predicate_builder.resolve_column_aliases(conditions) conditions.stringify_keys! predicate_builder.build_from_hash(conditions).map { |b| visit_nodes(b) }.join(' AND ') end def visit_nodes(node) # Rails 5.2 adds a BindParam node that prevents the visitor method from properly compiling the SQL query if self.class.version_greater_or_equal?('5.2.0') connection = @model_class.send(:connection) collector = Arel::Collectors::SubstituteBinds.new(connection, Arel::Collectors::SQLString.new) connection.visitor.accept(node, collector).value else @model_class.send(:connection).visitor.compile(node) end end end end end cancancan-3.0.1/lib/cancan/model_adapters/active_record_adapter.rb000066400000000000000000000116611345516370100252440ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'conditions_extractor.rb' require 'cancan/rules_compressor' module CanCan module ModelAdapters class ActiveRecordAdapter < AbstractAdapter def self.version_greater_or_equal?(version) Gem::Version.new(ActiveRecord.version).release >= Gem::Version.new(version) end def self.version_lower?(version) Gem::Version.new(ActiveRecord.version).release < Gem::Version.new(version) end def initialize(model_class, rules) super @compressed_rules = RulesCompressor.new(@rules.reverse).rules_collapsed.reverse ConditionsNormalizer.normalize(model_class, @compressed_rules) end # Returns conditions intended to be used inside a database query. Normally you will not call this # method directly, but instead go through ModelAdditions#accessible_by. # # If there is only one "can" definition, a hash of conditions will be returned matching the one defined. # # can :manage, User, :id => 1 # query(:manage, User).conditions # => { :id => 1 } # # If there are multiple "can" definitions, a SQL string will be returned to handle complex cases. # # can :manage, User, :id => 1 # can :manage, User, :manager_id => 1 # cannot :manage, User, :self_managed => true # query(:manage, User).conditions # => "not (self_managed = 't') AND ((manager_id = 1) OR (id = 1))" # def conditions conditions_extractor = ConditionsExtractor.new(@model_class) if @compressed_rules.size == 1 && @compressed_rules.first.base_behavior # Return the conditions directly if there's just one definition conditions_extractor.tableize_conditions(@compressed_rules.first.conditions).dup else extract_multiple_conditions(conditions_extractor, @compressed_rules) end end def extract_multiple_conditions(conditions_extractor, rules) rules.reverse.inject(false_sql) do |sql, rule| merge_conditions(sql, conditions_extractor.tableize_conditions(rule.conditions).dup, rule.base_behavior) end end def database_records if override_scope @model_class.where(nil).merge(override_scope) elsif @model_class.respond_to?(:where) && @model_class.respond_to?(:joins) build_relation(conditions) else @model_class.all(conditions: conditions, joins: joins) end end # Returns the associations used in conditions for the :joins option of a search. # See ModelAdditions#accessible_by def joins joins_hash = {} @compressed_rules.reverse_each do |rule| deep_merge(joins_hash, rule.associations_hash) end deep_clean(joins_hash) unless joins_hash.empty? end private # Removes empty hashes and moves everything into arrays. def deep_clean(joins_hash) joins_hash.map { |name, nested| nested.empty? ? name : { name => deep_clean(nested) } } end # Takes two hashes and does a deep merge. def deep_merge(base_hash, added_hash) added_hash.each do |key, value| if base_hash[key].is_a?(Hash) deep_merge(base_hash[key], value) unless value.empty? else base_hash[key] = value end end end def override_scope conditions = @compressed_rules.map(&:conditions).compact return unless conditions.any? { |c| c.is_a?(ActiveRecord::Relation) } return conditions.first if conditions.size == 1 raise_override_scope_error end def raise_override_scope_error rule_found = @compressed_rules.detect { |rule| rule.conditions.is_a?(ActiveRecord::Relation) } raise Error, 'Unable to merge an Active Record scope with other conditions. '\ "Instead use a hash or SQL for #{rule_found.actions.first} #{rule_found.subjects.first} ability." end def merge_conditions(sql, conditions_hash, behavior) if conditions_hash.blank? behavior ? true_sql : false_sql else merge_non_empty_conditions(behavior, conditions_hash, sql) end end def merge_non_empty_conditions(behavior, conditions_hash, sql) conditions = sanitize_sql(conditions_hash) case sql when true_sql behavior ? true_sql : "not (#{conditions})" when false_sql behavior ? conditions : false_sql else behavior ? "(#{conditions}) OR (#{sql})" : "not (#{conditions}) AND (#{sql})" end end def false_sql sanitize_sql(['?=?', true, false]) end def true_sql sanitize_sql(['?=?', true, true]) end def sanitize_sql(conditions) @model_class.send(:sanitize_sql, conditions) end end end end ActiveSupport.on_load(:active_record) do send :include, CanCan::ModelAdditions end cancancan-3.0.1/lib/cancan/model_adapters/conditions_extractor.rb000066400000000000000000000054071345516370100252000ustar00rootroot00000000000000# frozen_string_literal: true # this class is responsible of converting the hash of conditions # in "where conditions" to generate the sql query # it consists of a names_cache that helps calculating the next name given to the association # it tries to reflect the bahavior of ActiveRecord when generating aliases for tables. module CanCan module ModelAdapters class ConditionsExtractor def initialize(model_class) @names_cache = { model_class.table_name => [] }.with_indifferent_access @root_model_class = model_class end def tableize_conditions(conditions, model_class = @root_model_class, path_to_key = 0) return conditions unless conditions.is_a? Hash conditions.each_with_object({}) do |(key, value), result_hash| if value.is_a? Hash result_hash.merge!(calculate_result_hash(key, model_class, path_to_key, result_hash, value)) else result_hash[key] = value end result_hash end end private def calculate_result_hash(key, model_class, path_to_key, result_hash, value) reflection = model_class.reflect_on_association(key) nested_resulted = calculate_nested(model_class, result_hash, key, value.dup, path_to_key) association_class = reflection.klass.name.constantize tableize_conditions(nested_resulted, association_class, "#{path_to_key}_#{key}") end def calculate_nested(model_class, result_hash, relation_name, value, path_to_key) value.each_with_object({}) do |(k, v), nested| if v.is_a? Hash value.delete(k) nested[k] = v else table_alias = generate_table_alias(model_class, relation_name, path_to_key) result_hash[table_alias] = value end nested end end def generate_table_alias(model_class, relation_name, path_to_key) table_alias = model_class.reflect_on_association(relation_name).table_name.to_sym if alredy_used?(table_alias, relation_name, path_to_key) table_alias = "#{relation_name.to_s.pluralize}_#{model_class.table_name}".to_sym index = 1 while alredy_used?(table_alias, relation_name, path_to_key) table_alias = "#{table_alias}_#{index += 1}".to_sym end end add_to_cache(table_alias, relation_name, path_to_key) end def alredy_used?(table_alias, relation_name, path_to_key) @names_cache[table_alias].try(:exclude?, "#{path_to_key}_#{relation_name}") end def add_to_cache(table_alias, relation_name, path_to_key) @names_cache[table_alias] ||= [] @names_cache[table_alias] << "#{path_to_key}_#{relation_name}" table_alias end end end end cancancan-3.0.1/lib/cancan/model_adapters/conditions_normalizer.rb000066400000000000000000000030611345516370100253410ustar00rootroot00000000000000# this class is responsible of normalizing the hash of conditions # by exploding has_many through associations # when a condition is defined with an has_many thorugh association this is exploded in all its parts # TODO: it could identify STI and normalize it module CanCan module ModelAdapters class ConditionsNormalizer class << self def normalize(model_class, rules) rules.each { |rule| rule.conditions = normalize_conditions(model_class, rule.conditions) } end def normalize_conditions(model_class, conditions) return conditions unless conditions.is_a? Hash conditions.each_with_object({}) do |(key, value), result_hash| if value.is_a? Hash result_hash.merge!(calculate_result_hash(model_class, key, value)) else result_hash[key] = value end result_hash end end private def calculate_result_hash(model_class, key, value) reflection = model_class.reflect_on_association(key) unless reflection raise WrongAssociationName, "Association '#{key}' not defined in model '#{model_class.name}'" end if reflection.options[:through].present? key = reflection.options[:through] value = { reflection.source_reflection_name => value } reflection = model_class.reflect_on_association(key) end { key => normalize_conditions(reflection.klass.name.constantize, value) } end end end end end cancancan-3.0.1/lib/cancan/model_adapters/default_adapter.rb000066400000000000000000000003001345516370100240430ustar00rootroot00000000000000# frozen_string_literal: true module CanCan module ModelAdapters class DefaultAdapter < AbstractAdapter # This adapter is used when no matching adapter is found end end end cancancan-3.0.1/lib/cancan/model_additions.rb000066400000000000000000000023131345516370100211000ustar00rootroot00000000000000# frozen_string_literal: true module CanCan # This module adds the accessible_by class method to a model. It is included in the model adapters. module ModelAdditions module ClassMethods # Returns a scope which fetches only the records that the passed ability # can perform a given action on. The action defaults to :index. This # is usually called from a controller and passed the +current_ability+. # # @articles = Article.accessible_by(current_ability) # # Here only the articles which the user is able to read will be returned. # If the user does not have permission to read any articles then an empty # result is returned. Since this is a scope it can be combined with any # other scopes or pagination. # # An alternative action can optionally be passed as a second argument. # # @articles = Article.accessible_by(current_ability, :update) # # Here only the articles which the user can update are returned. def accessible_by(ability, action = :index) ability.model_adapter(self, action).database_records end end def self.included(base) base.extend ClassMethods end end end cancancan-3.0.1/lib/cancan/parameter_validators.rb000066400000000000000000000003741345516370100221570ustar00rootroot00000000000000# frozen_string_literal: true module CanCan module ParameterValidators def valid_attribute_param?(attribute) attribute.nil? || attribute.is_a?(Symbol) || (attribute.is_a?(Array) && attribute.all? { |a| a.is_a?(Symbol) }) end end end cancancan-3.0.1/lib/cancan/rule.rb000066400000000000000000000105451345516370100167170ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'conditions_matcher.rb' module CanCan # This class is used internally and should only be called through Ability. # it holds the information about a "can" call made on Ability and provides # helpful methods to determine permission checking and conditions hash generation. class Rule # :nodoc: include ConditionsMatcher include ParameterValidators attr_reader :base_behavior, :subjects, :actions, :conditions, :attributes attr_writer :expanded_actions, :conditions # The first argument when initializing is the base_behavior which is a true/false # value. True for "can" and false for "cannot". The next two arguments are the action # and subject respectively (such as :read, @project). The third argument is a hash # of conditions and the last one is the block passed to the "can" call. def initialize(base_behavior, action, subject, *extra_args, &block) # for backwards compatibility, attributes are an optional parameter. Check if # attributes were passed or are actually conditions attributes, extra_args = parse_attributes_from_extra_args(extra_args) condition_and_block_check(extra_args, block, action, subject) @match_all = action.nil? && subject.nil? raise Error, "Subject is required for #{action}" if action && subject.nil? @base_behavior = base_behavior @actions = Array(action) @subjects = Array(subject) @attributes = Array(attributes) @conditions = extra_args || {} @block = block end def inspect repr = "#<#{self.class.name}" repr += "#{@base_behavior ? 'can' : 'cannot'} #{@actions.inspect}, #{@subjects.inspect}, #{@attributes.inspect}" repr += if with_scope? ", #{@conditions.where_values_hash}" elsif [Hash, String].include?(@conditions.class) ", #{@conditions.inspect}" end repr + '>' end def can_rule? base_behavior end def cannot_catch_all? !can_rule? && catch_all? end def catch_all? (with_scope? && @conditions.where_values_hash.empty?) || (!with_scope? && [nil, false, [], {}, '', ' '].include?(@conditions)) end # Matches both the action, subject, and attribute, not necessarily the conditions def relevant?(action, subject) subject = subject.values.first if subject.class == Hash @match_all || (matches_action?(action) && matches_subject?(subject)) end def only_block? conditions_empty? && @block end def only_raw_sql? @block.nil? && !conditions_empty? && !@conditions.is_a?(Hash) end def with_scope? @conditions.is_a?(ActiveRecord::Relation) end def associations_hash(conditions = @conditions) hash = {} if conditions.is_a? Hash conditions.map do |name, value| hash[name] = associations_hash(value) if value.is_a? Hash end end hash end def attributes_from_conditions attributes = {} if @conditions.is_a? Hash @conditions.each do |key, value| attributes[key] = value unless [Array, Range, Hash].include? value.class end end attributes end def matches_attributes?(attribute) return true if @attributes.empty? return @base_behavior if attribute.nil? @attributes.include?(attribute.to_sym) end private def matches_action?(action) @expanded_actions.include?(:manage) || @expanded_actions.include?(action) end def matches_subject?(subject) @subjects.include?(:all) || @subjects.include?(subject) || matches_subject_class?(subject) end def matches_subject_class?(subject) @subjects.any? do |sub| sub.is_a?(Module) && (subject.is_a?(sub) || subject.class.to_s == sub.to_s || (subject.is_a?(Module) && subject.ancestors.include?(sub))) end end def parse_attributes_from_extra_args(args) attributes = args.shift if valid_attribute_param?(args.first) extra_args = args.shift [attributes, extra_args] end def condition_and_block_check(conditions, block, action, subject) return unless conditions.is_a?(Hash) && block raise BlockAndConditionsError, 'A hash of conditions is mutually exclusive with a block. '\ "Check \":#{action} #{subject}\" ability." end end end cancancan-3.0.1/lib/cancan/rules_compressor.rb000066400000000000000000000010701345516370100213470ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'conditions_matcher.rb' module CanCan class RulesCompressor attr_reader :initial_rules, :rules_collapsed def initialize(rules) @initial_rules = rules @rules_collapsed = compress(@initial_rules) end def compress(array) idx = array.rindex(&:catch_all?) return array unless idx value = array[idx] array[idx..-1] .drop_while { |n| n.base_behavior == value.base_behavior } .tap { |a| a.unshift(value) unless value.cannot_catch_all? } end end end cancancan-3.0.1/lib/cancan/unauthorized_message_resolver.rb000066400000000000000000000012611345516370100241110ustar00rootroot00000000000000# frozen_string_literal: true module CanCan module UnauthorizedMessageResolver def unauthorized_message(action, subject) keys = unauthorized_message_keys(action, subject) variables = { action: action.to_s } variables[:subject] = translate_subject(subject) message = I18n.translate(keys.shift, variables.merge(scope: :unauthorized, default: keys + [''])) message.blank? ? nil : message end def translate_subject(subject) klass = (subject.class == Class ? subject : subject.class) if klass.respond_to?(:model_name) klass.model_name.human else klass.to_s.underscore.humanize.downcase end end end end cancancan-3.0.1/lib/cancan/version.rb000066400000000000000000000001141345516370100174240ustar00rootroot00000000000000# frozen_string_literal: true module CanCan VERSION = '3.0.1'.freeze end cancancan-3.0.1/lib/cancancan.rb000066400000000000000000000001061345516370100164220ustar00rootroot00000000000000# frozen_string_literal: true require 'cancan' module CanCanCan end cancancan-3.0.1/lib/generators/000077500000000000000000000000001345516370100163445ustar00rootroot00000000000000cancancan-3.0.1/lib/generators/cancan/000077500000000000000000000000001345516370100175675ustar00rootroot00000000000000cancancan-3.0.1/lib/generators/cancan/ability/000077500000000000000000000000001345516370100212245ustar00rootroot00000000000000cancancan-3.0.1/lib/generators/cancan/ability/USAGE000066400000000000000000000002561345516370100220160ustar00rootroot00000000000000Description: The cancan:ability generator creates an Ability class in the models directory. You can move this file anywhere you want as long as it is in the load path. cancancan-3.0.1/lib/generators/cancan/ability/ability_generator.rb000066400000000000000000000004371345516370100252600ustar00rootroot00000000000000# frozen_string_literal: true module Cancan module Generators class AbilityGenerator < Rails::Generators::Base source_root File.expand_path('templates', __dir__) def generate_ability copy_file 'ability.rb', 'app/models/ability.rb' end end end end cancancan-3.0.1/lib/generators/cancan/ability/templates/000077500000000000000000000000001345516370100232225ustar00rootroot00000000000000cancancan-3.0.1/lib/generators/cancan/ability/templates/ability.rb000066400000000000000000000021101345516370100251760ustar00rootroot00000000000000# frozen_string_literal: true class Ability include CanCan::Ability def initialize(user) # Define abilities for the passed in user here. For example: # # user ||= User.new # guest user (not logged in) # if user.admin? # can :manage, :all # else # can :read, :all # end # # The first argument to `can` is the action you are giving the user # permission to do. # If you pass :manage it will apply to every action. Other common actions # here are :read, :create, :update and :destroy. # # The second argument is the resource the user can perform the action on. # If you pass :all it will apply to every resource. Otherwise pass a Ruby # class of the resource. # # The third argument is an optional hash of conditions to further filter the # objects. # For example, here the user can only update published articles. # # can :update, Article, :published => true # # See the wiki for details: # https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities end end cancancan-3.0.1/logo/000077500000000000000000000000001345516370100143655ustar00rootroot00000000000000cancancan-3.0.1/logo/cancancan.jpg000066400000000000000000001424771345516370100170130ustar00rootroot00000000000000ÿØÿàJFIF``ÿáªExifII*1 &i‡2Picasa 2.70210 ~ ¤!\f55385f4bff8942cdefa1e38235c4e39œ0100 ÿí,Photoshop 3.08BIMP Picasa 2.7ÿÛC  !"$"$ÿÛCÿÀR±"ÿÄ ÿÄJ!1AQ"2aq#‘BR¡$3±ÁÑ brð4²áñd’¢%‚CSTcƒÂâÿÄÿÄ'!1A"2Qaq#BRÿÚ ?¸´W ì<`ŸæÜ<ü© -NSe i÷ó¯Žï1Ù×ZÙy§’TÓ‰ZBŠIIÏ#¸¦Üf®Ix Øm*>¾¿éCíËe2ËmîqÌäq´æ‹às)ÆÒ@RÒ ì ïBV•|ªIûc¿{>ö┇JÊ™*?(>U“L–Žò·Ä;]HìO­]ÅÏ\YlßÚ”ÔÉ.!ï…LlN<Å8Sòä§T§ƒ¾¼ÜŸæ½"JJIÝŽ æ¼`p1´gŒcµ’®VäÌ÷5OŠ$ñù%äïçþ\溳TO¨w9× k~Ô¼F¤ûâ„uä‚€ÙÂqŽÝªhèwWÝÕz]¯(W¾ÅPnCÉõì û×\¸¬›fIµƒ B†R¤‘ô5è ùŠm0¤¾Òeá·ÏaÈ5ØÒV·[!d#<Šä×j¼$1\o!%¼ä\O¶Çb‡g#ÔW´„Ôq¸üX®æØ);É¡àÐÞÐ;þµ­öw£Š'Œþ»(¤ÖÙÂö‘Øñ]èNÐ8µ Ž™Ñ^Õ©CâíD“mÔR&«¼þgTàÇŒ­éBQœdšÛd¿[îÖÕKaô§Ã<‚~&”;ƒM*­IBJ– ”Žäœ äKfðÄaî=‡ŽœŸëPMúÛ~×WÛ„•ßf¢É”µ+(ñç$­'tÏEÚaé¹o™,8à^wž@è9ýk§„×µñY r29íVÞ›ë•Øº•Áé&]¦ã ²¨Ó>xËòÚ–¬•g,|YV*ö8¬BNOõ¬®›(¢¼4G´V¯>sŠÚ(¶ (¯ÚˆöŠÃ°%GœœùT×Q.q5eÃR™`2Ì/uO;Gäþ¹­Lm]'Z+–Õp‡t€Ìèüw…¤äk¡YÎ9ý+)¦TV)ýMe@Q^*°*ïEÓeQDV ø‡r?ZôvùL¨¢¼4G´V¢~"¢¢a \ih+Žúà”œàÑltQEb®ý茨¬QÛ<ÖT­ÂkW˜íÓEko·zÉDŽÔM2¢¹W‘QïšÌÇw&x:(¬7%d÷5ƒª8£2mºŠä`Aíë] IOžh·3¢«þ¬×ÚšÝÕ››Ö—L«|=‘× YØæTG¢¹©[Ij›^©¶¢dHVv¸ÊðÚ¼ÁJկɲc³ªŠÒÊ{«5´ ÊY§´V·\ÊøÕßá¢Ìví¢¹[ Á9ÍtÏ.:eEb£\«poÇzã·eÌßn+¡#ŠiíjLˆê¨é}¢ò*l,nH=‰ë'µ9ª¹ªo÷/üH¸ßíò•øÓ lŒœ8”à¡æ;Ö±ÇÉ4´¹´f˜zZ[õ+¿ðg ¬`nóÓÃiÆ1Y½5áþ»k>ZÂ0ÃXúÖN|´gZ­8¢Š*:¹µ Ȱâ!R¤¡€µ„¤©@dàñIP§CX[‚[+@”¨æê•ž×yµÄféùˆCÅI  NÓÏ \ú}kƒoyø.êKK|eĬ¬ Ä*éŽ2±¤í.ói…̹E¿äŒúR":£$ËÛÔVâùVЕ:ϧ5[S¥î ¾9vN§µ\Û†ƒîÌÜw¶[ã)Z†9Áó¦í÷©p’Äý=«,öUºÜ"ëOÃGγÛË)<A®“‡gšÞÊ»ÛYkl‹ŒDÛI.$R,-I¥Üº:–õ$'žã§á ysÉâ¾oMÕÚ†B¶®å%MgàBÜ$éI-͘‰I—K€çrTAÏ­uŸýgþ¬rï ù«…½eÆUœÄcƒK œãËÌ÷¯˜½4êÆ²Ñz‹”¤—P•ã.¹¹µ§Ìmí_Fzg«"kMoÔ1R™MåhþEŽâ¸rðÜÇ=¶j;¬kbã4öòd¼–‚£“öò¥å*O׿ûRdØ0æÝÔãê u ÚsòóÞ–ã¡8ø€ÎsÇ­riSz‰¦W¦5ÅÆ ”¬·*B¥BZ»-µœãî9â˜zgQ+§boÅJk»„¦[kå!*<=E\N°hÖu†‘´%7(É/B{¡cœ}Ž1TS­PÞ‘l¶ÞÂ<%5¹‡“žP¤’Ïé^¾,¼æ«–].õžÒå­^ñj§ I!Ä$œ„ƒLþ§õNVž½ÛíöxæeÁ”ìd´‚8*>^´ÊèŸY[“Ñ ‚f<•Þm J»º?Z_é®~è”]ïî¨? ø¯ÿ2ßÓé\¼|mòo{ôI·ug©—Yßœ›-º(YJ‰IZ‘ô8?J~Zº‹tˆA¹±T “>Ü œZÄžõ×*ý¢4þ¢gM©ˆ,H–ű$¬S[צôõ*$u°™$©àäIXZ299ç±-ŸÂoúqXµn»3ãA»Guácƒè} 9¢ÉŽðJÙ}µ¤Ž T9ªõÔ~™Ù­VÙš©7Öâû£Ju¯-¡Ç8݃…ç·o:‰zŸ¯âéý=`¹èË›°µøÁW&Ø^Xlc ì•{RqL¿Z—/êó©Ä„T™F™K±©wF¤éõ¬Ñ¶¸Wl(¶ê†àsÈN|ù<Ô§ºƒ¨d´ù½tþõ±¸ …ã¹½=¶¸ÂºM[(#ä) qšÍ‘9¸î™YÉÚ¤§#ËÎ¥Ës¶z4 uWJ¾Öù¿ˆÚÁ8&dÿÕ·æ‹©tóì¥æovõ6¡ð«ÞýÍ#\!ÊS„=x€üu'ãjK`çìsÅ1µ4]'¦þ¨DL—ÛAñXh‚€g…}qÍ5*ÜS yðd7âG™Ô2J‰ºí¤ôN­ ±eJ)}æÃLyÕ[êgV¾N[Ú^›rH}¶)K¾„§°=ê8»Ë•1å=)NHqg—9Qâ»áñÿý9[>ŸAô'Xô±–Ô+]Ü"k¤G};VIòôÍH¾oôPYôÇS,·{ìO:]¨ðZ'á ý+èë¶û(y—ãkHR“ {\¹xü/K½ÆtQA®C–âÒß·Èe¼î[KJyó Š…t­½*ÒmÆP•0…Æt)=׸¥YûóûÔæ*6Õðž°êfä0àE²êàJ‘Ø4ÿ™û(q÷­cZ”Ðöv½,9©ô#*ð§Ø¥ø°Ð³ó°°}ùŠ˜m·–Üü©ƒÝä•UL×:ŽGL}¨­ú©Ò÷áרDÀ oä_òíïSÿVn1¥Y#Ƴ#ñ ¬öð£´éJ–’2}Ú·ž=ËýYüIAIÛ»p#×5‚¤GJw)ö’=JÅBºK§ j×Z‚|‡Ý‹li*JêéÜþ…ÒîCSQ4„l§€]Z‡?psX²SÄþKˆpnmiXõIÍ¢G4EÚÞóZf¿im*ÜühóTR¡ô àVÝ2u#º¢vŸ»ßî!ųïœB’R†óœù§Œþ¯ŠUyæc·â>óm#ù– îk ´©hPRObA¯žwÔš×ûiu²Ý/Ò&µJ›h¥jJqÆ+Þ›uÃZè–]ŽÄÅNa+R‰ZF=3Ú»ãÝnV76úT; ɾÕMu7µ-þf›C¸-Až±ù¯v}©§ÓN­uíqmmZ’cé“-°ëN+r®F+3ƒ-n­³Òü׊í^Ñ\PÚê%ÍË6‡¼Ü›^Lj¿ ^‹<êEFvUÓOE‹6–㌳™ :H§ý|T³­¬CQi[…—Æ,*SE(wذAIýÀÓ.ŸAe¥LNŠá‹1—HIK€xþSÁB+xún]¤-%¨ízžËëk}.4òs·<¤ùƒJ«#Z­~Ì7ÖfÊÕ:&L¤32 ÷dAXXÊPOÄÐZr¹¯:7R½dÒ [.ì°¢Û“b=H95n[DàßËYTNÔ¦IÚ«Ö¶‡m_Ý ,ßšQ´Ú.m™.¬ÔÓžh‚— 6'ê6ö5§ŠBpóXp1P¦¿¸kûl/Çœ¨ìîrJ¤ÁIW‡ÿ*‡Zju篧t©Ûí£ZPœŠWä†ÜÙØáCÏ5gþ×zY(“¢=!qٔΣæB\CîužÆ¾^YµuþÓtLëuækRA;+*'½KšÚ_YiÆ]fç‹ãj)Ûï+ø‘Žã5×/”ôÇ”«¾âr¬ã&›}GÕ‘´~˜—vZÚ[̤6¥•QÞ˜KëE¾óÒEjkRÚ‰pp† u¯*eÃÇíçPö¶oüIÔ]¯W[ĸͥrJþ;ªHÎú× j}aª.$e‰“%¥>ä¦؆±ñ¤Ÿ/­XKjf&Ù7 †Xi!â—v9ÇëUã 2%·¨`ÅþÈÜa (œ^ï ƒßmÁûU_?Jr{Ò1BHã´q^$}«*æ–í_õ\bêˬW"HTŸ*IùÛV0¯éÚ™,˜çþ3Þì-Ü…"ZS" ‰;K‰Húа½P´5&ÜÝá¿ `œ•/€¶ÿ‰5RzñïÚOZXõåòZ’¶V‚OÄp~˜¯GåÒÛö¹6+»Ëm1îÈK2€ä”╟ —æGhÎàOùÔ5Öªê5²#ºBÔ"0ãaOÍtü!\n úæ¶Åé͆ҟŵö§vèãÈß´¸xW*G?Jçá¯kfÓj¯vw ·v‚¥‘úsÞµH»ZØåë„T'Ôºš‹gÑέ¥AéýÚZK{|e §áòîsHw3¡§6»uÆ×{±öxšž1©t•ÏQ4kn­¡zeÂŽá¥KýÅ-Úõ’çLJsŒê3ÏÆ¡¨ZÚ›€Ó-·ó⢿……6§SžùE>ÔúªÂóÑ-v®¬ä‡ÙYFNBN;šÖ<~WQ›~Öîë©,vÖ~}æf›HQS$+È7[uÊ2eAC+ù\C€¤½|Ì‘pŸ!KT¹ï:v€ÖUÅv@ÔWˆÑ“5Òc1²”¡õ$úëÿþ³95ôúvÈ?°®Š¬žÉVŸs˜4n£’·ÜØUC‡ž;¡Dò~•fëÏ–7ªevÁÑ”‘ëU{©v·,zÕø²¹§ŸT†TU€R£ŸèsVÓåQÇ[tôΗvcëm™Pu—T@wIÏpjá–«Rt‰zQvŒ¾ª]tÁyL‰±zN ^Gp>ã?µX;$‰Íu¸©C„¹ÏÅ÷ªU­%γß,zº6RëkJ²ßÐŒU‚B½kx1£i›zበ…½-ÁÂO˜I®œ˜z¦58Ç9o¾y¯\ùi½Ó»|Ëm€3:ræH+*ZÔsÏ¥8œùk‚}¹è¬¸¢¦›Ù©:ŠÛ¦4ò®W9-²ÒIÆãʈ‡Ö«ûþÑTÁSVyN Hþ% ú)wÛ‘·œÒºi,²·J®*NaðŽôÁ—cˆ®œ"âÕ‡žm½ªSçj“Ç$ôa†>2Ö%¦7[ºãwÕN¡‹¬Zc„Üü §]ú“ÇÚ˜6¾šê[n]î(D…+*q^ *8õ‘I:†ä·oIKIØY< ä¦ïJŸúJ¤·m.+²!MR‚d¨øn§ù’Ò½7ðÇñcö½ªÍæÑp´Ý·MeM>Ú°¤÷òïCVIŽ ­¶Ô°FãRÇ´tÈÕ+”Ü„¸ó€—6žÇ=©[ ,rä˜× ¶‘¹§qxùOÞºyþ>I®ô„áAqsZiô¸ËIJÔ¤çòké—B-:rËÓ;d]7/Þa-jxœ•¬‹>•NºÃ;O7tˆ›x±’™Åó*Î9ǧ*{;uۦƣµÝ®Mˆ--.ÄJ9 V>$¦¸so<[ÃR¬ü„6§‹¤@ùñ]Ñ9le?½0ziÔ N·mõ@C¬¸Éå·S…cÖ¤AÀãï^+,º®›é¼‡Ëõª+Ö¦á;§u¤6‚w5{t´6 à Î?üªô¬ß_úÏìëo×Z‰W«V ~ʹ.%S ïC§ÍC‘…cJëÅ”Æöç«bŽh«»–]KÇܘþ*­¬Ï˜¯¢Ú^äßö9‰01)ÃpNwqŠ¡~Ñ:wNz‚íždÑ=.4‰ I)ñ¯§¨ÅMžË]AºIÓ[¤FrcâÞB²ãH=”Séõ¯O6>xÌ¢autŒzÇ6ç3VȺM&:ÊÎA N1é¸ Wé7O5&ªar­òÕoINTâÖ¤öãŽO­9ý¢gF»[Ù‘ÅZ(øŠr9NUê ::)¨lö]6¯|ƒrSˆž“ƒ„åHÍ.Wã]ž:]i#µ¨.ÏÌ àøžËYÛÈ•WúÙd¶ÅÕ+iAm ¢€~Aãn<ªEëQSqcİÞVI(JŠWœðJOŸ•w{>ôÕ­[wUÛU!ÇÚZ7„ãâ'¿Ö¦ÂyeV÷ÔAJÒ·höäL÷wŸ›ÐSq´¹ã«ÄÎAÀ¯¡÷û+aÓSØ÷E†[ð I÷ª{sVš{U³"0uÑ”—æ·ÇËåôÎXèõöwÖƒ§2—vÓîÙä6vã=Àò5xô~¡·ê=÷ks|y( ©>† YvÝ'¨4v {Œ0é@)Bæcµì„™Pmz–Å!×n ÀwvJJ{ڼܚÊ\¾ÚÖ“½yŽh5ípAQn·ÓË>¥±6Á1¥GS±@à!J§÷©JšþbìεytHT²¦äxM—6c¢‘’|êãí¬TsÙÿ\Hé§VÙ9Jj#¯˜³R¿å'õ¾ ežY3n)ä‚^ 'rÁä|]Í|âë{TcuÔy TiR÷2‚Ê›QÏÌ <\¿e 7¬úo‡‚®v¤†I<©åWíÇé^ž|w&Q˜’/Z}3™m.S­ªoå1œÂHô)<L®­ÎU¾<;0\é²çœ…mJ}q‚MIõ õüÕ§«,»pøZj"ĨüJý0+χu©iFÙaoÒEÉ<ûþå{Ë¥DNsPåú ²š,Yç!ù3]*i'áðyíƒÞ¥Ýa¨"Ü´ÓíG”ÉyhVÁ»ì F6:=Þ4Ë»-He·ƒa €y®¸oºµ¡¯g&®m¢c·u´êþ'?/Ÿ<Šú¹Ó9–;Û«Ci˜ÉZ i²;ñW@¸—a©È(ñp~• õ¤éÞ Ûµ+°Ð»rUýã’“ß?Nئ¹Z\f•.Õ§î×+ÓV´B|¸ã¡°€ᓌŸNjíè½I+§1lZKT`Dv§nðܲ¡Èæ™ì®á«5Ä;¥‡O|b´‡VVAÈgŠvõæTEé€ËÈHRJ·–>A‘¾¤®Lüì•&:‰™$($‚ :ö“´ÌØ·=d7Û}‡c ¥h9J5ædSo©±™¡®¡mîS,š#ºVˆõ✕Éx„‹•ªU½Å© Ò›*Otäc"¬öE*ö¹mW-5a¾©?œÊÌuù¥I¯ ÓÛØÞùí¥nseº^½À @qånPdð€=9Èý)›Ôí?«î[†•Ô‘¤Ü¬V4(·6Ô’°Œ£x>xïQŸ³¾º:©¤8•‹l’X’…vØqåô'5ìñòãÔ]ë-¾ƒÀD(0ݽÌ( ÚV§UÝ)š‰æõ’çª5 ±èvÛJP¢§%ºÞpÞ;€iåÔÉÌÉÑÑæ”nž,-%aJ¿Nj1»ÎN‡Öè‰l±®å)Ї„\nƼØHÕöÓªôQ^‡"ï'XIT†Ò§JBp’d€+¯¢²:„›™&,@òÕµ$]Z>™òÿzîÔÝTÚäI¶ ŒG]c½<:EzkPX‘¹´ÚBRAÇ#ÌàV­¾=Å’m_zŤ×!¹¯©!3ÒTû¡Ôün,žqûUtwÞ½ŠJ¯@¥^Uô§Yi›}ÚÖòd§{iIVq•=æªSt\$Þ›KL­±¹ $§œpÇváäßUŒñCö«[÷RZi+qCø9ÍLÞÌvK £©1¤ëFÜŠ@ƒâ§òËùãqÿ*•ú!ÓéÆQq—mfQu!eä¯r08Ûþ•£®’ÔÂ…¡›0[³BTärÑA”3çšeÉå|a1×k<9Š 5:A>]˦Ö9S¿ó&*RáÎrGþ”ëó¯%š¨;ЇúÝV-U`ÕÒâ[ [ç†û¨”åµþ›HýEL4ÂëË _O]”„8¥C”Äáç €OØM\/kŸ¨W[މëUÂ÷mRâ¥ç”ê'âBÇ >õkº<ä6ôå®Û§ÜûÓd™;—ÉõªÕío‰t“g•S/É\U­Ä¡Äà$ úòjFö*Öl¿l™¥e‰±‡‰kþ4yÒ½<“|{1ë%ŽÔ÷Ë.³;ržâ›GŸÌªˆm}E×ÝBéÒíµj´ ”.BÆT¯ª})í5ªôˆvÕNZ²ø/6Ù%*H=¹íR‡Fe½-BŒ†YY+9ä$Ùõ5ËÇÇÖ½ÔwÕKF¸‰dR¶w[ÐPóO„8<ÀÅ44jun·³#I*S­q[S~IÂV‘ÇÄG©ÓãÚ‚á-6¥6ÇŠÎ07•ª|wæè è[bäGmeÆø1М¸3ÁR¼È­ùk ¦»U] ÝÓª’ÔĽÇÃ(ì@¦!ç’ÓaJRÕ‡â?µ_~®èfnZ~d¨°ó7ÃQø 'Lj¹ô¥«S7QiÔ„-ŸCnlSe'Èç÷ó®¸rï³qì¤ô–¨·5äÛÞtLp¼Žÿj±^ÎÙµMÁÙW›9qÉ}-¨½¸•xÒ·]"±|Ós#9¾<ÐT6ùáGÖ¤og˜v™‘®®¬Û¥ÍW…%Œ’¤Ÿ#æ räÏxíu¤a°YìHuˆ-ÄCªÞ´ œö'Ò”|ñåYVæô¯1Ñ^$äW´DyÖµI\+tDÇuèrZexdä'mVÞ¨ªŽÉ·¸ð~D7?º§ºÀJ¼ÿJ¶}E‹:^”–›Z’& Ü3Øò?j¬MtÖé?ZŦåh\in{·Hpãqù† wâ³í¯£{Ù\<Ì—ô<É(œ¢¸ŠQáã·ê*Î=ͤ-*—5"JÐ ŸðÞ¡ÇqžÂ¨mñÙÚ7ª’\b)·H·ÏÞ†QÈFp˜«e¥f^zÊÜWaØ:hãßÞQåüZO§Ö·Í‡~_IŽ]i×~ëÕŽÙƒ ™BP¥~JpÏœŸÞš÷Oh‰­ûäéw–ò‰Ùâ§áQòè+»§úVÙ;¨ömJãA•á2’[vúS“¬ÚBÑrÑζÜRÛÌŒ¶¶šëè+„ºÓ]é Ho[uzìÄù… ,!´lmÏ*>êF‹zÏy,)×ß@Ü—ôò"¥‚MN-’Ÿ”Ë«_ÂP› zŽ1VCXØt忯ç¿CajRÇ„…ý5»Éá–¾“ÇqAíqéò„@mÅœFàGÐ⻵?Nµ&Ÿ[M͆´¶¤oC»~=I§~¦ƒ§ôî­Œ«<™O%ïu·’œ~\¤ýêkÖ×ûrú~¦.ûB^Š|4(œà§))W¯¨®—’Ë4ÌÆ^É;Hȼ¢ë>âó×èߘÄ'Ä$0þj¶µJzN—­­iÍIÆ]‹ ø¼V²>™« §u8‰¬n—§Mþ /¬mI™í^~Yn[Y:IÎÔW×ks—x¶«wŠ´2ìƒâm=À÷©QÂ6ç=é£Ôk4‹Õ‰^å‘2:ƒ¬`ãâU˪Üô¬úútæSm„oŽp¸R²“Ò–½“/îÎ#LÈuSGÆ`gâÛüBº4þi_ø»R|7ÜÝïV¹ˆPmjç8òïPÄäÝÕ©ЄX˜¢¦¡9ùGÓê“Ë‹®ß@,l5m¢HÜI'Ì×kŸ-7:qtnõ¦šº²ðu©'rp>_¥8ÜùkÉVûhý¿j+Ú(Úö¼eÙ–­-oÛ˜¯\”©wHJAýM6:†Ï»é¸°-î4^y°•žJqûaÎb%£M´§”Ô‡¥ºVp2žïQŽž®5î[Þ°Éuóÿ—[¿ Mœd+wz0ŸŒ®që~ŸÁˆôuFš]¸ºòGð·]Ú^\´¤[„%ÜÚ*ø2Ù$cÒŸš£L3¡¥:çP4ô›‹ÐTøRp”ú ƒR¶€ºicÓ×Ûé¼hmN‚¶ä² Œ“æ}GÖºåŸ^· j¾=vêUóÇhǶ!×±™JÛ¹Dö¹§í—£×­\›|mNÀ_Ë&!*ð¾¤zsý)µx­´ßPƒ§,³$] ¥ ¸IŒOæd´;=jîtúغJÛôá—0GG¼­Ð Rȼ¾µ99.2kÑŒÛçÞ¡¶ÆN¿iéÉ‘hqðùIHV}2>¿ÒöÍG íº í§®¶ã^·bJCy $ŒdúU‹ö¤éö Öºv M*ˆy†µ<¸Ê%Â;m>½êªÜ,zªÎïá:Âͨ#1»Ä%¨>/Åç…c·­k¦q,ÕI>ÍÚŠåpê…µs"ÈŽ§AKm1ð6´c¹ut›á9ó>µMú5ÔŸhe:å»C꫌óʤ¹Äpz„Œ|#éR´i-:êÐÒÚž*Tq½È'oß5×rê,ô›$86žÇíZwna<*†]öˆéã’RÛ“f2êyðËD~ù¤÷ý¢­rf·oÓV97WWð…ø¡¶Â©®óËøéå!±ÿ1ïZBÍ©š`!¾¨î¯høP®SŸ¦sûÕRèæº› 5Ä+Äl)”¬"SJå.´OÄ’>Õzu¥ÖáÔ–ÜôíÿJ; \ØÊSIC‰u!iå*ÈûZùÑ:11æU[p§ƒ^ÏÞ埽®_µæÉ3Dé­Q¦âÄE¾S¾*Ëh*#åV8à×&“»¦ŽŠÇâ €â z?†”¶xãžù5 ô'^Yc¶æ‡×L‰znj¿-DóÎÛÇÒª¶Zm½CE¯FΓ¨mÑP}Í™ʓڳpñüMý¸ÿ²2¯·„‹5†[ñ­ÏŸÖ~0ØóqRæ—ÔH°èvà·àd´I+ŽÉÜNìGzãÕšãRtòßùg°ÿfaÝÐ †I}§\G@285"ô7¨éê¶™¹ÛÒÃ6«³m…&SL’ Æäçø‡úÖ3¶Í륞ÌšëÔ‹³SõäXâ81á¸ïæº|ˆGõ®)þÌwÈÐd.$øÒœA+ެOd”ŸÚ½|Ë…L¿¯¦úzïýe‰w¶¼—¢Jl8ÚÁò>_zý¡´ö›J6ô(~-Ê+Í–Öž*ð|þñU¯Ù;­éÒÓU¦5<°‹3ÊËN¨çÝ×ëö>uj[ên~ÓpSÛÎé³á;qÏzá–qä¸ê£N˜éÆå³9ùq$–ƒ»#¡}’Ÿ^kZKR?¬§&X¦!d—Q<òñær+­:º[:’l,Ü‹µ†;§Þi¼¥Åðóñzq^µTÈtêè÷K| +l‰7%*ŠRr0qVÛ¶^é=¤Û$[f³%9A*úSfÓ¥£]µÃ¶ýCquæÊ÷·nñ˘Ç$«Òžz\A×±®6–g4¹,6“ -`ŽJw ¤Ö™ÚWLjÈ:óð»^—]ŠÈ.Nº½-/¿7Œ+¸Ò¤ëK³öé¡Y!«•šbâ¹ïKKY-` çŽ*"Ö˸K¿[¥ß›*Ó—&bÊiÏu$á*àc Ñ0 §wc…ƒåÅqÝñÓ_{,jyk‰eym¦Z•´¨aHÁú“Ú™¶>›Å×ëgQL¾Žc± YðÿêYîr~´ìë‘zïñ8qº1¿»C KMËpö.(r¡ôâ›ýoªµ‰šÁ•Ùl°Û-D¶!(K~ƒ„ù “¬w J6‰ó4”©Ö]YuK‘ÒGº´úêB­¿ÙYNNX1Gˆâ2~$ŽÃõ5hg­=wµÆ˜Þ U‹YÁi(”H)xXóþ”éÐýU)çZ½ëD½J[då@vÝ“^|¤ßuÓ}"¨zBuÁÖ®v9Å$¾ ÔVANÁ‘ƒÛŸÞŸ—5ÔipÑoÔÒãZ­ \–ëƒpÀò矵-Êê=Ë¥÷8ZYZ-ˆ†„F—¥$¸À#¸ÏÒÏX4¤®¡iV¯nAšóí¡PÚuò†šr¬yŸ½jåw7é!Ot‹Aj»¶‰áó•&àã›Kªþd§ÓëLޤô¿¨m µÇø­¾>T‰†àØ¿éR‡³ßNõ&•÷‹Ž«œdÎZ-#ÄÞHôò©ní 3­R¡¸HCí)ƒƒ‚1Xÿ¥Ç/{["¤è}GlÓOé«´XJmþ ÎFô(”¤,:PoRZ/º†æÓ0ßE±Õ­èÎÉFeÅg)O $ŠTÖžÎw¤¯Þ´­Õ·NÖ^YBÁÆîÇõ® 'Gz¥åá)Û{ꊠZCÒÑŒã wÆ|ë®ð½í;Y-(ܦ4½¹™Ëß!1Ò®y¥„Ž;TPÒúàÙr†SI씽‚k$ꎩ[ÆëÆ“·¶Ù8J›œƒ¸ùqšóÜwöܺIîBŠòüG#´¥âRFjœ{hiAg×µ VÖˆóÚŠG ÿÚ¬ŒKÞ¯•(F˜Äk{JHQ’ÑñRòó~˜¦´…Wý /7‰•ø¡2œ¥C¾H­ñ_¢eÜ-ûI~GHGŒêœKs\J7y šWòÔ)ìg´t•A8Ç¿9Àû š×òÖy?jÃMî(¬:*¯üG$9MèÕ Dé àú!Íövö„¸Aµ·¥.Pܺ<=ÍEÀ•àwI'¿Òßñ%ãKhóÿÖIþªUC‘f5%§Û()*Gj÷qa3â’¸yYW?«]dÓš‹NÜ4®¢ÓÓ˜[Íe¤«Ýü*•Ÿ³%«Fé( ß™¹I…5òšÄ·SƒÛDf¢þß-·ÄC‘©ã‰ÍlPHqœò{ü\yÔƒ¥mfÓrÑŠÓöýW ÷‹ñ|9IŽãçnU’1ôÍc,dž1¹wvž5_QtÝ‹L½¨$8™l‡<(í´A\…ÿ*^)ç¥î ™§bܦÂU±N´S,Ø#<‘ôª‰ 4î›ÔeD·éÍM"DD‡ ÷€z'æl(íu?^*Äji—=A~²éÁM¢É-*T¥¼¤!çÂ|-¹ÈÌŠã–uÞÏ 98Ý™‘rJGººê“@ÿˆØ8 Ç¡9ÇÒ»JPæS·pî9¨Ÿ«ÝToIª‡Jf¿%V爈K8Û¹žßµ0z‡¬:ƒ:Í,ËôÍ!Äûø¶%AÅÆ7çÝø'§––n,Xí«rXBT|ö€kUéÈ0íïLšDfVê–@Hîj»éíA¨¡ wm+|›<2ÂZ\+©$¬¥@©M“ô$WF²ë¦½]Óv«<[Kk`ø†ä7ÁOÄ1Ø­?çv›íkIqÕ‰šŠÐ˜ïÛᥤFÞÞÄ,“ƒÀïO_L¤¨ÁCv# JK’œ ,# BjäÑzvÓÓΛÌÖ·&£Ü¤9%-¶Â °¢®Ïp)Áq½H¸ßÿ½2—`LBˆìy GÁ‘»OQô®ÖýOI?Òt‹Ý×JßâÍeçdÇñd´dØù`ÕJë­§ð^­jKxHJžæÀ?”œèjâ²ÿö~íwfÓ¦?Š· ñü¡¶8(@Èä§9úÔí©§oYÄÖ-¥·#Þ# ¾ã?á¦B´ÿ@Zß_’g:WÔ§5<{+ÜáÏÔª°ÍØ‚´+ÂYPAQþRj¥;rzÏxq޵!Ö Aƒ^œñòšs—U}.6)—öî:N÷ B­3*#Ùsáå;¹W½>½Þì:"5ÚÕ¦£Üíq¤-¸ÎÁØ—k”è`ƒççŠAÒzÆ|[óàg’ ¤¯¤ dõó® t‹nÓ³µ&•¸jF ¤©ûk“RäE‡7ªrp xuu§m¥MÔæ®Ô°¬öûLˆ/ÝޔƟ ÓC²¿ê©û°?ð«VPW÷§ÁÊO¦|Õôò¨/¨ú•Û¥ÝZwAL¼C/…6\iÁkr7„“œãž8®}-¨®ú5Èv½#|]ÚØ™>+±g°Kª NV<^ànÏê1X¸lÚÅj µ¯OÙœŸt’Ühl€ —çè©5'©ÖéÚ‘¨v½:¹–â”øòT߆â ò ¨|Cê 3ú·y¸ë µ“OÝãÅ´À¦ZÐe…xävàqô¬¶¿L©éžì©_ÝT‡?ጩ°²’‡ã G¨¦ßR,:2âî÷ç´²I‰NlK%gÓ#µG7ZK÷•7ìôYiZÛz¢KYWg~ùæ—d.Ù$iÇ5’R]”Dÿ:rxã·•O)ì‹Ó+/ƒmŠ›¢Yy^\]ÉÁiI* Iîvü?µ:ܶ~-1¨NAFÙ2—ÖÍŽFãúWèÂŒõµMˊ㦒rÒVœmûzUÖW§´ýßMé |…F‘=*zT¤£$6GÿjÝÚ¤­K«‘_¿¤í–ë[‚ØÓE˜oGåÌŒ’•𑚅ý¶t”‡ì–mzòR™‹&,àƒÂ\v©‹Ié¹5´ÝS|™ïÉ'pm‚ ”à,úµru² Õ½¾ÚSRœm£-‡‚²NÓœ`Ž8ô­a—ŽSIfãçË@¡Ýù÷©¯¡½UM”7§ïÍ—­o/à [ Wˆ#AP£ß–² µyÝ’’àWnrkÝ–3)ªã.Ÿ@ôì9 t÷´Û”Év Xd¤ã”a8ògÿÝéýM¤m°Û‹ ¦—5”JŽ˜øS© #nî pFsœÔYìÙÔôÜmÙ[ì„»"+Gð÷œ%XOòÛΤWõ*¬n™–F—†§†þ–£ G˜àz~Þ±²éÚ]Ä…1×tÌ£ØÔ9¤$­ %À‡Â@ä(T;sÞ¶i¾©½]v]½»k(*Y+Õô>B˜–øöo UâÞÌËL¦QfVJוžÊÏÃÀÈ©@XaÜìM7!À·i%ÇQJV¬ «¹ÝE¥`èœâšBIŒŸ™Ìð³è*ë-Òíw¿]l1D”Š•ŵ$—ÏeœùR­ÃRéû nÓ›£ ¥–ÁR³è@íúÔkÕsô£ri¹ÄºÇlû¼öHNâ?…CÌSlºúc­ ÃÓë´®Ç)«Œй¨iÜ¥pT~¹ëþÙI1¢!WŒù’ L±» '·©^C̱éö£2üŸG¼ËØ}Ã…«÷ô®ÛT‰õpnHRÀ Ú;ù SŸ¥ú™½7©bÏ™Sb¡ySÔI8ɾ88í\]H´ÈÓÚâñf”Óˆr$·Ú|Ò ý±HQ yRŽ@O5ô½ÇO ú#YYl:VÖ ö_¹¸æaÕš ÆU·Œ£¸àS?TÝ:{Ô^ªÆ¸^4íÕ¸¶vX%”©@å*#òõ¨sÙÃX%ÛŠ4¬éE¥«jw ¤g¸ÁýéV‚D©,nG¼{¤»[¤±*0ø’¼ñÁûW‹,|2u¤Ej –4;jg4Ú R– ª!;<²³þµ«Kë8‡TOµZ[ñ` x³ü²²~TúÒ pc\nk²Ê,¾d0eJm%·Ò•À×t—k¶M»LixÐW‚¤¸·‡†n<ÍpÔjÃÝ[rZã¶­Êlæ<‰ì)µ®µ;–ÇØ²Úð«Ì´…´ÙRÞì)jú ÂuÚߣtÊüy väYS¥­ÛÜuìsÀçþ•Kp¸½ùsrJå¶–”„„”!IiÇŸ>¾•qÅ6^nZ’ýpS7 Lͦ;)O¸Œ:%Yî>”Ÿ©oúšç:nãfŠ]5!‹»*Hl´uú+©¤ËŸbZXµ´™~8YXÉRÒˆµu²c®,£îþÇÝžå?@;½m£®-Áo!X”©!GiQåŽJqIÌö±%ùÑì€Ï}µnx$!ÒŒc)õÍrhShŽÌ‡ZuÖß$—c(mÂñÉO‘¥{UÂçwÔ>ìÃ(Dt éá>„{V}) Ëk‚9qÓÍ2»ó.´¦\} …°gÖ›ÝkMÿ^ôÞácÔ:}„LÜ`M†¥>ÑùÒU´”þÿ¥>仦¬./"ÝvžÄ)Lç™QJKža)ÎUß)=«ëšãK¶âp´Z$²¸1R¤´“Œ”ägþ^0jËw¶kç§»¸Û™Jö©<(5'tCXÁÓÚÔ ™l8ÚšJ–¼øa^ž”Óêe›û9¯/6t’JÒßý”ýø#šnÊFüdš÷Ù2Ž>ªäÛmÚ­~ O%Ç¢FqEi¾Òx>µ×+KÅMÙ½A{¾jwñ"T’ Ú’P~­#¸ãž*3öiê ,©ÛÉoº·ÚÊø’¡õ©Æû)µ‹srç8äwQœ)8QlžB¼ˆúW“)qºuš°ù•®´ò̯•d6ØññÛÈ8Âä÷íô¥í ttÒ±®ŒÛM®ÐVÃkÂp×’±åÇ5\¬¶Û5ÑùèbØlo|,²wË ›GÊIó¦ÿPMÊÛa·¿%‹T7¥µÈm…)e)!*Cª? äW+‡ñS…–R§6ìÎC./ò2; vWë]fE‚Ézd†˜l •8°‘ýj7ÕCKp.Ðl%:ÔTª&1‚”I°?×µ i›z'Û”íÂ,¹RÞÙ)žÿŒ†ÕçÛö¬øýÔÖÒÐÔ6C!,~-ÄP ŒãÖ”ÐB’’<‚;†eE´Bµþ<(ïH8aQÚ Cœ«<í­é®5h±’é·F޽¥ÛZŽ]`¸Uñ[‹j=òñ¬ïò®J‘*%¢ij J’ZBŠ9 z‚¯éYÀu76¦n’â\Rd GÂJT~Qæ<ù®ˆãð¸ÏDÚáoÄ!i(%ku]Ô³Œ+ôÅkLKœWRݽ 2ËØ%Ö›8@óó󪬔õÊ$¿ï .ÚÒR¦ÈYãÓyý3å«ÕL´ÝÎ夸x”ƒŽGÊq]v•Ám³¸#:¦Ê’œ¤›šréë=Á‡Óïqša‡’^zCkã>cíMéMm¡tþ¬ƒ:c­Ë…9øêßà^h ù}+ÑñíÝ•Ï2‹Õ7M#¨"Þ,òWC Iàú‚<Á«©Óî³j½ql†í¢¡§©DÅ<³ð sðú“TA «#å=HýÖNiksÛ#>àCÅ_/$rGÒºòñ̦þØÆ­WQõD«½Þ—™¶¬¼ãÏ4\mIÇ;Hý)Ϩ`ÆD¬Ó ¡Ð•Æ*`øA¢9ÚG§¥r›‹mËÓ“W0IGÂʲJø°iŸ¾íá³zþÑ3rµ@ZYD$lðÔ­¹+…_*òjWoIsCê‰úžó%ØÐÃ6hèKŽ‚yÏP<…:Ÿ—dm«»„ÿzgÀ‹>-¡Ù:výq6n!ÄQ¼|ûÒm¿©V{cð˜rÓvTI‹)UÌ´…,w$'*þ•Ï[ô•&:ãl´\qiB9RŽýiºæ±Ó?‰½m7ˆ©’Éi+ÀÏg¶yZ$ÌV¢¹ÞÞ»¿uvÔû…¨1Ë((Ûó¤qçžM'ZQ ›áëilÛŸ˜¤6·ž%ýÅ\,nÈ¿aWÄ“I³áîqÀîO¯nôÓê4äGM¹˜á©U!;Y*9Ûƒ•qž)¬ëWÉPݶê+Ô‰‘RîÚYðœZ3Œ¸´Ÿ‹LR¥’ÊÚ^›yÅÉKAµÖ\!£SR4íŽë0`¥Ô7 ¾¥à…£Ÿõj¤ø¶ì­Pû$(–ñ’EwÁaèò§7Øh«r“Æ}~´—·®·&I\†œÊÔÍáÁÏLKíMnY۳Ǹ¥†RÓOÊ.¤$q’9§KŸ-2»»r×mX¢¼Í—ETÿ‰Ù] '3$ÿèER_K}ªt-ƒ_i{u¦ë9VùèyÅÛdŸðÒæÑ”¯ËŠù׬ôÍßIßäYoQ”Ä–<ÊV<”“æ“ë_Cãe<4á”8úY¬ÿ”ݶàØ~Þ·¶2“úÔÔ¸QÔ_¿ÛîÌDD†”Q§8Çò”Ž UŠšzu®mw "týß0C^2äŽÄæ·ÉÜ1©RÝ}¾EºÚõ5¾4˜wôF ÂÚœ×X=+O»^õγnV¦É„¶Xi•+ ;ä°GcM‹LvÞ¹—&J𒸾e)#j‡!ÁåÍwiéˆ]ê#0«z^xfRR~ 8<ýq\,ÂqÓ‚-¾4‹Äˆ—tÇŸ±õÅ¢ê²Nå,žr;ÒÕ–Þ¹ ĸXgx¶¹ ¸ÊÛ–ð‚•c#׎GÚï(fÑ{º8ËŠ0Þ’C iAJ±Y˜Íui»m–Vó>[ãÂJ˜Bqâ•(îÁõo ó›r³ßlÙ"{Í]¬ò<¥p´cåw® UŽf«ÕQðóA‚ã’'ðSŒZEp%›•Ù¦_BUpu Ú¤m) ¤|?^âŸýE™ý™¹Øú§ÞbÖÍÒÖã³%);Ö­Ã÷¬zê+Ÿ[O²Ý4¥·@ÚeF¹%/\TBÓœ$ýë®Ó¥Ðbs´@mdÆd÷HÇ`i»ÓÛ{Öé‘4Ô·‘8Cp:§ÖÞÕºÈïÜ~´ã×·UN¸#)m´Ÿ“g'³ë¨¬.ý<ÜÆ tˆ‹C‰qIZŠ’êsòý8®]fÓšÇLOÒ’!;L”§Pÿ."3à—Od¨úçë\"4™!© ´6pâJÁR€BœWÍLÜkI‰ Ë1‡ã\~A{öÖŠ=¯´•óEêY6ô5Ɩ¸óJÓ䤟4‘ØÒx5n5 gP-ɲê$©ßwp¦<ÀŸïI>d÷F|ª¸õ3BÞ4ùVÛšRëN ñe6?.C~JIÿO*öaÉåÕöãqÑ[£Úþí£¯˜’±R‚iÎP xÉb _dÜ.sà‹U½-!¤)çZdžá=·劦£¸©» ZòßRl—XŠuéGÁCå|žPóÅg—¸¸ÔÈ‹fôo–g™µ:§Ò¥0–‰mÕ%? àãÈò¥¨Ñl“/PÜdÈ·¿~yixmÄrTJ~äšÂ=Žswk«a¸¾4@>ó\æµÅ±ê kSäÉ— ÎDD®2Yp(¨ñ¡@úŠóoýt-ƾ[®H¸Îy1äEƒá Ô‘T¯¯š&n‰ÖÒâ8­Ð¤-OBpP¶”r?j›J°žv)]^ޤéËf¦°7~ad´ÌÈÛ—W¨ó úUHꃻh»‚š¸Gñ"¯ü (9JÇ—Øý+ßÅÉå5}¸åŽœ::ñ"Ç|9‡±á­*88Ï<мšjýqÔšR5ÎÝî%…¶’·¸ÞÉO'?\W϶¤lINR=jMè–¿‘cÔMÂ)äZ¥þT„‚HNFÇÒ§/”ÙŽZ[¹’ï–Û¢¦4ÓR¢†ÜB•“ØêsN7¨¤AÓí\áW” ƒ)# cËΘ¤ÁM‘(µ{ÍÉö-óŒ6G'ê-h¢Å¾Av<†å&[»R“”x'npGb9<×’ÎNM7ie7›ž¡y„Çvä´¼YS¡Í‡o<Ò6 ¾Ç··"Ú¸n¸—\ñ[)=Æ})ÂëpTáŒäŶæ~ \ß½s\bÇ””Gz3 a9 t« XÅb^Ôš™3¥Fƒñ*Há²3±)<œQJÅ‘ wR‡ÃN¥§[))q)gïH6i>ã%PÒ„¶Gƒ¹@¸„y})ÁeÉ0U1ÆPã{ü<¤yä*ÑŠ&2óreGSÅo£rFPO˜Ç ïX2ÃÊm¶ÚeÕà—¥£ *<|ÕëwrŒ ©F2–Ë… Œç’šÙouN\£FZ”Ó{†ç ßWUv¢qvý:üÛ«{ì$t,¼áÝÀ=ùõ¤]'Kv…ܵ3Émà‘ÂIíÈà÷®kí¾ÛzêÅãñ#%ø˜LBUʳ•c?¥8 ª]ÁŸwá­µ·¶<•OPŠ›í}¡¦þ0ÞµjÚ°Ä‘áNR”¥i þCîVõ¶v¤Ÿ:úQ¯`ɸÙ&ÛïPì7Û-»…&¨çUz}'LHzTVÖí½N• rxü³^ÎMÍW,ñû1´ÅÑûú-Æ?å¹ÐâHú¾jîôâ÷Ttì©ÿï‘rt$à¡cøG×5F–²’NHî0jCéQ.º6ê"7;e¶B€}µŒ£èMo—)Òct¸´•3o~Íz‘í(S…@8€{¤JÍë igŒÜvâ¼°Ó»W%ç|7J|·}i›¤nl_mQdJK8Ü¥'b°çŽ|…+ʃaŠô…Cžúå+v:ÖV’§Ö¼v]Z-w[Óº¡Ù×+:¹Î**‡ÄYoJíõ¥X“ªSÑÖú›)Ñ‚•ã 4™qµ]-é&ÇqKÌ¡â±ÅŒàýÿʴɺ¾˜_Þm߇ݖsa'x?@¬~´ÐX´9ï\ ñ[R@ VNkIµÙ®Oìx2]áQaâC˜8ÁB†—›hmð˜­iKÀ ‚•dqƒåšégñ8l"RS ×*RË$­$çÍAÔ»sv»ËSÙµ¨¶ã¨4œ”Ÿ¯¡§.šƒ)©3n³,ìÆ ¶]ŠP~n3•zפfþ*ò°œ6Æ[ª8óÒ™úš4}O¬'ËŸz*´¬¥¨ˆ‡-HQ 8>f§¿aÅz~ë&ëp+/ĺÊÜëil¶†øIîAÎ;Ó¹ñX[q6Ç}i Þ¬©'Í#Ë?ïI²˜³ÙaE´Û¡KYl•+'ÏëJ‘\L8 m ’Ò€I<’š´Š±í…bCZå»ÌH«@”Ã~÷ƒHÛŸÔj 8¯‰$š¾ºûMZõE–\’B“·z~fýõÆ{U)ÖºNá¥ïO”ڃAgbÈî¼ÕåÃÊÝU»»Ç—)&Þ¥EB’èQm„gÃ8àýkR 32ß §áûæíÍ:Ð@Frœ¢~Ô‹dvõâÕs’ðx‡¾RõÿJUj}ÁiXêHS%µ0áÂß'ÈùúyW“N®ëf˜·A¹¼?q0ÝÂU"ŒcÄþu`ó‘ž~Ô¸ÅÁ—Ü“mCìBAYZÚÀ $sýM4£[Ö«ßâSc<²ã%æøÊG;Hä`}¼«’u²Ë-ô9\×e§–¦ö%+$(ƒ“ûS[ÇaÛÜ–Å—1Þ–ŸÍjB³¸sÈÎ¥+@qØ× twÒâœ!²ÇÉRi·e½r‘9¶XÊHBÑññæ;ÿµ+[_ðÖ¹²e©m)_à·ü¾¿JÍRŒè·÷Y—Zr „Ap¯¯5ÂýÉÈÎ2‘‚â†<‘Íiºˆ%ùIyàÙÊCJ<ãËV–“ÞЗñœh'8N(–¢»£s—qZ’ËjSÛ¢H)&šSüzù'S6óìY]†Û1_ŒùÞ€>”Ž0M;DyW-t‚ÖPûèÌ@<¯TѲÌÒí³ør¤{œY [l4rŽR|‡ÑB‘ eÛ×P½öDe’—/cjG™´Æë‡÷N=y€ørmµ¿ üaþø©šsÑâÀlJBÜÎu@á>yò®{VŸy¿Ëø’R  õã½je®Ë6ùé*ˆ’¼9-–”’AÜ1Ú†Õ…“‘ƒåV«ªý‘|}é–—#¿-_ 'úU_¿Ú¤YîÛ䤡æRâ"½¸g3q³K ÐN©5v‡OÞe´ÌèùK/¨ŒàñúT³piv´>Õ¾ÚÚ :“à ½EY%H\ו/»)#˜ªA8}Ŷ76G¡¥÷%xÏ¥·6­”ü@`cÖ³}©³ kw î¼nNÅäxIh lÁàžà÷§M¦L¨v3ÙJ_ðJCJ^và«4œÑµ9pðÑ•­ÐT6Œàç¿¥,µÛšu¥h[*´AÈ-#Ä5=Īj¯O¨ö©[†ÌŸ ŸJÕRÒ"»¿øŠ#°23võC8`¡ QeÔì)õÁ=ù¯\y¦Ý@m¢“€@Ý’£P>l¿ù3œõØçËIšeK]»rÒRJ*+µe‹íÏŠ+*(Ù£Õ¤ÚÕjŒ.‡+ZQú§š«~Óz6 û¦l_-k÷»•ˆáÇÊ—žAÿ¤Ôûí.ÿƒcµ Ëßp|#?Â9¨GAÌ-êqi˜´;mœÛ‘å%G‚•Œy×£‹ry9ýiMë$-H;’¢ /õO=¥uÍâÀúJL9N6Œù£'iýF)½^ùvâ™úIÔnÄZõcòÚAKN¤ò€EIÒe[œ·Ûmæø¹¶ô´T²Ø“¸ñúÕKÔ…¡n¾%¹˜lø¢@t%D«…,zW,øþÛ™&\¬Ðä*#0=ójò—/sÅvéû«Œ<Ë¢*Pâ¯ñU±¼ g¿•*Zôý†©™’f:&) ¨,Šz馴¼é-°·X*Æ3‚£ŠárÈbtâáoëß»ÜáF’¥8‡c´-\,zmQt§Ö«dØN…¬ ì^-W‡Xp©AQP{c?¯4¿­ô}žE½§ìn(K!IÊ‘œ‘ž1ØWGXlñìþϬ{üä%p&7&3JÎ$¨œ„3ßúTò›‹®‰¶æcÚïBTԺ⻤—Gùsä(–Wr»±?§—”íç?Ò¸Û“:þ¨KHG¼Ia [IO>Dyz¹g‡§ÄW½ñÕ:•oSXI>†³n•Ç"D .§"…ŸPÊ@ÉÉô¦ä»ÍÊâ°Óká[’¤ƒ’‘Î>ÿ¥hÕ™{~Sam­Åãi;}~”©¤TÀ½²¤²™RR¤8BE]jl´Ì›”çž”¥D/(¯*ç8?µ3zÍanýÓ­KiaHsN)¹±sçKJ8qþ\zwjMA=›³Ô´´Ó+RŽÏâÉìkËÑ©— vû‚[®Ì* Å_ʱ´sô8«-¢‹ùVM8ãkJÛZ r N¥Wkz˨î§Ð¤.,…¶B¸<)2½Î)›¦}_½AO]î T$­<³¹HøIô©ÓJ:‹ô„#Þ›t¤à­ÙF8àU)a*ñBI;†*Ðt†ß¨&4Çài`Êi°™ S¼)$s^~\$î7L^碢8Úd<ëe# !Í»‰þ"~Ù®[­ŽÃ2Àƒá$²ÒÕ±;ó´æ™RÙ“wpŽë + I ŽA<ÿßß*1c¼êÒb¸°¢yJó…ߎÿûW ­í#Ø éøÖ†?oß¡—TIõúT‡pÒáòÓÙÉÁq î:gC—Á÷!¼=Œ­%DëÏ`‘¾p ¨xIÎü­cM%ûT+DË<»\V’˜’‘‚”(ŒOZe½:>‘º?Ë*3Ò¼,¤ÔNFìçûÒÏJž?ˆ½çTµmÊSü$ú× ’êÝïTê%ÜXŠûñG»+kDŠI!=ø=ÅcÖÕ×.ç ¨âéáß[`ŒŸAHîr$ºò‘+ã+xúó]®[£&ÞŸ °0¢ÚÎTþÔÿ©´\Hå w­MýOèûì]ÜŽë/²<2áiõÏ%]`Úo!û-þb$¤éä/(*|JÔ º•Åv(’‚­¡ƒ<šô±§$©-8ÉRœQÚ•«%$zU—I§ÏÞ¥i9š7WN²MIòË„pâÊ¡ô"›HR›XSj;‡$ µ^ÛúY•[¬z²NÄ î<Ç)ÍU/œ¤ý+ÝÇ—–;qÊj¬CúË2ÅXîÉim-8fRÀÊ?å'ÌßÚÍ[fÈüì\"»â6•à ß‚>‡ú~ÿ;q[‚ˆÁàgúTïÐc©®4Ì&Õ5-¶Ù)äÜg< åËÅ=ƱËékZY>óïQ¤§-­ r¨¤¨ò#øå×fÚ‰m%g}}~õ¹»9p®î7µ#ü×Ê<ðO¥?³ëy¶×Ä0’ô¯+£ª×=‡¤¸M‚æ¢FwsÇ4¶Õ®q˜Ù÷6œ’’}i¦Ûñ­³1Z[­•!C£Ò”fÜ®^ðò¥|¾!N÷©b°õnqÆJV¬å•m Nx?zQ·ÎUŸC_/ÞêÚåE(R}á$Œqþþ^u²î—v‡’ÈÙ¸ tuM.+OÚ.A{ílJi¡£€µ)Ä¥ 8î?/?Äp阗šÎã©/lÅndÖ’Ôd§†€à}ûÓ…é±,ÀD€Ò©dzÖ™,ˆ²ÌÄ,- A;[þb hnß*irK‡bÛ¸ÏzàrR–âP Ê9 5äíÐB7vï«a¤ï[)JymÄ”ã‚il黳¨ñ𣺷æ'ï\¶Ç}Öu•¡ÇÇ=‡®ø7© ZÜRð²ÁéÏŸÐÔ»RŽƒ±Ü­—§‘áª/‚¡{“ê)ƒ¡­¶è²fªåIœì÷TÀ'á$éÇ•<áêY ËrF䥡¹'Ë_¬Ñ$x7ËJ\nkn=“Ÿ¿Â{µ7ýMïˆ}väHy+rKnðÊ;"¹í~ñ-+P†Ý• sÏcJöë²e]å[µÉ„ÿ…ù(’’èðOÒ ‰Ï!N3%£¦ÖR–ʳúՃמ#,ønªäc^¼ÓC]ií+ªT¸à¹C”‘矾)ëg†ýÁô©ÂQ;’É5¾}ŽØ¥©Rmc~FÖr@ô«.©¥(ê–„•£nÈmKmØÒ S. å'è~´Ì.$ (';‚{¸}zÒÖ»‡M*\Øù’ÚðN ~`=8=¾•N]ñð(cö÷¯g~QÇ)ª˜z7ÔÔYÜEŽì´¹gòÜYÉhúO¬,Ü»EL˜Ë.¡Ñ½rŸ®*‹†Ê†ô¥Ä¬jqè·V®¶ö#é«”¥˜_á²°¡”ØùV98þãXåõSôtIjr `ªPIqE„”ùm?­wÚmL±&T™ž'ˆAQ*Æ8þ¢¸í\Ÿ`¼…¶”ˆ9çË‘å]3$Ij"ž‘ àâˆB†ÓÛ“Ÿé^gGUª×l/ƒâ,BÐpó­Öûja©é –ë¥jÂ’¥ü!'ÐV»rÝq±îÑÔâT€R ;ß.ÙjO÷2ßù„( *DHj@ x ½Íu6Ì)È&:[x$üiïŠD¹Eœ­ûªÔÒmÁ®ësì´èðÊA'¿ëYÒºîVµ¶¡!+‚O‘ÆjäÕ,ÚlºVv¨ü»ÈÌxáyVK‡* ¤ùç'ô®«ƒ®E¶Ó´<…yÚsÞ°×½¨,q® ©Ù0!#ÄÃ* S. ä/Ÿ˜}ÿjOiIvg&\ø|û«E%n§jÞ+ïÞ»&\œCª[Ž¥ ¥Gvr=¾ô‹¦ÞuÍ=ójøªg5þ.Ð2Œž ÊGÒ•-¦$«¹jsn‚¦Â‡±çëZ°lbß*rºº[mÁäÙ+ý³U¿Ú»§S­.3ª””º‡œð\R[Ø qÜÕµTèñBDfðq…r?Z:ånwSh+„T¸ê”\BsÜŽÕxó³(™MÅ ÂP¢¡»>gÖºmS$C’‰\[N ä)Z¦°ã3ùNÒAËšgÃ#h%?_:ú+;Òn¦DÔÖÖ¬×e6Ìö¾ŒSêUëS¬V¨âÅyÙ W(Ýä‘åT‡¤Ö«•Ç\ÁE§yq§¼E+É(Or~•vúz=Ò qé!÷’IWª~Ÿjñóc1½:ávRcO°†”Ê‹Ÿ›Ê’šéM†Zøàppóƒ[•us%A ïÀ¬¸<çæ ‚¸vÛ M©ÈoxŠSjyCã–íËÚ §\…7‘ ­|óœã»˜_„R¢AÁíœÔ£> í|Dƒø\Yi’Ò‰‘!€Ö•rØ¢”¾µ8HHJ Nr+V¿'Œ+¼MïÛb#ûÄfÊR O‰WWU¹Ï hPIxqŒ|?z¿AébJQ w+µpØ¿ò_þã]«íXbûkÅç>”Q¤Mí=ŸÀ­8üç<û|"«UÅÿ{Kiyq¨éV_Úd'ð‹Nä•ëœ}UÞ<ËN÷Q2 Ôê²”nVݼק‹õ`Íö­´"ä‹P⤓uŽ˜³’òÈi dýÅ@µf5imÍ96Òñí’€SŒJ?á8 úŽ>õDÒVæÐóòç¶ ÐÁIÔå5êãËX錧fc -çÒJ– Ïzx¡¶ì—8‘`xªSd{Ã¥8ÂÍ{d‚Âÿ*×JY{ ’ç)ÙÜÜüý)Ø4Ĕǒ·ÑáºÓùÎäú’;­jä’]»92DeûÐÛ°%IqxýiNε&â|Mç)sqøO©4ÖvÛîµ%{GqÑâwŠ«eͲH[fB€Ø•y?Ú ;•žãcZ„ût¤©ðU’0ŽJš:qwÓºã§è]`ë ")÷ˆRæÝ®'”óô8®yMY“RýºZ¨ŸŒÎŽú[÷¦A ­@m$)]«›Y]%NvJÝþì¶•„¡#¿ÖÚÑúóHXÿ‹î†vï[Þ+uµgâ8ó­÷™­Ý­L^mÒ›yJ~Ü ùñäx¬k½©JØ$^%E‘1¿”îy|ŠÆçÒ½X…Z.å¹ Vå#ŠœW¸º†-öûtD„¡¦òâøéMëdSï1\VäåAjúŒù²}›)]ýíWý NGyÕ†ž@'äàâ‘ê5ÆS£hmÑ‚°S³ ó§ÏQžq™Û ñV9R¼¿Ò˜ú™.Ýñ›Úìu’ë¹NÒ3ß#ËÐUÅ*(ëÌY—ZAm¶©hJBw¼”üD<ã53|O 1+þP’MOšç)7(Vû\lǹÔ2±ÊTUñ ŽsŒÿß4ä±èy—¨ø—Ümæ›O†¾ÇH#Ìï3ñŒëuh­$¾Ä‰ÉÃëH[,’F‘Yô«1ÓM2lîÍ•¶††é@¤î< Ç OÓÙÓ–—ôíÙ’âS"C‹»ÎRŸ4Ÿ.*]‘îֲ̄¼•ÆZGˆpçô"¸ògkXÃ;TÜ“sÕ2X”¶”T@!c‡4±`Ñ—ØQrä¶”¤xiJ—…cïçú×¹þå(<Óˆ%¥ €  ¤þ•¥›’¤DIñÜ2ªÜUßÅc½tÑÅýœÄbÛN)¡ð8+*õÍ$· HV—S/;\m>DpiBÉs¸%h@w)YJpGž qÝ´…ÖÖP$FO‰³¸w'ö5 véØw[lˆ¯E–É;‚”3ñcÐÓÞš¹.ËŠ³r\ñ¤¤ ·1ŒçÒ¢(ÎÈeÿ·ÊÆNÑ““ROO/LYeï@`n5Ï)V4j%Xæ¿ã·ãÉoÇnZ;à¡G˾k…×Ûhzht8K!h(ÓŽ> ÿJuëk«:~Ç(\Uø‚e´¦ãÂJ2VOqŸJai]?&×dk¤çf.CªQOm£Ù?¦E'¡Ë¦âûá[¥âèܤ-#‘ô®¥Ûå¶•Jð'x‰'Ãïš^UÒ-‚ Ñ¡° ZHÆ8Ré«&ùpi±!מC;ŠÓ•O¨Ï•^èmõ<]mÓ{¦™SÈrsˆ2!!ÈþjK†ëo¸•„ïm{JOpAýªój„ͻΉ2ÏÖžAÊÖÃ}Áó'=þÕk®‡­Û ˜©°$,­xd¤O$‚kÑŜǪÆRÕti¡‚•{yTíìÛc¼ÚîÊÕ îŠÈH`­\‹Ò•´·Mt…´®EÑO]e4>Èíx'ŽßÖ¤ø–É“'ŶG,Æ…0Ãc´I``V¹9%šI‰×ïî¼ÛŽL’+ç8ÆÐ;gÈçý)jÕm*0qJ³È#qåŠÝmƒe³À-Ü€œù -Dú“å\°Ù°=7| /DQ9Sa_sÞ¼»t*[ÚµÇo% ;À·ÞäD»C0YwÂO—Þ™òä-I*,…­i)ÞÚÀऎ?jèÓÞ›‚¬”«)ÞN0@îjkì{nC¥kŽÞßxB‹8ÎrïN».³<];:ÞU Á¸¾V2…w}ˆ¦ŒO~µÝŸ†âŠH%~"SÀò ;ôΠR§2¤¨©³Ê¸Ç­2s”‡îÍ‹léR­­²ó3 à)Ä©!;T$ƒKRå°»k*¶œv'‘GàîÛ¦Ì~Þωgòˆ‘â´âŽ\øIø·‘ŽÆf\e³.5šám“o2Râ»!I¶žA°sØÔö®y—©¥¡ÕƒÙCŸÞ’¤:Û äïq8Ð|©Fék”ÛgÄm·T£ÂPsŸÔw¯bé¸l>‰s¶!ÇG(„ŠÔÐH}0æ¢dàɇ22ØwpÏÂSŽ}=jê[ríWùÐ1ßZ7d(FAó¯¡SlV`óî·1@ä ?J‡º…Óí#x\TÊ@sJ¼5 `ö$w?zíÜƱ”Ú¥6ÆÀIÆ{Tõìç .–ö¬»F~3{ "§Å+?Ö¤-5Ò-;f’Û–ûA¹ºæÐã’ÔÛ~`„þ• »¦õé-ɽ+ Ç)nM¤%´¤c‚>Ø9­çÍ,ÔI‹ž²Ø£³ï`¬)[·'~•ßmq銛`©J<¬Œ¥uܬ¡çÀr:Är@R1’=pi`©»PotP–Ã{PÃ$}«ÏkdIûšˆ°§âo¸9¬mÒRÚÊ'înO C“ƒßíJ†ðáu)Ž$%I-äŒÒlå»6Ci(IC›R­¸Üœò3Pyp˜Îä´ñ_ð¤+ÏÒ³—5Q‚ëÊp¼+„×t‹,Kyå#Å”°>n@Æ1ZRÖÛŒ ¥+ PÆ01Ü~´ ÃÞ5n‡™g¶¼×⬦|Uü[sÈIîœöÍ3žäååÛKÖÉ9 `¸Z|øÁÄŽ ’¡ð«÷¥8׹ߕp1T–؇6‚ðç>¾µÝÔhK¬c*|õ²ÔOÌŠàh6â³ÎÀ¾ËN|©:£ŠÝuaˆ¬À.+{@$«Ã äã·~?sX­ä 8—$2œsçÉò®«{M)~>Ë2޶6„©?·Ú±}ËkŠBAAÂT¸ëëAÇqDU2S+ `—B{®úðjëÛWàš®á ؉ ¨ŽèÏö«®dLJm‘6ÚÛlHZxh¯ Ïg€sšƒº§¢Q©/ ›è=éL…8…8µß†ê±œAÿ (¨ß4½¡­²®žxˆÜ¯<úsÜÓ­Ž‘Ï!ׯQ*Â׿=qR‹Óq­ih@orw~l„à©¥vË’k¦$MP¡Án i^Z“Îãë[nò$–Øl=¼(ÿ1=³IÚUÙå2Ë æbS…d§·})Ä­6ã,Oº#‚6¢8Ïs^;Õvué ÄÚV’S³â‡Ï:íšêw´úVë*#„ÁûÒ<Û p¡VË¢âËi6VÀŸ-Ãν$ÇŸ7)¿ËAB²—î¡YÐpF¸¶¢T¢w+Ëë\—± ”LVRâÎx¤¨¯:©‰m–ƒ„1ç÷4àkOºãŽ=*bŠÔAKc²f®‡+‹v‹ýËOêD3mrÞ°"¾ã¡˜Î>¡8HÏ8 w5‘¸þbl&S+HK†Èþ!úiÕe‘øÕÅ‹tè±gGe¿ñ^h)@$ýEGZÛUZošºËLK/6ˆÊŠâ‘¡I!xÉ8äÀûV§u ª¾Ê5,Ì@JÕÊRÊ· WlyÎÊŒøf#ïå$Œ·ôíXiÏÝܸMd¼I! PÁ}k¥­J¶IC Ch ÀÿALº“£¯‚÷6k6÷ EÕ-a ù2{jÚlÙ’šŒb:€µìhÀ«[Ô8®ß/î® Ž6ó© u à sœõɤ´+Þñ"UÒj¥aYNç0 =GÚ½S—§?Ín‘év¬Ù-Jt¿#ò¤<–òAùSç_µO/G • ) IYN™ò"¸¬¨´ ±S!€½»K'ø\ÄŸLñK·öÙ™lŽ·Á.!$• z}kÏž[­É¡§ Üî…×'xq1Âvü_¯•,˳©- ³7=ÁFsýkC›nÒǹ¸ @ڭܨ®;W©œã¨!JNà3Ÿ#\ûiÍ2ß:+Ö|'X øÑŒ+î+6‘1xq¨»›PÿBOÖ¶¿5¶mï­Õg 'êk¦éŠÃl¶¢@@ÈãÅ¿v]»<’˜è”œx¡@–Ô1\v¶îÔ«t§TÚ¼4+ÃÛãQÞ»Üy2;eQ׸…#âäqÞž·¸úVm™ ´Ê‘ øë8KŽnOéšc;;o¹¼ãM¨',¯d…gŒ.+¼µ ÈvHÚ}…2€e-Ä6 @Ä€26úƒÞ“®3Vô²¸ÌHCkAIB¬nË?j~hËž—³AŽ—`¶üµ¼T]sâ)ò¥9mZ¯IHsûÔV ’¢„‚ž0ù5.V}.¬é~ðÂPã*Çw>ÞžšwV±m´³O4ÉHqa<žy©ùÓý+~¶;:×!,©äp09ò¨Þf•sJßmðÔ”ø.íóœ+4ò™°«#VIº\L[¬væ7á”­ &¹.ºv{c2­n&Co­Iü3ÆEq©èíj R!áͻҠ¡çšë³ÞSoˆ–Üð¼4/~Õ ìVy"š×¡+û8=ss¼H[¨MŠ$E)æ9V9ãô5ék{s.W× 5"ŠjýòÎ^#qÜ¢¢28Ær)ïìûª¢FÕw |Ô8˜w¥)–6 ”—|þŸ÷Í%Ù4»šw^Ü4$È«Œ„ K‹áX§f™ÒÎ ti“fGeµ¢’®i[Ph(Ó"‡ ¦<…(•÷ÚSõд]mƒn™…{³«J’A<ç½]îtRlP *Ì—ÈRx$ySBùnm¾.,KîþfîÁ@úAÚº é)aJÿ NÜûÒµîë}š?ŒÏ÷¥<ÞÕ|I@'wßµfn ¶hÌ­!”œ:0FÞ :íÍ·aŒ$Ì}Õ(”¶*? ¦”ê[Šr™ïM**»{¬ÖÜP˜Ëeµ¡D'rsÅx™ÔãÒQs‚–Џ'ÌýkžLµ^¡56²‰i<½’Ç Mt±¦'K÷y^#ëp'Ÿœ¤}OýX˜e¼&>é Z@á%@N)~mÇyå4ÃXÈYWêAÏJHÜeH •+ç9“¥éטx´”¶£ÃNäÿíS¥8æßíÁˆP帘ìjx¤rô­š†Ñté=@ŠôwÒÔ<9SJwÂSyÉoiÊr2¯ïI0ÙmªS.—N2O˜=ó\6ÓS„M²¥ 6¾C¡dü>|UåIL §A `ò3ëMûÚY•~‡p-¡Ô4ê7$üÅ öý2iõtÒm¨rÁðÀ «š—ý#=†]vÛ%£üCì}jË ]~dPê˜_€Øk-…'æRp}+{×™R’[j:Ü` xgsŸý© ¦vÙ×&ä1|Qu¶@ ŸPiìüËR,­$q†²|Æ0*^¨h;|¸7qd°ìqƒážüýý+ ¤£æ §qe¡±ÔeIùRíÁ6‡”è[KmD¨Žvçµ4[t²£kœ§³Ÿ„å.&¬ é2Rw<½Ê!YÎF=+µ·$8çµ@¥9 J{Ÿæ‰à¡Åëî$í )X óÒŒG³Ù“!ô¼¥ƒ°©{¶¦jU!7&å1瞘ÁJò‚T1ßúW#N*b^y½»‚1 œùú朓î[…luJÝå’;v¦ü”ªSËS$¹žèN ð)¾#‰r† RRê–ÑÆ?µ-kˆ/¶+-õûT‰Ñm—Ó.ám(ŸiáCŠk¾”³Ó*RPrÚÎs]v]I6t,¼„6±…4çñÆÒ) LDÉNÇ”‡K‘ÞB\m+;N¨¬ã%‡ 8‡”JIÛØÑ"Ûl½OfEŠP!´üv×Ü>£ÕøNkj­ZºÓ|Š™ÂÝ%§AqIeD)Çšƒªáiޝ¢•†‘ñ#·Ú¹Ó¥ôÛì”L) ÁNÔ|'ÌÿJåºÞ§8·é[n·‚R£ØWUµq÷0§‚öŒ“Àóþ´’„›çOíQg1 8ìYgÄd¨ç(M-D´Z-ªM¾:J‹iSo¤±Ç>L¹P­Ì²Òßð•„xyÝúV6[ és”§ðUña+FJ~•­ÝwP·áO·!¨©Úâ989Ï­&4ä—”—Õ!K*îœ}hRîâ$8ÜæÓæ¤­¾ûRD¸—Æ>óXi%l`¥@žx=©4§æ2ÄÆš~jpµ‘ý+¥÷KÍ+ÀRÈi~* AÚ’rµ4àPWßo4«¥^“©§lahŽRØO¦ §•Êø-E §hN䟸® %Ùn¸ÈËLGñ@XJRx'¾>ôòèÓ­WÜÙwß qåŒ<Ëx(Ço_§íX }íÀûqîQÝJ“µ*[Aµ…yŒÓ7wŽSùŸ61ê?ZôÜ£µ_–ÑIPÂNãåÍguL&VœšxaÄ¡õáR; úÓªÓñ<—ÒGŠ[rUºã·ti¯c+JÆvÔUJߟ¾Ëð.E–IRc$:S–ÁÈâ¬íO„9…%C‘Z¢´ë2VË¿œÂøm@çzÓN<Õ ‡.Ž?)”‚¶TU·î)Ù§®–Ðɉ àáJ7/Ä+¿z–ä/“ùj;ç]ÞZV•…‘ ;Ö–ÆT å´ö®àôU7á2ÊPÎO•eNm?µPÖàFÒµ•Ÿ:QWÊi;NñoÆsñQWÊk,_mÑ^äzQFÍ¥Ü[*ЇJS©ÝްœU`곓¿˜©;³¸ly}*ÄõâÕp»À¶G·­´-+ueNg ÀIÏô¨WITµ›}Ù-.La´<ã®üL}#6%-¦ÖTÚT¯âõ•.B\¸¥Í­ Á^ÔüªŠs®ÏïqdJS™ea<(¥!Æf;Vòâð®ßO­zc.«BJn¯?!¯¤çjãÖˆêâºRI("qåô¬¼7[ЉÂdŒ’>µ¶#»·”‡“ÆÑÁþ¹ xéG:9grV¦T¡´ÆsJºšíóyŽÛˆÜˆ«Ê±ÜúTzõÙØrÓ)Ú„’œ•P+¯N>Àu+SĨ²²IRÒ;æ³qû6”,lØ\BŽÓ-:áÊ’àÎãJŸCÛŸÓr%¸ÔvUÂÁäñëQàyK»3"<¶›`(ü{œWkÔÛÝ™ˆíºÿá…Е½³rHóõ®v]µ¶¾€4â:¡jS« E+qhZ†BŠRpž}}G¥:tìÉ_øã¬…â܈ËzPK!ÏÌ;6ªJ’†8õØaV»º=Ý ˜Ñ–•Ô„žxŽ~¦œ:Ê e¤“­4­¹ Öw™ ¤¥É!$ñ߃ÇÛëR÷HÛ¯¬2 |,:ŸÒV§6öÏ•0ÙdÇy{W·²Æê“l:îÓ"‡ª›T%6Þï ”sè®ø¤‹ÈÒJO¿Z^}Ýà”! øI'š’ÙÕZi$¶Ä&Ÿˆã¢[nx¥.¨‘ž@õ®iòÕ>r„–|FÒFF•:õÝ™Ÿq†¿¥$vQïH}á¦Ö|;yäàÖ¥F1bÄZ)²âN壎x⺮ ƒ>°å!²´'j8 ‚žÇõ¥&ìP·¦BÜ@VÝåЬzŠÑo†Ï‹îʃ!È™ø¤!?/™×úSa Âe~#±D­Ä$é8Qý©U¹¯·":[y%{¾%ÀÏ$šëµ |¥<ÜG‡\–Ê7àà÷úÿ¥&HÒ·Q%×#¸PãÈ;£îÞ‚=ò§AzÇz¸T†^q´$,£+Î{RµŠ×§WoTYelÌψ·‹¸ÞO­34”‹„…ˆñÃÉIh!ÒR“Ï™¶ááh˜bÞ#‹pa¥'*ôÁêXlö“¦#IŽmN)!`þ[œcééûÖƒK¿d1gÛÝSÈZ’Û‰|ä´I÷'­ph5ÏeÔªD…Ø}iÝvœ«¤”Ûàä2Û‰SïÂ~QÿûbîV6éÓŽƒ"ÒÓá Vv”ŸP<Åa{¾9sŽ"5â4ÚRT¶Ûkæ*P—t€Ì0©Qi)Á `ŸóÍ&?*߆C2Ümik*ÛŽ1üÃíYŠLü!pÚeé,¼–;}3ØqK™ØC.2˜© .©X Xí­i]Á¹òzápdÁi!hðÁçî(Ôèo–c„©¸yDÿò«ØV‹Ý­#ÛÜœ–›¾ÀwÅA ¤?´`zðiؤ–æÜnµFS‹%MxQõIçëYÇÌg›~1Rmam¨À÷ö¶Î|ÜÄÆ[S¯´PðËwõ©nû;Ïß.$>ŸËÞ0£Ž ­¦bÄj*;(€3Mþ§¸”õBË:ÔÕÙPšA_†Œ£ÆJ°9òN2sô§”ù ¹CTY)LIQ‰JPùÀYûо´Èl­Ä!¤¤¨ ÓÉHÇjàméI)kÃü²wzsÉnÛ’ä† Ð¬„ýü¿ZIŠä”¼YC{ZI YœúcµnTj7¶ÜRÔ§ Rp|ùÛO ¥•ضWÇÔQ¹X=…'és’¤9 m¸¼ñOŸ|ý«®u“ÜoÎ1e×–|@d!Yºô±äWµˉºFiו‡XÊR{ã¾ÿÞ²^£ÕM=î«÷%•#r\i>”¿lÒ¯!äN½É1¸4UÀ­WÀ“uB㥔„§Q#‘Ågq\Öfnwt!Ë„—¶-%[Pva_jqÚ Ä`nÕa;Ï$ÓiZ‰Èî6Ôho>»€i?/§5Óo¹^ É·¿s %kÆÕ¥K(XºEm2m)ÂÊÓ¸“å\i³!íJÜŒ«!CiÝI²nW®X—m¶¥nQ#‘é÷¯gß'3'ß™†·i>*v$ç9ôûSTwë÷.U¯IÚ™S…÷•¸î ìÉsæ+Ÿ§Vv¬¶y[Kjñ a¯äÊ»uk‘Z•¦õ$v_K“œ }æÇ¾2Ÿ?ý«šç;ðWóã8y`oq¿JûÓëIÚŠé!r–½£;@Qÿ*M€ä¤¸¤ÊuÖÛR÷#±*TŸc¦½éç.:­á!y=€¤¨ó¦Û_} ½ƒ³œzâ·=ë$”¦Õ¶¯ÉQ ÈõÁ1{–Ù.îl(§÷¥È6»MÓãm:ÝÉWˆr•þUÊþ‹˜ÄÂö¢E)Wä©­éçÌ÷ÍMÁÃk\™/­ˆiÉÆånå'ŸÞœÍÙmòP ö¢NTÛô¤˜–‹¥ŽSsY–ÄÖRB\ §jÔ8ÏûÖù÷ˆ³›AŒð$(sÏíÚ—¿ARlwœþ¦$dGy üÿæõ¤ ý­·ÝU¶pñ³·bÕ”ãÏÞ®Ç$8òÔ‡ÇÄIÇqÚ•Ûoh'ÞÒ”yÁÁ ŠÆTÉi¥)È«*Û‡?åŠA¿"KrÔ—C H‹¸§°ä²›<]KÖPÊBÎÒ °AÿjGÖŒ=aêBnYn¿î¤û“Í%YeIìÞ;JÎ× ¥Èÿ¤eJ)ïö&–5òÏr‰ ¶]›""²‡"Ê-©¢FÄ|yTûBµ€&Je ir,„¥Mp0<‰<Öµ±§ßeMîe¥0ÒN ÊåÇý%õÑ"ÏmN§´j²Äˆéi¸—¹aµ6”œ©IìTxïšOÓw+¥Ò »¢,¥DRÒR¯ˆ$çrFI)­ÉÐSÉ:<'ÅJIËjÚSõÏýö®8×EF }Æ‘°mñóÎß,ãƒI©’Ô©møŠÃ[AhN⺠;–Üv6%jÝùçý«:WKšŸ)Sjm/©JøSòü?ZÜæ©KI÷uÆB´á<÷¤k¾¾\\Då>#K¾‚•4Ì{Z`\ot†Éxƒ%*X§HAe™_‰½pvÛ9¦>¡]Í‹åúåhŒ—å<\|rVëEJÚ8?§øü™/@þêÄ»œW+Œv`€UŽÜý)‡`7h F‘™eUp.„º¦æ'8*BÂŽsö¤û†%<•DSS6ìl¡À£Â€ŒÖ†®qåºâR@;¶¨“Ïé[´Ú[EÖᵯ"Ö–›Z0r¥ ç?~ÿJÆÜÌ™7z‹îì%C'ßÓÖ Â}¶râ‰ÈÂHÆT7ã?JÂÑo¹Ê˜”ÈCmŒŒ'±@iy¶cÈ„¤-÷)* ¤”â¶i˜²›¸¸ËŽ¥æ¶…4öàAú*›éHˆ•àÎ\y‰ZJˆ s”žxÅ(¾Ìe0’ç†ZÎ}FG§Ö³½7¶b›r:)8P çŠácO<ÃkT7ã*>"Y”¯éšys5Fm„¥@•UœäcÐw­5-ª5Õq^yiyH G?QŸ¥-¦-͸ÄíwÄ!E®F;ƒô¦Ô«LySœœ£á:û©_¼‚8XàŒy¡«4‡³ó£.3KŽ•¼·ÇÀ”v$zš!ÆSχÜeŒ@ÈúgιáAW¸&Nît:T­Ø+ôÆ8¥§žn<œqÆ}MaIÂAãð6„ã°®ˆÑ–¤ígœyý+ÒVµ€œ f”#MKHŽë€ä dP/éÖVÌ «V㸚Q=«†Æ¥˜g~3¸ö®ãڲ痶œ¦Š÷mlÜ×ó"²ÔHo¸„¹+ÄKIQÁY ު沶JwsÞ›RT¬œŸ3éR絘q‹MŠécñ¦CuÕÄðÀ!OhWÓƒûÒ\¤mf _-ª¶ÜP†ÃÈt%* RGÆ>æ»aøÍ³;šBÖ)Å,˜é)Dæ\+Kkù^NyIô4àµÂÓSì‰Öûµà?3 •¶29æ•ïZSNÛä¸Û—¦ÞQ”¡´å]ÿ‹©‰w(v‰P-÷™Ë`¤ƒŒg¸×{ô„ç[Ò–éî0™“Ò„§ápµœCXéØ:Eôºâµ8w$¥Æv¤úcëô®{”*âãë"cá (`Ÿ÷¬mºZõ/Äwðù/¶U€K[BHÏ š××±²ûømÓQ-ذ”ÄBÂZKƒæQJ@'Yï]zDªòëÎÛîZn2HWŽÚ²ÿ3^Lƒs„βËQø@)d¯`ì~!ÞÖ»Œ{N”z+P}ÜîK«p«âp@¸äT¶ë¢C.åenÓ5Û[‰hNAñ ø 3ÜyT‘Ó˜êMªd»£(U¡ Ë)À*òÀÿjMÓ3­7{·â·æ]\Âr¦ò(‡ØVÍG¬~xé+•QÔrR Á#Èý«7w¥†ýúöå¾ðÜt=¶!Øë,íÿžR¥+Ûµ£6˲äÅ™î­>‚â8ÜàŠŒu ÓHé ôȺªç*ípNÕ-ˆÄ`(c‚¯#MÿüYÑî\ÞSš^G¹¡DF>HÏí[ðßÒoKc¡uu¯X_Ÿ´ê-³"H`”ûIÉYàÏÜšeXí³´Îá¢.°£Hz6ëŒ îµ8pŸ\åUãLuÞ»üYC¶çY’—ÅyI 9Á^Ù«Dñ´ëɶÍ]cy¶õ l5pŠ©£)¤§ R{zýEc,ßo‹:ïqiÅ·âî•, ¥CÕ$ŠhÜ} -R'²›fš1^Ô:ç‹•+ŒÓ=ª¶ ©JÎån>u½ V9à÷êœ8Ç?:ú?Óo®šbͬ¯tâ*±4r£È‡!´ìñУÇZ€}.—9:¦Uµë£¤CÜÛn/(ÊO?J°Ñ¤Ï}¯-¢çŸwií…c4»§ ¦¢±Õ†Þ_)ÏãÖµGeäÊ †ÕÀ,Vwi-%ÂñøžŠ¤¸Ò7•}¼ëžöѸ¨ uTÄ–´xq½¹YÜŽEn¹Ûá®+r ·TÊY^O#¾j}H‰ïxaÉVæ|tÁZÕ0¤¥HB¸Úù¹ïŸJt{’]eû9O¾Ûa´?¼¡xÉÜFÅdw?÷ÅYvÎ9scÆC¬¯nÕ¹·vìzãÓÖ·Y­¶µ`¹vk`;¾l~´*DHÛÕ)…) !H|ëËP‚©‡™t´”d©|ñåȪ‘z…oBâXã–¡´¼q´që\6‰NÇ».l–ÔúÊ ;Î7(y PˆˆïI Ûc¶´>jïbJ•)æ°ïŠÎâ“/R®:!¥´#´GÄ”¨çÚU†zïP 2•²–$åN!\«ŽÆž2Ä(\n+‹~BÆ<@x% -ÉnJœRÔ·²®I óV]Auó­4MòVÒ0ü 1ˆL‰‹%Xì‘éP„Ž«k[´IBv©”ÉÝâ6”‚ •èí]^Ôíº®¹ê §ûâˆý‡úTd•~,ñåžq^Þ><|gN7+³þÏÕímõlËÔ›‚c…§Õ‘·Ï5pt/P#jÍø¤©.º’Û69JñþUóú:R§ ‰)籫Oì§B´üÕAœX_ŠœïJ‡ŸØÖy°šÚánÖ¨oiXö+¢^qæÔ…ÅPNâ•ðãÓœþô“® O«àµi»95„$*éÒŸÊÏÊâ{wÉÈà S¦Åd¶&ëGŠòå4½ÃjŽ3Žr;SuãÔA9¸ˆ%§ýÍ[•ð;°pæÁǕ䗷GU÷Æ‘ ‰HÈ~ôÃSþ­·'YÈÏÒ¶C‘6<7PoÞPâÐZˆJÀ?Â1éŠP·=cn.“ Œ”nÖž•©YŠÂÒ¨éeµñƒÊq÷â¹î3¶¬:ÚÒ¤¸¤«89¢åUÅÒÚ”“ò§þõ¶ßi·£å4À| ﶨö-Éø;žm%àQÂSÛ5£OÛÔ©m‚·¥)!œ×N ·L»­­Kbu¹R\h7r‚ÀZR“Ê~¸¯ÝÜ–åD†™Öµ%ÔG˜Âߊ­ø#øûã½nƒãB”O‚¢6=ÙíÍ0ì²=÷]¿e…j“mK‘¼¸SqÈÌíNÞ,6¶máHœ%í¹R}9¥š#܆”¶ÉA![÷œm­våÇm>*ü+ôó®«t¤9ƾ¸J% y'׊é7‡âÇŒÖÊ ‘ÀûÔVbæÓéh6âÖâÒw¨/ojÜ™ÌÆc )@'’¥dŸÞ¸¤0ë1šJZNÁ@ÀÉõ¤ë“WG’¤6†Ùo!%2ÿO*š ñ®O¼ãÌžÔ›©mLÜ {·‡á\.*@;v;äyšÙ NCKOït¢µc$RŠáÇœÚ[D´§ÃVRRîqô#¶(ѧËp}§ËÌㄟ‰àÅ+²ôé0•6c‰·Â@ñS£áJ<ùíšèÕ–æÑ;ò÷ËÈŒ T N2¬ÕSý«uñî »j\ÇZ¶Ç@›l”#aÎs]0ÇÎé›têh 3òü8ö{Œ¸Ì:[mÁ))C yà ×~”ën’vIKQî–×PÁqAäòªžÙüÐAsœdÒÌxZTžÄŠô^tçç_K4 å\tóR½á¹)YÊo²…/žÕ{,3-ž“C²BœQl“Ý5*«±¯SVƯ¶º(¢£H‹ÚVÞä…iY­Ýg,9 ©hPNTŸªp?z¬š‚%÷ðíOÚÀ-ãàã*h¶ áHäyÔÅÖdMS¯ÃÒ}à8²•ã„ãiÉýªCV[F®—kœ¸Ðàj¦D7Y*Z0]IQå+'ˆ'êl/â™r2Ϩ®ö¶â7ýñ’üI76´ƒÈR É'¸Éÿ¾ÕÇžúopu¶Õ*ãoµëkd¨qîzy´Èy§w%/²HÊwJMµÞ›}p&Bd¿k–éYm´îØT>% ü%9?zqÛcN¼;2¡’#ùE1Ñ(¥Y I<ùvW¡úÖ§ô}Ò" Ýíz¦ÖDcîꊰ#°úGñÂýOjòK5ªêÂ` LtÚ¢´ó1œRRÇÉð÷È•xå¦ö⟖ݶ3C(Bˆ=ýxïúÒuù:’Ã<]5,3&ÆêO†üEî÷%gÜ)nÖýÅûdg­÷_{K›‹ŽŒ|Ч½ddɵ»••ÖÝlŒ¸À¥I÷‹L×§ªM²:[‚—œ‚J±Œ~¹®×áÜž’Ü‹­É;\øÛWdá9â²Ñ÷ ’¤\`¸à‰!L8?Ãq'âÚsAÄíµvøþðúDmØØ}p;ÓŠÜ©J…‹uµn¸° œYïúÖqQåâ\/*Jxn;JÞ‘æGnq]iÔSå¦RÓ›Ôà<+íƒåQŸ¶µW™YoÊð–¨2ÔÖäðv,gê tâë8™wõ´«•Œd à÷¡¿‰G³Š×¸•ŸÚº"6㯥(;IÀÎ3ç^÷›ö5ÒOÝïr®ž"Ôd‚G%Gœd}êËÞl¦¦Ä ¿MòÐP ÝúÔwìãÞÓý/bé-è¨jbS)Ô¸‚…¤(á=ûÕ,¸ÿ»ÈMÙÒû‘¸J@ ùä×ÎåËy×lgN6oN! ÆTQßpÊy8?_¥m¶ÙäH½Ëj2ZBŽó­Iu-)ÞÈl#qOb·^•be–ŠÁÜ„£ã¿j Uí‘ðÞ¹ÝÜW†–ßJsÜ©9ýj qÀ¢§7«ÓÒ¬¯·e­¶õf™¸+a\‹HmjêBÔž>¸#½V×£'-«>¼Œ×Ñâ»Â8eíƒdøÈÂŽT|Å]_f»–žž52GåªZŠ‘Àª‰£-.]µ,m¶§TëÈHJ98Ï5ô~Áe´³fƒ KQⲤ“ÛŽs\¾FZ’5Çéë´X³…¤¬¨§rÕߊà×ûmÝJ°Îqû„g[Z’OÂòHRIŽFE9ÜcMHŽRp6éÂ0p<Á¦ßRâ7xVšL[”xScËPB ú‹dc¡¯$öë[¤Åm°Äé,ÇT–ßåå+ƒÏí[Så©qÂÞ}* (@®4ì‹æŸ›ï¦+>’Ä•¸%I<í¤Ž+5vŽ.(0ßÃÐ_K.®SxJ•ŽÃô Ù#snŸ èoÈùæ³K­3µKcüL¥¼÷4´ô»ºŸðbÈ€µ ƒnp 7JQ*æ\}Å‰í¼¦ÉO8N9 R¦í²ãì|ÈyJ#°YVOs–Yvj‘ƒƒÉç½aÙn2ˆN<û‘Bö‡’ËÎ{s@r¾0i˜rp“’P7¾ KB HwP°êc¶»ü5¯'üb¸ ‘¨Úmùu 7:8 >ŸZpN¼¸å±N6ÛVð· {ä«j€Éw¤ës0Úœ•©Ì©ÄÔ¼óc’Fi6öµʃÖyî­°)´<’ÁÊju.p¥n>Ui=¹¬m°åŠîÊÐê’å`¤èj¯•±D¯w#<ú7xG §a¶ÜNÅc÷&­Ï²µ ifûªQ*CêÁ#â)ÇúÕK…¹ËƒL6—2UòƒÞ¯ïH-W ^†³Ûü6Zñ»°o@PÏúÖ>EÖ:k ÚA~3íÉŽÚ¼0€€·7|‰ÈóÜ•zt-§?QSÒŒ¡8Q¤gJS!Sá¹lä rñ2÷*…x) I žþ`Ž+cö‹mæ"$:¡oŒ‹³ÁR¼•ÏÖ§þÇŸ‚‚”Hµ8Å•»”⹕!/>ˆ\ç ã%_jín×*a®-¿4¢’VxóǬÝYTPË2cÆt“»ÁNŒúù¿ÚbÂíϤ2‘i— Ô¼¸$pOJ¥`-%9NRrÓšú#¨lÑgiKÜ7&®Z× Æþ3ØmìqÞ¾wÏA!l)d)¥§5êø÷«¹'m[Q%-‚žØ52û2éãrÔ.Ì(P ¶H>YíéPó%Jå\¡ŒÕÄöKµGo@ɸ·)¦åÈp®§€cú×NlµŠa7R4k·àl+i?|·WtëTŒI…-Qo•©±Ê‡fÔ§›—înÊŠ—ŽO†Ò¾aYÜaÈj3²Ûó.%›e* ÜZðíØ•©m1ub£ÃLsøã fàà)Æ?{}kšÃt†ìº°“)§G;Nžùò®¸EpïÌ:ì”·-Ý®¸²AN<ð0 x›šy—&Õ|vȧÜÙ>:BKN(òqœ¨Œöýª£ºÈóLE÷w¢ï‚rJ\HÞ„Ÿ¶Ç Þ€j#‡i~ågU¦÷eK×g_rPÂ<¢G¼ÆÅÇ|U—®g[l¯ym%^¸Ång¨b›èþŸU¶ÛR'6üÞê™,ºP¢Ù$©Nj-Öö;¶†êmŸOÇuR4Ýãs0£ºê¶±„ò2xϘgJ•¬´W[Úƒ}†$4Ë¡æ”R¦Ö;)$v4Ç’ý®] I.×fI3–î p)áœsÛ=«F¨±L^šrâëí¹rŽ«{C–{äÔñD騶ÉVá<Ô¢¥:§Žõ’æ<Š×mÐÚfÙ6,èÖð©Q[aÇ+)Iûš×œMí_u|;Æ’¼Ä¹êçK–i¶ä²×»·„%åR±üÕ¶ÎoU²hz3ÖyVZzB?0xI¾Y«ªôíŸSÚWk½CD¸« í9øT;)$v"£Øý!ü=—aÇ”‰ð½à» 2\R\Š8J‡ Ï­Yœ³µ7’P»,@¥”ÚaP£±)$º¥l J{ã$c°4¹kéMêq˜7ëpm¤$ûÂ!ðP9>x#½=í:ÞÄxiºÉ“xv#ÅöU$€„,œä! þ¹§:üý3X¹ÿ ¾m{aiÅéαÏmé)’©iD’âQ°Ãž*ãwž3Vsþ!0–ލ[¥ãj·'BGúUc nàñ_GŠïã—´‘ìçf·ÞzŸmbæó-Cm~+Š}{„óßô«§©è×yÈŽÊ[ޝwˆëxðžA#fÒ9Ýš¬ÄH³Ìê’ì׫|I±çFZH;T>yò«ŸeéU†Ó˜1¥ÜŒdÑr l­Àè?jó|Œ§—n˜z3a.¨¸~+vmùéŠô?J-¡÷{{sŒSç¦zeÅéè· D&[ì {³í'd`<’>üäóOUB†á r+ Víù(îõ®šó\–×$ûlð\VÝŠ¬e¢>úTQ©:m:Ϩ޼h¶¼6$4ü`þÐtƒÇj˜«ßéRebJ…ï¶}Qk…"D«{—½×û¤vâéîé÷®Ûn›ÔW 2#ÖÕDŸ%²–·¨#ÀÈÀ;‡z—+Ú¾Kä†õ—N®¨I™¥\x!çšrᄃ℣j”‚O€q÷¦î‡åúØÌ‹Jσ+SEÕãD”­>D`аŠíÛéޛ󴆟“tOqK30P·˜%¥8“佸ÝúÕ™õÙ)•¦mm¾ ,¿!Ç’¹n4‚‰'ãÂûH~×ZjÊÏAoŽF·Çam­·‚€> {ÔÑi·ÄµÀn ÄvÆ„ö{P[\ºô/SGg;ÑÅyí"®9~Q-ÛåòFãÀÉÇõ§„†ÜýIn‰!;›rSm¯HYRG¡ÆriWJMT+ô)C‚ÛÉXÏ‘¾ôæúv.ýÉÿ„ErtW`4–"¾îæÛu @ ° þ╯mÛ嵨Y1ÀÊÛŽœãœö•4åÁ»¶Ÿ·ÜÙPR%FmàGüÉ”+äîºùÓt»Â|Ip§8Ck3Nœ¡iÆ8ô4«a´¦Ø$«Æ[Ë}Ò¼¨çhòHú T¢¦ÓtQEALþ©Ã”ö–pAi@ ®R™HñcæPã’8úâžU—DºF¨ÔVdÚ#ºëmÉak,)[NòQæ@íå[¤j8¯ÚÍá3c)–шÑÛu;9Ç ïŸ¦)øÄl>·ÙŠËn¸w)i@Ÿ3ZX³ÚXY[6Èm¨¨¯)a î>}»ÕÜoÈÛ³[.ófµ=æZµÇr?æ6R—.zïíŒ})/QÁ—a¼ZßæLD·–‡Ôê–Ûã!XàJ‘@ÅbëmºŽ!+O¢†EM§’œ{{ÈeIÑÍ7…¡LÈQZOÌŸ?Þª¹{Ä;\É8æ®üAìçð5zm-´·££*Ú¡þF©®ÅnÊÒ;ùWÐàý#ž^Òϳ#¿ñ.<™À†œ‚„ç$ö«§áj\imÆ}¤¥Ž_uÕcÃO®{U“Ø=¸r:‰v·ÌއÐôa.' aCµ]YvÇ!¤²õ®2ÛHÚQÆ>£Ïõ¯?È¿›xÝCpv­²7Û…8ó>C[l6õêËÔ;š²Ý¦ÖèrZ”êxÈ>c•ÜÐÙfM½›£‘-O<LxíìRš³òštZmñ-Vö Ad5¡„$â¸m«‘&ݤ-P/SîQüT‰Ï¦CÌnü¿ oÖ¶jm3o¼@’ߺÇL—°|Røœsœ g½.ÑSuÔ~õ²ñm‡9 ÁuJ@>츛Nä%<“œç÷® ˜˜Ã3]œÿÄëL'óêU’ýêN¯iµò4رÜ\{ÄC®A-•”¸Û¿UsýE6 N¸B¾ÌÓ·H¯{Ë)/·"XiÖÔ~I$ƒþÕ(ÓQiXWyhœ‰ MINçã«ĤüŠˆ«)2 Z¬±b[×pº:VÛeO+ÅÊ‹c9Ç×Ê·é‹,kÃ’.·ÎûÊ]Z#ÊR|5­¥AÛô¥ìÚeQz”«“D‚ÛjH@Gÿn3ÿµ/€ Ú¦ÖåüVïn¸1£ô¦ÖÛ-í Ïøyäü<äùÕ(siVÓû×Ð_m+I¸ôiÙ ª¶ÜíØƒþb¨H)*ïô¯wÇ¿ƒ–G?F4ÕÃTëèˆimo8­ÛV¬8'ʯý®Ó:Ê›k+„ôh‹(YuM¯n9úyTÛÙ"Dx]i³)匭J@ç‘Å}®?'/ËMcu ftòä© Ës,åÛNrQXúñûVÛF™÷uCvã!¹oÂÿä7°Ÿ¿?Òœ”WŸkåEx@P €Aî {EDkK % ÊYl6; $`~”ƒw‚Ο±Ü§i»#*œ¦Êƒl¶œ?ëö§]¡äK5ÒK“÷¦ã6B–ú•3³#(`w”á‰>>ŠñAˆê•à2â@/+ËhúùSæu¦×9ô¿2ß÷R R·@,ÒC:&À‰ yÖ‘á§c)yÒ¤²ŸD*Õ²µ23®ÚŠ5™eJ}m¤³nÛŠ°SJÚ«N¨zƛħS Õ»¾DD%*JYú{AÒ¶WÏbÞ×¼)!;•ñmLö¥…!+AmI A Žô¦ár5¡³:RC¡Ä©§#¨¤íùuó7XÅuMqŽ­Ãl•§*ì£_R˜°YØ–™LÁm¥;Aãlâ¾{{TißÀzÍyd¿xoƒÙ\×£ã_ÊÆ3»ˆ±~j6¸AíW/Ù®Dúe3«J¼5(#'Wœò|‡j§,ÇlÚFÔòAuo=•¬u~ƒvw‰݃4.Tt¡ CÍ©#OpržõÛŸõL=¥Û«C UÅ´™²Rv´Æãö±Ô¯Ü™¼[œ´ÚÞ¹9<íóK)Dg"–Üéü;|¸Ó4èn3­!Ä-/8²……ŽødyqJÚOGÙ´ó)Th­.n‰)HÖO~|«Å¸éäB“¡d8ü ¼9 E»0á[¥iøA"°2@®ø=?°©åÖßDÕ<$-ÔÌ‚~)ß^Ö|«;kŽËqØC, !´ %#ÈS[WénKzUžb Ê‘„È FPàí»Ô(Æ´RTÚ:cÔvø“-N0f§xLYAÀ°ã•Î~Õ¶º?>wÄhQ‰ZýÕõ…<¬qŸ¥HSmy#ÍI¢],ßìßKŒ ©ø®(¸Ó=pÆq»¢^fDVÝd=!Å,6³ü8ò©F¸ µûÑ•îÃǺÃ`÷õ«äLŒí—žÕ÷G—\·¹¤¨©mNv=ǧe¤mH¸9Þ™Ðb¾´»ŒWÑcÝ`OÒÝX2í ÷W™Gt+ú‘úTÏT£Øö¸ºÒåeqk)›”§ËrìþÙ«¯_;›ëbŠ(®@¢Š( (¢€¢Š( (¢‚ öã‚Ü®…É¡•ŚˈãÔ”Ÿó¯Ÿ yÕ”•wä×Ðn ÍÄèƒí¬òüÖP‘žç$×Ï€”©AJùsȯÆýÉ6{Þÿë=©9ðM Š¡Ø ãë_Cëå>Œ¸*Ù¨í÷ iøÒâ Ž6¬ùWÕ dz¶Å•Çç2‡8ú€kÉŸ”«=:(¢Šó(¢Š( (¢€¢Š( (¢«ÕÛRo}2ÔVÕ$ªN™)Ü?¨¯˜KBYqÍË$¥D`WÕmMΛ¹Œgû›¼zü¾X\’q ­]‡ë×ñ¯U2(h;ã–UîÈãHC‰Ïsƒ_P4ÝћՂ Ù‚ rØC£YÞ¾UEVÕ' ­@þ•ôÙø»×G ¡åeØn-ƒöŠ¿'¥1KôQExÔQEQ@QEQ@U1öù‚Ú5}¢kMáçb”­^ *çU7öøt«UÙY ùbFxvàýá}+;-ãº@^20ªž=Œ5A´uI»[ªW‡tmQˆÛÝCâIýÅ@Œ!N8O!BŸ]'½;b×–‹ƒJÖ_IOß5ìÎožßJh¬ZPq´¬vPVUóZQEEPQEEP`ª>¿ÒƒÞ½4WÏÏj;Z¬ýg½¡°‰ L€sßpÏoÞ£xÏ(,É?çRÿ¶Jœ_[eøiˆŒ‚O—°€´Ü+ȃÁ¯£…ü#Úí{ÜÓW[s´yûTÕP7±bÐW$„‘¶`ý~žkÃÉûV˜géEVgZùOÒ·Ö§C5ó²*§†29ò¯¦~Êv÷m½°´ð>#­—NG©©ò¯â¸%j(¢¼ ðö¬pAÏ•dkÌóE@¯h¢ˆ+Ï: À¯­ÜóA•QAòó¯R÷X5+©â¸;þêe4”¬%dùñO_hks¶Þ²êXJoj„çç<‘þtÃJV>eà§µ}\XÅö±Ä^"ºÉæ 0ñø{caïWÞ©‡°•ojk­ó*-Gá‚{nQíûf®}x~EüÚž…Q\QEQ@QEQAWâ<·£ìâCÒÖ²3ŽBxªT€è# ç„â®/üCÚR­:ic8¹åÆqTå p3ßúôŒåì¥hoĘÓh_æÀZú«¦R¶ôݱ·˜m}ö ×Íž„éÙ:“ªZ[ok’¥…p„Ê'@ké°À…qùW¹=¢Š+Ê¢Š( (¢ŠŠ( (¢Šd÷»é Ëüþ\ÕÇѳ_*f´W5Å8ò¥‚;s_Vu[õ¥î±±Ÿ¨ÇÝWË+«E©òR°¤¬ŒyÍzþ/ÚdÑ!D§±##w}…κ³¹GdÄœ,¦©+ ì@)åxäUðö)µ®I×5Ä©’Ô qÝ)¿‘ú&)ÒŠ(¯ BŠ( (¢ŠŠ( (¢Š©‡·s‰N¹·…­ùAµ;xÁ>µsê£û}Û3ìDÿ†Z[JϨ9ÿZíÁûÂúU†BRV–ËHóíN}Þu%½¤'óW! ½é½Ê<àæ¥ÿf8/Pµe ¤GwÇw#øSÍ{sºÆÖgµõˆ…7–ÕÊ’„¤ýÀ­´Q_1¡EPQEEPQE$d×¢€0kÚ )í’Ú£õŽC‹IÚìvˆúñQ I NyûùTõíÉMuÝ1(áøƒžüƒŠ€eIs)!*Æqë_C‹ôŒßk¥ìR¤«B]JsŸ}ÿí©ò ob¶VßO.­>$ÀAÇ|$Tó^.OÞ´óŠ+Ê+*Ö¬ÌÖ¿>ôXÉkFwÞ¶P¬W8­C$Ùò­Êùk•¯óF±t7Ú³­mv­”fûŽ9¯Ojñ=èDíãn2ú¹HB”a\tíG)?ú«ç›jH<æ¾§ûAÙ“~èÆ¨· ŒºŒŽÊGÆ?ô×Ë%íIÛ´dW¿â߯Æ2-è›{×Yn´²ÚŠ¥IC@ù$õ¯«:^ÜÍšÃÔÂJ[ŠÂHÏ ªìQ§ó¬Qä)ì[Щ+^3‚ýM}m<æ¸ü¬¿)ÆtÛEW™9šõ8Àâóœ ÍéQDxFx¯h¢€¯n+Ú(>|{rY«­Ò¦´É¸FjFò|ñ´ÿTšƒ¢–ÃxZ‚”¢5kâ%oJofæRp¸Îµ<Ò¬ÿþÕU­qýêthí¤e儌ùf¾— Þ›íôØ»M›H›šãe.ÜŸSÜŽvþµ8RO-Ù´-’ØÚ@LxM'×h'úš^¯ŸÞV´(¢ŠÈ(¢ŠŠ( (¢ŠŠ( ­ñ†—zyg—·+jj’ )ªBÒ»cœ>õ|=¼HÿÂ(àäfrp@Ï•Pö&ÞùP:÷üÑœ–ÛØ'G—.W-c (¡†ýÞ9?Ì®Toó«Q¿³^›kLôzÇ-ívK"KÇÕKäLT‘^N\¼²µ¡EW0QEQ@QEQA®BB㸂2‚õâ¾VkîºÂñIJKS]ÿÖk깯—ýo·¢V5$t úƒ¸ê¬ÿ­z¾-î¥ôE°²¹÷¦#4’]´¶‘_ûô㦶4i½ g²¥!&x6JV1ÀœÕÈö±„Ùn—÷I*Xe¥÷>§O ´Úœø–N2&¾‚{)[[·ôzÞ¤2[T…©Åg¹òÍzþEÖ,â–(¢Šð´(¢ŠŠ( (¢ŠŠ( (¢Š Áíßm*µØnéÀ-¸¶”}sƒUJÊ\’•'qòúÕÖöÔ·*_JD„`ª<¤«ž0U/Ò¬¸õæ+Oˆ²àþµîà¿‚eíôÙæÒ›WK­‰ÛµO£ÅPÆ;Ô‡I::0‡¥í±‚B|8ͤë´R©¯»­_o(£Tš×·+?JØ{W›y„¯SÀ¯hQ8kJWý+j¾#Zù Œ:cé¹ì+*ñ#ŠöŽuƒ„ŽÕâ|ˆïCŸJÉ?(£_D­iÞ´}æ7ÿÍ€ò?tù1Ÿ S¨Wp¢8û×׫ñÅŠyÿéœÿÒkäMÜ„ÝäÛÅW½{>'Û.ü:ìkKz–ú  ‡qæjà¤b ?a‹Gá½jal¥ËŒÇ^9óHÂGùŸoJáÍw_§´Q^ä<àžÕè£Î½ (¢ŠŠ( (¢¼VqÀÍdÿˆ4?AØ&mjsˆçÑHÿþj ôÚ ®æË'*rkIãþ±š»~ÝÐü~‰¦NßüµÅ•èúUPö[·®áÖÝ4ÎÎÀê‰ÂAQÿ*÷pßþ*—ÛéTvÃL6Ðà!!#ô²Š+¢Š( (¢ŠŠ( (¢ŠŠ( €=ºÛñ:<×Å·‘Ïèj•tîÒ»–²µÛÎפgýkæÎŸŠ§õXá½ÊT”$÷ ú‡fdG´C` ØB1öHêù7Òcé×EW‘EQAƒ§‚‘œ‘I3Q%ˆ‹y‰… @*C ý)]~¾X®[’vx@·ŽäöÎŽ˜útZÞrEº;îczЬ ×Mx„„¤%# ^ÑÌQEQAûQEL¾Ž^“·ymÌÏ©'I"ûÖ»µE;ä sÏ_»4éUý%;¿º«Z¦žÍÑPÿU¬ÑÔØÏŽ ȯW ü*ÙÜ}‚€Ü6›’€?¥n¯€0;WµåJ(¢ŠÚ<Ю íAíy^Ö;¹#´wò¡œd^01ŠØ‘ިŽ=¨9¯kç?J1ž¶ Ö€wyâ¶QkŽöÖiÉõŽàÿñ5òBþÎ5 ÆŠJÒqÿQ¯¬zÚKpô}âSªRPÔ'TJ{ü¦¾JO}&îü“—”¡“Ï:ö|_¶rôúЫcv®‘鈭|¿‡¶¿Õcqÿ:}§µSgßi[ÒîºwEÝì‘—Å7 Ýâ µ$ŽÞ•sÅyù0¸ÞÚ·pV Ïjμó®i'>u•PQEƒí^ÑE^Wµå=í’ÏÐ àÀ;\a]¿þફìHœõêxn‡ögiÙV›Û>O»ôðŠK0?Œôª uú˪áÍÓO­‹t%‚ßóžÇ×>|W³†oŽÄ¾ßVh¨ÓÙûPk›Æ›¨vôľ@}(QHH6¤$¤ŒóÎ1R]y,ÕÒŠ(¢ (¢ŠŠ( (¢ŠŠ( ­Þßü.œÚb'oNR€ú%?ûÔ_ìÚñ.àóÉJT˜6NÉ#"—¿ât|]4í­¢ Ç–3üÊÀÿÓU¯H?"=Ò7…ïkñ”øqÞ(ZÉòs^Þþ°¥ Óœ9ýkÓñ¿j—Òðû+EbFm,¶¦·¸VêÒ• äŸ1åÚ¥ZùÉÑùo7yB™¾Ý£]Ù[BÜÔ@J^YWÊ®x¯¢ìxž~/ø›Fï¾9®|Øxä­”QErQ@QEQ@QE'j|f®›¹æöû (Ò¾’˜zû)JÚ·¾¬ÿþ3V{5­rÌ+ã7D‚°‡CÁ=»+8¯¡]7ê~”Ö~ \ʼnÞ\S.¶RN;OcŠùÌâ·-ÇJ· ”~£Š´¾ÆË¿^.ˆCþàöF”W†üEðWsÇ•{9ñ—¦+_EW‰EQ@W›FíØçÍ{EEPQE^Õíxh]g =0¿mÆLEw5Gº;gLëëuâ`*a‡¿3g$Š»=yQo¥å%@w#&¾}Âe^ö6“€y½\xÕÊú} ÐÚÂϬ ».ЧJPJèÚFGâ¨sÙMËœ­"]Ç%%Ô¶Á( % J˜ëÏ”ÕÒQEVGŠí@úWŽ|´ çíEúeZÔ?ZÙ^+µ5#áïŠÚžÕ¤àrkcx##µÉb­ÙàⲯÍ€Wµ†p+!Ú‡í pM³£:žR”SýÉHI§Šùa¸(•+vìç¶kéW¶CÎ3Ð;ÑmX*Si?Qšù³>ñ!R²¥¨ cÌ×»âÏÆ³’È{ tíÍI¯µ“MºÎCˆ OÂãØø@ûwý*üTsìé¤Ñ}(´Z›H8Èú¶à©kçô©¼Ü¹ùå¶µ§„àf± ÝÛÖ²Wjç$£âÏrk·MЏ}k*2(¢¼Ï4ÑEW€ÐWÏoIjc£-GIÁ‘pm8õT:Ùâ{û~ê* IGp¯/ëW“þ ž–Û;íü@dgøj¯{4Yš½u—MÛßâ±ïI[©?ÊžÒ½Ü\{Kí|ú¤äišÛ¡OyÇî2$ËqÅ(­c8$ú)ÿEâ·wj(¢Š€¢Š( (¢€¢Š( (¬U»Ëµöç¹&OXU Dâ4&[ûg*ÿZÃØÇEBÕIMÎK%Ø–fÄ’•'ðþfšþÕW6ïn¾Êh$†––7 ç%)üóVþk§÷Ë€N]zz[Ýÿ*P¹5íÊøð§ÚÌÑEâQEPQEEPQEEPj”­‘_ò Ÿé_/5¤¡#ZÝÞ#r—-ÎÿõúƒpI\ HåM(Ú¾WêD)Z¦äv©2W¸ç¸×«ã{©—¥‘ö#Шº_dëÍ~M½[cg²> «Q§³%‘»Flm¥°‡$¶d92¯ý€©(W\¼²ªöŠ(®`¢Š( (¢€¢Š( (¢€¦?^¦*Gõ4„Œ‘c÷âŸûFcÿ5&T÷SœýÅkÚHwÆ‘òmÛÏÞ¾ˆ{9iˆúg¥V¤¶ÞœÐ–úˆÁ%|ú_=àíñËGr•ŽÕôÓ@îþÃX‚“´‹sŒvü´×§äÞ¤LKtQEyQEQ@QEQ@V9ɯMx cu埤º?ý15B,,ªDĶ”¨($(zçô­^émü:¬'ÝûñŠ£ú";IÔ°P´¨o”Þ3ÿP¯Wüi~—»¦–dX4=®Ú‘ñ!„•ŸU“N:Ó mˆÒ}Ÿò­Õå´¢Š( ÅÁ”׈È5‘Æ9£Å}=¯Ú½¯j#AÝœœVÖ°X3æ+j{Q¼¯OkÃ^ÑF€V~•°f¼ë*-ªûí˨‡Òæ´â·']d 4„Œ’”ç?ÔŠ¢–{4¦5Ln4¤ÉL¦Ò¤ÈVáÆ*ÝûyJKÝ$Tµ”:öäœñö¦·²_JæjÍZ½{¨˜Q¶ÆwÄŽã¹ëÏp+ÛÅ”Ãlëusl©»L4+ºc¶“ú$Wub”0ⲯUá¥iãµo­N½a±ƒ[kóÆRgDËØ¯1Í{^ôG´QEkÀ1^ÑAT½¹®r.·K‹d¶ÒU1n,÷ c¨ÿØ‚Ã*oV‘uKJ÷{{—Ž))÷4÷öŰÝo}VÓÑ,ì¼ä¹|6¶'̫֧>ƒôÞ'N4spÜdáÉ®ú¯ùGÐW§Îcůéö袊óŠ( (¢ŠŠ( (¢Š“µ<£N\f%[K1œX>„$Ò%êËc—­7pµ4ðer˜SAd|¹Í!<¤"Û¨tÛVxVץꋅÕo®F ÊO#÷«±ìá ]éßL¢YåãßßqR¥ü+V>Ð\]è­‡§á3ß)¹^þ¥Iø[ÿ ­JÕÛ—“Ë©èQEqQ@QEQ@QEQ@ËëuònéuòínVÙmGÚÒ¿”¨ã?Ö¨N­Ó(jõnLImΛ1–Ý{Ã;°âù)8óæ®ç´äkœÞ‘ωiŠô©¾ÊKm ­E;²xéL_gކ¦ÎóZ«VFO¾.,%sáz)_¥z8²˜c²¦ývÝh€â6-ˆm!Iô!#?Ö–<ëÚ+Î (¢€¢Š( (¢€¢Š( (¢€¨ÓÚ9KV€f"–‰.剋?ÂÊ•ñT—M>«i‰:¿G»eˆë ºãͬ-àHHžÞx­cua§)·¬·–Õ4å¶yS÷B› á ö$â¯v›e„2ÒBBBP‘ØÀ‡ ô£FØ´Zc¥´‰Õãâu~j&œ®Lüè(¢Šæ (¢€¢Š( (¢€¢Š(<=«VtPGýz·ËºhD[a¼¶Ü“>;Gj¶îI^Ϧ*Òý;wRõ’Za²Ü[=¥ðÚw')Çù’EXÝ}b•¨,HƒPˆúd´ò^#;v«’>¸®%§àé»B-ðÓ“’§]PøY䨟½tÇ?TªÒBžÉà}«:(®h(¢ŠŠ( (¢Š¯h¢€¢Š(<À¯h¢‘Ô~–é Oƒ7REû’RÐCÅ œáCÏšvZ-°m6Ö-¶èÍÇŠÂm ë¢®î´ (¢ +ÌJöŠŠ( (¢ŠŠ( (¢Š Èô¦¥;‡$4mÕ6 ‘ŸCÜVú( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠŠ( (¢ŠÿÙcancancan-3.0.1/logo/renuo.png000066400000000000000000000125231345516370100162260ustar00rootroot00000000000000‰PNG  IHDRú@3½j.bKGDÿÿÿ ½§“ pHYs,J,JwztMtIMEâ 3*>=àIDATxÚí{ØÓ½Ç?ï%7¹GBHØöÁᔺT«" F§Ä)ZN{zºõT[UǽIëÔ]]Z»¨KS*#ƒñ Âá¸E°mDÜ…+!É{9¬ßÈz×;³÷ì½ß÷Í~“õ}žý$ïÌš™5kÖw­ßúÝXXXXXXXXXXXôFd ùØcqÇ“ÊwÅ3K=»\ù…EoFƒIˆb6ý¿ؤ˜Í­ˆ;S¾0h¯àùmÑýÍ{ÇÜз˜Í-OY~ ¹Ôý-,6:¢GDÈò À?‡c€ùÀ=Ålnq©2…|#° ð])ßVÁó[€yÀ}Ålîã¤$SÈï  žî-fsË’È›)ä'° ð pW1›[e?·ÅÆŠæ˜cÛ×ßÒŽËò³¹•Ñ`ã€?»ÖP 2…üÙÅl®Ã !$߸Užá<àì’ï Ül¥ \dgu‹1ÄÝÓ 9ÀqÀ1¤j&ÕHrØ)áÜÏ ’›)äÇ$”?Þ 9Ài™B¾Á’Üb£'º·èSnPÂìß é‚z4Î Y÷7'Ô“„û 2õ-Ñ‹Ù\;ð°È(7#æÀZ`°ºÆz¼ <—p>UÌæÞJ(;°Æ8v‡¹,°°Ø(‰®™ žþ˜)¤ÿ#ð[àcöÄýW€#€‡€ò{#åo>p%pz1›K,Ž’_.N4ê¬ãnà'ÀÃÀó²ž?¹Dy ‹ ±æ5Y{ú+Ïä¸n¾újm/š÷@¿ Ÿß&÷^i qõ!"þgÅlî³Ræ51 —ò+ŠÙܪ Açx.¡ØžkQÑ#òšH"VRùJQÂTVÑsËÙ×{ˆ}EªÔܨ£X,þ˜úÁJô–ðUݢˈ>@ôÛÔ@tðí¢y¸x2"¹%¼EEkt‹nDkü5¡¬ QNIs€™ŽçnãxnCè_‰ô–èë­(k@ÚßZ¹&봯 ÀÇs›-Ù-¬è¾þD÷W€ñÚá'Pþ)¿Ë”“ÒÎrŸÁÄ+;\úA«ã-’Ðl› Ç0'ôƒË«8&¡§Èº_ ¯>n±$·°¢{/T5sÚ‚ÐN¾„tVð]ëxîÎú5%‰ÞœJÊÕ±’øõz†®Y±üyàGÀƒÆÚ}àú5‰ktÃ!¦å4SOh­ ~½ ¾:©|¯Ñ/ ýà”ï‹(Þ¶`‚vzpPèOTsÏ %Œ®n¯Šê°±fµ´R<ú×FQY"‰n':ð"pC1›[ZÂÉæ`”âëàêb6÷e~n!ºAöÓ€óµ%A psèÇUKÇs'¢€CQf½/¥À[¡,êJbÆuZÇs¡Â¤G£Ì‰R‡%À;¡|XIJ•Ód{mTÕuåê&}f°*«IïeÀ»¡¼_Ë€·nÌW»ÕéÀÖlœœ@rGÄØñZùÀÙX<ú§É`}Ë=ÒŒøz'qßF%<Ùå¾Ü_«Ã ©Ãàà¾Ð¾,Wyvà"YÞD’Ï]¡Ì®r9Õîxî¡À¡ÒæÀ[À…¡´T:+;ž; ˜,ï>Vúêm ] ,vð*ÇsŸ¾£êxî¨Ð§$ùG;È,ž„QZÿ;ž{eèU0³ŒCåˆ0xU– gûÊ7+eîýGù÷pàÇs¯ˆÈZ¦ýP‹>šäó0»†Ï°‡Ü3«¨`«JØIÀ¿»ËLžÆÔ}pœã¹wW„~Ð’–ì)ÕšQ7qØ$æØÀ ä1xÎXZ5i3|)±”ã¹÷¢¢÷,Crce&»Þñܳô'…µÿZ +$¿)¹ é}:6—'ïxî±)êÐ|aè2Zjl÷µt …þ2-ÉÏmv<÷÷@ø>0±‚w!’×Tà!ÇswLë,>x¿Î;ø³ÅlnA¹[ *F}ƒ6‚¯ŒùžýËt´qÀý¨¬>fÒP¾ôÇ9àtà¯B[g8ž{qÕo®qÕÄà>T‚»eöÃDà"Çs¿Ó”hÒö#ä½N•ÁÍÄÒÞïp£|ƒýP®Ð¦!{³©ŒC%_<8ÈÖ™2îKTœüyFõÁêNѸÿJÖz~ŸT¾—£Œ²rE‰Ž6øß˜¥Ù‹À)À“2û¶Èwo‘÷$à—À9Úì3ø©ã¹ï„~pUëÅFY*dŒÿ¯ÀåÀ›òü6)Û l*ƒ©ÌÜ8ÅñÜ×C?(Ö£F^kû¡¨Ü ÇÆ»EÎ-woÕÚªI$©p‚ñÍ·nu<÷û¡wf\Ql‚Aöc ©{8*WbÙµ’EÏäò¡ L3ŒbAè¯ëGël¢ÒZE(„~0£1Wîÿp­1£ìâxîð´zà™ÐÞ¯RÔþPÖµ:ÆKÛÔÓwÛå¤cð`%6𲿈ÒÊë˜àxî!qˆ%z;ްŽçŽn@9N4bø™ ÷Ú,f6¿ADÕÎTí¢Ó1ŽäM8L|À½Píl#å-t<÷YÖ™Kûï›`‰ÞƒØÉñÜÉ÷ÜKZV }T ‘‰%À´Ðî*Ñq¶0AÛÈL_ ¢6õˆˆ>LÈžVt_YóÛé“_Oþ‘ÔÔ¼ |ÚUƒ‰HÐÑ/b¸ã¹[˜A0ˆ—J¹” ºÒôÊëÞ]'6ôCbÖkåˆ^ óKB?¸¥L¹tܦªSkvÚF”;ª90¥Å†žÆ¬¯Ñ>‘nae-b{ ^€Gbx·6—"D¦o.fs-‘3M¹ SÈ÷-fskÌÍtå‡vóH¼º˜Í­.UŸDCu€¿„~ð”&Æ%•ïg|çf”ûhw úÀgæø[…rjêJ,‹ùEw-Ônª'#3…ü<àÒb6÷ž©ÅØý[(7ÉÁ™Bþàªb6÷^‰WFEÿ êF¢7-™Bþy`Z‰-ŸêíÚÑŠ2«ÝúÁª¸5}‚˜ÜSûÎõ·üîÔöæ,ßÔ̓kkÜ`·FßiP÷úf ù©ÅlnIÌ̼ Ê-/Úªx²”?·˜Í-3góL!¿#Ê,0¦‡{?g~µžgõçP-ýR{Ê¡d/TjçÑ6ÕÓSÌäVˆòj€¦;žÚ7ÆŒëKó6·§˜ Ö¢’vèEò®ÁÕÂÜ"ü‹˜çÆÆ£ïFg3ÉѲ®[bˆúÍÀ1;X:ãÓ1;ªI¢Çd ù““|ä{sB?8ÓñÜ4ÛLG£òפmõïq 0ÏñÜËRjo?Byb ×ÚãAY/vz Q+†ÖÖý(£È—Ý…ÆáíEgBiÝqÀÝá.Z§9ÝÚ ÅÀjꪕR”oƒåï1Bö¹µ¶‘¼¼o…~°&Na`b>*5ŽŽ»ãf€b6ׂ 00ã•g£¶\ŽÃMëáCß.:½šÓÅtTì¶9NF›Î51¢˜™xŽ5žÑe¨S’/1ˆ>¶ÚºŠ÷߸”ßnÊNÇá’¾«êö×®;ʘ#~ ‰žqšVýyÂ_ œ­¯ÏcfŒÉò€…À%À9ÑVÈ1x•:h.ÊðN7ýÞ•º]„JÙÓ+Ó>‡~Ð \ Õ1•P£!E‡}‚ŽvëÀíªÝÊɼ¦ÎsÉ·IÐ1ÑñÜ‘U¾óž¨< i»÷€ŒÓS€Ý4Ñ»–ÁôwÆ©÷PI;’=ã Ú“²öÖ×ãIöò6!í¾åÊGËöîɯÝÛâÑÝR9ž{ Ê,¦'l˜ŒÒÂÿWQônTÕ.Ú±ëÏTi’A£ƒöšC?ø¼ŽÅöààÛÚ±=€o3ÊÕ[Oæ(N0ñÙaâÚ©ÍñÜ'€¢ñÝ.EEŸ-¬¤ýèÅóéœî{FèKˆ©³¹D2”³‰Wrn} ·Æ£á¡×ˆ²EǹŽçNÖËÆtŽ·¤Së)¶ö~çxn£ÖšÔÉŒ´Êž»ã¹ƒëTlt#÷ÇÆGJÐIâ̪ Ž‘_%³î³¨„:vþäxîæ¥ž7ØÈßGÓ9¶¡(ˆ SííýàJ!—iŸ½]rÁ•Â…ÒétLΉ,†}‡à=zÎñÜÝd)q(j+ç¿EëÎzÓsˆOÿ<:ÆoƒÊöòkÇs‡%‘C#V_Çs§ R’ «âùÓÁ¦]ÚîfÇs3Žçö‰IWŽçþ•zJw¾úå ýYÒ€a}Ý{׬*aã"zF Üäxî¡Q¾ó¨sé¡OCùpVèSÓ(B-Ñ»¦ßwŸ.îÀWÑ9ÃÈ àÇs÷1?z”0B:㨴C—Џ‡>%êüð'àg¡Ü‘¢£Ei‹õ¶©¥ï5ÄÔ­e‚†D)ùsà2”»,—eÏ ¡„tÜ´¡ošÁFÚè” ò¹$ûš”Âû2ü4ôƒkÓÜŠî݇V™ 71°IFà®^¯ÿå0±)ë‚_ÚQÉç˜ë5Ãd÷pšã¹3Qø}€]Kˆ£‹åæ ¢\p“”Y†²h¦»ADÎ¥54C‹Ôe–,1ú£6:X[®ÍB?xWv™yHÖÈßQzH¹_Ée–¼sTï´oûe‡ ²/¦9ž{?Ê,ýMiû$…êTîý9(Ÿˆ‡#ï·´¦¹Ø°œÍ¹Öó•¢–ç­û¹(¯¶4Òå¡,éâµ'ŽçF;êßt-°(ôƒµå®×þžˆò«-Š·ÁÒyW)£2Þˆ ì¿dM«Gá})¾ªlßáòk“û.–†~ÐVêo€(¹¢äC¤-?î‡ò]hú È®+CX·qä‡i³öÄ´ýVÒö›É =DúÎ T¢ŠOP`o”z¿Šeüݘ)äûg ù~™B¾©² ×7ɵÕüªy^s%ׯÅPOÞ¯–ó²GXS­ï‘Æ>Ü“ï•I*—”KO¿¦«¾c‰:49žÛÜ•í–4£ïü'Ê´Ðf”øE1›[QbÀ8L]±¿úãÀIæþæúL)ä JŽaÆóÚDä:¥˜ÍÕý†õ0•ò«S‡˜n{ïž|çînû“8™B~W”Sƶ ×´³¹C ²E×€òàêª,&mÀÅlç}¸žÎ #¬n+fs?Ù÷^³°¨|F—l1?Få /‡ÑÅlnqÌl~² b¢ èkn},ñðçŠRª–£Ös<º…ÅzCcÌlÝVù’füîÚ*Y~ļg«ýÜ=Ñ£TQfŠe®»¿˜Í}jæ<âúJqƒ™CêÛ‚Ú±ã“×¶w³¹¶Þ¦jaÑ墻F¢½Q¡e&l´ ¨8õ‹Ùܪ¤›f ùQʼ­I—_«Ô,î£öªj-aB‹|‘ÇhÏk@Ù[ÿœÑÛOXXt*™ñ’Ì]Ý5k¦1¯uÕ»YXXXXXXXXXXXXXXXXXtþ-õÄŠXµÇÎIEND®B`‚cancancan-3.0.1/spec/000077500000000000000000000000001345516370100143575ustar00rootroot00000000000000cancancan-3.0.1/spec/README.md000066400000000000000000000005621345516370100156410ustar00rootroot00000000000000= CanCanCan Specs == Running the specs To run the specs first run the +bundle+ command to install the necessary gems. bundle Then run the appraisal command to install all the necessary test sets. bundle exec appraisal install You can then run all test sets: bundle exec appraisal rspec Or individual ones: bundle exec appraisal activerecord_5.2.0 rspec cancancan-3.0.1/spec/cancan/000077500000000000000000000000001345516370100156025ustar00rootroot00000000000000cancancan-3.0.1/spec/cancan/ability_spec.rb000066400000000000000000000663351345516370100206130ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' describe CanCan::Ability do before(:each) do (@ability = double).extend(CanCan::Ability) end it 'is able to :read anything' do @ability.can :read, :all expect(@ability.can?(:read, String)).to be(true) expect(@ability.can?(:read, 123)).to be(true) end it "does not have permission to do something it doesn't know about" do expect(@ability.can?(:foodfight, String)).to be(false) end it 'passes true to `can?` when non false/nil is returned in block' do @ability.can :read, Symbol do |sym| expect(sym).not_to be_nil 'foo' end expect(@ability.can?(:read, :some_symbol)).to be(true) end it 'passes nil to a block when no instance is passed' do @ability.can :read, Symbol do |sym| expect(sym).to be_nil true end expect(@ability.can?(:read, Symbol)).to be(true) end it 'passes to previous rule, if block returns false or nil' do @ability.can :read, Symbol @ability.can :read, Integer do |i| i < 5 end @ability.can :read, Integer do |i| i > 10 end expect(@ability.can?(:read, Symbol)).to be(true) expect(@ability.can?(:read, 11)).to be(true) expect(@ability.can?(:read, 1)).to be(true) expect(@ability.can?(:read, 6)).to be(false) end it 'overrides earlier rules with later ones (even if a different exact subject)' do @ability.cannot :read, Numeric @ability.can :read, Integer expect(@ability.can?(:read, 6)).to be(true) end it 'performs can(_, :all) before other checks when can(_, :all) is defined before' do @ability.can :manage, :all @ability.can :edit, String do |_string| raise 'Performed a check for :edit before the check for :all' end expect { @ability.can? :edit, 'a' }.to_not raise_exception end it 'performs can(_, :all) before other checks when can(_, :all) is defined after' do @ability.can :edit, String do |_string| raise 'Performed a check for :edit before the check for :all' end @ability.can :manage, :all expect { @ability.can? :edit, 'a' }.to_not raise_exception end it 'does not pass class with object if :all objects are accepted' do @ability.can :preview, :all do |object| expect(object).to eq(123) @block_called = true end @ability.can?(:preview, 123) expect(@block_called).to be(true) end it 'does not call block when only class is passed, only return true' do @block_called = false @ability.can :preview, :all do |_object| @block_called = true end expect(@ability.can?(:preview, Hash)).to be(true) expect(@block_called).to be(false) end it 'passes only object for global manage actions' do @ability.can :manage, String do |object| expect(object).to eq('foo') @block_called = true end expect(@ability.can?(:stuff, 'foo')).to be(true) expect(@block_called).to be(true) end it 'makes alias for update or destroy actions to modify action' do @ability.alias_action :update, :destroy, to: :modify @ability.can :modify, :all expect(@ability.can?(:update, 123)).to be(true) expect(@ability.can?(:destroy, 123)).to be(true) end it 'allows deeply nested aliased actions' do @ability.alias_action :increment, to: :sort @ability.alias_action :sort, to: :modify @ability.can :modify, :all expect(@ability.can?(:increment, 123)).to be(true) end it 'raises an Error if alias target is an exist action' do expect { @ability.alias_action :show, to: :show } .to raise_error(CanCan::Error, "You can't specify target (show) as alias because it is real action name") end it 'always calls block with arguments when passing no arguments to can' do @ability.can do |action, object_class, object| expect(action).to eq(:foo) expect(object_class).to eq(123.class) expect(object).to eq(123) @block_called = true end @ability.can?(:foo, 123) expect(@block_called).to be(true) end it 'passes nil to object when comparing class with can check' do @ability.can do |action, object_class, object| expect(action).to eq(:foo) expect(object_class).to eq(Hash) expect(object).to be_nil @block_called = true end @ability.can?(:foo, Hash) expect(@block_called).to be(true) end it 'automatically makes alias for index and show into read calls' do @ability.can :read, :all expect(@ability.can?(:index, 123)).to be(true) expect(@ability.can?(:show, 123)).to be(true) end it 'automatically makes alias for new and edit into create and update respectively' do @ability.can :create, :all @ability.can :update, :all expect(@ability.can?(:new, 123)).to be(true) expect(@ability.can?(:edit, 123)).to be(true) end it 'does not respond to prepare (now using initialize)' do expect(@ability).to_not respond_to(:prepare) end it 'offers cannot? method which is simply invert of can?' do expect(@ability.cannot?(:tie, String)).to be(true) end it 'is able to specify multiple actions and match any' do @ability.can %i[read update], :all expect(@ability.can?(:read, 123)).to be(true) expect(@ability.can?(:update, 123)).to be(true) expect(@ability.can?(:count, 123)).to be(false) end it 'is able to specify multiple classes and match any' do @ability.can :update, [String, Range] expect(@ability.can?(:update, 'foo')).to be(true) expect(@ability.can?(:update, 1..3)).to be(true) expect(@ability.can?(:update, 123)).to be(false) end it 'checks if there is a permission for any of given subjects' do @ability.can :update, [String, Range] expect(@ability.can?(:update, any: ['foo', 1..3])).to be(true) expect(@ability.can?(:update, any: [1..3, 'foo'])).to be(true) expect(@ability.can?(:update, any: [123, 'foo'])).to be(true) expect(@ability.can?(:update, any: [123, 1.0])).to be(false) end it 'lists all permissions' do @ability.can :manage, :all @ability.can :learn, Range @ability.can :interpret, Symbol, %i[size to_s] @ability.cannot :read, String @ability.cannot :read, Hash @ability.cannot :preview, Array expected_list = { can: { manage: { 'all' => [] }, learn: { 'Range' => [] }, interpret: { 'Symbol' => %i[size to_s] } }, cannot: { read: { 'String' => [], 'Hash' => [] }, index: { 'String' => [], 'Hash' => [] }, show: { 'String' => [], 'Hash' => [] }, preview: { 'Array' => [] } } } expect(@ability.permissions).to eq(expected_list) end it 'supports custom objects in the rule' do @ability.can :read, :stats expect(@ability.can?(:read, :stats)).to be(true) expect(@ability.can?(:update, :stats)).to be(false) expect(@ability.can?(:read, :nonstats)).to be(false) expect(@ability.can?(:read, any: %i[stats nonstats])).to be(true) expect(@ability.can?(:read, any: %i[nonstats neitherstats])).to be(false) end it 'checks ancestors of class' do @ability.can :read, Numeric expect(@ability.can?(:read, Integer)).to be(true) expect(@ability.can?(:read, 1.23)).to be(true) expect(@ability.can?(:read, 'foo')).to be(false) expect(@ability.can?(:read, any: [Integer, String])).to be(true) end it "supports 'cannot' method to define what user cannot do" do @ability.can :read, :all @ability.cannot :read, Integer expect(@ability.can?(:read, 'foo')).to be(true) expect(@ability.can?(:read, 123)).to be(false) expect(@ability.can?(:read, any: %w[foo bar])).to be(true) expect(@ability.can?(:read, any: [123, 'foo'])).to be(false) expect(@ability.can?(:read, any: [123, 456])).to be(false) end it 'passes to previous rule, if block returns false or nil' do @ability.can :read, :all @ability.cannot :read, Integer do |int| int > 10 ? nil : (int > 5) end expect(@ability.can?(:read, 'foo')).to be(true) expect(@ability.can?(:read, 3)).to be(true) expect(@ability.can?(:read, 8)).to be(false) expect(@ability.can?(:read, 123)).to be(true) expect(@ability.can?(:read, any: [123, 8])).to be(true) expect(@ability.can?(:read, any: [8, 9])).to be(false) end it 'always returns `false` for single cannot definition' do @ability.cannot :read, Integer do |int| int > 10 ? nil : (int > 5) end expect(@ability.can?(:read, 'foo')).to be(false) expect(@ability.can?(:read, 3)).to be(false) expect(@ability.can?(:read, 8)).to be(false) expect(@ability.can?(:read, 123)).to be(false) end it 'passes to previous cannot definition, if block returns false or nil' do @ability.cannot :read, :all @ability.can :read, Integer do |int| int > 10 ? nil : (int > 5) end expect(@ability.can?(:read, 'foo')).to be(false) expect(@ability.can?(:read, 3)).to be(false) expect(@ability.can?(:read, 10)).to be(true) expect(@ability.can?(:read, 123)).to be(false) end it 'appends aliased actions' do @ability.alias_action :update, to: :modify @ability.alias_action :destroy, to: :modify expect(@ability.aliased_actions[:modify]).to eq(%i[update destroy]) end it 'clears aliased actions' do @ability.alias_action :update, to: :modify @ability.clear_aliased_actions expect(@ability.aliased_actions[:modify]).to be_nil end it 'passes additional arguments to block from can?' do @ability.can :read, Integer do |int, x| int > x end expect(@ability.can?(:read, 2, 1)).to be(true) expect(@ability.can?(:read, 2, 3)).to be(false) expect(@ability.can?(:read, { any: [4, 5] }, 3)).to be(true) expect(@ability.can?(:read, { any: [2, 3] }, 3)).to be(false) end it 'uses conditions as third parameter and determine abilities from it' do @ability.can :read, Range, begin: 1, end: 3 expect(@ability.can?(:read, 1..3)).to be(true) expect(@ability.can?(:read, 1..4)).to be(false) expect(@ability.can?(:read, Range)).to be(true) expect(@ability.can?(:read, any: [1..3, 1..4])).to be(true) expect(@ability.can?(:read, any: [1..4, 2..4])).to be(false) end it 'allows an array of options in conditions hash' do @ability.can :read, Range, begin: [1, 3, 5] expect(@ability.can?(:read, 1..3)).to be(true) expect(@ability.can?(:read, 2..4)).to be(false) expect(@ability.can?(:read, 3..5)).to be(true) expect(@ability.can?(:read, any: [2..4, 3..5])).to be(true) expect(@ability.can?(:read, any: [2..4, 2..5])).to be(false) end it 'allows a range of options in conditions hash' do @ability.can :read, Range, begin: 1..3 expect(@ability.can?(:read, 1..10)).to be(true) expect(@ability.can?(:read, 3..30)).to be(true) expect(@ability.can?(:read, 4..40)).to be(false) end it 'allows a range of time in conditions hash' do @ability.can :read, Range, begin: 1.day.from_now..3.days.from_now expect(@ability.can?(:read, 1.day.from_now..10.days.from_now)).to be(true) expect(@ability.can?(:read, 2.days.from_now..20.days.from_now)).to be(true) expect(@ability.can?(:read, 4.days.from_now..40.days.from_now)).to be(false) end it 'allows nested hashes in conditions hash' do @ability.can :read, Range, begin: { to_i: 5 } expect(@ability.can?(:read, 5..7)).to be(true) expect(@ability.can?(:read, 6..8)).to be(false) end it "matches any element passed in to nesting if it's an array (for has_many associations)" do @ability.can :read, Range, to_a: { to_i: 3 } expect(@ability.can?(:read, 1..5)).to be(true) expect(@ability.can?(:read, 4..6)).to be(false) end it 'accepts a set as a condition value' do expect(object_with_foo_two = double(foo: 2)).to receive(:foo) expect(object_with_foo_three = double(foo: 3)).to receive(:foo) @ability.can :read, Object, foo: [1, 2, 5].to_set expect(@ability.can?(:read, object_with_foo_two)).to be(true) expect(@ability.can?(:read, object_with_foo_three)).to be(false) end it 'does not match subjects return nil for methods that must match nested a nested conditions hash' do expect(object_with_foo = double(foo: :bar)).to receive(:foo) @ability.can :read, Array, first: { foo: :bar } expect(@ability.can?(:read, [object_with_foo])).to be(true) expect(@ability.can?(:read, [])).to be(false) end it 'matches strings but not substrings specified in a conditions hash' do @ability.can :read, String, presence: 'declassified' expect(@ability.can?(:read, 'declassified')).to be(true) expect(@ability.can?(:read, 'classified')).to be(false) end it 'does not stop at cannot definition when comparing class' do @ability.can :read, Range @ability.cannot :read, Range, begin: 1 expect(@ability.can?(:read, 2..5)).to be(true) expect(@ability.can?(:read, 1..5)).to be(false) expect(@ability.can?(:read, Range)).to be(true) end it 'does not stop at cannot with block when comparing class' do @ability.can :read, Integer @ability.cannot(:read, Integer) { |int| int > 5 } expect(@ability.can?(:read, 123)).to be(false) expect(@ability.can?(:read, Integer)).to be(true) end it 'stops at cannot definition when no hash is present' do @ability.can :read, :all @ability.cannot :read, Range expect(@ability.can?(:read, 1..5)).to be(false) expect(@ability.can?(:read, Range)).to be(false) end it 'allows to check ability for Module' do module B end class A include B end @ability.can :read, B expect(@ability.can?(:read, A)).to be(true) expect(@ability.can?(:read, A.new)).to be(true) end it 'passes nil to a block for ability on Module when no instance is passed' do module B end class A include B end @ability.can :read, B do |sym| expect(sym).to be_nil true end expect(@ability.can?(:read, B)).to be(true) expect(@ability.can?(:read, A)).to be(true) end it 'checks permissions through association when passing a hash of subjects' do @ability.can :read, Range, string: { length: 3 } expect(@ability.can?(:read, 'foo' => Range)).to be(true) expect(@ability.can?(:read, 'foobar' => Range)).to be(false) expect(@ability.can?(:read, 123 => Range)).to be(true) expect(@ability.can?(:read, any: [{ 'foo' => Range }, { 'foobar' => Range }])).to be(true) expect(@ability.can?(:read, any: [{ 'food' => Range }, { 'foobar' => Range }])).to be(false) end it 'checks permissions correctly when passing a hash of subjects with multiple definitions' do @ability.can :read, Range, string: { length: 4 } @ability.can %i[create read], Range, string: { upcase: 'FOO' } expect(@ability.can?(:read, 'foo' => Range)).to be(true) expect(@ability.can?(:read, 'foobar' => Range)).to be(false) expect(@ability.can?(:read, 1234 => Range)).to be(true) expect(@ability.can?(:read, any: [{ 'foo' => Range }, { 'foobar' => Range }])).to be(true) expect(@ability.can?(:read, any: [{ 'foo.bar' => Range }, { 'foobar' => Range }])).to be(false) end it 'allows to check ability on Hash-like object' do class Container < Hash end @ability.can :read, Container expect(@ability.can?(:read, Container.new)).to be(true) end it "has initial values based on hash conditions of 'new' action" do @ability.can :manage, Range, foo: 'foo', hash: { skip: 'hashes' } @ability.can :create, Range, bar: 123, array: %w[skip arrays] @ability.can :new, Range, baz: 'baz', range: 1..3 @ability.cannot :new, Range, ignore: 'me' expect(@ability.attributes_for(:new, Range)).to eq(foo: 'foo', bar: 123, baz: 'baz') end # rubocop:disable Style/SymbolProc describe 'different usages of blocks and procs' do class A def active? true end end it 'can use a do...end block' do @ability.can :read, A do |a| a.active? end expect(@ability).to be_able_to(:read, A.new) end it 'can use a inline block' do @ability.can(:read, A) { |a| a.active? } expect(@ability).to be_able_to(:read, A.new) end it 'can use a method reference' do @ability.can :read, A, &:active? expect(@ability).to be_able_to(:read, A.new) end it 'can use a Proc' do proc = Proc.new(&:active?) @ability.can :read, A, &proc expect(@ability).to be_able_to(:read, A.new) end end # rubocop:enable Style/SymbolProc describe '#authorize!' do describe 'when ability is not authorized to perform an action' do it 'raises access denied exception' do begin @ability.authorize! :read, :foo, 1, 2, 3, message: 'Access denied!' rescue CanCan::AccessDenied => e expect(e.message).to eq('Access denied!') expect(e.action).to eq(:read) expect(e.subject).to eq(:foo) expect(e.conditions).to eq([1, 2, 3]) else raise 'Expected CanCan::AccessDenied exception to be raised' end end describe 'when no extra conditions are specified' do it 'raises access denied exception without conditions' do begin @ability.authorize! :read, :foo, message: 'Access denied!' rescue CanCan::AccessDenied => e expect(e.conditions).to eq([]) else raise 'Expected CanCan::AccessDenied exception to be raised' end end end describe 'when no message is specified' do it 'raises access denied exception with default message' do begin @ability.authorize! :read, :foo rescue CanCan::AccessDenied => e e.default_message = 'Access denied!' expect(e.message).to eq('Access denied!') else raise 'Expected CanCan::AccessDenied exception to be raised' end end end end describe 'when ability is authorized to perform an action' do it 'does not raise access denied exception' do @ability.can :read, :foo expect do expect(@ability.authorize!(:read, :foo)).to eq(:foo) end.to_not raise_error end end end it 'knows when block is used in conditions' do @ability.can :read, :foo expect(@ability).to_not have_block(:read, :foo) @ability.can :read, :foo do |_foo| false end expect(@ability).to have_block(:read, :foo) end it 'knows when raw sql is used in conditions' do @ability.can :read, :foo expect(@ability).to_not have_raw_sql(:read, :foo) @ability.can :read, :foo, 'false' expect(@ability).to have_raw_sql(:read, :foo) end it 'determines model adapter class by asking AbstractAdapter' do adapter_class = double model_class = double allow(CanCan::ModelAdapters::AbstractAdapter).to receive(:adapter_class).with(model_class) { adapter_class } allow(adapter_class).to receive(:new).with(model_class, []) { :adapter_instance } expect(@ability.model_adapter(model_class, :read)).to eq(:adapter_instance) end it "raises an error when attempting to use a block with a hash condition since it's not likely what they want" do expect do @ability.can :read, Array, published: true do false end end.to raise_error(CanCan::BlockAndConditionsError) end it 'allows attribute-level rules' do @ability.can :read, Array, :to_s expect(@ability.can?(:read, Array, :to_s)).to be(true) expect(@ability.can?(:read, Array, :size)).to be(false) expect(@ability.can?(:read, Array)).to be(true) end it 'allows an array of attributes in rules' do @ability.can :read, [Array, String], %i[to_s size] expect(@ability.can?(:read, String, :size)).to be(true) expect(@ability.can?(:read, Array, :to_s)).to be(true) end it 'allows cannot of rules with attributes' do @ability.can :read, Array @ability.cannot :read, Array, :to_s expect(@ability.can?(:read, Array, :to_s)).to be(false) expect(@ability.can?(:read, Array)).to be(true) expect(@ability.can?(:read, Array, :size)).to be(true) end it 'has precedence with attribute-level rules' do @ability.cannot :read, Array @ability.can :read, Array, :to_s expect(@ability.can?(:read, Array, :to_s)).to be(true) expect(@ability.can?(:read, Array, :size)).to be(false) expect(@ability.can?(:read, Array)).to be(true) end it 'allows permission on all attributes when none are given' do @ability.can :update, Object expect(@ability.can?(:update, Object, :password)).to be(true) end it 'allows strings when checking attributes' do @ability.can :update, Object, :name expect(@ability.can?(:update, Object, 'name')).to be(true) end it 'passes attribute to block; nil if no attribute given' do @ability.can :update, Range do |_range, attribute| attribute == :name end expect(@ability.can?(:update, 1..3, :name)).to be(true) expect(@ability.can?(:update, 2..4)).to be(false) end it 'combines attribute checks with conditions hash' do @ability.can :update, Range, begin: 1 @ability.can :update, Range, :name, begin: 2 expect(@ability.can?(:update, 1..3, :notname)).to be(true) expect(@ability.can?(:update, 2..4, :notname)).to be(false) expect(@ability.can?(:update, 2..4, :name)).to be(true) expect(@ability.can?(:update, 3..5, :name)).to be(false) expect(@ability.can?(:update, Range)).to be(true) expect(@ability.can?(:update, Range, :name)).to be(true) end it 'returns an array of permitted attributes for a given action and subject' do user_class = Class.new(ActiveRecord::Base) allow(user_class).to receive(:column_names).and_return(%w[first_name last_name]) allow(user_class).to receive(:primary_key).and_return('id') @ability.can :read, user_class @ability.can :read, Array, :special @ability.can :action, :subject, :attribute expect(@ability.permitted_attributes(:read, user_class)).to eq(%i[first_name last_name]) expect(@ability.permitted_attributes(:read, Array)).to eq([:special]) expect(@ability.permitted_attributes(:action, :subject)).to eq([:attribute]) end it 'returns permitted attributes when used with blocks' do user_class = Struct.new(:first_name, :last_name) @ability.can :read, user_class, %i[first_name last_name] @ability.cannot(:read, user_class, :first_name) { |u| u.last_name == 'Smith' } expect(@ability.permitted_attributes(:read, user_class.new('John', 'Jones'))).to eq(%i[first_name last_name]) expect(@ability.permitted_attributes(:read, user_class.new('John', 'Smith'))).to eq(%i[last_name]) end it 'returns permitted attributes when using conditions' do @ability.can :read, Range, %i[nil? to_s class] @ability.cannot :read, Range, %i[nil? to_s], begin: 2 @ability.can :read, Range, :to_s, end: 4 expect(@ability.permitted_attributes(:read, 1..3)).to eq(%i[nil? to_s class]) expect(@ability.permitted_attributes(:read, 2..5)).to eq([:class]) expect(@ability.permitted_attributes(:read, 2..4)).to eq(%i[class to_s]) end it 'respects inheritance when checking permitted attributes' do @ability.can :read, Integer, %i[nil? to_s class] @ability.cannot :read, Numeric, %i[nil? class] expect(@ability.permitted_attributes(:read, Integer)).to eq([:to_s]) end describe 'unauthorized message' do after(:each) do I18n.backend = nil end it 'uses action/subject in i18n' do I18n.backend.store_translations :en, unauthorized: { update: { array: 'foo' } } expect(@ability.unauthorized_message(:update, Array)).to eq('foo') expect(@ability.unauthorized_message(:update, [1, 2, 3])).to eq('foo') expect(@ability.unauthorized_message(:update, :missing)).to be_nil end it "uses model's name in i18n" do class Account include ActiveModel::Model end I18n.backend.store_translations :en, activemodel: { models: { account: 'english name' } }, unauthorized: { update: { all: '%{subject}' } } I18n.backend.store_translations :ja, activemodel: { models: { account: 'japanese name' } }, unauthorized: { update: { all: '%{subject}' } } I18n.with_locale(:en) do expect(@ability.unauthorized_message(:update, Account)).to eq('english name') end I18n.with_locale(:ja) do expect(@ability.unauthorized_message(:update, Account)).to eq('japanese name') end end it 'uses symbol as subject directly' do I18n.backend.store_translations :en, unauthorized: { has: { cheezburger: 'Nom nom nom. I eated it.' } } expect(@ability.unauthorized_message(:has, :cheezburger)).to eq('Nom nom nom. I eated it.') end it "falls back to 'manage' and 'all'" do I18n.backend.store_translations :en, unauthorized: { manage: { all: 'manage all', array: 'manage array' }, update: { all: 'update all', array: 'update array' } } expect(@ability.unauthorized_message(:update, Array)).to eq('update array') expect(@ability.unauthorized_message(:update, Hash)).to eq('update all') expect(@ability.unauthorized_message(:foo, Array)).to eq('manage array') expect(@ability.unauthorized_message(:foo, Hash)).to eq('manage all') end it 'follows aliased actions' do I18n.backend.store_translations :en, unauthorized: { modify: { array: 'modify array' } } @ability.alias_action :update, to: :modify expect(@ability.unauthorized_message(:update, Array)).to eq('modify array') expect(@ability.unauthorized_message(:edit, Array)).to eq('modify array') end it 'has variables for action and subject' do # old syntax for now in case testing with old I18n I18n.backend.store_translations :en, unauthorized: { manage: { all: '%s %s' } } expect(@ability.unauthorized_message(:update, Array)).to eq('update array') expect(@ability.unauthorized_message(:update, ArgumentError)).to eq('update argument error') expect(@ability.unauthorized_message(:edit, 1..3)).to eq('edit range') end end describe '#merge' do it 'adds the rules from the given ability' do @ability.can :use, :tools (another_ability = double).extend(CanCan::Ability) another_ability.can :use, :search @ability.merge(another_ability) expect(@ability.can?(:use, :search)).to be(true) expect(@ability.send(:rules).size).to eq(2) end it 'adds the aliased actions from the given ability' do @ability.alias_action :show, to: :see (another_ability = double).extend(CanCan::Ability) another_ability.alias_action :create, :update, to: :manage @ability.merge(another_ability) expect(@ability.aliased_actions).to eq( read: %i[index show], create: %i[new], update: %i[edit], manage: %i[create update], see: %i[show] ) end it 'overwrittes the aliased actions with the value from the given ability' do @ability.alias_action :show, :index, to: :see (another_ability = double).extend(CanCan::Ability) another_ability.alias_action :show, to: :see @ability.merge(another_ability) expect(@ability.aliased_actions).to eq( read: %i[index show], create: %i[new], update: %i[edit], see: %i[show] ) end it 'can add an empty ability' do (another_ability = double).extend(CanCan::Ability) @ability.merge(another_ability) expect(@ability.send(:rules).size).to eq(0) end end end cancancan-3.0.1/spec/cancan/controller_additions_spec.rb000066400000000000000000000200701345516370100233610ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' describe CanCan::ControllerAdditions do before(:each) do @controller_class = Class.new @controller = @controller_class.new allow(@controller).to receive(:params) { {} } allow(@controller).to receive(:current_user) { :current_user } expect(@controller_class).to receive(:helper_method).with(:can?, :cannot?, :current_ability) @controller_class.send(:include, CanCan::ControllerAdditions) end it 'authorize! assigns @_authorized instance variable and pass args to current ability' do allow(@controller.current_ability).to receive(:authorize!).with(:foo, :bar) @controller.authorize!(:foo, :bar) expect(@controller.instance_variable_get(:@_authorized)).to be(true) end it 'has a current_ability method which generates an ability for the current user' do expect(@controller.current_ability).to be_kind_of(Ability) end it 'provides a can? and cannot? methods which go through the current ability' do expect(@controller.current_ability).to be_kind_of(Ability) expect(@controller.can?(:foo, :bar)).to be(false) expect(@controller.cannot?(:foo, :bar)).to be(true) end it 'load_and_authorize_resource setups a before filter which passes call to ControllerResource' do expect(cancan_resource_class = double).to receive(:load_and_authorize_resource) allow(CanCan::ControllerResource).to receive(:new).with(@controller, nil, foo: :bar) { cancan_resource_class } expect(@controller_class) .to receive(:before_action).with({}) { |_options, &block| block.call(@controller) } @controller_class.load_and_authorize_resource foo: :bar end it 'load_and_authorize_resource properly passes first argument as the resource name' do expect(cancan_resource_class = double).to receive(:load_and_authorize_resource) allow(CanCan::ControllerResource).to receive(:new).with(@controller, :project, foo: :bar) { cancan_resource_class } expect(@controller_class) .to receive(:before_action).with({}) { |_options, &block| block.call(@controller) } @controller_class.load_and_authorize_resource :project, foo: :bar end it 'load_and_authorize_resource with :prepend prepends the before filter' do expect(@controller_class).to receive(:prepend_before_action).with({}) @controller_class.load_and_authorize_resource foo: :bar, prepend: true end it 'authorize_resource setups a before filter which passes call to ControllerResource' do expect(cancan_resource_class = double).to receive(:authorize_resource) allow(CanCan::ControllerResource).to receive(:new).with(@controller, nil, foo: :bar) { cancan_resource_class } expect(@controller_class) .to receive(:before_action).with(except: :show, if: true) do |_options, &block| block.call(@controller) end @controller_class.authorize_resource foo: :bar, except: :show, if: true end it 'load_resource setups a before filter which passes call to ControllerResource' do expect(cancan_resource_class = double).to receive(:load_resource) allow(CanCan::ControllerResource).to receive(:new).with(@controller, nil, foo: :bar) { cancan_resource_class } expect(@controller_class) .to receive(:before_action).with(only: %i[show index], unless: false) do |_options, &block| block.call(@controller) end @controller_class.load_resource foo: :bar, only: %i[show index], unless: false end it 'skip_authorization_check setups a before filter which sets @_authorized to true' do expect(@controller_class) .to receive(:before_action).with(:filter_options) { |_options, &block| block.call(@controller) } @controller_class.skip_authorization_check(:filter_options) expect(@controller.instance_variable_get(:@_authorized)).to be(true) end it 'check_authorization triggers AuthorizationNotPerformed in after filter' do expect(@controller_class) .to receive(:after_action).with(only: [:test]) { |_options, &block| block.call(@controller) } expect do @controller_class.check_authorization(only: [:test]) end.to raise_error(CanCan::AuthorizationNotPerformed) end it 'check_authorization does not trigger AuthorizationNotPerformed when :if is false' do allow(@controller).to receive(:check_auth?) { false } allow(@controller_class) .to receive(:after_action).with({}) { |_options, &block| block.call(@controller) } expect do @controller_class.check_authorization(if: :check_auth?) end.not_to raise_error end it 'check_authorization does not trigger AuthorizationNotPerformed when :unless is true' do allow(@controller).to receive(:engine_controller?) { true } expect(@controller_class) .to receive(:after_action).with({}) { |_options, &block| block.call(@controller) } expect do @controller_class.check_authorization(unless: :engine_controller?) end.not_to raise_error end it 'check_authorization does not raise error when @_authorized is set' do @controller.instance_variable_set(:@_authorized, true) expect(@controller_class) .to receive(:after_action).with(only: [:test]) { |_options, &block| block.call(@controller) } expect do @controller_class.check_authorization(only: [:test]) end.not_to raise_error end it 'cancan_resource_class is ControllerResource by default' do expect(@controller.class.cancan_resource_class).to eq(CanCan::ControllerResource) end it 'cancan_skipper is an empty hash with :authorize and :load options and remember changes' do expect(@controller_class.cancan_skipper).to eq(authorize: {}, load: {}) @controller_class.cancan_skipper[:load] = true expect(@controller_class.cancan_skipper[:load]).to be(true) end it 'skip_authorize_resource adds itself to the cancan skipper with given model name and options' do @controller_class.skip_authorize_resource(:project, only: %i[index show]) expect(@controller_class.cancan_skipper[:authorize][:project]).to eq(only: %i[index show]) @controller_class.skip_authorize_resource(only: %i[index show]) expect(@controller_class.cancan_skipper[:authorize][nil]).to eq(only: %i[index show]) @controller_class.skip_authorize_resource(:article) expect(@controller_class.cancan_skipper[:authorize][:article]).to eq({}) end it 'skip_load_resource adds itself to the cancan skipper with given model name and options' do @controller_class.skip_load_resource(:project, only: %i[index show]) expect(@controller_class.cancan_skipper[:load][:project]).to eq(only: %i[index show]) @controller_class.skip_load_resource(only: %i[index show]) expect(@controller_class.cancan_skipper[:load][nil]).to eq(only: %i[index show]) @controller_class.skip_load_resource(:article) expect(@controller_class.cancan_skipper[:load][:article]).to eq({}) end it 'skip_load_and_authore_resource adds itself to the cancan skipper with given model name and options' do @controller_class.skip_load_and_authorize_resource(:project, only: %i[index show]) expect(@controller_class.cancan_skipper[:load][:project]).to eq(only: %i[index show]) expect(@controller_class.cancan_skipper[:authorize][:project]).to eq(only: %i[index show]) end describe 'when inheriting' do before(:each) do @super_controller_class = Class.new @super_controller = @super_controller_class.new @sub_controller_class = Class.new(@super_controller_class) @sub_controller = @sub_controller_class.new allow(@super_controller_class).to receive(:helper_method) @super_controller_class.send(:include, CanCan::ControllerAdditions) @super_controller_class.skip_load_and_authorize_resource(only: %i[index show]) end it 'sub_classes should skip the same behaviors and actions as super_classes' do expect(@super_controller_class.cancan_skipper[:load][nil]).to eq(only: %i[index show]) expect(@super_controller_class.cancan_skipper[:authorize][nil]).to eq(only: %i[index show]) expect(@sub_controller_class.cancan_skipper[:load][nil]).to eq(only: %i[index show]) expect(@sub_controller_class.cancan_skipper[:authorize][nil]).to eq(only: %i[index show]) end end end cancancan-3.0.1/spec/cancan/controller_resource_spec.rb000066400000000000000000000711541345516370100232430ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' describe CanCan::ControllerResource do let(:ability) { Ability.new(nil) } let(:params) { HashWithIndifferentAccess.new(controller: 'models') } let(:controller_class) { Class.new } let(:controller) { controller_class.new } before(:each) do class Model attr_accessor :name def initialize(attributes = {}) attributes.each do |attribute, value| send("#{attribute}=", value) end end end allow(controller).to receive(:params) { params } allow(controller).to receive(:current_ability) { ability } allow(controller_class).to receive(:cancan_skipper) { { authorize: {}, load: {} } } end context 'on build actions' do before :each do params.merge!(action: 'new') end it 'builds a new resource with attributes from current ability' do ability.can(:create, Model, name: 'from conditions') resource = CanCan::ControllerResource.new(controller) resource.load_resource expect(controller.instance_variable_get(:@model).name).to eq('from conditions') end it 'overrides initial attributes with params' do params[:model] = { name: 'from params' } ability.can(:create, Model, name: 'from conditions') resource = CanCan::ControllerResource.new(controller) resource.load_resource expect(controller.instance_variable_get(:@model).name).to eq('from params') end it 'builds a resource when on custom new action even when params[:id] exists' do params.merge!(action: 'build', id: '123') allow(Model).to receive(:new) { :some_model } resource = CanCan::ControllerResource.new(controller, new: :build) resource.load_resource expect(controller.instance_variable_get(:@model)).to eq(:some_model) end it 'only authorizes :show action on parent resource' do model = Model.new allow(Model).to receive(:find).with('123') { model } params[:model_id] = 123 allow(controller).to receive(:authorize!).with(:show, model) { raise CanCan::AccessDenied } resource = CanCan::ControllerResource.new(controller, :model, parent: true) expect { resource.load_and_authorize_resource }.to raise_error(CanCan::AccessDenied) end end context 'on create actions' do before :each do params.merge!(action: 'create') end # Rails includes namespace in params, see issue #349 it 'creates through the namespaced params' do module MyEngine class Model < ::Model; end end params.merge!(controller: 'my_engine/models', my_engine_model: { name: 'foobar' }) resource = CanCan::ControllerResource.new(controller) resource.load_resource expect(controller.instance_variable_get(:@model).name).to eq('foobar') end it 'builds a new resource with hash if params[:id] is not specified' do params[:model] = { name: 'foobar' } resource = CanCan::ControllerResource.new(controller) resource.load_resource expect(controller.instance_variable_get(:@model).name).to eq('foobar') end it 'builds a new resource for namespaced model with hash if params[:id] is not specified' do module Sub class Model < ::Model; end end params['sub_model'] = { name: 'foobar' } resource = CanCan::ControllerResource.new(controller, class: ::Sub::Model) resource.load_resource expect(controller.instance_variable_get(:@model).name).to eq('foobar') end context 'params[:id] is not specified' do it 'builds a new resource for namespaced controller and namespaced model with hash' do params.merge!(:controller => 'admin/sub_models', 'sub_model' => { name: 'foobar' }) resource = CanCan::ControllerResource.new(controller, class: Model) resource.load_resource expect(controller.instance_variable_get(:@sub_model).name).to eq('foobar') end end it 'builds a new resource for namespaced controller given through folder format' do module Admin module SubModule class HiddenModel < ::Model; end end end params[:controller] = 'admin/sub_module/hidden_models' resource = CanCan::ControllerResource.new(controller) expect { resource.load_resource }.not_to raise_error end context 'with :singleton option' do it 'does not build record through has_one association because it can cause it to delete it in the database' do category = Class.new allow_any_instance_of(Model).to receive('category=').with(category) allow_any_instance_of(Model).to receive('category') { category } params[:model] = { name: 'foobar' } controller.instance_variable_set(:@category, category) resource = CanCan::ControllerResource.new(controller, through: :category, singleton: true) resource.load_resource expect(controller.instance_variable_get(:@model).name).to eq('foobar') expect(controller.instance_variable_get(:@model).category).to eq(category) end end it 'builds record through has_one association with :singleton and :shallow options' do params[:model] = { name: 'foobar' } resource = CanCan::ControllerResource.new(controller, through: :category, singleton: true, shallow: true) resource.load_resource expect(controller.instance_variable_get(:@model).name).to eq('foobar') end context 'with a strong parameters method' do before :each do params.merge!(controller: 'model', model: { name: 'test' }) end it 'accepts and uses the specified symbol for santitizing input' do allow(controller).to receive(:resource_params).and_return(resource: 'params') allow(controller).to receive(:model_params).and_return(model: 'params') allow(controller).to receive(:create_params).and_return(create: 'params') allow(controller).to receive(:custom_params).and_return(custom: 'params') resource = CanCan::ControllerResource.new(controller, param_method: :custom_params) expect(resource.send('resource_params')).to eq(custom: 'params') end it 'accepts the specified string for sanitizing input' do resource = CanCan::ControllerResource.new(controller, param_method: "{:custom => 'params'}") expect(resource.send('resource_params')).to eq(custom: 'params') end it 'accepts the specified proc for sanitizing input' do resource = CanCan::ControllerResource.new(controller, param_method: proc { |_c| { custom: 'params' } }) expect(resource.send('resource_params')).to eq(custom: 'params') end it 'prefers to use the create_params method for santitizing input' do allow(controller).to receive(:resource_params).and_return(resource: 'params') allow(controller).to receive(:model_params).and_return(model: 'params') allow(controller).to receive(:create_params).and_return(create: 'params') allow(controller).to receive(:custom_params).and_return(custom: 'params') resource = CanCan::ControllerResource.new(controller) expect(resource.send('resource_params')).to eq(create: 'params') end it 'prefers to use the _params method for santitizing input if create is not found' do allow(controller).to receive(:resource_params).and_return(resource: 'params') allow(controller).to receive(:model_params).and_return(model: 'params') allow(controller).to receive(:custom_params).and_return(custom: 'params') resource = CanCan::ControllerResource.new(controller) expect(resource.send('resource_params')).to eq(model: 'params') end it 'prefers to use the resource_params method for santitizing input if create or model is not found' do allow(controller).to receive(:resource_params).and_return(resource: 'params') allow(controller).to receive(:custom_params).and_return(custom: 'params') resource = CanCan::ControllerResource.new(controller) expect(resource.send('resource_params')).to eq(resource: 'params') end end end context 'on collection actions' do before :each do params[:action] = 'index' end it 'builds a collection when on index action when class responds to accessible_by' do allow(Model).to receive(:accessible_by).with(ability, :index) { :found_models } resource = CanCan::ControllerResource.new(controller, :model) resource.load_resource expect(controller.instance_variable_get(:@model)).to be_nil expect(controller.instance_variable_get(:@models)).to eq(:found_models) end it 'does not build a collection when on index action when class does not respond to accessible_by' do resource = CanCan::ControllerResource.new(controller) resource.load_resource expect(controller.instance_variable_get(:@model)).to be_nil expect(controller.instance_variable_defined?(:@models)).to be(false) end it 'does not use accessible_by when defining abilities through a block' do allow(Model).to receive(:accessible_by).with(ability) { :found_models } ability.can(:read, Model) { |_p| false } resource = CanCan::ControllerResource.new(controller) resource.load_resource expect(controller.instance_variable_get(:@model)).to be_nil expect(controller.instance_variable_defined?(:@models)).to be(false) end it 'does not authorize single resource in collection action' do allow(controller).to receive(:authorize!).with(:index, Model) { raise CanCan::AccessDenied } resource = CanCan::ControllerResource.new(controller) expect { resource.authorize_resource }.to raise_error(CanCan::AccessDenied) end it 'authorizes parent resource in collection action' do controller.instance_variable_set(:@category, :some_category) allow(controller).to receive(:authorize!).with(:show, :some_category) { raise CanCan::AccessDenied } resource = CanCan::ControllerResource.new(controller, :category, parent: true) expect { resource.authorize_resource }.to raise_error(CanCan::AccessDenied) end it 'authorizes with :custom_action for parent collection action' do controller.instance_variable_set(:@category, :some_category) allow(controller).to receive(:authorize!).with(:custom_action, :some_category) { raise CanCan::AccessDenied } resource = CanCan::ControllerResource.new(controller, :category, parent: true, parent_action: :custom_action) expect { resource.authorize_resource }.to raise_error(CanCan::AccessDenied) end it 'has the specified nested resource_class when using / for namespace' do module Admin class Dashboard; end end ability.can(:index, 'admin/dashboard') params[:controller] = 'admin/dashboard' resource = CanCan::ControllerResource.new(controller, authorize: true) expect(resource.send(:resource_class)).to eq(Admin::Dashboard) end it 'does not build a single resource when on custom collection action even with id' do params.merge!(action: 'sort', id: '123') resource = CanCan::ControllerResource.new(controller, collection: %i[sort list]) resource.load_resource expect(controller.instance_variable_get(:@model)).to be_nil end it 'loads a collection resource when on custom action with no id param' do allow(Model).to receive(:accessible_by).with(ability, :sort) { :found_models } params[:action] = 'sort' resource = CanCan::ControllerResource.new(controller) resource.load_resource expect(controller.instance_variable_get(:@model)).to be_nil expect(controller.instance_variable_get(:@models)).to eq(:found_models) end it 'loads parent resource through proper id parameter' do model = Model.new allow(Model).to receive(:find).with('1') { model } params.merge!(controller: 'categories', model_id: 1) resource = CanCan::ControllerResource.new(controller, :model) resource.load_resource expect(controller.instance_variable_get(:@model)).to eq(model) end it 'authorizes nested resource through parent association on index action' do controller.instance_variable_set(:@category, category = double) allow(controller).to receive(:authorize!).with(:index, category => Model) { raise CanCan::AccessDenied } resource = CanCan::ControllerResource.new(controller, through: :category) expect { resource.authorize_resource }.to raise_error(CanCan::AccessDenied) end end context 'on instance read actions' do before :each do params.merge!(action: 'show', id: '123') end it 'loads the resource into an instance variable if params[:id] is specified' do model = Model.new allow(Model).to receive(:find).with('123') { model } resource = CanCan::ControllerResource.new(controller) resource.load_resource expect(controller.instance_variable_get(:@model)).to eq(model) end it 'does not load resource into an instance variable if already set' do controller.instance_variable_set(:@model, :some_model) resource = CanCan::ControllerResource.new(controller) resource.load_resource expect(controller.instance_variable_get(:@model)).to eq(:some_model) end it 'loads resource for namespaced controller' do model = Model.new allow(Model).to receive(:find).with('123') { model } params[:controller] = 'admin/models' resource = CanCan::ControllerResource.new(controller) resource.load_resource expect(controller.instance_variable_get(:@model)).to eq(model) end it 'performs authorization using controller action and loaded model' do controller.instance_variable_set(:@model, :some_model) allow(controller).to receive(:authorize!).with(:show, :some_model) { raise CanCan::AccessDenied } resource = CanCan::ControllerResource.new(controller) expect { resource.authorize_resource }.to raise_error(CanCan::AccessDenied) end it 'performs authorization using controller action and non loaded model' do allow(controller).to receive(:authorize!).with(:show, Model) { raise CanCan::AccessDenied } resource = CanCan::ControllerResource.new(controller) expect { resource.authorize_resource }.to raise_error(CanCan::AccessDenied) end it 'calls load_resource and authorize_resource for load_and_authorize_resource' do resource = CanCan::ControllerResource.new(controller) expect(resource).to receive(:load_resource) expect(resource).to receive(:authorize_resource) resource.load_and_authorize_resource end it 'loads resource through the association of another parent resource using instance variable' do category = double(models: {}) controller.instance_variable_set(:@category, category) allow(category.models).to receive(:find).with('123') { :some_model } resource = CanCan::ControllerResource.new(controller, through: :category) resource.load_resource expect(controller.instance_variable_get(:@model)).to eq(:some_model) end it 'loads resource through the custom association name' do category = double(custom_models: {}) controller.instance_variable_set(:@category, category) allow(category.custom_models).to receive(:find).with('123') { :some_model } resource = CanCan::ControllerResource.new(controller, through: :category, through_association: :custom_models) resource.load_resource expect(controller.instance_variable_get(:@model)).to eq(:some_model) end it 'loads resource through the association of another parent resource using method' do category = double(models: {}) allow(controller).to receive(:category) { category } allow(category.models).to receive(:find).with('123') { :some_model } resource = CanCan::ControllerResource.new(controller, through: :category) resource.load_resource expect(controller.instance_variable_get(:@model)).to eq(:some_model) end it "does not load through parent resource if instance isn't loaded when shallow" do model = Model.new allow(Model).to receive(:find).with('123') { model } resource = CanCan::ControllerResource.new(controller, through: :category, shallow: true) resource.load_resource expect(controller.instance_variable_get(:@model)).to eq(model) end it 'raises AccessDenied when attempting to load resource through nil' do resource = CanCan::ControllerResource.new(controller, through: :category) expect do resource.load_resource end.to raise_error(CanCan::AccessDenied) { |exception| expect(exception.action).to eq(:show) expect(exception.subject).to eq(Model) } expect(controller.instance_variable_get(:@model)).to be_nil end it 'loads through first matching if multiple are given' do category = double(models: {}) controller.instance_variable_set(:@category, category) allow(category.models).to receive(:find).with('123') { :some_model } resource = CanCan::ControllerResource.new(controller, through: %i[category user]) resource.load_resource expect(controller.instance_variable_get(:@model)).to eq(:some_model) end it 'finds record through has_one association with :singleton option without id param' do params[:id] = nil category = double(model: :some_model) controller.instance_variable_set(:@category, category) resource = CanCan::ControllerResource.new(controller, through: :category, singleton: true) resource.load_resource expect(controller.instance_variable_get(:@model)).to eq(:some_model) end it 'does not try to load resource for other action if params[:id] is undefined' do params.merge!(action: 'list', id: nil) resource = CanCan::ControllerResource.new(controller) resource.load_resource expect(controller.instance_variable_get(:@model)).to be_nil end it 'does not try to load resource for other action if params[:id] is blank' do params.merge!(action: 'list', id: '') resource = CanCan::ControllerResource.new(controller) resource.load_resource expect(controller.instance_variable_get(:@model)).to be_nil end it 'finds record through has_one association with :singleton and :shallow options' do model = Model.new allow(Model).to receive(:find).with('123') { model } resource = CanCan::ControllerResource.new(controller, through: :category, singleton: true, shallow: true) resource.load_resource expect(controller.instance_variable_get(:@model)).to eq(model) end it 'loads the model using a custom class' do model = Model.new allow(Model).to receive(:find).with('123') { model } resource = CanCan::ControllerResource.new(controller, class: Model) resource.load_resource expect(controller.instance_variable_get(:@model)).to eq(model) end it 'loads the model using a custom namespaced class' do module Sub class Model < ::Model; end end model = Sub::Model.new allow(Sub::Model).to receive(:find).with('123') { model } resource = CanCan::ControllerResource.new(controller, class: ::Sub::Model) resource.load_resource expect(controller.instance_variable_get(:@model)).to eq(model) end it 'authorizes based on resource name if class is false' do allow(controller).to receive(:authorize!).with(:show, :model) { raise CanCan::AccessDenied } resource = CanCan::ControllerResource.new(controller, class: false) expect { resource.authorize_resource }.to raise_error(CanCan::AccessDenied) end it 'loads and authorize using custom instance name' do model = Model.new allow(Model).to receive(:find).with('123') { model } allow(controller).to receive(:authorize!).with(:show, model) { raise CanCan::AccessDenied } resource = CanCan::ControllerResource.new(controller, instance_name: :custom_model) expect { resource.load_and_authorize_resource }.to raise_error(CanCan::AccessDenied) expect(controller.instance_variable_get(:@custom_model)).to eq(model) end it 'loads resource using custom ID param' do model = Model.new allow(Model).to receive(:find).with('123') { model } params[:the_model] = 123 resource = CanCan::ControllerResource.new(controller, id_param: :the_model) resource.load_resource expect(controller.instance_variable_get(:@model)).to eq(model) end # CVE-2012-5664 it 'always converts id param to string' do params[:the_model] = { malicious: 'I am' } resource = CanCan::ControllerResource.new(controller, id_param: :the_model) expect(resource.send(:id_param).class).to eq(String) end it 'should id param return nil if param is nil' do params[:the_model] = nil resource = CanCan::ControllerResource.new(controller, id_param: :the_model) expect(resource.send(:id_param)).to be_nil end it 'loads resource using ActiveRecord find_by method' do model = Model.new allow(Model).to receive(:name).with('foo') { model } params.merge!(action: 'show', id: 'foo') resource = CanCan::ControllerResource.new(controller, find_by: :name) resource.load_resource expect(controller.instance_variable_get(:@model)).to eq(model) end it 'loads resource using custom find_by method' do model = Model.new allow(Model).to receive(:find_by).with(name: 'foo') { model } params.merge!(action: 'show', id: 'foo') resource = CanCan::ControllerResource.new(controller, find_by: :name) resource.load_resource expect(controller.instance_variable_get(:@model)).to eq(model) end it 'loads resource using dynamic finder method' do model = Model.new allow(Model).to receive(:find_by_name!).with('foo') { model } params.merge!(action: 'show', id: 'foo') resource = CanCan::ControllerResource.new(controller, find_by: :name) resource.load_resource expect(controller.instance_variable_get(:@model)).to eq(model) end it 'allows full find method to be passed into find_by option' do model = Model.new allow(Model).to receive(:find_by_name).with('foo') { model } params.merge!(action: 'show', id: 'foo') resource = CanCan::ControllerResource.new(controller, find_by: :find_by_name) resource.load_resource expect(controller.instance_variable_get(:@model)).to eq(model) end end context 'when @name passed as symbol' do it 'returns namespaced #resource_class' do module Admin; end class Admin::Dashboard; end params[:controller] = 'admin/dashboard' resource = CanCan::ControllerResource.new(controller, :dashboard) expect(resource.send(:resource_class)).to eq Admin::Dashboard end end it 'calls the santitizer when the parameter hash matches our object' do params.merge!(action: 'create', model: { name: 'test' }) allow(controller).to receive(:create_params).and_return({}) resource = CanCan::ControllerResource.new(controller) resource.load_resource expect(controller.instance_variable_get(:@model).name).to eq nil end it 'santitizes correctly when the instance name is overriden' do params.merge!(action: 'create', custom_name: { name: 'foobar' }) allow(controller).to receive(:create_params).and_return({}) resource = CanCan::ControllerResource.new(controller, instance_name: :custom_name) resource.load_resource expect(controller.instance_variable_get(:@custom_name).name).to eq nil end it 'calls the santitize method on non-save actions when required' do params.merge!(action: 'new', model: { name: 'test' }) allow(controller).to receive(:resource_params).and_return({}) resource = CanCan::ControllerResource.new(controller) resource.load_resource expect(controller.instance_variable_get(:@model).name).to eq nil end it "doesn't sanitize parameters on non-save actions when not required" do params.merge!(action: 'new', not_our_model: { name: 'test' }) allow(controller).to receive(:resource_params).and_raise resource = CanCan::ControllerResource.new(controller) expect do resource.load_resource end.to_not raise_error end it "is a parent resource when name is provided which doesn't match controller" do resource = CanCan::ControllerResource.new(controller, :category) expect(resource).to be_parent end it 'does not be a parent resource when name is provided which matches controller' do resource = CanCan::ControllerResource.new(controller, :model) expect(resource).to_not be_parent end it 'is parent if specified in options' do resource = CanCan::ControllerResource.new(controller, :model, parent: true) expect(resource).to be_parent end it 'does not be parent if specified in options' do resource = CanCan::ControllerResource.new(controller, :category, parent: false) expect(resource).to_not be_parent end it "has the specified resource_class if 'name' is passed to load_resource" do class Section; end resource = CanCan::ControllerResource.new(controller, :section) expect(resource.send(:resource_class)).to eq(Section) end it 'skips resource behavior for :only actions in array' do allow(controller_class).to receive(:cancan_skipper) { { load: { nil => { only: %i[index show] } } } } params[:action] = 'index' expect(CanCan::ControllerResource.new(controller).skip?(:load)).to be(true) expect(CanCan::ControllerResource.new(controller, :some_resource).skip?(:load)).to be(false) params[:action] = 'show' expect(CanCan::ControllerResource.new(controller).skip?(:load)).to be(true) params[:action] = 'other_action' expect(CanCan::ControllerResource.new(controller).skip?(:load)).to be_falsey end it 'skips resource behavior for :only one action on resource' do allow(controller_class).to receive(:cancan_skipper) { { authorize: { model: { only: :index } } } } params[:action] = 'index' expect(CanCan::ControllerResource.new(controller).skip?(:authorize)).to be(false) expect(CanCan::ControllerResource.new(controller, :model).skip?(:authorize)).to be(true) params[:action] = 'other_action' expect(CanCan::ControllerResource.new(controller, :model).skip?(:authorize)).to be_falsey end it 'skips resource behavior :except actions in array' do allow(controller_class).to receive(:cancan_skipper) { { load: { nil => { except: %i[index show] } } } } params[:action] = 'index' expect(CanCan::ControllerResource.new(controller).skip?(:load)).to be_falsey params[:action] = 'show' expect(CanCan::ControllerResource.new(controller).skip?(:load)).to be_falsey params[:action] = 'other_action' expect(CanCan::ControllerResource.new(controller).skip?(:load)).to be(true) expect(CanCan::ControllerResource.new(controller, :some_resource).skip?(:load)).to be(false) end it 'skips resource behavior :except one action on resource' do allow(controller_class).to receive(:cancan_skipper) { { authorize: { model: { except: :index } } } } params[:action] = 'index' expect(CanCan::ControllerResource.new(controller, :model).skip?(:authorize)).to be_falsey params[:action] = 'other_action' expect(CanCan::ControllerResource.new(controller).skip?(:authorize)).to be(false) expect(CanCan::ControllerResource.new(controller, :model).skip?(:authorize)).to be(true) end it 'skips loading and authorization' do allow(controller_class).to receive(:cancan_skipper) { { authorize: { nil => {} }, load: { nil => {} } } } params[:action] = 'new' resource = CanCan::ControllerResource.new(controller) expect { resource.load_and_authorize_resource }.not_to raise_error expect(controller.instance_variable_get(:@model)).to be_nil end context 'when model name is Action' do let(:action_params) { HashWithIndifferentAccess.new(controller: 'actions') } let(:action_controller_class) { Class.new } let(:action_controller) { controller_class.new } before :each do class Action attr_accessor :name def initialize(attributes = {}) attributes.each do |attribute, value| send("#{attribute}=", value) end end end allow(action_controller).to receive(:params) { action_params } allow(action_controller).to receive(:current_ability) { ability } allow(action_controller_class).to receive(:cancan_skipper) { { authorize: {}, load: {} } } end it 'builds a new resource with attributes from current ability' do action_params[:action] = 'new' ability.can(:create, Action, name: 'from conditions') resource = CanCan::ControllerResource.new(action_controller) resource.load_resource expect(action_controller.instance_variable_get(:@action).name).to eq('from conditions') end end end cancancan-3.0.1/spec/cancan/exceptions_spec.rb000066400000000000000000000037611345516370100213310ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' describe CanCan::AccessDenied do describe 'with action, subject, and conditions' do before(:each) do @exception = CanCan::AccessDenied.new(nil, :some_action, :some_subject, :some_conditions) end it 'has action, subject, and conditions accessors' do expect(@exception.action).to eq(:some_action) expect(@exception.subject).to eq(:some_subject) expect(@exception.conditions).to eq(:some_conditions) end it 'has a changable default message' do expect(@exception.message).to eq('You are not authorized to access this page.') @exception.default_message = 'Unauthorized!' expect(@exception.message).to eq('Unauthorized!') end end describe 'with only a message' do before(:each) do @exception = CanCan::AccessDenied.new('Access denied!') end it 'has nil action, subject, and conditions' do expect(@exception.action).to be_nil expect(@exception.subject).to be_nil expect(@exception.conditions).to be_nil end it 'has passed message' do expect(@exception.message).to eq('Access denied!') end end describe 'i18n in the default message' do after(:each) do I18n.backend = nil end it 'uses i18n for the default message' do I18n.backend.store_translations :en, unauthorized: { default: 'This is a different message' } @exception = CanCan::AccessDenied.new expect(@exception.message).to eq('This is a different message') end it 'defaults to a nice message' do @exception = CanCan::AccessDenied.new expect(@exception.message).to eq('You are not authorized to access this page.') end it 'does not use translation if a message is given' do @exception = CanCan::AccessDenied.new("Hey! You're not welcome here") expect(@exception.message).to eq("Hey! You're not welcome here") expect(@exception.message).to_not eq('You are not authorized to access this page.') end end end cancancan-3.0.1/spec/cancan/matchers_spec.rb000066400000000000000000000037111345516370100207510ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' describe 'be_able_to' do subject { double } context 'check single ability' do it 'delegates to can?' do is_expected.to receive(:can?).with(:read, 123) { true } is_expected.to be_able_to(:read, 123) end it 'reports a nice failure message for should' do is_expected.to receive(:can?).with(:read, 123) { false } expect do is_expected.to be_able_to(:read, 123) end.to raise_error('expected to be able to :read 123') end it 'reports a nice failure message for should not' do is_expected.to receive(:can?).with(:read, 123) { true } expect do is_expected.to_not be_able_to(:read, 123) end.to raise_error('expected not to be able to :read 123') end it 'delegates additional arguments to can? and reports in failure message' do is_expected.to receive(:can?).with(:read, 123, 456) { false } expect do is_expected.to be_able_to(:read, 123, 456) end.to raise_error('expected to be able to :read 123 456') end end context 'check array of abilities' do it 'delegates to can? with array of abilities with one action' do is_expected.to receive(:can?).with(:read, 123) { true } is_expected.to be_able_to([:read], 123) end it 'delegates to can? with array of abilities with multiple actions' do is_expected.to receive(:can?).with(:read, 123) { true } is_expected.to receive(:can?).with(:update, 123) { true } is_expected.to be_able_to(%i[read update], 123) end it 'delegates to can? with array of abilities with empty array' do is_expected.not_to be_able_to([], 123) end it 'delegates to can? with array of abilities with only one eligable ability' do is_expected.to receive(:can?).with(:read, 123) { true } is_expected.to receive(:can?).with(:update, 123) { false } is_expected.not_to be_able_to(%i[read update], 123) end end end cancancan-3.0.1/spec/cancan/model_adapters/000077500000000000000000000000001345516370100205655ustar00rootroot00000000000000cancancan-3.0.1/spec/cancan/model_adapters/accessible_by_has_many_through_spec.rb000066400000000000000000000051721345516370100303370ustar00rootroot00000000000000require 'spec_helper' # integration tests for latest ActiveRecord version. RSpec.describe CanCan::ModelAdapters::ActiveRecord5Adapter do let(:ability) { double.extend(CanCan::Ability) } let(:users_table) { Post.table_name } let(:posts_table) { Post.table_name } let(:likes_table) { Like.table_name } before :each do connect_db ActiveRecord::Migration.verbose = false ActiveRecord::Schema.define do create_table(:users) do |t| t.string :name t.timestamps null: false end create_table(:posts) do |t| t.string :title t.boolean :published, default: true t.integer :user_id t.timestamps null: false end create_table(:likes) do |t| t.integer :post_id t.integer :user_id t.timestamps null: false end create_table(:editors) do |t| t.integer :post_id t.integer :user_id t.timestamps null: false end end class User < ActiveRecord::Base has_many :posts has_many :likes has_many :editors end class Post < ActiveRecord::Base belongs_to :user has_many :likes has_many :editors end class Like < ActiveRecord::Base belongs_to :user belongs_to :post end class Editor < ActiveRecord::Base belongs_to :user belongs_to :post end end before do @user1 = User.create! @user2 = User.create! @post1 = Post.create!(title: 'post1', user: @user1) @post2 = Post.create!(user: @user1, published: false) @post3 = Post.create!(user: @user2) @like1 = Like.create!(post: @post1, user: @user1) @like2 = Like.create!(post: @post1, user: @user2) @editor1 = Editor.create(user: @user1, post: @post2) ability.can :read, Post, user_id: @user1 ability.can :read, Post, editors: { user_id: @user1 } end describe 'preloading of associatons' do it 'preloads associations correctly' do posts = Post.accessible_by(ability).includes(likes: :user) expect(posts[0].association(:likes)).to be_loaded expect(posts[0].likes[0].association(:user)).to be_loaded end end describe 'filtering of results' do it 'adds the where clause correctly' do posts = Post.accessible_by(ability).where(published: true) expect(posts.length).to eq 1 end end if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0') describe 'selecting custom columns' do it 'extracts custom columns correctly' do posts = Post.accessible_by(ability).select('title as mytitle') expect(posts[0].mytitle).to eq 'post1' end end end end cancancan-3.0.1/spec/cancan/model_adapters/accessible_by_integration_spec.rb000066400000000000000000000052311345516370100273170ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' # integration tests for latest ActiveRecord version. RSpec.describe CanCan::ModelAdapters::ActiveRecord5Adapter do let(:ability) { double.extend(CanCan::Ability) } let(:users_table) { Post.table_name } let(:posts_table) { Post.table_name } let(:likes_table) { Like.table_name } before :each do connect_db ActiveRecord::Migration.verbose = false ActiveRecord::Schema.define do create_table(:users) do |t| t.string :name t.timestamps null: false end create_table(:posts) do |t| t.string :title t.boolean :published, default: true t.integer :user_id t.timestamps null: false end create_table(:likes) do |t| t.integer :post_id t.integer :user_id t.timestamps null: false end create_table(:editors) do |t| t.integer :post_id t.integer :user_id t.timestamps null: false end end class User < ActiveRecord::Base has_many :posts has_many :likes has_many :editors end class Post < ActiveRecord::Base belongs_to :user has_many :likes has_many :editors end class Like < ActiveRecord::Base belongs_to :user belongs_to :post end class Editor < ActiveRecord::Base belongs_to :user belongs_to :post end end before do @user1 = User.create! @user2 = User.create! @post1 = Post.create!(title: 'post1', user: @user1) @post2 = Post.create!(user: @user1, published: false) @post3 = Post.create!(user: @user2) @like1 = Like.create!(post: @post1, user: @user1) @like2 = Like.create!(post: @post1, user: @user2) @editor1 = Editor.create(user: @user1, post: @post2) ability.can :read, Post, user_id: @user1 ability.can :read, Post, editors: { user_id: @user1 } end describe 'preloading of associatons' do it 'preloads associations correctly' do posts = Post.accessible_by(ability).includes(likes: :user) expect(posts[0].association(:likes)).to be_loaded expect(posts[0].likes[0].association(:user)).to be_loaded end end describe 'filtering of results' do it 'adds the where clause correctly' do posts = Post.accessible_by(ability).where(published: true) expect(posts.length).to eq 1 end end if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0') describe 'selecting custom columns' do it 'extracts custom columns correctly' do posts = Post.accessible_by(ability).select('title as mytitle') expect(posts[0].mytitle).to eq 'post1' end end end end cancancan-3.0.1/spec/cancan/model_adapters/active_record_4_adapter_spec.rb000066400000000000000000000122131345516370100266570ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' if CanCan::ModelAdapters::ActiveRecordAdapter.version_lower?('5.0.0') describe CanCan::ModelAdapters::ActiveRecord4Adapter do context 'with sqlite3' do before :each do connect_db ActiveRecord::Migration.verbose = false ActiveRecord::Schema.define do create_table(:parents) do |t| t.timestamps null: false end create_table(:children) do |t| t.timestamps null: false t.integer :parent_id end end class Parent < ActiveRecord::Base has_many :children, -> { order(id: :desc) } end class Child < ActiveRecord::Base belongs_to :parent end (@ability = double).extend(CanCan::Ability) end it 'respects scope on included associations' do @ability.can :read, [Parent, Child] parent = Parent.create! child1 = Child.create!(parent: parent, created_at: 1.hours.ago) child2 = Child.create!(parent: parent, created_at: 2.hours.ago) expect(Parent.accessible_by(@ability).order(created_at: :asc).includes(:children).first.children) .to eq [child2, child1] end if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('4.1.0') it 'allows filters on enums' do ActiveRecord::Schema.define do create_table(:shapes) do |t| t.integer :color, default: 0, null: false end end class Shape < ActiveRecord::Base enum color: %i[red green blue] unless defined_enums.key? 'color' end red = Shape.create!(color: :red) green = Shape.create!(color: :green) blue = Shape.create!(color: :blue) # A condition with a single value. @ability.can :read, Shape, color: Shape.colors[:green] expect(@ability.cannot?(:read, red)).to be true expect(@ability.can?(:read, green)).to be true expect(@ability.cannot?(:read, blue)).to be true accessible = Shape.accessible_by(@ability) expect(accessible).to contain_exactly(green) # A condition with multiple values. @ability.can :update, Shape, color: [Shape.colors[:red], Shape.colors[:blue]] expect(@ability.can?(:update, red)).to be true expect(@ability.cannot?(:update, green)).to be true expect(@ability.can?(:update, blue)).to be true accessible = Shape.accessible_by(@ability, :update) expect(accessible).to contain_exactly(red, blue) end it 'allows dual filter on enums' do ActiveRecord::Schema.define do create_table(:discs) do |t| t.integer :color, default: 0, null: false t.integer :shape, default: 3, null: false end end class Disc < ActiveRecord::Base enum color: %i[red green blue] unless defined_enums.key? 'color' enum shape: { triangle: 3, rectangle: 4 } unless defined_enums.key? 'shape' end red_triangle = Disc.create!(color: Disc.colors[:red], shape: Disc.shapes[:triangle]) green_triangle = Disc.create!(color: Disc.colors[:green], shape: Disc.shapes[:triangle]) green_rectangle = Disc.create!(color: Disc.colors[:green], shape: Disc.shapes[:rectangle]) blue_rectangle = Disc.create!(color: Disc.colors[:blue], shape: Disc.shapes[:rectangle]) # A condition with a dual filter. @ability.can :read, Disc, color: Disc.colors[:green], shape: Disc.shapes[:rectangle] expect(@ability.cannot?(:read, red_triangle)).to be true expect(@ability.cannot?(:read, green_triangle)).to be true expect(@ability.can?(:read, green_rectangle)).to be true expect(@ability.cannot?(:read, blue_rectangle)).to be true accessible = Disc.accessible_by(@ability) expect(accessible).to contain_exactly(green_rectangle) end end end context 'with postgresql' do before :each do connect_db ActiveRecord::Migration.verbose = false ActiveRecord::Schema.define do create_table(:parents) do |t| t.timestamps null: false end create_table(:children) do |t| t.timestamps null: false t.integer :parent_id end end class Parent < ActiveRecord::Base has_many :children, -> { order(id: :desc) } end class Child < ActiveRecord::Base belongs_to :parent end (@ability = double).extend(CanCan::Ability) end it 'allows overlapping conditions in SQL and merge with hash conditions' do @ability.can :read, Parent, children: { parent_id: 1 } @ability.can :read, Parent, children: { parent_id: 1 } parent = Parent.create! Child.create!(parent: parent, created_at: 1.hours.ago) Child.create!(parent: parent, created_at: 2.hours.ago) expect(Parent.accessible_by(@ability)).to eq([parent]) end end end end cancancan-3.0.1/spec/cancan/model_adapters/active_record_5_adapter_spec.rb000066400000000000000000000122331345516370100266620ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0') describe CanCan::ModelAdapters::ActiveRecord5Adapter do context 'with sqlite3' do before :each do connect_db ActiveRecord::Migration.verbose = false ActiveRecord::Schema.define do create_table(:shapes) do |t| t.integer :color, default: 0, null: false end create_table(:things) do |t| t.string :size, default: 'big', null: false end create_table(:discs) do |t| t.integer :color, default: 0, null: false t.integer :shape, default: 3, null: false end end unless defined?(Thing) class Thing < ActiveRecord::Base enum size: { big: 'big', medium: 'average', small: 'small' } end end unless defined?(Shape) class Shape < ActiveRecord::Base enum color: %i[red green blue] end end unless defined?(Disc) class Disc < ActiveRecord::Base enum color: %i[red green blue] enum shape: { triangle: 3, rectangle: 4 } end end end subject(:ability) { Ability.new(nil) } context 'when enums use integers as values' do let(:red) { Shape.create!(color: :red) } let(:green) { Shape.create!(color: :green) } let(:blue) { Shape.create!(color: :blue) } context 'when the condition contains a single value' do before do ability.can :read, Shape, color: :green end it 'can check ability on single models' do is_expected.not_to be_able_to(:read, red) is_expected.to be_able_to(:read, green) is_expected.not_to be_able_to(:read, blue) end it 'can use accessible_by helper' do accessible = Shape.accessible_by(ability) expect(accessible).to contain_exactly(green) end end context 'when the condition contains multiple values' do before do ability.can :update, Shape, color: %i[red blue] end it 'can check ability on single models' do is_expected.to be_able_to(:update, red) is_expected.not_to be_able_to(:update, green) is_expected.to be_able_to(:update, blue) end it 'can use accessible_by helper' do accessible = Shape.accessible_by(ability, :update) expect(accessible).to contain_exactly(red, blue) end end end context 'when enums use strings as values' do let(:big) { Thing.create!(size: :big) } let(:medium) { Thing.create!(size: :medium) } let(:small) { Thing.create!(size: :small) } context 'when the condition contains a single value' do before do ability.can :read, Thing, size: :medium end it 'can check ability on single models' do is_expected.not_to be_able_to(:read, big) is_expected.to be_able_to(:read, medium) is_expected.not_to be_able_to(:read, small) end it 'can use accessible_by helper' do expect(Thing.accessible_by(ability)).to contain_exactly(medium) end context 'when a rule is overriden' do before do ability.cannot :read, Thing, size: 'average' end it 'is recognised correctly' do is_expected.not_to be_able_to(:read, medium) expect(Thing.accessible_by(ability)).to be_empty end end end context 'when the condition contains multiple values' do before do ability.can :update, Thing, size: %i[big small] end it 'can check ability on single models' do is_expected.to be_able_to(:update, big) is_expected.not_to be_able_to(:update, medium) is_expected.to be_able_to(:update, small) end it 'can use accessible_by helper' do expect(Thing.accessible_by(ability, :update)).to contain_exactly(big, small) end end end context 'when multiple enums are present' do let(:red_triangle) { Disc.create!(color: :red, shape: :triangle) } let(:green_triangle) { Disc.create!(color: :green, shape: :triangle) } let(:green_rectangle) { Disc.create!(color: :green, shape: :rectangle) } let(:blue_rectangle) { Disc.create!(color: :blue, shape: :rectangle) } before do ability.can :read, Disc, color: :green, shape: :rectangle end it 'can check ability on single models' do is_expected.not_to be_able_to(:read, red_triangle) is_expected.not_to be_able_to(:read, green_triangle) is_expected.to be_able_to(:read, green_rectangle) is_expected.not_to be_able_to(:read, blue_rectangle) end it 'can use accessible_by helper' do expect(Disc.accessible_by(ability)).to contain_exactly(green_rectangle) end end end end end cancancan-3.0.1/spec/cancan/model_adapters/active_record_adapter_spec.rb000066400000000000000000000473341345516370100264500ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' describe CanCan::ModelAdapters::ActiveRecordAdapter do let(:true_v) do ActiveRecord::Base.connection.quoted_true end let(:false_v) do ActiveRecord::Base.connection.quoted_false end let(:false_condition) { "#{true_v}=#{false_v}" } before :each do connect_db ActiveRecord::Migration.verbose = false ActiveRecord::Schema.define do create_table(:categories) do |t| t.string :name t.boolean :visible t.timestamps null: false end create_table(:projects) do |t| t.string :name t.timestamps null: false end create_table(:companies) do |t| t.boolean :admin end create_table(:articles) do |t| t.string :name t.timestamps null: false t.boolean :published t.boolean :secret t.integer :priority t.integer :category_id t.integer :project_id t.integer :user_id end create_table(:comments) do |t| t.boolean :spam t.integer :article_id t.integer :project_id t.timestamps null: false end create_table(:legacy_mentions) do |t| t.integer :user_id t.integer :article_id t.timestamps null: false end create_table(:users) do |t| t.string :name t.timestamps null: false end end class Project < ActiveRecord::Base has_many :comments end class Category < ActiveRecord::Base has_many :articles end class Company < ActiveRecord::Base end class Article < ActiveRecord::Base belongs_to :category belongs_to :company has_many :comments has_many :mentions has_many :mentioned_users, through: :mentions, source: :user belongs_to :user belongs_to :project end class Mention < ActiveRecord::Base self.table_name = 'legacy_mentions' belongs_to :user belongs_to :article end class Comment < ActiveRecord::Base belongs_to :article end class User < ActiveRecord::Base has_many :articles has_many :mentions has_many :mentioned_articles, through: :mentions, source: :article end (@ability = double).extend(CanCan::Ability) @article_table = Article.table_name @comment_table = Comment.table_name end it 'is for only active record classes' do if ActiveRecord.version > Gem::Version.new('5') expect(CanCan::ModelAdapters::ActiveRecord5Adapter).to_not be_for_class(Object) expect(CanCan::ModelAdapters::ActiveRecord5Adapter).to be_for_class(Article) expect(CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article)) .to eq(CanCan::ModelAdapters::ActiveRecord5Adapter) elsif ActiveRecord.version > Gem::Version.new('4') expect(CanCan::ModelAdapters::ActiveRecord4Adapter).to_not be_for_class(Object) expect(CanCan::ModelAdapters::ActiveRecord4Adapter).to be_for_class(Article) expect(CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article)) .to eq(CanCan::ModelAdapters::ActiveRecord4Adapter) end end it 'finds record' do article = Article.create! adapter = CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article) expect(adapter.find(Article, article.id)).to eq(article) end it 'does not fetch any records when no abilities are defined' do Article.create! expect(Article.accessible_by(@ability)).to be_empty end it 'fetches all articles when one can read all' do @ability.can :read, Article article = Article.create! expect(Article.accessible_by(@ability)).to match_array([article]) end it 'fetches only the articles that are published' do @ability.can :read, Article, published: true article1 = Article.create!(published: true) Article.create!(published: false) expect(Article.accessible_by(@ability)).to match_array([article1]) end it 'fetches any articles which are published or secret' do @ability.can :read, Article, published: true @ability.can :read, Article, secret: true article1 = Article.create!(published: true, secret: false) article2 = Article.create!(published: true, secret: true) article3 = Article.create!(published: false, secret: true) Article.create!(published: false, secret: false) expect(Article.accessible_by(@ability)).to match_array([article1, article2, article3]) end it 'fetches any articles which we are cited in' do user = User.create! cited = Article.create! Article.create! cited.mentioned_users << user @ability.can :read, Article, mentioned_users: { id: user.id } @ability.can :read, Article, mentions: { user_id: user.id } expect(Article.accessible_by(@ability)).to match_array([cited]) end it 'fetches only the articles that are published and not secret' do @ability.can :read, Article, published: true @ability.cannot :read, Article, secret: true article1 = Article.create!(published: true, secret: false) Article.create!(published: true, secret: true) Article.create!(published: false, secret: true) Article.create!(published: false, secret: false) expect(Article.accessible_by(@ability)).to match_array([article1]) end it 'only reads comments for articles which are published' do @ability.can :read, Comment, article: { published: true } comment1 = Comment.create!(article: Article.create!(published: true)) Comment.create!(article: Article.create!(published: false)) expect(Comment.accessible_by(@ability)).to match_array([comment1]) end it 'should only read articles which are published or in visible categories' do @ability.can :read, Article, category: { visible: true } @ability.can :read, Article, published: true article1 = Article.create!(published: true) Article.create!(published: false) article3 = Article.create!(published: false, category: Category.create!(visible: true)) expect(Article.accessible_by(@ability)).to match_array([article1, article3]) end it 'should only read categories once even if they have multiple articles' do @ability.can :read, Category, articles: { published: true } @ability.can :read, Article, published: true category = Category.create! Article.create!(published: true, category: category) Article.create!(published: true, category: category) expect(Category.accessible_by(@ability)).to match_array([category]) end it 'only reads comments for visible categories through articles' do @ability.can :read, Comment, article: { category: { visible: true } } comment1 = Comment.create!(article: Article.create!(category: Category.create!(visible: true))) Comment.create!(article: Article.create!(category: Category.create!(visible: false))) expect(Comment.accessible_by(@ability)).to match_array([comment1]) end it 'allows conditions in SQL and merge with hash conditions' do @ability.can :read, Article, published: true @ability.can :read, Article, ['secret=?', true] article1 = Article.create!(published: true, secret: false) article2 = Article.create!(published: true, secret: true) article3 = Article.create!(published: false, secret: true) Article.create!(published: false, secret: false) expect(Article.accessible_by(@ability)).to match_array([article1, article2, article3]) end it 'allows a scope for conditions' do @ability.can :read, Article, Article.where(secret: true) article1 = Article.create!(secret: true) Article.create!(secret: false) expect(Article.accessible_by(@ability)).to match_array([article1]) end it 'fetches only associated records when using with a scope for conditions' do @ability.can :read, Article, Article.where(secret: true) category1 = Category.create!(visible: false) category2 = Category.create!(visible: true) article1 = Article.create!(secret: true, category: category1) Article.create!(secret: true, category: category2) expect(category1.articles.accessible_by(@ability)).to match_array([article1]) end it 'raises an exception when trying to merge scope with other conditions' do @ability.can :read, Article, published: true @ability.can :read, Article, Article.where(secret: true) expect(-> { Article.accessible_by(@ability) }) .to raise_error(CanCan::Error, 'Unable to merge an Active Record scope with other conditions. '\ 'Instead use a hash or SQL for read Article ability.') end it 'does not raise an exception when the rule with scope is suppressed' do @ability.can :read, Article, published: true @ability.can :read, Article, Article.where(secret: true) @ability.cannot :read, Article expect(-> { Article.accessible_by(@ability) }).not_to raise_error end it 'recognises empty scopes and compresses them' do @ability.can :read, Article, published: true @ability.can :read, Article, Article.all expect(-> { Article.accessible_by(@ability) }).not_to raise_error end it 'does not allow to fetch records when ability with just block present' do @ability.can :read, Article do false end expect(-> { Article.accessible_by(@ability) }).to raise_error(CanCan::Error) end it 'should support more than one deeply nested conditions' do @ability.can :read, Comment, article: { category: { name: 'foo', visible: true } } expect { Comment.accessible_by(@ability) }.to_not raise_error end it 'does not allow to check ability on object against SQL conditions without block' do @ability.can :read, Article, ['secret=?', true] expect(-> { @ability.can? :read, Article.new }).to raise_error(CanCan::Error) end it 'has false conditions if no abilities match' do expect(@ability.model_adapter(Article, :read).conditions).to eq(false_condition) end it 'returns false conditions for cannot clause' do @ability.cannot :read, Article expect(@ability.model_adapter(Article, :read).conditions).to eq(false_condition) end it 'returns SQL for single `can` definition in front of default `cannot` condition' do @ability.cannot :read, Article @ability.can :read, Article, published: false, secret: true expect(@ability.model_adapter(Article, :read)).to generate_sql(%( SELECT "articles".* FROM "articles" WHERE "articles"."published" = #{false_v} AND "articles"."secret" = #{true_v})) end it 'returns true condition for single `can` definition in front of default `can` condition' do @ability.can :read, Article @ability.can :read, Article, published: false, secret: true expect(@ability.model_adapter(Article, :read).conditions).to eq({}) expect(@ability.model_adapter(Article, :read)).to generate_sql(%(SELECT "articles".* FROM "articles")) end it 'returns `false condition` for single `cannot` definition in front of default `cannot` condition' do @ability.cannot :read, Article @ability.cannot :read, Article, published: false, secret: true expect(@ability.model_adapter(Article, :read).conditions).to eq(false_condition) end it 'returns `not (sql)` for single `cannot` definition in front of default `can` condition' do @ability.can :read, Article @ability.cannot :read, Article, published: false, secret: true expect(@ability.model_adapter(Article, :read).conditions) .to orderlessly_match( %["not (#{@article_table}"."published" = #{false_v} AND "#{@article_table}"."secret" = #{true_v})] ) end it 'returns appropriate sql conditions in complex case' do @ability.can :read, Article @ability.can :manage, Article, id: 1 @ability.can :update, Article, published: true @ability.cannot :update, Article, secret: true expect(@ability.model_adapter(Article, :update).conditions) .to eq(%[not ("#{@article_table}"."secret" = #{true_v}) ] + %[AND (("#{@article_table}"."published" = #{true_v}) ] + %[OR ("#{@article_table}"."id" = 1))]) expect(@ability.model_adapter(Article, :manage).conditions).to eq(id: 1) expect(@ability.model_adapter(Article, :read).conditions).to eq({}) expect(@ability.model_adapter(Article, :read)).to generate_sql(%(SELECT "articles".* FROM "articles")) end it 'returns appropriate sql conditions in complex case with nested joins' do @ability.can :read, Comment, article: { category: { visible: true } } expect(@ability.model_adapter(Comment, :read).conditions).to eq(Category.table_name.to_sym => { visible: true }) end it 'returns appropriate sql conditions in complex case with nested joins of different depth' do @ability.can :read, Comment, article: { published: true, category: { visible: true } } expect(@ability.model_adapter(Comment, :read).conditions) .to eq(Article.table_name.to_sym => { published: true }, Category.table_name.to_sym => { visible: true }) end it 'does not forget conditions when calling with SQL string' do @ability.can :read, Article, published: true @ability.can :read, Article, ['secret = ?', false] adapter = @ability.model_adapter(Article, :read) 2.times do expect(adapter.conditions).to eq(%[(secret = #{false_v}) OR ("#{@article_table}"."published" = #{true_v})]) end end it 'has nil joins if no rules' do expect(@ability.model_adapter(Article, :read).joins).to be_nil end it 'has nil joins if rules got compressed' do @ability.can :read, Comment, article: { category: { visible: true } } @ability.can :read, Comment expect(@ability.model_adapter(Comment, :read)) .to generate_sql("SELECT \"#{@comment_table}\".* FROM \"#{@comment_table}\"") expect(@ability.model_adapter(Comment, :read).joins).to be_nil end it 'has nil joins if no nested hashes specified in conditions' do @ability.can :read, Article, published: false @ability.can :read, Article, secret: true expect(@ability.model_adapter(Article, :read).joins).to be_nil end it 'merges separate joins into a single array' do @ability.can :read, Article, project: { blocked: false } @ability.can :read, Article, company: { admin: true } expect(@ability.model_adapter(Article, :read).joins.inspect).to orderlessly_match(%i[company project].inspect) end it 'merges same joins into a single array' do @ability.can :read, Article, project: { blocked: false } @ability.can :read, Article, project: { admin: true } expect(@ability.model_adapter(Article, :read).joins).to eq([:project]) end it 'merges nested and non-nested joins' do @ability.can :read, Article, project: { blocked: false } @ability.can :read, Article, project: { comments: { spam: true } } expect(@ability.model_adapter(Article, :read).joins).to eq([{ project: [:comments] }]) end it 'merges :all conditions with other conditions' do user = User.create! article = Article.create!(user: user) ability = Ability.new(user) ability.can :manage, :all ability.can :manage, Article, user_id: user.id expect(Article.accessible_by(ability)).to eq([article]) end it 'should not execute a scope when checking ability on the class' do relation = Article.where(secret: true) @ability.can :read, Article, relation do |article| article.secret == true end allow(relation).to receive(:count).and_raise('Unexpected scope execution.') expect { @ability.can? :read, Article }.not_to raise_error end it 'should ignore cannot rules with attributes when querying' do user = User.create! article = Article.create!(user: user) ability = Ability.new(user) ability.can :read, Article ability.cannot :read, Article, :secret expect(Article.accessible_by(ability)).to eq([article]) end context 'with namespaced models' do before :each do ActiveRecord::Schema.define do create_table(:table_xes) do |t| t.timestamps null: false end create_table(:table_zs) do |t| t.integer :table_x_id t.integer :user_id t.timestamps null: false end end module Namespace end class Namespace::TableX < ActiveRecord::Base has_many :table_zs end class Namespace::TableZ < ActiveRecord::Base belongs_to :table_x belongs_to :user end end it 'fetches all namespace::table_x when one is related by table_y' do user = User.create! ability = Ability.new(user) ability.can :read, Namespace::TableX, table_zs: { user_id: user.id } table_x = Namespace::TableX.create! table_x.table_zs.create(user: user) expect(Namespace::TableX.accessible_by(ability)).to match_array([table_x]) end end context 'when conditions are non iterable ranges' do before :each do ActiveRecord::Schema.define do create_table(:courses) do |t| t.datetime :start_at end end class Course < ActiveRecord::Base end end it 'fetches only the valid records' do @ability.can :read, Course, start_at: 1.day.ago..1.day.from_now Course.create!(start_at: 10.days.ago) valid_course = Course.create!(start_at: Time.now) expect(Course.accessible_by(@ability)).to match_array([valid_course]) end end context 'when a table references another one twice' do before do ActiveRecord::Schema.define do create_table(:transactions) do |t| t.integer :sender_id t.integer :receiver_id end end class Transaction < ActiveRecord::Base belongs_to :sender, class_name: 'User', foreign_key: :sender_id belongs_to :receiver, class_name: 'User', foreign_key: :receiver_id end end it 'can filter correctly on both associations' do sender = User.create! receiver = User.create! t1 = Transaction.create!(sender: sender, receiver: receiver) t2 = Transaction.create!(sender: receiver, receiver: sender) ability = Ability.new(sender) ability.can :read, Transaction, sender: { id: sender.id } ability.can :read, Transaction, receiver: { id: sender.id } expect(Transaction.accessible_by(ability)).to match_array([t1, t2]) end end context 'when a table is references multiple times' do it 'can filter correctly on the different associations' do u1 = User.create!(name: 'pippo') u2 = User.create!(name: 'paperino') a1 = Article.create!(user: u1) a2 = Article.create!(user: u2) ability = Ability.new(u1) ability.can :read, Article, user: { id: u1.id } ability.can :read, Article, mentioned_users: { name: u1.name } ability.can :read, Article, mentioned_users: { mentioned_articles: { id: a2.id } } ability.can :read, Article, mentioned_users: { articles: { user: { name: 'deep' } } } ability.can :read, Article, mentioned_users: { articles: { mentioned_users: { name: 'd2' } } } expect(Article.accessible_by(ability)).to match_array([a1]) end end context 'has_many through is defined and referenced differently' do it 'recognises it and simplifies the query' do u1 = User.create!(name: 'pippo') u2 = User.create!(name: 'paperino') a1 = Article.create!(mentioned_users: [u1]) a2 = Article.create!(mentioned_users: [u2]) ability = Ability.new(u1) ability.can :read, Article, mentioned_users: { name: u1.name } ability.can :read, Article, mentions: { user: { name: u2.name } } expect(Article.accessible_by(ability)).to match_array([a1, a2]) if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0') expect(ability.model_adapter(Article, :read)).to generate_sql(%( SELECT DISTINCT "articles".* FROM "articles" LEFT OUTER JOIN "legacy_mentions" ON "legacy_mentions"."article_id" = "articles"."id" LEFT OUTER JOIN "users" ON "users"."id" = "legacy_mentions"."user_id" WHERE (("users"."name" = 'paperino') OR ("users"."name" = 'pippo')))) end end end end cancancan-3.0.1/spec/cancan/model_adapters/conditions_extractor_spec.rb000066400000000000000000000117121345516370100263720ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' RSpec.describe CanCan::ModelAdapters::ConditionsExtractor do before do connect_db ActiveRecord::Migration.verbose = false ActiveRecord::Schema.define do create_table(:categories) do |t| t.string :name t.boolean :visible t.timestamps null: false end create_table(:projects) do |t| t.string :name t.timestamps null: false end create_table(:articles) do |t| t.string :name t.timestamps null: false t.boolean :published t.boolean :secret t.integer :priority t.integer :category_id t.integer :user_id end create_table(:comments) do |t| t.boolean :spam t.integer :article_id t.timestamps null: false end create_table(:legacy_mentions) do |t| t.integer :user_id t.integer :article_id t.timestamps null: false end create_table(:users) do |t| t.timestamps null: false end create_table(:transactions) do |t| t.integer :sender_id t.integer :receiver_id t.integer :supervisor_id end end class Project < ActiveRecord::Base end class Category < ActiveRecord::Base has_many :articles end class Article < ActiveRecord::Base belongs_to :category has_many :comments has_many :mentions has_many :mentioned_users, through: :mentions, source: :user belongs_to :user end class Mention < ActiveRecord::Base self.table_name = 'legacy_mentions' belongs_to :user belongs_to :article end class Comment < ActiveRecord::Base belongs_to :article end class User < ActiveRecord::Base has_many :articles has_many :mentions has_many :mentioned_articles, through: :mentions, source: :article end class Transaction < ActiveRecord::Base belongs_to :sender, class_name: 'User', foreign_key: :sender_id belongs_to :receiver, class_name: 'User', foreign_key: :receiver_id belongs_to :supervisor, class_name: 'User', foreign_key: :supervisor_id end end describe 'converts hash of conditions into database sql where format' do it 'converts a simple association' do conditions = described_class.new(User).tableize_conditions(articles: { id: 1 }) expect(conditions).to eq(articles: { id: 1 }) end it 'converts a nested association' do conditions = described_class.new(User).tableize_conditions(articles: { category: { id: 1 } }) expect(conditions).to eq(categories: { id: 1 }) end it 'converts two associations' do conditions = described_class.new(User).tableize_conditions(articles: { id: 2, category: { id: 1 } }) expect(conditions).to eq(articles: { id: 2 }, categories: { id: 1 }) end it 'converts has_many through' do conditions = described_class.new(Article).tableize_conditions(mentioned_users: { id: 1 }) expect(conditions).to eq(users: { id: 1 }) end it 'converts associations named differently from the table' do conditions = described_class.new(Transaction).tableize_conditions(sender: { id: 1 }) expect(conditions).to eq(users: { id: 1 }) end it 'converts associations properly when the same table is referenced twice' do conditions = described_class.new(Transaction).tableize_conditions(sender: { id: 1 }, receiver: { id: 2 }) expect(conditions).to eq(users: { id: 1 }, receivers_transactions: { id: 2 }) end it 'converts very complex nested sets' do original_conditions = { user: { id: 1 }, mentioned_users: { name: 'a name', mentioned_articles: { id: 2 }, articles: { user: { name: 'deep' }, mentioned_users: { name: 'd2' } } } } conditions = described_class.new(Article).tableize_conditions(original_conditions) expect(conditions).to eq(users: { id: 1 }, mentioned_articles_users: { id: 2 }, mentioned_users_articles: { name: 'a name' }, users_articles: { name: 'deep' }, mentioned_users_articles_2: { name: 'd2' }) end it 'converts complex nested sets with duplicates' do original_conditions = { sender: { id: 'sender', articles: { id: 'article1' } }, receiver: { id: 'receiver', articles: { id: 'article2' } } } conditions = described_class.new(Transaction).tableize_conditions(original_conditions) expect(conditions).to eq(users: { id: 'sender' }, articles: { id: 'article1' }, receivers_transactions: { id: 'receiver' }, articles_users: { id: 'article2' }) end end end cancancan-3.0.1/spec/cancan/model_adapters/conditions_normalizer_spec.rb000066400000000000000000000044121345516370100265400ustar00rootroot00000000000000require 'spec_helper' RSpec.describe CanCan::ModelAdapters::ConditionsNormalizer do before do connect_db ActiveRecord::Migration.verbose = false ActiveRecord::Schema.define do create_table(:articles) do |t| end create_table(:users) do |t| t.string :name end create_table(:comments) do |t| end create_table(:spread_comments) do |t| t.integer :article_id t.integer :comment_id end create_table(:legacy_mentions) do |t| t.integer :user_id t.integer :article_id end end class Article < ActiveRecord::Base has_many :spread_comments has_many :comments, through: :spread_comments has_many :mentions has_many :mentioned_users, through: :mentions, source: :user end class Comment < ActiveRecord::Base has_many :spread_comments has_many :articles, through: :spread_comments end class SpreadComment < ActiveRecord::Base belongs_to :comment belongs_to :article end class Mention < ActiveRecord::Base self.table_name = 'legacy_mentions' belongs_to :article belongs_to :user end class User < ActiveRecord::Base has_many :mentions has_many :mentioned_articles, through: :mentions, source: :article end end it 'simplifies has_many through associations' do rule = CanCan::Rule.new(true, :read, Comment, articles: { mentioned_users: { name: 'pippo' } }) CanCan::ModelAdapters::ConditionsNormalizer.normalize(Comment, [rule]) expect(rule.conditions).to eq(spread_comments: { article: { mentions: { user: { name: 'pippo' } } } }) end it 'normalizes the has_one through associations' do class Supplier < ActiveRecord::Base has_one :accountant has_one :account_history, through: :accountant end class Accountant < ActiveRecord::Base belongs_to :supplier has_one :account_history end class AccountHistory < ActiveRecord::Base belongs_to :accountant end rule = CanCan::Rule.new(true, :read, Supplier, account_history: { name: 'pippo' }) CanCan::ModelAdapters::ConditionsNormalizer.normalize(Supplier, [rule]) expect(rule.conditions).to eq(accountant: { account_history: { name: 'pippo' } }) end end cancancan-3.0.1/spec/cancan/model_adapters/default_adapter_spec.rb000066400000000000000000000004211345516370100252450ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' describe CanCan::ModelAdapters::DefaultAdapter do it 'is default for generic classes' do expect(CanCan::ModelAdapters::AbstractAdapter.adapter_class(Object)).to eq(CanCan::ModelAdapters::DefaultAdapter) end end cancancan-3.0.1/spec/cancan/model_adapters/has_and_belongs_to_many_spec.rb000066400000000000000000000036771345516370100267750ustar00rootroot00000000000000require 'spec_helper' RSpec.describe CanCan::ModelAdapters::ActiveRecord5Adapter do let(:ability) { double.extend(CanCan::Ability) } let(:users_table) { User.table_name } let(:posts_table) { Post.table_name } before :all do connect_db ActiveRecord::Migration.verbose = false ActiveRecord::Schema.define do create_table(:people) do |t| t.string :name t.timestamps null: false end create_table(:houses) do |t| t.boolean :restructured, default: true t.timestamps null: false end create_table(:houses_people) do |t| t.integer :person_id t.integer :house_id t.timestamps null: false end end class Person < ActiveRecord::Base has_and_belongs_to_many :houses end class House < ActiveRecord::Base has_and_belongs_to_many :people end end before do @person1 = Person.create! @person2 = Person.create! @house1 = House.create!(people: [@person1]) @house2 = House.create!(restructured: false, people: [@person1, @person2]) @house3 = House.create!(people: [@person2]) ability.can :read, House, people: { id: @person1.id } end describe 'fetching of records' do it 'it retreives the records correctly' do houses = House.accessible_by(ability) expect(houses).to match_array [@house2, @house1] end if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0') it 'generates the correct query' do expect(ability.model_adapter(House, :read)) .to generate_sql("SELECT DISTINCT \"houses\".* FROM \"houses\" LEFT OUTER JOIN \"houses_people\" ON \"houses_people\".\"house_id\" = \"houses\".\"id\" LEFT OUTER JOIN \"people\" ON \"people\".\"id\" = \"houses_people\".\"person_id\" WHERE \"people\".\"id\" = #{@person1.id}") end end end end cancancan-3.0.1/spec/cancan/rule_compressor_spec.rb000066400000000000000000000062211345516370100223650ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' describe CanCan::RulesCompressor do before do class Blog end end def can(action, subject, args = nil) CanCan::Rule.new(true, action, subject, args, nil) end def cannot(action, subject, args = nil) CanCan::Rule.new(false, action, subject, args, nil) end context 'a "cannot catch_all" rule is in first position' do let(:rules) do [cannot(:read, Blog), can(:read, Blog)] end it 'deletes it' do expect(described_class.new(rules).rules_collapsed).to eq rules[1..-1] end end context 'a "can catch all" rule is in last position' do let(:rules) do [cannot(:read, Blog, id: 2), can(:read, Blog, id: 1), can(:read, Blog)] end it 'deletes all previous rules' do expect(described_class.new(rules).rules_collapsed).to eq [rules.last] end end context 'a "can catch_all" rule is in front of others can rules' do let(:rules) do [can(:read, Blog, id: 1), can(:read, Blog), can(:read, Blog, id: 3), can(:read, Blog, author: { id: 3 }), cannot(:read, Blog, private: true)] end it 'deletes all previous rules and subsequent rules of the same type' do expect(described_class.new(rules).rules_collapsed).to eq [rules[1], rules.last] end end context 'a "cannot catch_all" rule is in front of others cannot rules' do let(:rules) do [can(:read, Blog, id: 1), can(:read, Blog), can(:read, Blog, id: 3), cannot(:read, Blog), cannot(:read, Blog, private: true), can(:read, Blog, id: 3)] end it 'deletes all previous rules and subsequent rules of the same type' do expect(described_class.new(rules).rules_collapsed).to eq [rules.last] end end context 'a lot of rules' do let(:rules) do [ cannot(:read, Blog, id: 4), can(:read, Blog, id: 1), can(:read, Blog), can(:read, Blog, id: 3), cannot(:read, Blog), cannot(:read, Blog, private: true), can(:read, Blog, id: 3), can(:read, Blog, id: 8), cannot(:read, Blog, id: 5) ] end it 'minimizes the rules' do expect(described_class.new(rules).rules_collapsed).to eq rules.last(3) end end # TODO: not supported yet xcontext 'duplicate rules' do let(:rules) do [can(:read, Blog, id: 4), can(:read, Blog, id: 1), can(:read, Blog, id: 2), can(:read, Blog, id: 2), can(:read, Blog, id: 3), can(:read, Blog, id: 2)] end it 'minimizes the rules, by removing duplicates' do expect(described_class.new(rules).rules_collapsed).to eq [rules[0], rules[1], rules[2], rules[4]] end end # TODO: not supported yet xcontext 'merges rules' do let(:rules) do [can(:read, Blog, id: 4), can(:read, Blog, id: 1), can(:read, Blog, id: 2), can(:read, Blog, id: 2), can(:read, Blog, id: 3), can(:read, Blog, id: 2)] end it 'minimizes the rules, by merging them' do expect(described_class.new(rules).rules_collapsed).to eq [can(:read, Blog, id: [4, 1, 2, 3])] end end end cancancan-3.0.1/spec/cancan/rule_spec.rb000066400000000000000000000050621345516370100201130ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' require 'ostruct' # for OpenStruct below # Most of Rule functionality is tested in Ability specs RSpec.describe CanCan::Rule do before(:each) do @conditions = {} @rule = CanCan::Rule.new(true, :read, Integer, @conditions) end it 'returns no association joins if none exist' do expect(@rule.associations_hash).to eq({}) end it 'returns no association for joins if just attributes' do @conditions[:foo] = :bar expect(@rule.associations_hash).to eq({}) end it 'returns single association for joins' do @conditions[:foo] = { bar: 1 } expect(@rule.associations_hash).to eq(foo: {}) end it 'returns multiple associations for joins' do @conditions[:foo] = { bar: 1 } @conditions[:test] = { 1 => 2 } expect(@rule.associations_hash).to eq(foo: {}, test: {}) end it 'returns nested associations for joins' do @conditions[:foo] = { bar: { 1 => 2 } } expect(@rule.associations_hash).to eq(foo: { bar: {} }) end it 'returns no association joins if conditions is nil' do rule = CanCan::Rule.new(true, :read, Integer, nil) expect(rule.associations_hash).to eq({}) end it 'allows nil in attribute spot for edge cases' do rule1 = CanCan::Rule.new(true, :action, :subject, nil, :var) expect(rule1.attributes).to eq [] expect(rule1.conditions).to eq :var rule2 = CanCan::Rule.new(true, :action, :subject, nil, %i[foo bar]) expect(rule2.attributes).to eq [] expect(rule2.conditions).to eq %i[foo bar] end unless RUBY_ENGINE == 'jruby' describe '#inspect' do def count_queries(&block) count = 0 counter_f = lambda { |_name, _started, _finished, _unique_id, payload| count += 1 unless payload[:name].in? %w[CACHE SCHEMA] } ActiveSupport::Notifications.subscribed(counter_f, 'sql.active_record', &block) count end before do ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') ActiveRecord::Migration.verbose = false ActiveRecord::Schema.define do create_table(:watermelons) do |t| t.boolean :visible end end class Watermelon < ActiveRecord::Base scope :visible, -> { where(visible: true) } end end it 'does not evaluate the conditions when they are scopes' do rule = CanCan::Rule.new(true, :read, Watermelon, Watermelon.visible, {}, {}) count = count_queries { rule.inspect } expect(count).to eq 0 end end end end cancancan-3.0.1/spec/changelog_spec.rb000066400000000000000000000053101345516370100176440ustar00rootroot00000000000000# frozen_string_literal: true # credits to https://github.com/rubocop-hq/rubocop for this CHANGELOG checker RSpec.describe 'changelog' do subject(:changelog) do path = File.join(File.dirname(__FILE__), '..', 'CHANGELOG.md') File.read(path) end it 'has newline at end of file' do expect(changelog.end_with?("\n")).to be true end it 'has link definitions for all implicit links' do implicit_link_names = changelog.scan(/\[([^\]]+)\]\[\]/).flatten.uniq implicit_link_names.each do |name| expect(changelog).to include("[#{name}]: http") end end describe 'entry' do subject(:entries) { lines.grep(/^\*/).map(&:chomp) } let(:lines) { changelog.each_line } it 'has a whitespace between the * and the body' do expect(entries).to all(match(/^\* \S/)) end context 'after version 1.17.0' do let(:lines) do changelog.each_line.take_while do |line| !line.start_with?('## 1.17.0') end end it 'has a link to the contributors at the end' do expect(entries).to all(match(/\(\[@\S+\]\[\](?:, \[@\S+\]\[\])*\)$/)) end end describe 'link to related issue' do let(:issues) do entries.map do |entry| entry.match(/\[(?[#\d]+)\]\((?[^\)]+)\)/) end.compact end it 'has an issue number prefixed with #' do issues.each do |issue| expect(issue[:number]).to match(/^#\d+$/) end end it 'has a valid URL' do issues.each do |issue| number = issue[:number].gsub(/\D/, '') pattern = %r{^https://github\.com/CanCanCommunity/cancancan/(?:issues|pull)/#{number}$} expect(issue[:url]).to match(pattern) end end it 'has a colon and a whitespace at the end' do entries_including_issue_link = entries.select do |entry| entry.match(/^\*\s*\[/) end expect(entries_including_issue_link).to all(include('): ')) end end describe 'contributor name' do subject(:contributor_names) { lines.grep(/\A\[@/).map(&:chomp) } it 'has a unique contributor name' do expect(contributor_names.uniq.size).to eq contributor_names.size end end describe 'body' do let(:bodies) do entries.map do |entry| entry .gsub(/`[^`]+`/, '``') .sub(/^\*\s*(?:\[.+?\):\s*)?/, '') .sub(/\s*\([^\)]+\)$/, '') end end it 'does not start with a lower case' do bodies.each do |body| expect(body).not_to match(/^[a-z]/) end end it 'ends with a punctuation' do expect(bodies).to all(match(/[\.\!]$/)) end end end end cancancan-3.0.1/spec/matchers.rb000066400000000000000000000007351345516370100165170ustar00rootroot00000000000000# frozen_string_literal: true RSpec::Matchers.define :orderlessly_match do |original_string| match do |given_string| original_string.split('').sort == given_string.split('').sort end failure_message do |given_string| "expected \"#{given_string}\" to have the same characters as \"#{original_string}\"" end failure_message_when_negated do |given_string| "expected \"#{given_string}\" not to have the same characters as \"#{original_string}\"" end end cancancan-3.0.1/spec/spec.opts000066400000000000000000000000241345516370100162140ustar00rootroot00000000000000--color --backtrace cancancan-3.0.1/spec/spec_helper.rb000066400000000000000000000017641345516370100172050ustar00rootroot00000000000000# frozen_string_literal: true require 'rubygems' require 'bundler/setup' Bundler.require require 'matchers' require 'cancan/matchers' # I8n setting to fix deprecation. I18n.enforce_available_locales = false if defined?(I18n) && I18n.respond_to?('enforce_available_locales=') # Add support to load paths $LOAD_PATH.unshift File.expand_path('support', __dir__) Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } RSpec.configure do |config| config.filter_run focus: true config.run_all_when_everything_filtered = true config.disable_monkey_patching = true config.mock_with :rspec config.order = 'random' config.expect_with :rspec do |c| c.syntax = :expect end config.include SQLHelpers end RSpec::Matchers.define :generate_sql do |expected| match do |actual| normalized_sql(actual) == expected.gsub(/\s+/, ' ').strip end failure_message do |actual| "Returned sql:\n#{normalized_sql(actual)}\ninstead of:\n#{expected.gsub(/\s+/, ' ').strip}" end end cancancan-3.0.1/spec/support/000077500000000000000000000000001345516370100160735ustar00rootroot00000000000000cancancan-3.0.1/spec/support/ability.rb000066400000000000000000000001511345516370100200520ustar00rootroot00000000000000# frozen_string_literal: true class Ability include CanCan::Ability def initialize(user) end end cancancan-3.0.1/spec/support/sql_helpers.rb000066400000000000000000000022151345516370100207410ustar00rootroot00000000000000# frozen_string_literal: true module SQLHelpers def normalized_sql(adapter) adapter.database_records.to_sql.strip.squeeze(' ') end def connect_db # ActiveRecord::Base.logger = Logger.new(STDOUT) ActiveRecord::Base.logger = nil if ENV['DB'] == 'sqlite' ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') elsif ENV['DB'] == 'postgres' connect_postgres else raise StandardError, 'database not supported' end end private def connect_postgres ActiveRecord::Base.establish_connection(adapter: 'postgresql', host: 'localhost', database: 'postgres', schema_search_path: 'public') ActiveRecord::Base.connection.drop_database('cancan_postgresql_spec') ActiveRecord::Base.connection.create_database('cancan_postgresql_spec', 'encoding' => 'utf-8', 'adapter' => 'postgresql') ActiveRecord::Base.establish_connection(adapter: 'postgresql', host: 'localhost', database: 'cancan_postgresql_spec') end end