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

565 lines
17 KiB
Perl

package Lemonldap::NG::Portal::Plugins::Register;
use strict;
use Encode;
use Mouse;
use POSIX qw(strftime);
use Lemonldap::NG::Common::FormEncode;
use Lemonldap::NG::Portal::Main::Constants qw(
PE_OK
PE_MAILOK
PE_NOTOKEN
PE_MAILERROR
PE_BADMAILTOKEN
PE_CAPTCHAEMPTY
PE_CAPTCHAERROR
PE_TOKENEXPIRED
PE_MAILCONFIRMOK
PE_MALFORMEDUSER
PE_REGISTERFORMEMPTY
PE_REGISTERFIRSTACCESS
PE_REGISTERALREADYEXISTS
PE_MAILCONFIRMATION_ALREADY_SENT
);
our $VERSION = '2.0.15';
extends qw(
Lemonldap::NG::Portal::Lib::SMTP
Lemonldap::NG::Portal::Main::Plugin
Lemonldap::NG::Portal::Lib::_tokenRule
);
# PROPERTIES
# Sub module (Demo, LDAP,...)
has registerModule => ( is => 'rw' );
# Register url to set in the mail
has registerUrl => (
is => 'rw',
lazy => 1,
default => sub {
my $p = $_[0]->conf->{portal};
$p =~ s#/*$##;
return "$p/register";
}
);
# Mail timeout token generator
has mailott => (
is => 'rw',
lazy => 1,
default => sub {
my $ott =
$_[0]->{p}->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken');
$ott->cache(0);
$ott->timeout( $_[0]->conf->{registerTimeout}
|| $_[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( register => 'register', [ 'POST', 'GET' ] );
# Initialize Captcha if needed
if ( $self->conf->{captcha_register_enabled} ) {
$self->captcha(1);
}
# Initialize form token if needed (captcha provides also a token)
else {
$_[0]->ott(
$_[0]->p->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken') )
or return 0;
$_[0]->ott->timeout( $_[0]->conf->{formTimeout} );
}
# Load registered module
$self->registerModule(
$self->p->loadPlugin( '::Register::' . $self->conf->{registerDB} ) )
or return 0;
return 1;
}
# RUNNIG METHODS
# Handle register requests
sub register {
my ( $self, $req ) = @_;
$self->p->controlUrl($req);
# Check parameters
$req->error( $self->_register($req) );
# Display form
my ( $tpl, $prms ) = $self->display($req);
return $self->p->sendHtml( $req, $tpl, params => $prms );
}
# Parameters check
sub _register {
my ( $self, $req ) = @_;
# Check if it's a first access
unless ( ( $req->method =~ /^POST$/i and $req->param('mail') )
or $req->param('register_token') )
{
# Set captcha or token
$self->setSecurity($req);
$self->logger->debug('First access to register form');
return PE_REGISTERFIRSTACCESS if ( $req->method eq 'GET' );
return PE_REGISTERFORMEMPTY;
}
# Get register token (mail link)
$req->data->{register_token} = $req->param('register_token');
# If a register token is present, find the corresponding info
if ( $req->data->{register_token} ) {
$self->logger->debug(
"Token provided for register: " . $req->data->{register_token} );
# Get the corresponding session
if ( my $data =
$self->mailott->getToken( $req->data->{register_token} ) )
{
$self->logger->debug(
'Token ' . $req->data->{register_token} . ' found' );
foreach (qw(mail firstname lastname ipAddr)) {
$req->data->{registerInfo}->{$_} = $data->{$_};
}
$self->logger->debug( "User associated to token: "
. $req->data->{registerInfo}->{mail} );
}
else {
return PE_BADMAILTOKEN;
}
}
# Case else: user tries to register
else {
# Use submitted value
$req->data->{registerInfo}->{mail} = $req->param('mail');
$req->data->{registerInfo}->{firstname} = $req->param('firstname');
$req->data->{registerInfo}->{lastname} = $req->param('lastname');
$req->data->{registerInfo}->{ipAddr} = $req->address;
# Check captcha/token only if register session does not already exist
if ( $req->data->{registerInfo}->{mail}
and
!$self->getRegisterSession( $req->data->{registerInfo}->{mail} ) )
{
# Captcha for register form
if ( $self->captcha ) {
my $result = $self->p->_captcha->check_captcha($req);
if ($result) {
$self->logger->debug("Captcha code verified");
}
else {
$self->setSecurity($req);
$self->userLogger->warn("Captcha failed");
return PE_CAPTCHAERROR;
}
}
elsif ( $self->ottRule->( $req, {} ) ) {
my $token = $req->param('token');
unless ($token) {
$self->setSecurity($req);
$self->userLogger->warn('Register try without token');
return PE_NOTOKEN;
}
unless ( $self->ott->getToken($token) ) {
$self->setSecurity($req);
$self->userLogger->warn(
'Register try with expired/bad token');
return PE_TOKENEXPIRED;
}
}
}
}
# Check mail
return PE_MALFORMEDUSER
unless ( $req->data->{registerInfo}->{mail} =~
m/$self->{conf}->{userControl}/o );
# Search for user using UserDB module
# If the user already exists, register is forbidden
$req->user( $req->data->{registerInfo}->{mail} );
if ( $self->p->_userDB->getUser( $req, useMail => 1 ) == PE_OK ) {
$self->userLogger->error(
"Register: refuse mail $req->{data}->{registerInfo}->{mail} because already exists in UserDB"
);
return PE_REGISTERALREADYEXISTS;
}
my $register_session =
$self->getRegisterSession( $req->data->{registerInfo}->{mail} );
$req->data->{mail_already_sent} =
( $register_session and !$req->id ) ? 1 : 0;
# Skip this step if confirmation was already sent
unless ( $req->data->{register_token} or $register_session ) {
# Create mail token
$register_session = $self->mailott->createToken( {
mail => $req->data->{registerInfo}->{mail},
firstname => $req->data->{registerInfo}->{firstname},
lastname => $req->data->{registerInfo}->{lastname},
ipAddr => $req->data->{registerInfo}->{ipAddr},
_type => 'register',
}
);
$self->logger->debug("Token $register_session created");
}
# Send confirmation mail
# Skip this step if user clicked on the confirmation link
unless ( $req->data->{register_token} ) {
# Check if confirmation mail has already been sent
$self->logger->debug('No register_token');
# Read session to get creation and expiration dates
$req->id($register_session) unless $req->id;
$self->logger->debug("Register session found: $register_session");
# Mail session expiration date
my $expTimestamp =
( $self->conf->{registerTimeout} || $self->conf->{timeout} ) + time;
$self->logger->debug("Register expiration timestamp: $expTimestamp");
$req->data->{expMailDate} =
strftime( "%d/%m/%Y", localtime $expTimestamp );
$req->data->{expMailTime} =
strftime( "%H:%M", localtime $expTimestamp );
# Mail session start date
my $startTimestamp = time;
$self->logger->debug("Register start timestamp: $startTimestamp");
$req->data->{startMailDate} =
strftime( "%d/%m/%Y", localtime $startTimestamp );
$req->data->{startMailTime} =
strftime( "%H:%M", localtime $startTimestamp );
# Ask if user want another confirmation email
if ( $req->data->{mail_already_sent}
and !$req->param('resendconfirmation') )
{
return PE_MAILCONFIRMATION_ALREADY_SENT;
}
# Build confirmation url
my $req_url = $req->data->{_url};
my $skin = $self->p->getSkin($req);
my $url =
$self->registerUrl . '?'
. build_urlencoded(
register_token => $req->{id},
skin => $skin,
( $req_url ? ( url => $req_url ) : () ),
);
# Build mail content
my $tr = $self->translate($req);
my $subject = $self->conf->{registerConfirmSubject};
unless ($subject) {
$self->logger->debug('Use default confirm subject');
$subject = 'registerConfirmSubject';
$tr->( \$subject );
}
my ( $body, $html );
if ( $self->conf->{registerConfirmBody} ) {
# We use a specific text message, no html
$self->logger->debug('Use specific confirm body message');
$body = $self->conf->{registerConfirmBody};
# Replace variables in body
$body =~ s/\$url/$url/g;
$body =~ s/\$expMailDate/$req->{data}->{expMailDate}/g;
$body =~ s/\$expMailTime/$req->{data}->{expMailTime}/g;
$body =~ s/\$(\w+)/$req->{data}->{registerInfo}->{$1} || ''/ge;
}
else {
# Use HTML template
$self->logger->debug('Use default confirm HTML template body');
$body = $self->loadMailTemplate(
$req,
'mail_register_confirm',
filter => $tr,
params => {
expMailDate => $req->data->{expMailDate},
expMailTime => $req->data->{expMailTime},
url => $url,
%{ $req->data->{registerInfo} || {} },
},
);
$html = 1;
}
# Send mail
return PE_MAILERROR
unless $self->send_mail( $req->data->{registerInfo}->{mail},
$subject, $body, $html );
$self->logger->debug('Register message sent');
return PE_MAILCONFIRMOK;
}
# Generate a complex password
my $password = $self->gen_password( $self->conf->{randomPasswordRegexp} );
$self->logger->debug( "Generated password: " . $password );
$req->data->{registerInfo}->{password} = $password;
$req->data->{forceReset} = 1;
# Find a login
my $result = $self->registerModule->computeLogin($req);
unless ( $result == PE_OK ) {
$self->logger->error( "Could not compute login for "
. $req->data->{registerInfo}->{mail} );
return $result;
}
# Create user
$self->logger->debug(
'Create new user ' . $req->data->{registerInfo}->{login} );
$result = $self->registerModule->createUser($req);
unless ( $result == PE_OK ) {
$self->logger->error(
"Could not create user " . $req->data->{registerInfo}->{login} );
return $result;
}
# Build portal url
my $url = $self->conf->{portal};
$url =~ s#/*$##;
my $req_url = $req->data->{_url};
my $skin = $self->p->getSkin($req);
$url .= '/?'
. build_urlencoded(
skin => $skin,
( $req_url ? ( url => $req_url ) : () ),
);
# Build mail content
my $tr = $self->translate($req);
my $subject = $self->conf->{registerDoneSubject};
unless ($subject) {
$self->logger->debug('Use default done subject');
$subject = 'registerDoneSubject';
$tr->( \$subject );
}
my ( $body, $html );
if ( $self->conf->{registerDoneBody} ) {
# We use a specific text message, no html
$self->logger->debug('Use specific done body message');
$body = $self->conf->{registerDoneBody};
# Replace variables in body
$body =~ s/\$url/$url/g;
$body =~ s/\$(\w+)/$req->{data}->{registerInfo}->{$1} || ''/ge;
}
else {
# Use HTML template
$self->logger->debug('Use default done HTML template body');
$body = $self->loadMailTemplate(
$req,
'mail_register_done',
filter => $tr,
params => {
url => $url,
%{ $req->data->{registerInfo} || {} },
},
);
$html = 1;
}
# Send mail
return PE_MAILERROR
unless $self->send_mail( $req->data->{registerInfo}->{mail},
$subject, $body, $html );
return PE_MAILOK;
}
sub display {
my ( $self, $req ) = @_;
my %templateParams = (
SKIN_PATH => $self->conf->{staticPrefix},
SKIN => $self->p->getSkin($req),
SKIN_BG => $self->conf->{portalSkinBackground},
MAIN_LOGO => $self->conf->{portalMainLogo},
AUTH_ERROR => $req->error,
AUTH_ERROR_TYPE => $req->error_type,
AUTH_ERROR_ROLE => $req->error_role,
AUTH_URL => $req->data->{_url},
CHOICE_PARAM => $self->conf->{authChoiceParam},
CHOICE_VALUE => $req->data->{_authChoice},
EXPMAILDATE => $req->data->{expMailDate},
EXPMAILTIME => $req->data->{expMailTime},
STARTMAILDATE => $req->data->{startMailDate},
STARTMAILTIME => $req->data->{startMailTime},
MAILALREADYSENT => $req->data->{mail_already_sent},
(
$req->data->{customScript}
? ( CUSTOM_SCRIPT => $req->data->{customScript} )
: ()
),
MAIL => $self->p->checkXSSAttack(
'mail', $req->data->{registerInfo}->{mail}
) ? ""
: $req->data->{registerInfo}->{mail},
FIRSTNAME => $self->p->checkXSSAttack( 'firstname',
$req->data->{registerInfo}->{firstname} ) ? ""
: $req->data->{registerInfo}->{firstname},
LASTNAME => $self->p->checkXSSAttack( 'lastname',
$req->data->{registerInfo}->{lastname} ) ? ""
: $req->data->{registerInfo}->{lastname},
REGISTER_TOKEN => $self->p->checkXSSAttack( 'register_token',
$req->data->{register_token} ) ? ""
: $req->data->{register_token},
);
# Display form the first time
if ( (
$req->error == PE_REGISTERFORMEMPTY
or $req->error == PE_REGISTERFIRSTACCESS
or $req->error == PE_CAPTCHAERROR
or $req->error == PE_CAPTCHAEMPTY
or $req->error == PE_NOTOKEN
or $req->error == PE_TOKENEXPIRED
)
and !$req->param('mail_token')
)
{
%templateParams = (
%templateParams,
DISPLAY_FORM => 1,
DISPLAY_RESEND_FORM => 0,
DISPLAY_CONFIRMMAILSENT => 0,
DISPLAY_MAILSENT => 0,
DISPLAY_PASSWORD_FORM => 0,
);
}
# Display captcha if it's enabled
if ( $req->captchaHtml ) {
$templateParams{CAPTCHA_HTML} = $req->captchaHtml;
}
if ( $req->token ) {
$templateParams{TOKEN} = $req->token;
}
# DEPRECATED: This is only used for compatibility with existing templates
if ( $req->captcha ) {
$templateParams{CAPTCHA_SRC} = $req->captcha;
$templateParams{CAPTCHA_SIZE} = $self->conf->{captcha_size};
}
if ( $req->error == PE_REGISTERALREADYEXISTS ) {
%templateParams = (
%templateParams,
DISPLAY_FORM => 0,
DISPLAY_RESEND_FORM => 0,
DISPLAY_CONFIRMMAILSENT => 0,
DISPLAY_MAILSENT => 0,
DISPLAY_PASSWORD_FORM => 0,
);
}
# Display mail confirmation resent form
if ( $req->{error} == PE_MAILCONFIRMATION_ALREADY_SENT ) {
%templateParams = (
%templateParams,
DISPLAY_FORM => 0,
DISPLAY_RESEND_FORM => 1,
DISPLAY_CONFIRMMAILSENT => 0,
DISPLAY_MAILSENT => 0,
DISPLAY_PASSWORD_FORM => 0,
);
}
# Display confirmation mail sent
if ( $req->{error} == PE_MAILCONFIRMOK ) {
%templateParams = (
%templateParams,
DISPLAY_FORM => 0,
DISPLAY_RESEND_FORM => 0,
DISPLAY_CONFIRMMAILSENT => 1,
DISPLAY_MAILSENT => 0,
DISPLAY_PASSWORD_FORM => 0,
);
}
# Display mail sent
if ( $req->{error} == PE_MAILOK ) {
%templateParams = (
%templateParams,
DISPLAY_FORM => 0,
DISPLAY_RESEND_FORM => 0,
DISPLAY_CONFIRMMAILSENT => 0,
DISPLAY_MAILSENT => 1,
DISPLAY_PASSWORD_FORM => 0,
);
}
# Display password change form
if ( $req->param('mail_token')
and $req->{error} != PE_MAILERROR
and $req->{error} != PE_BADMAILTOKEN
and $req->{error} != PE_MAILOK )
{
%templateParams = (
%templateParams,
DISPLAY_FORM => 0,
DISPLAY_RESEND_FORM => 0,
DISPLAY_CONFIRMMAILSENT => 0,
DISPLAY_MAILSENT => 0,
DISPLAY_PASSWORD_FORM => 1,
);
}
return ( 'register', \%templateParams );
}
sub setSecurity {
my ( $self, $req ) = @_;
if ( $self->captcha ) {
$self->p->_captcha->init_captcha($req);
}
elsif ( $self->ottRule->( $req, {} ) ) {
$self->ott->setToken($req);
}
}
1;