]> git.proxmox.com Git - pmg-api.git/blobdiff - PMG/UserConfig.pm
implement new role 'helpdesk'
[pmg-api.git] / PMG / UserConfig.pm
index e23dc8235db1134702e1fe903d2508917f69fca3..f3bc2e315b304ee4942315a8f0ef3179be0742ea 100644 (file)
@@ -1,6 +1,5 @@
 package PMG::UserConfig;
 
-
 use strict;
 use warnings;
 use Data::Dumper;
@@ -43,10 +42,18 @@ sub lock_config {
     }
 }
 
-our $schema = {
+my $schema = {
     additionalProperties => 0,
     properties => {
-       userid => get_standard_option('username'),
+       userid => get_standard_option('userid'),
+       username => get_standard_option('username', { optional => 1 }),
+       realm => {
+           description => "Authentication realm.",
+           type => 'string',
+           enum => ['pam', 'pmg'],
+           default => 'pmg',
+           optional => 1,
+       },
        email => {
            description => "Users E-Mail address.",
            type => 'string', format => 'email',
@@ -72,17 +79,17 @@ our $schema = {
            optional => 1,
        },
        role => {
-           description => "User role.",
+           description => "User role. Role 'root' is reseved for the Unix Superuser.",
            type => 'string',
-           enum => ['root', 'admin', 'qmanager', 'quser', 'audit'],
+           enum => ['root', 'admin', 'helpdesk', 'qmanager', 'audit'],
        },
-       first => {
+       firstname => {
            description => "First name.",
            type => 'string',
            maxLength => 64,
            optional => 1,
        },
-       'last' => {
+       lastname => {
            description => "Last name.",
            type => 'string',
            maxLength => 64,
@@ -102,19 +109,58 @@ our $schema = {
     },
 };
 
-our $update_schema = clone($schema);
+our $create_schema = clone($schema);
+delete $create_schema->{properties}->{username};
+delete $create_schema->{properties}->{realm};
+$create_schema->{properties}->{password} = {
+    description => "Password",
+    type => 'string',
+    maxLength => 32,
+    minLength => 5,
+    optional => 1,
+};
+
+our $update_schema = clone($create_schema);
 $update_schema->{properties}->{role}->{optional} = 1;
+$update_schema->{properties}->{delete} = {
+    type => 'string', format => 'pve-configid-list',
+    description => "A list of settings you want to delete.",
+    maxLength => 4096,
+    optional => 1,
+};
 
-my $verity_entry = sub {
+my $verify_entry = sub {
     my ($entry) = @_;
 
     my $errors = {};
+    my $userid = $entry->{userid};
+    if (defined(my $username = $entry->{username})) {
+       if ($userid !~ /^\Q$username\E\@/) {
+           $errors->{'username'} = 'invalid username for userid';
+       }
+    } else {
+       # make sure the username's length is checked
+       $entry->{username} = ($userid =~ s/\@.*$//r);
+    }
     PVE::JSONSchema::check_prop($entry, $schema, '', $errors);
     if (scalar(%$errors)) {
        raise "verify entry failed\n", errors => $errors;
     }
 };
 
+my $fixup_root_properties = sub {
+    my ($cfg) = @_;
+
+    $cfg->{'root@pam'}->{userid} = 'root@pam';
+    $cfg->{'root@pam'}->{username} = 'root';
+    $cfg->{'root@pam'}->{realm} = 'pam';
+    $cfg->{'root@pam'}->{enable} = 1;
+    $cfg->{'root@pam'}->{expire} = 0;
+    $cfg->{'root@pam'}->{comment} = 'Unix Superuser';
+    $cfg->{'root@pam'}->{role} = 'root';
+    delete $cfg->{'root@pam'}->{crypt_pass};
+};
+
 sub read_user_conf {
     my ($filename, $fh) = @_;
 
@@ -138,25 +184,29 @@ sub read_user_conf {
                (?<crypt_pass>(?:[^\s:]*)) :
                (?<role>[a-z]+) :
                (?<email>(?:[^\s:]*)) :
-               (?<first>(?:[^:]*)) :
-               (?<last>(?:[^:]*)) :
+               (?<firstname>(?:[^:]*)) :
+               (?<lastname>(?:[^:]*)) :
                (?<keys>(?:[^:]*)) :
                $/x
            ) {
                my $d = {
-                   userid => $+{userid},
+                   username => $+{userid},
+                   userid => $+{userid} . '@pmg',
+                   realm => 'pmg',
                    enable => $+{enable} || 0,
                    expire => $+{expire} || 0,
                    role => $+{role},
                };
                $d->{comment} = $comment if $comment;
                $comment = '';
-               foreach my $k (qw(crypt_pass email first last keys)) {
+               foreach my $k (qw(crypt_pass email firstname lastname keys)) {
                    $d->{$k} = $+{$k} if $+{$k};
                }
                eval {
-                   $verity_entry->($d);
+                   $verify_entry->($d);
                    $cfg->{$d->{userid}} = $d;
+                   die "role 'root' is reserved\n"
+                       if $d->{role} eq 'root' && $d->{userid} ne 'root@pmg';
                };
                if (my $err = $@) {
                    warn "$filename: $err";
@@ -168,12 +218,12 @@ sub read_user_conf {
        }
     }
 
-    $cfg->{root} //= {};
-    $cfg->{root}->{userid} = 'root';
-    $cfg->{root}->{enable} = 1;
-    $cfg->{root}->{comment} = 'Unix Superuser';
-    $cfg->{root}->{role} = 'root';
-    delete $cfg->{root}->{crypt_pass};
+    # hack: we list root@pam here (root@pmg is an alias for root@pam)
+    $cfg->{'root@pam'} = $cfg->{'root@pmg'} // {};
+    delete $cfg->{'root@pmg'};
+    $cfg->{'root@pam'} //= {};
+
+    $fixup_root_properties->($cfg);
 
     return $cfg;
 }
@@ -183,25 +233,49 @@ sub write_user_conf {
 
     my $raw = '';
 
-    delete $cfg->{root}->{crypt_pass};
+    $fixup_root_properties->($cfg);
 
     foreach my $userid (keys %$cfg) {
        my $d = $cfg->{$userid};
+
        $d->{userid} = $userid;
-       eval {
-           $verity_entry->($d);
-           $cfg->{$d->{userid}} = $d;
-       };
-       if (my $err = $@) {
-           die $err;
+
+       die "invalid userid '$userid'\n" if $userid eq 'root@pmg';
+       $verify_entry->($d);
+       $cfg->{$d->{userid}} = $d;
+
+       if ($d->{userid} ne 'root@pam') {
+           die "role 'root' is reserved\n" if $d->{role} eq 'root';
+           die "unable to add users for realm '$d->{realm}'\n"
+               if $d->{realm} && $d->{realm} ne 'pmg';
        }
-       my $line = "$userid:";
-       for my $k (qw(enable expire crypt_pass role email first last keys)) {
+
+       my $line;
+
+       if ($userid eq 'root@pam') {
+           $line = 'root:';
+           $d->{crypt_pass} = '',
+           $d->{expire} = '0',
+           $d->{role} = 'root';
+       } else {
+           next if $userid !~ m/^(?<username>.+)\@pmg$/;
+           $line = "$+{username}:";
+       }
+
+       for my $k (qw(enable expire crypt_pass role email firstname lastname keys)) {
            $line .= ($d->{$k} // '') . ':';
        }
+       if (my $comment = $d->{comment}) {
+           my $firstline = (split /\n/, $comment)[0]; # only allow one line
+           $raw .= "#$firstline\n";
+       }
        $raw .= $line . "\n";
     }
 
+    my $gid = getgrnam('www-data');
+    chown(0, $gid, $fh);
+    chmod(0640, $fh);
+
     PVE::Tools::safe_print($filename, $fh, $raw);
 }
 
@@ -243,13 +317,13 @@ sub authenticate_user {
     return 1;
 }
 
-sub set_password {
+sub set_user_password {
     my ($class, $username, $password) = @_;
 
     lock_config(sub {
        my $cfg = $class->new();
        my $data = $cfg->lookup_user_data($username); # user exists
-       my $epw = PMG::Utils::encrypt_pw($password);
+       my $epw = PVE::Tools::encrypt_pw($password);
        $data->{crypt_pass} = $epw;
        $cfg->write();
     });