1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2014 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;
54 use base 'RT::Record';
56 sub Table {'Attributes'}
58 use Storable qw/nfreeze thaw/;
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
73 SavedSearch => { create => 'EditSavedSearches',
74 update => 'EditSavedSearches',
75 delete => 'EditSavedSearches',
76 display => 'ShowSavedSearches' },
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',
91 =head2 LookupObjectRight { ObjectType => undef, ObjectId => undef, Name => undef, Right => { create, update, delete, display } }
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.
97 sub LookupObjectRight {
99 my %args = ( ObjectType => undef,
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'}});
112 # otherwise check the main ACL map
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'}});
123 =head2 Create PARAMHASH
125 Create takes a hash of values and creates a row in the database:
128 varchar(255) 'Content'.
129 varchar(16) 'ContentType',
130 varchar(64) 'ObjectType'.
133 You may pass a C<Object> instead of C<ObjectType> and C<ObjectId>.
150 if ($args{Object} and UNIVERSAL::can($args{Object}, 'Id')) {
151 $args{ObjectType} = $args{Object}->isa("RT::CurrentUser") ? "RT::User" : ref($args{Object});
152 $args{ObjectId} = $args{Object}->Id;
154 return(0, $self->loc("Required parameter '[_1]' not specified", 'Object'));
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(
161 ObjectId => $args{'ObjectId'},
162 ObjectType => $args{'ObjectType'},
163 Name => $args{'Name'}
165 if ($object_right eq 'deny') {
166 return (0, $self->loc('Permission Denied'));
168 elsif ($object_right eq 'allow') {
169 # do nothing, we're ok
171 elsif (!$self->CurrentUser->HasRight( Object => $args{Object}, Right => $object_right)) {
172 return (0, $self->loc('Permission Denied'));
176 if (ref ($args{'Content'}) ) {
177 eval {$args{'Content'} = $self->_SerializeContent($args{'Content'}); };
181 $args{'ContentType'} = 'storable';
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'},
198 =head2 LoadByNameAndObject (Object => OBJECT, Name => NAME)
200 Loads the Attribute named NAME for Object OBJECT.
204 sub LoadByNameAndObject {
214 Name => $args{'Name'},
215 ObjectType => ref($args{'Object'}),
216 ObjectId => $args{'Object'}->Id,
224 =head2 _DeserializeContent
226 DeserializeContent returns this Attribute's "Content" as a hashref.
231 sub _DeserializeContent {
236 eval {$hashref = thaw(decode_base64($content))} ;
238 $RT::Logger->error("Deserialization of attribute ".$self->Id. " failed");
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.
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); };
260 $RT::Logger->error("Deserialization of content for attribute ".$self->Id. " failed. Attribute was: ".$content);
268 sub _SerializeContent {
271 return( encode_base64(nfreeze($content)));
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) };
284 $RT::Logger->error("Content couldn't be frozen: $@");
285 return(0, "Content couldn't be frozen");
288 return $self->_Set( Field => 'Content', Value => $content );
293 Returns the subvalue for $key.
301 my $values = $self->Content();
302 return undef unless ref($values);
303 return($values->{$key});
306 =head2 DeleteSubValue NAME
308 Deletes the subvalue with the key NAME
315 my $values = $self->Content();
316 delete $values->{$key};
317 $self->SetContent($values);
321 =head2 DeleteAllSubValues
323 Deletes all subvalues for this attribute
328 sub DeleteAllSubValues {
330 $self->SetContent({});
333 =head2 SetSubValues { }
335 Takes a hash of keys and values and stores them in the content of this attribute.
337 Each key B<replaces> the existing key with the same name
339 Returns a tuple of (status, message)
347 my $values = ($self->Content() || {} );
348 foreach my $key (keys %args) {
349 $values->{$key} = $args{$key};
352 $self->SetContent($values);
359 my $object_type = $self->__Value('ObjectType');
361 eval { $object = $object_type->new($self->CurrentUser) };
362 unless(UNIVERSAL::isa($object, $object_type)) {
363 $RT::Logger->error("Attribute ".$self->Id." has a bogus object type - $object_type (".$@.")");
366 $object->Load($self->__Value('ObjectId'));
375 unless ($self->CurrentUserHasRight('delete')) {
376 return (0,$self->loc('Permission Denied'));
378 return($self->SUPER::Delete(@_));
384 unless ($self->CurrentUserHasRight('display')) {
385 return (0,$self->loc('Permission Denied'));
388 return($self->SUPER::_Value(@_));
396 unless ($self->CurrentUserHasRight('update')) {
398 return (0,$self->loc('Permission Denied'));
400 return($self->SUPER::_Set(@_));
405 =head2 CurrentUserHasRight
407 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 sub CurrentUserHasRight {
415 # object_right is the right that the user has to have on the object for them to have $right on this attribute
416 my $object_right = $self->LookupObjectRight(
418 ObjectId => $self->__Value('ObjectId'),
419 ObjectType => $self->__Value('ObjectType'),
420 Name => $self->__Value('Name')
423 return (1) if ($object_right eq 'allow');
424 return (0) if ($object_right eq 'deny');
425 return(1) if ($self->CurrentUser->HasRight( Object => $self->Object, Right => $object_right));
433 We should be deserializing the content on load and then enver again, rather than at every access
446 Returns the current value of id.
447 (In the database, id is stored as int(11).)
455 Returns the current value of Name.
456 (In the database, Name is stored as varchar(255).)
464 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
465 (In the database, Name will be stored as a varchar(255).)
473 Returns the current value of Description.
474 (In the database, Description is stored as varchar(255).)
478 =head2 SetDescription VALUE
481 Set Description to VALUE.
482 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
483 (In the database, Description will be stored as a varchar(255).)
491 Returns the current value of Content.
492 (In the database, Content is stored as blob.)
496 =head2 SetContent VALUE
499 Set Content to VALUE.
500 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
501 (In the database, Content will be stored as a blob.)
509 Returns the current value of ContentType.
510 (In the database, ContentType is stored as varchar(16).)
514 =head2 SetContentType VALUE
517 Set ContentType to VALUE.
518 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
519 (In the database, ContentType will be stored as a varchar(16).)
527 Returns the current value of ObjectType.
528 (In the database, ObjectType is stored as varchar(64).)
532 =head2 SetObjectType VALUE
535 Set ObjectType to VALUE.
536 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
537 (In the database, ObjectType will be stored as a varchar(64).)
545 Returns the current value of ObjectId.
546 (In the database, ObjectId is stored as int(11).)
550 =head2 SetObjectId VALUE
553 Set ObjectId to VALUE.
554 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
555 (In the database, ObjectId will be stored as a int(11).)
563 Returns the current value of Creator.
564 (In the database, Creator is stored as int(11).)
572 Returns the current value of Created.
573 (In the database, Created is stored as datetime.)
581 Returns the current value of LastUpdatedBy.
582 (In the database, LastUpdatedBy is stored as int(11).)
590 Returns the current value of LastUpdated.
591 (In the database, LastUpdated is stored as datetime.)
598 sub _CoreAccessible {
602 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
604 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
606 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
608 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'blob', default => ''},
610 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
612 {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
614 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
616 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
618 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
620 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
622 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
627 RT::Base->_ImportOverlays();