Commit 96974d27 authored by Francesc Guasch's avatar Francesc Guasch
Browse files

Merge branch 'develop' of github.com:UPC/ravada into develop

parents a35af754 73b78de4
......@@ -4420,7 +4420,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 +5425,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
);
}
......
......@@ -306,7 +306,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 +460,28 @@ sub search_group {
return $entries[0];
}
sub search_group_member($cn) {
my $base = "ou=groups,"._dc_base();
my $ldap = _init_ldap_admin();
my $mesg = $ldap ->search (
filter => "memberuid=$cn"
,base => $base
,sizelimit => 100
);
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
......
......@@ -1126,6 +1126,13 @@ sub ldap_entry($self) {
return $self->{_ldap_entry};
}
sub groups($self) {
return () if !$self->external_auth || $self->external_auth ne 'ldap';
my @groups = Ravada::Auth::LDAP::search_group_member($self->name);
return @groups;
}
sub AUTOLOAD($self, $domain=undef) {
my $name = $AUTOLOAD;
......
......@@ -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;
}
......
......@@ -6316,7 +6316,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;
......@@ -6360,14 +6360,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;
......
......@@ -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
,'cn': cn
})
).then(function(response) {
$scope.error = response.data.error;
$scope.list_user_groups(id_user);
});
};
$scope.remove_group_member = function(id_user, dn, group) {
$http.post("/ldap/group/remove_member/"
,JSON.stringify(
{ 'group': group
,'dn': dn
})
).then(function(response) {
$scope.error = response.data.error;
$scope.list_user_groups(id_user);
});
};
$scope.list_groups();
};
//On load code
};
function messagesPageC($scope, $http, $interval, request) {
$scope.getMessages = function() {
......
......@@ -314,7 +314,7 @@ get '/admin/group/#name' => sub($c) {
push @{$c->stash->{js}}, '/js/admin.js';
my $group = Ravada::Auth::LDAP::search_group(name => $c->stash('name'));
$c->stash(object_class => [$group->get_value('objectClass')]);
$c->stash(object_class => [ grep !/^top$/,$group->get_value('objectClass')]);
return $c->render( template => "/main/admin_group");
};
......@@ -1078,6 +1078,7 @@ any '/admin/user/(:id).(:type)' => sub {
my $c = shift;
return access_denied($c) if !$USER->can_manage_users() && !$USER->can_grant();
push @{$c->stash->{js}}, '/js/admin.js';
my $user = Ravada::Auth::SQL->search_by_id($c->stash('id'));
return $c->render(text => "Unknown user id: ".$c->stash('id'))
......@@ -1114,6 +1115,14 @@ any '/admin/user/(:id).(:type)' => sub {
return $c->render(template => 'main/manage_user');
};
get '/user/list_groups/(#id_user)' => sub($c) {
my $id_user = $c->stash('id_user');
return _access_denied($c) unless $USER->is_admin || $id_user == $USER->id;
my $user = Ravada::Auth::SQL->search_by_id($id_user);
return $c->render(json => [$user->groups()]);
};
any '/user/change_password' => sub {
my $c = shift;
return change_password($c);
......
use warnings;
use strict;
use Carp qw(carp confess cluck);
use Data::Dumper;
use Test::More;
use XML::LibXML;
no warnings "experimental::signatures";
use feature qw(signatures);
use_ok('Ravada');
use_ok('Ravada::Request');
use lib 't/lib';
use Test::Ravada;
init();
my $xml =<<EOT;
<domain type='kvm'>
<name>Windows-10-2021</name>
<uuid>fa79d9cf-2e6c-4266-a4e6-6a21a7f36c64</uuid>
<metadata>
<libosinfo:libosinfo xmlns:libosinfo="http://libosinfo.org/xmlns/libvirt/domain/1.0">
<libosinfo:os id="http://microsoft.com/win/10"/>
</libosinfo:libosinfo>
</metadata>
<memory unit='KiB'>4194304</memory>
<currentMemory unit='KiB'>4194304</currentMemory>
<vcpu placement='static'>2</vcpu>
<sysinfo type='smbios'/>
<os>
<type arch='x86_64' machine='pc-i440fx-2.11'>hvm</type>
<bootmenu enable='yes'/>
<smbios mode='sysinfo'/>
</os>
<features>
<acpi/>
<apic/>
<hyperv>
<relaxed state='on'/>
<vapic state='on'/>
<spinlocks state='on' retries='8191'/>
<vpindex state='on'/>
<synic state='on'/>
<stimer state='on'/>
<frequencies state='on'/>
</hyperv>
<vmport state='off'/>
<ioapic driver='kvm'/>
</features>
<cpu mode='host-passthrough' check='partial'>
<cache mode='passthrough'/>
</cpu>
<clock offset='localtime'>
<timer name='hypervclock' present='yes'/>
<timer name='rtc' tickpolicy='catchup'/>
<timer name='pit' tickpolicy='delay'/>
<timer name='hpet' present='no'/>
</clock>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>restart</on_crash>
<pm>
<suspend-to-mem enabled='no'/>
<suspend-to-disk enabled='no'/>
</pm>
<devices>
<emulator>/usr/bin/kvm-spice</emulator>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' cache='directsync' io='native' discard='unmap'/>
<source file='/var/lib/libvirt/images/Windows-10-2021-fernandosda.Windows-10-2021-sda.qcow2'/>
<backingStore/>
<target dev='sda' bus='scsi'/>
<boot order='1'/>
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk>
<disk type='file' device='cdrom'>
<driver name='qemu' type='raw'/>
<target dev='sdb' bus='scsi'/>
<readonly/>
<boot order='2'/>
<address type='drive' controller='0' bus='0' target='0' unit='1'/>
</disk>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' cache='directsync' io='native'/>
<source file='/var/lib/libvirt/images/Windows-10-2021-fernandosdd.Windows-10-2021-sdd.DATA.qcow2'/>
<backingStore/>
<target dev='sdd' bus='scsi'/>
<boot order='4'/>
<address type='drive' controller='0' bus='0' target='0' unit='3'/>
</disk>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' cache='directsync' io='native'/>
<source file='/var/lib/libvirt/images/Windows-10-2021-fernandosde.Windows-10-2021-sde.SWAP.qcow2'/>
<backingStore/>
<target dev='sde' bus='scsi'/>
<address type='drive' controller='0' bus='0' target='0' unit='4'/>
</disk>
<controller type='pci' index='0' model='pci-root'/>
<controller type='ide' index='0'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
</controller>
<controller type='virtio-serial' index='0'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/>
</controller>
<controller type='usb' index='0' model='qemu-xhci' ports='15'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x08' function='0x0'/>
</controller>
<controller type='scsi' index='0' model='virtio-scsi'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>
</controller>
<controller type='sata' index='0'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x09' function='0x0'/>
</controller>
<interface type='network'>
<mac address='52:54:00:3e:f4:e6'/>
<source network='default'/>
<model type='virtio'/>
<boot order='3'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
</interface>
<serial type='pty'>
<target type='isa-serial' port='0'>
<model name='isa-serial'/>
</target>
</serial>
<console type='pty'>
<target type='serial' port='0'/>
</console>
<channel type='spicevmc'>
<target type='virtio' name='com.redhat.spice.0'/>
<address type='virtio-serial' controller='0' bus='0' port='1'/>
</channel>
<channel type='unix'>
<target type='virtio' name='org.qemu.guest_agent.0'/>
<address type='virtio-serial' controller='0' bus='0' port='2'/>
</channel>
<channel type='spiceport'>
<source channel='org.spice-space.webdav.0'/>
<target type='virtio' name='org.spice-space.webdav.0'/>
<address type='virtio-serial' controller='0' bus='0' port='3'/>
</channel>
<input type='mouse' bus='ps2'/>
<input type='keyboard' bus='ps2'/>
<input type='tablet' bus='usb'>
<address type='usb' bus='0' port='5'/>
</input>
<graphics type='spice' autoport='yes' listen='147.83.36.253'>
<listen type='address' address='147.83.36.253'/>
<image compression='auto_glz'/>
<jpeg compression='auto'/>
<zlib compression='auto'/>
<playback compression='on'/>
<streaming mode='filter'/>
<gl enable='no'/>
</graphics>
<sound model='ac97'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
</sound>
<video>
<model type='qxl' ram='65536' vram='65536' vgamem='16384' heads='1' primary='yes'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
</video>
<redirdev bus='usb' type='spicevmc'>
<address type='usb' bus='0' port='1'/>
</redirdev>
<redirdev bus='usb' type='spicevmc'>
<address type='usb' bus='0' port='2'/>
</redirdev>
<redirdev bus='usb' type='spicevmc'>
<address type='usb' bus='0' port='3'/>
</redirdev>
<redirdev bus='usb' type='spicevmc'>
<address type='usb' bus='0' port='4'/>
</redirdev>
<memballoon model='virtio'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>
</memballoon>
</devices>
</domain>
EOT
sub _fix_domain_config($domain) {
my $old_doc = XML::LibXML->load_xml( string => $domain->xml_description() );
my ($old_uuid) = $old_doc->findnodes('/domain/uuid/text()');
my $doc = XML::LibXML->load_xml(string => $xml );
my ($node_name) = $doc->findnodes('/domain/name/text()');
$node_name->setData($domain->name);
my ($uuid) = $doc->findnodes('/domain/uuid/text()');
$uuid->setData($old_uuid);
for my $volume ( $doc->findnodes("/domain/devices/disk/source") ) {
my $old_file = $volume->getAttribute('file');
my ($path,$ext) = $old_file =~ m{(.*)/.*(-sd.*)};
my $new_drive = $path."/".new_domain_name().$ext;
$volume->setAttribute(file => $new_drive);
}
$domain->reload_config($doc);
}
sub test_change_capacity($vm, $new_boot_order = undef) {
my $domain = create_domain($vm);
_fix_domain_config($domain);
my $doc = XML::LibXML->load_xml(string => $domain->xml_description );
my $index = 0;
my @old_boot;
for my $disk ($doc->findnodes("/domain/devices/disk")) {
my ($boot_node) = $disk->findnodes('boot');
my $old_boot_order;
$old_boot_order = $boot_node->getAttribute('order') if $boot_node;
$old_boot[$index++] = $old_boot_order;
my $boot;
$boot = $new_boot_order if defined $new_boot_order;
next if $disk->getAttribute('device') eq 'cdrom';
my ($source) = $disk->findnodes('source');
die $disk->toString() unless $source;
my $file = $source->getAttribute('file');
$vm->run_command("qemu-img","create","-f","qcow2",$file,"10M");
my ($target) = $disk->findnodes('target');
my $driver = $target->getAttribute('bus');
my $new_capacity = '444M';
my $req = Ravada::Request->change_hardware(
uid => user_admin->id
,id_domain => $domain->id
,index => $index-1
,hardware => 'disk'
,data => { driver => $driver , boot => $boot, file => $file
, capacity => $new_capacity }
);
wait_request( debug => 0 );
is($req->status,'done');
is($req->error, '');
test_boot_order($domain, $index-1, $boot) if $new_boot_order;
}
test_all_boot_order($domain,\@old_boot) if !$new_boot_order;
$domain->start(user => user_admin , remote_ip => '192.0.9.1');
$domain->remove(user_admin);
}
sub test_boot_order($domain, $index, $boot) {
my $domain2 = Ravada::Domain->open($domain->id);
is($domain2->_get_boot_order($index), $boot,"Expecting boot order for $index") or exit;
}
sub test_all_boot_order($domain, $old_boot) {
my $doc = XML::LibXML->load_xml(string => $domain->xml_description );
my $index = 0;
for my $disk ($doc->findnodes("/domain/devices/disk")) {
my ($boot_node) = $disk->findnodes('boot');
my $boot;
$boot = $boot_node->getAttribute('order') if $boot_node;
is($boot,$old_boot->[$index],"boot order for disk $index");
$index++;
}
}
my $vm = rvd_back->search_vm('KVM');
test_change_capacity($vm);
for my $n ( 1 .. 4 ) {
test_change_capacity($vm,$n);
}
done_testing();
......@@ -3,12 +3,16 @@
<body id="page-top" data-spy="scroll" data-target=".fixed-top" role="document" ng-app="ravada.app">
<div id="wrapper">
%= include 'bootstrap/navigation'
<div id="page-wrapper">
<div id="page-wrapper" ng-controller="usersPage" ng-init="list_user_groups(<%= $user->id %>)">
<div class="page-header">
<div class="card">
<div class="card-header">
<h2><%=l 'User' %>&nbsp;<%= $user->name %></h2>
<h2><%=l 'User' %>&nbsp;<%= $user->name %>
% if ( $user->external_auth ) {
(<%= $user->external_auth %>)
% }
</h2>
</div> <!-- del panel heading-->
</div>
<ul class="nav nav-tabs" id="myTab" role="tablist">
......@@ -27,6 +31,12 @@
<a class="nav-link" href="#password" role="tab" data-toggle="tab" aria-controls="password" aria-selected="true">Password</a>
</li>
% }
% if ( $_user->is_admin && $user->is_external && $user->external_auth eq 'ldap' ) {
<li class="nav-item">
<a class="nav-link" href="#groups" role="tab" data-toggle="tab" aria-controls="groups" aria-selected="true">Groups</a>
</li>
% }
</ul>
<div class="tab-content" id="myTabContent">
% if ( $_user->is_admin ) {
......@@ -43,6 +53,11 @@
<div class="tab-pane fade" id="password" role="tabpanel" aria-labelledby="password-tab">
%= include '/main/manage_user_password'
</div>
% }
% if ( $_user->is_admin && $user->is_external && $user->external_auth eq 'ldap' ) {
<div class="tab-pane fade" id="groups" role="tabpanel" aria-labelledby="group-tab">
%= include '/main/manage_user_groups'
</div>
% }
</div>
</div><! --page-header -->
......
<div class="card-body">
<%=l 'Add to group' %>
<span ng-show="loading_users"><i class="fas fa-sync-alt fa-spin"></i></span>
<span ng-show="user_groups && !user_groups.length">
<%=l 'No LDAP groups created.' %>
<a href="/admin/groups"><%=l 'Add groups' %></a>
</span>
<select
ng-show="groups && groups.length"
ng-model="new_group"
ng-options="group for group in groups"
>