1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC
6 # <sales@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/licenses/old-licenses/gpl-2.0.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 }}}
49 package RT::Attribute;
52 no warnings qw(redefine);
53 use Storable qw/nfreeze thaw/;
65 # 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
68 SavedSearch => { create => 'EditSavedSearches',
69 update => 'EditSavedSearches',
70 delete => 'EditSavedSearches',
71 display => 'ShowSavedSearches' },
75 # There are a number of attributes that users should be able to modify for themselves, such as saved searches
76 # we could do this with a different set of "update" rights, but that gets very hacky very fast. this is even faster and even
77 # hackier. we're hardcoding that a different set of rights are needed for attributes on oneself
78 our $PERSONAL_ACL_MAP = {
79 SavedSearch => { create => 'ModifySelf',
80 update => 'ModifySelf',
81 delete => 'ModifySelf',
86 =head2 LookupObjectRight { ObjectType => undef, ObjectId => undef, Name => undef, Right => { create, update, delete, display } }
88 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.
92 sub LookupObjectRight {
94 my %args = ( ObjectType => undef,
100 # if it's an attribute on oneself, check the personal acl map
101 if (($args{'ObjectType'} eq 'RT::User') && ($args{'ObjectId'} eq $self->CurrentUser->Id)) {
102 return('allow') unless ($PERSONAL_ACL_MAP->{$args{'Name'}});
103 return('allow') unless ($PERSONAL_ACL_MAP->{$args{'Name'}}->{$args{'Right'}});
104 return($PERSONAL_ACL_MAP->{$args{'Name'}}->{$args{'Right'}});
107 # otherwise check the main ACL map
109 return('allow') unless ($ACL_MAP->{$args{'Name'}});
110 return('allow') unless ($ACL_MAP->{$args{'Name'}}->{$args{'Right'}});
111 return($ACL_MAP->{$args{'Name'}}->{$args{'Right'}});
118 =head2 Create PARAMHASH
120 Create takes a hash of values and creates a row in the database:
123 varchar(255) 'Content'.
124 varchar(16) 'ContentType',
125 varchar(64) 'ObjectType'.
128 You may pass a C<Object> instead of C<ObjectType> and C<ObjectId>.
145 if ($args{Object} and UNIVERSAL::can($args{Object}, 'Id')) {
146 $args{ObjectType} = ref($args{Object});
147 $args{ObjectId} = $args{Object}->Id;
149 return(0, $self->loc("Required parameter '[_1]' not specified", 'Object'));
153 # object_right is the right that the user has to have on the object for them to have $right on this attribute
154 my $object_right = $self->LookupObjectRight(
156 ObjectId => $args{'ObjectId'},
157 ObjectType => $args{'ObjectType'},
158 Name => $args{'Name'}
160 if ($object_right eq 'deny') {
161 return (0, $self->loc('Permission Denied'));
163 elsif ($object_right eq 'allow') {
164 # do nothing, we're ok
166 elsif (!$self->CurrentUser->HasRight( Object => $args{Object}, Right => $object_right)) {
167 return (0, $self->loc('Permission Denied'));
171 if (ref ($args{'Content'}) ) {
172 eval {$args{'Content'} = $self->_SerializeContent($args{'Content'}); };
176 $args{'ContentType'} = 'storable';
180 $self->SUPER::Create(
181 Name => $args{'Name'},
182 Content => $args{'Content'},
183 ContentType => $args{'ContentType'},
184 Description => $args{'Description'},
185 ObjectType => $args{'ObjectType'},
186 ObjectId => $args{'ObjectId'},
192 # {{{ sub LoadByNameAndObject
194 =head2 LoadByNameAndObject (Object => OBJECT, Name => NAME)
196 Loads the Attribute named NAME for Object OBJECT.
200 sub LoadByNameAndObject {
210 Name => $args{'Name'},
211 ObjectType => ref($args{'Object'}),
212 ObjectId => $args{'Object'}->Id,
221 =head2 _DeserializeContent
223 DeserializeContent returns this Attribute's "Content" as a hashref.
228 sub _DeserializeContent {
233 eval {$hashref = thaw(decode_base64($content))} ;
235 $RT::Logger->error("Deserialization of attribute ".$self->Id. " failed");
245 Returns this attribute's content. If it's a scalar, returns a scalar
246 If it's data structure returns a ref to that data structure.
252 # Here we call _Value to get the ACL check.
253 my $content = $self->_Value('Content');
254 if ($self->__Value('ContentType') eq 'storable') {
255 eval {$content = $self->_DeserializeContent($content); };
257 $RT::Logger->error("Deserialization of content for attribute ".$self->Id. " failed. Attribute was: ".$content);
265 sub _SerializeContent {
268 return( encode_base64(nfreeze($content)));
276 # Call __Value to avoid ACL check.
277 if ( $self->__Value('ContentType') eq 'storable' ) {
278 # We eval the serialization because it will lose on a coderef.
279 $content = eval { $self->_SerializeContent($content) };
281 $RT::Logger->error("Content couldn't be frozen: $@");
282 return(0, "Content couldn't be frozen");
285 return $self->SUPER::SetContent( $content );
290 Returns the subvalue for $key.
298 my $values = $self->Content();
299 return undef unless ref($values);
300 return($values->{$key});
303 =head2 DeleteSubValue NAME
305 Deletes the subvalue with the key NAME
312 my $values = $self->Content();
313 delete $values->{$key};
314 $self->SetContent($values);
318 =head2 DeleteAllSubValues
320 Deletes all subvalues for this attribute
325 sub DeleteAllSubValues {
327 $self->SetContent({});
330 =head2 SetSubValues { }
332 Takes a hash of keys and values and stores them in the content of this attribute.
334 Each key B<replaces> the existing key with the same name
336 Returns a tuple of (status, message)
344 my $values = ($self->Content() || {} );
345 foreach my $key (keys %args) {
346 $values->{$key} = $args{$key};
349 $self->SetContent($values);
356 my $object_type = $self->__Value('ObjectType');
358 eval { $object = $object_type->new($self->CurrentUser) };
359 unless(UNIVERSAL::isa($object, $object_type)) {
360 $RT::Logger->error("Attribute ".$self->Id." has a bogus object type - $object_type (".$@.")");
363 $object->Load($self->__Value('ObjectId'));
372 unless ($self->CurrentUserHasRight('delete')) {
373 return (0,$self->loc('Permission Denied'));
375 return($self->SUPER::Delete(@_));
381 unless ($self->CurrentUserHasRight('display')) {
382 return (0,$self->loc('Permission Denied'));
385 return($self->SUPER::_Value(@_));
393 unless ($self->CurrentUserHasRight('update')) {
395 return (0,$self->loc('Permission Denied'));
397 return($self->SUPER::_Set(@_));
402 =head2 CurrentUserHasRight
404 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.
408 sub CurrentUserHasRight {
412 # object_right is the right that the user has to have on the object for them to have $right on this attribute
413 my $object_right = $self->LookupObjectRight(
415 ObjectId => $self->__Value('ObjectId'),
416 ObjectType => $self->__Value('ObjectType'),
417 Name => $self->__Value('Name')
420 return (1) if ($object_right eq 'allow');
421 return (0) if ($object_right eq 'deny');
422 return(1) if ($self->CurrentUser->HasRight( Object => $self->Object, Right => $object_right));
430 We should be deserializing the content on load and then enver again, rather than at every access