diff options
author | Dylan William Hardison <dylan@mozilla.com> | 2015-05-22 18:54:38 +0200 |
---|---|---|
committer | Dylan William Hardison <dylan@hardison.net> | 2015-05-22 18:55:10 +0200 |
commit | d8cbd5b5c59f0c66772df100a4b28d4e26450771 (patch) | |
tree | c328d1a5b84989ab0c98d9975d8eefa51e1a477a | |
parent | 42d961c8712af7cbbb08d5eff1e55aa2c81c01a8 (diff) | |
download | bugzilla-d8cbd5b5c59f0c66772df100a4b28d4e26450771.tar.gz bugzilla-d8cbd5b5c59f0c66772df100a4b28d4e26450771.tar.xz |
Bug 1144468: Bugzilla Auth Delegation via API Keys
r=dkl,a=glob
-rw-r--r-- | Bugzilla/Config/Auth.pm | 5 | ||||
-rw-r--r-- | Bugzilla/Token.pm | 49 | ||||
-rwxr-xr-x | auth.cgi | 88 | ||||
-rw-r--r-- | docs/en/rst/administering/parameters.rst | 3 | ||||
-rw-r--r-- | docs/en/rst/api/core/v1/general.rst | 4 | ||||
-rw-r--r-- | docs/en/rst/integrating/auth-delegation.rst | 30 | ||||
-rw-r--r-- | docs/en/rst/integrating/index.rst | 1 | ||||
-rw-r--r-- | template/en/default/account/auth/delegation.html.tmpl | 37 | ||||
-rw-r--r-- | template/en/default/admin/params/auth.html.tmpl | 9 | ||||
-rw-r--r-- | template/en/default/global/user-error.html.tmpl | 23 |
10 files changed, 246 insertions, 3 deletions
diff --git a/Bugzilla/Config/Auth.pm b/Bugzilla/Config/Auth.pm index 78d719b15..3c9ee31f2 100644 --- a/Bugzilla/Config/Auth.pm +++ b/Bugzilla/Config/Auth.pm @@ -121,6 +121,11 @@ sub get_param_list { type => 'b', default => '1' }, + { + name => 'auth_delegation', + type => 'b', + default => 0, + }, ); return @param_list; } diff --git a/Bugzilla/Token.pm b/Bugzilla/Token.pm index a8358d4a7..c43ba9f07 100644 --- a/Bugzilla/Token.pm +++ b/Bugzilla/Token.pm @@ -25,6 +25,7 @@ use Digest::SHA qw(hmac_sha256_base64); use parent qw(Exporter); @Bugzilla::Token::EXPORT = qw(issue_api_token issue_session_token + issue_auth_delegation_token check_auth_delegation_token check_token_data delete_token issue_hash_token check_hash_token); @@ -46,6 +47,37 @@ sub issue_api_token { return $token // _create_token($user->id, 'api_token', ''); } +sub issue_auth_delegation_token { + my ($uri) = @_; + my $dbh = Bugzilla->dbh; + my $user = Bugzilla->user; + my $checksum = hmac_sha256_base64($user->id, $uri, Bugzilla->localconfig->{'site_wide_secret'}); + + return _create_token($user->id, 'auth_delegation', $checksum); +} + +sub check_auth_delegation_token { + my ($token, $uri) = @_; + my $dbh = Bugzilla->dbh; + my $user = Bugzilla->user; + + my ($eventdata) = $dbh->selectrow_array(" + SELECT eventdata FROM tokens + WHERE token = ? AND tokentype = 'auth_delegation' + AND (" . $dbh->sql_date_math('issuedate', '+', (MAX_TOKEN_AGE * 24 - 12), 'HOUR') . ") > NOW()", + undef, $token); + + if ($eventdata) { + my $checksum = hmac_sha256_base64($user->id, $uri, Bugzilla->localconfig->{'site_wide_secret'}); + if ($eventdata eq $checksum) { + delete_token($token); + return 1; + } + } + + return 0; +} + # Creates and sends a token to create a new user account. # It assumes that the login has the correct format and is not already in use. sub issue_new_user_account_token { @@ -608,6 +640,23 @@ although they can be used separately. Returns: A unique token. +=item C<issue_auth_delegation_token($uri)> + + Description: Creates and returns a token used to validate auth delegation confirmations. + + Params: $uri - The uri that auth will be delegated to. + + Returns: A unique token. + +=item C<check_auth_delegation_token($token, $uri)> + + Description: Checks if a token $token is a confirmation token for $uri. + + Params: $token - The token returned by issue_auth_delegation_token() + $uri - The uri that auth will be delegated to. + + Returns: a boolean value + =item C<check_token_data($token, $event)> Description: Makes sure the $token has been created by the currently logged in diff --git a/auth.cgi b/auth.cgi new file mode 100755 index 000000000..4bbb03c66 --- /dev/null +++ b/auth.cgi @@ -0,0 +1,88 @@ +#!/usr/bin/perl -T +# 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); + +use Bugzilla; +use Bugzilla::Constants; +use Bugzilla::Error; +use Bugzilla::Hook; +use Bugzilla::Util qw(trick_taint); +use Bugzilla::Token qw(issue_auth_delegation_token check_auth_delegation_token); +use Bugzilla::Mailer qw(MessageToMTA); + +use URI; +use URI::QueryParam; + +Bugzilla->login(LOGIN_REQUIRED); + +ThrowUserError('auth_delegation_disabled') unless Bugzilla->params->{auth_delegation}; + +my $cgi = Bugzilla->cgi; +my $template = Bugzilla->template; +my $user = Bugzilla->user; +my $callback = $cgi->param('callback') or ThrowUserError("auth_delegation_missing_callback"); +my $description = $cgi->param('description') or ThrowUserError("auth_delegation_missing_description"); + +trick_taint($callback); +trick_taint($description); + +my $callback_uri = URI->new($callback); +my $callback_base = $callback_uri->clone; +$callback_base->query(undef); + +my $skip_confirmation = 0; +my %args = ( skip_confirmation => \$skip_confirmation, + callback => $callback_uri, + description => $description, + callback_base => $callback_base ); + +Bugzilla::Hook::process('auth_delegation_confirm', \%args); + +my $confirmed = lc($cgi->request_method) eq 'post' && $cgi->param('confirm'); + +if ($confirmed || $skip_confirmation) { + my $token = $cgi->param('token'); + unless ($skip_confirmation) { + ThrowUserError("auth_delegation_missing_token") unless $token; + trick_taint($token); + + unless (check_auth_delegation_token($token, $callback)) { + ThrowUserError('auth_delegation_invalid_token', + { token => $token, callback => $callback }); + } + } + + my $new_key = Bugzilla::User::APIKey->create({ + user_id => $user->id, + description => $description, + }); + my $template = Bugzilla->template_inner($user->setting('lang')); + my $vars = { user => $user, new_key => $new_key }; + my $message; + $template->process('email/new-api-key.txt.tmpl', $vars, \$message) + or ThrowTemplateError($template->error()); + + MessageToMTA($message); + + $callback_uri->query_param(client_api_key => $new_key->api_key); + $callback_uri->query_param(client_api_login => $user->login); + + print $cgi->redirect($callback_uri); +} +else { + $args{token} = issue_auth_delegation_token($callback); + + print $cgi->header(); + $template->process("account/auth/delegation.html.tmpl", \%args) + or ThrowTemplateError($template->error()); +} diff --git a/docs/en/rst/administering/parameters.rst b/docs/en/rst/administering/parameters.rst index 80611ef6e..5b2eeadc7 100644 --- a/docs/en/rst/administering/parameters.rst +++ b/docs/en/rst/administering/parameters.rst @@ -180,6 +180,9 @@ password_complexity password_check_on_login If set, Bugzilla will check that the password meets the current complexity rules and minimum length requirements when the user logs into the Bugzilla web interface. If it doesn't, the user would not be able to log in, and will receive a message to reset their password. +auth_delegation + If set, Bugzilla will allow other websites to request API keys from its own users. See :ref:`auth-delegation`. + .. _param-attachments: Attachments diff --git a/docs/en/rst/api/core/v1/general.rst b/docs/en/rst/api/core/v1/general.rst index 06ef5b2fb..814592f58 100644 --- a/docs/en/rst/api/core/v1/general.rst +++ b/docs/en/rst/api/core/v1/general.rst @@ -110,9 +110,11 @@ There are two ways to authenticate yourself: You can specify ``Bugzilla_api_key`` or simply ``api_key`` as an argument to any call, and you will be logged in as that user if the key is correct and has -not been revoked. You can set up an API key by using the 'API Key' tab in the +not been revoked. You can set up an API key by using the :ref:`API Keys tab <api-keys>` in the Preferences pages. +API keys may also be requested via :ref:`Authentication Delegation <auth-delegation>`. + **Login and Password** You can specify ``Bugzilla_login`` and ``Bugzilla_password`` or simply diff --git a/docs/en/rst/integrating/auth-delegation.rst b/docs/en/rst/integrating/auth-delegation.rst new file mode 100644 index 000000000..811da0d90 --- /dev/null +++ b/docs/en/rst/integrating/auth-delegation.rst @@ -0,0 +1,30 @@ +.. _auth-delegation: + +Authentication Delegation via API Keys +###################################### + +Bugzilla provides a mechanism for web apps to request (with the user's consent) +an API key. API keys allow the web app to perform any action as the user and are as +a result very powerful. Because of this power, this feature is disabled by default. + +Authentication Flow +------------------- + +The authentication process begins by directing the user to th the Bugzilla site's auth.cgi. +For the sake of this example, our application's URL is `http://app.example.org` +and the Bugzilla site is `http://bugs.example.org`. + +1. Provide a link or redirect the user to `http://bugs.example.org/auth.cgi?callback=http://app.example.org/callback&description=app%description` +2. Assuming the user is agreeable, they will be redirected to `http://app.example.org/callback` via a GET request + with two additional parameters: `client_api_key` and `client_api_login`. +3. Finally, you should check that the API key and login are valid, using the :ref:`rest_user_valid_login` REST + resource. + +Your application should take measures to ensure when receiving a user at your +callback URL that you previously redirected them to Bugzilla. The simplest method would be ensuring the callback url always has the +hostname and path you specified, with only the query string parameters varying. + +The description should include the name of your application, in a form that will be recognizable to users. +This description is used in the :ref:`API Keys tab <api-keys>` in the Preferences page. + +The API key passed to the callback will be valid until the user revokes it. diff --git a/docs/en/rst/integrating/index.rst b/docs/en/rst/integrating/index.rst index 816ffe8e5..794bc0ad8 100644 --- a/docs/en/rst/integrating/index.rst +++ b/docs/en/rst/integrating/index.rst @@ -20,3 +20,4 @@ explains how to use the available mechanisms for integration and customization. templates extensions apis + auth-delegation diff --git a/template/en/default/account/auth/delegation.html.tmpl b/template/en/default/account/auth/delegation.html.tmpl new file mode 100644 index 000000000..2afdf1dc7 --- /dev/null +++ b/template/en/default/account/auth/delegation.html.tmpl @@ -0,0 +1,37 @@ +[%# 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. + #%] + +[% PROCESS global/header.html.tmpl + title = "Auth Delegation Request" %] + +<h1>[% title FILTER html %] </h1> +<p> + A third-party website (<a href="[% callback_base FILTER html %]">[% callback_base FILTER html %]</a>) + would like to have <strong>complete</strong> access to your [% terms.Bugzilla %] account. +</p> + +<p>The description of the site reads: + <blockquote> + [% description FILTER html %] + </blockquote> +</p> + +<p>Do you want this website to have <strong>complete</strong> access to your [% terms.Bugzilla %] + account?</p> + +<div> + <form action="auth.cgi" method="post"> + <input type="hidden" name="confirm" value="1"> + <input type="hidden" name="callback" value="[% callback FILTER html %]"> + <input type="hidden" name="description" value="[% description FILTER html %]"> + <input type="hidden" name="token" value="[% token FILTER html %]"> + <input type="submit" name="submit" value="Accept"> + </form> +</div> + +[% PROCESS global/footer.html.tmpl %] diff --git a/template/en/default/admin/params/auth.html.tmpl b/template/en/default/admin/params/auth.html.tmpl index 902d2fc82..06f85ed26 100644 --- a/template/en/default/admin/params/auth.html.tmpl +++ b/template/en/default/admin/params/auth.html.tmpl @@ -132,12 +132,17 @@ "<li>letters_numbers - Passwords must contain at least one UPPER and one " _ "lower case letter and a number.</li>" _ "<li>letters_numbers_specialchars - Passwords must contain at least one " _ - "letter, a number and a special character.</li></ul>" + "letter, a number and a special character.</li></ul>", password_check_on_login => "If set, $terms.Bugzilla will check that the password meets the current " _ "complexity rules and minimum length requirements when the user logs " _ "into the $terms.Bugzilla web interface. If it doesn't, the user would " _ - "not be able to log in, and recieve a message to reset their password." + "not be able to log in, and recieve a message to reset their password.", + + auth_delegation => + "If set, $terms.Bugzilla will allow third party applications " _ + "to request API keys for users." } + %] diff --git a/template/en/default/global/user-error.html.tmpl b/template/en/default/global/user-error.html.tmpl index 8306657e3..f38729ce6 100644 --- a/template/en/default/global/user-error.html.tmpl +++ b/template/en/default/global/user-error.html.tmpl @@ -117,6 +117,29 @@ account creation. Please contact an administrator to get a new account created. + [% ELSIF error == "auth_delegation_disabled" %] + [% title = "Can't use auth delegation" %] + This site does not have auth delegation enabled. + Please contact an administrator if you require this functionality. + + [% ELSIF error == "auth_delegation_missing_callback" %] + [% title = "Auth delegation impossible without callback URI" %] + It looks like auth delegation was attempted, but no callback URI was passed. + You were sent here by some other site; please contact them for support. + + [% ELSIF error == "auth_delegation_missing_description" %] + [% title = "Auth delegation impossible without description" %] + It looks like auth delegation was attempted, but no description was passed. + You were sent here by some other site; please contact them for support. + + [% ELSIF error == "auth_delegation_missing_token" %] + [% title = "Auth delegation can't be confirmed" %] + Auth delegation cannot be confirmed due to missing or invalid token. + + [% ELSIF error == "auth_delegation_invalid_token" %] + [% title = "Auth delegation can't be confirmed" %] + Auth delegation cannot be confirmed due to missing or invalid token. + [% ELSIF error == "auth_failure" %] [% title = "Authorization Required" %] [% admindocslinks = {'groups.html' => 'Group Security'} %] |