diff --git a/lib/Ravada.pm b/lib/Ravada.pm index f22db09e5..3fe67ec8e 100644 --- a/lib/Ravada.pm +++ b/lib/Ravada.pm @@ -3,7 +3,7 @@ package Ravada; use warnings; use strict; -our $VERSION = '2.3.0'; +our $VERSION = '2.3.1-beta3'; use utf8; @@ -182,6 +182,11 @@ sub _set_first_time_run($self) { } } +sub _clean_tls($self) { + my $sth = $CONNECTOR->dbh->prepare("UPDATE vms set tls=NULL"); + $sth->execute(); +} + sub _install($self) { my $pid = Proc::PID::File->new(name => $self->pid_name); $pid->file({dir => "/run/user/$>"}) if $>; @@ -2688,6 +2693,25 @@ sub _sql_insert_defaults($self){ ,name => "media-src" ,value => '' } + ,{ + id_parent => $id_frontend + ,name => 'auto_create_users' + ,value => 1 + } + ,{ + id_parent => $id_frontend + ,name => 'openid' + } + ,{ + id_parent => "/frontend/openid" + ,name => "enabled" + ,value => 0 + } + ,{ + id_parent => "/frontend/openid" + ,name => "logout_url" + ,value => '' + } ,{ id_parent => $id_backend ,name => 'start_limit' @@ -6448,7 +6472,9 @@ sub _cmd_cleanup($self, $request) { )) { $self->_clean_requests($cmd, $request,'done'); } + } + sub _verify_connection($self, $domain) { for ( 1 .. 60 ) { my $status = $domain->client_status(1); @@ -6878,6 +6904,42 @@ sub _cmd_post_login($self, $request) { my $user = Ravada::Auth::SQL->new(name => $request->args('user')); $user->unshown_messages(); $self->_post_login_locale($request); + $self->_check_tls_date($user) if $user->is_admin; +} + +sub _check_tls_date($self, $user) { + return if !$user->is_admin; + my $sth = $CONNECTOR->dbh->prepare("SELECT name,tls FROM vms"); + $sth->execute(); + while (my ($name,$tls) = $sth->fetchrow ) { + next if !$tls; + my $tls_h = {}; + eval { + $tls_h = decode_json($tls); + }; + warn "Warning: error decoding tls for $name '$tls' $@" if $@; + next if !keys %$tls_h; + my $not_after = $tls_h->{notAfter}; + next if !$not_after; + my $date; + eval { $date = DateTime::Format::DateParse->parse_datetime($not_after) }; + + if($date) { + my $duration = $date-DateTime->now; + my ($years, $months, $days) = $duration->in_units('years','months','days'); + if ($years<1 && $months<1) { + if ($years<0 || $months<0 || $days<=0 ) { + $not_after =~ s/(.*) \d+:\d+:\d+(.*)/$1$2/; + $user->send_message("Critical: TLS certificate for $name expired on $not_after"); + }elsif ($days<7) { + $user->send_message("Critical: TLS certificate for $name has only $days days left. $not_after"); + } elsif ($days<30) { + $user->send_message("Warning: TLS certificate for $name has only $days days left. $not_after"); + } + } + } + + } } sub _post_login_locale($self, $request) { diff --git a/lib/Ravada/Auth/Group.pm b/lib/Ravada/Auth/Group.pm index 469c934d0..1b3c76467 100644 --- a/lib/Ravada/Auth/Group.pm +++ b/lib/Ravada/Auth/Group.pm @@ -121,6 +121,15 @@ sub remove_member($self, $name) { $sth->execute($id_user); } +sub remove_other_members($self, $members) { + my %members = map { $_ => 1 } @$members; + + for my $name ($self->members ) { + $self->remove_member($name) if !$members{$name}; + } + +} + sub _remove_all_members($self) { my $sth = $$CON->dbh->prepare("DELETE FROM users_group " ." WHERE id_group=?" diff --git a/lib/Ravada/Auth/OpenID.pm b/lib/Ravada/Auth/OpenID.pm new file mode 100644 index 000000000..ca8f66aa5 --- /dev/null +++ b/lib/Ravada/Auth/OpenID.pm @@ -0,0 +1,82 @@ +package Ravada::Auth::OpenID; + +use strict; +use warnings; + +use Data::Dumper; + +use Ravada::Front; + +=head1 NAME + +Ravada::Auth::OpenID - OpenID library for Ravada + +=cut + +use Moose; + +no warnings "experimental::signatures"; +use feature qw(signatures state); + +use Ravada::Auth::SQL; + +with 'Ravada::Auth::User'; + +our $CONFIG = \$Ravada::CONFIG; +our $ERR; + +sub BUILD { + my $self = shift; + die sprintf('ERROR: Login failed %s', $self->name) + if !$self->login(); + return $self; +} + +sub add_user($name, $password, $storage='rfc2307', $algorithm=undef) { } + +sub remove_user { } + +sub search_user { } + +sub _check_user_profile($self) { + my $user_sql = Ravada::Auth::SQL->new(name => $self->name); + if ( $user_sql->id ) { + if ($user_sql->external_auth ne 'openid') { + $user_sql->external_auth('openid'); + } + return $user_sql; + } + + return if ! Ravada::Front::setting(undef,'/frontend/auto_create_users'); + + Ravada::Auth::SQL::add_user(name => $self->name, is_external => 1, is_temporary => 0 + , external_auth => 'openid'); + + return $user_sql; +} + +sub is_admin { } + +sub is_external { } + +sub login_external($name, $header) { + + for my $field (qw(OIDC_CLAIM_exp OIDC_access_token_expires)) { + if ( exists $header->{$field} && defined $header->{$field} && $header->{$field} < time() ) { + warn localtime($header->{$field})." $field expired \n"; + return 0; + } + } + + my $self = Ravada::Auth::OpenID->new(name => $name); + return if !$self->_check_user_profile(); + return $self; +} + +sub login($self) { + my $user_sql = Ravada::Auth::SQL->new(name => $self->name); + return 1 if $user_sql->external_auth && $user_sql->external_auth eq 'openid'; + return 1; +} + +1; diff --git a/lib/Ravada/Auth/SSO.pm b/lib/Ravada/Auth/SSO.pm index c01043ed6..e3851de0b 100644 --- a/lib/Ravada/Auth/SSO.pm +++ b/lib/Ravada/Auth/SSO.pm @@ -54,6 +54,8 @@ sub _check_user_profile { return; } + return if ! Ravada::Front::setting(undef,'/frontend/auto_create_users'); + Ravada::Auth::SQL::add_user(name => $self->name, is_external => 1, is_temporary => 0 , external_auth => 'sso'); } diff --git a/lib/Ravada/Front.pm b/lib/Ravada/Front.pm index 81388f440..8f04c903b 100644 --- a/lib/Ravada/Front.pm +++ b/lib/Ravada/Front.pm @@ -1912,6 +1912,108 @@ sub upload_users($self, $users, $type, $create=0) { return ($found, $count, \@error); } +=head2 upload_users_json + +Upload a list of users to the database + +=head3 Arguments + +=over + +=item * string with users and passwords in each line + +=item * type: it can be SQL, LDAP or SSO + +=back + +=cut + + +sub upload_users_json($self, $data_json, $type='openid') { + + my ($found, $count, @error); + my $data; + eval { + $data= decode_json($data_json); + }; + if ( $@ ) { + push @error,($@); + $data={} + } + + my $result = { + users_found => 0 + ,users_added => 0 + ,groups_found => 0 + ,groups_added => 0 + }; + if (exists $data->{groups} && + (!ref($data->{groups}) || ref($data->{groups}) ne 'ARRAY')) { + die "Expecting groups as an array , got ".ref($data->{groups}); + } + $data->{groups} = [] if !exists $data->{groups}; + for my $g0 (@{$data->{groups}}) { + $result->{groups_found}++; + my $g = $g0; + if (!ref($g)) { + $g = { name => $g0 }; + } + $found++; + my $group = Ravada::Auth::Group->new(name => $g->{name}); + my $members = delete $g->{members}; + if (!$group || !$group->id) { + unless (defined $members && !scalar(@$members) && $data->{options}->{flush} && $data->{options}->{remove_empty}) { + $result->{groups_added}++; + Ravada::Auth::Group::add_group(%$g); + } + } else { + push @error,("Group $g->{name} already added"); + } + $self->_add_users($members, $type, $result, \@error, 1); + $group->remove_other_members($members) if $data->{options}->{flush}; + + for my $m (@$members) { + my $user = Ravada::Auth::SQL->new(name => $m); + $user->add_to_group($g->{name}) unless $user->is_member($g->{name}); + } + if ( $data->{options}->{remove_empty} && $group->id && !$group->members ) { + $group->remove(); + $result->{groups_removed}++; + push @error,("Group ".$group->name." empty removed"); + } + } + + $self->_add_users($data->{users}, $type, $result, \@error) + if $data->{users}; + + return ($result, \@error); +} + +sub _add_users($self,$users, $type, $result, $error, $ignore_already=0) { + for my $u0 (@$users) { + $result->{users_found}++; + my $u = $u0; + $u = dclone($u0) if ref($u0); + if (!ref($u)) { + $u = { name => $u0 }; + } + if (!exists $u->{is_external}) { + if ($type ne 'sql') { + $u->{is_external} = 1; + $u->{external_auth} = $type ; + } + } + my $user = Ravada::Auth::SQL->new(name => $u->{name}); + if ($user && $user->id) { + push @$error,("User $u->{name} already added") + unless $ignore_already; + next; + } + Ravada::Auth::SQL::add_user(%$u); + $result->{users_added}++; + } +} + =head2 create_bundle Creates a new bundle diff --git a/lib/Ravada/I18N/ca.po b/lib/Ravada/I18N/ca.po index e00be5a2c..734524ed4 100644 --- a/lib/Ravada/I18N/ca.po +++ b/lib/Ravada/I18N/ca.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: 0.1.0-alpha\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-01-03 10:12+0300\n" -"PO-Revision-Date: 2024-09-21 06:40+0000\n" -"Last-Translator: fv3rdugo \n" +"PO-Revision-Date: 2024-07-24 12:09+0000\n" +"Last-Translator: Dani Sanchez \n" "Language-Team: Catalan \n" "Language: ca\n" diff --git a/lib/Ravada/I18N/id.po b/lib/Ravada/I18N/id.po index d03c792c0..6991744a4 100644 --- a/lib/Ravada/I18N/id.po +++ b/lib/Ravada/I18N/id.po @@ -5,8 +5,8 @@ msgstr "" "Project-Id-Version: ravada\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-01-03 10:12+0300\n" -"PO-Revision-Date: 2024-09-21 06:40+0000\n" -"Last-Translator: fv3rdugo \n" +"PO-Revision-Date: 2024-02-06 16:45+0000\n" +"Last-Translator: Dani Sanchez \n" "Language-Team: Indonesian \n" "Language: id\n" diff --git a/lib/Ravada/I18N/it.po b/lib/Ravada/I18N/it.po index 6cc7e7651..2c38b6373 100644 --- a/lib/Ravada/I18N/it.po +++ b/lib/Ravada/I18N/it.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: 0.1.0-alpha\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-01-03 10:12+0300\n" -"PO-Revision-Date: 2024-09-21 06:40+0000\n" -"Last-Translator: fv3rdugo \n" +"PO-Revision-Date: 2024-02-06 16:45+0000\n" +"Last-Translator: Dani Sanchez \n" "Language-Team: Italian \n" "Language: it\n" diff --git a/lib/Ravada/I18N/ru.po b/lib/Ravada/I18N/ru.po index 77eddb7fb..99adf9d26 100644 --- a/lib/Ravada/I18N/ru.po +++ b/lib/Ravada/I18N/ru.po @@ -2,8 +2,8 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) msgid "" msgstr "" -"PO-Revision-Date: 2024-09-21 06:40+0000\n" -"Last-Translator: fv3rdugo \n" +"PO-Revision-Date: 2024-07-17 22:09+0000\n" +"Last-Translator: Elena Mishina \n" "Language-Team: Russian \n" "Language: ru\n" diff --git a/lib/Ravada/VM.pm b/lib/Ravada/VM.pm index d93fb95a8..c3b65b635 100644 --- a/lib/Ravada/VM.pm +++ b/lib/Ravada/VM.pm @@ -1401,7 +1401,6 @@ sub is_locked($self) { next if defined $at && $at < time + 2; next if !$args; my $args_d = decode_json($args); - warn Dumper($args_d) if $args_d->{id_vm} && $args_d->{id_vm} eq 'KVM'; if ( exists $args_d->{id_vm} && $args_d->{id_vm} == $self->id ) { warn "locked by $command\n"; return 1; @@ -2403,9 +2402,22 @@ sub copy_file_storage($self, $file, $storage) { } sub _fetch_tls_host_subject($self) { + + return '' if !$self->dir_cert(); + + my $key = 'subject'; + my $subject = $self->_fetch_tls_cached($key); + return $subject if $self->readonly || $subject; + + return $self->_do_fetch_tls_host_subject(); + +} + +sub _do_fetch_tls_host_subject($self) { return '' if !$self->dir_cert(); - return $self->_fetch_tls_cached('host_subject') + my $key = 'subject'; + return $self->_fetch_tls_cached($key) if $self->readonly; my @cmd= qw(/usr/bin/openssl x509 -noout -text -in ); @@ -2423,12 +2435,13 @@ sub _fetch_tls_host_subject($self) { $subject =~ s/, /,/g; last; } - $self->_store_tls( subject => $subject ); + $self->_store_tls( $key => $subject ); return $subject; } sub _fetch_tls_cached($self, $field) { my $tls_json = $self->_data('tls'); + return if !$tls_json; my $tls = {}; eval { $tls = decode_json($tls_json) if length($tls); @@ -2451,7 +2464,13 @@ sub _store_tls($self, $field, $value ) { } sub _fetch_tls_ca($self) { - return $self->_fetch_tls_cached('ca') if $self->readonly; + my $ca = $self->_fetch_tls_cached('ca'); + return $ca if $self->readonly || $ca; + + return $self->_do_fetch_tls_ca(); +} + +sub _do_fetch_tls_ca($self) { my ($out, $err) = $self->run_command("/bin/cat", $self->dir_cert."/ca-cert.pem"); my $ca = join('\n', (split /\n/,$out) ); @@ -2460,6 +2479,17 @@ sub _fetch_tls_ca($self) { return $ca; } +sub _fetch_tls_dates($self) { + return if $self->readonly; + my ($out, $err) = $self->run_command("openssl","x509","-in", $self->dir_cert."/ca-cert.pem" + ,"-dates","-noout"); + for my $line (split /\n/,$out) { + my ($field,$value) = $line =~ m{(.*?)=(.*)}; + next if !$field || !$value; + $self->_store_tls( $field => $value ); + } +} + sub _fetch_tls($self) { return if $self->readonly || $self->type ne 'KVM' || $self->{_tls_fetched}++; @@ -2472,7 +2502,10 @@ sub _fetch_tls($self) { for (keys %$tls_hash) { delete $tls_hash->{$_} if !$tls_hash->{$_}; } + my $time = $tls_hash->{time}; if (!defined $tls || !$tls + || !$time + || ( time-$time > 300 && rand(10)<2) || !$tls_hash || !ref($tls_hash) || !keys(%$tls_hash)) { $self->_do_fetch_tls(); } @@ -2480,8 +2513,10 @@ sub _fetch_tls($self) { } sub _do_fetch_tls($self) { - $self->_fetch_tls_host_subject(); - $self->_fetch_tls_ca(); + $self->_store_tls( time => time ); + $self->_do_fetch_tls_host_subject(); + $self->_do_fetch_tls_ca(); + $self->_fetch_tls_dates(); } sub _store_mac_address($self, $force=0 ) { diff --git a/public/js/ravada.js b/public/js/ravada.js index 550c3a2f6..4670c7dd5 100644 --- a/public/js/ravada.js +++ b/public/js/ravada.js @@ -30,6 +30,7 @@ .controller("maintenance",maintenanceCtrl) .controller("notifCrtl", notifCrtl) .controller("run_domain_req",run_domain_req_ctrl) + .controller("login",login_ctrl) function newMachineCtrl($scope, $http) { @@ -1361,6 +1362,9 @@ }; + function login_ctrl($scope, $http, $timeout, request ) { + }; + function run_domain_req_ctrl($scope, $http, $timeout, request ) { var redirected_display = false; var already_subscribed_to_domain = false; diff --git a/script/rvd_back b/script/rvd_back index 6af0b23b0..c126327e5 100755 --- a/script/rvd_back +++ b/script/rvd_back @@ -334,6 +334,7 @@ sub start { $Ravada::CONNECTOR->dbh; $ravada->set_debug_value(); $ravada->_wait_pids(); + $ravada->_clean_tls(); autostart_machines($ravada); Ravada::Request->update_iso_urls(uid =>Ravada::Utils::user_daemon->id); Ravada::Request->refresh_storage(); diff --git a/script/rvd_front b/script/rvd_front index 1a61c6207..134e91b92 100644 --- a/script/rvd_front +++ b/script/rvd_front @@ -27,6 +27,7 @@ no warnings "experimental::signatures"; use feature qw(signatures); use Ravada::Auth; +use Ravada::Auth::OpenID; use Ravada::Booking; use Ravada::Front; use Ravada::Front::Domain; @@ -187,6 +188,7 @@ hook before_routes => sub { $USER = undef; + $c->stash(version => $RAVADA->version); my $url = $c->req->url->to_abs->path; my $host = $c->req->url->to_abs->host; @@ -230,6 +232,8 @@ hook before_routes => sub { return if $url =~ m{^/(anonymous_logout|login|logout|requirements|robots.txt|favicon.ico|status\.)}; + return if $url =~ m{^/(connect|profile)}; + my $bases_anonymous = $RAVADA->list_bases_anonymous(_remote_ip($c)); return access_denied($c) if $url =~ m{^/anonymous} && !@$bases_anonymous; @@ -276,6 +280,9 @@ any '/robots.txt' => sub { any '/' => sub { my $c = shift; + +my %header; + return quick_start($c); }; @@ -289,6 +296,50 @@ any '/login' => sub { return login($c); }; +any '/login_openid' => sub($c) { + my %header; + for my $name (@{$c->req->headers->names}) { + $header{$name} = $c->req->headers->header($name) + if $name =~ /OIDC/; + } + + my $auth_ok; + my $username_oidc = $header{OIDC_CLAIM_preferred_username}; + if ($username_oidc) { + $c->session('logoutURL' => $RAVADA->setting('/frontend/openid/logout_url')); + my $oidc_at_hash = $header{OIDC_CLAIM_at_hash}; + if (!$oidc_at_hash) { + warn "Error: no OIDC_CLAIM_at_hash in header ".join(" ",%header); + return access_denied($c); + } + $c->session('oidc_at_hash' => $oidc_at_hash); + eval { + $auth_ok = Ravada::Auth::OpenID::login_external($username_oidc, \%header); + }; + warn $@ if $@; + } + my $error = ($@ or ''); + if (!$username_oidc || !$auth_ok) { + if ( $CONFIG_FRONT->{log}->{log} ) { + app->log->error("Access denied with openid from "._remote_ip($c)); + app->log->error("Access denied error: ".($error or 'unknown')._remote_ip($c)); + } + return access_denied($c); + } + $c->session('logoutURL' => $RAVADA->setting('/frontend/openid/logout_url')); + $c->session('OIDC_CLAIM_at_hash' => $header{'OIDC_CLAIM_at_hash'}); + + _login_ok($c, $auth_ok); +}; + +sub _session_error($c) { + $c->render("text" => "Session error, please close the web browser and try again"); +} + +any '/login_openid/redirect_uri' => sub($c) { + return $c->render("text" => "protected redirect"); +}; + any '/test' => sub { my $c = shift; my $logged = _logged_in($c); @@ -1794,37 +1845,71 @@ any '/admin/users/upload.#req' => sub($c) { my $type = $c->req->param('type'); - return $c->render(template => "/main/upload_users", done => 0, count => 0, found => 0, type => 'sql') if !$type; + return $c->render(template => "/main/upload_users", done => 0, output => {} + ,error => [] + ,type => 'sql') if !$type; my $create = ( $c->req->param('create') or 0); return $c->render(json => { error => "Unknown type $type" }) - if $type !~ /^(sql|ldap|sso)/; + if $type !~ /^(sql|ldap|sso|openid)/; - my $csv = $c->req->upload('users'); - if($csv->headers->content_type !~ m{text/(csv|plain)}) { + my $file = $c->req->upload('users'); + + if($file->headers->content_type =~ m{text/(csv|plain)}) { + _upload_users_csv($c, $file, $type, $create); + } elsif ( $file->headers->content_type =~ m{application/json}) { + _upload_users_json($c, $file, $type, $create); + } else { return $c->render(status => 400 - ,text => "Wrong content type ".$csv->headers->content_type - ." , it should be text/csv or plain" + ,text => "Wrong content type ".$file->headers->content_type + ." , it should be text/csv , application/json or plain" ); + + } +}; + +sub _upload_users_json($c, $file, $type, $create) { + + my ($result, $error)=$RAVADA->upload_users_json($file->slurp, $type); + + if ($create) { + push @$error,("Warning: create not implemented with json upload"); } + return $c->render(json => + { + output => $result + ,error => $error + } + ) if $c->stash('req') eq 'json'; + + return $c->render(template => "/main/upload_users" + ,output => $result + ,error => $error + ,done => 1 + ); +} +sub _upload_users_csv($c, $csv, $type, $create) { my ($found, $count, $error) = $RAVADA->upload_users( $csv->slurp, $type, $create ); + my $output = { + users_found => $found + ,users_added => $count + }; return $c->render(json => - { output => "$count users added" + { output => $output ,error => $error + ,done => 1 }) if $c->stash('req') eq 'json'; return $c->render(template => "/main/upload_users" - ,count => $count - ,found => $found ,error => $error ,done => 1 ); -}; +} get '/admin/user/remove/#id' => sub($c) { return access_denied($c) unless $USER->is_admin; @@ -2859,6 +2944,10 @@ del '/v1/booking_entry/:id/:mode' => sub($c) { die $@ if $@; my $booking = Ravada::Booking->new( id => $entry->_data('id_booking')); + my %get_token = ( + redirect_uri => $c->url_for('connect')->userinfo(undef)->to_abs + ,authorize_query => { response_type => 'code' } + ); return error_access_denied_json($c) unless $booking->_data('id_owner') == $USER->id @@ -3277,8 +3366,6 @@ sub login($c, $status=200) { my $login = $c->param('login'); my $password = $c->param('password'); my $ticket = $c->param('ticket'); - my $url = ($c->param('url') or $c->req->url->to_abs->path); - $url = '/' if $url =~ m{^/login}; my @error =(); @@ -3301,73 +3388,104 @@ sub login($c, $status=200) { } if ( $auth_ok && !$@) { - $c->session('login' => $login); - my $expiration = $SESSION_TIMEOUT; - $expiration = $SESSION_TIMEOUT_ADMIN if $auth_ok->is_admin; - Ravada::Request->post_login( - user => $auth_ok->name - , locale => [_detect_languages($c)] - ); - - $auth_ok = Ravada::Auth::SQL->new(name => $auth_ok->name); - if ( $RAVADA->is_in_maintenance() ) { - return maintenance($c) unless $auth_ok->is_operator; - $auth_ok->send_message('Warning: Server under maintenance. Settings'); - } - - my $machines = $RAVADA->list_machines_user($auth_ok); - - $url = "/machine/clone/". $machines->[0]->{id}.".html" if scalar(@$machines) == 1 && !($auth_ok->is_admin); - my $auto_view = 1; - - $c->session(auto_view => $auto_view, expiration => $expiration); - app->log->info("Access granted to $login from "._remote_ip($c)) if $CONFIG_FRONT->{log}->{log}; - return $c->redirect_to($url); + _login_ok($c, $auth_ok); + return; } elsif (defined $c->param('submit') || $login || $password) { app->log->error("Access denied to $login from "._remote_ip($c)) if $CONFIG_FRONT->{log}->{log}; push @error,("Access denied"); } } - my @css_snippets = ["\t.intro {\n\t\tbackground:" - ." url($CONFIG_FRONT->{login_bg_file})" - ." no-repeat bottom center scroll;\n\t}"]; sleep 5 if scalar(@error) && !$ENV{mode} && !$ENV{mode} eq 'development'; - my @error_status = ( status => $status ); - @error_status = ( status => 403) if @error; + $status = 403 if @error; + + $c->stash('login' => $login); + _render_login($c, \@error, $status); +} +sub _render_login($c, $error, $status=undef) { + + $error = [$error] if !ref($error); + $status = 403 if @$error && !$status; + + my @status; + @status = ( status => $status ) if $status; + + my $css_snippets = ["\t.intro {\n\t\tbackground:" + ." url($CONFIG_FRONT->{login_bg_file})" + ." no-repeat bottom center scroll;\n\t}"]; $c->render( template => ($CONFIG_FRONT->{login_custom} or 'main/start') ,css => ['/css/main.css'] - ,csssnippets => @css_snippets - ,js => ['/js/main.js?v='.$RAVADA->version] + ,csssnippets => $css_snippets + ,js => [ + '/js/main.js?v='.$RAVADA->version + ,'/js/ravada.js?v='.$RAVADA->version + ] ,navbar_custom => 1 - ,login => $login - ,error => \@error + ,error => $error ,sso_available => ( $Ravada::Auth::SSO_OK && Ravada::Auth::SSO::init()) + ,openid_available => $RAVADA->setting('/frontend/openid/enabled') ,login_header => $CONFIG_FRONT->{login_header} ,login_message => $CONFIG_FRONT->{login_message} ,guide => $CONFIG_FRONT->{guide} ,login_hash => '' - ,@error_status + ,@status + ); +} + +sub _login_ok($c, $auth_ok) { + + my $login = $auth_ok->name; + + my $url = ($c->param('url') or $c->req->url->to_abs->path); + $url = '/' if $url =~ m{^/login}; + + $c->session('login' => $login); + my $expiration = $SESSION_TIMEOUT; + $expiration = $SESSION_TIMEOUT_ADMIN if $auth_ok->is_admin; + Ravada::Request->post_login( + user => $auth_ok->name + , locale => [_detect_languages($c)] ); + + $auth_ok = Ravada::Auth::SQL->new(name => $auth_ok->name); + if ( $RAVADA->is_in_maintenance() ) { + return maintenance($c) unless $auth_ok->is_operator; + $auth_ok->send_message('Warning: Server under maintenance. Settings'); + } + + my $machines = $RAVADA->list_machines_user($auth_ok); + + $url = "/machine/clone/". $machines->[0]->{id}.".html" if scalar(@$machines) == 1 && !($auth_ok->is_admin); + my $auto_view = 1; + + $c->session(auto_view => $auto_view, expiration => $expiration); + app->log->info("Access granted to $login from "._remote_ip($c)) if $CONFIG_FRONT->{log}->{log}; + + return $c->redirect_to($url); } sub logout { my $c = shift; $USER = undef; - $c->session(expires => 0); + $c->session(expires => 1); $c->session(login => undef); sleep 1; - $c->session(expires => 0); + $c->session(expires => 1); $c->session(login => undef); $c->session(ticket => undef); my $logout_url = $c->session('logoutURL'); $c->session(logoutURL => undef); + my $c_name= 'mod_auth_openidc_session'; + my $cookie_oidc = $c->req->headers->cookie($c_name); + + $c->cookie($c_name => '', {expires => 1}) if $cookie_oidc; + return $logout_url; } diff --git a/t/40_auth_sql.t b/t/40_auth_sql.t index a058340d8..24d2b7ae2 100644 --- a/t/40_auth_sql.t +++ b/t/40_auth_sql.t @@ -10,6 +10,7 @@ use Test::Ravada; use_ok('Ravada'); use_ok('Ravada::Auth::SQL'); +init(); my $RAVADA = rvd_back(); Ravada::Auth::SQL::add_user(name => 'test',password => $$); diff --git a/t/68_user_openid.t b/t/68_user_openid.t new file mode 100644 index 000000000..746d41b3b --- /dev/null +++ b/t/68_user_openid.t @@ -0,0 +1,54 @@ +use warnings; +use strict; + +use Carp qw(confess); +use Data::Dumper; +use Test::More; +use YAML qw(LoadFile DumpFile); + +no warnings "experimental::signatures"; +use feature qw(signatures); + +use lib 't/lib'; +use Test::Ravada; + +use_ok('Ravada'); +use_ok('Ravada::Auth::OpenID'); + +####################################################################### + +sub test_auto_create { + my $user_name = new_domain_name(); + my $header = {}; + + my $user = Ravada::Auth::OpenID::login_external($user_name, $header); + ok($user); + + $header = { OIDC_CLAIM_exp => time-10 }; + + $user = Ravada::Auth::OpenID::login_external($user_name, $header); + ok(!$user); + + $header = { OIDC_access_token_expires => time-10 }; + + $user = Ravada::Auth::OpenID::login_external($user_name, $header); + ok(!$user); + + rvd_front->setting('/frontend/auto_create_users' => 0); + + $user_name = new_domain_name(); + $header = {}; + + $user = Ravada::Auth::OpenID::login_external($user_name, $header); + ok(!$user); + +} + +###################################################################### + +init(); + +test_auto_create(); + +end(); +done_testing(); diff --git a/t/lib/Test/Ravada.pm b/t/lib/Test/Ravada.pm index 1ab20576a..a1776d586 100644 --- a/t/lib/Test/Ravada.pm +++ b/t/lib/Test/Ravada.pm @@ -599,8 +599,32 @@ sub init($config=undef, $sqlite = 1 , $flush=0) { $Ravada::VM::KVM::VERIFY_ISO = 0; $Ravada::VM::MIN_DISK_MB = 1; + _clean_old_users(); + _clean_old_groups(); } +sub _clean_old_users() { + my $sth = $CONNECTOR->dbh->prepare("SELECT id,name FROM users WHERE name like ? "); + $sth->execute(base_domain_name().'%'); + while ( my ($id,$name) = $sth->fetchrow ) { + next if $USER_ADMIN && $name eq $USER_ADMIN->name; + my $user = Ravada::Auth::SQL->search_by_id($id); + next if !$user; + $user->remove(); + } +} + +sub _clean_old_groups() { + my $sth = $CONNECTOR->dbh->prepare("SELECT id,name FROM groups_local WHERE name like ? "); + $sth->execute(base_domain_name().'%'); + while ( my ($id,$name) = $sth->fetchrow ) { + my $g = Ravada::Auth::Group->open($id); + next if !$g; + $g->remove(); + } +} + + sub _load_remote_config() { return {} if ! -e $FILE_CONFIG_REMOTE; my $conf; @@ -1573,6 +1597,7 @@ sub _qemu_storage_pool { sub remove_void_networks($vm=undef) { if (!defined $vm) { eval { $vm = rvd_back->search_vm('Void') }; + die $@ if $@; } my $dir_net = $vm->dir_img()."/networks"; return if ! -e $dir_net; diff --git a/t/mojo/10_login.t b/t/mojo/10_login.t index d81a7d4e8..63ed20d1b 100644 --- a/t/mojo/10_login.t +++ b/t/mojo/10_login.t @@ -382,7 +382,7 @@ sub test_login_fail { $t->post_ok('/login' => form => {login => "fail", password => 'bigtime'}); is($t->tx->res->code(),403); $t->get_ok("/admin/machines")->status_is(401); - like($t->tx->res->dom->at("button#submit")->text,qr'Login') or exit; + like($t->tx->res->dom->at("input#submit")->attr('value'),qr'Login') or exit; login( user_admin->name, "$$ $$"); @@ -390,10 +390,10 @@ sub test_login_fail { is($t->tx->res->code(),403); $t->get_ok("/admin/machines")->status_is(401); - like($t->tx->res->dom->at("button#submit")->text,qr'Login') or exit; + like($t->tx->res->dom->at("input#submit")->attr('value'),qr'Login') or exit; $t->get_ok("/admin/users")->status_is(401); - like($t->tx->res->dom->at("button#submit")->text,qr'Login') or exit; + like($t->tx->res->dom->at("input#submit")->attr('value'),qr'Login') or exit; } diff --git a/t/mojo/60_upload.t b/t/mojo/60_upload.t index 1877a7bdd..ffb5f4f6b 100644 --- a/t/mojo/60_upload.t +++ b/t/mojo/60_upload.t @@ -6,7 +6,7 @@ use Data::Dumper; use Test::More; use Test::Mojo; use Mojo::File 'path'; -use Mojo::JSON qw(decode_json); +use Mojo::JSON qw(encode_json decode_json); use lib 't/lib'; use Test::Ravada; @@ -68,7 +68,7 @@ sub test_upload_users_nopassword( $type, $mojo=0 ) { die $t->tx->res->body if $t->tx->res->code != 200; my $response = $t->tx->res->json(); - like($response->{output}, qr/2 users added/); + is($response->{output}->{users_added} ,2); is_deeply($response->{error},[]); } else { rvd_front->upload_users($users, $type); @@ -99,7 +99,7 @@ sub test_upload_users( $type, $create=0, $mojo=0 ) { die $t->tx->res->body if $t->tx->res->code != 200; my $response = $t->tx->res->json(); - like($response->{output}, qr/2 users added/); + is($response->{output}->{users_added} ,2); is_deeply($response->{error},[]); } else { rvd_front->upload_users($users, $type, $create); @@ -123,7 +123,7 @@ sub test_upload_users( $type, $create=0, $mojo=0 ) { die $t->tx->res->body if $t->tx->res->code != 200; my $response = $t->tx->res->json(); - like($response->{output}, qr/0 users added/); + is($response->{output}->{users_added},0); is(scalar(@{$response->{error}}),2); test_users_added($type, $user1, $user2); @@ -262,6 +262,466 @@ sub test_upload_group($mojo=0) { } +sub test_upload_json_fail() { + + _do_upload_users_fail(0); + _do_upload_users_fail(1); +} + +sub _do_upload_users_fail($mojo, $type='openid') { + my ($result, $error); + if (!$mojo) { + ($result, $error)=rvd_front->upload_users_json("wrong", $type); + } else { + $t->post_ok('/admin/users/upload.json' => form => { + type => $type + ,create => 0 + ,users => { content => "wrong", filename => 'data.json' + , 'Content-Type' => 'application/json' }, + })->status_is(200); + die $t->tx->res->body if $t->tx->res->code != 200; + + my $response = $t->tx->res->json(); + $result = $response->{output}; + $error = $response->{error}; + } + like($error->[0],qr/malformed JSON/); + is_deeply($result, { groups_found => 0 , groups_added => 0, users_found => 0, users_added => 0}); +} + +sub test_upload_json() { + + test_upload_json_members(); + + test_upload_json_members_flush(); + test_upload_json_members_remove_empty(); + + test_upload_json_users_groups(); + test_upload_json_users_groups2(); + test_upload_json_users_admin(); + test_upload_json_users_pass(); + test_upload_json_users(); +} + +sub _do_upload_users_json($data, $mojo, $exp_result=undef, $type='openid') { + + confess if ref($mojo); + confess if defined $exp_result && !ref($exp_result); + + my $data_h = $data; + if (ref($data)) { + $data = encode_json($data); + } else { + $data_h = decode_json($data); + } + if (!defined $exp_result) { + $exp_result= { groups_found => 0, groups_added => 0, users_found=>0, users_added => 0}; + if ($data_h->{groups}) { + $exp_result->{groups_found} = scalar(@{$data_h->{groups}}); + $exp_result->{groups_added} = scalar(@{$data_h->{groups}}); + confess"not array groups\n".Dumper($data_h) if ref($data_h->{groups}) ne 'ARRAY'; + for my $g ($data_h->{groups}) { + next if !ref($g) || ref($g) ne 'HASH' || !exists $g->{members}; + $exp_result->{users_found} += scalar(@{$g->{members}}); + $exp_result->{users_added} += scalar(@{$g->{members}}); + } + } + if ($data_h->{users}) { + $exp_result->{users_found} += scalar(@{$data_h->{users}}); + $exp_result->{users_added} += scalar(@{$data_h->{users}}); + }; + } + my $users = $data_h->{users}; + if ($users) { + for my $user (@$users) { + my $name = $user; + $name = $user->{name} if ref($user); + next if !$name; + remove_old_user($name); + } + } + my ($result, $error); + if (!$mojo) { + ($result, $error)=rvd_front->upload_users_json($data, $type); + } else { + my $url='/admin/users/upload.json'; + $t->post_ok( $url => form => { + type => $type + ,create => 0 + ,users => { content => $data, filename => 'data.json' + , 'Content-Type' => 'application/json' }, + })->status_is(200); + die $t->tx->res->body if $t->tx->res->code != 200; + + my $response = $t->tx->res->json(); + $result = $response->{output}; + $error = $response->{error}; + } + + for my $err (@$error) { + ok(0,$err) unless $err =~ /already added|empty removed/; + } + is_deeply($result, $exp_result) or die Dumper(["mojo=$mojo",$data,$error,$result, $exp_result]); + +} + +sub test_upload_json_users() { + _do_test_upload_json_users(0); + _do_test_upload_json_users(1); +} + +sub _do_test_upload_json_users($mojo) { + my @users = ( new_domain_name(), new_domain_name() ); + my $data = { + users => \@users + }; + + _do_upload_users_json( { users => \@users },$mojo ); + + for my $name ( @users ) { + my $user = Ravada::Auth::SQL->new(name => $name); + ok($user->id, "Expecting user $name created"); + is($user->external_auth, 'openid'); + + $user = undef; + eval { + $user = Ravada::Auth->login( $name , ''); + }; + like($@,qr/login failed/i); + ok(!$user) or warn $user->name; + } +} + +sub test_upload_json_users_groups() { + + _do_test_upload_json_users_groups(0); + _do_test_upload_json_users_groups(1); +} + +sub _do_test_upload_json_users_groups($mojo) { + my @users = ( + {name => new_domain_name() } + , {name => new_domain_name(), is_admin => 1 } + ); + my @groups = ( + new_domain_name() + ,new_domain_name() + ); + my $data = { + users => \@users + ,groups => \@groups + }; + + _do_upload_users_json( encode_json( $data ), $mojo, { groups_found => 2, groups_added => 2, users_found => 2, users_added => 2} ); + for my $u ( @users ) { + my $user = Ravada::Auth::SQL->new(name => $u->{name}); + ok($user->id, "Expecting user $u->{name} created"); + } + for my $g ( @groups) { + my $group = Ravada::Auth::Group->new(name => $g); + ok($group->id, "Expecting group $g created"); + } + +} + +sub test_upload_json_users_groups2() { + _do_test_upload_json_users_groups2(0); + _do_test_upload_json_users_groups2(1); +} + +sub _do_test_upload_json_users_groups2($mojo) { + my @users = ( + {name => new_domain_name() } + , {name => new_domain_name(), is_admin => 1 } + ); + my @groups = ( + {name => new_domain_name() } + ,{name => new_domain_name() } + ); + my $data = { + users => \@users + ,groups => \@groups + }; + + _do_upload_users_json( $data, $mojo ); + for my $u ( @users ) { + my $user = Ravada::Auth::SQL->new(name => $u->{name}); + ok($user->id, "Expecting user $u->{name} created"); + } + for my $g ( @groups) { + my $group = Ravada::Auth::Group->new(name => $g->{name}); + ok($group->id, "Expecting group $g->{name} created"); + } + +} + +sub test_upload_json_members() { + _do_test_upload_json_members(0); + _do_test_upload_json_members(1); +} + +sub _do_test_upload_json_members($mojo=0) { + my @users_g0 = ( + new_domain_name() + ,new_domain_name() + ); + + my @groups = ( + {name => new_domain_name() + ,members => \@users_g0 } + ,{name => new_domain_name() } + ); + my $data = { + groups => \@groups + }; + + _do_upload_users_json( encode_json( $data ),$mojo,{ groups_found => 2,groups_added => 2, users_found => 2, users_added => 2} ); + for my $u ( @users_g0 ) { + my $user = Ravada::Auth::SQL->new(name => $u ); + ok($user->id, "Expecting user $u created"); + } + for my $g ( @groups) { + my $group = Ravada::Auth::Group->new(name => $g->{name}); + ok($group->id, "Expecting group $g->{name} created"); + } + + my $g0 = Ravada::Auth::Group->new(name => $groups[0]->{name}); + ok($g0->members,"Expecting members in ".$g0->name); + + for my $m (@{$groups[0]->{members}}) { + my ($found) = grep (/^$m$/ , $g0->members); + ok($found,"Expecting $m member"); + } + + my $g1 = Ravada::Auth::Group->new(name => $groups[1]->{name}); + ok(!$g1->members,"Expecting no members in ".$g1->name); + + # add more users + my @users_g0b = ( + new_domain_name() + ,new_domain_name() + ,$users_g0[0] + ); + + $groups[0]->{members} = \@users_g0b; + + _do_upload_users_json( encode_json( {groups => \@groups}),$mojo, { groups_found => 2,groups_added => 0, users_found => 3, users_added => 2} ); + + for my $name ( @users_g0 , @users_g0b ) { + + my $user = Ravada::Auth::SQL->new(name => $name ); + ok($user->id, "Expecting user $name created mojo=$mojo") or exit; + + my $g0 = Ravada::Auth::Group->new(name => $groups[0]->{name}); + my ($found) = grep (/^$name$/ , $g0->members); + ok($found,"Expecting $name member"); + } +} + +sub test_upload_json_members_flush() { + _do_test_upload_json_members_flush(0); + _do_test_upload_json_members_flush(1); +} + +sub _do_test_upload_json_members_flush($mojo) { + my @users_g0 = ( + new_domain_name() + ,new_domain_name() + ); + + my @groups = ( + {name => new_domain_name() + ,members => \@users_g0 } + ,{name => new_domain_name() } + ); + my $data = { + groups => \@groups + }; + + _do_upload_users_json( encode_json( $data ),$mojo,{ groups_found => 2,groups_added => 2, users_found => 2, users_added => 2} ); + for my $u ( @users_g0 ) { + my $user = Ravada::Auth::SQL->new(name => $u ); + ok($user->id, "Expecting user $u created"); + } + for my $g ( @groups) { + my $group = Ravada::Auth::Group->new(name => $g->{name}); + ok($group->id, "Expecting group $g->{name} created"); + } + + my $g0 = Ravada::Auth::Group->new(name => $groups[0]->{name}); + ok($g0->members,"Expecting members in ".$g0->name); + + for my $m (@{$groups[0]->{members}}) { + my ($found) = grep (/^$m$/ , $g0->members); + ok($found,"Expecting $m member"); + } + + my $g1 = Ravada::Auth::Group->new(name => $groups[1]->{name}); + ok(!$g1->members,"Expecting no members in ".$g1->name); + + # add more users + my @users_g0b = ( + new_domain_name() + ,new_domain_name() + ,$users_g0[0] + ); + + $groups[0]->{members} = \@users_g0b; + + _do_upload_users_json( encode_json( {groups => \@groups, options => {'flush' => 1}}),$mojo, { groups_found => 2,groups_added => 0, users_found => 3, users_added => 2} ); + + for my $name ( $users_g0[1] ) { + + my $user = Ravada::Auth::SQL->new(name => $name ); + ok($user->id, "Expecting user $name created"); + + my $g0 = Ravada::Auth::Group->new(name => $groups[0]->{name}); + my ($found) = grep (/^$name$/ , $g0->members); + ok(!$found,"Expecting no $name member") or exit; + } + + for my $name ( @users_g0b ) { + + my $user = Ravada::Auth::SQL->new(name => $name ); + ok($user->id, "Expecting user $name created"); + + my $g0 = Ravada::Auth::Group->new(name => $groups[0]->{name}); + my ($found) = grep (/^$name$/ , $g0->members); + ok($found,"Expecting $name member"); + } + +} + +sub test_upload_json_members_remove_empty() { + _do_test_upload_json_members_remove_empty(0); + _do_test_upload_json_members_remove_empty(1); +} + +sub _do_test_upload_json_members_remove_empty($mojo) { + my @users_g0 = ( + new_domain_name() + ,new_domain_name() + ); + + my @groups = ( + {name => new_domain_name() + ,members => \@users_g0 } + ,{name => new_domain_name() } + ); + my $data = { + groups => \@groups + }; + + _do_upload_users_json( encode_json( $data ), $mojo, { groups_found => 2,groups_added => 2, users_found => 2, users_added => 2} ); + for my $u ( @users_g0 ) { + my $user = Ravada::Auth::SQL->new(name => $u ); + ok($user->id, "Expecting user $u created"); + } + for my $g ( @groups) { + my $group = Ravada::Auth::Group->new(name => $g->{name}); + ok($group->id, "Expecting group $g->{name} created"); + } + + my $g0 = Ravada::Auth::Group->new(name => $groups[0]->{name}); + ok($g0->members,"Expecting members in ".$g0->name); + + for my $m (@{$groups[0]->{members}}) { + my ($found) = grep (/^$m$/ , $g0->members); + ok($found,"Expecting $m member"); + } + + my $g1 = Ravada::Auth::Group->new(name => $groups[1]->{name}); + ok(!$g1->members,"Expecting no members in ".$g1->name); + + # add more users + my @users_g0b = ( + new_domain_name() + ,new_domain_name() + ,$users_g0[0] + ); + + $groups[1]->{members} = \@users_g0b; + $groups[0]->{members} = []; + + _do_upload_users_json( encode_json( {groups => \@groups, options => {'flush'=>1,'remove_empty'=>1}}), $mojo, { groups_found => 2,groups_added => 0, users_found => 3, users_added => 2, groups_removed => 1} ); + + for my $name ( @users_g0b ) { + + my $user = Ravada::Auth::SQL->new(name => $name ); + ok($user->id, "Expecting user $name created"); + + my $g1 = Ravada::Auth::Group->new(name => $groups[1]->{name}); + ok($g1 && $g1->id) or exit; + my ($found) = grep (/^$name$/ , $g0->members); + ok(!$found,"Expecting $name member") or exit; + } + + $g0 = Ravada::Auth::Group->new(name => $groups[0]->{name}); + ok(!$g0->id,"Expecting $groups[0]->{name} removed"); + +} + + + + +sub test_upload_json_users_admin() { + _do_test_upload_json_users_admin(0); + _do_test_upload_json_users_admin(1); +} + +sub _do_test_upload_json_users_admin($mojo) { + my @users = ( + {name => new_domain_name() } + , {name => new_domain_name(), is_admin => 0 } + , {name => new_domain_name(), is_admin => 1 } + ); + my $data = { + users => \@users + }; + + _do_upload_users_json( $data, $mojo ); + + for my $u ( @users ) { + my ($name, $password) = ($u->{name} , $u->{password}); + my $user = Ravada::Auth::SQL->new(name => $name); + ok($user->id, "Expecting user $name created"); + is($user->external_auth, 'openid') or exit; + $u->{is_admin}=0 if !exists $u->{is_admin}; + is($user->is_admin, $u->{is_admin}); + } + + +} + +sub test_upload_json_users_pass() { + _do_test_upload_json_users_pass(0); + _do_test_upload_json_users_pass(1); +} + +sub _do_test_upload_json_users_pass($mojo) { + my $p1='a'; + my $p2 = 'b'; + my @users = ( + {name => new_domain_name(), password => $p1 } + , {name => new_domain_name(), password => $p2 } + ); + + _do_upload_users_json( encode_json( { users => \@users }), $mojo, undef, 'sql' ); + + for my $u ( @users ) { + my ($name, $password) = ($u->{name} , $u->{password}); + my $user = Ravada::Auth::SQL->new(name => $name); + ok($user->id, "Expecting user $name created"); + is($user->external_auth, '') or exit; + + $user = undef; + eval { + $user = Ravada::Auth::login( $name , $password); + }; + is($@,''); + ok($user,"Expecting $name/$password") or exit; + } +} ################################################################################ @@ -278,6 +738,10 @@ test_upload_no_admin($t); _login($t); +test_upload_json_fail(); + +test_upload_json(); + test_upload_group(); test_upload_group(1); # mojo test_upload_group(2); # mojo post diff --git a/t/user/10_domains.t b/t/user/10_domains.t index 2d16597e4..0490c76f7 100644 --- a/t/user/10_domains.t +++ b/t/user/10_domains.t @@ -11,6 +11,7 @@ use_ok('Ravada'); use_ok('Ravada::VM::Void'); use_ok('Ravada::Auth::SQL'); +init(); my $ravada = rvd_back(); # diff --git a/t/user/35_share.t b/t/user/35_share.t index f0948ade7..40d8a13ef 100644 --- a/t/user/35_share.t +++ b/t/user/35_share.t @@ -134,6 +134,7 @@ sub test_machine_info_shared($user, $clone) { ############################################################## +init(); clean(); for my $vm_name ( vm_names() ) { diff --git a/templates/main/admin_settings.html.ep b/templates/main/admin_settings.html.ep index 9ade25e46..046e6442f 100644 --- a/templates/main/admin_settings.html.ep +++ b/templates/main/admin_settings.html.ep @@ -117,45 +117,52 @@ % } - -%= include "/main/admin_settings_submit" -
-
-
<%=l 'Widget' %> - +
+
- + + + <%=l 'Authenticated users are allowed to log in' %> +
+ <%=l 'Only previously authorized users can log in.' %> + <%=l 'You can grant access to users from the users administration form:'%> + upload +
-
Content Security Policy
-
-
+
+

Auth OpenID

-
-
- - +
+ +
+
+
-
- % for my $item (sort keys %$csp) {
-
-
<%= $item %>
-
- +
+ +
+
+
-
- % }
%= include "/main/admin_settings_submit" diff --git a/templates/main/start.html.ep b/templates/main/start.html.ep index 6c947ebe7..52a256014 100644 --- a/templates/main/start.html.ep +++ b/templates/main/start.html.ep @@ -1,21 +1,49 @@ - + %= include 'bootstrap/header' %= include 'bootstrap/navigation'
-
-
-
+
+
+

<%= $login_message %>

- - + + +
+ + + + - + + + +
+ % if (scalar @$error) { % for my $i (@$error) {
@@ -23,14 +51,17 @@
% } % } - + + + + % if ($sso_available) {
- % if ($sso_available) { - % }
-
+ % } + +
<%=l 'A viewer is required to run the virtual machines.' %> <%=l 'Read more.' %>
diff --git a/templates/main/upload_group_members.html.ep b/templates/main/upload_group_members.html.ep index 22cf2d77c..564159b53 100644 --- a/templates/main/upload_group_members.html.ep +++ b/templates/main/upload_group_members.html.ep @@ -17,7 +17,7 @@ <%=l 'Upload a text file with the user names and passwords separated by a colon :' %> - + <%=l 'Upload a text file with the user names' %> @@ -32,7 +32,8 @@
diff --git a/templates/main/upload_users.html.ep b/templates/main/upload_users.html.ep index 22cf2d77c..fbb81a09f 100644 --- a/templates/main/upload_users.html.ep +++ b/templates/main/upload_users.html.ep @@ -17,7 +17,7 @@ <%=l 'Upload a text file with the user names and passwords separated by a colon :' %> - + <%=l 'Upload a text file with the user names' %> @@ -32,13 +32,14 @@
- @@ -59,8 +60,15 @@
% } else { - <%= $found %> <%=l 'users found in uploaded file' %>, - <%= $count %> <%=l 'created' %>. + <%= $output->{users_found} %> <%=l 'users found in uploaded file' %>, + <%= $output->{users_added} %> <%=l 'created' %>. +% if (exists $output->{groups_found}) { + <%= $output->{groups_found} %> <%=l 'groups found in uploaded file' %>, + <%= $output->{groups_added} %> <%=l 'created' %>. +% } +% if (exists $output->{groups_removed}) { + <%= $output->{groups_removed} %> <%= l 'groups removed'%>. +% }
<%=l 'Errors found' %>: <%= scalar(@$error) %>