From 4bfca12b1168816e922b099ee72afa92cb4aa73c Mon Sep 17 00:00:00 2001
From: Sam <sam.saffron@gmail.com>
Date: Wed, 8 Apr 2015 12:29:43 +1000
Subject: [PATCH] FEATURE: anonymous_account_duration_minutes , cycle anon
 accounts after N minutes from last post

fixes it so anon users can not like stuff
---
 app/models/user.rb                            |  6 ++++
 app/serializers/current_user_serializer.rb    |  4 +--
 app/services/anonymous_shadow_creator.rb      | 15 ++++++++--
 config/locales/server.en.yml                  |  1 +
 config/site_settings.yml                      |  2 ++
 lib/guardian/post_guardian.rb                 |  4 ++-
 .../services/anonymous_shadow_creator_spec.rb | 30 ++++++++++++++++++-
 7 files changed, 55 insertions(+), 7 deletions(-)

diff --git a/app/models/user.rb b/app/models/user.rb
index 6a8eea014..62604333f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -725,6 +725,12 @@ class User < ActiveRecord::Base
     UserProfile.create(user_id: id)
   end
 
+  def anonymous?
+    SiteSetting.allow_anonymous_posting &&
+      trust_level >= 1 &&
+      custom_fields["master_id"].to_i > 0
+  end
+
   protected
 
   def badge_grant
diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb
index 83e1f57c3..e8b3e717a 100644
--- a/app/serializers/current_user_serializer.rb
+++ b/app/serializers/current_user_serializer.rb
@@ -104,9 +104,7 @@ class CurrentUserSerializer < BasicUserSerializer
   end
 
   def is_anonymous
-    SiteSetting.allow_anonymous_posting &&
-      object.trust_level >= 1 &&
-      object.custom_fields["master_id"].to_i > 0
+    object.anonymous?
   end
 
 end
diff --git a/app/services/anonymous_shadow_creator.rb b/app/services/anonymous_shadow_creator.rb
index 0fb66ad15..3441455d8 100644
--- a/app/services/anonymous_shadow_creator.rb
+++ b/app/services/anonymous_shadow_creator.rb
@@ -16,7 +16,14 @@ class AnonymousShadowCreator
        user.trust_level < SiteSetting.anonymous_posting_min_trust_level
 
     if (shadow_id = user.custom_fields["shadow_id"].to_i) > 0
-      User.find_by(id: shadow_id) || create_shadow(user)
+      shadow = User.find_by(id: shadow_id)
+
+      if shadow && shadow.post_count > 0 &&
+          shadow.last_posted_at < SiteSetting.anonymous_account_duration_minutes.minutes.ago
+        shadow = nil
+      end
+
+      shadow || create_shadow(user)
     else
       create_shadow(user)
     end
@@ -34,13 +41,17 @@ class AnonymousShadowCreator
         trust_level_locked: true,
         email_private_messages: false,
         email_digests: false,
-        created_at: user.created_at
+        created_at: 1.day.ago # bypass new user restrictions
       )
 
       shadow.email_tokens.update_all  confirmed: true
       shadow.activate
 
 
+      # can not hold dupes
+      UserCustomField.where(user_id: user.id,
+                            name: "shadow_id").destroy_all
+
       UserCustomField.create!(user_id: user.id,
                               name: "shadow_id",
                               value: shadow.id)
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 8605c2257..30b1c8dd8 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -1091,6 +1091,7 @@ en:
     enable_user_directory: "Provide a directory of users for browsing"
     allow_anonymous_posting: "Allow users to switch to anonymous mode"
     anonymous_posting_min_trust_level: "Minimum trust level required to enable anonymous posting"
+    anonymous_account_duration_minutes: "To protect anonymity create a new anonymous account every N minutes for each user. Example: if set to 600, as soon as 600 minutes elapse from last post AND user switches to anon, a new anonymous account is created."
 
     allow_profile_backgrounds: "Allow users to upload profile backgrounds."
 
diff --git a/config/site_settings.yml b/config/site_settings.yml
index 48fc75187..f7ccfda3c 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -315,6 +315,8 @@ users:
   anonymous_posting_min_trust_level:
     default: 1
     client: true
+  anonymous_account_duration_minutes:
+    default: 10080
 
 posting:
   min_post_length:
diff --git a/lib/guardian/post_guardian.rb b/lib/guardian/post_guardian.rb
index 4e6c57954..2e8031be7 100644
--- a/lib/guardian/post_guardian.rb
+++ b/lib/guardian/post_guardian.rb
@@ -8,7 +8,7 @@ module PostGuardian
     already_taken_this_action = taken.any? && taken.include?(PostActionType.types[action_key])
     already_did_flagging      = taken.any? && (taken & PostActionType.flag_types.values).any?
 
-    if authenticated? && post
+    result = if authenticated? && post && !@user.anonymous?
 
       return false if action_key == :notify_moderators && !SiteSetting.enable_private_messages
 
@@ -37,6 +37,8 @@ module PostGuardian
       # 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
+
+    !!result
   end
 
   def can_defer_flags?(post)
diff --git a/spec/services/anonymous_shadow_creator_spec.rb b/spec/services/anonymous_shadow_creator_spec.rb
index 2735f44af..1c6b58c1d 100644
--- a/spec/services/anonymous_shadow_creator_spec.rb
+++ b/spec/services/anonymous_shadow_creator_spec.rb
@@ -11,6 +11,26 @@ describe AnonymousShadowCreator do
     AnonymousShadowCreator.get(Fabricate.build(:user, trust_level: 0)).should == nil
   end
 
+  it "returns a new shadow once time expires" do
+    SiteSetting.allow_anonymous_posting = true
+    SiteSetting.anonymous_account_duration_minutes = 1
+
+    user = Fabricate(:user, trust_level: 3)
+    shadow = AnonymousShadowCreator.get(user)
+
+    freeze_time 2.minutes.from_now
+    shadow2 = AnonymousShadowCreator.get(user)
+
+    shadow.id.should == shadow2.id
+    create_post(user: shadow)
+
+    freeze_time 4.minutes.from_now
+    shadow3 = AnonymousShadowCreator.get(user)
+
+    shadow2.id.should_not == shadow3.id
+
+  end
+
   it "returns a shadow for a legit user" do
     SiteSetting.allow_anonymous_posting = true
     user = Fabricate(:user, trust_level: 3)
@@ -21,9 +41,17 @@ describe AnonymousShadowCreator do
     shadow.id.should == shadow2.id
 
     shadow.trust_level.should == 1
-
     shadow.username.should == "anonymous"
 
+    shadow.created_at.should_not == user.created_at
+
+
+    p = create_post
+    Guardian.new(shadow).post_can_act?(p, :like).should == false
+    Guardian.new(user).post_can_act?(p, :like).should == true
+
+    user.anonymous?.should == false
+    shadow.anonymous?.should == true
   end
 
 end