This commit was generated by cvs2svn to compensate for changes in r4888,
[freeside.git] / rt / lib / RT / Users_Overlay.pm
index 49e4a99..19f1bea 100644 (file)
@@ -1,8 +1,8 @@
-# {{{ BEGIN BPS TAGGED BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
 #  
 # 
 # COPYRIGHT:
 #  
-# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC 
+# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC 
 #                                          <jesse@bestpractical.com>
 # 
 # (Except where explicitly superseded by other copyright notices)
 #                                          <jesse@bestpractical.com>
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -42,7 +42,8 @@
 # works based on those contributions, and sublicense and distribute
 # those contributions and any derivatives thereof.
 # 
 # works based on those contributions, and sublicense and distribute
 # those contributions and any derivatives thereof.
 # 
-# }}} END BPS TAGGED BLOCK
+# END BPS TAGGED BLOCK }}}
+
 =head1 NAME
 
   RT::Users - Collection of RT::User objects
 =head1 NAME
 
   RT::Users - Collection of RT::User objects
@@ -65,6 +66,9 @@ ok(require RT::Users);
 
 =cut
 
 
 =cut
 
+
+package RT::Users;
+
 use strict;
 no warnings qw(redefine);
 
 use strict;
 no warnings qw(redefine);
 
@@ -84,10 +88,15 @@ sub _Init {
 
     $self->{'princalias'} = $self->NewAlias('Principals');
 
 
     $self->{'princalias'} = $self->NewAlias('Principals');
 
+    # XXX: should be generalized
     $self->Join( ALIAS1 => 'main',
                  FIELD1 => 'id',
                  ALIAS2 => $self->{'princalias'},
                  FIELD2 => 'id' );
     $self->Join( ALIAS1 => 'main',
                  FIELD1 => 'id',
                  ALIAS2 => $self->{'princalias'},
                  FIELD2 => 'id' );
+    $self->Limit( ALIAS => $self->{'princalias'},
+                  FIELD => 'PrincipalType',
+                  VALUE => 'User',
+                );
 
     return (@result);
 }
 
     return (@result);
 }
@@ -98,9 +107,9 @@ sub _Init {
 
 Returns the string that represents this Users object's primary "Principals" alias.
 
 
 Returns the string that represents this Users object's primary "Principals" alias.
 
-
 =cut
 
 =cut
 
+# XXX: should be generalized
 sub PrincipalsAlias {
     my $self = shift;
     return($self->{'princalias'});
 sub PrincipalsAlias {
     my $self = shift;
     return($self->{'princalias'});
@@ -137,10 +146,11 @@ Only find items that haven\'t been disabled
 
 =cut
 
 
 =cut
 
+# XXX: should be generalized
 sub LimitToEnabled {
     my $self = shift;
 
 sub LimitToEnabled {
     my $self = shift;
 
-    $self->Limit( ALIAS    => $self->{'princalias'},
+    $self->Limit( ALIAS    => $self->PrincipalsAlias,
                   FIELD    => 'Disabled',
                   VALUE    => '0',
                   OPERATOR => '=' );
                   FIELD    => 'Disabled',
                   VALUE    => '0',
                   OPERATOR => '=' );
@@ -183,7 +193,7 @@ sub MemberOfGroup {
     my $groupalias = $self->NewAlias('CachedGroupMembers');
 
     # Join the principal to the groups table
     my $groupalias = $self->NewAlias('CachedGroupMembers');
 
     # Join the principal to the groups table
-    $self->Join( ALIAS1 => $self->{'princalias'},
+    $self->Join( ALIAS1 => $self->PrincipalsAlias,
                  FIELD1 => 'id',
                  ALIAS2 => $groupalias,
                  FIELD2 => 'MemberId' );
                  FIELD1 => 'id',
                  ALIAS2 => $groupalias,
                  FIELD2 => 'MemberId' );
@@ -219,7 +229,7 @@ sub LimitToPrivileged {
 
 # {{{ WhoHaveRight
 
 
 # {{{ WhoHaveRight
 
-=head2 WhoHaveRight { Right => 'name', Object => $rt_object , IncludeSuperusers => undef, IncludeSubgroupMembers => undef, IncludeSystemRights => undef }
+=head2 WhoHaveRight { Right => 'name', Object => $rt_object , IncludeSuperusers => undef, IncludeSubgroupMembers => undef, IncludeSystemRights => undef, EquivObjects => [ ] }
 
 =begin testing
 
 
 =begin testing
 
@@ -228,99 +238,382 @@ $users->WhoHaveRight(Object =>$RT::System, Right =>'SuperUser');
 ok($users->Count == 1, "There is one privileged superuser - Found ". $users->Count );
 # TODO: this wants more testing
 
 ok($users->Count == 1, "There is one privileged superuser - Found ". $users->Count );
 # TODO: this wants more testing
 
+my $RTxUser = RT::User->new($RT::SystemUser);
+($id, $msg) = $RTxUser->Create( Name => 'RTxUser', Comments => "RTx extension user", Privileged => 1);
+ok ($id,$msg);
 
 
-=end testing
+my $group = RT::Group->new($RT::SystemUser);
+$group->LoadACLEquivalenceGroup($RTxUser->PrincipalObj);
+
+my $RTxSysObj = {};
+bless $RTxSysObj, 'RTx::System';
+*RTx::System::Id = sub { 1; };
+*RTx::System::id = *RTx::System::Id;
+my $ace = RT::Record->new($RT::SystemUser);
+$ace->Table('ACL');
+$ace->_BuildTableAttributes unless ($_TABLE_ATTR->{ref($self)});
+($id, $msg) = $ace->Create( PrincipalId => $group->id, PrincipalType => 'Group', RightName => 'RTxUserRight', ObjectType => 'RTx::System', ObjectId  => 1 );
+ok ($id, "ACL for RTxSysObj created");
+
+my $RTxObj = {};
+bless $RTxObj, 'RTx::System::Record';
+*RTx::System::Record::Id = sub { 4; };
+*RTx::System::Record::id = *RTx::System::Record::Id;
+
+$users = RT::Users->new($RT::SystemUser);
+$users->WhoHaveRight(Right => 'RTxUserRight', Object => $RTxSysObj);
+is($users->Count, 1, "RTxUserRight found for RTxSysObj");
+
+$users = RT::Users->new($RT::SystemUser);
+$users->WhoHaveRight(Right => 'RTxUserRight', Object => $RTxObj);
+is($users->Count, 0, "RTxUserRight not found for RTxObj");
 
 
+$users = RT::Users->new($RT::SystemUser);
+$users->WhoHaveRight(Right => 'RTxUserRight', Object => $RTxObj, EquivObjects => [ $RTxSysObj ]);
+is($users->Count, 1, "RTxUserRight found for RTxObj using EquivObjects");
+
+$ace = RT::Record->new($RT::SystemUser);
+$ace->Table('ACL');
+$ace->_BuildTableAttributes unless ($_TABLE_ATTR->{ref($self)});
+($id, $msg) = $ace->Create( PrincipalId => $group->id, PrincipalType => 'Group', RightName => 'RTxUserRight', ObjectType => 'RTx::System::Record', ObjectId => 5 );
+ok ($id, "ACL for RTxObj created");
+
+my $RTxObj2 = {};
+bless $RTxObj2, 'RTx::System::Record';
+*RTx::System::Record::Id = sub { 5; };
+*RTx::System::Record::id = sub { 5; };
+
+$users = RT::Users->new($RT::SystemUser);
+$users->WhoHaveRight(Right => 'RTxUserRight', Object => $RTxObj2);
+is($users->Count, 1, "RTxUserRight found for RTxObj2");
+
+$users = RT::Users->new($RT::SystemUser);
+$users->WhoHaveRight(Right => 'RTxUserRight', Object => $RTxObj2, EquivObjects => [ $RTxSysObj ]);
+is($users->Count, 1, "RTxUserRight found for RTxObj2");
+
+
+=end testing
 
 find all users who the right Right for this group, either individually
 or as members of groups
 
 
 find all users who the right Right for this group, either individually
 or as members of groups
 
+If passed a queue object, with no id, it will find users who have that right for _any_ queue
 
 
+=cut
 
 
+# XXX: should be generalized
+sub _JoinGroupMembers
+{
+    my $self = shift;
+    my %args = (
+        IncludeSubgroupMembers => 1,
+        @_
+    );
+
+    my $principals = $self->PrincipalsAlias;
+
+    # The cachedgroupmembers table is used for unrolling group memberships
+    # to allow fast lookups. if we bind to CachedGroupMembers, we'll find
+    # all members of groups recursively. if we don't we'll find only 'direct'
+    # members of the group in question
+    my $group_members;
+    if ( $args{'IncludeSubgroupMembers'} ) {
+        $group_members = $self->NewAlias('CachedGroupMembers');
+    }
+    else {
+        $group_members = $self->NewAlias('GroupMembers');
+    }
 
 
+    $self->Join(
+        ALIAS1 => $group_members,
+        FIELD1 => 'MemberId',
+        ALIAS2 => $principals,
+        FIELD2 => 'id'
+    );
 
 
-=cut
+    return $group_members;
+}
 
 
-sub WhoHaveRight {
+# XXX: should be generalized
+sub _JoinGroups
+{
     my $self = shift;
     my $self = shift;
-    my %args = ( Right                  => undef,
-                 Object =>              => undef,
-                 IncludeSystemRights    => undef,
-                 IncludeSuperusers      => undef,
-                 IncludeSubgroupMembers => 1,
-                 @_ );
+    my %args = (@_);
+
+    my $group_members = $self->_JoinGroupMembers( %args );
+    my $groups = $self->NewAlias('Groups');
+    $self->Join(
+        ALIAS1 => $groups,
+        FIELD1 => 'id',
+        ALIAS2 => $group_members,
+        FIELD2 => 'GroupId'
+    );
+
+    return $groups;
+}
 
 
-    if (defined $args{'ObjectType'} || defined $args{'ObjectId'}) {
-        $RT::Logger->crit("$self WhoHaveRight called with the Obsolete ObjectId/ObjectType API");
-        return(undef);
+# XXX: should be generalized
+sub _JoinACL
+{
+    my $self = shift;
+    my %args = (
+        Right                  => undef,
+        IncludeSuperusers      => undef,
+        @_,
+    );
+
+    my $acl = $self->NewAlias('ACL');
+    $self->Limit(
+        ALIAS    => $acl,
+        FIELD    => 'RightName',
+        OPERATOR => ( $args{Right} ? '=' : 'IS NOT' ),
+        VALUE => $args{Right} || 'NULL',
+        ENTRYAGGREGATOR => 'OR'
+    );
+    if ( $args{'IncludeSuperusers'} and $args{'Right'} ) {
+        $self->Limit(
+            ALIAS           => $acl,
+            FIELD           => 'RightName',
+            OPERATOR        => '=',
+            VALUE           => 'SuperUser',
+            ENTRYAGGREGATOR => 'OR'
+        );
     }
     }
-        my @privgroups;
-        my $Groups = RT::Groups->new($RT::SystemUser);
-        $Groups->WithRight(Right=> $args{'Right'},
-                     Object => $args{'Object'},
-                     IncludeSystemRights => $args{'IncludeSystemRights'},
-                     IncludeSuperusers => $args{'IncludeSuperusers'});
-        while (my $Group = $Groups->Next()) {
-                push @privgroups, $Group->Id();
+    return $acl;
+}
+
+# XXX: should be generalized
+sub _GetEquivObjects
+{
+    my $self = shift;
+    my %args = (
+        Object                 => undef,
+        IncludeSystemRights    => undef,
+        EquivObjects           => [ ],
+        @_
+    );
+    return () unless $args{'Object'};
+
+    my @objects = ($args{'Object'});
+    if ( UNIVERSAL::isa( $args{'Object'}, 'RT::Ticket' ) ) {
+        # If we're looking at ticket rights, we also want to look at the associated queue rights.
+        # 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.
+
+        # XXX: This should be abstracted into object itself
+        if( $args{'Object'}->id ) {
+            push @objects, $args{'Object'}->QueueObj;
+        } else {
+            push @objects, 'RT::Queue';
         }
         }
+    }
 
 
+    if( $args{'IncludeSystemRights'} ) {
+        push @objects, 'RT::System';
+    }
+    push @objects, @{ $args{'EquivObjects'} };
+    return grep $_, @objects;
+}
 
 
-    if (@privgroups) {
-        $self->WhoBelongToGroups(Groups => \@privgroups,
-                                 IncludeSubgroupMembers => $args{'IncludeSubgroupMembers'});
+# XXX: should be generalized
+sub WhoHaveRight {
+    my $self = shift;
+    my %args = (
+        Right                  => undef,
+        Object                 => undef,
+        IncludeSystemRights    => undef,
+        IncludeSuperusers      => undef,
+        IncludeSubgroupMembers => 1,
+        EquivObjects           => [ ],
+        @_
+    );
+
+    if ( defined $args{'ObjectType'} || defined $args{'ObjectId'} ) {
+        $RT::Logger->crit( "WhoHaveRight called with the Obsolete ObjectId/ObjectType API");
+        return (undef);
     }
     }
-    else {
-       # We don't have any group that matches -- make it impossible.
-       $self->Limit( FIELD => 'Id', VALUE => 'IS', OPERATOR => 'NULL' );
+
+    my $from_role = $self->Clone;
+    $from_role->WhoHaveRoleRight( %args );
+
+    my $from_group = $self->Clone;
+    $from_group->WhoHaveGroupRight( %args );
+
+    #XXX: DIRTY HACK
+    use DBIx::SearchBuilder::Union;
+    my $union = new DBIx::SearchBuilder::Union;
+    $union->add($from_role);
+    $union->add($from_group);
+    %$self = %$union;
+    bless $self, ref($union);
+
+    return;
+}
+# }}}
+
+# XXX: should be generalized
+sub WhoHaveRoleRight
+{
+    my $self = shift;
+    my %args = (
+        Right                  => undef,
+        Object                 => undef,
+        IncludeSystemRights    => undef,
+        IncludeSuperusers      => undef,
+        IncludeSubgroupMembers => 1,
+        EquivObjects           => [ ],
+        @_
+    );
+
+    my $groups = $self->_JoinGroups( %args );
+    my $acl = $self->_JoinACL( %args );
+
+    my ($check_roles, $check_objects) = ('','');
+    
+    my @objects = $self->_GetEquivObjects( %args );
+    if ( @objects ) {
+        my @role_clauses;
+        my @object_clauses;
+        foreach my $obj ( @objects ) {
+            my $type = ref($obj)? ref($obj): $obj;
+            my $id;
+            $id = $obj->id if ref($obj) && UNIVERSAL::can($obj, 'id') && $obj->id;
+
+            my $role_clause = "$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.
+            $role_clause   .= " AND $groups.Instance = '$id'" if $id;
+            push @role_clauses, "($role_clause)";
+
+            my $object_clause = "$acl.ObjectType = '$type'";
+            $object_clause   .= " AND $acl.ObjectId = $id" if $id;
+            push @object_clauses, "($object_clause)";
+        }
+
+        $check_roles .= join ' OR ', @role_clauses;
+        $check_objects = join ' OR ', @object_clauses;
+    } else {
+        if( !$args{'IncludeSystemRights'} ) {
+            $check_objects = "($acl.ObjectType != 'RT::System')";
+        }
     }
     }
+
+    $self->_AddSubClause( "WhichObject", "($check_objects)" );
+    $self->_AddSubClause( "WhichRole", "($check_roles)" );
+
+    $self->Limit( ALIAS => $acl,
+                  FIELD => 'PrincipalType',
+                  VALUE => "$groups.Type",
+                  QUOTEVALUE => 0,
+                );
+
+    # no system user
+    $self->Limit( ALIAS => $self->PrincipalsAlias,
+                  FIELD => 'id',
+                  OPERATOR => '!=',
+                  VALUE => $RT::SystemUser->id
+                );
+    return;
 }
 
 }
 
-# }}}
+# XXX: should be generalized
+sub _JoinGroupMembersForGroupRights
+{
+    my $self = shift;
+    my %args = (@_);
+    my $group_members = $self->_JoinGroupMembers( %args );
+    $self->Limit( ALIAS => $args{'ACLAlias'},
+                  FIELD => 'PrincipalId',
+                  VALUE => "$group_members.GroupId",
+                  QUOTEVALUE => 0,
+                );
+}
 
 
-# {{{ WhoBelongToGroups 
+# XXX: should be generalized
+sub WhoHaveGroupRight
+{
+    my $self = shift;
+    my %args = (
+        Right                  => undef,
+        Object                 => undef,
+        IncludeSystemRights    => undef,
+        IncludeSuperusers      => undef,
+        IncludeSubgroupMembers => 1,
+        EquivObjects           => [ ],
+        @_
+    );
+
+    # Find only rows where the right granted is
+    # the one we're looking up or _possibly_ superuser
+    my $acl = $self->_JoinACL( %args );
+
+    my ($check_objects) = ('');
+    my @objects = $self->_GetEquivObjects( %args );
+
+    if ( @objects ) {
+        my @object_clauses;
+        foreach my $obj ( @objects ) {
+            my $type = ref($obj)? ref($obj): $obj;
+            my $id;
+            $id = $obj->id if ref($obj) && UNIVERSAL::can($obj, 'id') && $obj->id;
+
+            my $object_clause = "$acl.ObjectType = '$type'";
+            $object_clause   .= " AND $acl.ObjectId   = $id" if $id;
+            push @object_clauses, "($object_clause)";
+        }
+
+        $check_objects = join ' OR ', @object_clauses;
+    } else {
+        if( !$args{'IncludeSystemRights'} ) {
+            $check_objects = "($acl.ObjectType != 'RT::System')";
+        }
+    }
+    $self->_AddSubClause( "WhichObject", "($check_objects)" );
+    
+    $self->_JoinGroupMembersForGroupRights( %args, ACLAlias => $acl );
+    # Find only members of groups that have the right.
+    $self->Limit( ALIAS => $acl,
+                  FIELD => 'PrincipalType',
+                  VALUE => 'Group',
+                );
+    
+    # no system user
+    $self->Limit( ALIAS => $self->PrincipalsAlias,
+                  FIELD => 'id',
+                  OPERATOR => '!=',
+                  VALUE => $RT::SystemUser->id
+                );
+    return;
+}
+
+# {{{ WhoBelongToGroups
 
 =head2 WhoBelongToGroups { Groups => ARRAYREF, IncludeSubgroupMembers => 1 }
 
 =cut
 
 
 =head2 WhoBelongToGroups { Groups => ARRAYREF, IncludeSubgroupMembers => 1 }
 
 =cut
 
+# XXX: should be generalized
 sub WhoBelongToGroups {
     my $self = shift;
     my %args = ( Groups                 => undef,
                  IncludeSubgroupMembers => 1,
                  @_ );
 
 sub WhoBelongToGroups {
     my $self = shift;
     my %args = ( Groups                 => undef,
                  IncludeSubgroupMembers => 1,
                  @_ );
 
-    # Unprivileged users can't be granted real system rights. 
+    # Unprivileged users can't be granted real system rights.
     # is this really the right thing to be saying?
     $self->LimitToPrivileged();
 
     # is this really the right thing to be saying?
     $self->LimitToPrivileged();
 
-    my $userprinc  = $self->{'princalias'};
-    my $cgm;
-
-    # The cachedgroupmembers table is used for unrolling group memberships to allow fast lookups 
-    # if we bind to CachedGroupMembers, we'll find all members of groups recursively.
-    # if we don't we'll find only 'direct' members of the group in question
+    my $group_members = $self->_JoinGroupMembers( %args );
 
 
-    if ( $args{'IncludeSubgroupMembers'} ) {
-        $cgm = $self->NewAlias('CachedGroupMembers');
-    }
-    else {
-        $cgm = $self->NewAlias('GroupMembers');
-    }
-
-    # {{{ Tie the users we're returning ($userprinc) to the groups that have rights granted to them ($groupprinc)
-    $self->Join( ALIAS1 => $cgm, FIELD1 => 'MemberId',
-                 ALIAS2 => $userprinc, FIELD2 => 'id' );
-    # }}} 
-
- #   my $and_check_groups = "($cgm.GroupId = NULL";
     foreach my $groupid (@{$args{'Groups'}}) {
     foreach my $groupid (@{$args{'Groups'}}) {
-        $self->Limit(ALIAS => $cgm, FIELD => 'GroupId', VALUE => $groupid, QUOTEVALUE => 0, ENTRYAGGREGATOR=> 'OR')
-
-        #$and_check_groups .= " OR $cgm.GroupId = $groupid";
+        $self->Limit( ALIAS           => $group_members,
+                      FIELD           => 'GroupId',
+                      VALUE           => $groupid,
+                      QUOTEVALUE      => 0,
+                      ENTRYAGGREGATOR => 'OR',
+                    );
     }
     }
-    #$and_check_groups .= ")";
-
-    #$self->_AddSubClause("WhichGroup", $and_check_groups);
 }
 # }}}
 
 }
 # }}}