]>
Commit | Line | Data |
---|---|---|
84fb5b46 MKG |
1 | # BEGIN BPS TAGGED BLOCK {{{ |
2 | # | |
3 | # COPYRIGHT: | |
4 | # | |
320f0092 | 5 | # This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC |
84fb5b46 MKG |
6 | # <sales@bestpractical.com> |
7 | # | |
8 | # (Except where explicitly superseded by other copyright notices) | |
9 | # | |
10 | # | |
11 | # LICENSE: | |
12 | # | |
13 | # This work is made available to you under the terms of Version 2 of | |
14 | # the GNU General Public License. A copy of that license should have | |
15 | # been provided with this software, but in any event can be snarfed | |
16 | # from www.gnu.org. | |
17 | # | |
18 | # This work is distributed in the hope that it will be useful, but | |
19 | # WITHOUT ANY WARRANTY; without even the implied warranty of | |
20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
21 | # General Public License for more details. | |
22 | # | |
23 | # You should have received a copy of the GNU General Public License | |
24 | # along with this program; if not, write to the Free Software | |
25 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
26 | # 02110-1301 or visit their web page on the internet at | |
27 | # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. | |
28 | # | |
29 | # | |
30 | # CONTRIBUTION SUBMISSION POLICY: | |
31 | # | |
32 | # (The following paragraph is not intended to limit the rights granted | |
33 | # to you to modify and distribute this software under the terms of | |
34 | # the GNU General Public License and is only of importance to you if | |
35 | # you choose to contribute your changes and enhancements to the | |
36 | # community by submitting them to Best Practical Solutions, LLC.) | |
37 | # | |
38 | # By intentionally submitting any modifications, corrections or | |
39 | # derivatives to this work, or any other work intended for use with | |
40 | # Request Tracker, to Best Practical Solutions, LLC, you confirm that | |
41 | # you are the copyright holder for those contributions and you grant | |
42 | # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, | |
43 | # royalty-free, perpetual, license to use, copy, create derivative | |
44 | # works based on those contributions, and sublicense and distribute | |
45 | # those contributions and any derivatives thereof. | |
46 | # | |
47 | # END BPS TAGGED BLOCK }}} | |
48 | ||
49 | =head1 NAME | |
50 | ||
51 | RT::User - RT User object | |
52 | ||
53 | =head1 SYNOPSIS | |
54 | ||
55 | use RT::User; | |
56 | ||
57 | =head1 DESCRIPTION | |
58 | ||
59 | =head1 METHODS | |
60 | ||
61 | =cut | |
62 | ||
63 | ||
64 | package RT::User; | |
65 | ||
66 | use strict; | |
67 | use warnings; | |
68 | ||
af59614d | 69 | use Scalar::Util qw(blessed); |
84fb5b46 MKG |
70 | |
71 | use base 'RT::Record'; | |
72 | ||
73 | sub Table {'Users'} | |
74 | ||
75 | ||
76 | ||
77 | ||
78 | ||
79 | ||
80 | use Digest::SHA; | |
81 | use Digest::MD5; | |
af59614d | 82 | use Crypt::Eksblowfish::Bcrypt qw(); |
84fb5b46 MKG |
83 | use RT::Principals; |
84 | use RT::ACE; | |
85 | use RT::Interface::Email; | |
84fb5b46 MKG |
86 | use Text::Password::Pronounceable; |
87 | ||
88 | sub _OverlayAccessible { | |
89 | { | |
90 | ||
320f0092 | 91 | Name => { public => 1, admin => 1 }, # loc_left_pair |
84fb5b46 | 92 | Password => { read => 0 }, |
320f0092 MKG |
93 | EmailAddress => { public => 1 }, # loc_left_pair |
94 | Organization => { public => 1, admin => 1 }, # loc_left_pair | |
95 | RealName => { public => 1 }, # loc_left_pair | |
96 | NickName => { public => 1 }, # loc_left_pair | |
97 | Lang => { public => 1 }, # loc_left_pair | |
84fb5b46 MKG |
98 | EmailEncoding => { public => 1 }, |
99 | WebEncoding => { public => 1 }, | |
100 | ExternalContactInfoId => { public => 1, admin => 1 }, | |
101 | ContactInfoSystem => { public => 1, admin => 1 }, | |
102 | ExternalAuthId => { public => 1, admin => 1 }, | |
103 | AuthSystem => { public => 1, admin => 1 }, | |
320f0092 MKG |
104 | Gecos => { public => 1, admin => 1 }, # loc_left_pair |
105 | PGPKey => { public => 1, admin => 1 }, # loc_left_pair | |
106 | SMIMECertificate => { public => 1, admin => 1 }, # loc_left_pair | |
320f0092 MKG |
107 | City => { public => 1 }, # loc_left_pair |
108 | Country => { public => 1 }, # loc_left_pair | |
109 | Timezone => { public => 1 }, # loc_left_pair | |
84fb5b46 MKG |
110 | } |
111 | } | |
112 | ||
113 | ||
114 | ||
115 | =head2 Create { PARAMHASH } | |
116 | ||
117 | ||
118 | ||
119 | =cut | |
120 | ||
121 | ||
122 | sub Create { | |
123 | my $self = shift; | |
124 | my %args = ( | |
125 | Privileged => 0, | |
126 | Disabled => 0, | |
127 | EmailAddress => '', | |
128 | _RecordTransaction => 1, | |
129 | @_ # get the real argumentlist | |
130 | ); | |
131 | ||
132 | # remove the value so it does not cripple SUPER::Create | |
133 | my $record_transaction = delete $args{'_RecordTransaction'}; | |
134 | ||
135 | #Check the ACL | |
136 | unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) { | |
137 | return ( 0, $self->loc('Permission Denied') ); | |
138 | } | |
139 | ||
140 | ||
141 | unless ($self->CanonicalizeUserInfo(\%args)) { | |
142 | return ( 0, $self->loc("Could not set user info") ); | |
143 | } | |
144 | ||
145 | $args{'EmailAddress'} = $self->CanonicalizeEmailAddress($args{'EmailAddress'}); | |
146 | ||
147 | # if the user doesn't have a name defined, set it to the email address | |
148 | $args{'Name'} = $args{'EmailAddress'} unless ($args{'Name'}); | |
149 | ||
150 | ||
151 | ||
152 | my $privileged = delete $args{'Privileged'}; | |
153 | ||
154 | ||
155 | if ($args{'CryptedPassword'} ) { | |
156 | $args{'Password'} = $args{'CryptedPassword'}; | |
157 | delete $args{'CryptedPassword'}; | |
158 | } elsif ( !$args{'Password'} ) { | |
159 | $args{'Password'} = '*NO-PASSWORD*'; | |
160 | } else { | |
161 | my ($ok, $msg) = $self->ValidatePassword($args{'Password'}); | |
162 | return ($ok, $msg) if !$ok; | |
163 | ||
164 | $args{'Password'} = $self->_GeneratePassword($args{'Password'}); | |
165 | } | |
166 | ||
167 | #TODO Specify some sensible defaults. | |
168 | ||
169 | unless ( $args{'Name'} ) { | |
170 | return ( 0, $self->loc("Must specify 'Name' attribute") ); | |
171 | } | |
172 | ||
c36a7e1d MKG |
173 | my ( $val, $msg ) = $self->ValidateName( $args{'Name'} ); |
174 | return ( 0, $msg ) unless $val; | |
175 | ( $val, $msg ) = $self->ValidateEmailAddress( $args{'EmailAddress'} ); | |
176 | return ( 0, $msg ) unless ($val); | |
84fb5b46 MKG |
177 | |
178 | $RT::Handle->BeginTransaction(); | |
179 | # Groups deal with principal ids, rather than user ids. | |
180 | # When creating this user, set up a principal Id for it. | |
181 | my $principal = RT::Principal->new($self->CurrentUser); | |
182 | my $principal_id = $principal->Create(PrincipalType => 'User', | |
183 | Disabled => $args{'Disabled'}, | |
184 | ObjectId => '0'); | |
185 | # If we couldn't create a principal Id, get the fuck out. | |
186 | unless ($principal_id) { | |
187 | $RT::Handle->Rollback(); | |
188 | $RT::Logger->crit("Couldn't create a Principal on new user create."); | |
189 | $RT::Logger->crit("Strange things are afoot at the circle K"); | |
190 | return ( 0, $self->loc('Could not create user') ); | |
191 | } | |
192 | ||
193 | $principal->__Set(Field => 'ObjectId', Value => $principal_id); | |
194 | delete $args{'Disabled'}; | |
195 | ||
196 | $self->SUPER::Create(id => $principal_id , %args); | |
197 | my $id = $self->Id; | |
198 | ||
199 | #If the create failed. | |
200 | unless ($id) { | |
201 | $RT::Handle->Rollback(); | |
202 | $RT::Logger->error("Could not create a new user - " .join('-', %args)); | |
203 | ||
204 | return ( 0, $self->loc('Could not create user') ); | |
205 | } | |
206 | ||
207 | my $aclstash = RT::Group->new($self->CurrentUser); | |
208 | my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal); | |
209 | ||
210 | unless ($stash_id) { | |
211 | $RT::Handle->Rollback(); | |
212 | $RT::Logger->crit("Couldn't stash the user in groupmembers"); | |
213 | return ( 0, $self->loc('Could not create user') ); | |
214 | } | |
215 | ||
216 | ||
217 | my $everyone = RT::Group->new($self->CurrentUser); | |
218 | $everyone->LoadSystemInternalGroup('Everyone'); | |
219 | unless ($everyone->id) { | |
220 | $RT::Logger->crit("Could not load Everyone group on user creation."); | |
221 | $RT::Handle->Rollback(); | |
222 | return ( 0, $self->loc('Could not create user') ); | |
223 | } | |
224 | ||
225 | ||
226 | my ($everyone_id, $everyone_msg) = $everyone->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId); | |
227 | unless ($everyone_id) { | |
228 | $RT::Logger->crit("Could not add user to Everyone group on user creation."); | |
229 | $RT::Logger->crit($everyone_msg); | |
230 | $RT::Handle->Rollback(); | |
231 | return ( 0, $self->loc('Could not create user') ); | |
232 | } | |
233 | ||
234 | ||
235 | my $access_class = RT::Group->new($self->CurrentUser); | |
236 | if ($privileged) { | |
237 | $access_class->LoadSystemInternalGroup('Privileged'); | |
238 | } else { | |
239 | $access_class->LoadSystemInternalGroup('Unprivileged'); | |
240 | } | |
241 | ||
242 | unless ($access_class->id) { | |
243 | $RT::Logger->crit("Could not load Privileged or Unprivileged group on user creation"); | |
244 | $RT::Handle->Rollback(); | |
245 | return ( 0, $self->loc('Could not create user') ); | |
246 | } | |
247 | ||
248 | ||
249 | my ($ac_id, $ac_msg) = $access_class->_AddMember( InsideTransaction => 1, PrincipalId => $self->PrincipalId); | |
250 | ||
251 | unless ($ac_id) { | |
252 | $RT::Logger->crit("Could not add user to Privileged or Unprivileged group on user creation. Aborted"); | |
253 | $RT::Logger->crit($ac_msg); | |
254 | $RT::Handle->Rollback(); | |
255 | return ( 0, $self->loc('Could not create user') ); | |
256 | } | |
257 | ||
258 | ||
259 | if ( $record_transaction ) { | |
260 | $self->_NewTransaction( Type => "Create" ); | |
261 | } | |
262 | ||
263 | $RT::Handle->Commit; | |
264 | ||
265 | return ( $id, $self->loc('User created') ); | |
266 | } | |
267 | ||
c36a7e1d MKG |
268 | =head2 ValidateName STRING |
269 | ||
270 | Returns either (0, "failure reason") or 1 depending on whether the given | |
271 | name is valid. | |
272 | ||
273 | =cut | |
274 | ||
275 | sub ValidateName { | |
276 | my $self = shift; | |
277 | my $name = shift; | |
278 | ||
279 | return ( 0, $self->loc('empty name') ) unless defined $name && length $name; | |
280 | ||
281 | my $TempUser = RT::User->new( RT->SystemUser ); | |
282 | $TempUser->Load($name); | |
283 | ||
284 | if ( $TempUser->id && ( !$self->id || $TempUser->id != $self->id ) ) { | |
285 | return ( 0, $self->loc('Name in use') ); | |
286 | } | |
287 | else { | |
288 | return 1; | |
289 | } | |
290 | } | |
291 | ||
84fb5b46 MKG |
292 | =head2 ValidatePassword STRING |
293 | ||
294 | Returns either (0, "failure reason") or 1 depending on whether the given | |
295 | password is valid. | |
296 | ||
297 | =cut | |
298 | ||
299 | sub ValidatePassword { | |
300 | my $self = shift; | |
301 | my $password = shift; | |
302 | ||
303 | if ( length($password) < RT->Config->Get('MinimumPasswordLength') ) { | |
c33a4027 | 304 | return ( 0, $self->loc("Password needs to be at least [quant,_1,character,characters] long", RT->Config->Get('MinimumPasswordLength')) ); |
84fb5b46 MKG |
305 | } |
306 | ||
307 | return 1; | |
308 | } | |
309 | ||
310 | =head2 SetPrivileged BOOL | |
311 | ||
312 | If passed a true value, makes this user a member of the "Privileged" PseudoGroup. | |
313 | Otherwise, makes this user a member of the "Unprivileged" pseudogroup. | |
314 | ||
315 | Returns a standard RT tuple of (val, msg); | |
316 | ||
317 | ||
318 | =cut | |
319 | ||
320 | sub SetPrivileged { | |
321 | my $self = shift; | |
322 | my $val = shift; | |
323 | ||
324 | #Check the ACL | |
325 | unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) { | |
326 | return ( 0, $self->loc('Permission Denied') ); | |
327 | } | |
328 | ||
329 | $self->_SetPrivileged($val); | |
330 | } | |
331 | ||
332 | sub _SetPrivileged { | |
333 | my $self = shift; | |
334 | my $val = shift; | |
335 | my $priv = RT::Group->new($self->CurrentUser); | |
336 | $priv->LoadSystemInternalGroup('Privileged'); | |
337 | unless ($priv->Id) { | |
338 | $RT::Logger->crit("Could not find Privileged pseudogroup"); | |
339 | return(0,$self->loc("Failed to find 'Privileged' users pseudogroup.")); | |
340 | } | |
341 | ||
342 | my $unpriv = RT::Group->new($self->CurrentUser); | |
343 | $unpriv->LoadSystemInternalGroup('Unprivileged'); | |
344 | unless ($unpriv->Id) { | |
345 | $RT::Logger->crit("Could not find unprivileged pseudogroup"); | |
346 | return(0,$self->loc("Failed to find 'Unprivileged' users pseudogroup")); | |
347 | } | |
348 | ||
349 | my $principal = $self->PrincipalId; | |
350 | if ($val) { | |
351 | if ($priv->HasMember($principal)) { | |
352 | #$RT::Logger->debug("That user is already privileged"); | |
353 | return (0,$self->loc("That user is already privileged")); | |
354 | } | |
355 | if ($unpriv->HasMember($principal)) { | |
356 | $unpriv->_DeleteMember($principal); | |
357 | } else { | |
358 | # if we had layered transactions, life would be good | |
359 | # sadly, we have to just go ahead, even if something | |
360 | # bogus happened | |
361 | $RT::Logger->crit("User ".$self->Id." is neither privileged nor ". | |
362 | "unprivileged. something is drastically wrong."); | |
363 | } | |
364 | my ($status, $msg) = $priv->_AddMember( InsideTransaction => 1, PrincipalId => $principal); | |
365 | if ($status) { | |
366 | return (1, $self->loc("That user is now privileged")); | |
367 | } else { | |
368 | return (0, $msg); | |
369 | } | |
370 | } else { | |
371 | if ($unpriv->HasMember($principal)) { | |
372 | #$RT::Logger->debug("That user is already unprivileged"); | |
373 | return (0,$self->loc("That user is already unprivileged")); | |
374 | } | |
375 | if ($priv->HasMember($principal)) { | |
376 | $priv->_DeleteMember( $principal ); | |
377 | } else { | |
378 | # if we had layered transactions, life would be good | |
379 | # sadly, we have to just go ahead, even if something | |
380 | # bogus happened | |
381 | $RT::Logger->crit("User ".$self->Id." is neither privileged nor ". | |
382 | "unprivileged. something is drastically wrong."); | |
383 | } | |
384 | my ($status, $msg) = $unpriv->_AddMember( InsideTransaction => 1, PrincipalId => $principal); | |
385 | if ($status) { | |
386 | return (1, $self->loc("That user is now unprivileged")); | |
387 | } else { | |
388 | return (0, $msg); | |
389 | } | |
390 | } | |
391 | } | |
392 | ||
393 | =head2 Privileged | |
394 | ||
395 | Returns true if this user is privileged. Returns undef otherwise. | |
396 | ||
397 | =cut | |
398 | ||
399 | sub Privileged { | |
400 | my $self = shift; | |
401 | if ( RT->PrivilegedUsers->HasMember( $self->id ) ) { | |
402 | return(1); | |
403 | } else { | |
404 | return(undef); | |
405 | } | |
406 | } | |
407 | ||
408 | #create a user without validating _any_ data. | |
409 | ||
410 | #To be used only on database init. | |
411 | # We can't localize here because it's before we _have_ a loc framework | |
412 | ||
413 | sub _BootstrapCreate { | |
414 | my $self = shift; | |
415 | my %args = (@_); | |
416 | ||
417 | $args{'Password'} = '*NO-PASSWORD*'; | |
418 | ||
419 | ||
420 | $RT::Handle->BeginTransaction(); | |
421 | ||
422 | # Groups deal with principal ids, rather than user ids. | |
423 | # When creating this user, set up a principal Id for it. | |
424 | my $principal = RT::Principal->new($self->CurrentUser); | |
425 | my $principal_id = $principal->Create(PrincipalType => 'User', ObjectId => '0'); | |
426 | $principal->__Set(Field => 'ObjectId', Value => $principal_id); | |
427 | ||
428 | # If we couldn't create a principal Id, get the fuck out. | |
429 | unless ($principal_id) { | |
430 | $RT::Handle->Rollback(); | |
431 | $RT::Logger->crit("Couldn't create a Principal on new user create. Strange things are afoot at the circle K"); | |
432 | return ( 0, 'Could not create user' ); | |
433 | } | |
434 | $self->SUPER::Create(id => $principal_id, %args); | |
435 | my $id = $self->Id; | |
436 | #If the create failed. | |
437 | unless ($id) { | |
438 | $RT::Handle->Rollback(); | |
439 | return ( 0, 'Could not create user' ) ; #never loc this | |
440 | } | |
441 | ||
442 | my $aclstash = RT::Group->new($self->CurrentUser); | |
443 | my $stash_id = $aclstash->_CreateACLEquivalenceGroup($principal); | |
444 | ||
445 | unless ($stash_id) { | |
446 | $RT::Handle->Rollback(); | |
447 | $RT::Logger->crit("Couldn't stash the user in groupmembers"); | |
448 | return ( 0, $self->loc('Could not create user') ); | |
449 | } | |
450 | ||
451 | $RT::Handle->Commit(); | |
452 | ||
453 | return ( $id, 'User created' ); | |
454 | } | |
455 | ||
456 | sub Delete { | |
457 | my $self = shift; | |
458 | ||
459 | return ( 0, $self->loc('Deleting this object would violate referential integrity') ); | |
460 | ||
461 | } | |
462 | ||
463 | =head2 Load | |
464 | ||
465 | Load a user object from the database. Takes a single argument. | |
466 | If the argument is numerical, load by the column 'id'. If a user | |
467 | object or its subclass passed then loads the same user by id. | |
468 | Otherwise, load by the "Name" column which is the user's textual | |
469 | username. | |
470 | ||
471 | =cut | |
472 | ||
473 | sub Load { | |
474 | my $self = shift; | |
475 | my $identifier = shift || return undef; | |
476 | ||
477 | if ( $identifier !~ /\D/ ) { | |
478 | return $self->SUPER::LoadById( $identifier ); | |
479 | } elsif ( UNIVERSAL::isa( $identifier, 'RT::User' ) ) { | |
480 | return $self->SUPER::LoadById( $identifier->Id ); | |
481 | } else { | |
482 | return $self->LoadByCol( "Name", $identifier ); | |
483 | } | |
484 | } | |
485 | ||
486 | =head2 LoadByEmail | |
487 | ||
488 | Tries to load this user object from the database by the user's email address. | |
489 | ||
490 | =cut | |
491 | ||
492 | sub LoadByEmail { | |
493 | my $self = shift; | |
494 | my $address = shift; | |
495 | ||
496 | # Never load an empty address as an email address. | |
497 | unless ($address) { | |
498 | return (undef); | |
499 | } | |
500 | ||
501 | $address = $self->CanonicalizeEmailAddress($address); | |
502 | ||
503 | #$RT::Logger->debug("Trying to load an email address: $address"); | |
504 | return $self->LoadByCol( "EmailAddress", $address ); | |
505 | } | |
506 | ||
507 | =head2 LoadOrCreateByEmail ADDRESS | |
508 | ||
509 | Attempts to find a user who has the provided email address. If that fails, creates an unprivileged user with | |
510 | the provided email address and loads them. Address can be provided either as L<Email::Address> object | |
511 | or string which is parsed using the module. | |
512 | ||
513 | Returns a tuple of the user's id and a status message. | |
514 | 0 will be returned in place of the user's id in case of failure. | |
515 | ||
516 | =cut | |
517 | ||
518 | sub LoadOrCreateByEmail { | |
519 | my $self = shift; | |
520 | my $email = shift; | |
521 | ||
522 | my ($message, $name); | |
523 | if ( UNIVERSAL::isa( $email => 'Email::Address' ) ) { | |
524 | ($email, $name) = ($email->address, $email->phrase); | |
525 | } else { | |
526 | ($email, $name) = RT::Interface::Email::ParseAddressFromHeader( $email ); | |
527 | } | |
528 | ||
529 | $self->LoadByEmail( $email ); | |
530 | $self->Load( $email ) unless $self->Id; | |
531 | $message = $self->loc('User loaded'); | |
532 | ||
533 | unless( $self->Id ) { | |
534 | my $val; | |
535 | ($val, $message) = $self->Create( | |
536 | Name => $email, | |
537 | EmailAddress => $email, | |
538 | RealName => $name, | |
539 | Privileged => 0, | |
540 | Comments => 'Autocreated when added as a watcher', | |
541 | ); | |
542 | unless ( $val ) { | |
543 | # Deal with the race condition of two account creations at once | |
544 | $self->LoadByEmail( $email ); | |
545 | unless ( $self->Id ) { | |
546 | sleep 5; | |
547 | $self->LoadByEmail( $email ); | |
548 | } | |
549 | if ( $self->Id ) { | |
550 | $RT::Logger->error("Recovered from creation failure due to race condition"); | |
551 | $message = $self->loc("User loaded"); | |
552 | } else { | |
553 | $RT::Logger->crit("Failed to create user ". $email .": " .$message); | |
554 | } | |
555 | } | |
556 | } | |
af59614d MKG |
557 | return wantarray ? (0, $message) : 0 unless $self->id; |
558 | return wantarray ? ($self->Id, $message) : $self->Id; | |
84fb5b46 MKG |
559 | } |
560 | ||
561 | =head2 ValidateEmailAddress ADDRESS | |
562 | ||
563 | Returns true if the email address entered is not in use by another user or is | |
564 | undef or ''. Returns false if it's in use. | |
565 | ||
566 | =cut | |
567 | ||
568 | sub ValidateEmailAddress { | |
569 | my $self = shift; | |
570 | my $Value = shift; | |
571 | ||
572 | # if the email address is null, it's always valid | |
573 | return (1) if ( !$Value || $Value eq "" ); | |
574 | ||
575 | if ( RT->Config->Get('ValidateUserEmailAddresses') ) { | |
576 | # We only allow one valid email address | |
577 | my @addresses = Email::Address->parse($Value); | |
578 | return ( 0, $self->loc('Invalid syntax for email address') ) unless ( ( scalar (@addresses) == 1 ) && ( $addresses[0]->address ) ); | |
579 | } | |
580 | ||
581 | ||
582 | my $TempUser = RT::User->new(RT->SystemUser); | |
583 | $TempUser->LoadByEmail($Value); | |
584 | ||
585 | if ( $TempUser->id && ( !$self->id || $TempUser->id != $self->id ) ) | |
586 | { # if we found a user with that address | |
587 | # it's invalid to set this user's address to it | |
588 | return ( 0, $self->loc('Email address in use') ); | |
589 | } else { #it's a valid email address | |
590 | return (1); | |
591 | } | |
592 | } | |
593 | ||
c36a7e1d MKG |
594 | =head2 SetName |
595 | ||
596 | Check to make sure someone else isn't using this name already | |
597 | ||
598 | =cut | |
599 | ||
600 | sub SetName { | |
601 | my $self = shift; | |
602 | my $Value = shift; | |
603 | ||
604 | my ( $val, $message ) = $self->ValidateName($Value); | |
605 | if ($val) { | |
606 | return $self->_Set( Field => 'Name', Value => $Value ); | |
607 | } | |
608 | else { | |
609 | return ( 0, $message ); | |
610 | } | |
611 | } | |
612 | ||
84fb5b46 MKG |
613 | =head2 SetEmailAddress |
614 | ||
615 | Check to make sure someone else isn't using this email address already | |
616 | so that a better email address can be returned | |
617 | ||
618 | =cut | |
619 | ||
620 | sub SetEmailAddress { | |
621 | my $self = shift; | |
622 | my $Value = shift; | |
623 | $Value = '' unless defined $Value; | |
624 | ||
625 | my ($val, $message) = $self->ValidateEmailAddress( $Value ); | |
626 | if ( $val ) { | |
627 | return $self->_Set( Field => 'EmailAddress', Value => $Value ); | |
628 | } else { | |
629 | return ( 0, $message ) | |
630 | } | |
631 | ||
632 | } | |
633 | ||
634 | =head2 EmailFrequency | |
635 | ||
636 | Takes optional Ticket argument in paramhash. Returns 'no email', | |
637 | 'squelched', 'daily', 'weekly' or empty string depending on | |
638 | user preferences. | |
639 | ||
640 | =over 4 | |
641 | ||
642 | =item 'no email' - user has no email, so can not recieve notifications. | |
643 | ||
644 | =item 'squelched' - returned only when Ticket argument is provided and | |
645 | notifications to the user has been supressed for this ticket. | |
646 | ||
647 | =item 'daily' - retruned when user recieve daily messages digest instead | |
648 | of immediate delivery. | |
649 | ||
650 | =item 'weekly' - previous, but weekly. | |
651 | ||
652 | =item empty string returned otherwise. | |
653 | ||
654 | =back | |
655 | ||
656 | =cut | |
657 | ||
658 | sub EmailFrequency { | |
659 | my $self = shift; | |
660 | my %args = ( | |
661 | Ticket => undef, | |
662 | @_ | |
663 | ); | |
664 | return '' unless $self->id && $self->id != RT->Nobody->id | |
665 | && $self->id != RT->SystemUser->id; | |
666 | return 'no email address' unless my $email = $self->EmailAddress; | |
667 | return 'email disabled for ticket' if $args{'Ticket'} && | |
668 | grep lc $email eq lc $_->Content, $args{'Ticket'}->SquelchMailTo; | |
669 | my $frequency = RT->Config->Get( 'EmailFrequency', $self ) || ''; | |
670 | return 'daily' if $frequency =~ /daily/i; | |
671 | return 'weekly' if $frequency =~ /weekly/i; | |
672 | return ''; | |
673 | } | |
674 | ||
675 | =head2 CanonicalizeEmailAddress ADDRESS | |
676 | ||
677 | CanonicalizeEmailAddress converts email addresses into canonical form. | |
678 | it takes one email address in and returns the proper canonical | |
679 | form. You can dump whatever your proper local config is in here. Note | |
680 | that it may be called as a static method; in this case the first argument | |
681 | is class name not an object. | |
682 | ||
683 | =cut | |
684 | ||
685 | sub CanonicalizeEmailAddress { | |
686 | my $self = shift; | |
687 | my $email = shift; | |
688 | # Example: the following rule would treat all email | |
689 | # coming from a subdomain as coming from second level domain | |
690 | # foo.com | |
691 | if ( my $match = RT->Config->Get('CanonicalizeEmailAddressMatch') and | |
692 | my $replace = RT->Config->Get('CanonicalizeEmailAddressReplace') ) | |
693 | { | |
694 | $email =~ s/$match/$replace/gi; | |
695 | } | |
696 | return ($email); | |
697 | } | |
698 | ||
699 | =head2 CanonicalizeUserInfo HASH of ARGS | |
700 | ||
701 | CanonicalizeUserInfo can convert all User->Create options. | |
702 | it takes a hashref of all the params sent to User->Create and | |
703 | returns that same hash, by default nothing is done. | |
704 | ||
705 | This function is intended to allow users to have their info looked up via | |
706 | an outside source and modified upon creation. | |
707 | ||
708 | =cut | |
709 | ||
710 | sub CanonicalizeUserInfo { | |
711 | my $self = shift; | |
712 | my $args = shift; | |
713 | my $success = 1; | |
714 | ||
715 | return ($success); | |
716 | } | |
717 | ||
718 | ||
719 | =head2 Password and authentication related functions | |
720 | ||
721 | =head3 SetRandomPassword | |
722 | ||
723 | Takes no arguments. Returns a status code and a new password or an error message. | |
724 | If the status is 1, the second value returned is the new password. | |
725 | If the status is anything else, the new value returned is the error code. | |
726 | ||
727 | =cut | |
728 | ||
729 | sub SetRandomPassword { | |
730 | my $self = shift; | |
731 | ||
732 | unless ( $self->CurrentUserCanModify('Password') ) { | |
733 | return ( 0, $self->loc("Permission Denied") ); | |
734 | } | |
735 | ||
736 | ||
737 | my $min = ( RT->Config->Get('MinimumPasswordLength') > 6 ? RT->Config->Get('MinimumPasswordLength') : 6); | |
738 | my $max = ( RT->Config->Get('MinimumPasswordLength') > 8 ? RT->Config->Get('MinimumPasswordLength') : 8); | |
739 | ||
740 | my $pass = $self->GenerateRandomPassword( $min, $max) ; | |
741 | ||
742 | # If we have "notify user on | |
743 | ||
744 | my ( $val, $msg ) = $self->SetPassword($pass); | |
745 | ||
746 | #If we got an error return the error. | |
747 | return ( 0, $msg ) unless ($val); | |
748 | ||
749 | #Otherwise, we changed the password, lets return it. | |
750 | return ( 1, $pass ); | |
751 | ||
752 | } | |
753 | ||
754 | =head3 ResetPassword | |
755 | ||
756 | Returns status, [ERROR or new password]. Resets this user's password to | |
757 | a randomly generated pronouncable password and emails them, using a | |
758 | global template called "PasswordChange". | |
759 | ||
760 | This function is currently unused in the UI, but available for local scripts. | |
761 | ||
762 | =cut | |
763 | ||
764 | sub ResetPassword { | |
765 | my $self = shift; | |
766 | ||
767 | unless ( $self->CurrentUserCanModify('Password') ) { | |
768 | return ( 0, $self->loc("Permission Denied") ); | |
769 | } | |
770 | my ( $status, $pass ) = $self->SetRandomPassword(); | |
771 | ||
772 | unless ($status) { | |
773 | return ( 0, "$pass" ); | |
774 | } | |
775 | ||
776 | my $ret = RT::Interface::Email::SendEmailUsingTemplate( | |
777 | To => $self->EmailAddress, | |
778 | Template => 'PasswordChange', | |
779 | Arguments => { | |
780 | NewPassword => $pass, | |
781 | }, | |
782 | ); | |
783 | ||
784 | if ($ret) { | |
785 | return ( 1, $self->loc('New password notification sent') ); | |
786 | } else { | |
787 | return ( 0, $self->loc('Notification could not be sent') ); | |
788 | } | |
789 | ||
790 | } | |
791 | ||
792 | =head3 GenerateRandomPassword MIN_LEN and MAX_LEN | |
793 | ||
794 | Returns a random password between MIN_LEN and MAX_LEN characters long. | |
795 | ||
796 | =cut | |
797 | ||
798 | sub GenerateRandomPassword { | |
799 | my $self = shift; # just to drop it | |
800 | return Text::Password::Pronounceable->generate(@_); | |
801 | } | |
802 | ||
803 | sub SafeSetPassword { | |
804 | my $self = shift; | |
805 | my %args = ( | |
806 | Current => undef, | |
807 | New => undef, | |
808 | Confirmation => undef, | |
809 | @_, | |
810 | ); | |
811 | return (1) unless defined $args{'New'} && length $args{'New'}; | |
812 | ||
813 | my %cond = $self->CurrentUserRequireToSetPassword; | |
814 | ||
815 | unless ( $cond{'CanSet'} ) { | |
816 | return (0, $self->loc('You can not set password.') .' '. $cond{'Reason'} ); | |
817 | } | |
818 | ||
819 | my $error = ''; | |
820 | if ( $cond{'RequireCurrent'} && !$self->CurrentUser->IsPassword($args{'Current'}) ) { | |
821 | if ( defined $args{'Current'} && length $args{'Current'} ) { | |
822 | $error = $self->loc("Please enter your current password correctly."); | |
823 | } else { | |
824 | $error = $self->loc("Please enter your current password."); | |
825 | } | |
826 | } elsif ( $args{'New'} ne $args{'Confirmation'} ) { | |
827 | $error = $self->loc("Passwords do not match."); | |
828 | } | |
829 | ||
830 | if ( $error ) { | |
831 | $error .= ' '. $self->loc('Password has not been set.'); | |
832 | return (0, $error); | |
833 | } | |
834 | ||
835 | return $self->SetPassword( $args{'New'} ); | |
836 | } | |
837 | ||
838 | =head3 SetPassword | |
839 | ||
840 | Takes a string. Checks the string's length and sets this user's password | |
841 | to that string. | |
842 | ||
843 | =cut | |
844 | ||
845 | sub SetPassword { | |
846 | my $self = shift; | |
847 | my $password = shift; | |
848 | ||
849 | unless ( $self->CurrentUserCanModify('Password') ) { | |
850 | return ( 0, $self->loc('Password: Permission Denied') ); | |
851 | } | |
852 | ||
853 | if ( !$password ) { | |
854 | return ( 0, $self->loc("No password set") ); | |
855 | } else { | |
856 | my ($val, $msg) = $self->ValidatePassword($password); | |
857 | return ($val, $msg) if !$val; | |
858 | ||
859 | my $new = !$self->HasPassword; | |
860 | $password = $self->_GeneratePassword($password); | |
861 | ||
862 | ( $val, $msg ) = $self->_Set(Field => 'Password', Value => $password); | |
863 | if ($val) { | |
864 | return ( 1, $self->loc("Password set") ) if $new; | |
865 | return ( 1, $self->loc("Password changed") ); | |
866 | } else { | |
867 | return ( $val, $msg ); | |
868 | } | |
869 | } | |
870 | ||
871 | } | |
872 | ||
af59614d MKG |
873 | sub _GeneratePassword_bcrypt { |
874 | my $self = shift; | |
875 | my ($password, @rest) = @_; | |
876 | ||
877 | my $salt; | |
878 | my $rounds; | |
879 | if (@rest) { | |
880 | # The first split is the number of rounds | |
881 | $rounds = $rest[0]; | |
882 | ||
883 | # The salt is the first 22 characters, b64 encoded usign the | |
884 | # special bcrypt base64. | |
885 | $salt = Crypt::Eksblowfish::Bcrypt::de_base64( substr($rest[1], 0, 22) ); | |
886 | } else { | |
887 | $rounds = RT->Config->Get('BcryptCost'); | |
888 | ||
889 | # Generate a random 16-octet base64 salt | |
890 | $salt = ""; | |
891 | $salt .= pack("C", int rand(256)) for 1..16; | |
892 | } | |
893 | ||
894 | my $hash = Crypt::Eksblowfish::Bcrypt::bcrypt_hash({ | |
895 | key_nul => 1, | |
896 | cost => $rounds, | |
897 | salt => $salt, | |
c33a4027 | 898 | }, Digest::SHA::sha512( Encode::encode( 'UTF-8', $password) ) ); |
af59614d MKG |
899 | |
900 | return join("!", "", "bcrypt", sprintf("%02d", $rounds), | |
901 | Crypt::Eksblowfish::Bcrypt::en_base64( $salt ). | |
902 | Crypt::Eksblowfish::Bcrypt::en_base64( $hash ) | |
903 | ); | |
904 | } | |
905 | ||
84fb5b46 MKG |
906 | sub _GeneratePassword_sha512 { |
907 | my $self = shift; | |
908 | my ($password, $salt) = @_; | |
909 | ||
910 | # Generate a 16-character base64 salt | |
911 | unless ($salt) { | |
912 | $salt = ""; | |
913 | $salt .= ("a".."z", "A".."Z","0".."9", "+", "/")[rand 64] | |
914 | for 1..16; | |
915 | } | |
916 | ||
917 | my $sha = Digest::SHA->new(512); | |
918 | $sha->add($salt); | |
c33a4027 | 919 | $sha->add(Encode::encode( 'UTF-8', $password)); |
84fb5b46 MKG |
920 | return join("!", "", "sha512", $salt, $sha->b64digest); |
921 | } | |
922 | ||
923 | =head3 _GeneratePassword PASSWORD [, SALT] | |
924 | ||
925 | Returns a string to store in the database. This string takes the form: | |
926 | ||
927 | !method!salt!hash | |
928 | ||
af59614d | 929 | By default, the method is currently C<bcrypt>. |
84fb5b46 MKG |
930 | |
931 | =cut | |
932 | ||
933 | sub _GeneratePassword { | |
934 | my $self = shift; | |
af59614d | 935 | return $self->_GeneratePassword_bcrypt(@_); |
84fb5b46 MKG |
936 | } |
937 | ||
938 | =head3 HasPassword | |
939 | ||
940 | Returns true if the user has a valid password, otherwise returns false. | |
941 | ||
942 | =cut | |
943 | ||
944 | sub HasPassword { | |
945 | my $self = shift; | |
946 | my $pwd = $self->__Value('Password'); | |
947 | return undef if !defined $pwd | |
948 | || $pwd eq '' | |
949 | || $pwd eq '*NO-PASSWORD*'; | |
950 | return 1; | |
951 | } | |
952 | ||
953 | =head3 IsPassword | |
954 | ||
955 | Returns true if the passed in value is this user's password. | |
956 | Returns undef otherwise. | |
957 | ||
958 | =cut | |
959 | ||
960 | sub IsPassword { | |
961 | my $self = shift; | |
962 | my $value = shift; | |
963 | ||
964 | #TODO there isn't any apparent way to legitimately ACL this | |
965 | ||
966 | # RT does not allow null passwords | |
967 | if ( ( !defined($value) ) or ( $value eq '' ) ) { | |
968 | return (undef); | |
969 | } | |
970 | ||
971 | if ( $self->PrincipalObj->Disabled ) { | |
972 | $RT::Logger->info( | |
973 | "Disabled user " . $self->Name . " tried to log in" ); | |
974 | return (undef); | |
975 | } | |
976 | ||
977 | unless ($self->HasPassword) { | |
978 | return(undef); | |
979 | } | |
980 | ||
981 | my $stored = $self->__Value('Password'); | |
982 | if ($stored =~ /^!/) { | |
983 | # If it's a new-style (>= RT 4.0) password, it starts with a '!' | |
af59614d MKG |
984 | my (undef, $method, @rest) = split /!/, $stored; |
985 | if ($method eq "bcrypt") { | |
986 | return 0 unless $self->_GeneratePassword_bcrypt($value, @rest) eq $stored; | |
987 | # Upgrade to a larger number of rounds if necessary | |
988 | return 1 unless $rest[0] < RT->Config->Get('BcryptCost'); | |
989 | } elsif ($method eq "sha512") { | |
990 | return 0 unless $self->_GeneratePassword_sha512($value, @rest) eq $stored; | |
84fb5b46 MKG |
991 | } else { |
992 | $RT::Logger->warn("Unknown hash method $method"); | |
993 | return 0; | |
994 | } | |
995 | } elsif (length $stored == 40) { | |
996 | # The truncated SHA256(salt,MD5(passwd)) form from 2010/12 is 40 characters long | |
997 | my $hash = MIME::Base64::decode_base64($stored); | |
998 | # Decoding yields 30 byes; first 4 are the salt, the rest are substr(SHA256,0,26) | |
999 | my $salt = substr($hash, 0, 4, ""); | |
c33a4027 | 1000 | return 0 unless substr(Digest::SHA::sha256($salt . Digest::MD5::md5(Encode::encode( "UTF-8", $value))), 0, 26) eq $hash; |
84fb5b46 MKG |
1001 | } elsif (length $stored == 32) { |
1002 | # Hex nonsalted-md5 | |
c33a4027 | 1003 | return 0 unless Digest::MD5::md5_hex(Encode::encode( "UTF-8", $value)) eq $stored; |
84fb5b46 MKG |
1004 | } elsif (length $stored == 22) { |
1005 | # Base64 nonsalted-md5 | |
c33a4027 | 1006 | return 0 unless Digest::MD5::md5_base64(Encode::encode( "UTF-8", $value)) eq $stored; |
84fb5b46 MKG |
1007 | } elsif (length $stored == 13) { |
1008 | # crypt() output | |
c33a4027 | 1009 | return 0 unless crypt(Encode::encode( "UTF-8", $value), $stored) eq $stored; |
84fb5b46 | 1010 | } else { |
dab09ea8 | 1011 | $RT::Logger->warning("Unknown password form"); |
84fb5b46 MKG |
1012 | return 0; |
1013 | } | |
1014 | ||
1015 | # We got here by validating successfully, but with a legacy | |
1016 | # password form. Update to the most recent form. | |
1017 | my $obj = $self->isa("RT::CurrentUser") ? $self->UserObj : $self; | |
1018 | $obj->_Set(Field => 'Password', Value => $self->_GeneratePassword($value) ); | |
1019 | return 1; | |
1020 | } | |
1021 | ||
1022 | sub CurrentUserRequireToSetPassword { | |
1023 | my $self = shift; | |
1024 | ||
1025 | my %res = ( | |
1026 | CanSet => 1, | |
1027 | Reason => '', | |
1028 | RequireCurrent => 1, | |
1029 | ); | |
1030 | ||
af59614d MKG |
1031 | if ( RT->Config->Get('WebRemoteUserAuth') |
1032 | && !RT->Config->Get('WebFallbackToRTLogin') | |
84fb5b46 MKG |
1033 | ) { |
1034 | $res{'CanSet'} = 0; | |
1035 | $res{'Reason'} = $self->loc("External authentication enabled."); | |
1036 | } elsif ( !$self->CurrentUser->HasPassword ) { | |
1037 | if ( $self->CurrentUser->id == ($self->id||0) ) { | |
1038 | # don't require current password if user has no | |
1039 | $res{'RequireCurrent'} = 0; | |
1040 | } else { | |
1041 | $res{'CanSet'} = 0; | |
1042 | $res{'Reason'} = $self->loc("Your password is not set."); | |
1043 | } | |
1044 | } | |
1045 | ||
1046 | return %res; | |
1047 | } | |
1048 | ||
1049 | =head3 AuthToken | |
1050 | ||
1051 | Returns an authentication string associated with the user. This | |
1052 | string can be used to generate passwordless URLs to integrate | |
1053 | RT with services and programms like callendar managers, rss | |
1054 | readers and other. | |
1055 | ||
1056 | =cut | |
1057 | ||
1058 | sub AuthToken { | |
1059 | my $self = shift; | |
1060 | my $secret = $self->_Value( AuthToken => @_ ); | |
1061 | return $secret if $secret; | |
1062 | ||
1063 | $secret = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16); | |
1064 | ||
1065 | my $tmp = RT::User->new( RT->SystemUser ); | |
1066 | $tmp->Load( $self->id ); | |
1067 | my ($status, $msg) = $tmp->SetAuthToken( $secret ); | |
1068 | unless ( $status ) { | |
1069 | $RT::Logger->error( "Couldn't set auth token: $msg" ); | |
1070 | return undef; | |
1071 | } | |
1072 | return $secret; | |
1073 | } | |
1074 | ||
1075 | =head3 GenerateAuthToken | |
1076 | ||
1077 | Generate a random authentication string for the user. | |
1078 | ||
1079 | =cut | |
1080 | ||
1081 | sub GenerateAuthToken { | |
1082 | my $self = shift; | |
1083 | my $token = substr(Digest::MD5::md5_hex(time . {} . rand()),0,16); | |
1084 | return $self->SetAuthToken( $token ); | |
1085 | } | |
1086 | ||
1087 | =head3 GenerateAuthString | |
1088 | ||
1089 | Takes a string and returns back a hex hash string. Later you can use | |
1090 | this pair to make sure it's generated by this user using L</ValidateAuthString> | |
1091 | ||
1092 | =cut | |
1093 | ||
1094 | sub GenerateAuthString { | |
1095 | my $self = shift; | |
1096 | my $protect = shift; | |
1097 | ||
c33a4027 | 1098 | my $str = Encode::encode( "UTF-8", $self->AuthToken . $protect ); |
84fb5b46 MKG |
1099 | |
1100 | return substr(Digest::MD5::md5_hex($str),0,16); | |
1101 | } | |
1102 | ||
1103 | =head3 ValidateAuthString | |
1104 | ||
1105 | Takes auth string and protected string. Returns true is protected string | |
1106 | has been protected by user's L</AuthToken>. See also L</GenerateAuthString>. | |
1107 | ||
1108 | =cut | |
1109 | ||
1110 | sub ValidateAuthString { | |
1111 | my $self = shift; | |
1112 | my $auth_string = shift; | |
1113 | my $protected = shift; | |
1114 | ||
c33a4027 | 1115 | my $str = Encode::encode( "UTF-8", $self->AuthToken . $protected ); |
84fb5b46 MKG |
1116 | |
1117 | return $auth_string eq substr(Digest::MD5::md5_hex($str),0,16); | |
1118 | } | |
1119 | ||
1120 | =head2 SetDisabled | |
1121 | ||
1122 | Toggles the user's disabled flag. | |
1123 | If this flag is | |
1124 | set, all password checks for this user will fail. All ACL checks for this | |
1125 | user will fail. The user will appear in no user listings. | |
1126 | ||
1127 | =cut | |
1128 | ||
1129 | sub SetDisabled { | |
1130 | my $self = shift; | |
1131 | my $val = shift; | |
1132 | unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) { | |
1133 | return (0, $self->loc('Permission Denied')); | |
1134 | } | |
1135 | ||
1136 | $RT::Handle->BeginTransaction(); | |
af59614d MKG |
1137 | my ($status, $msg) = $self->PrincipalObj->SetDisabled($val); |
1138 | unless ($status) { | |
84fb5b46 MKG |
1139 | $RT::Handle->Rollback(); |
1140 | $RT::Logger->warning(sprintf("Couldn't %s user %s", ($val == 1) ? "disable" : "enable", $self->PrincipalObj->Id)); | |
af59614d | 1141 | return ($status, $msg); |
84fb5b46 MKG |
1142 | } |
1143 | $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" ); | |
1144 | ||
1145 | $RT::Handle->Commit(); | |
1146 | ||
1147 | if ( $val == 1 ) { | |
1148 | return (1, $self->loc("User disabled")); | |
1149 | } else { | |
1150 | return (1, $self->loc("User enabled")); | |
1151 | } | |
1152 | ||
1153 | } | |
1154 | ||
1155 | =head2 Disabled | |
1156 | ||
1157 | Returns true if user is disabled or false otherwise | |
1158 | ||
1159 | =cut | |
1160 | ||
1161 | sub Disabled { | |
1162 | my $self = shift; | |
1163 | return $self->PrincipalObj->Disabled(@_); | |
1164 | } | |
1165 | ||
1166 | =head2 PrincipalObj | |
1167 | ||
1168 | Returns the principal object for this user. returns an empty RT::Principal | |
1169 | if there's no principal object matching this user. | |
1170 | The response is cached. PrincipalObj should never ever change. | |
1171 | ||
1172 | =cut | |
1173 | ||
1174 | sub PrincipalObj { | |
1175 | my $self = shift; | |
1176 | ||
1177 | unless ( $self->id ) { | |
1178 | $RT::Logger->error("Couldn't get principal for an empty user"); | |
1179 | return undef; | |
1180 | } | |
1181 | ||
1182 | if ( !$self->{_principal_obj} ) { | |
1183 | ||
1184 | my $obj = RT::Principal->new( $self->CurrentUser ); | |
1185 | $obj->LoadById( $self->id ); | |
1186 | if (! $obj->id ) { | |
1187 | $RT::Logger->crit( 'No principal for user #' . $self->id ); | |
1188 | return undef; | |
1189 | } elsif ( $obj->PrincipalType ne 'User' ) { | |
1190 | $RT::Logger->crit( 'User #' . $self->id . ' has principal of ' . $obj->PrincipalType . ' type' ); | |
1191 | return undef; | |
1192 | } | |
1193 | $self->{_principal_obj} = $obj; | |
1194 | } | |
1195 | return $self->{_principal_obj}; | |
1196 | } | |
1197 | ||
1198 | ||
1199 | =head2 PrincipalId | |
1200 | ||
1201 | Returns this user's PrincipalId | |
1202 | ||
1203 | =cut | |
1204 | ||
1205 | sub PrincipalId { | |
1206 | my $self = shift; | |
1207 | return $self->Id; | |
1208 | } | |
1209 | ||
1210 | =head2 HasGroupRight | |
1211 | ||
1212 | Takes a paramhash which can contain | |
1213 | these items: | |
1214 | GroupObj => RT::Group or Group => integer | |
1215 | Right => 'Right' | |
1216 | ||
1217 | ||
1218 | Returns 1 if this user has the right specified in the paramhash for the Group | |
1219 | passed in. | |
1220 | ||
1221 | Returns undef if they don't. | |
1222 | ||
1223 | =cut | |
1224 | ||
1225 | sub HasGroupRight { | |
1226 | my $self = shift; | |
1227 | my %args = ( | |
1228 | GroupObj => undef, | |
1229 | Group => undef, | |
1230 | Right => undef, | |
1231 | @_ | |
1232 | ); | |
1233 | ||
1234 | ||
1235 | if ( defined $args{'Group'} ) { | |
1236 | $args{'GroupObj'} = RT::Group->new( $self->CurrentUser ); | |
1237 | $args{'GroupObj'}->Load( $args{'Group'} ); | |
1238 | } | |
1239 | ||
1240 | # Validate and load up the GroupId | |
1241 | unless ( ( defined $args{'GroupObj'} ) and ( $args{'GroupObj'}->Id ) ) { | |
1242 | return undef; | |
1243 | } | |
1244 | ||
1245 | # Figure out whether a user has the right we're asking about. | |
1246 | my $retval = $self->HasRight( | |
1247 | Object => $args{'GroupObj'}, | |
1248 | Right => $args{'Right'}, | |
1249 | ); | |
1250 | ||
1251 | return ($retval); | |
1252 | } | |
1253 | ||
1254 | =head2 OwnGroups | |
1255 | ||
1256 | Returns a group collection object containing the groups of which this | |
1257 | user is a member. | |
1258 | ||
1259 | =cut | |
1260 | ||
1261 | sub OwnGroups { | |
1262 | my $self = shift; | |
1263 | my $groups = RT::Groups->new($self->CurrentUser); | |
1264 | $groups->LimitToUserDefinedGroups; | |
1265 | $groups->WithMember( | |
1266 | PrincipalId => $self->Id, | |
1267 | Recursively => 1 | |
1268 | ); | |
1269 | return $groups; | |
1270 | } | |
1271 | ||
1272 | =head2 HasRight | |
1273 | ||
1274 | Shim around PrincipalObj->HasRight. See L<RT::Principal>. | |
1275 | ||
1276 | =cut | |
1277 | ||
1278 | sub HasRight { | |
1279 | my $self = shift; | |
1280 | return $self->PrincipalObj->HasRight(@_); | |
1281 | } | |
1282 | ||
1283 | =head2 CurrentUserCanSee [FIELD] | |
1284 | ||
1285 | Returns true if the current user can see the user, based on if it is | |
1286 | public, ourself, or we have AdminUsers | |
1287 | ||
1288 | =cut | |
1289 | ||
1290 | sub CurrentUserCanSee { | |
1291 | my $self = shift; | |
af59614d | 1292 | my ($what, $txn) = @_; |
84fb5b46 | 1293 | |
af59614d MKG |
1294 | # If it's a public property, fine |
1295 | return 1 if $self->_Accessible( $what, 'public' ); | |
84fb5b46 | 1296 | |
af59614d MKG |
1297 | # Users can see all of their own properties |
1298 | return 1 if defined($self->Id) and $self->CurrentUser->Id == $self->Id; | |
84fb5b46 MKG |
1299 | |
1300 | # If the user has the admin users right, that's also enough | |
af59614d MKG |
1301 | return 1 if $self->CurrentUserHasRight( 'AdminUsers' ); |
1302 | ||
1303 | # Transactions of public properties are visible to users with ShowUserHistory | |
1304 | if ($what eq "Transaction" and $self->CurrentUserHasRight( 'ShowUserHistory' )) { | |
1305 | my $type = $txn->__Value('Type'); | |
1306 | my $field = $txn->__Value('Field'); | |
1307 | return 1 if $type eq "Set" and $self->CurrentUserCanSee($field, $txn); | |
1308 | ||
1309 | # RT::Transaction->CurrentUserCanSee deals with ensuring we meet | |
1310 | # the ACLs on CFs, so allow them here | |
1311 | return 1 if $type eq "CustomField"; | |
84fb5b46 | 1312 | } |
af59614d MKG |
1313 | |
1314 | return 0; | |
84fb5b46 MKG |
1315 | } |
1316 | ||
1317 | =head2 CurrentUserCanModify RIGHT | |
1318 | ||
1319 | If the user has rights for this object, either because | |
1320 | he has 'AdminUsers' or (if he's trying to edit himself and the right isn't an | |
1321 | admin right) 'ModifySelf', return 1. otherwise, return undef. | |
1322 | ||
1323 | =cut | |
1324 | ||
1325 | sub CurrentUserCanModify { | |
1326 | my $self = shift; | |
1327 | my $field = shift; | |
1328 | ||
1329 | if ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) { | |
1330 | return (1); | |
1331 | } | |
1332 | ||
1333 | #If the field is marked as an "administrators only" field, | |
1334 | # don't let the user touch it. | |
1335 | elsif ( $self->_Accessible( $field, 'admin' ) ) { | |
1336 | return (undef); | |
1337 | } | |
1338 | ||
1339 | #If the current user is trying to modify themselves | |
1340 | elsif ( ( $self->id == $self->CurrentUser->id ) | |
1341 | and ( $self->CurrentUser->HasRight(Right => 'ModifySelf', Object => $RT::System) ) ) | |
1342 | { | |
1343 | return (1); | |
1344 | } | |
1345 | ||
1346 | #If we don't have a good reason to grant them rights to modify | |
1347 | # by now, they lose | |
1348 | else { | |
1349 | return (undef); | |
1350 | } | |
1351 | ||
1352 | } | |
1353 | ||
1354 | =head2 CurrentUserHasRight | |
1355 | ||
1356 | Takes a single argument. returns 1 if $Self->CurrentUser | |
1357 | has the requested right. returns undef otherwise | |
1358 | ||
1359 | =cut | |
1360 | ||
1361 | sub CurrentUserHasRight { | |
1362 | my $self = shift; | |
1363 | my $right = shift; | |
1364 | ||
1365 | return ( $self->CurrentUser->HasRight(Right => $right, Object => $RT::System) ); | |
1366 | } | |
1367 | ||
1368 | sub _PrefName { | |
1369 | my $name = shift; | |
1370 | if (ref $name) { | |
1371 | $name = ref($name).'-'.$name->Id; | |
1372 | } | |
1373 | ||
af59614d | 1374 | return 'Pref-'. $name; |
84fb5b46 MKG |
1375 | } |
1376 | ||
1377 | =head2 Preferences NAME/OBJ DEFAULT | |
1378 | ||
1379 | Obtain user preferences associated with given object or name. | |
1380 | Returns DEFAULT if no preferences found. If DEFAULT is a hashref, | |
1381 | override the entries with user preferences. | |
1382 | ||
1383 | =cut | |
1384 | ||
1385 | sub Preferences { | |
1386 | my $self = shift; | |
af59614d | 1387 | my $name = _PrefName(shift); |
84fb5b46 MKG |
1388 | my $default = shift; |
1389 | ||
c33a4027 MKG |
1390 | my ($attr) = $self->Attributes->Named( $name ); |
1391 | my $content = $attr ? $attr->Content : undef; | |
84fb5b46 MKG |
1392 | unless ( ref $content eq 'HASH' ) { |
1393 | return defined $content ? $content : $default; | |
1394 | } | |
1395 | ||
1396 | if (ref $default eq 'HASH') { | |
1397 | for (keys %$default) { | |
1398 | exists $content->{$_} or $content->{$_} = $default->{$_}; | |
1399 | } | |
1400 | } elsif (defined $default) { | |
af59614d | 1401 | $RT::Logger->error("Preferences $name for user #".$self->Id." is hash but default is not"); |
84fb5b46 MKG |
1402 | } |
1403 | return $content; | |
1404 | } | |
1405 | ||
1406 | =head2 SetPreferences NAME/OBJ VALUE | |
1407 | ||
1408 | Set user preferences associated with given object or name. | |
1409 | ||
1410 | =cut | |
1411 | ||
1412 | sub SetPreferences { | |
1413 | my $self = shift; | |
1414 | my $name = _PrefName( shift ); | |
1415 | my $value = shift; | |
1416 | ||
1417 | return (0, $self->loc("No permission to set preferences")) | |
1418 | unless $self->CurrentUserCanModify('Preferences'); | |
1419 | ||
c33a4027 MKG |
1420 | my ($attr) = $self->Attributes->Named( $name ); |
1421 | if ( $attr ) { | |
84fb5b46 MKG |
1422 | my ($ok, $msg) = $attr->SetContent( $value ); |
1423 | return (1, "No updates made") | |
1424 | if $msg eq "That is already the current value"; | |
1425 | return ($ok, $msg); | |
1426 | } else { | |
1427 | return $self->AddAttribute( Name => $name, Content => $value ); | |
1428 | } | |
1429 | } | |
1430 | ||
c33a4027 MKG |
1431 | =head2 DeletePreferences NAME/OBJ VALUE |
1432 | ||
1433 | Delete user preferences associated with given object or name. | |
1434 | ||
1435 | =cut | |
1436 | ||
1437 | sub DeletePreferences { | |
1438 | my $self = shift; | |
1439 | my $name = _PrefName( shift ); | |
1440 | ||
1441 | return (0, $self->loc("No permission to set preferences")) | |
1442 | unless $self->CurrentUserCanModify('Preferences'); | |
1443 | ||
1444 | my ($attr) = $self->DeleteAttribute( $name ); | |
1445 | return (0, $self->loc("Preferences were not found")) | |
1446 | unless $attr; | |
1447 | ||
1448 | return 1; | |
1449 | } | |
1450 | ||
84fb5b46 MKG |
1451 | =head2 Stylesheet |
1452 | ||
1453 | Returns a list of valid stylesheets take from preferences. | |
1454 | ||
1455 | =cut | |
1456 | ||
1457 | sub Stylesheet { | |
1458 | my $self = shift; | |
1459 | ||
1460 | my $style = RT->Config->Get('WebDefaultStylesheet', $self->CurrentUser); | |
1461 | ||
1462 | if (RT::Interface::Web->ComponentPathIsSafe($style)) { | |
af59614d MKG |
1463 | for my $root (RT::Interface::Web->StaticRoots) { |
1464 | if (-d "$root/css/$style") { | |
84fb5b46 MKG |
1465 | return $style |
1466 | } | |
1467 | } | |
1468 | } | |
1469 | ||
1470 | # Fall back to the system stylesheet. | |
1471 | return RT->Config->Get('WebDefaultStylesheet'); | |
1472 | } | |
1473 | ||
1474 | =head2 WatchedQueues ROLE_LIST | |
1475 | ||
1476 | Returns a RT::Queues object containing every queue watched by the user. | |
1477 | ||
1478 | Takes a list of roles which is some subset of ('Cc', 'AdminCc'). Defaults to: | |
1479 | ||
1480 | $user->WatchedQueues('Cc', 'AdminCc'); | |
1481 | ||
1482 | =cut | |
1483 | ||
1484 | sub WatchedQueues { | |
1485 | ||
1486 | my $self = shift; | |
5b0d0914 | 1487 | my @roles = @_ ? @_ : ('Cc', 'AdminCc'); |
84fb5b46 MKG |
1488 | |
1489 | $RT::Logger->debug('WatcheQueues got user ' . $self->Name); | |
1490 | ||
1491 | my $watched_queues = RT::Queues->new($self->CurrentUser); | |
1492 | ||
1493 | my $group_alias = $watched_queues->Join( | |
1494 | ALIAS1 => 'main', | |
1495 | FIELD1 => 'id', | |
1496 | TABLE2 => 'Groups', | |
1497 | FIELD2 => 'Instance', | |
1498 | ); | |
1499 | ||
1500 | $watched_queues->Limit( | |
1501 | ALIAS => $group_alias, | |
1502 | FIELD => 'Domain', | |
1503 | VALUE => 'RT::Queue-Role', | |
1504 | ENTRYAGGREGATOR => 'AND', | |
af59614d | 1505 | CASESENSITIVE => 0, |
84fb5b46 MKG |
1506 | ); |
1507 | if (grep { $_ eq 'Cc' } @roles) { | |
1508 | $watched_queues->Limit( | |
1509 | SUBCLAUSE => 'LimitToWatchers', | |
1510 | ALIAS => $group_alias, | |
af59614d | 1511 | FIELD => 'Name', |
84fb5b46 MKG |
1512 | VALUE => 'Cc', |
1513 | ENTRYAGGREGATOR => 'OR', | |
1514 | ); | |
1515 | } | |
1516 | if (grep { $_ eq 'AdminCc' } @roles) { | |
1517 | $watched_queues->Limit( | |
1518 | SUBCLAUSE => 'LimitToWatchers', | |
1519 | ALIAS => $group_alias, | |
af59614d | 1520 | FIELD => 'Name', |
84fb5b46 MKG |
1521 | VALUE => 'AdminCc', |
1522 | ENTRYAGGREGATOR => 'OR', | |
1523 | ); | |
1524 | } | |
1525 | ||
1526 | my $queues_alias = $watched_queues->Join( | |
1527 | ALIAS1 => $group_alias, | |
1528 | FIELD1 => 'id', | |
1529 | TABLE2 => 'CachedGroupMembers', | |
1530 | FIELD2 => 'GroupId', | |
1531 | ); | |
1532 | $watched_queues->Limit( | |
1533 | ALIAS => $queues_alias, | |
1534 | FIELD => 'MemberId', | |
1535 | VALUE => $self->PrincipalId, | |
1536 | ); | |
1537 | $watched_queues->Limit( | |
1538 | ALIAS => $queues_alias, | |
1539 | FIELD => 'Disabled', | |
1540 | VALUE => 0, | |
1541 | ); | |
1542 | ||
1543 | ||
1544 | $RT::Logger->debug("WatchedQueues got " . $watched_queues->Count . " queues"); | |
1545 | ||
1546 | return $watched_queues; | |
1547 | ||
1548 | } | |
1549 | ||
1550 | sub _Set { | |
1551 | my $self = shift; | |
1552 | ||
1553 | my %args = ( | |
1554 | Field => undef, | |
1555 | Value => undef, | |
1556 | TransactionType => 'Set', | |
1557 | RecordTransaction => 1, | |
1558 | @_ | |
1559 | ); | |
1560 | ||
1561 | # Nobody is allowed to futz with RT_System or Nobody | |
1562 | ||
1563 | if ( ($self->Id == RT->SystemUser->Id ) || | |
1564 | ($self->Id == RT->Nobody->Id)) { | |
1565 | return ( 0, $self->loc("Can not modify system users") ); | |
1566 | } | |
1567 | unless ( $self->CurrentUserCanModify( $args{'Field'} ) ) { | |
1568 | return ( 0, $self->loc("Permission Denied") ); | |
1569 | } | |
1570 | ||
1571 | my $Old = $self->SUPER::_Value("$args{'Field'}"); | |
1572 | ||
1573 | my ($ret, $msg) = $self->SUPER::_Set( Field => $args{'Field'}, | |
1574 | Value => $args{'Value'} ); | |
1575 | ||
1576 | #If we can't actually set the field to the value, don't record | |
1577 | # a transaction. instead, get out of here. | |
1578 | if ( $ret == 0 ) { return ( 0, $msg ); } | |
1579 | ||
1580 | if ( $args{'RecordTransaction'} == 1 ) { | |
1581 | if ($args{'Field'} eq "Password") { | |
1582 | $args{'Value'} = $Old = '********'; | |
1583 | } | |
1584 | my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction( | |
1585 | Type => $args{'TransactionType'}, | |
1586 | Field => $args{'Field'}, | |
1587 | NewValue => $args{'Value'}, | |
1588 | OldValue => $Old, | |
1589 | TimeTaken => $args{'TimeTaken'}, | |
1590 | ); | |
1591 | return ( $Trans, scalar $TransObj->BriefDescription ); | |
1592 | } else { | |
1593 | return ( $ret, $msg ); | |
1594 | } | |
1595 | } | |
1596 | ||
1597 | =head2 _Value | |
1598 | ||
1599 | Takes the name of a table column. | |
1600 | Returns its value as a string, if the user passes an ACL check | |
1601 | ||
1602 | =cut | |
1603 | ||
1604 | sub _Value { | |
1605 | ||
1606 | my $self = shift; | |
1607 | my $field = shift; | |
1608 | ||
1609 | # Defer to the abstraction above to know if the field can be read | |
1610 | return $self->SUPER::_Value($field) if $self->CurrentUserCanSee($field); | |
1611 | return undef; | |
1612 | } | |
1613 | ||
1614 | =head2 FriendlyName | |
1615 | ||
1616 | Return the friendly name | |
1617 | ||
1618 | =cut | |
1619 | ||
1620 | sub FriendlyName { | |
1621 | my $self = shift; | |
af59614d MKG |
1622 | return $self->RealName if defined $self->RealName and length $self->RealName; |
1623 | return $self->Name; | |
1624 | } | |
1625 | ||
1626 | =head2 Format | |
1627 | ||
1628 | Class or object method. | |
1629 | ||
1630 | Returns a string describing a user in the current user's preferred format. | |
1631 | ||
1632 | May be invoked in three ways: | |
1633 | ||
1634 | $UserObj->Format; | |
1635 | RT::User->Format( User => $UserObj ); # same as above | |
1636 | RT::User->Format( Address => $AddressObj, CurrentUser => $CurrentUserObj ); | |
1637 | ||
1638 | Possible arguments are: | |
1639 | ||
1640 | =over | |
1641 | ||
1642 | =item User | |
1643 | ||
1644 | An L<RT::User> object representing the user to format. Preferred to Address. | |
1645 | ||
1646 | =item Address | |
1647 | ||
1648 | An L<Email::Address> object representing the user address to format. Address | |
1649 | will be used to lookup an L<RT::User> if possible. | |
1650 | ||
1651 | =item CurrentUser | |
1652 | ||
1653 | Required when Format is called as a class method with an Address argument. | |
1654 | Otherwise, this argument is ignored in preference to the CurrentUser of the | |
1655 | involved L<RT::User> object. | |
1656 | ||
1657 | =item Format | |
1658 | ||
1659 | Specifies the format to use, overriding any set from the config or current | |
1660 | user's preferences. | |
1661 | ||
1662 | =back | |
1663 | ||
1664 | =cut | |
1665 | ||
1666 | sub Format { | |
1667 | my $self = shift; | |
1668 | my %args = ( | |
1669 | User => undef, | |
1670 | Address => undef, | |
1671 | CurrentUser => undef, | |
1672 | Format => undef, | |
1673 | @_ | |
1674 | ); | |
1675 | ||
1676 | if (blessed($self) and $self->id) { | |
1677 | @args{"User", "CurrentUser"} = ($self, $self->CurrentUser); | |
1678 | } | |
1679 | elsif ($args{User} and $args{User}->id) { | |
1680 | $args{CurrentUser} = $args{User}->CurrentUser; | |
1681 | } | |
1682 | elsif ($args{Address} and $args{CurrentUser}) { | |
1683 | $args{User} = RT::User->new( $args{CurrentUser} ); | |
1684 | $args{User}->LoadByEmail( $args{Address}->address ); | |
1685 | if ($args{User}->id) { | |
1686 | delete $args{Address}; | |
1687 | } else { | |
1688 | delete $args{User}; | |
1689 | } | |
1690 | } | |
1691 | else { | |
1692 | RT->Logger->warning("Invalid arguments to RT::User->Format at @{[join '/', caller]}"); | |
1693 | return ""; | |
1694 | } | |
1695 | ||
1696 | $args{Format} ||= RT->Config->Get("UsernameFormat", $args{CurrentUser}); | |
1697 | $args{Format} =~ s/[^A-Za-z0-9_]+//g; | |
1698 | ||
1699 | my $method = "_FormatUser" . ucfirst lc $args{Format}; | |
1700 | my $formatter = $self->can($method); | |
1701 | ||
1702 | unless ($formatter) { | |
1703 | RT->Logger->error( | |
1704 | "Either system config or user #" . $args{CurrentUser}->id . | |
1705 | " picked UsernameFormat $args{Format}, but RT::User->$method doesn't exist" | |
1706 | ); | |
1707 | $formatter = $self->can("_FormatUserRole"); | |
1708 | } | |
1709 | return $formatter->( $self, map { $_ => $args{$_} } qw(User Address) ); | |
1710 | } | |
1711 | ||
1712 | sub _FormatUserRole { | |
1713 | my $self = shift; | |
1714 | my %args = @_; | |
1715 | ||
1716 | my $user = $args{User}; | |
1717 | return $self->_FormatUserVerbose(@_) | |
1718 | unless $user and $user->Privileged; | |
1719 | ||
1720 | my $name = $user->Name; | |
1721 | $name .= " (".$user->RealName.")" | |
1722 | if $user->RealName and lc $user->RealName ne lc $user->Name; | |
1723 | return $name; | |
1724 | } | |
1725 | ||
1726 | sub _FormatUserConcise { | |
1727 | my $self = shift; | |
1728 | my %args = @_; | |
1729 | return $args{User} ? $args{User}->FriendlyName : $args{Address}->address; | |
1730 | } | |
1731 | ||
1732 | sub _FormatUserVerbose { | |
1733 | my $self = shift; | |
1734 | my %args = @_; | |
1735 | my ($user, $address) = @args{"User", "Address"}; | |
1736 | ||
1737 | my $email = ''; | |
1738 | my $phrase = ''; | |
1739 | my $comment = ''; | |
1740 | ||
1741 | if ($user) { | |
1742 | $email = $user->EmailAddress || ''; | |
1743 | $phrase = $user->RealName if $user->RealName and lc $user->RealName ne lc $email; | |
1744 | $comment = $user->Name if lc $user->Name ne lc $email; | |
1745 | } else { | |
1746 | ($email, $phrase, $comment) = (map { $address->$_ } "address", "phrase", "comment"); | |
1747 | } | |
1748 | ||
1749 | return join " ", grep { $_ } ($phrase || $comment || ''), ($email ? "<$email>" : ""); | |
84fb5b46 MKG |
1750 | } |
1751 | ||
1752 | =head2 PreferredKey | |
1753 | ||
1754 | Returns the preferred key of the user. If none is set, then this will query | |
1755 | GPG and set the preferred key to the maximally trusted key found (and then | |
1756 | return it). Returns C<undef> if no preferred key can be found. | |
1757 | ||
1758 | =cut | |
1759 | ||
1760 | sub PreferredKey | |
1761 | { | |
1762 | my $self = shift; | |
1763 | return undef unless RT->Config->Get('GnuPG')->{'Enable'}; | |
1764 | ||
1765 | if ( ($self->CurrentUser->Id != $self->Id ) && | |
1766 | !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) { | |
1767 | return undef; | |
1768 | } | |
1769 | ||
1770 | ||
1771 | ||
1772 | my $prefkey = $self->FirstAttribute('PreferredKey'); | |
1773 | return $prefkey->Content if $prefkey; | |
1774 | ||
1775 | # we don't have a preferred key for this user, so now we must query GPG | |
af59614d | 1776 | my %res = RT::Crypt->GetKeysForEncryption($self->EmailAddress); |
84fb5b46 MKG |
1777 | return undef unless defined $res{'info'}; |
1778 | my @keys = @{ $res{'info'} }; | |
1779 | return undef if @keys == 0; | |
1780 | ||
1781 | if (@keys == 1) { | |
af59614d | 1782 | $prefkey = $keys[0]->{'id'} || $keys[0]->{'Fingerprint'}; |
84fb5b46 MKG |
1783 | } else { |
1784 | # prefer the maximally trusted key | |
1785 | @keys = sort { $b->{'TrustLevel'} <=> $a->{'TrustLevel'} } @keys; | |
af59614d | 1786 | $prefkey = $keys[0]->{'id'} || $keys[0]->{'Fingerprint'}; |
84fb5b46 MKG |
1787 | } |
1788 | ||
1789 | $self->SetAttribute(Name => 'PreferredKey', Content => $prefkey); | |
1790 | return $prefkey; | |
1791 | } | |
1792 | ||
1793 | sub PrivateKey { | |
1794 | my $self = shift; | |
1795 | ||
1796 | ||
1797 | #If the user wants to see their own values, let them. | |
1798 | #If the user is an admin, let them. | |
1799 | #Otherwwise, don't let them. | |
1800 | # | |
1801 | if ( ($self->CurrentUser->Id != $self->Id ) && | |
1802 | !$self->CurrentUser->HasRight(Right =>'AdminUsers', Object => $RT::System) ) { | |
1803 | return undef; | |
1804 | } | |
1805 | ||
1806 | my $key = $self->FirstAttribute('PrivateKey') or return undef; | |
1807 | return $key->Content; | |
1808 | } | |
1809 | ||
1810 | sub SetPrivateKey { | |
1811 | my $self = shift; | |
1812 | my $key = shift; | |
1813 | ||
c33a4027 MKG |
1814 | # Users should not be able to change their own PrivateKey values |
1815 | unless ( $self->CurrentUser->HasRight(Right => 'AdminUsers', Object => $RT::System) ) { | |
84fb5b46 MKG |
1816 | return (0, $self->loc("Permission Denied")); |
1817 | } | |
1818 | ||
1819 | unless ( $key ) { | |
1820 | my ($status, $msg) = $self->DeleteAttribute('PrivateKey'); | |
1821 | unless ( $status ) { | |
1822 | $RT::Logger->error( "Couldn't delete attribute: $msg" ); | |
1823 | return ($status, $self->loc("Couldn't unset private key")); | |
1824 | } | |
1825 | return ($status, $self->loc("Unset private key")); | |
1826 | } | |
1827 | ||
1828 | # check that it's really private key | |
1829 | { | |
af59614d | 1830 | my %tmp = RT::Crypt->GetKeysForSigning( Signer => $key, Protocol => 'GnuPG' ); |
84fb5b46 MKG |
1831 | return (0, $self->loc("No such key or it's not suitable for signing")) |
1832 | if $tmp{'exit_code'} || !$tmp{'info'}; | |
1833 | } | |
1834 | ||
1835 | my ($status, $msg) = $self->SetAttribute( | |
1836 | Name => 'PrivateKey', | |
1837 | Content => $key, | |
1838 | ); | |
1839 | return ($status, $self->loc("Couldn't set private key")) | |
1840 | unless $status; | |
1841 | return ($status, $self->loc("Set private key")); | |
1842 | } | |
1843 | ||
af59614d MKG |
1844 | sub SetLang { |
1845 | my $self = shift; | |
1846 | my ($lang) = @_; | |
1847 | ||
1848 | unless ($self->CurrentUserCanModify('Lang')) { | |
1849 | return (0, $self->loc("Permission Denied")); | |
1850 | } | |
1851 | ||
1852 | # Local hack to cause the result message to be in the _new_ language | |
1853 | # if we're updating ourselves | |
1854 | $self->CurrentUser->{LangHandle} = RT::I18N->get_handle( $lang ) | |
1855 | if $self->CurrentUser->id == $self->id; | |
1856 | return $self->_Set( Field => 'Lang', Value => $lang ); | |
1857 | } | |
1858 | ||
84fb5b46 MKG |
1859 | sub BasicColumns { |
1860 | ( | |
1861 | [ Name => 'Username' ], | |
1862 | [ EmailAddress => 'Email' ], | |
1863 | [ RealName => 'Name' ], | |
1864 | [ Organization => 'Organization' ], | |
1865 | ); | |
1866 | } | |
1867 | ||
af59614d MKG |
1868 | =head2 Bookmarks |
1869 | ||
1870 | Returns an unordered list of IDs representing the user's bookmarked tickets. | |
1871 | ||
1872 | =cut | |
1873 | ||
1874 | sub Bookmarks { | |
1875 | my $self = shift; | |
1876 | my $bookmarks = $self->FirstAttribute('Bookmarks'); | |
1877 | return if !$bookmarks; | |
1878 | ||
1879 | $bookmarks = $bookmarks->Content; | |
1880 | return if !$bookmarks; | |
1881 | ||
1882 | return keys %$bookmarks; | |
1883 | } | |
1884 | ||
1885 | =head2 HasBookmark TICKET | |
1886 | ||
1887 | Returns whether the provided ticket is bookmarked by the user. | |
1888 | ||
1889 | =cut | |
1890 | ||
1891 | sub HasBookmark { | |
1892 | my $self = shift; | |
1893 | my $ticket = shift; | |
1894 | my $id = $ticket->id; | |
1895 | ||
1896 | # maintain bookmarks across merges | |
1897 | my @ids = ($id, $ticket->Merged); | |
1898 | ||
1899 | my $bookmarks = $self->FirstAttribute('Bookmarks'); | |
1900 | $bookmarks = $bookmarks ? $bookmarks->Content : {}; | |
1901 | ||
1902 | my @bookmarked = grep { $bookmarks->{ $_ } } @ids; | |
1903 | return @bookmarked ? 1 : 0; | |
1904 | } | |
1905 | ||
1906 | =head2 ToggleBookmark TICKET | |
1907 | ||
1908 | Toggles whether the provided ticket is bookmarked by the user. | |
1909 | ||
1910 | =cut | |
1911 | ||
1912 | sub ToggleBookmark { | |
1913 | my $self = shift; | |
1914 | my $ticket = shift; | |
1915 | my $id = $ticket->id; | |
1916 | ||
1917 | # maintain bookmarks across merges | |
1918 | my @ids = ($id, $ticket->Merged); | |
1919 | ||
1920 | my $bookmarks = $self->FirstAttribute('Bookmarks'); | |
1921 | $bookmarks = $bookmarks ? $bookmarks->Content : {}; | |
1922 | ||
1923 | my $is_bookmarked; | |
1924 | ||
1925 | if ( grep { $bookmarks->{ $_ } } @ids ) { | |
1926 | delete $bookmarks->{ $_ } foreach @ids; | |
1927 | $is_bookmarked = 0; | |
1928 | } else { | |
1929 | $bookmarks->{ $id } = 1; | |
1930 | $is_bookmarked = 1; | |
1931 | } | |
1932 | ||
1933 | $self->SetAttribute( | |
1934 | Name => 'Bookmarks', | |
1935 | Content => $bookmarks, | |
1936 | ); | |
1937 | ||
1938 | return $is_bookmarked; | |
1939 | } | |
1940 | ||
84fb5b46 MKG |
1941 | =head2 Create PARAMHASH |
1942 | ||
1943 | Create takes a hash of values and creates a row in the database: | |
1944 | ||
1945 | varchar(200) 'Name'. | |
1946 | varbinary(256) 'Password'. | |
1947 | varchar(16) 'AuthToken'. | |
1948 | text 'Comments'. | |
1949 | text 'Signature'. | |
1950 | varchar(120) 'EmailAddress'. | |
1951 | text 'FreeformContactInfo'. | |
1952 | varchar(200) 'Organization'. | |
1953 | varchar(120) 'RealName'. | |
1954 | varchar(16) 'NickName'. | |
1955 | varchar(16) 'Lang'. | |
1956 | varchar(16) 'EmailEncoding'. | |
1957 | varchar(16) 'WebEncoding'. | |
1958 | varchar(100) 'ExternalContactInfoId'. | |
1959 | varchar(30) 'ContactInfoSystem'. | |
1960 | varchar(100) 'ExternalAuthId'. | |
1961 | varchar(30) 'AuthSystem'. | |
1962 | varchar(16) 'Gecos'. | |
1963 | varchar(30) 'HomePhone'. | |
1964 | varchar(30) 'WorkPhone'. | |
1965 | varchar(30) 'MobilePhone'. | |
1966 | varchar(30) 'PagerPhone'. | |
1967 | varchar(200) 'Address1'. | |
1968 | varchar(200) 'Address2'. | |
1969 | varchar(100) 'City'. | |
1970 | varchar(100) 'State'. | |
1971 | varchar(16) 'Zip'. | |
1972 | varchar(50) 'Country'. | |
1973 | varchar(50) 'Timezone'. | |
1974 | text 'PGPKey'. | |
1975 | ||
1976 | =cut | |
1977 | ||
1978 | ||
1979 | ||
1980 | ||
1981 | =head2 id | |
1982 | ||
1983 | Returns the current value of id. | |
1984 | (In the database, id is stored as int(11).) | |
1985 | ||
1986 | ||
1987 | =cut | |
1988 | ||
1989 | ||
1990 | =head2 Name | |
1991 | ||
1992 | Returns the current value of Name. | |
1993 | (In the database, Name is stored as varchar(200).) | |
1994 | ||
1995 | ||
1996 | ||
1997 | =head2 SetName VALUE | |
1998 | ||
1999 | ||
2000 | Set Name to VALUE. | |
2001 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2002 | (In the database, Name will be stored as a varchar(200).) | |
2003 | ||
2004 | ||
2005 | =cut | |
2006 | ||
2007 | ||
2008 | =head2 Password | |
2009 | ||
2010 | Returns the current value of Password. | |
2011 | (In the database, Password is stored as varchar(256).) | |
2012 | ||
2013 | ||
2014 | ||
2015 | =head2 SetPassword VALUE | |
2016 | ||
2017 | ||
2018 | Set Password to VALUE. | |
2019 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2020 | (In the database, Password will be stored as a varchar(256).) | |
2021 | ||
2022 | ||
2023 | =cut | |
2024 | ||
2025 | ||
2026 | =head2 AuthToken | |
2027 | ||
2028 | Returns the current value of AuthToken. | |
2029 | (In the database, AuthToken is stored as varchar(16).) | |
2030 | ||
2031 | ||
2032 | ||
2033 | =head2 SetAuthToken VALUE | |
2034 | ||
2035 | ||
2036 | Set AuthToken to VALUE. | |
2037 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2038 | (In the database, AuthToken will be stored as a varchar(16).) | |
2039 | ||
2040 | ||
2041 | =cut | |
2042 | ||
2043 | ||
2044 | =head2 Comments | |
2045 | ||
2046 | Returns the current value of Comments. | |
2047 | (In the database, Comments is stored as text.) | |
2048 | ||
2049 | ||
2050 | ||
2051 | =head2 SetComments VALUE | |
2052 | ||
2053 | ||
2054 | Set Comments to VALUE. | |
2055 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2056 | (In the database, Comments will be stored as a text.) | |
2057 | ||
2058 | ||
2059 | =cut | |
2060 | ||
2061 | ||
2062 | =head2 Signature | |
2063 | ||
2064 | Returns the current value of Signature. | |
2065 | (In the database, Signature is stored as text.) | |
2066 | ||
2067 | ||
2068 | ||
2069 | =head2 SetSignature VALUE | |
2070 | ||
2071 | ||
2072 | Set Signature to VALUE. | |
2073 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2074 | (In the database, Signature will be stored as a text.) | |
2075 | ||
2076 | ||
2077 | =cut | |
2078 | ||
2079 | ||
2080 | =head2 EmailAddress | |
2081 | ||
2082 | Returns the current value of EmailAddress. | |
2083 | (In the database, EmailAddress is stored as varchar(120).) | |
2084 | ||
2085 | ||
2086 | ||
2087 | =head2 SetEmailAddress VALUE | |
2088 | ||
2089 | ||
2090 | Set EmailAddress to VALUE. | |
2091 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2092 | (In the database, EmailAddress will be stored as a varchar(120).) | |
2093 | ||
2094 | ||
2095 | =cut | |
2096 | ||
2097 | ||
2098 | =head2 FreeformContactInfo | |
2099 | ||
2100 | Returns the current value of FreeformContactInfo. | |
2101 | (In the database, FreeformContactInfo is stored as text.) | |
2102 | ||
2103 | ||
2104 | ||
2105 | =head2 SetFreeformContactInfo VALUE | |
2106 | ||
2107 | ||
2108 | Set FreeformContactInfo to VALUE. | |
2109 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2110 | (In the database, FreeformContactInfo will be stored as a text.) | |
2111 | ||
2112 | ||
2113 | =cut | |
2114 | ||
2115 | ||
2116 | =head2 Organization | |
2117 | ||
2118 | Returns the current value of Organization. | |
2119 | (In the database, Organization is stored as varchar(200).) | |
2120 | ||
2121 | ||
2122 | ||
2123 | =head2 SetOrganization VALUE | |
2124 | ||
2125 | ||
2126 | Set Organization to VALUE. | |
2127 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2128 | (In the database, Organization will be stored as a varchar(200).) | |
2129 | ||
2130 | ||
2131 | =cut | |
2132 | ||
2133 | ||
2134 | =head2 RealName | |
2135 | ||
2136 | Returns the current value of RealName. | |
2137 | (In the database, RealName is stored as varchar(120).) | |
2138 | ||
2139 | ||
2140 | ||
2141 | =head2 SetRealName VALUE | |
2142 | ||
2143 | ||
2144 | Set RealName to VALUE. | |
2145 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2146 | (In the database, RealName will be stored as a varchar(120).) | |
2147 | ||
2148 | ||
2149 | =cut | |
2150 | ||
2151 | ||
2152 | =head2 NickName | |
2153 | ||
2154 | Returns the current value of NickName. | |
2155 | (In the database, NickName is stored as varchar(16).) | |
2156 | ||
2157 | ||
2158 | ||
2159 | =head2 SetNickName VALUE | |
2160 | ||
2161 | ||
2162 | Set NickName to VALUE. | |
2163 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2164 | (In the database, NickName will be stored as a varchar(16).) | |
2165 | ||
2166 | ||
2167 | =cut | |
2168 | ||
2169 | ||
2170 | =head2 Lang | |
2171 | ||
2172 | Returns the current value of Lang. | |
2173 | (In the database, Lang is stored as varchar(16).) | |
2174 | ||
2175 | ||
2176 | ||
2177 | =head2 SetLang VALUE | |
2178 | ||
2179 | ||
2180 | Set Lang to VALUE. | |
2181 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2182 | (In the database, Lang will be stored as a varchar(16).) | |
2183 | ||
2184 | ||
2185 | =cut | |
2186 | ||
2187 | ||
2188 | =head2 EmailEncoding | |
2189 | ||
2190 | Returns the current value of EmailEncoding. | |
2191 | (In the database, EmailEncoding is stored as varchar(16).) | |
2192 | ||
2193 | ||
2194 | ||
2195 | =head2 SetEmailEncoding VALUE | |
2196 | ||
2197 | ||
2198 | Set EmailEncoding to VALUE. | |
2199 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2200 | (In the database, EmailEncoding will be stored as a varchar(16).) | |
2201 | ||
2202 | ||
2203 | =cut | |
2204 | ||
2205 | ||
2206 | =head2 WebEncoding | |
2207 | ||
2208 | Returns the current value of WebEncoding. | |
2209 | (In the database, WebEncoding is stored as varchar(16).) | |
2210 | ||
2211 | ||
2212 | ||
2213 | =head2 SetWebEncoding VALUE | |
2214 | ||
2215 | ||
2216 | Set WebEncoding to VALUE. | |
2217 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2218 | (In the database, WebEncoding will be stored as a varchar(16).) | |
2219 | ||
2220 | ||
2221 | =cut | |
2222 | ||
2223 | ||
2224 | =head2 ExternalContactInfoId | |
2225 | ||
2226 | Returns the current value of ExternalContactInfoId. | |
2227 | (In the database, ExternalContactInfoId is stored as varchar(100).) | |
2228 | ||
2229 | ||
2230 | ||
2231 | =head2 SetExternalContactInfoId VALUE | |
2232 | ||
2233 | ||
2234 | Set ExternalContactInfoId to VALUE. | |
2235 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2236 | (In the database, ExternalContactInfoId will be stored as a varchar(100).) | |
2237 | ||
2238 | ||
2239 | =cut | |
2240 | ||
2241 | ||
2242 | =head2 ContactInfoSystem | |
2243 | ||
2244 | Returns the current value of ContactInfoSystem. | |
2245 | (In the database, ContactInfoSystem is stored as varchar(30).) | |
2246 | ||
2247 | ||
2248 | ||
2249 | =head2 SetContactInfoSystem VALUE | |
2250 | ||
2251 | ||
2252 | Set ContactInfoSystem to VALUE. | |
2253 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2254 | (In the database, ContactInfoSystem will be stored as a varchar(30).) | |
2255 | ||
2256 | ||
2257 | =cut | |
2258 | ||
2259 | ||
2260 | =head2 ExternalAuthId | |
2261 | ||
2262 | Returns the current value of ExternalAuthId. | |
2263 | (In the database, ExternalAuthId is stored as varchar(100).) | |
2264 | ||
2265 | ||
2266 | ||
2267 | =head2 SetExternalAuthId VALUE | |
2268 | ||
2269 | ||
2270 | Set ExternalAuthId to VALUE. | |
2271 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2272 | (In the database, ExternalAuthId will be stored as a varchar(100).) | |
2273 | ||
2274 | ||
2275 | =cut | |
2276 | ||
2277 | ||
2278 | =head2 AuthSystem | |
2279 | ||
2280 | Returns the current value of AuthSystem. | |
2281 | (In the database, AuthSystem is stored as varchar(30).) | |
2282 | ||
2283 | ||
2284 | ||
2285 | =head2 SetAuthSystem VALUE | |
2286 | ||
2287 | ||
2288 | Set AuthSystem to VALUE. | |
2289 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2290 | (In the database, AuthSystem will be stored as a varchar(30).) | |
2291 | ||
2292 | ||
2293 | =cut | |
2294 | ||
2295 | ||
2296 | =head2 Gecos | |
2297 | ||
2298 | Returns the current value of Gecos. | |
2299 | (In the database, Gecos is stored as varchar(16).) | |
2300 | ||
2301 | ||
2302 | ||
2303 | =head2 SetGecos VALUE | |
2304 | ||
2305 | ||
2306 | Set Gecos to VALUE. | |
2307 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2308 | (In the database, Gecos will be stored as a varchar(16).) | |
2309 | ||
2310 | ||
2311 | =cut | |
2312 | ||
2313 | ||
2314 | =head2 HomePhone | |
2315 | ||
2316 | Returns the current value of HomePhone. | |
2317 | (In the database, HomePhone is stored as varchar(30).) | |
2318 | ||
2319 | ||
2320 | ||
2321 | =head2 SetHomePhone VALUE | |
2322 | ||
2323 | ||
2324 | Set HomePhone to VALUE. | |
2325 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2326 | (In the database, HomePhone will be stored as a varchar(30).) | |
2327 | ||
2328 | ||
2329 | =cut | |
2330 | ||
2331 | ||
2332 | =head2 WorkPhone | |
2333 | ||
2334 | Returns the current value of WorkPhone. | |
2335 | (In the database, WorkPhone is stored as varchar(30).) | |
2336 | ||
2337 | ||
2338 | ||
2339 | =head2 SetWorkPhone VALUE | |
2340 | ||
2341 | ||
2342 | Set WorkPhone to VALUE. | |
2343 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2344 | (In the database, WorkPhone will be stored as a varchar(30).) | |
2345 | ||
2346 | ||
2347 | =cut | |
2348 | ||
2349 | ||
2350 | =head2 MobilePhone | |
2351 | ||
2352 | Returns the current value of MobilePhone. | |
2353 | (In the database, MobilePhone is stored as varchar(30).) | |
2354 | ||
2355 | ||
2356 | ||
2357 | =head2 SetMobilePhone VALUE | |
2358 | ||
2359 | ||
2360 | Set MobilePhone to VALUE. | |
2361 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2362 | (In the database, MobilePhone will be stored as a varchar(30).) | |
2363 | ||
2364 | ||
2365 | =cut | |
2366 | ||
2367 | ||
2368 | =head2 PagerPhone | |
2369 | ||
2370 | Returns the current value of PagerPhone. | |
2371 | (In the database, PagerPhone is stored as varchar(30).) | |
2372 | ||
2373 | ||
2374 | ||
2375 | =head2 SetPagerPhone VALUE | |
2376 | ||
2377 | ||
2378 | Set PagerPhone to VALUE. | |
2379 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2380 | (In the database, PagerPhone will be stored as a varchar(30).) | |
2381 | ||
2382 | ||
2383 | =cut | |
2384 | ||
2385 | ||
2386 | =head2 Address1 | |
2387 | ||
2388 | Returns the current value of Address1. | |
2389 | (In the database, Address1 is stored as varchar(200).) | |
2390 | ||
2391 | ||
2392 | ||
2393 | =head2 SetAddress1 VALUE | |
2394 | ||
2395 | ||
2396 | Set Address1 to VALUE. | |
2397 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2398 | (In the database, Address1 will be stored as a varchar(200).) | |
2399 | ||
2400 | ||
2401 | =cut | |
2402 | ||
2403 | ||
2404 | =head2 Address2 | |
2405 | ||
2406 | Returns the current value of Address2. | |
2407 | (In the database, Address2 is stored as varchar(200).) | |
2408 | ||
2409 | ||
2410 | ||
2411 | =head2 SetAddress2 VALUE | |
2412 | ||
2413 | ||
2414 | Set Address2 to VALUE. | |
2415 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2416 | (In the database, Address2 will be stored as a varchar(200).) | |
2417 | ||
2418 | ||
2419 | =cut | |
2420 | ||
2421 | ||
2422 | =head2 City | |
2423 | ||
2424 | Returns the current value of City. | |
2425 | (In the database, City is stored as varchar(100).) | |
2426 | ||
2427 | ||
2428 | ||
2429 | =head2 SetCity VALUE | |
2430 | ||
2431 | ||
2432 | Set City to VALUE. | |
2433 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2434 | (In the database, City will be stored as a varchar(100).) | |
2435 | ||
2436 | ||
2437 | =cut | |
2438 | ||
2439 | ||
2440 | =head2 State | |
2441 | ||
2442 | Returns the current value of State. | |
2443 | (In the database, State is stored as varchar(100).) | |
2444 | ||
2445 | ||
2446 | ||
2447 | =head2 SetState VALUE | |
2448 | ||
2449 | ||
2450 | Set State to VALUE. | |
2451 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2452 | (In the database, State will be stored as a varchar(100).) | |
2453 | ||
2454 | ||
2455 | =cut | |
2456 | ||
2457 | ||
2458 | =head2 Zip | |
2459 | ||
2460 | Returns the current value of Zip. | |
2461 | (In the database, Zip is stored as varchar(16).) | |
2462 | ||
2463 | ||
2464 | ||
2465 | =head2 SetZip VALUE | |
2466 | ||
2467 | ||
2468 | Set Zip to VALUE. | |
2469 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2470 | (In the database, Zip will be stored as a varchar(16).) | |
2471 | ||
2472 | ||
2473 | =cut | |
2474 | ||
2475 | ||
2476 | =head2 Country | |
2477 | ||
2478 | Returns the current value of Country. | |
2479 | (In the database, Country is stored as varchar(50).) | |
2480 | ||
2481 | ||
2482 | ||
2483 | =head2 SetCountry VALUE | |
2484 | ||
2485 | ||
2486 | Set Country to VALUE. | |
2487 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2488 | (In the database, Country will be stored as a varchar(50).) | |
2489 | ||
2490 | ||
2491 | =cut | |
2492 | ||
2493 | ||
2494 | =head2 Timezone | |
2495 | ||
2496 | Returns the current value of Timezone. | |
2497 | (In the database, Timezone is stored as varchar(50).) | |
2498 | ||
2499 | ||
2500 | ||
2501 | =head2 SetTimezone VALUE | |
2502 | ||
2503 | ||
2504 | Set Timezone to VALUE. | |
2505 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2506 | (In the database, Timezone will be stored as a varchar(50).) | |
2507 | ||
2508 | ||
2509 | =cut | |
2510 | ||
2511 | ||
2512 | =head2 PGPKey | |
2513 | ||
2514 | Returns the current value of PGPKey. | |
2515 | (In the database, PGPKey is stored as text.) | |
2516 | ||
2517 | ||
2518 | ||
2519 | =head2 SetPGPKey VALUE | |
2520 | ||
2521 | ||
2522 | Set PGPKey to VALUE. | |
2523 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2524 | (In the database, PGPKey will be stored as a text.) | |
2525 | ||
2526 | ||
2527 | =cut | |
2528 | ||
2529 | ||
af59614d MKG |
2530 | =head2 SMIMECertificate |
2531 | ||
2532 | Returns the current value of SMIMECertificate. | |
2533 | (In the database, SMIMECertificate is stored as text.) | |
2534 | ||
2535 | ||
2536 | ||
2537 | =head2 SetSMIMECertificate VALUE | |
2538 | ||
2539 | ||
2540 | Set SMIMECertificate to VALUE. | |
2541 | Returns (1, 'Status message') on success and (0, 'Error Message') on failure. | |
2542 | (In the database, SMIMECertificate will be stored as a text.) | |
2543 | ||
2544 | ||
2545 | =cut | |
2546 | ||
2547 | ||
84fb5b46 MKG |
2548 | =head2 Creator |
2549 | ||
2550 | Returns the current value of Creator. | |
2551 | (In the database, Creator is stored as int(11).) | |
2552 | ||
2553 | ||
2554 | =cut | |
2555 | ||
2556 | ||
2557 | =head2 Created | |
2558 | ||
2559 | Returns the current value of Created. | |
2560 | (In the database, Created is stored as datetime.) | |
2561 | ||
2562 | ||
2563 | =cut | |
2564 | ||
2565 | ||
2566 | =head2 LastUpdatedBy | |
2567 | ||
2568 | Returns the current value of LastUpdatedBy. | |
2569 | (In the database, LastUpdatedBy is stored as int(11).) | |
2570 | ||
2571 | ||
2572 | =cut | |
2573 | ||
2574 | ||
2575 | =head2 LastUpdated | |
2576 | ||
2577 | Returns the current value of LastUpdated. | |
2578 | (In the database, LastUpdated is stored as datetime.) | |
2579 | ||
2580 | ||
2581 | =cut | |
2582 | ||
2583 | ||
2584 | ||
2585 | sub _CoreAccessible { | |
2586 | { | |
2587 | ||
2588 | id => | |
2589 | {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, | |
2590 | Name => | |
2591 | {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, | |
2592 | Password => | |
2593 | {read => 1, write => 1, sql_type => 12, length => 256, is_blob => 0, is_numeric => 0, type => 'varchar(256)', default => ''}, | |
2594 | AuthToken => | |
2595 | {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''}, | |
2596 | Comments => | |
2597 | {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''}, | |
2598 | Signature => | |
2599 | {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''}, | |
2600 | EmailAddress => | |
2601 | {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''}, | |
2602 | FreeformContactInfo => | |
2603 | {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''}, | |
2604 | Organization => | |
2605 | {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, | |
2606 | RealName => | |
2607 | {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''}, | |
2608 | NickName => | |
2609 | {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''}, | |
2610 | Lang => | |
2611 | {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''}, | |
2612 | EmailEncoding => | |
2613 | {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''}, | |
2614 | WebEncoding => | |
2615 | {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''}, | |
2616 | ExternalContactInfoId => | |
2617 | {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''}, | |
2618 | ContactInfoSystem => | |
2619 | {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''}, | |
2620 | ExternalAuthId => | |
2621 | {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''}, | |
2622 | AuthSystem => | |
2623 | {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''}, | |
2624 | Gecos => | |
2625 | {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''}, | |
2626 | HomePhone => | |
2627 | {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''}, | |
2628 | WorkPhone => | |
2629 | {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''}, | |
2630 | MobilePhone => | |
2631 | {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''}, | |
2632 | PagerPhone => | |
2633 | {read => 1, write => 1, sql_type => 12, length => 30, is_blob => 0, is_numeric => 0, type => 'varchar(30)', default => ''}, | |
2634 | Address1 => | |
2635 | {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, | |
2636 | Address2 => | |
2637 | {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''}, | |
2638 | City => | |
2639 | {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''}, | |
2640 | State => | |
2641 | {read => 1, write => 1, sql_type => 12, length => 100, is_blob => 0, is_numeric => 0, type => 'varchar(100)', default => ''}, | |
2642 | Zip => | |
2643 | {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''}, | |
2644 | Country => | |
2645 | {read => 1, write => 1, sql_type => 12, length => 50, is_blob => 0, is_numeric => 0, type => 'varchar(50)', default => ''}, | |
2646 | Timezone => | |
2647 | {read => 1, write => 1, sql_type => 12, length => 50, is_blob => 0, is_numeric => 0, type => 'varchar(50)', default => ''}, | |
2648 | PGPKey => | |
2649 | {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''}, | |
af59614d MKG |
2650 | SMIMECertificate => |
2651 | {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''}, | |
84fb5b46 MKG |
2652 | Creator => |
2653 | {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, | |
2654 | Created => | |
2655 | {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, | |
2656 | LastUpdatedBy => | |
2657 | {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, | |
2658 | LastUpdated => | |
2659 | {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, | |
2660 | ||
2661 | } | |
2662 | }; | |
2663 | ||
af59614d MKG |
2664 | sub UID { |
2665 | my $self = shift; | |
2666 | return undef unless defined $self->Name; | |
2667 | return "@{[ref $self]}-@{[$self->Name]}"; | |
2668 | } | |
2669 | ||
2670 | sub FindDependencies { | |
2671 | my $self = shift; | |
2672 | my ($walker, $deps) = @_; | |
2673 | ||
2674 | $self->SUPER::FindDependencies($walker, $deps); | |
2675 | ||
2676 | # ACL equivalence group | |
2677 | my $objs = RT::Groups->new( $self->CurrentUser ); | |
320f0092 | 2678 | $objs->Limit( FIELD => 'Domain', VALUE => 'ACLEquivalence', CASESENSITIVE => 0 ); |
af59614d MKG |
2679 | $objs->Limit( FIELD => 'Instance', VALUE => $self->Id ); |
2680 | $deps->Add( in => $objs ); | |
2681 | ||
2682 | # Memberships in SystemInternal groups | |
2683 | $objs = RT::GroupMembers->new( $self->CurrentUser ); | |
2684 | $objs->Limit( FIELD => 'MemberId', VALUE => $self->Id ); | |
2685 | my $principals = $objs->Join( | |
2686 | ALIAS1 => 'main', | |
2687 | FIELD1 => 'GroupId', | |
2688 | TABLE2 => 'Principals', | |
2689 | FIELD2 => 'id', | |
2690 | ); | |
2691 | my $groups = $objs->Join( | |
2692 | ALIAS1 => $principals, | |
2693 | FIELD1 => 'ObjectId', | |
2694 | TABLE2 => 'Groups', | |
2695 | FIELD2 => 'Id', | |
2696 | ); | |
2697 | $objs->Limit( | |
2698 | ALIAS => $groups, | |
2699 | FIELD => 'Domain', | |
2700 | VALUE => 'SystemInternal', | |
320f0092 | 2701 | CASESENSITIVE => 0 |
af59614d MKG |
2702 | ); |
2703 | $deps->Add( in => $objs ); | |
2704 | ||
2705 | # XXX: This ignores the myriad of "in" references from the Creator | |
2706 | # and LastUpdatedBy columns. | |
2707 | } | |
2708 | ||
2709 | sub Serialize { | |
2710 | my $self = shift; | |
2711 | return ( | |
2712 | Disabled => $self->PrincipalObj->Disabled, | |
2713 | Principal => $self->PrincipalObj->UID, | |
2714 | PrincipalId => $self->PrincipalObj->Id, | |
2715 | $self->SUPER::Serialize(@_), | |
2716 | ); | |
2717 | } | |
2718 | ||
2719 | sub PreInflate { | |
2720 | my $class = shift; | |
2721 | my ($importer, $uid, $data) = @_; | |
2722 | ||
2723 | my $principal_uid = delete $data->{Principal}; | |
2724 | my $principal_id = delete $data->{PrincipalId}; | |
2725 | my $disabled = delete $data->{Disabled}; | |
2726 | ||
2727 | my $obj = RT::User->new( RT->SystemUser ); | |
2728 | $obj->LoadByCols( Name => $data->{Name} ); | |
2729 | $obj->LoadByEmail( $data->{EmailAddress} ) unless $obj->Id; | |
2730 | if ($obj->Id) { | |
2731 | # User already exists -- merge | |
2732 | ||
2733 | # XXX: We might be merging a privileged user into an unpriv one, | |
2734 | # in which case we should probably promote the unpriv user to | |
2735 | # being privileged. Of course, we don't know if the user being | |
2736 | # imported is privileged yet, as its group memberships show up | |
2737 | # later in the stream... | |
2738 | $importer->MergeValues($obj, $data); | |
2739 | $importer->SkipTransactions( $uid ); | |
2740 | ||
2741 | # Mark both the principal and the user object as resolved | |
2742 | $importer->Resolve( | |
2743 | $principal_uid, | |
2744 | ref($obj->PrincipalObj), | |
2745 | $obj->PrincipalObj->Id | |
2746 | ); | |
2747 | $importer->Resolve( $uid => ref($obj) => $obj->Id ); | |
2748 | return; | |
2749 | } | |
2750 | ||
2751 | # Create a principal first, so we know what ID to use | |
2752 | my $principal = RT::Principal->new( RT->SystemUser ); | |
2753 | my ($id) = $principal->Create( | |
2754 | PrincipalType => 'User', | |
2755 | Disabled => $disabled, | |
2756 | ObjectId => 0, | |
2757 | ); | |
320f0092 MKG |
2758 | |
2759 | # Now we have a principal id, set the id for the user record | |
2760 | $data->{id} = $id; | |
2761 | ||
af59614d MKG |
2762 | $importer->Resolve( $principal_uid => ref($principal), $id ); |
2763 | ||
2764 | $importer->Postpone( | |
2765 | for => $uid, | |
2766 | uid => $principal_uid, | |
2767 | column => "ObjectId", | |
2768 | ); | |
2769 | ||
2770 | return $class->SUPER::PreInflate( $importer, $uid, $data ); | |
2771 | } | |
2772 | ||
2773 | sub PostInflate { | |
2774 | my $self = shift; | |
2775 | RT->InitSystemObjects if $self->Name eq "RT_System"; | |
2776 | } | |
2777 | ||
84fb5b46 MKG |
2778 | RT::Base->_ImportOverlays(); |
2779 | ||
2780 | ||
2781 | 1; |