FEATURE: initial implemenation of anonymous posting mode

This commit is contained in:
Sam 2015-04-07 18:02:10 +10:00
parent cd5af46fb7
commit f5d89169e2
11 changed files with 158 additions and 2 deletions

View file

@ -1,10 +1,24 @@
export default Ember.ArrayController.extend({ export default Ember.ArrayController.extend({
showAdminLinks: Em.computed.alias("currentUser.staff"), 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: { actions: {
logout() { logout() {
Discourse.logout(); Discourse.logout();
return false; return false;
},
toggleAnon() {
Discourse.ajax("/users/toggle-anon", {method: 'POST'}).then(function(){
window.location.reload();
});
} }
} }
}); });

View file

@ -8,6 +8,9 @@
</li> </li>
<li>{{#link-to 'userActivity.bookmarks' currentUser}}{{i18n 'user.bookmarks'}}{{/link-to}}</li> <li>{{#link-to 'userActivity.bookmarks' currentUser}}{{i18n 'user.bookmarks'}}{{/link-to}}</li>
<li>{{#link-to 'preferences' currentUser}}{{i18n 'user.preferences'}}{{/link-to}}</li> <li>{{#link-to 'preferences' currentUser}}{{i18n 'user.preferences'}}{{/link-to}}</li>
{{#if allowAnon}}
<li><a href {{action toggleAnon}}>{{#if isAnon}}{{i18n 'switch_from_anon'}}{{else}}{{i18n 'switch_to_anon'}}{{/if}}</a></li>
{{/if}}
<li>{{d-button action="logout" class="btn-danger right logout" icon="sign-out" label="user.log_out"}}</li> <li>{{d-button action="logout" class="btn-danger right logout" icon="sign-out" label="user.log_out"}}</li>
</ul> </ul>
</section> </section>

View file

@ -6,7 +6,7 @@ require_dependency 'rate_limiter'
class UsersController < ApplicationController class UsersController < ApplicationController
skip_before_filter :authorize_mini_profiler, only: [:avatar] 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 :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] before_filter :respond_to_suspicious_request, only: [:create]
@ -343,6 +343,18 @@ class UsersController < ApplicationController
@success = I18n.t(message) @success = I18n.t(message)
end 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 def change_email
params.require(:email) params.require(:email)
user = fetch_user_from_params user = fetch_user_from_params

View file

@ -25,7 +25,8 @@ class CurrentUserSerializer < BasicUserSerializer
:disable_jump_reply, :disable_jump_reply,
:custom_fields, :custom_fields,
:muted_category_ids, :muted_category_ids,
:dismissed_banner_key :dismissed_banner_key,
:is_anonymous
def include_site_flagged_posts_count? def include_site_flagged_posts_count?
object.staff? object.staff?
@ -102,4 +103,10 @@ class CurrentUserSerializer < BasicUserSerializer
object.user_profile.dismissed_banner_key object.user_profile.dismissed_banner_key
end end
def is_anonymous
SiteSetting.allow_anonymous_posting &&
object.trust_level >= 1 &&
object.custom_fields["master_id"].to_i > 0
end
end end

View file

@ -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

View file

@ -213,6 +213,9 @@ en:
revert: "Revert" revert: "Revert"
failed: "Failed" failed: "Failed"
switch_to_anon: "Anonymous Mode"
switch_from_anon: "Exit Anonymous Mode"
banner: banner:
close: "Dismiss this banner." close: "Dismiss this banner."

View file

@ -44,6 +44,7 @@ en:
purge_reason: "Automatically deleted as abandoned, unactivated account" 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." disable_remote_images_download_reason: "Remote images download was disabled because there wasn't enough disk space available."
anonymous: "Anonymous"
errors: &errors errors: &errors
format: ! '%{attribute} %{message}' 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." 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." 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" 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." allow_profile_backgrounds: "Allow users to upload profile backgrounds."

View file

@ -229,6 +229,7 @@ Discourse::Application.routes.draw do
get "privacy" => "static#show", id: "privacy", as: 'privacy' get "privacy" => "static#show", id: "privacy", as: 'privacy'
get "signup" => "list#latest" get "signup" => "list#latest"
post "users/toggle-anon" => "users#toggle_anon"
post "users/read-faq" => "users#read_faq" post "users/read-faq" => "users#read_faq"
get "users/search/users" => "users#search_users" get "users/search/users" => "users#search_users"
get "users/account-created/" => "users#account_created" get "users/account-created/" => "users#account_created"

View file

@ -309,6 +309,12 @@ users:
enable_user_directory: enable_user_directory:
client: true client: true
default: true default: true
allow_anonymous_posting:
default: false
client: true
anonymous_posting_min_trust_level:
default: 1
client: true
posting: posting:
min_post_length: min_post_length:

View file

@ -309,6 +309,25 @@ describe UsersController do
end end
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 describe '#create' do
before do before do

View file

@ -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