From nobody Fri Oct 20 06:09:13 2023 X-Original-To: dev-commits-ports-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4SBZ0q0QgFz4xMtM; Fri, 20 Oct 2023 06:09:15 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4SBZ0n6dH5z3Xm5; Fri, 20 Oct 2023 06:09:13 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1697782153; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=oSnNwjJW8p9UDP4kmODUtG/H0eo17W4eojg3VO9jImI=; b=TSRIo4msdUlbriLVOqXSvYKx2sj7/LCOmUZxzrld/KbCPQT9G8qiUzg8j2Id2azZkIzNqn FdKX/4/OTQ8cR1qFun8qdVo9QU/Bu11+XRMvG7ITptBd/UGnYd1SCfX52omzrqgNg4L0R1 vOtgNQGRBieX1ZAvq74+rpG9BccU7BOBzlVsgBW/KTTTeTYYxAGUQyyen96BcEPJ1RziLr tzuU1RuYCHN8aWEcwm6eqpBXPLR/juqNlEWGaFMQkSYYjfNWADFHq8YznyJA2k4FomhwvM R4z8V+esVt/G9G20O0VHakXg+K3qhV0CUF/LsmM3uWAv+xdvbhj5BZxEN7HR1Q== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1697782153; a=rsa-sha256; cv=none; b=kM9KHDtUPD+pQvKtrsLlS8OA04wk4izOxjM5IzMZjBSLbXy2iV5kDvCbkl4+agGAVaqY5z ozTAIS5CdzA9mQfJ23/GX8QmeCjTnxO7Nsq0w1bTJFTsdzqavFb4hKardAHkGjoCQupxqE sByDxb0ECG4cz7mEcyF6J5LcYIBeZOFCnTUCxJzE2aSzPh5VdKIRiGTuzM+iplTFp3GKdX iM0GWTG1u9kSCs43CU+6cudaCRyEIcm7bwB3yp3aUGZhYI6jfNfZyeiVvNQbz5j5sFEPnZ WGlvovjdVlXTDkcr/dyWVykIMYU75wT9I0gha5AE0Um3p98j3ekEsm5Tkce8NA== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1697782153; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=oSnNwjJW8p9UDP4kmODUtG/H0eo17W4eojg3VO9jImI=; b=xF9xi0E+C8Ww3A5HP4bzKBZGSg4Q/JUMRBDg3PQtJMckE6NETkdQFmivEb3o77uuM5x4yM +6qfR4uznEGjQWP7QobRN+QVQUx0vo1ejtYB1u7qVk+oWY/uP0vW0AlaF/0xV2XdNwS4r+ PLXqqPw8MjfVWBPde9fpbkmcq8bd29Kd/DkVjz1phhOkwHxYSd0yU+ORvgYb0upg/CoRlm D9c0oD+lqynSaae4ZMMPUN7jWBSdyRWQoNWQiU/3I9DxBNgT7ukfRVelLPokxVkxRbumFi e3suzd99Z+6XAQSNcmkjWvv9rIR/Eg/D521qCg2trtqYzWb/W8iigF/TF61l1A== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 4SBZ0n5SKdz6JC; Fri, 20 Oct 2023 06:09:13 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.17.1/8.17.1) with ESMTP id 39K69DYc042074; Fri, 20 Oct 2023 06:09:13 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.17.1/8.17.1/Submit) id 39K69DoP042070; Fri, 20 Oct 2023 06:09:13 GMT (envelope-from git) Date: Fri, 20 Oct 2023 06:09:13 GMT Message-Id: <202310200609.39K69DoP042070@gitrepo.freebsd.org> To: ports-committers@FreeBSD.org, dev-commits-ports-all@FreeBSD.org, dev-commits-ports-main@FreeBSD.org From: Mikael Urankar Subject: git: 9f8d5a5f3399 - main - www/rt50: Fix vulnerabilities List-Id: Commit messages for all branches of the ports repository List-Archive: https://lists.freebsd.org/archives/dev-commits-ports-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: Sender: owner-dev-commits-ports-all@freebsd.org X-BeenThere: dev-commits-ports-all@freebsd.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: mikael X-Git-Repository: ports X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 9f8d5a5f339905355c4f2728befc3adcb83d47e5 Auto-Submitted: auto-generated The branch main has been updated by mikael: URL: https://cgit.FreeBSD.org/ports/commit/?id=9f8d5a5f339905355c4f2728befc3adcb83d47e5 commit 9f8d5a5f339905355c4f2728befc3adcb83d47e5 Author: Mikael Urankar AuthorDate: 2023-10-04 07:57:55 +0000 Commit: Mikael Urankar CommitDate: 2023-10-20 06:08:18 +0000 www/rt50: Fix vulnerabilities The following issues are addressed with these security updates: - RT is vulnerable to unvalidated email headers in incoming email and the mail-gateway REST interface. This vulnerability is assigned CVE-2023-41259. - RT is vulnerable to information leakage via response messages returned from requests sent via the mail-gateway REST interface. This vulnerability is assigned CVE-2023-41260. - RT 5.0 is vulnerable to information leakage via transaction searches made by authenticated users in the transaction query builder. This vulnerability is assigned CVE-2023-45024. - RT 5.0 can reveal information about data on various RT objects in errors and other response messages to REST 2 requests. --- www/rt50/Makefile | 1 + www/rt50/files/patch-vuln-2023-09-26 | 1118 ++++++++++++++++++++++++++++++++++ 2 files changed, 1119 insertions(+) diff --git a/www/rt50/Makefile b/www/rt50/Makefile index ef850fa1a351..09b2f7c61d04 100644 --- a/www/rt50/Makefile +++ b/www/rt50/Makefile @@ -1,5 +1,6 @@ PORTNAME= rt DISTVERSION= 5.0.4 +PORTREVISION= 1 CATEGORIES= www MASTER_SITES= http://download.bestpractical.com/pub/rt/release/ PKGNAMESUFFIX= 50 diff --git a/www/rt50/files/patch-vuln-2023-09-26 b/www/rt50/files/patch-vuln-2023-09-26 new file mode 100644 index 000000000000..83e7ddf5b63b --- /dev/null +++ b/www/rt50/files/patch-vuln-2023-09-26 @@ -0,0 +1,1118 @@ +The following issues are addressed with these security updates: + + RT is vulnerable to unvalidated email headers in incoming email and the mail-gateway REST interface. This vulnerability is assigned CVE-2023-41259. + RT is vulnerable to information leakage via response messages returned from requests sent via the mail-gateway REST interface. This vulnerability is assigned CVE-2023-41260. + RT 5.0 is vulnerable to information leakage via transaction searches made by authenticated users in the transaction query builder. This vulnerability is assigned CVE-2023-45024. + RT 5.0 can reveal information about data on various RT objects in errors and other response messages to REST 2 requests. + +diff --git a/docs/web_deployment.pod b/docs/web_deployment.pod +index 6ee03d48d8..c22001103a 100644 +--- docs/web_deployment.pod ++++ docs/web_deployment.pod +@@ -127,6 +127,31 @@ RT to access the Authorization header. + + More information is available in L. + ++=head3 Restricting the REST 1.0 mail-gateway ++ ++RT processes email via a REST 1.0 endpoint. If you accept email on the same ++server as your running RT, you can restrict this endpoint to localhost only ++with a configuration like the following: ++ ++ # Accept requests only from localhost ++ ++ Require local ++ ++ ++If you run C on a separate server, you can update ++the above to allow additional IP addresses. ++ ++ ++ Require ip 127.0.0.1 ::1 192.0.2.0 # Add you actual IPs ++ ++ ++See the L ++for additional configuration options. ++ ++After adding this configuration, test receiving email and confirm ++your C utility and C configurations ++can successfully submit email to RT. ++ + =head2 nginx + + C requires that you start RT's fastcgi process externally, for +diff --git a/lib/RT/Articles.pm b/lib/RT/Articles.pm +index 79e771884e..787924ffff 100644 +--- lib/RT/Articles.pm ++++ lib/RT/Articles.pm +@@ -970,6 +970,11 @@ sub SimpleSearch { + return $self; + } + ++sub CurrentUserCanSeeAll { ++ my $self = shift; ++ return $self->CurrentUser->HasRight( Right => 'ShowArticle', Object => RT->System ) ? 1 : 0; ++} ++ + RT::Base->_ImportOverlays(); + + 1; +diff --git a/lib/RT/Assets.pm b/lib/RT/Assets.pm +index f6cf8ee647..516869f9cc 100644 +--- lib/RT/Assets.pm ++++ lib/RT/Assets.pm +@@ -1932,6 +1932,11 @@ sub _ProcessRestrictions { + + } + ++sub CurrentUserCanSeeAll { ++ my $self = shift; ++ return $self->CurrentUser->HasRight( Right => 'ShowAsset', Object => RT->System ) ? 1 : 0; ++} ++ + 1; + + RT::Base->_ImportOverlays(); +diff --git a/lib/RT/Attachment.pm b/lib/RT/Attachment.pm +index 2c50447a91..3fed08a360 100644 +--- lib/RT/Attachment.pm ++++ lib/RT/Attachment.pm +@@ -1090,6 +1090,17 @@ sub _CacheConfig { + } + + ++=head2 CurrentUserCanSee ++ ++Returns true if the current user can see the attachment, via corresponding ++transaction's rights check. ++ ++=cut ++ ++sub CurrentUserCanSee { ++ my $self = shift; ++ return $self->TransactionObj->CurrentUserCanSee; ++} + + + =head2 id +diff --git a/lib/RT/Catalog.pm b/lib/RT/Catalog.pm +index 1354207523..31946435b1 100644 +--- lib/RT/Catalog.pm ++++ lib/RT/Catalog.pm +@@ -297,6 +297,28 @@ sub CurrentUserCanSee { + || $self->CurrentUserHasRight('AdminCatalog'); + } + ++=head2 CurrentUserCanCreate ++ ++Returns true if the current user can create a new catalog, using I. ++ ++=cut ++ ++sub CurrentUserCanCreate { ++ my $self = shift; ++ return $self->CurrentUserHasRight('AdminCatalog'); ++} ++ ++=head2 CurrentUserCanModify ++ ++Returns true if the current user can modify the catalog, using I. ++ ++=cut ++ ++sub CurrentUserCanModify { ++ my $self = shift; ++ return $self->CurrentUserHasRight('AdminCatalog'); ++} ++ + =head2 Owner + + Returns an L object for this catalog's I role group. On error, +diff --git a/lib/RT/Catalogs.pm b/lib/RT/Catalogs.pm +index 250793c47b..6d67a47720 100644 +--- lib/RT/Catalogs.pm ++++ lib/RT/Catalogs.pm +@@ -114,6 +114,11 @@ sub _Init { + + sub Table { "Catalogs" } + ++sub CurrentUserCanSeeAll { ++ my $self = shift; ++ return $self->CurrentUser->HasRight( Right => 'ShowCatalog', Object => RT->System ) ? 1 : 0; ++} ++ + RT::Base->_ImportOverlays(); + + 1; +diff --git a/lib/RT/Class.pm b/lib/RT/Class.pm +index 01079040c0..03a7926cc2 100644 +--- lib/RT/Class.pm ++++ lib/RT/Class.pm +@@ -507,6 +507,39 @@ sub IncludeArticleCFValue { + return $self->{'_cf_include_hash'}{"Value-".$cfobj->Id}; + } + ++=head2 CurrentUserCanSee ++ ++Returns true if the current user can see the class, using I. ++ ++=cut ++ ++sub CurrentUserCanSee { ++ my $self = shift; ++ return $self->CurrentUserHasRight('SeeClass'); ++} ++ ++=head2 CurrentUserCanCreate ++ ++Returns true if the current user can create a new class, using I. ++ ++=cut ++ ++sub CurrentUserCanCreate { ++ my $self = shift; ++ return $self->CurrentUserHasRight('AdminClass'); ++} ++ ++=head2 CurrentUserCanModify ++ ++Returns true if the current user can modify the class, using I. ++ ++=cut ++ ++sub CurrentUserCanModify { ++ my $self = shift; ++ return $self->CurrentUserHasRight('AdminClass'); ++} ++ + =head2 id + + Returns the current value of id. +diff --git a/lib/RT/Classes.pm b/lib/RT/Classes.pm +index ff446dddfb..9cac032d48 100644 +--- lib/RT/Classes.pm ++++ lib/RT/Classes.pm +@@ -81,6 +81,11 @@ sub AddRecord { + + sub _SingularClass { "RT::Class" } + ++sub CurrentUserCanSeeAll { ++ my $self = shift; ++ return $self->CurrentUser->HasRight( Right => 'SeeClass', Object => RT->System ) ? 1 : 0; ++} ++ + RT::Base->_ImportOverlays(); + + 1; +diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm +index 79c1f1f5a5..e261eb6f8e 100644 +--- lib/RT/CustomField.pm ++++ lib/RT/CustomField.pm +@@ -2072,6 +2072,28 @@ sub CurrentUserCanSee { + return 0; + } + ++=head2 CurrentUserCanCreate ++ ++If the user has I they can create a new custom field. ++ ++=cut ++ ++sub CurrentUserCanCreate { ++ my $self = shift; ++ return $self->CurrentUserHasRight('AdminCustomField'); ++} ++ ++=head2 CurrentUserCanModify ++ ++If the user has I they can modify the custom field. ++ ++=cut ++ ++sub CurrentUserCanModify { ++ my $self = shift; ++ return $self->CurrentUserHasRight('AdminCustomField'); ++} ++ + =head2 IncludeContentForValue [VALUE] (and SetIncludeContentForValue) + + Gets or sets the C for this custom field. RT +diff --git a/lib/RT/CustomFields.pm b/lib/RT/CustomFields.pm +index 253513d0f9..0fc5569adb 100644 +--- lib/RT/CustomFields.pm ++++ lib/RT/CustomFields.pm +@@ -475,6 +475,11 @@ sub LimitToCatalog { + } + } + ++sub CurrentUserCanSeeAll { ++ my $self = shift; ++ return $self->CurrentUser->HasRight( Right => 'SeeCustomField', Object => RT->System ) ? 1 : 0; ++} ++ + RT::Base->_ImportOverlays(); + + 1; +diff --git a/lib/RT/CustomRole.pm b/lib/RT/CustomRole.pm +index ec37402925..750aaf2575 100644 +--- lib/RT/CustomRole.pm ++++ lib/RT/CustomRole.pm +@@ -544,6 +544,28 @@ sub GroupType { + return 'RT::CustomRole-' . $self->id; + } + ++=head2 CurrentUserCanCreate ++ ++Returns true if the current user can create a new custom role, using I. ++ ++=cut ++ ++sub CurrentUserCanCreate { ++ my $self = shift; ++ return $self->CurrentUserHasRight('AdminClass'); ++} ++ ++=head2 CurrentUserCanModify ++ ++Returns true if the current user can modify the custom role, using I. ++ ++=cut ++ ++sub CurrentUserCanModify { ++ my $self = shift; ++ return $self->CurrentUserHasRight('AdminClass'); ++} ++ + =head2 id + + Returns the current value of id. +diff --git a/lib/RT/CustomRoles.pm b/lib/RT/CustomRoles.pm +index 6cac676858..75587c71ed 100644 +--- lib/RT/CustomRoles.pm ++++ lib/RT/CustomRoles.pm +@@ -213,6 +213,12 @@ sub LimitToAdded { + return RT::ObjectCustomRoles->new( $self->CurrentUser )->LimitTargetToAdded( $self => @_ ); + } + ++sub CurrentUserCanSeeAll { ++ my $self = shift; ++ # Not typo, user needs SeeQueue to see CustomRoles ++ return $self->CurrentUser->HasRight( Right => 'SeeQueue', Object => RT->System ) ? 1 : 0; ++} ++ + RT::Base->_ImportOverlays(); + + 1; +diff --git a/lib/RT/Group.pm b/lib/RT/Group.pm +index ccc92fe7c1..03e20eb707 100644 +--- lib/RT/Group.pm ++++ lib/RT/Group.pm +@@ -1311,17 +1311,43 @@ sub _Set { + + =head2 CurrentUserCanSee + +-Always returns 1; unfortunately, for historical reasons, users have +-always been able to examine groups they have indirect access to, even if +-they do not have SeeGroup explicitly. ++Unfortunately, for historical reasons, users have always been able to ++examine groups they have indirect access to, even if they do not have ++SeeGroup explicitly. ++ ++We do require "SeeGroup" to see transactions of current group. + + =cut + + sub CurrentUserCanSee { + my $self = shift; +- return 1; ++ my ($what, $txn) = @_; ++ ++ return 1 if ( $what // '' ) ne 'Transaction'; ++ return $self->CurrentUserHasRight('SeeGroup'); ++} ++ ++=head2 CurrentUserCanCreate ++ ++Returns true if the current user can create a new group, using I. ++ ++=cut ++ ++sub CurrentUserCanCreate { ++ my $self = shift; ++ return $self->CurrentUserHasRight('AdminGroup'); + } + ++=head2 CurrentUserCanModify ++ ++Returns true if the current user can modify the group, using I. ++ ++=cut ++ ++sub CurrentUserCanModify { ++ my $self = shift; ++ return $self->CurrentUserHasRight('AdminGroup'); ++} + + =head2 PrincipalObj + +diff --git a/lib/RT/Groups.pm b/lib/RT/Groups.pm +index 2f341bbba3..51a43f95e3 100644 +--- lib/RT/Groups.pm ++++ lib/RT/Groups.pm +@@ -537,6 +537,11 @@ sub SimpleSearch { + return $self; + } + ++sub CurrentUserCanSeeAll { ++ my $self = shift; ++ return $self->CurrentUser->HasRight( Right => 'SeeGroup', Object => RT->System ) ? 1 : 0; ++} ++ + RT::Base->_ImportOverlays(); + + 1; +diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm +index 39bd5c4169..648e0e7f61 100644 +--- lib/RT/Interface/Email.pm ++++ lib/RT/Interface/Email.pm +@@ -161,6 +161,10 @@ sub Gateway { + ); + } + ++ # Clean up sensitive headers. Crypt related headers are cleaned up in RT::Interface::Email::Crypt::VerifyDecrypt ++ my @headers = qw( RT-Attach RT-Send-Cc RT-Send-Bcc RT-Message-ID RT-DetectedAutoGenerated RT-Squelch-Replies-To ); ++ $Message->head->delete($_) for @headers; ++ + #Set up a queue object + my $SystemQueueObj = RT::Queue->new( RT->SystemUser ); + $SystemQueueObj->Load( $args{'queue'} ); +diff --git a/lib/RT/Interface/Email/Crypt.pm b/lib/RT/Interface/Email/Crypt.pm +index bc3427ca49..9d4d4fe584 100644 +--- lib/RT/Interface/Email/Crypt.pm ++++ lib/RT/Interface/Email/Crypt.pm +@@ -73,13 +73,14 @@ sub VerifyDecrypt { + ); + + # we clean all possible headers +- my @headers = ++ my @headers = ( + qw( + X-RT-Incoming-Encryption + X-RT-Incoming-Signature X-RT-Privacy + X-RT-Sign X-RT-Encrypt + ), +- map "X-RT-$_-Status", RT::Crypt->Protocols; ++ map "X-RT-$_-Status", RT::Crypt->Protocols ++ ); + foreach my $p ( $args{'Message'}->parts_DFS ) { + $p->head->delete($_) for @headers; + } +diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm +index 444eb1c24e..da0ec92bf8 100644 +--- lib/RT/Interface/Web.pm ++++ lib/RT/Interface/Web.pm +@@ -5792,6 +5792,28 @@ sub ParseCalendarData { + return undef; + } + ++sub PreprocessTransactionSearchQuery { ++ my %args = ( ++ Query => undef, ++ ObjectType => 'RT::Ticket', ++ @_ ++ ); ++ ++ my @limits; ++ if ( $args{ObjectType} eq 'RT::Ticket' ) { ++ @limits = ( ++ q{TicketType = 'ticket'}, ++ qq{ObjectType = '$args{ObjectType}'}, ++ $args{Query} =~ /^\s*\(.*\)$/ ? $args{Query} : "($args{Query})" ++ ); ++ } ++ else { ++ # Other ObjectTypes are not supported for now ++ @limits = 'id = 0'; ++ } ++ return join ' AND ', @limits; ++} ++ + package RT::Interface::Web; + RT::Base->_ImportOverlays(); + +diff --git a/lib/RT/ObjectCustomFieldValue.pm b/lib/RT/ObjectCustomFieldValue.pm +index 53e62c2334..4815f76406 100644 +--- lib/RT/ObjectCustomFieldValue.pm ++++ lib/RT/ObjectCustomFieldValue.pm +@@ -815,6 +815,7 @@ object, otherwise false. + + sub CurrentUserCanSee { + my $self = shift; ++ return undef unless $self->Id; + return $self->CustomFieldObj->CurrentUserHasRight('SeeCustomField'); + } + +diff --git a/lib/RT/Queue.pm b/lib/RT/Queue.pm +index 29ba7db3ad..609a7ebe5b 100644 +--- lib/RT/Queue.pm ++++ lib/RT/Queue.pm +@@ -810,6 +810,29 @@ sub CurrentUserCanSee { + return $self->CurrentUserHasRight('SeeQueue'); + } + ++ ++=head2 CurrentUserCanCreate ++ ++Returns true if the current user can create a new queue, using I. ++ ++=cut ++ ++sub CurrentUserCanCreate { ++ my $self = shift; ++ return $self->CurrentUserHasRight('AdminQueue'); ++} ++ ++=head2 CurrentUserCanModify ++ ++Returns true if the current user can modify the queue, using I. ++ ++=cut ++ ++sub CurrentUserCanModify { ++ my $self = shift; ++ return $self->CurrentUserHasRight('AdminQueue'); ++} ++ + =head2 id + + Returns the current value of id. +diff --git a/lib/RT/Queues.pm b/lib/RT/Queues.pm +index 5a916d70a0..0d031b8ebe 100644 +--- lib/RT/Queues.pm ++++ lib/RT/Queues.pm +@@ -128,6 +128,11 @@ sub ItemsOrderBy { + return $items; + } + ++sub CurrentUserCanSeeAll { ++ my $self = shift; ++ return $self->CurrentUser->HasRight( Right => 'SeeQueue', Object => RT->System ) ? 1 : 0; ++} ++ + RT::Base->_ImportOverlays(); + + 1; +diff --git a/lib/RT/REST2/Resource/Article.pm b/lib/RT/REST2/Resource/Article.pm +index f3a0eb32d7..d52fff739d 100644 +--- lib/RT/REST2/Resource/Article.pm ++++ lib/RT/REST2/Resource/Article.pm +@@ -80,17 +80,11 @@ sub create_record { + + return (\400, "Invalid Class") if !$data->{Class}; + +- my $class = RT::Class->new(RT->SystemUser); ++ my $class = RT::Class->new($self->current_user); + $class->Load($data->{Class}); + +- return (\400, "Invalid Class") if !$class->Id; +- + return ( \403, $self->record->loc("Permission Denied") ) +- unless $self->record->CurrentUser->HasRight( +- Right => 'CreateArticle', +- Object => $class, +- ) +- and $class->Disabled != 1; ++ unless $class->Id and !$class->__Value('Disabled') and $class->CurrentUserHasRight('CreateArticle'); + + my ($ok, $txn, $msg) = $self->_create_record($data); + return ($ok, $msg); +diff --git a/lib/RT/REST2/Resource/Asset.pm b/lib/RT/REST2/Resource/Asset.pm +index a29f637b7c..70e66464ba 100644 +--- lib/RT/REST2/Resource/Asset.pm ++++ lib/RT/REST2/Resource/Asset.pm +@@ -83,10 +83,8 @@ sub create_record { + my $catalog = RT::Catalog->new($self->record->CurrentUser); + $catalog->Load($data->{Catalog}); + +- return (\400, "Invalid Catalog") if !$catalog->Id; +- +- return (\403, $self->record->loc("Permission Denied", $catalog->Name)) +- unless $catalog->CurrentUserHasRight('CreateAsset'); ++ return (\403, $self->record->loc("Permission Denied")) ++ unless $catalog->Id and !$catalog->__Value('Disabled') and $catalog->CurrentUserHasRight('CreateAsset'); + + return $self->_create_record($data); + } +diff --git a/lib/RT/REST2/Resource/Collection.pm b/lib/RT/REST2/Resource/Collection.pm +index 2f0f16eab2..7653d5695c 100644 +--- lib/RT/REST2/Resource/Collection.pm ++++ lib/RT/REST2/Resource/Collection.pm +@@ -189,7 +189,7 @@ sub serialize { + + my %results = ( + count => scalar(@results) + 0, +- total => $collection->CountAll + 0, ++ total => $collection->CurrentUserCanSeeAll ? ( $collection->CountAll + 0 ) : undef, + per_page => $collection->RowsPerPage + 0, + page => ($collection->FirstRow / $collection->RowsPerPage) + 1, + items => \@results, +@@ -205,18 +205,20 @@ sub serialize { + } + } + +- $results{pages} = ceil($results{total} / $results{per_page}); +- if ($results{page} < $results{pages}) { +- my $page = $results{page} + 1; +- $uri->query_form( @query_form, page => $results{page} + 1 ); +- $results{next_page} = $uri->as_string; +- }; +- if ($results{page} > 1) { +- # If we're beyond the last page, set prev_page as the last page +- # available, otherwise, the previous page. +- $uri->query_form( @query_form, page => ($results{page} > $results{pages} ? $results{pages} : $results{page} - 1) ); +- $results{prev_page} = $uri->as_string; +- }; ++ $results{pages} = defined $results{total} ? ceil($results{total} / $results{per_page}) : undef; ++ if ( $results{pages} ) { ++ if ($results{page} < $results{pages}) { ++ my $page = $results{page} + 1; ++ $uri->query_form( @query_form, page => $results{page} + 1 ); ++ $results{next_page} = $uri->as_string; ++ } ++ if ($results{page} > 1) { ++ # If we're beyond the last page, set prev_page as the last page ++ # available, otherwise, the previous page. ++ $uri->query_form( @query_form, page => ($results{page} > $results{pages} ? $results{pages} : $results{page} - 1) ); ++ $results{prev_page} = $uri->as_string; ++ } ++ } + + return \%results; + } +diff --git a/lib/RT/REST2/Resource/CustomField.pm b/lib/RT/REST2/Resource/CustomField.pm +index 62e2d737b7..1924a95f2f 100644 +--- lib/RT/REST2/Resource/CustomField.pm ++++ lib/RT/REST2/Resource/CustomField.pm +@@ -87,21 +87,6 @@ sub serialize { + return $data; + } + +-sub forbidden { +- my $self = shift; +- my $method = $self->request->method; +- if ($self->record->id) { +- if ($method eq 'GET') { +- return !$self->record->CurrentUserHasRight('SeeCustomField'); +- } else { +- return !($self->record->CurrentUserHasRight('SeeCustomField') && $self->record->CurrentUserHasRight('AdminCustomField')); +- } +- } else { +- return !$self->current_user->HasRight(Right => "AdminCustomField", Object => RT->System); +- } +- return 0; +-} +- + sub hypermedia_links { + my $self = shift; + my $links = $self->_default_hypermedia_links(@_); +diff --git a/lib/RT/REST2/Resource/Group.pm b/lib/RT/REST2/Resource/Group.pm +index 58f46c1255..c955d1558f 100644 +--- lib/RT/REST2/Resource/Group.pm ++++ lib/RT/REST2/Resource/Group.pm +@@ -58,9 +58,7 @@ extends 'RT::REST2::Resource::Record'; + with 'RT::REST2::Resource::Record::Readable' + => { -alias => { serialize => '_default_serialize' } }, + 'RT::REST2::Resource::Record::DeletableByDisabling', +- => { -alias => { delete_resource => '_delete_resource' } }, + 'RT::REST2::Resource::Record::Writable', +- => { -alias => { create_record => '_create_record' } }, + 'RT::REST2::Resource::Record::Hypermedia' + => { -alias => { hypermedia_links => '_default_hypermedia_links' } }; + +@@ -102,33 +100,20 @@ sub hypermedia_links { + return $links; + } + +-sub create_record { ++override forbidden => sub { + my $self = shift; +- my $data = shift; + +- return (\403, $self->record->loc("Permission Denied")) +- unless $self->current_user->HasRight( +- Right => "AdminGroup", +- Object => RT->System, +- ); +- +- return $self->_create_record($data); +-} +- +-sub delete_resource { +- my $self = shift; +- +- return (\403, $self->record->loc("Permission Denied")) +- unless $self->record->CurrentUserHasRight('AdminGroup'); +- +- return $self->_delete_resource; +-} +- +-sub forbidden { +- my $self = shift; +- return 0 unless $self->record->id; +- return !$self->record->CurrentUserHasRight('SeeGroup'); +-} ++ # For historical reasons, RT::Group::CurrentUserCanSee always returns true. ++ # For REST2, we want to check SeeGroup. ++ no warnings 'redefine'; ++ my $original_can_see = \&RT::Group::CurrentUserCanSee; ++ local *RT::Group::CurrentUserCanSee = sub { ++ my $self = shift; ++ return 0 unless $original_can_see->($self, @_); ++ return $self->CurrentUserHasRight('SeeGroup'); ++ }; ++ return super(); ++}; + + __PACKAGE__->meta->make_immutable; + +diff --git a/lib/RT/REST2/Resource/GroupMembers.pm b/lib/RT/REST2/Resource/GroupMembers.pm +index 4c0bb0a49a..df555babec 100644 +--- lib/RT/REST2/Resource/GroupMembers.pm ++++ lib/RT/REST2/Resource/GroupMembers.pm +@@ -114,9 +114,7 @@ sub dispatch_rules { + + sub forbidden { + my $self = shift; +- return 0 unless $self->group->id; + return !$self->group->CurrentUserHasRight('AdminGroupMembership'); +- return 1; + } + + sub serialize { +diff --git a/lib/RT/REST2/Resource/ObjectCustomFieldValue.pm b/lib/RT/REST2/Resource/ObjectCustomFieldValue.pm +index b9d6fa1cb3..4dbc9cb7a8 100644 +--- lib/RT/REST2/Resource/ObjectCustomFieldValue.pm ++++ lib/RT/REST2/Resource/ObjectCustomFieldValue.pm +@@ -71,12 +71,6 @@ sub content_types_provided { + { [ {$self->record->ContentType || 'text/plain; charset=utf-8' => 'to_binary'} ] }; + } + +-sub forbidden { +- my $self = shift; +- return 0 unless $self->record->id; +- return !$self->record->CurrentUserHasRight('SeeCustomField'); +-} +- + sub to_binary { + my $self = shift; + unless ($self->record->CustomFieldObj->Type =~ /^(?:Image|Binary)$/) { +diff --git a/lib/RT/REST2/Resource/RT.pm b/lib/RT/REST2/Resource/RT.pm +index 6a31ba3a71..724880af72 100644 +--- lib/RT/REST2/Resource/RT.pm ++++ lib/RT/REST2/Resource/RT.pm +@@ -71,7 +71,9 @@ sub to_json { + my $self = shift; + return JSON::to_json({ + Version => $RT::VERSION, +- Plugins => [ RT->Config->Get('Plugins') ], ++ $self->current_user->HasRight( Object => RT->System, Right => 'SuperUser' ) ++ ? ( Plugins => [ RT->Config->Get('Plugins') ] ) ++ : (), + }, { pretty => 1 }); + } + __PACKAGE__->meta->make_immutable; +diff --git a/lib/RT/REST2/Resource/Record.pm b/lib/RT/REST2/Resource/Record.pm +index b335dab09e..df1223e993 100644 +--- lib/RT/REST2/Resource/Record.pm ++++ lib/RT/REST2/Resource/Record.pm +@@ -100,10 +100,21 @@ sub resource_exists { + + sub forbidden { + my $self = shift; +- return 0 unless $self->record->id; ++ my $method = $self->request->method; ++ ++ my $right_method; ++ if ( $self->record->id ) { ++ $right_method = $method =~ /^(?:GET|HEAD)$/ ? 'CurrentUserCanSee' : 'CurrentUserCanModify'; ++ } ++ else { ++ # Even without id, the method can be GET, e.g. to access a not-exsting record. ++ $right_method = $method =~ /^(?:GET|HEAD)$/ ? 'CurrentUserCanSee' : 'CurrentUserCanCreate'; ++ } ++ ++ if ( $self->record->can($right_method) ) { ++ return !$self->record->$right_method; ++ } + +- my $can_see = $self->record->can("CurrentUserCanSee"); +- return 1 if $can_see and not $self->record->$can_see(); + return 0; + } + +diff --git a/lib/RT/REST2/Resource/Ticket.pm b/lib/RT/REST2/Resource/Ticket.pm +index 1873f0a636..64b4d67647 100644 +--- lib/RT/REST2/Resource/Ticket.pm ++++ lib/RT/REST2/Resource/Ticket.pm +@@ -225,16 +225,11 @@ sub validate_input { + if ( $args{'Action'} eq 'create' ) { + return (0, "Could not create ticket. Queue not set", 400) if !$data->{Queue}; + +- my $queue = RT::Queue->new(RT->SystemUser); ++ my $queue = RT::Queue->new($self->current_user); + $queue->Load($data->{Queue}); + +- return (0, "Unable to find queue", 400) if !$queue->Id; +- +- return (0, $self->record->loc("No permission to create tickets in the queue '[_1]'", $queue->Name), 403) +- unless $self->record->CurrentUser->HasRight( +- Right => 'CreateTicket', +- Object => $queue, +- ) and $queue->Disabled != 1; ++ return (0, $self->record->loc("No permission to create tickets in the queue '[_1]'", $data->{Queue}), 403) ++ unless $queue->Id and $queue->__Value('Disabled') != 1 and $queue->CurrentUserHasRight('CreateTicket'); + } + + if ( $args{'Action'} eq 'update' ) { +diff --git a/lib/RT/REST2/Resource/User.pm b/lib/RT/REST2/Resource/User.pm +index 510a8c7740..af4c8c0c2b 100644 +--- lib/RT/REST2/Resource/User.pm ++++ lib/RT/REST2/Resource/User.pm +@@ -105,9 +105,8 @@ around 'serialize' => sub { + + sub forbidden { + my $self = shift; +- return 0 if not $self->record->id; +- return 0 if $self->record->id == $self->current_user->id; + return 0 if $self->current_user->Privileged; ++ return 0 if ( $self->record->id || 0 ) == $self->current_user->id; + return 1; + } + +diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm +index adb1e4e953..03088d9394 100644 +--- lib/RT/SearchBuilder.pm ++++ lib/RT/SearchBuilder.pm +@@ -1150,6 +1150,11 @@ sub DistinctFieldValues { + return @values; + } + ++sub CurrentUserCanSeeAll { ++ my $self = shift; ++ return $self->CurrentUser->HasRight( Right => 'SuperUser', Object => RT->System ) ? 1 : 0; ++} ++ + RT::Base->_ImportOverlays(); + + 1; +diff --git a/lib/RT/SearchBuilder/Role/Roles.pm b/lib/RT/SearchBuilder/Role/Roles.pm +index ac36e8538c..06462d3e88 100644 +--- lib/RT/SearchBuilder/Role/Roles.pm ++++ lib/RT/SearchBuilder/Role/Roles.pm +@@ -97,7 +97,7 @@ sub _RoleGroupClass { + + sub _RoleGroupsJoin { + my $self = shift; +- my %args = (New => 0, Class => '', Name => '', @_); ++ my %args = (New => 0, Class => '', Name => '', Alias => 'main', @_); + + $args{'Class'} ||= $self->_RoleGroupClass; + +@@ -118,7 +118,7 @@ sub _RoleGroupsJoin { + # Previously (before 4.4) this used an inner join. + my $groups = $self->Join( + TYPE => 'left', +- ALIAS1 => 'main', ++ ALIAS1 => $args{Alias}, + FIELD1 => $instance, + TABLE2 => 'Groups', + FIELD2 => 'Instance', +diff --git a/lib/RT/Ticket.pm b/lib/RT/Ticket.pm +index 741d2e3007..ec7f6922fb 100644 +--- lib/RT/Ticket.pm ++++ lib/RT/Ticket.pm +@@ -3788,6 +3788,10 @@ sub Serialize { + $obj->Load( $store{EffectiveId} ); + $store{EffectiveId} = \($obj->UID); + ++ unless ( $self->CurrentUserCanSeeTime ) { ++ delete $store{$_} for qw/TimeEstimated TimeLeft TimeWorked/; ++ } ++ + return %store; + } + +diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm +index fbaa2d602a..99fd3db13f 100644 +--- lib/RT/Tickets.pm ++++ lib/RT/Tickets.pm +@@ -2856,7 +2856,8 @@ sub CurrentUserCanSee { + return unless @queues; + $self->Limit( + SUBCLAUSE => 'ACL', +- ALIAS => 'main', ++ # RT::Transactions::CurrentUserCanSee reuses RT::Tickets::CurrentUserCanSee ++ ALIAS => $self->isa('RT::Transactions') ? $self->_JoinTickets : 'main', + FIELD => 'Queue', + OPERATOR => 'IN', + VALUE => [ @queues ], +@@ -2912,6 +2913,8 @@ sub CurrentUserCanSee { + FIELD => 'Owner', + VALUE => $id, + ENTRYAGGREGATOR => $ea, ++ # RT::Transactions::CurrentUserCanSee reuses RT::Tickets::CurrentUserCanSee ++ ALIAS => $self->isa('RT::Transactions') ? $self->_JoinTickets : 'main', + ); + } + else { +@@ -3563,6 +3566,12 @@ sub Query { + return $self->{_sql_query}; + } + ++sub CurrentUserCanSeeAll { ++ my $self = shift; ++ return 1 if RT->Config->Get('UseSQLForACLChecks'); ++ return $self->CurrentUser->HasRight( Right => 'ShowTicket', Object => RT->System ) ? 1 : 0; ++} ++ + RT::Base->_ImportOverlays(); + + 1; +diff --git a/lib/RT/Transactions.pm b/lib/RT/Transactions.pm +index 21ca3cd86e..e03d2995a8 100644 +--- lib/RT/Transactions.pm ++++ lib/RT/Transactions.pm +@@ -142,7 +142,28 @@ sub AddRecord { + my $self = shift; + my ($record) = @_; + +- return unless $record->CurrentUserCanSee; ++ if ( $self->{_is_ticket_only_search} && RT->Config->Get('UseSQLForACLChecks') ) { ++ # UseSQLForACLChecks implies ShowTicket only, need to check out extra rights here. ++ my $type = $record->__Value('Type'); ++ if ( $type eq 'Comment' ) { ++ return unless $record->CurrentUserHasRight('ShowTicketComments'); ++ } ++ elsif ( $type eq 'CommentEmailRecord' ) { ++ return ++ unless $record->CurrentUserHasRight('ShowTicketComments') ++ && $record->CurrentUserHasRight('ShowOutgoingEmail'); ++ } ++ elsif ( $type eq 'EmailRecord' ) { ++ return unless $record->CurrentUserHasRight('ShowOutgoingEmail'); ++ } ++ elsif ( $type eq 'CustomField' ) { ++ return unless $record->CurrentUserCanSee; ++ } ++ } ++ else { ++ return unless $record->CurrentUserCanSee; ++ } ++ + return $self->SUPER::AddRecord($record); + } + +@@ -1111,6 +1132,28 @@ sub _parser { + return $self->_CloseParen unless $node->isLeaf; + } + ); ++ ++ # Determine if it's a ticket transaction search ++ $tree->traverse( ++ sub { ++ my $node = shift; ++ return unless $node->isLeaf and $node->getNodeValue; ++ my ($key, $subkey, $meta, $op, $value, $bundle) ++ = @{$node->getNodeValue}{qw/Key Subkey Meta Op Value Bundle/}; ++ return unless $key eq 'ObjectType' && $value eq 'RT::Ticket' && $op eq '='; ++ ++ my $is_ticket_only_search = 1; ++ while ( my $parent = $node->getParent ) { ++ last if $parent->isRoot; ++ if ( lc( $parent->getNodeValue // '' ) eq 'or' ) { ++ $is_ticket_only_search = 0; ++ last; ++ } ++ $node = $parent; ++ } ++ $self->{_is_ticket_only_search} ||= $is_ticket_only_search; ++ } ++ ); + } *** 177 LINES SKIPPED ***