summaryrefslogtreecommitdiffstats
path: root/extensions/GitHubAuth/lib/Client.pm
blob: 77c8a6c6155bf84a9d65de3046c74189df5e0826 (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
# 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::Extension::GitHubAuth::Client;
use strict;
use warnings;

use JSON qw(decode_json);
use URI;
use URI::QueryParam;
use Digest;

use Bugzilla::Extension::GitHubAuth::Client::Error qw(ThrowUserError ThrowCodeError);
use Bugzilla::Util qw(remote_ip correct_urlbase);

use constant DIGEST_HASH => 'SHA1';

use fields qw(user_agent);

use constant {
    GH_ACCESS_TOKEN_URI => 'https://github.com/login/oauth/access_token',
    GH_AUTHORIZE_URI    => 'https://github.com/login/oauth/authorize',
    GH_USER_EMAILS_URI  => 'https://api.github.com/user/emails',
};

sub new {
    my ($class, %init) = @_;
    my $self = $class->fields::new();

    return $self;
}

sub login_uri {
    my ($class, $target_uri) = @_;

    my $uri = URI->new(correct_urlbase() . "github.cgi");
    $uri->query_form(target_uri => $target_uri);
    return $uri;
}

sub authorize_uri {
    my ($class, $state) = @_;

    my $uri = URI->new(GH_AUTHORIZE_URI);
    $uri->query_form(
        client_id    => Bugzilla->params->{github_client_id},
        scope        => 'user:email',
        state        => $state,
        redirect_uri => correct_urlbase() . "github.cgi",
    );

    return $uri;
}

sub get_email_key {
    my ($class, $email) = @_;

    my $cgi    = Bugzilla->cgi;
    my $digest = Digest->new(DIGEST_HASH);
    $digest->add($email);
    $digest->add(remote_ip());
    $digest->add($cgi->cookie('Bugzilla_github_token') // Bugzilla->request_cache->{github_token} // '');
    $digest->add(Bugzilla->localconfig->{site_wide_secret});
    return $digest->hexdigest;
}

sub _handle_response {
    my ($self, $response) = @_;
    my $data = eval {
        decode_json($response->content);
    };
    if ($@) {
        ThrowCodeError("github_bad_response", { message => "Unable to parse json response" });
    }

    unless ($response->is_success) {
        ThrowCodeError("github_error", { response => $response });
    }
    return $data;
}

sub get_access_token {
    my ($self, $code) = @_;

    my $response = $self->user_agent->post(
        GH_ACCESS_TOKEN_URI,
        { client_id     => Bugzilla->params->{github_client_id},
          client_secret => Bugzilla->params->{github_client_secret},
          code          => $code },
        Accept => 'application/json',
    );
    my $data = $self->_handle_response($response);
    return $data->{access_token} if exists $data->{access_token};
}

sub get_user_emails {
    my ($self, $access_token) = @_;
    my $uri = URI->new(GH_USER_EMAILS_URI);
    $uri->query_form(access_token => $access_token);

    my $response = $self->user_agent->get($uri, Accept => 'application/json');

    return $self->_handle_response($response);
}

sub user_agent {
    my ($self) = @_;
    $self->{user_agent} //= $self->_build_user_agent;

    return $self->{user_agent};
}

sub _build_user_agent {
    my ($self) = @_;
    my $ua = LWP::UserAgent->new( timeout => 10 );

    if (Bugzilla->params->{proxy_url}) {
        $ua->proxy('https', Bugzilla->params->{proxy_url});
    }

    return $ua;
}

1;