if ($self->{_info_getter}->{successful}->requires_persistence
and !Bugzilla->request_cache->{auth_no_automatic_login})
{
- $self->{_persister}->persist_login($user);
+ $user->{_login_token} = $self->{_persister}->persist_login($user);
}
}
elsif ($fail_code == AUTH_ERROR) {
use Bugzilla::WebService::Constants;
use Bugzilla::Util;
use Bugzilla::Error;
+use Bugzilla::Token;
sub get_login_info {
my ($self) = @_;
my $params = Bugzilla->input_params;
+ my $cgi = Bugzilla->cgi;
+
+ my $login = trim(delete $params->{'Bugzilla_login'});
+ my $password = delete $params->{'Bugzilla_password'};
+ # The token must match the cookie to authenticate the request.
+ my $login_token = delete $params->{'Bugzilla_login_token'};
+ my $login_cookie = $cgi->cookie('Bugzilla_login_request_cookie');
- my $username = trim(delete $params->{"Bugzilla_login"});
- my $password = delete $params->{"Bugzilla_password"};
+ my $valid = 0;
+ # If the web browser accepts cookies, use them.
+ if ($login_token && $login_cookie) {
+ my ($time, undef) = split(/-/, $login_token);
+ # Regenerate the token based on the information we have.
+ my $expected_token = issue_hash_token(['login_request', $login_cookie], $time);
+ $valid = 1 if $expected_token eq $login_token;
+ $cgi->remove_cookie('Bugzilla_login_request_cookie');
+ }
+ # WebServices and other local scripts can bypass this check.
+ # This is safe because we won't store a login cookie in this case.
+ elsif (Bugzilla->usage_mode != USAGE_MODE_BROWSER) {
+ $valid = 1;
+ }
+ # Else falls back to the Referer header and accept local URLs.
+ # Attachments are served from a separate host (ideally), and so
+ # an evil attachment cannot abuse this check with a redirect.
+ elsif (my $referer = $cgi->referer) {
+ my $urlbase = correct_urlbase();
+ $valid = 1 if $referer =~ /^\Q$urlbase\E/;
+ }
+ # If the web browser doesn't accept cookies and the Referer header
+ # is missing, we have no way to make sure that the authentication
+ # request comes from the user.
+ elsif ($login && $password) {
+ ThrowUserError('auth_untrusted_request', { login => $login });
+ }
- if (!defined $username || !defined $password) {
+ if (!$login || !$password || !$valid) {
return { failure => AUTH_NODATA };
}
- return { username => $username, password => $password };
+ return { username => $login, password => $password };
}
sub fail_nodata {
$dbh->bz_commit_transaction();
+ # We do not want WebServices to generate login cookies.
+ # All we need is the login token for User.login.
+ return $login_cookie if i_am_webservice();
+
# Prevent JavaScript from accessing login cookies.
my %cookieargs = ('-httponly' => 1);
# Override header so we can add the cookies in
sub header {
my $self = shift;
+ my $user = Bugzilla->user;
# If there's only one parameter, then it's a Content-Type.
if (scalar(@_) == 1) {
unshift(@_, '-type' => shift(@_));
}
+ if (!$user->id && $user->authorizer->can_login
+ && !$self->cookie('Bugzilla_login_request_cookie'))
+ {
+ my %args;
+ $args{'-secure'} = 1 if Bugzilla->params->{ssl_redirect};
+
+ $self->send_cookie(-name => 'Bugzilla_login_request_cookie',
+ -value => generate_random_password(),
+ -httponly => 1,
+ %args);
+ }
+
# Add the cookies in if we have any
if (scalar(@{$self->{Bugzilla_cookie_list}})) {
unshift(@_, '-cookie' => $self->{Bugzilla_cookie_list});
# Allow templates to generate a token themselves.
'issue_hash_token' => \&Bugzilla::Token::issue_hash_token,
+ 'get_login_request_token' => sub {
+ my $cookie = Bugzilla->cgi->cookie('Bugzilla_login_request_cookie');
+ return $cookie ? issue_hash_token(['login_request', $cookie]) : '';
+ },
+
# A way for all templates to get at Field data, cached.
'bug_fields' => sub {
my $cache = Bugzilla->request_cache;
@Bugzilla::Util::EXPORT = qw(trick_taint detaint_natural detaint_signed
html_quote url_quote xml_quote
css_class_quote html_light_quote
- i_am_cgi correct_urlbase remote_ip validate_ip
- do_ssl_redirect_if_required use_attachbase
+ i_am_cgi i_am_webservice correct_urlbase remote_ip
+ validate_ip do_ssl_redirect_if_required use_attachbase
diff_arrays on_main_db say
trim wrap_hard wrap_comment find_wrap_point
format_time validate_date validate_time datetime_from
return exists $ENV{'SERVER_SOFTWARE'} ? 1 : 0;
}
+sub i_am_webservice {
+ my $usage_mode = Bugzilla->usage_mode;
+ return $usage_mode == USAGE_MODE_XMLRPC
+ || $usage_mode == USAGE_MODE_JSON;
+}
+
# This exists as a separate function from Bugzilla::CGI::redirect_to_https
# because we don't want to create a CGI object during XML-RPC calls
# (doing so can mess up XML-RPC).
# Functions that tell you about your environment
my $is_cgi = i_am_cgi();
+ my $is_webservice = i_am_webservice();
my $urlbase = correct_urlbase();
# Data manipulation
server. For example, it would return false if the caller is running
in a command-line script.
+=item C<i_am_webservice()>
+
+Tells you whether or not the current usage mode is WebServices related
+such as JSONRPC or XMLRPC.
+
=item C<correct_urlbase()>
Returns either the C<sslbase> or C<urlbase> parameter, depending on the
=item C<User.login>
You can use L<Bugzilla::WebService::User/login> to log in as a Bugzilla
-user. This issues standard HTTP cookies that you must then use in future
-calls, so your client must be capable of receiving and transmitting
-cookies.
+user. This issues a token that you must then use in future calls.
=item C<Bugzilla_login> and C<Bugzilla_password>
=item C<Bugzilla_restrictlogin> (boolean) - Optional. If true,
then your login will only be valid for your IP address.
-=item C<Bugzilla_rememberlogin> (boolean) - Optional. If true,
-then the cookie sent back to you with the method response will
-not expire.
-
=back
-The C<Bugzilla_restrictlogin> and C<Bugzilla_rememberlogin> options
-are only used when you have also specified C<Bugzilla_login> and
-C<Bugzilla_password>.
+The C<Bugzilla_restrictlogin> option is only used when you have also
+specified C<Bugzilla_login> and C<Bugzilla_password>.
+
+=item C<Bugzilla_token>
+
+B<Added in Bugzilla 5.0 and backported to 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
+the token returned when calling C<User.login> mentioned above.
-Note that Bugzilla will return HTTP cookies along with the method
-response when you use these arguments (just like the C<User.login> method
-above).
+Support for using login cookies for authentication has been dropped
+for security reasons.
=back
sub login {
my ($self, $params) = @_;
- my $remember = $params->{remember};
# Username and password params are required
foreach my $param ("login", "password") {
|| ThrowCodeError('param_required', { param => $param });
}
- # Convert $remember from a boolean 0/1 value to a CGI-compatible one.
- if (defined($remember)) {
- $remember = $remember? 'on': '';
- }
- else {
- # Use Bugzilla's default if $remember is not supplied.
- $remember =
- Bugzilla->params->{'rememberlogin'} eq 'defaulton'? 'on': '';
- }
-
# Make sure the CGI user info class works if necessary.
my $input_params = Bugzilla->input_params;
$input_params->{'Bugzilla_login'} = $params->{login};
$input_params->{'Bugzilla_password'} = $params->{password};
- $input_params->{'Bugzilla_remember'} = $remember;
+ $input_params->{'Bugzilla_restrictlogin'} = $params->{restrict_login};
my $user = Bugzilla->login();
my $result = { id => $self->type('int', $user->id) };
- # We will use the stored cookie value combined with the user id
- # to create a token that can be used with future requests in the
- # query parameters
- my $login_cookie = first { $_->name eq 'Bugzilla_logincookie' }
- @{ Bugzilla->cgi->{'Bugzilla_cookie_list'} };
- if ($login_cookie) {
- $result->{'token'} = $user->id . "-" . $login_cookie->value;
+ if ($user->{_login_token}) {
+ $result->{'token'} = $user->id . "-" . $user->{_login_token};
}
return $result;
=item C<password> (string) - The user's password.
-=item C<remember> (bool) B<Optional> - if the cookies returned by the
-call to login should expire with the session or not. In order for
-this option to have effect the Bugzilla server must be configured to
-allow the user to set this option - the Bugzilla parameter
-I<rememberlogin> must be set to "defaulton" or
-"defaultoff". Addionally, the client application must implement
-management of cookies across sessions.
+=item C<restrict_login> (bool) B<Optional> - If set to a true value,
+the token returned by this method will only be valid from the IP address
+which called this method.
=back
=item B<Returns>
On success, a hash containing two items, C<id>, the numeric id of the
-user that was logged in, and a C<token> which can be passed in
-the parameters as authentication in other calls. A set of http cookies
-is also sent with the response. These cookies *or* the token can be sent
-along with any future requests to the webservice, for the duration of the
-session.
+user that was logged in, and a C<token> which can be passed in the parameters
+as authentication in other calls. The token can be sent along with any future
+requests to the webservice, for the duration of the session, i.e. till
+L<User.logout|/logout> is called.
=item B<Errors>
=back
+=item B<History>
+
+=over
+
+=item C<remember> was removed in Bugzilla B<4.4> as this method no longer
+creates a login cookie.
+
+=item C<restrict_login> was added in Bugzilla B<4.4>.
+
+=item C<token> was added in Bugzilla B<4.4>.
+
+=back
+
=back
=head2 logout
# Keep a temporary record of the user visiting this page
$vars->{'token'} = issue_session_token('sudo_prepared');
+ if ($user->authorizer->can_login) {
+ my $value = generate_random_password();
+ my %args;
+ $args{'-secure'} = 1 if Bugzilla->params->{ssl_redirect};
+
+ $cgi->send_cookie(-name => 'Bugzilla_login_request_cookie',
+ -value => $value,
+ -httponly => 1,
+ %args);
+
+ $vars->{'login_request_token'} = issue_hash_token(['login_request', $value]);
+ }
+
# Show the sudo page
$vars->{'target_login_default'} = $cgi->param('target_login');
$vars->{'reason_default'} = $cgi->param('reason');
[%+ "checked" IF Param('rememberlogin') == "defaulton" %]>
<label for="Bugzilla_remember[% qs_suffix %]">Remember</label>
[% END %]
- <input type="submit" name="GoAheadAndLogIn" value="Log in"
+ <input type="hidden" name="Bugzilla_login_token"
+ value="[% get_login_request_token() FILTER html %]">
+ <input type="submit" name="GoAheadAndLogIn" value="Log in"
id="log_in[% qs_suffix %]">
<script type="text/javascript">
mini_login_constants = {
[% PROCESS "global/hidden-fields.html.tmpl"
exclude="^Bugzilla_(login|password|restrictlogin)$" %]
+ <input type="hidden" name="Bugzilla_login_token"
+ value="[% get_login_request_token() FILTER html %]">
<input type="submit" name="GoAheadAndLogIn" value="Log in" id="log_in">
-
+
<p>
(Note: you should make sure cookies are enabled for this site.
Otherwise, you will be required to log in frequently.)
<p>
Finally, enter <label for="Bugzilla_password">your [% terms.Bugzilla %]
password</label>:
- <input type="hidden" name="Bugzilla_login" value="
- [%- user.login FILTER html %]">
+ <input type="hidden" name="Bugzilla_login" value="[% user.login FILTER html %]">
<input type="password" id="Bugzilla_password" name="Bugzilla_password" size="20">
+ <input type="hidden" name="Bugzilla_login_token"
+ value="[% login_request_token FILTER html %]">
<br>
This is done for two reasons. First of all, it is done to reduce
the chances of someone doing large amounts of damage using your
[% Hook.process("auth_failure") %]
+ [% ELSIF error == "auth_untrusted_request" %]
+ [% title = "Untrusted Authentication Request" %]
+ You tried to log in using the <em>[% login FILTER html %]</em> account,
+ but [% terms.Bugzilla %] is unable to trust your request. Make sure
+ your web browser accepts cookies and that you haven't been redirected
+ here from an external web site.
+ <a href="index.cgi?GoAheadAndLogIn=1">Click here</a> if you really want
+ to log in.
+
[% ELSIF error == "attachment_deletion_disabled" %]
[% title = "Attachment Deletion Disabled" %]
Attachment deletion is disabled on this installation.