From c0d00a57eebb31d3e2cdef0ebb9219ebe2fe2bab Mon Sep 17 00:00:00 2001 From: Dylan William Hardison Date: Tue, 3 Mar 2015 22:27:15 -0500 Subject: Github Auth Extension --- extensions/GitHubAuth/lib/Client.pm | 146 ++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 extensions/GitHubAuth/lib/Client.pm (limited to 'extensions/GitHubAuth/lib/Client.pm') diff --git a/extensions/GitHubAuth/lib/Client.pm b/extensions/GitHubAuth/lib/Client.pm new file mode 100644 index 000000000..896e82eff --- /dev/null +++ b/extensions/GitHubAuth/lib/Client.pm @@ -0,0 +1,146 @@ +# 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); + +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 ($self, $target) = @_; + + $target->query_param(GoAheadAndLogIn => 1); + $target->query_param(github_login => 1); + $target->query_param_delete('logout'); + + my $uri = URI->new(GH_AUTHORIZE_URI); + + $uri->query_form( + client_id => Bugzilla->params->{github_client_id}, + scope => 'user:email', + state => $self->get_state($target), + redirect_uri => $target, + ); + + return $uri; +} + +sub get_email_key { + my ($class, $email) = @_; + + my $digest = Digest->new(DIGEST_HASH); + $digest->add($email); + $digest->add(remote_ip()); + $digest->add(Bugzilla->localconfig->{site_wide_secret}); + return $digest->hexdigest; +} + +sub get_state { + my ($class, $target) = @_; + my $sorted_target = $target->clone; + $sorted_target->query_form({}); + + foreach my $key (sort $target->query_param) { + $sorted_target->query_param($key, $target->query_param($key)); + } + + $sorted_target->query_param_delete("code"); + $sorted_target->query_param_delete("state"); + $sorted_target->query_param_delete('github_email_key'); + $sorted_target->query_param_delete('github_email'); + $sorted_target->query_param_delete('GoAheadAndLogIn'); + $sorted_target->query_param_delete('github_login'); + + my $digest = Digest->new(DIGEST_HASH); + $digest->add($sorted_target->as_string); + $digest->add(remote_ip()); + $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; -- cgit v1.2.3-24-g4f1b