mirror of
https://github.com/OpenNebula/one.git
synced 2025-01-11 05:17:41 +03:00
* F #962: Sunstone 2FA with WebAuthn
This strengthens the login with e.g. U2F/FIDO2 authentication keys.
Signed-off-by: Dennis Felsch <dennis.felsch@ruhr-uni-bochum.de>
(cherry picked from commit 487a6247a9
)
This commit is contained in:
parent
6f0ec36f6f
commit
fb1294b386
@ -36,6 +36,7 @@ end
|
||||
|
||||
if RUBY_VERSION >= '2.4.0'
|
||||
gem 'xmlrpc'
|
||||
gem 'webauthn' # sunstone
|
||||
end
|
||||
|
||||
if RUBY_VERSION < '2.1'
|
||||
|
@ -97,6 +97,40 @@
|
||||
# Two Factor Authentication Issuer Label
|
||||
:two_factor_auth_issuer: opennebula
|
||||
|
||||
################################################################################
|
||||
# WebAuthn
|
||||
################################################################################
|
||||
|
||||
# This value needs to match `window.location.origin` evaluated by the User Agent
|
||||
# during registration and authentication ceremonies. Remember that WebAuthn
|
||||
# requires TLS on anything else than localhost.
|
||||
:webauthn_origin: http://localhost:9869
|
||||
|
||||
# Relying Party name for display purposes
|
||||
:webauthn_rpname: 'OpenNebula Cloud'
|
||||
|
||||
# Optional client timeout hint, in milliseconds. Specifies how long the browser
|
||||
# should wait for any interaction with the user.
|
||||
:webauthn_timeout: 60000
|
||||
|
||||
# Optional differing Relying Party ID
|
||||
# See https://www.w3.org/TR/webauthn/#relying-party-identifier
|
||||
# :webauthn_rpid: example.com
|
||||
|
||||
# Supported cryptographic algorithms
|
||||
# See https://www.iana.org/assignments/jose/jose.xhtml
|
||||
# Possible is any list of
|
||||
# ES256 | ES384 | ES512 | PS256 | PS384 | PS512 | RS256 | RS384 | RS512 | RS1
|
||||
# :webauthn_algorithms: [ES256, PS256, RS256]
|
||||
|
||||
################################################################################
|
||||
# Check Upgrades
|
||||
################################################################################
|
||||
|
||||
# To check for the latest release. Comment this value if you don't want to check
|
||||
# this.
|
||||
:remote_version: http://downloads.opennebula.org/latest
|
||||
|
||||
################################################################################
|
||||
# UI Settings
|
||||
################################################################################
|
||||
|
155
src/sunstone/models/OpenNebula2FA/SunstoneWebAuthn.rb
Normal file
155
src/sunstone/models/OpenNebula2FA/SunstoneWebAuthn.rb
Normal file
@ -0,0 +1,155 @@
|
||||
# -------------------------------------------------------------------------- #
|
||||
# Copyright 2002-2019, OpenNebula Project, OpenNebula Systems #
|
||||
# #
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
|
||||
# not use this file except in compliance with the License. You may obtain #
|
||||
# a copy of the License at #
|
||||
# #
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 #
|
||||
# #
|
||||
# Unless required by applicable law or agreed to in writing, software #
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, #
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
||||
# See the License for the specific language governing permissions and #
|
||||
# limitations under the License. #
|
||||
#--------------------------------------------------------------------------- #
|
||||
|
||||
require 'json'
|
||||
require 'webauthn'
|
||||
|
||||
# WebAuthn authentication
|
||||
module SunstoneWebAuthn
|
||||
|
||||
def self.configure(conf)
|
||||
@client = OpenNebula::Client.new
|
||||
@challenges = Hash.new
|
||||
WebAuthn.configure do |config|
|
||||
if !conf.include?(:webauthn_origin) || conf[:webauthn_origin] == ''
|
||||
raise StandardError.new("Configuration of ':webauthn_origin' is missing")
|
||||
end
|
||||
config.origin = conf[:webauthn_origin]
|
||||
|
||||
if !conf.include?(:webauthn_rpname) || conf[:webauthn_rpname] == ''
|
||||
raise StandardError.new("Configuration of ':webauthn_rpname' is missing")
|
||||
end
|
||||
config.rp_name = conf[:webauthn_rpname]
|
||||
|
||||
conf[:webauthn_timeout] ||= 60000
|
||||
config.credential_options_timeout = conf[:webauthn_timeout]
|
||||
|
||||
if conf.include?(:webauthn_rpid) && conf[:webauthn_rpid] != ''
|
||||
config.rp_id = conf[:webauthn_rpid]
|
||||
end
|
||||
|
||||
conf[:webauthn_algorithms] ||= ["ES256", "PS256", "RS256"]
|
||||
config.algorithms = conf[:webauthn_algorithms]
|
||||
end
|
||||
end
|
||||
|
||||
def self.getOptionsForCreate(user_id, user_name)
|
||||
known_credentials = getCredentialIDsForUser(user_id)
|
||||
options = WebAuthn::Credential.options_for_create(
|
||||
user: { id: Base64.urlsafe_encode64(user_id, padding: false), name: user_name },
|
||||
authenticator_selection: { user_verification: 'discouraged' },
|
||||
exclude: known_credentials
|
||||
)
|
||||
@challenges[user_id] = options.challenge
|
||||
return renderJSON(options.as_json)
|
||||
end
|
||||
|
||||
def self.getOptionsForGet(user_id)
|
||||
known_credentials = getCredentialIDsForUser(user_id)
|
||||
unless known_credentials.length == 0
|
||||
options = WebAuthn::Credential.options_for_get(
|
||||
user_verification: 'discouraged',
|
||||
allow: known_credentials
|
||||
)
|
||||
@challenges[user_id] = options.challenge
|
||||
return renderJSON(options.as_json)
|
||||
end
|
||||
end
|
||||
|
||||
def self.getCredentialsForUser(user_id)
|
||||
user = User.new_with_id(user_id, @client)
|
||||
rc = user.info
|
||||
|
||||
if OpenNebula.is_error?(rc)
|
||||
$cloud_auth.logger.error {"user.info error: #{rc.message}"}
|
||||
return nil
|
||||
end
|
||||
|
||||
credentials = []
|
||||
json_str = user['TEMPLATE/SUNSTONE/WEBAUTHN_CREDENTIALS']
|
||||
if json_str.nil?
|
||||
return credentials
|
||||
end
|
||||
begin
|
||||
credentials = JSON.parse(json_str.gsub("'", '"'))['cs']
|
||||
rescue Exception => e
|
||||
return OpenNebula::Error.new(e.message)
|
||||
end
|
||||
return credentials
|
||||
end
|
||||
|
||||
def self.getCredentialIDsForUser(user_id)
|
||||
return getCredentialsForUser(user_id).map { |hash| hash['id'] }
|
||||
end
|
||||
|
||||
def self.verifyCredentialFromRegistration(user_id, publicKeyCredential)
|
||||
credential_with_attestation = WebAuthn::Credential.from_create(publicKeyCredential)
|
||||
challenge = @challenges.delete(user_id.to_s)
|
||||
begin
|
||||
credential_with_attestation.verify(challenge)
|
||||
return credential_with_attestation
|
||||
rescue WebAuthn::Error => e
|
||||
return OpenNebula::Error.new(e.message)
|
||||
end
|
||||
end
|
||||
|
||||
def self.authenticate(user_id, publicKeyCredential_s)
|
||||
begin
|
||||
publicKeyCredential = JSON.parse(publicKeyCredential_s)
|
||||
received_credential = WebAuthn::Credential.from_get(publicKeyCredential)
|
||||
rescue Exception => e
|
||||
return false
|
||||
end
|
||||
credentials = getCredentialsForUser(user_id)
|
||||
stored_credential = credentials.find { |hash| hash['id'] == publicKeyCredential['id'] }
|
||||
challenge = @challenges.delete(user_id.to_s)
|
||||
begin
|
||||
received_credential.verify(challenge, public_key: stored_credential['pk'], sign_count: Integer(stored_credential['cnt']))
|
||||
stored_credential['cnt'] = received_credential.sign_count
|
||||
updateCredentials(user_id, credentials)
|
||||
return true
|
||||
rescue WebAuthn::SignCountVerificationError => e
|
||||
# Cryptographic verification of the authenticator data succeeded, but the signature counter was less then or equal
|
||||
# to the stored value. This can have several reasons and depending on risk tolerance an implementation can choose to fail or
|
||||
# pass authentication. For more information see https://www.w3.org/TR/webauthn/#sign-counter. Here, we fail authentication.
|
||||
return false
|
||||
rescue WebAuthn::Error => e
|
||||
return false
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
def self.updateCredentials(user_id, credentials)
|
||||
user = User.new_with_id(user_id, @client)
|
||||
rc = user.info
|
||||
|
||||
if OpenNebula.is_error?(rc)
|
||||
$cloud_auth.logger.error {"user.info error: #{rc.message}"}
|
||||
return nil
|
||||
end
|
||||
|
||||
credentialsJSON = JSON.generate({ "cs" => credentials}).gsub('"', "'")
|
||||
# This does not work; breaks the template (guess the replace function is buggy)
|
||||
# user.replace({'WEBAUTHN_CREDENTIALS' => credentialsJSON}, 'TEMPLATE/SUNSTONE')
|
||||
user.retrieve_xmlelements('TEMPLATE/SUNSTONE/WEBAUTHN_CREDENTIALS')[0].set_content(credentialsJSON)
|
||||
user.update(user.template_like_str('TEMPLATE'))
|
||||
end
|
||||
|
||||
def self.renderJSON(hash)
|
||||
return JSON.generate(hash)
|
||||
end
|
||||
|
||||
end
|
@ -14,9 +14,15 @@
|
||||
# limitations under the License. #
|
||||
#--------------------------------------------------------------------------- #
|
||||
|
||||
require 'json'
|
||||
require 'OpenNebulaJSON/JSONUtils'
|
||||
require 'sunstone_2f_auth'
|
||||
|
||||
begin
|
||||
require "SunstoneWebAuthn"
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
module OpenNebulaJSON
|
||||
class UserJSON < OpenNebula::User
|
||||
include JSONUtils
|
||||
@ -46,6 +52,8 @@ module OpenNebulaJSON
|
||||
when "update" then self.update(action_hash['params'])
|
||||
when "enable_two_factor_auth" then self.enable_two_factor_auth(action_hash['params'])
|
||||
when "disable_two_factor_auth" then self.disable_two_factor_auth(action_hash['params'])
|
||||
when "enable_security_key" then self.enable_security_key(action_hash['params'])
|
||||
when "disable_security_key" then self.disable_security_key(action_hash['params'])
|
||||
when "set_quota" then self.set_quota(action_hash['params'])
|
||||
when "addgroup" then self.addgroup(action_hash['params'])
|
||||
when "delgroup" then self.delgroup(action_hash['params'])
|
||||
@ -93,6 +101,46 @@ module OpenNebulaJSON
|
||||
def disable_two_factor_auth(params=Hash.new)
|
||||
sunstone_setting = params["current_sunstone_setting"]
|
||||
sunstone_setting.delete("TWO_FACTOR_AUTH_SECRET")
|
||||
if !params['delete_all'].nil? && params['delete_all'] == true
|
||||
sunstone_setting.delete("WEBAUTHN_CREDENTIALS")
|
||||
end
|
||||
sunstone_setting = { "sunstone" => sunstone_setting }
|
||||
template_raw = template_to_str_sunstone_with_explicite_empty_value(sunstone_setting)
|
||||
update_params = { "template_raw" => template_raw, "append" => true }
|
||||
update(update_params)
|
||||
end
|
||||
|
||||
def enable_security_key(params=Hash.new)
|
||||
if !$conf[:webauthn_avail]
|
||||
return OpenNebula::Error.new("WebAuthn not available.")
|
||||
end
|
||||
sunstone_setting = params["current_sunstone_setting"]
|
||||
webauthn_credential = SunstoneWebAuthn.verifyCredentialFromRegistration(@pe_id, params['publicKeyCredential'])
|
||||
template_credentials = self['TEMPLATE/SUNSTONE/WEBAUTHN_CREDENTIALS'] || "{'cs':[]}"
|
||||
credentials = parse_json(template_credentials.gsub("'", '"'), 'cs')
|
||||
credentials.append({
|
||||
'id' => webauthn_credential.id,
|
||||
'pk' => webauthn_credential.public_key,
|
||||
'cnt' => webauthn_credential.sign_count,
|
||||
'name' => params['nickname']
|
||||
})
|
||||
sunstone_setting["WEBAUTHN_CREDENTIALS"] = JSON.generate({ "cs" => credentials}).gsub('"', "'")
|
||||
sunstone_setting = { "sunstone" => sunstone_setting }
|
||||
template_raw = template_to_str_sunstone_with_explicite_empty_value(sunstone_setting)
|
||||
update_params = { "template_raw" => template_raw, "append" => true }
|
||||
update(update_params)
|
||||
end
|
||||
|
||||
def disable_security_key(params=Hash.new)
|
||||
sunstone_setting = params["current_sunstone_setting"]
|
||||
credentials = parse_json(sunstone_setting["WEBAUTHN_CREDENTIALS"].gsub("'", '"'), 'cs')
|
||||
for i in 0..credentials.length() do
|
||||
if credentials[i]["pk"] == params["tokenid_to_remove"]
|
||||
credentials.delete_at(i)
|
||||
break
|
||||
end
|
||||
end
|
||||
sunstone_setting["WEBAUTHN_CREDENTIALS"] = JSON.generate({ "cs" => credentials}).gsub('"', "'")
|
||||
sunstone_setting = { "sunstone" => sunstone_setting }
|
||||
template_raw = template_to_str_sunstone_with_explicite_empty_value(sunstone_setting)
|
||||
update_params = { "template_raw" => template_raw, "append" => true }
|
||||
|
@ -17,11 +17,15 @@
|
||||
define(function(require) {
|
||||
require('../bower_components/jquery/dist/jquery.min');
|
||||
var OpenNebulaAuth = require('opennebula/auth');
|
||||
var WebAuthnJSON = require('../bower_components/webauthn-json/dist/index');
|
||||
|
||||
var showErrorAuth = false;
|
||||
var uid;
|
||||
|
||||
var textOpenNebulaNotRunning = "OpenNebula is not running or there was a server exception. Please check the server logs.";
|
||||
var textInvalidUserorPassword = "Invalid username or password";
|
||||
var textNoAnswerFromServer = "No answer from server. Is it running?";
|
||||
var textTwoFactorTokenInvalid = "Two factor Token Invalid";
|
||||
var textTwoFactorTokenInvalid = "Invalid second factor authentication";
|
||||
var idElementTwoFactor = "#two_factor_auth_token";
|
||||
|
||||
function auth_success(req, response) {
|
||||
@ -29,6 +33,9 @@ define(function(require) {
|
||||
$("#login_form").hide();
|
||||
$("#login_spinner").hide();
|
||||
$("#two_factor_auth").fadeIn("slow");
|
||||
$("#two_factor_auth_token").focus();
|
||||
$("#login_btn")[0].type = "button";
|
||||
$("#two_factor_auth_login_btn")[0].type = "submit";
|
||||
if(!showErrorAuth){
|
||||
showErrorAuth = true;
|
||||
} else {
|
||||
@ -36,19 +43,57 @@ define(function(require) {
|
||||
$("#error_box").fadeIn("slow");
|
||||
$("#login_spinner").hide();
|
||||
}
|
||||
uid = response.uid;
|
||||
prepareWebAuthn(uid);
|
||||
} else {
|
||||
showErrorAuth = false;
|
||||
window.location.href = ".";
|
||||
}
|
||||
}
|
||||
|
||||
function prepareWebAuthn(uid) {
|
||||
$("#webauthn_login_btn").unbind();
|
||||
$.ajax({
|
||||
url: "webauthn_options_for_get?uid=" + uid,
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: function (response) {
|
||||
if (!response) {
|
||||
return
|
||||
}
|
||||
if (!navigator.credentials) {
|
||||
$("#webauthn_login_div").hide();
|
||||
console.warn('WebAuthn functionality unavailable. Ask your cloud administrator to enable TLS.');
|
||||
}
|
||||
$("#webauthn_login_btn").click(function () {
|
||||
WebAuthnJSON.get({ "publicKey": response }).then(authenticate)
|
||||
.catch((e) => {
|
||||
$("#error_message").text(e.message);
|
||||
$("#error_box").fadeIn("slow");
|
||||
$("#login_spinner").hide();
|
||||
});
|
||||
});
|
||||
},
|
||||
error: function (response) {
|
||||
if (response.status == 501) {
|
||||
$("#webauthn_login_div").hide();
|
||||
console.warn('WebAuthn functionality unavailable. Ask your cloud administrator to upgrade the Ruby version.');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function auth_error(req, error) {
|
||||
|
||||
var status = error.error.http_status;
|
||||
|
||||
switch (status){
|
||||
case 401:
|
||||
$("#error_message").text(textInvalidUserorPassword);
|
||||
if (showErrorAuth) {
|
||||
$("#error_message").text(textTwoFactorTokenInvalid);
|
||||
} else {
|
||||
$("#error_message").text(textInvalidUserorPassword);
|
||||
}
|
||||
break;
|
||||
case 500:
|
||||
$("#error_message").text(textOpenNebulaNotRunning);
|
||||
@ -63,11 +108,22 @@ define(function(require) {
|
||||
$("#login_spinner").hide();
|
||||
}
|
||||
|
||||
function authenticate() {
|
||||
function authenticate(publicKeyCredential) {
|
||||
var username = $("#username").val();
|
||||
var password = $("#password").val();
|
||||
var remember = $("#check_remember").is(":checked");
|
||||
var two_factor_auth_token = $("#two_factor_auth_token").val();
|
||||
var two_factor_auth_token;
|
||||
var error_callback;
|
||||
if (publicKeyCredential == undefined) {
|
||||
two_factor_auth_token = $("#two_factor_auth_token").val();
|
||||
error_callback = auth_error
|
||||
} else {
|
||||
two_factor_auth_token = JSON.stringify(publicKeyCredential);
|
||||
error_callback = (req, error) => {
|
||||
auth_error(req, error);
|
||||
prepareWebAuthn(uid);
|
||||
}
|
||||
}
|
||||
|
||||
$("#error_box").fadeOut("slow");
|
||||
$("#login_spinner").show();
|
||||
@ -80,7 +136,7 @@ define(function(require) {
|
||||
remember: remember,
|
||||
success: auth_success,
|
||||
two_factor_auth_token: two_factor_auth_token,
|
||||
error: auth_error
|
||||
error: error_callback
|
||||
});
|
||||
}
|
||||
|
||||
@ -130,7 +186,7 @@ define(function(require) {
|
||||
return false;
|
||||
});
|
||||
|
||||
$("#two_factor_auth_login").click(function() {
|
||||
$("#two_factor_auth_login_btn").click(function() {
|
||||
if($(idElementTwoFactor) && $(idElementTwoFactor).val().length){
|
||||
authenticate();
|
||||
}
|
||||
|
@ -108,6 +108,14 @@ define(function(require) {
|
||||
var action_obj = params.data.extra_param;
|
||||
OpenNebulaAction.simple_action(params, RESOURCE, "disable_two_factor_auth", action_obj);
|
||||
},
|
||||
"enable_sunstone_security_key": function(params) {
|
||||
var action_obj = params.data.extra_param;
|
||||
OpenNebulaAction.simple_action(params, RESOURCE, "enable_security_key", action_obj);
|
||||
},
|
||||
"disable_sunstone_security_key": function(params) {
|
||||
var action_obj = params.data.extra_param;
|
||||
OpenNebulaAction.simple_action(params, RESOURCE, "disable_security_key", action_obj);
|
||||
},
|
||||
"accounting" : function(params) {
|
||||
OpenNebulaAction.monitor(params, RESOURCE, false);
|
||||
},
|
||||
|
@ -98,11 +98,7 @@ define(function(require) {
|
||||
|
||||
$("#provision_user_views_select option[value=\"" + config["user_config"]["default_view"] + "\"]", context).attr("selected", "selected");
|
||||
|
||||
if (that.element.TEMPLATE.SUNSTONE && that.element.TEMPLATE.SUNSTONE.TWO_FACTOR_AUTH_SECRET) {
|
||||
$(".provision_two_factor_auth_button", context).html(Locale.tr("Disable"));
|
||||
} else {
|
||||
$(".provision_two_factor_auth_button", context).html(Locale.tr("Manage two factor authentication"));
|
||||
}
|
||||
$(".provision_two_factor_auth_button", context).html(Locale.tr("Manage two factor authentication"));
|
||||
|
||||
// Login token button
|
||||
context.off("click", ".provision_login_token_button");
|
||||
@ -116,23 +112,14 @@ define(function(require) {
|
||||
context.off("click", ".provision_two_factor_auth_button");
|
||||
context.on("click", ".provision_two_factor_auth_button", function(){
|
||||
var sunstone_setting = that.element.TEMPLATE.SUNSTONE || {};
|
||||
if (sunstone_setting.TWO_FACTOR_AUTH_SECRET) {
|
||||
Sunstone.runAction(
|
||||
"User.disable_sunstone_two_factor_auth",
|
||||
that.element.ID,
|
||||
{current_sunstone_setting: sunstone_setting}
|
||||
);
|
||||
} else {
|
||||
Sunstone.getDialog(TWO_FACTOR_AUTH_DIALOG_ID).setParams({
|
||||
element: that.element,
|
||||
sunstone_setting: sunstone_setting
|
||||
});
|
||||
Sunstone.getDialog(TWO_FACTOR_AUTH_DIALOG_ID).reset();
|
||||
Sunstone.getDialog(TWO_FACTOR_AUTH_DIALOG_ID).show();
|
||||
}
|
||||
Sunstone.getDialog(TWO_FACTOR_AUTH_DIALOG_ID).setParams({
|
||||
element: that.element,
|
||||
sunstone_setting: sunstone_setting
|
||||
});
|
||||
Sunstone.getDialog(TWO_FACTOR_AUTH_DIALOG_ID).reset();
|
||||
Sunstone.getDialog(TWO_FACTOR_AUTH_DIALOG_ID).show();
|
||||
});
|
||||
|
||||
|
||||
$("#provision_change_password_form").submit(function() {
|
||||
var pw = $("#provision_new_password", this).val();
|
||||
var confirm_password = $("#provision_new_confirm_password", this).val();
|
||||
|
@ -239,7 +239,7 @@
|
||||
<div id="provision_two_factor_auth_accordion" class="accordion-content" data-tab-content>
|
||||
<br/>
|
||||
<p>
|
||||
{{tr "Two factor authentication can be enabled for loging into Sunestone UI."}}
|
||||
{{tr "Two factor authentication can be enabled for logging into Sunestone UI."}}
|
||||
</p>
|
||||
<div class="row">
|
||||
<div class="large-12 columns">
|
||||
|
@ -29,6 +29,7 @@ define(function(require) {
|
||||
var AUTH_DRIVER_DIALOG_ID = require("./dialogs/auth-driver/dialogId");
|
||||
var QUOTAS_DIALOG_ID = require("./dialogs/quotas/dialogId");
|
||||
var GROUPS_DIALOG_ID = require("./dialogs/groups/dialogId");
|
||||
var TWO_FACTOR_AUTH_DIALOG_ID = require('tabs/users-tab/dialogs/two-factor-auth/dialogId');
|
||||
|
||||
var RESOURCE = "User";
|
||||
var XML_ROOT = "USER";
|
||||
@ -194,6 +195,62 @@ define(function(require) {
|
||||
error: Notifier.onError
|
||||
},
|
||||
|
||||
"User.enable_sunstone_security_key" : {
|
||||
type: "single",
|
||||
call: OpenNebulaResource.enable_sunstone_security_key,
|
||||
callback: function(request, response) {
|
||||
OpenNebulaResource.show({
|
||||
data : {
|
||||
id: request.request.data[0]
|
||||
},
|
||||
success: function(request, response) {
|
||||
var sunstone_template = {};
|
||||
if (response[XML_ROOT].TEMPLATE.SUNSTONE) {
|
||||
$.extend(sunstone_template, response[XML_ROOT].TEMPLATE.SUNSTONE);
|
||||
}
|
||||
Sunstone.getDialog(TWO_FACTOR_AUTH_DIALOG_ID).hide();
|
||||
Sunstone.getDialog(TWO_FACTOR_AUTH_DIALOG_ID).setParams({
|
||||
element: response[XML_ROOT],
|
||||
sunstone_setting: sunstone_template
|
||||
});
|
||||
Sunstone.getDialog(TWO_FACTOR_AUTH_DIALOG_ID).reset();
|
||||
Sunstone.getDialog(TWO_FACTOR_AUTH_DIALOG_ID).show();
|
||||
},
|
||||
error: Notifier.onError
|
||||
});
|
||||
Sunstone.runAction("Settings.refresh");
|
||||
},
|
||||
error: Notifier.onError
|
||||
},
|
||||
|
||||
"User.disable_sunstone_security_key" : {
|
||||
type: "single",
|
||||
call: OpenNebulaResource.disable_sunstone_security_key,
|
||||
callback: function(request, response) {
|
||||
OpenNebulaResource.show({
|
||||
data : {
|
||||
id: request.request.data[0]
|
||||
},
|
||||
success: function(request, response) {
|
||||
var sunstone_template = {};
|
||||
if (response[XML_ROOT].TEMPLATE.SUNSTONE) {
|
||||
$.extend(sunstone_template, response[XML_ROOT].TEMPLATE.SUNSTONE);
|
||||
}
|
||||
Sunstone.getDialog(TWO_FACTOR_AUTH_DIALOG_ID).hide();
|
||||
Sunstone.getDialog(TWO_FACTOR_AUTH_DIALOG_ID).setParams({
|
||||
element: response[XML_ROOT],
|
||||
sunstone_setting: sunstone_template
|
||||
});
|
||||
Sunstone.getDialog(TWO_FACTOR_AUTH_DIALOG_ID).reset();
|
||||
Sunstone.getDialog(TWO_FACTOR_AUTH_DIALOG_ID).show();
|
||||
},
|
||||
error: Notifier.onError
|
||||
});
|
||||
Sunstone.runAction("Settings.refresh");
|
||||
},
|
||||
error: Notifier.onError
|
||||
},
|
||||
|
||||
"User.append_sunstone_setting_refresh" : {
|
||||
type: "single",
|
||||
call: function(params){
|
||||
|
@ -22,8 +22,8 @@ define(function(require) {
|
||||
var Sunstone = require('sunstone');
|
||||
var Notifier = require('utils/notifier');
|
||||
var Locale = require('utils/locale');
|
||||
var OpenNebula = require('opennebula');
|
||||
var ResourceSelect = require('utils/resource-select');
|
||||
var WebAuthnJSON = require('../../../../bower_components/webauthn-json/dist/index');
|
||||
|
||||
|
||||
/* CONSTANTS */
|
||||
|
||||
@ -54,24 +54,111 @@ define(function(require) {
|
||||
}
|
||||
|
||||
function _html() {
|
||||
var authTokens = [];
|
||||
if (this.element != undefined && this.element.TEMPLATE.SUNSTONE != undefined) {
|
||||
var sunstone_setting = this.element.TEMPLATE.SUNSTONE || {};
|
||||
if (sunstone_setting.TWO_FACTOR_AUTH_SECRET != undefined) {
|
||||
authTokens.push({ 'ID': 0, 'TYPE': 'Authenticator app (HOTP)', 'NAME': '' });
|
||||
}
|
||||
if (sunstone_setting.WEBAUTHN_CREDENTIALS != undefined) {
|
||||
// The handling of double quotes in JSONUtils.template_to_str() is buggy, WEBAUTHN_CREDENTIALS uses single quotes instead
|
||||
var credentials = JSON.parse(sunstone_setting.WEBAUTHN_CREDENTIALS.replace(/\'/g, '"')).cs || {};
|
||||
$.each(credentials, function () {
|
||||
authTokens.push({ 'ID': this.pk, 'TYPE': 'Security key (FIDO2 / U2F / WebAuthn)', 'NAME': this.name });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return TemplateHTML({
|
||||
'dialogId': this.dialogId
|
||||
'dialogId': this.dialogId,
|
||||
'authTokens': authTokens
|
||||
});
|
||||
}
|
||||
|
||||
function _setup(context) {
|
||||
var that = this;
|
||||
var secret = randomBase32();
|
||||
$("#secret", context).val(secret);
|
||||
$('#qr_code', context).html('<img src="'+ '/two_factor_auth_hotp_qr_code?secret=' + secret + '&csrftoken=' + csrftoken + '" width="75%" alt="' + secret + '" />');
|
||||
$("#enable_btn", context).click(function(){
|
||||
var secret = $("#secret", context).val();
|
||||
var token = $("#token", context).val();
|
||||
Sunstone.runAction(
|
||||
"User.enable_sunstone_two_factor_auth",
|
||||
that.element.ID,
|
||||
{current_sunstone_setting: that.sunstone_setting, secret: secret, token: token}
|
||||
);
|
||||
var sunstone_setting = this.element != undefined ? this.element.TEMPLATE.SUNSTONE : {};
|
||||
|
||||
context.on("click", "i.remove-tab", function(){
|
||||
var tr = $(this).closest("tr");
|
||||
$(this).closest("td").html("<i class=\"fas fa-spinner fa-spin\"/>");
|
||||
var tokenid = $(tr).attr("data-tokenid");
|
||||
var sunstone_setting = that.element.TEMPLATE.SUNSTONE || {};
|
||||
if (tokenid == 0) {
|
||||
Sunstone.runAction(
|
||||
"User.disable_sunstone_two_factor_auth",
|
||||
that.element.ID,
|
||||
{ current_sunstone_setting: sunstone_setting }
|
||||
);
|
||||
} else {
|
||||
Sunstone.runAction(
|
||||
"User.disable_sunstone_security_key",
|
||||
that.element.ID,
|
||||
{ current_sunstone_setting: sunstone_setting, tokenid_to_remove: tokenid }
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Register authenticator app button
|
||||
if (sunstone_setting != undefined && sunstone_setting.TWO_FACTOR_AUTH_SECRET != undefined) {
|
||||
$("#register_authenticator_app", context).prop("disabled", true);
|
||||
$("#register_authenticator_app", context).html(Locale.tr("Authenticator app already registered"));
|
||||
}
|
||||
|
||||
context.off("click", "#register_authenticator_app");
|
||||
context.on("click", "#register_authenticator_app", function () {
|
||||
$("#authenticator_app_div").show();
|
||||
$("#security_key_div").hide();
|
||||
var secret = randomBase32();
|
||||
$("#secret", context).val(secret);
|
||||
$('#qr_code', context).html('<img src="' + '/two_factor_auth_hotp_qr_code?secret=' + secret + '&csrftoken=' + csrftoken + '" width="75%" alt="' + secret + '" />');
|
||||
$("#enable_btn", context).click(function () {
|
||||
var secret = $("#secret", context).val();
|
||||
var token = $("#token", context).val();
|
||||
Sunstone.runAction(
|
||||
"User.enable_sunstone_two_factor_auth",
|
||||
that.element.ID,
|
||||
{ current_sunstone_setting: that.sunstone_setting, secret: secret, token: token }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
context.off("click", "#register_security_key");
|
||||
context.on("click", "#register_security_key", function () {
|
||||
$("#authenticator_app_div").hide();
|
||||
$("#security_key_div").show();
|
||||
context.off("click", "#add_btn");
|
||||
$.ajax({
|
||||
url: "webauthn_options_for_create",
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: function (response) {
|
||||
if (!navigator.credentials) {
|
||||
$("#security_key_div").hide();
|
||||
Notifier.onError(null, { error: { message: 'WebAuthn functionality unavailable. Ask your cloud administrator to enable TLS.' } });
|
||||
return;
|
||||
}
|
||||
context.on("click", "#add_btn", function () {
|
||||
WebAuthnJSON.create({ "publicKey": response }).then(publicKeyCredential => {
|
||||
var nickname = $("#nickname", context).val();
|
||||
Sunstone.runAction(
|
||||
"User.enable_sunstone_security_key",
|
||||
that.element.ID,
|
||||
{ nickname: nickname, publicKeyCredential: publicKeyCredential, current_sunstone_setting: that.sunstone_setting }
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
Notifier.onError(null, { error: { message: e.message } });
|
||||
});
|
||||
});
|
||||
},
|
||||
error: function (response) {
|
||||
if (response.status == 501) {
|
||||
$("#security_key_div").hide();
|
||||
Notifier.onError(null, { error: { message: 'WebAuthn functionality unavailable. Ask your cloud administrator to upgrade the Ruby version.' } });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return false;
|
||||
|
@ -26,11 +26,45 @@
|
||||
<div class="large-12 columns">
|
||||
<br/>
|
||||
<p>
|
||||
{{tr "Two factor authentication can be enabled for loging into Sunestone UI."}}
|
||||
{{tr "Two factor authentication can be enabled for logging into Sunstone UI."}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{{#if authTokens}}
|
||||
<div class="row">
|
||||
<div class="large-12 columns">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{tr "Authenticator Type"}}</th>
|
||||
<th>{{tr "Authenticator Name"}}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each authTokens}}
|
||||
<tr data-tokenid="{{ID}}">
|
||||
<td>{{tr TYPE}}</td>
|
||||
<td>{{NAME}}</td>
|
||||
<td>
|
||||
<a href="#"><i class="fas fa-times-circle remove-tab"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="form_buttons row columns">
|
||||
<button id="register_authenticator_app" type="button" class="button radius left">
|
||||
{{tr "Register authenticator app"}}
|
||||
</button>
|
||||
<button id="register_security_key" type="button" class="button radius left">
|
||||
{{tr "Register new security key"}}
|
||||
</button>
|
||||
</div>
|
||||
<div id="authenticator_app_div" class="row fieldset" style="display: none;">
|
||||
<div class="large-6 columns">
|
||||
<div id="qr_code"></div>
|
||||
</div>
|
||||
@ -60,6 +94,22 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="security_key_div" class="row fieldset" style="display: none;">
|
||||
<div class="row">
|
||||
<div class="large-4 medium-6 columns">
|
||||
<label>
|
||||
{{tr "Nickname for this security key"}}
|
||||
<input id="nickname" value="" type="text" class="box" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="large-4 medium-6 columns end">
|
||||
<label> </label>
|
||||
<button id="add_btn" type="button" class="button radius">
|
||||
{{tr "Add"}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="close-button" data-close aria-label="{{tr "Close modal"}}" type="button">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
|
@ -97,27 +97,27 @@ define(function(require) {
|
||||
//===
|
||||
|
||||
// Change two factor auth
|
||||
if (that.element.TEMPLATE.SUNSTONE && that.element.TEMPLATE.SUNSTONE.TWO_FACTOR_AUTH_SECRET) {
|
||||
$("#manage_two_factor_auth", context).html(Locale.tr("Disable"));
|
||||
if (that.element.ID == config['user_id']) {
|
||||
$("#manage_two_factor_auth", context).html(Locale.tr("Manage two factor authentication"));
|
||||
} else {
|
||||
if (that.element.ID == config['user_id']) {
|
||||
$("#manage_two_factor_auth", context).html(Locale.tr("Manage two factor authentication"));
|
||||
if (that.element.TEMPLATE.SUNSTONE && (that.element.TEMPLATE.SUNSTONE.TWO_FACTOR_AUTH_SECRET || (that.element.TEMPLATE.SUNSTONE.WEBAUTHN_CREDENTIALS != undefined && that.element.TEMPLATE.SUNSTONE.WEBAUTHN_CREDENTIALS != "{'cs':[]}"))) {
|
||||
$("#manage_two_factor_auth", context).html(Locale.tr("Disable all authenticators"));
|
||||
} else {
|
||||
$("#manage_two_factor_auth", context).prop("disabled", true);
|
||||
$("#manage_two_factor_auth", context).html(Locale.tr("No"));
|
||||
}
|
||||
}
|
||||
context.off("click", "#manage_two_factor_auth");
|
||||
context.on("click", "#manage_two_factor_auth", function() {
|
||||
var sunstone_setting = that.element.TEMPLATE.SUNSTONE || {};
|
||||
if (sunstone_setting.TWO_FACTOR_AUTH_SECRET) {
|
||||
context.on("click", "#manage_two_factor_auth", function () {
|
||||
var sunstone_setting = that.element.TEMPLATE.SUNSTONE || {};
|
||||
if (that.element.ID != config['user_id'] && (sunstone_setting.TWO_FACTOR_AUTH_SECRET || (sunstone_setting.WEBAUTHN_CREDENTIALS != undefined && sunstone_setting.WEBAUTHN_CREDENTIALS != "{'cs':[]}"))) {
|
||||
Sunstone.runAction(
|
||||
"User.disable_sunstone_two_factor_auth",
|
||||
that.element.ID,
|
||||
{current_sunstone_setting: sunstone_setting}
|
||||
{ current_sunstone_setting: sunstone_setting, delete_all: true }
|
||||
);
|
||||
} else {
|
||||
Sunstone.getDialog(TWO_FACTOR_AUTH_DIALOG_ID).setParams({element: that.element, sunstone_setting: sunstone_setting});
|
||||
Sunstone.getDialog(TWO_FACTOR_AUTH_DIALOG_ID).setParams({ element: that.element, sunstone_setting: sunstone_setting });
|
||||
Sunstone.getDialog(TWO_FACTOR_AUTH_DIALOG_ID).reset();
|
||||
Sunstone.getDialog(TWO_FACTOR_AUTH_DIALOG_ID).show();
|
||||
}
|
||||
|
@ -35,7 +35,7 @@
|
||||
</tr>
|
||||
{{#isTabActionEnabled tabId "User.two_factor_auth"}}
|
||||
<tr>
|
||||
<td class="key_td">{{tr "Two factor authtentication"}}</td>
|
||||
<td class="key_td">{{tr "Two factor authentication"}}</td>
|
||||
<td class="value_td" colspan="2">
|
||||
<button id="manage_two_factor_auth" type="button" class="button small radius secondary" style="min-width:80%">
|
||||
{{tr "Manage two factor authentication"}}
|
||||
|
@ -19,6 +19,7 @@
|
||||
"sprintf": "1.0.3",
|
||||
"jquery-ui": "^1.12.1",
|
||||
"wickedpicker": "https://github.com/OpenNebula/sunstone-deps.git#9398b3f"
|
||||
"webauthn-json": "https://registry.npmjs.org/@github/webauthn-json/-/webauthn-json-0.4.1.tgz"
|
||||
},
|
||||
"authors": [
|
||||
"Daniel Molina <dmolina@opennebula.org>",
|
||||
|
@ -5710,7 +5710,7 @@ msgstr "Dvoufázové ověřování"
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:29
|
||||
#: ../app/tabs/settings-tab/panels/user-config/html.hbs:233
|
||||
msgid "Two factor authentication can be enabled for loging into Sunestone UI."
|
||||
msgid "Two factor authentication can be enabled for logging into Sunestone UI."
|
||||
msgstr "Pro přihlašování do uživatelského rozhraní Sunstone lze zapnout dvoufázové ověřování."
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:40
|
||||
@ -5803,7 +5803,7 @@ msgid "Authentication driver"
|
||||
msgstr "Ovladač ověřování"
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:38
|
||||
msgid "Two factor authtentication"
|
||||
msgid "Two factor authentication"
|
||||
msgstr "Dvoufázové ověřování"
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:51
|
||||
|
@ -5703,7 +5703,7 @@ msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:29
|
||||
#: ../app/tabs/settings-tab/panels/user-config/html.hbs:233
|
||||
msgid "Two factor authentication can be enabled for loging into Sunestone UI."
|
||||
msgid "Two factor authentication can be enabled for logging into Sunestone UI."
|
||||
msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:40
|
||||
@ -5796,7 +5796,7 @@ msgid "Authentication driver"
|
||||
msgstr "Autentifikationsdriver"
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:38
|
||||
msgid "Two factor authtentication"
|
||||
msgid "Two factor authentication"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:51
|
||||
|
@ -5726,7 +5726,7 @@ msgstr "Autenticacion en dos pasos"
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:29
|
||||
#: ../app/tabs/settings-tab/panels/user-config/html.hbs:233
|
||||
msgid "Two factor authentication can be enabled for loging into Sunestone UI."
|
||||
msgid "Two factor authentication can be enabled for logging into Sunestone UI."
|
||||
msgstr "La autenticacion en dos pasos puede ser habilitada para Sunstone UI."
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:40
|
||||
@ -5819,7 +5819,7 @@ msgid "Authentication driver"
|
||||
msgstr "Driver de autenticación"
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:38
|
||||
msgid "Two factor authtentication"
|
||||
msgid "Two factor authentication"
|
||||
msgstr "Autenticacion en dos pasos."
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:51
|
||||
|
@ -5703,7 +5703,7 @@ msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:29
|
||||
#: ../app/tabs/settings-tab/panels/user-config/html.hbs:233
|
||||
msgid "Two factor authentication can be enabled for loging into Sunestone UI."
|
||||
msgid "Two factor authentication can be enabled for logging into Sunestone UI."
|
||||
msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:40
|
||||
@ -5796,7 +5796,7 @@ msgid "Authentication driver"
|
||||
msgstr "Autentimise draiver"
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:38
|
||||
msgid "Two factor authtentication"
|
||||
msgid "Two factor authentication"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:51
|
||||
|
@ -5732,7 +5732,7 @@ msgstr "Identification à deux facteurs"
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:29
|
||||
#: ../app/tabs/settings-tab/panels/user-config/html.hbs:233
|
||||
msgid "Two factor authentication can be enabled for loging into Sunestone UI."
|
||||
msgid "Two factor authentication can be enabled for logging into Sunestone UI."
|
||||
msgstr "L'identification à deux facteurs peut être activée pour se connecter à Sunstone"
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:40
|
||||
@ -5825,7 +5825,7 @@ msgid "Authentication driver"
|
||||
msgstr "Pilote d’authentification"
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:38
|
||||
msgid "Two factor authtentication"
|
||||
msgid "Two factor authentication"
|
||||
msgstr "Identification à deux facteurs"
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:51
|
||||
|
@ -5708,7 +5708,7 @@ msgstr "二要素認証"
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:29
|
||||
#: ../app/tabs/settings-tab/panels/user-config/html.hbs:233
|
||||
msgid "Two factor authentication can be enabled for loging into Sunestone UI."
|
||||
msgid "Two factor authentication can be enabled for logging into Sunestone UI."
|
||||
msgstr "二要素認証はSunstoneユーザインタフェースへのログイン用に有効化できます。"
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:40
|
||||
@ -5801,7 +5801,7 @@ msgid "Authentication driver"
|
||||
msgstr "認証ドライバー"
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:38
|
||||
msgid "Two factor authtentication"
|
||||
msgid "Two factor authentication"
|
||||
msgstr "二要素認証"
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:51
|
||||
|
@ -5713,7 +5713,7 @@ msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:29
|
||||
#: ../app/tabs/settings-tab/panels/user-config/html.hbs:233
|
||||
msgid "Two factor authentication can be enabled for loging into Sunestone UI."
|
||||
msgid "Two factor authentication can be enabled for logging into Sunestone UI."
|
||||
msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:40
|
||||
@ -5806,7 +5806,7 @@ msgid "Authentication driver"
|
||||
msgstr "Authenticatie driver"
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:38
|
||||
msgid "Two factor authtentication"
|
||||
msgid "Two factor authentication"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:51
|
||||
|
@ -5715,7 +5715,7 @@ msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:29
|
||||
#: ../app/tabs/settings-tab/panels/user-config/html.hbs:233
|
||||
msgid "Two factor authentication can be enabled for loging into Sunestone UI."
|
||||
msgid "Two factor authentication can be enabled for logging into Sunestone UI."
|
||||
msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:40
|
||||
@ -5808,7 +5808,7 @@ msgid "Authentication driver"
|
||||
msgstr "Driver de Authenticação"
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:38
|
||||
msgid "Two factor authtentication"
|
||||
msgid "Two factor authentication"
|
||||
msgstr "Autênticação de dois fatores"
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:51
|
||||
|
@ -5705,7 +5705,7 @@ msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:29
|
||||
#: ../app/tabs/settings-tab/panels/user-config/html.hbs:233
|
||||
msgid "Two factor authentication can be enabled for loging into Sunestone UI."
|
||||
msgid "Two factor authentication can be enabled for logging into Sunestone UI."
|
||||
msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:40
|
||||
@ -5798,7 +5798,7 @@ msgid "Authentication driver"
|
||||
msgstr "Driver de autenticação"
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:38
|
||||
msgid "Two factor authtentication"
|
||||
msgid "Two factor authentication"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:51
|
||||
|
@ -5724,7 +5724,7 @@ msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:29
|
||||
#: ../app/tabs/settings-tab/panels/user-config/html.hbs:233
|
||||
msgid "Two factor authentication can be enabled for loging into Sunestone UI."
|
||||
msgid "Two factor authentication can be enabled for logging into Sunestone UI."
|
||||
msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:40
|
||||
@ -5817,7 +5817,7 @@ msgid "Authentication driver"
|
||||
msgstr "Драйвер аутентификации"
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:38
|
||||
msgid "Two factor authtentication"
|
||||
msgid "Two factor authentication"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:51
|
||||
|
@ -5706,7 +5706,7 @@ msgstr "Dvojfaktorová autentifikácia"
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:29
|
||||
#: ../app/tabs/settings-tab/panels/user-config/html.hbs:233
|
||||
msgid "Two factor authentication can be enabled for loging into Sunestone UI."
|
||||
msgid "Two factor authentication can be enabled for logging into Sunestone UI."
|
||||
msgstr "Aktivovanie dvojfaktorovej autentifikácie pre prihlásenie sa do Sunstone."
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:40
|
||||
@ -5799,7 +5799,7 @@ msgid "Authentication driver"
|
||||
msgstr "Ovládač autentifikácie"
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:38
|
||||
msgid "Two factor authtentication"
|
||||
msgid "Two factor authentication"
|
||||
msgstr "Dvojfaktorová autentifikácia"
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:51
|
||||
|
@ -5702,7 +5702,7 @@ msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:29
|
||||
#: ../app/tabs/settings-tab/panels/user-config/html.hbs:233
|
||||
msgid "Two factor authentication can be enabled for loging into Sunestone UI."
|
||||
msgid "Two factor authentication can be enabled for logging into Sunestone UI."
|
||||
msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:40
|
||||
@ -5795,7 +5795,7 @@ msgid "Authentication driver"
|
||||
msgstr "Authentication driver"
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:38
|
||||
msgid "Two factor authtentication"
|
||||
msgid "Two factor authentication"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:51
|
||||
|
@ -5706,7 +5706,7 @@ msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:29
|
||||
#: ../app/tabs/settings-tab/panels/user-config/html.hbs:233
|
||||
msgid "Two factor authentication can be enabled for loging into Sunestone UI."
|
||||
msgid "Two factor authentication can be enabled for logging into Sunestone UI."
|
||||
msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:40
|
||||
@ -5799,7 +5799,7 @@ msgid "Authentication driver"
|
||||
msgstr "Kimlik doğrulama sürücüsü"
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:38
|
||||
msgid "Two factor authtentication"
|
||||
msgid "Two factor authentication"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:51
|
||||
|
@ -5706,7 +5706,7 @@ msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:29
|
||||
#: ../app/tabs/settings-tab/panels/user-config/html.hbs:233
|
||||
msgid "Two factor authentication can be enabled for loging into Sunestone UI."
|
||||
msgid "Two factor authentication can be enabled for logging into Sunestone UI."
|
||||
msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:40
|
||||
@ -5799,7 +5799,7 @@ msgid "Authentication driver"
|
||||
msgstr "Kimlik doğrulama sürücüsü"
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:38
|
||||
msgid "Two factor authtentication"
|
||||
msgid "Two factor authentication"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:51
|
||||
|
@ -5716,7 +5716,7 @@ msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:29
|
||||
#: ../app/tabs/settings-tab/panels/user-config/html.hbs:233
|
||||
msgid "Two factor authentication can be enabled for loging into Sunestone UI."
|
||||
msgid "Two factor authentication can be enabled for logging into Sunestone UI."
|
||||
msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/dialogs/two-factor-auth/html.hbs:40
|
||||
@ -5809,7 +5809,7 @@ msgid "Authentication driver"
|
||||
msgstr "身份验证驱动器"
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:38
|
||||
msgid "Two factor authtentication"
|
||||
msgid "Two factor authentication"
|
||||
msgstr ""
|
||||
|
||||
#: ../app/tabs/users-tab/panels/auth/html.hbs:51
|
||||
|
@ -46,9 +46,16 @@ body#login{
|
||||
background-image: linear-gradient(180deg, #F6F6F6 0%, rgba(0,0,0,0.2) 51%, rgba(0,0,0,0.3) 100%);
|
||||
|
||||
&:after{
|
||||
content: "Login";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&#login_btn:after, &#two_factor_auth_login_btn:after{
|
||||
content: "Login";
|
||||
}
|
||||
|
||||
&#webauthn_login_btn:after{
|
||||
content: "Use security key";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,6 +112,13 @@ require 'CloudAuth'
|
||||
require 'SunstoneServer'
|
||||
require 'SunstoneViews'
|
||||
|
||||
begin
|
||||
require "SunstoneWebAuthn"
|
||||
webauthn_avail = true
|
||||
rescue LoadError
|
||||
webauthn_avail = false
|
||||
end
|
||||
|
||||
##############################################################################
|
||||
# Configuration
|
||||
##############################################################################
|
||||
@ -128,6 +135,7 @@ if $conf[:one_xmlrpc_timeout]
|
||||
end
|
||||
|
||||
$conf[:debug_level] ||= 3
|
||||
$conf[:webauthn_avail] = webauthn_avail
|
||||
|
||||
# Set Sunstone Session Timeout
|
||||
$conf[:session_expire_time] ||= 3600
|
||||
@ -220,6 +228,17 @@ rescue StandardError => e
|
||||
exit -1
|
||||
end
|
||||
|
||||
if $conf[:webauthn_avail]
|
||||
begin
|
||||
SunstoneWebAuthn.configure($conf)
|
||||
rescue => e
|
||||
logger.error {
|
||||
"Error initializing WebAuthn" }
|
||||
logger.error { e.message }
|
||||
exit -1
|
||||
end
|
||||
end
|
||||
|
||||
#start VNC proxy
|
||||
|
||||
$vnc = OpenNebulaVNC.new($conf, logger)
|
||||
@ -234,7 +253,6 @@ $addons = OpenNebulaAddons.new(logger)
|
||||
|
||||
DEFAULT_TABLE_ORDER = "desc"
|
||||
DEFAULT_PAGE_LENGTH = 10
|
||||
DEFAULT_TWO_FACTOR_AUTH = false
|
||||
|
||||
SUPPORT = {
|
||||
:zendesk_url => "https://opennebula.zendesk.com/api/v2",
|
||||
@ -345,21 +363,24 @@ helpers do
|
||||
end
|
||||
|
||||
# two factor_auth
|
||||
two_factor_auth =
|
||||
if user[TWO_FACTOR_AUTH_SECRET_XPATH]
|
||||
user[TWO_FACTOR_AUTH_SECRET_XPATH] != ""
|
||||
else
|
||||
DEFAULT_TWO_FACTOR_AUTH
|
||||
end
|
||||
if two_factor_auth
|
||||
isHOTPConfigured = (user[TWO_FACTOR_AUTH_SECRET_XPATH] && user[TWO_FACTOR_AUTH_SECRET_XPATH] != "")
|
||||
isWebAuthnConfigured = $conf[:webauthn_avail] && SunstoneWebAuthn.getCredentialIDsForUser(user.id).length > 0
|
||||
if isHOTPConfigured || isWebAuthnConfigured
|
||||
two_factor_auth_token = params[:two_factor_auth_token]
|
||||
if !two_factor_auth_token || two_factor_auth_token == ""
|
||||
return [202, { code: "two_factor_auth" }.to_json]
|
||||
else
|
||||
unless Sunstone2FAuth.authenticate(user[TWO_FACTOR_AUTH_SECRET_XPATH], two_factor_auth_token)
|
||||
logger.info { "Unauthorized two factor authentication login attempt" }
|
||||
return [401, ""]
|
||||
end
|
||||
return [202, { code: "two_factor_auth", uid: user.id }.to_json]
|
||||
end
|
||||
serverResponse =
|
||||
isTwoFactorAuthSuccessful = false
|
||||
if isHOTPConfigured && Sunstone2FAuth.authenticate(user[TWO_FACTOR_AUTH_SECRET_XPATH], two_factor_auth_token)
|
||||
isTwoFactorAuthSuccessful = true
|
||||
end
|
||||
if isWebAuthnConfigured && SunstoneWebAuthn.authenticate(user.id, two_factor_auth_token)
|
||||
isTwoFactorAuthSuccessful = true
|
||||
end
|
||||
if !isTwoFactorAuthSuccessful
|
||||
logger.info { "Unauthorized two factor authentication login attempt" }
|
||||
return [401, "Two factor authentication failed"]
|
||||
end
|
||||
end
|
||||
|
||||
@ -474,7 +495,7 @@ before do
|
||||
@request_body = request.body.read
|
||||
request.body.rewind
|
||||
|
||||
unless %w(/ /login /vnc /spice /version).include?(request.path)
|
||||
unless %w(/ /login /vnc /spice /version /webauthn_options_for_get).include?(request.path)
|
||||
halt [401, "csrftoken"] unless authorized? && valid_csrftoken?
|
||||
end
|
||||
|
||||
@ -546,7 +567,7 @@ before do
|
||||
end
|
||||
|
||||
after do
|
||||
unless request.path=='/login' || request.path=='/' || request.path=='/'
|
||||
unless request.path == '/login' || request.path == '/' || request.path == '/'
|
||||
# secure cookies
|
||||
if request.scheme == 'https'
|
||||
env['rack.session.options'][:secure] = true
|
||||
@ -611,6 +632,32 @@ get '/two_factor_auth_hotp_qr_code' do
|
||||
[200, qr_code.as_svg]
|
||||
end
|
||||
|
||||
get '/webauthn_options_for_create' do
|
||||
content_type 'application/json'
|
||||
if !$conf[:webauthn_avail]
|
||||
return [501, '']
|
||||
end
|
||||
options = SunstoneWebAuthn.getOptionsForCreate(session[:user_id], session[:user])
|
||||
[200, options]
|
||||
end
|
||||
|
||||
get '/webauthn_options_for_get' do
|
||||
content_type 'application/json'
|
||||
if !$conf[:webauthn_avail]
|
||||
return [501, '']
|
||||
end
|
||||
begin
|
||||
user_id = Integer(params[:uid]).to_s
|
||||
rescue ArgumentError => e
|
||||
return [401, '']
|
||||
end
|
||||
options = SunstoneWebAuthn.getOptionsForGet(user_id)
|
||||
if options.nil?
|
||||
return [204, '']
|
||||
end
|
||||
[200, options]
|
||||
end
|
||||
|
||||
get '/vnc' do
|
||||
content_type 'text/html', :charset => 'utf-8'
|
||||
if !authorized?
|
||||
|
@ -34,19 +34,38 @@
|
||||
<% end %>
|
||||
</form>
|
||||
<div id="two_factor_auth" class="border" style="display: none;">
|
||||
<div class="border columns small-6 small-centered small-offset-3 text-center" id="login">
|
||||
<div class="content">
|
||||
<label class="text-left">
|
||||
Two Factor Token
|
||||
<input value="" type="number" maxlength="15" name="two_factor_auth_token" id="two_factor_auth_token" class="box"/>
|
||||
</label>
|
||||
<div class="row buttons small-collapse">
|
||||
<div class="columns small-offset-6 small-6 text-right">
|
||||
<button id="two_factor_auth_login" type="button"></button>
|
||||
<form id="two_factor_form" method="post" class="row">
|
||||
<div class="border columns small-6 small-centered small-offset-3 text-center" id="login">
|
||||
<div class="content">
|
||||
<div class="fieldset">
|
||||
<label class="text-left">
|
||||
Enter the six-digit code from your authenticator app
|
||||
<input value="" type="text" maxlength="15" name="two_factor_auth_token" id="two_factor_auth_token" class="box"/>
|
||||
</label>
|
||||
<div class="row buttons small-collapse">
|
||||
<div class="columns small-offset-6 small-6 text-right">
|
||||
<button id="two_factor_auth_login_btn" type="button"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="webauthn_login_div">
|
||||
<div class="text-center">
|
||||
- or -
|
||||
</div>
|
||||
<div class="fieldset">
|
||||
<div class="small-offset-2 small-8 text-center">
|
||||
When you are ready to authenticate with your security key, press the button below.
|
||||
<div class="row buttons small-collapse">
|
||||
<div class="columns text-center">
|
||||
<button id="webauthn_login_btn" type="button"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="small-offset-3 small-6 error-place">
|
||||
<div id="error_box" class="alert alert-box callout hidden secondary small" style="display: none">
|
||||
|
Loading…
Reference in New Issue
Block a user