a0edb15c43881f46a4e47cc2476089ce3bd79fb4
[usit-rt.git] / lib / RT / Attribute.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2013 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} = 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     
185     $self->SUPER::Create(
186                          Name => $args{'Name'},
187                          Content => $args{'Content'},
188                          ContentType => $args{'ContentType'},
189                          Description => $args{'Description'},
190                          ObjectType => $args{'ObjectType'},
191                          ObjectId => $args{'ObjectId'},
192 );
193
194 }
195
196
197
198 =head2  LoadByNameAndObject (Object => OBJECT, Name => NAME)
199
200 Loads the Attribute named NAME for Object OBJECT.
201
202 =cut
203
204 sub LoadByNameAndObject {
205     my $self = shift;
206     my %args = (
207         Object => undef,
208         Name  => undef,
209         @_,
210     );
211
212     return (
213         $self->LoadByCols(
214             Name => $args{'Name'},
215             ObjectType => ref($args{'Object'}),
216             ObjectId => $args{'Object'}->Id,
217         )
218     );
219
220 }
221
222
223
224 =head2 _DeserializeContent
225
226 DeserializeContent returns this Attribute's "Content" as a hashref.
227
228
229 =cut
230
231 sub _DeserializeContent {
232     my $self = shift;
233     my $content = shift;
234
235     my $hashref;
236     eval {$hashref  = thaw(decode_base64($content))} ; 
237     if ($@) {
238         $RT::Logger->error("Deserialization of attribute ".$self->Id. " failed");
239     }
240
241     return($hashref);
242
243 }
244
245
246 =head2 Content
247
248 Returns this attribute's content. If it's a scalar, returns a scalar
249 If it's data structure returns a ref to that data structure.
250
251 =cut
252
253 sub Content {
254     my $self = shift;
255     # Here we call _Value to get the ACL check.
256     my $content = $self->_Value('Content');
257     if ( ($self->__Value('ContentType') || '') eq 'storable') {
258         eval {$content = $self->_DeserializeContent($content); };
259         if ($@) {
260             $RT::Logger->error("Deserialization of content for attribute ".$self->Id. " failed. Attribute was: ".$content);
261         }
262     } 
263
264     return($content);
265
266 }
267
268 sub _SerializeContent {
269     my $self = shift;
270     my $content = shift;
271         return( encode_base64(nfreeze($content))); 
272 }
273
274
275 sub SetContent {
276     my $self = shift;
277     my $content = shift;
278
279     # Call __Value to avoid ACL check.
280     if ( ($self->__Value('ContentType')||'') eq 'storable' ) {
281         # We eval the serialization because it will lose on a coderef.
282         $content = eval { $self->_SerializeContent($content) };
283         if ($@) {
284             $RT::Logger->error("Content couldn't be frozen: $@");
285             return(0, "Content couldn't be frozen");
286         }
287     }
288     return $self->_Set( Field => 'Content', Value => $content );
289 }
290
291 =head2 SubValue KEY
292
293 Returns the subvalue for $key.
294
295
296 =cut
297
298 sub SubValue {
299     my $self = shift;
300     my $key = shift;
301     my $values = $self->Content();
302     return undef unless ref($values);
303     return($values->{$key});
304 }
305
306 =head2 DeleteSubValue NAME
307
308 Deletes the subvalue with the key NAME
309
310 =cut
311
312 sub DeleteSubValue {
313     my $self = shift;
314     my $key = shift;
315     my %values = $self->Content();
316     delete $values{$key};
317     $self->SetContent(%values);
318
319     
320
321 }
322
323
324 =head2 DeleteAllSubValues 
325
326 Deletes all subvalues for this attribute
327
328 =cut
329
330
331 sub DeleteAllSubValues {
332     my $self = shift; 
333     $self->SetContent({});
334 }
335
336 =head2 SetSubValues  {  }
337
338 Takes a hash of keys and values and stores them in the content of this attribute.
339
340 Each key B<replaces> the existing key with the same name
341
342 Returns a tuple of (status, message)
343
344 =cut
345
346
347 sub SetSubValues {
348    my $self = shift;
349    my %args = (@_); 
350    my $values = ($self->Content() || {} );
351    foreach my $key (keys %args) {
352     $values->{$key} = $args{$key};
353    }
354
355    $self->SetContent($values);
356
357 }
358
359
360 sub Object {
361     my $self = shift;
362     my $object_type = $self->__Value('ObjectType');
363     my $object;
364     eval { $object = $object_type->new($self->CurrentUser) };
365     unless(UNIVERSAL::isa($object, $object_type)) {
366         $RT::Logger->error("Attribute ".$self->Id." has a bogus object type - $object_type (".$@.")");
367         return(undef);
368      }
369     $object->Load($self->__Value('ObjectId'));
370
371     return($object);
372
373 }
374
375
376 sub Delete {
377     my $self = shift;
378     unless ($self->CurrentUserHasRight('delete')) {
379         return (0,$self->loc('Permission Denied'));
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 RT::Base->_ImportOverlays();
631
632 1;