The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
##@file
# Google authentication backend file - Migration to OpenID Connect

##@class
# Google authentication backend class.
package Lemonldap::NG::Portal::AuthGoogleMigration;

#== Disclaimer
# This module provides a quick and unsafe solution to
# migrate from old Google module (OpenID 2.0)
# It will be replaced by AuthOpenIDConnect in next
# major version
#==

use strict;
use Lemonldap::NG::Portal::Simple;
use Lemonldap::NG::Portal::_Browser;
use URI::Escape;
use JSON;
use MIME::Base64
  qw/encode_base64url encode_base64 decode_base64url decode_base64/;
our @ISA = (qw(Lemonldap::NG::Portal::_Browser));

our $VERSION = '1.4.4';

## @apmethod int authInit()
# @return Lemonldap::NG::Portal constant
sub authInit {
    my $self = shift;

    unless ( $self->{googleClientId} and $self->{googleClientSecret} ) {
        $self->lmLog(
"You need to register this application and configure client ID and client secret",
            'error'
        );
        return PE_ERROR;
    }
    PE_OK;
}

## @apmethod int extractFormInfo()
# Read username return by Google authentication system.
# @return Lemonldap::NG::Portal constant
sub extractFormInfo {
    my $self = shift;

    my $response_type = "code";
    my $scope         = "openid email";
    my $client_id     = $self->{googleClientId};
    my $client_secret = $self->{googleClientSecret};
    my $redirect_uri  = $self->{portal} . "?googlecb=1";
    my $state         = encode_base64url( $self->{urldc} );

    # Ask for profile if googleExportedVars requested
    my %vars = ( %{ $self->{exportedVars} }, %{ $self->{googleExportedVars} } );
    if (%vars) {
        $scope .= " profile";
    }

    my $callback = $self->param("googlecb");

    if ($callback) {
        my $code  = $self->param("code");
        my $error = $self->param("error");

        if ($error) {
            $self->lmLog( "Error returned by Google: $error", 'error' );
            return PE_ERROR;
        }

        my %form;
        $form{"code"}          = $code;
        $form{"redirect_uri"}  = $redirect_uri;
        $form{"grant_type"}    = "authorization_code";
        $form{"client_id"}     = $client_id;
        $form{"client_secret"} = $client_secret;
        my $response =
          $self->ua->post( "https://www.googleapis.com/oauth2/v3/token",
            \%form, "Content-Type" => 'application/x-www-form-urlencoded' );

        if ( $response->is_error ) {
            $self->lmLog( "Error returned by Google: " . $response->message,
                'error' );
            return PE_ERROR;
        }

        my $content = $response->decoded_content;

        my $json;
        eval { $json = decode_json $content; };

        my $access_token = $json->{access_token};
        my $id_token     = $json->{id_token};

        my ( $id_token_header, $id_token_payload, $id_token_signature ) =
          split( /\./, $id_token );

        my $id_token_payload_raw = decode_base64url($id_token_payload);

        my $id_token_payload_hash;
        eval { $id_token_payload_hash = decode_json $id_token_payload_raw; };

        $self->{user} = $id_token_payload_hash->{email};

        if ( $self->param("state") ) {
            $self->{urldc} = decode_base64url( $self->param("state") );
        }

        if (%vars) {

            # Request UserInfo
            my $ui_response = $self->ua->get(
                "https://www.googleapis.com/oauth2/v3/userinfo",
                "Authorization" => "Bearer $access_token"
            );
            my $ui_content = $ui_response->decoded_content;

            my $ui_json;
            eval { $ui_json = decode_json($ui_content); };

            # Convert OpenID attribute name into OIDC UserInfo field
            my $convertAttr = {
                "firstname" => "given_name",
                "lastname"  => "family_name",
                "language"  => "locale",
                "email"     => "email",
            };

            # Store attributes in session
            while ( my ( $k, $v ) = each %vars ) {
                my $attr = $k;
                $attr =~ s/^!//;
                my $oidc_attr = $convertAttr->{$v};
                $self->{sessionInfo}->{$attr} = $ui_json->{$oidc_attr};
            }
        }

        return PE_OK;
    }

    my $redirect_url =
        "https://accounts.google.com/o/oauth2/auth"
      . "?response_type="
      . uri_escape($response_type)
      . "&client_id="
      . uri_escape($client_id)
      . "&scope="
      . uri_escape($scope)
      . "&redirect_uri="
      . uri_escape($redirect_uri)
      . "&state="
      . uri_escape($state);

    $self->{urldc} = $redirect_url;
    $self->lmLog( "Redirect user to $redirect_url", 'debug' );
    $self->_sub('autoRedirect');
}

## @apmethod int setAuthSessionInfo()
# Set _user and authenticationLevel.
# @return Lemonldap::NG::Portal constant
sub setAuthSessionInfo {
    my $self = shift;

    $self->{sessionInfo}->{'_user'} = $self->{user};

    $self->{sessionInfo}->{authenticationLevel} = $self->{googleAuthnLevel};

    PE_OK;
}

## @apmethod int authenticate()
# Does nothing.
# @return Lemonldap::NG::Portal constant
sub authenticate {
    PE_OK;
}

## @apmethod int authFinish()
# Does nothing.
# @return Lemonldap::NG::Portal constant
sub authFinish {
    PE_OK;
}

## @apmethod int authLogout()
# Does nothing
# @return Lemonldap::NG::Portal constant
sub authLogout {
    PE_OK;
}

## @apmethod boolean authForce()
# Does nothing
# @return result
sub authForce {
    return 0;
}

## @method string getDisplayType
# @return display type
sub getDisplayType {
    return "logo";
}

1;
__END__

=head1 NAME

=encoding utf8

Lemonldap::NG::Portal::AuthGoogle - Perl extension for building Lemonldap::NG
compatible portals with Google authentication (migration to OpenID Connect).

=head1 SYNOPSIS

  use Lemonldap::NG::Portal::SharedConf;
  my $portal = new Lemonldap::NG::Portal::Simple(
         configStorage     => {...}, # See Lemonldap::NG::Portal
         authentication    => 'GoogleMigration',
         googleClientId => '...',
         googleClientSecret => '...',
    );

  if($portal->process()) {
    # Write here the menu with CGI methods. This page is displayed ONLY IF
    # the user was not redirected here.
    print $portal->header('text/html; charset=utf-8'); # DON'T FORGET THIS (see CGI(3))
    print "...";
  }
  else {
    # If the user enters here, IT MEANS THAT CAS REDIRECTION DOES NOT WORK
    print $portal->header('text/html; charset=utf-8'); # DON'T FORGET THIS (see CGI(3))
    print "<html><body><h1>Unable to work</h1>";
    print "This server isn't well configured. Contact your administrator.";
    print "</body></html>";
  }

=head1 DESCRIPTION

This library just overload few methods of Lemonldap::NG::Portal::Simple to use
Google authentication mechanism.

See L<Lemonldap::NG::Portal::Simple> for usage and other methods.

=head1 SEE ALSO

L<Lemonldap::NG::Portal>, L<Lemonldap::NG::Portal::Simple>,
L<http://lemonldap-ng.org/>,
L<https://developers.google.com/accounts/docs/OpenID>

=head1 AUTHOR

=over

=item Clement Oudot, E<lt>clem.oudot@gmail.comE<gt>

=item Xavier Guimard, E<lt>x.guimard@free.frE<gt>

=back

=head1 BUG REPORT

Use OW2 system to report bug or ask for features:
L<http://jira.ow2.org>

=head1 DOWNLOAD

Lemonldap::NG is available at
L<http://forge.objectweb.org/project/showfiles.php?group_id=274>

=head1 COPYRIGHT AND LICENSE

=over

=item Copyright (C) 2013 by Xavier Guimard, E<lt>x.guimard@free.frE<gt>

=item Copyright (C) 2013 by Clement Oudot, E<lt>clem.oudot@gmail.comE<gt>

=back

This library is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see L<http://www.gnu.org/licenses/>.

=cut