lemonldap-ng/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Auth/GitHub.pm

322 lines
8.6 KiB
Perl

package Lemonldap::NG::Portal::Auth::GitHub;
use strict;
use JSON;
use Mouse;
use MIME::Base64 qw/encode_base64 decode_base64/;
use Lemonldap::NG::Common::FormEncode;
use Lemonldap::NG::Common::UserAgent;
use Lemonldap::NG::Portal::Main::Constants qw(PE_OK PE_ERROR PE_REDIRECT);
our $VERSION = '2.0.12';
extends 'Lemonldap::NG::Portal::Main::Auth';
# INITIALIZATION
# return LWP::UserAgent object
has ua => (
is => 'rw',
lazy => 1,
builder => sub {
# TODO : LWP options to use a proxy for example
my $ua = Lemonldap::NG::Common::UserAgent->new( $_[0]->{conf} );
$ua->env_proxy();
return $ua;
}
);
has githubAuthorizationEndpoint => (
is => 'ro',
lazy => 1,
default => sub {
$_[0]->conf->{githubAuthorizationEndpoint}
|| 'https://github.com/login/oauth/authorize';
}
);
has githubTokenEndpoint => (
is => 'ro',
lazy => 1,
default => sub {
$_[0]->conf->{githubTokenEndpoint}
|| 'https://github.com/login/oauth/access_token';
}
);
has githubUserEndpoint => (
is => 'ro',
lazy => 1,
default => sub {
$_[0]->conf->{githubUserEndpoint}
|| 'https://api.github.com/user';
}
);
has githubPublicKeysEndpoint => (
is => 'ro',
lazy => 1,
default => sub {
$_[0]->conf->{githubPublicKeysEndpoint}
|| 'https://api.github.com/user/keys';
}
);
has githubGPGKeysEndpoint => (
is => 'ro',
lazy => 1,
default => sub {
$_[0]->conf->{githubGPGKeysEndpoint}
|| 'https://api.github.com/user/gpg_keys';
}
);
sub init {
my ($self) = @_;
my $ret = 1;
foreach my $arg (qw(githubClientID githubClientSecret)) {
unless ( $self->conf->{$arg} ) {
$ret = 0;
$self->error("Parameter $arg is required");
}
}
return $ret;
}
# RUNNING METHODS
sub extractFormInfo {
my ( $self, $req ) = @_;
my $nonce = time;
# Build redirect_uri
my $callback_url = $self->conf->{portal};
# Check return values
my $code = $req->param("code");
my $state = $req->param("state");
# Code
if ($code) {
my %form;
$form{"code"} = $code;
$form{"state"} = $state if $state;
$form{"client_id"} = $self->conf->{githubClientID};
$form{"client_secret"} = $self->conf->{githubClientSecret};
$form{"redirect_uri"} = $callback_url;
my $response = $self->ua->post(
$self->githubTokenEndpoint,
\%form,
"Content-Type" => 'application/x-www-form-urlencoded',
'Accept' => 'application/json'
);
if ( $response->is_error ) {
$self->logger->error(
"Bad authorization response: " . $response->message );
$self->logger->debug( $response->content );
return PE_ERROR;
}
my $content = $response->decoded_content;
my $json_hash;
eval { $json_hash = from_json( $content, { allow_nonref => 1 } ); };
if ($@) {
$self->logger->error("Unable to decode JSON $content");
return PE_ERROR;
}
my $access_token = $json_hash->{access_token};
$self->logger->debug("Get access token $access_token from GitHub");
# Call User EndPoint URI
$self->logger->debug(
"Call GitHub User Endpoint " . $self->githubUserEndpoint );
my $user_response = $self->ua->get( $self->githubUserEndpoint,
"Authorization" => "token $access_token" );
if ( $user_response->is_error ) {
$self->logger->error(
"Bad authorization response: " . $user_response->message );
$self->logger->debug( $user_response->content );
return PE_ERROR;
}
my $user_content = $user_response->decoded_content;
$self->logger->debug("Response from GitHub User API: $user_content");
eval { $json_hash = from_json( $user_content, { allow_nonref => 1 } ); };
if ($@) {
$self->logger->error("Unable to decode JSON $user_content");
return PE_ERROR;
}
foreach ( keys %$json_hash ) {
$req->data->{githubData}->{$_} = $json_hash->{$_};
}
# Fetch SSH keys
if ( $self->conf->{githubScope} =~ /public_key/ ) {
$self->logger->debug("Scope public_key requested, fetch SSH keys");
my $public_keys_response = $self->ua->get(
$self->githubPublicKeysEndpoint,
"Authorization" => "token $access_token"
);
if ( $public_keys_response->is_error ) {
$self->logger->error( "Bad authorization response: "
. $public_keys_response->message );
$self->logger->debug( $public_keys_response->content );
return PE_ERROR;
}
my $public_keys_content = $public_keys_response->decoded_content;
$self->logger->debug(
"Response from GitHub Keys API: $public_keys_content");
eval {
$json_hash =
from_json( $public_keys_content, { allow_nonref => 1 } );
};
if ($@) {
$self->logger->error(
"Unable to decode JSON $public_keys_content");
return PE_ERROR;
}
$req->data->{githubData}->{"public_keys"} = $json_hash;
}
# Fetch GPG keys
if ( $self->conf->{githubScope} =~ /gpg_key/ ) {
$self->logger->debug("Scope gpg_key requested, fetch SSH keys");
my $gpg_keys_response =
$self->ua->get( $self->githubGPGKeysEndpoint,
"Authorization" => "token $access_token" );
if ( $gpg_keys_response->is_error ) {
$self->logger->error( "Bad authorization response: "
. $gpg_keys_response->message );
$self->logger->debug( $gpg_keys_response->content );
return PE_ERROR;
}
my $gpg_keys_content = $gpg_keys_response->decoded_content;
$self->logger->debug(
"Response from GitHub GPG Keys API: $gpg_keys_content");
eval {
$json_hash =
from_json( $gpg_keys_content, { allow_nonref => 1 } );
};
if ($@) {
$self->logger->error("Unable to decode JSON $gpg_keys_content");
return PE_ERROR;
}
$req->data->{githubData}->{"gpg_keys"} = $json_hash;
}
# Extract state
if ($state) {
my $stateSession = $self->p->getApacheSession( $state, 1 );
$req->urldc( $stateSession->data->{urldc} );
$req->{checkLogins} = $stateSession->data->{checkLogins};
$stateSession->remove;
}
$req->user(
$req->data->{githubData}->{ $self->conf->{githubUserField} } );
$self->logger->debug( "Good GitHub authentication for " . $req->user );
return PE_OK;
}
# No code, redirect to GitHub
else {
$self->logger->debug('Redirection to GitHub');
# Store state
my $stateSession =
$self->p->getApacheSession( undef, 1, 0, 'GitHubState' );
my $stateInfos = {};
$stateInfos->{_utime} = time() + $self->conf->{timeout};
$stateInfos->{urldc} = $req->urldc;
$stateInfos->{checkLogins} = $req->{checkLogins};
$stateSession->update($stateInfos);
my $authn_uri = $self->githubAuthorizationEndpoint;
my $client_id = $self->conf->{githubClientID};
my $scope = $self->conf->{githubScope};
$authn_uri .= '?'
. build_urlencoded(
response_type => 'code',
client_id => $client_id,
redirect_uri => $callback_url,
scope => $scope,
state => $stateSession->id,
);
$req->urldc($authn_uri);
$self->logger->debug( "Redirect user to " . $req->urldc );
return PE_REDIRECT;
}
}
sub setAuthSessionInfo {
my ( $self, $req ) = @_;
$req->{sessionInfo}->{authenticationLevel} =
$self->conf->{githubAuthnLevel};
foreach ( keys %{ $req->data->{githubData} } ) {
$req->{sessionInfo}->{ 'github_' . $_ } =
$req->data->{githubData}->{$_};
}
return PE_OK;
}
sub authenticate {
return PE_OK;
}
sub authFinish {
return PE_OK;
}
sub authLogout {
return PE_OK;
}
sub authForce {
return 0;
}
sub getDisplayType {
return "logo";
}
1;