mirror of
https://github.com/discourse/discourse.git
synced 2025-05-22 07:53:49 +08:00
DEV: New readonly mode. Only applies to non-staff (#16243)
This commit is contained in:

committed by
GitHub

parent
985afe1092
commit
6e53f4d913
@ -256,6 +256,12 @@ export default Controller.extend(ModalFunctionality, {
|
|||||||
// Failed to login
|
// Failed to login
|
||||||
if (e.jqXHR && e.jqXHR.status === 429) {
|
if (e.jqXHR && e.jqXHR.status === 429) {
|
||||||
this.flash(I18n.t("login.rate_limit"), "error");
|
this.flash(I18n.t("login.rate_limit"), "error");
|
||||||
|
} else if (
|
||||||
|
e.jqXHR &&
|
||||||
|
e.jqXHR.status === 503 &&
|
||||||
|
e.jqXHR.responseJSON.error_type === "read_only"
|
||||||
|
) {
|
||||||
|
this.flash(I18n.t("read_only_mode.login_disabled"), "error");
|
||||||
} else if (!areCookiesEnabled()) {
|
} else if (!areCookiesEnabled()) {
|
||||||
this.flash(I18n.t("login.cookies_error"), "error");
|
this.flash(I18n.t("login.cookies_error"), "error");
|
||||||
} else {
|
} else {
|
||||||
|
@ -129,6 +129,7 @@ Site.reopenClass(Singleton, {
|
|||||||
const store = getOwner(this).lookup("service:store");
|
const store = getOwner(this).lookup("service:store");
|
||||||
const siteAttributes = PreloadStore.get("site");
|
const siteAttributes = PreloadStore.get("site");
|
||||||
siteAttributes["isReadOnly"] = PreloadStore.get("isReadOnly");
|
siteAttributes["isReadOnly"] = PreloadStore.get("isReadOnly");
|
||||||
|
siteAttributes["isStaffWritesOnly"] = PreloadStore.get("isStaffWritesOnly");
|
||||||
return store.createRecord("site", siteAttributes);
|
return store.createRecord("site", siteAttributes);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -17,7 +17,17 @@ import showModal from "discourse/lib/show-modal";
|
|||||||
|
|
||||||
function unlessReadOnly(method, message) {
|
function unlessReadOnly(method, message) {
|
||||||
return function () {
|
return function () {
|
||||||
if (this.site.get("isReadOnly")) {
|
if (this.site.isReadOnly) {
|
||||||
|
bootbox.alert(message);
|
||||||
|
} else {
|
||||||
|
this[method]();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function unlessStrictlyReadOnly(method, message) {
|
||||||
|
return function () {
|
||||||
|
if (this.site.isReadOnly && !this.site.isStaffWritesOnly) {
|
||||||
bootbox.alert(message);
|
bootbox.alert(message);
|
||||||
} else {
|
} else {
|
||||||
this[method]();
|
this[method]();
|
||||||
@ -114,7 +124,7 @@ const ApplicationRoute = DiscourseRoute.extend(OpenComposer, {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
showLogin: unlessReadOnly(
|
showLogin: unlessStrictlyReadOnly(
|
||||||
"handleShowLogin",
|
"handleShowLogin",
|
||||||
I18n.t("read_only_mode.login_disabled")
|
I18n.t("read_only_mode.login_disabled")
|
||||||
),
|
),
|
||||||
|
@ -8,7 +8,7 @@ class ApplicationController < ActionController::Base
|
|||||||
include JsonError
|
include JsonError
|
||||||
include GlobalPath
|
include GlobalPath
|
||||||
include Hijack
|
include Hijack
|
||||||
include ReadOnlyHeader
|
include ReadOnlyMixin
|
||||||
include VaryHeader
|
include VaryHeader
|
||||||
|
|
||||||
attr_reader :theme_id
|
attr_reader :theme_id
|
||||||
@ -631,6 +631,7 @@ class ApplicationController < ActionController::Base
|
|||||||
store_preloaded("banner", banner_json)
|
store_preloaded("banner", banner_json)
|
||||||
store_preloaded("customEmoji", custom_emoji)
|
store_preloaded("customEmoji", custom_emoji)
|
||||||
store_preloaded("isReadOnly", @readonly_mode.to_s)
|
store_preloaded("isReadOnly", @readonly_mode.to_s)
|
||||||
|
store_preloaded("isStaffWritesOnly", @staff_writes_only_mode.to_s)
|
||||||
store_preloaded("activatedThemes", activated_themes_json)
|
store_preloaded("activatedThemes", activated_themes_json)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -876,11 +877,6 @@ class ApplicationController < ActionController::Base
|
|||||||
!disqualified_from_2fa_enforcement && enforcing_2fa && !current_user.has_any_second_factor_methods_enabled?
|
!disqualified_from_2fa_enforcement && enforcing_2fa && !current_user.has_any_second_factor_methods_enabled?
|
||||||
end
|
end
|
||||||
|
|
||||||
def block_if_readonly_mode
|
|
||||||
return if request.fullpath.start_with?(path "/admin/backups")
|
|
||||||
raise Discourse::ReadOnly.new if !(request.get? || request.head?) && @readonly_mode
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_not_found_page(opts = {})
|
def build_not_found_page(opts = {})
|
||||||
if SiteSetting.bootstrap_error_pages?
|
if SiteSetting.bootstrap_error_pages?
|
||||||
preload_json
|
preload_json
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "read_only_header"
|
require "read_only_mixin"
|
||||||
|
|
||||||
class ForumsController < ActionController::Base
|
class ForumsController < ActionController::Base
|
||||||
include ReadOnlyHeader
|
include ReadOnlyMixin
|
||||||
|
|
||||||
before_action :check_readonly_mode
|
before_action :check_readonly_mode
|
||||||
after_action :add_readonly_header
|
after_action :add_readonly_header
|
||||||
|
@ -10,6 +10,8 @@ class SessionController < ApplicationController
|
|||||||
|
|
||||||
requires_login only: [:second_factor_auth_show, :second_factor_auth_perform]
|
requires_login only: [:second_factor_auth_show, :second_factor_auth_perform]
|
||||||
|
|
||||||
|
allow_in_staff_writes_only_mode :create
|
||||||
|
|
||||||
ACTIVATE_USER_KEY = "activate_user"
|
ACTIVATE_USER_KEY = "activate_user"
|
||||||
|
|
||||||
def csrf
|
def csrf
|
||||||
@ -116,7 +118,7 @@ class SessionController < ApplicationController
|
|||||||
|
|
||||||
def sso_login
|
def sso_login
|
||||||
raise Discourse::NotFound unless SiteSetting.enable_discourse_connect
|
raise Discourse::NotFound unless SiteSetting.enable_discourse_connect
|
||||||
raise Discourse::ReadOnly if @readonly_mode
|
raise Discourse::ReadOnly if @readonly_mode && !staff_writes_only_mode?
|
||||||
|
|
||||||
params.require(:sso)
|
params.require(:sso)
|
||||||
params.require(:sig)
|
params.require(:sig)
|
||||||
@ -147,6 +149,7 @@ class SessionController < ApplicationController
|
|||||||
invite = validate_invitiation!(sso)
|
invite = validate_invitiation!(sso)
|
||||||
|
|
||||||
if user = sso.lookup_or_create_user(request.remote_ip)
|
if user = sso.lookup_or_create_user(request.remote_ip)
|
||||||
|
raise Discourse::ReadOnly if staff_writes_only_mode? && !user&.staff?
|
||||||
|
|
||||||
if user.suspended?
|
if user.suspended?
|
||||||
render_sso_error(text: failed_to_login(user)[:error], status: 403)
|
render_sso_error(text: failed_to_login(user)[:error], status: 403)
|
||||||
@ -270,6 +273,9 @@ class SessionController < ApplicationController
|
|||||||
return invalid_credentials if params[:password].length > User.max_password_length
|
return invalid_credentials if params[:password].length > User.max_password_length
|
||||||
|
|
||||||
user = User.find_by_username_or_email(normalized_login_param)
|
user = User.find_by_username_or_email(normalized_login_param)
|
||||||
|
|
||||||
|
raise Discourse::ReadOnly if staff_writes_only_mode? && !user&.staff?
|
||||||
|
|
||||||
rate_limit_second_factor!(user)
|
rate_limit_second_factor!(user)
|
||||||
|
|
||||||
if user.present?
|
if user.present?
|
||||||
@ -303,7 +309,11 @@ class SessionController < ApplicationController
|
|||||||
return render(json: @second_factor_failure_payload)
|
return render(json: @second_factor_failure_payload)
|
||||||
end
|
end
|
||||||
|
|
||||||
(user.active && user.email_confirmed?) ? login(user, second_factor_auth_result) : not_activated(user)
|
if user.active && user.email_confirmed?
|
||||||
|
login(user, second_factor_auth_result)
|
||||||
|
else
|
||||||
|
not_activated(user)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def email_login_info
|
def email_login_info
|
||||||
|
@ -14,6 +14,8 @@ class Users::OmniauthCallbacksController < ApplicationController
|
|||||||
# will not have a CSRF token, however the payload is all validated so its safe
|
# will not have a CSRF token, however the payload is all validated so its safe
|
||||||
skip_before_action :verify_authenticity_token, only: :complete
|
skip_before_action :verify_authenticity_token, only: :complete
|
||||||
|
|
||||||
|
allow_in_staff_writes_only_mode :complete
|
||||||
|
|
||||||
def confirm_request
|
def confirm_request
|
||||||
self.class.find_authenticator(params[:provider])
|
self.class.find_authenticator(params[:provider])
|
||||||
render locals: { hide_auth_buttons: true }
|
render locals: { hide_auth_buttons: true }
|
||||||
@ -22,7 +24,7 @@ class Users::OmniauthCallbacksController < ApplicationController
|
|||||||
def complete
|
def complete
|
||||||
auth = request.env["omniauth.auth"]
|
auth = request.env["omniauth.auth"]
|
||||||
raise Discourse::NotFound unless request.env["omniauth.auth"]
|
raise Discourse::NotFound unless request.env["omniauth.auth"]
|
||||||
raise Discourse::ReadOnly if @readonly_mode
|
raise Discourse::ReadOnly if @readonly_mode && !staff_writes_only_mode?
|
||||||
|
|
||||||
auth[:session] = session
|
auth[:session] = session
|
||||||
|
|
||||||
@ -71,6 +73,8 @@ class Users::OmniauthCallbacksController < ApplicationController
|
|||||||
|
|
||||||
return render_auth_result_failure if @auth_result.failed?
|
return render_auth_result_failure if @auth_result.failed?
|
||||||
|
|
||||||
|
raise Discourse::ReadOnly if staff_writes_only_mode? && !@auth_result.user&.staff?
|
||||||
|
|
||||||
complete_response_data
|
complete_response_data
|
||||||
|
|
||||||
return render_auth_result_failure if @auth_result.failed?
|
return render_auth_result_failure if @auth_result.failed?
|
||||||
|
@ -51,6 +51,8 @@ class UsersController < ApplicationController
|
|||||||
|
|
||||||
after_action :add_noindex_header, only: [:show, :my_redirect]
|
after_action :add_noindex_header, only: [:show, :my_redirect]
|
||||||
|
|
||||||
|
allow_in_staff_writes_only_mode :admin_login
|
||||||
|
|
||||||
MAX_RECENT_SEARCHES = 5
|
MAX_RECENT_SEARCHES = 5
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
@ -504,6 +504,9 @@ module Discourse
|
|||||||
USER_READONLY_MODE_KEY ||= 'readonly_mode:user'
|
USER_READONLY_MODE_KEY ||= 'readonly_mode:user'
|
||||||
PG_FORCE_READONLY_MODE_KEY ||= 'readonly_mode:postgres_force'
|
PG_FORCE_READONLY_MODE_KEY ||= 'readonly_mode:postgres_force'
|
||||||
|
|
||||||
|
# Psuedo readonly mode, where staff can still write
|
||||||
|
STAFF_WRITES_ONLY_MODE_KEY ||= 'readonly_mode:staff_writes_only'
|
||||||
|
|
||||||
READONLY_KEYS ||= [
|
READONLY_KEYS ||= [
|
||||||
READONLY_MODE_KEY,
|
READONLY_MODE_KEY,
|
||||||
PG_READONLY_MODE_KEY,
|
PG_READONLY_MODE_KEY,
|
||||||
@ -516,7 +519,7 @@ module Discourse
|
|||||||
Sidekiq.pause!("pg_failover") if !Sidekiq.paused?
|
Sidekiq.pause!("pg_failover") if !Sidekiq.paused?
|
||||||
end
|
end
|
||||||
|
|
||||||
if key == USER_READONLY_MODE_KEY || key == PG_FORCE_READONLY_MODE_KEY
|
if [USER_READONLY_MODE_KEY, PG_FORCE_READONLY_MODE_KEY, STAFF_WRITES_ONLY_MODE_KEY].include?(key)
|
||||||
Discourse.redis.set(key, 1)
|
Discourse.redis.set(key, 1)
|
||||||
else
|
else
|
||||||
ttl =
|
ttl =
|
||||||
@ -594,6 +597,10 @@ module Discourse
|
|||||||
recently_readonly? || Discourse.redis.exists?(*keys)
|
recently_readonly? || Discourse.redis.exists?(*keys)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.staff_writes_only_mode?
|
||||||
|
Discourse.redis.get(STAFF_WRITES_ONLY_MODE_KEY).present?
|
||||||
|
end
|
||||||
|
|
||||||
def self.pg_readonly_mode?
|
def self.pg_readonly_mode?
|
||||||
Discourse.redis.get(PG_READONLY_MODE_KEY).present?
|
Discourse.redis.get(PG_READONLY_MODE_KEY).present?
|
||||||
end
|
end
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module ReadOnlyHeader
|
|
||||||
|
|
||||||
def check_readonly_mode
|
|
||||||
@readonly_mode = Discourse.readonly_mode?
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_readonly_header
|
|
||||||
response.headers['Discourse-Readonly'] = 'true' if @readonly_mode
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
57
lib/read_only_mixin.rb
Normal file
57
lib/read_only_mixin.rb
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module ReadOnlyMixin
|
||||||
|
module ClassMethods
|
||||||
|
def actions_allowed_in_staff_writes_only_mode
|
||||||
|
@actions_allowed_in_staff_writes_only_mode ||= []
|
||||||
|
end
|
||||||
|
|
||||||
|
def allow_in_staff_writes_only_mode(*actions)
|
||||||
|
actions_allowed_in_staff_writes_only_mode.concat(actions.map(&:to_sym))
|
||||||
|
end
|
||||||
|
|
||||||
|
def allowed_in_staff_writes_only_mode?(action_name)
|
||||||
|
actions_allowed_in_staff_writes_only_mode.include?(action_name.to_sym)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def staff_writes_only_mode?
|
||||||
|
@staff_writes_only_mode
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_readonly_mode
|
||||||
|
if Discourse.readonly_mode?
|
||||||
|
@readonly_mode = true
|
||||||
|
@staff_writes_only_mode = false
|
||||||
|
elsif Discourse.staff_writes_only_mode?
|
||||||
|
@readonly_mode = true
|
||||||
|
@staff_writes_only_mode = true
|
||||||
|
else
|
||||||
|
@readonly_mode = false
|
||||||
|
@staff_writes_only_mode = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_readonly_header
|
||||||
|
response.headers['Discourse-Readonly'] = 'true' if @readonly_mode
|
||||||
|
end
|
||||||
|
|
||||||
|
def allowed_in_staff_writes_only_mode?
|
||||||
|
self.class.allowed_in_staff_writes_only_mode?(action_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def block_if_readonly_mode
|
||||||
|
return if request.fullpath.start_with?(path "/admin/backups")
|
||||||
|
return if request.get? || request.head?
|
||||||
|
|
||||||
|
if @staff_writes_only_mode
|
||||||
|
raise Discourse::ReadOnly.new if !current_user&.staff? && !allowed_in_staff_writes_only_mode?
|
||||||
|
elsif @readonly_mode
|
||||||
|
raise Discourse::ReadOnly.new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.included(base)
|
||||||
|
base.extend(ClassMethods)
|
||||||
|
end
|
||||||
|
end
|
@ -15,6 +15,13 @@ RSpec.describe ForumsController do
|
|||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
expect(response.headers['Discourse-Readonly']).to eq('true')
|
expect(response.headers['Discourse-Readonly']).to eq('true')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "returns a readonly header if the site is in staff-writes-only mode" do
|
||||||
|
Discourse.stubs(:staff_writes_only_mode?).returns(true)
|
||||||
|
get "/srv/status"
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(response.headers['Discourse-Readonly']).to eq('true')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "cluster parameter" do
|
describe "cluster parameter" do
|
||||||
|
@ -168,6 +168,33 @@ RSpec.describe Users::OmniauthCallbacksController do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "in staff writes only mode" do
|
||||||
|
use_redis_snapshotting
|
||||||
|
|
||||||
|
before do
|
||||||
|
Discourse.enable_readonly_mode(Discourse::STAFF_WRITES_ONLY_MODE_KEY)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a 503 for non-staff" do
|
||||||
|
mock_auth(user.email, user.username, user.name)
|
||||||
|
get "/auth/google_oauth2/callback.json"
|
||||||
|
expect(response.status).to eq(503)
|
||||||
|
logged_on_user = Discourse.current_user_provider.new(request.env).current_user
|
||||||
|
|
||||||
|
expect(logged_on_user).to eq(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "completes for staff" do
|
||||||
|
user.update!(admin: true)
|
||||||
|
mock_auth(user.email, user.username, user.name)
|
||||||
|
get "/auth/google_oauth2/callback.json"
|
||||||
|
expect(response.status).to eq(302)
|
||||||
|
logged_on_user = Discourse.current_user_provider.new(request.env).current_user
|
||||||
|
|
||||||
|
expect(logged_on_user).not_to eq(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "without an `omniauth.auth` env" do
|
context "without an `omniauth.auth` env" do
|
||||||
it "should return a 404" do
|
it "should return a 404" do
|
||||||
get "/auth/eviltrout/callback"
|
get "/auth/eviltrout/callback"
|
||||||
|
@ -6,6 +6,9 @@ describe SessionController do
|
|||||||
let(:user) { Fabricate(:user) }
|
let(:user) { Fabricate(:user) }
|
||||||
let(:email_token) { Fabricate(:email_token, user: user) }
|
let(:email_token) { Fabricate(:email_token, user: user) }
|
||||||
|
|
||||||
|
fab!(:admin) { Fabricate(:admin) }
|
||||||
|
let(:admin_email_token) { Fabricate(:email_token, user: admin) }
|
||||||
|
|
||||||
shared_examples 'failed to continue local login' do
|
shared_examples 'failed to continue local login' do
|
||||||
it 'should return the right response' do
|
it 'should return the right response' do
|
||||||
expect(response).not_to be_successful
|
expect(response).not_to be_successful
|
||||||
@ -549,6 +552,41 @@ describe SessionController do
|
|||||||
sso
|
sso
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'in staff writes only mode' do
|
||||||
|
use_redis_snapshotting
|
||||||
|
|
||||||
|
before do
|
||||||
|
Discourse.enable_readonly_mode(Discourse::STAFF_WRITES_ONLY_MODE_KEY)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows staff to login' do
|
||||||
|
sso = get_sso('/a/')
|
||||||
|
sso.external_id = '666'
|
||||||
|
sso.email = 'bob@bob.com'
|
||||||
|
sso.name = 'Bob Bobson'
|
||||||
|
sso.username = 'bob'
|
||||||
|
sso.admin = true
|
||||||
|
|
||||||
|
get "/session/sso_login", params: Rack::Utils.parse_query(sso.payload), headers: headers
|
||||||
|
|
||||||
|
logged_on_user = Discourse.current_user_provider.new(request.env).current_user
|
||||||
|
expect(logged_on_user).not_to eq(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'doesn\'t allow non-staff to login' do
|
||||||
|
sso = get_sso('/a/')
|
||||||
|
sso.external_id = '666'
|
||||||
|
sso.email = 'bob@bob.com'
|
||||||
|
sso.name = 'Bob Bobson'
|
||||||
|
sso.username = 'bob'
|
||||||
|
|
||||||
|
get "/session/sso_login", params: Rack::Utils.parse_query(sso.payload), headers: headers
|
||||||
|
|
||||||
|
logged_on_user = Discourse.current_user_provider.new(request.env).current_user
|
||||||
|
expect(logged_on_user).to eq(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it 'does not create superfluous auth tokens when already logged in' do
|
it 'does not create superfluous auth tokens when already logged in' do
|
||||||
user = Fabricate(:user)
|
user = Fabricate(:user)
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
@ -1494,6 +1532,55 @@ describe SessionController do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe '#create' do
|
describe '#create' do
|
||||||
|
context 'read only mode' do
|
||||||
|
use_redis_snapshotting
|
||||||
|
|
||||||
|
before do
|
||||||
|
Discourse.enable_readonly_mode
|
||||||
|
EmailToken.confirm(email_token.token)
|
||||||
|
EmailToken.confirm(admin_email_token.token)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'prevents login by regular users' do
|
||||||
|
post "/session.json", params: {
|
||||||
|
login: user.username, password: 'myawesomepassword'
|
||||||
|
}
|
||||||
|
expect(response.status).not_to eq(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'prevents login by admins' do
|
||||||
|
post "/session.json", params: {
|
||||||
|
login: user.username, password: 'myawesomepassword'
|
||||||
|
}
|
||||||
|
expect(response.status).not_to eq(200)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'staff writes only mode' do
|
||||||
|
use_redis_snapshotting
|
||||||
|
|
||||||
|
before do
|
||||||
|
Discourse.enable_readonly_mode(Discourse::STAFF_WRITES_ONLY_MODE_KEY)
|
||||||
|
EmailToken.confirm(email_token.token)
|
||||||
|
EmailToken.confirm(admin_email_token.token)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows admin login' do
|
||||||
|
post "/session.json", params: {
|
||||||
|
login: admin.username, password: 'myawesomepassword'
|
||||||
|
}
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(response.parsed_body['error']).not_to be_present
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'prevents login by regular users' do
|
||||||
|
post "/session.json", params: {
|
||||||
|
login: user.username, password: 'myawesomepassword'
|
||||||
|
}
|
||||||
|
expect(response.status).not_to eq(200)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'local login is disabled' do
|
context 'local login is disabled' do
|
||||||
before do
|
before do
|
||||||
SiteSetting.enable_local_logins = false
|
SiteSetting.enable_local_logins = false
|
||||||
|
Reference in New Issue
Block a user