This commit was generated by cvs2svn to compensate for changes in r4407,
[freeside.git] / rt / lib / RT / Attribute_Overlay.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2
3 # COPYRIGHT:
4 #  
5 # This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC 
6 #                                          <jesse@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., 675 Mass Ave, Cambridge, MA 02139, USA.
26
27
28 # CONTRIBUTION SUBMISSION POLICY:
29
30 # (The following paragraph is not intended to limit the rights granted
31 # to you to modify and distribute this software under the terms of
32 # the GNU General Public License and is only of importance to you if
33 # you choose to contribute your changes and enhancements to the
34 # community by submitting them to Best Practical Solutions, LLC.)
35
36 # By intentionally submitting any modifications, corrections or
37 # derivatives to this work, or any other work intended for use with
38 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
39 # you are the copyright holder for those contributions and you grant
40 # Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
41 # royalty-free, perpetual, license to use, copy, create derivative
42 # works based on those contributions, and sublicense and distribute
43 # those contributions and any derivatives thereof.
44
45 # END BPS TAGGED BLOCK }}}
46 package RT::Attribute;
47
48 use strict;
49 no warnings qw(redefine);
50 use Storable qw/nfreeze thaw/;
51 use MIME::Base64;
52
53
54 =head1 NAME
55
56   RT::Attribute_Overlay 
57
58 =head1 Content
59
60 =cut
61
62 # 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
63
64 our $ACL_MAP = {
65     SavedSearch => { create => 'EditSavedSearches',
66                      update => 'EditSavedSearches',
67                      delete => 'EditSavedSearches',
68                      display => 'ShowSavedSearches' },
69
70 };
71
72 # There are a number of attributes that users should be able to modify for themselves, such as saved searches
73 #  we could do this with a different set of "modify" rights, but that gets very hacky very fast. this is even faster and even
74 # hackier. we're hardcoding that a different set of rights are needed for attributes on oneself
75 our $PERSONAL_ACL_MAP = { 
76     SavedSearch => { create => 'ModifySelf',
77                      update => 'ModifySelf',
78                      delete => 'ModifySelf',
79                      display => 'allow' },
80
81 };
82
83 =head2 LookupObjectRight { ObjectType => undef, ObjectId => undef, Name => undef, Right => { create, update, delete, display } }
84
85 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.
86
87 =cut
88
89 sub LookupObjectRight { 
90     my $self = shift;
91     my %args = ( ObjectType => undef,
92                  ObjectId => undef,
93                  Right => undef,
94                  Name => undef,
95                  @_);
96
97     # if it's an attribute on oneself, check the personal acl map
98     if (($args{'ObjectType'} eq 'RT::User') && ($args{'ObjectId'} eq $self->CurrentUser->Id)) {
99     return('allow') unless ($PERSONAL_ACL_MAP->{$args{'Name'}});
100     return('allow') unless ($PERSONAL_ACL_MAP->{$args{'Name'}}->{$args{'Right'}});
101     return($PERSONAL_ACL_MAP->{$args{'Name'}}->{$args{'Right'}}); 
102
103     }
104    # otherwise check the main ACL map
105     else {
106     return('allow') unless ($ACL_MAP->{$args{'Name'}});
107     return('allow') unless ($ACL_MAP->{$args{'Name'}}->{$args{'Right'}});
108     return($ACL_MAP->{$args{'Name'}}->{$args{'Right'}}); 
109     }
110 }
111
112
113
114
115 =head2 Create PARAMHASH
116
117 Create takes a hash of values and creates a row in the database:
118
119   varchar(200) 'Name'.
120   varchar(255) 'Content'.
121   varchar(16) 'ContentType',
122   varchar(64) 'ObjectType'.
123   int(11) 'ObjectId'.
124
125 You may pass a C<Object> instead of C<ObjectType> and C<ObjectId>.
126
127 =cut
128
129
130
131
132 sub Create {
133     my $self = shift;
134     my %args = ( 
135                 Name => '',
136                 Description => '',
137                 Content => '',
138                 ContentType => '',
139                 Object => undef,
140                   @_);
141
142     if ($args{Object} and UNIVERSAL::can($args{Object}, 'Id')) {
143             $args{ObjectType} = ref($args{Object});
144             $args{ObjectId} = $args{Object}->Id;
145     } else {
146         return(0, $self->loc("Required parameter '[_1]' not specified", 'Object'));
147
148     }
149    
150     # object_right is the right that the user has to have on the object for them to have $right on this attribute
151     my $object_right = $self->LookupObjectRight(
152         Right      => 'create',
153         ObjectId   => $args{'ObjectId'},
154         ObjectType => $args{'ObjectType'},
155         Name       => $args{'Name'}
156     );
157     if ($object_right eq 'deny') { 
158         return (0, $self->loc('Permission Denied'));
159     } 
160     elsif ($object_right eq 'allow') {
161         # do nothing, we're ok
162     }
163     elsif (!$self->CurrentUser->HasRight( Object => $args{Object}, Right => $object_right)) {
164         return (0, $self->loc('Permission Denied'));
165     }
166
167    
168     if (ref ($args{'Content'}) ) { 
169         eval  {$args{'Content'} = $self->_SerializeContent($args{'Content'}); };
170         if ($@) {
171          return(0, $@);
172         }
173         $args{'ContentType'} = 'storable';
174     }
175
176     
177     $self->SUPER::Create(
178                          Name => $args{'Name'},
179                          Content => $args{'Content'},
180                          ContentType => $args{'ContentType'},
181                          Description => $args{'Description'},
182                          ObjectType => $args{'ObjectType'},
183                          ObjectId => $args{'ObjectId'},
184 );
185
186 }
187
188
189 # {{{ sub LoadByNameAndObject
190
191 =head2  LoadByNameAndObject (Object => OBJECT, Name => NAME)
192
193 Loads the Attribute named NAME for Object OBJECT.
194
195 =cut
196
197 sub LoadByNameAndObject {
198     my $self = shift;
199     my %args = (
200         Object => undef,
201         Name  => undef,
202         @_,
203     );
204
205     return (
206         $self->LoadByCols(
207             Name => $args{'Name'},
208             ObjectType => ref($args{'Object'}),
209             ObjectId => $args{'Object'}->Id,
210         )
211     );
212
213 }
214
215 # }}}
216
217
218 =head2 _DeserializeContent
219
220 DeserializeContent returns this Attribute's "Content" as a hashref.
221
222
223 =cut
224
225 sub _DeserializeContent {
226     my $self = shift;
227     my $content = shift;
228
229     my $hashref;
230     eval {$hashref  = thaw(decode_base64($content))} ; 
231     if ($@) {
232         $RT::Logger->error("Deserialization of attribute ".$self->Id. " failed");
233     }
234
235     return($hashref);
236
237 }
238
239
240 =head2 Content
241
242 Returns this attribute's content. If it's a scalar, returns a scalar
243 If it's data structure returns a ref to that data structure.
244
245 =cut
246
247 sub Content {
248     my $self = shift;
249     # Here we call _Value to get the ACL check.
250     my $content = $self->_Value('Content');
251     if ($self->__Value('ContentType') eq 'storable') {
252         eval {$content = $self->_DeserializeContent($content); };
253         if ($@) {
254             $RT::Logger->error("Deserialization of content for attribute ".$self->Id. " failed. Attribute was: ".$content);
255         }
256     } 
257
258     return($content);
259
260 }
261
262 sub _SerializeContent {
263     my $self = shift;
264     my $content = shift;
265         return( encode_base64(nfreeze($content))); 
266 }
267
268
269 sub SetContent {
270     my $self = shift;
271     my $content = shift;
272     
273     # Call __Value to avoid ACL check.
274     if ($self->__Value('ContentType') eq 'storable') {
275     # We eval the serialization because it will lose on a coderef.
276     eval  {$content = $self->_SerializeContent($content); };
277     if ($@) {
278         $RT::Logger->error("For some reason, content couldn't be frozen");
279         return(0, $@);
280     }
281     }
282     return ($self->SUPER::SetContent($content));
283 }
284
285 =head2 SubValue KEY
286
287 Returns the subvalue for $key.
288
289 =begin testing
290
291 my $user = $RT::SystemUser;
292 my ($id, $msg) =  $user->AddAttribute(Name => 'SavedSearch', Content => { Query => 'Foo'} );
293 ok ($id, $msg);
294 my $attr = RT::Attribute->new($RT::SystemUser);
295 $attr->Load($id);
296 ok($attr->Name eq 'SavedSearch');
297 $attr->SetSubValues( Format => 'baz');
298
299 my $format = $attr->SubValue('Format');
300 is ($format , 'baz');
301
302 $attr->SetSubValues( Format => 'bar');
303 $format = $attr->SubValue('Format');
304 is ($format , 'bar');
305
306 $attr->DeleteAllSubValues();
307 $format = $attr->SubValue('Format');
308 is ($format, undef);
309
310 $attr->SetSubValues(Format => 'This is a format');
311
312 my $attr2 = RT::Attribute->new($RT::SystemUser);
313 $attr2->Load($id);
314 is ($attr2->SubValue('Format'), 'This is a format');
315
316
317 =end testing
318
319 =cut
320
321 sub SubValue {
322     my $self = shift;
323     my $key = shift;
324     my $values = $self->Content();
325     return undef unless ref($values);
326     return($values->{$key});
327 }
328
329 =head2 DeleteSubValue NAME
330
331 Deletes the subvalue with the key NAME
332
333 =cut
334
335 sub DeleteSubValue {
336     my $self = shift;
337     my $key = shift;
338     my %values = $self->Content();
339     delete $values{$key};
340     $self->SetContent(%values);
341
342     
343
344 }
345
346
347 =head2 DeleteAllSubValues 
348
349 Deletes all subvalues for this attribute
350
351 =cut
352
353
354 sub DeleteAllSubValues {
355     my $self = shift; 
356     $self->SetContent({});
357 }
358
359 =head2 SetSubValues  {  }
360
361 Takes a hash of keys and values and stores them in the content of this attribute.
362
363 Each key B<replaces> the existing key with the same name
364
365 Returns a tuple of (status, message)
366
367 =cut
368
369
370 sub SetSubValues {
371    my $self = shift;
372    my %args = (@_); 
373    my $values = ($self->Content() || {} );
374    foreach my $key (keys %args) {
375     $values->{$key} = $args{$key};
376    }
377
378    $self->SetContent($values);
379
380 }
381
382
383 sub Object {
384     my $self = shift;
385     my $object_type = $self->__Value('ObjectType');
386     my $object;
387     eval { $object = $object_type->new($self->CurrentUser) };
388     unless(UNIVERSAL::isa($object, $object_type)) {
389         $RT::Logger->error("Attribute ".$self->Id." has a bogus object type - $object_type (".$@.")");
390         return(undef);
391      }
392     $object->Load($self->__Value('ObjectId'));
393
394     return($object);
395
396 }
397
398
399 sub Delete {
400     my $self = shift;
401     unless ($self->CurrentUserHasRight('delete')) {
402         return (0,$self->loc('Permission Denied'));
403     }
404     return($self->SUPER::Delete(@_));
405 }
406
407
408 sub _Value {
409     my $self = shift;
410     unless ($self->CurrentUserHasRight('display')) {
411         return (0,$self->loc('Permission Denied'));
412     }
413
414     return($self->SUPER::_Value(@_));
415
416
417 }
418
419
420 sub _Set {
421     my $self = shift;
422     unless ($self->CurrentUserHasRight('modify')) {
423
424         return (0,$self->loc('Permission Denied'));
425     }
426     return($self->SUPER::_Set(@_));
427
428 }
429
430
431 =head2 CurrentUserHasRight
432
433 One of "display" "modify" "delete" or "create" and returns 1 if the user has that right for attributes of this name for this object.Returns undef otherwise.
434
435 =cut
436
437 sub CurrentUserHasRight {
438     my $self = shift;
439     my $right = shift;
440
441     # object_right is the right that the user has to have on the object for them to have $right on this attribute
442     my $object_right = $self->LookupObjectRight(
443         Right      => $right,
444         ObjectId   => $self->__Value('ObjectId'),
445         ObjectType => $self->__Value('ObjectType'),
446         Name       => $self->__Value('Name')
447     );
448    
449     return (1) if ($object_right eq 'allow');
450     return (0) if ($object_right eq 'deny');
451     return(1) if ($self->CurrentUser->HasRight( Object => $self->Object, Right => $object_right));
452     return(0);
453
454 }
455
456
457 =head1 TODO
458
459 We should be deserializing the content on load and then enver again, rather than at every access
460
461 =cut
462
463
464 1;