package PMG::UserConfig;
-
use strict;
use warnings;
+use Data::Dumper;
+use Clone 'clone';
use PVE::Tools;
use PVE::INotify;
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.",
- text => 'string', format => 'email',
+ type => 'string', format => 'email',
optional => 1,
},
expire => {
- description => "Account expiration date, expressed as unix epoch.",
+ description => "Account expiration date (seconds since epoch). '0' means no expiration date.",
type => 'integer',
minimum => 0,
+ default => 0,
+ optional => 1,
},
enable => {
description => "Flag to enable or disable the account.",
type => 'boolean',
+ default => 0,
+ optional => 1,
},
crypt_pass => {
description => "Encrypted password (see `man crypt`)",
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,
optional => 1,
},
keys => {
- description => "OTP Keys",
+ description => "Keys for two factor auth (yubico).",
type => 'string',
maxLength => 128,
optional => 1,
},
+ comment => {
+ description => "Comment.",
+ type => 'string',
+ optional => 1,
+ },
},
};
-my $verity_entry = sub {
+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 $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) = @_;
if ($line =~ m/^
(?<userid>(?:[^\s:]+)) :
- (?<enable>[01]) :
- (?<expire>\d+) :
- (?<pass>(?:[^\s:]*)) :
+ (?<enable>[01]?) :
+ (?<expire>\d*) :
+ (?<crypt_pass>(?:[^\s:]*)) :
(?<role>[a-z]+) :
(?<email>(?:[^\s:]*)) :
- (?<first>(?:[^:]*)) :
- (?<last>(?:[^:]*)) :
+ (?<firstname>(?:[^:]*)) :
+ (?<lastname>(?:[^:]*)) :
(?<keys>(?:[^:]*)) :
$/x
) {
my $d = {
- userid => $+{userid},
- enable => $+{enable},
- expire => $+{expire},
- crypt_pass => $+{pass},
+ 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(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";
}
}
- $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;
}
my $raw = '';
+ $fixup_root_properties->($cfg);
+
+ foreach my $userid (keys %$cfg) {
+ my $d = $cfg->{$userid};
+
+ $d->{userid} = $userid;
+
+ 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;
+
+ 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);
}
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();
});