1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC
6 # <jesse@bestpractical.com>
8 # (Except where explicitly superseded by other copyright notices)
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
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.
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/copyleft/gpl.html.
30 # CONTRIBUTION SUBMISSION POLICY:
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.)
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.
47 # END BPS TAGGED BLOCK }}}
48 package RT::Attribute;
51 no warnings qw(redefine);
52 use Storable qw/nfreeze thaw/;
64 # 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
67 SavedSearch => { create => 'EditSavedSearches',
68 update => 'EditSavedSearches',
69 delete => 'EditSavedSearches',
70 display => 'ShowSavedSearches' },
74 # There are a number of attributes that users should be able to modify for themselves, such as saved searches
75 # we could do this with a different set of "modify" rights, but that gets very hacky very fast. this is even faster and even
76 # hackier. we're hardcoding that a different set of rights are needed for attributes on oneself
77 our $PERSONAL_ACL_MAP = {
78 SavedSearch => { create => 'ModifySelf',
79 update => 'ModifySelf',
80 delete => 'ModifySelf',
85 =head2 LookupObjectRight { ObjectType => undef, ObjectId => undef, Name => undef, Right => { create, update, delete, display } }
87 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.
91 sub LookupObjectRight {
93 my %args = ( ObjectType => undef,
99 # if it's an attribute on oneself, check the personal acl map
100 if (($args{'ObjectType'} eq 'RT::User') && ($args{'ObjectId'} eq $self->CurrentUser->Id)) {
101 return('allow') unless ($PERSONAL_ACL_MAP->{$args{'Name'}});
102 return('allow') unless ($PERSONAL_ACL_MAP->{$args{'Name'}}->{$args{'Right'}});
103 return($PERSONAL_ACL_MAP->{$args{'Name'}}->{$args{'Right'}});
106 # otherwise check the main ACL map
108 return('allow') unless ($ACL_MAP->{$args{'Name'}});
109 return('allow') unless ($ACL_MAP->{$args{'Name'}}->{$args{'Right'}});
110 return($ACL_MAP->{$args{'Name'}}->{$args{'Right'}});
117 =head2 Create PARAMHASH
119 Create takes a hash of values and creates a row in the database:
122 varchar(255) 'Content'.
123 varchar(16) 'ContentType',
124 varchar(64) 'ObjectType'.
127 You may pass a C<Object> instead of C<ObjectType> and C<ObjectId>.
144 if ($args{Object} and UNIVERSAL::can($args{Object}, 'Id')) {
145 $args{ObjectType} = ref($args{Object});
146 $args{ObjectId} = $args{Object}->Id;
148 return(0, $self->loc("Required parameter '[_1]' not specified", 'Object'));
152 # object_right is the right that the user has to have on the object for them to have $right on this attribute
153 my $object_right = $self->LookupObjectRight(
155 ObjectId => $args{'ObjectId'},
156 ObjectType => $args{'ObjectType'},
157 Name => $args{'Name'}
159 if ($object_right eq 'deny') {
160 return (0, $self->loc('Permission Denied'));
162 elsif ($object_right eq 'allow') {
163 # do nothing, we're ok
165 elsif (!$self->CurrentUser->HasRight( Object => $args{Object}, Right => $object_right)) {
166 return (0, $self->loc('Permission Denied'));
170 if (ref ($args{'Content'}) ) {
171 eval {$args{'Content'} = $self->_SerializeContent($args{'Content'}); };
175 $args{'ContentType'} = 'storable';
179 $self->SUPER::Create(
180 Name => $args{'Name'},
181 Content => $args{'Content'},
182 ContentType => $args{'ContentType'},
183 Description => $args{'Description'},
184 ObjectType => $args{'ObjectType'},
185 ObjectId => $args{'ObjectId'},
191 # {{{ sub LoadByNameAndObject
193 =head2 LoadByNameAndObject (Object => OBJECT, Name => NAME)
195 Loads the Attribute named NAME for Object OBJECT.
199 sub LoadByNameAndObject {
209 Name => $args{'Name'},
210 ObjectType => ref($args{'Object'}),
211 ObjectId => $args{'Object'}->Id,
220 =head2 _DeserializeContent
222 DeserializeContent returns this Attribute's "Content" as a hashref.
227 sub _DeserializeContent {
232 eval {$hashref = thaw(decode_base64($content))} ;
234 $RT::Logger->error("Deserialization of attribute ".$self->Id. " failed");
244 Returns this attribute's content. If it's a scalar, returns a scalar
245 If it's data structure returns a ref to that data structure.
251 # Here we call _Value to get the ACL check.
252 my $content = $self->_Value('Content');
253 if ($self->__Value('ContentType') eq 'storable') {
254 eval {$content = $self->_DeserializeContent($content); };
256 $RT::Logger->error("Deserialization of content for attribute ".$self->Id. " failed. Attribute was: ".$content);
264 sub _SerializeContent {
267 return( encode_base64(nfreeze($content)));
275 # Call __Value to avoid ACL check.
276 if ($self->__Value('ContentType') eq 'storable') {
277 # We eval the serialization because it will lose on a coderef.
278 eval {$content = $self->_SerializeContent($content); };
280 $RT::Logger->error("For some reason, content couldn't be frozen");
284 return ($self->SUPER::SetContent($content));
289 Returns the subvalue for $key.
293 my $user = $RT::SystemUser;
294 my ($id, $msg) = $user->AddAttribute(Name => 'SavedSearch', Content => { Query => 'Foo'} );
296 my $attr = RT::Attribute->new($RT::SystemUser);
298 ok($attr->Name eq 'SavedSearch');
299 $attr->SetSubValues( Format => 'baz');
301 my $format = $attr->SubValue('Format');
302 is ($format , 'baz');
304 $attr->SetSubValues( Format => 'bar');
305 $format = $attr->SubValue('Format');
306 is ($format , 'bar');
308 $attr->DeleteAllSubValues();
309 $format = $attr->SubValue('Format');
312 $attr->SetSubValues(Format => 'This is a format');
314 my $attr2 = RT::Attribute->new($RT::SystemUser);
316 is ($attr2->SubValue('Format'), 'This is a format');
318 my $attr3 = RT::Attribute->new($RT::SystemUser);
319 my ($id) = $attr3->Load($id);
329 my $values = $self->Content();
330 return undef unless ref($values);
331 return($values->{$key});
334 =head2 DeleteSubValue NAME
336 Deletes the subvalue with the key NAME
343 my %values = $self->Content();
344 delete $values{$key};
345 $self->SetContent(%values);
352 =head2 DeleteAllSubValues
354 Deletes all subvalues for this attribute
359 sub DeleteAllSubValues {
361 $self->SetContent({});
364 =head2 SetSubValues { }
366 Takes a hash of keys and values and stores them in the content of this attribute.
368 Each key B<replaces> the existing key with the same name
370 Returns a tuple of (status, message)
378 my $values = ($self->Content() || {} );
379 foreach my $key (keys %args) {
380 $values->{$key} = $args{$key};
383 $self->SetContent($values);
390 my $object_type = $self->__Value('ObjectType');
392 eval { $object = $object_type->new($self->CurrentUser) };
393 unless(UNIVERSAL::isa($object, $object_type)) {
394 $RT::Logger->error("Attribute ".$self->Id." has a bogus object type - $object_type (".$@.")");
397 $object->Load($self->__Value('ObjectId'));
406 unless ($self->CurrentUserHasRight('delete')) {
407 return (0,$self->loc('Permission Denied'));
409 return($self->SUPER::Delete(@_));
415 unless ($self->CurrentUserHasRight('display')) {
416 return (0,$self->loc('Permission Denied'));
419 return($self->SUPER::_Value(@_));
427 unless ($self->CurrentUserHasRight('modify')) {
429 return (0,$self->loc('Permission Denied'));
431 return($self->SUPER::_Set(@_));
436 =head2 CurrentUserHasRight
438 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.
442 sub CurrentUserHasRight {
446 # object_right is the right that the user has to have on the object for them to have $right on this attribute
447 my $object_right = $self->LookupObjectRight(
449 ObjectId => $self->__Value('ObjectId'),
450 ObjectType => $self->__Value('ObjectType'),
451 Name => $self->__Value('Name')
454 return (1) if ($object_right eq 'allow');
455 return (0) if ($object_right eq 'deny');
456 return(1) if ($self->CurrentUser->HasRight( Object => $self->Object, Right => $object_right));
464 We should be deserializing the content on load and then enver again, rather than at every access