lemonldap-ng/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/MailReset.pm

408 lines
12 KiB
Perl
Raw Normal View History

2017-01-28 13:58:22 +01:00
package Lemonldap::NG::Portal::Plugins::Register;
use strict;
use Encode;
use Mouse;
use POSIX qw(strftime);
use Lemonldap::NG::Portal::Main::Constants qw(
PE_BADCREDENTIALS
PE_BADMAILTOKEN
PE_CAPTCHAEMPTY
PE_CAPTCHAERROR
PE_MAILCONFIRMOK
PE_MAILERROR
PE_MAILFIRSTACCESS
PE_MAILFORMEMPTY
PE_MAILOK
PE_MALFORMEDUSER
PE_NOTOKEN
PE_OK
PE_PASSWORDFIRSTACCESS
PE_PASSWORDFORMEMPTY
PE_PASSWORD_OK
PE_TOKENEXPIRED
PE_USERNOTFOUND
);
our $VERSION = '2.0.0';
extends 'Lemonldap::NG::Portal::Main::Plugin',
'Lemonldap::NG::Portal::Lib::SMTP';
# PROPERTIES
# Mail timeout token generator
has mailott => (
is => 'rw',
default => sub {
my $ott =
$_[0]->{p}->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken');
$ott->timeout( $_[0]->conf->{mailTimeout} || $_[0]->conf->{timeout} );
return $ott;
}
);
# Form timout token generator (used if requireToken is set)
has ott => ( is => 'rw' );
# Captcha generator
has captcha => ( is => 'rw' );
# INITIALIZATION
sub init {
my ($self) = @_;
# Declare REST route
$self->addUnauthRoute( resetpwd => 'resetPwd', [ 'POST', 'GET' ] );
# Initialize Captcha if needed
if ( $self->conf->{captcha_mail_enabled} ) {
$self->captcha( $self->p->loadModule('::Lib::Captcha') ) or return 0;
}
# Initialize form token if needed (captcha provides also a token)
elsif ( $self->conf->{requireToken} ) {
$_[0]->ott(
$_[0]->p->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken') )
or return 0;
$_[0]->ott->timeout( $_[0]->conf->{formTimeout} );
}
2017-01-29 10:11:20 +01:00
return 1;
2017-01-28 13:58:22 +01:00
}
# RUNNIG METHODS
# Handle reset requests
sub resetPwd {
my ( $self, $req ) = @_;
# Check parameters
$req->error( $self->_reset($req) );
# Display form
my ( $tpl, $prms ) = $self->display($req);
return $self->p->sendHtml( $req, $tpl, params => $prms );
}
sub _reset {
my ( $self, $req ) = @_;
my ( $mailToken, $newPwd, $confirmPwd );
# Check for first access
$mailToken = $req->param('mail_token');
unless ( $req->param('mail') || $mailToken ) {
$self->setSecurity($req);
return PE_MAILFIRSTACCESS if ( $req->method eq 'GET' );
return PE_MAILFORMEMPTY;
}
# Check for mail link
if ($mailToken) {
$self->lmLog( "Token given for password reset: " . $mailToken,
'debug' );
# Check if token is valid
my $mailSession = $self->mailott->getToken($mailToken);
unless ($mailSession) {
$self->p->userNotice('Bad reset token');
return PE_BADMAILTOKEN;
}
$req->{mail} = $mailSession->{user};
$req->datas->{mailAddress} =
$mailSession->{ $self->conf->{mailSessionKey} };
$self->lmLog( 'User associated to: ' . $req->{mail} . 'debug' );
}
# Check for values posted
else {
# Use submitted value
$req->{mail} = $req->param('mail');
# Check if token exists
my $token;
if ( $self->ott or $self->captcha ) {
$token = $req->param('token');
unless ($token) {
$self->setSecurity($req);
$self->p->userNotice('Reset try without token');
return PE_NOTOKEN;
}
}
# Captcha for register form
if ( $self->captcha ) {
my $captcha = $req->param('captcha');
unless ($captcha) {
$self->p->userNotice('Reset try with captcha not filled');
# Set captcha or token
$self->setSecurity($req);
return PE_CAPTCHAEMPTY;
}
# Check captcha
unless ( $self->captcha->validateCaptcha( $token, $captcha ) ) {
$self->p->userInfo('Captcha failed: wrong code');
# Set captcha or token
$self->setSecurity($req);
return PE_CAPTCHAERROR;
}
$self->lmLog( "Captcha code verified", 'debug' );
}
elsif ( $self->ott ) {
unless ( $self->ott->getToken($token) ) {
$self->setSecurity($req);
$self->p->userNotice('Reset try with expired/bas token');
return PE_TOKENEXPIRED;
}
}
unless ( $req->{mail} =~ /$self->{conf}->{userControl}/o ) {
$self->setSecurity($req);
return PE_MALFORMEDUSER;
}
}
# Search user in database
$req->user( $req->{mail} );
$req->steps(
[
'getUser', 'setSessionInfo',
'setMacros', 'setGroups',
'setPersistentSessionInfo', 'setLocalGroups'
]
);
if ( my $error = $self->p->process($req) ) {
if ( $error == PE_USERNOTFOUND or $error = PE_BADCREDENTIALS ) {
$self->p->userNotice(
"Reset asked for a unvalid user ($req->{mail})");
# To avoid mail enumeration, return OK
return PE_MAILCONFIRMOK;
}
return $error;
}
# Build temporary session
my $mailSession;
unless ( ( $mailSession = $self->getMailSession( $req->{mail} ) )
or $mailToken )
{
# Create a new session
my $infos = {};
$mailSession = $self->p->getApacheSession();
# Set _utime for session autoremove
# Use default session timeout and mail session timeout to compute it
my $time = time();
my $timeout = $self->conf->{timeout};
my $mailTimeout = $self->conf->{mailTimeout} || $timeout;
$infos->{_utime} = $time + ( $mailTimeout - $timeout );
# Store expiration timestamp for further use
$infos->{mailSessionTimeoutTimestamp} = $time + $mailTimeout;
# Store start timestamp for further use
$infos->{mailSessionStartTimestamp} = $time;
# Store mail
$infos->{ $self->conf->{mailSessionKey} } =
$self->p->getFirstValue(
$req->{sessionInfo}->{ $self->conf->{mailSessionKey} } );
# Store user
$infos->{user} = $req->{mail};
# Store type
$infos->{_type} = "mail";
# Update session
$mailSession->update($infos);
}
# Send confirmation mail
unless ($mailToken) {
my $mailAlreadySent = ( $mailSession->{id} and !$req->id ) ? 1 : 0;
$req->{id} ||= $mailSession->{id};
$self->lmLog( 'Mail session found: ' . $mailSession->id, 'debug' );
# Mail session expiration date
my $expTimestamp = $mailSession->data->{mailSessionTimeoutTimestamp};
$self->lmLog( "Mail expiration timestamp: $expTimestamp", 'debug' );
my $expMailDate = strftime( "%d/%m/%Y", localtime $expTimestamp );
my $expMailTime = strftime( "%H:%M", localtime $expTimestamp );
# Mail session start date
my $startTimestamp = $mailSession->data->{mailSessionStartTimestamp};
$self->lmLog( "Mail start timestamp: $startTimestamp", 'debug' );
my $startMailDate = strftime( "%d/%m/%Y", localtime $startTimestamp );
my $startMailTime = strftime( "%H:%M", localtime $startTimestamp );
# Ask if user want another confirmation email
if ( $self->mailAlreadySent and !$req->param('resendconfirmation') ) {
$self->p->userNotice(
'Reset mail already sent to ' . $req->{mail} );
# To avoid enumeration, return OK
return PE_MAILCONFIRMOK;
}
# Get mail address
$req->datas->{mailAddress} ||=
$self->p->getFirstValue(
$req->{sessionInfo}->{ $self->conf->{mailSessionKey} } );
# Build confirmation url
my $url =
$self->conf->{mailUrl}
. "?mail_token="
. $req->{id}
. '&skin='
. $self->p->getSkin;
$url .= '&'
. $self->conf->{authChoiceParam} . '='
. $req->datas->{_authChoice}
if ( $req->datas->{_authChoice} );
# Build mail content
my $subject = $self->conf->{mailConfirmSubject};
my $body;
my $html;
if ( $self->conf->{mailConfirmBody} ) {
# We use a specific text message, no html
$body = $self->{mailConfirmBody};
}
else {
# Use HTML template
my $tplfile =
$self->conf->{templateDir} . '/'
. $self->conf->{portalSkin}
. '/mail_confirm.tpl';
$tplfile = $self->conf->{templateDir} . '/common/mail_confirm.tpl'
unless ( -e $tplfile );
my $template = HTML::Template->new( filename => $tplfile, );
$body = $template->output();
$html = 1;
}
# Replace variables in body
$body =~ s/\$expMailDate/$expMailDate/g;
$body =~ s/\$expMailTime/$expMailTime/g;
$body =~ s/\$url/$url/g;
$body =~ s/\$(\w+)/decode("utf8",$req->{sessionInfo}->{$1})/ge;
# Send mail
unless (
$self->send_mail( $self->{mailAddress}, $subject, $body, $html ) )
{
$self->lmLog( 'Unable to send reset mail', 'debug' );
# Don't return an error here to avoid enumeration
}
return PE_MAILCONFIRMOK;
}
# mailToken is valid, time to change password
# Check if user wants to generate the new password
if ( $req->param('reset') ) {
$self->lmLog(
"Reset password request for " . $req->{sessionInfo}->{_user},
'debug' );
# Generate a complex password
my $password =
$self->gen_password( $self->conf->{randomPasswordRegexp} );
$self->lmLog( "Generated password: " . $password, 'debug' );
$req->datas->{newpassword} = $password;
$req->datas->{confirmpassword} = $password;
$req->datas->{forceReset} = 1;
}
# Else a password is required in request
else {
$req->datas->{newpassword} = $req->param('newpassword');
$req->datas->{confirmpassword} = $req->param('confirmpassword');
unless ( $req->datas->{newpassword} && $req->datas->{confirmpassword} )
{
return PE_PASSWORDFIRSTACCESS if ( $req->method eq 'GET' );
return PE_PASSWORDFORMEMPTY;
}
# Modify the password TODO: change this
my $tmp = $self->conf->{portalRequireOldPassword};
$self->conf->{portalRequireOldPassword} = 0;
my $result = $self->p->_passwordDB->modifyPassword($req);
# Mail token can be used only one time, delete the session if all is ok
return $result unless ( $result == PE_PASSWORD_OK or $result == PE_OK );
}
# Send mail containing the new password
$req->datas->{mailAddress} ||=
$self->p->getFirstValue(
$req->{sessionInfo}->{ $self->conf->{mailSessionKey} } );
# Build mail content
my $subject = $self->conf->{mailSubject};
my $body;
my $html;
if ( $self->conf->{mailBody} ) {
# We use a specific text message, no html
$body = $self->{mailBody};
}
else {
# Use HTML template
my $tplfile =
$self->conf->{templateDir} . '/'
. $self->conf->{portalSkin}
. '/mail_password.tpl';
$tplfile = $self->conf->{templateDir} . '/common/mail_password.tpl'
unless ( -e $tplfile );
my $template = HTML::Template->new( filename => $tplfile, );
$body = $template->output();
$html = 1;
}
# Replace variables in body
my $password = $req->datas->{newpassword};
$body =~ s/\$password/$password/g;
$body =~ s/\$(\w+)/decode("utf8",$req->{sessionInfo}->{$1})/ge;
# Send mail
return PE_MAILERROR
unless $self->send_mail( $self->{mailAddress}, $subject, $body, $html );
PE_MAILOK;
}
sub setSecurity {
my ( $self, $req ) = @_;
if ( $self->captcha ) {
$self->captcha->setCaptcha($req);
}
elsif ( $self->ott ) {
$self->ott->setToken($req);
}
}
1;