]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 1361151 - Bugzilla Security groups are periodically synced to Phabricator project...
authorDavid Lawrence <dkl@mozilla.com>
Mon, 22 May 2017 14:47:27 +0000 (14:47 +0000)
committerDavid Lawrence <dkl@mozilla.com>
Mon, 22 May 2017 14:47:27 +0000 (14:47 +0000)
extensions/PhabBugz/Config.pm [new file with mode: 0644]
extensions/PhabBugz/Extension.pm [new file with mode: 0644]
extensions/PhabBugz/bin/update_project_members.pl [new file with mode: 0755]
extensions/PhabBugz/lib/Config.pm [new file with mode: 0644]
extensions/PhabBugz/template/en/default/admin/params/phabbugz.html.tmpl [new file with mode: 0644]
extensions/PhabBugz/template/en/default/hook/global/code-error-errors.html.tmpl [new file with mode: 0644]
extensions/PhabBugz/template/en/default/hook/global/user-error-errors.html.tmpl [new file with mode: 0644]
t/Support/Files.pm

diff --git a/extensions/PhabBugz/Config.pm b/extensions/PhabBugz/Config.pm
new file mode 100644 (file)
index 0000000..008838c
--- /dev/null
@@ -0,0 +1,16 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Extension::PhabBugz;
+
+use 5.10.1;
+use strict;
+use warnings;
+
+use constant NAME => 'PhabBugz';
+
+__PACKAGE__->NAME;
diff --git a/extensions/PhabBugz/Extension.pm b/extensions/PhabBugz/Extension.pm
new file mode 100644 (file)
index 0000000..501fbc6
--- /dev/null
@@ -0,0 +1,23 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Extension::PhabBugz;
+
+use 5.10.1;
+use strict;
+use warnings;
+use parent qw(Bugzilla::Extension);
+
+our $VERSION = '0.01';
+
+sub config_add_panels {
+    my ($self, $args) = @_;
+    my $modules = $args->{panel_modules};
+    $modules->{PhabBugz} = "Bugzilla::Extension::PhabBugz::Config";
+}
+
+__PACKAGE__->NAME;
diff --git a/extensions/PhabBugz/bin/update_project_members.pl b/extensions/PhabBugz/bin/update_project_members.pl
new file mode 100755 (executable)
index 0000000..712368d
--- /dev/null
@@ -0,0 +1,187 @@
+#!/usr/bin/perl
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+use 5.10.1;
+use strict;
+use warnings;
+
+use lib qw(. lib local/lib/perl5);
+
+use Bugzilla;
+BEGIN { Bugzilla->extensions() }
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Group;
+
+use LWP::UserAgent;
+use JSON qw(encode_json decode_json);
+
+Bugzilla->usage_mode(USAGE_MODE_CMDLINE);
+
+my ($phab_uri, $phab_api_key, $phab_sync_groups, $ua);
+
+# Sanity checks
+unless ($phab_uri = Bugzilla->params->{phabricator_base_uri}) {
+    ThrowUserError('invalid_phabricator_uri');
+}
+
+unless ($phab_api_key = Bugzilla->params->{phabricator_api_key}) {
+    ThrowUserError('invalid_phabricator_api_key');
+}
+
+unless ($phab_sync_groups = Bugzilla->params->{phabricator_sync_groups}) {
+    ThrowUserError('invalid_phabricator_sync_groups');
+}
+
+# Loop through each group and perform the following:
+#
+# 1. Load flattened list of group members
+# 2. Check to see if Phab project exists for 'bmo-<group_name>'
+# 3. Create if does not exist with locked down policy.
+# 4. Set project members to exact list
+# 5. Profit
+
+my $sync_groups = Bugzilla::Group->match({ name => [ split('[,\s]+', $phab_sync_groups) ] });
+
+foreach my $group (@$sync_groups) {
+    my @users = get_group_members($group);
+
+    # Create group project if one does not yet exist
+    my $phab_project_name = 'bmo-' . $group->name;
+    my $project_id = get_phab_project($phab_project_name);
+    if (!$project_id) {
+        $project_id = create_phab_project($phab_project_name, 'BMO Security Group for ' . $group->name);
+    }
+
+    # Get the internal user ids for the bugzilla group members
+    my $phab_user_ids = get_phab_members_by_bmo_id(\@users);
+
+    # Set the project members to the exact list
+    set_phab_project_members($project_id, $phab_user_ids);
+}
+
+# Bugzilla
+
+sub get_group_members {
+    my ($group) = @_;
+    my $group_obj = ref $group ? $group : Bugzilla::Group->check({ name => $group });
+    my $members_all = $group_obj->members_complete();
+    my %users;
+    foreach my $name (keys %$members_all) {
+        foreach my $user (@{ $members_all->{$name} }) {
+            $users{$user->id} = $user;
+        }
+    }
+    return values %users;
+}
+
+# Projects
+
+sub get_phab_project {
+    my ($project) = @_;
+
+    my $data = {
+        queryKey => 'active',
+        constraints => {
+            name => $project
+        }
+    };
+
+    my $result = request('project.search', $data);
+    if (!$result->{result}{data}) {
+        return undef;
+    }
+    return $result->{result}{data}[0]{phid};
+}
+
+sub create_phab_project {
+    my ($project, $description, $members) = @_;
+
+    my $data = {
+        transactions => [
+            { type => 'name',  value => $project },
+            { type => 'description', value => $description },
+            { type => 'edit',  value => 'admin'},
+            { type => 'join',  value => 'admin' },
+            { type => 'icon',  value => 'group' },
+            { type => 'color', value => 'red' }
+        ]
+    };
+
+    my $result = request('project.edit', $data);
+    return $result->{result}{object}{phid};
+}
+
+sub set_phab_project_members {
+    my ($project_id, $phab_user_ids) = @_;
+
+    my $data = {
+        objectIdentifier => $project_id,
+        transactions => [
+            { type => 'members.set',  value => $phab_user_ids }
+        ]
+    };
+
+    my $result = request('project.edit', $data);
+    return $result->{result}{object}{phid};
+}
+
+# Members
+
+sub get_phab_members_by_bmo_id {
+    my ($users) = @_;
+
+    my $data = {
+        accountids => [ map { $_->id } @$users ]
+    };
+
+    my $result = request('bmoexternalaccount.search', $data);
+    if (!$result->{result}) {
+        return [];
+    }
+
+    my @phab_ids;
+    foreach my $user (@{ $result->{result} }) {
+        push(@phab_ids, $user->{phid});
+    }
+    return \@phab_ids;
+}
+
+# Utility
+
+sub request {
+    my ($method, $data) = @_;
+
+    if (!$ua) {
+        $ua = LWP::UserAgent->new(timeout => 10);
+        if (Bugzilla->params->{proxy_url}) {
+            $ua->proxy('https', Bugzilla->params->{proxy_url});
+        }
+        $ua->default_header('Content-Type' => 'application/x-www-form-urlencoded');
+    }
+
+    my $full_uri = $phab_uri . '/api/' . $method;
+
+    $data->{__conduit__} = { token => $phab_api_key };
+
+    my $response = $ua->post($full_uri, { params => encode_json($data) });
+
+    $response->is_error
+        && ThrowCodeError('phabricator_api_error',
+                          { reason => $response->message });
+
+    my $result = decode_json($response->content);
+    if ($result->{error_code}) {
+        ThrowCodeError('phabricator_api_error',
+                       { code   => $result->{error_code},
+                         reason => $result->{error_info} });
+    }
+    return $result;
+}
diff --git a/extensions/PhabBugz/lib/Config.pm b/extensions/PhabBugz/lib/Config.pm
new file mode 100644 (file)
index 0000000..686198a
--- /dev/null
@@ -0,0 +1,43 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::Extension::PhabBugz::Config;
+
+use 5.10.1;
+use strict;
+use warnings;
+
+use Bugzilla::Config::Common;
+
+our $sortkey = 1300;
+
+sub get_param_list {
+    my ($class) = @_;
+
+    my @params = (
+        {
+            name    => 'phabricator_base_uri',
+            type    => 't',
+            default => '',
+            checker => \&check_urlbase
+        },
+        {
+            name    => 'phabricator_sync_groups',
+            type    => 't',
+            default => '',
+        },
+        {
+            name => 'phabricator_api_key',
+            type => 't',
+            default => '',
+        },
+    );
+
+    return @params;
+}
+
+1;
diff --git a/extensions/PhabBugz/template/en/default/admin/params/phabbugz.html.tmpl b/extensions/PhabBugz/template/en/default/admin/params/phabbugz.html.tmpl
new file mode 100644 (file)
index 0000000..922fcf7
--- /dev/null
@@ -0,0 +1,20 @@
+[%# This Source Code Form is subject to the terms of the Mozilla Public
+  # License, v. 2.0. If a copy of the MPL was not distributed with this
+  # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+  #
+  # This Source Code Form is "Incompatible With Secondary Licenses", as
+  # defined by the Mozilla Public License, v. 2.0.
+  #%]
+
+[%
+   title = "PhabBugz"
+   desc = "Configure Phabricator Integration"
+%]
+
+[%
+   param_descs = {
+     phabricator_base_uri    => 'Phabricator Base URI',
+     phabricator_api_key     => 'Phabricator User API Key',
+     phabricator_sync_groups => 'Comma delimited list of Bugzilla groups to sync to Phabricator projects',
+   }
+%]
diff --git a/extensions/PhabBugz/template/en/default/hook/global/code-error-errors.html.tmpl b/extensions/PhabBugz/template/en/default/hook/global/code-error-errors.html.tmpl
new file mode 100644 (file)
index 0000000..11dbfc2
--- /dev/null
@@ -0,0 +1,13 @@
+[%# This Source Code Form is subject to the terms of the Mozilla Public
+  # License, v. 2.0. If a copy of the MPL was not distributed with this
+  # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+  #
+  # This Source Code Form is "Incompatible With Secondary Licenses", as
+  # defined by the Mozilla Public License, v. 2.0.
+  #%]
+
+[% IF error == "phabricator_api_error" %]
+  [% title = "Phabricator API Error" %]
+  [% IF code %]Code: [% code FILTER html %]<br>[% END %]
+  Reason: [% reason FILTER html %]
+[% END %]
diff --git a/extensions/PhabBugz/template/en/default/hook/global/user-error-errors.html.tmpl b/extensions/PhabBugz/template/en/default/hook/global/user-error-errors.html.tmpl
new file mode 100644 (file)
index 0000000..6959c75
--- /dev/null
@@ -0,0 +1,22 @@
+[%# This Source Code Form is subject to the terms of the Mozilla Public
+  # License, v. 2.0. If a copy of the MPL was not distributed with this
+  # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+  #
+  # This Source Code Form is "Incompatible With Secondary Licenses", as
+  # defined by the Mozilla Public License, v. 2.0.
+  #%]
+
+[% IF error == "invalid_phabricator_uri" %]
+  [% title = "Invalid Phabricator URI" %]
+  You must provide a valid Phabricator URI.
+
+[% ELSIF error == "invalid_phabricator_api_key" %]
+  [% title = "Invalid Phabricator API Key" %]
+  You must provide a valid Phabricator API Key.
+
+[% ELSIF error == "invalid_phabricator_sync_groups" %]
+  [% title = "Invalid Phabricator Sync Groups" %]
+  You must provide a comma delimited list of security groups
+  to sync with Phabricator.
+
+[% END %]
index 1bdf2eac70aa6007867233159504605b5fbd7d84..603d95cf9544c8f932e5b9ad30b3e7bd095406c2 100644 (file)
@@ -30,7 +30,7 @@ our @extensions =
     glob('extensions/*');
 
 foreach my $extension (@extensions) {
-    find(sub { push(@files, $File::Find::name) if $_ =~ /\.pm$/;}, $extension);
+    find(sub { push(@files, $File::Find::name) if $_ =~ /\.pm$|\.pl$/;}, $extension);
 }
 
 our @test_files = glob('t/*.t xt/*/*.t');