diff --git a/app/assets/javascripts/discourse/controllers/user-dropdown.js.es6 b/app/assets/javascripts/discourse/controllers/user-dropdown.js.es6
index af91f6c6f16..a45daf93344 100644
--- a/app/assets/javascripts/discourse/controllers/user-dropdown.js.es6
+++ b/app/assets/javascripts/discourse/controllers/user-dropdown.js.es6
@@ -1,10 +1,24 @@
export default Ember.ArrayController.extend({
showAdminLinks: Em.computed.alias("currentUser.staff"),
+ allowAnon: function(){
+ return Discourse.SiteSettings.allow_anonymous_posting &&
+ Discourse.User.currentProp("trust_level") >= Discourse.SiteSettings.anonymous_posting_min_trust_level;
+ }.property(),
+
+ isAnon: function(){
+ return Discourse.User.currentProp("is_anonymous");
+ }.property(),
+
actions: {
logout() {
Discourse.logout();
return false;
+ },
+ toggleAnon() {
+ Discourse.ajax("/users/toggle-anon", {method: 'POST'}).then(function(){
+ window.location.reload();
+ });
}
}
});
diff --git a/app/assets/javascripts/discourse/templates/user-dropdown.hbs b/app/assets/javascripts/discourse/templates/user-dropdown.hbs
index 12d2fe78de2..affeae79b4c 100644
--- a/app/assets/javascripts/discourse/templates/user-dropdown.hbs
+++ b/app/assets/javascripts/discourse/templates/user-dropdown.hbs
@@ -8,6 +8,9 @@
{{#link-to 'userActivity.bookmarks' currentUser}}{{i18n 'user.bookmarks'}}{{/link-to}}
{{#link-to 'preferences' currentUser}}{{i18n 'user.preferences'}}{{/link-to}}
+ {{#if allowAnon}}
+ {{#if isAnon}}{{i18n 'switch_from_anon'}}{{else}}{{i18n 'switch_to_anon'}}{{/if}}
+ {{/if}}
{{d-button action="logout" class="btn-danger right logout" icon="sign-out" label="user.log_out"}}
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index dff0d10376d..23dfb48705e 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -6,7 +6,7 @@ require_dependency 'rate_limiter'
class UsersController < ApplicationController
skip_before_filter :authorize_mini_profiler, only: [:avatar]
- skip_before_filter :check_xhr, only: [:show, :password_reset, :update, :account_created, :activate_account, :perform_account_activation, :authorize_email, :user_preferences_redirect, :avatar, :my_redirect]
+ skip_before_filter :check_xhr, only: [:show, :password_reset, :update, :account_created, :activate_account, :perform_account_activation, :authorize_email, :user_preferences_redirect, :avatar, :my_redirect, :toggle_anon]
before_filter :ensure_logged_in, only: [:username, :update, :change_email, :user_preferences_redirect, :upload_user_image, :pick_avatar, :destroy_user_image, :destroy, :check_emails]
before_filter :respond_to_suspicious_request, only: [:create]
@@ -343,6 +343,18 @@ class UsersController < ApplicationController
@success = I18n.t(message)
end
+ def toggle_anon
+ user = AnonymousShadowCreator.get_master(current_user) ||
+ AnonymousShadowCreator.get(current_user)
+
+ if user
+ log_on_user(user)
+ render json: success_json
+ else
+ render json: failed_json, status: 403
+ end
+ end
+
def change_email
params.require(:email)
user = fetch_user_from_params
diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb
index f07cd907e61..83e1f57c361 100644
--- a/app/serializers/current_user_serializer.rb
+++ b/app/serializers/current_user_serializer.rb
@@ -25,7 +25,8 @@ class CurrentUserSerializer < BasicUserSerializer
:disable_jump_reply,
:custom_fields,
:muted_category_ids,
- :dismissed_banner_key
+ :dismissed_banner_key,
+ :is_anonymous
def include_site_flagged_posts_count?
object.staff?
@@ -102,4 +103,10 @@ class CurrentUserSerializer < BasicUserSerializer
object.user_profile.dismissed_banner_key
end
+ def is_anonymous
+ SiteSetting.allow_anonymous_posting &&
+ object.trust_level >= 1 &&
+ object.custom_fields["master_id"].to_i > 0
+ end
+
end
diff --git a/app/services/anonymous_shadow_creator.rb b/app/services/anonymous_shadow_creator.rb
new file mode 100644
index 00000000000..0fb66ad1544
--- /dev/null
+++ b/app/services/anonymous_shadow_creator.rb
@@ -0,0 +1,59 @@
+class AnonymousShadowCreator
+
+ def self.get_master(user)
+ return unless user
+ return if !SiteSetting.allow_anonymous_posting
+
+ if (master_id = user.custom_fields["master_id"].to_i) > 0
+ User.find_by(id: master_id)
+ end
+
+ end
+
+ def self.get(user)
+ return unless user
+ return if !SiteSetting.allow_anonymous_posting ||
+ 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)
+ else
+ create_shadow(user)
+ end
+ end
+
+ def self.create_shadow(user)
+ User.transaction do
+ shadow = User.create!(
+ password: SecureRandom.hex,
+ email: "#{SecureRandom.hex}@#{SecureRandom.hex}.com",
+ name: "",
+ username: UserNameSuggester.suggest(I18n.t(:anonymous).downcase),
+ active: true,
+ trust_level: 1,
+ trust_level_locked: true,
+ email_private_messages: false,
+ email_digests: false,
+ created_at: user.created_at
+ )
+
+ shadow.email_tokens.update_all confirmed: true
+ shadow.activate
+
+
+ UserCustomField.create!(user_id: user.id,
+ name: "shadow_id",
+ value: shadow.id)
+
+ UserCustomField.create!(user_id: shadow.id,
+ name: "master_id",
+ value: user.id)
+
+ shadow.reload
+ user.reload
+
+ shadow
+
+ end
+ end
+end
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 5bdc6701787..87747847c34 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -213,6 +213,9 @@ en:
revert: "Revert"
failed: "Failed"
+ switch_to_anon: "Anonymous Mode"
+ switch_from_anon: "Exit Anonymous Mode"
+
banner:
close: "Dismiss this banner."
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 97f0e6b6c4d..8605c225722 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -44,6 +44,7 @@ en:
purge_reason: "Automatically deleted as abandoned, unactivated account"
disable_remote_images_download_reason: "Remote images download was disabled because there wasn't enough disk space available."
+ anonymous: "Anonymous"
errors: &errors
format: ! '%{attribute} %{message}'
@@ -1088,6 +1089,8 @@ en:
public_user_custom_fields: "A whitelist of custom fields for a user that can be shown publicly."
staff_user_custom_fields: "A whitelist of custom fields for a user that can be shown to staff."
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"
allow_profile_backgrounds: "Allow users to upload profile backgrounds."
diff --git a/config/routes.rb b/config/routes.rb
index 9c6fe01f5f0..ebac63bacfd 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -229,6 +229,7 @@ Discourse::Application.routes.draw do
get "privacy" => "static#show", id: "privacy", as: 'privacy'
get "signup" => "list#latest"
+ post "users/toggle-anon" => "users#toggle_anon"
post "users/read-faq" => "users#read_faq"
get "users/search/users" => "users#search_users"
get "users/account-created/" => "users#account_created"
diff --git a/config/site_settings.yml b/config/site_settings.yml
index 019583cb92c..48fc7518781 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -309,6 +309,12 @@ users:
enable_user_directory:
client: true
default: true
+ allow_anonymous_posting:
+ default: false
+ client: true
+ anonymous_posting_min_trust_level:
+ default: 1
+ client: true
posting:
min_post_length:
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index cbd2d81659f..159e007c712 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -309,6 +309,25 @@ describe UsersController do
end
end
+ describe '#toggle_anon' do
+ it 'allows you to toggle anon if enabled' do
+ SiteSetting.allow_anonymous_posting = true
+
+ user = log_in
+ user.trust_level = 1
+ user.save
+
+ post :toggle_anon
+ expect(response).to be_success
+ expect(session[:current_user_id]).to eq(AnonymousShadowCreator.get(user).id)
+
+ post :toggle_anon
+ expect(response).to be_success
+ expect(session[:current_user_id]).to eq(user.id)
+
+ end
+ end
+
describe '#create' do
before do
diff --git a/spec/services/anonymous_shadow_creator_spec.rb b/spec/services/anonymous_shadow_creator_spec.rb
new file mode 100644
index 00000000000..2735f44af7d
--- /dev/null
+++ b/spec/services/anonymous_shadow_creator_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe AnonymousShadowCreator do
+
+ it "returns no shadow by default" do
+ AnonymousShadowCreator.get(Fabricate.build(:user)).should == nil
+ end
+
+ it "returns no shadow if trust level is not met" do
+ SiteSetting.allow_anonymous_posting = true
+ AnonymousShadowCreator.get(Fabricate.build(:user, trust_level: 0)).should == nil
+ end
+
+ it "returns a shadow for a legit user" do
+ SiteSetting.allow_anonymous_posting = true
+ user = Fabricate(:user, trust_level: 3)
+
+ shadow = AnonymousShadowCreator.get(user)
+ shadow2 = AnonymousShadowCreator.get(user)
+
+ shadow.id.should == shadow2.id
+
+ shadow.trust_level.should == 1
+
+ shadow.username.should == "anonymous"
+
+ end
+
+end