#
# COPYRIGHT:
#
-# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC
# <jesse@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
-# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/copyleft/gpl.html.
#
#
# CONTRIBUTION SUBMISSION POLICY:
Object => an RT style object (->id will get its id)
-
-
Returns 1 if a matching ACE was found.
Returns undef if no ACE was found.
Right => undef,
Object => undef,
EquivObjects => undef,
- @_
+ @_,
);
+ unless ( $args{'Right'} ) {
+ $RT::Logger->crit("HasRight called without a right");
+ return (undef);
+ }
+
+ $args{'EquivObjects'} = [ @{ $args{'EquivObjects'} } ]
+ if $args{'EquivObjects'};
+
if ( $self->Disabled ) {
- $RT::Logger->err( "Disabled User: "
+ $RT::Logger->error( "Disabled User #"
. $self->id
. " failed access check for "
. $args{'Right'} );
return (undef);
}
- if ( !defined $args{'Right'} ) {
- $RT::Logger->crit("HasRight called without a right");
- return (undef);
- }
-
if ( defined( $args{'Object'} )
&& UNIVERSAL::can( $args{'Object'}, 'id' )
- && $args{'Object'}->id )
- {
- push( @{ $args{'EquivObjects'} }, $args{Object} );
+ && $args{'Object'}->id ) {
+
+ push @{ $args{'EquivObjects'} }, $args{'Object'};
}
else {
- $RT::Logger->crit("$self HasRight called with no valid object");
+ $RT::Logger->crit("HasRight called with no valid object");
return (undef);
}
# If this object is a ticket, we care about ticket roles and queue roles
- if ( ( ref( $args{'Object'} ) eq 'RT::Ticket' ) && $args{'Object'}->Id ) {
+ if ( UNIVERSAL::isa( $args{'Object'} => 'RT::Ticket' ) ) {
-# 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 );
+ # 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.
+ unshift @{ $args{'EquivObjects'} }, $args{'Object'}->QueueObj;
}
- # {{{ If we've cached a win or loss for this lookup say so
+ unshift @{ $args{'EquivObjects'} }, $RT::System
+ unless $self->can('_IsOverrideGlobalACL')
+ && $self->_IsOverrideGlobalACL( $args{'Object'} );
- # {{{ 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
- );
- };
-
- # }}}
-
- # {{{ if we've cached a positive result for this query, return 1
-
- my $cached_answer = $_ACL_CACHE->fetch($hashkey);
-
- # Returns undef on cache miss
- if ( defined $cached_answer ) {
- if ( $cached_answer == 1 ) {
- return (1);
- }
- elsif ( $cached_answer == -1 ) {
- return (0);
- }
- }
- my ( $or_look_at_object_rights, $or_check_roles );
- my $right = $args{'Right'};
-
- # {{{ 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} );
+ # {{{ If we've cached a win or loss for this lookup say so
- 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')";
+ # Construct a hashkeys to cache decisions:
+ # 1) full_hashkey - key for any result and for full combination of uid, right and objects
+ # 2) short_hashkey - one key for each object to store positive results only, it applies
+ # only to direct group rights and partly to role rights
+ my $self_id = $self->id;
+ my $full_hashkey = join ";:;", $self_id, $args{'Right'};
+ foreach ( @{ $args{'EquivObjects'} } ) {
+ my $ref_id = _ReferenceId($_);
+ $full_hashkey .= ";:;$ref_id";
+
+ my $short_hashkey = join ";:;", $self_id, $args{'Right'}, $ref_id;
+ my $cached_answer = $_ACL_CACHE->fetch($short_hashkey);
+ return $cached_answer > 0 if defined $cached_answer;
}
- # }}}
-
- # {{{ 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 $cached_answer = $_ACL_CACHE->fetch($full_hashkey);
+ return $cached_answer > 0 if defined $cached_answer;
+ }
- # 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 . "' "
- .
+ my ($hitcount, $via_obj) = $self->_HasRight( %args );
- # Make sure the rights apply to the entire system or to the object in question
- "AND ( " . join( ' OR ', @look_at_objects ) . ") ";
+ $_ACL_CACHE->set( $full_hashkey => $hitcount? 1: -1 );
+ $_ACL_CACHE->set( "$self_id;:;$args{'Right'};:;$via_obj" => 1 )
+ if $via_obj && $hitcount;
-# The groups query does the query based on group membership and individual user rights
+ return ($hitcount);
+}
- my $groups_query = $query_base .
+=head2 _HasRight
-# 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'))"
- .
+Low level HasRight implementation, use HasRight method instead.
- " ) ";
- $self->_Handle->ApplyLimits( \$groups_query, 1 ); #only return one result
+=cut
- my @roles;
- foreach my $object ( @{ $args{'EquivObjects'} } ) {
- push( @roles, $self->_RolesForObject( ref($object), $object->id ) );
+sub _HasRight
+{
+ my $self = shift;
+ {
+ my ($hit, @other) = $self->_HasGroupRight( @_ );
+ return ($hit, @other) if $hit;
}
-
- # 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
-
+ {
+ my ($hit, @other) = $self->_HasRoleRight( @_ );
+ return ($hit, @other) if $hit;
}
+ return (0);
+}
- # }}}
+# this method handles role rights partly in situations
+# where user plays role X on an object and as well the right is
+# assigned to this role X of the object, for example right CommentOnTicket
+# is granted to Cc role of a queue and user is in cc list of the queue
+sub _HasGroupRight
+{
+ my $self = shift;
+ my %args = (
+ Right => undef,
+ EquivObjects => [],
+ @_
+ );
+ my $right = $args{'Right'};
- # {{{ Actually check the ACL by performing an SQL query
- # $RT::Logger->debug("Now Trying $groups_query");
- my $hitcount = $self->_Handle->FetchResult($groups_query);
+ my $query =
+ "SELECT ACL.id, ACL.ObjectType, ACL.ObjectId " .
+ "FROM ACL, Principals, CachedGroupMembers WHERE " .
- # }}}
+ # Only find superuser or rights with the name $right
+ "(ACL.RightName = 'SuperUser' OR ACL.RightName = '$right') "
- # {{{ if there's a match, the right is granted
- if ($hitcount) {
- $_ACL_CACHE->set( $hashkey => 1 );
- return (1);
- }
+ # Never find disabled groups.
+ . "AND Principals.id = ACL.PrincipalId "
+ . "AND Principals.PrincipalType = 'Group' "
+ . "AND Principals.Disabled = 0 "
+
+ # 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 CachedGroupMembers.GroupId = ACL.PrincipalId "
+ . "AND CachedGroupMembers.GroupId = Principals.id "
+ . "AND CachedGroupMembers.MemberId = ". $self->Id ." "
+ . "AND CachedGroupMembers.Disabled = 0 ";
+
+ my @clauses;
+ foreach my $obj ( @{ $args{'EquivObjects'} } ) {
+ my $type = ref( $obj ) || $obj;
+ my $clause = "ACL.ObjectType = '$type'";
- # Now check the roles query
- $hitcount = $self->_Handle->FetchResult($roles_query);
+ if ( ref($obj) && UNIVERSAL::can($obj, 'id') && $obj->id ) {
+ $clause .= " AND ACL.ObjectId = ". $obj->id;
+ }
- if ($hitcount) {
- $_ACL_CACHE->set( $hashkey => 1 );
- return (1);
+ push @clauses, "($clause)";
+ }
+ if ( @clauses ) {
+ $query .= " AND (". join( ' OR ', @clauses ) .")";
}
- # We failed to find an acl hit
- $_ACL_CACHE->set( $hashkey => -1 );
- return (undef);
-}
-
-# }}}
-
-# {{{ _RolesForObject
+ $self->_Handle->ApplyLimits( \$query, 1 );
+ my ($hit, $obj, $id) = $self->_Handle->FetchResult( $query );
+ return (0) unless $hit;
+ $obj .= "-$id" if $id;
+ return (1, $obj);
+}
+sub _HasRoleRight
+{
+ my $self = shift;
+ my %args = (
+ Right => undef,
+ EquivObjects => [],
+ @_
+ );
+ my $right = $args{'Right'};
-=head2 _RolesForObject( $object_type, $object_id)
+ my $query =
+ "SELECT ACL.id " .
+ "FROM ACL, Groups, Principals, CachedGroupMembers WHERE " .
-Returns an SQL clause finding role groups for Objects
+ # Only find superuser or rights with the name $right
+ "(ACL.RightName = 'SuperUser' OR ACL.RightName = '$right') "
-=cut
+ # Never find disabled things
+ . "AND Principals.Disabled = 0 "
+ . "AND CachedGroupMembers.Disabled = 0 "
+ # We always grant rights to Groups
+ . "AND Principals.id = Groups.id "
+ . "AND Principals.PrincipalType = 'Group' "
-sub _RolesForObject {
- my $self = shift;
- my $type = shift;
- my $id = shift;
+ # 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 ." "
+ . "AND ACL.PrincipalType = Groups.Type ";
- unless ($id) {
- $id = '0';
- }
+ my (@object_clauses);
+ foreach my $obj ( @{ $args{'EquivObjects'} } ) {
+ my $type = ref($obj)? ref($obj): $obj;
+ my $id;
+ $id = $obj->id if ref($obj) && UNIVERSAL::can($obj, 'id') && $obj->id;
- # This should never be true.
- unless ($id =~ /^\d+$/) {
- $RT::Logger->crit("RT::Prinicipal::_RolesForObject called with type $type and a non-integer id: '$id'");
- $id = "'$id'";
- }
+ my $object_clause = "ACL.ObjectType = '$type'";
+ $object_clause .= " AND ACL.ObjectId = $id" if $id;
+ push @object_clauses, "($object_clause)";
+ }
+ # find ACLs that are related to our objects only
+ $query .= " AND (". join( ' OR ', @object_clauses ) .")";
- my $clause = "(Groups.Domain = '".$type."-Role' AND Groups.Instance = $id) ";
+ # because of mysql bug in versions up to 5.0.45 we do one query per object
+ # each query should be faster on any DB as it uses indexes more effective
+ foreach my $obj ( @{ $args{'EquivObjects'} } ) {
+ my $type = ref($obj)? ref($obj): $obj;
+ my $id;
+ $id = $obj->id if ref($obj) && UNIVERSAL::can($obj, 'id') && $obj->id;
+
+ my $tmp = $query;
+ $tmp .= " AND Groups.Domain = '$type-Role'";
+ # XXX: Groups.Instance is VARCHAR in DB, we should quote value
+ # if we want mysql 4.0 use indexes here. we MUST convert that
+ # field to integer and drop this quotes.
+ $tmp .= " AND Groups.Instance = '$id'" if $id;
+
+ $self->_Handle->ApplyLimits( \$tmp, 1 );
+ my ($hit) = $self->_Handle->FetchResult( $tmp );
+ return (1) if $hit;
+ }
- return($clause);
+ return 0;
}
# }}}
# just return the value for non-objects
return $scalar unless UNIVERSAL::can($scalar, 'id');
+ return ref($scalar) unless $scalar->id;
+
# an object -- return the class and id
return(ref($scalar)."-". $scalar->id);
}