[Rt-commit] rt branch, 5.0/show-edit-system-config-option, created. rt-5.0.0alpha1-107-g8bcc0421b1

Jim Brandt jbrandt at bestpractical.com
Tue Apr 28 11:07:23 EDT 2020


The branch, 5.0/show-edit-system-config-option has been created
        at  8bcc0421b1f6d0703990d0d243353ab2939be43c (commit)

- Log -----------------------------------------------------------------
commit eb82aebe01d43163ffd2153e80f3951fb28141e1
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Mon Apr 13 17:06:51 2020 -0400

    Core JSON initialdata format handler

diff --git a/docs/initialdata.pod b/docs/initialdata.pod
index 7a58fbb21d..3ab74b62c5 100644
--- a/docs/initialdata.pod
+++ b/docs/initialdata.pod
@@ -28,11 +28,17 @@ some in your install with:
 
 =head1 What can be in an initialdata file?
 
-initialdata files are Perl, but often consist primarily of a bunch of data
-structures defining the new records you want and not much extra code.  There's
-nothing stopping you from writing a bunch of code, however!
+By default, initialdata files are Perl, but often consist primarily of a
+bunch of data structures defining the new records you want and not
+much extra code.  There's nothing stopping you from writing a bunch of
+code, however!
 
-The basic template of a new initialdata file should look something like this:
+RT's initialdata importer is also pluggable and you can add handlers for
+other formats using the option L<RT_Config/InitialdataFormatHandlers>.
+Starting in RT 5.0, RT supports JSON in addition to Perl. See below for
+details.
+
+The basic template of a new Perl initialdata file should look something like this:
 
     use strict;
     use warnings;
@@ -526,3 +532,82 @@ is to start reading the code there.
 RT takes care of the ordering so that your new queues are created before it
 processes the new ACLs for those queues.  This lets you refer to new queues you
 just created by Name.
+
+=head1 JSON initialdata
+
+To configure RT to load JSON-formatted initialdata, add this option:
+
+    Set( $InitialdataFormatHandlers,
+         [
+            'perl',
+            'RT::Initialdata::JSON',
+         ]
+       );
+
+There is a direct one-to-one mapping between the Perl initialdata structures and
+the JSON file data structures, with the exception of how the top-level elements
+are composed. In the Perl file, each array is named separately, like this:
+
+    @Queues = ( {...}, {...} );
+    @Scrips = ( {...}, {...} );
+
+To represent this in JSON, the root-level element is a JSON object--a key/value
+structure that is analogous to a perl hash. The key is the name of the array
+you would normally type as C<@Queues>, and the value is a JSON array:
+
+    {
+        "Queues":[ {...}, {...} ],
+        "Scrips":[ {...}, {...} ]
+    }
+
+You can find details on JSON formatting rules at L<http://json.org>.
+
+=head2 Example JSON File
+
+There is a JSON file with examples in the RT test files here:
+
+    F<t/data/initialdata/initialdata.json>
+
+=head2 Limitations
+
+The JSON initialdata format cannot support the full functionality of the perl
+format, as the perl format allows executable code. Specifically, these elements
+cannot be used, and if present, will be ignored:
+
+=over 4
+
+=item C<@Initial>
+
+No C<Initial> elements will be used.
+
+=item C<@Final>
+
+No C<Final> elements will be used.
+
+=item C<@Attributes>
+
+You can use an C<Attributes> element with some restrictions, but you cannot
+specify an C<Object> key inside it.  The only valid way to pass a value to the
+C<Object> key is via a perl object, and without executable code this is
+currently impossible.
+
+There are two valid ways you that you can specify attributes:
+
+=over 4
+
+=item System-Wide Attributes
+
+C<Attribute> elements without the C<Object> key in them are valid, and
+will pply system-wide as described in the RT documentation.
+
+=item As Children of Other Elements
+
+You can supply C<Attribute> elements as children of other objects in
+the JSON initialdata file, such as C<Users>. This sets the parent of the
+C<Attribte> as the C<Object> for the C<Attribute>.
+
+=back
+
+=back
+
+=cut
diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index be661201ff..20e416b1f3 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -1495,9 +1495,13 @@ Set the C<$InitialdataFormatHandlers> to an arrayref containing a list of
 format handler modules. The 'perl' entry is the system default, and handles
 perl-style intialdata files.
 
+The JSON format handler is also available in RT, but it is not loaded by
+default. Add it to your configuration as shown below to enable it.
+
     Set( $InitialdataFormatHandlers,
          [
             'perl',
+            'RT::Initialdata::JSON',
             'RT::Extension::Initialdata::Foo',
             ...
          [
diff --git a/lib/RT.pm b/lib/RT.pm
index 853a700adf..5ea2881df9 100644
--- a/lib/RT.pm
+++ b/lib/RT.pm
@@ -769,6 +769,7 @@ our %CORED_PLUGINS = (
     'RT::Extension::ConfigInDatabase' => '5.0',
     'RT::Extension::CustomRole::Visibility' => '5.0',
     'RT::Extension::PriorityAsString' => '5.0',
+    'RT::Extension::Initialdata::JSON' => '5.0',
 );
 
 sub InitPlugins {
diff --git a/lib/RT/Initialdata/JSON.pm b/lib/RT/Initialdata/JSON.pm
new file mode 100644
index 0000000000..d181251460
--- /dev/null
+++ b/lib/RT/Initialdata/JSON.pm
@@ -0,0 +1,134 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2020 Best Practical Solutions, LLC
+#                                          <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+=head1 NAME
+
+RT::Initialdata::JSON - Support for JSON-format initialdata files
+
+=head1 DESCRIPTION
+
+RT supports pluggable parsers for initialdata in different source
+formats. This module supports JSON.
+
+Perl-based initialdata files can contain not just data, but also
+perl code that executes when they are processed. The JSON format
+is purely for data serialization and does not support code sections.
+
+Files used with this handler must be UTF-8 encoded text containing a valid
+JSON structure. See http://json.org for JSON syntax specifications.
+
+=cut
+
+package RT::Initialdata::JSON;
+
+use strict;
+use warnings;
+
+use JSON;
+
+=head2 C<CanLoad($json)>
+
+This is called by base RT to determine if an initialdata file is whatever type
+is associated with this module. It must return true or false. Takes one arg,
+the content of the file to check.
+
+=cut
+
+sub CanLoad {
+    my $self = shift;
+    my $json = shift;
+    return 0 unless $json;
+
+    my $parsed;
+    eval { $parsed = JSON->new->decode($json) };
+    return 0 if ($@);
+    return 1;
+}
+
+
+=head2 C<Load($data, \@Var, ...)>
+
+This is the main routine called when initialdata file handlers are enabled. It
+is passed the file contents and refs to the arrays that will be populated from
+the file. If the file parsing fails, due to invalid JSON (generally indicating
+that the file is actually a perl initialdata file), the sub will return false.
+
+=cut
+
+sub Load {
+    my ($self, $json, $vars) = @_;
+    return 0 unless $json;
+
+    my $parsed;
+    eval { $parsed = JSON->new->decode($json) };
+    if ($@) {
+        RT::Logger->debug("Could not parse initialdata as JSON: ($@)");
+        return 0;
+    }
+
+    RT::Logger->info("JSON initialdata has unsupported 'Initial' or 'Final'; ignoring.")
+        if ($parsed->{Initial} or $parsed->{Final});
+
+    foreach (keys %$vars) {
+        # Explicitly skip Initial and Final, because we don't want any code or
+        # data that will be eval'd like these keys will be.
+        next if /Initial/ or /Final/;
+        next unless $parsed->{$_};
+        die "JSON initialdata error: The key named $_ must have a value of type array, but does not."
+            unless ref $parsed->{$_} eq 'ARRAY';
+        no strict 'refs';
+        @{$vars->{$_}} =  @{$parsed->{$_}};
+    }
+
+    return 1;
+}
+
+
+RT::Base->_ImportOverlays();
+
+1;
diff --git a/t/api/json-initialdata.t b/t/api/json-initialdata.t
new file mode 100644
index 0000000000..e9675d93c3
--- /dev/null
+++ b/t/api/json-initialdata.t
@@ -0,0 +1,73 @@
+use warnings;
+use strict;
+
+use RT::Test tests => undef;
+
+RT->Config->Set('InitialdataFormatHandlers' => [ 'perl', 'RT::Initialdata::JSON' ]);
+
+# None of this should be in the DB
+CheckDB('not_ok');
+
+# Load the db from the initialdata test file
+my $initialdata = RT::Test::get_relocatable_file("initialdata.json" => "..", "data", "initialdata");
+my ($rv, $msg) = RT->DatabaseHandle->InsertData($initialdata, undef, disconnect_after => 0);
+ok ($rv, "Insert test data from $initialdata ($msg)");
+
+# Now all of this should be in the DB
+CheckDB('ok');
+
+done_testing();
+
+sub CheckDB {
+    no strict 'refs';
+    no warnings 'uninitialized';
+
+    my $tester = shift;
+
+    my ($r,$m);
+    my $su = RT->SystemUser;
+
+    ($r,$m) = RT::Group->new($su)->LoadByCols(Name => 'Test Group 1');
+    &$tester ($r, "Test Group 1 found in DB - should be $tester ($m)");
+
+    my $tu1 = RT::User->new($su);
+    ($r,$m) = $tu1->Load('testuser1');
+    &$tester ($r, "testuser1 user found in DB - should be $tester ($m)");
+
+    my $tq1 = RT::Queue->new($su);
+    ($r,$m) = $tq1->Load('Test Queue 1');
+    &$tester ($r, "Test Queue 1 found in DB - should be $tester ($m)");
+
+    &$tester ($tu1->HasRight(Object => $tq1, Right => 'SeeQueue'),
+        "testuser1 has SeeQueue on Test Queue 1 - should be $tester"
+        ) if ($tu1->id and $tq1->id);
+
+    ($r,$m) = RT::ScripAction->new($su)->Load('Test Action 1');
+    &$tester ($r, "Test Action 1 found in DB - should be $tester ($m)");
+
+    ($r,$m) = RT::ScripCondition->new($su)->Load('Test Condition 1');
+    &$tester ($r, "Test Condition 1 found in DB - should be $tester ($m)");
+
+    ($r,$m) = RT::Template->new($su)->Load('Test Template 1');
+    &$tester ($r, "Test Template 1 found in DB - should be $tester ($m)");
+
+    ($r,$m) = RT::CustomField->new($su)->Load('Favorite Color red or blue');
+    &$tester ($r, "Favorite Color CF found in DB - should be $tester ($m)");
+
+    ($r,$m) = RT::CustomField->new($su)->Load('Favorite Song');
+    &$tester ($r, "Favorite Song CF found in DB - should be $tester ($m)");
+
+    ($r,$m) = RT::Scrip->new($su)->LoadByCols(Description => 'Test Scrip 1');
+    &$tester ($r, "Test Scrip 1 found in DB - should be $tester ($m)");
+
+    ($r,$m) = RT::Attribute->new($su)->LoadByNameAndObject(
+        Name => 'Test Search 1',
+        Object => RT->System
+        );
+    &$tester ($r, "Test Search 1 found in DB - should be $tester ($m)");
+}
+
+sub not_ok {
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+    ok (!shift, shift);
+}
diff --git a/t/data/initialdata/initialdata.json b/t/data/initialdata/initialdata.json
new file mode 100644
index 0000000000..8aa3d01784
--- /dev/null
+++ b/t/data/initialdata/initialdata.json
@@ -0,0 +1,116 @@
+{
+    "InvalidKey":"Invalid Keys and Data will be silently ignored",
+    "Groups":[
+    {
+        "Name":"Test Group 1",
+        "Description":"This is the first test group",
+        "Members":{"Users":["testuser1","root"]}
+    }
+    ],
+    "Users":[
+    {
+        "Name":"testuser1",
+        "Gecos":"testuser1-gecos",
+        "RealName":"Testuser One",
+        "Password":"password",
+        "EmailAddress":"testuser1 at somewhere",
+        "Comments":"the best user ever",
+        "Privileged":1,
+        "Disabled":0
+    }
+    ],
+    "Members":[
+    ],
+    "ACL":[
+    {
+        "Right":"SeeQueue",
+        "Queue":"Test Queue 1",
+        "UserId":"testuser1"
+    }
+    ],
+    "Queues":[
+    {
+        "Name":"Test Queue 1",
+        "Description":"Test Queue One",
+        "CommentAddress":"test-comment at example.com"
+    }
+    ],
+    "Classes":[
+    ],
+    "ScripActions":[
+    {
+        "Name":"Test Action 1",
+        "Description":"Same as Notify Requestors",
+        "ExecModule":"Notify",
+        "Argument":"Requestor"
+    }
+    ],
+    "ScripConditions":[
+    {
+        "Name":"Test Condition 1",
+        "Description":"Same as On Comment",
+        "ExecModule":"AnyTransaction",
+        "ApplicableTransTypes":"Comment"
+    }
+    ],
+    "Templates":[
+    {
+        "Queue":0,
+        "Name":"Test Template 1",
+        "Description":"A Test template",
+        "Content":"Subject: AutoReply: {$Ticket->Subject}\nContent-Type: text/html\n\n<p>Greetings,</p>\n\n<p>This message has been automatically generated in response to the \ncreation of a trouble ticket regarding <b>{$Ticket->Subject()}</b>,\na summary of which appears below.</p>\n\n<p>There is no need to reply to this message right now.  Your ticket has been\nassigned an ID of <b>{$Ticket->SubjectTag}</b>.</p>\n\n<p>Please include the string <b>{$Ticket->SubjectTag}</b>\nin the subject line of all future correspondence about this issue. To do so, \nyou may reply to this message.</p>\n\n<p>Thank you,<br/>\n{$Ticket->QueueObj->CorrespondAddress()}</p>\n\n<hr/>\n{$Transaction->Content(Type => 'text/html')}\n"
+    }
+    ],
+    "CustomFields":[
+    {
+        "Name":"Favorite Color red or blue",
+        "Type":"FreeformSingle",
+        "LookupType":"RT::Queue-RT::Ticket",
+        "ApplyTo":"Test Queue 1",
+        "Pattern":"^(red|blue)$"
+    },
+    {
+        "Name":"Favorite Song",
+        "Type":"SelectSingle",
+        "LookupType":"RT::Queue-RT::Ticket",
+        "RenderType":"Dropdown",
+        "Values":[
+            {"Name":"Never Gonna Give You Up - Rick Astley","Description":"Best Song Ever","SortOrder":1},
+            {"Name":"Nyan Cat","Description":"12 Hours Continuous","SortOrder":2}
+        ]
+    }
+    ],
+    "CustomRoles":[
+    ],
+    "Scrips":[
+    {
+        "Description":"Test Scrip 1",
+        "ScripCondition":"Test Condition 1",
+        "ScripAction":"Test Action 1",
+        "Template":"Test Template 1"
+    }
+    ],
+    "Attributes":[
+    {
+        "Name":"Test Search 1",
+        "Description":"Stalled Tickets Test 1",
+        "Content":{
+            "Query":"Status = 'stalled' AND Queue = 'Test Queue'",
+            "OrderBy":"id",
+            "Order":"DESC"
+        }
+    }
+    ],
+    "Initial":[
+        "sub {die('This Initial block will be skipped and never run')}",
+        "die('This Initial block will be skipped and never run, either')"
+    ],
+    "Final":[
+        "sub {die('This Final block will be skipped and never run')}",
+        "die('This Final block will be skipped and never run, either')"
+    ],
+    "Catalogs":[
+    ],
+    "Assets":[
+    ]
+}

commit 8b5281b21951a01d8819be33b6d8662f02fcccd7
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Thu Apr 16 03:59:45 2020 +0800

    Add ShowEditSystemConfig option to disable EditConfig page

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 20e416b1f3..3896644512 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -3598,6 +3598,18 @@ Set(%AdminSearchResultRows,
     Assets       => 50,
 );
 
+=item C<$ShowEditSystemConfig>
+
+Starting in RT 5.0, SuperUsers can edit RT system configuration via the web UI.
+Options set in the web UI take precedence over those set in configuration files.
+
+If you prefer to set configuration only via files, set C<$ShowEditSystemConfig>
+to 0 to disable the web UI editing interface.
+
+=cut
+
+Set($ShowEditSystemConfig, 1);
+
 =back
 
 
diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 9dc3c1e0e3..b71b1f5512 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -1551,6 +1551,10 @@ our %META;
     ShowBccHeader => {
         Widget => '/Widgets/Form/Boolean',
     },
+    ShowEditSystemConfig => {
+        Immutable => 1,
+        Widget    => '/Widgets/Form/Boolean',
+    },
     ShowMoreAboutPrivilegedUsers => {
         Widget => '/Widgets/Form/Boolean',
     },
diff --git a/lib/RT/Interface/Web/MenuBuilder.pm b/lib/RT/Interface/Web/MenuBuilder.pm
index 03dd352964..7aa2e09324 100644
--- a/lib/RT/Interface/Web/MenuBuilder.pm
+++ b/lib/RT/Interface/Web/MenuBuilder.pm
@@ -718,7 +718,7 @@ sub BuildMainNav {
 
     if ( $request_path =~ m{^/Admin/Tools/(Configuration|EditConfig|ConfigHistory)} ) {
         $page->child( display => title => loc('View'), path => "/Admin/Tools/Configuration.html" );
-        $page->child( modify => title => loc('Edit'), path => "/Admin/Tools/EditConfig.html" );
+        $page->child( modify => title => loc('Edit'), path => "/Admin/Tools/EditConfig.html" ) if RT->Config->Get('ShowEditSystemConfig');
         $page->child( history => title => loc('History'), path => "/Admin/Tools/ConfigHistory.html" );
     }
 
diff --git a/share/html/Admin/Tools/EditConfig.html b/share/html/Admin/Tools/EditConfig.html
index 788e1806f3..2341e6656e 100644
--- a/share/html/Admin/Tools/EditConfig.html
+++ b/share/html/Admin/Tools/EditConfig.html
@@ -46,6 +46,8 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 <%INIT>
+Abort( loc( 'Permission Denied' ) ) unless RT->Config->Get('ShowEditSystemConfig');
+
 my $title = loc('System Configuration');
 unless ($session{'CurrentUser'}->HasRight( Object=> $RT::System, Right => 'SuperUser')) {
  Abort(loc('This feature is only available to system administrators'));

commit ac47a7c33aac039ab0fe888a52ae4c9e5313d194
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Thu Apr 16 04:08:59 2020 +0800

    Test ShowEditSystemConfig option

diff --git a/t/web/admin_tools_editconfig.t b/t/web/admin_tools_editconfig.t
index 826c8adf58..f23d2fdb04 100644
--- a/t/web/admin_tools_editconfig.t
+++ b/t/web/admin_tools_editconfig.t
@@ -4,11 +4,22 @@ use warnings;
 use Test::Deep;
 use Data::Dumper ();
 
-use RT::Test tests => undef;
+use RT::Test tests => undef, config => 'Set($ShowEditSystemConfig, 0);';
 
 my ( $url, $m ) = RT::Test->started_ok;
 ok( $m->login(), 'logged in' );
 
+$m->follow_link_ok( { text => 'System Configuration' }, 'followed link to "System Configuration"' );
+ok( !$m->find_link( text => 'Edit' ), 'no edit link' );
+$m->get_ok('/Admin/Tools/EditConfig.html');
+$m->content_contains('Permission Denied');
+
+RT::Test->stop_server;
+RT->Config->Set( ShowEditSystemConfig => 1 );
+
+( $url, $m ) = RT::Test->started_ok;
+ok( $m->login(), 'logged in' );
+
 $m->follow_link_ok( { text => 'System Configuration' }, 'followed link to "System Configuration"' );
 $m->follow_link_ok( { text => 'History' }, 'followed link to History page' );
 $m->follow_link_ok( { text => 'Edit' }, 'followed link to Edit page' );

commit 8bcc0421b1f6d0703990d0d243353ab2939be43c
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Tue Apr 28 11:06:46 2020 -0400

    Note web UI configuration in upgrading doc

diff --git a/docs/UPGRADING-5.0 b/docs/UPGRADING-5.0
index 64b533f828..a458f24289 100644
--- a/docs/UPGRADING-5.0
+++ b/docs/UPGRADING-5.0
@@ -49,6 +49,17 @@ characters and are not effected by this update.
 
 =item *
 
+System configuration options can now be changed by SuperUsers via the
+web UI. File-based configuration options are still loaded. Changes made
+via the web UI take precedence over file-based options if both are set.
+
+If you prefer to keep all configuration in files and disable editing in
+the web UI, set this option to 0:
+
+    Set($ShowEditSystemConfig, 0);
+
+=item *
+
 The variables which alter the set of HTML elements allowed in HTML
 scrubbing have moved; they have been renamed, and are now found under
 L<RT::Interface::Web::Scrubber>.

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


More information about the rt-commit mailing list