[Bps-public-commit] rt-extension-rightsdebugger branch, master, updated. 3123dc04c2e43da999cad7c5d35e5e5ead2c12e8

Shawn Moore shawn at bestpractical.com
Thu Mar 9 16:08:13 EST 2017


The branch, master has been updated
       via  3123dc04c2e43da999cad7c5d35e5e5ead2c12e8 (commit)
       via  3a95a7bc55181579ff95b149f0797fa0e426f2af (commit)
       via  1b2ee20cfd6994a33f4c1f126843fd1b22c426ad (commit)
       via  4ee4f22278f6f9ce3d1207a8f52da01d61bc4e01 (commit)
       via  842d2bf069d95902965a5e3dc18212af37c1d510 (commit)
       via  eb32dac3db180b25daab7f243f7632b523c75ec9 (commit)
       via  5abaaa6f0fb6367db29ffc29cb2a3c6ca6ab0606 (commit)
       via  4af7f5b9977105a7c2126069f13053d8141f5371 (commit)
       via  4bb9ce8bf6267d7be6e35fb0894d0616c37b9ebc (commit)
       via  d450471325712482c32beb8eedd944aad6161b8a (commit)
      from  6c0d9a50383758b4ed054d6608ff678cd1716159 (commit)

Summary of changes:
 html/Admin/RightsDebugger/index.html |  13 +-
 lib/RT/Extension/RightsDebugger.pm   | 315 +++++++++++++++++++++++++++++++----
 2 files changed, 298 insertions(+), 30 deletions(-)

- Log -----------------------------------------------------------------
commit d450471325712482c32beb8eedd944aad6161b8a
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Mar 9 18:55:51 2017 +0000

    Factor out a $PageCount magic number

diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index b08a133..6d12a70 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -8,6 +8,7 @@ RT->AddStyleSheets("rights-debugger.css");
 RT->AddJavaScript("rights-debugger.js");
 RT->AddJavaScript("handlebars-4.0.6.min.js");
 
+my $PageLimit = 100;
 
 $RT::Interface::Web::WHITELISTED_COMPONENT_ARGS{'/Admin/RightsDebugger/index.html'} = ['Principal', 'Object', 'Right'];
 
@@ -306,7 +307,7 @@ sub Search {
 
     $ACL->UnLimit unless $has_search;
 
-    $ACL->RowsPerPage(100);
+    $ACL->RowsPerPage($PageLimit);
 
     my $continueAfter;
 

commit 4bb9ce8bf6267d7be6e35fb0894d0616c37b9ebc
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Mar 9 18:56:07 2017 +0000

    Linkify system and queue role ACL management

diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index 6d12a70..96ba277 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -517,8 +517,18 @@ sub URLForRecord {
         return RT->Config->Get('WebURL') . 'Admin/Users/Modify.html?id=' . $id;
     }
     elsif ($record->isa('RT::Group')) {
-        return undef unless $record->Domain eq 'UserDefined';
-        return RT->Config->Get('WebURL') . 'Admin/Groups/Modify.html?id=' . $id;
+        if ($record->Domain eq 'UserDefined') {
+            return RT->Config->Get('WebURL') . 'Admin/Groups/Modify.html?id=' . $id;
+        }
+        elsif ($record->Domain eq 'RT::System-Role') {
+            return RT->Config->Get('WebURL') . 'Admin/Global/GroupRights.html#acl-' . $id;
+        }
+        elsif ($record->Domain eq 'RT::Queue-Role') {
+            return RT->Config->Get('WebURL') . 'Admin/Queues/GroupRights.html?id=' . $record->Instance . '#acl-' . $id;
+        }
+        else {
+            return undef;
+        }
     }
     elsif ($record->isa('RT::CustomField')) {
         return RT->Config->Get('WebURL') . 'Admin/CustomFields/Modify.html?id=' . $id;

commit 4af7f5b9977105a7c2126069f13053d8141f5371
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Mar 9 20:30:53 2017 +0000

    Support for extra detail, and separate links for detail & extra detail

diff --git a/html/Admin/RightsDebugger/index.html b/html/Admin/RightsDebugger/index.html
index e9ab702..21d0b21 100644
--- a/html/Admin/RightsDebugger/index.html
+++ b/html/Admin/RightsDebugger/index.html
@@ -24,7 +24,18 @@
     </span>
 
     <span class="detail">
-      {{{detail_highlighted}}}
+      {{#if detail_url}}
+        <a target="_blank" href="{{detail_url}}">{{{detail_highlighted}}}</a>
+      {{else}}
+        {{{detail_highlighted}}}
+      {{/if}}
+
+      {{#if detail_extra_url}}
+        <a target="_blank" href="{{detail_extra_url}}">{{detail_extra}}</a>
+      {{else}}
+        {{detail_extra}}
+      {{/if}}
+
       {{#if disabled}}
         (disabled)
       {{/if}}

commit 5abaaa6f0fb6367db29ffc29cb2a3c6ca6ab0606
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Mar 9 20:31:28 2017 +0000

    Better fail to load messages

diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index 96ba277..bc6d285 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -116,11 +116,13 @@ sub PrincipalForSpec {
         }
 
         return $group->PrincipalObj if $group->Id;
+        return (0, "Unable to load group $identifier");
     }
     elsif ($type =~ /^(u|user)$/i) {
         my $user = RT::User->new($self->CurrentUser);
-        $user->Load($identifier);
+        my ($ok, $msg) = $user->Load($identifier);
         return $user->PrincipalObj if $user->Id;
+        return (0, "Unable to load user $identifier");
     }
     else {
         RT->Logger->debug("Unexpected type '$type'");
@@ -166,6 +168,8 @@ sub ObjectForSpec {
 
     $record->Load($identifier);
     return $record if $record->Id;
+    my $class = ref($record); $class =~ s/^RT:://;
+    return (0, "Unable to load $class '$identifier'");
 
     return undef;
 }
@@ -207,9 +211,9 @@ sub Search {
                 \s*
             $
         }xi) {
-            my $record = $self->ObjectForSpec($type, $identifier);
+            my ($record, $msg) = $self->ObjectForSpec($type, $identifier);
             if (!$record) {
-                return { error => 'Unable to find row' };
+                return { error => $msg || 'Unable to find row' };
             }
 
             $has_search = 1;
@@ -235,9 +239,9 @@ sub Search {
                 \s*
             $
         }xi) {
-            my $principal = $self->PrincipalForSpec($type, $identifier);
+            my ($principal, $msg) = $self->PrincipalForSpec($type, $identifier);
             if (!$principal) {
-                return { error => 'Unable to find row' };
+                return { error => $msg || 'Unable to find row' };
             }
 
             $has_search = 1;

commit eb32dac3db180b25daab7f243f7632b523c75ec9
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Mar 9 20:31:59 2017 +0000

    Linkify catalog roles

diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index bc6d285..d066ab2 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -530,6 +530,9 @@ sub URLForRecord {
         elsif ($record->Domain eq 'RT::Queue-Role') {
             return RT->Config->Get('WebURL') . 'Admin/Queues/GroupRights.html?id=' . $record->Instance . '#acl-' . $id;
         }
+        elsif ($record->Domain eq 'RT::Catalog-Role') {
+            return RT->Config->Get('WebURL') . 'Admin/Assets/Catalogs/GroupRights.html?id=' . $record->Instance . '#acl-' . $id;
+        }
         else {
             return undef;
         }

commit 842d2bf069d95902965a5e3dc18212af37c1d510
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Mar 9 20:35:00 2017 +0000

    EscapeURI

diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index d066ab2..e4e79a6 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -22,6 +22,12 @@ sub _EscapeHTML {
     return $s;
 }
 
+sub _EscapeURI {
+    my $s = shift;
+    RT::Interface::Web::EscapeURI(\$s);
+    return $s;
+}
+
 # used to convert a search term (e.g. "root") into a regex for highlighting
 # in the UI. potentially useful hook point for implementing say, "ro*t"
 sub RegexifyTermForHighlight {

commit 4ee4f22278f6f9ce3d1207a8f52da01d61bc4e01
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Mar 9 20:41:29 2017 +0000

    Improve generated query
    
    Using subclauses means we have better control over how the different clauses combine

diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index e4e79a6..b227dd0 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -205,6 +205,27 @@ sub Search {
         object    => undef,
     );
 
+    if ($args{right}) {
+        $has_search = 1;
+        for my $term (split ' ', $args{right}) {
+            $ACL->Limit(
+                FIELD           => 'RightName',
+                OPERATOR        => 'LIKE',
+                VALUE           => $term,
+                CASESENSITIVE   => 0,
+                ENTRYAGGREGATOR => 'OR',
+            );
+        }
+        $ACL->Limit(
+            FIELD           => 'RightName',
+            OPERATOR        => '=',
+            VALUE           => 'SuperUser',
+            ENTRYAGGREGATOR => 'OR',
+        );
+    }
+
+    my $search_paren;
+
     if ($args{object}) {
         if (my ($type, $identifier) = $args{object} =~ m{
             ^
@@ -228,7 +249,22 @@ sub Search {
             $primary_records{object} = $record;
 
             for my $obj ($record, $record->ACLEquivalenceObjects, RT->System) {
-                $ACL->LimitToObject($obj);
+                $search_paren ||= do { $ACL->_OpenParen('search'); 1 };
+                $ACL->Limit(
+                    SUBCLAUSE       => 'search',
+                    FIELD           => 'ObjectType',
+                    OPERATOR        => '=',
+                    VALUE           => ref($obj),
+                    ENTRYAGGREGATOR => 'OR',
+                );
+                $ACL->Limit(
+                    SUBCLAUSE       => 'search',
+                    FIELD           => 'ObjectId',
+                    OPERATOR        => '=',
+                    VALUE           => $obj->Id,
+                    QUOTEVALUE      => 0,
+                    ENTRYAGGREGATOR => 'AND',
+                );
             }
         }
     }
@@ -268,45 +304,45 @@ sub Search {
                 TABLE2 => 'CachedGroupMembers',
                 FIELD2 => 'GroupId',
             );
+            $search_paren ||= do { $ACL->_OpenParen('search'); 1 };
             $ACL->Limit(
+                SUBCLAUSE => 'search',
                 ALIAS => $cgm_alias,
                 FIELD => 'Disabled',
+                QUOTEVALUE => 0,
                 VALUE => 0,
+                ENTRYAGGREGATOR => 'AND',
             );
             $ACL->Limit(
+                SUBCLAUSE => 'search',
                 ALIAS => $cgm_alias,
                 FIELD => 'MemberId',
                 VALUE => $principal->Id,
+                QUOTEVALUE => 0,
+                ENTRYAGGREGATOR => 'AND',
             );
         }
     }
 
-    if ($args{right}) {
-        $has_search = 1;
-        for my $term (split ' ', $args{right}) {
-            $ACL->Limit(
-                FIELD           => 'RightName',
-                OPERATOR        => 'LIKE',
-                VALUE           => $term,
-                CASESENSITIVE   => 0,
-                ENTRYAGGREGATOR => 'OR',
             );
         }
-        $ACL->Limit(
-            FIELD           => 'RightName',
-            OPERATOR        => '=',
-            VALUE           => 'SuperUser',
-            ENTRYAGGREGATOR => 'OR',
-        );
+    }
+
+    if ($search_paren) {
+        $ACL->_CloseParen('search');
     }
 
     if ($args{continueAfter}) {
         $has_search = 1;
+        $ACL->_OpenParen('paging');
         $ACL->Limit(
-            FIELD    => 'id',
-            OPERATOR => '>',
-            VALUE    => $args{continueAfter},
+            SUBCLAUSE => 'paging',
+            FIELD     => 'id',
+            OPERATOR  => '>',
+            VALUE     => int($args{continueAfter}),
+            QUOTEVALUE => 0,
         );
+        $ACL->_CloseParen('paging');
     }
 
     $ACL->OrderBy(

commit 1b2ee20cfd6994a33f4c1f126843fd1b22c426ad
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Mar 9 20:42:22 2017 +0000

    Add "inner role" ACLs to debugger results
    
    A query like "user:50" would be incomplete because it was looking only
    for system- and queue-level rights. If user 50 were granted rights by
    being assigned role membership on a ticket (membership in an "inner
    role"), then such results were hidden from the straightforward lookups
    of the ACL table this extension had done before this commit.
    
    The reason is that when a user is a member of the AdminCc group on a
    ticket, they are _not_ automatically included as members of the AdminCc
    group on the queue, or on the system. Presumably, if they were, then
    they would have enjoy their AdminCc rights on a global level.
    
    The fix, then, is to include ticket roles in the ACL query. To do so is
    not easy, as we have to do a large join across these tables:
    
        - ACL
        - Groups (queue and/or system role groups, which is where ACLs are assigned)
        - Tickets (to bridge between queue role groups and ticket role groups)
        - Groups (ticket role groups)
        - CachedGroupMembers (principals in ticket role groups)
    
    Though it may be possible to build some monster query that encompasses both
    cases (direct privilege granting and ticket role privilege granting),
    instead what we do is perform this new "inner role" query using raw SQL
    (though taking care to avoid injection attacks), and then pulling out
    the IDs. A counterfeit join to be sure, but it does mean we have a
    better chance to stash away which ticket IDs, and how many tickets, a
    principal has ticket role rights on. The following commits expose that
    information in the debugger UI.
    
    We also apply the same improvements for assets and catalogs. Extending
    %ParentMap should allow additional objects to participate.

diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index b227dd0..c1c7be8 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -2,6 +2,18 @@ package RT::Extension::RightsDebugger;
 use strict;
 use warnings;
 
+# glossary:
+#     inner role - being granted a right by way of ticket role membership
+#                  which is treated in a special way in RT. this is because
+#                  members of ticket AdminCc group are neither members of
+#                  the queue AdminCc group nor the system AdminCc group.
+#                  this means we have to do a really gnarly joins to recover
+#                  such ACLs.
+#     principal  - the recipient of a privilege; e.g. user or group
+#     object     - the scope of the privilege; e.g. queue or system
+#     record     - generalization of principal and object since rendering
+#                  and whatnot can share code
+
 our $VERSION = '0.01';
 
 RT->AddStyleSheets("rights-debugger.css");
@@ -180,6 +192,101 @@ sub ObjectForSpec {
     return undef;
 }
 
+our %ParentMap = (
+    'RT::Ticket' => [Queue => 'RT::Queue'],
+    'RT::Asset' => [Catalog => 'RT::Catalog'],
+);
+
+# see inner role glossary entry
+# this has three modes, depending on which parameters are passed
+# - principal_id but no inner_id: find tickets/assets this principal
+#   has permissions for
+# - inner_id but no principal_id: find the queue/system permissions that affect
+#   this ticket
+# - principal and inner_id: find all permissions this principal has on
+#   this "inner" object
+# there's no analagous query in the RT codebase because it uses a caching approach;
+# see RT::Tickets::_RolesCanSee
+sub InnerRoleQuery {
+    my $self = shift;
+    my %args = (
+        inner_class  => '', # RT::Ticket, RT::Asset
+        principal_id => undef,
+        inner_id     => undef,
+        right_search => undef,
+        @_,
+    );
+
+    my $inner_class  = $args{inner_class};
+    my $principal_id = $args{principal_id};
+    my $inner_id     = $args{inner_id};
+    my $inner_table  = $inner_class->Table;
+
+    my ($parent_column, $parent_class) = @{ $ParentMap{$inner_class} || [] }
+        or die "No parent mapping specified for $inner_class";
+    my $parent_table = $parent_class->Table;
+
+    my @query = qq[
+        SELECT main.id,
+               MIN(InnerRecords.id) AS example_record,
+               COUNT(InnerRecords.id)-1 AS other_count
+        FROM ACL AS main
+        JOIN Groups AS ParentRoles
+             ON main.PrincipalId = ParentRoles.id
+        JOIN $inner_table AS InnerRecords
+             ON   (ParentRoles.Domain = '$parent_class-Role' AND InnerRecords.$parent_column = ParentRoles.Instance)
+                OR ParentRoles.Domain = 'RT::System-Role'
+        JOIN Groups AS InnerRoles
+             ON  InnerRoles.Instance = InnerRecords.Id
+             AND InnerRoles.Name = main.PrincipalType
+    ];
+    if ($principal_id) {
+        push @query, qq[
+            JOIN CachedGroupMembers AS CGM
+                 ON CGM.GroupId = InnerRoles.id
+        ];
+    }
+
+    push @query, qq[ WHERE ];
+
+    if ($args{right_search}) {
+        my $LIKE = RT->Config->Get('DatabaseType') eq 'Pg' ? 'ILIKE' : 'LIKE';
+
+        push @query, qq[ ( ];
+        for my $term (split ' ', $args{right_search}) {
+            my $quoted = $RT::Handle->Quote($term);
+            push @query, qq[
+                main.RightName $LIKE $quoted OR
+            ],
+        }
+        push @query, qq[main.RightName $LIKE 'SuperUser'];
+        push @query, qq[ ) AND ];
+    }
+
+    if ($principal_id) {
+        push @query, qq[
+             CGM.MemberId = $principal_id AND
+             CGM.Disabled = 0 AND
+        ];
+    }
+    else {
+        #push @query, qq[
+        #         CGM.MemberId = $principal_id AND
+        #];
+    }
+
+    push @query, qq[
+             InnerRecords.id = $inner_id AND
+    ] if $inner_id;
+
+    push @query, qq[
+             InnerRoles.Domain = '$inner_class-Role'
+        GROUP BY main.id
+    ];
+
+    return join "\n", @query;
+}
+
 # key entry point into this extension; takes a query (principal, object, right)
 # and produces a list of highlighted results
 sub Search {
@@ -324,7 +431,35 @@ sub Search {
         }
     }
 
+    # now we need to address the unfortunate fact that ticket role
+    # members are not listed as queue role members. the way we do this
+    # is with a many-join query to map queue roles to ticket roles
+    if ($primary_records{principal} || $primary_records{object}) {
+        for my $inner_class (keys %ParentMap) {
+            next if $primary_records{object}
+                 && !$primary_records{object}->isa($inner_class);
+
+            my $query = $self->InnerRoleQuery(
+                inner_class  => $inner_class,
+                principal_id => ($primary_records{principal} ? $primary_records{principal}->Id : undef),
+                inner_id     => ($primary_records{object} ? $primary_records{object}->Id : undef),
+                right_search => $args{right},
             );
+            my $sth = $ACL->_Handle->SimpleQuery($query);
+            my @acl_ids;
+            while (my ($acl_id, $record_id, $other_count) = $sth->fetchrow_array) {
+                push @acl_ids, $acl_id;
+            }
+            if (@acl_ids) {
+                $search_paren ||= do { $ACL->_OpenParen('search'); 1 };
+                $ACL->Limit(
+                    SUBCLAUSE => 'search',
+                    FIELD     => 'id',
+                    OPERATOR  => 'IN',
+                    VALUE     => \@acl_ids,
+                    ENTRYAGGREGATOR => 'OR',
+                );
+            }
         }
     }
 

commit 3a95a7bc55181579ff95b149f0797fa0e426f2af
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Mar 9 20:52:10 2017 +0000

    Display inner role information in debugger UI
    
    If we're rendering a result row because it was pulled in from the "inner
    role" query (see previous commit) then instead of displaying, say:
    
        AdminCc
        Queue Role
    
    Then with this commit we can now be more accurate and helpful, instead
    displaying:
    
        AdminCc
        Ticket #3 Role (+4 others)
    
    This is more accurate because the user is _not_ an AdminCc on the queue;
    it just appears that way due to ACLEquivalenceObjects. The user is
    AdminCc on only 5 tickets.

diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index c1c7be8..6305442 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -8,7 +8,9 @@ use warnings;
 #                  members of ticket AdminCc group are neither members of
 #                  the queue AdminCc group nor the system AdminCc group.
 #                  this means we have to do a really gnarly joins to recover
-#                  such ACLs.
+#                  such ACLs. to improve comprehensibility we keep track
+#                  of such inner roles then massage the serialized data
+#                  afterwards to reference these implicit relationships
 #     principal  - the recipient of a privilege; e.g. user or group
 #     object     - the scope of the privilege; e.g. queue or system
 #     record     - generalization of principal and object since rendering
@@ -311,6 +313,7 @@ sub Search {
         principal => undef,
         object    => undef,
     );
+    my %inner_role;
 
     if ($args{right}) {
         $has_search = 1;
@@ -449,6 +452,7 @@ sub Search {
             my @acl_ids;
             while (my ($acl_id, $record_id, $other_count) = $sth->fetchrow_array) {
                 push @acl_ids, $acl_id;
+                $inner_role{$acl_id} = [$inner_class, $record_id, $other_count];
             }
             if (@acl_ids) {
                 $search_paren ||= do { $ACL->_OpenParen('search'); 1 };
@@ -494,7 +498,7 @@ sub Search {
 
     ACE: while (my $ACE = $ACL->Next) {
         $continueAfter = $ACE->Id;
-        my $serialized = $self->SerializeACE($ACE, \%primary_records);
+        my $serialized = $self->SerializeACE($ACE, \%primary_records, \%inner_role);
 
         KEY: for my $key (qw/principal object/) {
 	    # filtering on the serialized record is hacky, but doing the
@@ -535,14 +539,21 @@ sub SerializeACE {
     my $self = shift;
     my $ACE = shift;
     my $primary_records = shift;
+    my $inner_role = shift;
 
-    return {
+    my $serialized = {
         principal      => $self->SerializeRecord($ACE->PrincipalObj, $primary_records->{principal}),
         object         => $self->SerializeRecord($ACE->Object, $primary_records->{object}),
         right          => $ACE->RightName,
         ace            => { id => $ACE->Id },
         disable_revoke => $self->DisableRevoke($ACE),
     };
+
+    if ($inner_role->{$ACE->Id}) {
+        $self->InjectSerializedWithInnerRoleDetails($serialized, $ACE, $inner_role->{$ACE->Id}, $primary_records);
+    }
+
+    return $serialized;
 }
 
 # should the "Revoke" button be disabled? by default it is for the two required
@@ -627,6 +638,36 @@ sub SerializeRecord {
     return $serialized;
 }
 
+sub InjectSerializedWithInnerRoleDetails {
+    my $self = shift;
+    my $serialized = shift;
+    my $ACE = shift;
+    my $inner_role = shift;
+    my $primary_records = shift;
+
+    my $principal = $self->CanonicalizeRecord($ACE->PrincipalObj);
+    my $object = $self->CanonicalizeRecord($ACE->Object);
+    my $primary_principal = $self->CanonicalizeRecord($primary_records->{principal}) || $principal;
+    my $primary_object = $self->CanonicalizeRecord($primary_records->{object}) || $object;
+
+    if ($principal->isa('RT::Group') || $principal->isa('RT::CustomRole')) {
+        my ($inner_class, $inner_id, $inner_count) = @$inner_role;
+        my $inner_record = $inner_class->new($self->CurrentUser);
+        $inner_record->Load($inner_id);
+
+        $inner_class =~ s/^RT:://i;
+        my $detail = "$inner_class #$inner_id ";
+        $detail .= $principal->isa('RT::Group') ? 'Role' : 'CustomRole';
+
+        $serialized->{principal}{detail} = $detail;
+        $serialized->{principal}{detail_url} = $self->URLForRecord($inner_record);
+
+        if ($inner_count) {
+            $serialized->{principal}{detail_extra} = $self->CurrentUser->loc("(+[quant,_1,other,others])", $inner_count);
+        }
+    }
+}
+
 # primary display label for a record (e.g. user name, ticket subject)
 sub LabelForRecord {
     my $self = shift;

commit 3123dc04c2e43da999cad7c5d35e5e5ead2c12e8
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu Mar 9 20:54:44 2017 +0000

    Linkify "(+x others)" with a TicketSQL search when applicable
    
    If a user is a member of AdminCc on several tickets, the rights debugger
    shows only the first, as well as a count of how many others there are.
    This is appropriate for such a small UI (listing all the tickets by ID
    would be completely bonkers).
    
    You can easily search for which tickets this user is an AdminCc member,
    so let's do so. The resulting looks like
    
        Queue = 'General'
        AND AdminCc.Name = 'user'
    
    If AdminCc rights are granted on a system level, then such ACLs
    shouldn't be limited to any queue, so the query is simply:
    
        AdminCc.Name = 'user'

diff --git a/lib/RT/Extension/RightsDebugger.pm b/lib/RT/Extension/RightsDebugger.pm
index 6305442..2af6187 100644
--- a/lib/RT/Extension/RightsDebugger.pm
+++ b/lib/RT/Extension/RightsDebugger.pm
@@ -664,6 +664,27 @@ sub InjectSerializedWithInnerRoleDetails {
 
         if ($inner_count) {
             $serialized->{principal}{detail_extra} = $self->CurrentUser->loc("(+[quant,_1,other,others])", $inner_count);
+
+            if ($inner_class eq 'Ticket' && $primary_principal->isa('RT::User')) {
+                my $query;
+                if ($ACE->Object->isa('RT::Queue')) {
+                    my $name = $ACE->Object->Name;
+                    $name =~ s/(['\\])/\\$1/g;
+                    $query .= "Queue = '$name' AND ";
+                }
+                my $user_name = $primary_principal->Name;
+                $user_name =~ s/(['\\])/\\$1/g;
+
+                my $role_name = $principal->Name;
+                $role_name =~ s/(['\\])/\\$1/g;
+
+                my $role_term = $principal->isa('RT::Group') ? $role_name
+                              : "CustomRole.{$role_name}";
+
+                $query .= "$role_term.Name = '$user_name'";
+
+                $serialized->{principal}{detail_extra_url} = RT->Config->Get('WebURL') . 'Search/Results.html?Query=' . _EscapeURI($query);
+            }
         }
     }
 }

-----------------------------------------------------------------------


More information about the Bps-public-commit mailing list