diff --git a/app/assets/javascripts/discourse/templates/components/reviewable-user.hbs b/app/assets/javascripts/discourse/templates/components/reviewable-user.hbs index 96977b4b53b..35a0e75fdfc 100644 --- a/app/assets/javascripts/discourse/templates/components/reviewable-user.hbs +++ b/app/assets/javascripts/discourse/templates/components/reviewable-user.hbs @@ -21,6 +21,14 @@ name=(i18n 'review.user.email') value=reviewable.payload.email}} + {{reviewable-field classes='reviewable-user-details bio' + name=(i18n 'review.user.bio') + value=reviewable.payload.bio}} + + {{reviewable-field classes='reviewable-user-details bio' + name=(i18n 'review.user.website') + value=reviewable.payload.website}} + {{#each userFields as |f|}} {{reviewable-field classes='reviewable-user-details user-field' name=f.name diff --git a/app/jobs/scheduled/enqueue_suspect_users.rb b/app/jobs/scheduled/enqueue_suspect_users.rb new file mode 100644 index 00000000000..529dd058c01 --- /dev/null +++ b/app/jobs/scheduled/enqueue_suspect_users.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Jobs + class EnqueueSuspectUsers < ::Jobs::Scheduled + every 2.hours + + def execute(_args) + return unless SiteSetting.approve_suspect_users + + users = AdminUserIndexQuery.new + .suspect_users + .joins("LEFT OUTER JOIN reviewables r ON r.target_id = users.id AND r.target_type = 'User'") + .where('r.id IS NULL') + .limit(10) + + users.each do |user| + user_profile = user.user_profile + + reviewable = ReviewableUser.needs_review!( + target: user, + created_by: Discourse.system_user, + reviewable_by_moderator: true, + payload: { + username: user.username, + name: user.name, + email: user.email, + bio: user_profile.bio_raw, + website: user_profile.website, + } + ) + + if reviewable.created_new + reviewable.add_score( + Discourse.system_user, + ReviewableScore.types[:needs_approval], + reason: :suspect_user, + force_review: true + ) + end + end + end + end +end diff --git a/app/models/reviewable_user.rb b/app/models/reviewable_user.rb index 59e107105c9..2159b1278b7 100644 --- a/app/models/reviewable_user.rb +++ b/app/models/reviewable_user.rb @@ -59,6 +59,10 @@ class ReviewableUser < Reviewable if target.present? destroyer = UserDestroyer.new(performed_by) + if reviewable_scores.any? { |rs| rs.reason == 'suspect_user' } + DiscourseEvent.trigger(:suspect_user_deleted, target) + end + begin delete_args = {} delete_args[:block_ip] = true if args[:block_ip] diff --git a/app/serializers/reviewable_user_serializer.rb b/app/serializers/reviewable_user_serializer.rb index 8785d0c59d3..abaf56b4c93 100644 --- a/app/serializers/reviewable_user_serializer.rb +++ b/app/serializers/reviewable_user_serializer.rb @@ -7,7 +7,9 @@ class ReviewableUserSerializer < ReviewableSerializer payload_attributes( :username, :email, - :name + :name, + :bio, + :website ) def link_admin diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index e22d930491d..2f723b30d19 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -443,6 +443,8 @@ en: deleted_post: "(post deleted)" deleted_user: "(user deleted)" user: + bio: "Bio" + website: "Website" username: "Username" email: "Email" name: "Name" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 6b8ebd02d5d..543d018c726 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1511,6 +1511,7 @@ en: markdown_typographer_quotation_marks: "List of double and single quotes replacement pairs" post_undo_action_window_mins: "Number of minutes users are allowed to undo recent actions on a post (like, flag, etc)." must_approve_users: "Staff must approve all new user accounts before they are allowed to access the site." + approve_suspect_users: "Staff must approve all suspect accounts" pending_users_reminder_delay: "Notify moderators if new users have been waiting for approval for longer than this many hours. Set to -1 to disable notifications." maximum_session_age: "User will remain logged in for n hours since last visit" ga_universal_tracking_code: "Google Universal Analytics (analytics.js) tracking code ID, eg: UA-12345678-9; see https://google.com/analytics" @@ -4667,6 +4668,7 @@ en: invite_only: "All new users should be invited. See `invite_only`." email_auth_res_enqueue: "This email failed a DMARC check, it most likely isn't from whom it seems to be from. Check the raw email headers for more information." email_spam: "This email was flagged as spam by the header defined in `email_in_spam_header`." + suspect_user: "Users in the suspect list must be examined by staff. See `approve_suspect_users`." actions: agree: diff --git a/config/site_settings.yml b/config/site_settings.yml index 33f8e750f57..b7678336687 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -855,6 +855,8 @@ posting: approve_new_topics_unless_trust_level: default: 0 enum: "TrustLevelSetting" + approve_suspect_users: + default: false approve_unless_staged: default: false notify_about_queued_posts_after: diff --git a/spec/jobs/enqueue_suspect_users_spec.rb b/spec/jobs/enqueue_suspect_users_spec.rb new file mode 100644 index 00000000000..30514abed11 --- /dev/null +++ b/spec/jobs/enqueue_suspect_users_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Jobs::EnqueueSuspectUsers do + before { SiteSetting.approve_suspect_users = true } + + it 'does nothing when there are no suspect users' do + subject.execute({}) + + expect(ReviewableUser.count).to be_zero + end + + context 'with suspect users' do + fab!(:suspect_user) { Fabricate(:active_user, created_at: 1.day.ago) } + + it 'creates a reviewable when there is a suspect user' do + subject.execute({}) + + expect(ReviewableUser.count).to eq(1) + end + + it 'only creates one reviewable per user' do + review_user = ReviewableUser.needs_review!( + target: suspect_user, + created_by: Discourse.system_user, + reviewable_by_moderator: true + ) + + subject.execute({}) + + expect(ReviewableUser.count).to eq(1) + expect(ReviewableUser.last).to eq(review_user) + end + + it 'adds a score' do + subject.execute({}) + score = ReviewableScore.last + + expect(score.reason).to eq('suspect_user') + end + end +end