From ffa65d4d725fc376037cd8390f30df45f85b6d8e Mon Sep 17 00:00:00 2001 From: Andy Allan Date: Thu, 1 Mar 2018 10:24:35 +0800 Subject: [PATCH 01/27] Add cancancan and the first ability definitions for site_controller --- Gemfile | 1 + Gemfile.lock | 2 ++ app/controllers/application_controller.rb | 6 ++++ app/controllers/site_controller.rb | 3 +- app/controllers/user_controller.rb | 2 ++ app/models/ability.rb | 37 +++++++++++++++++++++++ 6 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 app/models/ability.rb diff --git a/Gemfile b/Gemfile index 78e2aac97..8b8eae0bb 100644 --- a/Gemfile +++ b/Gemfile @@ -54,6 +54,7 @@ gem "rails-i18n", "~> 4.0.0" gem "record_tag_helper" gem "rinku", ">= 1.2.2", :require => "rails_rinku" gem "validates_email_format_of", ">= 1.5.1" +gem "cancancan" # Native OSM extensions gem "quad_tile", "~> 1.0.1" diff --git a/Gemfile.lock b/Gemfile.lock index 417a7634a..06ddc0fe2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -59,6 +59,7 @@ GEM binding_of_caller (0.8.0) debug_inspector (>= 0.0.1) builder (3.2.3) + cancancan (2.1.3) canonical-rails (0.2.3) rails (>= 4.1, < 5.3) capybara (2.18.0) @@ -369,6 +370,7 @@ DEPENDENCIES better_errors bigdecimal (~> 1.1.0) binding_of_caller + cancancan canonical-rails capybara (~> 2.13) coffee-rails (~> 4.2) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index db4ae9ad3..394b04d58 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,5 +1,6 @@ class ApplicationController < ActionController::Base include SessionPersistence + check_authorization protect_from_forgery :with => :exception @@ -467,6 +468,11 @@ class ApplicationController < ActionController::Base raise end + rescue_from CanCan::AccessDenied do |exception| + raise "Access denied on #{exception.action} #{exception.subject.inspect}" + # ... + end + private # extract authorisation credentials from headers, returns user = nil if none diff --git a/app/controllers/site_controller.rb b/app/controllers/site_controller.rb index 6cbe302d6..bfff50c6d 100644 --- a/app/controllers/site_controller.rb +++ b/app/controllers/site_controller.rb @@ -6,10 +6,11 @@ class SiteController < ApplicationController before_action :set_locale before_action :redirect_browse_params, :only => :index before_action :redirect_map_params, :only => [:index, :edit, :export] - before_action :require_user, :only => [:welcome] before_action :require_oauth, :only => [:index] before_action :update_totp, :only => [:index] + authorize_resource :class => false + def index session[:location] ||= OSM.ip_location(request.env["REMOTE_ADDR"]) unless STATUS == :database_readonly || STATUS == :database_offline end diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb index 63fad8c83..d853d4822 100644 --- a/app/controllers/user_controller.rb +++ b/app/controllers/user_controller.rb @@ -1,6 +1,8 @@ class UserController < ApplicationController layout "site", :except => [:api_details] + skip_authorization_check :only => [:login, :logout] + skip_before_action :verify_authenticity_token, :only => [:api_read, :api_details, :api_gpx_files, :auth_success] before_action :disable_terms_redirect, :only => [:terms, :save, :logout, :api_details] before_action :authorize, :only => [:api_details, :api_gpx_files] diff --git a/app/models/ability.rb b/app/models/ability.rb new file mode 100644 index 000000000..c712e3e82 --- /dev/null +++ b/app/models/ability.rb @@ -0,0 +1,37 @@ +class Ability + include CanCan::Ability + + def initialize(user) + can :index, :site + + if user + can :welcome, :site + end + # 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 From 2ab3d56102661f31cc41ef3edca5d72f904a3070 Mon Sep 17 00:00:00 2001 From: Chris Flipse Date: Fri, 8 Jun 2018 10:21:19 -0400 Subject: [PATCH 02/27] don't check authorization everywhere --- app/controllers/application_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 394b04d58..5f88eb983 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,6 +1,6 @@ class ApplicationController < ActionController::Base include SessionPersistence - check_authorization + # check_authorization protect_from_forgery :with => :exception From b16aa11f65ed1120ee546712150ad6f57ec50102 Mon Sep 17 00:00:00 2001 From: Chris Flipse Date: Fri, 8 Jun 2018 16:57:35 -0400 Subject: [PATCH 03/27] fix tests for site controller --- app/controllers/site_controller.rb | 4 +++- app/models/ability.rb | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/controllers/site_controller.rb b/app/controllers/site_controller.rb index bfff50c6d..8f4aafa44 100644 --- a/app/controllers/site_controller.rb +++ b/app/controllers/site_controller.rb @@ -103,7 +103,9 @@ class SiteController < ApplicationController @locale = params[:copyright_locale] || I18n.locale end - def welcome; end + def welcome + require_user + end def help; end diff --git a/app/models/ability.rb b/app/models/ability.rb index c712e3e82..864225e8e 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -3,9 +3,10 @@ class Ability def initialize(user) can :index, :site + can [:permalink, :edit, :help, :fixthemap, :offline, :export, :about, :preview, :copyright, :key, :id, :welcome], :site if user - can :welcome, :site + can :weclome, :site end # Define abilities for the passed in user here. For example: # From 6da3ece68354f77f626de8963770c0217048e19d Mon Sep 17 00:00:00 2001 From: Chris Flipse Date: Fri, 8 Jun 2018 16:58:49 -0400 Subject: [PATCH 04/27] use token in ability checks --- app/controllers/application_controller.rb | 4 ++++ app/models/ability.rb | 8 +++++++- test/models/abilities_test.rb | 7 +++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 test/models/abilities_test.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 5f88eb983..84adc1a32 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -473,6 +473,10 @@ class ApplicationController < ActionController::Base # ... end + def current_ability + @current_ability ||= Ability.new(current_user, current_token) + end + private # extract authorisation credentials from headers, returns user = nil if none diff --git a/app/models/ability.rb b/app/models/ability.rb index 864225e8e..897316691 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + class Ability include CanCan::Ability - def initialize(user) + def initialize(user, token) can :index, :site can [:permalink, :edit, :help, :fixthemap, :offline, :export, :about, :preview, :copyright, :key, :id, :welcome], :site @@ -35,4 +37,8 @@ class Ability # See the wiki for details: # https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities end + + def has_capability?(token, cap) + token && token.read_attribute(cap) + end end diff --git a/test/models/abilities_test.rb b/test/models/abilities_test.rb new file mode 100644 index 000000000..ab8458531 --- /dev/null +++ b/test/models/abilities_test.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "test_helper" + +class AbilityTest < ActiveSupport::TestCase + +end From 6b44a1976cf07ba50ba8aed8b34434e69a45e62d Mon Sep 17 00:00:00 2001 From: Chris Flipse Date: Sat, 9 Jun 2018 16:20:21 -0400 Subject: [PATCH 05/27] use a controller method to handle cancan denials This will let controllers override for specific circumstances --- app/controllers/application_controller.rb | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 84adc1a32..54d5835bb 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -4,6 +4,8 @@ class ApplicationController < ActionController::Base protect_from_forgery :with => :exception + rescue_from CanCan::AccessDenied, :with => :deny_access + before_action :fetch_body around_action :better_errors_allow_inline, :if => proc { Rails.env.development? } @@ -468,13 +470,17 @@ class ApplicationController < ActionController::Base raise end - rescue_from CanCan::AccessDenied do |exception| - raise "Access denied on #{exception.action} #{exception.subject.inspect}" - # ... + def current_ability + Ability.new(current_user, current_token) end - def current_ability - @current_ability ||= Ability.new(current_user, current_token) + def deny_access(exception) + if current_user + raise "Access denied on #{exception.action} #{exception.subject.inspect}" + # ... + else + require_user + end end private From 523291442766e7cd4adbad6d2bc7c6803cdd3811 Mon Sep 17 00:00:00 2001 From: Chris Flipse Date: Sat, 9 Jun 2018 16:35:17 -0400 Subject: [PATCH 06/27] Implement the cancan filters for diary entries Access logic is not _entirely_ exported from the controller, unfortunately. For interface reasons, some actions which require admin have to be listed within the controller's deny_access method. This is required because, being a default-deny system, cancancan _cannot_ tell you the reason you were denied access; and so the "nice" feedback presenting next steps can't be gleaned from the exception --- app/controllers/diary_entry_controller.rb | 31 ++++++++++++------- app/models/ability.rb | 8 +++++ test/models/abilities_test.rb | 36 +++++++++++++++++++++++ 3 files changed, 64 insertions(+), 11 deletions(-) diff --git a/app/controllers/diary_entry_controller.rb b/app/controllers/diary_entry_controller.rb index f2c11fc74..ff8758990 100644 --- a/app/controllers/diary_entry_controller.rb +++ b/app/controllers/diary_entry_controller.rb @@ -3,13 +3,15 @@ class DiaryEntryController < ApplicationController before_action :authorize_web before_action :set_locale - before_action :require_user, :only => [:new, :edit, :comment, :hide, :hidecomment, :subscribe, :unsubscribe] + + authorize_resource + before_action :lookup_user, :only => [:view, :comments] before_action :check_database_readable before_action :check_database_writable, :only => [:new, :edit, :comment, :hide, :hidecomment, :subscribe, :unsubscribe] - before_action :require_administrator, :only => [:hide, :hidecomment] before_action :allow_thirdparty_images, :only => [:new, :edit, :list, :view, :comments] + def new @title = t "diary_entry.new.title" @@ -215,6 +217,22 @@ class DiaryEntryController < ApplicationController private + # This is required because, being a default-deny system, cancancan + # _cannot_ tell you the reason you were denied access; and so + # the "nice" feedback presenting next steps can't be gleaned from + # the exception + ## + # for the hide actions, require that the user is a administrator, or fill out + # a helpful error message and return them to the user page. + def deny_access(exception) + if current_user && exception.action.in?([:hide, :hidecomment]) + flash[:error] = t("user.filter.not_an_administrator") + redirect_to :action => "view" + else + super + end + end + ## # return permitted diary entry parameters def entry_params @@ -229,15 +247,6 @@ class DiaryEntryController < ApplicationController params.require(:diary_comment).permit(:body) end - ## - # require that the user is a administrator, or fill out a helpful error message - # and return them to the user page. - def require_administrator - unless current_user.administrator? - flash[:error] = t("user.filter.not_an_administrator") - redirect_to :action => "view" - end - end ## # decide on a location for the diary entry map diff --git a/app/models/ability.rb b/app/models/ability.rb index 897316691..59b1c5ec3 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -7,8 +7,16 @@ class Ability can :index, :site can [:permalink, :edit, :help, :fixthemap, :offline, :export, :about, :preview, :copyright, :key, :id, :welcome], :site + can [:list, :rss, :view, :comments], DiaryEntry + if user can :weclome, :site + + can [:create, :edit, :comment, :subscribe, :unsubscribe], DiaryEntry + + if user.administrator? + can [:hide, :hidecomment], [DiaryEntry, DiaryComment] + end end # Define abilities for the passed in user here. For example: # diff --git a/test/models/abilities_test.rb b/test/models/abilities_test.rb index ab8458531..6472ad2e3 100644 --- a/test/models/abilities_test.rb +++ b/test/models/abilities_test.rb @@ -4,4 +4,40 @@ require "test_helper" class AbilityTest < ActiveSupport::TestCase + test "diary permissions for a guest" do + ability = Ability.new(nil, []) + [:list, :rss, :view, :comments].each do |action| + assert ability.can?(action, DiaryEntry), "should be able to #{action} DiaryEntries" + end + + [:create, :edit, :comment, :subscribe, :unsubscribe, :hide, :hidecomment].each do |action| + assert ability.cannot?(action, DiaryEntry), "should be able to #{action} DiaryEntries" + assert ability.cannot?(action, DiaryComment), "should be able to #{action} DiaryEntries" + end + end + + + test "Diary permissions for a normal user" do + ability = Ability.new(create(:user), []) + + [:list, :rss, :view, :comments, :create, :edit, :comment, :subscribe, :unsubscribe].each do |action| + assert ability.can?(action, DiaryEntry), "should be able to #{action} DiaryEntries" + end + + [:hide, :hidecomment].each do |action| + assert ability.cannot?(action, DiaryEntry), "should be able to #{action} DiaryEntries" + assert ability.cannot?(action, DiaryComment), "should be able to #{action} DiaryEntries" + end + end + + test "Diary for an administrator" do + ability = Ability.new(create(:administrator_user), []) + [:list, :rss, :view, :comments, :create, :edit, :comment, :subscribe, :unsubscribe, :hide, :hidecomment].each do |action| + assert ability.can?(action, DiaryEntry), "should be able to #{action} DiaryEntries" + end + + [:hide, :hidecomment].each do |action| + assert ability.can?(action, DiaryComment), "should be able to #{action} DiaryComment" + end + end end From ac7c45bca036db2c5bfd692d77ff7f1da526955f Mon Sep 17 00:00:00 2001 From: Chris Flipse Date: Sat, 9 Jun 2018 19:53:17 -0400 Subject: [PATCH 07/27] add test helper to set oauth tokens --- test/controllers/user_preferences_controller_test.rb | 5 +++++ test/test_helper.rb | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/test/controllers/user_preferences_controller_test.rb b/test/controllers/user_preferences_controller_test.rb index 3e5cbb369..1a51779ed 100644 --- a/test/controllers/user_preferences_controller_test.rb +++ b/test/controllers/user_preferences_controller_test.rb @@ -35,6 +35,7 @@ class UserPreferencesControllerTest < ActionController::TestCase # authenticate as a user with no preferences basic_authorization create(:user).email, "test" + grant_oauth_token :allow_read_prefs # try the read again get :read @@ -75,6 +76,7 @@ class UserPreferencesControllerTest < ActionController::TestCase # authenticate as a user with preferences basic_authorization user.email, "test" + grant_oauth_token :allow_read_prefs # try the read again get :read_one, :params => { :preference_key => "key" } @@ -108,6 +110,7 @@ class UserPreferencesControllerTest < ActionController::TestCase # authenticate as a user with preferences basic_authorization user.email, "test" + grant_oauth_token :allow_write_prefs # try the put again assert_no_difference "UserPreference.count" do @@ -159,6 +162,7 @@ class UserPreferencesControllerTest < ActionController::TestCase # authenticate as a user with preferences basic_authorization user.email, "test" + grant_oauth_token :allow_write_prefs # try adding a new preference assert_difference "UserPreference.count", 1 do @@ -196,6 +200,7 @@ class UserPreferencesControllerTest < ActionController::TestCase # authenticate as a user with preferences basic_authorization user.email, "test" + grant_oauth_token :allow_write_prefs # try the delete again assert_difference "UserPreference.count", -1 do diff --git a/test/test_helper.rb b/test/test_helper.rb index 7198519a7..39e8cdd05 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -85,6 +85,17 @@ module ActiveSupport @request.env["HTTP_AUTHORIZATION"] = format("Basic %{auth}", :auth => Base64.encode64("#{user}:#{pass}")) end + ## + # set oauth token permissions + def grant_oauth_token(*tokens) + request.env["oauth.token"] = AccessToken.new do |token| + tokens.each do |t| + token.public_send("#{t}=", true) + end + end + end + + ## # set request readers to ask for a particular error format def error_format(format) From 060c686c1923534d2899ee129cc740e0f2fa7c9f Mon Sep 17 00:00:00 2001 From: Chris Flipse Date: Sat, 9 Jun 2018 19:53:45 -0400 Subject: [PATCH 08/27] Use cancancan to authorize user_preference_controller --- app/controllers/application_controller.rb | 4 ++-- app/controllers/user_preferences_controller.rb | 5 +++-- app/models/ability.rb | 3 +++ test/models/abilities_test.rb | 1 - 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 54d5835bb..b6a2467a4 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -476,8 +476,8 @@ class ApplicationController < ActionController::Base def deny_access(exception) if current_user - raise "Access denied on #{exception.action} #{exception.subject.inspect}" - # ... + set_locale + report_error t("oauth.permissions.missing"), :forbidden else require_user end diff --git a/app/controllers/user_preferences_controller.rb b/app/controllers/user_preferences_controller.rb index 0aa2e8d52..915c847de 100644 --- a/app/controllers/user_preferences_controller.rb +++ b/app/controllers/user_preferences_controller.rb @@ -2,8 +2,9 @@ class UserPreferencesController < ApplicationController skip_before_action :verify_authenticity_token before_action :authorize - before_action :require_allow_read_prefs, :only => [:read_one, :read] - before_action :require_allow_write_prefs, :except => [:read_one, :read] + + authorize_resource + around_action :api_call_handle_error ## diff --git a/app/models/ability.rb b/app/models/ability.rb index 59b1c5ec3..6a61eeff3 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -14,6 +14,9 @@ class Ability can [:create, :edit, :comment, :subscribe, :unsubscribe], DiaryEntry + can [:read, :read_one], UserPreference if has_capability?(token, :allow_read_prefs) + can [:update, :update_one, :delete_one], UserPreference if has_capability?(token, :allow_write_prefs) + if user.administrator? can [:hide, :hidecomment], [DiaryEntry, DiaryComment] end diff --git a/test/models/abilities_test.rb b/test/models/abilities_test.rb index 6472ad2e3..bc8e24781 100644 --- a/test/models/abilities_test.rb +++ b/test/models/abilities_test.rb @@ -16,7 +16,6 @@ class AbilityTest < ActiveSupport::TestCase end end - test "Diary permissions for a normal user" do ability = Ability.new(create(:user), []) From 2a44ff581f4c547a3637ea52567a3398b1d8bfe0 Mon Sep 17 00:00:00 2001 From: Chris Flipse Date: Sun, 10 Jun 2018 11:31:54 -0400 Subject: [PATCH 09/27] fix and improve ability coverage to account for tokens --- test/models/abilities_test.rb | 65 ++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/test/models/abilities_test.rb b/test/models/abilities_test.rb index bc8e24781..4976b0925 100644 --- a/test/models/abilities_test.rb +++ b/test/models/abilities_test.rb @@ -4,8 +4,20 @@ require "test_helper" class AbilityTest < ActiveSupport::TestCase + def tokens(*toks) + AccessToken.new do |token| + toks.each do |t| + token.public_send("#{t}=", true) + end + end + end + +end + +class GuestAbilityTest < AbilityTest + test "diary permissions for a guest" do - ability = Ability.new(nil, []) + ability = Ability.new nil, tokens [:list, :rss, :view, :comments].each do |action| assert ability.can?(action, DiaryEntry), "should be able to #{action} DiaryEntries" end @@ -16,8 +28,12 @@ class AbilityTest < ActiveSupport::TestCase end end - test "Diary permissions for a normal user" do - ability = Ability.new(create(:user), []) +end + +class UserAbilityTest < AbilityTest + + test "Diary permissions" do + ability = Ability.new create(:user), tokens [:list, :rss, :view, :comments, :create, :edit, :comment, :subscribe, :unsubscribe].each do |action| assert ability.can?(action, DiaryEntry), "should be able to #{action} DiaryEntries" @@ -29,8 +45,39 @@ class AbilityTest < ActiveSupport::TestCase end end + test "user preferences" do + user = create(:user) + ability = Ability.new create(:user), tokens + + [:read, :read_one, :update, :update_one, :delete_one].each do |act| + assert ability.cannot? act, UserPreference + end + + ability = Ability.new user, tokens(:allow_read_prefs) + + [:update, :update_one, :delete_one].each do |act| + assert ability.cannot? act, UserPreference + end + + [:read, :read_one].each do |act| + assert ability.can? act, UserPreference + end + + ability = Ability.new user, tokens(:allow_write_prefs) + [:read, :read_one].each do |act| + assert ability.cannot? act, UserPreference + end + + [:update, :update_one, :delete_one].each do |act| + assert ability.can? act, UserPreference + end + end +end + +class AdministratorAbilityTest < AbilityTest + test "Diary for an administrator" do - ability = Ability.new(create(:administrator_user), []) + ability = Ability.new create(:administrator_user), tokens [:list, :rss, :view, :comments, :create, :edit, :comment, :subscribe, :unsubscribe, :hide, :hidecomment].each do |action| assert ability.can?(action, DiaryEntry), "should be able to #{action} DiaryEntries" end @@ -39,4 +86,14 @@ class AbilityTest < ActiveSupport::TestCase assert ability.can?(action, DiaryComment), "should be able to #{action} DiaryComment" end end + + test "administrator does not auto-grant user preferences" do + ability = Ability.new create(:administrator_user), tokens + + [:read, :read_one, :update, :update_one, :delete_one].each do |act| + assert ability.cannot? act, UserPreference + end + end + + end From 464c7f863e8413f67b22999fd1c629969731c309 Mon Sep 17 00:00:00 2001 From: Chris Flipse Date: Sun, 10 Jun 2018 13:06:10 -0400 Subject: [PATCH 10/27] Update capabilities check to actually reflect the existing logic The OAuth capabilities are essentially user permissions that have been granted to the app. If the user authenticates through a non-oauth method, they are assumed to have granted all capabilities to the app --- app/models/ability.rb | 4 +++- test/models/abilities_test.rb | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 6a61eeff3..8fc15ded5 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -49,7 +49,9 @@ class Ability # https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities end + # If a user provides no tokens, they've authenticated via a non-oauth method + # and permission to access to all capabilities is assumed. def has_capability?(token, cap) - token && token.read_attribute(cap) + token.nil? || token.read_attribute(cap) end end diff --git a/test/models/abilities_test.rb b/test/models/abilities_test.rb index 4976b0925..de9f9ba9b 100644 --- a/test/models/abilities_test.rb +++ b/test/models/abilities_test.rb @@ -47,6 +47,14 @@ class UserAbilityTest < AbilityTest test "user preferences" do user = create(:user) + + # a user with no tokens + ability = Ability.new create(:user), nil + [:read, :read_one, :update, :update_one, :delete_one].each do |act| + assert ability.can? act, UserPreference + end + + # A user with empty tokens ability = Ability.new create(:user), tokens [:read, :read_one, :update, :update_one, :delete_one].each do |act| From 4d20a2c96a3ff722071e7b1093d260b1236b2eeb Mon Sep 17 00:00:00 2001 From: Benjamin Reynolds Date: Sun, 10 Jun 2018 12:09:32 -0400 Subject: [PATCH 11/27] Authorize actions on GeocoderController with CanCanCan Ability --- app/models/ability.rb | 3 +++ test/models/abilities_test.rb | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/app/models/ability.rb b/app/models/ability.rb index 8fc15ded5..d33430fb4 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -9,6 +9,9 @@ class Ability can [:list, :rss, :view, :comments], DiaryEntry + can [:search, :search_latlon, :search_ca_postcode, :search_osm_nominatim, + :search_geonames, :search_osm_nominatim_reverse, :search_geonames_reverse], :geocoder + if user can :weclome, :site diff --git a/test/models/abilities_test.rb b/test/models/abilities_test.rb index de9f9ba9b..298e8299b 100644 --- a/test/models/abilities_test.rb +++ b/test/models/abilities_test.rb @@ -16,6 +16,15 @@ end class GuestAbilityTest < AbilityTest + test "geocoder permission for a guest" do + ability = Ability.new nil, tokens + + [:search, :search_latlon, :search_ca_postcode, :search_osm_nominatim, + :search_geonames, :search_osm_nominatim_reverse, :search_geonames_reverse].each do |action| + assert ability.can?(action, :geocoder), "should be able to #{action} geocoder" + end + end + test "diary permissions for a guest" do ability = Ability.new nil, tokens [:list, :rss, :view, :comments].each do |action| From 91fc65a2e3ad47414344a6d5dc03cb5fb2a08ce1 Mon Sep 17 00:00:00 2001 From: Chris Flipse Date: Sun, 17 Jun 2018 13:15:49 -0400 Subject: [PATCH 12/27] separate ability and capability These are asking fundamentally different questions; Abilities are asking the application if the user has a role that allows the user to take a certain action Capabilities are asking if the user has granted the application to perform a certain type of action CanCanCan makes no distinction, however, so the `granted_capabilities` method is provided as a point that can be checked in rescue methods, so that one can _attempt_ to continue to provide the more informative error messages around permission refusals --- app/controllers/application_controller.rb | 6 ++- app/models/ability.rb | 11 +---- app/models/capability.rb | 19 ++++++++ test/models/abilities_test.rb | 55 +++-------------------- test/models/capability_test.rb | 51 +++++++++++++++++++++ 5 files changed, 81 insertions(+), 61 deletions(-) create mode 100644 app/models/capability.rb create mode 100644 test/models/capability_test.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b6a2467a4..2be8c1637 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -471,7 +471,11 @@ class ApplicationController < ActionController::Base end def current_ability - Ability.new(current_user, current_token) + Ability.new(current_user).merge(granted_capabily) + end + + def granted_capabily + Capability.new(current_user, current_token) end def deny_access(exception) diff --git a/app/models/ability.rb b/app/models/ability.rb index d33430fb4..2f86ea412 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -3,7 +3,7 @@ class Ability include CanCan::Ability - def initialize(user, token) + def initialize(user) can :index, :site can [:permalink, :edit, :help, :fixthemap, :offline, :export, :about, :preview, :copyright, :key, :id, :welcome], :site @@ -17,9 +17,6 @@ class Ability can [:create, :edit, :comment, :subscribe, :unsubscribe], DiaryEntry - can [:read, :read_one], UserPreference if has_capability?(token, :allow_read_prefs) - can [:update, :update_one, :delete_one], UserPreference if has_capability?(token, :allow_write_prefs) - if user.administrator? can [:hide, :hidecomment], [DiaryEntry, DiaryComment] end @@ -51,10 +48,4 @@ class Ability # See the wiki for details: # https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities end - - # If a user provides no tokens, they've authenticated via a non-oauth method - # and permission to access to all capabilities is assumed. - def has_capability?(token, cap) - token.nil? || token.read_attribute(cap) - end end diff --git a/app/models/capability.rb b/app/models/capability.rb new file mode 100644 index 000000000..174687503 --- /dev/null +++ b/app/models/capability.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class Capability + include CanCan::Ability + + def initialize(user, token) + if user + can [:read, :read_one], UserPreference if has_capability?(token, :allow_read_prefs) + can [:update, :update_one, :delete_one], UserPreference if has_capability?(token, :allow_write_prefs) + + end + end + + # If a user provides no tokens, they've authenticated via a non-oauth method + # and permission to access to all capabilities is assumed. + def has_capability?(token, cap) + token.nil? || token.read_attribute(cap) + end +end diff --git a/test/models/abilities_test.rb b/test/models/abilities_test.rb index 298e8299b..77c14f40f 100644 --- a/test/models/abilities_test.rb +++ b/test/models/abilities_test.rb @@ -3,21 +3,12 @@ require "test_helper" class AbilityTest < ActiveSupport::TestCase - - def tokens(*toks) - AccessToken.new do |token| - toks.each do |t| - token.public_send("#{t}=", true) - end - end - end - end class GuestAbilityTest < AbilityTest test "geocoder permission for a guest" do - ability = Ability.new nil, tokens + ability = Ability.new nil [:search, :search_latlon, :search_ca_postcode, :search_osm_nominatim, :search_geonames, :search_osm_nominatim_reverse, :search_geonames_reverse].each do |action| @@ -26,7 +17,7 @@ class GuestAbilityTest < AbilityTest end test "diary permissions for a guest" do - ability = Ability.new nil, tokens + ability = Ability.new nil [:list, :rss, :view, :comments].each do |action| assert ability.can?(action, DiaryEntry), "should be able to #{action} DiaryEntries" end @@ -42,7 +33,7 @@ end class UserAbilityTest < AbilityTest test "Diary permissions" do - ability = Ability.new create(:user), tokens + ability = Ability.new create(:user) [:list, :rss, :view, :comments, :create, :edit, :comment, :subscribe, :unsubscribe].each do |action| assert ability.can?(action, DiaryEntry), "should be able to #{action} DiaryEntries" @@ -53,48 +44,12 @@ class UserAbilityTest < AbilityTest assert ability.cannot?(action, DiaryComment), "should be able to #{action} DiaryEntries" end end - - test "user preferences" do - user = create(:user) - - # a user with no tokens - ability = Ability.new create(:user), nil - [:read, :read_one, :update, :update_one, :delete_one].each do |act| - assert ability.can? act, UserPreference - end - - # A user with empty tokens - ability = Ability.new create(:user), tokens - - [:read, :read_one, :update, :update_one, :delete_one].each do |act| - assert ability.cannot? act, UserPreference - end - - ability = Ability.new user, tokens(:allow_read_prefs) - - [:update, :update_one, :delete_one].each do |act| - assert ability.cannot? act, UserPreference - end - - [:read, :read_one].each do |act| - assert ability.can? act, UserPreference - end - - ability = Ability.new user, tokens(:allow_write_prefs) - [:read, :read_one].each do |act| - assert ability.cannot? act, UserPreference - end - - [:update, :update_one, :delete_one].each do |act| - assert ability.can? act, UserPreference - end - end end class AdministratorAbilityTest < AbilityTest test "Diary for an administrator" do - ability = Ability.new create(:administrator_user), tokens + ability = Ability.new create(:administrator_user) [:list, :rss, :view, :comments, :create, :edit, :comment, :subscribe, :unsubscribe, :hide, :hidecomment].each do |action| assert ability.can?(action, DiaryEntry), "should be able to #{action} DiaryEntries" end @@ -105,7 +60,7 @@ class AdministratorAbilityTest < AbilityTest end test "administrator does not auto-grant user preferences" do - ability = Ability.new create(:administrator_user), tokens + ability = Ability.new create(:administrator_user) [:read, :read_one, :update, :update_one, :delete_one].each do |act| assert ability.cannot? act, UserPreference diff --git a/test/models/capability_test.rb b/test/models/capability_test.rb new file mode 100644 index 000000000..2d5d65002 --- /dev/null +++ b/test/models/capability_test.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "test_helper" + +class CapabilityTest < ActiveSupport::TestCase + def tokens(*toks) + AccessToken.new do |token| + toks.each do |t| + token.public_send("#{t}=", true) + end + end + end +end + +class UserCapabilityTest < CapabilityTest + test "user preferences" do + user = create(:user) + + # a user with no tokens + capability = Capability.new create(:user), nil + [:read, :read_one, :update, :update_one, :delete_one].each do |act| + assert capability.can? act, UserPreference + end + + # A user with empty tokens + capability = Capability.new create(:user), tokens + + [:read, :read_one, :update, :update_one, :delete_one].each do |act| + assert capability.cannot? act, UserPreference + end + + capability = Capability.new user, tokens(:allow_read_prefs) + + [:update, :update_one, :delete_one].each do |act| + assert capability.cannot? act, UserPreference + end + + [:read, :read_one].each do |act| + assert capability.can? act, UserPreference + end + + capability = Capability.new user, tokens(:allow_write_prefs) + [:read, :read_one].each do |act| + assert capability.cannot? act, UserPreference + end + + [:update, :update_one, :delete_one].each do |act| + assert capability.can? act, UserPreference + end + end +end From 25256a484914ee40d829d9184fb4107dd0a77d44 Mon Sep 17 00:00:00 2001 From: Chris Flipse Date: Sun, 17 Jun 2018 20:27:17 -0400 Subject: [PATCH 13/27] Make rubocop happy --- Gemfile | 2 +- app/controllers/application_controller.rb | 2 +- app/controllers/diary_entry_controller.rb | 2 -- app/models/ability.rb | 4 +--- app/models/capability.rb | 8 +++++--- test/models/abilities_test.rb | 6 ------ test/test_helper.rb | 1 - 7 files changed, 8 insertions(+), 17 deletions(-) diff --git a/Gemfile b/Gemfile index 8b8eae0bb..a385851ff 100644 --- a/Gemfile +++ b/Gemfile @@ -42,6 +42,7 @@ gem "image_optim_rails" # Load rails plugins gem "actionpack-page_caching" +gem "cancancan" gem "composite_primary_keys", "~> 10.0.0" gem "dynamic_form" gem "http_accept_language", "~> 2.0.0" @@ -54,7 +55,6 @@ gem "rails-i18n", "~> 4.0.0" gem "record_tag_helper" gem "rinku", ">= 1.2.2", :require => "rails_rinku" gem "validates_email_format_of", ">= 1.5.1" -gem "cancancan" # Native OSM extensions gem "quad_tile", "~> 1.0.1" diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 2be8c1637..eed183893 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -478,7 +478,7 @@ class ApplicationController < ActionController::Base Capability.new(current_user, current_token) end - def deny_access(exception) + def deny_access(_exception) if current_user set_locale report_error t("oauth.permissions.missing"), :forbidden diff --git a/app/controllers/diary_entry_controller.rb b/app/controllers/diary_entry_controller.rb index ff8758990..6e9268008 100644 --- a/app/controllers/diary_entry_controller.rb +++ b/app/controllers/diary_entry_controller.rb @@ -11,7 +11,6 @@ class DiaryEntryController < ApplicationController before_action :check_database_writable, :only => [:new, :edit, :comment, :hide, :hidecomment, :subscribe, :unsubscribe] before_action :allow_thirdparty_images, :only => [:new, :edit, :list, :view, :comments] - def new @title = t "diary_entry.new.title" @@ -247,7 +246,6 @@ class DiaryEntryController < ApplicationController params.require(:diary_comment).permit(:body) end - ## # decide on a location for the diary entry map def set_map_location diff --git a/app/models/ability.rb b/app/models/ability.rb index 2f86ea412..5be0b37e5 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -17,9 +17,7 @@ class Ability can [:create, :edit, :comment, :subscribe, :unsubscribe], DiaryEntry - if user.administrator? - can [:hide, :hidecomment], [DiaryEntry, DiaryComment] - end + can [:hide, :hidecomment], [DiaryEntry, DiaryComment] if user.administrator? end # Define abilities for the passed in user here. For example: # diff --git a/app/models/capability.rb b/app/models/capability.rb index 174687503..db2d71711 100644 --- a/app/models/capability.rb +++ b/app/models/capability.rb @@ -5,15 +5,17 @@ class Capability def initialize(user, token) if user - can [:read, :read_one], UserPreference if has_capability?(token, :allow_read_prefs) - can [:update, :update_one, :delete_one], UserPreference if has_capability?(token, :allow_write_prefs) + can [:read, :read_one], UserPreference if capability?(token, :allow_read_prefs) + can [:update, :update_one, :delete_one], UserPreference if capability?(token, :allow_write_prefs) end end + private + # If a user provides no tokens, they've authenticated via a non-oauth method # and permission to access to all capabilities is assumed. - def has_capability?(token, cap) + def capability?(token, cap) token.nil? || token.read_attribute(cap) end end diff --git a/test/models/abilities_test.rb b/test/models/abilities_test.rb index 77c14f40f..28a5c7fd9 100644 --- a/test/models/abilities_test.rb +++ b/test/models/abilities_test.rb @@ -6,7 +6,6 @@ class AbilityTest < ActiveSupport::TestCase end class GuestAbilityTest < AbilityTest - test "geocoder permission for a guest" do ability = Ability.new nil @@ -27,11 +26,9 @@ class GuestAbilityTest < AbilityTest assert ability.cannot?(action, DiaryComment), "should be able to #{action} DiaryEntries" end end - end class UserAbilityTest < AbilityTest - test "Diary permissions" do ability = Ability.new create(:user) @@ -47,7 +44,6 @@ class UserAbilityTest < AbilityTest end class AdministratorAbilityTest < AbilityTest - test "Diary for an administrator" do ability = Ability.new create(:administrator_user) [:list, :rss, :view, :comments, :create, :edit, :comment, :subscribe, :unsubscribe, :hide, :hidecomment].each do |action| @@ -66,6 +62,4 @@ class AdministratorAbilityTest < AbilityTest assert ability.cannot? act, UserPreference end end - - end diff --git a/test/test_helper.rb b/test/test_helper.rb index 39e8cdd05..df88a6ee0 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -95,7 +95,6 @@ module ActiveSupport end end - ## # set request readers to ask for a particular error format def error_format(format) From f8f7ab15685403a2b440723f83fdd8451488c908 Mon Sep 17 00:00:00 2001 From: Andy Allan Date: Wed, 10 Oct 2018 11:40:41 +0200 Subject: [PATCH 14/27] Change abilities based on upstream renamings --- app/controllers/diary_entry_controller.rb | 4 ++-- app/models/ability.rb | 2 +- test/models/abilities_test.rb | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/diary_entry_controller.rb b/app/controllers/diary_entry_controller.rb index d3d7f6a7c..cff57920b 100644 --- a/app/controllers/diary_entry_controller.rb +++ b/app/controllers/diary_entry_controller.rb @@ -225,8 +225,8 @@ class DiaryEntryController < ApplicationController # a helpful error message and return them to the user page. def deny_access(exception) if current_user && exception.action.in?([:hide, :hidecomment]) - flash[:error] = t("user.filter.not_an_administrator") - redirect_to :action => "view" + flash[:error] = t("users.filter.not_an_administrator") + redirect_to :action => "show" else super end diff --git a/app/models/ability.rb b/app/models/ability.rb index 5be0b37e5..3919bde92 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -7,7 +7,7 @@ class Ability can :index, :site can [:permalink, :edit, :help, :fixthemap, :offline, :export, :about, :preview, :copyright, :key, :id, :welcome], :site - can [:list, :rss, :view, :comments], DiaryEntry + can [:index, :rss, :show, :comments], DiaryEntry can [:search, :search_latlon, :search_ca_postcode, :search_osm_nominatim, :search_geonames, :search_osm_nominatim_reverse, :search_geonames_reverse], :geocoder diff --git a/test/models/abilities_test.rb b/test/models/abilities_test.rb index 28a5c7fd9..be659af4a 100644 --- a/test/models/abilities_test.rb +++ b/test/models/abilities_test.rb @@ -17,7 +17,7 @@ class GuestAbilityTest < AbilityTest test "diary permissions for a guest" do ability = Ability.new nil - [:list, :rss, :view, :comments].each do |action| + [:index, :rss, :show, :comments].each do |action| assert ability.can?(action, DiaryEntry), "should be able to #{action} DiaryEntries" end @@ -32,7 +32,7 @@ class UserAbilityTest < AbilityTest test "Diary permissions" do ability = Ability.new create(:user) - [:list, :rss, :view, :comments, :create, :edit, :comment, :subscribe, :unsubscribe].each do |action| + [:index, :rss, :show, :comments, :create, :edit, :comment, :subscribe, :unsubscribe].each do |action| assert ability.can?(action, DiaryEntry), "should be able to #{action} DiaryEntries" end @@ -46,7 +46,7 @@ end class AdministratorAbilityTest < AbilityTest test "Diary for an administrator" do ability = Ability.new create(:administrator_user) - [:list, :rss, :view, :comments, :create, :edit, :comment, :subscribe, :unsubscribe, :hide, :hidecomment].each do |action| + [:index, :rss, :show, :comments, :create, :edit, :comment, :subscribe, :unsubscribe, :hide, :hidecomment].each do |action| assert ability.can?(action, DiaryEntry), "should be able to #{action} DiaryEntries" end From fb2c1f6cfd7895da49889f1a915c93d3e7e32a3a Mon Sep 17 00:00:00 2001 From: Andy Allan Date: Wed, 10 Oct 2018 11:49:45 +0200 Subject: [PATCH 15/27] Refactor site#welcome to use abilities instead of require_user --- app/controllers/site_controller.rb | 4 +--- app/models/ability.rb | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/controllers/site_controller.rb b/app/controllers/site_controller.rb index 2fa91256e..4b960e4e2 100644 --- a/app/controllers/site_controller.rb +++ b/app/controllers/site_controller.rb @@ -103,9 +103,7 @@ class SiteController < ApplicationController @locale = params[:copyright_locale] || I18n.locale end - def welcome - require_user - end + def welcome; end def help; end diff --git a/app/models/ability.rb b/app/models/ability.rb index 3919bde92..3ff466568 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -5,7 +5,7 @@ class Ability def initialize(user) can :index, :site - can [:permalink, :edit, :help, :fixthemap, :offline, :export, :about, :preview, :copyright, :key, :id, :welcome], :site + can [:permalink, :edit, :help, :fixthemap, :offline, :export, :about, :preview, :copyright, :key, :id], :site can [:index, :rss, :show, :comments], DiaryEntry @@ -13,7 +13,7 @@ class Ability :search_geonames, :search_osm_nominatim_reverse, :search_geonames_reverse], :geocoder if user - can :weclome, :site + can :welcome, :site can [:create, :edit, :comment, :subscribe, :unsubscribe], DiaryEntry From 901c29a82076d6c3079216254ea1f70cee28cced Mon Sep 17 00:00:00 2001 From: Andy Allan Date: Wed, 10 Oct 2018 11:55:00 +0200 Subject: [PATCH 16/27] Fix typo in method name --- app/controllers/application_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 2c5fbe51d..ad91b3a3b 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -470,10 +470,10 @@ class ApplicationController < ActionController::Base end def current_ability - Ability.new(current_user).merge(granted_capabily) + Ability.new(current_user).merge(granted_capability) end - def granted_capabily + def granted_capability Capability.new(current_user, current_token) end From dfb9e408206899808c7049df66212d0b0f7f2d7d Mon Sep 17 00:00:00 2001 From: Andy Allan Date: Wed, 10 Oct 2018 16:34:44 +0200 Subject: [PATCH 17/27] Move issues and reports to authorization system --- app/controllers/issue_comments_controller.rb | 10 ++++++---- app/controllers/issues_controller.rb | 11 +++++++---- app/controllers/reports_controller.rb | 3 ++- app/models/ability.rb | 16 ++++++++++++---- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/app/controllers/issue_comments_controller.rb b/app/controllers/issue_comments_controller.rb index 8d1acec75..0e4a7079e 100644 --- a/app/controllers/issue_comments_controller.rb +++ b/app/controllers/issue_comments_controller.rb @@ -3,8 +3,8 @@ class IssueCommentsController < ApplicationController before_action :authorize_web before_action :set_locale - before_action :require_user - before_action :check_permission + + authorize_resource def create @issue = Issue.find(params[:issue_id]) @@ -22,10 +22,12 @@ class IssueCommentsController < ApplicationController params.require(:issue_comment).permit(:body) end - def check_permission - unless current_user.administrator? || current_user.moderator? + def deny_access(_exception) + if current_user flash[:error] = t("application.require_moderator_or_admin.not_a_moderator_or_admin") redirect_to root_path + else + super end end diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index ad38454f0..8943f2d4a 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -3,8 +3,9 @@ class IssuesController < ApplicationController before_action :authorize_web before_action :set_locale - before_action :require_user - before_action :check_permission + + authorize_resource + before_action :find_issue, :only => [:show, :resolve, :reopen, :ignore] def index @@ -82,10 +83,12 @@ class IssuesController < ApplicationController @issue = Issue.find(params[:id]) end - def check_permission - unless current_user.administrator? || current_user.moderator? + def deny_access(_exception) + if current_user flash[:error] = t("application.require_moderator_or_admin.not_a_moderator_or_admin") redirect_to root_path + else + super end end end diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index ef87a8699..808726819 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -3,7 +3,8 @@ class ReportsController < ApplicationController before_action :authorize_web before_action :set_locale - before_action :require_user + + authorize_resource def new if required_new_report_params_present? diff --git a/app/models/ability.rb b/app/models/ability.rb index 3ff466568..5107a768b 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -6,19 +6,27 @@ class Ability def initialize(user) can :index, :site can [:permalink, :edit, :help, :fixthemap, :offline, :export, :about, :preview, :copyright, :key, :id], :site - can [:index, :rss, :show, :comments], DiaryEntry - can [:search, :search_latlon, :search_ca_postcode, :search_osm_nominatim, :search_geonames, :search_osm_nominatim_reverse, :search_geonames_reverse], :geocoder if user can :welcome, :site - can [:create, :edit, :comment, :subscribe, :unsubscribe], DiaryEntry + can [:new, :create], Report - can [:hide, :hidecomment], [DiaryEntry, DiaryComment] if user.administrator? + if user.moderator? + can [:index, :show, :resolve, :ignore, :reopen], Issue + can :create, IssueComment + end + + if user.administrator? + can [:hide, :hidecomment], [DiaryEntry, DiaryComment] + can [:index, :show, :resolve, :ignore, :reopen], Issue + can :create, IssueComment + end end + # Define abilities for the passed in user here. For example: # # user ||= User.new # guest user (not logged in) From 8360f275f95450f3d2ff9815c06cdb014a32a2e4 Mon Sep 17 00:00:00 2001 From: Andy Allan Date: Wed, 10 Oct 2018 16:42:01 +0200 Subject: [PATCH 18/27] Refactor to show the Issues link based on the calculated permissions --- app/views/layouts/_header.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/_header.html.erb b/app/views/layouts/_header.html.erb index e17c6a77b..946f95feb 100644 --- a/app/views/layouts/_header.html.erb +++ b/app/views/layouts/_header.html.erb @@ -54,7 +54,7 @@