summaryrefslogtreecommitdiff
path: root/rt/lib/RT/Attribute.pm
diff options
context:
space:
mode:
Diffstat (limited to 'rt/lib/RT/Attribute.pm')
-rw-r--r--rt/lib/RT/Attribute.pm452
1 files changed, 374 insertions, 78 deletions
diff --git a/rt/lib/RT/Attribute.pm b/rt/lib/RT/Attribute.pm
index 01d8318ec..cd0b54e33 100644
--- a/rt/lib/RT/Attribute.pm
+++ b/rt/lib/RT/Attribute.pm
@@ -2,7 +2,7 @@
#
# COPYRIGHT:
#
-# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC
+# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
# <sales@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -46,42 +46,76 @@
#
# END BPS TAGGED BLOCK }}}
-# Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
-# WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.
-#
-# !! DO NOT EDIT THIS FILE !!
-#
+package RT::Attribute;
use strict;
+use warnings;
+use base 'RT::Record';
-=head1 NAME
+sub Table {'Attributes'}
-RT::Attribute
+use Storable qw/nfreeze thaw/;
+use MIME::Base64;
-=head1 SYNOPSIS
+=head1 NAME
-=head1 DESCRIPTION
+ RT::Attribute_Overlay
-=head1 METHODS
+=head1 Content
=cut
-package RT::Attribute;
-use RT::Record;
+# 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
+our $ACL_MAP = {
+ SavedSearch => { create => 'EditSavedSearches',
+ update => 'EditSavedSearches',
+ delete => 'EditSavedSearches',
+ display => 'ShowSavedSearches' },
-use vars qw( @ISA );
-@ISA= qw( RT::Record );
+};
-sub _Init {
- my $self = shift;
+# There are a number of attributes that users should be able to modify for themselves, such as saved searches
+# we could do this with a different set of "update" rights, but that gets very hacky very fast. this is even faster and even
+# hackier. we're hardcoding that a different set of rights are needed for attributes on oneself
+our $PERSONAL_ACL_MAP = {
+ SavedSearch => { create => 'ModifySelf',
+ update => 'ModifySelf',
+ delete => 'ModifySelf',
+ display => 'allow' },
- $self->Table('Attributes');
- $self->SUPER::_Init(@_);
-}
+};
+=head2 LookupObjectRight { ObjectType => undef, ObjectId => undef, Name => undef, Right => { create, update, delete, display } }
+
+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.
+
+=cut
+
+sub LookupObjectRight {
+ my $self = shift;
+ my %args = ( ObjectType => undef,
+ ObjectId => undef,
+ Right => undef,
+ Name => undef,
+ @_);
+
+ # if it's an attribute on oneself, check the personal acl map
+ if (($args{'ObjectType'} eq 'RT::User') && ($args{'ObjectId'} eq $self->CurrentUser->Id)) {
+ return('allow') unless ($PERSONAL_ACL_MAP->{$args{'Name'}});
+ return('allow') unless ($PERSONAL_ACL_MAP->{$args{'Name'}}->{$args{'Right'}});
+ return($PERSONAL_ACL_MAP->{$args{'Name'}}->{$args{'Right'}});
+
+ }
+ # otherwise check the main ACL map
+ else {
+ return('allow') unless ($ACL_MAP->{$args{'Name'}});
+ return('allow') unless ($ACL_MAP->{$args{'Name'}}->{$args{'Right'}});
+ return($ACL_MAP->{$args{'Name'}}->{$args{'Right'}});
+ }
+}
@@ -90,13 +124,14 @@ sub _Init {
Create takes a hash of values and creates a row in the database:
- varchar(255) 'Name'.
- varchar(255) 'Description'.
- text 'Content'.
- varchar(16) 'ContentType'.
+ varchar(200) 'Name'.
+ varchar(255) 'Content'.
+ varchar(16) 'ContentType',
varchar(64) 'ObjectType'.
int(11) 'ObjectId'.
+You may pass a C<Object> instead of C<ObjectType> and C<ObjectId>.
+
=cut
@@ -109,15 +144,49 @@ sub Create {
Description => '',
Content => '',
ContentType => '',
- ObjectType => '',
- ObjectId => '',
-
+ Object => undef,
@_);
+
+ if ($args{Object} and UNIVERSAL::can($args{Object}, 'Id')) {
+ $args{ObjectType} = ref($args{Object});
+ $args{ObjectId} = $args{Object}->Id;
+ } else {
+ return(0, $self->loc("Required parameter '[_1]' not specified", 'Object'));
+
+ }
+
+ # object_right is the right that the user has to have on the object for them to have $right on this attribute
+ my $object_right = $self->LookupObjectRight(
+ Right => 'create',
+ ObjectId => $args{'ObjectId'},
+ ObjectType => $args{'ObjectType'},
+ Name => $args{'Name'}
+ );
+ if ($object_right eq 'deny') {
+ return (0, $self->loc('Permission Denied'));
+ }
+ elsif ($object_right eq 'allow') {
+ # do nothing, we're ok
+ }
+ elsif (!$self->CurrentUser->HasRight( Object => $args{Object}, Right => $object_right)) {
+ return (0, $self->loc('Permission Denied'));
+ }
+
+
+ if (ref ($args{'Content'}) ) {
+ eval {$args{'Content'} = $self->_SerializeContent($args{'Content'}); };
+ if ($@) {
+ return(0, $@);
+ }
+ $args{'ContentType'} = 'storable';
+ }
+
+
$self->SUPER::Create(
Name => $args{'Name'},
- Description => $args{'Description'},
Content => $args{'Content'},
ContentType => $args{'ContentType'},
+ Description => $args{'Description'},
ObjectType => $args{'ObjectType'},
ObjectId => $args{'ObjectId'},
);
@@ -126,9 +195,255 @@ sub Create {
+=head2 LoadByNameAndObject (Object => OBJECT, Name => NAME)
+
+Loads the Attribute named NAME for Object OBJECT.
+
+=cut
+
+sub LoadByNameAndObject {
+ my $self = shift;
+ my %args = (
+ Object => undef,
+ Name => undef,
+ @_,
+ );
+
+ return (
+ $self->LoadByCols(
+ Name => $args{'Name'},
+ ObjectType => ref($args{'Object'}),
+ ObjectId => $args{'Object'}->Id,
+ )
+ );
+
+}
+
+
+
+=head2 _DeserializeContent
+
+DeserializeContent returns this Attribute's "Content" as a hashref.
+
+
+=cut
+
+sub _DeserializeContent {
+ my $self = shift;
+ my $content = shift;
+
+ my $hashref;
+ eval {$hashref = thaw(decode_base64($content))} ;
+ if ($@) {
+ $RT::Logger->error("Deserialization of attribute ".$self->Id. " failed");
+ }
+
+ return($hashref);
+
+}
+
+
+=head2 Content
+
+Returns this attribute's content. If it's a scalar, returns a scalar
+If it's data structure returns a ref to that data structure.
+
+=cut
+
+sub Content {
+ my $self = shift;
+ # Here we call _Value to get the ACL check.
+ my $content = $self->_Value('Content');
+ if ( ($self->__Value('ContentType') || '') eq 'storable') {
+ eval {$content = $self->_DeserializeContent($content); };
+ if ($@) {
+ $RT::Logger->error("Deserialization of content for attribute ".$self->Id. " failed. Attribute was: ".$content);
+ }
+ }
+
+ return($content);
+
+}
+
+sub _SerializeContent {
+ my $self = shift;
+ my $content = shift;
+ return( encode_base64(nfreeze($content)));
+}
+
+
+sub SetContent {
+ my $self = shift;
+ my $content = shift;
+
+ # Call __Value to avoid ACL check.
+ if ( ($self->__Value('ContentType')||'') eq 'storable' ) {
+ # We eval the serialization because it will lose on a coderef.
+ $content = eval { $self->_SerializeContent($content) };
+ if ($@) {
+ $RT::Logger->error("Content couldn't be frozen: $@");
+ return(0, "Content couldn't be frozen");
+ }
+ }
+ return $self->_Set( Field => 'Content', Value => $content );
+}
+
+=head2 SubValue KEY
+
+Returns the subvalue for $key.
+
+
+=cut
+
+sub SubValue {
+ my $self = shift;
+ my $key = shift;
+ my $values = $self->Content();
+ return undef unless ref($values);
+ return($values->{$key});
+}
+
+=head2 DeleteSubValue NAME
+
+Deletes the subvalue with the key NAME
+
+=cut
+
+sub DeleteSubValue {
+ my $self = shift;
+ my $key = shift;
+ my $values = $self->Content();
+ delete $values->{$key};
+ $self->SetContent($values);
+}
+
+
+=head2 DeleteAllSubValues
+
+Deletes all subvalues for this attribute
+
+=cut
+
+
+sub DeleteAllSubValues {
+ my $self = shift;
+ $self->SetContent({});
+}
+
+=head2 SetSubValues { }
+
+Takes a hash of keys and values and stores them in the content of this attribute.
+
+Each key B<replaces> the existing key with the same name
+
+Returns a tuple of (status, message)
+
+=cut
+
+
+sub SetSubValues {
+ my $self = shift;
+ my %args = (@_);
+ my $values = ($self->Content() || {} );
+ foreach my $key (keys %args) {
+ $values->{$key} = $args{$key};
+ }
+
+ $self->SetContent($values);
+
+}
+
+
+sub Object {
+ my $self = shift;
+ my $object_type = $self->__Value('ObjectType');
+ my $object;
+ eval { $object = $object_type->new($self->CurrentUser) };
+ unless(UNIVERSAL::isa($object, $object_type)) {
+ $RT::Logger->error("Attribute ".$self->Id." has a bogus object type - $object_type (".$@.")");
+ return(undef);
+ }
+ $object->Load($self->__Value('ObjectId'));
+
+ return($object);
+
+}
+
+
+sub Delete {
+ my $self = shift;
+ unless ($self->CurrentUserHasRight('delete')) {
+ return (0,$self->loc('Permission Denied'));
+ }
+ return($self->SUPER::Delete(@_));
+}
+
+
+sub _Value {
+ my $self = shift;
+ unless ($self->CurrentUserHasRight('display')) {
+ return (0,$self->loc('Permission Denied'));
+ }
+
+ return($self->SUPER::_Value(@_));
+
+
+}
+
+
+sub _Set {
+ my $self = shift;
+ unless ($self->CurrentUserHasRight('update')) {
+
+ return (0,$self->loc('Permission Denied'));
+ }
+ return($self->SUPER::_Set(@_));
+
+}
+
+
+=head2 CurrentUserHasRight
+
+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.
+
+=cut
+
+sub CurrentUserHasRight {
+ my $self = shift;
+ my $right = shift;
+
+ # object_right is the right that the user has to have on the object for them to have $right on this attribute
+ my $object_right = $self->LookupObjectRight(
+ Right => $right,
+ ObjectId => $self->__Value('ObjectId'),
+ ObjectType => $self->__Value('ObjectType'),
+ Name => $self->__Value('Name')
+ );
+
+ return (1) if ($object_right eq 'allow');
+ return (0) if ($object_right eq 'deny');
+ return(1) if ($self->CurrentUser->HasRight( Object => $self->Object, Right => $object_right));
+ return(0);
+
+}
+
+
+=head1 TODO
+
+We should be deserializing the content on load and then enver again, rather than at every access
+
+=cut
+
+
+
+
+
+
+
+
=head2 id
-Returns the current value of id.
+Returns the current value of id.
(In the database, id is stored as int(11).)
@@ -137,7 +452,7 @@ Returns the current value of id.
=head2 Name
-Returns the current value of Name.
+Returns the current value of Name.
(In the database, Name is stored as varchar(255).)
@@ -145,7 +460,7 @@ Returns the current value of Name.
=head2 SetName VALUE
-Set Name to VALUE.
+Set Name to VALUE.
Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
(In the database, Name will be stored as a varchar(255).)
@@ -155,7 +470,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
=head2 Description
-Returns the current value of Description.
+Returns the current value of Description.
(In the database, Description is stored as varchar(255).)
@@ -163,7 +478,7 @@ Returns the current value of Description.
=head2 SetDescription VALUE
-Set Description to VALUE.
+Set Description to VALUE.
Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
(In the database, Description will be stored as a varchar(255).)
@@ -173,17 +488,17 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
=head2 Content
-Returns the current value of Content.
-(In the database, Content is stored as text.)
+Returns the current value of Content.
+(In the database, Content is stored as blob.)
=head2 SetContent VALUE
-Set Content to VALUE.
+Set Content to VALUE.
Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
-(In the database, Content will be stored as a text.)
+(In the database, Content will be stored as a blob.)
=cut
@@ -191,7 +506,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
=head2 ContentType
-Returns the current value of ContentType.
+Returns the current value of ContentType.
(In the database, ContentType is stored as varchar(16).)
@@ -199,7 +514,7 @@ Returns the current value of ContentType.
=head2 SetContentType VALUE
-Set ContentType to VALUE.
+Set ContentType to VALUE.
Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
(In the database, ContentType will be stored as a varchar(16).)
@@ -209,7 +524,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
=head2 ObjectType
-Returns the current value of ObjectType.
+Returns the current value of ObjectType.
(In the database, ObjectType is stored as varchar(64).)
@@ -217,7 +532,7 @@ Returns the current value of ObjectType.
=head2 SetObjectType VALUE
-Set ObjectType to VALUE.
+Set ObjectType to VALUE.
Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
(In the database, ObjectType will be stored as a varchar(64).)
@@ -227,7 +542,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
=head2 ObjectId
-Returns the current value of ObjectId.
+Returns the current value of ObjectId.
(In the database, ObjectId is stored as int(11).)
@@ -235,7 +550,7 @@ Returns the current value of ObjectId.
=head2 SetObjectId VALUE
-Set ObjectId to VALUE.
+Set ObjectId to VALUE.
Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
(In the database, ObjectId will be stored as a int(11).)
@@ -245,7 +560,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
=head2 Creator
-Returns the current value of Creator.
+Returns the current value of Creator.
(In the database, Creator is stored as int(11).)
@@ -254,7 +569,7 @@ Returns the current value of Creator.
=head2 Created
-Returns the current value of Created.
+Returns the current value of Created.
(In the database, Created is stored as datetime.)
@@ -263,7 +578,7 @@ Returns the current value of Created.
=head2 LastUpdatedBy
-Returns the current value of LastUpdatedBy.
+Returns the current value of LastUpdatedBy.
(In the database, LastUpdatedBy is stored as int(11).)
@@ -272,7 +587,7 @@ Returns the current value of LastUpdatedBy.
=head2 LastUpdated
-Returns the current value of LastUpdated.
+Returns the current value of LastUpdated.
(In the database, LastUpdated is stored as datetime.)
@@ -282,28 +597,28 @@ Returns the current value of LastUpdated.
sub _CoreAccessible {
{
-
+
id =>
{read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
- Name =>
+ Name =>
{read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
- Description =>
+ Description =>
{read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
- Content =>
- {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'text', default => ''},
- ContentType =>
+ Content =>
+ {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'blob', default => ''},
+ ContentType =>
{read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
- ObjectType =>
+ ObjectType =>
{read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
- ObjectId =>
+ ObjectId =>
{read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
- Creator =>
+ Creator =>
{read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
- Created =>
+ Created =>
{read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
- LastUpdatedBy =>
+ LastUpdatedBy =>
{read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
- LastUpdated =>
+ LastUpdated =>
{read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
}
@@ -311,23 +626,4 @@ sub _CoreAccessible {
RT::Base->_ImportOverlays();
-=head1 SEE ALSO
-
-This class allows "overlay" methods to be placed
-into the following files _Overlay is for a System overlay by the original author,
-_Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customizations.
-
-These overlay files can contain new subs or subs to replace existing subs in this module.
-
-Each of these files should begin with the line
-
- no warnings qw(redefine);
-
-so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.
-
-RT::Attribute_Overlay, RT::Attribute_Vendor, RT::Attribute_Local
-
-=cut
-
-
1;