From: dklawren Date: Tue, 20 Nov 2018 21:52:42 +0000 (-0500) Subject: Bug 1354589 - Implement OAuth2 on BMO (Client Admin) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=213fd9801acdc5e83b49aed15558fd7aee3ca4b1;p=thirdparty%2Fbugzilla.git Bug 1354589 - Implement OAuth2 on BMO (Client Admin) --- diff --git a/Bugzilla/Quantum/OAuth2.pm b/Bugzilla/Quantum/OAuth2.pm index 87d1aaf0a..62a1e1c50 100644 --- a/Bugzilla/Quantum/OAuth2.pm +++ b/Bugzilla/Quantum/OAuth2.pm @@ -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 index 000000000..9a81f821d --- /dev/null +++ b/Bugzilla/Quantum/OAuth2/Clients.pm @@ -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; diff --git a/template/en/default/admin/admin.html.tmpl b/template/en/default/admin/admin.html.tmpl index a9bb811ee..4924536d4 100644 --- a/template/en/default/admin/admin.html.tmpl +++ b/template/en/default/admin/admin.html.tmpl @@ -133,6 +133,10 @@
View the queue of undelivered/deferred jobs/emails.
[% END %] + [% class = user.in_group('admin') ? "" : "forbidden" %] +
OAuth2 Clients
+
Add, modify or remove OAuth2 clients.
+ [% Hook.process('end_links_right') %] 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 index 000000000..64bae7ab4 --- /dev/null +++ b/template/en/default/admin/oauth/confirm-delete.html.tmpl @@ -0,0 +1,31 @@ +[% PROCESS global/header.html.tmpl + title = "Delete OAuth2 Client" +%] + + + + + + + + + + + + + + +
FieldValue
Client Description[% client.description FILTER html %]
Client ID[% client.id FILTER html %]
+ +

Confirmation

+ +

Do you really want to delete this client?

+ +

+ + + + +
+ +[% 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 index 000000000..f9f5973b9 --- /dev/null +++ b/template/en/default/admin/oauth/create.html.tmpl @@ -0,0 +1,45 @@ +[% PROCESS global/header.html.tmpl + title = "Add OAuth2 Client" + onload = "document.forms['f'].client_id.focus()" +%] + +
+ + + + + + + + + + + + + + + + + +
[% id FILTER html %]
[% secret FILTER html %]
+ + [% FOREACH scope = scopes %] + + + + + [% END %] +
+ + + [% scope.description FILTER html %] +
+
+ + + + +
+ +[% 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 index 000000000..e25ee82dc --- /dev/null +++ b/template/en/default/admin/oauth/edit.html.tmpl @@ -0,0 +1,52 @@ +[% PROCESS global/header.html.tmpl + title = "Edit OAuth2 Client" + onload = "document.forms['f'].client_id.select()" +%] + +
+ + + + + + + + + + + + + + + + + + + + + +
[% client.id FILTER html %]
[% client.secret FILTER html %]
+ + [% FOREACH scope = scopes %] + + + + + [% END %] +
+ + + [% scope.description FILTER html %] +
+
+ + + + +
+ +[% 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 index 000000000..35b1c416b --- /dev/null +++ b/template/en/default/admin/oauth/list.html.tmpl @@ -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 +%] + +Add a client. + +[% PROCESS global/footer.html.tmpl %]