summaryrefslogtreecommitdiffstats
path: root/Bugzilla
diff options
context:
space:
mode:
authorSimon Green <sgreen@redhat.com>2014-07-27 10:47:21 +0200
committerSimon Green <sgreen@redhat.com>2014-07-27 10:47:21 +0200
commitfd29ee56c4678749c00e7698ef245f7e2967ee10 (patch)
tree9d0696c9a89b8df8a6d46e2be6602a449b7354c3 /Bugzilla
parent9f0f44b7fb73e9af0cdaefe8f5ff617f14fec2ed (diff)
downloadbugzilla-fd29ee56c4678749c00e7698ef245f7e2967ee10.tar.gz
bugzilla-fd29ee56c4678749c00e7698ef245f7e2967ee10.tar.xz
Bug 726696 - All authenticated WebServices methods should require username/pass, token or a valid API key for authentication
r=dkl, a=sgreen
Diffstat (limited to 'Bugzilla')
-rw-r--r--Bugzilla/Auth.pm2
-rw-r--r--Bugzilla/Auth/Login/APIKey.pm52
-rw-r--r--Bugzilla/Auth/Login/Cookie.pm14
-rw-r--r--Bugzilla/DB/Schema.pm22
-rw-r--r--Bugzilla/Install/DB.pm4
-rw-r--r--Bugzilla/Template.pm6
-rw-r--r--Bugzilla/Token.pm18
-rw-r--r--Bugzilla/User/APIKey.pm154
-rw-r--r--Bugzilla/WebService.pm34
-rw-r--r--Bugzilla/WebService/Constants.pm3
-rw-r--r--Bugzilla/WebService/User.pm16
-rw-r--r--Bugzilla/WebService/Util.pm5
12 files changed, 316 insertions, 14 deletions
diff --git a/Bugzilla/Auth.pm b/Bugzilla/Auth.pm
index 42e4ee784..5d4c348da 100644
--- a/Bugzilla/Auth.pm
+++ b/Bugzilla/Auth.pm
@@ -30,7 +30,7 @@ sub new {
my $self = fields::new($class);
$params ||= {};
- $params->{Login} ||= Bugzilla->params->{'user_info_class'} . ',Cookie';
+ $params->{Login} ||= Bugzilla->params->{'user_info_class'} . ',Cookie,APIKey';
$params->{Verify} ||= Bugzilla->params->{'user_verify_class'};
$self->{_info_getter} = new Bugzilla::Auth::Login::Stack($params->{Login});
diff --git a/Bugzilla/Auth/Login/APIKey.pm b/Bugzilla/Auth/Login/APIKey.pm
new file mode 100644
index 000000000..902ce4da7
--- /dev/null
+++ b/Bugzilla/Auth/Login/APIKey.pm
@@ -0,0 +1,52 @@
+# 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::Auth::Login::APIKey;
+
+use 5.10.1;
+use strict;
+
+use base qw(Bugzilla::Auth::Login);
+
+use Bugzilla::Constants;
+use Bugzilla::User::APIKey;
+use Bugzilla::Util;
+use Bugzilla::Error;
+
+use constant requires_persistence => 0;
+use constant requires_verification => 0;
+use constant can_login => 0;
+use constant can_logout => 0;
+
+# This method is only available to web services. An API key can never
+# be used to authenticate a Web request.
+sub get_login_info {
+ my ($self) = @_;
+ my $params = Bugzilla->input_params;
+ my ($user_id, $login_cookie);
+
+ my $api_key_text = trim(delete $params->{'Bugzilla_api_key'});
+ if (!i_am_webservice() || !$api_key_text) {
+ return { failure => AUTH_NODATA };
+ }
+
+ my $api_key = Bugzilla::User::APIKey->new({ name => $api_key_text });
+
+ if (!$api_key or $api_key->api_key ne $api_key_text) {
+ # The second part checks the correct capitalisation. Silly MySQL
+ ThrowUserError("api_key_not_valid");
+ }
+ elsif ($api_key->revoked) {
+ ThrowUserError('api_key_revoked');
+ }
+
+ $api_key->update_last_used();
+
+ return { user_id => $api_key->user_id };
+}
+
+1;
diff --git a/Bugzilla/Auth/Login/Cookie.pm b/Bugzilla/Auth/Login/Cookie.pm
index 29e2310a0..9c18903b6 100644
--- a/Bugzilla/Auth/Login/Cookie.pm
+++ b/Bugzilla/Auth/Login/Cookie.pm
@@ -14,8 +14,9 @@ use base qw(Bugzilla::Auth::Login);
use fields qw(_login_token);
use Bugzilla::Constants;
-use Bugzilla::Util;
use Bugzilla::Error;
+use Bugzilla::Token;
+use Bugzilla::Util;
use List::Util qw(first);
@@ -49,6 +50,17 @@ sub get_login_info {
@{$cgi->{'Bugzilla_cookie_list'}};
$user_id = $cookie->value if $cookie;
}
+
+ # If the call is for a web service, and an api token is provided, check
+ # it is valid.
+ if (i_am_webservice() && Bugzilla->input_params->{Bugzilla_api_token}) {
+ my $api_token = Bugzilla->input_params->{Bugzilla_api_token};
+ my ($token_user_id, undef, undef, $token_type)
+ = Bugzilla::Token::GetTokenData($api_token);
+ if ($token_type ne 'api_token' || $user_id != $token_user_id) {
+ ThrowUserError('auth_invalid_token', { token => $api_token });
+ }
+ }
}
# If no cookies were provided, we also look for a login token
diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm
index 632ab639b..b175f1554 100644
--- a/Bugzilla/DB/Schema.pm
+++ b/Bugzilla/DB/Schema.pm
@@ -1177,7 +1177,7 @@ use constant ABSTRACT_SCHEMA => {
issuedate => {TYPE => 'DATETIME', NOTNULL => 1} ,
token => {TYPE => 'varchar(16)', NOTNULL => 1,
PRIMARYKEY => 1},
- tokentype => {TYPE => 'varchar(8)', NOTNULL => 1} ,
+ tokentype => {TYPE => 'varchar(16)', NOTNULL => 1} ,
eventdata => {TYPE => 'TINYTEXT'},
],
INDEXES => [
@@ -1733,6 +1733,26 @@ use constant ABSTRACT_SCHEMA => {
bug_user_last_visit_last_visit_ts_idx => ['last_visit_ts'],
],
},
+
+ user_api_keys => {
+ FIELDS => [
+ id => {TYPE => 'INTSERIAL', NOTNULL => 1,
+ PRIMARYKEY => 1},
+ user_id => {TYPE => 'INT3', NOTNULL => 1,
+ REFERENCES => {TABLE => 'profiles',
+ COLUMN => 'userid',
+ DELETE => 'CASCADE'}},
+ api_key => {TYPE => 'VARCHAR(40)', NOTNULL => 1},
+ description => {TYPE => 'VARCHAR(255)'},
+ revoked => {TYPE => 'BOOLEAN', NOTNULL => 1,
+ DEFAULT => 'FALSE'},
+ last_used => {TYPE => 'DATETIME'},
+ ],
+ INDEXES => [
+ user_api_keys_key => {FIELDS => ['api_key'], TYPE => 'UNIQUE'},
+ user_api_keys_user_id => {FIELDS => ['user_id']},
+ ],
+ },
};
# Foreign Keys are added in Bugzilla::DB::bz_add_field_tables
diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm
index 9e197a907..bbddf1620 100644
--- a/Bugzilla/Install/DB.pm
+++ b/Bugzilla/Install/DB.pm
@@ -719,6 +719,10 @@ sub update_table_definitions {
'bug_user_last_visit_last_visit_ts_idx',
['last_visit_ts']);
+ # 2014-07-14 sgreen@redhat.com - Bug 726696
+ $dbh->bz_alter_column('tokens', 'tokentype',
+ {TYPE => 'varchar(16)', NOTNULL => 1});
+
################################################################
# New --TABLE-- changes should go *** A B O V E *** this point #
################################################################
diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm
index 8fe50fa4f..b36249b2f 100644
--- a/Bugzilla/Template.pm
+++ b/Bugzilla/Template.pm
@@ -1001,6 +1001,12 @@ sub create {
return $cookie ? issue_hash_token(['login_request', $cookie]) : '';
},
+ 'get_api_token' => sub {
+ return '' unless Bugzilla->user->id;
+ my $cache = Bugzilla->request_cache;
+ return $cache->{api_token} //= issue_api_token();
+ },
+
# A way for all templates to get at Field data, cached.
'bug_fields' => sub {
my $cache = Bugzilla->request_cache;
diff --git a/Bugzilla/Token.pm b/Bugzilla/Token.pm
index 535263868..71f24183b 100644
--- a/Bugzilla/Token.pm
+++ b/Bugzilla/Token.pm
@@ -23,13 +23,21 @@ use Digest::SHA qw(hmac_sha256_base64);
use parent qw(Exporter);
-@Bugzilla::Token::EXPORT = qw(issue_session_token check_token_data delete_token
+@Bugzilla::Token::EXPORT = qw(issue_api_token issue_session_token
+ check_token_data delete_token
issue_hash_token check_hash_token);
################################################################################
# Public Functions
################################################################################
+# Create a token used for internal API authentication
+sub issue_api_token {
+ # Generates a random token, adds it to the tokens table, and returns
+ # the token to the caller.
+ return _create_token(Bugzilla->user->id, 'api_token', '');
+}
+
# 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 {
@@ -466,6 +474,14 @@ Bugzilla::Token - Provides different routines to manage tokens.
=over
+=item C<issue_api_token($login_name)>
+
+ Description: Creates a token that can be used for API calls on the web page.
+
+ Params: None.
+
+ Returns: The token.
+
=item C<issue_new_user_account_token($login_name)>
Description: Creates and sends a token per email to the email address
diff --git a/Bugzilla/User/APIKey.pm b/Bugzilla/User/APIKey.pm
new file mode 100644
index 000000000..75a4a6beb
--- /dev/null
+++ b/Bugzilla/User/APIKey.pm
@@ -0,0 +1,154 @@
+# 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::User::APIKey;
+
+use 5.10.1;
+use strict;
+
+use parent qw(Bugzilla::Object);
+
+use Bugzilla::User;
+use Bugzilla::Util qw(generate_random_password trim);
+
+#####################################################################
+# Overriden Constants that are used as methods
+#####################################################################
+
+use constant DB_TABLE => 'user_api_keys';
+use constant DB_COLUMNS => qw(
+ id
+ user_id
+ api_key
+ description
+ revoked
+ last_used
+);
+
+use constant UPDATE_COLUMNS => qw(description revoked last_used);
+use constant VALIDATORS => {
+ api_key => \&_check_api_key,
+ description => \&_check_description,
+ revoked => \&Bugzilla::Object::check_boolean,
+};
+use constant LIST_ORDER => 'id';
+use constant NAME_FIELD => 'api_key';
+
+# turn off auditing and exclude these objects from memcached
+use constant { AUDIT_CREATES => 0,
+ AUDIT_UPDATES => 0,
+ AUDIT_REMOVES => 0,
+ USE_MEMCACHED => 0 };
+
+# Accessors
+sub id { return $_[0]->{id} }
+sub user_id { return $_[0]->{user_id} }
+sub api_key { return $_[0]->{api_key} }
+sub description { return $_[0]->{description} }
+sub revoked { return $_[0]->{revoked} }
+sub last_used { return $_[0]->{last_used} }
+
+# Helpers
+sub user {
+ my $self = shift;
+ $self->{user} //= Bugzilla::User->new({name => $self->user_id, cache => 1});
+ return $self->{user};
+}
+
+sub update_last_used {
+ my $self = shift;
+ my $timestamp = shift
+ || Bugzilla->dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
+ $self->set('last_used', $timestamp);
+ $self->update;
+}
+
+# Setters
+sub set_description { $_[0]->set('description', $_[1]); }
+sub set_revoked { $_[0]->set('revoked', $_[1]); }
+
+# Validators
+sub _check_api_key { return generate_random_password(40); }
+sub _check_description { return trim($_[1]) || ''; }
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::User::APIKey - Model for an api key belonging to a user.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::User::APIKey;
+
+ my $api_key = Bugzilla::User::APIKey->new($id);
+ my $api_key = Bugzilla::User::APIKey->new({ name => $api_key });
+
+ # Class Functions
+ $user_api_key = Bugzilla::User::APIKey->create({
+ description => $description,
+ });
+
+=head1 DESCRIPTION
+
+This package handles Bugzilla User::APIKey.
+
+C<Bugzilla::User::APIKey> is an implementation of L<Bugzilla::Object>, and
+thus provides all the methods of L<Bugzilla::Object> in addition to the methods
+listed below.
+
+=head1 METHODS
+
+=head2 Accessor Methods
+
+=over
+
+=item C<id>
+
+The internal id of the api key.
+
+=item C<user>
+
+The Bugzilla::User object that this api key belongs to.
+
+=item C<user_id>
+
+The user id that this api key belongs to.
+
+=item C<api_key>
+
+The API key, which is a random string.
+
+=item C<description>
+
+An optional string that lets the user describe what a key is used for.
+For example: "Dashboard key", "Application X key".
+
+=item C<revoked>
+
+If true, this api key cannot be used.
+
+=item C<last_used>
+
+The date that this key was last used. undef if never used.
+
+=item C<update_last_used>
+
+Updates the last used value to the current timestamp. This is updated even
+if the RPC call resulted in an error. It is not updated when the description
+or the revoked flag is changed.
+
+=item C<set_description>
+
+Sets the new description
+
+=item C<set_revoked>
+
+Sets the revoked flag
+
+=back
diff --git a/Bugzilla/WebService.pm b/Bugzilla/WebService.pm
index 1dc04c1f6..d12d4dbac 100644
--- a/Bugzilla/WebService.pm
+++ b/Bugzilla/WebService.pm
@@ -134,14 +134,22 @@ how this is implemented for those frontends.
=head1 LOGGING IN
-There are various ways to log in:
+Some methods do not require you to log in. An example of this is Bug.get.
+However, authenticating yourself allows you to see non public information. For
+example, a bug that is not publicly visible.
+
+There are two ways to authenticate yourself:
=over
-=item C<User.login>
+=item C<Bugzilla_api_key>
-You can use L<Bugzilla::WebService::User/login> to log in as a Bugzilla
-user. This issues a token that you must then use in future calls.
+B<Added in Bugzilla 5.0>
+
+You can specify C<Bugzilla_api_key> as an argument to any WebService method, 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
+Preferences pages.
=item C<Bugzilla_login> and C<Bugzilla_password>
@@ -164,15 +172,29 @@ then your login will only be valid for your IP address.
=back
The C<Bugzilla_restrictlogin> option is only used when you have also
-specified C<Bugzilla_login> and C<Bugzilla_password>.
+specified C<Bugzilla_login> and C<Bugzilla_password>. This value will be
+deprecated in the release after Bugzilla 5.0 and you will be required to
+pass the Bugzilla_login and Bugzilla_password for every call.
For REST, you may also use the C<login> and C<password> variable
names instead of C<Bugzilla_login> and C<Bugzilla_password> as a
convenience. You may also use C<token> instead of C<Bugzilla_token>.
+=back
+
+There are also two deprecreated methods of authentications. This will be
+removed in the version after Bugzilla 5.0.
+
+=over
+
+=item C<User.login>
+
+You can use L<Bugzilla::WebService::User/login> to log in as a Bugzilla
+user. This issues a token that you must then use in future calls.
+
=item C<Bugzilla_token>
-B<Added in Bugzilla 5.0>
+B<Added in Bugzilla 4.4.3>
You can specify C<Bugzilla_token> as argument to any WebService method,
and you will be logged in as that user if the token is correct. This is
diff --git a/Bugzilla/WebService/Constants.pm b/Bugzilla/WebService/Constants.pm
index 2c21de15e..2fa1bdc28 100644
--- a/Bugzilla/WebService/Constants.pm
+++ b/Bugzilla/WebService/Constants.pm
@@ -142,6 +142,9 @@ use constant WS_ERROR_CODE => {
extern_id_conflict => -303,
auth_failure => 304,
password_current_too_short => 305,
+ api_key_not_valid => 306,
+ api_key_revoked => 306,
+ auth_invalid_token => 307,
# Except, historically, AUTH_NODATA, which is 410.
login_required => 410,
diff --git a/Bugzilla/WebService/User.pm b/Bugzilla/WebService/User.pm
index f05b2b247..32d6f28ec 100644
--- a/Bugzilla/WebService/User.pm
+++ b/Bugzilla/WebService/User.pm
@@ -432,9 +432,13 @@ where applicable.
=head1 Logging In and Out
+These method are now deprecated, and will be removed in the release after
+Bugzilla 5.0. The correct way of use these REST and RPC calls is noted in
+L<Bugzilla::WebService>
+
=head2 login
-B<STABLE>
+B<DEPRECATED>
=over
@@ -499,7 +503,9 @@ creates a login cookie.
=item C<restrict_login> was added in Bugzilla B<5.0>.
-=item C<token> was added in Bugzilla B<5.0>.
+=item C<token> was added in Bugzilla B<4.4.3>.
+
+=item This function will be removed in the release after Bugzilla 5.0, in favour of API keys.
=back
@@ -507,7 +513,7 @@ creates a login cookie.
=head2 logout
-B<STABLE>
+B<DEPRECATED>
=over
@@ -525,7 +531,7 @@ Log out the user. Does nothing if there is no user logged in.
=head2 valid_login
-B<UNSTABLE>
+B<DEPRECATED>
=over
@@ -563,6 +569,8 @@ for the provided username.
=item Added in Bugzilla B<5.0>.
+=item This function will be removed in the release after Bugzilla 5.0, in favour of API keys.
+
=back
=back
diff --git a/Bugzilla/WebService/Util.pm b/Bugzilla/WebService/Util.pm
index 26558fc6a..6290139a3 100644
--- a/Bugzilla/WebService/Util.pm
+++ b/Bugzilla/WebService/Util.pm
@@ -268,6 +268,11 @@ sub fix_credentials {
$params->{'Bugzilla_login'} = $params->{'login'};
$params->{'Bugzilla_password'} = $params->{'password'};
}
+ # Allow user to pass api_key=12345678 as a convenience which becomes
+ # "Bugzilla_api_key" which is what the auth code looks for.
+ if (exists $params->{api_key}) {
+ $params->{Bugzilla_api_key} = delete $params->{api_key};
+ }
# Allow user to pass token=12345678 as a convenience which becomes
# "Bugzilla_token" which is what the auth code looks for.
if (exists $params->{'token'}) {