summaryrefslogtreecommitdiffstats
path: root/extensions/PhabBugz
diff options
context:
space:
mode:
authorDavid Lawrence <dkl@mozilla.com>2017-05-22 16:47:27 +0200
committerDavid Lawrence <dkl@mozilla.com>2017-05-22 16:47:27 +0200
commit44cef9cca23e9dd59ded0039efe1fb4454c2eb57 (patch)
tree93d301c57f82f9b5eacdaf46c47bcd703f5dfabb /extensions/PhabBugz
parentd7e7c68ec34a994e2b089c5db1986031a6399a9a (diff)
downloadbugzilla-44cef9cca23e9dd59ded0039efe1fb4454c2eb57.tar.gz
bugzilla-44cef9cca23e9dd59ded0039efe1fb4454c2eb57.tar.xz
Bug 1361151 - Bugzilla Security groups are periodically synced to Phabricator project membership
Diffstat (limited to 'extensions/PhabBugz')
-rw-r--r--extensions/PhabBugz/Config.pm16
-rw-r--r--extensions/PhabBugz/Extension.pm23
-rwxr-xr-xextensions/PhabBugz/bin/update_project_members.pl187
-rw-r--r--extensions/PhabBugz/lib/Config.pm43
-rw-r--r--extensions/PhabBugz/template/en/default/admin/params/phabbugz.html.tmpl20
-rw-r--r--extensions/PhabBugz/template/en/default/hook/global/code-error-errors.html.tmpl13
-rw-r--r--extensions/PhabBugz/template/en/default/hook/global/user-error-errors.html.tmpl22
7 files changed, 324 insertions, 0 deletions
diff --git a/extensions/PhabBugz/Config.pm b/extensions/PhabBugz/Config.pm
new file mode 100644
index 000000000..008838c1b
--- /dev/null
+++ b/extensions/PhabBugz/Config.pm
@@ -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
index 000000000..501fbc65d
--- /dev/null
+++ b/extensions/PhabBugz/Extension.pm
@@ -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
index 000000000..712368d2d
--- /dev/null
+++ b/extensions/PhabBugz/bin/update_project_members.pl
@@ -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
index 000000000..686198a25
--- /dev/null
+++ b/extensions/PhabBugz/lib/Config.pm
@@ -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
index 000000000..922fcf709
--- /dev/null
+++ b/extensions/PhabBugz/template/en/default/admin/params/phabbugz.html.tmpl
@@ -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
index 000000000..11dbfc2eb
--- /dev/null
+++ b/extensions/PhabBugz/template/en/default/hook/global/code-error-errors.html.tmpl
@@ -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
index 000000000..6959c759d
--- /dev/null
+++ b/extensions/PhabBugz/template/en/default/hook/global/user-error-errors.html.tmpl
@@ -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 %]