]> git.proxmox.com Git - pve-access-control.git/blame - PVE/Auth/Plugin.pm
token create: return also full token id for convenience
[pve-access-control.git] / 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
d29d2d4a
TL
52my $realm_sync_options_desc = {
53 scope => {
54 description => "Select what to sync.",
55 type => 'string',
56 enum => [qw(users groups both)],
57 optional => '1',
58 },
59 full => {
60 description => "If set, uses the LDAP Directory as source of truth,"
61 ." deleting users or groups not returned from the sync. Otherwise"
62 ." only syncs information which is not already present, and does not"
63 ." deletes or modifies anything else.",
64 type => 'boolean',
65 optional => '1',
66 },
67 'enable-new' => {
68 description => "Enable newly synced users immediately.",
69 type => 'boolean',
70 default => '1',
71 optional => '1',
72 },
73 purge => {
74 description => "Remove ACLs for users or groups which were removed from"
75 ." the config during a sync.",
76 type => 'boolean',
77 optional => '1',
78 },
79};
80PVE::JSONSchema::register_standard_option('realm-sync-options', $realm_sync_options_desc);
81PVE::JSONSchema::register_format('realm-sync-options', $realm_sync_options_desc);
82
5bb4e06a
DM
83PVE::JSONSchema::register_format('pve-userid', \&verify_username);
84sub verify_username {
85 my ($username, $noerr) = @_;
86
87 $username = '' if !$username;
88 my $len = length($username);
89 if ($len < 3) {
90 die "user name '$username' is too short\n" if !$noerr;
91 return undef;
92 }
93 if ($len > 64) {
94 die "user name '$username' is too long ($len > 64)\n" if !$noerr;
95 return undef;
96 }
97
98 # we only allow a limited set of characters
0a6e09fd 99 # colon is not allowed, because we store usernames in
5bb4e06a
DM
100 # colon separated lists)!
101 # slash is not allowed because it is used as pve API delimiter
0a6e09fd 102 # also see "man useradd"
8e23f971 103 if ($username =~ m!^(${user_regex})\@(${realm_regex})$!) {
5bb4e06a
DM
104 return wantarray ? ($username, $1, $2) : $username;
105 }
106
107 die "value '$username' does not look like a valid user name\n" if !$noerr;
108
109 return undef;
110}
111
112PVE::JSONSchema::register_standard_option('userid', {
af5d7da7 113 description => "User ID",
5bb4e06a
DM
114 type => 'string', format => 'pve-userid',
115 maxLength => 64,
116});
117
9401be39
WB
118my $tfa_format = {
119 type => {
120 description => "The type of 2nd factor authentication.",
121 format_description => 'TFATYPE',
122 type => 'string',
123 enum => [qw(yubico oath)],
124 },
125 id => {
126 description => "Yubico API ID.",
127 format_description => 'ID',
128 type => 'string',
129 optional => 1,
130 },
131 key => {
132 description => "Yubico API Key.",
133 format_description => 'KEY',
134 type => 'string',
135 optional => 1,
136 },
137 url => {
138 description => "Yubico API URL.",
139 format_description => 'URL',
140 type => 'string',
141 optional => 1,
142 },
143 digits => {
144 description => "TOTP digits.",
145 format_description => 'COUNT',
146 type => 'integer',
147 minimum => 6, maximum => 8,
148 default => 6,
149 optional => 1,
150 },
151 step => {
152 description => "TOTP time period.",
153 format_description => 'SECONDS',
154 type => 'integer',
155 minimum => 10,
156 default => 30,
157 optional => 1,
158 },
159};
160
161PVE::JSONSchema::register_format('pve-tfa-config', $tfa_format);
96f8ebd6
DM
162
163PVE::JSONSchema::register_standard_option('tfa', {
164 description => "Use Two-factor authentication.",
165 type => 'string', format => 'pve-tfa-config',
166 optional => 1,
167 maxLength => 128,
168});
169
170sub parse_tfa_config {
171 my ($data) = @_;
172
9401be39 173 return PVE::JSONSchema::parse_property_string($tfa_format, $data);
96f8ebd6
DM
174}
175
5bb4e06a
DM
176my $defaultData = {
177 propertyList => {
178 type => { description => "Realm type." },
179 realm => get_standard_option('realm'),
180 },
181};
182
183sub private {
184 return $defaultData;
185}
186
187sub parse_section_header {
188 my ($class, $line) = @_;
189
190 if ($line =~ m/^(\S+):\s*(\S+)\s*$/) {
191 my ($type, $realm) = (lc($1), $2);
192 my $errmsg = undef; # set if you want to skip whole section
193 eval { pve_verify_realm($realm); };
194 $errmsg = $@ if $@;
195 my $config = {}; # to return additional attributes
196 return ($type, $realm, $errmsg, $config);
197 }
198 return undef;
199}
200
201sub parse_config {
202 my ($class, $filename, $raw) = @_;
203
204 my $cfg = $class->SUPER::parse_config($filename, $raw);
205
206 my $default;
207 foreach my $realm (keys %{$cfg->{ids}}) {
208 my $data = $cfg->{ids}->{$realm};
209 # make sure there is only one default marker
210 if ($data->{default}) {
211 if ($default) {
212 delete $data->{default};
213 } else {
214 $default = $realm;
215 }
216 }
217
218 if ($data->{comment}) {
219 $data->{comment} = PVE::Tools::decode_text($data->{comment});
220 }
221
222 }
223
224 # add default domains
225
96f8ebd6
DM
226 $cfg->{ids}->{pve}->{type} = 'pve'; # force type
227 $cfg->{ids}->{pve}->{comment} = "Proxmox VE authentication server"
228 if !$cfg->{ids}->{pve}->{comment};
5bb4e06a 229
96f8ebd6
DM
230 $cfg->{ids}->{pam}->{type} = 'pam'; # force type
231 $cfg->{ids}->{pam}->{plugin} = 'PVE::Auth::PAM';
232 $cfg->{ids}->{pam}->{comment} = "Linux PAM standard authentication"
233 if !$cfg->{ids}->{pam}->{comment};
5bb4e06a
DM
234
235 return $cfg;
236};
237
238sub write_config {
239 my ($class, $filename, $cfg) = @_;
240
5bb4e06a
DM
241 foreach my $realm (keys %{$cfg->{ids}}) {
242 my $data = $cfg->{ids}->{$realm};
243 if ($data->{comment}) {
244 $data->{comment} = PVE::Tools::encode_text($data->{comment});
245 }
246 }
0a6e09fd 247
5bb4e06a
DM
248 $class->SUPER::write_config($filename, $cfg);
249}
250
251sub authenticate_user {
252 my ($class, $config, $realm, $username, $password) = @_;
253
254 die "overwrite me";
255}
256
257sub store_password {
258 my ($class, $config, $realm, $username, $password) = @_;
259
260 my $type = $class->type();
261
262 die "can't set password on auth type '$type'\n";
263}
264
265sub delete_user {
266 my ($class, $config, $realm, $username) = @_;
267
268 # do nothing by default
269}
270
2711;