]> git.sur5r.net Git - bacula/bacula/blobdiff - gui/bweb/lib/Bweb.pm
ebl Add role checks for media
[bacula/bacula] / gui / bweb / lib / Bweb.pm
index 1ee279e8a81ba1db85bb802f9c2b9ffbb59a4ccb..24c2b60a88cc2ffbb5a9c90878c3faa7b332ee9d 100644 (file)
@@ -6,7 +6,7 @@ use strict;
    Bweb - A Bacula web interface
    Bacula® - The Network Backup Solution
 
-   Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
+   Copyright (C) 2000-2007 Free Software Foundation Europe e.V.
 
    The main author of Bweb is Eric Bollengier.
    The main author of Bacula is Kern Sibbald, with contributions from
@@ -218,6 +218,7 @@ our %k_re = ( dbi      => qr/^(dbi:(Pg|mysql):(?:\w+=[\w\d\.-]+;?)+)$/i,
              stat_job_table => qr!^(\w*)$!,
              display_log_time => qr!^(on)?$!,
              enable_security => qr/^(on)?$/,
+             enable_security_acl => qr/^(on)?$/,
              );
 
 =head1 FUNCTION
@@ -348,8 +349,9 @@ sub modify
     $self->{error} = '';
     # we need to reset checkbox first
     $self->{debug} = 0;
-    $self->{enable_security} = 0;
     $self->{display_log_time} = 0;
+    $self->{enable_security} = 0;
+    $self->{enable_security_acl} = 0;
 
     foreach my $k (CGI::param())
     {
@@ -1056,6 +1058,7 @@ our %sql_func = (
              STARTTIME_PWEEK => " date_part('week', Job.StartTime) ",
              DB_SIZE => " SELECT pg_database_size(current_database()) ",
              CAT_POOL_TYPE => " MediaType || '_' || Pool.Name ",
+             CONCAT_SEP => "",
          },
          mysql => {
              UNIX_TIMESTAMP => 'UNIX_TIMESTAMP',
@@ -1076,9 +1079,16 @@ our %sql_func = (
              # works only with mysql 5
              # DB_SIZE => " SELECT sum(DATA_LENGTH) FROM INFORMATION_SCHEMA.TABLES ",
              CAT_POOL_TYPE => " CONCAT(MediaType,'_',Pool.Name) ",
+             CONCAT_SEP => " SEPARATOR '' ",
          },
         );
 
+sub dbh_is_mysql
+{
+    my ($self) = @_;
+    return $self->{info}->{dbi} =~ /dbi:mysql/i;
+}
+
 sub dbh_disconnect
 {
     my ($self) = @_;
@@ -1143,7 +1153,7 @@ sub dbh_selectrow_hashref
 sub dbh_strcat
 {
     my ($self, @what) = @_;
-    if ($self->{conf}->{connection_string} =~ /dbi:mysql/i) {
+    if ($self->dbh_is_mysql()) {
        return 'CONCAT(' . join(',', @what) . ')' ;
     } else {
        return join(' || ', @what);
@@ -1204,15 +1214,29 @@ sub human_enabled
 {
     my $val = shift || 0;
 
-    if ($val == 1 or $val eq "yes") {
+    if ($val eq '1' or $val eq "yes") {
        return "yes";
-    } elsif ($val == 2 or $val eq "archived") {
+    } elsif ($val eq '2' or $val eq "archived") {
        return "archived";
     } else {
        return  "no";
     }
 }
 
+# display Enabled
+sub from_human_enabled
+{
+    my $val = shift || 0;
+
+    if ($val == 1 or $val eq "yes") {
+       return 1;
+    } elsif ($val == 2 or $val eq "archived") {
+       return 2;
+    } else {
+       return  0;
+    }
+}
+
 # get Day, Hour, Year
 sub from_human_sec
 {
@@ -1240,6 +1264,7 @@ sub connect_db
     my ($self) = @_;
 
     unless ($self->{dbh}) {
+
        $self->{dbh} = DBI->connect($self->{info}->{dbi}, 
                                    $self->{info}->{user},
                                    $self->{info}->{password});
@@ -1249,7 +1274,9 @@ sub connect_db
 
        $self->{dbh}->{FetchHashKeyName} = 'NAME_lc';
 
-       if ($self->{info}->{dbi} =~ /^dbi:Pg/i) {
+       if ($self->dbh_is_mysql()) {
+           $self->{dbh}->do("SET group_concat_max_len=1000000");
+       } else {
            $self->{dbh}->do("SET datestyle TO 'ISO, YMD'");
        }
     }
@@ -1295,18 +1322,21 @@ sub display_end
 sub display_clients
 {
     my ($self) = @_;
+    my $where='';      # by default
 
-    my $where='';
-    my $arg = $self->get_form("client", "qre_client", "jclient_groups", "qnotingroup");
+    my $arg = $self->get_form("client", "qre_client", 
+                             "jclient_groups", "qnotingroup");
 
     if ($arg->{qre_client}) {
        $where = "WHERE Name $self->{sql}->{MATCH} $arg->{qre_client} ";
     } elsif ($arg->{client}) {
        $where = "WHERE Name = '$arg->{client}' ";
     } elsif ($arg->{jclient_groups}) {
-       $where = "JOIN client_group_member ON (Client.ClientId = client_group_member.clientid) 
-                  JOIN client_group USING (client_group_id)
-                  WHERE client_group_name IN ($arg->{jclient_groups})";
+       # $filter could already contains client_group_member 
+       $where = "
+ JOIN client_group_member USING (ClientId) 
+ JOIN client_group USING (client_group_id)
+ WHERE client_group_name IN ($arg->{jclient_groups}) ";
     } elsif ($arg->{qnotingroup}) {
        $where =   "
   WHERE NOT EXISTS
@@ -1314,7 +1344,6 @@ sub display_clients
      WHERE Client.ClientId = client_group_member.ClientId
    )
 ";
-   
     }
 
     my $query = "
@@ -1323,9 +1352,8 @@ SELECT Name   AS name,
        AutoPrune AS autoprune,
        FileRetention AS fileretention,
        JobRetention  AS jobretention
-FROM Client
-$where
-";
+FROM Client " . $self->get_client_filter() .
+$where ;
 
     my $all = $self->dbh_selectall_hashref($query, 'name') ;
 
@@ -1531,9 +1559,15 @@ sub get_form
     }
 
     if ($what{db_clients}) {
+       my $filter='';
+       if ($what{filter}) {
+           # get security filter only if asked
+           $filter = $self->get_client_filter();
+       }
+
        my $query = "
 SELECT Client.Name as clientname
-  FROM Client
+  FROM Client $filter
 ";
 
        my $clients = $self->dbh_selectall_hashref($query, 'clientname');
@@ -1542,9 +1576,15 @@ SELECT Client.Name as clientname
     }
 
     if ($what{db_client_groups}) {
+       my $filter='';
+       if ($what{filter}) {
+           # get security filter only if asked
+           $filter = $self->get_client_group_filter();
+       }
+
        my $query = "
 SELECT client_group_name AS name 
-  FROM client_group
+  FROM client_group $filter
 ";
 
        my $grps = $self->dbh_selectall_hashref($query, 'name');
@@ -1617,9 +1657,13 @@ SELECT FileSet.FileSet AS fileset
     }
 
     if ($what{db_jobnames}) {
+       my $filter='';
+       if ($what{filter}) {
+           $filter = " JOIN Client USING (ClientId) " . $self->get_client_filter();
+       }
        my $query = "
 SELECT DISTINCT Job.Name AS jobname 
-  FROM Job
+  FROM Job $filter
 ";
 
        my $jobnames = $self->dbh_selectall_hashref($query, 'jobname');
@@ -1648,8 +1692,8 @@ sub display_graph
     my ($self) = @_;
 
     my $fields = $self->get_form(qw/age level status clients filesets 
-                                    graph gtype type
-                                   db_clients limit db_filesets width height
+                                    graph gtype type filter db_clients
+                                   limit db_filesets width height
                                    qclients qfilesets qjobnames db_jobnames/);
                                
 
@@ -1668,43 +1712,6 @@ sub display_graph
 
 }
 
-sub display_client_job
-{
-    my ($self, %arg) = @_ ;
-
-    $arg{order} = ' Job.JobId DESC ';
-    my ($limit, $label) = $self->get_limit(%arg);
-
-    my $clientname = $self->dbh_quote($arg{clientname});
-
-    my $query="
-SELECT DISTINCT Job.JobId       AS jobid,
-               Job.Name        AS jobname,
-                FileSet.FileSet AS fileset,
-                Level           AS level,
-                StartTime       AS starttime,
-                JobFiles        AS jobfiles, 
-                JobBytes        AS jobbytes,
-                JobStatus       AS jobstatus,
-               JobErrors       AS joberrors
-
- FROM Client,Job,FileSet
- WHERE Client.Name=$clientname
- AND Client.ClientId=Job.ClientId
- AND Job.FileSetId=FileSet.FileSetId
- $limit
-";
-
-    my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
-
-    $self->display({ clientname => $arg{clientname},
-                    Filter => $label,
-                    ID => $cur_id++,
-                    Jobs => [ values %$all ],
-                  },
-                  "display_client_job.tpl") ;
-}
-
 sub get_selected_media_location
 {
     my ($self) = @_ ;
@@ -2024,13 +2031,6 @@ sub get_param
        }
     }
 
-    # fill this only when security is enabled
-    if ($elt{username} and $self->{info}->{enable_security}) {
-        my $u = $self->dbh_quote($self->{loginname});
-        $ret{username}=$self->{loginname};
-        $limit .= "AND bweb_user.username = $u ";
-    }
-
     return ($limit, %ret);
 }
 
@@ -2043,6 +2043,7 @@ sub get_param
 sub display_job
 {
     my ($self, %arg) = @_ ;
+    return if $self->cant_do('r_view_job');
 
     $arg{order} = ' Job.JobId DESC ';
 
@@ -2055,14 +2056,14 @@ sub display_job
                                          'pools',
                                          'jobid',
                                          'status');
-
-    my $cgq = '';
+    my $cgq='';
     if (CGI::param('client_group')) {
-       $cgq = "
-LEFT JOIN client_group_member ON (Job.ClientId = client_group_member.ClientId)
-LEFT JOIN client_group USING (client_group_id)
+       $cgq .= "
+JOIN client_group_member USING (ClientId)
+JOIN client_group USING (client_group_id)
 ";
     }
+    my $filter = $self->get_client_filter();
 
     my $query="
 SELECT  Job.JobId       AS jobid,
@@ -2082,10 +2083,9 @@ SELECT  Job.JobId       AS jobid,
 
         JobErrors      AS joberrors
 
- FROM Client, 
+ FROM Client $filter $cgq
       Job LEFT JOIN Pool     ON (Job.PoolId    = Pool.PoolId)
           LEFT JOIN FileSet  ON (Job.FileSetId = FileSet.FileSetId)
-          $cgq
  WHERE Client.ClientId=Job.ClientId
    AND Job.JobStatus NOT IN ('R', 'C')
  $where
@@ -2109,9 +2109,13 @@ SELECT  Job.JobId       AS jobid,
 sub display_job_zoom
 {
     my ($self, $jobid) = @_ ;
+    $self->can_do('r_view_job');
 
     $jobid = $self->dbh_quote($jobid);
-    
+
+    # get security filter
+    my $filter = $self->get_client_filter();
+
     my $query="
 SELECT DISTINCT Job.JobId       AS jobid,
                 Client.Name     AS client,
@@ -2127,7 +2131,7 @@ SELECT DISTINCT Job.JobId       AS jobid,
                 $self->{sql}->{SEC_TO_TIME}(  $self->{sql}->{UNIX_TIMESTAMP}(EndTime)  
                                             - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) AS duration
 
- FROM Client,
+ FROM Client $filter,
       Job LEFT JOIN FileSet ON (Job.FileSetId = FileSet.FileSetId)
           LEFT JOIN Pool    ON (Job.PoolId    = Pool.PoolId)
  WHERE Client.ClientId=Job.ClientId
@@ -2155,13 +2159,14 @@ WHERE Job.JobId = $jobid
 sub display_job_group
 {
     my ($self, %arg) = @_;
+    $self->can_do('r_view_job');
 
     my ($limit, $label) = $self->get_limit(groupby => 'client_group_name',  %arg);
 
     my ($where, undef) = $self->get_param('client_groups',
                                          'level',
                                          'pools');
-    
+    my $filter = $self->get_client_group_filter();
     my $query = 
 "
 SELECT client_group_name AS client_group_name,
@@ -2172,7 +2177,7 @@ SELECT client_group_name AS client_group_name,
        COALESCE(joberr.nbjobs,0) AS nbjoberr,
        COALESCE(jobok.duration, '0:0:0') AS duration
 
-FROM client_group LEFT JOIN (
+FROM client_group $filter LEFT JOIN (
     SELECT client_group_name AS client_group_name, COUNT(1) AS nbjobs, 
            SUM(JobFiles) AS jobfiles, SUM(JobBytes) AS jobbytes, 
            SUM(JobErrors) AS joberrors,
@@ -2374,6 +2379,7 @@ SELECT LocationLog.Date    AS date,
 sub location_edit
 {
     my ($self) = @_ ;
+    $self->can_do('r_location_mgnt');
 
     my $loc = $self->get_form('qlocation');
     unless ($loc->{qlocation}) {
@@ -2389,17 +2395,17 @@ WHERE Location.Location = $loc->{qlocation}
 ";
 
     my $row = $self->dbh_selectrow_hashref($query);
-
+    $row->{enabled} = human_enabled($row->{enabled});
     $self->display({ ID => $cur_id++,
                     %$row }, "location_edit.tpl") ;
-
 }
 
 sub location_save
 {
     my ($self) = @_ ;
+    $self->can_do('r_location_mgnt');
 
-    my $arg = $self->get_form(qw/qlocation qnewlocation cost/) ;
+    my $arg = $self->get_form(qw/qlocation qnewlocation cost enabled/) ;
     unless ($arg->{qlocation}) {
        return $self->error("Can't get location");
     }    
@@ -2410,8 +2416,7 @@ sub location_save
        return $self->error("Can't get new cost");
     }
 
-    my $enabled = CGI::param('enabled') || '';
-    $enabled = $enabled?1:0;
+    my $enabled = from_human_enabled($arg->{enabled});
 
     my $query = "
 UPDATE Location SET Cost     = $arg->{cost}, 
@@ -2428,6 +2433,8 @@ WHERE Location.Location = $arg->{qlocation}
 sub location_del
 {
     my ($self) = @_ ;
+    $self->can_do('r_location_mgnt');
+
     my $arg = $self->get_form(qw/qlocation/) ;
 
     unless ($arg->{qlocation}) {
@@ -2447,7 +2454,7 @@ WHERE Location = $arg->{qlocation}
     }
 
     $query = "
-DELETE FROM Location WHERE Location = $arg->{qlocation} LIMIT 1
+DELETE FROM Location WHERE Location = $arg->{qlocation}
 ";
 
     $self->dbh_do($query);
@@ -2455,10 +2462,11 @@ DELETE FROM Location WHERE Location = $arg->{qlocation} LIMIT 1
     $self->location_display();
 }
 
-
 sub location_add
 {
     my ($self) = @_ ;
+    $self->can_do('r_location_mgnt');
+
     my $arg = $self->get_form(qw/qlocation cost/) ;
 
     unless ($arg->{qlocation}) {
@@ -2527,12 +2535,16 @@ sub update_location
 sub groups_edit
 {
     my ($self) = @_;
+    $self->can_do('r_group_mgnt');
 
     my $grp = $self->get_form(qw/qclient_group db_clients/);
-    $self->debug($grp);
 
     unless ($grp->{qclient_group}) {
-       return $self->error("Can't get group");
+       $self->display({ ID => $cur_id++,
+                        client_group => "''",
+                        %$grp,
+                    }, "groups_edit.tpl");
+       return;
     }
 
     my $query = "
@@ -2554,12 +2566,23 @@ WHERE client_group_name = $grp->{qclient_group}
 sub groups_save
 {
     my ($self) = @_;
+    $self->can_do('r_group_mgnt');
 
     my $arg = $self->get_form(qw/qclient_group jclients qnewgroup/);
+
+    if (!$arg->{qclient_group} and $arg->{qnewgroup}) {
+       my $query = "
+INSERT INTO client_group (client_group_name) 
+VALUES ($arg->{qnewgroup})
+";
+       $self->dbh_do($query);
+       $arg->{qclient_group} = $arg->{qnewgroup};
+    }
+
     unless ($arg->{qclient_group}) {
        return $self->error("Can't get groups");
     }
-    
+
     $self->{dbh}->begin_work();
 
     my $query = "
@@ -2600,6 +2623,8 @@ UPDATE client_group
 sub groups_del
 {
     my ($self) = @_;
+    $self->can_do('r_group_mgnt');
+
     my $arg = $self->get_form(qw/qclient_group/);
 
     unless ($arg->{qclient_group}) {
@@ -2615,6 +2640,12 @@ DELETE FROM client_group_member
               FROM client_group 
              WHERE client_group_name = $arg->{qclient_group});
 
+DELETE FROM bweb_client_group_acl
+      WHERE client_group_id IN
+           (SELECT client_group_id 
+              FROM client_group 
+             WHERE client_group_name = $arg->{qclient_group});
+
 DELETE FROM client_group
       WHERE client_group_name = $arg->{qclient_group};
 ";
@@ -2625,27 +2656,6 @@ DELETE FROM client_group
     $self->display_groups();
 }
 
-
-sub groups_add
-{
-    my ($self) = @_;
-    my $arg = $self->get_form(qw/qclient_group/) ;
-
-    unless ($arg->{qclient_group}) {
-       $self->display({}, "groups_add.tpl");
-       return 1;
-    }
-
-    my $query = "
-INSERT INTO client_group (client_group_name) 
-VALUES ($arg->{qclient_group})
-";
-
-    $self->dbh_do($query);
-
-    $self->display_groups();
-}
-
 sub display_groups
 {
     my ($self) = @_;
@@ -2665,13 +2675,9 @@ sub display_groups
 
 ###########################################################
 
-
-# TODO: avoir un mode qui coupe le programme avec une page d'erreur
-# we can also get all security and fill {security} hash
-sub can_do
+sub get_roles
 {
-    my ($self, $action) = @_;
-    # is security enabled in configuration ?
+    my ($self) = @_;
     if (not $self->{info}->{enable_security}) {
         return 1;
     }
@@ -2679,45 +2685,140 @@ sub can_do
     if ($self->{loginname} eq 'admin') {
         return 1;
     }
-    # must be logged
     if (!$self->{loginname}) {
-        $self->error("Can't do $action, your are not logged. " .
-                     "Check security with your administrator");
-        $self->display_end();
-        exit (0);
+       return 0;
     }
-    # already checked
-    if ($self->{security}->{$action}) {
-        return 1;
+    # already fill
+    if (defined $self->{security}) {
+       return 1;
     }
-    my ($u, $r) = ($self->dbh_quote($self->{loginname}),
-                   $self->dbh_quote($action));
+    $self->{security} = {};
+    my $u = $self->dbh_quote($self->{loginname});
+           
     my $query = "
- SELECT 1, username, rolename
+ SELECT use_acl, rolename
   FROM bweb_user 
        JOIN bweb_role_member USING (userid)
        JOIN bweb_role USING (roleid)
  WHERE username = $u
-   AND rolename = $r
 ";
-
-    my $row = $self->dbh_selectrow_hashref($query);
+    my $rows = $self->dbh_selectall_arrayref($query);
     # do cache with this role   
-    if (!$row) {
-        $self->error("$u sorry, but this action ($action) is not permited. " .
-                     "Check security with your administrator");
+    if (!$rows) {
+        return 0;
+    }
+    foreach my $r (@$rows) {
+       $self->{security}->{$r->[1]}=1;
+    }
+
+    $self->{security}->{use_acl} = $rows->[0]->[0];
+    return 1;
+}
+
+sub cant_do
+{
+    my ($self, $action) = @_;
+    # is security enabled in configuration ?
+    if (not $self->{info}->{enable_security}) {
+        return 0
+    }
+    # admin is a special user that can do everything
+    if ($self->{loginname} eq 'admin') {
+        return 0;
+    }
+    # must be logged
+    if (!$self->{loginname}) {
+       $self->{error} = "Can't do $action, your are not logged. " .
+           "Check security with your administrator";
+        return 1;
+    }
+    $self->get_roles();
+    if (!$self->{security}->{$action}) {
+        $self->{error} =
+           "$self->{loginname} sorry, but this action ($action) " .
+           "is not permited. " .
+           "Check security with your administrator";
+        return 1;
+    }
+    return 0;
+}
+
+# make like an assert (program die)
+sub can_do
+{
+    my ($self, $action) = @_;
+    if ($self->cant_do($action)) {
+        $self->error($self->{error});
         $self->display_end();
-        exit (0);
-    } 
-    $self->{security}->{$row->{rolename}} = 1;    
+        exit 0;
+    }
     return 1;
 }
+
+sub use_filter
+{
+    my ($self) = @_;
+
+    if (!$self->{info}->{enable_security} or 
+       !$self->{info}->{enable_security_acl})
+    {
+       return 0 ;
+    }
+    
+    if ($self->get_roles()) {
+       return $self->{security}->{use_acl};
+    } else {
+       return 0;
+    }
+}
+
+# JOIN Client USING (ClientId) " . $b->get_client_filter() . "
+sub get_client_filter
+{
+    my ($self, $login) = @_;
+    my $u;
+    if ($login) {
+       $u = $self->dbh_quote($login);
+    } elsif ($self->use_filter()) {
+       $u = $self->dbh_quote($self->{loginname});
+    } else {
+       return '';
+    }
+    return "
+ JOIN (SELECT ClientId FROM client_group_member
+   JOIN client_group USING (client_group_id) 
+   JOIN bweb_client_group_acl USING (client_group_id) 
+   JOIN bweb_user USING (userid)
+   WHERE bweb_user.username = $u 
+ ) AS filter USING (ClientId)";
+}
+
+#JOIN client_group USING (client_group_id)" . $b->get_client_group_filter()
+sub get_client_group_filter
+{
+    my ($self, $login) = @_;
+    my $u;
+    if ($login) {
+       $u = $self->dbh_quote($login);
+    } elsif ($self->use_filter()) {
+       $u = $self->dbh_quote($self->{loginname});
+    } else {
+       return '';
+    }
+    return "
+ JOIN (SELECT client_group_id 
+         FROM bweb_client_group_acl
+         JOIN bweb_user USING (userid)
+   WHERE bweb_user.username = $u 
+ ) AS filter USING (client_group_id)";
+}
+
 # role and username have to be quoted before
 # role and username can be a quoted list
 sub revoke
 {
     my ($self, $role, $username) = @_;
-    $self->can_do("user_mgnt");
+    $self->can_do("r_user_mgnt");
     
     my $nb = $self->dbh_do("
  DELETE FROM bweb_role_member 
@@ -2733,7 +2834,7 @@ sub revoke
 sub grant
 {
     my ($self, $role, $username) = @_;
-    $self->can_do("user_mgnt");
+    $self->can_do("r_user_mgnt");
 
     my $nb = $self->dbh_do("
    INSERT INTO bweb_role_member (roleid, userid)
@@ -2749,7 +2850,7 @@ sub grant
 sub grant_like
 {
     my ($self, $copy, $user) = @_;
-    $self->can_do("user_mgnt");
+    $self->can_do("r_user_mgnt");
 
     my $nb = $self->dbh_do("
   INSERT INTO bweb_role_member (roleid, userid) 
@@ -2765,21 +2866,27 @@ sub grant_like
 sub revoke_all
 {
     my ($self, $username) = @_;
-    $self->can_do("user_mgnt");
+    $self->can_do("r_user_mgnt");
 
     $self->dbh_do("
    DELETE FROM bweb_role_member
          WHERE userid IN (
            SELECT userid 
              FROM bweb_user 
-            WHERE username in ($username)
-)");
+            WHERE username in ($username))");
+    $self->dbh_do("
+DELETE FROM bweb_client_group_acl 
+ WHERE userid IN (
+  SELECT userid 
+    FROM bweb_user 
+   WHERE username IN ($username))");
+    
 }
 
 sub users_del
 {
     my ($self) = @_;
-    $self->can_do("user_mgnt");
+    $self->can_do("r_user_mgnt");
 
     my $arg = $self->get_form(qw/jusernames/);
 
@@ -2790,7 +2897,8 @@ sub users_del
     $self->{dbh}->begin_work();
     {
         $self->revoke_all($arg->{jusernames});
-        $self->dbh_do("DELETE FROM bweb_user WHERE username IN ($arg->{jusernames})");
+        $self->dbh_do("
+DELETE FROM bweb_user WHERE username IN ($arg->{jusernames})");
     }
     $self->{dbh}->commit();
     
@@ -2800,18 +2908,20 @@ sub users_del
 sub users_add
 {
     my ($self) = @_;
-    $self->can_do("user_mgnt");
+    $self->can_do("r_user_mgnt");
 
     # we don't quote username directly to check that it is conform
-    my $arg = $self->get_form(qw/username qpasswd qcomment jrolenames qcreate qcopy_username/) ;
+    my $arg = $self->get_form(qw/username qpasswd qcomment jrolenames qcreate qcopy_username jclient_groups/) ;
 
     if (not $arg->{qcreate}) {
-        $arg = $self->get_form(qw/db_roles db_usernames/);
+        $arg = $self->get_form(qw/db_roles db_usernames db_client_groups/);
         $self->display($arg, "display_user.tpl");
         return 1;
     }
 
     my $u = $self->dbh_quote($arg->{username});
+    
+    $arg->{use_acl}=(CGI::param('use_acl')?'true':'false');
 
     if (!$arg->{qpasswd}) {
         $arg->{qpasswd} = "''";
@@ -2821,13 +2931,17 @@ sub users_add
     }
 
     # will fail if user already exists
+    # UPDATE with mysql dbi does not return if update is ok
+    ($self->dbh_do("
+  UPDATE bweb_user 
+     SET passwd=$arg->{qpasswd}, comment=$arg->{qcomment}, 
+         use_acl=$arg->{use_acl}
+   WHERE username = $u") 
+#     and (! $self->dbh_is_mysql() )
+     ) and
     $self->dbh_do("
-  UPDATE bweb_user SET passwd=$arg->{qpasswd}, comment=$arg->{qcomment}
-   WHERE username = $u")
-        or
-    $self->dbh_do("
-  INSERT INTO bweb_user (username, passwd, comment) 
-        VALUES ($u, $arg->{qpasswd}, $arg->{qcomment})");
+  INSERT INTO bweb_user (username, passwd, use_acl, comment) 
+        VALUES ($u, $arg->{qpasswd}, $arg->{use_acl}, $arg->{qcomment})");
 
     $self->{dbh}->begin_work();
     {
@@ -2838,6 +2952,16 @@ sub users_add
         } else {
             $self->grant($arg->{jrolenames}, $u);
         }
+
+       if ($arg->{jclient_groups}) {
+           $self->dbh_do("
+INSERT INTO bweb_client_group_acl (client_group_id, userid)
+ SELECT client_group_id, userid 
+   FROM client_group, bweb_user
+  WHERE client_group_name IN ($arg->{jclient_groups})
+    AND username = $u
+");
+       }
     }
     $self->{dbh}->commit();
 
@@ -2848,7 +2972,7 @@ sub users_add
 sub display_users
 {
     my ($self) = @_;
-    $self->can_do("user_mgnt");
+    $self->can_do("r_user_mgnt");
 
     my $arg = $self->get_form(qw/db_usernames/) ;
 
@@ -2864,20 +2988,24 @@ sub display_users
 sub display_user
 {
     my ($self) = @_;
-    $self->can_do("user_mgnt");
+    $self->can_do("r_user_mgnt");
 
-    my $arg = $self->get_form(qw/username db_usernames/);
+    my $arg = $self->get_form('username');
     my $user = $self->dbh_quote($arg->{username});
 
     my $userp = $self->dbh_selectrow_hashref("
-   SELECT username, passwd, comment
+   SELECT username, passwd, comment, use_acl
      FROM bweb_user
     WHERE username = $user
 ");
-
     if (!$userp) {
         return $self->error("Can't find $user in catalog");
     }
+    my $filter = $self->get_client_group_filter($arg->{username});
+    my $scg = $self->dbh_selectall_hashref("
+ SELECT client_group_name AS name 
+   FROM client_group $filter
+", 'name');
 
 #  rolename  | userid
 #------------+--------
@@ -2894,11 +3022,16 @@ SELECT rolename, temp.userid
 ORDER BY rolename
 ", 'rolename');
 
+    $arg = $self->get_form(qw/db_usernames db_client_groups/);    
+
     $self->display({
         db_usernames => $arg->{db_usernames},
         username => $userp->{username},
         comment => $userp->{comment},
         passwd => $userp->{passwd},
+       use_acl => $userp->{use_acl},
+       db_client_groups => $arg->{db_client_groups},
+       client_group => [ values %$scg ],
         db_roles => [ values %$role], 
     }, "display_user.tpl");
 }
@@ -2974,6 +3107,7 @@ WHERE Media.VolumeName = $media->{qmedia}
 sub save_location
 {
     my ($self) = @_ ;
+    $self->can_do('r_media_mgnt');
 
     my $arg = $self->get_form('jmedias', 'qnewlocation') ;
 
@@ -3003,6 +3137,7 @@ sub save_location
 sub location_change
 {
     my ($self) = @_ ;
+    $self->can_do('r_media_mgnt');
 
     my $media = $self->get_selected_media_location();
     unless ($media) {
@@ -3051,11 +3186,13 @@ INSERT LocationLog (Date, Comment, MediaId, LocationId, NewVolStatus)
 sub display_client_stats
 {
     my ($self, %arg) = @_ ;
+    $self->can_do('r_view_stat');
 
     my $client = $self->dbh_quote($arg{clientname});
+    # get security filter
+    my $filter = $self->get_client_filter();
 
     my ($limit, $label) = $self->get_limit(%arg);
-
     my $query = "
 SELECT 
     count(Job.JobId)     AS nb_jobs,
@@ -3063,7 +3200,7 @@ SELECT
     sum(Job.JobErrors)   AS nb_err,
     sum(Job.JobFiles)    AS nb_files,
     Client.Name          AS clientname
-FROM Job JOIN Client USING (ClientId)
+FROM Job JOIN Client USING (ClientId) $filter
 WHERE 
     Client.Name = $client
     $limit 
@@ -3213,14 +3350,17 @@ GROUP BY VolStatus
 sub display_running_job
 {
     my ($self) = @_;
+    return if $self->cant_do('r_view_running_job');
 
     my $arg = $self->get_form('client', 'jobid');
 
     if (!$arg->{client} and $arg->{jobid}) {
+       # get security filter
+       my $filter = $self->get_client_filter();
 
        my $query = "
 SELECT Client.Name AS name
-FROM Job INNER JOIN Client USING (ClientId)
+FROM Job INNER JOIN Client USING (ClientId) $filter
 WHERE Job.JobId = $arg->{jobid}
 ";
 
@@ -3246,7 +3386,11 @@ WHERE Job.JobId = $arg->{jobid}
 sub display_running_jobs
 {
     my ($self, $display_action) = @_;
-    
+    return if $self->cant_do('r_view_running_job');
+
+    # get security filter
+    my $filter = $self->get_client_filter();
+
     my $query = "
 SELECT Job.JobId AS jobid, 
        Job.Name  AS jobname,
@@ -3259,8 +3403,9 @@ $self->{sql}->{SEC_TO_TIME}(  $self->{sql}->{UNIX_TIMESTAMP}(NOW())
                             - $self->{sql}->{UNIX_TIMESTAMP}(StartTime)) 
          AS duration,
        Client.Name AS clientname
-FROM Job INNER JOIN Client USING (ClientId) 
-WHERE JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
+FROM Job INNER JOIN Client USING (ClientId) $filter
+WHERE 
+  JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
 ";     
     my $all = $self->dbh_selectall_hashref($query, 'jobid') ;
     
@@ -3274,6 +3419,8 @@ WHERE JobStatus IN ('C','R','B','e','D','F','S','m','M','s','j','c','d','t','p')
 sub eject_media
 {
     my ($self) = @_;
+    $self->can_do('r_media_mgnt');
+
     my %ret; 
     my $arg = $self->get_form('jmedias');
 
@@ -3377,6 +3524,7 @@ sub ach_get
 sub ach_register
 {
     my ($self, $ach) = @_;
+    $self->can_do('r_configure');
 
     $self->{info}->{ach_list}->{$ach->{name}} = $ach;
 
@@ -3388,6 +3536,8 @@ sub ach_register
 sub ach_edit
 {
     my ($self) = @_;
+    $self->can_do('r_configure');
+
     my $arg = $self->get_form('ach');
     if (!$arg->{ach} 
        or !$self->{info}->{ach_list} 
@@ -3417,6 +3567,8 @@ sub ach_edit
 sub ach_del
 {
     my ($self) = @_;
+    $self->can_do('r_configure');
+
     my $arg = $self->get_form('ach');
 
     if (!$arg->{ach} 
@@ -3435,6 +3587,8 @@ sub ach_del
 sub ach_add
 {
     my ($self) = @_;
+    $self->can_do('r_configure');
+
     my $arg = $self->get_form('ach', 'mtxcmd', 'device', 'precmd');
 
     my $b = $self->get_bconsole();
@@ -3478,6 +3632,8 @@ sub ach_add
 sub delete
 {
     my ($self) = @_;
+    $self->can_do('r_delete_job');
+
     my $arg = $self->get_form('jobid');
 
     if ($arg->{jobid}) {
@@ -3495,6 +3651,7 @@ sub delete
 sub do_update_media
 {
     my ($self) = @_ ;
+    $self->can_do('r_media_mgnt');
 
     my $arg = $self->get_form(qw/media volstatus inchanger pool
                                 slot volretention voluseduration 
@@ -3589,6 +3746,7 @@ UPDATE Media
 sub update_slots
 {
     my ($self) = @_;
+    $self->can_do('r_autochanger_mgnt');
 
     my $ach = CGI::param('ach') ;
     $ach = $self->ach_get($ach);
@@ -3605,6 +3763,7 @@ sub update_slots
 sub get_job_log
 {
     my ($self) = @_;
+    $self->can_do('r_view_log');
 
     my $arg = $self->get_form('jobid', 'limit', 'offset');
     unless ($arg->{jobid}) {
@@ -3614,18 +3773,12 @@ sub get_job_log
     if ($arg->{limit} == 100) {
         $arg->{limit} = 1000;
     }
-
-    my $t = CGI::param('time') || $self->{info}->{display_log_time} || '';
-
-    # display only Error and Warning messages
-    my $filter = '';
-    if (CGI::param('error')) {
-       $filter = " AND LogText $self->{sql}->{MATCH} 'Error|Warning' ";
-    }
+    # get security filter
+    my $filter = $self->get_client_filter();
 
     my $query = "
 SELECT Job.Name as name, Client.Name as clientname
- FROM  Job INNER JOIN Client ON (Job.ClientId = Client.ClientId)
+ FROM  Job INNER JOIN Client USING (ClientId) $filter
  WHERE JobId = $arg->{jobid}
 ";
 
@@ -3635,33 +3788,45 @@ SELECT Job.Name as name, Client.Name as clientname
        return $self->error("Can't find $arg->{jobid} in catalog");
     }
 
+    # display only Error and Warning messages
+    $filter = '';
+    if (CGI::param('error')) {
+       $filter = " AND LogText $self->{sql}->{MATCH} 'Error|Warning' ";
+    }
+
+    my $logtext;
+    if (CGI::param('time') || $self->{info}->{display_log_time}) {
+       $logtext = $self->dbh_strcat('Time', " ' ' ", 'LogText');
+    } else {
+       $logtext = 'LogText';
+    }
+
     $query = "
-SELECT Time AS time, LogText AS log 
-  FROM  Log 
- WHERE ( Log.JobId = $arg->{jobid} 
-    OR (Log.JobId = 0 AND Time >= (SELECT StartTime FROM Job WHERE JobId=$arg->{jobid}) 
-                      AND Time <= (SELECT COALESCE(EndTime,NOW()) FROM Job WHERE JobId=$arg->{jobid})
-       )
-       ) $filter
+SELECT count(1) AS nbline, JobId AS jobid, 
+       GROUP_CONCAT($logtext $self->{sql}->{CONCAT_SEP}) AS logtxt
+  FROM  (
+    SELECT JobId, Time, LogText
+    FROM Log 
+   WHERE ( Log.JobId = $arg->{jobid} 
+      OR (Log.JobId = 0 
+          AND Time >= (SELECT StartTime FROM Job WHERE JobId=$arg->{jobid}) 
+          AND Time <= (SELECT COALESCE(EndTime,NOW()) FROM Job WHERE JobId=$arg->{jobid})
+       ) ) $filter
  ORDER BY LogId
  LIMIT $arg->{limit}
  OFFSET $arg->{offset}
+ ) AS temp
+ GROUP BY JobId
+
 ";
 
-    my $log = $self->dbh_selectall_arrayref($query);
+    my $log = $self->dbh_selectrow_hashref($query);
     unless ($log) {
        return $self->error("Can't get log for jobid $arg->{jobid}");
     }
 
-    my $logtxt;
-    if ($t) {
-       # log contains \n
-       $logtxt = join("", map { ($_->[0] . ' ' . $_->[1]) } @$log ) ; 
-    } else {
-       $logtxt = join("", map { $_->[1] } @$log ) ; 
-    }
-    
-    $self->display({ lines=> $logtxt,
+    $self->display({ lines=> $log->{logtxt},
+                    nbline => $log->{nbline},
                     jobid => $arg->{jobid},
                     name  => $row->{name},
                     client => $row->{clientname},
@@ -3670,10 +3835,10 @@ SELECT Time AS time, LogText AS log
                 }, 'display_log.tpl');
 }
 
-
 sub label_barcodes
 {
     my ($self) = @_ ;
+    $self->can_do('r_autochanger_mgnt');
 
     my $arg = $self->get_form('ach', 'slots', 'drive');
 
@@ -3725,6 +3890,7 @@ sub label_barcodes
 sub purge
 {
     my ($self) = @_;
+    $self->can_do('r_purge');
 
     my @volume = CGI::param('media');
 
@@ -3747,6 +3913,7 @@ sub purge
 sub prune
 {
     my ($self) = @_;
+    $self->can_do('r_prune');
 
     my @volume = CGI::param('media');
     unless (@volume) {
@@ -3768,6 +3935,7 @@ sub prune
 sub cancel_job
 {
     my ($self) = @_;
+    $self->can_do('r_cancel_job');
 
     my $arg = $self->get_form('jobid');
     unless ($arg->{jobid}) {
@@ -3818,6 +3986,7 @@ sub director_show_sched
 sub enable_disable_job
 {
     my ($self, $what) = @_ ;
+    $self->can_do('r_run_job');
 
     my $name = CGI::param('job') || '';
     unless ($name =~ /^[\w\d\.\-\s]+$/) {
@@ -3849,6 +4018,8 @@ sub get_bconsole
 sub run_job_select
 {
     my ($self) = @_;
+    $self->can_do('r_run_job');
+
     my $b = $self->get_bconsole();
 
     my $joblist = [ map { { name => $_ } } $b->list_job() ];
@@ -3884,6 +4055,8 @@ sub run_parse_job
 sub run_job_mod
 {
     my ($self) = @_;
+    $self->can_do('r_run_job');
+
     my $b = $self->get_bconsole();
     
     my $job = CGI::param('job') || '';
@@ -3915,6 +4088,8 @@ sub run_job_mod
 sub run_job
 {
     my ($self) = @_;
+    $self->can_do('r_run_job');
+
     my $b = $self->get_bconsole();
     
     my $jobs   = [ map {{ name => $_ }} $b->list_job() ];
@@ -3927,6 +4102,8 @@ sub run_job
 sub run_job_now
 {
     my ($self) = @_;
+    $self->can_do('r_run_job');
+
     my $b = $self->get_bconsole();
     
     # TODO: check input (don't use pool, level)