discourse/spec/requests/users_controller_spec.rb
Martin Brennan 49a70a37f1
FIX: Remove last_unread_post excerpt logic for bookmarks (#17979)
The logic to determine what post excerpt to show for
a topic-level bookmark based on the last unread post
was complex and slow, so we decided to remove it and
always just use the first post excerpt.

This commit also fixes an issue where a couple of
instances of for_topic were missed when doing the
Bookmarkable refactors, so:

1. Clicking the topic bookmark link was not taking
   the user to the last unread post
2. When replying to a topic where there was a topic
   level bookmark with the auto delete preference
   of "on owner reply", we were not removing the
   bookmark from the UI correctly.

A test has been added for the former, the latter would
be quite time-consuming to test and not really worth
it considering it's quite an edge case UI bug.
2022-08-19 09:35:25 +10:00

5937 lines
203 KiB
Ruby

# frozen_string_literal: true
require 'rotp'
RSpec.describe UsersController do
fab!(:user) { Fabricate(:user) }
fab!(:user1) { Fabricate(:user) }
fab!(:another_user) { Fabricate(:user) }
fab!(:invitee) { Fabricate(:user) }
fab!(:inviter) { Fabricate(:user) }
fab!(:admin) { Fabricate(:admin) }
fab!(:moderator) { Fabricate(:moderator) }
fab!(:inactive_user) { Fabricate(:inactive_user) }
# Unfortunately, there are tests that depend on the user being created too
# late for fab! to work.
let(:user_deferred) { Fabricate(:user) }
describe "#full account registration flow" do
it "will correctly handle honeypot and challenge" do
get '/session/hp.json'
expect(response.status).to eq(200)
json = response.parsed_body
params = {
email: 'jane@jane.com',
name: 'jane',
username: 'jane',
password_confirmation: json['value'],
challenge: json['challenge'].reverse,
password: SecureRandom.hex
}
secure_session = SecureSession.new(session["secure_session_id"])
expect(secure_session[UsersController::HONEYPOT_KEY]).to eq(json["value"])
expect(secure_session[UsersController::CHALLENGE_KEY]).to eq(json["challenge"])
post '/u.json', params: params
expect(response.status).to eq(200)
jane = User.find_by(username: 'jane')
expect(jane.email).to eq('jane@jane.com')
expect(secure_session[UsersController::HONEYPOT_KEY]).to eq(nil)
expect(secure_session[UsersController::CHALLENGE_KEY]).to eq(nil)
end
end
describe '#perform_account_activation' do
let(:email_token) { Fabricate(:email_token, user: user_deferred) }
before do
UsersController.any_instance.stubs(:honeypot_or_challenge_fails?).returns(false)
end
context 'with invalid token' do
it 'return success' do
put "/u/activate-account/invalid-tooken"
expect(response.status).to eq(200)
expect(flash[:error]).to be_present
end
end
context 'with valid token' do
context 'with welcome message' do
it 'enqueues a welcome message if the user object indicates so' do
SiteSetting.send_welcome_message = true
user_deferred.update(active: false)
put "/u/activate-account/#{email_token.token}"
expect(response.status).to eq(200)
expect(Jobs::SendSystemMessage.jobs.size).to eq(1)
expect(Jobs::SendSystemMessage.jobs.first["args"].first["message_type"]).to eq("welcome_user")
end
it "doesn't enqueue the welcome message if the object returns false" do
user_deferred.update(active: true)
put "/u/activate-account/#{email_token.token}"
expect(response.status).to eq(200)
expect(Jobs::SendSystemMessage.jobs.size).to eq(0)
end
end
context "with honeypot" do
it "raises an error if the honeypot is invalid" do
UsersController.any_instance.stubs(:honeypot_or_challenge_fails?).returns(true)
put "/u/activate-account/#{email_token.token}"
expect(response.status).to eq(403)
end
end
context 'with response' do
it 'correctly logs on user' do
email_token
events = DiscourseEvent.track_events do
put "/u/activate-account/#{email_token.token}"
end
expect(events.map { |event| event[:event_name] }).to contain_exactly(
:user_confirmed_email, :user_first_logged_in, :user_logged_in
)
expect(response.status).to eq(200)
expect(flash[:error]).to be_blank
expect(session[:current_user_id]).to be_present
expect(CGI.unescapeHTML(response.body))
.to_not include(I18n.t('activation.approval_required'))
end
end
context 'when user is not approved' do
before do
SiteSetting.must_approve_users = true
end
it 'should return the right response' do
put "/u/activate-account/#{email_token.token}"
expect(response.status).to eq(200)
expect(CGI.unescapeHTML(response.body))
.to include(I18n.t('activation.approval_required'))
expect(response.body).to_not have_tag(:script, with: {
src: '/assets/application.js'
})
expect(flash[:error]).to be_blank
expect(session[:current_user_id]).to be_blank
end
end
end
context 'when cookies contains a destination URL' do
it 'should redirect to the URL' do
destination_url = 'http://thisisasite.com/somepath'
cookies[:destination_url] = destination_url
put "/u/activate-account/#{email_token.token}"
expect(response).to redirect_to(destination_url)
end
end
context 'when cookies does not contain a destination URL but users was invited to topic' do
let(:invite) { Fabricate(:invite) }
let(:topic) { Fabricate(:topic) }
before do
TopicInvite.create!(topic: topic, invite: invite)
Fabricate(:invited_user, invite: invite, user: email_token.user)
invite.reload
end
it 'should redirect to the topic' do
put "/u/activate-account/#{email_token.token}"
expect(response).to redirect_to(topic.relative_url)
end
end
end
describe '#password_reset' do
let(:token) { SecureRandom.hex }
context "when login is required" do
it "returns success" do
SiteSetting.login_required = true
get "/u/password-reset/#{token}"
expect(response.status).to eq(200)
expect(CGI.unescapeHTML(response.body)).to include(I18n.t('password_reset.no_token'))
end
end
context 'with missing token' do
it 'disallows login' do
get "/u/password-reset/#{token}"
expect(response.status).to eq(200)
expect(CGI.unescapeHTML(response.body))
.to include(I18n.t('password_reset.no_token'))
expect(response.body).to_not have_tag(:script, with: {
src: '/assets/application.js'
})
expect(session[:current_user_id]).to be_blank
end
it "responds with proper error message" do
get "/u/password-reset/#{token}.json"
expect(response.status).to eq(200)
expect(response.parsed_body["message"]).to eq(I18n.t('password_reset.no_token'))
expect(session[:current_user_id]).to be_blank
end
end
context 'with invalid token' do
it 'disallows login' do
get "/u/password-reset/ev!l_trout@!"
expect(response.status).to eq(200)
expect(CGI.unescapeHTML(response.body))
.to include(I18n.t('password_reset.no_token'))
expect(response.body).to_not have_tag(:script, with: {
src: '/assets/application.js'
})
expect(session[:current_user_id]).to be_blank
end
it "responds with proper error message" do
put "/u/password-reset/evil_trout!.json", params: { password: "awesomeSecretPassword" }
expect(response.status).to eq(200)
expect(response.parsed_body["message"]).to eq(I18n.t('password_reset.no_token'))
expect(session[:current_user_id]).to be_blank
end
end
context 'with valid token' do
let!(:user_auth_token) { UserAuthToken.generate!(user_id: user1.id) }
let!(:email_token) { Fabricate(:email_token, user: user1, scope: EmailToken.scopes[:password_reset]) }
context 'when rendered' do
it 'renders referrer never on get requests' do
get "/u/password-reset/#{email_token.token}"
expect(response.status).to eq(200)
expect(response.body).to include('<meta name="referrer" content="never">')
end
end
it 'returns success' do
events = DiscourseEvent.track_events do
put "/u/password-reset/#{email_token.token}", params: { password: 'hg9ow8yhg98o' }
end
expect(events.map { |event| event[:event_name] }).to contain_exactly(
:user_logged_in, :user_first_logged_in, :user_confirmed_email
)
expect(response.status).to eq(200)
expect(response.body).to have_tag("div#data-preloaded") do |element|
json = JSON.parse(element.current_scope.attribute('data-preloaded').value)
expect(json['password_reset']).to include('{"is_developer":false,"admin":false,"second_factor_required":false,"security_key_required":false,"backup_enabled":false,"multiple_second_factor_methods":false}')
end
expect(session["password-#{email_token.token}"]).to be_blank
expect(UserAuthToken.where(id: user_auth_token.id).count).to eq(0)
end
it 'disallows double password reset' do
put "/u/password-reset/#{email_token.token}", params: { password: 'hg9ow8yHG32O' }
put "/u/password-reset/#{email_token.token}", params: { password: 'test123987AsdfXYZ' }
expect(user1.reload.confirm_password?('hg9ow8yHG32O')).to eq(true)
expect(user1.user_auth_tokens.count).to eq(1)
end
it "doesn't redirect to wizard on get" do
user1.update!(admin: true)
get "/u/password-reset/#{email_token.token}.json"
expect(response).not_to redirect_to(wizard_path)
end
it "redirects to the wizard if you're the first admin" do
user1.update!(admin: true)
get "/u/password-reset/#{email_token.token}"
put "/u/password-reset/#{email_token.token}", params: { password: 'hg9ow8yhg98oadminlonger' }
expect(response).to redirect_to(wizard_path)
end
it "sets the users timezone if the param is present" do
get "/u/password-reset/#{email_token.token}"
expect(user1.user_option.timezone).to eq(nil)
put "/u/password-reset/#{email_token.token}", params: { password: 'hg9ow8yhg98oadminlonger', timezone: "America/Chicago" }
expect(user1.user_option.reload.timezone).to eq("America/Chicago")
end
it "logs the password change" do
get "/u/password-reset/#{email_token.token}"
expect do
put "/u/password-reset/#{email_token.token}", params: { password: 'hg9ow8yhg98oadminlonger' }
end.to change { UserHistory.count }.by (1)
user_history = UserHistory.last
expect(user_history.target_user_id).to eq(user1.id)
expect(user_history.action).to eq(UserHistory.actions[:change_password])
end
it "doesn't invalidate the token when loading the page" do
get "/u/password-reset/#{email_token.token}.json"
expect(response.status).to eq(200)
expect(email_token.reload.confirmed).to eq(false)
expect(UserAuthToken.where(id: user_auth_token.id).count).to eq(1)
end
context "with rate limiting" do
before { RateLimiter.clear_all!; RateLimiter.enable }
it "rate limits reset passwords" do
freeze_time
6.times do
put "/u/password-reset/#{email_token.token}", params: {
second_factor_token: 123456,
second_factor_method: 1
}
expect(response.status).to eq(200)
end
put "/u/password-reset/#{email_token.token}", params: {
second_factor_token: 123456,
second_factor_method: 1
}
expect(response.status).to eq(429)
end
it "rate limits reset passwords by username" do
freeze_time
6.times do |x|
put "/u/password-reset/#{email_token.token}", params: {
second_factor_token: 123456,
second_factor_method: 1
}, env: { "REMOTE_ADDR": "1.2.3.#{x}" }
expect(response.status).to eq(200)
end
put "/u/password-reset/#{email_token.token}", params: {
second_factor_token: 123456,
second_factor_method: 1
}, env: { "REMOTE_ADDR": "1.2.3.4" }
expect(response.status).to eq(429)
end
end
context 'when 2 factor authentication is required' do
fab!(:second_factor) { Fabricate(:user_second_factor_totp, user: user1) }
it 'does not change with an invalid token' do
user1.user_auth_tokens.destroy_all
get "/u/password-reset/#{email_token.token}"
expect(response.body).to have_tag("div#data-preloaded") do |element|
json = JSON.parse(element.current_scope.attribute('data-preloaded').value)
expect(json['password_reset']).to include('{"is_developer":false,"admin":false,"second_factor_required":true,"security_key_required":false,"backup_enabled":false,"multiple_second_factor_methods":false}')
end
put "/u/password-reset/#{email_token.token}", params: {
password: 'hg9ow8yHG32O',
second_factor_token: '000000',
second_factor_method: UserSecondFactor.methods[:totp]
}
expect(response.body).to include(I18n.t("login.invalid_second_factor_code"))
user1.reload
expect(user1.confirm_password?('hg9ow8yHG32O')).not_to eq(true)
expect(user1.user_auth_tokens.count).not_to eq(1)
end
it 'changes password with valid 2-factor tokens' do
get "/u/password-reset/#{email_token.token}"
put "/u/password-reset/#{email_token.token}", params: {
password: 'hg9ow8yHG32O',
second_factor_token: ROTP::TOTP.new(second_factor.data).now,
second_factor_method: UserSecondFactor.methods[:totp]
}
user1.reload
expect(response.status).to eq(200)
expect(user1.confirm_password?('hg9ow8yHG32O')).to eq(true)
expect(user1.user_auth_tokens.count).to eq(1)
end
end
context 'when security key authentication is required' do
let!(:user_security_key) do
Fabricate(
:user_security_key,
user: user1,
credential_id: valid_security_key_data[:credential_id],
public_key: valid_security_key_data[:public_key]
)
end
before do
simulate_localhost_webauthn_challenge
# store challenge in secure session by visiting the email login page
get "/u/password-reset/#{email_token.token}"
end
it 'preloads with a security key challenge and allowed credential ids' do
expect(response.body).to have_tag("div#data-preloaded") do |element|
json = JSON.parse(element.current_scope.attribute('data-preloaded').value)
password_reset = JSON.parse(json['password_reset'])
expect(password_reset['challenge']).not_to eq(nil)
expect(password_reset['allowed_credential_ids']).to eq([user_security_key.credential_id])
expect(password_reset['security_key_required']).to eq(true)
end
end
it 'stages a webauthn challenge and rp-id for the user' do
secure_session = SecureSession.new(session["secure_session_id"])
expect(Webauthn.challenge(user1, secure_session)).not_to eq(nil)
expect(Webauthn.rp_id(user1, secure_session)).to eq(Discourse.current_hostname)
end
it 'changes password with valid security key challenge and authentication' do
put "/u/password-reset/#{email_token.token}.json", params: {
password: 'hg9ow8yHG32O',
second_factor_token: valid_security_key_auth_post_data,
second_factor_method: UserSecondFactor.methods[:security_key]
}
expect(response.status).to eq(200)
user1.reload
expect(user1.confirm_password?('hg9ow8yHG32O')).to eq(true)
expect(user1.user_auth_tokens.count).to eq(1)
end
it "does not change a password if a fake TOTP token is provided" do
put "/u/password-reset/#{email_token.token}.json", params: {
password: 'hg9ow8yHG32O',
second_factor_token: 'blah',
second_factor_method: UserSecondFactor.methods[:security_key]
}
expect(response.status).to eq(200)
expect(user1.reload.confirm_password?('hg9ow8yHG32O')).to eq(false)
end
context "when security key authentication fails" do
it 'shows an error message and does not change password' do
put "/u/password-reset/#{email_token.token}", params: {
password: 'hg9ow8yHG32O',
second_factor_token: {
signature: 'bad',
clientData: 'bad',
authenticatorData: 'bad',
credentialId: 'bad'
},
second_factor_method: UserSecondFactor.methods[:security_key]
}
expect(response.status).to eq(200)
expect(response.body).to include(I18n.t("webauthn.validation.not_found_error"))
expect(user1.reload.confirm_password?('hg9ow8yHG32O')).to eq(false)
end
end
end
end
context 'with submit change' do
let(:email_token) { Fabricate(:email_token, user: user1, scope: EmailToken.scopes[:password_reset]) }
it "fails when the password is blank" do
put "/u/password-reset/#{email_token.token}.json", params: { password: '' }
expect(response.status).to eq(200)
expect(response.parsed_body["errors"]).to be_present
expect(session[:current_user_id]).to be_blank
end
it "fails when the password is too long" do
put "/u/password-reset/#{email_token.token}.json", params: { password: ('x' * (User.max_password_length + 1)) }
expect(response.status).to eq(200)
expect(response.parsed_body["errors"]).to be_present
expect(session[:current_user_id]).to be_blank
end
it "logs in the user" do
put "/u/password-reset/#{email_token.token}.json", params: { password: 'ksjafh928r' }
expect(response.status).to eq(200)
expect(response.parsed_body["errors"]).to be_blank
expect(session[:current_user_id]).to be_present
end
it "doesn't log in the user when not approved" do
SiteSetting.must_approve_users = true
user1.update!(approved: false)
put "/u/password-reset/#{email_token.token}.json", params: { password: 'ksjafh928r' }
expect(response.parsed_body["errors"]).to be_blank
expect(session[:current_user_id]).to be_blank
end
end
end
describe '#confirm_email_token' do
let!(:email_token) { Fabricate(:email_token, user: user1) }
it "token doesn't match any records" do
get "/u/confirm-email-token/#{SecureRandom.hex}.json"
expect(response.status).to eq(200)
expect(email_token.reload.confirmed).to eq(false)
end
it "token matches" do
get "/u/confirm-email-token/#{email_token.token}.json"
expect(response.status).to eq(200)
expect(email_token.reload.confirmed).to eq(true)
end
end
describe '#admin_login' do
it 'enqueues mail with admin email and sso enabled' do
put "/u/admin-login", params: { email: admin.email }
expect(response.status).to eq(200)
expect(Jobs::CriticalUserEmail.jobs.size).to eq(1)
args = Jobs::CriticalUserEmail.jobs.first["args"].first
expect(args["user_id"]).to eq(admin.id)
end
it 'passes through safe mode' do
put "/u/admin-login", params: { email: admin.email, use_safe_mode: true }
expect(response.status).to eq(200)
expect(Jobs::CriticalUserEmail.jobs.size).to eq(1)
args = Jobs::CriticalUserEmail.jobs.first["args"].first
expect(args["email_token"]).to end_with("?safe_mode=no_plugins,no_themes")
end
context 'when email is incorrect' do
it 'should return the right response' do
put "/u/admin-login", params: { email: 'random' }
expect(response.status).to eq(200)
response_body = response.body
expect(response_body).to match(I18n.t("admin_login.errors.unknown_email_address"))
expect(response_body).to_not match(I18n.t("login.second_factor_description"))
end
end
end
describe '#toggle_anon' do
it 'allows you to toggle anon if enabled' do
SiteSetting.allow_anonymous_posting = true
user = sign_in(Fabricate(:user))
user.trust_level = 1
user.save!
post "/u/toggle-anon.json"
expect(response.status).to eq(200)
expect(session[:current_user_id]).to eq(AnonymousShadowCreator.get(user).id)
post "/u/toggle-anon.json"
expect(response.status).to eq(200)
expect(session[:current_user_id]).to eq(user.id)
end
end
describe '#create' do
def honeypot_magic(params)
get '/session/hp.json'
json = response.parsed_body
params[:password_confirmation] = json["value"]
params[:challenge] = json["challenge"].reverse
params
end
before do
UsersController.any_instance.stubs(:honeypot_value).returns(nil)
UsersController.any_instance.stubs(:challenge_value).returns(nil)
SiteSetting.allow_new_registrations = true
@user = Fabricate.build(:user, email: "foobar@example.com", password: "strongpassword")
end
let(:post_user_params) do
{ name: @user.name,
username: @user.username,
password: "strongpassword",
email: @user.email }
end
def post_user(extra_params = {})
post "/u.json", params: post_user_params.merge(extra_params)
end
context 'when email params is missing' do
it 'should raise the right error' do
post "/u.json", params: {
name: @user.name,
username: @user.username,
password: 'testing12352343'
}
expect(response.status).to eq(400)
end
end
context 'when creating a user' do
it 'sets the user locale to I18n.locale' do
SiteSetting.default_locale = 'en'
I18n.stubs(:locale).returns(:fr)
post_user
expect(User.find_by(username: @user.username).locale).to eq('fr')
end
it 'requires invite code when specified' do
expect(SiteSetting.require_invite_code).to eq(false)
SiteSetting.invite_code = "abc def"
expect(SiteSetting.require_invite_code).to eq(true)
post_user(invite_code: "abcd")
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["success"]).to eq(false)
# case insensitive and stripped of leading/ending spaces
post_user(invite_code: " AbC deF ")
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["success"]).to eq(true)
end
context "when timezone is provided as a guess on signup" do
it "sets the timezone" do
post_user(timezone: "Australia/Brisbane")
expect(response.status).to eq(200)
expect(User.find_by(username: @user.username).user_option.timezone).to eq("Australia/Brisbane")
end
end
context "with local logins disabled" do
before do
SiteSetting.enable_local_logins = false
SiteSetting.enable_google_oauth2_logins = true
end
it "blocks registration without authenticator information" do
post_user
expect(response.status).to eq(403)
end
it "blocks with a regular api key" do
api_key = Fabricate(:api_key, user: user1)
post "/u.json", params: post_user_params, headers: { HTTP_API_KEY: api_key.key }
expect(response.status).to eq(403)
end
it "works with an admin api key" do
api_key = Fabricate(:api_key, user: admin)
post "/u.json", params: post_user_params, headers: { HTTP_API_KEY: api_key.key }
expect(response.status).to eq(200)
end
end
context 'with external_ids' do
fab!(:api_key, refind: false) { Fabricate(:api_key, user: admin) }
let(:plugin_auth_provider) do
authenticator_class = Class.new(Auth::ManagedAuthenticator) do
def name
'pluginauth'
end
def enabled?
true
end
end
provider = Auth::AuthProvider.new
provider.authenticator = authenticator_class.new
provider
end
before do
DiscoursePluginRegistry.register_auth_provider(plugin_auth_provider)
end
after do
DiscoursePluginRegistry.reset!
end
it 'creates User record' do
params = {
username: 'foobar',
email: 'test@example.com',
external_ids: { 'pluginauth' => 'pluginauth_uid' },
}
expect { post "/u.json", params: params, headers: { HTTP_API_KEY: api_key.key } }
.to change { UserAssociatedAccount.count }.by(1)
.and change { User.count }.by(1)
expect(response.status).to eq(200)
user = User.last
user_associated_account = UserAssociatedAccount.last
expect(user.username).to eq('foobar')
expect(user.email).to eq('test@example.com')
expect(user.user_associated_account_ids).to contain_exactly(user_associated_account.id)
expect(user_associated_account.provider_name).to eq('pluginauth')
expect(user_associated_account.provider_uid).to eq('pluginauth_uid')
expect(user_associated_account.user_id).to eq(user.id)
end
it 'returns error if external ID provider does not exist' do
params = {
username: 'foobar',
email: 'test@example.com',
external_ids: { 'pluginauth2' => 'pluginauth_uid' },
}
post "/u.json", params: params, headers: { HTTP_API_KEY: api_key.key }
expect(response.status).to eq(400)
end
end
end
context 'when creating a non active user (unconfirmed email)' do
it 'returns 403 forbidden when local logins are disabled' do
SiteSetting.enable_local_logins = false
post_user
expect(response.status).to eq(403)
end
it 'returns an error when new registrations are disabled' do
SiteSetting.allow_new_registrations = false
post_user
expect(response.status).to eq(200)
json = response.parsed_body
expect(json['success']).to eq(false)
expect(json['message']).to be_present
end
it 'creates a user correctly' do
post_user
expect(response.status).to eq(200)
expect(response.parsed_body['active']).to be_falsey
# should save user_created_message in session
expect(session["user_created_message"]).to be_present
expect(session[SessionController::ACTIVATE_USER_KEY]).to be_present
expect(Jobs::SendSystemMessage.jobs.size).to eq(0)
expect(Jobs::CriticalUserEmail.jobs.size).to eq(1)
args = Jobs::CriticalUserEmail.jobs.first["args"].first
expect(args["type"]).to eq("signup")
end
context "when `must approve users` site setting is enabled" do
before { SiteSetting.must_approve_users = true }
it 'creates a user correctly' do
post_user
expect(response.status).to eq(200)
expect(response.parsed_body['active']).to be_falsey
# should save user_created_message in session
expect(session["user_created_message"]).to be_present
expect(session[SessionController::ACTIVATE_USER_KEY]).to be_present
expect(Jobs::SendSystemMessage.jobs.size).to eq(0)
expect(Jobs::CriticalUserEmail.jobs.size).to eq(1)
args = Jobs::CriticalUserEmail.jobs.first["args"].first
expect(args["type"]).to eq("signup")
end
end
context 'when users already exists with given email' do
let!(:existing) { Fabricate(:user, email: post_user_params[:email]) }
it 'returns an error if hide_email_address_taken is disabled' do
SiteSetting.hide_email_address_taken = false
post_user
expect(response.status).to eq(200)
json = response.parsed_body
expect(json['success']).to eq(false)
expect(json['message']).to be_present
end
it 'returns success if hide_email_address_taken is enabled' do
SiteSetting.hide_email_address_taken = true
expect {
post_user
}.to_not change { User.count }
expect(response.status).to eq(200)
expect(session["user_created_message"]).to be_present
json = response.parsed_body
expect(json['active']).to be_falsey
expect(json['message']).to eq(I18n.t("login.activate_email", email: post_user_params[:email]))
expect(json['user_id']).not_to be_present
existing.destroy!
expect {
post_user
}.to change { User.count }
expect(response.status).to eq(200)
json = response.parsed_body
expect(json['active']).to be_falsey
expect(json['message']).to eq(I18n.t("login.activate_email", email: post_user_params[:email]))
expect(json['user_id']).not_to be_present
end
end
end
context "when creating as active" do
it "won't create the user as active" do
post "/u.json", params: post_user_params.merge(active: true)
expect(response.status).to eq(200)
expect(response.parsed_body['active']).to be_falsey
end
context "with a regular api key" do
fab!(:api_key, refind: false) { Fabricate(:api_key, user: user1) }
it "won't create the user as active with a regular key" do
post "/u.json",
params: post_user_params.merge(active: true), headers: { HTTP_API_KEY: api_key.key }
expect(response.status).to eq(200)
expect(response.parsed_body['active']).to be_falsey
end
end
context "with an admin api key" do
fab!(:api_key, refind: false) { Fabricate(:api_key, user: admin) }
it "creates the user as active with a an admin key" do
SiteSetting.send_welcome_message = true
SiteSetting.must_approve_users = true
#Sidekiq::Client.expects(:enqueue).never
post "/u.json", params: post_user_params.merge(approved: true, active: true), headers: { HTTP_API_KEY: api_key.key }
expect(Jobs::CriticalUserEmail.jobs.size).to eq(0)
expect(Jobs::SendSystemMessage.jobs.size).to eq(0)
expect(response.status).to eq(200)
expect(response.parsed_body['active']).to be_truthy
new_user = User.find(response.parsed_body["user_id"])
expect(new_user.active).to eq(true)
expect(new_user.approved).to eq(true)
expect(new_user.approved_by_id).to eq(admin.id)
expect(new_user.approved_at).to_not eq(nil)
expect(new_user.email_tokens.where(confirmed: true, email: new_user.email)).to exist
end
it "will create a reviewable when a user is created as active but not approved" do
Jobs.run_immediately!
SiteSetting.must_approve_users = true
post "/u.json", params: post_user_params.merge(active: true), headers: { HTTP_API_KEY: api_key.key }
expect(response.status).to eq(200)
json = response.parsed_body
new_user = User.find(json["user_id"])
expect(json['active']).to be_truthy
expect(new_user.approved).to eq(false)
expect(ReviewableUser.pending.find_by(target: new_user)).to be_present
end
it "won't create a reviewable when a user is not active" do
Jobs.run_immediately!
SiteSetting.must_approve_users = true
post "/u.json", params: post_user_params, headers: { HTTP_API_KEY: api_key.key }
expect(response.status).to eq(200)
json = response.parsed_body
new_user = User.find(json["user_id"])
expect(json['active']).to eq(false)
expect(new_user.approved).to eq(false)
expect(ReviewableUser.pending.find_by(target: new_user)).to be_blank
end
it "won't create the developer as active" do
UsernameCheckerService.expects(:is_developer?).returns(true)
post "/u.json", params: post_user_params.merge(active: true), headers: { HTTP_API_KEY: api_key.key }
expect(response.status).to eq(200)
expect(response.parsed_body['active']).to be_falsy
end
it "won't set the new user's locale to the admin's locale" do
SiteSetting.allow_user_locale = true
admin.update!(locale: :fr)
post "/u.json", params: post_user_params.merge(active: true), headers: { HTTP_API_KEY: api_key.key }
expect(response.status).to eq(200)
json = response.parsed_body
new_user = User.find(json["user_id"])
expect(new_user.locale).not_to eq("fr")
end
it "will auto approve user if the user email domain matches auto_approve_email_domains setting" do
Jobs.run_immediately!
SiteSetting.must_approve_users = true
SiteSetting.auto_approve_email_domains = "example.com"
post "/u.json", params: post_user_params.merge(active: true), headers: { HTTP_API_KEY: api_key.key }
expect(response.status).to eq(200)
json = response.parsed_body
new_user = User.find(json["user_id"])
expect(json['active']).to be_truthy
expect(new_user.approved).to be_truthy
expect(ReviewableUser.pending.find_by(target: new_user)).to be_blank
end
end
end
context "when creating as staged" do
it "won't create the user as staged" do
post "/u.json", params: post_user_params.merge(staged: true)
expect(response.status).to eq(200)
new_user = User.where(username: post_user_params[:username]).first
expect(new_user.staged?).to eq(false)
end
context "with a regular api key" do
fab!(:api_key, refind: false) { Fabricate(:api_key, user: user1) }
it "won't create the user as staged with a regular key" do
post "/u.json", params: post_user_params.merge(staged: true), headers: { HTTP_API_KEY: api_key.key }
expect(response.status).to eq(200)
new_user = User.where(username: post_user_params[:username]).first
expect(new_user.staged?).to eq(false)
end
end
context "with an admin api key" do
fab!(:user) { admin }
fab!(:api_key, refind: false) { Fabricate(:api_key, user: user) }
it "creates the user as staged with a regular key" do
post "/u.json", params: post_user_params.merge(staged: true), headers: { HTTP_API_KEY: api_key.key }
expect(response.status).to eq(200)
new_user = User.where(username: post_user_params[:username]).first
expect(new_user.staged?).to eq(true)
end
it "won't create the developer as staged" do
UsernameCheckerService.expects(:is_developer?).returns(true)
post "/u.json", params: post_user_params.merge(staged: true), headers: { HTTP_API_KEY: api_key.key }
expect(response.status).to eq(200)
new_user = User.where(username: post_user_params[:username]).first
expect(new_user.staged?).to eq(false)
end
end
end
context 'when creating an active user (confirmed email)' do
before { User.any_instance.stubs(:active?).returns(true) }
it 'enqueues a welcome email' do
User.any_instance.expects(:enqueue_welcome_message).with('welcome_user')
post_user
expect(response.status).to eq(200)
# should save user_created_message in session
expect(session["user_created_message"]).to be_present
expect(session[SessionController::ACTIVATE_USER_KEY]).to be_present
end
it "shows the 'active' message" do
User.any_instance.expects(:enqueue_welcome_message)
post_user
expect(response.status).to eq(200)
expect(response.parsed_body['message']).to eq(
I18n.t 'login.active'
)
end
it "should be logged in" do
User.any_instance.expects(:enqueue_welcome_message)
post_user
expect(response.status).to eq(200)
expect(session[:current_user_id]).to be_present
end
it 'indicates the user is active in the response' do
User.any_instance.expects(:enqueue_welcome_message)
post_user
expect(response.status).to eq(200)
expect(response.parsed_body['active']).to be_truthy
end
it 'doesn\'t succeed when new registrations are disabled' do
SiteSetting.allow_new_registrations = false
post_user
expect(response.status).to eq(200)
json = response.parsed_body
expect(json['success']).to eq(false)
expect(json['message']).to be_present
end
context 'with authentication records for' do
before do
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:twitter] = OmniAuth::AuthHash.new(
provider: 'twitter',
uid: '123545',
info: OmniAuth::AuthHash::InfoHash.new(
email: "osama@mail.com",
nickname: "testosama",
name: "Osama Test"
)
)
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
SiteSetting.enable_twitter_logins = true
get "/auth/twitter/callback.json"
end
after do
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter] = nil
OmniAuth.config.test_mode = false
end
it 'should create twitter user info if required' do
post "/u.json", params: {
name: "Test Osama",
username: "testosama",
password: "strongpassword",
email: "osama@mail.com"
}
expect(response.status).to eq(200)
expect(UserAssociatedAccount.where(provider_name: "twitter").count).to eq(1)
end
it "returns an error when email has been changed from the validated email address" do
post "/u.json", params: {
name: "Test Osama",
username: "testosama",
password: "strongpassword",
email: "unvalidatedemail@mail.com"
}
expect(response.status).to eq(200)
json = response.parsed_body
expect(json['success']).to eq(false)
expect(json['message']).to be_present
end
it "will create the user successfully if email validation is required" do
post "/u.json", params: {
name: "Test Osama",
username: "testosama",
password: "strongpassword",
email: "osama@mail.com"
}
expect(response.status).to eq(200)
json = response.parsed_body
expect(json['success']).to eq(true)
end
it "doesn't use provided username/name if sso_overrides is enabled" do
SiteSetting.auth_overrides_username = true
SiteSetting.auth_overrides_name = true
post "/u.json", params: {
username: "attemptednewname",
name: "Attempt At New Name",
password: "strongpassword",
email: "osama@mail.com"
}
expect(response.status).to eq(200)
json = response.parsed_body
expect(json['success']).to eq(true)
user = User.last
expect(user.username).to eq('testosama')
expect(user.name).to eq('Osama Test')
end
end
context "with no email in the auth payload" do
before do
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:twitter] = OmniAuth::AuthHash.new(
provider: 'twitter',
uid: '123545',
info: OmniAuth::AuthHash::InfoHash.new(
nickname: "testosama",
name: "Osama Test"
)
)
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter]
SiteSetting.enable_twitter_logins = true
get "/auth/twitter/callback.json"
end
after do
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:twitter] = nil
OmniAuth.config.test_mode = false
end
it "will create the user successfully" do
Rails.application.env_config["omniauth.auth"].info.email = nil
post "/u.json", params: {
name: "Test Osama",
username: "testosama",
password: "strongpassword",
email: "osama@mail.com"
}
expect(response.status).to eq(200)
json = response.parsed_body
expect(json['success']).to eq(true)
end
end
end
it "creates user successfully but doesn't activate the account" do
post_user
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["success"]).to eq(true)
expect(User.find_by(username: @user.username).active).to eq(false)
end
shared_examples 'honeypot fails' do
it 'should not create a new user' do
User.any_instance.expects(:enqueue_welcome_message).never
expect {
post "/u.json", params: create_params
}.to_not change { User.count }
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["success"]).to eq(true)
# should not change the session
expect(session["user_created_message"]).to be_blank
expect(session[SessionController::ACTIVATE_USER_KEY]).to be_blank
end
end
context 'when honeypot value is wrong' do
before do
UsersController.any_instance.stubs(:honeypot_value).returns('abc')
end
let(:create_params) { { name: @user.name, username: @user.username, password: "strongpassword", email: @user.email, password_confirmation: 'wrong' } }
include_examples 'honeypot fails'
end
context 'when challenge answer is wrong' do
before do
UsersController.any_instance.stubs(:challenge_value).returns('abc')
end
let(:create_params) { { name: @user.name, username: @user.username, password: "strongpassword", email: @user.email, challenge: 'abc' } }
include_examples 'honeypot fails'
end
context "when 'invite only' setting is enabled" do
before { SiteSetting.invite_only = true }
let(:create_params) { {
name: @user.name,
username: @user.username,
password: 'strongpassword',
email: @user.email
}}
include_examples 'honeypot fails'
end
shared_examples 'failed signup' do
it 'should not create a new User' do
expect { post "/u.json", params: create_params }.to_not change { User.count }
expect(response.status).to eq(200)
end
it 'should report failed' do
post "/u.json", params: create_params
json = response.parsed_body
expect(json["success"]).not_to eq(true)
# should not change the session
expect(session["user_created_message"]).to be_blank
expect(session[SessionController::ACTIVATE_USER_KEY]).to be_blank
end
end
context 'when password is blank' do
let(:create_params) { { name: @user.name, username: @user.username, password: "", email: @user.email } }
include_examples 'failed signup'
end
context 'when password is too long' do
let(:create_params) { { name: @user.name, username: @user.username, password: "x" * (User.max_password_length + 1), email: @user.email } }
include_examples 'failed signup'
end
context 'when password param is missing' do
let(:create_params) { { name: @user.name, username: @user.username, email: @user.email } }
include_examples 'failed signup'
end
context 'with a reserved username' do
let(:create_params) { { name: @user.name, username: 'Reserved', email: @user.email, password: 'strongpassword' } }
before { SiteSetting.reserved_usernames = 'a|reserved|b' }
include_examples 'failed signup'
end
context 'with a username that matches a user route' do
let(:create_params) { { name: @user.name, username: 'account-created', email: @user.email, password: 'strongpassword' } }
include_examples 'failed signup'
end
context 'with a missing username' do
let(:create_params) { { name: @user.name, email: @user.email, password: "x" * 20 } }
it 'should not create a new User' do
expect { post "/u.json", params: create_params }.to_not change { User.count }
expect(response.status).to eq(400)
end
end
context 'when an Exception is raised' do
before { User.any_instance.stubs(:save).raises(ActiveRecord::StatementInvalid.new('Oh no')) }
let(:create_params) {
{ name: @user.name, username: @user.username,
password: "strongpassword", email: @user.email }
}
include_examples 'failed signup'
end
context "with custom fields" do
fab!(:user_field) { Fabricate(:user_field) }
fab!(:another_field) { Fabricate(:user_field) }
fab!(:optional_field) { Fabricate(:user_field, required: false) }
context "without a value for the fields" do
let(:create_params) { { name: @user.name, password: 'watwatwat', username: @user.username, email: @user.email } }
include_examples 'failed signup'
end
context "with values for the fields" do
let(:update_user_url) { "/u/#{user1.username}.json" }
let(:field_id) { user_field.id.to_s }
before { sign_in(user1) }
context "with multple select fields" do
let(:valid_options) { %w[Axe Sword] }
fab!(:user_field) do
Fabricate(:user_field, field_type: 'multiselect') do
user_field_options do
[
Fabricate(:user_field_option, value: 'Axe'),
Fabricate(:user_field_option, value: 'Sword')
]
end
end
end
it "should allow single values and not just arrays" do
expect do
put update_user_url, params: { user_fields: { field_id => 'Axe' } }
end.to change { user1.reload.user_fields[field_id] }.from(nil).to('Axe')
expect do
put update_user_url, params: { user_fields: { field_id => %w[Axe Juice Sword] } }
end.to change { user1.reload.user_fields[field_id] }.from('Axe').to(%w[Axe Sword])
end
it "shouldn't allow unregistered field values" do
expect do
put update_user_url, params: { user_fields: { field_id => %w[Juice] } }
end.not_to change { user1.reload.user_fields[field_id] }
end
it "should filter valid values" do
expect do
put update_user_url, params: { user_fields: { field_id => %w[Axe Juice Sword] } }
end.to change { user1.reload.user_fields[field_id] }.from(nil).to(valid_options)
end
it "allows registered field values" do
expect do
put update_user_url, params: { user_fields: { field_id => valid_options } }
end.to change { user1.reload.user_fields[field_id] }.from(nil).to(valid_options)
end
it "value can't be nil or empty if the field is required" do
put update_user_url, params: { user_fields: { field_id => valid_options } }
user_field.update!(required: true)
expect do
put update_user_url, params: { user_fields: { field_id => nil } }
end.not_to change { user1.reload.user_fields[field_id] }
expect do
put update_user_url, params: { user_fields: { field_id => "" } }
end.not_to change { user1.reload.user_fields[field_id] }
end
it 'value can nil or empty if the field is not required' do
put update_user_url, params: { user_fields: { field_id => valid_options } }
user_field.update!(required: false)
expect do
put update_user_url, params: { user_fields: { field_id => nil } }
end.to change { user1.reload.user_fields[field_id] }.from(valid_options).to(nil)
expect do
put update_user_url, params: { user_fields: { field_id => "" } }
end.to change { user1.reload.user_fields[field_id] }.from(nil).to("")
end
end
context "with dropdown fields" do
let(:valid_options) { ['Black Mesa', 'Fox Hound'] }
fab!(:user_field) do
Fabricate(:user_field, field_type: 'dropdown') do
user_field_options do
[
Fabricate(:user_field_option, value: 'Black Mesa'),
Fabricate(:user_field_option, value: 'Fox Hound')
]
end
end
end
it "shouldn't allow unregistered field values" do
expect do
put update_user_url, params: { user_fields: { field_id => 'Umbrella Corporation' } }
end.not_to change { user1.reload.user_fields[field_id] }
end
it "allows registered field values" do
expect do
put update_user_url, params: { user_fields: { field_id => valid_options.first } }
end.to change { user1.reload.user_fields[field_id] }.from(nil).to(valid_options.first)
end
it "value can't be nil if the field is required" do
put update_user_url, params: { user_fields: { field_id => valid_options.first } }
user_field.update!(required: true)
expect do
put update_user_url, params: { user_fields: { field_id => nil } }
end.not_to change { user1.reload.user_fields[field_id] }
end
it 'value can be set to nil if the field is not required' do
put update_user_url, params: { user_fields: { field_id => valid_options.last } }
user_field.update!(required: false)
expect do
put update_user_url, params: { user_fields: { field_id => nil } }
end.to change { user1.reload.user_fields[field_id] }.from(valid_options.last).to(nil)
end
end
let(:create_params) { {
name: @user.name,
password: 'suChS3cuRi7y',
username: @user.username,
email: @user.email,
user_fields: {
user_field.id.to_s => 'value1',
another_field.id.to_s => 'value2',
}
} }
it "should succeed without the optional field" do
post "/u.json", params: create_params
expect(response.status).to eq(200)
inserted = User.find_by_email(@user.email)
expect(inserted).to be_present
expect(inserted.custom_fields).to be_present
expect(inserted.custom_fields["user_field_#{user_field.id}"]).to eq('value1')
expect(inserted.custom_fields["user_field_#{another_field.id}"]).to eq('value2')
expect(inserted.custom_fields["user_field_#{optional_field.id}"]).to be_blank
end
it "should succeed with the optional field" do
create_params[:user_fields][optional_field.id.to_s] = 'value3'
post "/u.json", params: create_params.merge(create_params)
expect(response.status).to eq(200)
inserted = User.find_by_email(@user.email)
expect(inserted).to be_present
expect(inserted.custom_fields).to be_present
expect(inserted.custom_fields["user_field_#{user_field.id}"]).to eq('value1')
expect(inserted.custom_fields["user_field_#{another_field.id}"]).to eq('value2')
expect(inserted.custom_fields["user_field_#{optional_field.id}"]).to eq('value3')
end
it "trims excessively long fields" do
create_params[:user_fields][optional_field.id.to_s] = ('x' * 3000)
post "/u.json", params: create_params.merge(create_params)
expect(response.status).to eq(200)
inserted = User.find_by_email(@user.email)
val = inserted.custom_fields["user_field_#{optional_field.id}"]
expect(val.length).to eq(UserField.max_length)
end
end
end
context "with only optional custom fields" do
fab!(:user_field) { Fabricate(:user_field, required: false) }
context "without values for the fields" do
let(:create_params) { {
name: @user.name,
password: 'suChS3cuRi7y',
username: @user.username,
email: @user.email,
} }
it "should succeed" do
post "/u.json", params: create_params
expect(response.status).to eq(200)
inserted = User.find_by_email(@user.email)
expect(inserted).to be_present
expect(inserted.custom_fields).not_to be_present
expect(inserted.custom_fields["user_field_#{user_field.id}"]).to be_blank
end
end
end
context "when taking over a staged account" do
before do
UsersController.any_instance.stubs(:honeypot_value).returns("abc")
UsersController.any_instance.stubs(:challenge_value).returns("efg")
SessionController.any_instance.stubs(:honeypot_value).returns("abc")
SessionController.any_instance.stubs(:challenge_value).returns("efg")
end
fab!(:staged) { Fabricate(:staged, email: "staged@account.com", active: true) }
it "succeeds" do
post '/u.json', params: honeypot_magic(
email: staged.email,
username: "zogstrip",
password: "P4ssw0rd$$"
)
expect(response.status).to eq(200)
result = response.parsed_body
expect(result["success"]).to eq(true)
created_user = User.find_by_email(staged.email)
expect(created_user.staged).to eq(false)
expect(created_user.active).to eq(false)
expect(created_user.registration_ip_address).to be_present
expect(!!created_user.custom_fields["from_staged"]).to eq(true)
# do not allow emails changes please
put "/u/update-activation-email.json", params: { email: 'bob@bob.com' }
created_user.reload
expect(created_user.email).to eq("staged@account.com")
expect(response.status).to eq(403)
end
end
end
describe '#username' do
it 'raises an error when not logged in' do
put "/u/somename/preferences/username.json"
expect(response.status).to eq(403)
end
context 'while logged in' do
let(:old_username) { "OrigUsername" }
let(:new_username) { "#{old_username}1234" }
fab!(:user) { Fabricate(:user, username: "OrigUsername") }
before do
user.username = old_username
sign_in(user)
end
it 'raises an error without a new_username param' do
put "/u/#{user.username}/preferences/username.json", params: { username: user.username }
expect(response.status).to eq(400)
expect(user.reload.username).to eq(old_username)
end
it 'raises an error when you don\'t have permission to change the username' do
Guardian.any_instance.expects(:can_edit_username?).with(user).returns(false)
put "/u/#{user.username}/preferences/username.json", params: { new_username: new_username }
expect(response).to be_forbidden
expect(user.reload.username).to eq(old_username)
end
it 'raises an error when change_username fails' do
put "/u/#{user.username}/preferences/username.json", params: { new_username: '@' }
expect(response.status).to eq(422)
body = response.parsed_body
expect(body['errors'].first).to include(I18n.t(
'user.username.short', min: User.username_length.begin
))
expect(user.reload.username).to eq(old_username)
end
it 'should succeed in normal circumstances' do
put "/u/#{user.username}/preferences/username.json", params: { new_username: new_username }
expect(response.status).to eq(200)
expect(user.reload.username).to eq(new_username)
end
it 'raises an error when the username clashes with an existing user route' do
put "/u/#{user.username}/preferences/username.json", params: { new_username: 'account-created' }
body = response.parsed_body
expect(body['errors'].first).to include(I18n.t('login.reserved_username'))
end
it 'raises an error when the username is in the reserved list' do
SiteSetting.reserved_usernames = 'reserved'
put "/u/#{user.username}/preferences/username.json", params: { new_username: 'reserved' }
body = response.parsed_body
expect(body['errors'].first).to include(I18n.t('login.reserved_username'))
end
it 'should fail if the user is old' do
# Older than the change period and >1 post
user.created_at = Time.now - (SiteSetting.username_change_period + 1).days
PostCreator.new(user,
title: 'This is a test topic',
raw: 'This is a test this is a test'
).create
put "/u/#{user.username}/preferences/username.json", params: { new_username: new_username }
expect(response).to be_forbidden
expect(user.reload.username).to eq(old_username)
end
it 'should create a staff action log when a staff member changes the username' do
acting_user = admin
sign_in(acting_user)
put "/u/#{user.username}/preferences/username.json", params: { new_username: new_username }
expect(response.status).to eq(200)
expect(UserHistory.where(action: UserHistory.actions[:change_username], target_user_id: user.id, acting_user_id: acting_user.id)).to be_present
expect(user.reload.username).to eq(new_username)
end
it 'should return a JSON response with the updated username' do
put "/u/#{user.username}/preferences/username.json", params: { new_username: new_username }
expect(response.parsed_body['username']).to eq(new_username)
end
it 'should respond with proper error message if auth_overrides_username is enabled' do
SiteSetting.discourse_connect_url = 'http://someurl.com'
SiteSetting.enable_discourse_connect = true
SiteSetting.auth_overrides_username = true
acting_user = admin
sign_in(acting_user)
put "/u/#{user.username}/preferences/username.json", params: { new_username: new_username }
expect(response.status).to eq(422)
expect(response.parsed_body['errors'].first).to include(I18n.t('errors.messages.auth_overrides_username'))
end
end
end
describe '#check_username' do
it 'raises an error without any parameters' do
get "/u/check_username.json"
expect(response.status).to eq(400)
end
shared_examples 'when username is unavailable' do
it 'should return available as false in the JSON and return a suggested username' do
expect(response.status).to eq(200)
expect(response.parsed_body['available']).to eq(false)
expect(response.parsed_body['suggestion']).to be_present
end
end
shared_examples 'when username is available' do
it 'should return available in the JSON' do
expect(response.status).to eq(200)
expect(response.parsed_body['available']).to eq(true)
end
end
it 'returns nothing when given an email param but no username' do
get "/u/check_username.json", params: { email: 'dood@example.com' }
expect(response.status).to eq(200)
end
context 'when username is available' do
before do
get "/u/check_username.json", params: { username: 'BruceWayne' }
end
include_examples 'when username is available'
end
context 'when username is unavailable' do
before do
get "/u/check_username.json", params: { username: user1.username }
end
include_examples 'when username is unavailable'
end
shared_examples 'checking an invalid username' do
it 'should not return an available key but should return an error message' do
expect(response.status).to eq(200)
expect(response.parsed_body['available']).to eq(nil)
expect(response.parsed_body['errors']).to be_present
end
end
context 'when has invalid characters' do
before do
get "/u/check_username.json", params: { username: 'bad username' }
end
include_examples 'checking an invalid username'
it 'should return the invalid characters message' do
expect(response.status).to eq(200)
expect(response.parsed_body['errors']).to include(I18n.t(:'user.username.characters'))
end
end
context 'when is too long' do
before do
get "/u/check_username.json", params: { username: SecureRandom.alphanumeric(SiteSetting.max_username_length.to_i + 1) }
end
include_examples 'checking an invalid username'
it 'should return the "too long" message' do
expect(response.status).to eq(200)
expect(response.parsed_body['errors']).to include(I18n.t(:'user.username.long', max: SiteSetting.max_username_length))
end
end
describe 'different case of existing username' do
context "when it's my username" do
fab!(:user) { Fabricate(:user, username: 'hansolo') }
before do
sign_in(user)
get "/u/check_username.json", params: { username: 'HanSolo' }
end
include_examples 'when username is available'
end
context "when it's someone else's username" do
fab!(:user) { Fabricate(:user, username: 'hansolo') }
fab!(:someone_else) { Fabricate(:user) }
before do
sign_in(someone_else)
get "/u/check_username.json", params: { username: 'HanSolo' }
end
include_examples 'when username is unavailable'
end
context "when an admin changing it for someone else" do
fab!(:user) { Fabricate(:user, username: 'hansolo') }
before do
sign_in(admin)
get "/u/check_username.json", params: { username: 'HanSolo', for_user_id: user.id }
end
include_examples 'when username is available'
end
end
end
describe '#check_email' do
it 'returns success if hide_email_address_taken is true' do
SiteSetting.hide_email_address_taken = true
get "/u/check_email.json", params: { email: user1.email }
expect(response.parsed_body["success"]).to be_present
end
it 'returns success if email is empty' do
get "/u/check_email.json"
expect(response.parsed_body["success"]).to be_present
end
it 'returns failure if email is not valid' do
get "/u/check_email.json", params: { email: "invalid" }
expect(response.parsed_body["failed"]).to be_present
end
it 'returns failure if email exists' do
get "/u/check_email.json", params: { email: user1.email }
expect(response.parsed_body["failed"]).to be_present
get "/u/check_email.json", params: { email: user1.email.upcase }
expect(response.parsed_body["failed"]).to be_present
end
it 'returns success if email does not exists' do
get "/u/check_email.json", params: { email: "available@example.com" }
expect(response.parsed_body["success"]).to be_present
end
it 'return success if user email is taken by staged user' do
get "/u/check_email.json", params: { email: Fabricate(:staged).email }
expect(response.parsed_body["success"]).to be_present
end
end
describe '#invited' do
it 'fails for anonymous users' do
get "/u/#{user1.username}/invited.json", params: { username: user1.username }
expect(response.status).to eq(403)
end
it 'returns success' do
user = Fabricate(:user, trust_level: 2)
Fabricate(:invite, invited_by: user)
sign_in(user)
get "/u/#{user.username}/invited.json", params: { username: user.username }
expect(response.status).to eq(200)
expect(response.parsed_body["counts"]["pending"]).to eq(1)
expect(response.parsed_body["counts"]["total"]).to eq(1)
end
it 'filters by all if viewing self' do
inviter = Fabricate(:user, trust_level: 2)
sign_in(inviter)
Fabricate(:invite, email: 'billybob@example.com', invited_by: inviter)
redeemed_invite = Fabricate(:invite, email: 'jimtom@example.com', invited_by: inviter)
Fabricate(:invited_user, invite: redeemed_invite, user: invitee)
get "/u/#{inviter.username}/invited.json", params: { filter: 'pending', search: 'billybob' }
expect(response.status).to eq(200)
invites = response.parsed_body['invites']
expect(invites.size).to eq(1)
expect(invites.first).to include('email' => 'billybob@example.com')
get "/u/#{inviter.username}/invited.json", params: { filter: 'redeemed', search: invitee.username }
expect(response.status).to eq(200)
invites = response.parsed_body['invites']
expect(invites.size).to eq(1)
expect(invites[0]['user']).to be_present
end
it "doesn't filter by email if another regular user" do
inviter = Fabricate(:user, trust_level: 2)
sign_in(Fabricate(:user, trust_level: 2))
Fabricate(:invite, email: 'billybob@example.com', invited_by: inviter)
redeemed_invite = Fabricate(:invite, email: 'jimtom@example.com', invited_by: inviter)
Fabricate(:invited_user, invite: redeemed_invite, user: invitee)
get "/u/#{inviter.username}/invited.json", params: { filter: 'pending', search: 'billybob' }
expect(response.status).to eq(200)
invites = response.parsed_body['invites']
expect(invites.size).to eq(0)
get "/u/#{inviter.username}/invited.json", params: { filter: 'redeemed', search: invitee.username }
expect(response.status).to eq(200)
invites = response.parsed_body['invites']
expect(invites.size).to eq(1)
expect(invites[0]['user']).to be_present
end
it "filters by email if staff" do
inviter = Fabricate(:user, trust_level: 2)
sign_in(moderator)
invite_1 = Fabricate(:invite, email: 'billybob@example.com', invited_by: inviter)
invitee_1 = Fabricate(:user)
Fabricate(:invited_user, invite: invite_1, user: invitee_1)
invite_2 = Fabricate(:invite, email: 'jimtom@example.com', invited_by: inviter)
invitee_2 = Fabricate(:user)
Fabricate(:invited_user, invite: invite_2, user: invitee_2)
get "/u/#{inviter.username}/invited.json", params: { search: 'billybob' }
expect(response.status).to eq(200)
invites = response.parsed_body['invites']
expect(invites.size).to eq(1)
expect(invites[0]['user']).to include('id' => invitee_1.id)
end
context 'with guest' do
context 'with pending invites' do
it 'does not return invites' do
Fabricate(:invite, invited_by: inviter)
get "/u/#{user1.username}/invited/pending.json"
expect(response.status).to eq(403)
end
end
context 'with redeemed invites' do
it 'returns invited_users' do
inviter = Fabricate(:user, trust_level: 2)
sign_in(inviter)
invite = Fabricate(:invite, invited_by: inviter)
_invited_user = Fabricate(:invited_user, invite: invite, user: invitee)
get "/u/#{inviter.username}/invited.json"
expect(response.status).to eq(200)
invites = response.parsed_body['invites']
expect(invites.size).to eq(1)
expect(invites[0]).to include('id' => invite.id)
end
end
end
context 'with authenticated user' do
context 'with pending invites' do
context 'with permission to see pending invites' do
it 'returns invites' do
inviter = Fabricate(:user, trust_level: 2)
invite = Fabricate(:invite, invited_by: inviter)
sign_in(inviter)
get "/u/#{inviter.username}/invited/pending.json"
expect(response.status).to eq(200)
invites = response.parsed_body['invites']
expect(invites.size).to eq(1)
expect(invites.first).to include("email" => invite.email)
expect(response.parsed_body['can_see_invite_details']).to eq(true)
end
end
context 'without permission to see pending invites' do
it 'does not return invites' do
user = sign_in(Fabricate(:user))
Fabricate(:invite, invited_by: inviter)
stub_guardian(user) do |guardian|
guardian.stubs(:can_see_invite_details?).
with(inviter).returns(false)
end
get "/u/#{inviter.username}/invited/pending.json"
expect(response.status).to eq(422)
end
end
context 'with permission to see invite links' do
it 'returns own invites' do
inviter = sign_in(Fabricate(:user, trust_level: 2))
invite = Fabricate(:invite, invited_by: inviter, email: nil, max_redemptions_allowed: 5, expires_at: 1.month.from_now, emailed_status: Invite.emailed_status_types[:not_required])
get "/u/#{inviter.username}/invited/pending.json"
expect(response.status).to eq(200)
invites = response.parsed_body['invites']
expect(invites.size).to eq(1)
expect(invites.first).to include("id" => invite.id)
expect(response.parsed_body['can_see_invite_details']).to eq(true)
end
it 'allows admin to see invites' do
inviter = Fabricate(:user, trust_level: 2)
_admin = sign_in(Fabricate(:admin))
invite = Fabricate(:invite, invited_by: inviter, email: nil, max_redemptions_allowed: 5, expires_at: 1.month.from_now, emailed_status: Invite.emailed_status_types[:not_required])
get "/u/#{inviter.username}/invited/pending.json"
expect(response.status).to eq(200)
invites = response.parsed_body['invites']
expect(invites.size).to eq(1)
expect(invites.first).to include("id" => invite.id)
expect(response.parsed_body['can_see_invite_details']).to eq(true)
end
end
context 'without permission to see invite links' do
it 'does not return invites' do
_user = Fabricate(:user, trust_level: 2)
inviter = admin
Fabricate(:invite, invited_by: inviter, email: nil, max_redemptions_allowed: 5, expires_at: 1.month.from_now, emailed_status: Invite.emailed_status_types[:not_required])
get "/u/#{inviter.username}/invited/pending.json"
expect(response.status).to eq(403)
end
end
end
context 'with redeemed invites' do
it 'returns invites' do
sign_in(moderator)
invite = Fabricate(:invite, invited_by: inviter)
Fabricate(:invited_user, invite: invite, user: invitee)
get "/u/#{inviter.username}/invited.json"
expect(response.status).to eq(200)
invites = response.parsed_body['invites']
expect(invites.size).to eq(1)
expect(invites[0]).to include('id' => invite.id)
end
end
end
end
describe '#update' do
context 'with guest' do
it 'raises an error' do
put "/u/guest.json"
expect(response.status).to eq(403)
end
end
it "does not allow name to be updated if auth auth_overrides_name is enabled" do
SiteSetting.auth_overrides_name = true
sign_in(user1)
put "/u/#{user1.username}", params: { name: 'test.test' }
expect(response.status).to eq(200)
expect(user1.reload.name).to_not eq('test.test')
end
context "when username contains a period" do
before do
sign_in(user)
end
fab!(:user) { Fabricate(:user, username: 'test.test', name: "Test User") }
it "should be able to update a user" do
put "/u/#{user.username}", params: { name: 'test.test' }
expect(response.status).to eq(200)
expect(user.reload.name).to eq('test.test')
end
end
context "as a staff user" do
context "with uneditable field" do
fab!(:user_field) { Fabricate(:user_field, editable: false) }
it "allows staff to edit the field" do
sign_in(admin)
put "/u/#{user.username}.json", params: {
name: 'Jim Tom',
title: "foobar",
user_fields: { user_field.id.to_s => 'happy' }
}
expect(response.status).to eq(200)
user.reload
expect(user.user_fields[user_field.id.to_s]).to eq('happy')
expect(user.title).to eq("foobar")
end
end
end
context 'with authenticated user' do
context 'with permission to update' do
fab!(:upload) { Fabricate(:upload) }
fab!(:user) { Fabricate(:user) }
before do
sign_in(user)
end
it 'allows the update' do
SiteSetting.tagging_enabled = true
user2 = Fabricate(:user)
user3 = Fabricate(:user)
tags = [Fabricate(:tag), Fabricate(:tag)]
tag_synonym = Fabricate(:tag, target_tag: tags[1])
put "/u/#{user.username}.json", params: {
name: 'Jim Tom',
muted_usernames: "#{user2.username},#{user3.username}",
watched_tags: "#{tags[0].name},#{tag_synonym.name}",
card_background_upload_url: upload.url,
profile_background_upload_url: upload.url
}
expect(response.status).to eq(200)
expect(response.parsed_body['user']['watched_tags'].count).to eq(2)
user.reload
expect(user.name).to eq 'Jim Tom'
expect(user.muted_users.pluck(:username).sort).to eq [user2.username, user3.username].sort
expect(TagUser.where(
user: user,
notification_level: TagUser.notification_levels[:watching]
).pluck(:tag_id)).to contain_exactly(tags[0].id, tags[1].id)
theme = Fabricate(:theme, user_selectable: true)
put "/u/#{user.username}.json", params: {
muted_usernames: "",
theme_ids: [theme.id],
email_level: UserOption.email_level_types[:always]
}
user.reload
expect(user.muted_users.pluck(:username).sort).to be_empty
expect(user.user_option.theme_ids).to eq([theme.id])
expect(user.user_option.email_level).to eq(UserOption.email_level_types[:always])
expect(user.profile_background_upload).to eq(upload)
expect(user.card_background_upload).to eq(upload)
end
it 'updates watched tags in everyone tag group' do
SiteSetting.tagging_enabled = true
tags = [Fabricate(:tag), Fabricate(:tag)]
group = Fabricate(:group, name: 'group', mentionable_level: Group::ALIAS_LEVELS[:everyone])
tag_group = Fabricate(:tag_group, tags: tags)
Fabricate(:tag_group_permission, tag_group: tag_group, group: group)
tag_synonym = Fabricate(:tag, target_tag: tags[1])
put "/u/#{user.username}.json", params: {
watched_tags: "#{tags[0].name},#{tag_synonym.name}"
}
expect(response.status).to eq(200)
expect(response.parsed_body['user']['watched_tags'].count).to eq(2)
end
context 'when a locale is chosen that differs from I18n.locale' do
before do
SiteSetting.allow_user_locale = true
end
it "updates the user's locale" do
I18n.locale = :fr
put "/u/#{user.username}.json", params: { locale: :fa_IR }
expect(user.reload.locale).to eq('fa_IR')
end
it "updates the title" do
BadgeGranter.enable_queue
user.update!(locale: :fr)
user.change_trust_level!(TrustLevel[4])
BadgeGranter.process_queue!
leader_title = I18n.t("badges.leader.name", locale: :fr)
put "/u/#{user.username}.json", params: { title: leader_title }
expect(user.reload.title).to eq(leader_title)
ensure
BadgeGranter.disable_queue
BadgeGranter.clear_queue!
end
end
context "with user fields" do
context "with an editable field" do
fab!(:user_field) { Fabricate(:user_field) }
fab!(:optional_field) { Fabricate(:user_field, required: false) }
it "should update the user field" do
put "/u/#{user.username}.json", params: { name: 'Jim Tom', user_fields: { user_field.id.to_s => 'happy' } }
expect(response.status).to eq(200)
expect(user.user_fields[user_field.id.to_s]).to eq 'happy'
end
it "cannot be updated to blank" do
put "/u/#{user.username}.json", params: { name: 'Jim Tom', user_fields: { user_field.id.to_s => '' } }
expect(response.status).to eq(422)
expect(user.user_fields[user_field.id.to_s]).not_to eq('happy')
end
it "trims excessively large fields" do
put "/u/#{user.username}.json", params: { name: 'Jim Tom', user_fields: { user_field.id.to_s => ('x' * 3000) } }
expect(user.user_fields[user_field.id.to_s].size).to eq(UserField.max_length)
end
it "should retain existing user fields" do
put "/u/#{user.username}.json", params: { name: 'Jim Tom', user_fields: { user_field.id.to_s => 'happy', optional_field.id.to_s => 'feet' } }
expect(response.status).to eq(200)
expect(user.user_fields[user_field.id.to_s]).to eq('happy')
expect(user.user_fields[optional_field.id.to_s]).to eq('feet')
put "/u/#{user.username}.json", params: { name: 'Jim Tom', user_fields: { user_field.id.to_s => 'sad' } }
expect(response.status).to eq(200)
user.reload
expect(user.user_fields[user_field.id.to_s]).to eq('sad')
expect(user.user_fields[optional_field.id.to_s]).to eq('feet')
end
end
context "with user_notification_schedule attributes" do
it "updates the user's notification schedule" do
params = {
user_notification_schedule: {
enabled: true,
day_0_start_time: 30,
day_0_end_time: 60,
day_1_start_time: 30,
day_1_end_time: 60,
day_2_start_time: 30,
day_2_end_time: 60,
day_3_start_time: 30,
day_3_end_time: 60,
day_4_start_time: 30,
day_4_end_time: 60,
day_5_start_time: 30,
day_5_end_time: 60,
day_6_start_time: 30,
day_6_end_time: 60,
}
}
put "/u/#{user.username}.json", params: params
user.reload
expect(user.user_notification_schedule.enabled).to eq(true)
expect(user.user_notification_schedule.day_0_start_time).to eq(30)
expect(user.user_notification_schedule.day_0_end_time).to eq(60)
expect(user.user_notification_schedule.day_6_start_time).to eq(30)
expect(user.user_notification_schedule.day_6_end_time).to eq(60)
end
end
context "with uneditable field" do
fab!(:user_field) { Fabricate(:user_field, editable: false) }
it "does not update the user field" do
put "/u/#{user.username}.json", params: { name: 'Jim Tom', user_fields: { user_field.id.to_s => 'happy' } }
expect(response.status).to eq(200)
expect(user.user_fields[user_field.id.to_s]).to be_blank
end
end
context "with custom_field" do
before do
plugin = Plugin::Instance.new
plugin.register_editable_user_custom_field :test2
plugin.register_editable_user_custom_field :test3, staff_only: true
end
after do
DiscoursePluginRegistry.reset!
end
it "only updates allowed user fields" do
put "/u/#{user.username}.json", params: { custom_fields: { test1: :hello1, test2: :hello2, test3: :hello3 } }
expect(response.status).to eq(200)
expect(user.custom_fields["test1"]).to be_blank
expect(user.custom_fields["test2"]).to eq("hello2")
expect(user.custom_fields["test3"]).to be_blank
end
it "works alongside a user field" do
user_field = Fabricate(:user_field, editable: true)
put "/u/#{user.username}.json", params: { custom_fields: { test1: :hello1, test2: :hello2, test3: :hello3 }, user_fields: { user_field.id.to_s => 'happy' } }
expect(response.status).to eq(200)
expect(user.custom_fields["test1"]).to be_blank
expect(user.custom_fields["test2"]).to eq("hello2")
expect(user.custom_fields["test3"]).to eq(nil)
expect(user.user_fields[user_field.id.to_s]).to eq('happy')
end
it "works alongside a user field during creation" do
api_key = Fabricate(:api_key, user: admin)
user_field = Fabricate(:user_field, editable: true)
post "/u.json", params: {
name: "Test User",
username: "testuser",
email: "user@mail.com",
password: 'supersecure',
active: true,
custom_fields: {
test2: 'custom field value'
},
user_fields: {
user_field.id.to_s => 'user field value'
}
}, headers: {
HTTP_API_KEY: api_key.key
}
expect(response.status).to eq(200)
u = User.find_by_email('user@mail.com')
val = u.custom_fields["user_field_#{user_field.id}"]
expect(val).to eq('user field value')
val = u.custom_fields["test2"]
expect(val).to eq('custom field value')
end
it "is secure when there are no registered editable fields" do
DiscoursePluginRegistry.reset!
put "/u/#{user.username}.json", params: { custom_fields: { test1: :hello1, test2: :hello2, test3: :hello3 } }
expect(response.status).to eq(200)
expect(user.custom_fields["test1"]).to be_blank
expect(user.custom_fields["test2"]).to be_blank
expect(user.custom_fields["test3"]).to be_blank
put "/u/#{user.username}.json", params: { custom_fields: ["arrayitem1", "arrayitem2"] }
expect(response.status).to eq(200)
end
it "allows staff to edit staff-editable fields" do
sign_in(admin)
put "/u/#{user.username}.json", params: { custom_fields: { test1: :hello1, test2: :hello2, test3: :hello3 } }
expect(response.status).to eq(200)
expect(user.custom_fields["test1"]).to be_blank
expect(user.custom_fields["test2"]).to eq("hello2")
expect(user.custom_fields["test3"]).to eq("hello3")
end
end
end
it 'returns user JSON' do
put "/u/#{user.username}.json"
json = response.parsed_body
expect(json['user']['id']).to eq user.id
end
context 'with experimental sidebar' do
before do
SiteSetting.enable_experimental_sidebar_hamburger = true
end
it 'does not remove category or tag sidebar section links when params are not present' do
Fabricate(:category_sidebar_section_link, user: user)
Fabricate(:tag_sidebar_section_link, user: user)
expect do
put "/u/#{user.username}.json"
expect(response.status).to eq(200)
end.to_not change { user.sidebar_section_links.count }
end
it "should allow user to remove all category sidebar section links" do
Fabricate(:category_sidebar_section_link, user: user)
expect do
put "/u/#{user.username}.json", params: { sidebar_category_ids: nil }
expect(response.status).to eq(200)
end.to change { user.sidebar_section_links.count }.from(1).to(0)
end
it "should allow user to modify category sidebar section links" do
category = Fabricate(:category)
restricted_category = Fabricate(:category, read_restricted: true)
category_sidebar_section_link = Fabricate(:category_sidebar_section_link, user: user)
put "/u/#{user.username}.json", params: { sidebar_category_ids: [category.id, restricted_category.id] }
expect(response.status).to eq(200)
expect(user.sidebar_section_links.count).to eq(1)
expect(SidebarSectionLink.exists?(id: category_sidebar_section_link.id)).to eq(false)
sidebar_section_link = user.sidebar_section_links.first
expect(sidebar_section_link.linkable).to eq(category)
end
it 'should allow user to remove all tag sidebar section links' do
SiteSetting.tagging_enabled = true
Fabricate(:tag_sidebar_section_link, user: user)
expect do
put "/u/#{user.username}.json", params: { sidebar_tag_names: nil }
expect(response.status).to eq(200)
end.to change { user.sidebar_section_links.count }.from(1).to(0)
end
it 'should not allow user to add tag sidebar section links when tagging is disabled' do
SiteSetting.tagging_enabled = false
tag = Fabricate(:tag)
put "/u/#{user.username}.json", params: { sidebar_tag_names: [tag.name] }
expect(response.status).to eq(200)
expect(user.reload.sidebar_section_links.count).to eq(0)
end
it "should allow user to add tag sidebar section links" do
SiteSetting.tagging_enabled = true
tag = Fabricate(:tag)
tag_sidebar_section_link = Fabricate(:tag_sidebar_section_link, user: user)
put "/u/#{user.username}.json", params: { sidebar_tag_names: [tag.name, "somerandomtag"] }
expect(response.status).to eq(200)
expect(user.sidebar_section_links.count).to eq(1)
expect(SidebarSectionLink.exists?(id: tag_sidebar_section_link.id)).to eq(false)
sidebar_section_link = user.sidebar_section_links.first
expect(sidebar_section_link.linkable).to eq(tag)
end
end
end
context 'without permission to update' do
it 'does not allow the update' do
user = Fabricate(:user, name: 'Billy Bob')
sign_in(Fabricate(:user))
put "/u/#{user.username}.json", params: { name: 'Jim Tom' }
expect(response).to be_forbidden
expect(user.reload.name).not_to eq 'Jim Tom'
end
end
end
context 'with external_ids' do
fab!(:api_key, refind: false) { Fabricate(:api_key, user: admin) }
let(:plugin_auth_provider) do
authenticator_class = Class.new(Auth::ManagedAuthenticator) do
def name
'pluginauth'
end
def enabled?
true
end
end
provider = Auth::AuthProvider.new
provider.authenticator = authenticator_class.new
provider
end
before do
DiscoursePluginRegistry.register_auth_provider(plugin_auth_provider)
end
after do
DiscoursePluginRegistry.reset!
end
it 'can create UserAssociatedAccount records' do
params = {
external_ids: { 'pluginauth' => 'pluginauth_uid' },
}
expect { put "/u/#{user.username}.json", params: params, headers: { HTTP_API_KEY: api_key.key } }
.to change { UserAssociatedAccount.count }.by(1)
expect(response.status).to eq(200)
user_associated_account = UserAssociatedAccount.last
expect(user.reload.user_associated_account_ids).to contain_exactly(user_associated_account.id)
expect(user_associated_account.provider_name).to eq('pluginauth')
expect(user_associated_account.provider_uid).to eq('pluginauth_uid')
expect(user_associated_account.user_id).to eq(user.id)
end
it 'can destroy UserAssociatedAccount records' do
user.user_associated_accounts.create!(provider_name: 'pluginauth', provider_uid: 'pluginauth_uid')
params = {
external_ids: { 'pluginauth' => nil },
}
expect { put "/u/#{user.username}.json", params: params, headers: { HTTP_API_KEY: api_key.key } }
.to change { UserAssociatedAccount.count }.by(-1)
expect(response.status).to eq(200)
expect(user.reload.user_associated_account_ids).to be_blank
end
it 'returns error if external ID provider does not exist' do
params = {
external_ids: { 'pluginauth2' => 'pluginauth_uid' },
}
put "/u/#{user.username}.json", params: params, headers: { HTTP_API_KEY: api_key.key }
expect(response.status).to eq(400)
end
end
end
describe '#badge_title' do
fab!(:badge) { Fabricate(:badge) }
let(:user_badge) { BadgeGranter.grant(badge, user1) }
it "sets the user's title to the badge name if it is titleable" do
sign_in(user1)
put "/u/#{user1.username}/preferences/badge_title.json", params: { user_badge_id: user_badge.id }
expect(user1.reload.title).not_to eq(badge.display_name)
badge.update allow_title: true
put "/u/#{user1.username}/preferences/badge_title.json", params: { user_badge_id: user_badge.id }
expect(user1.reload.title).to eq(badge.display_name)
expect(user1.user_profile.badge_granted_title).to eq(true)
expect(user1.user_profile.granted_title_badge_id).to eq(badge.id)
badge.update allow_title: false
put "/u/#{user1.username}/preferences/badge_title.json", params: { user_badge_id: user_badge.id }
user1.reload
user1.user_profile.reload
expect(user1.title).to eq('')
expect(user1.user_profile.badge_granted_title).to eq(false)
expect(user1.user_profile.granted_title_badge_id).to eq(nil)
end
it "is not raising an erroring when user revokes title" do
sign_in(user1)
badge.update allow_title: true
put "/u/#{user1.username}/preferences/badge_title.json", params: { user_badge_id: user_badge.id }
put "/u/#{user1.username}/preferences/badge_title.json", params: { user_badge_id: 0 }
expect(response.status).to eq(200)
end
context "with overridden name" do
fab!(:badge) { Fabricate(:badge, name: 'Demogorgon', allow_title: true) }
let(:user_badge) { BadgeGranter.grant(badge, user1) }
before do
TranslationOverride.upsert!('en', 'badges.demogorgon.name', 'Boss')
end
after do
TranslationOverride.revert!('en', ['badges.demogorgon.name'])
end
it "uses the badge display name as user title" do
sign_in(user1)
put "/u/#{user1.username}/preferences/badge_title.json", params: { user_badge_id: user_badge.id }
expect(user1.reload.title).to eq(badge.display_name)
end
end
end
describe '#send_activation_email' do
before do
UsersController.any_instance.stubs(:honeypot_value).returns(nil)
UsersController.any_instance.stubs(:challenge_value).returns(nil)
end
let(:post_user) do
post "/u.json", params: {
username: "osamatest",
password: "strongpassword",
email: "dsdsds@sasa.com"
}
User.find_by(username: "osamatest")
end
context 'for an existing user' do
context 'for an activated account with email confirmed' do
it 'fails' do
user = post_user
email_token = Fabricate(:email_token, user: user).token
EmailToken.confirm(email_token)
post "/u/action/send_activation_email.json", params: { username: user.username }
expect(response.status).to eq(409)
expect(response.parsed_body['errors']).to include(I18n.t(
'activation.activated'
))
expect(session[SessionController::ACTIVATE_USER_KEY]).to eq(nil)
end
end
context 'for an activated account with unconfirmed email' do
it 'should send an email' do
user = post_user
user.update!(active: true)
Fabricate(:email_token, user: user)
expect_enqueued_with(job: :critical_user_email, args: { type: :signup, to_address: user.email }) do
post "/u/action/send_activation_email.json", params: {
username: user.username
}
end
expect(response.status).to eq(200)
expect(session[SessionController::ACTIVATE_USER_KEY]).to eq(nil)
end
end
context "when approval is enabled" do
before do
SiteSetting.must_approve_users = true
end
it "should raise an error" do
user = post_user
user.update(active: true)
user.save!
Fabricate(:email_token, user: user1)
post "/u/action/send_activation_email.json", params: {
username: user.username
}
expect(response.status).to eq(403)
end
end
describe 'when user does not have a valid session' do
it 'should not be valid' do
post "/u/action/send_activation_email.json", params: {
username: user.username
}
expect(response.status).to eq(403)
end
it 'should allow staff regardless' do
sign_in(admin)
user = Fabricate(:user, active: false)
post "/u/action/send_activation_email.json", params: {
username: user.username
}
expect(response.status).to eq(200)
end
end
context 'with a valid email_token' do
it 'should send the activation email' do
user = post_user
expect_enqueued_with(job: :critical_user_email, args: { type: :signup }) do
post "/u/action/send_activation_email.json", params: {
username: user.username
}
end
expect(response.status).to eq(200)
expect(session[SessionController::ACTIVATE_USER_KEY]).to eq(nil)
end
end
context 'without an existing email_token' do
let(:user) { post_user }
before do
user.email_tokens.each { |t| t.destroy }
user.reload
end
it 'should generate a new token' do
expect {
post "/u/action/send_activation_email.json", params: { username: user.username }
}.to change { user.reload.email_tokens.count }.by(1)
end
it 'should send an email' do
expect do
post "/u/action/send_activation_email.json", params: {
username: user.username
}
end.to change { Jobs::CriticalUserEmail.jobs.size }.by(1)
expect(session[SessionController::ACTIVATE_USER_KEY]).to eq(nil)
end
end
end
context 'when username does not exist' do
it 'should not send an email' do
post "/u/action/send_activation_email.json", params: { username: 'nopenopenopenope' }
expect(response.status).to eq(404)
expect(Jobs::CriticalUserEmail.jobs.size).to eq(0)
end
end
end
describe '#pick_avatar' do
it 'raises an error when not logged in' do
put "/u/asdf/preferences/avatar/pick.json", params: { avatar_id: 1, type: "custom" }
expect(response.status).to eq(403)
end
context 'while logged in' do
before do
sign_in(user1)
end
fab!(:upload) do
Fabricate(:upload, user: user1)
end
it "raises an error when you don't have permission to toggle the avatar" do
put "/u/#{another_user.username}/preferences/avatar/pick.json", params: {
upload_id: upload.id, type: "custom"
}
expect(response).to be_forbidden
end
it "raises an error when discourse_connect_overrides_avatar is disabled" do
SiteSetting.discourse_connect_overrides_avatar = true
put "/u/#{user1.username}/preferences/avatar/pick.json", params: {
upload_id: upload.id, type: "custom"
}
expect(response.status).to eq(422)
end
it "raises an error when selecting the custom/uploaded avatar and allow_uploaded_avatars is disabled" do
SiteSetting.allow_uploaded_avatars = 'disabled'
put "/u/#{user1.username}/preferences/avatar/pick.json", params: {
upload_id: upload.id, type: "custom"
}
expect(response.status).to eq(422)
end
it "raises an error when selecting the custom/uploaded avatar and allow_uploaded_avatars is admin" do
SiteSetting.allow_uploaded_avatars = 'admin'
put "/u/#{user1.username}/preferences/avatar/pick.json", params: {
upload_id: upload.id, type: "custom"
}
expect(response.status).to eq(422)
user1.update!(admin: true)
put "/u/#{user1.username}/preferences/avatar/pick.json", params: {
upload_id: upload.id, type: "custom"
}
expect(response.status).to eq(200)
end
it "raises an error when selecting the custom/uploaded avatar and allow_uploaded_avatars is staff" do
SiteSetting.allow_uploaded_avatars = 'staff'
put "/u/#{user1.username}/preferences/avatar/pick.json", params: {
upload_id: upload.id, type: "custom"
}
expect(response.status).to eq(422)
user1.update!(moderator: true)
put "/u/#{user1.username}/preferences/avatar/pick.json", params: {
upload_id: upload.id, type: "custom"
}
expect(response.status).to eq(200)
end
it "raises an error when selecting the custom/uploaded avatar and allow_uploaded_avatars is a trust level" do
SiteSetting.allow_uploaded_avatars = '3'
put "/u/#{user1.username}/preferences/avatar/pick.json", params: {
upload_id: upload.id, type: "custom"
}
expect(response.status).to eq(422)
user1.update!(trust_level: 3)
put "/u/#{user1.username}/preferences/avatar/pick.json", params: {
upload_id: upload.id, type: "custom"
}
expect(response.status).to eq(200)
end
it 'ignores the upload if picking a system avatar' do
SiteSetting.allow_uploaded_avatars = 'disabled'
another_upload = Fabricate(:upload)
put "/u/#{user1.username}/preferences/avatar/pick.json", params: {
upload_id: another_upload.id, type: "system"
}
expect(response.status).to eq(200)
expect(user1.reload.uploaded_avatar_id).to eq(nil)
end
it 'raises an error if the type is invalid' do
SiteSetting.allow_uploaded_avatars = 'disabled'
another_upload = Fabricate(:upload)
put "/u/#{user1.username}/preferences/avatar/pick.json", params: {
upload_id: another_upload.id, type: "x"
}
expect(response.status).to eq(422)
end
it 'can successfully pick the system avatar' do
put "/u/#{user1.username}/preferences/avatar/pick.json"
expect(response.status).to eq(200)
expect(user1.reload.uploaded_avatar_id).to eq(nil)
end
it 'disables the use_site_small_logo_as_system_avatar setting when picking an avatar for the system user' do
system_user = Discourse.system_user
SiteSetting.use_site_small_logo_as_system_avatar = true
another_upload = Fabricate(:upload, user: system_user)
sign_in(system_user)
put "/u/#{system_user.username}/preferences/avatar/pick.json", params: {
upload_id: another_upload.id, type: "uploaded"
}
expect(response.status).to eq(200)
expect(SiteSetting.use_site_small_logo_as_system_avatar).to eq(false)
end
it 'can successfully pick a gravatar' do
user1.user_avatar.update_columns(gravatar_upload_id: upload.id)
put "/u/#{user1.username}/preferences/avatar/pick.json", params: {
upload_id: upload.id, type: "gravatar"
}
expect(response.status).to eq(200)
expect(user1.reload.uploaded_avatar_id).to eq(upload.id)
expect(user1.user_avatar.reload.gravatar_upload_id).to eq(upload.id)
end
it 'can not pick uploads that were not created by user' do
upload2 = Fabricate(:upload)
put "/u/#{user1.username}/preferences/avatar/pick.json", params: {
upload_id: upload2.id, type: "custom"
}
expect(response.status).to eq(403)
end
it 'can successfully pick a custom avatar' do
events = DiscourseEvent.track_events do
put "/u/#{user1.username}/preferences/avatar/pick.json", params: {
upload_id: upload.id, type: "custom"
}
end
expect(events.map { |event| event[:event_name] }).to include(:user_updated)
expect(response.status).to eq(200)
expect(user1.reload.uploaded_avatar_id).to eq(upload.id)
expect(user1.user_avatar.reload.custom_upload_id).to eq(upload.id)
end
end
end
describe '#select_avatar' do
it 'raises an error when not logged in' do
put "/u/asdf/preferences/avatar/select.json", params: { url: "https://meta.discourse.org" }
expect(response.status).to eq(403)
end
context 'while logged in' do
before do
sign_in(user1)
end
fab!(:avatar1) { Fabricate(:upload) }
fab!(:avatar2) { Fabricate(:upload) }
let(:url) { "https://www.discourse.org" }
it 'raises an error when url is blank' do
put "/u/#{user1.username}/preferences/avatar/select.json", params: { url: "" }
expect(response.status).to eq(422)
end
it 'raises an error when selectable avatars is disabled' do
put "/u/#{user1.username}/preferences/avatar/select.json", params: { url: url }
expect(response.status).to eq(422)
end
context 'when selectable avatars is enabled' do
before do
SiteSetting.selectable_avatars = [avatar1, avatar2]
SiteSetting.selectable_avatars_mode = "no_one"
end
it 'raises an error when selectable avatars is empty' do
SiteSetting.selectable_avatars = ""
put "/u/#{user1.username}/preferences/avatar/select.json", params: { url: url }
expect(response.status).to eq(422)
end
context 'when selectable avatars is properly setup' do
it 'raises an error when url is not in selectable avatars list' do
put "/u/#{user1.username}/preferences/avatar/select.json", params: { url: url }
expect(response.status).to eq(422)
end
it 'can successfully select an avatar' do
events = DiscourseEvent.track_events do
put "/u/#{user1.username}/preferences/avatar/select.json", params: { url: avatar1.url }
end
expect(events.map { |event| event[:event_name] }).to include(:user_updated)
expect(response.status).to eq(200)
expect(user1.reload.uploaded_avatar_id).to eq(avatar1.id)
expect(user1.user_avatar.reload.custom_upload_id).to eq(avatar1.id)
end
it 'can successfully select an avatar using a cooked URL' do
events = DiscourseEvent.track_events do
put "/u/#{user1.username}/preferences/avatar/select.json", params: { url: UrlHelper.cook_url(avatar1.url) }
end
expect(events.map { |event| event[:event_name] }).to include(:user_updated)
expect(response.status).to eq(200)
expect(user1.reload.uploaded_avatar_id).to eq(avatar1.id)
expect(user1.user_avatar.reload.custom_upload_id).to eq(avatar1.id)
end
it 'disables the use_site_small_logo_as_system_avatar setting when picking an avatar for the system user' do
system_user = Discourse.system_user
SiteSetting.use_site_small_logo_as_system_avatar = true
sign_in(system_user)
put "/u/#{system_user.username}/preferences/avatar/select.json", params: {
url: UrlHelper.cook_url(avatar1.url)
}
expect(response.status).to eq(200)
expect(SiteSetting.use_site_small_logo_as_system_avatar).to eq(false)
end
end
end
end
end
describe '#destroy_user_image' do
it 'raises an error when not logged in' do
delete "/u/asdf/preferences/user_image.json", params: { type: 'profile_background' }
expect(response.status).to eq(403)
end
context 'while logged in' do
before do
sign_in(user1)
end
it 'raises an error when you don\'t have permission to clear the profile background' do
delete "/u/#{another_user.username}/preferences/user_image.json", params: { type: 'profile_background' }
expect(response).to be_forbidden
end
it "requires the `type` param" do
delete "/u/#{user1.username}/preferences/user_image.json"
expect(response.status).to eq(400)
end
it "only allows certain `types`" do
delete "/u/#{user1.username}/preferences/user_image.json", params: { type: 'wat' }
expect(response.status).to eq(400)
end
it 'can clear the profile background' do
delete "/u/#{user1.username}/preferences/user_image.json", params: { type: 'profile_background' }
expect(user1.reload.profile_background_upload).to eq(nil)
expect(response.status).to eq(200)
end
end
end
describe '#destroy' do
it 'raises an error when not logged in' do
delete "/u/nobody.json"
expect(response.status).to eq(403)
end
context 'while logged in' do
before do
sign_in(user1)
end
it 'raises an error when you cannot delete your account' do
UserDestroyer.any_instance.expects(:destroy).never
stat = user1.user_stat
stat.post_count = 3
stat.save!
delete "/u/#{user1.username}.json"
expect(response).to be_forbidden
end
it "raises an error when you try to delete someone else's account" do
UserDestroyer.any_instance.expects(:destroy).never
delete "/u/#{another_user.username}.json"
expect(response).to be_forbidden
end
it "deletes your account when you're allowed to" do
UserDestroyer.any_instance.expects(:destroy).with(user1, anything).returns(user1)
delete "/u/#{user1.username}.json"
expect(response.status).to eq(200)
end
end
end
describe "#notification_level" do
it 'raises an error when `notification_level` param is not a valid value' do
sign_in(user)
invalid_arg = "invalid"
put "/u/#{user.username}/notification_level.json", params: { notification_level: invalid_arg }
expect(response.status).to eq(422)
expect(response.parsed_body["errors"].first).to eq(I18n.t("notification_level.invalid_value", value: invalid_arg))
end
end
describe '#ignore' do
it 'raises an error when not logged in' do
put "/u/#{user1.username}/notification_level.json", params: { notification_level: "" }
expect(response.status).to eq(403)
end
context 'while logged in' do
fab!(:user) { Fabricate(:user, trust_level: 2) }
before do
sign_in(user)
end
fab!(:ignored_user) { Fabricate(:ignored_user, user: user, ignored_user: another_user) }
fab!(:muted_user) { Fabricate(:muted_user, user: user, muted_user: another_user) }
context "when you can't change the notification" do
fab!(:staff_user) { admin }
it "ignoring includes a helpful error message" do
put "/u/#{staff_user.username}/notification_level.json", params: { notification_level: 'ignore' }
expect(response.status).to eq(422)
expect(response.parsed_body['errors'][0]).to eq(I18n.t("notification_level.ignore_error"))
end
it "muting includes a helpful error message" do
put "/u/#{staff_user.username}/notification_level.json", params: { notification_level: 'mute' }
expect(response.status).to eq(422)
expect(response.parsed_body['errors'][0]).to eq(I18n.t("notification_level.mute_error"))
end
end
context 'when changing notification level to normal' do
it 'changes notification level to normal' do
put "/u/#{another_user.username}/notification_level.json", params: { notification_level: "normal" }
expect(IgnoredUser.count).to eq(0)
expect(MutedUser.count).to eq(0)
end
end
context 'when changing notification level to mute' do
it 'changes notification level to mute' do
put "/u/#{another_user.username}/notification_level.json", params: { notification_level: "mute" }
expect(IgnoredUser.count).to eq(0)
expect(MutedUser.find_by(user_id: user.id, muted_user_id: another_user.id)).to be_present
end
end
context 'when changing notification level to ignore' do
it 'changes notification level to ignore' do
put "/u/#{another_user.username}/notification_level.json", params: {
notification_level: "ignore",
expiring_at: 3.days.from_now
}
expect(response.status).to eq(200)
expect(MutedUser.count).to eq(0)
expect(IgnoredUser.find_by(user_id: user.id, ignored_user_id: another_user.id)).to be_present
end
it "allows admin to change the ignore status for a source user" do
ignored_user.destroy!
sign_in(Fabricate(:user, admin: true))
put "/u/#{another_user.username}/notification_level.json", params: {
notification_level: "ignore",
acting_user_id: user.id,
expiring_at: 3.days.from_now
}
expect(response.status).to eq(200)
expect(IgnoredUser.find_by(user_id: user.id, ignored_user_id: another_user.id)).to be_present
end
it "does not allow a regular user to change the ignore status for anyone but themself" do
ignored_user.destroy!
acting_user = Fabricate(:user)
put "/u/#{another_user.username}/notification_level.json", params: {
notification_level: "ignore",
acting_user_id: acting_user.id,
expiring_at: 3.days.from_now
}
expect(response.status).to eq(422)
expect(IgnoredUser.find_by(user_id: acting_user.id, ignored_user_id: another_user.id)).to eq(nil)
put "/u/#{another_user.username}/notification_level.json", params: {
notification_level: "ignore",
expiring_at: 3.days.from_now
}
expect(response.status).to eq(200)
expect(IgnoredUser.find_by(user_id: user.id, ignored_user_id: another_user.id)).to be_present
end
context 'when expiring_at param is set' do
it 'changes notification level to ignore' do
freeze_time(Time.now) do
expiring_at = 3.days.from_now
put "/u/#{another_user.username}/notification_level.json", params: { notification_level: "ignore", expiring_at: expiring_at }
ignored_user = IgnoredUser.find_by(user_id: user.id, ignored_user_id: another_user.id)
expect(ignored_user).to be_present
expect(ignored_user.expiring_at.to_i).to eq(expiring_at.to_i)
expect(MutedUser.count).to eq(0)
end
end
end
end
end
end
describe "for user with period in username" do
fab!(:user_with_period) { Fabricate(:user, username: "myname.test") }
it "still works" do
sign_in(user_with_period)
UserDestroyer.any_instance.expects(:destroy).with(user_with_period, anything).returns(user_with_period)
delete "/u/#{user_with_period.username}", xhr: true
expect(response.status).to eq(200)
end
end
describe '#my_redirect' do
it "redirects if the user is not logged in" do
get "/my/wat"
expect(response).to redirect_to("/login-preferences")
expect(response.cookies).to have_key("destination_url")
expect(response.cookies["destination_url"]).to eq("/my/wat")
expect(response.headers['X-Robots-Tag']).to eq('noindex')
end
context "when the user is logged in" do
before do
sign_in(user1)
end
it "will not redirect to an invalid path" do
get "/my/wat/..password.txt"
expect(response).not_to be_redirect
end
it "will redirect to an valid path" do
get "/my/preferences"
expect(response).to redirect_to("/u/#{user1.username}/preferences")
end
it "permits forward slashes" do
get "/my/activity/posts"
expect(response).to redirect_to("/u/#{user1.username}/activity/posts")
end
it "correctly redirects for Unicode usernames" do
SiteSetting.unicode_usernames = true
user = sign_in(Fabricate(:unicode_user))
get "/my/preferences"
expect(response).to redirect_to("/u/#{user.encoded_username}/preferences")
end
end
end
describe '#check_emails' do
it 'raises an error when not logged in' do
get "/u/zogstrip/emails.json"
expect(response.status).to eq(403)
end
context 'while logged in' do
let(:sign_in_admin) { sign_in(admin) }
it "raises an error when you aren't allowed to check emails" do
sign_in(Fabricate(:user))
get "/u/#{Fabricate(:user).username}/emails.json"
expect(response).to be_forbidden
end
it "returns emails and associated_accounts for self" do
Fabricate(:email_change_request, user: user1)
sign_in(user)
get "/u/#{user.username}/emails.json"
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["email"]).to eq(user.email)
expect(json["secondary_emails"]).to eq(user.secondary_emails)
expect(json["unconfirmed_emails"]).to eq(user.unconfirmed_emails)
expect(json["associated_accounts"]).to eq([])
end
it "returns emails and associated_accounts when you're allowed to see them" do
Fabricate(:email_change_request, user: user1)
sign_in_admin
get "/u/#{user.username}/emails.json"
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["email"]).to eq(user.email)
expect(json["secondary_emails"]).to eq(user.secondary_emails)
expect(json["unconfirmed_emails"]).to eq(user.unconfirmed_emails)
expect(json["associated_accounts"]).to eq([])
end
it "works on inactive users" do
inactive_user = Fabricate(:user, active: false)
Fabricate(:email_change_request, user: inactive_user)
sign_in_admin
get "/u/#{inactive_user.username}/emails.json"
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["email"]).to eq(inactive_user.email)
expect(json["secondary_emails"]).to eq(inactive_user.secondary_emails)
expect(json["unconfirmed_emails"]).to eq(inactive_user.unconfirmed_emails)
expect(json["associated_accounts"]).to eq([])
end
end
end
describe '#check_sso_email' do
it 'raises an error when not logged in' do
get "/u/zogstrip/sso-email.json"
expect(response.status).to eq(403)
end
context 'while logged in' do
let(:sign_in_admin) { sign_in(admin) }
it "raises an error when you aren't allowed to check sso email" do
sign_in(Fabricate(:user))
get "/u/#{user1.username}/sso-email.json"
expect(response).to be_forbidden
end
it "returns emails and associated_accounts when you're allowed to see them" do
user1.single_sign_on_record = SingleSignOnRecord.create(user_id: user1.id, external_email: "foobar@example.com", external_id: "example", last_payload: "looks good")
sign_in_admin
get "/u/#{user1.username}/sso-email.json"
expect(response.status).to eq(200)
expect(response.parsed_body["email"]).to eq("foobar@example.com")
end
end
end
describe '#check_sso_payload' do
it 'raises an error when not logged in' do
get "/u/zogstrip/sso-payload.json"
expect(response.status).to eq(403)
end
context 'while logged in' do
let(:sign_in_admin) { sign_in(admin) }
it "raises an error when you aren't allowed to check sso payload" do
sign_in(Fabricate(:user))
get "/u/#{user1.username}/sso-payload.json"
expect(response).to be_forbidden
end
it "returns SSO payload when you're allowed to see" do
user1.single_sign_on_record = SingleSignOnRecord.create(user_id: user1.id, external_email: "foobar@example.com", external_id: "example", last_payload: "foobar")
sign_in_admin
get "/u/#{user1.username}/sso-payload.json"
expect(response.status).to eq(200)
expect(response.parsed_body["payload"]).to eq("foobar")
end
end
end
describe '#update_primary_email' do
let(:user_email) { user1.primary_email }
fab!(:other_email) { Fabricate(:secondary_email, user: user1) }
before do
SiteSetting.email_editable = true
sign_in(user1)
end
it "changes user's primary email" do
put "/u/#{user1.username}/preferences/primary-email.json", params: { email: user_email.email }
expect(response.status).to eq(200)
expect(user_email.reload.primary).to eq(true)
expect(other_email.reload.primary).to eq(false)
event = DiscourseEvent.track_events {
expect { put "/u/#{user1.username}/preferences/primary-email.json", params: { email: other_email.email } }
.to change { UserHistory.where(action: UserHistory.actions[:update_email], acting_user_id: user1.id).count }.by(1)
}.last
expect(response.status).to eq(200)
expect(user_email.reload.primary).to eq(false)
expect(other_email.reload.primary).to eq(true)
expect(event[:event_name]).to eq(:user_updated)
expect(event[:params].first).to eq(user1)
end
end
describe '#destroy_email' do
fab!(:user_email) { user1.primary_email }
fab!(:other_email) { Fabricate(:secondary_email, user: user1) }
before do
SiteSetting.email_editable = true
sign_in(user1)
end
it "can destroy secondary emails" do
delete "/u/#{user1.username}/preferences/email.json", params: { email: user_email.email }
expect(response.status).to eq(428)
expect(user1.reload.user_emails.pluck(:email)).to contain_exactly(user_email.email, other_email.email)
event = DiscourseEvent.track_events {
expect { delete "/u/#{user1.username}/preferences/email.json", params: { email: other_email.email } }
.to change { UserHistory.where(action: UserHistory.actions[:destroy_email], acting_user_id: user1.id).count }.by(1)
}.last
expect(response.status).to eq(200)
expect(user1.reload.user_emails.pluck(:email)).to contain_exactly(user_email.email)
expect(event[:event_name]).to eq(:user_updated)
expect(event[:params].first).to eq(user1)
end
it "can destroy unconfirmed emails" do
request_1 = EmailChangeRequest.create!(
user: user1,
new_email: user_email.email,
change_state: EmailChangeRequest.states[:authorizing_new]
)
EmailChangeRequest.create!(
user: user1,
new_email: other_email.email,
change_state: EmailChangeRequest.states[:authorizing_new]
)
EmailChangeRequest.create!(
user: user1,
new_email: other_email.email,
change_state: EmailChangeRequest.states[:authorizing_new]
)
delete "/u/#{user1.username}/preferences/email.json", params: { email: other_email.email }
expect(user1.user_emails.pluck(:email)).to contain_exactly(user_email.email, other_email.email)
expect(user1.email_change_requests).to contain_exactly(request_1)
end
it "can destroy associated email tokens" do
new_email = 'new.n.cool@example.com'
updater = EmailUpdater.new(guardian: user1.guardian, user: user1)
expect { updater.change_to(new_email) }
.to change { user1.email_tokens.count }.by(1)
expect { delete "/u/#{user1.username}/preferences/email.json", params: { email: new_email } }
.to change { user1.email_tokens.count }.by(-1)
expect(user1.email_tokens.first.email).to eq(user1.email)
end
end
describe '#is_local_username' do
fab!(:group) { Fabricate(:group, name: "Discourse", mentionable_level: Group::ALIAS_LEVELS[:everyone]) }
let(:unmentionable) {
Fabricate(:group, name: "Unmentionable", mentionable_level: Group::ALIAS_LEVELS[:nobody])
}
fab!(:topic) { Fabricate(:topic) }
fab!(:allowed_user) { Fabricate(:user) }
fab!(:private_topic) { Fabricate(:private_message_topic, user: allowed_user) }
it "finds the user" do
get "/u/is_local_username.json", params: { username: user1.username }
expect(response.status).to eq(200)
expect(response.parsed_body["valid"][0]).to eq(user1.username)
end
it "finds the group" do
sign_in(user1)
get "/u/is_local_username.json", params: { username: group.name }
expect(response.status).to eq(200)
expect(response.parsed_body["valid_groups"]).to include(group.name)
expect(response.parsed_body["mentionable_groups"].find { |g| g['name'] == group.name }).to be_present
end
it "finds unmentionable groups" do
sign_in(user1)
get "/u/is_local_username.json", params: { username: unmentionable.name }
expect(response.status).to eq(200)
expect(response.parsed_body["valid_groups"]).to include(unmentionable.name)
expect(response.parsed_body["mentionable_groups"]).to be_blank
end
it "supports multiples usernames" do
get "/u/is_local_username.json", params: { usernames: [user1.username, "system"] }
expect(response.status).to eq(200)
expect(response.parsed_body["valid"]).to contain_exactly(user1.username, "system")
end
it "never includes staged accounts" do
staged = Fabricate(:user, staged: true)
get "/u/is_local_username.json", params: { usernames: [staged.username] }
expect(response.status).to eq(200)
expect(response.parsed_body["valid"]).to be_blank
end
it "returns user who cannot see topic" do
Guardian.any_instance.expects(:can_see?).with(topic).returns(false)
get "/u/is_local_username.json", params: {
usernames: [user1.username], topic_id: topic.id
}
expect(response.status).to eq(200)
expect(response.parsed_body["cannot_see"][user1.username]).to eq("category")
end
it "never returns a user who can see the topic" do
get "/u/is_local_username.json", params: {
usernames: [user1.username], topic_id: topic.id
}
expect(response.status).to eq(200)
expect(response.parsed_body["cannot_see"]).to be_blank
end
it "returns user who cannot see a private topic" do
get "/u/is_local_username.json", params: {
usernames: [user1.username], topic_id: private_topic.id
}
expect(response.status).to eq(200)
expect(response.parsed_body["cannot_see"][user1.username]).to eq("private")
end
it "returns user who was not invited to topic" do
sign_in(Fabricate(:admin))
get "/u/is_local_username.json", params: {
usernames: [admin.username], topic_id: private_topic.id
}
expect(response.status).to eq(200)
expect(response.parsed_body["cannot_see"][admin.username]).to eq("not_allowed")
end
it "never returns a user who can see the topic" do
get "/u/is_local_username.json", params: {
usernames: [allowed_user.username], topic_id: private_topic.id
}
expect(response.status).to eq(200)
expect(response.parsed_body["cannot_see"]).to be_blank
end
it "returns the appropriate reason why user cannot see the topic" do
TopicUser.create!(user_id: user1.id, topic_id: topic.id, notification_level: TopicUser.notification_levels[:muted])
sign_in(admin)
get "/u/is_local_username.json", params: {
usernames: [user1.username], topic_id: topic.id
}
expect(response.status).to eq(200)
expect(response.parsed_body["cannot_see"][user1.username]).to eq("muted_topic")
end
end
describe '#topic_tracking_state' do
context 'when anon' do
it "raises an error on anon for topic_tracking_state" do
get "/u/#{user1.username}/topic-tracking-state.json"
expect(response.status).to eq(403)
end
end
context 'when logged on' do
it "detects new topic" do
sign_in(user1)
topic = Fabricate(:topic)
get "/u/#{user1.username}/topic-tracking-state.json"
expect(response.status).to eq(200)
states = response.parsed_body
expect(states[0]["topic_id"]).to eq(topic.id)
end
end
end
describe '#summary' do
it "generates summary info" do
create_post(user: user)
get "/u/#{user.username_lower}/summary.json"
expect(response.headers['X-Robots-Tag']).to eq('noindex')
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["user_summary"]["topic_count"]).to eq(1)
expect(json["user_summary"]["post_count"]).to eq(0)
end
context 'when `hide_profile_and_presence` user option is checked' do
before_all do
user1.user_option.update_columns(hide_profile_and_presence: true)
end
it "returns 404" do
get "/u/#{user1.username_lower}/summary.json"
expect(response.status).to eq(404)
end
it "returns summary info if `allow_users_to_hide_profile` is false" do
SiteSetting.allow_users_to_hide_profile = false
get "/u/#{user1.username_lower}/summary.json"
expect(response.status).to eq(200)
end
end
context 'with avatar flair in Most... sections' do
it "returns data for automatic groups flair" do
liker = Fabricate(:user, admin: true, moderator: true, trust_level: 1)
create_and_like_post(user_deferred, liker)
get "/u/#{user_deferred.username_lower}/summary.json"
json = response.parsed_body
expect(json["user_summary"]["most_liked_by_users"][0]["admin"]).to eq(true)
expect(json["user_summary"]["most_liked_by_users"][0]["moderator"]).to eq(true)
expect(json["user_summary"]["most_liked_by_users"][0]["trust_level"]).to eq(1)
end
it "returns data for flair when an icon is used" do
group = Fabricate(:group, name: "Groupie", flair_bg_color: "#111111", flair_color: "#999999", flair_icon: "icon")
liker = Fabricate(:user, flair_group: group)
create_and_like_post(user_deferred, liker)
get "/u/#{user_deferred.username_lower}/summary.json"
json = response.parsed_body
expect(json["user_summary"]["most_liked_by_users"][0]["flair_name"]).to eq("Groupie")
expect(json["user_summary"]["most_liked_by_users"][0]["flair_url"]).to eq("icon")
expect(json["user_summary"]["most_liked_by_users"][0]["flair_bg_color"]).to eq("#111111")
expect(json["user_summary"]["most_liked_by_users"][0]["flair_color"]).to eq("#999999")
end
it "returns data for flair when an image is used" do
upload = Fabricate(:upload)
group = Fabricate(:group, name: "Groupie", flair_bg_color: "#111111", flair_upload: upload)
liker = Fabricate(:user, flair_group: group)
create_and_like_post(user_deferred, liker)
get "/u/#{user_deferred.username_lower}/summary.json"
json = response.parsed_body
expect(json["user_summary"]["most_liked_by_users"][0]["flair_name"]).to eq("Groupie")
expect(json["user_summary"]["most_liked_by_users"][0]["flair_url"]).to eq(upload.url)
expect(json["user_summary"]["most_liked_by_users"][0]["flair_bg_color"]).to eq("#111111")
end
def create_and_like_post(likee, liker)
UserActionManager.enable
post = create_post(user: likee)
PostActionCreator.like(liker, post)
end
end
end
describe '#confirm_admin' do
it "fails without a valid token" do
get "/u/confirm-admin/invalid-token.json"
expect(response).not_to be_successful
end
it "fails with a missing token" do
get "/u/confirm-admin/a0a0a0a0a0.json"
expect(response).to_not be_successful
end
it "succeeds with a valid code as anonymous" do
ac = AdminConfirmation.new(user1, admin)
ac.create_confirmation
get "/u/confirm-admin/#{ac.token}.json"
expect(response.status).to eq(200)
user1.reload
expect(user1.admin?).to eq(false)
end
it "succeeds with a valid code when logged in as that user" do
sign_in(admin)
ac = AdminConfirmation.new(user1, admin)
ac.create_confirmation
get "/u/confirm-admin/#{ac.token}.json", params: { token: ac.token }
expect(response.status).to eq(200)
user1.reload
expect(user1.admin?).to eq(false)
end
it "fails if you're logged in as a different account" do
sign_in(admin)
ac = AdminConfirmation.new(user1, Fabricate(:admin))
ac.create_confirmation
get "/u/confirm-admin/#{ac.token}.json"
expect(response).to_not be_successful
user1.reload
expect(user1.admin?).to eq(false)
end
describe "post" do
it "gives the user admin access when POSTed" do
ac = AdminConfirmation.new(user1, admin)
ac.create_confirmation
post "/u/confirm-admin/#{ac.token}.json"
expect(response.status).to eq(200)
user1.reload
expect(user1.admin?).to eq(true)
end
end
end
describe '#update_activation_email' do
before do
UsersController.any_instance.stubs(:honeypot_value).returns(nil)
UsersController.any_instance.stubs(:challenge_value).returns(nil)
end
let(:post_user) do
post "/u.json", params: {
username: "osamatest",
password: "strongpassword",
email: "osama@example.com"
}
user = User.where(username: "osamatest").first
user.active = false
user.save!
user
end
context "with a session variable" do
it "raises an error with an invalid session value" do
post_user
post "/u.json", params: {
username: "osamatest2",
password: "strongpassword2",
email: "osama22@example.com"
}
user = User.where(username: "osamatest2").first
user.destroy
put "/u/update-activation-email.json", params: {
email: 'osamaupdated@example.com'
}
expect(response.status).to eq(403)
end
it "raises an error for an active user" do
user = post_user
user.update(active: true)
user.save!
put "/u/update-activation-email.json", params: {
email: 'osama@example.com'
}
expect(response.status).to eq(403)
end
it "raises an error when logged in" do
sign_in(moderator)
post_user
put "/u/update-activation-email.json", params: {
email: 'updatedemail@example.com'
}
expect(response.status).to eq(403)
end
it "raises an error when the new email is taken" do
active_user = Fabricate(:user)
post_user
put "/u/update-activation-email.json", params: {
email: active_user.email
}
expect(response.status).to eq(422)
end
it "raises an error when the email is blocklisted" do
post_user
SiteSetting.blocked_email_domains = 'example.com'
put "/u/update-activation-email.json", params: { email: 'test@example.com' }
expect(response.status).to eq(422)
end
it "can be updated" do
user = post_user
token = user.email_tokens.first
put "/u/update-activation-email.json", params: {
email: 'updatedemail@example.com'
}
expect(response.status).to eq(200)
user.reload
expect(user.email).to eq('updatedemail@example.com')
expect(user.email_tokens.where(email: 'updatedemail@example.com', expired: false)).to be_present
token.reload
expect(token.expired?).to eq(true)
end
it 'tells the user to slow down after many requests' do
RateLimiter.enable
RateLimiter.clear_all!
freeze_time
user = post_user
token = user.email_tokens.first
6.times do |n|
put "/u/update-activation-email.json", params: {
email: "updatedemail#{n}@example.com"
}, env: { "REMOTE_ADDR": "1.2.3.#{n}" }
end
expect(response.status).to eq(429)
end
end
context "with a username and password" do
it "raises an error with an invalid username" do
put "/u/update-activation-email.json", params: {
username: 'eviltrout',
password: 'invalid-password',
email: 'updatedemail@example.com'
}
expect(response.status).to eq(403)
end
it "raises an error with an invalid password" do
put "/u/update-activation-email.json", params: {
username: inactive_user.username,
password: 'invalid-password',
email: 'updatedemail@example.com'
}
expect(response.status).to eq(403)
end
it "raises an error for an active user" do
put "/u/update-activation-email.json", params: {
username: Fabricate(:walter_white).username,
password: 'letscook',
email: 'updatedemail@example.com'
}
expect(response.status).to eq(403)
end
it "raises an error when logged in" do
sign_in(moderator)
put "/u/update-activation-email.json", params: {
username: inactive_user.username,
password: 'qwerqwer123',
email: 'updatedemail@example.com'
}
expect(response.status).to eq(403)
end
it "raises an error when the new email is taken" do
put "/u/update-activation-email.json", params: {
username: inactive_user.username,
password: 'qwerqwer123',
email: user.email
}
expect(response.status).to eq(422)
end
it "can be updated" do
user = inactive_user
token = user.email_tokens.first
put "/u/update-activation-email.json", params: {
username: user.username,
password: 'qwerqwer123',
email: 'updatedemail@example.com'
}
expect(response.status).to eq(200)
user.reload
expect(user.email).to eq('updatedemail@example.com')
expect(user.email_tokens.where(email: 'updatedemail@example.com', expired: false)).to be_present
token.reload
expect(token.expired?).to eq(true)
end
it 'tells the user to slow down after many requests' do
RateLimiter.enable
RateLimiter.clear_all!
freeze_time
user = inactive_user
token = user.email_tokens.first
6.times do |n|
put "/u/update-activation-email.json", params: {
username: user.username,
password: 'qwerqwer123',
email: "updatedemail#{n}@example.com"
}, env: { "REMOTE_ADDR": "1.2.3.#{n}" }
end
expect(response.status).to eq(429)
end
end
end
describe '#show' do
context "when anon" do
let(:user) { Discourse.system_user }
it "returns success" do
get "/u/#{user.username}.json"
expect(response.status).to eq(200)
parsed = response.parsed_body["user"]
expect(parsed['username']).to eq(user.username)
expect(parsed["profile_hidden"]).to be_blank
expect(parsed["trust_level"]).to be_present
end
it "returns a hidden profile" do
user.user_option.update_column(:hide_profile_and_presence, true)
get "/u/#{user.username}.json"
expect(response.status).to eq(200)
parsed = response.parsed_body["user"]
expect(parsed["username"]).to eq(user.username)
expect(parsed["profile_hidden"]).to eq(true)
expect(parsed["trust_level"]).to be_blank
end
it "should redirect to login page for anonymous user when profiles are hidden" do
SiteSetting.hide_user_profiles_from_public = true
get "/u/#{user.username}.json"
expect(response).to redirect_to '/login'
end
describe "user profile views" do
it "should track a user profile view for an anon user" do
get "/"
UserProfileView.expects(:add).with(another_user.user_profile.id, request.remote_ip, nil)
get "/u/#{another_user.username}.json"
end
it "skips tracking" do
UserProfileView.expects(:add).never
get "/u/#{user.username}.json", params: { skip_track_visit: true }
end
end
end
context "when logged in" do
before do
sign_in(user1)
end
it 'returns success' do
get "/u/#{user1.username}.json"
expect(response.status).to eq(200)
expect(response.headers['X-Robots-Tag']).to eq('noindex')
json = response.parsed_body
expect(json["user"]["has_title_badges"]).to eq(false)
end
it "returns not found when the username doesn't exist" do
get "/u/madeuppity.json"
expect(response).not_to be_successful
end
it 'returns not found when the user is inactive' do
inactive = Fabricate(:user, active: false)
get "/u/#{inactive.username}.json"
expect(response).not_to be_successful
end
it 'returns success when show_inactive_accounts is true and user is logged in' do
SiteSetting.show_inactive_accounts = true
inactive = Fabricate(:user, active: false)
get "/u/#{inactive.username}.json"
expect(response.status).to eq(200)
end
it "raises an error on invalid access" do
Guardian.any_instance.expects(:can_see?).with(user1).returns(false)
get "/u/#{user1.username}.json"
expect(response).to be_forbidden
end
describe "user profile views" do
it "should track a user profile view for a signed in user" do
UserProfileView.expects(:add).with(another_user.user_profile.id, request.remote_ip, user1.id)
get "/u/#{another_user.username}.json"
end
it "should not track a user profile view for a user viewing his own profile" do
UserProfileView.expects(:add).never
get "/u/#{user1.username}.json"
end
it "skips tracking" do
UserProfileView.expects(:add).never
get "/u/#{user1.username}.json", params: { skip_track_visit: true }
end
end
context "when fetching a user by external_id" do
before { user1.create_single_sign_on_record(external_id: '997', last_payload: '') }
it "returns fetch for a matching external_id" do
get "/u/by-external/997.json"
expect(response.status).to eq(200)
expect(response.parsed_body["user"]["username"]).to eq(user1.username)
end
it "returns not found when external_id doesn't match" do
get "/u/by-external/99.json"
expect(response).not_to be_successful
end
context "for an external provider" do
before do
sign_in(admin)
SiteSetting.enable_google_oauth2_logins = true
UserAssociatedAccount.create!(user: user1, provider_uid: 'myuid', provider_name: 'google_oauth2')
end
it "doesn't work for non-admin" do
sign_in(user1)
get "/u/by-external/google_oauth2/myuid.json"
expect(response.status).to eq(403)
end
it "can fetch the user" do
get "/u/by-external/google_oauth2/myuid.json"
expect(response.status).to eq(200)
expect(response.parsed_body["user"]["username"]).to eq(user1.username)
end
it "fails for disabled provider" do
SiteSetting.enable_google_oauth2_logins = false
get "/u/by-external/google_oauth2/myuid.json"
expect(response.status).to eq(404)
end
it "returns 404 for missing user" do
get "/u/by-external/google_oauth2/myotheruid.json"
expect(response.status).to eq(404)
end
end
end
describe "include_post_count_for" do
fab!(:topic) { Fabricate(:topic) }
before_all do
Fabricate(:post, user: user1, topic: topic)
Fabricate(:post, user: admin, topic: topic)
Fabricate(:post, user: admin, topic: topic, post_type: Post.types[:whisper])
end
it "includes only visible posts" do
get "/u/#{admin.username}.json", params: { include_post_count_for: topic.id }
topic_post_count = response.parsed_body.dig("user", "topic_post_count")
expect(topic_post_count[topic.id.to_s]).to eq(1)
end
it "includes all post types for staff members" do
SiteSetting.enable_whispers = true
sign_in(admin)
get "/u/#{admin.username}.json", params: { include_post_count_for: topic.id }
topic_post_count = response.parsed_body.dig("user", "topic_post_count")
expect(topic_post_count[topic.id.to_s]).to eq(2)
end
end
end
it "should be able to view a user" do
get "/u/#{user1.username}"
expect(response.status).to eq(200)
expect(response.body).to include(user1.username)
end
it "should not be able to view a private user profile" do
user1.user_profile.update!(bio_raw: "Hello world!")
user1.user_option.update!(hide_profile_and_presence: true)
get "/u/#{user1.username}"
expect(response.status).to eq(200)
expect(response.body).not_to include("Hello world!")
end
describe 'when username contains a period' do
before_all do
user1.update!(username: 'test.test')
end
it "should be able to view a user" do
get "/u/#{user1.username}"
expect(response.status).to eq(200)
expect(response.body).to include(user1.username)
end
end
end
describe "#show_card" do
context "when anon" do
let(:user) { Discourse.system_user }
it "returns success" do
get "/u/#{user.username}/card.json"
expect(response.status).to eq(200)
parsed = response.parsed_body["user"]
expect(parsed["username"]).to eq(user.username)
expect(parsed["profile_hidden"]).to be_blank
expect(parsed["trust_level"]).to be_present
end
it "should redirect to login page for anonymous user when profiles are hidden" do
SiteSetting.hide_user_profiles_from_public = true
get "/u/#{user.username}/card.json"
expect(response).to redirect_to '/login'
end
end
context "when logged in" do
before do
sign_in(user1)
end
it 'works correctly' do
get "/u/#{user1.username}/card.json"
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["user"]["associated_accounts"]).to eq(nil) # Not serialized in card
expect(json["user"]["username"]).to eq(user1.username)
end
it "returns not found when the username doesn't exist" do
get "/u/madeuppity/card.json"
expect(response).not_to be_successful
end
it "raises an error on invalid access" do
Guardian.any_instance.expects(:can_see?).with(user1).returns(false)
get "/u/#{user1.username}/card.json"
expect(response).to be_forbidden
end
end
end
describe "#cards" do
fab!(:user) { Discourse.system_user }
fab!(:user2) { Fabricate(:user) }
it "returns success" do
get "/user-cards.json?user_ids=#{user.id},#{user2.id}"
expect(response.status).to eq(200)
parsed = response.parsed_body["users"]
expect(parsed.map { |u| u["username"] }).to contain_exactly(user.username, user2.username)
end
it "should redirect to login page for anonymous user when profiles are hidden" do
SiteSetting.hide_user_profiles_from_public = true
get "/user-cards.json?user_ids=#{user.id},#{user2.id}"
expect(response).to redirect_to '/login'
end
context 'when `hide_profile_and_presence` user option is checked' do
before do
user2.user_option.update_columns(hide_profile_and_presence: true)
end
it "does not include hidden profiles" do
get "/user-cards.json?user_ids=#{user.id},#{user2.id}"
expect(response.status).to eq(200)
parsed = response.parsed_body["users"]
expect(parsed.map { |u| u["username"] }).to contain_exactly(user.username)
end
it "does include hidden profiles when `allow_users_to_hide_profile` is false" do
SiteSetting.allow_users_to_hide_profile = false
get "/user-cards.json?user_ids=#{user.id},#{user2.id}"
expect(response.status).to eq(200)
parsed = response.parsed_body["users"]
expect(parsed.map { |u| u["username"] }).to contain_exactly(user.username, user2.username)
end
end
end
describe '#badges' do
it "renders fine by default" do
get "/u/#{user1.username}/badges"
expect(response.status).to eq(200)
end
it "fails if badges are disabled" do
SiteSetting.enable_badges = false
get "/u/#{user1.username}/badges"
expect(response.status).to eq(404)
end
end
describe "#account_created" do
it "returns a message when no session is present" do
get "/u/account-created"
expect(response.status).to eq(200)
body = response.body
expect(body).to match(I18n.t('activation.missing_session'))
end
it "redirects when the user is logged in" do
sign_in(user1)
get "/u/account-created"
expect(response).to redirect_to("/")
end
context 'when cookies contains a destination URL' do
it 'should redirect to the URL' do
sign_in(user1)
destination_url = 'http://thisisasite.com/somepath'
cookies[:destination_url] = destination_url
get "/u/account-created"
expect(response).to redirect_to(destination_url)
end
end
context "when the user account is created" do
include ApplicationHelper
it "returns the message when set in the session" do
user1 = create_user
get "/u/account-created"
expect(response.status).to eq(200)
expect(response.body).to have_tag("div#data-preloaded") do |element|
json = JSON.parse(element.current_scope.attribute('data-preloaded').value)
expect(json['accountCreated']).to include(
"{\"message\":\"#{I18n.t("login.activate_email", email: user1.email).gsub!("</", "<\\/")}\",\"show_controls\":true,\"username\":\"#{user1.username}\",\"email\":\"#{user1.email}\"}"
)
end
end
end
end
describe '#search_users' do
fab!(:topic) { Fabricate :topic }
let(:user) { Fabricate :user, username: "joecabot", name: "Lawrence Tierney" }
let(:post1) { Fabricate(:post, user: user, topic: topic) }
let(:staged_user) { Fabricate(:user, staged: true) }
before do
SearchIndexer.enable
post1
end
it "searches when provided the term only" do
get "/u/search/users.json", params: { term: user.name.split(" ").last }
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["users"].map { |u| u["username"] }).to include(user.username)
end
it "searches when provided the topic only" do
get "/u/search/users.json", params: { topic_id: topic.id }
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["users"].map { |u| u["username"] }).to include(user.username)
end
it "searches when provided the term and topic" do
get "/u/search/users.json", params: {
term: user.name.split(" ").last, topic_id: topic.id
}
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["users"].map { |u| u["username"] }).to include(user.username)
end
it "searches only for users who have access to private topic" do
searching_user = Fabricate(:user)
privileged_user = Fabricate(:user, trust_level: 4, username: "joecabit", name: "Lawrence Tierney")
privileged_group = Fabricate(:group)
privileged_group.add(searching_user)
privileged_group.add(privileged_user)
privileged_group.save
category = Fabricate(:category)
category.set_permissions(privileged_group => :readonly)
category.save
private_topic = Fabricate(:topic, category: category)
sign_in(searching_user)
get "/u/search/users.json", params: {
term: user.name.split(" ").last, topic_id: private_topic.id, topic_allowed_users: "true"
}
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["users"].map { |u| u["username"] }).to_not include(user.username)
expect(json["users"].map { |u| u["username"] }).to include(privileged_user.username)
end
it "interprets blank category id correctly" do
pm_topic = Fabricate(:private_message_post).topic
sign_in(pm_topic.user)
get "/u/search/users.json", params: {
term: "", topic_id: pm_topic.id, category_id: ""
}
expect(response.status).to eq(200)
end
context 'with limit' do
it "returns an error if value is invalid" do
get "/u/search/users.json", params: { limit: '-1' }
expect(response.status).to eq(400)
end
end
context "when `enable_names` is true" do
before do
SiteSetting.enable_names = true
end
it "returns names" do
get "/u/search/users.json", params: { term: user.name }
json = response.parsed_body
expect(json["users"].map { |u| u["name"] }).to include(user.name)
end
end
context "when `enable_names` is false" do
before do
SiteSetting.enable_names = false
end
it "returns names" do
get "/u/search/users.json", params: { term: user.name }
json = response.parsed_body
expect(json["users"].map { |u| u["name"] }).not_to include(user.name)
end
end
context 'with groups' do
fab!(:mentionable_group) do
Fabricate(:group,
mentionable_level: Group::ALIAS_LEVELS[:everyone],
messageable_level: Group::ALIAS_LEVELS[:nobody],
visibility_level: Group.visibility_levels[:public],
name: 'aaa1bbb'
)
end
fab!(:mentionable_group_2) do
Fabricate(:group,
mentionable_level: Group::ALIAS_LEVELS[:everyone],
messageable_level: Group::ALIAS_LEVELS[:nobody],
visibility_level: Group.visibility_levels[:logged_on_users],
name: 'bbb1aaa'
)
end
fab!(:messageable_group) do
Fabricate(:group,
mentionable_level: Group::ALIAS_LEVELS[:nobody],
messageable_level: Group::ALIAS_LEVELS[:everyone],
visibility_level: Group.visibility_levels[:logged_on_users],
name: 'ccc1aaa'
)
end
fab!(:private_group) do
Fabricate(:group,
mentionable_level: Group::ALIAS_LEVELS[:members_mods_and_admins],
messageable_level: Group::ALIAS_LEVELS[:members_mods_and_admins],
visibility_level: Group.visibility_levels[:members],
name: 'ddd1aaa'
)
end
describe 'when signed in' do
before do
sign_in(user)
end
it "correctly sorts on prefix" do
get "/u/search/users.json", params: { include_groups: "true", term: 'bbb' }
expect(response.status).to eq(200)
groups = response.parsed_body["groups"]
expect(groups.map { |g| g["name"] }).to eq(["bbb1aaa", "aaa1bbb"])
end
it "does not search for groups if there is no term" do
get "/u/search/users.json", params: { include_groups: "true" }
expect(response.status).to eq(200)
groups = response.parsed_body["groups"]
expect(groups).to eq(nil)
end
it "only returns visible groups" do
get "/u/search/users.json", params: { include_groups: "true", term: 'a' }
expect(response.status).to eq(200)
groups = response.parsed_body["groups"]
expect(groups.map { |group| group['name'] })
.to_not include(private_group.name)
end
it 'allows plugins to register custom groups filter' do
get "/u/search/users.json", params: { include_groups: "true", term: "a" }
expect(response.status).to eq(200)
groups = response.parsed_body["groups"]
expect(groups.count).to eq(6)
plugin = Plugin::Instance.new
plugin.register_groups_callback_for_users_search_controller_action(:admins_filter) do |original_groups, user|
original_groups.where(name: "admins")
end
get "/u/search/users.json", params: { include_groups: "true", admins_filter: "true", term: "a" }
expect(response.status).to eq(200)
groups = response.parsed_body["groups"]
expect(groups).to eq([{ "name" => "admins", "full_name" => nil }])
DiscoursePluginRegistry.reset!
end
it "doesn't search for groups" do
get "/u/search/users.json", params: {
include_mentionable_groups: 'false',
include_messageable_groups: 'false',
term: 'a'
}
expect(response.status).to eq(200)
expect(response.parsed_body).not_to have_key(:groups)
end
it "searches for messageable groups" do
get "/u/search/users.json", params: {
include_mentionable_groups: 'false',
include_messageable_groups: 'true',
term: 'a'
}
expect(response.status).to eq(200)
expect(response.parsed_body["groups"].map { |group| group['name'] })
.to contain_exactly(messageable_group.name, Group.find(Group::AUTO_GROUPS[:moderators]).name)
end
it 'searches for mentionable groups' do
get "/u/search/users.json", params: {
include_messageable_groups: 'false',
include_mentionable_groups: 'true',
term: 'a'
}
expect(response.status).to eq(200)
groups = response.parsed_body["groups"]
expect(groups.map { |group| group['name'] })
.to contain_exactly(mentionable_group.name, mentionable_group_2.name)
end
end
describe 'when not signed in' do
it 'should not include mentionable/messageable groups' do
get "/u/search/users.json", params: {
include_mentionable_groups: 'false',
include_messageable_groups: 'false',
term: 'a'
}
expect(response.status).to eq(200)
expect(response.parsed_body).not_to have_key(:groups)
get "/u/search/users.json", params: {
include_mentionable_groups: 'false',
include_messageable_groups: 'true',
term: 'a'
}
expect(response.status).to eq(200)
expect(response.parsed_body).not_to have_key(:groups)
get "/u/search/users.json", params: {
include_messageable_groups: 'false',
include_mentionable_groups: 'true',
term: 'a'
}
expect(response.status).to eq(200)
expect(response.parsed_body).not_to have_key(:groups)
end
end
describe 'when searching by group name' do
fab!(:exclusive_group) { Fabricate(:group) }
it 'return results if the user is a group member' do
exclusive_group.add(user)
get "/u/search/users.json", params: {
group: exclusive_group.name,
term: user.username
}
expect(users_found).to contain_exactly(user.username)
end
it 'does not return results if the user is not a group member' do
get "/u/search/users.json", params: {
group: exclusive_group.name,
term: user.username
}
expect(users_found).to be_empty
end
it 'returns results if the user is member of one of the groups' do
exclusive_group.add(user)
get "/u/search/users.json", params: {
groups: [exclusive_group.name],
term: user.username
}
expect(users_found).to contain_exactly(user.username)
end
it 'does not return results if the user is not a member of the groups' do
get "/u/search/users.json", params: {
groups: [exclusive_group.name],
term: user.username
}
expect(users_found).to be_empty
end
def users_found
response.parsed_body['users'].map { |u| u['username'] }
end
end
end
context 'with `include_staged_users`' do
it "includes staged users when the param is true" do
get "/u/search/users.json", params: { term: staged_user.name, include_staged_users: true }
json = response.parsed_body
expect(json["users"].map { |u| u["name"] }).to include(staged_user.name)
end
it "doesn't include staged users when the param is not passed" do
get "/u/search/users.json", params: { term: staged_user.name }
json = response.parsed_body
expect(json["users"].map { |u| u["name"] }).not_to include(staged_user.name)
end
it "doesn't include staged users when the param explicitly set to false" do
get "/u/search/users.json", params: { term: staged_user.name, include_staged_users: false }
json = response.parsed_body
expect(json["users"].map { |u| u["name"] }).not_to include(staged_user.name)
end
end
context 'with `last_seen_users`' do
it "returns results when the param is true" do
get "/u/search/users.json", params: { last_seen_users: true }
json = response.parsed_body
expect(json["users"]).not_to be_empty
end
it "respects limit parameter at the same time" do
limit = 3
get "/u/search/users.json", params: { last_seen_users: true, limit: limit }
json = response.parsed_body
expect(json["users"]).not_to be_empty
expect(json["users"].size).to eq(limit)
end
end
it "returns avatar_template" do
get "/u/search/users.json", params: { term: user.username }
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["users"][0]).to have_key("avatar_template")
expect(json["users"][0]["avatar_template"]).to eq("/letter_avatar_proxy/v4/letter/j/f475e1/{size}.png")
end
describe "#status" do
it "returns user status if enabled in site settings" do
SiteSetting.enable_user_status = true
emoji = "tooth"
description = "off to dentist"
user.set_status!(description, emoji)
get "/u/search/users.json", params: { term: user.name }
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["users"][0]).to have_key("status")
expect(json["users"][0]["status"]["description"]).to eq(description)
expect(json["users"][0]["status"]["emoji"]).to eq(emoji)
end
it "doesn't return user status if disabled in site settings" do
SiteSetting.enable_user_status = false
user.set_status!("off to dentist", "tooth")
get "/u/search/users.json", params: { term: user.name }
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["users"][0]).not_to have_key("status")
end
end
end
describe '#email_login' do
before do
SiteSetting.enable_local_logins_via_email = true
end
it "enqueues the right email" do
post "/u/email-login.json", params: { login: user1.email }
expect(response.status).to eq(200)
expect(response.parsed_body['user_found']).to eq(true)
job_args = Jobs::CriticalUserEmail.jobs.last["args"].first
expect(job_args["user_id"]).to eq(user1.id)
expect(job_args["type"]).to eq("email_login")
expect(EmailToken.hash_token(job_args["email_token"])).to eq(user1.email_tokens.last.token_hash)
end
describe 'when enable_local_logins_via_email is disabled' do
before do
SiteSetting.enable_local_logins_via_email = false
end
it 'should return the right response' do
post "/u/email-login.json", params: { login: user1.email }
expect(response.status).to eq(404)
end
end
describe 'when username or email is not valid' do
it 'should not enqueue the email to login' do
post "/u/email-login.json", params: { login: '@random' }
expect(response.status).to eq(200)
json = response.parsed_body
expect(json['user_found']).to eq(false)
expect(json['hide_taken']).to eq(false)
expect(Jobs::CriticalUserEmail.jobs).to eq([])
end
end
describe 'when hide_email_address_taken is true' do
it 'should return the right response' do
SiteSetting.hide_email_address_taken = true
post "/u/email-login.json", params: { login: user1.email }
expect(response.status).to eq(200)
json = response.parsed_body
expect(json.has_key?('user_found')).to eq(false)
expect(json['hide_taken']).to eq(true)
end
end
describe "when user is already logged in" do
it 'should redirect to the root path' do
sign_in(user1)
post "/u/email-login.json", params: { login: user1.email }
expect(response).to redirect_to("/")
end
end
end
describe '#create_second_factor_totp' do
context 'when not logged in' do
it 'should return the right response' do
post "/users/second_factors.json", params: {
password: 'wrongpassword'
}
expect(response.status).to eq(403)
end
end
context 'when logged in' do
before do
sign_in(user1)
end
describe 'create 2fa request' do
it 'fails on incorrect password' do
ApplicationController.any_instance.expects(:secure_session).returns("confirmed-password-#{user1.id}" => "false")
post "/users/create_second_factor_totp.json"
expect(response.status).to eq(403)
end
describe 'when local logins are disabled' do
it 'should return the right response' do
SiteSetting.enable_local_logins = false
post "/users/create_second_factor_totp.json"
expect(response.status).to eq(404)
end
end
describe 'when SSO is enabled' do
it 'should return the right response' do
SiteSetting.discourse_connect_url = 'http://someurl.com'
SiteSetting.enable_discourse_connect = true
post "/users/create_second_factor_totp.json"
expect(response.status).to eq(404)
end
end
it 'succeeds on correct password' do
ApplicationController.any_instance.stubs(:secure_session).returns("confirmed-password-#{user1.id}" => "true")
post "/users/create_second_factor_totp.json"
expect(response.status).to eq(200)
response_body = response.parsed_body
expect(response_body['key']).to be_present
expect(response_body['qr']).to be_present
end
end
end
end
describe "#enable_second_factor_totp" do
before do
sign_in(user1)
end
def create_totp
stub_secure_session_confirmed
post "/users/create_second_factor_totp.json"
end
it "creates a totp for the user successfully" do
create_totp
staged_totp_key = read_secure_session["staged-totp-#{user1.id}"]
token = ROTP::TOTP.new(staged_totp_key).now
post "/users/enable_second_factor_totp.json", params: { name: "test", second_factor_token: token }
expect(response.status).to eq(200)
expect(user1.user_second_factors.count).to eq(1)
end
it "rate limits by IP address" do
RateLimiter.enable
RateLimiter.clear_all!
create_totp
staged_totp_key = read_secure_session["staged-totp-#{user1.id}"]
token = ROTP::TOTP.new(staged_totp_key).now
7.times do |x|
post "/users/enable_second_factor_totp.json", params: { name: "test", second_factor_token: token }
end
expect(response.status).to eq(429)
end
it "rate limits by username" do
RateLimiter.enable
RateLimiter.clear_all!
create_totp
staged_totp_key = read_secure_session["staged-totp-#{user1.id}"]
token = ROTP::TOTP.new(staged_totp_key).now
7.times do |x|
post "/users/enable_second_factor_totp.json", params: { name: "test", second_factor_token: token }, env: { "REMOTE_ADDR": "1.2.3.#{x}" }
end
expect(response.status).to eq(429)
end
context "when an incorrect token is provided" do
before do
create_totp
post "/users/enable_second_factor_totp.json", params: { name: "test", second_factor_token: "123456" }
end
it "shows a helpful error message to the user" do
expect(response.parsed_body['error']).to eq(I18n.t("login.invalid_second_factor_code"))
end
end
context "when a name is not provided" do
before do
create_totp
post "/users/enable_second_factor_totp.json", params: { second_factor_token: "123456" }
end
it "shows a helpful error message to the user" do
expect(response.parsed_body['error']).to eq(I18n.t("login.missing_second_factor_name"))
end
end
context "when a token is not provided" do
before do
create_totp
post "/users/enable_second_factor_totp.json", params: { name: "test" }
end
it "shows a helpful error message to the user" do
expect(response.parsed_body['error']).to eq(I18n.t("login.missing_second_factor_code"))
end
end
end
describe '#update_second_factor' do
fab!(:user_second_factor) { Fabricate(:user_second_factor_totp, user: user1) }
context 'when not logged in' do
it 'should return the right response' do
put "/users/second_factor.json"
expect(response.status).to eq(403)
end
end
context 'when logged in' do
before do
sign_in(user1)
end
context 'when user has totp setup' do
context 'when token is missing' do
it 'returns the right response' do
put "/users/second_factor.json", params: {
disable: 'true',
second_factor_target: UserSecondFactor.methods[:totp],
id: user_second_factor.id
}
expect(response.status).to eq(403)
end
end
context 'when token is valid' do
before do
stub_secure_session_confirmed
end
it 'should allow second factor for the user to be renamed' do
put "/users/second_factor.json", params: {
name: 'renamed',
second_factor_target: UserSecondFactor.methods[:totp],
id: user_second_factor.id
}
expect(response.status).to eq(200)
expect(user1.reload.user_second_factors.totps.first.name).to eq("renamed")
end
it 'should allow second factor for the user to be disabled' do
put "/users/second_factor.json", params: {
disable: 'true',
second_factor_target: UserSecondFactor.methods[:totp],
id: user_second_factor.id
}
expect(response.status).to eq(200)
expect(user1.reload.user_second_factors.totps.first).to eq(nil)
end
end
end
context "when user is updating backup codes" do
context 'when token is missing' do
it 'returns the right response' do
put "/users/second_factor.json", params: {
second_factor_target: UserSecondFactor.methods[:backup_codes]
}
expect(response.status).to eq(403)
end
end
context 'when token is valid' do
before do
ApplicationController.any_instance.stubs(:secure_session).returns("confirmed-password-#{user1.id}" => "true")
end
it 'should allow second factor backup for the user to be disabled' do
put "/users/second_factor.json", params: {
second_factor_target: UserSecondFactor.methods[:backup_codes],
disable: 'true'
}
expect(response.status).to eq(200)
expect(user1.reload.user_second_factors.backup_codes).to be_empty
end
end
end
end
end
describe '#create_second_factor_backup' do
fab!(:user_second_factor) { Fabricate(:user_second_factor_totp, user: user1) }
context 'when not logged in' do
it 'should return the right response' do
put "/users/second_factors_backup.json", params: {
second_factor_token: 'wrongtoken',
second_factor_method: UserSecondFactor.methods[:totp]
}
expect(response.status).to eq(403)
end
end
context 'when logged in' do
before do
sign_in(user1)
end
describe 'create 2fa request' do
it 'fails on incorrect password' do
ApplicationController.any_instance.expects(:secure_session).returns("confirmed-password-#{user1.id}" => "false")
put "/users/second_factors_backup.json"
expect(response.status).to eq(403)
end
describe 'when local logins are disabled' do
it 'should return the right response' do
SiteSetting.enable_local_logins = false
put "/users/second_factors_backup.json"
expect(response.status).to eq(404)
end
end
describe 'when SSO is enabled' do
it 'should return the right response' do
SiteSetting.discourse_connect_url = 'http://someurl.com'
SiteSetting.enable_discourse_connect = true
put "/users/second_factors_backup.json"
expect(response.status).to eq(404)
end
end
it 'succeeds on correct password' do
ApplicationController.any_instance.expects(:secure_session).returns("confirmed-password-#{user1.id}" => "true")
put "/users/second_factors_backup.json"
expect(response.status).to eq(200)
response_body = response.parsed_body
# we use SecureRandom.hex(16) for backup codes, ensure this continues to be the case
expect(response_body['backup_codes'].map(&:length)).to eq([32] * 10)
end
end
end
end
describe "#create_second_factor_security_key" do
it "stores the challenge in the session and returns challenge data, user id, and supported algorithms" do
create_second_factor_security_key
secure_session = read_secure_session
response_parsed = response.parsed_body
expect(response_parsed["challenge"]).to eq(
Webauthn.challenge(user1, secure_session)
)
expect(response_parsed["rp_id"]).to eq(
Webauthn.rp_id(user1, secure_session)
)
expect(response_parsed["rp_name"]).to eq(
Webauthn.rp_name(user1, secure_session)
)
expect(response_parsed["user_secure_id"]).to eq(
user1.reload.create_or_fetch_secure_identifier
)
expect(response_parsed["supported_algorithms"]).to eq(
::Webauthn::SUPPORTED_ALGORITHMS
)
end
context "if the user has security key credentials already" do
fab!(:user_security_key) { Fabricate(:user_security_key_with_random_credential, user: user1) }
it "returns those existing active credentials" do
create_second_factor_security_key
response_parsed = response.parsed_body
expect(response_parsed["existing_active_credential_ids"]).to eq(
[user_security_key.credential_id]
)
end
end
end
describe "#register_second_factor_security_key" do
context "when creation parameters are valid" do
it "creates a security key for the user" do
simulate_localhost_webauthn_challenge
create_second_factor_security_key
_response_parsed = response.parsed_body
post "/u/register_second_factor_security_key.json", params: valid_security_key_create_post_data
expect(user1.security_keys.count).to eq(1)
expect(user1.security_keys.last.credential_id).to eq(valid_security_key_create_post_data[:rawId])
expect(user1.security_keys.last.name).to eq(valid_security_key_create_post_data[:name])
end
end
context "when the creation parameters are invalid" do
it "shows a security key error and does not create a key" do
stub_as_dev_localhost
create_second_factor_security_key
_response_parsed = response.parsed_body
post "/u/register_second_factor_security_key.json", params: {
id: "bad id",
rawId: "bad rawId",
type: "public-key",
attestation: "bad attestation",
clientData: Base64.encode64('{"bad": "json"}'),
name: "My Bad Key"
}
expect(user1.security_keys.count).to eq(0)
expect(response.parsed_body["error"]).to eq(I18n.t("webauthn.validation.invalid_type_error"))
end
end
end
describe '#disable_second_factor' do
context 'when logged in with secure session' do
before do
sign_in(user1)
stub_secure_session_confirmed
end
context 'when user has a registered totp and security key' do
before do
_totp_second_factor = Fabricate(:user_second_factor_totp, user: user1)
_security_key_second_factor = Fabricate(:user_security_key, user: user1, factor_type: UserSecurityKey.factor_types[:second_factor])
end
it 'should disable all totp and security keys' do
expect_enqueued_with(job: :critical_user_email, args: { type: :account_second_factor_disabled, user_id: user1.id }) do
put "/u/disable_second_factor.json"
expect(response.status).to eq(200)
expect(user1.reload.user_second_factors).to be_empty
expect(user1.security_keys).to be_empty
end
end
end
end
end
describe '#revoke_account' do
it 'errors for unauthorised users' do
post "/u/#{user1.username}/preferences/revoke-account.json", params: {
provider_name: 'facebook'
}
expect(response.status).to eq(403)
sign_in(another_user)
post "/u/#{user1.username}/preferences/revoke-account.json", params: {
provider_name: 'facebook'
}
expect(response.status).to eq(403)
end
context 'while logged in' do
before do
sign_in(user1)
end
it 'returns an error when there is no matching account' do
post "/u/#{user1.username}/preferences/revoke-account.json", params: {
provider_name: 'facebook'
}
expect(response.status).to eq(404)
end
context "with fake provider" do
let(:authenticator) do
Class.new(Auth::Authenticator) do
attr_accessor :can_revoke
def name
"testprovider"
end
def enabled?
true
end
def description_for_user(user)
"an account"
end
def can_revoke?
can_revoke
end
def revoke(user, skip_remote: false)
true
end
end.new
end
before do
DiscoursePluginRegistry.register_auth_provider(Auth::AuthProvider.new(authenticator: authenticator))
end
after do
DiscoursePluginRegistry.reset!
end
it 'returns an error when revoking is not allowed' do
authenticator.can_revoke = false
post "/u/#{user1.username}/preferences/revoke-account.json", params: {
provider_name: 'testprovider'
}
expect(response.status).to eq(404)
authenticator.can_revoke = true
post "/u/#{user1.username}/preferences/revoke-account.json", params: {
provider_name: 'testprovider'
}
expect(response.status).to eq(200)
end
it 'works' do
authenticator.can_revoke = true
post "/u/#{user1.username}/preferences/revoke-account.json", params: {
provider_name: 'testprovider'
}
expect(response.status).to eq(200)
end
end
end
end
describe '#revoke_auth_token' do
context 'while logged in' do
before do
2.times { sign_in(user1) }
end
it 'logs user out' do
ids = user1.user_auth_tokens.order(:created_at).pluck(:id)
post "/u/#{user1.username}/preferences/revoke-auth-token.json",
params: { token_id: ids[0] }
expect(response.status).to eq(200)
user1.user_auth_tokens.reload
expect(user1.user_auth_tokens.count).to eq(1)
expect(user1.user_auth_tokens.first.id).to eq(ids[1])
end
it 'checks if token exists' do
ids = user1.user_auth_tokens.order(:created_at).pluck(:id)
post "/u/#{user1.username}/preferences/revoke-auth-token.json",
params: { token_id: ids[0] }
expect(response.status).to eq(200)
post "/u/#{user1.username}/preferences/revoke-auth-token.json",
params: { token_id: ids[0] }
expect(response.status).to eq(400)
end
it 'does not let user log out of current session' do
token = UserAuthToken.generate!(user_id: user1.id)
cookie = create_auth_cookie(
token: token.unhashed_auth_token,
user_id: user1.id,
trust_level: user1.trust_level,
issued_at: 5.minutes.ago,
)
post "/u/#{user1.username}/preferences/revoke-auth-token.json",
params: { token_id: token.id },
headers: { "HTTP_COOKIE" => "_t=#{cookie}" }
expect(token.reload.id).to be_present
expect(response.status).to eq(400)
end
it 'logs user out from everywhere if token_id is not present' do
post "/u/#{user1.username}/preferences/revoke-auth-token.json"
expect(response.status).to eq(200)
expect(user1.user_auth_tokens.count).to eq(0)
end
end
end
describe '#list_second_factors' do
let(:user) { user1 }
before do
sign_in(user)
end
context 'when SSO is enabled' do
before do
SiteSetting.discourse_connect_url = 'https://discourse.test/sso'
SiteSetting.enable_discourse_connect = true
end
it 'does not allow access' do
post "/u/second_factors.json"
expect(response.status).to eq(404)
end
end
context 'when local logins are not enabled' do
before do
SiteSetting.enable_local_logins = false
end
it 'does not allow access' do
post "/u/second_factors.json"
expect(response.status).to eq(404)
end
end
context 'when the site settings allow second factors' do
before do
SiteSetting.enable_local_logins = true
SiteSetting.enable_discourse_connect = false
end
context 'when the password parameter is not provided' do
let(:password) { '' }
before do
post "/u/second_factors.json", params: { password: password }
end
it 'returns password required response' do
expect(response.status).to eq(200)
response_body = response.parsed_body
expect(response_body['password_required']).to eq(true)
end
end
context 'when the password is provided' do
fab!(:user) { Fabricate(:user, password: '8555039dd212cc66ec68') }
context 'when the password is correct' do
let(:password) { '8555039dd212cc66ec68' }
it 'returns a list of enabled totps and security_key second factors' do
totp_second_factor = Fabricate(:user_second_factor_totp, user: user)
security_key_second_factor = Fabricate(:user_security_key, user: user, factor_type: UserSecurityKey.factor_types[:second_factor])
post "/u/second_factors.json", params: { password: password }
expect(response.status).to eq(200)
response_body = response.parsed_body
expect(response_body['totps'].map { |second_factor| second_factor['id'] }).to include(totp_second_factor.id)
expect(response_body['security_keys'].map { |second_factor| second_factor['id'] }).to include(security_key_second_factor.id)
end
end
context 'when the password is not correct' do
let(:password) { 'wrongpassword' }
it 'returns the incorrect password response' do
post "/u/second_factors.json", params: { password: password }
response_body = response.parsed_body
expect(response_body['error']).to eq(
I18n.t("login.incorrect_password")
)
end
end
end
end
end
describe '#feature_topic' do
fab!(:topic) { Fabricate(:topic) }
fab!(:other_topic) { Fabricate(:topic) }
fab!(:private_message) { Fabricate(:private_message_topic, user: another_user) }
fab!(:category) { Fabricate(:category_with_definition) }
describe "site setting enabled" do
before do
SiteSetting.allow_featured_topic_on_user_profiles = true
end
it 'requires the user to be logged in' do
put "/u/#{user1.username}/feature-topic.json", params: { topic_id: topic.id }
expect(response.status).to eq(403)
end
it 'returns an error if the user tries to set for another user' do
sign_in(user1)
topic.update(user_id: another_user.id)
put "/u/#{another_user.username}/feature-topic.json", params: { topic_id: topic.id }
expect(response.status).to eq(403)
end
it 'returns an error if the topic is a PM' do
sign_in(another_user)
put "/u/#{another_user.username}/feature-topic.json", params: { topic_id: private_message.id }
expect(response.status).to eq(403)
end
it "returns an error if the topic is not visible" do
sign_in(user1)
topic.update_status('visible', false, user1)
put "/u/#{user1.username}/feature-topic.json", params: { topic_id: topic.id }
expect(response.status).to eq(403)
end
it "returns an error if the topic's category is read_restricted" do
sign_in(user1)
category.set_permissions({})
topic.update(category_id: category.id)
put "/u/#{another_user.username}/feature-topic.json", params: { topic_id: topic.id }
expect(response.status).to eq(403)
end
it 'sets featured_topic correctly for user created topic' do
sign_in(user1)
topic.update(user_id: user1.id)
put "/u/#{user1.username}/feature-topic.json", params: { topic_id: topic.id }
expect(response.status).to eq(200)
expect(user1.user_profile.featured_topic).to eq topic
end
it 'sets featured_topic correctly for non-user-created topic' do
sign_in(user1)
put "/u/#{user1.username}/feature-topic.json", params: { topic_id: other_topic.id }
expect(response.status).to eq(200)
expect(user1.user_profile.featured_topic).to eq other_topic
end
describe "site setting disabled" do
before do
SiteSetting.allow_featured_topic_on_user_profiles = false
end
it "does not allow setting featured_topic for user_profiles" do
sign_in(user1)
topic.update(user_id: user1.id)
put "/u/#{user1.username}/feature-topic.json", params: { topic_id: topic.id }
expect(response.status).to eq(403)
end
end
end
end
describe '#clear_featured_topic' do
fab!(:topic) { Fabricate(:topic) }
it 'requires the user to be logged in' do
put "/u/#{user1.username}/clear-featured-topic.json"
expect(response.status).to eq(403)
end
it 'returns an error if the the current user does not have access' do
sign_in(user1)
topic.update(user_id: another_user.id)
put "/u/#{another_user.username}/clear-featured-topic.json"
expect(response.status).to eq(403)
end
it 'clears the user_profiles featured_topic correctly' do
sign_in(user1)
topic.update(user: user1)
put "/u/#{user1.username}/clear-featured-topic.json"
expect(response.status).to eq(200)
expect(user1.user_profile.featured_topic).to eq nil
end
end
describe "#bookmarks" do
before do
register_test_bookmarkable
TopicUser.change(user1.id, bookmark1.bookmarkable.topic_id, total_msecs_viewed: 1)
TopicUser.change(user1.id, bookmark2.bookmarkable_id, total_msecs_viewed: 1)
Fabricate(:post, topic: bookmark2.bookmarkable)
bookmark3 && bookmark4
end
after do
Bookmark.registered_bookmarkables = []
end
let(:bookmark1) { Fabricate(:bookmark, user: user1, bookmarkable: Fabricate(:post)) }
let(:bookmark2) { Fabricate(:bookmark, user: user1, bookmarkable: Fabricate(:topic)) }
let(:bookmark3) { Fabricate(:bookmark, user: user1, bookmarkable: Fabricate(:user)) }
let(:bookmark4) { Fabricate(:bookmark) }
it "returns a list of serialized bookmarks for the user" do
sign_in(user1)
get "/u/#{user1.username}/bookmarks.json"
expect(response.status).to eq(200)
expect(response.parsed_body['user_bookmark_list']['bookmarks'].map { |b| b['id'] }).to match_array(
[bookmark1.id, bookmark2.id, bookmark3.id]
)
end
it "returns a list of serialized bookmarks for the user including custom registered bookmarkables" do
sign_in(user1)
bookmark3.bookmarkable.user_profile.update!(bio_raw: "<p>Something cooked</p>")
bookmark3.bookmarkable.user_profile.rebake!
get "/u/#{user1.username}/bookmarks.json"
expect(response.status).to eq(200)
response_bookmarks = response.parsed_body['user_bookmark_list']['bookmarks']
expect(response_bookmarks.map { |b| b['id'] }).to match_array(
[bookmark1.id, bookmark2.id, bookmark3.id]
)
expect(response_bookmarks.find { |b| b['id'] == bookmark3.id }['excerpt']).to eq('Something cooked')
end
it "returns an .ics file of bookmark reminders for the user in date order" do
bookmark1.update!(name: nil, reminder_at: 1.day.from_now)
bookmark2.update!(name: "Some bookmark note", reminder_at: 1.week.from_now)
bookmark3.update!(name: nil, reminder_at: 2.weeks.from_now)
sign_in(user1)
get "/u/#{user1.username}/bookmarks.ics"
expect(response.status).to eq(200)
expect(response.body).to eq(<<~ICS)
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Discourse//#{Discourse.current_hostname}//#{Discourse.full_version}//EN
BEGIN:VEVENT
UID:bookmark_reminder_##{bookmark1.id}@#{Discourse.current_hostname}
DTSTAMP:#{bookmark1.updated_at.strftime(I18n.t("datetime_formats.formats.calendar_ics"))}
DTSTART:#{bookmark1.reminder_at_ics}
DTEND:#{bookmark1.reminder_at_ics(offset: 1.hour)}
SUMMARY:#{bookmark1.bookmarkable.topic.title}
DESCRIPTION:#{bookmark1.bookmarkable.full_url}
URL:#{bookmark1.bookmarkable.full_url}
END:VEVENT
BEGIN:VEVENT
UID:bookmark_reminder_##{bookmark2.id}@#{Discourse.current_hostname}
DTSTAMP:#{bookmark2.updated_at.strftime(I18n.t("datetime_formats.formats.calendar_ics"))}
DTSTART:#{bookmark2.reminder_at_ics}
DTEND:#{bookmark2.reminder_at_ics(offset: 1.hour)}
SUMMARY:Some bookmark note
DESCRIPTION:#{bookmark2.bookmarkable.url}
URL:#{bookmark2.bookmarkable.url}
END:VEVENT
BEGIN:VEVENT
UID:bookmark_reminder_##{bookmark3.id}@#{Discourse.current_hostname}
DTSTAMP:#{bookmark3.updated_at.strftime(I18n.t("datetime_formats.formats.calendar_ics"))}
DTSTART:#{bookmark3.reminder_at_ics}
DTEND:#{bookmark3.reminder_at_ics(offset: 1.hour)}
SUMMARY:#{bookmark3.bookmarkable.username}
DESCRIPTION:#{Discourse.base_url}/u/#{bookmark3.bookmarkable.username}
URL:#{Discourse.base_url}/u/#{bookmark3.bookmarkable.username}
END:VEVENT
END:VCALENDAR
ICS
end
it "does not show another user's bookmarks" do
sign_in(Fabricate(:user))
get "/u/#{bookmark3.user.username}/bookmarks.json"
expect(response.status).to eq(403)
end
it "shows a helpful message if no bookmarks are found" do
bookmark1.destroy
bookmark2.destroy
bookmark3.destroy
sign_in(user1)
get "/u/#{user1.username}/bookmarks.json"
expect(response.status).to eq(200)
expect(response.parsed_body['bookmarks']).to eq([])
end
it "shows a helpful message if no bookmarks are found for the search" do
sign_in(user1)
get "/u/#{user1.username}/bookmarks.json", params: {
q: 'badsearch'
}
expect(response.status).to eq(200)
expect(response.parsed_body['bookmarks']).to eq([])
end
end
describe "#bookmarks excerpts" do
fab!(:user) { Fabricate(:user) }
let!(:topic) { Fabricate(:topic, user: user) }
let!(:post) { Fabricate(:post, topic: topic) }
let!(:bookmark) { Fabricate(:bookmark, name: 'Test', user: user, bookmarkable: topic) }
it "uses the first post of the topic for the bookmarks excerpt" do
TopicUser.change(user.id, bookmark.bookmarkable.id, { last_read_post_number: post.post_number })
sign_in(user)
get "/u/#{user.username}/bookmarks.json"
expect(response.status).to eq(200)
bookmark_list = response.parsed_body["user_bookmark_list"]["bookmarks"]
expected_excerpt = PrettyText.excerpt(topic.first_post.cooked, 300, keep_emoji_images: true)
expect(bookmark_list.first["excerpt"]).to eq(expected_excerpt)
end
describe "bookmarkable_url" do
context "with the link_to_first_unread_post option" do
it "is a full topic URL to the first unread post in the topic when the option is set" do
TopicUser.change(user.id, bookmark.bookmarkable.id, { last_read_post_number: post.post_number })
sign_in(user)
get "/u/#{user.username}/user-menu-bookmarks.json"
expect(response.status).to eq(200)
bookmark_list = response.parsed_body["bookmarks"]
expect(bookmark_list.first["bookmarkable_url"]).to end_with("/t/#{topic.slug}/#{topic.id}/#{post.post_number + 1}")
end
it "is a full topic URL to the first post in the topic when the option isn't set" do
TopicUser.change(user.id, bookmark.bookmarkable.id, { last_read_post_number: post.post_number })
sign_in(user)
get "/u/#{user.username}/bookmarks.json"
expect(response.status).to eq(200)
bookmark_list = response.parsed_body["user_bookmark_list"]["bookmarks"]
expect(bookmark_list.first["bookmarkable_url"]).to end_with("/t/#{topic.slug}/#{topic.id}")
end
end
end
end
describe "#private_message_topic_tracking_state" do
fab!(:user_2) { Fabricate(:user) }
fab!(:private_message) do
create_post(
user: user1,
target_usernames: [user_2.username],
archetype: Archetype.private_message
).topic
end
before do
sign_in(user_2)
end
it 'does not allow an unauthorized user to access the state of another user' do
get "/u/#{user1.username}/private-message-topic-tracking-state.json"
expect(response.status).to eq(403)
end
it 'returns the right response' do
get "/u/#{user_2.username}/private-message-topic-tracking-state.json"
expect(response.status).to eq(200)
topic_state = response.parsed_body.first
expect(topic_state["topic_id"]).to eq(private_message.id)
expect(topic_state["highest_post_number"]).to eq(1)
expect(topic_state["last_read_post_number"]).to eq(nil)
expect(topic_state["notification_level"]).to eq(NotificationLevels.all[:watching])
expect(topic_state["group_ids"]).to eq([])
end
end
describe "#reset_recent_searches" do
it 'does nothing for anon' do
delete "/u/recent-searches.json"
expect(response.status).to eq(403)
end
it 'works for logged in user' do
freeze_time
sign_in(user1)
delete "/u/recent-searches.json"
expect(response.status).to eq(200)
user1.reload
expect(user1.user_option.oldest_search_log_date).to be_within(5.seconds).of(1.second.ago)
end
end
describe "#recent_searches" do
it 'does nothing for anon' do
get "/u/recent-searches.json"
expect(response.status).to eq(403)
end
it 'works for logged in user' do
freeze_time
sign_in(user1)
SiteSetting.log_search_queries = true
user1.user_option.update!(oldest_search_log_date: nil)
get "/u/recent-searches.json"
expect(response.status).to eq(200)
expect(response.parsed_body["recent_searches"]).to eq([])
SearchLog.create!(
term: "old one",
user_id: user1.id,
search_type: 1,
ip_address: '192.168.0.1',
created_at: 5.minutes.ago
)
SearchLog.create!(
term: "also old",
user_id: user1.id,
search_type: 1,
ip_address: '192.168.0.1',
created_at: 15.minutes.ago
)
get "/u/recent-searches.json"
expect(response.status).to eq(200)
expect(response.parsed_body["recent_searches"]).to eq(["old one", "also old"])
user1.user_option.update!(oldest_search_log_date: 20.minutes.ago)
get "/u/recent-searches.json"
expect(response.status).to eq(200)
expect(response.parsed_body["recent_searches"]).to eq(["old one", "also old"])
user1.user_option.update!(oldest_search_log_date: 10.seconds.ago)
get "/u/recent-searches.json"
expect(response.status).to eq(200)
expect(response.parsed_body["recent_searches"]).to eq([])
SearchLog.create!(
term: "new search",
user_id: user1.id,
search_type: 1,
ip_address: '192.168.0.1',
created_at: 2.seconds.ago
)
get "/u/recent-searches.json"
expect(response.status).to eq(200)
expect(response.parsed_body["recent_searches"]).to eq(["new search"])
end
it 'shows an error message when log_search_queries are off' do
sign_in(user1)
SiteSetting.log_search_queries = false
get "/u/recent-searches.json"
expect(response.status).to eq(403)
expect(response.parsed_body["error"]).to eq(I18n.t("user_activity.no_log_search_queries"))
end
end
describe "#user_menu_bookmarks" do
fab!(:post) { Fabricate(:post) }
fab!(:topic) { Fabricate(:post).topic }
fab!(:bookmark_with_reminder) { Fabricate(:bookmark, user: user, bookmarkable: post) }
fab!(:bookmark_without_reminder) { Fabricate(:bookmark, user: user, bookmarkable: topic) }
before do
TopicUser.change(user.id, post.topic.id, total_msecs_viewed: 1)
TopicUser.change(user.id, topic.id, total_msecs_viewed: 1)
BookmarkReminderNotificationHandler
.new(bookmark_with_reminder)
.send_notification
end
context "when logged out" do
it "responds with 404" do
get "/u/#{user.username}/user-menu-bookmarks"
expect(response.status).to eq(404)
end
end
context "when logged in" do
before do
sign_in(user)
end
it "responds with 403 when requesting bookmarks list of another user" do
get "/u/#{user1.username}/user-menu-bookmarks"
expect(response.status).to eq(403)
end
it "sends an array of unread bookmark_reminder notifications" do
bookmark_with_reminder2 = Fabricate(:bookmark, user: user, bookmarkable: Fabricate(:post))
TopicUser.change(user.id, bookmark_with_reminder2.bookmarkable.topic, total_msecs_viewed: 1)
BookmarkReminderNotificationHandler
.new(bookmark_with_reminder2)
.send_notification
user
.notifications
.where(notification_type: Notification.types[:bookmark_reminder])
.where("data::json ->> 'bookmark_id' = ?", bookmark_with_reminder2.id.to_s)
.first
.update!(read: true)
get "/u/#{user.username}/user-menu-bookmarks"
expect(response.status).to eq(200)
notifications = response.parsed_body["notifications"]
expect(notifications.size).to eq(1)
expect(notifications.first["data"]["bookmark_id"]).to eq(bookmark_with_reminder.id)
end
it "responds with an array of bookmarks that are not associated with any of the unread bookmark_reminder notifications" do
get "/u/#{user.username}/user-menu-bookmarks"
expect(response.status).to eq(200)
bookmarks = response.parsed_body["bookmarks"]
expect(bookmarks.size).to eq(1)
expect(bookmarks.first["id"]).to eq(bookmark_without_reminder.id)
bookmark_reminder = user
.notifications
.where(notification_type: Notification.types[:bookmark_reminder])
.where("data::json ->> 'bookmark_id' = ?", bookmark_with_reminder.id.to_s)
.first
bookmark_reminder.update!(read: true)
get "/u/#{user.username}/user-menu-bookmarks"
expect(response.status).to eq(200)
bookmarks = response.parsed_body["bookmarks"]
expect(bookmarks.map { |bookmark| bookmark["id"] }).to contain_exactly(
bookmark_with_reminder.id,
bookmark_without_reminder.id
)
data = bookmark_reminder.data_hash
data.delete(:bookmark_id)
bookmark_reminder.update!(data: data.to_json, read: false)
get "/u/#{user.username}/user-menu-bookmarks"
expect(response.status).to eq(200)
notifications = response.parsed_body["notifications"]
expect(notifications.size).to eq(1)
expect(notifications.first["data"]["bookmark_id"]).to be_nil
bookmarks = response.parsed_body["bookmarks"]
expect(bookmarks.map { |bookmark| bookmark["id"] }).to contain_exactly(
bookmark_with_reminder.id,
bookmark_without_reminder.id
)
end
it "fills up the remaining of the USER_MENU_LIST_LIMIT limit with bookmarks" do
bookmark2 = Fabricate(
:bookmark,
user: user,
bookmarkable: Fabricate(:post, topic: topic)
)
stub_const(UsersController, "USER_MENU_LIST_LIMIT", 2) do
get "/u/#{user.username}/user-menu-bookmarks"
end
expect(response.status).to eq(200)
notifications = response.parsed_body["notifications"]
expect(notifications.size).to eq(1)
bookmarks = response.parsed_body["bookmarks"]
expect(bookmarks.size).to eq(1)
stub_const(UsersController, "USER_MENU_LIST_LIMIT", 3) do
get "/u/#{user.username}/user-menu-bookmarks"
end
expect(response.status).to eq(200)
notifications = response.parsed_body["notifications"]
expect(notifications.size).to eq(1)
bookmarks = response.parsed_body["bookmarks"]
expect(bookmarks.size).to eq(2)
BookmarkReminderNotificationHandler.new(bookmark2).send_notification
stub_const(UsersController, "USER_MENU_LIST_LIMIT", 3) do
get "/u/#{user.username}/user-menu-bookmarks"
end
expect(response.status).to eq(200)
notifications = response.parsed_body["notifications"]
expect(notifications.size).to eq(2)
bookmarks = response.parsed_body["bookmarks"]
expect(bookmarks.size).to eq(1)
end
end
end
describe "#user_menu_messages" do
fab!(:message_without_notification) { Fabricate(:private_message_post, recipient: user).topic }
fab!(:message_with_read_notification) { Fabricate(:private_message_post, recipient: user).topic }
fab!(:message_with_unread_notification) { Fabricate(:private_message_post, recipient: user).topic }
fab!(:user1_message_without_notification) do
Fabricate(:private_message_post, recipient: user1).topic
end
fab!(:user1_message_with_read_notification) do
Fabricate(:private_message_post, recipient: user1).topic
end
fab!(:user1_message_with_unread_notification) do
Fabricate(:private_message_post, recipient: user1).topic
end
fab!(:unread_notification) do
Fabricate(
:private_message_notification,
read: false,
user: user,
topic: message_with_unread_notification
)
end
fab!(:read_notification) do
Fabricate(
:private_message_notification,
read: true,
user: user,
topic: message_with_read_notification
)
end
fab!(:user1_unread_notification) do
Fabricate(
:private_message_notification,
read: false,
user: user1,
topic: user1_message_with_unread_notification
)
end
fab!(:user1_read_notification) do
Fabricate(
:private_message_notification,
read: true,
user: user1,
topic: user1_message_with_read_notification
)
end
context "when logged out" do
it "responds with 404" do
get "/u/#{user.username}/user-menu-private-messages"
expect(response.status).to eq(404)
end
end
context "when logged in" do
before do
sign_in(user)
end
it "responds with 403 when requesting messages list of another user" do
get "/u/#{user1.username}/user-menu-private-messages"
expect(response.status).to eq(403)
end
it "responds with 403 if private messages are disabled and the user isn't staff" do
SiteSetting.enable_personal_messages = false
get "/u/#{user.username}/user-menu-private-messages"
expect(response.status).to eq(403)
end
it "doesn't respond with 403 if private messages are disabled and the user is staff" do
SiteSetting.enable_personal_messages = false
user.update!(moderator: true)
get "/u/#{user.username}/user-menu-private-messages"
expect(response.status).to eq(200)
end
it "sends an array of unread private_message notifications" do
get "/u/#{user.username}/user-menu-private-messages"
expect(response.status).to eq(200)
notifications = response.parsed_body["notifications"]
expect(notifications.map { |notification| notification["id"] }).to contain_exactly(
unread_notification.id
)
end
it "responds with an array of PM topics that are not associated with any of the unread private_message notifications" do
get "/u/#{user.username}/user-menu-private-messages"
expect(response.status).to eq(200)
topics = response.parsed_body["topics"]
expect(topics.map { |topic| topic["id"] }).to contain_exactly(
message_without_notification.id,
message_with_read_notification.id
)
end
it "fills up the remaining of the USER_MENU_LIST_LIMIT limit with PM topics" do
stub_const(UsersController, "USER_MENU_LIST_LIMIT", 2) do
get "/u/#{user.username}/user-menu-private-messages"
end
expect(response.status).to eq(200)
notifications = response.parsed_body["notifications"]
expect(notifications.size).to eq(1)
topics = response.parsed_body["topics"]
expect(topics.size).to eq(1)
message2 = Fabricate(:private_message_post, recipient: user).topic
Fabricate(
:private_message_notification,
read: false,
user: user,
topic: message2
)
stub_const(UsersController, "USER_MENU_LIST_LIMIT", 2) do
get "/u/#{user.username}/user-menu-private-messages"
end
expect(response.status).to eq(200)
notifications = response.parsed_body["notifications"]
expect(notifications.size).to eq(2)
topics = response.parsed_body["topics"]
expect(topics.size).to eq(0)
end
end
end
def create_second_factor_security_key
sign_in(user1)
stub_secure_session_confirmed
post "/u/create_second_factor_security_key.json"
end
def stub_secure_session_confirmed
UsersController.any_instance.stubs(:secure_session_confirmed?).returns(true)
end
end