Putting 4.2.0 on top of 4.0.17
[usit-rt.git] / lib / RT / SharedSetting.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
49=head1 NAME
50
51RT::SharedSetting - an API for settings that belong to an RT::User or RT::Group
52
53=head1 SYNOPSIS
54
55 use RT::SharedSetting;
56
57=head1 DESCRIPTION
58
59A RT::SharedSetting is an object that can belong to an L<RT::User> or an <RT::Group>.
60It consists of an ID, a name, and some arbitrary data.
61
62=cut
63
64package RT::SharedSetting;
65use strict;
66use warnings;
af59614d 67use base qw/RT::Base/;
84fb5b46
MKG
68
69use RT::Attribute;
70use Scalar::Util 'blessed';
84fb5b46
MKG
71
72=head1 METHODS
73
74=head2 new
75
76Returns a new L<RT::SharedSetting> object.
77Takes the current user, see also L<RT::Base>.
78
79=cut
80
81sub new {
82 my $proto = shift;
83 my $class = ref($proto) || $proto;
84 my $self = {};
85 $self->{'Id'} = 0;
86 bless ($self, $class);
87 $self->CurrentUser(@_);
88 return $self;
89}
90
91=head2 Load
92
93Takes a privacy specification and a shared-setting ID. Loads the given object
94ID if it belongs to the stated user or group. Calls the L</PostLoad> method on
95success for any further initialization. Returns a tuple of status and message,
96where status is true on success.
97
98=cut
99
100sub Load {
101 my $self = shift;
102 my ($privacy, $id) = @_;
103 my $object = $self->_GetObject($privacy);
104
105 if ($object) {
af59614d
MKG
106 $self->{'Attribute'} = RT::Attribute->new($self->CurrentUser);
107 $self->{'Attribute'}->Load( $id );
84fb5b46
MKG
108 if ($self->{'Attribute'}->Id) {
109 $self->{'Id'} = $self->{'Attribute'}->Id;
110 $self->{'Privacy'} = $privacy;
111 $self->PostLoad();
112
af59614d 113 return wantarray ? (0, $self->loc("Permission Denied")) : 0
84fb5b46
MKG
114 unless $self->CurrentUserCanSee;
115
116 my ($ok, $msg) = $self->PostLoadValidate;
af59614d 117 return wantarray ? ($ok, $msg) : $ok if !$ok;
84fb5b46 118
af59614d 119 return wantarray ? (1, $self->loc("Loaded [_1] [_2]", $self->ObjectName, $self->Name)) : 1;
84fb5b46
MKG
120 } else {
121 $RT::Logger->error("Could not load attribute " . $id
122 . " for object " . $privacy);
af59614d 123 return wantarray ? (0, $self->loc("Failed to load [_1] [_2]", $self->ObjectName, $id)) : 0;
84fb5b46
MKG
124 }
125 } else {
126 $RT::Logger->warning("Could not load object $privacy when loading " . $self->ObjectName);
af59614d 127 return wantarray ? (0, $self->loc("Could not load object for [_1]", $privacy)) : 0;
84fb5b46
MKG
128 }
129}
130
131=head2 LoadById
132
133First loads up the L<RT::Attribute> for this shared setting by ID, then calls
134L</Load> with the correct parameters. Returns a tuple of status and message,
135where status is true on success.
136
137=cut
138
139sub LoadById {
140 my $self = shift;
141 my $id = shift;
142
143 my $attr = RT::Attribute->new($self->CurrentUser);
144 my ($ok, $msg) = $attr->LoadById($id);
145
146 if (!$ok) {
af59614d 147 return wantarray ? (0, $self->loc("Failed to load [_1] [_2]: [_3]", $self->ObjectName, $id, $msg)) : 0;
84fb5b46
MKG
148 }
149
150 my $privacy = $self->_build_privacy($attr->ObjectType, $attr->ObjectId);
af59614d 151 return wantarray ? (0, $self->loc("Bad privacy for attribute [_1]", $id)) : 0
84fb5b46
MKG
152 if !$privacy;
153
154 return $self->Load($privacy, $id);
155}
156
157=head2 PostLoad
158
159Called after a successful L</Load>.
160
161=cut
162
163sub PostLoad { }
164
165=head2 PostLoadValidate
166
167Called just before returning success from L</Load>; may be used to validate
168that the record is correct. This method is expected to return a (ok, msg)
169pair.
170
171=cut
172
173sub PostLoadValidate {
174 return 1;
175}
176
177=head2 Save
178
179Creates a new shared setting. Takes a privacy, a name, and any other arguments.
180Saves the given parameters to the appropriate user/group object, and loads the
181resulting object. Arguments are passed to the L</SaveAttribute> method, which
182does the actual update. Returns a tuple of status and message, where status is
183true on success. Defaults are:
184
185 Privacy: CurrentUser only
186 Name: "new (ObjectName)"
187
188=cut
189
190sub Save {
191 my $self = shift;
192 my %args = (
193 'Privacy' => 'RT::User-' . $self->CurrentUser->Id,
194 'Name' => "new " . $self->ObjectName,
af59614d 195 @_,
84fb5b46
MKG
196 );
197
198 my $privacy = $args{'Privacy'};
199 my $name = $args{'Name'},
200 my $object = $self->_GetObject($privacy);
201
202 return (0, $self->loc("Failed to load object for [_1]", $privacy))
203 unless $object;
204
af59614d 205 return (0, $self->loc("Permission Denied"))
84fb5b46
MKG
206 unless $self->CurrentUserCanCreate($privacy);
207
208 my ($att_id, $att_msg) = $self->SaveAttribute($object, \%args);
209
210 if ($att_id) {
af59614d
MKG
211 $self->{'Attribute'} = RT::Attribute->new($self->CurrentUser);
212 $self->{'Attribute'}->Load( $att_id );
84fb5b46
MKG
213 $self->{'Id'} = $att_id;
214 $self->{'Privacy'} = $privacy;
5b0d0914 215 return ( 1, $self->loc( "Saved [_1] [_2]", $self->loc( $self->ObjectName ), $name ) );
84fb5b46
MKG
216 }
217 else {
218 $RT::Logger->error($self->ObjectName . " save failure: $att_msg");
5b0d0914 219 return ( 0, $self->loc("Failed to create [_1] attribute", $self->loc( $self->ObjectName ) ) );
84fb5b46
MKG
220 }
221}
222
223=head2 SaveAttribute
224
225An empty method for subclassing. Called from L</Save> method.
226
227=cut
228
229sub SaveAttribute { }
230
231=head2 Update
232
233Updates the parameters of an existing shared setting. Any arguments are passed
234to the L</UpdateAttribute> method. Returns a tuple of status and message, where
235status is true on success.
236
237=cut
238
239sub Update {
240 my $self = shift;
241 my %args = @_;
242
243 return(0, $self->loc("No [_1] loaded", $self->ObjectName)) unless $self->Id;
244 return(0, $self->loc("Could not load [_1] attribute", $self->ObjectName))
245 unless $self->{'Attribute'}->Id;
246
af59614d 247 return (0, $self->loc("Permission Denied"))
84fb5b46
MKG
248 unless $self->CurrentUserCanModify;
249
250 my ($status, $msg) = $self->UpdateAttribute(\%args);
251
252 return (1, $self->loc("[_1] update: Nothing changed", ucfirst($self->ObjectName)))
253 if !defined $msg;
254
255 # prevent useless warnings
256 return (1, $self->loc("[_1] updated"), ucfirst($self->ObjectName))
257 if $msg =~ /That is already the current value/;
258
259 return ($status, $self->loc("[_1] update: [_2]", ucfirst($self->ObjectName), $msg));
260}
261
262=head2 UpdateAttribute
263
264An empty method for subclassing. Called from L</Update> method.
265
266=cut
267
268sub UpdateAttribute { }
269
270=head2 Delete
271
272Deletes the existing shared setting. Returns a tuple of status and message,
273where status is true upon success.
274
275=cut
276
277sub Delete {
278 my $self = shift;
af59614d 279 return (0, $self->loc("Permission Denied"))
84fb5b46
MKG
280 unless $self->CurrentUserCanDelete;
281
282 my ($status, $msg) = $self->{'Attribute'}->Delete;
283 $self->CurrentUser->ClearAttributes; # force the current user's attribute cache to be cleaned up
284 if ($status) {
285 return (1, $self->loc("Deleted [_1]", $self->ObjectName));
286 } else {
287 return (0, $self->loc("Delete failed: [_1]", $msg));
288 }
289}
290
291### Accessor methods
292
293=head2 Name
294
295Returns the name of this shared setting.
296
297=cut
298
299sub Name {
300 my $self = shift;
301 return unless ref($self->{'Attribute'}) eq 'RT::Attribute';
302 return $self->{'Attribute'}->Description();
303}
304
305=head2 Id
306
307Returns the numerical ID of this shared setting.
308
309=cut
310
311sub Id {
312 my $self = shift;
313 return $self->{'Id'};
314}
315
316*id = \&Id;
317
318
319=head2 Privacy
320
321Returns the principal object to whom this shared setting belongs, in a string
322"<class>-<id>", e.g. "RT::Group-16".
323
324=cut
325
326sub Privacy {
327 my $self = shift;
328 return $self->{'Privacy'};
329}
330
331=head2 GetParameter
332
333Returns the given named parameter of the setting.
334
335=cut
336
337sub GetParameter {
338 my $self = shift;
339 my $param = shift;
340 return unless ref($self->{'Attribute'}) eq 'RT::Attribute';
341 return $self->{'Attribute'}->SubValue($param);
342}
343
344=head2 IsVisibleTo Privacy
345
346Returns true if the setting is visible to all principals of the given privacy.
347This does not deal with ACLs, this only looks at membership.
348
349=cut
350
351sub IsVisibleTo {
352 my $self = shift;
353 my $to = shift;
354 my $privacy = $self->Privacy || '';
355
356 # if the privacies are the same, then they can be seen. this handles
357 # a personal setting being visible to that user.
358 return 1 if $privacy eq $to;
359
360 # If the setting is systemwide, then any user can see it.
361 return 1 if $privacy =~ /^RT::System/;
362
363 # Only privacies that are RT::System can be seen by everyone.
364 return 0 if $to =~ /^RT::System/;
365
366 # If the setting is group-wide...
367 if ($privacy =~ /^RT::Group-(\d+)$/) {
368 my $setting_group = RT::Group->new($self->CurrentUser);
369 $setting_group->Load($1);
370
371 if ($to =~ /-(\d+)$/) {
372 my $to_id = $1;
373
374 # then any principal that is a member of the setting's group can see
375 # the setting
376 return $setting_group->HasMemberRecursively($to_id);
377 }
378 }
379
380 return 0;
381}
382
383sub CurrentUserCanSee { 1 }
384sub CurrentUserCanCreate { 1 }
385sub CurrentUserCanModify { 1 }
386sub CurrentUserCanDelete { 1 }
387
388### Internal methods
389
390# _GetObject: helper routine to load the correct object whose parameters
391# have been passed.
392
393sub _GetObject {
394 my $self = shift;
395 my $privacy = shift;
396
397 # short circuit: if they pass the object we want anyway, just return it
398 if (blessed($privacy) && $privacy->isa('RT::Record')) {
399 return $privacy;
400 }
401
402 my ($obj_type, $obj_id) = split(/\-/, ($privacy || ''));
403
404 unless ($obj_type && $obj_id) {
405 $privacy = '(undef)' if !defined($privacy);
406 $RT::Logger->debug("Invalid privacy string '$privacy'");
407 return undef;
408 }
409
410 my $object = $self->_load_privacy_object($obj_type, $obj_id);
411
412 unless (ref($object) eq $obj_type) {
413 $RT::Logger->error("Could not load object of type $obj_type with ID $obj_id, got object of type " . (ref($object) || 'undef'));
414 return undef;
415 }
416
417 # Do not allow the loading of a user object other than the current
418 # user, or of a group object of which the current user is not a member.
419
420 if ($obj_type eq 'RT::User' && $object->Id != $self->CurrentUser->UserObj->Id) {
421 $RT::Logger->debug("Permission denied for user other than self");
422 return undef;
423 }
424
425 if ( $obj_type eq 'RT::Group'
426 && !$object->HasMemberRecursively($self->CurrentUser->PrincipalObj)
427 && !$self->CurrentUser->HasRight( Object => $RT::System, Right => 'SuperUser' ) ) {
428 $RT::Logger->debug("Permission denied, ".$self->CurrentUser->Name.
429 " is not a member of group");
430 return undef;
431 }
432
433 return $object;
434}
435
436sub _load_privacy_object {
437 my ($self, $obj_type, $obj_id) = @_;
438 if ( $obj_type eq 'RT::User' ) {
439 if ( $obj_id == $self->CurrentUser->Id ) {
440 return $self->CurrentUser->UserObj;
441 } else {
442 $RT::Logger->warning("User #". $self->CurrentUser->Id ." tried to load container user #". $obj_id);
443 return undef;
444 }
445 }
446 elsif ($obj_type eq 'RT::Group') {
447 my $group = RT::Group->new($self->CurrentUser);
448 $group->Load($obj_id);
449 return $group;
450 }
451 elsif ($obj_type eq 'RT::System') {
452 return RT::System->new($self->CurrentUser);
453 }
454
455 $RT::Logger->error(
456 "Tried to load a ". $self->ObjectName
457 ." belonging to an $obj_type, which is neither a user nor a group"
458 );
459
460 return undef;
461}
462
463sub _build_privacy {
464 my ($self, $obj_type, $obj_id) = @_;
465
466 # allow passing in just an object to find its privacy string
467 if (ref($obj_type)) {
468 my $Object = $obj_type;
469 return $Object->isa('RT::User') ? 'RT::User-' . $Object->Id
470 : $Object->isa('RT::Group') ? 'RT::Group-' . $Object->Id
471 : $Object->isa('RT::System') ? 'RT::System-' . $Object->Id
472 : undef;
473 }
474
475 return undef unless ($obj_type); # undef workaround
476 return $obj_type eq 'RT::User' ? "$obj_type-$obj_id"
477 : $obj_type eq 'RT::Group' ? "$obj_type-$obj_id"
478 : $obj_type eq 'RT::System' ? "$obj_type-$obj_id"
479 : undef;
480}
481
482=head2 ObjectsForLoading
483
484Returns a list of objects that can be used to load this shared setting. It
485is ACL checked.
486
487=cut
488
489sub ObjectsForLoading {
490 my $self = shift;
491 return grep { $self->CurrentUserCanSee($_) } $self->_PrivacyObjects;
492}
493
494=head2 ObjectsForCreating
495
496Returns a list of objects that can be used to create this shared setting. It
497is ACL checked.
498
499=cut
500
501sub ObjectsForCreating {
502 my $self = shift;
503 return grep { $self->CurrentUserCanCreate($_) } $self->_PrivacyObjects;
504}
505
506=head2 ObjectsForModifying
507
508Returns a list of objects that can be used to modify this shared setting. It
509is ACL checked.
510
511=cut
512
513sub ObjectsForModifying {
514 my $self = shift;
515 return grep { $self->CurrentUserCanModify($_) } $self->_PrivacyObjects;
516}
517
518RT::Base->_ImportOverlays();
519
5201;