Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
Projets publics
Ravada-Mirror
Commits
8862e5fd
Commit
8862e5fd
authored
Sep 10, 2021
by
Francesc Guasch
Browse files
Merge branch 'develop' into main
parents
f1fa2642
76cf708b
Changes
12
Hide whitespace changes
Inline
Side-by-side
lib/Ravada.pm
View file @
8862e5fd
...
...
@@ -1457,7 +1457,7 @@ sub _add_grants($self) {
$self
->
_add_grant
('
reboot_all
',
0
,"
Can reboot all virtual machines.
");
$self
->
_add_grant
('
reboot_clones
',
0
,"
Can reboot clones own virtual machines.
");
$self
->
_add_grant
('
screenshot
',
1
,"
Can get a screenshot of own virtual machines.
");
$self
->
_add_grant
('
start_many
',
0
,"
Can have
more than one
machine started.
");
$self
->
_add_grant
('
start_many
',
0
,"
Can have
an unlimited amount of
machine
s
started.
");
$self
->
_add_grant
('
expose_ports
',
0
,"
Can expose virtual machine ports.
");
$self
->
_add_grant
('
view_groups
',
0
,'
Can view groups.
');
$self
->
_add_grant
('
manage_groups
',
0
,'
Can manage groups.
');
...
...
lib/Ravada/Auth/SQL.pm
View file @
8862e5fd
...
...
@@ -649,7 +649,7 @@ Returns if the user is allowed to perform a privileged action
sub
can_do
($self, $grant) {
$self
->
_load_grants
();
confess
"
Wrong grant
'
$grant
'
\n
"
.
Dumper
(
$self
->
{
_grant_alias
})
confess
"
Permission
'
$grant
'
invalid
\n
"
.
Dumper
(
$self
->
{
_grant_alias
})
if
$grant
!~
/^[a-z_]+$/
;
$grant
=
$self
->
_grant_alias
(
$grant
);
...
...
@@ -739,6 +739,8 @@ sub _load_grants($self) {
my
$grant_alias
=
$self
->
_grant_alias
(
$name
);
$self
->
{
_grant
}
->
{
$grant_alias
}
=
$allowed
if
$enabled
;
$self
->
{
_grant_disabled
}
->
{
$grant_alias
}
=
!
$enabled
;
$self
->
{
_grant_type
}
->
{
$grant_alias
}
=
'
boolean
';
$self
->
{
_grant_type
}
->
{
$grant_alias
}
=
'
int
'
if
$is_int
;
}
$sth
->
finish
;
}
...
...
@@ -876,6 +878,14 @@ sub grant($self,$user,$permission,$value=1) {
.
Dumper
(
\
@perms
);
}
if
(
$value
eq
'
false
'
||
!
$value
)
{
$value
=
0
;
}
else
{
$value
=
1
;
}
return
0
if
!
$value
&&
!
$user
->
can_do
(
$permission
);
my
$value_sql
=
$user
->
can_do
(
$permission
);
return
0
if
!
$value
&&
!
$value_sql
;
return
$value
if
defined
$value_sql
&&
$value_sql
eq
$value
;
...
...
@@ -944,6 +954,10 @@ sub list_all_permissions($self) {
return
@list
;
}
sub
grant_type
($self, $permission) {
return
$self
->
{
_grant_type
}
->
{
$permission
};
}
=head2 list_permissions
Returns a list of all the permissions granted to the user
...
...
@@ -1107,6 +1121,16 @@ sub grants($self) {
return
%
{
$self
->
{
_grant
}};
}
sub
grants_info
($self) {
my
%grants
=
$self
->
grants
();
my
%grants_info
;
for
my
$key
(
keys
%grants
)
{
$grants_info
{
$key
}
->
[
0
]
=
$grants
{
$key
};
$grants_info
{
$key
}
->
[
1
]
=
$self
->
{
_grant_type
}
->
{
$key
};
}
return
%grants_info
;
}
=head2 ldap_entry
Returns the ldap entry as a Net::LDAP::Entry of the user if it has
...
...
lib/Ravada/Auth/User.pm
View file @
8862e5fd
...
...
@@ -176,6 +176,7 @@ sub send_message($self, $subject, $message='') {
"
INSERT INTO messages (id_user, subject, message)
"
.
"
VALUES(?, ? , ? )
");
$subject
=
substr
(
$subject
,
0
,
120
)
if
length
(
$subject
)
>
120
;
$sth
->
execute
(
$self
->
id
,
$subject
,
$message
);
}
...
...
lib/Ravada/I18N/ca.po
View file @
8862e5fd
...
...
@@ -307,7 +307,7 @@ msgid "Password"
msgstr "Contrasenya"
msgid "Start session"
msgstr "Inicieu la Sessió
\n
"
msgstr "Inicieu la Sessió"
msgid "bits) in your computer."
msgstr "bits) al teu ordinador."
...
...
@@ -972,3 +972,10 @@ msgstr "Esperant que surti la xarxa"
msgid "Press SHIFT + F12 to exit"
msgstr "Premeu SHIFT + F12 per sortir"
msgid "Permission granted to user"
msgstr "Permís concedit a l'usuari"
msgid "Permission revoked from user"
msgstr "Permís denegat a l'usuari"
public/js/admin.js
View file @
8862e5fd
...
...
@@ -440,8 +440,44 @@ ravadaApp.directive("solShowMachine", swMach)
});
};
$scope
.
load_grants
=
function
(
id
)
{
id_user
=
id
;
$http
.
get
(
"
/user/grants/
"
+
id_user
).
then
(
function
(
response
)
{
$scope
.
perm
=
response
.
data
;
});
$http
.
get
(
"
/user/info/
"
+
id_user
).
then
(
function
(
response
)
{
$scope
.
user
=
response
.
data
;
});
};
$scope
.
toggle_grant
=
function
(
grant
)
{
$scope
.
perm
[
grant
]
=
!
$scope
.
perm
[
grant
];
$http
.
get
(
"
/user/grant/
"
+
id_user
+
"
/
"
+
grant
+
"
/
"
+
$scope
.
perm
[
grant
]).
then
(
function
(
response
)
{
$scope
.
error
=
response
.
data
.
error
;
$scope
.
info
=
response
.
data
.
info
;
});
};
$scope
.
update_grant
=
function
(
grant
)
{
$http
.
get
(
"
/user/grant/
"
+
id_user
+
"
/
"
+
grant
+
"
/
"
+
$scope
.
perm
[
grant
]).
then
(
function
(
response
)
{
$scope
.
error
=
response
.
data
.
error
;
$scope
.
info
=
response
.
data
.
info
;
});
};
$scope
.
change_user
=
function
(
data
)
{
$http
.
post
(
'
/user/set/
'
+
id_user
,
JSON
.
stringify
(
data
)
).
then
(
function
(
response
)
{
$scope
.
load_grants
(
id_user
);
});
};
$scope
.
init
=
function
(
id
)
{
$scope
.
load_grants
(
id
);
$scope
.
list_user_groups
(
id
);
};
$scope
.
list_groups
();
};
var
id_user
;
};
function
messagesPageC
(
$scope
,
$http
,
$interval
,
request
)
{
$scope
.
getMessages
=
function
()
{
...
...
script/rvd_front
View file @
8862e5fd
...
...
@@ -1109,7 +1109,7 @@ any '/group/new' => sub {
any '/admin/user/(#id).(:type)' => sub {
my $c = shift;
return access_denied($c)
if !
$USER->can_manage_users()
&&
!
$USER->can_grant();
return access_denied($c)
unless
$USER->can_manage_users()
&&
$USER->can_grant();
push @{$c->stash->{js}}, '/js/admin.js';
my $id = $c->stash('id');
...
...
@@ -1138,35 +1138,121 @@ any '/admin/user/(#id).(:type)' => sub {
}
}
if ($c->param('make_admin')) {
$USER->make_admin($c->stash('id')) if $c->param('is_admin');
$USER->remove_admin($c->stash('id'))if !$c->param('is_admin');
$user = Ravada::Auth::SQL->search_by_id($c->stash('id'));
}
if ($c->param('grant')) {
return access_denied($c) if !$USER->can_grant();
my %grant;
for my $param_name (@{$c->req->params->names}) {
if ( $param_name =~ /^perm_(.*)/ ) {
my $max = defined($c->req->params->param('max_perm_' . $1)) ? $c->req->params->param('max_perm_' . $1) : 1;
$grant{$1} = $max > 1 ? $max : 1;
} elsif ($param_name =~ /^off_perm_(.*)/) {
$grant{$1} = 0 if !exists $grant{$1};
}
push @{$c->stash->{js}}, '/js/admin.js';
$c->stash(user => $user);
return $c->render(template => 'main/manage_user');
};
get '/user/grants/(:id)' => sub($c) {
return access_denied($c) unless $USER->can_manage_users()
&&
$USER->can_grant();
my $user = Ravada::Auth::SQL->search_by_id($c->stash('id'));
my %grants = $user->grants_info;
for my $key (keys %grants) {
my ($value, $type) = @{$grants{$key}};
if ( $type eq 'boolean' ) {
$grants{$key} = \1 if $value;
$grants{$key} = \0 if !$value;
} else {
$grants{$key} = $value;
}
for my $perm (keys %grant) {
if ( $grant{$perm} ) {
$USER->grant($user, $perm, $grant{$perm});
} else {
$USER->revoke($user, $perm);
}
}
return $c->render(json => \%grants);
};
get '/user/info/(:id)' => sub ($c) {
return access_denied($c) unless $USER->can_manage_users();
my $user = Ravada::Auth::SQL->search_by_id($c->stash('id'));
my %info = %{$user->{_data}};
for my $key (keys %info) {
next if $key !~ /^is_/;
if($info{$key}) {
$info{$key} = \1;
} else {
$info{$key} = \0;
}
}
return $c->render(json => \%info);
};
post '/user/set/(:id)' => sub ($c) {
return access_denied($c) unless $USER->can_manage_users()
&&
$USER->can_grant();
my $user = Ravada::Auth::SQL->search_by_id($c->stash('id'));
my $args = decode_json($c->req->body);
lock_hash(%$args);
my $sql = "";
my @values;
my $error = '';
for my $key (sort keys %$args ) {
if ($key !~ /^[a-z][a-z_]+$/) {
my $error = "Permission '$key' invalid";
$USER->send_message($error);
return $c->render(json => { error => $error });
}
if ($key eq 'is_admin') {
eval {
if ( $args->{$key} eq 'true' || $args->{$key} eq '1' ) {
$USER->send_message("Granting admin permissions to user ".$user->name);
$USER->make_admin($user->id);
} elsif ( $args->{$key} eq 'false' || !$args->{$key}) {
$USER->send_message("Revoking admin persmissions from user ".$user->name);
$USER->remove_admin($user->id);
}
};
my $subj = $error;
$USER->send_message($subj, $error) if $error;
next;
}
$sql .= " , " if $sql;
$sql .= "$key= ?";
push @values,($args->{$key});
}
if ($c->param('set_password')) {
$user->change_password($c->param('password'), $c->param('force_change_password'));
if ($sql) {
$sql = "UPDATE users set $sql WHERE id=?";
eval {
my $sth = $RAVADA->_dbh->prepare($sql);
$sth->execute(@values, $c->stash('id'));
};
}
$c->stash(user => $user);
return $c->render(template => 'main/manage_user');
$error = $@;
my $subj = $error;
$subj =~ s/ at.*//;
$USER->send_message($subj, $error) if $error;
return $c->render(json => { error => $error });
};
get '/user/grant/(:id_user)/(:grant)/(:value)' => sub($c) {
return access_denied($c) unless $USER->can_grant();
my $user = Ravada::Auth::SQL->search_by_id($c->stash('id_user'));
my $error = '';
my $value = $c->stash('value');
if ($value eq 'false' || !$value) {
$value = 0;
} else {
$value = 1;
}
eval { $USER->grant($user,$c->stash('grant'),$value) };
$error = $@;
my $info = '';
if ($error) {
$USER->send_message($error);
} else {
my $grant = $c->stash('grant');
$grant =~ s/_/ /g;
if ( $value ) {
$info = "Permission granted to user"
} else {
$info = "Permission revoked from user";
}
$info = $c->stash->{i18n}->localize($info);
$USER->send_message($info." ".$user->name." : $grant");
}
return $c->render(json => { error => $error, info => $info });
};
get '/user/list_groups/(#id_user)' => sub($c) {
...
...
@@ -3489,3 +3575,15 @@ Hi <%= $name %>,
</h2>
</body>
</html>
@@ exception.development.html.ep
<!DOCTYPE html>
<html>
<head><title>
Ravada Server error
</title></head>
<body>
<h1>
Exception
</h1>
<p><
%=
$
exception-
>
message %>
</p>
<h1>
Stash
</h1>
<pre><
%=
dumper
$
snapshot
%
></pre>
</body>
</html>
t/mojo/10_login.t
View file @
8862e5fd
...
...
@@ -189,6 +189,22 @@ sub test_login_fail {
$t
->
get_ok
("
/admin/machines
")
->
status_is
(
401
);
like
(
$t
->
tx
->
res
->
dom
->
at
("
button#submit
")
->
text
,
qr'Login'
)
or
exit
;
$t
->
get_ok
("
/admin/users
")
->
status_is
(
401
);
is
(
$t
->
tx
->
res
->
dom
->
at
("
button#submit
")
->
text
,'
Login
')
or
exit
;
}
sub
test_copy_without_prepare
($clone) {
is
(
$clone
->
is_base
,
0
)
or
die
"
Clone
"
.
$clone
->
name
.
"
is supposed to be non-base
";
my
$n_clones
=
3
;
mojo_request
(
$t
,
"
clone
",
{
id_domain
=>
$clone
->
id
,
number
=>
$n_clones
});
wait_request
(
debug
=>
1
,
check_error
=>
1
,
background
=>
1
,
timeout
=>
120
);
my
@clones
=
$clone
->
clones
();
is
(
scalar
@clones
,
$n_clones
)
or
exit
;
remove_machines
(
$clone
);
}
sub
test_validate_html
($url) {
...
...
t/mojo/30_grants.t
0 → 100644
View file @
8862e5fd
use
warnings
;
use
strict
;
use
Data::
Dumper
;
use
HTML::
Lint
;
use
Test::
More
;
use
Test::
Mojo
;
use
Mojo::
File
'
path
';
use
lib
'
t/lib
';
use
Test::
Ravada
;
no
warnings
"
experimental::signatures
";
use
feature
qw(signatures)
;
my
$t
;
my
$URL_LOGOUT
=
'
/logout
';
my
(
$USERNAME
,
$PASSWORD
);
my
$SCRIPT
=
path
(
__FILE__
)
->
dirname
->
sibling
('
../script/rvd_front
');
$ENV
{
MOJO_MODE
}
=
'
development
';
init
('
/etc/ravada.conf
',
0
);
my
$connector
=
rvd_back
->
connector
;
like
(
$connector
->
{
driver
}
,
qr/mysql/
i
)
or
BAIL_OUT
;
$t
=
Test::
Mojo
->
new
(
$SCRIPT
);
$t
->
ua
->
inactivity_timeout
(
900
);
$t
->
ua
->
connect_timeout
(
60
);
sub
test_non_admin
()
{
my
(
$username
,
$password
)
=
(
new_domain_name
(),
$$
);
my
$user_db
=
Ravada::Auth::
SQL
->
new
(
name
=>
$username
);
$user_db
->
remove
();
my
$user
=
create_user
(
$username
,
$password
);
mojo_login
(
$t
,
$user
->
name
,
$password
);
_test_user_grants
(
$user
,
403
);
$user
->
remove
();
}
sub
test_admin
()
{
my
(
$username
,
$password
)
=
(
new_domain_name
(),
$$
);
my
$user_db
=
Ravada::Auth::
SQL
->
new
(
name
=>
$username
);
$user_db
->
remove
();
my
$user_login
=
create_user
(
$username
,
$password
,
1
);
mojo_login
(
$t
,
$user_login
->
name
,
$password
);
(
$username
,
$password
)
=
(
new_domain_name
(),
$$
);
$user_db
=
Ravada::Auth::
SQL
->
new
(
name
=>
$username
);
$user_db
->
remove
();
my
$user
=
create_user
(
$username
,
$password
);
_test_user_grants
(
$user
,
200
);
$user
->
remove
();
}
sub
_test_user_grants
($user, $expected_code) {
$t
->
get_ok
("
/admin/users
");
is
(
$t
->
tx
->
res
->
code
(),
$expected_code
);
$t
->
get_ok
("
/admin/user/
"
.
$user
->
id
.
"
.html
");
is
(
$t
->
tx
->
res
->
code
(),
$expected_code
);
$t
->
get_ok
("
/user/grants/
"
.
$user
->
id
);
is
(
$t
->
tx
->
res
->
code
(),
$expected_code
);
$t
->
get_ok
("
/user/info/
"
.
$user
->
id
);
is
(
$t
->
tx
->
res
->
code
(),
$expected_code
);
_test_set_admin
(
$user
,
$expected_code
);
_test_grant
(
$user
,
$expected_code
);
}
sub
_test_set_admin
($user,$expected_code) {
$t
->
post_ok
("
/user/set/
"
.
$user
->
id
=>
json
=>
{
is_admin
=>
'
true
'});
is
(
$t
->
tx
->
res
->
code
(),
$expected_code
);
if
(
$expected_code
==
200
)
{
is
(
$t
->
tx
->
res
->
json
->
{
error
},'');
$user
->
_load_data
();
is
(
$user
->
is_admin
,
1
,"
Expected
"
.
$user
->
name
.
"
is admin
");
}
$t
->
post_ok
("
/user/set/
"
.
$user
->
id
=>
json
=>
{
is_admin
=>
'
false
'});
is
(
$t
->
tx
->
res
->
code
(),
$expected_code
);
if
(
$expected_code
==
200
)
{
is
(
$t
->
tx
->
res
->
json
->
{
error
},'');
$user
->
_load_data
();
is
(
$user
->
is_admin
,
0
);
}
$t
->
post_ok
("
/user/set/
"
.
$user
->
id
=>
json
=>
{
is_admin
=>
1
});
is
(
$t
->
tx
->
res
->
code
(),
$expected_code
);
if
(
$expected_code
==
200
)
{
is
(
$t
->
tx
->
res
->
json
->
{
error
},'');
$user
->
_load_data
();
is
(
$user
->
is_admin
,
1
,"
Expected
"
.
$user
->
name
.
"
is admin
");
}
$t
->
post_ok
("
/user/set/
"
.
$user
->
id
=>
json
=>
{
is_admin
=>
0
});
is
(
$t
->
tx
->
res
->
code
(),
$expected_code
);
if
(
$expected_code
==
200
)
{
is
(
$t
->
tx
->
res
->
json
->
{
error
},'');
$user
->
_load_data
();
is
(
$user
->
is_admin
,
0
);
}
}
sub
_test_grant
($user, $expected_code) {
$t
->
get_ok
("
/user/grant/
"
.
$user
->
id
.
"
/missing grant/true
");
is
(
$t
->
tx
->
res
->
code
(),
$expected_code
);
if
(
$expected_code
==
200
)
{
my
$error
=
$t
->
tx
->
res
->
json
->
{
error
};
like
(
$error
,
qr/Permission.*invalid/
);
}
$t
->
get_ok
("
/user/grant/
"
.
$user
->
id
.
"
/clone/false
");
is
(
$t
->
tx
->
res
->
code
(),
$expected_code
);
if
(
$expected_code
==
200
)
{
is
(
$t
->
tx
->
res
->
json
->
{
error
},'');
$user
->
_load_data
();
is
(
$user
->
can_clone
,
0
,"
Expecting user
"
.
$user
->
name
.
"
"
.
$user
->
id
.
"
can not clone
")
or
die
;
}
else
{
ok
(
$user
->
can_clone
);
}
$t
->
get_ok
("
/user/grant/
"
.
$user
->
id
.
"
/clone/1
");
is
(
$t
->
tx
->
res
->
code
(),
$expected_code
);
if
(
$expected_code
==
200
)
{
is
(
$t
->
tx
->
res
->
json
->
{
error
},'');
$user
->
_reload_grants
();
is
(
$user
->
can_clone
,
1
,"
Expecting user
"
.
$user
->
name
.
"
"
.
$user
->
id
.
"
can clone
")
or
die
;
}
$t
->
get_ok
("
/user/grant/
"
.
$user
->
id
.
"
/clone_all/true
");
is
(
$t
->
tx
->
res
->
code
(),
$expected_code
);
if
(
$expected_code
==
200
)
{
is
(
$t
->
tx
->
res
->
json
->
{
error
},'');
$user
->
_reload_grants
();
is
(
$user
->
can_clone_all
,
1
,"
Expecting user
"
.
$user
->
name
.
"
"
.
$user
->
id
.
"
can clone all
")
or
die
;
}
else
{
ok
(
!
$user
->
can_clone_all
);
}
$user
->
remove
();
}
test_non_admin
();
test_admin
();
done_testing
();
t/user/20_grants.t
View file @
8862e5fd
...
...
@@ -64,6 +64,18 @@ sub test_defaults {
}
}
my
%grants
=
$user
->
grants
();
my
%grants_info
=
$user
->
grants_info
();
for
my
$key
(
keys
%grants
)
{
is
(
$grants_info
{
$key
}
->
[
0
],
$grants
{
$key
},
$key
);
if
(
$key
eq
'
start_limit
')
{
is
(
$grants_info
{
$key
}
->
[
1
],"
int
"
,
$key
);
}
else
{
is
(
$grants_info
{
$key
}
->
[
1
],"
boolean
"
,
$key
);
}
}
$user
->
remove
();
}
...
...
templates/main/manage_user.html.ep
View file @
8862e5fd
<html>
<html>
%= include 'bootstrap/header'
<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"
ng-controller=
"usersPage"
ng-init=
"
list_user_groups
(<%= $user->id %>)"
>
<div
id=
"page-wrapper"
ng-controller=
"usersPage"
ng-init=
"
init
(<%= $user->id %>)"
>
<div
class=
"page-header"
>
<div
class=
"card"
>
...
...
templates/main/manage_user_admin.html.ep
View file @
8862e5fd
<div class="card">
<div class="card-body">
<form method="post" action="/admin/user/<%= $user->id %>.html">
% my $checked = '';
% $checked = 'checked' if $user->is_admin;
<input type="checkbox" name="is_admin" value="1" <%= $checked %>>
<i ng-show="!user" class="fas fa-sync-alt fa-spin"></i>
<form ng-show="user">
<input type="checkbox" name="is_admin"
ng-click="change_user({ 'is_admin': !user.is_admin})"
ng-model="user.is_admin"
/>
<label for="is_admin"><%=l 'is admin' %></label><br/>
<button type="reset" class="btn btn-outline-secondary" onclick = "location='/admin/users'"><%=l 'Cancel' %></button>
<input type="submit" class="btn btn-primary" name="make_admin" value="<%=l 'Submit' %>">
</form>
</div>
</div>
templates/main/manage_user_grants.html.ep
View file @
8862e5fd
<div class="card">
<div class="card-body">
<form method="post" action="/admin/user/<%= $user->id %>.html">
<form method="post" action="/admin/user/<%= $user->id %>.html"
ng-show="perm"
>
% for my $perm ($_user->list_all_permissions) {
% my $can_do = $user->can_do($perm->{name});
% my $checked = $can_do ? 'checked' : '';
<input type="checkbox" <%= $checked %>
name="perm_<%= $perm->{name} %>">
<input type="hidden" name="off_perm_<%= $perm->{name} %>" value="off">
<label for="perm_<%= $perm->{name} %>"><%= $perm->{name} %>: <%=l($perm->{description}) %></label><br/>
% if ($perm->{is_int} != 0) {
<input type="number" name="max_perm_<%= $perm->{name} %>" min="1" value="<%= $can_do %>" max="999" style="margin-left: 15px; margin-bottom: 10px"><br>
% if (!$perm->{is_int} ) {
<input type="checkbox" ng-model="perm['<%= $perm->{name} %>']"
ng-click="toggle_grant('<%= $perm->{name} %>')"
>
% }
% if ($perm->{is_int}) {
<input type="number" name="perm_<%= $perm->{name} %>" min="1" ng-model="perm['<%= $perm->{name} %>']" max="999" style="margin-left: 15px; margin-bottom: 10px"
size="4"
ng-hide="<%= $perm->{name} eq 'start_limit' %> && perm['start_many']"
ng-change="update_grant('<%= $perm->{name} %>')"
>
% }
% my $hide = $perm->{name} eq 'start_limit';
% $hide = 0 if !$hide;
<label for="perm_<%= $perm->{name} %>"
ng-hide="<%= $hide %> && perm['start_many']"
>
<span ng-show="<%= $perm->{name} ne 'start_limit'%>"><%= $perm->{name} %>:</span>
<%=l($perm->{description}) %>
</label>
<br ng-hide="<%= $hide %> && perm['start_many']"/>
% }
<button type="reset" class="btn btn-outline-secondary" onclick = "location='/admin/users'"><%=l 'Cancel' %></button>
<input type="submit" class="btn btn-primary" name="grant" value="<%=l 'Submit' %>">
</form>
</div>
</div>
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment