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