package Lemonldap::NG::Portal::Plugins::Upgrade;

use strict;
use Mouse;
use Lemonldap::NG::Portal::Main::Constants qw(
  PE_OK
  PE_CONFIRM
  PE_TOKENEXPIRED
  PE_SENDRESPONSE
);

our $VERSION = '2.23.0';

extends 'Lemonldap::NG::Portal::Main::Plugin';

sub forAuthUser { 'handleAuthRequests' }

# INITIALIZATION

has ott => (
    is      => 'rw',
    lazy    => 1,
    default => sub {
        my $ott =
          $_[0]->{p}->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken');
        $ott->timeout( $_[0]->{conf}->{formTimeout} );
        return $ott;
    }
);

sub init {
    my ($self) = @_;
    if ( $self->conf->{forceGlobalStorageUpgradeOTT} ) {
        $self->logger->debug(
            "-> Upgrade tokens will be stored into global storage");
        $self->ott->cache(undef);
    }
    $self->addAuthRoute( upgradesession => 'askUpgrade', ['GET'] )
      ->addAuthRoute( upgradesession => 'confirmUpgrade', ['POST'] )
      ->addAuthRoute( renewsession   => 'askRenew',       ['GET'] )
      ->addAuthRoute( renewsession   => 'confirmRenew',   ['POST'] );

    return 1;
}

sub askUpgrade {
    my ( $self, $req ) = @_;
    $self->ask( $req, '/upgradesession', 'askToUpgrade', 'upgradeSession' );
}

sub askRenew {
    my ( $self, $req ) = @_;
    $self->ask( $req, '/renewsession', 'askToRenew', 'renewSession' );
}

sub confirmUpgrade {
    my ( $self, $req ) = @_;

    $req->data->{discardChoiceForCurrentSession} = 1;

    # sfOnlyUpgrade feature can only be used during session renew
    return $self->confirm( $req, $self->conf->{sfOnlyUpgrade} );
}

sub confirmRenew {
    my ( $self, $req ) = @_;
    return $self->confirm($req);
}

# RUNNING METHOD

sub ask {
    my ( $self, $req, $form_action, $message, $buttonlabel ) = @_;

    # Check if auth is already running
    # and verify token
    return $self->confirm($req)
      if ( $req->param('upgrading') or $req->param('kerberos') );

    my $url          = $req->param('url')          || '';
    my $forceUpgrade = $req->param('forceUpgrade') || '';
    my $action       = ( $message =~ /^askTo(\w+)$/ )[0];
    $self->logger->debug(" -> $action required");
    $self->logger->debug(" -> Skip confirmation is enabled")
      if $self->conf->{"skip${action}Confirmation"};

    $url          = '' if $self->p->checkXSSAttack( 'url', $url );
    $forceUpgrade = ''
      if $self->p->checkXSSAttack( 'forceUpgrade', $forceUpgrade );

    my $cacheTag = $self->p->cacheTag;

    # Display form
    return $self->p->sendHtml(
        $req,
        'upgradesession',
        params => {
            FORMACTION   => $form_action,
            PORTALBUTTON => 1,
            MSG          => $message,
            BUTTON       => $buttonlabel,
            CONFIRMKEY   => $self->p->stamp,
            FORCEUPGRADE => $forceUpgrade,
            URL          => $url,
            (
                $self->conf->{"skip${action}Confirmation"}
                ? ( CUSTOM_SCRIPT =>
qq'<script type="text/javascript" src="$self->{p}->{staticPrefix}/common/js/autoRenew.min.js?v=$cacheTag"></script>'
                  )
                : ()
            )
        }
    );
}

sub confirm {
    my ( $self, $req, $sfOnly ) = @_;
    my $upg;

    my $old_id = $req->userData->{_session_id};

    if ( $req->param('kerberos') ) {
        $upg = 1;
    }
    else {
        if ( my $t = $req->param('upgrading') ) {
            if ( $self->ott->getToken($t) ) {
                $upg = 1;
            }
            else {
                return $self->p->doPE( $req, PE_TOKENEXPIRED );
            }
        }
    }

    $req->steps( ['controlUrl'] );
    my $res = $self->p->process($req);
    return $self->p->doPE( $req, $res ) if $res;

    if ( $upg or $req->param('confirm') and $req->param('confirm') == 1 ) {
        $req->data->{noerror} = 1;

        if ($sfOnly) {
            $req->data->{doingSfUpgrade} = 1;

            # Short circuit the first part of login, only do a 2FA step
            return $self->p->do(
                $req,
                [
                    'importHandlerData',      'secondFactor',
                    @{ $self->p->afterData }, $self->p->validSession,
                    @{ $self->p->endAuth },
                ]
            );
        }
        else {
            $self->p->setHiddenFormValue(
                $req,
                upgrading => $self->ott->createToken,
                '', 0
            );    # Insert token
                  # Do a regular login
            my $res = $self->p->login($req);

            if ( $old_id and $req->id and $old_id ne $req->id ) {
                $self->p->processHook( $req, 'updateSessionId', $old_id,
                    $req->id );
            }
            return $res;
        }
    }
    else {

        # Go to portal
        $self->logger->debug("Upgrade session did not trigger -> Go to Portal");
        $req->mustRedirect(1);
        return $self->p->doPE( $req, PE_OK );
    }
}

# Takes care of callbacks for external methods (SAML, OIDC...)
sub handleAuthRequests {
    my ( $self, $req ) = @_;
    if (    $self->p->_authentication->can('isCallback')
        and $self->p->_authentication->isCallback($req) )
    {
        $req->id(undef);
        $req->parameters->{'confirm'} = 1;
        $req->response( $self->confirm($req) );
        return PE_SENDRESPONSE;
    }
    return PE_OK;
}

1;
