mirror of
https://github.com/discourse/discourse.git
synced 2025-05-08 08:42:58 +08:00

Revamped system for managing authentication tokens. - Every user has 1 token per client (web browser) - Tokens are rotated every 10 minutes New system migrates the old tokens to "legacy" tokens, so users still remain logged on. Also introduces weekly job to expire old auth tokens.
381 lines
11 KiB
Ruby
381 lines
11 KiB
Ruby
require_dependency 'user_destroyer'
|
|
require_dependency 'admin_user_index_query'
|
|
|
|
class Admin::UsersController < Admin::AdminController
|
|
|
|
before_filter :fetch_user, only: [:suspend,
|
|
:unsuspend,
|
|
:refresh_browsers,
|
|
:log_out,
|
|
:revoke_admin,
|
|
:grant_admin,
|
|
:revoke_moderation,
|
|
:grant_moderation,
|
|
:approve,
|
|
:activate,
|
|
:deactivate,
|
|
:block,
|
|
:unblock,
|
|
:trust_level,
|
|
:trust_level_lock,
|
|
:add_group,
|
|
:remove_group,
|
|
:primary_group,
|
|
:generate_api_key,
|
|
:revoke_api_key,
|
|
:anonymize,
|
|
:reset_bounce_score]
|
|
|
|
def index
|
|
users = ::AdminUserIndexQuery.new(params).find_users
|
|
|
|
if params[:show_emails] == "true"
|
|
guardian.can_see_emails = true
|
|
StaffActionLogger.new(current_user).log_show_emails(users)
|
|
end
|
|
|
|
render_serialized(users, AdminUserListSerializer)
|
|
end
|
|
|
|
def show
|
|
@user = User.find_by(id: params[:id])
|
|
raise Discourse::NotFound unless @user
|
|
render_serialized(@user, AdminDetailedUserSerializer, root: false)
|
|
end
|
|
|
|
def delete_all_posts
|
|
@user = User.find_by(id: params[:user_id])
|
|
@user.delete_all_posts!(guardian)
|
|
# staff action logs will have an entry for each post
|
|
render nothing: true
|
|
end
|
|
|
|
def suspend
|
|
guardian.ensure_can_suspend!(@user)
|
|
@user.suspended_till = params[:duration].to_i.days.from_now
|
|
@user.suspended_at = DateTime.now
|
|
@user.save!
|
|
@user.revoke_api_key
|
|
StaffActionLogger.new(current_user).log_user_suspend(@user, params[:reason])
|
|
@user.logged_out
|
|
render nothing: true
|
|
end
|
|
|
|
def unsuspend
|
|
guardian.ensure_can_suspend!(@user)
|
|
@user.suspended_till = nil
|
|
@user.suspended_at = nil
|
|
@user.save!
|
|
StaffActionLogger.new(current_user).log_user_unsuspend(@user)
|
|
render nothing: true
|
|
end
|
|
|
|
def log_out
|
|
if @user
|
|
@user.user_auth_tokens.destroy_all
|
|
@user.logged_out
|
|
render json: success_json
|
|
else
|
|
render json: {error: I18n.t('admin_js.admin.users.id_not_found')}, status: 404
|
|
end
|
|
end
|
|
|
|
def refresh_browsers
|
|
refresh_browser @user
|
|
render nothing: true
|
|
end
|
|
|
|
def revoke_admin
|
|
guardian.ensure_can_revoke_admin!(@user)
|
|
@user.revoke_admin!
|
|
StaffActionLogger.new(current_user).log_revoke_admin(@user)
|
|
render nothing: true
|
|
end
|
|
|
|
def generate_api_key
|
|
api_key = @user.generate_api_key(current_user)
|
|
render_serialized(api_key, ApiKeySerializer)
|
|
end
|
|
|
|
def revoke_api_key
|
|
@user.revoke_api_key
|
|
render nothing: true
|
|
end
|
|
|
|
def grant_admin
|
|
guardian.ensure_can_grant_admin!(@user)
|
|
@user.grant_admin!
|
|
StaffActionLogger.new(current_user).log_grant_admin(@user)
|
|
render_serialized(@user, AdminUserSerializer)
|
|
end
|
|
|
|
def revoke_moderation
|
|
guardian.ensure_can_revoke_moderation!(@user)
|
|
@user.revoke_moderation!
|
|
StaffActionLogger.new(current_user).log_revoke_moderation(@user)
|
|
render nothing: true
|
|
end
|
|
|
|
def grant_moderation
|
|
guardian.ensure_can_grant_moderation!(@user)
|
|
@user.grant_moderation!
|
|
StaffActionLogger.new(current_user).log_grant_moderation(@user)
|
|
render_serialized(@user, AdminUserSerializer)
|
|
end
|
|
|
|
def add_group
|
|
group = Group.find(params[:group_id].to_i)
|
|
return render_json_error group unless group && !group.automatic
|
|
|
|
group.add(@user)
|
|
GroupActionLogger.new(current_user, group).log_add_user_to_group(@user)
|
|
|
|
render nothing: true
|
|
end
|
|
|
|
def remove_group
|
|
group = Group.find(params[:group_id].to_i)
|
|
return render_json_error group unless group && !group.automatic
|
|
group.remove(@user)
|
|
GroupActionLogger.new(current_user, group).log_remove_user_from_group(@user)
|
|
render nothing: true
|
|
end
|
|
|
|
|
|
def primary_group
|
|
guardian.ensure_can_change_primary_group!(@user)
|
|
@user.primary_group_id = params[:primary_group_id]
|
|
@user.save!
|
|
render nothing: true
|
|
end
|
|
|
|
def trust_level
|
|
guardian.ensure_can_change_trust_level!(@user)
|
|
level = params[:level].to_i
|
|
|
|
|
|
if !@user.trust_level_locked && [0,1,2].include?(level) && Promotion.send("tl#{level+1}_met?", @user)
|
|
@user.trust_level_locked = true
|
|
@user.save
|
|
end
|
|
|
|
if !@user.trust_level_locked && level == 3 && Promotion.tl3_lost?(@user)
|
|
@user.trust_level_locked = true
|
|
@user.save
|
|
end
|
|
|
|
@user.change_trust_level!(level, log_action_for: current_user)
|
|
|
|
render_serialized(@user, AdminUserSerializer)
|
|
rescue Discourse::InvalidAccess => e
|
|
render_json_error(e.message)
|
|
end
|
|
|
|
def trust_level_lock
|
|
guardian.ensure_can_change_trust_level!(@user)
|
|
|
|
new_lock = params[:locked].to_s
|
|
unless new_lock =~ /true|false/
|
|
return render_json_error I18n.t('errors.invalid_boolean')
|
|
end
|
|
|
|
@user.trust_level_locked = new_lock == "true"
|
|
@user.save
|
|
|
|
StaffActionLogger.new(current_user).log_lock_trust_level(@user)
|
|
|
|
unless @user.trust_level_locked
|
|
p = Promotion.new(@user)
|
|
2.times{ p.review }
|
|
p.review_tl2
|
|
if @user.trust_level == 3 && Promotion.tl3_lost?(@user)
|
|
@user.change_trust_level!(2, log_action_for: current_user)
|
|
end
|
|
end
|
|
|
|
render nothing: true
|
|
end
|
|
|
|
def approve
|
|
guardian.ensure_can_approve!(@user)
|
|
@user.approve(current_user)
|
|
render nothing: true
|
|
end
|
|
|
|
def approve_bulk
|
|
User.where(id: params[:users]).each do |u|
|
|
u.approve(current_user) if guardian.can_approve?(u)
|
|
end
|
|
render nothing: true
|
|
end
|
|
|
|
def activate
|
|
guardian.ensure_can_activate!(@user)
|
|
@user.activate
|
|
StaffActionLogger.new(current_user).log_user_activate(@user, I18n.t('user.activated_by_staff'))
|
|
render json: success_json
|
|
end
|
|
|
|
def deactivate
|
|
guardian.ensure_can_deactivate!(@user)
|
|
@user.deactivate
|
|
StaffActionLogger.new(current_user).log_user_deactivate(@user, I18n.t('user.deactivated_by_staff'))
|
|
refresh_browser @user
|
|
render nothing: true
|
|
end
|
|
|
|
def block
|
|
guardian.ensure_can_block_user! @user
|
|
UserBlocker.block(@user, current_user, keep_posts: true)
|
|
render nothing: true
|
|
end
|
|
|
|
def unblock
|
|
guardian.ensure_can_unblock_user! @user
|
|
UserBlocker.unblock(@user, current_user)
|
|
render nothing: true
|
|
end
|
|
|
|
def reject_bulk
|
|
success_count = 0
|
|
d = UserDestroyer.new(current_user)
|
|
|
|
User.where(id: params[:users]).each do |u|
|
|
success_count += 1 if guardian.can_delete_user?(u) and d.destroy(u, params.slice(:context)) rescue UserDestroyer::PostsExistError
|
|
end
|
|
|
|
render json: {
|
|
success: success_count,
|
|
failed: (params[:users].try(:size) || 0) - success_count
|
|
}
|
|
end
|
|
|
|
def destroy
|
|
user = User.find_by(id: params[:id].to_i)
|
|
guardian.ensure_can_delete_user!(user)
|
|
begin
|
|
options = params.slice(:delete_posts, :block_email, :block_urls, :block_ip, :context, :delete_as_spammer)
|
|
if UserDestroyer.new(current_user).destroy(user, options)
|
|
render json: { deleted: true }
|
|
else
|
|
render json: {
|
|
deleted: false,
|
|
user: AdminDetailedUserSerializer.new(user, root: false).as_json
|
|
}
|
|
end
|
|
rescue UserDestroyer::PostsExistError
|
|
raise Discourse::InvalidAccess.new("User #{user.username} has #{user.post_count} posts, so can't be deleted.")
|
|
end
|
|
end
|
|
|
|
def badges
|
|
end
|
|
|
|
def tl3_requirements
|
|
end
|
|
|
|
def ip_info
|
|
params.require(:ip)
|
|
ip = params[:ip]
|
|
|
|
# should we cache results in redis?
|
|
location = Excon.get("http://ipinfo.io/#{ip}/json", read_timeout: 30, connect_timeout: 30).body rescue nil
|
|
|
|
render json: location
|
|
end
|
|
|
|
def sync_sso
|
|
return render nothing: true, status: 404 unless SiteSetting.enable_sso
|
|
|
|
sso = DiscourseSingleSignOn.parse("sso=#{params[:sso]}&sig=#{params[:sig]}")
|
|
|
|
begin
|
|
user = sso.lookup_or_create_user
|
|
render_serialized(user, AdminDetailedUserSerializer, root: false)
|
|
rescue ActiveRecord::RecordInvalid => ex
|
|
render json: failed_json.merge(message: ex.message), status: 403
|
|
end
|
|
end
|
|
|
|
def delete_other_accounts_with_same_ip
|
|
params.require(:ip)
|
|
params.require(:exclude)
|
|
params.require(:order)
|
|
|
|
user_destroyer = UserDestroyer.new(current_user)
|
|
options = { delete_posts: true, block_email: true, block_urls: true, block_ip: true, delete_as_spammer: true }
|
|
|
|
AdminUserIndexQuery.new(params).find_users(50).each do |user|
|
|
user_destroyer.destroy(user, options) rescue nil
|
|
end
|
|
|
|
render json: success_json
|
|
end
|
|
|
|
def total_other_accounts_with_same_ip
|
|
params.require(:ip)
|
|
params.require(:exclude)
|
|
params.require(:order)
|
|
|
|
render json: { total: AdminUserIndexQuery.new(params).count_users }
|
|
end
|
|
|
|
def invite_admin
|
|
|
|
email = params[:email]
|
|
unless user = User.find_by_email(email)
|
|
name = params[:name] if params[:name].present?
|
|
username = params[:username] if params[:username].present?
|
|
|
|
user = User.new(email: email)
|
|
user.password = SecureRandom.hex
|
|
user.username = UserNameSuggester.suggest(username || name || email)
|
|
user.name = User.suggest_name(name || username || email)
|
|
end
|
|
|
|
user.active = true
|
|
user.save!
|
|
user.grant_admin!
|
|
user.change_trust_level!(4)
|
|
user.email_tokens.update_all confirmed: true
|
|
|
|
email_token = user.email_tokens.create(email: user.email)
|
|
|
|
unless params[:send_email] == '0' || params[:send_email] == 'false'
|
|
Jobs.enqueue( :critical_user_email,
|
|
type: :account_created,
|
|
user_id: user.id,
|
|
email_token: email_token.token)
|
|
end
|
|
|
|
render json: success_json.merge!(password_url: "#{Discourse.base_url}/users/password-reset/#{email_token.token}")
|
|
|
|
end
|
|
|
|
def anonymize
|
|
guardian.ensure_can_anonymize_user!(@user)
|
|
if user = UserAnonymizer.new(@user, current_user).make_anonymous
|
|
render json: success_json.merge(username: user.username)
|
|
else
|
|
render json: failed_json.merge(user: AdminDetailedUserSerializer.new(user, root: false).as_json)
|
|
end
|
|
end
|
|
|
|
def reset_bounce_score
|
|
guardian.ensure_can_reset_bounce_score!(@user)
|
|
@user.user_stat.update_columns(bounce_score: 0, reset_bounce_score_after: nil)
|
|
render json: success_json
|
|
end
|
|
|
|
private
|
|
|
|
def fetch_user
|
|
@user = User.find_by(id: params[:user_id])
|
|
end
|
|
|
|
def refresh_browser(user)
|
|
MessageBus.publish "/file-change", ["refresh"], user_ids: [user.id]
|
|
end
|
|
|
|
end
|