From 62bbce41e03b6ad0092602832ea20e014ab22af4 Mon Sep 17 00:00:00 2001 From: Francesc Guasch Date: Tue, 26 Mar 2024 12:27:52 +0100 Subject: [PATCH 01/35] wip(test): check access by group --- lib/Ravada.pm | 22 +++++++++- lib/Ravada/Auth/SQL.pm | 11 +++++ lib/Ravada/Auth/User.pm | 2 + lib/Ravada/Domain.pm | 21 ++++++---- t/00_libs.t | 1 + t/front/80_access.t | 89 +++++++++++++++++++++++++++++++++++++---- 6 files changed, 131 insertions(+), 15 deletions(-) diff --git a/lib/Ravada.pm b/lib/Ravada.pm index 108b4999a..cc0e5e0d2 100644 --- a/lib/Ravada.pm +++ b/lib/Ravada.pm @@ -1629,6 +1629,12 @@ sub _add_indexes_generic($self) { ,"index(id_base)" ,"index(id_booking_entry)" ] + ,groups_local => [ + 'UNIQUE (name)' + ] + ,users_group => [ + 'UNIQUE(id_user, id_group)' + ] ,volumes => [ "index(id_domain)" @@ -2216,7 +2222,8 @@ sub _sql_create_tables($self) { group_access => { id => 'integer NOT NULL PRIMARY KEY AUTO_INCREMENT' ,id_domain => 'integer NOT NULL references `domains` (`id`) ON DELETE CASCADE' - ,name => 'char(80)' + ,name => 'char(80) NOT NULL' + ,type => 'char(40)' } ] , @@ -2377,6 +2384,19 @@ sub _sql_create_tables($self) { } ] , + [ groups_local => { + id => 'integer PRIMARY KEY AUTO_INCREMENT', + ,name => 'char(255) NOT NULL' + ,is_external => 'int NOT NULL default(0)' + ,external_auth => 'varchar(64) default NULL' + } + ], + [ users_group => { + id => 'integer PRIMARY KEY AUTO_INCREMENT', + ,id_user => 'integer NOT NULL' + ,id_group =>'integer NOT NULL' + } + ], [ volumes => { id => 'integer PRIMARY KEY AUTO_INCREMENT', diff --git a/lib/Ravada/Auth/SQL.pm b/lib/Ravada/Auth/SQL.pm index 50f0ea64f..c2312512b 100644 --- a/lib/Ravada/Auth/SQL.pm +++ b/lib/Ravada/Auth/SQL.pm @@ -1309,6 +1309,17 @@ sub disk_used($self) { return $used; } +=head2 add_to_group + +Adds the user to a group or list of groups + +Arguments: list of group names + +=cut + +sub add_to_group($self, @group) { +} + sub _load_network($network) { confess "Error: undefined network" if !defined $network; diff --git a/lib/Ravada/Auth/User.pm b/lib/Ravada/Auth/User.pm index f807a453c..361cc05fe 100644 --- a/lib/Ravada/Auth/User.pm +++ b/lib/Ravada/Auth/User.pm @@ -470,4 +470,6 @@ sub list_requests($self, $date_req=Ravada::Utils::date_now(3600)) { return @req; } +sub is_member {} + 1; diff --git a/lib/Ravada/Domain.pm b/lib/Ravada/Domain.pm index 43fdeb229..da7b21fea 100644 --- a/lib/Ravada/Domain.pm +++ b/lib/Ravada/Domain.pm @@ -2073,6 +2073,7 @@ sub info($self, $user) { ,is_public => $self->is_public ,show_clones => $self->show_clones ,id_base => $self->id_base + ,is_public => $self->is_public ,is_active => $is_active ,is_hibernated => $self->is_hibernated ,spice_password => $self->spice_password @@ -4959,7 +4960,7 @@ sub type { if (!exists $self->{_data} || !exists $self->{_data}->{vm}) { my ($type) = ref($self) =~ /.*::([a-zA-Z][a-zA-Z0-9]*)/; confess "Unknown type from ".ref($self) if !$type; - return $type; + return $type if $type ne 'Domain'; } confess "Unknown vm ".Dumper($self->{_data}) if !$self->_data('vm'); @@ -6281,7 +6282,7 @@ Arguments is a named list 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'; + return $self->_allow_group_access(%args, type=> $type) if $type =~ /^group/; my $attribute = delete $args{attribute} or confess "Error: Missing attribute"; my $value = delete $args{value} or confess "Error: Missing value"; @@ -6320,13 +6321,18 @@ sub grant_access($self, %args) { sub _allow_group_access($self, %args) { my $group = delete $args{group} or confess "Error: group required"; + + my $type = delete $args{type}; + $type =~ s/.*\.(.*)/$1/; + $type = 'ldap' if !$type || $type eq 'group'; + confess "Error: unknown args ".Dumper(\%args) if keys %args; my $sth = $$CONNECTOR->dbh->prepare( "INSERT INTO group_access " - ."( id_domain,name)" - ." VALUES(?,? )" + ."( id_domain,name, type)" + ." VALUES(?,?,? )" ); - $sth->execute($self->id, $group); + $sth->execute($self->id, $group, $type); } =head2 list_access_groups @@ -6335,11 +6341,12 @@ Returns the list of groups who can access this virtual machine =cut -sub list_access_groups($self) { +sub list_access_groups($self, $type) { my $sth = $$CONNECTOR->dbh->prepare("SELECT name from group_access " ." WHERE id_domain=?" + ." AND type=?" ); - $sth->execute($self->id); + $sth->execute($self->id, $type); my @groups; while ( my ($name) = $sth->fetchrow ) { push @groups,($name); diff --git a/t/00_libs.t b/t/00_libs.t index a2befc589..fd622bec1 100644 --- a/t/00_libs.t +++ b/t/00_libs.t @@ -6,6 +6,7 @@ use Test::More; use_ok('Ravada::Auth'); use_ok('Ravada::Auth::LDAP'); use_ok('Ravada::Auth::SQL'); +use_ok('Ravada::Auth::Group'); use_ok('Ravada::VM'); use_ok('Ravada::Domain'); use_ok('Ravada::Front::Domain'); diff --git a/t/front/80_access.t b/t/front/80_access.t index 4a8b8ff51..fc3111ad6 100644 --- a/t/front/80_access.t +++ b/t/front/80_access.t @@ -11,6 +11,8 @@ use feature qw(signatures); use lib 't/lib'; use Test::Ravada; +use_ok('Ravada::Auth::Group'); + init('t/etc/ravada_ldap_basic.conf'); clean(); @@ -38,19 +40,24 @@ sub _remove_bases(@bases) { } } -sub test_access_by_group($vm) { - my $base = create_domain($vm->type); - $base->prepare_base(user_admin); - $base->is_public(1); - +sub _create_group_ldap() { my $g_name = new_domain_name(); my $group = Ravada::Auth::LDAP::search_group(name => $g_name); if (!$group) { Ravada::Auth::LDAP::add_group($g_name); } + return $g_name; +} + +sub test_access_by_group_ldap($vm, $type='group') { + my $base = create_domain($vm->type); + $base->prepare_base(user_admin); + $base->is_public(1); + + my $g_name = _create_group_ldap(); $base->grant_access( - type => 'group' + type => $type ,group => $g_name ); my $user = create_user(new_domain_name(),$$); @@ -76,6 +83,70 @@ sub test_access_by_group($vm) { remove_domain($base); } +sub test_access_by_group_sql($vm) { + my $base = create_domain($vm->type); + $base->prepare_base(user_admin); + $base->is_public(1); + + my $g_name = new_domain_name(); + my $group = Ravada::Auth::Group::add_group(name => $g_name); + + $base->grant_access( + type => 'group.local' + ,group => $g_name + ); + + my $g_name_ldap = _create_group_ldap(); + $base->grant_access( + type => 'group.ldap' + ,group => $g_name_ldap + ); + + my $user = create_user(new_domain_name(),$$); + is($user->is_admin, 0 ); + + my $list_bases = rvd_front->list_machines_user($user); + is(scalar(@$list_bases),0) or exit; + + my $user_sql = create_user(new_domain_name(), $$); + $list_bases = rvd_front->list_machines_user($user_sql); + is(scalar(@$list_bases),0) or exit; + + $user_sql->add_to_group($g_name); + + is($user_sql->is_member($g_name),1); + + $user_sql->_load_allowed(1); + is($user_sql->allowed_access($base->id),1); + + $list_bases = rvd_front->list_machines_user($user_sql); + is(scalar(@$list_bases),1); + + $list_bases = rvd_front->list_machines_user(user_admin); + is(scalar(@$list_bases),1); + + my $user_ldap0 = create_ldap_user(new_domain_name(), $$); + my $user_ldap = Ravada::Auth::SQL->new(name => $user_ldap0->get_value('cn')); + $list_bases = rvd_front->list_machines_user($user_ldap); + is(scalar(@$list_bases),0) or exit; + + $base->is_public(0); + $base->show_clones(0); + + $list_bases = rvd_front->list_machines_user($user_sql); + is(scalar(@$list_bases),0) or exit; + + $base->show_clones(1); + + $list_bases = rvd_front->list_machines_user($user_sql); + is(scalar(@$list_bases),0) or exit; + + remove_domain($base); + + $group->remove() if $group; +} + + sub test_access_by_agent($vm, $do_clones=0) { @@ -378,7 +449,11 @@ for my $vm_name (reverse vm_names()) { skip($msg,10) if !$vm; diag("Testing access restrictions in domain for $vm_name"); - test_access_by_group($vm); + test_access_by_group_ldap($vm); + test_access_by_group_ldap($vm,'group.ldap'); + test_access_by_group_sql($vm); + + # test_access_by_group_sql_or_ldap($vm); test_access_by_agent($vm); From 6d9da821309564529238ea84dd5e58f9bae12501 Mon Sep 17 00:00:00 2001 From: Francesc Guasch Date: Tue, 26 Mar 2024 13:13:23 +0100 Subject: [PATCH 02/35] test: list bases access --- t/lib/Test/Ravada.pm | 10 ++++ t/mojo/10_login.t | 2 +- t/mojo/15_list_bases.t | 103 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 112 insertions(+), 3 deletions(-) diff --git a/t/lib/Test/Ravada.pm b/t/lib/Test/Ravada.pm index cbf675416..7debf59d4 100644 --- a/t/lib/Test/Ravada.pm +++ b/t/lib/Test/Ravada.pm @@ -23,6 +23,7 @@ use feature qw(signatures); use Ravada; use Ravada::Auth::SQL; +use Ravada::Auth::Group; use Ravada::Domain::Void; use vars qw($VERSION @ISA @EXPORT @EXPORT_OK); @@ -84,6 +85,7 @@ create_domain mojo_request_url mojo_request_url_post + create_group remove_old_user remove_old_users remove_old_users_ldap @@ -1200,6 +1202,14 @@ sub create_user($name=new_domain_name(), $pass=$$, $is_admin=0) { return $user; } +sub create_group($name = new_domain_name()) { + my $group = Ravada::Auth::Group->new(name => $name); + return $group if $group && $group->id; + + $group = Ravada::Auth::Group::add_group(name => $name); + return $group; +} + sub create_ldap_user($name, $password, $keep=0) { my $ldap = Ravada::Auth::LDAP::_init_ldap_admin(); diff --git a/t/mojo/10_login.t b/t/mojo/10_login.t index a7d555b74..d81a7d4e8 100644 --- a/t/mojo/10_login.t +++ b/t/mojo/10_login.t @@ -496,7 +496,7 @@ sub _check_html_lint($url, $content, $option = {}) { for my $error ( $lint->errors() ) { next if $error->errtext =~ /Entity .*is unknown/; next if $option->{internal} && $error->errtext =~ /(body|head|html|title).*required/; - if ( $error->errtext =~ /Unknown element <(footer|header|nav|ldap-groups)/ + if ( $error->errtext =~ /Unknown element <(footer|header|nav|ldap-groups|local-groups)/ || $error->errtext =~ /Entity && is unknown/ || $error->errtext =~ /should be written as/ || $error->errtext =~ /Unknown attribute.*%/ diff --git a/t/mojo/15_list_bases.t b/t/mojo/15_list_bases.t index 7bfca48e7..de665381c 100644 --- a/t/mojo/15_list_bases.t +++ b/t/mojo/15_list_bases.t @@ -1,7 +1,7 @@ use warnings; use strict; -use Carp qw(confess); +use Carp qw( croak confess); use Data::Dumper; use HTML::Lint; use Test::More; @@ -55,11 +55,109 @@ sub _create_base($vm_name) { return $base; } -sub test_list_machines_user($vm_name) { +sub test_list_fail($base) { + $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 croak "Expecting no ".$base->name." in listing"; + + $t->get_ok("/machine/clone/".$base->id.".html") + ->status_is(403); + +} + +sub _list_machines_user($base) { + $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"; + + return $base_f; +} + + +sub test_list_match($base, $do_clone=1) { + + my $base_f = _list_machines_user($base); + return if !$do_clone; + + $t->get_ok("/machine/clone/".$base->id.".html") + ->status_is(200); + + wait_request(); + + $base_f = _list_machines_user($base); + + my $id_clone = $base_f->{list_clones}->[0]->{id}; + ok($id_clone) or return; + + $t->get_ok("/machine/view/".$id_clone.".html") + ->status_is(200); + + return $id_clone; +} + +sub test_list_machines_group($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(); + my $group = create_group(); + $base->grant_access( group => $group->name, type => 'group.local'); + + mojo_login($t, $user->name,$$); + + test_list_fail($base); + + $user->add_to_group($group->name); + + my $id_clone = test_list_match($base); + + $user->remove_from_group($group->name); + $base->_data('show_clones' => 0); + + test_list_fail($base); + + $t->get_ok("/machine/clone/".$base->id.".html") + ->status_is(403); + + $t->get_ok("/machine/view/".$id_clone.".html") + ->status_is(403); + + diag("Check list machines match with show clones=1"); + + $base->_data('show_clones' => 1); + is ($user->allowed_access($base->id),0) or exit; + is ($user->allowed_access_group($base->id),0) or exit; + + test_list_match($base,0); + + # access previous clone + $t->get_ok("/machine/view/".$id_clone.".html") + ->status_is(200); + + # but will not be able to clone + $t->get_ok("/machine/clone/".$base->id.".html") + ->status_is(403); + + +} + +sub test_list_machines_user($vm_name) { + mojo_login($t, $USERNAME, $PASSWORD); + my $base = _create_base($vm_name); + $base->is_public(1); + $t->ua->get($URL_LOGOUT); my $user = create_user(); @@ -150,6 +248,7 @@ for my $vm_name (reverse @{rvd_front->list_vm_types} ) { diag("Testing new machine in $vm_name"); + test_list_machines_group($vm_name); test_list_machines_user($vm_name); } remove_old_domains_req(0); # 0=do not wait for them From 03568b88d0b2e5493976c18ddd74c8104778720f Mon Sep 17 00:00:00 2001 From: Francesc Guasch Date: Tue, 26 Mar 2024 16:39:51 +0100 Subject: [PATCH 03/35] wip: group base code --- lib/Ravada/Auth/Group.pm | 83 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 lib/Ravada/Auth/Group.pm diff --git a/lib/Ravada/Auth/Group.pm b/lib/Ravada/Auth/Group.pm new file mode 100644 index 000000000..8a6a97f67 --- /dev/null +++ b/lib/Ravada/Auth/Group.pm @@ -0,0 +1,83 @@ +package Ravada::Auth::Group; + +use warnings; +use strict; + +=head1 NAME + +Ravada::Auth::Group - Group management library for Ravada + +=cut + +use Carp qw(carp); +use Data::Dumper qw(Dumper); +use Hash::Util qw(lock_hash); + +use Moose; + +use feature qw(signatures); +no warnings "experimental::signatures"; + +has 'name' => ( + is => 'rw' + ,isa => 'Str' + ,required => 1 +); + +our $CON; + +sub _init_connector { + my $connector = shift; + + $CON = \$connector if defined $connector; + return if $CON; + + $CON= \$Ravada::CONNECTOR if !$CON || !$$CON; + $CON= \$Ravada::Front::CONNECTOR if !$CON || !$$CON; + + if (!$CON || !$$CON) { + my $connector = Ravada::_connect_dbh(); + $CON = \$connector; + } + + die "Undefined connector" if !$CON || !$$CON; +} + +sub BUILD { + my $self = shift; + _init_connector(); + $self->_load_data(); +} + +sub _load_data($self) { + _init_connector(); + + confess "No group name nor id " if !defined $self->name && !$self->id; + + confess "Undefined \$\$CON" if !defined $$CON; + my $sth = $$CON->dbh->prepare( + "SELECT * FROM groups_local WHERE name=? "); + $sth->execute($self->name); + my ($found) = $sth->fetchrow_hashref; + $sth->finish; + + return if !$found->{name}; + + lock_hash %$found; + $self->{_data} = $found if ref $self && $found; + +} + +sub id { + my $self = shift; + my $id; + eval { $id = $self->{_data}->{id} }; + confess $@ if $@; + + return $id; +} + +sub add_group(%args) { +} + +1; From 91af9df02f2f0dd741d37da0f049dd70db5405d3 Mon Sep 17 00:00:00 2001 From: Francesc Guasch Date: Wed, 27 Mar 2024 13:32:27 +0100 Subject: [PATCH 04/35] wip: group management backend --- lib/Ravada/Auth/Group.pm | 63 ++++++ lib/Ravada/Auth/SQL.pm | 52 +++++ lib/Ravada/Auth/User.pm | 68 +++++- lib/Ravada/Domain.pm | 3 +- public/js/ravada.js | 26 +++ script/rvd_front | 249 ++++++++++++++++++---- t/front/80_access.t | 4 + t/lib/Test/Ravada.pm | 26 +++ t/mojo/70_groups.t | 259 +++++++++++++++++++++++ templates/bootstrap/new_group.html.ep | 3 +- templates/main/admin_group.html.ep | 12 +- templates/ng-templates/new_group.html.ep | 50 +++-- 12 files changed, 739 insertions(+), 76 deletions(-) create mode 100644 t/mojo/70_groups.t diff --git a/lib/Ravada/Auth/Group.pm b/lib/Ravada/Auth/Group.pm index 8a6a97f67..cb00c08e5 100644 --- a/lib/Ravada/Auth/Group.pm +++ b/lib/Ravada/Auth/Group.pm @@ -78,6 +78,69 @@ sub id { } sub add_group(%args) { + _init_connector(); + my $name = delete $args{name}; + my $external_auth = delete $args{external_auth}; + my $is_external = 0; + $is_external = 1 if $external_auth; + + confess "WARNING: Unknown arguments ".Dumper(\%args) + if keys %args; + + + my $sth; + eval { $sth = $$CON->dbh->prepare( + "INSERT INTO groups_local(name,is_external,external_auth)" + ." VALUES(?,?,?)"); + $sth->execute($name, $is_external, $external_auth); + }; + confess $@ if $@; + return Ravada::Auth::Group->new(name => $name); +} + +sub members($self) { +} + +sub _remove_all_members($self) { + my $sth = $$CON->dbh->prepare("DELETE FROM users_group " + ." WHERE id_group=?" + ); + $sth->execute($self->id); +} + +sub _remove_access($self) { + my $sth = $$CON->dbh->prepare("DELETE FROM group_access " + ." WHERE type='local'" + ." AND name=?" + ); + $sth->execute($self->name); +} + +sub members_info($self) { + my $sth = $$CON->dbh->prepare( + "SELECT u.id,u.name FROM users u,users_group ug " + ." WHERE u.id = ug.id_user " + ." AND ug.id_group=?" + ." ORDER BY name" + ); + $sth->execute($self->id); + my @members; + while (my ($uid,$name) = $sth->fetchrow) { + push @members,({ id => $uid, name => $name}); + } + return @members; +} + +sub remove($self) { + my $id = $self->id; + + $self->_remove_all_members(); + $self->_remove_access(); + + my $sth = $$CON->dbh->prepare( + "DELETE FROM groups_local WHERE id=?" + ); + $sth->execute($id); } 1; diff --git a/lib/Ravada/Auth/SQL.pm b/lib/Ravada/Auth/SQL.pm index c2312512b..1fbb34fdb 100644 --- a/lib/Ravada/Auth/SQL.pm +++ b/lib/Ravada/Auth/SQL.pm @@ -633,6 +633,11 @@ Removes the user =cut sub remove($self) { + return if !$self->id; + + die "Error: user ".$self->name." can not be removed.\n" + if $self->id == Ravada::Utils::user_daemon->id; + my $sth = $$CON->dbh->prepare("DELETE FROM grants_user where id_user=?"); $sth->execute($self->id); @@ -1287,6 +1292,26 @@ sub groups($self) { } +=head2 groups_local + +Returns a list of the local groups this user belogs to + +=cut + +sub groups_local($self) { + my $sth = $$CON->dbh->prepare("SELECT g.name FROM groups_local g,users_group ug " + ." WHERE g.id = ug.id_group " + ." AND ug.id_user = ?" + ." ORDER BY g.name " + ); + $sth->execute($self->id); + my @groups; + while (my ($name) = $sth->fetchrow) { + push @groups,($name); + } + return @groups; +} + =head2 disk_used Returns the amount of disk space used by this user in MB @@ -1318,8 +1343,35 @@ Arguments: list of group names =cut sub add_to_group($self, @group) { + my $sth = $$CON->dbh->prepare( + "INSERT INTO users_group (id_group, id_user) " + ." VALUES (?,?)" + ); + for my $group (@group) { + if (!ref($group)) { + $group = Ravada::Auth::Group->new(name => $group); + } + confess "Error: unknown group ".$group->name + if !$group->id; + + $sth->execute($group->id,$self->id); + } +} + +sub remove_from_group($self, @group) { + + my $sth = $$CON->dbh->prepare( + "DELETE FROM users_group WHERE id_group=? AND id_user=? " + ); + for my $group (@group) { + if (!ref($group)) { + $group = Ravada::Auth::Group->new(name => $group); + } + $sth->execute($group->id,$self->id); + } } + sub _load_network($network) { confess "Error: undefined network" if !defined $network; diff --git a/lib/Ravada/Auth/User.pm b/lib/Ravada/Auth/User.pm index 361cc05fe..ca65a68ed 100644 --- a/lib/Ravada/Auth/User.pm +++ b/lib/Ravada/Auth/User.pm @@ -16,6 +16,9 @@ use Encode; use Mojo::JSON qw(decode_json); use Moose::Role; +use Ravada::Auth::LDAP; +use Ravada::Auth::Group; + no warnings "experimental::signatures"; use feature qw(signatures); @@ -340,6 +343,33 @@ sub allowed_access($self,$id_domain) { return 0; } +=head2 allowed_access_group + +Return true if the user belongs to a group that can access the base + +=cut + +sub allowed_access_group($self,$id_domain) { + return 1 if $self->is_admin; + + my $sth = $$CONNECTOR->dbh->prepare( + "SELECT name from group_access " + ." WHERE id_domain=?" + ." AND type=?" + ); + $sth->execute($id_domain, 'group.sql'); + my @groups; + while ( my ($name) = $sth->fetchrow ) { + push @groups,($name); + } + return 0 if !@groups; + + for my $name ( @groups ) { + return 1 if $self->is_member($name); + } + return 0; +} + sub _list_domains_access($self) { _init_connector(); @@ -416,24 +446,36 @@ sub _load_allowed { sub _load_allowed_groups($self) { - my $sth = $$CONNECTOR->dbh->prepare("SELECT id_domain,name from group_access"); - my ($id_domain, $name); + my $sth = $$CONNECTOR->dbh->prepare("SELECT id_domain,name,type from group_access"); + my ($id_domain, $name, $type); $sth->execute(); - $sth->bind_columns(\($id_domain, $name)); + $sth->bind_columns(\($id_domain, $name, $type)); while ( $sth->fetch ) { + $type = 'ldap' if !defined $type; next if $self->{_allowed}->{$id_domain}; $self->{_allowed}->{$id_domain} = 0; - next unless $self->is_external && $self->external_auth eq 'ldap'; - - if (!Ravada::Auth::LDAP::search_group(name => $name)) { - next; + if ($type eq 'ldap') { + next unless $self->is_external && $self->external_auth eq 'ldap'; + if (!Ravada::Auth::LDAP::search_group(name => $name)) { + next; + } + $self->{_allowed}->{$id_domain} = 1 + if $self->ldap_entry + && Ravada::Auth::LDAP::is_member($self->ldap_entry, $name); + } elsif ($type eq 'local') { + my $group = Ravada::Auth::Group->new(name => $name); + if (!$group || !$group->id) { + warn "Error: unkonwon group '$name' for group access"; + } else { + $self->{_allowed}->{$id_domain} = 1 + if $self->is_member($group); + } + } else { + warn "Error: unknown type '$type' for group access '$name'"; } - $self->{_allowed}->{$id_domain} = 1 - if $self->ldap_entry - && Ravada::Auth::LDAP::is_member($self->ldap_entry, $name); } } @@ -470,6 +512,10 @@ sub list_requests($self, $date_req=Ravada::Utils::date_now(3600)) { return @req; } -sub is_member {} +sub is_member($self, $group) { + $group = Ravada::Auth::Group->new(name => $group) if !ref($group); + my $is_member = grep { $_ eq $group->name} $self->groups_local; + return ($is_member or 0); +} 1; diff --git a/lib/Ravada/Domain.pm b/lib/Ravada/Domain.pm index da7b21fea..9ff7cdf24 100644 --- a/lib/Ravada/Domain.pm +++ b/lib/Ravada/Domain.pm @@ -4962,8 +4962,7 @@ sub type { confess "Unknown type from ".ref($self) if !$type; return $type if $type ne 'Domain'; } - confess "Unknown vm ".Dumper($self->{_data}) - if !$self->_data('vm'); + return 'Unknown' if !exists $self->{_data}->{vm}; return $self->_data('vm'); } diff --git a/public/js/ravada.js b/public/js/ravada.js index 34f2b1404..9f7c5fde1 100644 --- a/public/js/ravada.js +++ b/public/js/ravada.js @@ -20,6 +20,7 @@ .service("listMess", gtListMess) .controller("SupportForm", suppFormCtrl) .controller("AddUserForm",addUserFormCrtl) + .controller("AddGroupForm",addGroupFormCrtl) .controller("ChangePasswordForm",changePasswordFormCrtl) // .controller("machines", machinesCrtl) // .controller("messages", messagesCrtl) @@ -68,6 +69,31 @@ }; + function addGroupFormCrtl($scope, $http, request){ + $scope.type = 'local'; + $scope.group_name = ''; + $scope.object_class = { + 'posixGroup': true + ,'nsMemberOf': false + ,'groupOfUniqueNames': false + }; + $scope.list_object_class=Object.keys($scope.object_class); + + $scope.add_group = function() { + $scope.new_group_done = false; + $http.post('/group/new' + , JSON.stringify({ 'type': $scope.type + ,'group_name': $scope.group_name + ,'object_class': $scope.object_class + } + ) + ).then(function(response) { + $scope.error = response.data.error; + $scope.new_group_done = $scope.error.length == 0; + }); + }; + }; + function addUserFormCrtl($scope, $http, request){ diff --git a/script/rvd_front b/script/rvd_front index 23369e6ec..a88bc3ca3 100644 --- a/script/rvd_front +++ b/script/rvd_front @@ -354,19 +354,34 @@ post '/settings_global' => sub($c) { return $c->render(json => { ok => 1, reload => $reload }); }; -get '/admin/group/#name' => sub($c) { +get '/admin/group/#type/#name' => sub($c) { + my $name = $c->stash('name'); + my $type= $c->stash('type'); + return _admin_group($c, $name, $type); +}; + +sub _admin_group($c,$name, $type) { return access_denied($c) unless $USER->can_manage_groups || $USER->can_view_groups; _add_admin_libs($c); - my $group = Ravada::Auth::LDAP::search_group(name => $c->stash('name')); - return $c->render( text => "Error: group ".$c->stash('name')." not found." - ."Groups" - , status => 302) if !$group; - $c->stash(object_class => [ grep !/^top$/,$group->get_value('objectClass')]); + my $group; + if ($type eq 'ldap') { + $group = Ravada::Auth::LDAP::search_group(name => $name); + return not_found($c) if !$group; + $c->stash(object_class => [ grep !/^top$/,$group->get_value('objectClass')]); + } elsif ($type eq 'local') { + $group = Ravada::Auth::Group->new(name => $name); + return not_found($c) if !$group || !$group->id; + $c->stash(object_class => [ ]); + } else { + die "Error group type '$type' unknown for group $name"; + } + return $c->render( template => "/main/admin_group" ,group => $group + ,type => $type ); }; @@ -1080,7 +1095,9 @@ get '/machine/view/(:id).(:type)' => sub { if ( $domain->id_owner == $USER->id || $USER->can_start_machine($domain) ) { if ( $domain->id_base) { my $base = Ravada::Front::Domain->open($domain->id_base); - if ($base->is_public || $base->show_clones()) { + if (($base->show_clones || $USER->allowed_access_group($base->id)) + || ($base->is_public && $USER->allowed_access($base->id)) + ) { return view_machine($c); } } else { @@ -1353,34 +1370,37 @@ get '/machine/move_access/(#id_domain)/(#id_access)/(#position)' => sub { return $c->render(json => { ok => 1 }); }; -get '/machine/list_access_groups/(#id_domain)' => sub { + +get '/machine/list_access_groups/#type/#id_domain' => sub { my $c = shift; return _access_denied($c) if !$USER->is_admin; my $domain_id = $c->stash('id_domain'); my $domain = Ravada::Front::Domain->open($domain_id); - return $c->render( json => [ $domain->list_access_groups ] ); + return $c->render( json => [ $domain->list_access_groups($c->stash('type')) ] ); }; -get '/machine/add_access_group/(#id_domain)/(#group)' => sub($c) { +get '/machine/add_access_group/#type/#group/#id_domain' => sub($c) { + my $type = $c->stash('type'); my $id_domain = $c->stash('id_domain'); my $group = $c->stash('group'); my $ok = 0; eval { my $domain = Ravada::Front::Domain->open($id_domain); - $domain->grant_access(type => 'group' , group => $group); + $domain->grant_access(type => "group.$type" , group => $group); $ok =1; }; return $c->render( json => { ok => $ok, error => $@ }); }; -get '/machine/remove_access_group/(#id_domain)/(#group)' => sub($c) { +get '/machine/remove_access_group/#type/#group/#id_domain' => sub($c) { my $sth = $RAVADA->_dbh->prepare("DELETE FROM group_access WHERE id_domain=? " ." AND name=?" + ." AND type=?" ); - $sth->execute($c->stash('id_domain'), $c->stash('group')); + $sth->execute($c->stash('id_domain'), $c->stash('group'), $c->stash('type')); return $c->render(json => { ok => 1 } ); }; @@ -1544,13 +1564,42 @@ any '/users/register' => sub { return register($c); }; -any '/group/new' => sub { +get '/group/new' => sub($c) { - my $c = shift; - return access_denied($c) if !$USER->is_admin(); - return new_group($c); + return access_denied($c) if !$USER->is_admin(); + + my $type = ( $c->req->param('type') or 'local'); + $c->render(template => 'bootstrap/new_group', type => $type); }; +post '/group/new' => sub($c) { + return new_group($c); +}; + +get '/group/#type/list' => sub($c) { + return _group_list($c); +}; + +get '/group/#type/list/#filter' => sub($c) { + return _group_list($c); +}; + +sub _group_list($c) { + return access_denied($c) unless $USER->can_view_groups || $USER->can_manage_groups; + + my $type = $c->stash('type'); + my $filter = $c->stash('filter'); + + if ($type eq 'ldap') { + $filter = '*' if !defined $filter; + return _list_ldap_groups($c,$filter); + } elsif($type eq 'local') { + return _list_sql_groups($c, $filter); + } else { + return not_found($c); + } +} + any '/admin/users/upload.#req' => sub($c) { return access_denied_json($c) unless $USER->is_admin; @@ -2320,6 +2369,16 @@ get '/list_ldap_groups' => sub($c) { return _list_ldap_groups($c,'*'); }; +get '/group/#type/list_members/#name' => sub($c) { + return access_denied($c) if !$USER->can_view_groups; + + my $name = $c->stash('name'); + my $type = $c->stash('type'); + + return _list_group_members($c,$type,$name); + +}; + get '/list_ldap_users' => sub($c) { return access_denied($c) unless $USER->can_manage_users || $USER->can_manage_groups; return _list_ldap_users($c,'*'); @@ -2349,47 +2408,118 @@ get '/list_ldap_group_members/#name' => sub($c) { return _list_ldap_group_members($c,$name); }; -post '/ldap/group/add_member' => sub($c) { +post '/group/#type/add_member' => sub($c) { return access_denied($c) if !$USER->can_manage_groups; my $arg = decode_json($c->req->body); - my $login = delete $arg->{cn}; + my $login = delete $arg->{name}; + my $id_user = delete $arg->{id_user}; my $group = delete $arg->{group}; return $c->render(json => { error => "Error: unknown args ".Dumper($arg)}) if keys %$arg; - eval { - Ravada::Auth::LDAP::add_to_group($login, $group); - }; - my $error = $@; + my $type = $c->stash('type'); + my $error = ''; + + my $user; + if ($id_user) { + $user = Ravada::Auth::SQL->search_by_id($id_user); + $login = $user->name if $user; + } + + if ($type eq 'ldap') { + die "Error: missing login name" if !$login; + eval { + Ravada::Auth::LDAP::add_to_group($login, $group); + }; + $error = $@; + } elsif ($type eq 'local') { + if ($user->is_member($group)) { + $error = "Error: user $login already added to $group"; + } else { + $user->add_to_group($group); + } + } else { $error = "Error: unkonwn group type '$type" } $error =~ s/(.*) at lib.*/$1/ if $error; return $c->render(json => { error => $error } ); }; -post '/ldap/group/remove_member' => sub($c) { + +post '/group/#type/remove_member' => sub($c) { return access_denied($c) if !$USER->can_manage_groups; my $arg = decode_json($c->req->body); - my $dn = delete $arg->{dn}; + my $type = $c->stash('type'); my $group = delete $arg->{group}; + my $user_name = delete $arg->{name}; + my $id_user = delete $arg->{id_user}; return $c->render(json => { error => "Error: unknown args ".Dumper($arg)}) if keys %$arg; + if ($id_user && $id_user =~ /^\d+$/) { + my $user = Ravada::Auth::SQL->search_by_id($id_user); + $user_name = $user->name; + } - eval { - Ravada::Auth::LDAP::remove_from_group($dn, $group); - }; - return $c->render(json => { error => ($@ or '') } ); + my $error = ''; + $error = "Error: unkonwn args ".Dumper($arg) if keys %$arg; + if ($type =~ /ldap/i) { + if (!defined $user_name) { + $error = "Error: missing user name"; + } else { + eval { + Ravada::Auth::LDAP::remove_from_group($user_name, $group); + }; + $error = ( $@ or ''); + } + } elsif ($type eq 'local') { + if (!defined $id_user) { + $error = "Error: missing user name"; + } else { + my $user = Ravada::Auth::SQL->search_by_id($id_user); + $user->remove_from_group($group); + } + } + return $c->render(json => { error => $error } ); }; + get '/ldap/group/remove/(#group)' => sub($c) { return access_denied($c) if !$USER->can_manage_groups; eval { Ravada::Auth::LDAP::remove_group($c->stash('group')) }; return $c->render( json => { error => ($@ or '') }); }; +get '/group/#type/remove/#group' => sub($c) { + return access_denied($c) if !$USER->can_manage_groups; + my $error = ''; + my $type = $c->stash('type'); + + if ($type =~ /ldap/i) { + eval { + Ravada::Auth::LDAP::remove_group($c->stash('group')); + my $sth = $RAVADA->_dbh->prepare( + "DELETE FROM group_access WHERE type = 'ldap' " + ." AND name=?" + ); + $sth->execute($c->stash('group')); + }; + + $error = $@; + } elsif ($type eq 'local') { + my $group = Ravada::Auth::Group->new(name => $c->stash('group')); + if ($group && $group->id) { + $group->remove; + } else { + $error = "Error: unknown group $group"; + } + } + warn $error if $error; + return $c->render( json => { error => $error }); +}; + get '/booking' => sub($c) { return $c->render(template => '/ng-templates/error' ,message => "LDAP is required to set up bookings. " @@ -3501,33 +3631,37 @@ sub _search_requested_machine { sub new_group($c) { my @error = (); + my $arg = decode_json($c->req->body); - my $groupname = ($c->param('groupname') or ''); - my $object_class = $c->every_param('object_class'); - - my $object_class_checkbox; - for (@$object_class) { - $object_class_checkbox->{$_} = 'checked'; + my $type = delete $arg->{'type'}; + my $groupname = delete $arg->{'group_name'}; + my $h_object_class = delete $arg->{'object_class'}; + if(!keys %$h_object_class) { + $h_object_class->{'posixGroup'}=1; } - $c->stash(object_class => $object_class_checkbox); + warn "Warning: extra arguments ignored ".Dumper($arg) + if keys %$arg; - $object_class = ['top',@$object_class]; + my $object_class = ['top',keys %$h_object_class]; if ($groupname) { if ($groupname =~ /^[0-9a-zA-Z._-]+$/) { - eval { + if ($type eq 'ldap') { + eval { Ravada::Auth::LDAP::add_group($groupname,undef, $object_class); - }; - push @error,($@) if $@; + }; + push @error,($@) if $@; + } else { + Ravada::Auth::Group::add_group(name => $groupname); + } } else { push @error,("Error: group name '$groupname' invalid." ,"Group name can only contain words, numbers, dashes, dots and underscores" ); } } else { - $c->stash(object_class => { posixGroup => 'checked' }); + push @error,("Empty group name"); } - $c->render(template => 'bootstrap/new_group', error => \@error - ,groupname => $groupname); + return $c->render(json => { error => \@error }); } sub register { @@ -4281,6 +4415,23 @@ sub maintenance($c) { ); } +sub _list_sql_groups($c, $filter=undef) { + my $sql = "SELECT name FROM groups_local "; + $sql .= " WHERE name like ? " if defined $filter; + $sql .= " ORDER BY name"; + my $sth = $RAVADA->_dbh->prepare($sql); + if (defined $filter) { + $sth->execute('%'.$filter.'%'); + } else { + $sth->execute(); + } + my @groups; + while (my ($name) = $sth->fetchrow) { + push @groups,($name); + } + return $c->render(json => \@groups); +} + sub _list_ldap_groups($c, $name='*') { return $c->render(json => []) if !$RAVADA->feature('ldap'); @@ -4322,9 +4473,19 @@ sub _list_ldap_users($c, $filter='*' ) { } sub _list_ldap_group_members($c, $name) { - return $c->render(json => [ Ravada::Auth::LDAP::_group_members($name) ] ); + return $c->render(json => [ map { { id => $_, name => $_ } } Ravada::Auth::LDAP::_group_members($name) ] ); } +sub _list_group_members($c, $type, $name) { + if ($type eq 'ldap') { + return _list_ldap_group_members($c, $name); + } elsif ($type eq 'local') { + my $group = Ravada::Auth::Group->new(name => $name); + return $c->render(json => [ $group->members_info ]); + } else { + confess "Error: unkonwn group type $type"; + } +} ################################################################################# my $routes = app->routes->children; diff --git a/t/front/80_access.t b/t/front/80_access.t index fc3111ad6..fe210ee58 100644 --- a/t/front/80_access.t +++ b/t/front/80_access.t @@ -112,6 +112,7 @@ sub test_access_by_group_sql($vm) { $list_bases = rvd_front->list_machines_user($user_sql); is(scalar(@$list_bases),0) or exit; + is($user_sql->is_member($g_name),0); $user_sql->add_to_group($g_name); is($user_sql->is_member($g_name),1); @@ -144,6 +145,9 @@ sub test_access_by_group_sql($vm) { remove_domain($base); $group->remove() if $group; + + my $group2 = Ravada::Auth::Group->new(name => $g_name); + is($group2->id,undef); } diff --git a/t/lib/Test/Ravada.pm b/t/lib/Test/Ravada.pm index 7debf59d4..2692512dc 100644 --- a/t/lib/Test/Ravada.pm +++ b/t/lib/Test/Ravada.pm @@ -42,6 +42,7 @@ create_domain flush_rules_node flush_rules vm_names + user remote_config remote_config_nodes @@ -125,6 +126,7 @@ our $CONT = 0; our $CONT_POOL= 0; our $CONT_VOL= 0; our $USER_ADMIN; +our $USER; our @USERS_LDAP; our $CHAIN = 'RAVADA'; @@ -194,6 +196,30 @@ sub config_host_devices($type, $die=1) { return $config->{$type}; } +sub user { + + return $USER if $USER; + + my $login; + my $name = new_domain_name()."-$$"; + my $pass = "$$ $$"; + eval { + $login = Ravada::Auth::SQL->new(name => $name, password => $pass ); + }; + if ($@ && $@ =~ /Login failed/ ) { + $login = Ravada::Auth::SQL->new(name => $name); + $login->remove() if $login->id; + $login = undef; + } elsif ($@) { + die $@; + } + $USER = $login if $login && $login->id; + $USER = create_user($name, $pass, 0) + if !$USER; + + return $USER; +} + sub user_admin { return $USER_ADMIN if $USER_ADMIN; diff --git a/t/mojo/70_groups.t b/t/mojo/70_groups.t new file mode 100644 index 000000000..d6864dd56 --- /dev/null +++ b/t/mojo/70_groups.t @@ -0,0 +1,259 @@ +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 ($USERNAME2, $PASSWORD2); +my $SCRIPT = path(__FILE__)->dirname->sibling('../script/rvd_front'); + +$ENV{MOJO_MODE} = 'devel'; + +sub _clean_group($name) { + my $ldap = Ravada::Auth::LDAP::_init_ldap_admin(); + for my $group (Ravada::Auth::LDAP::search_group(name => $name)) { + diag("removing old LDAP group ".$group->dn); + my $mesg=$ldap->delete($group); + warn "ERROR: removing ".$group->dn." ".$mesg->code." : ".$mesg->error + if $mesg->code; + } + my $group = Ravada::Auth::Group->new(name => $name); + if ($group && $group->id) { + diag("removing old group ".$name); + $group->remove; + } + my $sth = connector->dbh->prepare("DELETE FROM group_access WHERE name=?"); + $sth->execute($name); +} + +sub test_group_created($type, $name) { + if ($type eq 'ldap') { + my $group = Ravada::Auth::LDAP::search_group(name => $name); + ok($group,"Expecting group $name in LDAP") or exit; + } elsif ($type eq 'local') { + my $group = Ravada::Auth::Group->new(name => $name); + ok($group->id,"Expecting group $name in SQL") or exit; + } else { + die "Unknown type '$type'"; + } +} + +################################################################### +sub test_group($type) { + my $group_name = new_domain_name(); + _clean_group($group_name); + $t->post_ok("/group/new",json => { type => $type , group_name => $group_name }) + ->status_is(200); + + test_group_created($type, $group_name); + + my $user_name = new_domain_name(); + my @args = ( group => $group_name ); + if ($type eq 'ldap') { + create_ldap_user($user_name,$$); + push @args,(name => $user_name); + } else { + + my $login; + eval { $login = Ravada::Auth::SQL->new(name => $user_name ) }; + $login->remove if $login && $login->id; + my $user = create_user($user_name); + push @args,( id_user => $user->id ); + } + + $t->post_ok("/group/$type/add_member", json => {@args})->status_is(200); + my $result = decode_json($t->tx->res->body); + is($result->{error},''); + + $t->get_ok("/group/$type/list_members/$group_name")->status_is(200); + my $members = decode_json($t->tx->res->body); + ok(grep {$_->{name} eq $user_name } @$members) or warn "Expecting $user_name in ".Dumper($members); + + my $id_domain = test_access($type, $group_name, $user_name); + + if ($type eq 'ldap') { + my $entry = Ravada::Auth::LDAP::search_user(name => $user_name); + my $dn = $entry->dn; + $t->post_ok("/group/$type/remove_member", json => {@args})->status_is(200); + } else { + $t->post_ok("/group/$type/remove_member", json => {@args})->status_is(200); + } + die $t->tx->res->body if $t->tx->res->code != 200; + my $result2 = decode_json($t->tx->res->body); + is($result2->{error},''); + + $t->get_ok("/group/$type/list_members/$group_name")->status_is(200); + my $members2 = decode_json($t->tx->res->body); + is_deeply($members2,[]); + + $t->get_ok("/admin/group/$type/$group_name")->status_is(200); + die $t->tx->res->body if $t->tx->res->code != 200; + + test_list_groups($type, $group_name); + + test_group_removed($type, $group_name, $user_name, $id_domain); + + mojo_login($t, $USERNAME2, $PASSWORD2); + $t->get_ok("/admin/group/$type/$group_name")->status_is(403); + + mojo_login($t, $USERNAME, $PASSWORD); + +} + +sub test_list_groups($type, $group_name) { + $t->get_ok("/group/$type/list")->status_is(200); + return if $t->tx->res->code != 200; + + my $list = decode_json($t->tx->res->body); + return if ref($list) ne 'ARRAY'; + + ok(grep(/^$group_name$/, @$list), "Missing $type $group_name in ".Dumper($list)); + + my ($first) = $group_name =~ /^(.)/; + $t->get_ok("/group/$type/list/$first")->status_is(200); + $list = decode_json($t->tx->res->body); + return if ref($list) ne 'ARRAY'; + + ok(grep(/^$group_name$/, @$list), "Missing $type $group_name in ".Dumper($list)); + +} + +sub test_add_access($type,$group_name, $user_name, $id_domain) { + + $t->get_ok("/machine/add_access_group/$type/$group_name/$id_domain") + ->status_is(200); + my $result = decode_json($t->tx->res->body); + is($result->{error},''); + + my $sth = connector->dbh->prepare( + "SELECT * FROM group_access WHERE name=?" + ); + $sth->execute($group_name); + my ($found) = $sth->fetchrow_hashref(); + ok($found); + is($found->{type}, $type); + + my $user = Ravada::Auth::SQL->new(name => $user_name); + is($user->allowed_access($id_domain),1); + my $list2 = rvd_front->list_machines_user($user); + + my ($found_machine) = grep { $_->{id} eq $id_domain } @$list2; + ok($found_machine,"Expecting $id_domain") or die Dumper([ map {$_->{id} } @$list2]); + + $t->get_ok("/machine/list_access_groups/$type/$id_domain")->status_is(200); + + my $list_groups = decode_json($t->tx->res->body); + is($result->{error},''); + + my ($found_groups) = grep (/^$group_name$/,@$list_groups); + is($found_groups, $group_name) or warn Dumper($list_groups); + +} + +sub test_remove_access($type, $group_name, $user_name, $id_domain) { + $t->get_ok("/machine/remove_access_group/$type/$group_name/$id_domain") + ->status_is(200); + $t->get_ok("/machine/list_access_groups/$type/$id_domain")->status_is(200); + + my $list_groups = decode_json($t->tx->res->body); + + my ($found_groups) = grep (/^$group_name$/,@$list_groups); + is($found_groups, undef) or die Dumper($list_groups); + +} + +sub _search_id_domain { + my $list = rvd_front->list_machines_user(user_admin); + my ($domain) = grep { $_->{is_public} } @$list; + if (!$domain) { + ($domain) = $list->[0]; + } + return $domain->{id}; +} + +sub test_access($type, $group_name, $user_name) { + my $id_domain = _search_id_domain(); + test_add_access($type, $group_name, $user_name, $id_domain); + test_remove_access($type, $group_name, $user_name, $id_domain); + return $id_domain; +} + +sub test_group_removed($type, $group_name, $user_name, $id_domain) { + + my $group0 = Ravada::Auth::Group->new(name => $group_name); + my $id_group = $group0->id; + + my $user = Ravada::Auth::SQL->new( name => $user_name); + + $t->post_ok("/group/$type/add_member", json => {id_user => $user->id, group => $group_name })->status_is(200); + + $t->get_ok("/machine/add_access_group/$type/$group_name/$id_domain") + ->status_is(200); + + $t->get_ok("/group/$type/remove/$group_name")->status_is(200); + die $t->tx->res->body if $t->tx->res->code != 200; + + if ($type eq 'ldap') { + my $group = Ravada::Auth::LDAP::search_group(name => $group_name); + ok(!$group,"Expecting no group $group_name in LDAP") or exit; + } elsif ($type eq 'local') { + my $group = Ravada::Auth::Group->new(name => $group_name); + ok(!$group->id,"Expecting no group $group_name in SQL") or exit; + + my $sth = connector->dbh->prepare("SELECT count(*) FROM users_group " + ." WHERE id_group=?" + ); + $sth->execute($id_group); + my ($found) = $sth->fetchrow; + is($found,0); + + } else { + die "Unknown type '$type'"; + } + + my $sth2 = connector->dbh->prepare("SELECT * FROM group_access " + ." WHERE name = ?" + ); + $sth2->execute($group_name); + my ($found) = $sth2->fetchrow_hashref; + is($found,undef); + + $t->get_ok("/admin/group/$type/$group_name")->status_is(404); + +} + +################################################################### + +init('/etc/ravada.conf',0); +$Test::Ravada::BACKGROUND=1; +($USERNAME, $PASSWORD) = ( user_admin->name, "$$ $$"); +($USERNAME2, $PASSWORD2) = ( user->name, "$$ $$"); + +$t = Test::Mojo->new($SCRIPT); +$t->ua->inactivity_timeout(900); +$t->ua->connect_timeout(60); + + +mojo_login($t, $USERNAME, $PASSWORD); + +test_group('ldap'); +test_group('local'); + +done_testing(); diff --git a/templates/bootstrap/new_group.html.ep b/templates/bootstrap/new_group.html.ep index 543f715ab..127518436 100644 --- a/templates/bootstrap/new_group.html.ep +++ b/templates/bootstrap/new_group.html.ep @@ -6,7 +6,8 @@ %= include 'bootstrap/navigation'
-
+
diff --git a/templates/main/admin_group.html.ep b/templates/main/admin_group.html.ep index 36bf7b04d..caa2becc8 100644 --- a/templates/main/admin_group.html.ep +++ b/templates/main/admin_group.html.ep @@ -6,15 +6,19 @@ %= include 'bootstrap/navigation'
@@ -22,7 +26,7 @@
<%=l 'New group member' %> @@ -33,7 +37,7 @@ >
@@ -58,7 +62,7 @@ - {{user}} + {{user.name}} diff --git a/templates/ng-templates/new_group.html.ep b/templates/ng-templates/new_group.html.ep index de13e6b6c..5c862b7c8 100644 --- a/templates/ng-templates/new_group.html.ep +++ b/templates/ng-templates/new_group.html.ep @@ -1,27 +1,49 @@
-
+ {{item}} +
+
- <%= $groupname %> -
+ +
+
+
+ +
-
-% for (sort ('nsMemberOf','posixGroup','groupOfUniqueNames') ) { - {$_} or '') %> name="object_class" value="<%= $_ %>"> <%= $_ %>
-% } + +
+
+ + +
-
-
+
+
<%=l 'Oops!' %> <%=l 'Group name is required' %>.
-
+
<%=l 'Oops!' %> <%=l 'Group name can not exceed 80 characters' %>.
- + % if (scalar @$error) { % for my $i (@$error) {
@@ -30,7 +52,7 @@ % } % } -
- <%=l 'Group name' %> <%= $groupname %> <%=l 'added' %>. +
+ <%=l 'Group' %> {{group_name}} <%=l 'added' %>.
From 231ea89a4da05759a979a4134c0fd582d1822cfd Mon Sep 17 00:00:00 2001 From: Francesc Guasch Date: Wed, 27 Mar 2024 16:46:30 +0100 Subject: [PATCH 05/35] wip(frontend): list local and ldap groups --- templates/main/admin_groups.html.ep | 38 +++++------------------- templates/main/list_groups_ldap.html.ep | 19 ++++++++++++ templates/main/list_groups_local.html.ep | 17 +++++++++++ 3 files changed, 43 insertions(+), 31 deletions(-) create mode 100644 templates/main/list_groups_ldap.html.ep create mode 100644 templates/main/list_groups_local.html.ep diff --git a/templates/main/admin_groups.html.ep b/templates/main/admin_groups.html.ep index 47d8b93cb..f301e3ddf 100644 --- a/templates/main/admin_groups.html.ep +++ b/templates/main/admin_groups.html.ep @@ -6,29 +6,17 @@ %= include 'bootstrap/navigation'
-
+
<%=l 'New group member' %> + + <%=l 'No users found' %> +
- {{$ctrl.entry.local_groups}}
@@ -124,6 +123,8 @@ ng-click="$ctrl.save()" ><%=l 'Save changes' %> + invalid: '{{form_booking.$invalid}}'
+ pristine: '{{form_booking.$pristine}}'
- {{ form_booking.ldap_groups }} diff --git a/templates/booking/localGroup.component.html.ep b/templates/booking/localGroup.component.html.ep index bf2aff104..a88ec0b82 100644 --- a/templates/booking/localGroup.component.html.ep +++ b/templates/booking/localGroup.component.html.ep @@ -5,7 +5,7 @@ ng-required="!$ctrl.ngModel.$valid" uib-typeahead="name for name in $ctrl.getGroups($viewValue)" typeahead-min-length="0" - typeahead-on-select="$ctrl.add_ldap_group()" + typeahead-on-select="$ctrl.add_local_group()" typeahead-loading="$ctrl.loadingGroups" typeahead-no-results="$ctrl.noResults" class="form-control"> @@ -20,7 +20,7 @@
{{group}} - + diff --git a/templates/bootstrap/navigation.html.ep b/templates/bootstrap/navigation.html.ep index 233e7a5a5..5c8fab373 100644 --- a/templates/bootstrap/navigation.html.ep +++ b/templates/bootstrap/navigation.html.ep @@ -24,7 +24,7 @@ navbar-dark bg-dark fixed-top navbar-expand-lg navbar-inverse"> <%=l 'Available Machines' %> % } -% if ($bookings && defined $_user && $FEATURE->{ldap} ) { +% if ($bookings && defined $_user) {
-
- <%=l 'Bookings require LDAP authentication.' %> -
From 23bf82489d8e557efa4993e72e8af602ba8ff98c Mon Sep 17 00:00:00 2001 From: Francesc Guasch Date: Tue, 9 Apr 2024 12:37:42 +0200 Subject: [PATCH 16/35] wip: fixed change groups clears pristine flag --- public/js/booking/ldapGroups.component.js | 2 ++ public/js/booking/localGroups.component.js | 2 ++ templates/booking/formEvent.component.html.ep | 2 -- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/public/js/booking/ldapGroups.component.js b/public/js/booking/ldapGroups.component.js index eeea28dfc..b93a67a9e 100644 --- a/public/js/booking/ldapGroups.component.js +++ b/public/js/booking/ldapGroups.component.js @@ -43,6 +43,8 @@ function grpCtrl(apiLDAP, $scope, $timeout) { } self.onAdd({ group: self.group_selected}) self.group_selected = null; + // issue a bogus remove so the form is not pristine + self.remove_ldap_group('*UNDEF*'); }; self.remove_ldap_group = group => { self.selected_groups = remove_array_element(self.selected_groups,group) diff --git a/public/js/booking/localGroups.component.js b/public/js/booking/localGroups.component.js index 8e411f088..75b832a0f 100644 --- a/public/js/booking/localGroups.component.js +++ b/public/js/booking/localGroups.component.js @@ -43,6 +43,8 @@ function grpCtrl(apiLocal, $scope, $timeout) { } self.onAdd({ group: self.group_selected}) self.group_selected = null; + // issue a bogus remove so the form is not pristine + self.remove_local_group('*UNDEF*'); }; self.remove_local_group = group => { self.selected_groups = remove_array_element(self.selected_groups,group) diff --git a/templates/booking/formEvent.component.html.ep b/templates/booking/formEvent.component.html.ep index 4b0a393d1..8866e3743 100644 --- a/templates/booking/formEvent.component.html.ep +++ b/templates/booking/formEvent.component.html.ep @@ -123,8 +123,6 @@ ng-click="$ctrl.save()" ><%=l 'Save changes' %> - invalid: '{{form_booking.$invalid}}'
- pristine: '{{form_booking.$pristine}}'
+
+ <%=l 'Enables schedule server bookings for exclusive use' %> +
From 71be09932100d739be61b3c2911ca65e933ae066 Mon Sep 17 00:00:00 2001 From: Francesc Guasch Date: Tue, 9 Apr 2024 15:06:11 +0200 Subject: [PATCH 19/35] wip: hide LDAP groups select when no LDAP --- templates/booking/formEvent.component.html.ep | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/templates/booking/formEvent.component.html.ep b/templates/booking/formEvent.component.html.ep index a1d6258dc..a7a951b1d 100644 --- a/templates/booking/formEvent.component.html.ep +++ b/templates/booking/formEvent.component.html.ep @@ -89,7 +89,9 @@
-
+
From bb995c8b4152666215096f39220861513f385aa7 Mon Sep 17 00:00:00 2001 From: Francesc Guasch Date: Wed, 10 Apr 2024 11:27:14 +0200 Subject: [PATCH 20/35] wip: now bookings are enabled by default --- t/vm/r30_reserve.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/vm/r30_reserve.t b/t/vm/r30_reserve.t index 77d3c1001..aeaf4c28f 100644 --- a/t/vm/r30_reserve.t +++ b/t/vm/r30_reserve.t @@ -1296,7 +1296,7 @@ sub test_config { }; like($@, qr/LDAP required/i); - is(rvd_back->setting('/backend/bookings'),0); + is(rvd_back->setting('/backend/bookings'),1); } ################################################################### From 6cd2c4ab18f80cfe794cd1fa8344f833c7643195 Mon Sep 17 00:00:00 2001 From: Francesc Guasch Date: Wed, 10 Apr 2024 11:37:44 +0200 Subject: [PATCH 21/35] wip(doc): new methods --- lib/Ravada/Auth/SQL.pm | 9 +++++++++ lib/Ravada/Auth/User.pm | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/lib/Ravada/Auth/SQL.pm b/lib/Ravada/Auth/SQL.pm index a5d6af463..8d51f5fb5 100644 --- a/lib/Ravada/Auth/SQL.pm +++ b/lib/Ravada/Auth/SQL.pm @@ -1358,6 +1358,15 @@ sub add_to_group($self, @group) { } } +=head2 remove_from_group + +Removes the user from a group or list of groups + +Arguments: list of group names + +=cut + + sub remove_from_group($self, @group) { my $sth = $$CON->dbh->prepare( diff --git a/lib/Ravada/Auth/User.pm b/lib/Ravada/Auth/User.pm index ca65a68ed..8a9e51a6d 100644 --- a/lib/Ravada/Auth/User.pm +++ b/lib/Ravada/Auth/User.pm @@ -512,6 +512,14 @@ sub list_requests($self, $date_req=Ravada::Utils::date_now(3600)) { return @req; } +=head2 is_member + +Returns wether an user is member of a group + +Arguments: group name or object + +=cut + sub is_member($self, $group) { $group = Ravada::Auth::Group->new(name => $group) if !ref($group); my $is_member = grep { $_ eq $group->name} $self->groups_local; From a8ef18c37518adadcbdcf0e28b5c8409403309c1 Mon Sep 17 00:00:00 2001 From: Francesc Guasch Date: Thu, 11 Apr 2024 12:44:27 +0200 Subject: [PATCH 22/35] wip: cloacked angular while loading --- MANIFEST | 2 +- templates/bootstrap/new_group.html.ep | 2 +- templates/main/admin_groups.html.ep | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/MANIFEST b/MANIFEST index ea4e194d6..4b71696c2 100644 --- a/MANIFEST +++ b/MANIFEST @@ -55,6 +55,6 @@ lib/Ravada/I18N/ca.po lib/Ravada/NetInterface.pm lib/Ravada/Auth.pm lib/Ravada/Domain.pm -lib/Ravada/Routes.pm +lib/Ravada/Route.pm script/rvd_front script/rvd_back diff --git a/templates/bootstrap/new_group.html.ep b/templates/bootstrap/new_group.html.ep index 127518436..eca7cbdbf 100644 --- a/templates/bootstrap/new_group.html.ep +++ b/templates/bootstrap/new_group.html.ep @@ -9,7 +9,7 @@
-
+

<%=l 'Create a new group' %>

diff --git a/templates/main/admin_groups.html.ep b/templates/main/admin_groups.html.ep index f301e3ddf..367ccafa9 100644 --- a/templates/main/admin_groups.html.ep +++ b/templates/main/admin_groups.html.ep @@ -9,8 +9,8 @@ ng-init="list_groups()" >