<commit_before>#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

# -------------------------------------------------------------------------- #
# Copyright 2002-2020, 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.                                             #
#--------------------------------------------------------------------------- #

ONE_LOCATION = ENV['ONE_LOCATION']

if !ONE_LOCATION
    LOG_LOCATION      = '/var/log/one'
    VAR_LOCATION      = '/var/lib/one'
    ETC_LOCATION      = '/etc/one'
    SHARE_LOCATION    = '/usr/share/one'
    RUBY_LIB_LOCATION = '/usr/lib/one/ruby'
    GEMS_LOCATION     = '/usr/share/one/gems'
    SUNSTONE_LOCATION = '/usr/lib/one/sunstone'
else
    VAR_LOCATION      = ONE_LOCATION + '/var'
    LOG_LOCATION      = ONE_LOCATION + '/var'
    ETC_LOCATION      = ONE_LOCATION + '/etc'
    SHARE_LOCATION    = ONE_LOCATION + '/share'
    RUBY_LIB_LOCATION = ONE_LOCATION + '/lib/ruby'
    GEMS_LOCATION     = ONE_LOCATION + '/share/gems'
    SUNSTONE_LOCATION = ONE_LOCATION + '/lib/sunstone'
end

SUNSTONE_AUTH             = VAR_LOCATION + '/.one/sunstone_auth'
SUNSTONE_LOG              = LOG_LOCATION + '/sunstone.log'
CONFIGURATION_FILE        = ETC_LOCATION + '/sunstone-server.conf'
PLUGIN_CONFIGURATION_FILE = ETC_LOCATION + '/sunstone-plugins.yaml'
LOGOS_CONFIGURATION_FILE  = ETC_LOCATION + '/sunstone-logos.yaml'

SUNSTONE_ROOT_DIR = File.dirname(__FILE__)

if File.directory?(GEMS_LOCATION)
    $LOAD_PATH.reject! {|l| l =~ /vendor_ruby/ }
    require 'rubygems'
    Gem.use_paths(File.realpath(GEMS_LOCATION))

    # for some platforms, we redistribute newer base Ruby gems which
    # should be loaded instead of default ones in the distributions
    %w[openssl json].each do |name|
        begin
            gem name
        rescue LoadError
            # ignore
        end
    end
end

$LOAD_PATH << RUBY_LIB_LOCATION
$LOAD_PATH << RUBY_LIB_LOCATION + '/cloud'
$LOAD_PATH << SUNSTONE_ROOT_DIR
$LOAD_PATH << SUNSTONE_ROOT_DIR + '/models'
$LOAD_PATH << SUNSTONE_ROOT_DIR + '/models/OpenNebula2FA'

DISPLAY_NAME_XPATH = 'TEMPLATE/SUNSTONE/DISPLAY_NAME'
TABLE_ORDER_XPATH = 'TEMPLATE/SUNSTONE/TABLE_ORDER'
DEFAULT_VIEW_XPATH = 'TEMPLATE/SUNSTONE/DEFAULT_VIEW'
GROUP_ADMIN_DEFAULT_VIEW_XPATH = 'TEMPLATE/SUNSTONE/GROUP_ADMIN_DEFAULT_VIEW'
TABLE_DEFAULT_PAGE_LENGTH_XPATH = 'TEMPLATE/SUNSTONE/TABLE_DEFAULT_PAGE_LENGTH'
LANG_XPATH = 'TEMPLATE/SUNSTONE/LANG'
TWO_FACTOR_AUTH_SECRET_XPATH = 'TEMPLATE/SUNSTONE/TWO_FACTOR_AUTH_SECRET'
DEFAULT_ZONE_ENDPOINT_XPATH = 'TEMPLATE/SUNSTONE/DEFAULT_ZONE_ENDPOINT'

ONED_CONF_OPTS = {
    # If no costs are defined in oned.conf these values will be used
    'DEFAULT_COST' => {
        'CPU_COST' => 0,
        'MEMORY_COST' => 0,
        'DISK_COST' => 0
    },
    # Only these values will be shown when retrieving oned.conf from the browser
    'ALLOWED_KEYS' => [
        'DEFAULT_COST',
        'DS_MAD_CONF',
        'MARKET_MAD_CONF',
        'VM_MAD',
        'IM_MAD',
        'AUTH_MAD'
    ],
    # Generate an array if there is only 1 element
    'ARRAY_KEYS' => [
        'DS_MAD_CONF',
        'MARKET_MAD_CONF',
        'VM_MAD',
        'IM_MAD'
    ]
}

##############################################################################
# Required libraries
##############################################################################
require 'rubygems'
require 'sinatra'
require 'erb'
require 'yaml'
require 'securerandom'
require 'tmpdir'
require 'fileutils'
require 'base64'
require 'rexml/document'
require 'uri'
require 'open3'

require "sunstone_qr_code"
require "sunstone_optp"
require "sunstone_2f_auth"
require 'CloudAuth'
require 'SunstoneServer'
require 'SunstoneViews'

begin
    require "SunstoneWebAuthn"
    webauthn_avail = true
rescue LoadError
    webauthn_avail = false
end

##############################################################################
# Configuration
##############################################################################

begin
    $conf = YAML.load_file(CONFIGURATION_FILE)
rescue Exception => e
    STDERR.puts "Error parsing config file #{CONFIGURATION_FILE}: #{e.message}"
    exit 1
end

if $conf[:one_xmlrpc_timeout]
    ENV['ONE_XMLRPC_TIMEOUT'] = $conf[:one_xmlrpc_timeout].to_s unless ENV['ONE_XMLRPC_TIMEOUT']
end

$conf[:debug_level] ||= 3
$conf[:webauthn_avail] = webauthn_avail

# Set Sunstone Session Timeout
$conf[:session_expire_time] ||= 3600

# Set the TMPDIR environment variable for uploaded images
ENV['TMPDIR']=$conf[:tmpdir] if $conf[:tmpdir]

CloudServer.print_configuration($conf)

#Sinatra configuration

set :config, $conf
set :bind, $conf[:host]
set :port, $conf[:port]

if (proxy = $conf[:proxy])
    ENV['http_proxy'] = proxy
    ENV['HTTP_PROXY'] = proxy
end

if (no_proxy = $conf[:no_proxy])
    ENV['no_proxy'] = no_proxy
    ENV['NO_PROXY'] = no_proxy
end

if ENV["APP_ENV"] &&
    !ENV["APP_ENV"].empty? &&
    %w{production development test}.include?(ENV["APP_ENV"])
    set :environment, ENV["APP_ENV"].to_sym
else
    case $conf[:env]
    when "dev"
        set :environment, :development
    else
        set :environment, :production
    end
end

case $conf[:sessions]
when 'memory', nil
    use Rack::Session::Pool, :key => 'sunstone'
when 'memcache'
    memcache_server=$conf[:memcache_host]+':'<<
        $conf[:memcache_port].to_s

    STDERR.puts memcache_server

    use Rack::Session::Memcache,
        :memcache_server => memcache_server,
        :namespace => $conf[:memcache_namespace]
when 'memcache-dalli'
    require 'rack/session/dalli'
    memcache_server=$conf[:memcache_host]+':'<<
        $conf[:memcache_port].to_s

    STDERR.puts memcache_server

    use Rack::Session::Dalli,
      :memcache_server => memcache_server,
      :namespace => $conf[:memcache_namespace],
      :cache => Dalli::Client.new
else
    STDERR.puts "Wrong value for :sessions in configuration file"
    exit(-1)
end

use Rack::Deflater

# Enable logger

include CloudLogger
logger=enable_logging(SUNSTONE_LOG, $conf[:debug_level].to_i)

begin
    ENV["ONE_CIPHER_AUTH"] = SUNSTONE_AUTH
    $cloud_auth = CloudAuth.new($conf, logger)
rescue => e
    logger.error {
        "Error initializing authentication system" }
    logger.error { e.message }
    exit -1
end

set :cloud_auth, $cloud_auth

begin
    $views_config = SunstoneViews.new($conf[:mode])
rescue StandardError => e
    logger.error { e.message }
    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)

#init Guacamole server
$guac = OpenNebulaGuac.new($conf, logger)

configure do
    set :run, false
    set :vnc, $vnc
    set :erb, :trim => '-'
end

$addons = OpenNebulaAddons.new(logger)

DEFAULT_TABLE_ORDER = "desc"
DEFAULT_PAGE_LENGTH = 10

SUPPORT = {
    :zendesk_url => "https://opennebula.zendesk.com/api/v2",
    :custom_field_version => 391130,
    :custom_field_severity => 391197,
    :author_id => 21231023,
    :author_name => "<NAME>",
    :support_subscription => "https://opennebula.io/support/",
    :account => "https://opennebula.io/buy-support",
    :docs => "https://docs.opennebula.io/5.13/",
    :community => "https://opennebula.io/usec",
    :project => "OpenNebula"
}

UPGRADE = {
    :upgrade => "<span style='color: #0098c3' id='itemUpdate' style='display:none;'>Upgrade Available</span>&nbsp;<span style='color:#DC7D24'><i class='fas fa-exclamation-circle'></i></span>",
    :no_upgrade => "",
    :url => "https://opennebula.io/use/"
}

##############################################################################
# Helpers
##############################################################################
helpers do
    def valid_csrftoken?
        csrftoken = nil

        if params[:csrftoken]
            csrftoken = params[:csrftoken]
        else
            begin
                # Extract "csrftoken" and remove from @request_body if present
                request_body  = JSON.parse(@request_body)
                csrftoken     = request_body.delete("csrftoken")
                @request_body = request_body.to_json
            rescue
            end
        end

        session[:csrftoken] && session[:csrftoken] == csrftoken
    end

    def authorized?
        session[:ip] && session[:ip] == request.ip
    end

    def build_conf_locals
        logos_conf = nil
        begin
            logos_conf = YAML.load_file(LOGOS_CONFIGURATION_FILE)
        rescue Exception => e
            logger.error { "Error parsing config file #{LOGOS_CONFIGURATION_FILE}: #{e.message}" }
            error 500, ""
        end
        serveradmin_client = $cloud_auth.client(nil, session[:active_zone_endpoint])
        rc = OpenNebula::System.new(serveradmin_client).get_configuration
        if OpenNebula.is_error?(rc)
            logger.error { rc.message }
            error 500, ""
        end
        oned_conf_template = rc.to_hash()['OPENNEBULA_CONFIGURATION']
        oned_conf = {}
        ONED_CONF_OPTS['ALLOWED_KEYS'].each do |key|
            value = oned_conf_template[key]
            if key == 'DEFAULT_COST'
                if value
                    oned_conf[key] = value
                else
                    oned_conf[key] = ONED_CONF_OPTS['DEFAULT_COST']
                end
            else
                if ONED_CONF_OPTS['ARRAY_KEYS'].include?(key) && !value.is_a?(Array)
                    oned_conf[key] = [value]
                else
                    oned_conf[key] = value
                end
            end
        end
        $conf[:locals] = {
            :logos_conf => logos_conf,
            :oned_conf  => oned_conf,
            :support    => SUPPORT,
            :upgrade    => UPGRADE
        }
    end

    def build_session
        begin
            result = $cloud_auth.auth(request.env, params)
        rescue StandardError => e
            logger.error { e.message }
            return [500, '']
        end

        if result.nil?
            logger.info { 'Unauthorized login attempt' }
            return [401, '']
        end

        client  = $cloud_auth.client(result)
        user_id = OpenNebula::User::SELF

        user    = OpenNebula::User.new_with_id(user_id, client)
        rc = user.info
        if OpenNebula.is_error?(rc)
            logger.error { rc.message }
            return [500, '']
        end

        # 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", 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<commit_msg>M #-: Clean sunstone-server.rb (#194)<commit_after>#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

# -------------------------------------------------------------------------- #
# Copyright 2002-2020, 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.                                             #
#--------------------------------------------------------------------------- #

ONE_LOCATION = ENV['ONE_LOCATION']

if !ONE_LOCATION
    LOG_LOCATION      = '/var/log/one'
    VAR_LOCATION      = '/var/lib/one'
    ETC_LOCATION      = '/etc/one'
    SHARE_LOCATION    = '/usr/share/one'
    RUBY_LIB_LOCATION = '/usr/lib/one/ruby'
    GEMS_LOCATION     = '/usr/share/one/gems'
    SUNSTONE_LOCATION = '/usr/lib/one/sunstone'
else
    VAR_LOCATION      = ONE_LOCATION + '/var'
    LOG_LOCATION      = ONE_LOCATION + '/var'
    ETC_LOCATION      = ONE_LOCATION + '/etc'
    SHARE_LOCATION    = ONE_LOCATION + '/share'
    RUBY_LIB_LOCATION = ONE_LOCATION + '/lib/ruby'
    GEMS_LOCATION     = ONE_LOCATION + '/share/gems'
    SUNSTONE_LOCATION = ONE_LOCATION + '/lib/sunstone'
end

SUNSTONE_AUTH             = VAR_LOCATION + '/.one/sunstone_auth'
SUNSTONE_LOG              = LOG_LOCATION + '/sunstone.log'
CONFIGURATION_FILE        = ETC_LOCATION + '/sunstone-server.conf'
PLUGIN_CONFIGURATION_FILE = ETC_LOCATION + '/sunstone-plugins.yaml'
LOGOS_CONFIGURATION_FILE  = ETC_LOCATION + '/sunstone-logos.yaml'

SUNSTONE_ROOT_DIR = File.dirname(__FILE__)

if File.directory?(GEMS_LOCATION)
    $LOAD_PATH.reject! {|l| l =~ /vendor_ruby/ }
    require 'rubygems'
    Gem.use_paths(File.realpath(GEMS_LOCATION))

    # for some platforms, we redistribute newer base Ruby gems which
    # should be loaded instead of default ones in the distributions
    %w[openssl json].each do |name|
        begin
            gem name
        rescue LoadError
            # ignore
        end
    end
end

$LOAD_PATH << RUBY_LIB_LOCATION
$LOAD_PATH << RUBY_LIB_LOCATION + '/cloud'
$LOAD_PATH << SUNSTONE_ROOT_DIR
$LOAD_PATH << SUNSTONE_ROOT_DIR + '/models'
$LOAD_PATH << SUNSTONE_ROOT_DIR + '/models/OpenNebula2FA'

DISPLAY_NAME_XPATH = 'TEMPLATE/SUNSTONE/DISPLAY_NAME'
TABLE_ORDER_XPATH = 'TEMPLATE/SUNSTONE/TABLE_ORDER'
DEFAULT_VIEW_XPATH = 'TEMPLATE/SUNSTONE/DEFAULT_VIEW'
GROUP_ADMIN_DEFAULT_VIEW_XPATH = 'TEMPLATE/SUNSTONE/GROUP_ADMIN_DEFAULT_VIEW'
TABLE_DEFAULT_PAGE_LENGTH_XPATH = 'TEMPLATE/SUNSTONE/TABLE_DEFAULT_PAGE_LENGTH'
LANG_XPATH = 'TEMPLATE/SUNSTONE/LANG'
TWO_FACTOR_AUTH_SECRET_XPATH = 'TEMPLATE/SUNSTONE/TWO_FACTOR_AUTH_SECRET'
DEFAULT_ZONE_ENDPOINT_XPATH = 'TEMPLATE/SUNSTONE/DEFAULT_ZONE_ENDPOINT'

ONED_CONF_OPTS = {
    # If no costs are defined in oned.conf these values will be used
    'DEFAULT_COST' => {
        'CPU_COST' => 0,
        'MEMORY_COST' => 0,
        'DISK_COST' => 0
    },
    # Only these values will be shown when retrieving oned.conf from the browser
    'ALLOWED_KEYS' => [
        'DEFAULT_COST',
        'DS_MAD_CONF',
        'MARKET_MAD_CONF',
        'VM_MAD',
        'IM_MAD',
        'AUTH_MAD'
    ],
    # Generate an array if there is only 1 element
    'ARRAY_KEYS' => [
        'DS_MAD_CONF',
        'MARKET_MAD_CONF',
        'VM_MAD',
        'IM_MAD'
    ]
}

##############################################################################
# Required libraries
##############################################################################
require 'rubygems'
require 'sinatra'
require 'erb'
require 'yaml'
require 'securerandom'
require 'tmpdir'
require 'fileutils'
require 'base64'
require 'rexml/document'
require 'uri'
require 'open3'

require "sunstone_qr_code"
require "sunstone_optp"
require "sunstone_2f_auth"
require 'CloudAuth'
require 'SunstoneServer'
require 'SunstoneViews'

begin
    require "SunstoneWebAuthn"
    webauthn_avail = true
rescue LoadError
    webauthn_avail = false
end

##############################################################################
# Configuration
##############################################################################

begin
    $conf = YAML.load_file(CONFIGURATION_FILE)
rescue Exception => e
    STDERR.puts "Error parsing config file #{CONFIGURATION_FILE}: #{e.message}"
    exit 1
end

if $conf[:one_xmlrpc_timeout]
    ENV['ONE_XMLRPC_TIMEOUT'] = $conf[:one_xmlrpc_timeout].to_s unless ENV['ONE_XMLRPC_TIMEOUT']
end

$conf[:debug_level] ||= 3
$conf[:webauthn_avail] = webauthn_avail

# Set Sunstone Session Timeout
$conf[:session_expire_time] ||= 3600

# Set the TMPDIR environment variable for uploaded images
ENV['TMPDIR']=$conf[:tmpdir] if $conf[:tmpdir]

CloudServer.print_configuration($conf)

#Sinatra configuration

set :config, $conf
set :bind, $conf[:host]
set :port, $conf[:port]

if (proxy = $conf[:proxy])
    ENV['http_proxy'] = proxy
    ENV['HTTP_PROXY'] = proxy
end

if (no_proxy = $conf[:no_proxy])
    ENV['no_proxy'] = no_proxy
    ENV['NO_PROXY'] = no_proxy
end

if ENV["APP_ENV"] &&
    !ENV["APP_ENV"].empty? &&
    %w{production development test}.include?(ENV["APP_ENV"])
    set :environment, ENV["APP_ENV"].to_sym
else
    case $conf[:env]
    when "dev"
        set :environment, :development
    else
        set :environment, :production
    end
end

case $conf[:sessions]
when 'memory', nil
    use Rack::Session::Pool, :key => 'sunstone'
when 'memcache'
    memcache_server=$conf[:memcache_host]+':'<<
        $conf[:memcache_port].to_s

    STDERR.puts memcache_server

    use Rack::Session::Memcache,
        :memcache_server => memcache_server,
        :namespace => $conf[:memcache_namespace]
when 'memcache-dalli'
    require 'rack/session/dalli'
    memcache_server=$conf[:memcache_host]+':'<<
        $conf[:memcache_port].to_s

    STDERR.puts memcache_server

    use Rack::Session::Dalli,
      :memcache_server => memcache_server,
      :namespace => $conf[:memcache_namespace],
      :cache => Dalli::Client.new
else
    STDERR.puts "Wrong value for :sessions in configuration file"
    exit(-1)
end

use Rack::Deflater

# Enable logger

include CloudLogger
logger=enable_logging(SUNSTONE_LOG, $conf[:debug_level].to_i)

begin
    ENV["ONE_CIPHER_AUTH"] = SUNSTONE_AUTH
    $cloud_auth = CloudAuth.new($conf, logger)
rescue => e
    logger.error {
        "Error initializing authentication system" }
    logger.error { e.message }
    exit -1
end

set :cloud_auth, $cloud_auth

begin
    $views_config = SunstoneViews.new($conf[:mode])
rescue StandardError => e
    logger.error { e.message }
    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)

#init Guacamole server
$guac = OpenNebulaGuac.new($conf, logger)

configure do
    set :run, false
    set :vnc, $vnc
    set :erb, :trim => '-'
end

$addons = OpenNebulaAddons.new(logger)

DEFAULT_TABLE_ORDER = "desc"
DEFAULT_PAGE_LENGTH = 10

SUPPORT = {
    :zendesk_url => "https://opennebula.zendesk.com/api/v2",
    :custom_field_version => 391130,
    :custom_field_severity => 391197,
    :author_id => 21231023,
    :author_name => "OpenNebula Support Team",
    :support_subscription => "https://opennebula.io/support/",
    :account => "https://opennebula.io/buy-support",
    :docs => "https://docs.opennebula.io/5.13/",
    :community => "https://opennebula.io/usec",
    :project => "OpenNebula"
}

UPGRADE = {
    :upgrade => "<span style='color: #0098c3' id='itemUpdate' style='display:none;'>Upgrade Available</span>&nbsp;<span style='color:#DC7D24'><i class='fas fa-exclamation-circle'></i></span>",
    :no_upgrade => "",
    :url => "https://opennebula.io/use/"
}

##############################################################################
# Helpers
##############################################################################
helpers do
    def valid_csrftoken?
        csrftoken = nil

        if params[:csrftoken]
            csrftoken = params[:csrftoken]
        else
            begin
                # Extract "csrftoken" and remove from @request_body if present
                request_body  = JSON.parse(@request_body)
                csrftoken     = request_body.delete("csrftoken")
                @request_body = request_body.to_json
            rescue
            end
        end

        session[:csrftoken] && session[:csrftoken] == csrftoken
    end

    def authorized?
        session[:ip] && session[:ip] == request.ip
    end

    def build_conf_locals
        logos_conf = nil
        begin
            logos_conf = YAML.load_file(LOGOS_CONFIGURATION_FILE)
        rescue Exception => e
            logger.error { "Error parsing config file #{LOGOS_CONFIGURATION_FILE}: #{e.message}" }
            error 500, ""
        end
        serveradmin_client = $cloud_auth.client(nil, session[:active_zone_endpoint])
        rc = OpenNebula::System.new(serveradmin_client).get_configuration
        if OpenNebula.is_error?(rc)
            logger.error { rc.message }
            error 500, ""
        end
        oned_conf_template = rc.to_hash()['OPENNEBULA_CONFIGURATION']
        oned_conf = {}
        ONED_CONF_OPTS['ALLOWED_KEYS'].each do |key|
            value = oned_conf_template[key]
            if key == 'DEFAULT_COST'
                if value
                    oned_conf[key] = value
                else
                    oned_conf[key] = ONED_CONF_OPTS['DEFAULT_COST']
                end
            else
                if ONED_CONF_OPTS['ARRAY_KEYS'].include?(key) && !value.is_a?(Array)
                    oned_conf[key] = [value]
                else
                    oned_conf[key] = value
                end
            end
        end
        $conf[:locals] = {
            :logos_conf => logos_conf,
            :oned_conf  => oned_conf,
            :support    => SUPPORT,
            :upgrade    => UPGRADE
        }
    end

    def build_session
        begin
            result = $cloud_auth.auth(request.env, params)
        rescue StandardError => e
            logger.error { e.message }
            return [500, '']
        end

        if result.nil?
            logger.info { 'Unauthorized login attempt' }
            return [401, '']
        end

        client  = $cloud_auth.client(result)
        user_id = OpenNebula::User::SELF

        user    = OpenNebula::User.new_with_id(user_id, client)
        rc = user.info
        if OpenNebula.is_error?(rc)
            logger.error { rc.message }
            return [500, '']
        end

        # 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", uid: user.id }.to_json]
            end
            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
{'fraction_non_alphanumeric': 0.15256907416383908, 'fraction_numerical': 0.007068993375343351, 'mean_word_length': 2.516619318181818, 'pattern_counts': {'":': 0, '<': 44, '<?xml version=': 0, '>': 90, 'https://': 12, 'lorem ipsum': 0, 'www.': 2, 'xml': 6}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 26, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7198091', 'n_tokens_mistral': 8009, 'n_tokens_neox': 7696, 'n_words': 1551}