summaryrefslogtreecommitdiff
path: root/rt/lib/RT/Principal_Overlay.pm
diff options
context:
space:
mode:
Diffstat (limited to 'rt/lib/RT/Principal_Overlay.pm')
-rw-r--r--rt/lib/RT/Principal_Overlay.pm367
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 = ();
}
# }}}