Upgrade to 4.2.2
[usit-rt.git] / lib / RT / Attribute.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
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 package RT::Attribute;
50
51 use strict;
52 use warnings;
53
54 use base 'RT::Record';
55
56 sub Table {'Attributes'}
57
58 use Storable qw/nfreeze thaw/;
59 use MIME::Base64;
60
61
62 =head1 NAME
63
64   RT::Attribute_Overlay 
65
66 =head1 Content
67
68 =cut
69
70 # the acl map is a map of "name of attribute" and "what right the user must have on the associated object to see/edit it
71
72 our $ACL_MAP = {
73     SavedSearch => { create => 'EditSavedSearches',
74                      update => 'EditSavedSearches',
75                      delete => 'EditSavedSearches',
76                      display => 'ShowSavedSearches' },
77
78 };
79
80 # There are a number of attributes that users should be able to modify for themselves, such as saved searches
81 #  we could do this with a different set of "update" rights, but that gets very hacky very fast. this is even faster and even
82 # hackier. we're hardcoding that a different set of rights are needed for attributes on oneself
83 our $PERSONAL_ACL_MAP = { 
84     SavedSearch => { create => 'ModifySelf',
85                      update => 'ModifySelf',
86                      delete => 'ModifySelf',
87                      display => 'allow' },
88
89 };
90
91 =head2 LookupObjectRight { ObjectType => undef, ObjectId => undef, Name => undef, Right => { create, update, delete, display } }
92
93 Returns the right that the user needs to have on this attribute's object to perform the related attribute operation. Returns "allow" if the right is otherwise unspecified.
94
95 =cut
96
97 sub LookupObjectRight { 
98     my $self = shift;
99     my %args = ( ObjectType => undef,
100                  ObjectId => undef,
101                  Right => undef,
102                  Name => undef,
103                  @_);
104
105     # if it's an attribute on oneself, check the personal acl map
106     if (($args{'ObjectType'} eq 'RT::User') && ($args{'ObjectId'} eq $self->CurrentUser->Id)) {
107     return('allow') unless ($PERSONAL_ACL_MAP->{$args{'Name'}});
108     return('allow') unless ($PERSONAL_ACL_MAP->{$args{'Name'}}->{$args{'Right'}});
109     return($PERSONAL_ACL_MAP->{$args{'Name'}}->{$args{'Right'}}); 
110
111     }
112    # otherwise check the main ACL map
113     else {
114     return('allow') unless ($ACL_MAP->{$args{'Name'}});
115     return('allow') unless ($ACL_MAP->{$args{'Name'}}->{$args{'Right'}});
116     return($ACL_MAP->{$args{'Name'}}->{$args{'Right'}}); 
117     }
118 }
119
120
121
122
123 =head2 Create PARAMHASH
124
125 Create takes a hash of values and creates a row in the database:
126
127   varchar(200) 'Name'.
128   varchar(255) 'Content'.
129   varchar(16) 'ContentType',
130   varchar(64) 'ObjectType'.
131   int(11) 'ObjectId'.
132
133 You may pass a C<Object> instead of C<ObjectType> and C<ObjectId>.
134
135 =cut
136
137
138
139
140 sub Create {
141     my $self = shift;
142     my %args = ( 
143                 Name => '',
144                 Description => '',
145                 Content => '',
146                 ContentType => '',
147                 Object => undef,
148                 @_);
149
150     if ($args{Object} and UNIVERSAL::can($args{Object}, 'Id')) {
151         $args{ObjectType} = $args{Object}->isa("RT::CurrentUser") ? "RT::User" : ref($args{Object});
152         $args{ObjectId} = $args{Object}->Id;
153     } else {
154         return(0, $self->loc("Required parameter '[_1]' not specified", 'Object'));
155
156     }
157    
158     # object_right is the right that the user has to have on the object for them to have $right on this attribute
159     my $object_right = $self->LookupObjectRight(
160         Right      => 'create',
161         ObjectId   => $args{'ObjectId'},
162         ObjectType => $args{'ObjectType'},
163         Name       => $args{'Name'}
164     );
165     if ($object_right eq 'deny') { 
166         return (0, $self->loc('Permission Denied'));
167     } 
168     elsif ($object_right eq 'allow') {
169         # do nothing, we're ok
170     }
171     elsif (!$self->CurrentUser->HasRight( Object => $args{Object}, Right => $object_right)) {
172         return (0, $self->loc('Permission Denied'));
173     }
174
175    
176     if (ref ($args{'Content'}) ) { 
177         eval  {$args{'Content'} = $self->_SerializeContent($args{'Content'}); };
178         if ($@) {
179          return(0, $@);
180         }
181         $args{'ContentType'} = 'storable';
182     }
183
184     delete $RT::User::PREFERENCES_CACHE{ $args{'ObjectId'} }{ $args{'Name'} }
185         if $args{'ObjectType'} eq 'RT::User';
186
187     $self->SUPER::Create(
188                          Name => $args{'Name'},
189                          Content => $args{'Content'},
190                          ContentType => $args{'ContentType'},
191                          Description => $args{'Description'},
192                          ObjectType => $args{'ObjectType'},
193                          ObjectId => $args{'ObjectId'},
194 );
195
196 }
197
198
199
200 =head2  LoadByNameAndObject (Object => OBJECT, Name => NAME)
201
202 Loads the Attribute named NAME for Object OBJECT.
203
204 =cut
205
206 sub LoadByNameAndObject {
207     my $self = shift;
208     my %args = (
209         Object => undef,
210         Name  => undef,
211         @_,
212     );
213
214     return (
215         $self->LoadByCols(
216             Name => $args{'Name'},
217             ObjectType => ref($args{'Object'}),
218             ObjectId => $args{'Object'}->Id,
219         )
220     );
221
222 }
223
224
225
226 =head2 _DeserializeContent
227
228 DeserializeContent returns this Attribute's "Content" as a hashref.
229
230
231 =cut
232
233 sub _DeserializeContent {
234     my $self = shift;
235     my $content = shift;
236
237     my $hashref;
238     eval {$hashref  = thaw(decode_base64($content))} ; 
239     if ($@) {
240         $RT::Logger->error("Deserialization of attribute ".$self->Id. " failed");
241     }
242
243     return($hashref);
244
245 }
246
247
248 =head2 Content
249
250 Returns this attribute's content. If it's a scalar, returns a scalar
251 If it's data structure returns a ref to that data structure.
252
253 =cut
254
255 sub Content {
256     my $self = shift;
257     # Here we call _Value to get the ACL check.
258     my $content = $self->_Value('Content');
259     if ( ($self->__Value('ContentType') || '') eq 'storable') {
260         eval {$content = $self->_DeserializeContent($content); };
261         if ($@) {
262             $RT::Logger->error("Deserialization of content for attribute ".$self->Id. " failed. Attribute was: ".$content);
263         }
264     } 
265
266     return($content);
267
268 }
269
270 sub _SerializeContent {
271     my $self = shift;
272     my $content = shift;
273         return( encode_base64(nfreeze($content))); 
274 }
275
276
277 sub SetContent {
278     my $self = shift;
279     my $content = shift;
280
281     if ( $self->__Value('ObjectType') eq 'RT::User' ) {
282         delete $RT::User::PREFERENCES_CACHE
283             { $self->__Value('ObjectId') }{ $self->__Value('Name') };
284     }
285
286     # Call __Value to avoid ACL check.
287     if ( ($self->__Value('ContentType')||'') eq 'storable' ) {
288         # We eval the serialization because it will lose on a coderef.
289         $content = eval { $self->_SerializeContent($content) };
290         if ($@) {
291             $RT::Logger->error("Content couldn't be frozen: $@");
292             return(0, "Content couldn't be frozen");
293         }
294     }
295     return $self->_Set( Field => 'Content', Value => $content );
296 }
297
298 =head2 SubValue KEY
299
300 Returns the subvalue for $key.
301
302
303 =cut
304
305 sub SubValue {
306     my $self = shift;
307     my $key = shift;
308     my $values = $self->Content();
309     return undef unless ref($values);
310     return($values->{$key});
311 }
312
313 =head2 DeleteSubValue NAME
314
315 Deletes the subvalue with the key NAME
316
317 =cut
318
319 sub DeleteSubValue {
320     my $self = shift;
321     my $key = shift;
322     my %values = $self->Content();
323     delete $values{$key};
324     $self->SetContent(%values);
325
326     
327
328 }
329
330
331 =head2 DeleteAllSubValues 
332
333 Deletes all subvalues for this attribute
334
335 =cut
336
337
338 sub DeleteAllSubValues {
339     my $self = shift; 
340     $self->SetContent({});
341 }
342
343 =head2 SetSubValues  {  }
344
345 Takes a hash of keys and values and stores them in the content of this attribute.
346
347 Each key B<replaces> the existing key with the same name
348
349 Returns a tuple of (status, message)
350
351 =cut
352
353
354 sub SetSubValues {
355    my $self = shift;
356    my %args = (@_); 
357    my $values = ($self->Content() || {} );
358    foreach my $key (keys %args) {
359     $values->{$key} = $args{$key};
360    }
361
362    $self->SetContent($values);
363
364 }
365
366
367 sub Object {
368     my $self = shift;
369     my $object_type = $self->__Value('ObjectType');
370     my $object;
371     eval { $object = $object_type->new($self->CurrentUser) };
372     unless(UNIVERSAL::isa($object, $object_type)) {
373         $RT::Logger->error("Attribute ".$self->Id." has a bogus object type - $object_type (".$@.")");
374         return(undef);
375      }
376     $object->Load($self->__Value('ObjectId'));
377
378     return($object);
379
380 }
381
382
383 sub Delete {
384     my $self = shift;
385     unless ($self->CurrentUserHasRight('delete')) {
386         return (0,$self->loc('Permission Denied'));
387     }
388     return($self->SUPER::Delete(@_));
389 }
390
391
392 sub _Value {
393     my $self = shift;
394     unless ($self->CurrentUserHasRight('display')) {
395         return (0,$self->loc('Permission Denied'));
396     }
397
398     return($self->SUPER::_Value(@_));
399
400
401 }
402
403
404 sub _Set {
405     my $self = shift;
406     unless ($self->CurrentUserHasRight('update')) {
407
408         return (0,$self->loc('Permission Denied'));
409     }
410     return($self->SUPER::_Set(@_));
411
412 }
413
414
415 =head2 CurrentUserHasRight
416
417 One of "display" "update" "delete" or "create" and returns 1 if the user has that right for attributes of this name for this object.Returns undef otherwise.
418
419 =cut
420
421 sub CurrentUserHasRight {
422     my $self = shift;
423     my $right = shift;
424
425     # object_right is the right that the user has to have on the object for them to have $right on this attribute
426     my $object_right = $self->LookupObjectRight(
427         Right      => $right,
428         ObjectId   => $self->__Value('ObjectId'),
429         ObjectType => $self->__Value('ObjectType'),
430         Name       => $self->__Value('Name')
431     );
432    
433     return (1) if ($object_right eq 'allow');
434     return (0) if ($object_right eq 'deny');
435     return(1) if ($self->CurrentUser->HasRight( Object => $self->Object, Right => $object_right));
436     return(0);
437
438 }
439
440
441 =head1 TODO
442
443 We should be deserializing the content on load and then enver again, rather than at every access
444
445 =cut
446
447
448
449
450
451
452
453
454 =head2 id
455
456 Returns the current value of id.
457 (In the database, id is stored as int(11).)
458
459
460 =cut
461
462
463 =head2 Name
464
465 Returns the current value of Name.
466 (In the database, Name is stored as varchar(255).)
467
468
469
470 =head2 SetName VALUE
471
472
473 Set Name to VALUE.
474 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
475 (In the database, Name will be stored as a varchar(255).)
476
477
478 =cut
479
480
481 =head2 Description
482
483 Returns the current value of Description.
484 (In the database, Description is stored as varchar(255).)
485
486
487
488 =head2 SetDescription VALUE
489
490
491 Set Description to VALUE.
492 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
493 (In the database, Description will be stored as a varchar(255).)
494
495
496 =cut
497
498
499 =head2 Content
500
501 Returns the current value of Content.
502 (In the database, Content is stored as blob.)
503
504
505
506 =head2 SetContent VALUE
507
508
509 Set Content to VALUE.
510 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
511 (In the database, Content will be stored as a blob.)
512
513
514 =cut
515
516
517 =head2 ContentType
518
519 Returns the current value of ContentType.
520 (In the database, ContentType is stored as varchar(16).)
521
522
523
524 =head2 SetContentType VALUE
525
526
527 Set ContentType to VALUE.
528 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
529 (In the database, ContentType will be stored as a varchar(16).)
530
531
532 =cut
533
534
535 =head2 ObjectType
536
537 Returns the current value of ObjectType.
538 (In the database, ObjectType is stored as varchar(64).)
539
540
541
542 =head2 SetObjectType VALUE
543
544
545 Set ObjectType to VALUE.
546 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
547 (In the database, ObjectType will be stored as a varchar(64).)
548
549
550 =cut
551
552
553 =head2 ObjectId
554
555 Returns the current value of ObjectId.
556 (In the database, ObjectId is stored as int(11).)
557
558
559
560 =head2 SetObjectId VALUE
561
562
563 Set ObjectId to VALUE.
564 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
565 (In the database, ObjectId will be stored as a int(11).)
566
567
568 =cut
569
570
571 =head2 Creator
572
573 Returns the current value of Creator.
574 (In the database, Creator is stored as int(11).)
575
576
577 =cut
578
579
580 =head2 Created
581
582 Returns the current value of Created.
583 (In the database, Created is stored as datetime.)
584
585
586 =cut
587
588
589 =head2 LastUpdatedBy
590
591 Returns the current value of LastUpdatedBy.
592 (In the database, LastUpdatedBy is stored as int(11).)
593
594
595 =cut
596
597
598 =head2 LastUpdated
599
600 Returns the current value of LastUpdated.
601 (In the database, LastUpdated is stored as datetime.)
602
603
604 =cut
605
606
607
608 sub _CoreAccessible {
609     {
610
611         id =>
612                 {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
613         Name =>
614                 {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
615         Description =>
616                 {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
617         Content =>
618                 {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'blob', default => ''},
619         ContentType =>
620                 {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
621         ObjectType =>
622                 {read => 1, write => 1, sql_type => 12, length => 64,  is_blob => 0,  is_numeric => 0,  type => 'varchar(64)', default => ''},
623         ObjectId =>
624                 {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
625         Creator =>
626                 {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
627         Created =>
628                 {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
629         LastUpdatedBy =>
630                 {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
631         LastUpdated =>
632                 {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
633
634  }
635 };
636
637 sub FindDependencies {
638     my $self = shift;
639     my ($walker, $deps) = @_;
640
641     $self->SUPER::FindDependencies($walker, $deps);
642     $deps->Add( out => $self->Object );
643 }
644
645 sub PreInflate {
646     my $class = shift;
647     my ($importer, $uid, $data) = @_;
648
649     if ($data->{Object} and ref $data->{Object}) {
650         my $on_uid = ${ $data->{Object} };
651         return if $importer->ShouldSkipTransaction($on_uid);
652     }
653     return $class->SUPER::PreInflate( $importer, $uid, $data );
654 }
655
656 RT::Base->_ImportOverlays();
657
658 1;