]>
git.proxmox.com Git - pmg-api.git/blob - PMG/UserConfig.pm
1 package PMG
::UserConfig
;
10 use PVE
::JSONSchema
qw(get_standard_option);
11 use PVE
::Exception
qw(raise);
15 my $inotify_file_id = 'pmg-user.conf';
16 my $config_filename = '/etc/pmg/user.conf';
21 my $class = ref($type) || $type;
23 my $cfg = PVE
::INotify
::read_file
($inotify_file_id);
25 return bless $cfg, $class;
31 PVE
::INotify
::write_file
($inotify_file_id, $self);
34 my $lockfile = "/var/lock/pmguser.lck";
37 my ($code, $errmsg) = @_;
39 my $p = PVE
::Tools
::lock_file
($lockfile, undef, $code);
41 $errmsg ?
die "$errmsg: $err" : die $err;
46 additionalProperties
=> 0,
48 userid
=> get_standard_option
('userid'),
49 username
=> get_standard_option
('username', { optional
=> 1 }),
51 description
=> "Authentication realm.",
53 enum
=> ['pam', 'pmg'],
58 description
=> "Users E-Mail address.",
59 type
=> 'string', format
=> 'email',
63 description
=> "Account expiration date (seconds since epoch). '0' means no expiration date.",
70 description
=> "Flag to enable or disable the account.",
76 description
=> "Encrypted password (see `man crypt`)",
78 pattern
=> '\$\d\$[a-zA-Z0-9\.\/]+\$[a-zA-Z0-9\.\/]+',
82 description
=> "User role. Role 'root' is reseved for the Unix Superuser.",
84 enum
=> ['root', 'admin', 'helpdesk', 'qmanager', 'audit'],
87 description
=> "First name.",
93 description
=> "Last name.",
99 description
=> "Keys for two factor auth (yubico).",
105 description
=> "Comment.",
112 our $create_schema = clone
($schema);
113 delete $create_schema->{properties
}->{username
};
114 delete $create_schema->{properties
}->{realm
};
115 $create_schema->{properties
}->{password
} = {
116 description
=> "Password",
123 our $update_schema = clone
($create_schema);
124 $update_schema->{properties
}->{role}->{optional
} = 1;
125 $update_schema->{properties
}->{delete} = {
126 type
=> 'string', format
=> 'pve-configid-list',
127 description
=> "A list of settings you want to delete.",
132 my $verify_entry = sub {
136 my $userid = $entry->{userid
};
137 if (defined(my $username = $entry->{username
})) {
138 if ($userid !~ /^\Q$username\E\@/) {
139 $errors->{'username'} = 'invalid username for userid';
142 # make sure the username's length is checked
143 $entry->{username
} = ($userid =~ s/\@.*$//r);
145 PVE
::JSONSchema
::check_prop
($entry, $schema, '', $errors);
146 if (scalar(%$errors)) {
147 raise
"verify entry failed\n", errors
=> $errors;
151 my $fixup_root_properties = sub {
154 $cfg->{'root@pam'}->{userid
} = 'root@pam';
155 $cfg->{'root@pam'}->{username
} = 'root';
156 $cfg->{'root@pam'}->{realm
} = 'pam';
157 $cfg->{'root@pam'}->{enable
} = 1;
158 $cfg->{'root@pam'}->{expire
} = 0;
159 $cfg->{'root@pam'}->{comment
} = 'Unix Superuser';
160 $cfg->{'root@pam'}->{role} = 'root';
161 delete $cfg->{'root@pam'}->{crypt_pass
};
165 my ($filename, $fh) = @_;
173 while (defined(my $line = <$fh>)) {
174 next if $line =~ m/^\s*$/;
175 if ($line =~ m/^#(.*)$/) {
181 (?
<userid
>(?
:[^\s
:]+)) :
184 (?
<crypt_pass
>(?
:[^\s
:]*)) :
186 (?
<email
>(?
:[^\s
:]*)) :
187 (?
<firstname
>(?
:[^:]*)) :
188 (?
<lastname
>(?
:[^:]*)) :
193 username
=> $+{userid
},
194 userid
=> $+{userid
} . '@pmg',
196 enable
=> $+{enable
} || 0,
197 expire
=> $+{expire
} || 0,
200 $d->{comment
} = $comment if $comment;
202 foreach my $k (qw(crypt_pass email firstname lastname keys)) {
203 $d->{$k} = $+{$k} if $+{$k};
207 $cfg->{$d->{userid
}} = $d;
208 die "role 'root' is reserved\n"
209 if $d->{role} eq 'root' && $d->{userid
} ne 'root@pmg';
212 warn "$filename: $err";
215 warn "$filename: ignore invalid line $.\n";
221 # hack: we list root@pam here (root@pmg is an alias for root@pam)
222 $cfg->{'root@pam'} = $cfg->{'root@pmg'} // {};
223 delete $cfg->{'root@pmg'};
224 $cfg->{'root@pam'} //= {};
226 $fixup_root_properties->($cfg);
231 sub write_user_conf
{
232 my ($filename, $fh, $cfg) = @_;
236 $fixup_root_properties->($cfg);
238 foreach my $userid (keys %$cfg) {
239 my $d = $cfg->{$userid};
241 $d->{userid
} = $userid;
243 die "invalid userid '$userid'\n" if $userid eq 'root@pmg';
245 $cfg->{$d->{userid
}} = $d;
247 if ($d->{userid
} ne 'root@pam') {
248 die "role 'root' is reserved\n" if $d->{role} eq 'root';
249 die "unable to add users for realm '$d->{realm}'\n"
250 if $d->{realm
} && $d->{realm
} ne 'pmg';
255 if ($userid eq 'root@pam') {
257 $d->{crypt_pass
} = '',
261 next if $userid !~ m/^(?<username>.+)\@pmg$/;
262 $line = "$+{username}:";
265 for my $k (qw(enable expire crypt_pass role email firstname lastname keys)) {
266 $line .= ($d->{$k} // '') . ':';
268 if (my $comment = $d->{comment
}) {
269 my $firstline = (split /\n/, $comment)[0]; # only allow one line
270 $raw .= "#$firstline\n";
272 $raw .= $line . "\n";
275 my $gid = getgrnam('www-data');
279 PVE
::Tools
::safe_print
($filename, $fh, $raw);
282 PVE
::INotify
::register_file
($inotify_file_id, $config_filename,
286 always_call_parser
=> 1);
288 sub lookup_user_data
{
289 my ($self, $username, $noerr) = @_;
291 return $self->{$username} if $self->{$username};
293 die "no such user ('$username')\n" if !$noerr;
298 sub authenticate_user
{
299 my ($self, $username, $password) = @_;
301 die "no password\n" if !$password;
303 my $data = $self->lookup_user_data($username);
306 my $expire = $data->{expire
};
308 die "account expired\n" if $expire && ($expire < $ctime);
310 if ($data->{crypt_pass
}) {
311 my $encpw = crypt($password, $data->{crypt_pass
});
312 die "invalid credentials\n" if ($encpw ne $data->{crypt_pass
});
314 die "no password set\n";
320 sub set_user_password
{
321 my ($class, $username, $password) = @_;
324 my $cfg = $class->new();
325 my $data = $cfg->lookup_user_data($username); # user exists
326 my $epw = PVE
::Tools
::encrypt_pw
($password);
327 $data->{crypt_pass
} = $epw;