package Thirdlane::Auth::OAuth2::Google; use parent 'Thirdlane::Auth::OAuth2::Generic'; use utf8; use strict; use warnings; use URI; use Digest::MD5 qw(md5_hex); use JSON::XS qw(encode_json decode_json); use constant { DISCOVERY_URL => q{https://accounts.google.com/.well-known/openid-configuration} }; sub discovery_url { my $self = shift; return DISCOVERY_URL; } sub get_access_token { my $self = shift; my ( $args ) = @_; my $result = { }; my $access_token_query = { code => $args->{code}, client_id => $self->{provider}->{client_id}, client_secret => $self->{provider}->{client_secret}, redirect_uri => $self->{provider}->{redirect_uri}, grant_type => "authorization_code" }; if ( defined $args ) { $access_token_query = $self->merge( $access_token_query, $args ); } my $token_response = eval { $self->web_req( { method => "POST", url => $self->get_provider_data("token_endpoint"), body => [ %{ $access_token_query } ], json => 1 } ) }; if ( defined $token_response ) { $result->{access_token} = $token_response; # my $key_response = $self->cache->get('oauth::config::provider::' . $self->name . '::jwks'); # unless ( defined $key_response ) { my $key_response = eval { $self->web_req( { method => "GET", url => $self->get_provider_data("jwks_uri"), json => 1 } ) }; # $self->cache->set('oauth::config::provider::' . $self->name . '::jwks', $key_response, 60*60) # if defined $key_response; # } if ( defined $key_response ) { $result->{public_keys} = $key_response; my $verify_result = eval { $self->try_decode_jwt($token_response->{id_token}, $key_response) }; if ( $verify_result ) { $result->{id_token} = $verify_result; } } } return $result; } sub refresh_access_token { my $self = shift; my ( $args ) = @_; my $result = { }; my $refresh_token_query = { client_id => $self->{provider}->{client_id}, client_secret => $self->{provider}->{client_secret}, refresh_token => $args->{refresh_token}, grant_type => "refresh_token" }; if ( defined $args ) { $refresh_token_query = $self->merge( $refresh_token_query, $args ); } my $token_response = eval { $self->web_req( { method => "POST", url => $self->get_provider_data("token_endpoint"), body => [ %{ $refresh_token_query } ], json => 1 } ) }; if ( defined $token_response ) { $result->{access_token} = $token_response; } return $result; } sub revoke_token { my $self = shift; my ( $args ) = @_; my $result = { }; my $revoke_token_query = { token => $args->{token} || $args->{access_token} || $args->{refresh_token} }; # if ( defined $args ) { # $revoke_token_query = $self->merge( $revoke_token_query, $args ); # } $self->debug(["Revoke token: ", $args, "query", $revoke_token_query]); my $revoke_token_response = eval { $self->web_req( { method => "POST", url => $self->get_provider_data("revocation_endpoint"), body => [ %{ $revoke_token_query } ] } ) }; $self->debug(["Revoke token result: ", $revoke_token_response]); if ( defined $revoke_token_response ) { return 1; } return undef; } sub authentication_url { my $self = shift; my ( $args ) = @_; my $uri = URI->new( $self->get_provider_data("authorization_endpoint") ); my $authentication_query = { scope => "openid email profile https://www.googleapis.com/auth/contacts.readonly", # state => "state", prompt => "select_account", #none consent select_account "" # display => "", #page, popup, touch, and wap # login_hint => "", #email google id "sub" str access_type => "offline", #offline(refresh token) online include_granted_scopes => "true", response_type => "code", client_id => $self->{provider}->{client_id}, redirect_uri => $self->{provider}->{redirect_uri} }; if ( defined $args ) { $authentication_query = $self->merge( $authentication_query, $args ); } $uri->query_form( $authentication_query ); return $uri->as_string; } sub authorization_url { my $self = shift; my ( $args ) = @_; my $uri = URI->new( $self->get_provider_data("authorization_endpoint") ); my $authorization_query = { scope => "https://www.googleapis.com/auth/contacts.readonly", # state => "state", prompt => "select_account", #none consent select_account "" # login_hint => "", #email google id "sub" str access_type => "offline", #offline(refresh token) online include_granted_scopes => "true", response_type => "code", client_id => $self->{provider}->{client_id}, redirect_uri => $self->{provider}->{redirect_uri} }; if ( defined $args ) { $authorization_query = $self->merge( $authorization_query, $args ); } $uri->query_form( $authorization_query ); return $uri->as_string; } sub signin_url { my $self = shift; my ( $args ) = @_; my $uri = URI->new( $self->get_provider_data("authorization_endpoint") ); my $authentication_query = { scope => "openid email profile https://www.googleapis.com/auth/contacts.readonly", # state => "state", # prompt => "", #none consent select_account "" # display => "", #page, popup, touch, and wap # login_hint => "", #email google id "sub" str access_type => "offline", #offline(refresh token) online include_granted_scopes => "true", response_type => "code", client_id => $self->{provider}->{client_id}, redirect_uri => $self->{provider}->{redirect_uri} }; if ( defined $args ) { $authentication_query = $self->merge( $authentication_query, $args ); } $uri->query_form( $authentication_query ); return $uri->as_string; } sub is_provider { my $self = shift; return 1; } sub get_access_token_from_context { my $self = shift; my ( $args ) = @_; return undef unless exists $args->{context} && defined $args->{context} && length $args->{context}; my $auth_data = $self->get_auth_data( { context => $args->{context} } ); if ( exists $auth_data->{ $args->{context} }->{authentications}->{access_token}->{string} ) { return $auth_data->{ $args->{context} }->{authentications}->{access_token}->{string}; } return undef; } sub build_ua { my $self = shift; my ( $args ) = @_; $self->debug(["Building user agent args: ", $args]); return undef unless exists $args->{context} && defined $args->{context} && length $args->{context}; # my $auth_data = $self->get_auth_data( { context => $args->{context} } ); # if ( exists $auth_data->{ $args->{context} }->{authentications}->{access_token}->{string} ) { # $args->{access_token} = $auth_data->{ $args->{context} }->{authentications}->{access_token}->{string}; # } my $ua = $self->get_ua(); $ua->add_handler( request_prepare => sub { my($request, $ua, $h) = @_; $request->header( Authorization => 'Bearer ' . $self->get_access_token_from_context( { context => $args->{context} } ) ) if exists $args->{context}; $self->debug(["User agent request: ", $request]); $self->debug(["User agent request auth data ", 'Bearer ' . $self->get_access_token_from_context( { context => $args->{context} } ), $args ]); }); $ua->add_handler( response_done => sub { my($response, $ua, $h) = @_; $response->header( Tlprovider => $self->name ); $self->debug(["User agent response: ", $response]); if ( defined $response ) { my $res_data = $response->decoded_content(); $res_data = eval { decode_json( $res_data ) }; unless ( $@ ) { if ( defined $res_data && exists $res_data->{error} && exists $res_data->{error}->{status} ) { if ( $res_data->{error}->{status} eq q{UNAUTHENTICATED} ) { $response->header( Tlintstatus => 'stale_access_token'); $response->header( Tlintstatuscontext => $args->{context} ); $self->debug(["Stale access_token detected"]); } } } } }); $self->debug(["Building user agent finished: ", $ua]); return $ua; } 1; __END__ { [56/1855] "error": { "code": 401, "message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.", "status": "UNAUTHENTICATED" } }