diff --git a/app/models/topic.rb b/app/models/topic.rb index a521b60b1df..d5bc6f426aa 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -745,4 +745,7 @@ class Topic < ActiveRecord::Base self.auto_close_at = (num_days and num_days.to_i > 0.0 ? num_days.to_i.days.from_now : nil) end + def secure_category? + category && category.secure + end end diff --git a/lib/guardian.rb b/lib/guardian.rb index fb7595ec398..13f5b5362d0 100644 --- a/lib/guardian.rb +++ b/lib/guardian.rb @@ -1,60 +1,66 @@ # The guardian is responsible for confirming access to various site resources and operations class Guardian - attr_reader :user - - def initialize(user=nil) - @user = user + class AnonymousUser + def blank?; true; end + def admin?; false; end + def staff?; false; end + def approved?; false; end + def secure_category_ids; []; end + def has_trust_level?(level); false; end end - def current_user - @user + def initialize(user=nil) + @user = user.presence || AnonymousUser.new + end + + def user + @user.presence + end + alias :current_user :user + + def anonymous? + !authenticated? + end + + def authenticated? + @user.present? end def is_admin? - @user && @user.admin? + @user.admin? end def is_staff? - @user && @user.staff? + @user.staff? end # Can the user see the object? def can_see?(obj) - return false if obj.blank? - - see_method = :"can_see_#{obj.class.name.underscore}?" - return send(see_method, obj) if respond_to?(see_method) - - return true + if obj + see_method = method_name_for :see, obj + return (see_method ? send(see_method, obj) : true) + end end # Can the user edit the obj def can_edit?(obj) - return false if obj.blank? - return false if @user.blank? - - edit_method = :"can_edit_#{obj.class.name.underscore}?" - return send(edit_method, obj) if respond_to?(edit_method) - - true + if obj && authenticated? + edit_method = method_name_for :edit, obj + return (edit_method ? send(edit_method, obj) : true) + end end # Can we delete the object def can_delete?(obj) - return false if obj.blank? - return false if @user.blank? - - delete_method = :"can_delete_#{obj.class.name.underscore}?" - return send(delete_method, obj) if respond_to?(delete_method) - - true + if obj && authenticated? + delete_method = method_name_for :delete, obj + return (delete_method ? send(delete_method, obj) : true) + end end def can_moderate?(obj) - return false if obj.blank? - return false if @user.blank? - @user.staff? + obj && is_staff? end alias :can_move_posts? :can_moderate? alias :can_see_flags? :can_moderate? @@ -62,8 +68,7 @@ class Guardian # Can the user create a topic in the forum def can_create?(klass, parent=nil) - return false if klass.blank? - return false if @user.blank? + return false unless authenticated? && klass # If no parent is provided, we look for a can_i_create_klass? # custom method. @@ -84,79 +89,52 @@ class Guardian # Can we impersonate this user? def can_impersonate?(target) - return false if target.blank? - return false if @user.blank? + target && # You must be an admin to impersonate - return false unless @user.admin? + is_admin? && # You may not impersonate other admins - return false if target.admin? + not(target.admin?) - # You may not impersonate yourself - return false if @user == target - - true + # Additionally, you may not impersonate yourself; + # but the two tests for different admin statuses + # make it impossible to be the same user. end # Can we approve it? def can_approve?(target) - return false if target.blank? - return false if @user.blank? - return false if target.approved? - @user.staff? + is_staff? && target && not(target.approved?) end alias :can_activate? :can_approve? def can_ban?(user) - is_staff? && user && !user.staff? + user && is_staff? && not(user.staff?) end - alias :can_deactivate? :can_ban? def can_clear_flags?(post) - return false if @user.blank? - return false if post.blank? - @user.staff? + is_staff? && post end def can_revoke_admin?(admin) - return false unless @user.try(:admin?) - return false if admin.blank? - return false if @user.id == admin.id - return false unless admin.admin? - true + can_administer_user?(admin) && admin.admin? end def can_grant_admin?(user) - return false unless @user.try(:admin?) - return false if user.blank? - return false if @user.id == user.id - return false if user.admin? - true + can_administer_user?(user) && not(user.admin?) end def can_revoke_moderation?(moderator) - return false unless is_admin? - return false if moderator.blank? - return false if @user.id == moderator.id && !is_admin? - return false unless moderator.moderator? - true + can_administer?(moderator) && moderator.moderator? end def can_grant_moderation?(user) - return false unless is_admin? - return false unless user - return false if @user.id == user.id && !is_admin? - return false if user.moderator? - true + can_administer?(user) && not(user.moderator?) end def can_delete_user?(user_to_delete) - return false unless is_admin? - return false unless user_to_delete - return false if user_to_delete.post_count > 0 - true + can_administer?(user_to_delete) && user_to_delete.post_count <= 0 end # Can we see who acted on a post in a particular way? @@ -187,35 +165,26 @@ class Guardian end def can_see_pending_invites_from?(user) - return false unless user && @user - return user == @user + is_me?(user) end # For now, can_invite_to is basically can_see? def can_invite_to?(object) - return false unless @user - return false unless can_see?(object) - return false if SiteSetting.must_approve_users? - @user.has_trust_level?(:regular) || @user.staff? + authenticated? && can_see?(object) && + not(SiteSetting.must_approve_users?) && + (@user.has_trust_level?(:regular) || is_staff?) end - def can_see_deleted_posts? - return true if is_staff? - false + is_staff? end def can_see_private_messages?(user_id) - return true if is_staff? - return false unless @user - @user.id == user_id + is_staff? || (authenticated? && @user.id == user_id) end def can_delete_all_posts?(user) - return false unless is_staff? - return false if user.created_at < 7.days.ago - - true + is_staff? && user.created_at >= 7.days.ago end # Support for ensure_{blah}! methods. @@ -243,10 +212,7 @@ class Guardian end def can_create_post_on_topic?(topic) - return true if is_staff? - return false if topic.closed? - return false if topic.archived? - true + is_staff? || not(topic.closed? || topic.archived?) end # Editing Methods @@ -255,20 +221,15 @@ class Guardian end def can_edit_post?(post) - return true if is_staff? - return false if post.topic.archived? - (post.user == @user) + is_staff? || (not(post.topic.archived?) && is_my_own?(post)) end def can_edit_user?(user) - return true if user == @user - is_staff? + is_me?(user) || is_staff? end def can_edit_topic?(topic) - return true if is_staff? - return true if topic.user == @user - false + is_staff? || is_my_own?(topic) end # Deleting Methods @@ -277,92 +238,69 @@ class Guardian return false if post.post_number == 1 # You can delete your own posts - return !post.user_deleted? if post.user == @user + return !post.user_deleted? if is_my_own?(post) is_staff? end # Recovery Method def can_recover_post?(post) - return false unless @user is_staff? end def can_delete_category?(category) - return false unless is_staff? - return category.topic_count == 0 + is_staff? && category.topic_count == 0 end def can_delete_topic?(topic) - return false unless is_staff? - return false if Category.exists?(topic_id: topic.id) - true + is_staff? && not(Category.exists?(topic_id: topic.id)) end def can_delete_post_action?(post_action) - # You can only undo your own actions - return false unless @user - return false unless post_action.user_id == @user.id - return false if post_action.is_private_message? + is_my_own?(post_action) && not(post_action.is_private_message?) && # Make sure they want to delete it within the window - return post_action.created_at > SiteSetting.post_undo_action_window_mins.minutes.ago + post_action.created_at > SiteSetting.post_undo_action_window_mins.minutes.ago end def can_send_private_message?(target) - return false unless User === target || Group === target - return false unless @user + (User === target || Group === target) && + authenticated? && # Can't send message to yourself - return false if User === target && @user.id == target.id + is_not_me?(target) && # Have to be a basic level at least - return false unless @user.has_trust_level?(:basic) + @user.has_trust_level?(:basic) && SiteSetting.enable_private_messages end def can_reply_as_new_topic?(topic) - return false unless @user - return false unless topic - return false if topic.private_message? - - @user.has_trust_level?(:basic) + authenticated? && topic && not(topic.private_message?) && @user.has_trust_level?(:basic) end def can_see_topic?(topic) - return false unless topic + if topic + is_staff? || - return true if @user && is_staff? - return false if topic.deleted_at + topic.deleted_at.nil? && - if topic.category && topic.category.secure - return false unless @user && can_see_category?(topic.category) + # not secure, or I can see it + (not(topic.secure_category?) || can_see_category?(topic.category)) && + + # not private, or I am allowed (or an admin) + (not(topic.private_message?) || authenticated? && (topic.all_allowed_users.where(id: @user.id).exists? || is_admin?)) end - - if topic.private_message? - return false unless @user - return true if topic.all_allowed_users.where(id: @user.id).exists? - return is_admin? - end - true end def can_see_post?(post) - return false unless post - - return true if @user && is_staff? - return false if post.deleted_at.present? - - can_see_topic?(post.topic) + post.present? && (is_staff? || (!post.deleted_at.present? && can_see_topic?(post.topic))) end def can_see_category?(category) - return true unless category.secure - return false unless @user - - secure_category_ids.include?(category.id) + not(category.secure) || secure_category_ids.include?(category.id) end def can_vote?(post, opts={}) @@ -372,37 +310,62 @@ class Guardian # Can the user act on the post in a particular way. # taken_actions = the list of actions the user has already taken def post_can_act?(post, action_key, opts={}) - return false if @user.blank? - return false if post.blank? - taken = opts[:taken_actions] - taken = taken.keys if taken + taken = opts[:taken_actions].try(:keys).to_a + is_flag = PostActionType.is_flag?(action_key) + already_taken_this_action = taken.any? && taken.include?(PostActionType.types[action_key]) + already_did_flagging = taken.any? && (taken & PostActionType.flag_types.values).any? - # we always allow flagging - if PostActionType.is_flag?(action_key) - return false unless @user.has_trust_level?(:basic) + if authenticated? && post + # we always allow flagging - NOTE: this does not seem true, see specs. (MVH) + (is_flag && @user.has_trust_level?(:basic) && not(already_did_flagging)) || - if taken - return false unless (taken & PostActionType.flag_types.values).empty? - end - else - return false if taken && taken.include?(PostActionType.types[action_key]) + # not a flagging action, and haven't done it already + not(is_flag || already_taken_this_action) && + + # nothing except flagging on archived posts + not(post.topic.archived?) && + + # don't like your own stuff + not(action_key == :like && is_my_own?(post)) && + + # no voting more than once on single vote topics + not(action_key == :vote && opts[:voted_in_topic] && post.topic.has_meta_data_boolean?(:single_vote)) end - - # nothing else on archived posts - return false if post.topic.archived? - - case action_key - when :like - return false if post.user == @user - when :vote then - return false if opts[:voted_in_topic] && post.topic.has_meta_data_boolean?(:single_vote) - end - - return true end def secure_category_ids - @secure_category_ids ||= @user ? @user.secure_category_ids : [] + @secure_category_ids ||= @user.secure_category_ids end + + private + + def is_my_own?(obj) + @user.present? && + (obj.respond_to?(:user) || obj.respond_to?(:user_id)) && + (obj.respond_to?(:user) ? obj.user == @user : true) && + (obj.respond_to?(:user_id) ? (obj.user_id == @user.id) : true) + end + + def is_me?(other) + other && authenticated? && User === other && @user == other + end + + def is_not_me?(other) + @user.blank? || !is_me?(other) + end + + def can_administer?(obj) + is_admin? && obj.present? + end + + def can_administer_user?(other_user) + can_administer?(other_user) && is_not_me?(other_user) + end + + def method_name_for(action, obj) + method_name = :"can_#{action}_#{obj.class.name.underscore}?" + return method_name if respond_to?(method_name) + end + end diff --git a/spec/components/guardian_spec.rb b/spec/components/guardian_spec.rb index 648c114fd50..ea75df26218 100644 --- a/spec/components/guardian_spec.rb +++ b/spec/components/guardian_spec.rb @@ -38,6 +38,11 @@ describe Guardian do Guardian.new(user).post_can_act?(post, :like).should be_false end + it "always allows flagging" do + post.topic.archived = true + Guardian.new(user).post_can_act?(post, :spam).should be_true + end + it "returns false when liking yourself" do Guardian.new(post.user).post_can_act?(post, :like).should be_false end @@ -694,7 +699,7 @@ describe Guardian do user.id = 1 post.id = 1 - a = PostAction.new(user_id: user.id, post_id: post.id, post_action_type_id: 1) + a = PostAction.new(user: user, post: post, post_action_type_id: 1) a.created_at = 1.minute.ago a } @@ -794,7 +799,7 @@ describe Guardian do Guardian.new.can_grant_moderation?(user).should be_false end - it "wont allow a regular user to revoke an modearator's access" do + it "wont allow a regular user to revoke an moderator's access" do Guardian.new(user).can_grant_moderation?(moderator).should be_false end diff --git a/spec/models/topic_spec.rb b/spec/models/topic_spec.rb index ad7f12f3eb0..c0075f8ca29 100644 --- a/spec/models/topic_spec.rb +++ b/spec/models/topic_spec.rb @@ -1104,4 +1104,21 @@ describe Topic do end end + describe '#secure_category?' do + let(:category){ Category.new } + + it "is true if the category is secure" do + category.stubs(:secure).returns(true) + Topic.new(:category => category).should be_secure_category + end + + it "is false if the category is not secure" do + category.stubs(:secure).returns(false) + Topic.new(:category => category).should_not be_secure_category + end + + it "is false if there is no category" do + Topic.new(:category => nil).should_not be_secure_category + end + end end