Upgrade to 4.0.10.
[usit-rt.git] / lib / RT / Attribute.pm
CommitLineData
84fb5b46
MKG
1# BEGIN BPS TAGGED BLOCK {{{
2#
3# COPYRIGHT:
4#
403d7b0b 5# This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
84fb5b46
MKG
6# <sales@bestpractical.com>
7#
8# (Except where explicitly superseded by other copyright notices)
9#
10#
11# LICENSE:
12#
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
16# from www.gnu.org.
17#
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.
22#
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.
28#
29#
30# CONTRIBUTION SUBMISSION POLICY:
31#
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.)
37#
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.
46#
47# END BPS TAGGED BLOCK }}}
48
49package RT::Attribute;
50
51use strict;
52use warnings;
53
54use base 'RT::Record';
55
56sub Table {'Attributes'}
57
58use Storable qw/nfreeze thaw/;
59use MIME::Base64;
60
61
62=head1 NAME
63
64 RT::Attribute_Overlay
65
66=head1 Content
67
68=cut
69
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
71
72our $ACL_MAP = {
73 SavedSearch => { create => 'EditSavedSearches',
74 update => 'EditSavedSearches',
75 delete => 'EditSavedSearches',
76 display => 'ShowSavedSearches' },
77
78};
79
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
83our $PERSONAL_ACL_MAP = {
84 SavedSearch => { create => 'ModifySelf',
85 update => 'ModifySelf',
86 delete => 'ModifySelf',
87 display => 'allow' },
88
89};
90
91=head2 LookupObjectRight { ObjectType => undef, ObjectId => undef, Name => undef, Right => { create, update, delete, display } }
92
93Returns 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.
94
95=cut
96
97sub LookupObjectRight {
98 my $self = shift;
99 my %args = ( ObjectType => undef,
100 ObjectId => undef,
101 Right => undef,
102 Name => undef,
103 @_);
104
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'}});
110
111 }
112 # otherwise check the main ACL map
113 else {
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'}});
117 }
118}
119
120
121
122
123=head2 Create PARAMHASH
124
125Create takes a hash of values and creates a row in the database:
126
127 varchar(200) 'Name'.
128 varchar(255) 'Content'.
129 varchar(16) 'ContentType',
130 varchar(64) 'ObjectType'.
131 int(11) 'ObjectId'.
132
133You may pass a C<Object> instead of C<ObjectType> and C<ObjectId>.
134
135=cut
136
137
138
139
140sub Create {
141 my $self = shift;
142 my %args = (
143 Name => '',
144 Description => '',
145 Content => '',
146 ContentType => '',
147 Object => undef,
148 @_);
149
150 if ($args{Object} and UNIVERSAL::can($args{Object}, 'Id')) {
151 $args{ObjectType} = ref($args{Object});
152 $args{ObjectId} = $args{Object}->Id;
153 } else {
154 return(0, $self->loc("Required parameter '[_1]' not specified", 'Object'));
155
156 }
157
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(
160 Right => 'create',
161 ObjectId => $args{'ObjectId'},
162 ObjectType => $args{'ObjectType'},
163 Name => $args{'Name'}
164 );
165 if ($object_right eq 'deny') {
166 return (0, $self->loc('Permission Denied'));
167 }
168 elsif ($object_right eq 'allow') {
169 # do nothing, we're ok
170 }
171 elsif (!$self->CurrentUser->HasRight( Object => $args{Object}, Right => $object_right)) {
172 return (0, $self->loc('Permission Denied'));
173 }
174
175
176 if (ref ($args{'Content'}) ) {
177 eval {$args{'Content'} = $self->_SerializeContent($args{'Content'}); };
178 if ($@) {
179 return(0, $@);
180 }
181 $args{'ContentType'} = 'storable';
182 }
183
184
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'},
192);
193
194}
195
196
197
198=head2 LoadByNameAndObject (Object => OBJECT, Name => NAME)
199
200Loads the Attribute named NAME for Object OBJECT.
201
202=cut
203
204sub LoadByNameAndObject {
205 my $self = shift;
206 my %args = (
207 Object => undef,
208 Name => undef,
209 @_,
210 );
211
212 return (
213 $self->LoadByCols(
214 Name => $args{'Name'},
215 ObjectType => ref($args{'Object'}),
216 ObjectId => $args{'Object'}->Id,
217 )
218 );
219
220}
221
222
223
224=head2 _DeserializeContent
225
226DeserializeContent returns this Attribute's "Content" as a hashref.
227
228
229=cut
230
231sub _DeserializeContent {
232 my $self = shift;
233 my $content = shift;
234
235 my $hashref;
236 eval {$hashref = thaw(decode_base64($content))} ;
237 if ($@) {
238 $RT::Logger->error("Deserialization of attribute ".$self->Id. " failed");
239 }
240
241 return($hashref);
242
243}
244
245
246=head2 Content
247
248Returns this attribute's content. If it's a scalar, returns a scalar
249If it's data structure returns a ref to that data structure.
250
251=cut
252
253sub Content {
254 my $self = shift;
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); };
259 if ($@) {
260 $RT::Logger->error("Deserialization of content for attribute ".$self->Id. " failed. Attribute was: ".$content);
261 }
262 }
263
264 return($content);
265
266}
267
268sub _SerializeContent {
269 my $self = shift;
270 my $content = shift;
271 return( encode_base64(nfreeze($content)));
272}
273
274
275sub SetContent {
276 my $self = shift;
277 my $content = shift;
278
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) };
283 if ($@) {
284 $RT::Logger->error("Content couldn't be frozen: $@");
285 return(0, "Content couldn't be frozen");
286 }
287 }
288 return $self->_Set( Field => 'Content', Value => $content );
289}
290
291=head2 SubValue KEY
292
293Returns the subvalue for $key.
294
295
296=cut
297
298sub SubValue {
299 my $self = shift;
300 my $key = shift;
301 my $values = $self->Content();
302 return undef unless ref($values);
303 return($values->{$key});
304}
305
306=head2 DeleteSubValue NAME
307
308Deletes the subvalue with the key NAME
309
310=cut
311
312sub DeleteSubValue {
313 my $self = shift;
314 my $key = shift;
315 my %values = $self->Content();
316 delete $values{$key};
317 $self->SetContent(%values);
318
319
320
321}
322
323
324=head2 DeleteAllSubValues
325
326Deletes all subvalues for this attribute
327
328=cut
329
330
331sub DeleteAllSubValues {
332 my $self = shift;
333 $self->SetContent({});
334}
335
336=head2 SetSubValues { }
337
338Takes a hash of keys and values and stores them in the content of this attribute.
339
340Each key B<replaces> the existing key with the same name
341
342Returns a tuple of (status, message)
343
344=cut
345
346
347sub SetSubValues {
348 my $self = shift;
349 my %args = (@_);
350 my $values = ($self->Content() || {} );
351 foreach my $key (keys %args) {
352 $values->{$key} = $args{$key};
353 }
354
355 $self->SetContent($values);
356
357}
358
359
360sub Object {
361 my $self = shift;
362 my $object_type = $self->__Value('ObjectType');
363 my $object;
364 eval { $object = $object_type->new($self->CurrentUser) };
365 unless(UNIVERSAL::isa($object, $object_type)) {
366 $RT::Logger->error("Attribute ".$self->Id." has a bogus object type - $object_type (".$@.")");
367 return(undef);
368 }
369 $object->Load($self->__Value('ObjectId'));
370
371 return($object);
372
373}
374
375
376sub Delete {
377 my $self = shift;
378 unless ($self->CurrentUserHasRight('delete')) {
379 return (0,$self->loc('Permission Denied'));
380 }
381 return($self->SUPER::Delete(@_));
382}
383
384
385sub _Value {
386 my $self = shift;
387 unless ($self->CurrentUserHasRight('display')) {
388 return (0,$self->loc('Permission Denied'));
389 }
390
391 return($self->SUPER::_Value(@_));
392
393
394}
395
396
397sub _Set {
398 my $self = shift;
399 unless ($self->CurrentUserHasRight('update')) {
400
401 return (0,$self->loc('Permission Denied'));
402 }
403 return($self->SUPER::_Set(@_));
404
405}
406
407
408=head2 CurrentUserHasRight
409
410One 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
412=cut
413
414sub CurrentUserHasRight {
415 my $self = shift;
416 my $right = shift;
417
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(
420 Right => $right,
421 ObjectId => $self->__Value('ObjectId'),
422 ObjectType => $self->__Value('ObjectType'),
423 Name => $self->__Value('Name')
424 );
425
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));
429 return(0);
430
431}
432
433
434=head1 TODO
435
436We should be deserializing the content on load and then enver again, rather than at every access
437
438=cut
439
440
441
442
443
444
445
446
447=head2 id
448
449Returns the current value of id.
450(In the database, id is stored as int(11).)
451
452
453=cut
454
455
456=head2 Name
457
458Returns the current value of Name.
459(In the database, Name is stored as varchar(255).)
460
461
462
463=head2 SetName VALUE
464
465
466Set Name to VALUE.
467Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
468(In the database, Name will be stored as a varchar(255).)
469
470
471=cut
472
473
474=head2 Description
475
476Returns the current value of Description.
477(In the database, Description is stored as varchar(255).)
478
479
480
481=head2 SetDescription VALUE
482
483
484Set Description to VALUE.
485Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
486(In the database, Description will be stored as a varchar(255).)
487
488
489=cut
490
491
492=head2 Content
493
494Returns the current value of Content.
495(In the database, Content is stored as blob.)
496
497
498
499=head2 SetContent VALUE
500
501
502Set Content to VALUE.
503Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
504(In the database, Content will be stored as a blob.)
505
506
507=cut
508
509
510=head2 ContentType
511
512Returns the current value of ContentType.
513(In the database, ContentType is stored as varchar(16).)
514
515
516
517=head2 SetContentType VALUE
518
519
520Set ContentType to VALUE.
521Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
522(In the database, ContentType will be stored as a varchar(16).)
523
524
525=cut
526
527
528=head2 ObjectType
529
530Returns the current value of ObjectType.
531(In the database, ObjectType is stored as varchar(64).)
532
533
534
535=head2 SetObjectType VALUE
536
537
538Set ObjectType to VALUE.
539Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
540(In the database, ObjectType will be stored as a varchar(64).)
541
542
543=cut
544
545
546=head2 ObjectId
547
548Returns the current value of ObjectId.
549(In the database, ObjectId is stored as int(11).)
550
551
552
553=head2 SetObjectId VALUE
554
555
556Set ObjectId to VALUE.
557Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
558(In the database, ObjectId will be stored as a int(11).)
559
560
561=cut
562
563
564=head2 Creator
565
566Returns the current value of Creator.
567(In the database, Creator is stored as int(11).)
568
569
570=cut
571
572
573=head2 Created
574
575Returns the current value of Created.
576(In the database, Created is stored as datetime.)
577
578
579=cut
580
581
582=head2 LastUpdatedBy
583
584Returns the current value of LastUpdatedBy.
585(In the database, LastUpdatedBy is stored as int(11).)
586
587
588=cut
589
590
591=head2 LastUpdated
592
593Returns the current value of LastUpdated.
594(In the database, LastUpdated is stored as datetime.)
595
596
597=cut
598
599
600
601sub _CoreAccessible {
602 {
603
604 id =>
605 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
606 Name =>
607 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
608 Description =>
609 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
610 Content =>
611 {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'blob', default => ''},
612 ContentType =>
613 {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''},
614 ObjectType =>
615 {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''},
616 ObjectId =>
617 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
618 Creator =>
619 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
620 Created =>
621 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
622 LastUpdatedBy =>
623 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
624 LastUpdated =>
625 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
626
627 }
628};
629
630RT::Base->_ImportOverlays();
631
6321;