]> git.proxmox.com Git - pve-access-control.git/blame - src/PVE/Auth/Plugin.pm
tree wide: typo fixes
[pve-access-control.git] / src / PVE / Auth / Plugin.pm
CommitLineData
5bb4e06a
DM
1package PVE::Auth::Plugin;
2
3use strict;
4use warnings;
b49abe2d 5
5bb4e06a 6use Digest::SHA;
b49abe2d
TL
7use Encode;
8
5bb4e06a 9use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_lock_file);
b49abe2d
TL
10use PVE::JSONSchema qw(get_standard_option);
11use PVE::SectionConfig;
12use PVE::Tools;
5bb4e06a 13
5bb4e06a
DM
14use base qw(PVE::SectionConfig);
15
16my $domainconfigfile = "domains.cfg";
17
0a6e09fd 18cfs_register_file($domainconfigfile,
5bb4e06a
DM
19 sub { __PACKAGE__->parse_config(@_); },
20 sub { __PACKAGE__->write_config(@_); });
21
22sub lock_domain_config {
23 my ($code, $errmsg) = @_;
24
25 cfs_lock_file($domainconfigfile, undef, $code);
26 my $err = $@;
27 if ($err) {
28 $errmsg ? die "$errmsg: $err" : die $err;
29 }
30}
31
8e23f971
FG
32our $realm_regex = qr/[A-Za-z][A-Za-z0-9\.\-_]+/;
33our $user_regex = qr![^\s:/]+!;
5bb4e06a
DM
34
35PVE::JSONSchema::register_format('pve-realm', \&pve_verify_realm);
36sub pve_verify_realm {
37 my ($realm, $noerr) = @_;
0a6e09fd 38
5bb4e06a
DM
39 if ($realm !~ m/^${realm_regex}$/) {
40 return undef if $noerr;
0a6e09fd 41 die "value does not look like a valid realm\n";
5bb4e06a
DM
42 }
43 return $realm;
44}
45
46PVE::JSONSchema::register_standard_option('realm', {
47 description => "Authentication domain ID",
48 type => 'string', format => 'pve-realm',
49 maxLength => 32,
50});
51
98df2ebc
DC
52my $remove_options = "(?:acl|properties|entry)";
53
d29d2d4a
TL
54my $realm_sync_options_desc = {
55 scope => {
56 description => "Select what to sync.",
57 type => 'string',
58 enum => [qw(users groups both)],
59 optional => '1',
60 },
98df2ebc
DC
61 'remove-vanished' => {
62 description => "A semicolon-seperated list of things to remove when they or the user"
63 ." vanishes during a sync. The following values are possible: 'entry' removes the"
64 ." user/group when not returned from the sync. 'properties' removes the set"
65 ." properties on existing user/group that do not appear in the source (even custom ones)."
66 ." 'acl' removes acls when the user/group is not returned from the sync.",
67 type => 'string',
68 typetext => "[acl];[properties];[entry]",
69 pattern => "(?:$remove_options\;)*$remove_options",
70 optional => '1',
71 },
72 # TODO check/rewrite in pve7to8, and remove with 8.0
d29d2d4a 73 full => {
98df2ebc
DC
74 description => "DEPRECATED: use 'remove-vanished' instead. If set, uses the LDAP Directory as source of truth,"
75 ." deleting users or groups not returned from the sync and removing"
76 ." all locally modified properties of synced users. If not set,"
77 ." only syncs information which is present in the synced data, and does not"
78 ." delete or modify anything else.",
d29d2d4a
TL
79 type => 'boolean',
80 optional => '1',
81 },
82 'enable-new' => {
83 description => "Enable newly synced users immediately.",
84 type => 'boolean',
85 default => '1',
86 optional => '1',
87 },
88 purge => {
98df2ebc
DC
89 description => "DEPRECATED: use 'remove-vanished' instead. Remove ACLs for users or"
90 ." groups which were removed from the config during a sync.",
d29d2d4a
TL
91 type => 'boolean',
92 optional => '1',
93 },
94};
95PVE::JSONSchema::register_standard_option('realm-sync-options', $realm_sync_options_desc);
96PVE::JSONSchema::register_format('realm-sync-options', $realm_sync_options_desc);
97
5bb4e06a
DM
98PVE::JSONSchema::register_format('pve-userid', \&verify_username);
99sub verify_username {
100 my ($username, $noerr) = @_;
101
102 $username = '' if !$username;
103 my $len = length($username);
104 if ($len < 3) {
105 die "user name '$username' is too short\n" if !$noerr;
106 return undef;
107 }
108 if ($len > 64) {
109 die "user name '$username' is too long ($len > 64)\n" if !$noerr;
110 return undef;
111 }
112
113 # we only allow a limited set of characters
0a6e09fd 114 # colon is not allowed, because we store usernames in
5bb4e06a
DM
115 # colon separated lists)!
116 # slash is not allowed because it is used as pve API delimiter
0a6e09fd 117 # also see "man useradd"
8e23f971 118 if ($username =~ m!^(${user_regex})\@(${realm_regex})$!) {
5bb4e06a
DM
119 return wantarray ? ($username, $1, $2) : $username;
120 }
121
122 die "value '$username' does not look like a valid user name\n" if !$noerr;
123
124 return undef;
125}
126
127PVE::JSONSchema::register_standard_option('userid', {
af5d7da7 128 description => "User ID",
5bb4e06a
DM
129 type => 'string', format => 'pve-userid',
130 maxLength => 64,
131});
132
9401be39
WB
133my $tfa_format = {
134 type => {
135 description => "The type of 2nd factor authentication.",
136 format_description => 'TFATYPE',
137 type => 'string',
138 enum => [qw(yubico oath)],
139 },
140 id => {
141 description => "Yubico API ID.",
142 format_description => 'ID',
143 type => 'string',
144 optional => 1,
145 },
146 key => {
147 description => "Yubico API Key.",
148 format_description => 'KEY',
149 type => 'string',
150 optional => 1,
151 },
152 url => {
153 description => "Yubico API URL.",
154 format_description => 'URL',
155 type => 'string',
156 optional => 1,
157 },
158 digits => {
159 description => "TOTP digits.",
160 format_description => 'COUNT',
161 type => 'integer',
162 minimum => 6, maximum => 8,
163 default => 6,
164 optional => 1,
165 },
166 step => {
167 description => "TOTP time period.",
168 format_description => 'SECONDS',
169 type => 'integer',
170 minimum => 10,
171 default => 30,
172 optional => 1,
173 },
174};
175
176PVE::JSONSchema::register_format('pve-tfa-config', $tfa_format);
96f8ebd6
DM
177
178PVE::JSONSchema::register_standard_option('tfa', {
179 description => "Use Two-factor authentication.",
180 type => 'string', format => 'pve-tfa-config',
181 optional => 1,
182 maxLength => 128,
183});
184
185sub parse_tfa_config {
186 my ($data) = @_;
187
9401be39 188 return PVE::JSONSchema::parse_property_string($tfa_format, $data);
96f8ebd6
DM
189}
190
5bb4e06a
DM
191my $defaultData = {
192 propertyList => {
193 type => { description => "Realm type." },
194 realm => get_standard_option('realm'),
195 },
196};
197
198sub private {
199 return $defaultData;
200}
201
202sub parse_section_header {
203 my ($class, $line) = @_;
204
205 if ($line =~ m/^(\S+):\s*(\S+)\s*$/) {
206 my ($type, $realm) = (lc($1), $2);
207 my $errmsg = undef; # set if you want to skip whole section
208 eval { pve_verify_realm($realm); };
209 $errmsg = $@ if $@;
210 my $config = {}; # to return additional attributes
211 return ($type, $realm, $errmsg, $config);
212 }
213 return undef;
214}
215
216sub parse_config {
217 my ($class, $filename, $raw) = @_;
218
219 my $cfg = $class->SUPER::parse_config($filename, $raw);
220
221 my $default;
222 foreach my $realm (keys %{$cfg->{ids}}) {
223 my $data = $cfg->{ids}->{$realm};
224 # make sure there is only one default marker
225 if ($data->{default}) {
226 if ($default) {
227 delete $data->{default};
228 } else {
229 $default = $realm;
230 }
231 }
232
233 if ($data->{comment}) {
234 $data->{comment} = PVE::Tools::decode_text($data->{comment});
235 }
236
237 }
238
239 # add default domains
240
96f8ebd6
DM
241 $cfg->{ids}->{pve}->{type} = 'pve'; # force type
242 $cfg->{ids}->{pve}->{comment} = "Proxmox VE authentication server"
243 if !$cfg->{ids}->{pve}->{comment};
5bb4e06a 244
96f8ebd6
DM
245 $cfg->{ids}->{pam}->{type} = 'pam'; # force type
246 $cfg->{ids}->{pam}->{plugin} = 'PVE::Auth::PAM';
247 $cfg->{ids}->{pam}->{comment} = "Linux PAM standard authentication"
248 if !$cfg->{ids}->{pam}->{comment};
5bb4e06a
DM
249
250 return $cfg;
251};
252
253sub write_config {
254 my ($class, $filename, $cfg) = @_;
255
5bb4e06a
DM
256 foreach my $realm (keys %{$cfg->{ids}}) {
257 my $data = $cfg->{ids}->{$realm};
258 if ($data->{comment}) {
259 $data->{comment} = PVE::Tools::encode_text($data->{comment});
260 }
261 }
0a6e09fd 262
5bb4e06a
DM
263 $class->SUPER::write_config($filename, $cfg);
264}
265
266sub authenticate_user {
267 my ($class, $config, $realm, $username, $password) = @_;
268
269 die "overwrite me";
270}
271
272sub store_password {
273 my ($class, $config, $realm, $username, $password) = @_;
274
275 my $type = $class->type();
276
277 die "can't set password on auth type '$type'\n";
278}
279
280sub delete_user {
281 my ($class, $config, $realm, $username) = @_;
282
283 # do nothing by default
284}
285
89338e4d
TL
286# called during addition of realm (before the new domain config got written)
287# `password` is moved to %param to avoid writing it out to the config
7d23b7ca 288# die to abort addition if there are (grave) problems
89338e4d
TL
289# NOTE: runs in a domain config *locked* context
290sub on_add_hook {
291 my ($class, $realm, $config, %param) = @_;
292 # do nothing by default
293}
294
295# called during domain configuration update (before the updated domain config got
296# written). `password` is moved to %param to avoid writing it out to the config
297# die to abort the update if there are (grave) problems
298# NOTE: runs in a domain config *locked* context
299sub on_update_hook {
300 my ($class, $realm, $config, %param) = @_;
301 # do nothing by default
302}
303
304# called during deletion of realms (before the new domain config got written)
305# and if the activate check on addition fails, to cleanup all storage traces
306# which on_add_hook may have created.
307# die to abort deletion if there are (very grave) problems
308# NOTE: runs in a storage config *locked* context
309sub on_delete_hook {
310 my ($class, $realm, $config) = @_;
311 # do nothing by default
312}
313
5bb4e06a 3141;