Skip to content

Commit

Permalink
Feat: hide clones (#2020)
Browse files Browse the repository at this point in the history
manage base options and non-public access

closes #2012
  • Loading branch information
frankiejol authored Feb 5, 2024
1 parent cda3f4b commit 35534f7
Show file tree
Hide file tree
Showing 12 changed files with 317 additions and 28 deletions.
1 change: 1 addition & 0 deletions lib/Ravada.pm
Original file line number Diff line number Diff line change
Expand Up @@ -2888,6 +2888,7 @@ sub _upgrade_tables {
$self->_upgrade_table('domains','has_backups','int not null default 0');
$self->_upgrade_table('domains','auto_compact','int default NULL');
$self->_upgrade_table('domains','date_status_change' , 'datetime');
$self->_upgrade_table('domains','show_clones' , 'int not null default 1');

$self->_upgrade_table('domains_network','allowed','int not null default 1');

Expand Down
6 changes: 6 additions & 0 deletions lib/Ravada/Domain.pm
Original file line number Diff line number Diff line change
Expand Up @@ -2069,6 +2069,8 @@ sub info($self, $user) {
id => $self->id
,name => $self->name
,is_base => $self->is_base
,is_public => $self->is_public
,show_clones => $self->show_clones
,id_base => $self->id_base
,is_active => $is_active
,is_hibernated => $self->is_hibernated
Expand Down Expand Up @@ -4490,6 +4492,10 @@ sub is_public {
return $self->_data('is_public');
}

sub show_clones($self,$value=undef) {
return $self->_data('show_clones',$value);
}

=head2 is_volatile
Returns if the domain is volatile, so it will be removed on shutdown
Expand Down
16 changes: 11 additions & 5 deletions lib/Ravada/Front.pm
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,14 @@ Returns: listref of machines

sub list_machines_user($self, $user, $access_data={}) {
my $sth = $CONNECTOR->dbh->prepare(
"SELECT id,name,alias,is_public, description, screenshot, id_owner, is_base, date_changed"
"SELECT id,name,alias,is_public, description, screenshot, id_owner, is_base, date_changed, show_clones"
." FROM domains "
." WHERE ( is_base=1 OR ( id_base IS NULL AND id_owner=?))"
." ORDER BY alias"
);
my ($id, $name, $alias, $is_public, $description, $screenshot, $id_owner, $is_base, $date_changed);
my ($id, $name, $alias, $is_public, $description, $screenshot, $id_owner, $is_base, $date_changed, $show_clones);
$sth->execute($user->id);
$sth->bind_columns(\($id, $name, $alias, $is_public, $description, $screenshot, $id_owner, $is_base, $date_changed));
$sth->bind_columns(\($id, $name, $alias, $is_public, $description, $screenshot, $id_owner, $is_base, $date_changed,$show_clones));

my $bookings_enabled = $self->setting('/backend/bookings');
my @list;
Expand All @@ -154,7 +154,13 @@ sub list_machines_user($self, $user, $access_data={}) {
push @clones,$self->_search_shared($id, $user->id);

my ($clone) = ($clones[0] or undef);
next unless $clone || $user->is_admin || ($is_public && $user->allowed_access($id)) || ($id_owner == $user->id);

next unless
$clone && $show_clones
|| $user->is_admin
|| ($is_public && $user->allowed_access($id))
|| ($id_owner == $user->id);

$name = $alias if defined $alias;
my $base = { id => $id, name => Encode::decode_utf8($name)
, alias => Encode::decode_utf8($alias or $name)
Expand Down Expand Up @@ -322,7 +328,7 @@ sub list_domains($self, %args) {

my $query = "SELECT d.name,d.alias, d.id, id_base, is_base, id_vm, status, is_public "
." ,vms.name as node , is_volatile, client_status, id_owner "
." ,comment, is_pool"
." ,comment, is_pool, show_clones"
." ,d.date_changed"
." FROM domains d LEFT JOIN vms "
." ON d.id_vm = vms.id ";
Expand Down
23 changes: 16 additions & 7 deletions script/rvd_front
Original file line number Diff line number Diff line change
Expand Up @@ -1074,13 +1074,20 @@ get '/machine/view/(:id).(:type)' => sub {
my ($domain) = _search_requested_machine($c);
return access_denied($c) if !$domain;

return access_denied($c) unless $USER->is_admin
|| $domain->id_owner == $USER->id
|| $USER->can_view_all
|| $USER->can_start_machine($domain)
;

return view_machine($c);
return view_machine($c) if $USER->is_admin
|| $USER->can_view_all;

if ( $domain->id_owner == $USER->id ) {
if ( $domain->id_base) {
my $base = Ravada::Front::Domain->open($domain->id_base);
if ($base->is_public || $base->show_clones()) {
return view_machine($c);
}
} else {
return view_machine($c);
}
}
return access_denied($c);
};

any '/machine/clone/(:id).(:type)' => sub {
Expand All @@ -1091,6 +1098,8 @@ any '/machine/clone/(:id).(:type)' => sub {
if ( $USER && $USER->can_clone() && !$USER->is_temporary() ) {
my $base = Ravada::Front::Domain->open($c->stash('id'));
if (!$base->is_public) {
return access_denied($c) if !$base->show_clones;

my @clones = $base->clones();
my ($clone) = grep { $_->{id_owner} == $USER->id } @clones;
return access_denied($c) if !$clone;
Expand Down
71 changes: 71 additions & 0 deletions t/front/40_list_domains.t
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,75 @@ sub test_list_bases_many_clones($vm) {

}

sub test_list_bases_show_clones($vm) {
my $base = create_domain($vm);

my $list = rvd_front->list_machines_user(user_admin);

$base->prepare_base(user_admin);

my $clone1 = $base->clone(user => user_admin
, name => new_domain_name);

$list = rvd_front->list_machines_user(user_admin);
my ($entry) = grep { $_->{id} == $base->id} @$list;

ok($entry);

$base->is_public(0);
$base->show_clones(1);

$list = rvd_front->list_machines_user(user_admin);
($entry) = grep { $_->{id} == $base->id} @$list;
ok($entry);

$base->show_clones(0);

$list = rvd_front->list_machines_user(user_admin);
($entry) = grep { $_->{id} == $base->id} @$list;
ok($entry);

my $user = create_user();
$list = rvd_front->list_machines_user($user);
($entry) = grep { $_->{id} == $base->id} @$list;
ok(!$entry);

$base->is_public(1);
$list = rvd_front->list_machines_user($user);
($entry) = grep { $_->{id} == $base->id} @$list;
ok($entry);

is(scalar(@{$entry->{list_clones}}),0);

my $clone2 = $base->clone(user => $user
, name => new_domain_name);

$list = rvd_front->list_machines_user($user);
($entry) = grep { $_->{id} == $base->id} @$list;
ok($entry);
ok($entry->{list_clones}->[0]);
is($entry->{list_clones}->[0]->{name},$clone2->name) or die Dumper($entry);
is($entry->{list_clones}->[0]->{id},$clone2->id) or die Dumper($entry);

$base->is_public(0);
$base->show_clones(1);

$list = rvd_front->list_machines_user($user);
($entry) = grep { $_->{id} == $base->id} @$list;
ok($entry) or die Dumper($list);

ok($entry->{list_clones}->[0]);+ is($entry->{list_clones}->[0]->{name},$clone2->name) or die Dumper($entry);
is($entry->{list_clones}->[0]->{id},$clone2->id) or die Dumper($entry);

$base->show_clones(0);

$list = rvd_front->list_machines_user($user);
($entry) = grep { $_->{id} == $base->id} @$list;
ok(!$entry);

remove_domain($base);
}

#########################################################

remove_old_domains();
Expand Down Expand Up @@ -219,6 +288,8 @@ for my $vm_name (reverse sort @VMS) {

use_ok($CLASS);

test_list_bases_show_clones($vm);

test_list_bases_many_clones($vm);

my $domain = test_create_domain($vm_name);
Expand Down
158 changes: 158 additions & 0 deletions t/mojo/15_list_bases.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
use warnings;
use strict;

use Carp qw(confess);
use Data::Dumper;
use HTML::Lint;
use Test::More;
use Test::Mojo;
use Mojo::File 'path';
use Mojo::JSON qw(decode_json);

use lib 't/lib';
use Test::Ravada;

no warnings "experimental::signatures";
use feature qw(signatures);

my $SECONDS_TIMEOUT = 15;

my $t;

my $URL_LOGOUT = '/logout';
my ($USERNAME, $PASSWORD);
my $SCRIPT = path(__FILE__)->dirname->sibling('../script/rvd_front');

########################################################################################

sub _create_base($vm_name) {
my $name = new_domain_name();

my $iso_name = 'Alpine%';
mojo_check_login($t);
$t->post_ok('/new_machine.html' => form => {
backend => $vm_name
,id_iso => search_id_iso($iso_name)
,name => $name
,disk => 1
,ram => 1
,swap => 1
,start => 0
,submit => 1
}
)->status_is(302);
die $t->tx->res->body if $t->tx->res->code() != 302;
wait_request();
my $base=_wait_domain($name);

die "Error: $name not created" if !$base;
mojo_request($t, 'prepare_base',{ id_domain => $base->id });
for ( 1 .. 120 ) {
return $base if $base->is_base;
sleep 1;
}
die "Error: $name not prepared" if !$base->is_base;
return $base;
}

sub test_list_machines_user($vm_name) {
mojo_check_login($t, $USERNAME, $PASSWORD);
my $base = _create_base($vm_name);
$base->is_public(1);

$t->ua->get($URL_LOGOUT);
my $user = create_user();

mojo_login($t, $user->name,$$);

$t->get_ok("/list_machines_user.json")->status_is(200);
my $body = $t->tx->res->body;
my $bases0;
eval { $bases0 = decode_json($body) };
is($@, '') or return;

my ($base_f) = grep { $_->{id} == $base->id } @$bases0;
ok($base_f) or die "Expecting ".$base->name." in listing";

$t->get_ok("/machine/clone/".$base->id.".html")
->status_is(200);

return if $t->tx->res->code != 200;

$base->is_public(0);
$base->show_clones(0);

$t->get_ok("/machine/clone/".$base->id.".html")
->status_is(403);

my $clone = _wait_domain($base->name."-".$user->name);
ok($clone) or exit;

$t->get_ok("/machine/view/".$clone->id.".html")
->status_is(403);

$base->show_clones(1);
$t->get_ok("/machine/clone/".$base->id.".html")
->status_is(200);

$t->get_ok("/machine/view/".$clone->id.".html")
->status_is(200);

my $user2 = create_user();

mojo_login($t, $user2->name,$$);

$t->get_ok("/list_machines_user.json")->status_is(200);
$body = $t->tx->res->body;
eval { $bases0 = decode_json($body) };
is($@, '') or return;

my ($base_f2) = grep { $_->{id} == $base->id } @$bases0;
ok(!$base_f2) or die "Expecting ".$base->name." in listing";

$t->get_ok("/machine/clone/".$base->id.".html")
->status_is(403);
}

sub _wait_domain($name) {
for ( 1 .. 120 ) {
my $domain = rvd_front->search_domain($name);
return $domain if $domain;
sleep 1;
diag("waiting for $name");
}
}

########################################################################################

$ENV{MOJO_MODE} = 'development';
init('/etc/ravada.conf',0);
my $connector = rvd_back->connector;
like($connector->{driver} , qr/mysql/i) or BAIL_OUT;

if (!ping_backend()) {
diag("SKIPPED: no backend");
done_testing();
exit;
}
$Test::Ravada::BACKGROUND=1;

remove_old_domains_req(1); # 0=do not wait for them

$t = Test::Mojo->new($SCRIPT);
$t->ua->inactivity_timeout(900);
$t->ua->connect_timeout(60);

$USERNAME = user_admin->name;
$PASSWORD = "$$ $$";

for my $vm_name (reverse @{rvd_front->list_vm_types} ) {

diag("Testing new machine in $vm_name");

test_list_machines_user($vm_name);
}
remove_old_domains_req(0); # 0=do not wait for them
remove_old_users();

done_testing();
10 changes: 10 additions & 0 deletions templates/main/admin_machines.html.ep
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,16 @@
</div>
<div class="modal-body">
<p><%=l 'Are you sure you want to change the Public state of' %> {{machine.name}} <%=l 'to' %> {{!(machine.is_public==0)}}?</p>
<div ng-show="machine.is_public==0" class="ml-4">
<input type="checkbox" name="show_clones" ng-model="machine.show_clones"
ng-true-value="1" ng-false-value="0"
>
<label for="show_clones"><%=l 'Show already created clones' %></label>
<button type="button" class="badge" ng-click="help_show_clones=!help_show_clones">?</button>
<div ng-show="help_show_clones">
<span class="info"><%=l 'The user will be able to access the clone, even when the base is hidden.' %> <%=l 'But no new clones can be created.' %></span>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" ng-click="cancel_modal(machine,'is_public')"><%=l 'No' %></button>
Expand Down
6 changes: 4 additions & 2 deletions templates/main/vm_base.html.ep
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,18 @@
<div ng-show="!showmachine || (pending_request && pending_request.status != 'done')"><i class="fas fa-sync-alt fa-spin"></i>
</div>

%= include "/main/vm_base_public"

<div ng-hide="!showmachine || (pending_request && pending_request.status != 'done')">

%= include "/main/vm_remove_base"

%= include "/main/vm_rebase"

%= include "/main/vm_spinoff"

%= include "/main/vm_prepare_base"

%= include "/main/vm_remove_base"

%= include "/main/vm_base_policy"

</div>
Expand Down
4 changes: 2 additions & 2 deletions templates/main/vm_base_policy.html.ep
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<div class="row" ng-show="showmachine.is_base && nodes.length && balance_options.length">
<div class="col-md-1">
<div class="col-md-2" align="right">
<label><b><%=l 'Balance' %></b></label>
</div>
<div class="col-md-11">
<div class="col-md-10">

<select ng-model="new_balance_policy"
ng-change="set_value('balance_policy',new_balance_policy)"
Expand Down
Loading

0 comments on commit 35534f7

Please sign in to comment.