Commit 8206cede authored by Francesc Guasch's avatar Francesc Guasch
Browse files

feature(frontend): manage LDAP groups

parent b39a3e96
......@@ -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)"
......@@ -1757,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'
}
]
,
......@@ -1770,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'
}
]
,
......@@ -1779,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'
}
]
,
......@@ -1788,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'
}
]
,
......@@ -1797,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'
}
]
,
......@@ -2088,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;
}
......@@ -4141,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);
}
......@@ -4158,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')
......
......@@ -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';
......@@ -460,7 +456,11 @@ sub search_group {
return $entries[0];
}
sub search_group_member($cn, $retry = 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 (
......@@ -470,7 +470,7 @@ sub search_group_member($cn, $retry = 0) {
);
if ( ($mesg->code == 1 || $mesg->code == 81) && $retry <3 ) {
$LDAP_ADMIN = undef;
return search_group_member($cn, $retry+1);
return search_group_members($cn, $retry+1);
}
warn $mesg->code." ".$mesg->error." [base: $base]" if $mesg->code;
......@@ -497,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;
}
......@@ -590,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(@_);
}
......@@ -956,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,15 +1120,25 @@ 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 $self->{_ldap_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_member($self->name);
my @groups = Ravada::Auth::LDAP::search_group_members($self->name);
return @groups;
}
......
......@@ -422,7 +422,7 @@ sub _load_allowed_groups($self) {
next unless $self->is_external && $self->external_auth eq 'ldap';
$self->{_allowed}->{$id_domain} = 1
if Ravada::Auth::LDAP::is_member($self->name, $name);
if Ravada::Auth::LDAP::is_member($self->ldap_entry, $name);
}
}
......
......@@ -5673,6 +5673,24 @@ sub _allow_group_access($self, %args) {
$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;
......@@ -6353,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=? "
......
......@@ -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);
}
......
......@@ -848,7 +848,6 @@ ravadaApp.directive("solShowMachine", swMach)
$scope.loading_users = false;
$scope.error = response.data.error;
$scope.users = response.data.entries;
console.log(response.data.error);
});
};
$scope.add_member = function(cn) {
......@@ -873,6 +872,12 @@ ravadaApp.directive("solShowMachine", swMach)
$scope.error = response.data.error;
});
};
$scope.remove_group = function() {
$scope.confirm_remove=false;
$http.post("/ldap/group/remove/"+group).then(function(response) {
$scope.error=response.data.error;
});
};
};
......
......@@ -425,6 +425,7 @@
$scope.list_ldap_attributes();
list_interfaces();
list_users();
list_access_groups();
}
$scope.hardware_types = Object.keys(response.data.hardware);
$scope.copy_ram = $scope.showmachine.max_mem / 1024 / 1024;
......@@ -891,18 +892,24 @@
});
};
$scope.list_access_groups = function() {
/* $http.get("/domain/access_groups/"+showmachine.id).then(function(response) {
var list_access_groups = function() {
$http.get("/machine/list_access_groups/"+$scope.showmachine.id).then(function(response) {
$scope.access_groups=response.data;
});*/
});
};
$scope.add_group_access = function(group) {
$http.get("/machine/add_access_group/"+$scope.showmachine.id+"/"+group)
.then(function(response) {
$scope.list_access_groups();
list_access_groups();
});
};
$scope.remove_group_access = function(group) {
$http.get("/machine/remove_access_group/"+$scope.showmachine.id+"/"+group)
.then(function(response) {
list_access_groups();
});
};
$scope.message = [];
$scope.disk_remove = [];
$scope.pending_before = 10;
......@@ -912,11 +919,9 @@
$scope.access_value = [ ];
$scope.access_allowed = [ ];
$scope.access_last = [ ];
$scope.access_groups = [ ];
$scope.new_base = undefined;
$scope.list_ldap_attributes();
$scope.list_access_groups();
};
function swListMach() {
......
......@@ -916,6 +916,15 @@ get '/machine/move_access/(#id_domain)/(#id_access)/(#position)' => sub {
return $c->render(json => { ok => 1 });
};
get '/machine/list_access_groups/(#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 ] );
};
get '/machine/add_access_group/(#id_domain)/(#group)' => sub($c) {
my $id_domain = $c->stash('id_domain');
......@@ -923,12 +932,21 @@ get '/machine/add_access_group/(#id_domain)/(#group)' => sub($c) {
my $ok = 0;
eval {
my $domain = Ravada::Front::Domain->open($id_domain);
$domain->add_access_group($group);
$domain->grant_access(type => 'group' , group => $group);
$ok =1;
};
return $c->render( json => { ok => $ok, error => $@ });
};
get '/machine/remove_access_group/(#id_domain)/(#group)' => sub($c) {
my $sth = $RAVADA->_dbh->prepare("DELETE FROM group_access WHERE id_domain=? "
." AND name=?"
);
$sth->execute($c->stash('id_domain'), $c->stash('group'));
return $c->render(json => { ok => 1 } );
};
get '/machine/compact/(#id_domain)' => sub($c) {
my $req = Ravada::Request->compact(
id_domain => $c->stash('id_domain')
......@@ -973,7 +991,7 @@ get '/machine/public/#id/#value' => sub {
};
get '/machine/set/#id/#field/#value' => sub {
my %privileged = map { $_ => 1 } qw(timeout id_owner);
my %privileged = map { $_ => 1 } qw(timeout id_owner shutdown_disconnected shutdown_timeout autostart);
my $c = shift;
my $id = $c->stash('id');
......@@ -1086,15 +1104,36 @@ any '/group/new' => sub {
return new_group($c);
};
any '/admin/user/(:id).(:type)' => sub {
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'));
my $id = $c->stash('id');
my $user;
if ($id =~ /cn=(.*?),/) {
$user = Ravada::Auth::SQL->new(name => $1);
} elsif ($id =~ /^\d+$/) {
$user = Ravada::Auth::SQL->search_by_id($id);
} else {
$user = Ravada::Auth::SQL->new(name => $id);
}
return $c->render(text => "Unknown user id: ".$c->stash('id'))
if !$user;
my $origin = $c->param('origin');
$c->stash(origin => $origin, warning => '');
return $c->render(text => "Unknown user ".$c->stash('id'))
if !$user || (!$user->id && !$c->stash('origin'));
if ($c->param('import') ) {
return $c->render( text => "Error: missing origin") if !$origin;
if ($user->id) {
$c->stash( warning => "Warning: user already imported");
} else {
Ravada::Auth::SQL::add_user(name => $user->name, is_external => 1, is_temporary => 0
, external_auth => $origin);
$user = Ravada::Auth::SQL->new(name => $user->name);
}
}
if ($c->param('make_admin')) {
$USER->make_admin($c->stash('id')) if $c->param('is_admin');
......@@ -1132,6 +1171,7 @@ get '/user/list_groups/(#id_user)' => sub($c) {
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 => [] ) if !$user;
return $c->render(json => [$user->groups()]);
};
......
......@@ -46,12 +46,28 @@
<button ng-click="remove_member(user)"
ng-show="<%= $_user->can_manage_groups %>"
class="badge badge-light text-blue">x</button>
{{user}}
<a ng-bind-html="user" href="/admin/user/{{user}}.html?origin=ldap">{{user}}</a>
</td>
</tr>
</tbody>
</table>
<div ng-show="group_members.length==0">No members found</div>
<div class="alert alert-warning" ng-show="group_members.length==0">No members found</div>
<input type="button" class="btn btn-outline-secondary"
ng-show="group_members && group_members.length==0
&& !confirm_remove"
ng-click="confirm_remove=true"
value="Remove group"/>
<div ng-show="confirm_remove" class="card">
<div ng-show="confirm_remove" class="card-body">
<span><%=l 'Are you sure you want to remove this group ?'%></span><br/>
<input type="button" class="btn btn-primary" value="Yes"
ng-click="remove_group()"/>
<input type="button" class="btn btn-danger" value="No"
ng-click="confirm_remove=false"/>
</div>
</div>
</div>
</div>
</div>
......
<i ng-show="!ldap_groups" class="fas fa-sync-alt fa-spin"></i>
<div class="card">
<div class="card-body">
<button ng-enabled="allow_group"
ng-click="add_group_access(allow_group)">Add</button>
<select ng-model="allow_group"
ng-show="ldap_groups"
ng-options="group for group in ldap_groups | orderBy:group"
>
</select>
<button ng-show="allow_group"
ng-click="add_group_access(allow_group)">Add</button>
<div class="alert alert-warning" ng-show="access_groups.length == 0">
<div ng-show="ldap_groups && !access_groups">
<i class="fas fa-sync-alt fa-spin"></i>
</div>
<div class="card-body">
<div class="alert alert-warning" ng-show="access_groups && access_groups.length == 0">
<div ng-show="ldap_groups && ldap_groups.length == 0 ">
<%=l 'There are no LDAP groups defined.' %>
% if ( $USER->is_admin ) {
<a href="/admin/groups">Create one.</a>
% }
</div>
<%=l 'This virtual machine has no group restrictions.' %>
</div>
<div ng-show="access_groups.length > 0">
<div ng-show="access_groups && access_groups.length > 0">
<b><%=l 'Only users from these groups will be allowed to execute this machine' %></b>
</div>
<table>
</thead>
<table ng-show="access_groups.length" class="card-table table-stripped">
<tbody>
<tr ng-repeat="group in access_groups">
<td>
<button ng-click="remove_group_access(group)"
class="badge badge-light text-blue">x</button>
{{group}}
<a ng-bind-html="group" href="/admin/group/{{group}}">{{group}}</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
......@@ -9,12 +9,30 @@
<div class="card">
<div class="card-header">
<h2><%=l 'User' %>&nbsp;<%= $user->name %>
% if ( $user->external_auth ) {
(<%= $user->external_auth %>)
% if ( $user->external_auth || $origin ) {
(<%= ($user->external_auth or $origin) %>)
% }
</h2>
% if ($user->external_auth && $user->ldap_entry ) {
<%= $user->ldap_entry->dn %>
% }
</div> <!-- del panel heading-->
</div>
% if (!$user->id) {
<div>
<%=l 'This user has never logged in the Ravada server.' %>
<br/>
<a class="btn btn-outline-secondary" href="/admin/groups">Cancel</a>
<a class="btn btn-primary" href="/admin/user/<%= $user->name %>.html?origin=<%= $origin %>&import=1">Import</a>
</div>
% }
% if ($warning) {
<div class="alert alert-warning"><%= $warning %></div>
% }
% if ($user->id) {
<ul class="nav nav-tabs" id="myTab" role="tablist">
% if ( $_user->is_admin ) {
<li class="nav-item">
......@@ -38,28 +56,29 @@
% }
</ul>
% if ( $_user->is_admin && $user->is_external && $user->external_auth eq 'ldap' && !$user->ldap_entry ) {
% }
% if ($user->id && $_user->is_admin && $user->is_external && $user->external_auth eq 'ldap' && !$user->ldap_entry ) {
<div class="alert alert-danger">
<%=l 'Error: the LDAP entry for this user has been removed.' %>
</div>
% }
<div class="tab-content" id="myTabContent">
% if ( $_user->is_admin ) {
% if ($user->id && $_user->is_admin ) {
<div class="tab-pane fade show active" id="admin" role="tabpanel" aria-labelledby="admin-tab">
%= include '/main/manage_user_admin'
</div>
% }
% if ( $_user->can_grant) {
% if ($user->id && $_user->can_grant) {
<div class="tab-pane fade" id="grants" role="tabpanel" aria-labelledby="grants-tab">
%= include '/main/manage_user_grants'
</div>
% }
% if (( $_user->is_admin ) && (! $user->is_external)) {
% if ($user->id && ( $_user->is_admin ) && (! $user->is_external)) {
<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' && $user->ldap_entry ) {
% if ( $_user->is_admin && $user->id && $user->is_external && $user->external_auth eq 'ldap' && $user->ldap_entry ) {
<div class="tab-pane fade" id="groups" role="tabpanel" aria-labelledby="group-tab">
%= include '/main/manage_user_groups'
</div>
......
......@@ -27,7 +27,7 @@
<button ng-click="remove_group_member( <%= $user->id %>,'<%= $user->ldap_entry->dn %>',group)"
ng-show="<%= $_user->can_manage_users %>"
class="badge badge-light text-blue">x</button>
{{group}}
<a ng-bind-html="group" href="/admin/group/{{group}}">{{group}}</a>
</td>
</tr>
</tbody>
......
Markdown is supported