512 lines
17 KiB
Ruby
512 lines
17 KiB
Ruby
require 'spec_helper'
|
|
|
|
describe SessionController do
|
|
|
|
describe 'become' do
|
|
let!(:user) { Fabricate(:user) }
|
|
|
|
it "does not work when not in development mode" do
|
|
Rails.env.stubs(:development?).returns(false)
|
|
get :become, session_id: user.username
|
|
response.should_not be_redirect
|
|
session[:current_user_id].should be_blank
|
|
end
|
|
|
|
it "works in developmenet mode" do
|
|
Rails.env.stubs(:development?).returns(true)
|
|
get :become, session_id: user.username
|
|
response.should be_redirect
|
|
session[:current_user_id].should == user.id
|
|
end
|
|
end
|
|
|
|
describe '.sso_login' do
|
|
|
|
before do
|
|
@sso_url = "http://somesite.com/discourse_sso"
|
|
@sso_secret = "shjkfdhsfkjh"
|
|
|
|
SiteSetting.enable_sso = true
|
|
SiteSetting.sso_url = @sso_url
|
|
SiteSetting.sso_secret = @sso_secret
|
|
|
|
# We have 2 options, either fabricate an admin or don't
|
|
# send welcome messages
|
|
Fabricate(:admin)
|
|
# skip for now
|
|
# SiteSetting.stubs("send_welcome_message").returns(false)
|
|
end
|
|
|
|
def get_sso(return_path)
|
|
nonce = SecureRandom.hex
|
|
dso = DiscourseSingleSignOn.new
|
|
dso.nonce = nonce
|
|
dso.register_nonce(return_path)
|
|
|
|
sso = SingleSignOn.new
|
|
sso.nonce = nonce
|
|
sso.sso_secret = @sso_secret
|
|
sso
|
|
end
|
|
|
|
it 'can take over an account' do
|
|
sso = get_sso("/")
|
|
user = Fabricate(:user)
|
|
sso.email = user.email
|
|
sso.external_id = 'abc'
|
|
sso.username = 'sam'
|
|
|
|
get :sso_login, Rack::Utils.parse_query(sso.payload)
|
|
|
|
response.should redirect_to('/')
|
|
logged_on_user = Discourse.current_user_provider.new(request.env).current_user
|
|
logged_on_user.email.should == user.email
|
|
logged_on_user.single_sign_on_record.external_id.should == "abc"
|
|
logged_on_user.single_sign_on_record.external_username.should == 'sam'
|
|
end
|
|
|
|
it 'allows you to create an admin account' do
|
|
sso = get_sso('/a/')
|
|
sso.external_id = '666' # the number of the beast
|
|
sso.email = 'bob@bob.com'
|
|
sso.name = 'Sam Saffron'
|
|
sso.username = 'sam'
|
|
sso.custom_fields["shop_url"] = "http://my_shop.com"
|
|
sso.custom_fields["shop_name"] = "Sam"
|
|
sso.admin = true
|
|
|
|
get :sso_login, Rack::Utils.parse_query(sso.payload)
|
|
|
|
logged_on_user = Discourse.current_user_provider.new(request.env).current_user
|
|
logged_on_user.admin.should == true
|
|
|
|
end
|
|
|
|
it 'allows you to create an account' do
|
|
sso = get_sso('/a/')
|
|
sso.external_id = '666' # the number of the beast
|
|
sso.email = 'bob@bob.com'
|
|
sso.name = 'Sam Saffron'
|
|
sso.username = 'sam'
|
|
sso.custom_fields["shop_url"] = "http://my_shop.com"
|
|
sso.custom_fields["shop_name"] = "Sam"
|
|
|
|
get :sso_login, Rack::Utils.parse_query(sso.payload)
|
|
response.should redirect_to('/a/')
|
|
|
|
logged_on_user = Discourse.current_user_provider.new(request.env).current_user
|
|
|
|
# ensure nothing is transient
|
|
logged_on_user = User.find(logged_on_user.id)
|
|
|
|
logged_on_user.admin.should == false
|
|
logged_on_user.email.should == 'bob@bob.com'
|
|
logged_on_user.name.should == 'Sam Saffron'
|
|
logged_on_user.username.should == 'sam'
|
|
|
|
logged_on_user.single_sign_on_record.external_id.should == "666"
|
|
logged_on_user.single_sign_on_record.external_username.should == 'sam'
|
|
logged_on_user.active.should == true
|
|
logged_on_user.custom_fields["shop_url"].should == "http://my_shop.com"
|
|
logged_on_user.custom_fields["shop_name"].should == "Sam"
|
|
logged_on_user.custom_fields["bla"].should == nil
|
|
end
|
|
|
|
it 'allows login to existing account with valid nonce' do
|
|
sso = get_sso('/hello/world')
|
|
sso.external_id = '997'
|
|
sso.sso_url = "http://somewhere.over.com/sso_login"
|
|
|
|
user = Fabricate(:user)
|
|
user.create_single_sign_on_record(external_id: '997', last_payload: '')
|
|
|
|
get :sso_login, Rack::Utils.parse_query(sso.payload)
|
|
|
|
user.single_sign_on_record.reload
|
|
user.single_sign_on_record.last_payload.should == sso.unsigned_payload
|
|
|
|
response.should redirect_to('/hello/world')
|
|
logged_on_user = Discourse.current_user_provider.new(request.env).current_user
|
|
|
|
user.id.should == logged_on_user.id
|
|
|
|
# nonce is bad now
|
|
get :sso_login, Rack::Utils.parse_query(sso.payload)
|
|
response.code.should == '500'
|
|
end
|
|
|
|
it 'can act as an SSO provider' do
|
|
SiteSetting.enable_sso_provider = true
|
|
SiteSetting.enable_sso = false
|
|
SiteSetting.enable_local_logins = true
|
|
SiteSetting.sso_secret = "topsecret"
|
|
|
|
sso = SingleSignOn.new
|
|
sso.nonce = "mynonce"
|
|
sso.sso_secret = SiteSetting.sso_secret
|
|
sso.return_sso_url = "http://somewhere.over.rainbow/sso"
|
|
|
|
get :sso_provider, Rack::Utils.parse_query(sso.payload)
|
|
|
|
response.should redirect_to("/login")
|
|
|
|
user = Fabricate(:user, password: "frogs", active: true, admin: true)
|
|
EmailToken.update_all(confirmed: true)
|
|
|
|
xhr :post, :create, login: user.username, password: "frogs", format: :json
|
|
|
|
location = response.header["Location"]
|
|
location.should =~ /^http:\/\/somewhere.over.rainbow\/sso/
|
|
|
|
payload = location.split("?")[1]
|
|
|
|
sso2 = SingleSignOn.parse(payload, "topsecret")
|
|
|
|
sso2.email.should == user.email
|
|
sso2.name.should == user.name
|
|
sso2.username.should == user.username
|
|
sso2.external_id.should == user.id.to_s
|
|
sso2.admin.should == true
|
|
sso2.moderator.should == false
|
|
|
|
end
|
|
|
|
describe 'local attribute override from SSO payload' do
|
|
before do
|
|
SiteSetting.stubs("sso_overrides_email").returns(true)
|
|
SiteSetting.stubs("sso_overrides_username").returns(true)
|
|
SiteSetting.stubs("sso_overrides_name").returns(true)
|
|
|
|
@user = Fabricate(:user)
|
|
|
|
@sso = get_sso('/hello/world')
|
|
@sso.external_id = '997'
|
|
|
|
@reversed_username = @user.username.reverse
|
|
@sso.username = @reversed_username
|
|
@sso.email = "#{@reversed_username}@garbage.org"
|
|
@reversed_name = @user.name.reverse
|
|
@sso.name = @reversed_name
|
|
|
|
@suggested_username = UserNameSuggester.suggest(@sso.username || @sso.name || @sso.email)
|
|
@suggested_name = User.suggest_name(@sso.name || @sso.username || @sso.email)
|
|
@user.create_single_sign_on_record(external_id: '997', last_payload: '')
|
|
end
|
|
|
|
it 'stores the external attributes' do
|
|
get :sso_login, Rack::Utils.parse_query(@sso.payload)
|
|
@user.single_sign_on_record.reload
|
|
@user.single_sign_on_record.external_username.should == @sso.username
|
|
@user.single_sign_on_record.external_email.should == @sso.email
|
|
@user.single_sign_on_record.external_name.should == @sso.name
|
|
end
|
|
|
|
it 'overrides attributes' do
|
|
get :sso_login, Rack::Utils.parse_query(@sso.payload)
|
|
|
|
logged_on_user = Discourse.current_user_provider.new(request.env).current_user
|
|
logged_on_user.username.should == @suggested_username
|
|
logged_on_user.email.should == "#{@reversed_username}@garbage.org"
|
|
logged_on_user.name.should == @suggested_name
|
|
end
|
|
|
|
it 'does not change matching attributes for an existing account' do
|
|
@sso.username = @user.username
|
|
@sso.name = @user.name
|
|
@sso.email = @user.email
|
|
|
|
get :sso_login, Rack::Utils.parse_query(@sso.payload)
|
|
|
|
logged_on_user = Discourse.current_user_provider.new(request.env).current_user
|
|
logged_on_user.username.should == @user.username
|
|
logged_on_user.name.should == @user.name
|
|
logged_on_user.email.should == @user.email
|
|
end
|
|
|
|
it 'does not change attributes for unchanged external attributes' do
|
|
@user.single_sign_on_record.external_username = @sso.username
|
|
@user.single_sign_on_record.external_email = @sso.email
|
|
@user.single_sign_on_record.external_name = @sso.name
|
|
@user.single_sign_on_record.save
|
|
|
|
get :sso_login, Rack::Utils.parse_query(@sso.payload)
|
|
logged_on_user = Discourse.current_user_provider.new(request.env).current_user
|
|
logged_on_user.username.should == @user.username
|
|
logged_on_user.email.should == @user.email
|
|
logged_on_user.name.should == @user.name
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '.create' do
|
|
|
|
let(:user) { Fabricate(:user) }
|
|
|
|
context 'when email is confirmed' do
|
|
before do
|
|
token = user.email_tokens.find_by(email: user.email)
|
|
EmailToken.confirm(token.token)
|
|
end
|
|
|
|
it "raises an error when the login isn't present" do
|
|
lambda { xhr :post, :create }.should raise_error(ActionController::ParameterMissing)
|
|
end
|
|
|
|
describe 'invalid password' do
|
|
it "should return an error with an invalid password" do
|
|
xhr :post, :create, login: user.username, password: 'sssss'
|
|
::JSON.parse(response.body)['error'].should be_present
|
|
end
|
|
end
|
|
|
|
describe 'invalid password' do
|
|
it "should return an error with an invalid password if too long" do
|
|
User.any_instance.expects(:confirm_password?).never
|
|
xhr :post, :create, login: user.username, password: ('s' * (User.max_password_length + 1))
|
|
::JSON.parse(response.body)['error'].should be_present
|
|
end
|
|
end
|
|
|
|
describe 'suspended user' do
|
|
it 'should return an error' do
|
|
User.any_instance.stubs(:suspended?).returns(true)
|
|
User.any_instance.stubs(:suspended_till).returns(2.days.from_now)
|
|
xhr :post, :create, login: user.username, password: 'myawesomepassword'
|
|
::JSON.parse(response.body)['error'].should be_present
|
|
end
|
|
end
|
|
|
|
describe 'deactivated user' do
|
|
it 'should return an error' do
|
|
User.any_instance.stubs(:active).returns(false)
|
|
xhr :post, :create, login: user.username, password: 'myawesomepassword'
|
|
expect(JSON.parse(response.body)['error']).to eq(I18n.t('login.not_activated'))
|
|
end
|
|
end
|
|
|
|
describe 'success by username' do
|
|
it 'logs in correctly' do
|
|
xhr :post, :create, login: user.username, password: 'myawesomepassword'
|
|
|
|
user.reload
|
|
|
|
session[:current_user_id].should == user.id
|
|
user.auth_token.should be_present
|
|
cookies[:_t].should == user.auth_token
|
|
end
|
|
end
|
|
|
|
describe 'local logins disabled' do
|
|
it 'fails' do
|
|
SiteSetting.stubs(:enable_local_logins).returns(false)
|
|
xhr :post, :create, login: user.username, password: 'myawesomepassword'
|
|
response.status.to_i.should == 500
|
|
end
|
|
end
|
|
|
|
describe 'strips leading @ symbol' do
|
|
before do
|
|
xhr :post, :create, login: "@" + user.username, password: 'myawesomepassword'
|
|
user.reload
|
|
end
|
|
|
|
it 'sets a session id' do
|
|
session[:current_user_id].should == user.id
|
|
end
|
|
end
|
|
|
|
describe 'also allow login by email' do
|
|
before do
|
|
xhr :post, :create, login: user.email, password: 'myawesomepassword'
|
|
end
|
|
|
|
it 'sets a session id' do
|
|
session[:current_user_id].should == user.id
|
|
end
|
|
end
|
|
|
|
context 'login has leading and trailing space' do
|
|
let(:username) { " #{user.username} " }
|
|
let(:email) { " #{user.email} " }
|
|
|
|
it "strips spaces from the username" do
|
|
xhr :post, :create, login: username, password: 'myawesomepassword'
|
|
::JSON.parse(response.body)['error'].should_not be_present
|
|
end
|
|
|
|
it "strips spaces from the email" do
|
|
xhr :post, :create, login: email, password: 'myawesomepassword'
|
|
::JSON.parse(response.body)['error'].should_not be_present
|
|
end
|
|
end
|
|
|
|
describe "when the site requires approval of users" do
|
|
before do
|
|
SiteSetting.expects(:must_approve_users?).returns(true)
|
|
end
|
|
|
|
context 'with an unapproved user' do
|
|
before do
|
|
xhr :post, :create, login: user.email, password: 'myawesomepassword'
|
|
end
|
|
|
|
it "doesn't log in the user" do
|
|
session[:current_user_id].should be_blank
|
|
end
|
|
|
|
it "shows the 'not approved' error message" do
|
|
expect(JSON.parse(response.body)['error']).to eq(
|
|
I18n.t('login.not_approved')
|
|
)
|
|
end
|
|
end
|
|
|
|
context "with an unapproved user who is an admin" do
|
|
before do
|
|
User.any_instance.stubs(:admin?).returns(true)
|
|
xhr :post, :create, login: user.email, password: 'myawesomepassword'
|
|
end
|
|
|
|
it 'sets a session id' do
|
|
session[:current_user_id].should == user.id
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when admins are restricted by ip address' do
|
|
let(:permitted_ip_address) { '111.234.23.11' }
|
|
|
|
before do
|
|
Fabricate(:screened_ip_address, ip_address: permitted_ip_address, action_type: ScreenedIpAddress.actions[:allow_admin])
|
|
end
|
|
|
|
it 'is successful for admin at the ip address' do
|
|
User.any_instance.stubs(:admin?).returns(true)
|
|
ActionDispatch::Request.any_instance.stubs(:remote_ip).returns(permitted_ip_address)
|
|
xhr :post, :create, login: user.username, password: 'myawesomepassword'
|
|
session[:current_user_id].should == user.id
|
|
end
|
|
|
|
it 'returns an error for admin not at the ip address' do
|
|
User.any_instance.stubs(:admin?).returns(true)
|
|
ActionDispatch::Request.any_instance.stubs(:remote_ip).returns("111.234.23.12")
|
|
xhr :post, :create, login: user.username, password: 'myawesomepassword'
|
|
JSON.parse(response.body)['error'].should be_present
|
|
session[:current_user_id].should_not == user.id
|
|
end
|
|
|
|
it 'is successful for non-admin not at the ip address' do
|
|
User.any_instance.stubs(:admin?).returns(false)
|
|
ActionDispatch::Request.any_instance.stubs(:remote_ip).returns("111.234.23.12")
|
|
xhr :post, :create, login: user.username, password: 'myawesomepassword'
|
|
session[:current_user_id].should == user.id
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when email has not been confirmed' do
|
|
def post_login
|
|
xhr :post, :create, login: user.email, password: 'myawesomepassword'
|
|
end
|
|
|
|
it "doesn't log in the user" do
|
|
post_login
|
|
session[:current_user_id].should be_blank
|
|
end
|
|
|
|
it "shows the 'not activated' error message" do
|
|
post_login
|
|
expect(JSON.parse(response.body)['error']).to eq(
|
|
I18n.t 'login.not_activated'
|
|
)
|
|
end
|
|
|
|
context "and the 'must approve users' site setting is enabled" do
|
|
before { SiteSetting.expects(:must_approve_users?).returns(true) }
|
|
|
|
it "shows the 'not approved' error message" do
|
|
post_login
|
|
expect(JSON.parse(response.body)['error']).to eq(
|
|
I18n.t 'login.not_approved'
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '.destroy' do
|
|
before do
|
|
@user = log_in
|
|
xhr :delete, :destroy, id: @user.username
|
|
end
|
|
|
|
it 'removes the session variable' do
|
|
session[:current_user_id].should be_blank
|
|
end
|
|
|
|
|
|
it 'removes the auth token cookie' do
|
|
cookies[:_t].should be_blank
|
|
end
|
|
end
|
|
|
|
describe '.forgot_password' do
|
|
|
|
it 'raises an error without a username parameter' do
|
|
lambda { xhr :post, :forgot_password }.should raise_error(ActionController::ParameterMissing)
|
|
end
|
|
|
|
context 'for a non existant username' do
|
|
it "doesn't generate a new token for a made up username" do
|
|
lambda { xhr :post, :forgot_password, login: 'made_up'}.should_not change(EmailToken, :count)
|
|
end
|
|
|
|
it "doesn't enqueue an email" do
|
|
Jobs.expects(:enqueue).with(:user_mail, anything).never
|
|
xhr :post, :forgot_password, login: 'made_up'
|
|
end
|
|
end
|
|
|
|
context 'for an existing username' do
|
|
let(:user) { Fabricate(:user) }
|
|
|
|
it "returns a 500 if local logins are disabled" do
|
|
SiteSetting.enable_local_logins = false
|
|
xhr :post, :forgot_password, login: user.username
|
|
response.code.to_i.should == 500
|
|
end
|
|
|
|
it "generates a new token for a made up username" do
|
|
lambda { xhr :post, :forgot_password, login: user.username}.should change(EmailToken, :count)
|
|
end
|
|
|
|
it "enqueues an email" do
|
|
Jobs.expects(:enqueue).with(:user_email, has_entries(type: :forgot_password, user_id: user.id))
|
|
xhr :post, :forgot_password, login: user.username
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
describe '.current' do
|
|
context "when not logged in" do
|
|
it "retuns 404" do
|
|
xhr :get, :current
|
|
response.should_not be_success
|
|
end
|
|
end
|
|
|
|
context "when logged in" do
|
|
let!(:user) { log_in }
|
|
|
|
it "returns the JSON for the user" do
|
|
xhr :get, :current
|
|
response.should be_success
|
|
json = ::JSON.parse(response.body)
|
|
json['current_user'].should be_present
|
|
json['current_user']['id'].should == user.id
|
|
end
|
|
end
|
|
end
|
|
end
|