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