]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 1354589 - Implement OAuth2 on BMO (Client Admin)
authordklawren <dklawren@users.noreply.github.com>
Tue, 20 Nov 2018 21:52:42 +0000 (16:52 -0500)
committerDylan William Hardison <dylan@hardison.net>
Tue, 20 Nov 2018 21:52:42 +0000 (16:52 -0500)
Bugzilla/Quantum/OAuth2.pm
Bugzilla/Quantum/OAuth2/Clients.pm [new file with mode: 0644]
template/en/default/admin/admin.html.tmpl
template/en/default/admin/oauth/confirm-delete.html.tmpl [new file with mode: 0644]
template/en/default/admin/oauth/create.html.tmpl [new file with mode: 0644]
template/en/default/admin/oauth/edit.html.tmpl [new file with mode: 0644]
template/en/default/admin/oauth/list.html.tmpl [new file with mode: 0644]

index 87d1aaf0a43e3831463c06d60f154d17a402c93f..62a1e1c5003191425cddb5d5db375aa85d0c8999 100644 (file)
@@ -40,6 +40,25 @@ sub oauth2 {
     }
   );
 
+  # Manage the client list
+  my $r            = $self->routes;
+  my $client_route = $r->under(
+    '/admin/oauth' => sub {
+      my ($c) = @_;
+      my $user = $c->bugzilla->login(LOGIN_REQUIRED) || return undef;
+      $user->in_group('admin')
+        || ThrowUserError('auth_failure',
+        {group => 'admin', action => 'edit', object => 'oauth_clients'});
+      return 1;
+    }
+  );
+  $client_route->any('/list')->to('OAuth2::Clients#list')->name('list_clients');
+  $client_route->any('/create')->to('OAuth2::Clients#create')
+    ->name('create_client');
+  $client_route->any('/delete')->to('OAuth2::Clients#delete')
+    ->name('delete_client');
+  $client_route->any('/edit')->to('OAuth2::Clients#edit')->name('edit_client');
+
   $self->helper(
     'bugzilla.oauth' => sub {
         my ($c, @scopes) = @_;
diff --git a/Bugzilla/Quantum/OAuth2/Clients.pm b/Bugzilla/Quantum/OAuth2/Clients.pm
new file mode 100644 (file)
index 0000000..9a81f82
--- /dev/null
@@ -0,0 +1,202 @@
+# 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::Quantum::OAuth2::Clients;
+use Mojo::Base 'Mojolicious::Controller';
+
+use 5.10.1;
+use List::Util qw(first);
+use Moo;
+
+use Bugzilla;
+use Bugzilla::Error;
+use Bugzilla::Token;
+use Bugzilla::Util qw(generate_random_password);
+
+# Show list of clients
+sub list {
+  my ($self) = @_;
+  my $clients = Bugzilla->dbh->selectall_arrayref('SELECT * FROM oauth2_client',
+    {Slice => {}});
+  $self->stash(clients => $clients);
+  return $self->render(template => 'admin/oauth/list', handler => 'bugzilla');
+}
+
+# Create new client
+sub create {
+  my ($self) = @_;
+  my $dbh    = Bugzilla->dbh;
+  my $vars   = {};
+
+  if ($self->req->method ne 'POST') {
+    $vars->{id}     = generate_random_password(20);
+    $vars->{secret} = generate_random_password(40);
+    $vars->{token}  = issue_session_token('create_oauth_client');
+    $vars->{scopes}
+      = $dbh->selectall_arrayref('SELECT * FROM oauth2_scope', {Slice => {}});
+    $self->stash(%{$vars});
+    return $self->render(template => 'admin/oauth/create',
+      handler => 'bugzilla');
+  }
+
+  $dbh->bz_start_transaction;
+
+  my $description = $self->param('description');
+  my $id          = $self->param('id');
+  my $secret      = $self->param('secret');
+  my @scopes      = $self->param('scopes');
+  $description || ThrowCodeError('param_required', {param => 'description'});
+  $id          || ThrowCodeError('param_required', {param => 'id'});
+  $secret      || ThrowCodeError('param_required', {param => 'secret'});
+  @scopes      || ThrowCodeError('param_required', {param => 'scopes'});
+  my $token = $self->param('token');
+  check_token_data($token, 'create_oauth_client');
+
+
+  $dbh->do(
+    'INSERT INTO oauth2_client (id, description, secret) VALUES (?, ?, ?)',
+    undef, $id, $description, $secret);
+
+  foreach my $scope_id (@scopes) {
+    $scope_id
+      = $dbh->selectrow_array('SELECT id FROM oauth2_scope WHERE id = ?',
+      undef, $scope_id);
+    if (!$scope_id) {
+      ThrowCodeError('param_required', {param => 'scopes'});
+    }
+    $dbh->do(
+      'INSERT INTO oauth2_client_scope (client_id, scope_id, allowed) VALUES (?, ?, 1)',
+      undef, $id, $scope_id
+    );
+  }
+
+  delete_token($token);
+
+  my $clients
+    = $dbh->selectall_arrayref('SELECT * FROM oauth2_client', {Slice => {}});
+
+  $dbh->bz_commit_transaction;
+
+  $vars->{'message'} = 'oauth_client_created';
+  $vars->{'client'}  = {description => $description};
+  $vars->{'clients'} = $clients;
+  $self->stash(%{$vars});
+  return $self->render(template => 'admin/oauth/list', handler => 'bugzilla');
+}
+
+# Delete client
+sub delete {
+  my ($self) = @_;
+  my $dbh    = Bugzilla->dbh;
+  my $vars   = {};
+
+  my $id = $self->param('id');
+  my $client
+    = $dbh->selectrow_hashref('SELECT * FROM oauth2_client WHERE id = ?',
+    undef, $id);
+
+  if (!$self->param('deleteme')) {
+    $vars->{'client'} = $client;
+    $vars->{'token'}  = issue_session_token('delete_oauth_client');
+    $self->stash(%{$vars});
+    return $self->render(
+      template => 'admin/oauth/confirm-delete',
+      handler  => 'bugzilla'
+    );
+  }
+
+  $dbh->bz_start_transaction;
+
+  my $token = $self->param('token');
+  check_token_data($token, 'delete_oauth_client');
+
+  $dbh->do('DELETE FROM oauth2_client WHERE id = ?', undef, $id);
+
+  delete_token($token);
+
+  my $clients
+    = $dbh->selectall_arrayref('SELECT * FROM oauth2_client', {Slice => {}});
+
+  $dbh->bz_commit_transaction;
+
+  $vars->{'message'} = 'oauth_client_deleted';
+  $vars->{'client'}  = {description => $client->{description}};
+  $vars->{'clients'} = $clients;
+  $self->stash(%{$vars});
+  return $self->render(template => 'admin/oauth/list', handler => 'bugzilla');
+}
+
+#  Edit client
+sub edit {
+  my ($self) = @_;
+  my $dbh    = Bugzilla->dbh;
+  my $vars   = {};
+  my $id     = $self->param('id');
+
+  my $client
+    = $dbh->selectrow_hashref('SELECT * FROM oauth2_client WHERE id = ?',
+    undef, $id);
+  my $client_scopes
+    = $dbh->selectall_arrayref(
+    'SELECT scope_id FROM oauth2_client_scope WHERE client_id = ?',
+    undef, $id);
+  $client->{scopes} = [map { $_->[0] } @{$client_scopes}];
+  $vars->{client} = $client;
+
+  # All scopes
+  my $all_scopes
+    = $dbh->selectall_arrayref('SELECT * FROM oauth2_scope', {Slice => {}});
+  $vars->{scopes} = $all_scopes;
+
+  if ($self->req->method ne 'POST') {
+    $vars->{token} = issue_session_token('edit_oauth_client');
+    $self->stash(%{$vars});
+    return $self->render(template => 'admin/oauth/edit', handler => 'bugzilla');
+  }
+
+  $dbh->bz_start_transaction;
+
+  my $token = $self->param('token');
+  check_token_data($token, 'edit_oauth_client');
+
+  my $description = $self->param('description');
+  my $active      = $self->param('active');
+  my @scopes      = $self->param('scopes');
+
+  if ($description ne $client->{description}) {
+    $dbh->do('UPDATE oauth2_client SET description = ? WHERE id = ?',
+      undef, $description, $id);
+  }
+
+  if ($active ne $client->{active}) {
+    $dbh->do('UPDATE oauth2_client SET active = ? WHERE id = ?',
+      undef, $active, $id);
+  }
+
+  $dbh->do('DELETE FROM oauth2_client_scope WHERE client_id = ?', undef, $id);
+  foreach my $scope_id (@scopes) {
+    $dbh->do(
+      'INSERT INTO oauth2_client_scope (client_id, scope_id, allowed) VALUES (?, ?, 1)',
+      undef, $id, $scope_id
+    );
+  }
+
+  delete_token($token);
+
+  my $clients
+    = $dbh->selectall_arrayref('SELECT * FROM oauth2_client', {Slice => {}});
+
+  $dbh->bz_commit_transaction;
+
+  $vars->{'message'} = 'oauth_client_updated';
+  $vars->{'client'}  = {description => $description};
+  $vars->{'clients'} = $clients;
+  $self->stash(%{$vars});
+  return $self->render(template => 'admin/oauth/list', handler => 'bugzilla');
+}
+
+1;
index a9bb811ee115c1f5b14d8186e9bc536084c5db7f..4924536d41b5aee3960381702a26cae4f09350f9 100644 (file)
           <dd class="[% class %]">View the queue of undelivered/deferred jobs/emails.</dd>
         [% END %]
 
+        [% class = user.in_group('admin') ? "" : "forbidden" %]
+        <dt id="custom_fields" class="[% class %]"><a href="/admin/oauth/list">OAuth2 Clients</a></dt>
+        <dd class="[% class %]">Add, modify or remove OAuth2 clients.</dd>
+
         [% Hook.process('end_links_right') %]
       </dl>
     </td>
diff --git a/template/en/default/admin/oauth/confirm-delete.html.tmpl b/template/en/default/admin/oauth/confirm-delete.html.tmpl
new file mode 100644 (file)
index 0000000..64bae7a
--- /dev/null
@@ -0,0 +1,31 @@
+[% PROCESS global/header.html.tmpl
+  title = "Delete OAuth2 Client"
+%]
+
+<table border="1" cellpadding="4" cellspacing="0">
+<tr bgcolor="#6666FF">
+  <th valign="top" align="left">Field</th>
+  <th valign="top" align="left">Value</th>
+</tr>
+<tr>
+  <td valign="top">Client Description</td>
+  <td valign="top">[% client.description FILTER html %]</td>
+</tr>
+<tr>
+  <td valign="top">Client ID</td>
+  <td valign="top">[% client.id FILTER html %]</td>
+</tr>
+</table>
+
+<h2>Confirmation</h2>
+
+<p>Do you really want to delete this client?<p>
+
+<form method="post" action="[% basepath FILTER none %]admin/oauth/delete">
+  <input type="submit" id="delete" value="Yes, delete">
+  <input type="hidden" name="id" value="[% client.id FILTER html %]">
+  <input type="hidden" name="deleteme" value="delete">
+  <input type="hidden" name="token" value="[% token FILTER html %]">
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/oauth/create.html.tmpl b/template/en/default/admin/oauth/create.html.tmpl
new file mode 100644 (file)
index 0000000..f9f5973
--- /dev/null
@@ -0,0 +1,45 @@
+[% PROCESS global/header.html.tmpl
+  title = "Add OAuth2 Client"
+  onload = "document.forms['f'].client_id.focus()"
+%]
+
+<form name="f" method="post" action="[% basepath FILTER none %]admin/oauth/create">
+  <table border="0" cellpadding="4" cellspacing="0">
+    <tr>
+      <th align="right"><label for="description">Client Description:</label></th>
+      <td><input id="description" size="20" maxlength="20" name="description"></td>
+    </tr>
+    <tr>
+      <th align="right"><label for="id">Client ID:</label></th>
+      <td>[% id FILTER html %]</td>
+    </tr>
+    <tr>
+      <th align="right"><label for="secret">Client Secret:</label></th>
+      <td>[% secret FILTER html %]</td>
+    </tr>
+    <tr>
+      <th class="field_label"><label for="scopes">Scopes:</label></th>
+      <td>
+        <table>
+        [% FOREACH scope = scopes %]
+          <tr>
+            <td>
+              <input id="scope_[% scope.id FILTER html %]"
+                     name="scopes" type="checkbox" value="[% scope.id FILTER html %]">
+            </td>
+            <td>
+              [% scope.description FILTER html %]
+            </td>
+          </tr>
+        [% END %]
+        </table>
+      </td>
+    </tr>
+  </table>
+  <input type="submit" id="create" value="Add">
+  <input type="hidden" name="token" value="[% token FILTER html %]">
+  <input type="hidden" name="id" value="[% id FILTER html %]">
+  <input type="hidden" name="secret" value="[% secret FILTER html %]">
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/oauth/edit.html.tmpl b/template/en/default/admin/oauth/edit.html.tmpl
new file mode 100644 (file)
index 0000000..e25ee82
--- /dev/null
@@ -0,0 +1,52 @@
+[% PROCESS global/header.html.tmpl
+  title = "Edit OAuth2 Client"
+  onload = "document.forms['f'].client_id.select()"
+%]
+
+<form name="f" method="post" action="[% basepath FILTER none %]admin/oauth/edit">
+  <table border="0" cellpadding="4" cellspacing="0">
+    <tr>
+      <th class="field_label"><label for="description">Client Description:</label></th>
+      <td><input id="description" size="20" maxlength="20" name="description" value="
+      [%- client.description FILTER html %]"></td>
+    </tr>
+    <tr>
+      <th class="field_label"><label for="id">Client ID:</label></th>
+      <td>[% client.id FILTER html %]</td>
+    </tr>
+    <tr>
+      <th class="field_label"><label for="secret">Client Secret:</label></th>
+      <td>[% client.secret FILTER html %]</td>
+    </tr>
+    <tr>
+      <th class="field_label"><label for="active">Active:</label></th>
+      <td><input id="active" name="active" type="checkbox" value="1"
+                 [%+ 'checked="checked"' IF client.active %]></td>
+    </tr>
+    <tr>
+      <th class="field_label"><label for="scopes">Scopes:</label></th>
+      <td>
+        <table>
+        [% FOREACH scope = scopes %]
+          <tr>
+            <td>
+              <input id="scope_[% scope.id FILTER html %]"
+                     name="scopes" type="checkbox" value="[% scope.id FILTER html %]"
+                 [% ' checked="checked"' IF client.scopes.contains(scope.id) %]>
+            </td>
+            <td>
+              [% scope.description FILTER html %]
+            </td>
+          </tr>
+        [% END %]
+        </table>
+      </td>
+    </tr>
+  </table>
+
+  <input type="hidden" name="id" value="[% client.id FILTER html %]">
+  <input type="hidden" name="token" value="[% token FILTER html %]">
+  <input type="submit" id="update" value="Save Changes">
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/admin/oauth/list.html.tmpl b/template/en/default/admin/oauth/list.html.tmpl
new file mode 100644 (file)
index 0000000..35b1c41
--- /dev/null
@@ -0,0 +1,35 @@
+[% PROCESS global/header.html.tmpl
+  title = "Select OAuth2 Client"
+%]
+
+[% columns = [
+     {
+       name => "description"
+       heading => "Edit client..."
+       contentlink => "admin/oauth/edit?id=%%id%%"
+     },
+     {
+       name => "active"
+       heading => "Active"
+       yesno_field => 1
+     }
+     {
+       name => "action"
+       heading => "Action"
+       content => "Delete"
+       contentlink => "admin/oauth/delete?id=%%id%%"
+     }
+   ]
+%]
+
+[% Hook.process('before_table') %]
+
+[% PROCESS admin/table.html.tmpl
+     columns = columns
+     data = clients
+     overrides = overrides
+%]
+
+<a title="Add a client" href="[% basepath FILTER none %]admin/oauth/create">Add</a> a client.
+
+[% PROCESS global/footer.html.tmpl %]