Commit ddf4cfab authored by Francesc Guasch's avatar Francesc Guasch
Browse files

Merge branch 'develop' into main

parents 5996399c 549d85df
......@@ -5,7 +5,7 @@ https://code.jquery.com/jquery-3.5.1.slim.min.js jquery/
https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js
https://jqueryui.com/resources/download/jquery-ui-1.11.4.zip
https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js
https://code.angularjs.org/1.7.8/angular-1.7.8.zip
https://code.angularjs.org/1.8.2/angular-1.8.2.zip
https://cdn.jsdelivr.net/npm/ui-bootstrap4@3.0.6/dist/ui-bootstrap-tpls.js
https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js raphael.js/
https://cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.min.js morris.js/
......
......@@ -661,7 +661,7 @@ sub _update_isos {
,min_disk_size => '0'
}
);
$self->_scheduled_fedora_releases(\%data);
$self->_scheduled_fedora_releases(\%data) if $0 !~ /\.t$/;
$self->_update_table($table, $field, \%data);
}
......@@ -1181,6 +1181,9 @@ sub _add_indexes_generic($self) {
,"unique (id_domain,name):name"
,"unique(id_vm,public_port)"
]
,group_access => [
"unique (id_domain,name)"
]
,requests => [
"index(status,at_time)"
,"index(id,date_changed,status,at_time)"
......@@ -1730,6 +1733,13 @@ sub _sql_create_tables($self) {
,extra => 'TEXT'
}
]
,[
group_access => {
id => 'integer NOT NULL PRIMARY KEY AUTO_INCREMENT'
,id_domain => 'integer NOT NULL references `domains` (`id`) ON DELETE CASCADE'
,name => 'char(80)'
}
]
,
[
settings => {
......@@ -1750,6 +1760,7 @@ sub _sql_create_tables($self) {
,id_owner => 'int not null'
,background_color => 'varchar(20)'
,date_created => 'datetime DEFAULT CURRENT_TIMESTAMP'
,date_changed => 'timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'
}
]
,
......@@ -1763,6 +1774,7 @@ sub _sql_create_tables($self) {
,time_end => 'time not null'
,date_booking => 'date'
,visibility => "enum ('private','public') default 'public'"
,date_changed => 'timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'
}
]
,
......@@ -1772,6 +1784,7 @@ sub _sql_create_tables($self) {
,id_booking_entry
=> 'int not null references `booking_entries` (`id`) ON DELETE CASCADE'
,ldap_group => 'varchar(255) not null'
,date_changed => 'timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'
}
]
,
......@@ -1781,6 +1794,7 @@ sub _sql_create_tables($self) {
,id_booking_entry
=> 'int not null references `booking_entries` (`id`) ON DELETE CASCADE'
,id_user => 'int not null references `users` (`id`) ON DELETE CASCADE'
,date_changed => 'timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'
}
]
,
......@@ -1790,6 +1804,7 @@ sub _sql_create_tables($self) {
,id_booking_entry
=> 'int not null references `booking_entries` (`id`) ON DELETE CASCADE'
,id_base => 'int not null references `domains` (`id`) ON DELETE CASCADE'
,date_changed => 'timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'
}
]
,
......@@ -2081,6 +2096,9 @@ sub _port_definition($driver, $definition0){
my ($size) = sort map { length($_) } @found;
return " varchar($size) $default";
}
elsif ($definition0 =~ /^timestamp /) {
$definition0 = 'timestamp';
}
}
return $definition0;
}
......@@ -4134,6 +4152,9 @@ sub _cmd_remove_hardware {
my $domain = $self->search_domain_by_id($id_domain);
my $user = Ravada::Auth::SQL->search_by_id($uid);
die "Error: User ".$user->name." not allowed to remove hardware from machine "
.$domain->name
if !$user->is_admin;
$domain->remove_controller($hardware, $index);
}
......@@ -4151,7 +4172,7 @@ sub _cmd_change_hardware {
my $user = Ravada::Auth::SQL->search_by_id($uid);
die "Error: User ".$user->name." not allowed\n"
if !$user->is_admin;
if $hardware ne 'memory' && !$user->is_admin;
$domain->change_hardware(
$request->args('hardware')
......@@ -4420,7 +4441,8 @@ sub _cmd_refresh_machine($self, $request) {
$domain->info($user);
$domain->client_status(1) if $is_active;
Ravada::Request->refresh_machine_ports(id_domain => $domain->id, uid => $user->id)
Ravada::Request->refresh_machine_ports(id_domain => $domain->id, uid => $user->id
,retry => 20)
if $is_active && $domain->ip;
}
......@@ -5424,7 +5446,7 @@ sub _cmd_open_exposed_ports($self, $request) {
Ravada::Request->refresh_machine_ports(
uid => $request->args('uid'),
,id_domain => $domain->id
,retry => 10
,retry => 100
);
}
......
......@@ -121,11 +121,11 @@ sub enable_LDAP {
return $value;
}
=head2 enable_CAS
=head2 enable_SSO
Sets or get CAS support.
Sets or get SSO support.
Ravada::Auth::enable_CAS(0);
Ravada::Auth::enable_SSO(0);
print "SSO is supported" if Ravada::Auth::enable_SSO();
......
......@@ -181,10 +181,6 @@ sub _new_uid($ldap=_init_ldap_admin(), $base=_dc_base()) {
}
}
sub default_object_class() {
return @OBJECT_CLASS;
}
sub _password_store($password, $storage, $algorithm=undef) {
return _password_rfc2307($password, $algorithm) if lc($storage) eq 'rfc2307';
return _password_pbkdf2($password, $algorithm) if lc($storage) eq 'pbkdf2';
......@@ -306,7 +302,6 @@ sub search_user {
timelimit => $timelimit
);
warn "LDAP retry ".$mesg->code." ".$mesg->error if $retry > 1;
if ( $retry <= 3 && $mesg->code && $mesg->code != 4 ) {
warn "LDAP error ".$mesg->code." ".$mesg->error."."
......@@ -461,6 +456,36 @@ sub search_group {
return $entries[0];
}
=head2 search_group_members
=cut
sub search_group_members($cn, $retry = 0) {
my $base = "ou=groups,"._dc_base();
my $ldap = _init_ldap_admin();
my $mesg = $ldap ->search (
filter => "memberuid=$cn"
,base => $base
,sizelimit => 100
);
if ( ($mesg->code == 1 || $mesg->code == 81) && $retry <3 ) {
$LDAP_ADMIN = undef;
return search_group_members($cn, $retry+1);
}
warn $mesg->code." ".$mesg->error." [base: $base]" if $mesg->code;
my @entries = map { $_->get_value('cn') } $mesg->entries();
$mesg = $ldap ->search (
filter => "member=cn=$cn,"._dc_base()
,base => $base
,sizelimit => 100
);
my @entries2 = map { $_->get_value('cn') } $mesg->entries();
return (sort (@entries,@entries2));
}
=head2 add_to_group
Adds user to group
......@@ -472,7 +497,10 @@ Adds user to group
sub add_to_group {
my ($dn, $group_name) = @_;
if ( $dn !~ /=.*,/ ) {
my $user = search_user(name => $dn) or confess "Error: user '$dn' not found";
my $user = search_user(name => $dn, field => 'uid');
$user = search_user(name => $dn, field => 'cn') if !$user;
confess "Error: user '$dn' not found" if !$user;
$dn = $user->dn;
}
......@@ -565,6 +593,12 @@ sub _search_posix_group($self, $name) {
return $posix_group[0];
}
=head2 group_members
Returns a list of the group members
=cut
sub group_members {
return _group_members(@_);
}
......@@ -931,9 +965,16 @@ sub is_member($cn, $group) {
my @members = _group_members($group);
return 1 if grep /^$cn$/, @members;
$user = search_user($cn) or confess "Error: unknown user '$cn'"
if !$user;
$dn = $user->dn if !$dn;
if (!$dn) {
if (!$user) {
for my $field ( 'uid','cn') {
$user = search_user(name => $cn, field => $field);
last if $user;
}
confess "Error: unknown user '$cn'" if !$user;
}
$dn = $user->dn if !$dn;
}
return 1 if grep /^$dn$/, @members;
......
......@@ -1120,10 +1120,27 @@ sub ldap_entry($self) {
return $self->{_ldap_entry} if $self->{_ldap_entry};
my @entries = Ravada::Auth::LDAP::search_user( name => $self->name );
$self->{_ldap_entry} = $entries[0];
for my $field ( qw(uid cn)) {
my ($entry) = Ravada::Auth::LDAP::search_user( name => $self->name,field => $field );
next if !$entry;
$self->{_ldap_entry} = $entry;
return $entry;
}
return;
}
=head2 groups
Returns a list of the groups this user belogs to
=cut
sub groups($self) {
return () if !$self->external_auth || $self->external_auth ne 'ldap';
my @groups = Ravada::Auth::LDAP::search_group_members($self->name);
return @groups;
return $self->{_ldap_entry};
}
sub AUTOLOAD($self, $domain=undef) {
......
......@@ -362,6 +362,7 @@ sub _load_allowed {
my $ldap_entry;
$ldap_entry = $self->ldap_entry if $self->external_auth && $self->external_auth eq 'ldap';
my @domains = $self->_list_domains_access();
for my $id_domain ( @domains ) {
......@@ -403,6 +404,26 @@ sub _load_allowed {
$self->{_allowed}->{$id_domain} = 1;
}
}
$self->_load_allowed_groups();
}
sub _load_allowed_groups($self) {
my $sth = $$CONNECTOR->dbh->prepare("SELECT id_domain,name from group_access");
my ($id_domain, $name);
$sth->execute();
$sth->bind_columns(\($id_domain, $name));
while ( $sth->fetch ) {
next if $self->{_allowed}->{$id_domain};
$self->{_allowed}->{$id_domain} = 0;
next unless $self->is_external && $self->external_auth eq 'ldap';
$self->{_allowed}->{$id_domain} = 1
if Ravada::Auth::LDAP::is_member($self->ldap_entry, $name);
}
}
1;
......@@ -364,10 +364,7 @@ sub user_allowed($entry, $user_name) {
return 1 if $user_name eq $allowed_user_name;
}
for my $group_name ($entry->ldap_groups) {
my $group = Ravada::Auth::LDAP->_search_posix_group($group_name);
my @member = $group->get_value('memberUid');
my ($found) = grep /^$user_name$/,@member;
return 1 if $found;
return 1 if Ravada::Auth::LDAP::is_member($user_name, $group_name);
}
return 0;
}
......
......@@ -499,7 +499,7 @@ sub _start_checks($self, @args) {
}
sub _search_already_started($self, $fast = 0) {
my $sql = "SELECT id FROM vms where vm_type=?";
my $sql = "SELECT id FROM vms where vm_type=? AND enabled=1";
$sql .= " AND is_active=1" if $fast;
my $sth = $$CONNECTOR->dbh->prepare($sql);
$sth->execute($self->_vm->type);
......@@ -3809,9 +3809,12 @@ sub _post_start {
, retry => $RETRY_SET_TIME
) if $set_time;
Ravada::Request->enforce_limits(at => time + 60);
Ravada::Request->manage_pools(
if ( $self->is_pool ) {
$self->_data('comment' => $arg{user}->name);
Ravada::Request->manage_pools(
uid => Ravada::Utils::user_daemon->id
) if $self->is_pool;
)
}
$self->_check_port_conflicts();
......@@ -5133,7 +5136,7 @@ sub _search_pool_clone($self, $user) {
my ($clone_down, $clone_free_up, $clone_free_down);
my ($clones_in_pool, $clones_used) = (0,0);
for my $current ( $self->clones) {
for my $current ( sort { $a->{name} cmp $b->{name} } $self->clones) {
if ( $current->{id_owner} == $user->id
&& $current->{status} =~ /^(active|hibernated)$/) {
my $clone = Ravada::Domain->open($current->{id});
......@@ -5620,9 +5623,12 @@ Arguments is a named list
=cut
sub grant_access($self, %args) {
my $type = delete $args{type} or confess "Error: Missing type";
return $self->_allow_group_access(%args) if $type eq 'group';
my $attribute = delete $args{attribute} or confess "Error: Missing attribute";
my $value = delete $args{value} or confess "Error: Missing value";
my $type = delete $args{type} or confess "Error: Missing type";
my $allowed = delete $args{allowed};
$allowed = 1 if !defined $allowed;
my $last = ( delete $args{last} or 0 );
......@@ -5656,6 +5662,35 @@ sub grant_access($self, %args) {
$self->_fix_default_access($type) unless $value eq '*';
}
sub _allow_group_access($self, %args) {
my $group = delete $args{group} or confess "Error: group required";
confess "Error: unknown args ".Dumper(\%args) if keys %args;
my $sth = $$CONNECTOR->dbh->prepare(
"INSERT INTO group_access "
."( id_domain,name)"
." VALUES(?,? )"
);
$sth->execute($self->id, $group);
}
=head2 list_access_groups
Returns the list of groups who can access this virtual machine
=cut
sub list_access_groups($self) {
my $sth = $$CONNECTOR->dbh->prepare("SELECT name from group_access "
." WHERE id_domain=?"
);
$sth->execute($self->id);
my @groups;
while ( my ($name) = $sth->fetchrow ) {
push @groups,($name);
}
return @groups;
}
sub _fix_default_access($self, $type) {
my @list = $self->list_access($type);
my $id_default;
......@@ -6313,7 +6348,7 @@ sub purge($self, $request=undef) {
sub _check_port($self, $port, $ip=$self->ip, $request=undef) {
my ($out, $err) = $self->_vm->run_command("nc","-z","-v",$ip,$port);
$request->error($err) if $err;
return 1 if $err =~ /succeeded!/;
return 0 if $err =~ /failed/;
warn $err;
......@@ -6336,6 +6371,12 @@ sub _set_displays_down($self) {
$sth->execute($self->id);
}
=head2 refresh_ports
Refresh the status of the exposed ports
=cut
sub refresh_ports($self, $request=undef) {
my $sth_update = $$CONNECTOR->dbh->prepare("UPDATE domain_ports "
." SET is_active=? "
......@@ -6357,14 +6398,16 @@ sub refresh_ports($self, $request=undef) {
$is_port_active = $self->_check_port($port->{internal_port}, $ip, $request);
} else {
$is_port_active = 0;
$port_down++;
}
$port_down++ if !$is_port_active;
$sth_update->execute($is_port_active, $self->id, $port->{id});
$sth_update_display->execute($is_port_active, $port->{id})
if $port->{name};
$msg .= " , " if $msg;
$msg .= " $port->{internal_port} $is_port_active";
my $is_port_active_txt = "up";
$is_port_active_txt = "down" if !$is_port_active;
$msg .= " $port->{internal_port}:$is_port_active_txt";
}
die "Virtual machine ".$self->name." is not up. retry.\n"if !$ip;
die "Virtual machine ".$self->name." $ip has ports down: $msg. retry.\n"
......
......@@ -1253,6 +1253,20 @@ sub _set_boot_order($self, $index, $order) {
$self->domain($new_domain);
}
sub _get_boot_order($self, $index) {
my $doc = XML::LibXML->load_xml(string => $self->domain->get_xml_description(Sys::Virt::Domain::XML_INACTIVE));
my $count = 0;
for my $device ($doc->findnodes('/domain/devices/disk')) {
if ( $count++ == $index ) {
my ($boot) = $device->findnodes('boot');
if ($boot) {
return $boot->getAttribute('order');
}
return;
}
}
}
sub _cmd_boot_order($self, $set, $index=undef, $order=1) {
my $doc = XML::LibXML->load_xml(string => $self->domain->get_xml_description(Sys::Virt::Domain::XML_INACTIVE));
my $count = 0;
......@@ -1260,26 +1274,55 @@ sub _cmd_boot_order($self, $set, $index=undef, $order=1) {
# if index is not numeric is the file, search the real index
$index = $self->_search_volume_index($index) if defined $index && $index !~ /^\d+$/;
if ( $set ) {
my $current_order = $self->_get_boot_order($index);
return $doc if defined $current_order && $current_order == $order;
}
my %used_order;
my $changed = 0;
for my $device ($doc->findnodes('/domain/devices/disk')) {
my ($boot) = $device->findnodes('boot');
if ( defined $index && $count++ != $index) {
next if !$set || !$boot;
my $this_order = $boot->getAttribute('order');
next if $this_order < $order;
next if !defined $this_order || $this_order < $order;
$boot->setAttribute( order => $this_order+1);
$used_order{$this_order+1}++;
$changed++;
next;
}
if (!$set) {
next if !$boot;
$device->removeChild($boot);
} else {
my $old_order;
$old_order = $boot->getAttribute('order') if $boot;
return $doc if defined $old_order && $old_order == $order;
$boot = $device->addNewChild(undef,'boot') if !$boot;
$boot->setAttribute( order => $order );
$used_order{$order}++;
$changed++;
}
}
$self->_bump_boot_order_interfaces($doc,\%used_order) if $changed;
return $doc;
}
sub _bump_boot_order_interfaces($self, $doc, $used_order) {
for my $boot ($doc->findnodes('/domain/devices/interface/boot')) {
my $current_order = $boot->getAttribute('order');
next if !defined $current_order || !$used_order->{$current_order};
my $free_order = $current_order;
for (;;) {
last if !$used_order->{$free_order};
$free_order++;
}
$boot->setAttribute('order' => $free_order);
}
}
sub _search_volume_index($self, $file) {
my $doc = XML::LibXML->load_xml(string => $self->domain->get_xml_description(Sys::Virt::Domain::XML_INACTIVE));
my $index = 0;
......
......@@ -338,7 +338,7 @@ sub list_domains($self, %args) {
$row->{node} = $domain->_vm->name if $domain->_vm;
$row->{remote_ip} = $domain->client_status
if $domain->client_status && $domain->client_status !~ /^connected/;
if ( $domain->client_status && $domain->client_status =~ /^connected \((.*?)\)/ ) {
if ($domain->remote_ip && $domain->client_status && $domain->client_status =~ /^connected \((.*?)\)/ ) {
$row->{remote_ip} = $domain->remote_ip.".$1";
}
$row->{autostart} = $domain->_data('autostart');
......
......@@ -48,6 +48,8 @@ our %TABLE_CHANNEL = (
list_alerts => 'messages'
,list_machines => 'domains'
,list_machines_tree => 'domains'
,list_machines_user_including_privates => ['domains','bookings','booking_entries'
,'booking_entry_ldap_groups', 'booking_entry_users','booking_entry_bases']
,list_requests => 'requests'
);
......@@ -390,7 +392,7 @@ sub _date_changed_table($self, $table) {
my $sth = $rvd->_dbh->prepare("SELECT MAX(date_changed) FROM $table");
$sth->execute;
my ($date) = $sth->fetchrow;
return $date;
return ($date or '');
}
sub _count_table($self, $table) {
......@@ -406,9 +408,22 @@ sub _new_info($self, $key) {
my $channel = $self->clients->{$key}->{channel};
$channel =~ s{/.*}{};
my $table = $TABLE_CHANNEL{$channel} or return;
my $table0 = $TABLE_CHANNEL{$channel} or return;
if (!ref($table0)) {
$table0 = [$table0];
}
my $count = '';
my $date = '';
return ($self->_count_table($table),$self->_date_changed_table($table));
for my $table (@$table0) {
$count .= ":" if $count;
$count .= $self->_count_table($table);
$date .= ":" if $date;
$date .= $self->_date_changed_table($table)
}
return ($count, $date);
}
......
......@@ -3,7 +3,7 @@
"description": "Ravada Virtual Desktop",
"version": "0.3.1",
"dependencies": {
"angular": "1.7.9",
"angular": "1.8.2",
"angular-animate": "1.5.5",
"angular-aria": "1.5.5",
"angular-material": "1.1.0",
......
......@@ -401,12 +401,47 @@ ravadaApp.directive("solShowMachine", swMach)
$scope.show_machine = { '0': false };
};
function usersPageC($scope, $http, $interval, request) {
$scope.action = function(target,action,machineId){
$http.get('/'+target+'/'+action+'/'+machineId+'.json');
function usersPageC($scope, $http, $interval, request) {
$scope.list_groups= function() {
$scope.loading_groups = true;
$scope.error = '';
$http.get('/list_ldap_groups')
.then(function(response) {
$scope.loading_groups = false;
$scope.groups = response.data;
});
};
$scope.list_user_groups = function(id_user) {
$http.get('/user/list_groups/'+id_user)
.then(function(response) {
$scope.user_groups = response.data;
});
};
$scope.add_group_member = function(id_user, cn, group) {
$http.post("/ldap/group/add_member/"
,JSON.stringify(
{ 'group': group