Master to 4.2.8
[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     $self->SUPER::Create(
185                          Name => $args{'Name'},
186                          Content => $args{'Content'},
187                          ContentType => $args{'ContentType'},
188                          Description => $args{'Description'},
189                          ObjectType => $args{'ObjectType'},
190                          ObjectId => $args{'ObjectId'},
191 );
192
193 }
194
195
196
197 =head2  LoadByNameAndObject (Object => OBJECT, Name => NAME)
198
199 Loads the Attribute named NAME for Object OBJECT.
200
201 =cut
202
203 sub LoadByNameAndObject {
204     my $self = shift;
205     my %args = (
206         Object => undef,
207         Name  => undef,
208         @_,
209     );
210
211     return (
212         $self->LoadByCols(
213             Name => $args{'Name'},
214             ObjectType => ref($args{'Object'}),
215             ObjectId => $args{'Object'}->Id,
216         )
217     );
218
219 }
220
221
222
223 =head2 _DeserializeContent
224
225 DeserializeContent returns this Attribute's "Content" as a hashref.
226
227
228 =cut
229
230 sub _DeserializeContent {
231     my $self = shift;
232     my $content = shift;
233
234     my $hashref;
235     eval {$hashref  = thaw(decode_base64($content))} ; 
236     if ($@) {
237         $RT::Logger->error("Deserialization of attribute ".$self->Id. " failed");
238     }
239
240     return($hashref);
241
242 }
243
244
245 =head2 Content
246
247 Returns this attribute's content. If it's a scalar, returns a scalar
248 If it's data structure returns a ref to that data structure.
249
250 =cut
251
252 sub Content {
253     my $self = shift;
254     # Here we call _Value to get the ACL check.
255     my $content = $self->_Value('Content');
256     if ( ($self->__Value('ContentType') || '') eq 'storable') {
257         eval {$content = $self->_DeserializeContent($content); };
258         if ($@) {
259             $RT::Logger->error("Deserialization of content for attribute ".$self->Id. " failed. Attribute was: ".$content);
260         }
261     } 
262
263     return($content);
264
265 }
266
267 sub _SerializeContent {
268     my $self = shift;
269     my $content = shift;
270         return( encode_base64(nfreeze($content))); 
271 }
272
273
274 sub SetContent {
275     my $self = shift;
276     my $content = shift;
277
278     # Call __Value to avoid ACL check.
279     if ( ($self->__Value('ContentType')||'') eq 'storable' ) {
280         # We eval the serialization because it will lose on a coderef.
281         $content = eval { $self->_SerializeContent($content) };
282         if ($@) {
283             $RT::Logger->error("Content couldn't be frozen: $@");
284             return(0, "Content couldn't be frozen");
285         }
286     }
287     return $self->_Set( Field => 'Content', Value => $content );
288 }
289
290 =head2 SubValue KEY
291
292 Returns the subvalue for $key.
293
294
295 =cut
296
297 sub SubValue {
298     my $self = shift;
299     my $key = shift;
300     my $values = $self->Content();
301     return undef unless ref($values);
302     return($values->{$key});
303 }
304
305 =head2 DeleteSubValue NAME
306
307 Deletes the subvalue with the key NAME
308
309 =cut
310
311 sub DeleteSubValue {
312     my $self = shift;
313     my $key = shift;
314     my %values = $self->Content();
315     delete $values{$key};
316     $self->SetContent(%values);
317
318     
319
320 }
321
322
323 =head2 DeleteAllSubValues 
324
325 Deletes all subvalues for this attribute
326
327 =cut
328
329
330 sub DeleteAllSubValues {
331     my $self = shift; 
332     $self->SetContent({});
333 }
334
335 =head2 SetSubValues  {  }
336
337 Takes a hash of keys and values and stores them in the content of this attribute.
338
339 Each key B<replaces> the existing key with the same name
340
341 Returns a tuple of (status, message)
342
343 =cut
344
345
346 sub SetSubValues {
347    my $self = shift;
348    my %args = (@_); 
349    my $values = ($self->Content() || {} );
350    foreach my $key (keys %args) {
351     $values->{$key} = $args{$key};
352    }
353
354    $self->SetContent($values);
355
356 }
357
358
359 sub Object {
360     my $self = shift;
361     my $object_type = $self->__Value('ObjectType');
362     my $object;
363     eval { $object = $object_type->new($self->CurrentUser) };
364     unless(UNIVERSAL::isa($object, $object_type)) {
365         $RT::Logger->error("Attribute ".$self->Id." has a bogus object type - $object_type (".$@.")");
366         return(undef);
367      }
368     $object->Load($self->__Value('ObjectId'));
369
370     return($object);
371
372 }
373
374
375 sub Delete {
376     my $self = shift;
377     unless ($self->CurrentUserHasRight('delete')) {
378         return (0,$self->loc('Permission Denied'));
379     }
380
381     return($self->SUPER::Delete(@_));
382 }
383
384
385 sub _Value {
386     my $self = shift;
387     unless ($self->CurrentUserHasRight('display')) {
388         return (0,$self->loc('Permission Denied'));
389     }
390
391     return($self->SUPER::_Value(@_));
392
393
394 }
395
396
397 sub _Set {
398     my $self = shift;
399     unless ($self->CurrentUserHasRight('update')) {
400
401         return (0,$self->loc('Permission Denied'));
402     }
403     return($self->SUPER::_Set(@_));
404
405 }
406
407
408 =head2 CurrentUserHasRight
409
410 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.
411
412 =cut
413
414 sub CurrentUserHasRight {
415     my $self = shift;
416     my $right = shift;
417
418     # object_right is the right that the user has to have on the object for them to have $right on this attribute
419     my $object_right = $self->LookupObjectRight(
420         Right      => $right,
421         ObjectId   => $self->__Value('ObjectId'),
422         ObjectType => $self->__Value('ObjectType'),
423         Name       => $self->__Value('Name')
424     );
425    
426     return (1) if ($object_right eq 'allow');
427     return (0) if ($object_right eq 'deny');
428     return(1) if ($self->CurrentUser->HasRight( Object => $self->Object, Right => $object_right));
429     return(0);
430
431 }
432
433
434 =head1 TODO
435
436 We should be deserializing the content on load and then enver again, rather than at every access
437
438 =cut
439
440
441
442
443
444
445
446
447 =head2 id
448
449 Returns the current value of id.
450 (In the database, id is stored as int(11).)
451
452
453 =cut
454
455
456 =head2 Name
457
458 Returns the current value of Name.
459 (In the database, Name is stored as varchar(255).)
460
461
462
463 =head2 SetName VALUE
464
465
466 Set Name to VALUE.
467 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
468 (In the database, Name will be stored as a varchar(255).)
469
470
471 =cut
472
473
474 =head2 Description
475
476 Returns the current value of Description.
477 (In the database, Description is stored as varchar(255).)
478
479
480
481 =head2 SetDescription VALUE
482
483
484 Set Description to VALUE.
485 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
486 (In the database, Description will be stored as a varchar(255).)
487
488
489 =cut
490
491
492 =head2 Content
493
494 Returns the current value of Content.
495 (In the database, Content is stored as blob.)
496
497
498
499 =head2 SetContent VALUE
500
501
502 Set Content to VALUE.
503 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
504 (In the database, Content will be stored as a blob.)
505
506
507 =cut
508
509
510 =head2 ContentType
511
512 Returns the current value of ContentType.
513 (In the database, ContentType is stored as varchar(16).)
514
515
516
517 =head2 SetContentType VALUE
518
519
520 Set ContentType to VALUE.
521 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
522 (In the database, ContentType will be stored as a varchar(16).)
523
524
525 =cut
526
527
528 =head2 ObjectType
529
530 Returns the current value of ObjectType.
531 (In the database, ObjectType is stored as varchar(64).)
532
533
534
535 =head2 SetObjectType VALUE
536
537
538 Set ObjectType to VALUE.
539 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
540 (In the database, ObjectType will be stored as a varchar(64).)
541
542
543 =cut
544
545
546 =head2 ObjectId
547
548 Returns the current value of ObjectId.
549 (In the database, ObjectId is stored as int(11).)
550
551
552
553 =head2 SetObjectId VALUE
554
555
556 Set ObjectId to VALUE.
557 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
558 (In the database, ObjectId will be stored as a int(11).)
559
560
561 =cut
562
563
564 =head2 Creator
565
566 Returns the current value of Creator.
567 (In the database, Creator is stored as int(11).)
568
569
570 =cut
571
572
573 =head2 Created
574
575 Returns the current value of Created.
576 (In the database, Created is stored as datetime.)
577
578
579 =cut
580
581
582 =head2 LastUpdatedBy
583
584 Returns the current value of LastUpdatedBy.
585 (In the database, LastUpdatedBy is stored as int(11).)
586
587
588 =cut
589
590
591 =head2 LastUpdated
592
593 Returns the current value of LastUpdated.
594 (In the database, LastUpdated is stored as datetime.)
595
596
597 =cut
598
599
600
601 sub _CoreAccessible {
602     {
603
604         id =>
605                 {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
606         Name =>
607                 {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
608         Description =>
609                 {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
610         Content =>
611                 {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'blob', default => ''},
612         ContentType =>
613                 {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
614         ObjectType =>
615                 {read => 1, write => 1, sql_type => 12, length => 64,  is_blob => 0,  is_numeric => 0,  type => 'varchar(64)', default => ''},
616         ObjectId =>
617                 {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
618         Creator =>
619                 {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
620         Created =>
621                 {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
622         LastUpdatedBy =>
623                 {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
624         LastUpdated =>
625                 {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
626
627  }
628 };
629
630 sub FindDependencies {
631     my $self = shift;
632     my ($walker, $deps) = @_;
633
634     $self->SUPER::FindDependencies($walker, $deps);
635     $deps->Add( out => $self->Object );
636 }
637
638 sub PreInflate {
639     my $class = shift;
640     my ($importer, $uid, $data) = @_;
641
642     if ($data->{Object} and ref $data->{Object}) {
643         my $on_uid = ${ $data->{Object} };
644         return if $importer->ShouldSkipTransaction($on_uid);
645     }
646     return $class->SUPER::PreInflate( $importer, $uid, $data );
647 }
648
649 RT::Base->_ImportOverlays();
650
651 1;