FEATURE: Enforce two-factor authentication. (#6348)

This commit is contained in:
Bianca Nenciu 2019-03-15 13:09:37 +02:00 committed by GitHub
parent e711d3cb3a
commit d352baa1a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 184 additions and 4 deletions

View File

@ -39,6 +39,26 @@ export default Ember.Controller.extend({
return findAll().length > 0;
},
@computed(
"siteSettings.enforce_second_factor",
"currentUser",
"currentUser.second_factor_enabled",
"currentUser.staff"
)
showEnforcedNotice(
enforce_second_factor,
user,
second_factor_enabled,
staff
) {
return (
user &&
!second_factor_enabled &&
(enforce_second_factor === "all" ||
(enforce_second_factor === "staff" && staff))
);
},
toggleSecondFactor(enable) {
if (!this.get("secondFactorToken")) return;
this.set("loading", true);

View File

@ -13,5 +13,28 @@ export default RestrictedUserRoute.extend({
setupController(controller, model) {
controller.setProperties({ model, newUsername: model.get("username") });
},
actions: {
willTransition(transition) {
this._super(...arguments);
const controller = this.controllerFor("preferences/second-factor");
const user = controller.get("currentUser");
const settings = controller.get("siteSettings");
if (
transition.targetName === "preferences.second-factor" ||
!user ||
user.second_factor_enabled ||
(settings.enforce_second_factor === "staff" && !user.staff) ||
settings.enforce_second_factor === "no"
) {
return true;
}
transition.abort();
return false;
}
}
});

View File

@ -1,6 +1,14 @@
<section class='user-preferences'>
<form class="form-horizontal">
{{#if showEnforcedNotice}}
<div class="control-group">
<div class="controls">
<div class='alert alert-error'>{{i18n 'user.second_factor.enforced_notice'}}</div>
</div>
</div>
{{/if}}
{{#if errorMessage}}
<div class="control-group">
<div class="controls">

View File

@ -685,10 +685,9 @@ class ApplicationController < ActionController::Base
end
def redirect_to_login_if_required
return if current_user || (request.format.json? && is_api?)
if SiteSetting.login_required?
return if request.format.json? && is_api?
if !current_user && SiteSetting.login_required?
flash.keep
dont_cache_page
@ -704,6 +703,18 @@ class ApplicationController < ActionController::Base
redirect_to path("/login")
end
end
if current_user &&
!current_user.totp_enabled? &&
!request.format.json? &&
!is_api? &&
((SiteSetting.enforce_second_factor == 'staff' && current_user.staff?) ||
SiteSetting.enforce_second_factor == 'all')
redirect_path = "#{GlobalSetting.relative_url_root}/u/#{current_user.username}/preferences/second-factor"
if !request.fullpath.start_with?(redirect_path)
redirect_to path(redirect_path)
end
end
end
def block_if_readonly_mode

View File

@ -3,7 +3,7 @@
class ExtraLocalesController < ApplicationController
layout :false
skip_before_action :check_xhr, :preload_json
skip_before_action :check_xhr, :preload_json, :redirect_to_login_if_required
def show
bundle = params[:bundle]

View File

@ -810,6 +810,7 @@ en:
Two factor authentication adds extra security to your account by requiring a one-time token in addition to your password. Tokens can be generated on <a href="https://www.google.com/search?q=authenticator+apps+for+android" target='_blank'>Android</a> and <a href="https://www.google.com/search?q=authenticator+apps+for+ios">iOS</a> devices.
oauth_enabled_warning: "Please note that social logins will be disabled once two factor authentication has been enabled on your account."
use: "<a href>Use Authenticator app</a>"
enforced_notice: "You are required to enable two factor authentication before accessing this site."
change_about:
title: "Change About Me"

View File

@ -1339,6 +1339,7 @@ en:
notification_email: "The from: email address used when sending all essential system emails. The domain specified here must have SPF, DKIM and reverse PTR records set correctly for email to arrive."
email_custom_headers: "A pipe-delimited list of custom email headers"
email_subject: "Customizable subject format for standard emails. See <a href='https://meta.discourse.org/t/customize-subject-format-for-standard-emails/20801' target='_blank'>https://meta.discourse.org/t/customize-subject-format-for-standard-emails/20801</a>"
enforce_second_factor: "Forces users to enable second factor authentication. Select 'all' to enforce it to all users. Select 'staff' to enforce it to staff users only."
force_https: "Force your site to use HTTPS only. WARNING: do NOT enable this until you verify HTTPS is fully set up and working absolutely everywhere! Did you check your CDN, all social logins, and any external logos / dependencies to make sure they are all HTTPS compatible, too?"
same_site_cookies: "Use same site cookies, they eliminate all vectors Cross Site Request Forgery on supported browsers (Lax or Strict). Warning: Strict will only work on sites that force login and use SSO."
summary_score_threshold: "The minimum score required for a post to be included in 'Summarize This Topic'"

View File

@ -1220,6 +1220,14 @@ trust:
default: true
security:
enforce_second_factor:
client: true
type: enum
default: 'no'
choices:
- 'no'
- 'staff'
- 'all'
force_https:
default: false
shadowed_by_global: true

View File

@ -20,6 +20,63 @@ RSpec.describe ApplicationController do
end
end
describe '#redirect_to_second_factor_if_required' do
let(:admin) { Fabricate(:admin) }
let(:user) { Fabricate(:user) }
before do
admin # to skip welcome wizard at home page `/`
end
it "should redirect admins when enforce_second_factor is 'all'" do
SiteSetting.enforce_second_factor = "all"
sign_in(admin)
get "/"
expect(response).to redirect_to("/u/#{admin.username}/preferences/second-factor")
end
it "should redirect users when enforce_second_factor is 'all'" do
SiteSetting.enforce_second_factor = "all"
sign_in(user)
get "/"
expect(response).to redirect_to("/u/#{user.username}/preferences/second-factor")
end
it "should redirect admins when enforce_second_factor is 'staff'" do
SiteSetting.enforce_second_factor = "staff"
sign_in(admin)
get "/"
expect(response).to redirect_to("/u/#{admin.username}/preferences/second-factor")
end
it "should not redirect users when enforce_second_factor is 'staff'" do
SiteSetting.enforce_second_factor = "staff"
sign_in(user)
get "/"
expect(response.status).to eq(200)
end
it "should not redirect admins when turned off" do
SiteSetting.enforce_second_factor = "no"
sign_in(admin)
get "/"
expect(response.status).to eq(200)
end
it "should not redirect users when turned off" do
SiteSetting.enforce_second_factor = "no"
sign_in(user)
get "/"
expect(response.status).to eq(200)
end
end
describe 'invalid request params' do
before do
@old_logger = Rails.logger

View File

@ -0,0 +1,51 @@
import { acceptance, replaceCurrentUser } from "helpers/qunit-helpers";
acceptance("Enforce Second Factor", {
loggedIn: true
});
QUnit.test("as an admin", async assert => {
await visit("/u/eviltrout/preferences/second-factor");
Discourse.SiteSettings.enforce_second_factor = "staff";
await visit("/u/eviltrout/summary");
assert.equal(
find(".control-label").text(),
"Password",
"it will not transition from second-factor preferences"
);
await click("#toggle-hamburger-menu");
await click("a.admin-link");
assert.equal(
find(".control-label").text(),
"Password",
"it stays at second-factor preferences"
);
});
QUnit.test("as a user", async assert => {
replaceCurrentUser({ staff: false, admin: false });
await visit("/u/eviltrout/preferences/second-factor");
Discourse.SiteSettings.enforce_second_factor = "all";
await visit("/u/eviltrout/summary");
assert.equal(
find(".control-label").text(),
"Password",
"it will not transition from second-factor preferences"
);
await click("#toggle-hamburger-menu");
await click("a.about-link");
assert.equal(
find(".control-label").text(),
"Password",
"it stays at second-factor preferences"
);
});