summaryrefslogtreecommitdiffstats
path: root/Bugzilla/Auth/Verify.pm
blob: 19d8dcc9ec0be20347c33ed3f4c55826a6111d4b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# 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::Verify;

use 5.10.1;
use strict;
use warnings;

use fields qw();

use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::User;
use Bugzilla::Util;

use constant user_can_create_account => 1;
use constant extern_id_used => 0;

sub new {
    my ($class, $login_type) = @_;
    my $self = fields::new($class);
    return $self;
}

sub can_change_password {
    return $_[0]->can('change_password');
}

sub create_or_update_user {
    my ($self, $params) = @_;
    my $dbh = Bugzilla->dbh;

    my $extern_id = $params->{extern_id};
    my $username  = $params->{bz_username} || $params->{username};
    my $password  = $params->{password} || '*';
    my $real_name = $params->{realname} || '';
    my $user_id   = $params->{user_id};

    # A passed-in user_id always overrides anything else, for determining
    # what account we should return.
    if (!$user_id) {
        my $username_user_id = login_to_id($username || '');
        my $extern_user_id;
        if ($extern_id) {
            trick_taint($extern_id);
            $extern_user_id = $dbh->selectrow_array('SELECT userid
                 FROM profiles WHERE extern_id = ?', undef, $extern_id);
        }

        # If we have both a valid extern_id and a valid username, and they are
        # not the same id, then we have a conflict.
        if ($username_user_id && $extern_user_id
            && $username_user_id ne $extern_user_id)
        {
            my $extern_name = Bugzilla::User->new($extern_user_id)->login;
            return { failure => AUTH_ERROR, error => "extern_id_conflict",
                     details => {extern_id   => $extern_id,
                                 extern_user => $extern_name,
                                 username    => $username} };
        }

        # If we have a valid username, but no valid id,
        # then we have to create the user. This happens when we're
        # passed only a username, and that username doesn't exist already.
        if ($username && !$username_user_id && !$extern_user_id) {
            validate_email_syntax($username)
              || return { failure => AUTH_ERROR,
                          error   => 'auth_invalid_email',
                          details => {addr => $username} };
            # Usually we'd call validate_password, but external authentication
            # systems might follow different standards than ours. So in this
            # place here, we call trick_taint without checks.
            trick_taint($password);

            # XXX Theoretically this could fail with an error, but the fix for
            # that is too involved to be done right now.
            my $user = Bugzilla::User->create({
                login_name    => $username,
                cryptpassword => $password,
                realname      => $real_name});
            $username_user_id = $user->id;
        }

        # If we have a valid username id and an extern_id, but no valid
        # extern_user_id, then we have to set the user's extern_id.
        if ($extern_id && $username_user_id && !$extern_user_id) {
            $dbh->do('UPDATE profiles SET extern_id = ? WHERE userid = ?',
                     undef, $extern_id, $username_user_id);
            Bugzilla->memcached->clear({ table => 'profiles', id => $username_user_id });
        }

        # Finally, at this point, one of these will give us a valid user id.
        $user_id = $extern_user_id || $username_user_id;
    }

    # If we still don't have a valid user_id, then we weren't passed
    # enough information in $params, and we should die right here.
    ThrowCodeError('bad_arg', {argument => 'params', function =>
        'Bugzilla::Auth::Verify::create_or_update_user'})
        unless $user_id;

    my $user = new Bugzilla::User({ id => $user_id, cache => 1 });

    # Now that we have a valid User, we need to see if any data has to be
    # updated.
    my $user_updated = 0;
    if ($username && lc($user->login) ne lc($username)) {
        validate_email_syntax($username)
          || return { failure => AUTH_ERROR, error => 'auth_invalid_email',
                      details => {addr => $username} };
        $user->set_login($username);
        $user_updated = 1;
    }
    if ($real_name && $user->name ne $real_name) {
        # $real_name is more than likely tainted, but we only use it
        # in a placeholder and we never use it after this.
        trick_taint($real_name);
        $user->set_name($real_name);
        $user_updated = 1;
    }
    $user->update() if $user_updated;

    return { user => $user };
}

1;

__END__

=head1 NAME

Bugzilla::Auth::Verify - An object that verifies usernames and passwords.

=head1 DESCRIPTION

Bugzilla::Auth::Verify provides the "Verifier" part of the Bugzilla
login process. (For details, see the "STRUCTURE" section of
L<Bugzilla::Auth>.)

It is mostly an abstract class, requiring subclasses to implement
most methods.

Note that callers outside of the C<Bugzilla::Auth> package should never
create this object directly. Just create a C<Bugzilla::Auth> object
and call C<login> on it.

=head1 VERIFICATION METHODS

These are the methods that have to do with the actual verification.

Subclasses MUST implement these methods.

=over 4

=item C<check_credentials($login_data)>

Description: Checks whether or not a username is valid.
Params:      $login_data - A C<$login_data> hashref, as described in
                           L<Bugzilla::Auth>.
                           This C<$login_data> hashref MUST contain
                           C<username>, and SHOULD also contain
                           C<password>.
Returns:     A C<$login_data> hashref with C<bz_username> set. This
             method may also set C<realname>. It must avoid changing
             anything that is already set.

=back

=head1 MODIFICATION METHODS

These are methods that change data in the actual authentication backend.

These methods are optional, they do not have to be implemented by
subclasses.

=over 4

=item C<create_or_update_user($login_data)>

Description: Automatically creates a user account in the database
             if it doesn't already exist, or updates the account
             data if C<$login_data> contains newer information.

Params:      $login_data - A C<$login_data> hashref, as described in
                           L<Bugzilla::Auth>.
                           This C<$login_data> hashref MUST contain
                           either C<user_id>, C<bz_username>, or
                           C<username>. If both C<username> and C<bz_username>
                           are specified, C<bz_username> is used as the
                           login name of the user to create in the database.
                           It MAY also contain C<extern_id>, in which
                           case it still MUST contain C<bz_username> or
                           C<username>.
                           It MAY contain C<password> and C<realname>.

Returns:     A hashref with one element, C<user>, which is a
             L<Bugzilla::User> object. May also return a login error
             as described in L<Bugzilla::Auth>.

Note:        This method is not abstract, it is actually implemented
             and creates accounts in the Bugzilla database. Subclasses
             should probably all call the C<Bugzilla::Auth::Verify>
             version of this function at the end of their own
             C<create_or_update_user>.

=item C<change_password($user, $password)>

Description: Modifies the user's password in the authentication backend.
Params:      $user - A L<Bugzilla::User> object representing the user
                     whose password we want to change.
             $password - The user's new password.
Returns:     Nothing.

=back

=head1 INFO METHODS

These are methods that describe the capabilities of this object.
These are all no-parameter methods that return either C<true> or
C<false>.

=over 4

=item C<user_can_create_account>

Whether or not users can manually create accounts in this type of
account source. Defaults to C<true>.

=item C<extern_id_used>

Whether or not this verifier method uses the extern_id field. If
used, users with editusers permission will be be allowed to
edit the extern_id for all users.

The default value is C<false>.

=back