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';
184 $self->SUPER::Create(
185 Name => $args{'Name'},
186 Content => $args{'Content'},
187 ContentType => $args{'ContentType'},
188 Description => $args{'Description'},
189 ObjectType => $args{'ObjectType'},
190 ObjectId => $args{'ObjectId'},
197 =head2 LoadByNameAndObject (Object => OBJECT, Name => NAME)
199 Loads the Attribute named NAME for Object OBJECT.
203 sub LoadByNameAndObject {
213 Name => $args{'Name'},
214 ObjectType => ref($args{'Object'}),
215 ObjectId => $args{'Object'}->Id,
223 =head2 _DeserializeContent
225 DeserializeContent returns this Attribute's "Content" as a hashref.
230 sub _DeserializeContent {
235 eval {$hashref = thaw(decode_base64($content))} ;
237 $RT::Logger->error("Deserialization of attribute ".$self->Id. " failed");
247 Returns this attribute's content. If it's a scalar, returns a scalar
248 If it's data structure returns a ref to that data structure.
254 # Here we call _Value to get the ACL check.
255 my $content = $self->_Value('Content');
256 if ( ($self->__Value('ContentType') || '') eq 'storable') {
257 eval {$content = $self->_DeserializeContent($content); };
259 $RT::Logger->error("Deserialization of content for attribute ".$self->Id. " failed. Attribute was: ".$content);
267 sub _SerializeContent {
270 return( encode_base64(nfreeze($content)));
278 # Call __Value to avoid ACL check.
279 if ( ($self->__Value('ContentType')||'') eq 'storable' ) {
280 # We eval the serialization because it will lose on a coderef.
281 $content = eval { $self->_SerializeContent($content) };
283 $RT::Logger->error("Content couldn't be frozen: $@");
284 return(0, "Content couldn't be frozen");
287 return $self->_Set( Field => 'Content', Value => $content );
292 Returns the subvalue for $key.
300 my $values = $self->Content();
301 return undef unless ref($values);
302 return($values->{$key});
305 =head2 DeleteSubValue NAME
307 Deletes the subvalue with the key NAME
314 my %values = $self->Content();
315 delete $values{$key};
316 $self->SetContent(%values);
323 =head2 DeleteAllSubValues
325 Deletes all subvalues for this attribute
330 sub DeleteAllSubValues {
332 $self->SetContent({});
335 =head2 SetSubValues { }
337 Takes a hash of keys and values and stores them in the content of this attribute.
339 Each key B<replaces> the existing key with the same name
341 Returns a tuple of (status, message)
349 my $values = ($self->Content() || {} );
350 foreach my $key (keys %args) {
351 $values->{$key} = $args{$key};
354 $self->SetContent($values);
361 my $object_type = $self->__Value('ObjectType');
363 eval { $object = $object_type->new($self->CurrentUser) };
364 unless(UNIVERSAL::isa($object, $object_type)) {
365 $RT::Logger->error("Attribute ".$self->Id." has a bogus object type - $object_type (".$@.")");
368 $object->Load($self->__Value('ObjectId'));
377 unless ($self->CurrentUserHasRight('delete')) {
378 return (0,$self->loc('Permission Denied'));
381 return($self->SUPER::Delete(@_));
387 unless ($self->CurrentUserHasRight('display')) {
388 return (0,$self->loc('Permission Denied'));
391 return($self->SUPER::_Value(@_));
399 unless ($self->CurrentUserHasRight('update')) {
401 return (0,$self->loc('Permission Denied'));
403 return($self->SUPER::_Set(@_));
408 =head2 CurrentUserHasRight
410 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.
414 sub CurrentUserHasRight {
418 # object_right is the right that the user has to have on the object for them to have $right on this attribute
419 my $object_right = $self->LookupObjectRight(
421 ObjectId => $self->__Value('ObjectId'),
422 ObjectType => $self->__Value('ObjectType'),
423 Name => $self->__Value('Name')
426 return (1) if ($object_right eq 'allow');
427 return (0) if ($object_right eq 'deny');
428 return(1) if ($self->CurrentUser->HasRight( Object => $self->Object, Right => $object_right));
436 We should be deserializing the content on load and then enver again, rather than at every access
449 Returns the current value of id.
450 (In the database, id is stored as int(11).)
458 Returns the current value of Name.
459 (In the database, Name is stored as varchar(255).)
467 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
468 (In the database, Name will be stored as a varchar(255).)
476 Returns the current value of Description.
477 (In the database, Description is stored as varchar(255).)
481 =head2 SetDescription VALUE
484 Set Description to VALUE.
485 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
486 (In the database, Description will be stored as a varchar(255).)
494 Returns the current value of Content.
495 (In the database, Content is stored as blob.)
499 =head2 SetContent VALUE
502 Set Content to VALUE.
503 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
504 (In the database, Content will be stored as a blob.)
512 Returns the current value of ContentType.
513 (In the database, ContentType is stored as varchar(16).)
517 =head2 SetContentType VALUE
520 Set ContentType to VALUE.
521 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
522 (In the database, ContentType will be stored as a varchar(16).)
530 Returns the current value of ObjectType.
531 (In the database, ObjectType is stored as varchar(64).)
535 =head2 SetObjectType VALUE
538 Set ObjectType to VALUE.
539 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
540 (In the database, ObjectType will be stored as a varchar(64).)
548 Returns the current value of ObjectId.
549 (In the database, ObjectId is stored as int(11).)
553 =head2 SetObjectId VALUE
556 Set ObjectId to VALUE.
557 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
558 (In the database, ObjectId will be stored as a int(11).)
566 Returns the current value of Creator.
567 (In the database, Creator is stored as int(11).)
575 Returns the current value of Created.
576 (In the database, Created is stored as datetime.)
584 Returns the current value of LastUpdatedBy.
585 (In the database, LastUpdatedBy is stored as int(11).)
593 Returns the current value of LastUpdated.
594 (In the database, LastUpdated is stored as datetime.)
601 sub _CoreAccessible {
605 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
607 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
609 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
611 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'blob', default => ''},
613 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
615 {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
617 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
619 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
621 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
623 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
625 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
630 sub FindDependencies {
632 my ($walker, $deps) = @_;
634 $self->SUPER::FindDependencies($walker, $deps);
635 $deps->Add( out => $self->Object );
640 my ($importer, $uid, $data) = @_;
642 if ($data->{Object} and ref $data->{Object}) {
643 my $on_uid = ${ $data->{Object} };
644 return if $importer->ShouldSkipTransaction($on_uid);
646 return $class->SUPER::PreInflate( $importer, $uid, $data );
649 RT::Base->_ImportOverlays();