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