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