diff options
Diffstat (limited to 'rt/lib/RT/Principal_Overlay.pm')
-rw-r--r-- | rt/lib/RT/Principal_Overlay.pm | 367 |
1 files changed, 189 insertions, 178 deletions
diff --git a/rt/lib/RT/Principal_Overlay.pm b/rt/lib/RT/Principal_Overlay.pm index 4783c5ca6..3e2edaac6 100644 --- a/rt/lib/RT/Principal_Overlay.pm +++ b/rt/lib/RT/Principal_Overlay.pm @@ -1,8 +1,8 @@ -# BEGIN BPS TAGGED BLOCK {{{ +# {{{ BEGIN BPS TAGGED BLOCK # # COPYRIGHT: # -# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC # <jesse@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -42,27 +42,15 @@ # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. # -# END BPS TAGGED BLOCK }}} -# - -package RT::Principal; - +# }}} END BPS TAGGED BLOCK use strict; -use warnings; no warnings qw(redefine); - -use Cache::Simple::TimedExpiry; - - +use vars qw(%_ACL_KEY_CACHE); use RT::Group; use RT::User; -# Set up the ACL cache on startup -our $_ACL_CACHE; -InvalidateACLCache(); - # {{{ IsGroup =head2 IsGroup @@ -143,11 +131,6 @@ sub Object { A helper function which calls RT::ACE->Create - - - Returns a tuple of (STATUS, MESSAGE); If the call succeeded, STATUS is true. Otherwise it's - false. - =cut sub GrantRight { @@ -157,6 +140,11 @@ sub GrantRight { @_); + #if we haven't specified any sort of right, we're talking about a global right + if (!defined $args{'Object'} && !defined $args{'ObjectId'} && !defined $args{'ObjectType'}) { + $args{'Object'} = $RT::System; + } + unless ($args{'Right'}) { return(0, $self->loc("Invalid Right")); } @@ -184,11 +172,6 @@ sub GrantRight { Delete a right that a user has - - Returns a tuple of (STATUS, MESSAGE); If the call succeeded, STATUS is true. Otherwise it's - false. - - =cut sub RevokeRight { @@ -223,40 +206,7 @@ sub RevokeRight { # }}} -# {{{ sub _CleanupInvalidDelegations - -=head2 sub _CleanupInvalidDelegations { InsideTransaction => undef } - -Revokes all ACE entries delegated by this principal which are -inconsistent with this principal's current delegation rights. Does -not perform permission checks, but takes no action and returns success -if this principal still retains DelegateRights. Should only ever be -called from inside the RT library. - -If this principal is a group, recursively calls this method on each -cached user member of itself. - -If called from inside a transaction, specify a true value for the -InsideTransaction parameter. -Returns a true value if the deletion succeeded; returns a false value -and logs an internal error if the deletion fails (should not happen). - -=cut - -# This is currently just a stub for the methods of the same name in -# RT::User and RT::Group. - -sub _CleanupInvalidDelegations { - my $self = shift; - unless ( $self->Id ) { - $RT::Logger->warning("Principal not loaded."); - return (undef); - } - return ($self->Object->_CleanupInvalidDelegations(@_)); -} - -# }}} # {{{ sub HasRight @@ -291,31 +241,28 @@ Returns undef if no ACE was found. sub HasRight { my $self = shift; - my %args = ( - Right => undef, - Object => undef, - EquivObjects => undef, - @_ - ); + my %args = ( Right => undef, + Object => undef, + EquivObjects => undef, + @_ ); if ( $self->Disabled ) { - $RT::Logger->err( "Disabled User: " - . $self->id - . " failed access check for " - . $args{'Right'} ); + $RT::Logger->err( "Disabled User: " . $self->id . " failed access check for " . $args{'Right'} ); return (undef); } if ( !defined $args{'Right'} ) { - $RT::Logger->crit("HasRight called without a right"); + require Carp; + $RT::Logger->debug( Carp::cluck("HasRight called without a right") ); return (undef); } - if ( defined( $args{'Object'} ) - && UNIVERSAL::can( $args{'Object'}, 'id' ) - && $args{'Object'}->id ) - { - push( @{ $args{'EquivObjects'} }, $args{Object} ); + if ( defined( $args{'Object'} )) { + return (undef) unless (UNIVERSAL::can( $args{'Object'}, 'id' ) ); + push(@{$args{'EquivObjects'}}, $args{Object}); + } + elsif ( $args{'ObjectId'} && $args{'ObjectType'} ) { + $RT::Logger->crit(Carp::cluck("API not supprted")); } else { $RT::Logger->crit("$self HasRight called with no valid object"); @@ -323,50 +270,88 @@ sub HasRight { } # If this object is a ticket, we care about ticket roles and queue roles - if ( ( ref( $args{'Object'} ) eq 'RT::Ticket' ) && $args{'Object'}->Id ) { - -# this is a little bit hacky, but basically, now that we've done the ticket roles magic, we load the queue object -# and ask all the rest of our questions about the queue. - push( @{ $args{'EquivObjects'} }, $args{'Object'}->QueueObj ); + if ( (ref($args{'Object'}) eq 'RT::Ticket') && $args{'Object'}->Id) { + # this is a little bit hacky, but basically, now that we've done the ticket roles magic, we load the queue object + # and ask all the rest of our questions about the queue. + push (@{$args{'EquivObjects'}}, $args{'Object'}->QueueObj); } + # {{{ If we've cached a win or loss for this lookup say so # {{{ Construct a hashkey to cache decisions in my $hashkey = do { - no warnings 'uninitialized'; - - # We don't worry about the hash ordering, as this is only - # temporarily used; also if the key changes it would be - # invalidated anyway. - join( - ";:;", - $self->Id, - map { - $_, # the key of each arguments - ( $_ eq 'EquivObjects' ) # for object arrayref... - ? map( _ReferenceId($_), @{ $args{$_} } ) # calculate each - : _ReferenceId( $args{$_} ) # otherwise just the value - } keys %args + no warnings 'uninitialized'; + + # We don't worry about the hash ordering, as this is only + # temporarily used; also if the key changes it would be + # invalidated anyway. + join ( + ";:;", $self->Id, map { + $_, # the key of each arguments + ($_ eq 'EquivObjects') # for object arrayref... + ? map(_ReferenceId($_), @{$args{$_}}) # calculate each + : _ReferenceId( $args{$_} ) # otherwise just the value + } keys %args ); }; - # }}} + #Anything older than 60 seconds needs to be rechecked + my $cache_timeout = ( time - 60 ); + # {{{ if we've cached a positive result for this query, return 1 + if ( ( defined $self->_ACLCache->{"$hashkey"} ) + && ( $self->_ACLCache->{"$hashkey"}{'val'} == 1 ) + && ( defined $self->_ACLCache->{"$hashkey"}{'set'} ) + && ( $self->_ACLCache->{"$hashkey"}{'set'} > $cache_timeout ) ) { - my $cached_answer = $_ACL_CACHE->fetch($hashkey); + #$RT::Logger->debug("Cached ACL win for ". $args{'Right'}.$args{'Scope'}. $args{'AppliesTo'}."\n"); + return ( 1); + } + # }}} - # Returns undef on cache miss - if ( defined $cached_answer ) { - if ( $cached_answer == 1 ) { - return (1); - } - elsif ( $cached_answer == -1 ) { - return (0); - } + # {{{ if we've cached a negative result for this query return undef + elsif ( ( defined $self->_ACLCache->{"$hashkey"} ) + && ( $self->_ACLCache->{"$hashkey"}{'val'} == -1 ) + && ( defined $self->_ACLCache->{"$hashkey"}{'set'} ) + && ( $self->_ACLCache->{"$hashkey"}{'set'} > $cache_timeout ) ) { + + #$RT::Logger->debug("Cached ACL loss decision for ". $args{'Right'}.$args{'Scope'}. $args{'AppliesTo'}."\n"); + + return (undef); } + # }}} + + # }}} + + + + # {{{ Out of date docs + + # We want to grant the right if: + + + # # The user has the right as a member of a system-internal or + # # user-defined group + # + # Find all records from the ACL where they're granted to a group + # of type "UserDefined" or "System" + # for the object "System or the object "Queue N" and the group we're looking + # at has the recursive member $self->Id + # + # # The user has the right based on a role + # + # Find all the records from ACL where they're granted to the role "foo" + # for the object "System" or the object "Queue N" and the group we're looking + # at is of domain ("RT::Queue-Role" and applies to the right queue) + # or ("RT::Ticket-Role" and applies to the right ticket) + # and the type is the same as the type of the ACL and the group has + # the recursive member $self->Id + # + + # }}} my ( $or_look_at_object_rights, $or_check_roles ); my $right = $args{'Right'}; @@ -374,108 +359,119 @@ sub HasRight { # {{{ Construct Right Match # If an object is defined, we want to look at rights for that object - + my @look_at_objects; - push( @look_at_objects, "ACL.ObjectType = 'RT::System'" ) - unless $self->can('_IsOverrideGlobalACL') - and $self->_IsOverrideGlobalACL( $args{Object} ); - - foreach my $obj ( @{ $args{'EquivObjects'} } ) { - next unless ( UNIVERSAL::can( $obj, 'id' ) ); - my $type = ref($obj); - my $id = $obj->id; - - unless ($id) { - use Carp; - Carp::cluck( - "Trying to check $type rights for an unspecified $type"); - $RT::Logger->crit( - "Trying to check $type rights for an unspecified $type"); - } - push @look_at_objects, - "(ACL.ObjectType = '$type' AND ACL.ObjectId = '$id')"; - } + push (@look_at_objects, "ACL.ObjectType = 'RT::System'") + unless $self->can('_IsOverrideGlobalACL') and $self->_IsOverrideGlobalACL($args{Object}); + + + foreach my $obj (@{$args{'EquivObjects'}}) { + next unless (UNIVERSAL::can($obj, 'id')); + my $type = ref($obj); + my $id = $obj->id; + + unless ($id) { + use Carp; + Carp::cluck("Trying to check $type rights for an unspecified $type"); + $RT::Logger->crit("Trying to check $type rights for an unspecified $type"); + } + push @look_at_objects, "(ACL.ObjectType = '$type' AND ACL.ObjectId = '$id')"; + } + + # }}} # {{{ Build that honkin-big SQL query - my $query_base = - "SELECT ACL.id from ACL, Groups, Principals, CachedGroupMembers WHERE " . + - # Only find superuser or rights with the name $right - "(ACL.RightName = 'SuperUser' OR ACL.RightName = '$right') " . + my $query_base = "SELECT ACL.id from ACL, Groups, Principals, CachedGroupMembers WHERE ". + # Only find superuser or rights with the name $right + "(ACL.RightName = 'SuperUser' OR ACL.RightName = '$right') ". + # Never find disabled groups. + "AND Principals.Disabled = 0 " . + "AND CachedGroupMembers.Disabled = 0 ". + "AND Principals.id = Groups.id " . # We always grant rights to Groups - # Never find disabled groups. - "AND Principals.Disabled = 0 " - . "AND CachedGroupMembers.Disabled = 0 " - . "AND Principals.id = Groups.id " - . # We always grant rights to Groups + # See if the principal is a member of the group recursively or _is the rightholder_ + # never find recursively disabled group members + # also, check to see if the right is being granted _directly_ to this principal, + # as is the case when we want to look up group rights + "AND Principals.id = CachedGroupMembers.GroupId AND CachedGroupMembers.MemberId = '" . $self->Id . "' ". -# See if the principal is a member of the group recursively or _is the rightholder_ -# never find recursively disabled group members -# also, check to see if the right is being granted _directly_ to this principal, -# as is the case when we want to look up group rights -"AND Principals.id = CachedGroupMembers.GroupId AND CachedGroupMembers.MemberId = '" - . $self->Id . "' " - . + # Make sure the rights apply to the entire system or to the object in question + "AND ( ".join(' OR ', @look_at_objects).") "; - # Make sure the rights apply to the entire system or to the object in question - "AND ( " . join( ' OR ', @look_at_objects ) . ") "; -# The groups query does the query based on group membership and individual user rights - my $groups_query = $query_base . + # The groups query does the query based on group membership and individual user rights -# limit the result set to groups of types ACLEquivalence (user) UserDefined, SystemInternal and Personal -"AND ( ( ACL.PrincipalId = Principals.id AND ACL.PrincipalType = 'Group' AND " - . "(Groups.Domain = 'SystemInternal' OR Groups.Domain = 'UserDefined' OR Groups.Domain = 'ACLEquivalence' OR Groups.Domain = 'Personal'))" - . + my $groups_query = $query_base . - " ) "; - $self->_Handle->ApplyLimits( \$groups_query, 1 ); #only return one result + # limit the result set to groups of types ACLEquivalence (user) UserDefined, SystemInternal and Personal + "AND ( ( ACL.PrincipalId = Principals.id AND ACL.PrincipalType = 'Group' AND ". + "(Groups.Domain = 'SystemInternal' OR Groups.Domain = 'UserDefined' OR Groups.Domain = 'ACLEquivalence' OR Groups.Domain = 'Personal'))". + " ) "; + $self->_Handle->ApplyLimits(\$groups_query, 1); #only return one result + my @roles; - foreach my $object ( @{ $args{'EquivObjects'} } ) { - push( @roles, $self->_RolesForObject( ref($object), $object->id ) ); + foreach my $object (@{$args{'EquivObjects'}}) { + push (@roles, $self->_RolesForObject(ref($object), $object->id)); } # The roles query does the query based on roles my $roles_query; if (@roles) { - $roles_query = - $query_base . "AND " . " ( (" - . join( ' OR ', @roles ) . " ) " - . " AND Groups.Type = ACL.PrincipalType AND Groups.Id = Principals.id AND Principals.PrincipalType = 'Group') "; - $self->_Handle->ApplyLimits( \$roles_query, 1 ); #only return one result + $roles_query = $query_base . "AND ". + " ( (".join (' OR ', @roles)." ) ". + " AND Groups.Type = ACL.PrincipalType AND Groups.Id = Principals.id AND Principals.PrincipalType = 'Group') "; + $self->_Handle->ApplyLimits(\$roles_query, 1); #only return one result + + } + - } # }}} # {{{ Actually check the ACL by performing an SQL query - # $RT::Logger->debug("Now Trying $groups_query"); + # $RT::Logger->debug("Now Trying $groups_query"); my $hitcount = $self->_Handle->FetchResult($groups_query); # }}} - - # {{{ if there's a match, the right is granted + + # {{{ if there's a match, the right is granted if ($hitcount) { - $_ACL_CACHE->set( $hashkey => 1 ); + + # Cache a positive hit. + $self->_ACLCache->{"$hashkey"}{'set'} = time; + $self->_ACLCache->{"$hashkey"}{'val'} = 1; return (1); } + # }}} + # {{{ If there's no match on groups, try it on roles + else { - # Now check the roles query - $hitcount = $self->_Handle->FetchResult($roles_query); + $hitcount = $self->_Handle->FetchResult($roles_query); - if ($hitcount) { - $_ACL_CACHE->set( $hashkey => 1 ); - return (1); - } + if ($hitcount) { + + # Cache a positive hit. + $self->_ACLCache->{"$hashkey"}{'set'} = time; + $self->_ACLCache->{"$hashkey"}{'val'} = 1; + return (1); + } - # We failed to find an acl hit - $_ACL_CACHE->set( $hashkey => -1 ); - return (undef); + else { + # cache a negative hit + $self->_ACLCache->{"$hashkey"}{'set'} = time; + $self->_ACLCache->{"$hashkey"}{'val'} = -1; + + return (undef); + } + } + # }}} } # }}} @@ -517,19 +513,34 @@ sub _RolesForObject { # {{{ ACL caching +# {{{ _ACLCache -# {{{ InvalidateACLCache - -=head2 InvalidateACLCache +=head2 _ACLCache -Cleans out and reinitializes the user rights cache +# Function: _ACLCache +# Type : private instance +# Args : none +# Lvalue : hash: ACLCache +# Desc : Returns a reference to the Key cache hash =cut -sub InvalidateACLCache { - $_ACL_CACHE = Cache::Simple::TimedExpiry->new(); - $_ACL_CACHE->expire_after($RT::ACLCacheLifetime||60); +sub _ACLCache { + return(\%_ACL_KEY_CACHE); +} + +# }}} + +# {{{ _InvalidateACLCache + +=head2 _InvalidateACLCache + +Cleans out and reinitializes the user rights key cache + +=cut +sub _InvalidateACLCache { + %_ACL_KEY_CACHE = (); } # }}} |