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
|
#!/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 local/lib/perl5);
use Bugzilla;
use Bugzilla::Util qw(remote_ip);
use Bugzilla::Error;
use Bugzilla::Constants;
use Bugzilla::Token qw( issue_short_lived_session_token
set_token_extra_data
get_token_extra_data
delete_token );
use URI;
use URI::QueryParam;
BEGIN { Bugzilla->extensions }
use Bugzilla::Extension::GitHubAuth::Client;
my $cgi = Bugzilla->cgi;
my $urlbase = Bugzilla->localconfig->{urlbase};
if (lc($cgi->request_method) eq 'post') {
# POST requests come from Bugzilla itself and begin the GitHub login process
# by redirecting the user to GitHub's authentication endpoint.
my $user = Bugzilla->login(LOGIN_OPTIONAL);
my $target_uri = $cgi->param('target_uri') or ThrowCodeError("github_invalid_target");
my $github_secret = $cgi->param('github_secret') or ThrowCodeError("github_invalid_request", { reason => 'invalid secret' });
my $github_secret2 = Bugzilla->github_secret or ThrowCodeError("github_invalid_request", { reason => 'invalid secret' });
if ($github_secret ne $github_secret2) {
Bugzilla->check_rate_limit('github', remote_ip());
ThrowCodeError("github_invalid_request", { reason => 'invalid secret' });
}
ThrowCodeError("github_invalid_target", { target_uri => $target_uri })
unless $target_uri =~ /^\Q$urlbase\E/;
ThrowCodeError("github_insecure_referer", { target_uri => $target_uri })
if $cgi->referer && $cgi->referer =~ /(?:reset_password\.cgi|token\.cgi|\bt=|token=|api_key=)/;
if ($user->id) {
print $cgi->redirect($target_uri);
exit;
}
my $state = issue_short_lived_session_token("github_state");
set_token_extra_data($state, { type => 'github_login', target_uri => $target_uri });
$cgi->send_cookie(-name => 'github_state',
-value => $state,
-httponly => 1);
print $cgi->redirect(Bugzilla::Extension::GitHubAuth::Client->authorize_uri($state));
}
elsif (lc($cgi->request_method) eq 'get') {
# GET requests come from GitHub, with this script acting as the OAuth2 callback.
my $state_param = $cgi->param('state');
my $state_cookie = $cgi->cookie('github_state');
# If the state or params are missing, or the github_state cookie is missing
# we just redirect to index.cgi.
unless ($state_param && $state_cookie && ($cgi->param('code') || $cgi->param('email'))) {
print $cgi->redirect($urlbase . "index.cgi");
exit;
}
my $invalid_request = $state_param ne $state_cookie;
my $state_data;
unless ($invalid_request) {
$state_data = get_token_extra_data($state_param);
$invalid_request = !( $state_data && $state_data->{type} && $state_data->{type} =~ /^github_(?:login|email)$/ );
}
if ($invalid_request) {
Bugzilla->check_rate_limit('github', remote_ip());
ThrowCodeError("github_invalid_request", { reason => 'invalid state param' } )
}
$cgi->remove_cookie('github_state');
delete_token($state_param);
if ($state_data->{type} eq 'github_login') {
Bugzilla->request_cache->{github_action} = 'login';
Bugzilla->request_cache->{github_target_uri} = $state_data->{target_uri};
}
elsif ($state_data->{type} eq 'github_email') {
Bugzilla->request_cache->{github_action} = 'email';
Bugzilla->request_cache->{github_emails} = $state_data->{emails};
}
my $user = Bugzilla->login(LOGIN_REQUIRED);
my $target_uri = URI->new($state_data->{target_uri});
# It makes very little sense to login to a page with the logout parameter.
# doing so would be a no-op, so we ignore the logout param here.
$target_uri->query_param_delete('logout');
if ($target_uri->path =~ /attachment\.cgi/) {
my $attachment_uri = URI->new($urlbase . "attachment.cgi");
$attachment_uri->query_param(id => scalar $target_uri->query_param('id'));
if ($target_uri->query_param('action')) {
$attachment_uri->query_param(action => scalar $target_uri->query_param('action'));
}
print $cgi->redirect($attachment_uri);
}
else {
print $cgi->redirect($target_uri);
}
}
|