File Coverage

blib/lib/SPOPS/Secure.pm
Criterion Covered Total %
statement 48 279 17.2
branch 0 176 0.0
condition 0 77 0.0
subroutine 16 34 47.0
pod 5 16 31.2
total 69 582 11.8


line stmt bran cond sub pod time code
1             package SPOPS::Secure;
2              
3             # $Id: Secure.pm,v 3.14 2004/06/02 00:48:21 lachoy Exp $
4              
5 3     3   79097 use strict;
  3         8  
  3         126  
6 3     3   18 use base qw( Exporter );
  3         6  
  3         627  
7 3     3   18 use vars qw( $EMPTY );
  3         7  
  3         318  
8 3     3   1259 use Data::Dumper qw( Dumper );
  3         20358  
  3         218  
9 3     3   9404 use Log::Log4perl qw( get_logger );
  3         229466  
  3         105  
10              
11             $SPOPS::Secure::VERSION = sprintf("%d.%02d", q$Revision: 3.14 $ =~ /(\d+)\.(\d+)/);
12              
13             # Stuff for security constants and exporting
14              
15 3     3   339 use constant SEC_LEVEL_NONE => 1;
  3         10  
  3         192  
16 3     3   18 use constant SEC_LEVEL_SUMMARY => 2;
  3         6  
  3         136  
17 3     3   18 use constant SEC_LEVEL_READ => 4;
  3         6  
  3         127  
18 3     3   15 use constant SEC_LEVEL_WRITE => 8;
  3         6  
  3         122  
19              
20 3     3   34 use constant SEC_LEVEL_NONE_VERBOSE => 'NONE';
  3         7  
  3         147  
21 3     3   115 use constant SEC_LEVEL_SUMMARY_VERBOSE => 'SUMMARY';
  3         5  
  3         153  
22 3     3   16 use constant SEC_LEVEL_READ_VERBOSE => 'READ';
  3         6  
  3         152  
23 3     3   97 use constant SEC_LEVEL_WRITE_VERBOSE => 'WRITE';
  3         6  
  3         142  
24              
25 3     3   17 use constant SEC_SCOPE_USER => 'u';
  3         5  
  3         147  
26 3     3   16 use constant SEC_SCOPE_GROUP => 'g';
  3         7  
  3         139  
27 3     3   14 use constant SEC_SCOPE_WORLD => 'w';
  3         5  
  3         41437  
28              
29             my $log = get_logger();
30              
31             my @LEVEL = qw( SEC_LEVEL_NONE SEC_LEVEL_SUMMARY SEC_LEVEL_READ SEC_LEVEL_WRITE );
32             my @SCOPE = qw( SEC_SCOPE_USER SEC_SCOPE_GROUP SEC_SCOPE_WORLD );
33             my @VRBS = qw( SEC_LEVEL_NONE_VERBOSE SEC_LEVEL_SUMMARY_VERBOSE
34             SEC_LEVEL_READ_VERBOSE SEC_LEVEL_WRITE_VERBOSE );
35              
36             @SPOPS::Secure::EXPORT_OK = ( '$EMPTY', @LEVEL, @SCOPE, @VRBS );
37             %SPOPS::Secure::EXPORT_TAGS = (
38             all => [ @LEVEL, @SCOPE, @VRBS ],
39             scope => [ @SCOPE ],
40             level => [ @LEVEL ],
41             verbose => [ @VRBS ],
42             );
43              
44             # Dummy (empty) hashref to pass back if we need to
45             # basically deny the request -- e.g., they asked for a
46             # user that isn't an object, they asked for the current
47             # user and there is none, etc.
48              
49             $EMPTY = {
50             SEC_SCOPE_WORLD() => SEC_LEVEL_NONE,
51             SEC_SCOPE_USER() => SEC_LEVEL_NONE,
52             SEC_SCOPE_GROUP() => {}
53             };
54              
55             my %LEVEL_VERBOSE = (
56             SEC_LEVEL_NONE_VERBOSE() => SEC_LEVEL_NONE,
57             SEC_LEVEL_SUMMARY_VERBOSE() => SEC_LEVEL_SUMMARY,
58             SEC_LEVEL_READ_VERBOSE() => SEC_LEVEL_READ,
59             SEC_LEVEL_WRITE_VERBOSE() => SEC_LEVEL_WRITE,
60             );
61              
62             my %LEVEL_CODE = map { $LEVEL_VERBOSE{ $_ } => $_ } keys %LEVEL_VERBOSE;
63              
64             my $INITIAL_SECURITY_DEFAULT = SEC_LEVEL_NONE;
65              
66             # This needs to be down here so we don't get into a deadlock (and die)
67             # situation
68              
69             require SPOPS::Exception::Security;
70             require SPOPS::Secure::Util;
71              
72             ########################################
73             # RETRIEVE SECURITY
74             ########################################
75              
76             # Typical calls:
77             # $self->check_action_security({ required => SEC_LEVEL_WRITE });
78             # $class->check_action_security({ required => SEC_LEVEL_READ, id => $id });
79              
80             # Note that we return SEC_LEVEL_WRITE to all requests where the object
81             # does not have an ID -- meaning that the object has not yet been
82             # saved, and this object creation security must be handled by the
83             # application rather than SPOPS
84              
85             # Returns the security level if ok, die()s with an error message if not
86              
87             # TODO: What is the difference between check_security and
88             # check_action_security? Do we need both? Should we only expose
89             # check_action_security()?
90              
91             sub check_action_security {
92 0     0 0   my ( $item, $p ) = @_;
93 0 0         $log->is_debug &&
    0          
94             $log->debug( "Trying to check security on: ",
95             ( ref $item ) ? ref( $item ) . " " . $item->id : $item,
96             " with params: ", Dumper( $p ) );
97              
98             # since the assumption outlined above (only saved objects have ids)
99             # might not be true in all cases, provide an escape route for classes
100             # that need security and want to handle their ids themselves
101              
102 0 0         return SEC_LEVEL_WRITE if ( $p->{is_add} ); # or ! $item->is_saved );
103              
104             # If the class has told us they're not using security (even tho
105             # SPOPS::Secure is in the 'isa', then everyone can do everything
106              
107 0 0         return SEC_LEVEL_WRITE if ( $item->no_security );
108              
109             # This gets filled with the found security level, oddly, the user
110             # can pass in a security level if it's already been found
111              
112 0           my $level = $p->{security_level};
113              
114 0           my ( $class, $id );
115              
116             # If not already defined, find the security level explicitly
117              
118 0 0         unless ( $level ) {
119              
120             # Check to see that the ID exists -- if not, it's an add and will
121             # not be checked since SPOPS relies on your application to implement
122             # who should and should not create an object.
123              
124 0   0       $class = ref $item || $item;
125 0 0         $id = ( ref $item ) ? $item->id : $p->{id};
126 0 0         unless ( $id ) {
127 0 0         $log->is_info &&
128             $log->info( "ID not found, returning WRITE security" );
129 0           return SEC_LEVEL_WRITE;
130             }
131             $log->is_debug &&
132 0 0         $log->debug( "Checking action on $class [$id] and required ",
133             "level is [$p->{required}]" );
134              
135             # Calls to SPOPS::Secure->... note that we do not need to
136             # explicitly pass in group/user information, since SPOPS::Secure
137             # will retrieve it for us.
138              
139             # TODO: Revisit that we allow exceptions to bubble up
140              
141 0 0         $level = $class->check_security({ class => $class,
142             object_id => $id,
143             object => ( ref $item ) ? $item : undef });
144              
145             }
146             $log->is_info &&
147 0 0         $log->info( "Found security level of ($level)" );
148              
149             # If the level is below what is necessary call
150             # register_security_error() which should set an error message and
151             # die with a general one.
152              
153 0 0         if ( $level < $p->{required} ) {
154 0           $class->register_security_error({ class => $class, id => $id,
155             level => $level,
156             required => $p->{required} });
157             }
158 0           return $level; # Rock and roll
159             }
160              
161              
162             sub register_security_error {
163 0     0 0   my ( $class, $p ) = @_;
164 0 0         $log->is_info &&
165             $log->info( "Cannot access $p->{class} record with ID $p->{id}; ",
166             "access: $p->{level} while $p->{required} is required." );
167 0           SPOPS::Exception::Security->throw( "Access denied due to security level",
168             { required => $p->{required},
169             found => $p->{level} } );
170             }
171              
172              
173             # Returns: security level for a particular object/class given a scope
174             # and if necessary, a scope_id; should always return at least the
175             # security level for the WORLD scope, since everything must have at
176             # least a permission for the WORLD scope.
177              
178             sub check_security {
179 0     0 1   my ( $class, $p ) = @_;
180 0           my $sec_info = $p->{sec_info};
181 0 0         unless ( $sec_info ) {
182 0 0         $log->is_info &&
183             $log->info( "Retrieving security information using get_security()" );
184 0 0         $p->{user} = shift @{ $p->{user} } if ( ref $p->{user} eq 'ARRAY' );
  0            
185              
186             # Retrieve security. If a subclass wants to implement a different
187             # way of implementing security, this is the method to override.
188              
189             # TODO: Revisit exception bubble up from here
190              
191 0           $sec_info = $class->get_security( $p );
192             }
193              
194             $log->is_info &&
195 0 0         $log->info( "Security information:\n", Dumper( $sec_info ) );
196              
197             # If a user security level exists, return it
198              
199 0 0         if ( my $user_level = $sec_info->{ SEC_SCOPE_USER() } ) {
200 0 0         $log->is_info &&
201             $log->info( "Return level [$user_level] at scope USER." );
202 0           return $user_level;
203             }
204              
205             # Go through the groups; if there are groups, we return the highest
206             # level among them.
207              
208 0           my $group_max = 0;
209 0   0       $sec_info->{ SEC_SCOPE_GROUP() } ||= {};
210 0           foreach my $gid ( keys %{ $sec_info->{ SEC_SCOPE_GROUP() } } ) {
  0            
211 0           my $group_level = $sec_info->{ SEC_SCOPE_GROUP() }{ $gid };
212 0 0         next unless ( $group_level );
213 0 0         $group_max = ( $group_level > $group_max ) ? $group_level : $group_max;
214 0 0         $log->is_info &&
215             $log->info( "Level of GROUP [$gid] is [$group_level]" );
216             }
217 0 0         return $group_max if ( $group_max );
218              
219 0           my $world_level = $sec_info->{ SEC_SCOPE_WORLD() };
220 0 0         $log->is_info &&
221             $log->info( "Return level [$world_level] at scope WORLD" );
222 0           return $world_level;
223             }
224              
225              
226             # Returns hashref
227              
228             sub get_security {
229 0     0 1   my ( $item, $p ) = @_;
230              
231             # Since we can pass in the class/oid, those take precedence
232              
233 0           my ( $class, $oid ) = SPOPS::Secure::Util->find_class_and_oid( $item, $p );
234 0 0         $log->is_info &&
235             $log->info( "Checking security for [$class] [$oid] with:\n", Dumper( $p ) );
236 0           my ( $user, $group_list ) = $item->get_security_scopes( $p );
237              
238 0 0         if ( my $security_info = $item->_check_superuser( $user, $group_list ) ) {
239 0 0         $log->is_info &&
240             $log->info( "Superuser is logged in, can do anything" );
241 0           return $security_info;
242             }
243              
244 0   0       my $sec_obj_class = $p->{security_object_class} ||
245             $item->global_security_object_class;
246 0 0         $log->is_info &&
247             $log->info( "Using security object [$sec_obj_class]" );
248              
249             # TODO: Revisit exception bubble up from here
250              
251 0           my $sec_listing = $sec_obj_class->fetch_by_object( $class,
252             { object_id => $oid,
253             user => $user,
254             group => $group_list } );
255 0   0       return $sec_listing || \%{ $EMPTY };
256             }
257              
258              
259              
260             sub get_security_scopes {
261 0     0 1   my ( $item, $p ) = @_;
262 0           my $user = undef;
263 0           my $group_list = [];
264              
265 0 0         $log->is_info &&
266             $log->info( "Checking security scopes with:\n", Dumper( $p ) );
267              
268             # If both user and group(s) are passed in, we need to modify the
269             # group list to include the groups that the user belongs to as well
270             # as the groups specified
271              
272 0 0 0       if ( $p->{user} and $p->{group} ) {
    0 0        
    0          
    0          
273 0 0         $log->is_info &&
274             $log->info( "Both user and group were specified." );
275 0           $user = $p->{user};
276 0           $group_list = eval { $user->group };
  0            
277 0 0         $log->warn( "Cannot fetch groups from user record: $@." ) if ( $@ );
278 0           push @{ $group_list }, ( ref $p->{group} eq 'ARRAY' )
  0            
279 0 0         ? @{ $p->{group} } : ( $p->{group} );
280             }
281              
282             # The default (no user, no group) is just to get the user and its
283             # groups
284              
285             elsif ( ! $p->{user} and ! $p->{group} ) {
286 0 0         $log->is_info &&
287             $log->info( "Neither user/group specified, using logins." );
288 0           $user = $item->global_user_current;
289 0   0       $group_list = $item->global_group_current || [];
290              
291             # If no user or group was passed in, and we cannot retrieve a
292             # user object with the global_user_current() or group objects
293             # with global_group_current(), then all we want to get is the
294             # WORLD security level, which means we can skip the
295             # user/group_list stuff altogether
296              
297             # NOTE: even tho it doesn't appear, there IS a dependency between
298             # the next two clauses; that is, you *MUST NOT* check to see if
299             # $user->{user_id} == 1 if there actually is no user. Otherwise
300             # perl will autovivify a hashref in $R->{auth}{user} which
301             # will throw a 800-pound monkey wrench into operations.
302             # We really need to look into that, it's quite brittle.
303              
304 0 0 0       unless ( $user or scalar @{ $group_list } > 0 ) {
  0            
305 0 0         $log->is_info &&
306             $log->info( "No user or groups found." );
307 0           $user = undef;
308 0           $group_list = undef;
309             }
310             }
311              
312             # If we were given a user to check, base the group_list around the
313             # groups the user belongs to
314              
315             elsif ( $p->{user} ) {
316 0 0         $log->is_info &&
317             $log->info( "Only user specified; using user's groups." );
318 0           $user = $p->{user};
319 0           $group_list = eval { $user->group; };
  0            
320 0 0         $log->warn( "Cannot fetch groups from user record: $@." ) if ( $@ );
321             }
322              
323             # Otherwise, the group list is based on whatever was passed in
324              
325             elsif ( $p->{group} ) {
326 0 0         $log->is_info &&
327             $log->info( "Only group specified." );
328 0 0         $group_list = ( ref $p->{group} eq 'ARRAY' )
329             ? $p->{group}: [ $p->{group} ];
330             }
331 0           return ( $user, $group_list );
332             }
333              
334              
335             ########################################
336             # SET SECURITY
337             ########################################
338              
339              
340             sub create_initial_security {
341 0     0 1   my ( $item, $p ) = @_;
342              
343             # Since we can pass in the class/oid, those take precedence
344              
345 0           my ( $class, $oid ) = SPOPS::Secure::Util->find_class_and_oid( $item, $p );
346 0 0         $log->is_info &&
347             $log->info( "Setting initial security for $class ($oid)" );
348              
349             # \%init describes the initial security to create for this object;
350             # note that \%init may describe code to execute or it may simply
351             # describe a level to denote
352              
353 0           my $init = $class->creation_security;
354 0 0 0       return undef unless ( ref $init and scalar keys %{ $init } );
  0            
355              
356             # Get the current user and groups
357              
358 0           my $user = $class->global_user_current;
359 0           my $group = $class->global_group_current;
360              
361             # \%level holds the actual security settings for this object
362              
363 0           my $level = {};
364              
365             # If our level assignment looks like this:
366             # creation_security => {
367             # code => [ 'MyApp::SecurityPolicy' => 'handler' ] },
368             # },
369             #
370             # Then we execute "MyApp::SecurityPolicy->handler( \% ), passing the
371             # parameters class and oid (for the object), $user (current user
372             # object) and $group (arrayref of groups the user belongs to)
373             #
374              
375             # The code should return a hashref of either scope => SEC_LEVEL_* (in
376             # the case of USER and WORLD) or scope => { scope_id => SEC_LEVEL* }
377             # (in the case of GROUP). If an 'undef' is passed for a scope then
378             # that scope will not be processed. For example:
379             #
380             # return { u => undef,
381             # g => { $main_gid => SEC_LEVEL_READ, $admin_gid => SEC_LEVEL_WRITE },
382             # w => SEC_LEVEL_NONE };
383              
384 0 0         if ( ref $init->{code} eq 'ARRAY' ) {
385 0           my ( $pkg, $method ) = @{ $init->{code} };
  0            
386 0 0         $log->is_info &&
387             $log->info( "$pkg\-\>$method being executed for security" );
388 0           $level = $pkg->$method({ class => $class,
389             object_id => $oid,
390             user => $user,
391             group => $group });
392 0 0         $log->is_info &&
393             $log->info( "Result of code:\n", Dumper( $level ) );
394             }
395              
396             # Go through each scope specified in the init and evaluate the
397             # specification for initial security.
398              
399             else {
400              
401             # Create a list of the group_id for ez-reference
402              
403 0           my @gid = map { $_->{group_id} } @{ $group };
  0            
  0            
404              
405 0           SCOPE:
406 0           foreach my $scope ( keys %{ $init } ) {
407 0           my $todo = $init->{ $scope };
408 0 0         next unless ( $todo );
409 0 0         $log->is_info &&
410             $log->info( "Determining security level for $scope" );
411              
412             # If our level assignment looks like this:
413             # creation_security => {
414             # ...,
415             # g => { 3 => WRITE },
416             # ...,
417             # },
418             #
419             # Then we want to do the assignments for the IDs in that scope
420              
421 0 0         if ( ref $todo eq 'HASH' ) {
422 0           $level->{ $scope } = { map { $_ => $LEVEL_VERBOSE{ uc $todo->{$_} } }
  0            
423 0           keys %{ $todo } };
424             }
425              
426             # Otherwise it will look like this:
427             # creation_security => {
428             # ...,
429             # g => 'WRITE',
430             # ...,
431             # },
432             #
433             # Which means we'd want to apply WRITE for all the groups
434             # to which this user belongs. Be careful with this!
435             # (remember that 'public' is a group, too).
436              
437             else {
438 0 0 0       if ( $scope eq 'w' ) {
    0          
    0          
439 0           $level->{w} = $LEVEL_VERBOSE{ uc $todo };
440             }
441             elsif ( $scope eq 'u' and ref $user ) {
442 0           $level->{u} = { $user->id() => $LEVEL_VERBOSE{ uc $todo } };
443             }
444             elsif ( $scope eq 'g' ) {
445 0           $level->{g} = { map { $_ => $LEVEL_VERBOSE{ uc $todo } } @gid };
  0            
446             }
447             }
448             }
449             $log->is_info &&
450 0 0         $log->info( "Level assigned:\n", Dumper( $level ) );
451             }
452              
453             # Now that \%level is all setup, process it
454              
455             # Ensure that this is a *$class* (this was the focus of bugs earlier,
456             # exhibited by something in the sys_security table that looks like
457             # "This::Class=HASH(0x8bb7028)"
458              
459 0   0       my $obj_class = ref $class || $class;
460              
461             # First do WORLD
462              
463 0   0       $level->{w} ||= $INITIAL_SECURITY_DEFAULT;
464 0           $class->set_item_security({
465             class => $obj_class,
466             object_id => $oid,
467             security_level => $level->{w},
468             scope => SEC_SCOPE_WORLD });
469 0 0         $log->is_info &&
470             $log->info( "Set initial security for WORLD to $level" );
471              
472             # Doing the user and group perms is identical, so we don't
473             # need to partition by scope for them
474              
475             # Note that we're relying on the fact that u => SEC_SCOPE_USER and
476             # g => SEC_SCOPE_GROUP; if this changes we'll have to do a little
477             # mapping from the scopes in $level to the actual scope values
478              
479             # TODO: Should collect exceptions as we go?
480              
481 0           foreach my $scope ( ( SEC_SCOPE_USER, SEC_SCOPE_GROUP ) ) {
482 0 0         next unless ( ref $level->{ $scope } eq 'HASH' );
483 0           foreach my $id ( keys %{ $level->{ $scope } } ) {
  0            
484 0   0       $id ||= '';
485 0           $class->set_item_security({
486             class => $obj_class,
487             object_id => $oid,
488             security_level => $level->{ $scope }{ $id },
489             scope => $scope,
490             scope_id => $id });
491 0 0         $log->is_info &&
492             $log->info( "Set initial security for $scope ($id) to $level->{$scope}{$id}" );
493             }
494             }
495 0           return 1;
496             }
497              
498              
499             # Set security for one or more objects
500              
501             sub set_security {
502 0     0 1   my ( $item, $p ) = @_;
503 0   0       my $sec_obj_class = $p->{security_object_class} ||
504             $item->global_security_object_class;
505              
506 0   0       my $level = $p->{level} || $p->{security_level};
507              
508             # First ensure that both a level is specified...
509              
510 0 0         unless ( $level ) {
511 0           SPOPS::Exception->throw( 'Cannot set security: no permissions defined' );
512             }
513              
514             # ...and that a scope is specified
515              
516 0 0         unless ( $p->{scope} ) {
517 0           SPOPS::Exception->throw( 'Cannot set security: no scope defined' );
518             }
519              
520             # Since we can pass in the class/oid, those take precedence
521              
522 0           my ( $class, $oid ) = SPOPS::Secure::Util->find_class_and_oid( $item, $p );
523 0 0         $log->is_info &&
524             $log->info( "Checking security for $class [$oid]" );
525              
526             # If we were passed a particular scope, just return
527             # the results of updating that information
528              
529 0 0         unless ( ref $p->{scope} ) {
530 0 0 0       if ( $p->{scope} eq SEC_SCOPE_WORLD ) {
    0          
531 0           return $item->set_item_security({
532             class => $class,
533             object_id => $oid,
534             security_level => $level,
535             scope => $p->{scope},
536             scope_id => $p->{scope_id} } );
537             }
538              
539             # For user/group, we can pass in multiple items for which we
540             # want to set security acting upon a particular class/object;
541             # the test for this is if $level is a hashref.
542              
543             elsif ( $p->{scope} eq SEC_SCOPE_GROUP or $p->{scope} eq SEC_SCOPE_USER ) {
544 0 0         if ( ref $level eq 'HASH' ) {
545 0           return $item->set_multiple_security({
546             class => $class,
547             object_id => $oid,
548             security_level => $level,
549             scope => $p->{scope} } );
550             }
551 0           return $item->set_item_security({
552             class => $class,
553             object_id => $oid,
554             security_level => $level,
555             scope => $p->{scope},
556             scope_id => $p->{scope_id} } );
557             }
558 0           SPOPS::Exception->throw( "Set security failed: unrecognized scope " .
559             "[$p->{scope}] defined" );
560             }
561              
562             # If we've made it here, the scope should be a reference. But if
563             # it's not an arrayref, we have a problem
564              
565 0 0         if ( ref $p->{scope} ne 'ARRAY' ) {
566 0           SPOPS::Exception->throw( "Set security failed: unrecognized scope " .
567             "[$p->{scope}] defined" );
568             }
569              
570             # If level is not a hashref (since we are using multiple scopes)
571             # at this point, we have a problem
572              
573 0 0         if ( ref $level ne 'HASH' ) {
574 0           SPOPS::Exception->throw( "Set security failed: there are multiple scopes" .
575             "but security_level does not match " );
576             }
577              
578             # If we were passed multiple scope entries, go through each one
579             # and total up the items changed for return. Note that we no
580             # longer have a need for scope_id (for user/group) since that logic
581             # is embedded within the level hashref
582              
583             # Note that *removing* security must be done outside this routine.
584             # That is, you can't simply pass a full list of 'new' security
585             # options for a particular object/class and expect this method to
586             # sort them out for you
587              
588 0           my $count = 0;
589              
590 0           SCOPE:
591 0           foreach my $scope ( @{ $p->{scope} } ) {
592 0 0 0       if ( $scope eq SEC_SCOPE_WORLD ) {
    0          
593 0           $count += $item->set_item_security({
594             class => $class,
595             object_id => $oid,
596             scope => $scope,
597             security_level => $level->{ $scope } });
598             }
599             elsif ( $scope eq SEC_SCOPE_GROUP or $scope eq SEC_SCOPE_USER ) {
600 0           $count += $item->set_multiple_security({
601             class => $class,
602             object_id => $oid,
603             scope => $scope,
604             security_level => $level->{ $scope } });
605             }
606             else {
607 0           $log->warn( "Cannot set security for scope [$scope] since it is not ",
608             join( '/', SEC_SCOPE_WORLD, SEC_SCOPE_USER, SEC_SCOPE_GROUP ) );
609             }
610             }
611 0           return $count;
612             }
613              
614              
615             sub set_item_security {
616 0     0 0   my ( $item, $p ) = @_;
617              
618 0   0       my $level = $p->{level} || $p->{security_level};
619              
620             # Since we can pass in the class/oid, those take precedence
621              
622 0           my ( $class, $oid ) = SPOPS::Secure::Util->find_class_and_oid( $item, $p );
623              
624 0   0       $p->{scope_id} ||= '';
625 0 0         $log->is_info &&
626             $log->info( "Modifying scope $p->{scope} ($p->{scope_id}) for ",
627             "$class ($oid) with $level" );
628              
629 0   0       my $sec_obj_class = $p->{security_object_class} ||
630             $item->global_security_object_class;
631 0           my $obj = $sec_obj_class->fetch_match( $class,
632             { object_id => $oid,
633             scope => $p->{scope},
634             scope_id => $p->{scope_id} } );
635 0 0 0       return 1 if ( $obj and $obj->{security_level} == $level );
636              
637 0 0         unless ( $obj ) {
638 0 0         $log->is_info &&
639             $log->info( "Current object does not exist. Creating one [$oid]" );
640 0           $obj = $sec_obj_class->new({ class => $class,
641             object_id => $oid,
642             scope => $p->{scope},
643             scope_id => $p->{scope_id} });
644             }
645              
646              
647             # Otherwise set the level and save, letting any errors from the
648             # save bubble up
649              
650 0           $obj->{security_level} = $level;
651 0           return $obj->save;
652             }
653              
654              
655             sub set_multiple_security {
656 0     0 0   my ( $item, $p ) = @_;
657              
658             # Since we can pass in the class/oid, those take precedence
659              
660 0           my ( $class, $oid ) = SPOPS::Secure::Util->find_class_and_oid( $item, $p );
661 0 0         $log->is_info &&
662             $log->info( "Setting multiple security for $class ($oid) and ",
663             "scope $p->{scope}." );
664              
665 0   0       my $sec_obj_class = $p->{security_object_class} ||
666             $item->global_security_object_class;
667              
668 0   0       my $level = $p->{level} || $p->{security_level};
669              
670 0           $item->_remove_superuser_level( $level );
671              
672             # Count up the number of modifications we are making -- if there
673             # are none then we're done
674              
675 0 0         return 1 unless ( scalar keys %{ $level } );
  0            
676 0           my $count = 0;
677              
678 0           ITEM:
679 0           foreach my $id ( keys %{ $level } ) {
680 0 0         $log->is_info &&
681             $log->info( "Setting ID $id to $level->{$id}" );
682 0           $count += $item->set_item_security({ class => $class,
683             object_id => $oid,
684             scope => $p->{scope},
685             scope_id => $id,
686             security_level => $level->{ $id } });
687             }
688 0           return $count;
689             }
690              
691              
692             sub remove_item_security {
693 0     0 0   my ( $item, $p ) = @_;
694 0 0 0       if ( $p->{scope} ne SEC_SCOPE_WORLD and $p->{scope_id} == 1 ) {
695 0           $log->warn( "Will not remove security with scope $p->{scope} ($p->{scope_id}) - admin." );
696 0           return undef;
697             }
698              
699             # Since we can pass in the class/oid, those take precedence
700              
701 0           my ( $class, $oid ) = SPOPS::Secure::Util->find_class_and_oid( $item, $p );
702 0 0         $log->is_info &&
703             $log->info( "Removing security for $class ($oid) with ",
704             "scope $p->{scope} ($p->{scope_id})" );
705              
706 0   0       my $sec_obj_class = $p->{security_object_class} ||
707             $item->global_security_object_class;
708 0           my $obj = eval { $sec_obj_class->fetch_match(
  0            
709             $class,
710             { object_id => $oid,
711             scope => $p->{scope},
712             scope_id => $p->{scope_id} }) };
713 0 0         if ( $@ ) {
714 0           $log->warn( "Error found trying to match parameters to an existing object\n",
715             "Error: $@->{error}\nSQL: $@->{sql}" );
716             }
717 0 0         unless ( $obj ) {
718 0           $log->warn( "Security object does not exist with parameters, so we cannot remove it." );
719 0           return undef;
720             }
721              
722             # Let error trickle up
723              
724 0           return $obj->remove;
725             }
726              
727              
728             ########################################
729             # SCOPE RETRIEVAL METHODS
730             ########################################
731              
732             # If no users/groups are available, these ensure we just check WORLD
733              
734             sub global_user_current {
735 0 0   0 0   $log->is_info &&
736             $log->info( "Using empty definition for current user; this may not be what you want" );
737             return undef
738 0           }
739              
740              
741             sub global_group_current {
742 0 0   0 0   $log->is_info &&
743             $log->info( "Using empty definition for current groups; this may not be what you want" );
744 0           return [];
745             }
746              
747              
748             ########################################
749             # ROOT CHECKS
750             ########################################
751              
752 0     0 0   sub get_superuser_id { return 1 }
753 0     0 0   sub get_supergroup_id { return 1 }
754              
755             # Define comparison operations for superuser/supergroup
756              
757             sub is_superuser {
758 0     0 0   my ( $class, $id ) = @_;
759 0           return ( $id eq $class->get_superuser_id );
760             }
761              
762             sub is_supergroup {
763 0     0 0   my ( $class, @id ) = @_;
764 0           my $super_gid = $class->get_supergroup_id;
765 0           return grep { $_ eq $super_gid } @id;
  0            
766             }
767              
768              
769             # See if this is the superuser or a member of the supergroup
770              
771             sub _check_superuser {
772 0     0     my ( $item, $user, $group_list ) = @_;
773 0 0 0       return undef unless ( $user or $group_list );
774 0           my %allow_all = %{ $EMPTY };
  0            
775 0           $allow_all{ SEC_SCOPE_USER() } = SEC_LEVEL_WRITE;
776              
777 0 0 0       if ( ref $user and $item->is_superuser( $user->{user_id} ) ) {
778 0 0         $log->is_info &&
779             $log->info( "User is superuser, checking ($item)" );
780 0           return \%allow_all;
781             }
782 0 0         if ( ref $group_list eq 'ARRAY' ) {
783 0 0         if ( $item->is_supergroup( map { $_->{group_id} } @{ $group_list } ) ) {
  0            
  0            
784 0           return \%allow_all ;
785             }
786             }
787 0           return undef;
788             }
789              
790              
791             # Removes the superuser and supergroup levels from \%level
792              
793             sub _remove_superuser_level {
794 0     0     my ( $class, $level ) = @_;
795 0 0 0       return unless ( ref $level eq 'HASH' and scalar keys %{ $level } );
  0            
796 0           my $super_gid = $class->get_supergroup_id;
797 0           delete $level->{ $class->get_superuser_id };
798 0           delete $level->{ $class->get_supergroup_id };
799             }
800              
801              
802             1;
803              
804             __END__