Commit f6c25020 authored by Clément OUDOT's avatar Clément OUDOT

Portal - new feature: token to reset password by mail:

* A token is sent when user ask for password reset
* The token is linked to an apache session
* The password is reset if the token is valid
parent 26db0f0d
......@@ -308,9 +308,10 @@ install_portal_site: install_conf_dir
done
@cp -pR --remove-destination ${SRCPORTALDIR}/example/index_skin.pl ${RPORTALDIR}/index.pl
@cp -pR --remove-destination ${SRCPORTALDIR}/example/error.pl ${RPORTALDIR}
@cp -pR --remove-destination ${SRCPORTALDIR}/example/mail.pl ${RPORTALDIR}
@cp -pR --remove-destination ${SRCPORTALDIR}/example/apps ${RPORTALDIR}
@$(PERL) -i -pe 's#__SKINDIR__#$(PORTALDIR)/skins#; \
s#__APPSXMLFILE__#$(CONFDIR)/apps-list.xml#;' ${RPORTALDIR}/index.pl ${RPORTALDIR}/error.pl
s#__APPSXMLFILE__#$(CONFDIR)/apps-list.xml#;' ${RPORTALDIR}/index.pl ${RPORTALDIR}/error.pl ${RPORTALDIR}/mail.pl
@cp -pR --remove-destination ${SRCPORTALDIR}/example/skins/* $(RPORTALSKINSDIR)
@if [ "$(PORTALDIR)/skins/" != "$(PORTALSKINSDIR)/" ]; then \
for skin in $$(ls lemonldap-ng-portal/example/skins/); do \
......@@ -546,7 +547,8 @@ debian-diff:
$(DIFF) -x 'jquery*' lemonldap-ng-portal/example/skins/$$i /usr/share/lemonldap-ng/portal-skins/$$i; \
done ||true
@$(DIFF) -I '$$skin_dir' -I '$$appsxmlfile' lemonldap-ng-portal/example/index_skin.pl /var/lib/lemonldap-ng/portal/index.pl ||true
@$(DIFF) lemonldap-ng-portal/example/error.pl /var/lib/lemonldap-ng/portal/error.pl ||true
@$(DIFF) -I '$$skin_dir' lemonldap-ng-portal/example/error.pl /var/lib/lemonldap-ng/portal/error.pl ||true
@$(DIFF) -I '$$skin_dir' lemonldap-ng-portal/example/mail.pl /var/lib/lemonldap-ng/portal/mail.pl ||true
@# Handler
@$(DIFF) lemonldap-ng-handler/lib/Lemonldap/NG/Handler /usr/share/perl5/Lemonldap/NG/Handler ||true
@# Common
......@@ -569,7 +571,8 @@ default-diff:
@$(DIFF) lemonldap-ng-portal/example/scripts/buildPortalWSDL $(LMPREFIX)/bin/buildPortalWSDL ||true
@$(DIFF) lemonldap-ng-portal/example/skins $(LMPREFIX)/htdocs/portal/skins ||true
@$(DIFF) -I '$$skin_dir' -I '$$appsxmlfile' lemonldap-ng-portal/example/index_skin.pl $(LMPREFIX)/htdocs/portal/index.pl ||true
@$(DIFF) lemonldap-ng-portal/example/error.pl $(LMPREFIX)/htdocs/portal/error.pl ||true
@$(DIFF) -I '$$skin_dir' lemonldap-ng-portal/example/error.pl $(LMPREFIX)/htdocs/portal/error.pl ||true
@$(DIFF) -I '$$skin_dir' lemonldap-ng-portal/example/mail.pl $(LMPREFIX)/htdocs/portal/mail.pl ||true
@# Handler
@$(DIFF) lemonldap-ng-handler/lib/Lemonldap/NG/Handler /usr/local/share/perl/5.10.0/Lemonldap/NG/Handler ||true
@$(DIFF) lemonldap-ng-handler/example/MyHandler.pm $(LMPREFIX)/handler/MyHandler.pm ||true
......
#!/usr/bin/perl
use HTML::Template;
my $skin_dir = "/usr/local/lemonldap-ng/htdocs/portal/skins";
my $skin_dir = "__SKINDIR__";
my $portal = Lemonldap::NG::Portal::SharedConf->new(
# PORTAL CUSTOMIZATION
# Skin
#portalSkin => 'pastel',
......@@ -23,7 +24,7 @@ my $template = HTML::Template->new(
filename => "$skin_dir/$skin/error.tpl",
die_on_bad_params => 0,
cache => 0,
filter => sub{$portal->translate_template(@_)}
filter => sub { $portal->translate_template(@_) }
);
$template->param( PORTAL_URL => "$portal_url" );
......
......@@ -253,6 +253,7 @@ else {
SKIN => $skin,
DISPLAY_RESETPASSWORD => $portal->{portalDisplayResetPassword},
DISPLAY_FORM => 1,
MAIL_URL => $portal->{mailUrl},
# Adapt template if password policy error
(
......
#!/usr/bin/perl
use Lemonldap::NG::Portal::MailReset;
use HTML::Template;
use strict;
my $skin_dir = "__SKINDIR__";
# Load portal module
my $portal = Lemonldap::NG::Portal::MailReset->new();
my $skin = $portal->{portalSkin};
my $portal_url = $portal->{portal};
# Process
$portal->process();
# Template creation
my $template = HTML::Template->new(
filename => "$skin_dir/$skin/mail.tpl",
die_on_bad_params => 0,
cache => 0,
filter => sub { $portal->translate_template(@_) }
);
$template->param( PORTAL_URL => "$portal_url" );
$template->param( SKIN => "$skin" );
$template->param( AUTH_ERROR => $portal->error );
$template->param( AUTH_ERROR_TYPE => $portal->error_type );
# Display form the first time
$template->param( DISPLAY_FORM => 1 )
if ( $portal->{error} == PE_MAILFORMEMPTY
or ( $portal->{error} == PE_BADCREDENTIALS and !$portal->{mail_token} ) );
# Display password if change is OK
$template->param( NEW_PASSWORD => $portal->{reset_password} )
if ( $portal->{error} == PE_PASSWORD_OK );
print $portal->header('text/html; charset=utf8');
print $template->output;
......@@ -30,28 +30,15 @@
<lang en="Connect" fr="Se connecter" />
</button>
</div></td></tr>
</table>
</form>
</TMPL_IF>
<TMPL_IF NAME="DISPLAY_RESETPASSWORD">
<form action="#" method="post" class="login">
<h3><lang en="Forgot your password?" fr="Mot de passe oubli&eacute; ?"/></h3>
<table>
<tr><th><lang en="Mail" fr="Adresse mail"/></th>
<td><input name="mail" type="text"/></td>
</tr>
<tr><td colspan="2">
<div class="buttons">
<button type="submit" class="positive">
<img src="skins/common/accept.png" alt="" />
<lang en="Send me a new password" fr="Envoyez-moi un nouveau mot de passe" />
</button>
<a href="<TMPL_VAR NAME="MAIL_URL">" class="positive">
<img src="skins/common/email.png" alt="" />
<lang en="Reset my password" fr="R&eacute;initialiser mon mot de passe"/>
</a>
</div></td></tr>
</TMPL_IF>
</table>
</form>
......@@ -60,12 +47,14 @@
<TMPL_INCLUDE NAME="password.tpl">
<TMPL_IF NAME="LOGOUT_URL">
<div id="logout">
<div class="buttons">
<a href="<TMPL_VAR NAME="LOGOUT_URL">" class="negative">
<img src="skins/common/cancel.png" alt="" />
<lang en="Logout" fr="Se d&eacute;connecter"/>
</a>
</div>
</div>
</TMPL_IF>
<TMPL_INCLUDE NAME="footer.tpl">
<TMPL_INCLUDE NAME="header.tpl">
<div class="message <TMPL_VAR NAME="AUTH_ERROR_TYPE">"><ul><li><TMPL_VAR NAME="AUTH_ERROR"></li></ul></div>
<div class="loginlogo"></div>
<TMPL_IF NAME="DISPLAY_FORM">
<form action="#" method="post" class="login">
<h3><lang en="Forgot your password?" fr="Mot de passe oubli&eacute; ?"/></h3>
<table>
<tr><th><lang en="Mail" fr="Adresse mail"/></th>
<td><input name="mail" type="text"/></td>
</tr>
<tr><td colspan="2">
<div class="buttons">
<button type="submit" class="positive">
<img src="skins/common/accept.png" alt="" />
<lang en="Send me a new password" fr="Envoyez-moi un nouveau mot de passe" />
</button>
</div></td></tr>
</table>
</form>
</TMPL_IF>
<div class="link">
<TMPL_IF NAME="NEW_PASSWORD">
<h3><lang en="Your new password is" fr="Votre nouveau mot de passe est"/> <TMPL_VAR NAME="NEW_PASSWORD"></h3>
</TMPL_IF>
<a href="<TMPL_VAR NAME="PORTAL_URL">">
<lang en="Go back to portal" fr="Retourner au portail" />
</a>
</div>
<TMPL_INCLUDE NAME="footer.tpl">
......@@ -380,3 +380,8 @@ p.removeOther{
font-weight:bold;
}
div.link {
text-align:center;
font-weight:bold;
margin:40px 200px;
}
##@file
# Module for password reset by mail
##@class Lemonldap::NG::Portal::MailReset
# Module for password reset by mail
package Lemonldap::NG::Portal::MailReset;
use strict;
use warnings;
our $VERSION = '0.1';
use Lemonldap::NG::Portal::Simple qw(:all);
use base qw(Lemonldap::NG::Portal::SharedConf Exporter);
*EXPORT_OK = *Lemonldap::NG::Portal::Simple::EXPORT_OK;
*EXPORT_TAGS = *Lemonldap::NG::Portal::Simple::EXPORT_TAGS;
*EXPORT = *Lemonldap::NG::Portal::Simple::EXPORT;
##@method boolean process()
# Call functions to handle password reset by mail issued from
# - itself:
# - smtpInit
# - extractMailInfo
# - storeMailSession
# - sendConfirmationMail
# - portal core module:
# - setMacros
# - setLocalGroups
# - setGroups
# - userDB module:
# - userDBInit
# - getUser
# - setSessionInfo
# - passwordDB module:
# - passwordDBInit
# - resetPassword
#@return 1 if all is OK
sub process {
my ($self) = @_;
# Process subroutines
$self->{error} = PE_OK;
$self->{error} = $self->_subProcess(
qw(smtpInit userDBInit passwordDBInit extractMailInfo
getUser setSessionInfo setMacros setLocalGroups setGroups
storeMailSession sendConfirmationMail resetPassword)
);
return (
(
$self->{error} <= 0
or $self->{error} == PE_PASSWORD_OK
or $self->{error} == PE_MAILOK
) ? 0 : 1
);
}
##@method int smtpInit()
# Load SMTP methods
#@return Lemonldap::NG::Portal constant
sub smtpInit {
my ($self) = @_;
eval { use base qw(Lemonldap::NG::Portal::_SMTP) };
if ($@) {
$self->lmLog( "Unable to load SMTP functions ($@)", 'error' );
return PE_ERROR;
}
PE_OK;
}
##@method int extractMailInfo
# Get mail from form or from mail_token
#@return Lemonldap::NG::Portal constant
sub extractMailInfo {
my ($self) = @_;
return PE_MAILFORMEMPTY
unless ( $self->param('mail') || $self->param('mail_token') );
$self->{mail_token} = $self->param('mail_token');
# If a mail token is present, find the corresponding mail
if ( $self->{mail_token} ) {
$self->lmLog( "Token given for password reset: " . $self->{mail_token},
'debug' );
# Get the corresponding session
my $h = $self->getApacheSession( $self->{mail_token} );
if ( ref $h ) {
$self->{mail} = $h->{ $self->{mailSessionKey} };
$self->lmLog( "Mail associated to token: " . $self->{mail},
'debug' );
}
# Mail token can be used only one time, delete the session
tied(%$h)->delete() if ref $h;
return PE_BADMAILTOKEN unless ( $self->{mail} );
}
else {
# Use submitted value
$self->{mail} = $self->param('mail');
}
PE_OK;
}
sub storeMailSession {
my ($self) = @_;
# Skip this step if confirmation was already sent
return PE_OK if $self->{mail_token};
# Create a new session
my $h = $self->getApacheSession();
# Set _utime for session autoremove
$h->{_utime} = time();
# Store mail
$h->{ $self->{mailSessionKey} } = $self->{mail};
# Untie session
untie %$h;
PE_OK;
}
sub sendConfirmationMail {
my ($self) = @_;
# Skip this step if confirmation was already sent
return PE_OK if $self->{mail_token};
# Build confirmation url
my $url = $self->{mailUrl} . "?mail_token=" . $self->{id};
# Replace variables in mail body
$self->{mailBody} =~ s/\$url/$url/g;
$self->{mailBody} =~ s/\$(\w+)/$self->{sessionInfo}->{$1}/g;
# Send mail
return PE_MAILERROR unless $self->send_mail( $self->{mail} );
PE_MAILOK;
}
1;
__END__
=head1 NAME
=encoding utf8
Lemonldap::NG::Portal::MailReset - Manage password reset by mail
=head1 SYNOPSIS
use Lemonldap::NG::Portal::MailReset;
my $portal = new Lemonldap::NG::Portal::Reset();
$portal->process();
# Write here HTML to manage errors and confirmation messages
=head1 DESCRIPTION
Lemonldap::NG::Portal::MailReset enables password reset by mail
See L<Lemonldap::NG::Portal::SharedConf> for a complete example of use of
Lemonldap::Portal::* libraries.
=head1 METHODS
=head3 process
Main method.
=head1 SEE ALSO
L<Lemonldap::NG::Handler>, L<Lemonldap::NG::Portal::SharedConf>, L<CGI>,
http://wiki.lemonldap.objectweb.org/xwiki/bin/view/NG/Presentation
=head1 AUTHOR
Clement Oudot, E<lt>clement@oodo.netE<gt>
=head1 BUG REPORT
Use OW2 system to report bug or ask for features:
L<http://forge.objectweb.org/tracker/?group_id=274>
=head1 DOWNLOAD
Lemonldap::NG is available at
L<http://forge.objectweb.org/project/showfiles.php?group_id=274>
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2005-2009 by Xavier Guimard E<lt>x.guimard@free.frE<gt> and
Clement Oudot, E<lt>clement@oodo.netE<gt>
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.4 or,
at your option, any later version of Perl 5 you may have available.
=cut
......@@ -9,6 +9,7 @@ use strict;
use Lemonldap::NG::Portal::Simple;
use Lemonldap::NG::Portal::AuthDBI; #inherits
use base qw(Lemonldap::NG::Portal::_DBI );
#use Lemonldap::NG::Portal::_SMTP; #inherits
our $VERSION = '0.1';
......@@ -23,8 +24,7 @@ sub passwordDBInit {
$self->lmLog( "Unable to load SMTP functions ($@)", 'error' );
return PE_ERROR;
}
unless ( $self->{dbiPasswordMailCol} )
{
unless ( $self->{dbiPasswordMailCol} ) {
$self->lmLog( "Missing configuration parameters for DBI password reset",
'error' );
return PE_ERROR;
......@@ -57,21 +57,25 @@ sub modifyPassword {
if ( $self->{oldpassword} ) {
# Password hash
my $password = $self->hash_password( $self->{oldpassword}, $self->{dbiAuthPasswordHash} );
my $password =
$self->hash_password( $self->{oldpassword},
$self->{dbiAuthPasswordHash} );
my $result = $self->check_password( $user, $password );
unless ( $result ) {
unless ($result) {
return PE_BADOLDPASSWORD;
}
}
# Modify password
my $password = $self->hash_password( $self->{newpassword}, $self->{dbiAuthPasswordHash} );
my $password =
$self->hash_password( $self->{newpassword},
$self->{dbiAuthPasswordHash} );
my $result = $self->modify_password( $user, $password );
unless ( $result ) {
unless ($result) {
return PE_ERROR;
}
......@@ -79,40 +83,14 @@ sub modifyPassword {
PE_PASSWORD_OK;
}
## @apmethod int resetPasswordByMail()
# Reset the password and send a mail.
## @apmethod int resetPassword()
# Reset the password
# @return Lemonldap::NG::Portal constant
sub resetPasswordByMail {
sub resetPassword {
my $self = shift;
# Exit method if no mail
return PE_OK unless ( $self->{mail} );
# Find mail in database
my $dbh =
$self->dbh( $self->{dbiAuthChain}, $self->{dbiAuthUser},
$self->{dbiAuthPassword} );
return PE_ERROR unless $dbh;
my $table = $self->{dbiAuthTable};
my $mail = $self->{mail};
my $mailCol = $self->{dbiPasswordMailCol};
$mail =~ s/'/''/g;
my $sth;
eval {
$sth = $dbh->prepare("SELECT * FROM $table WHERE $mailCol='$mail'");
$sth->execute();
};
if ($@) {
$self->lmLog( "DBI error: $@", 'error' );
return PE_ERROR;
}
unless ( $sth->fetchrow_hashref() ) {
$self->lmLog( "Mail $mail not found", 'notice' );
return PE_BADCREDENTIALS;
}
# Exit method if no mail and mail_token
return PE_OK unless ( $self->{mail} && $self->{mail_token} );
$self->lmLog( "Reset password request for " . $self->{mail}, 'debug' );
......@@ -122,15 +100,16 @@ sub resetPasswordByMail {
$self->lmLog( "Generated password: " . $password, 'debug' );
# Modify password
my $hpassword = $self->hash_password( $password, $self->{dbiAuthPasswordHash} );
my $result = $self->modify_password( $self->{mail}, $hpassword, $self->{dbiPasswordMailCol} );
my $hpassword =
$self->hash_password( $password, $self->{dbiAuthPasswordHash} );
my $result =
$self->modify_password( $self->{mail}, $hpassword,
$self->{dbiPasswordMailCol} );
return PE_ERROR unless $result;
# Send new password by mail
$result = $self->send_password( $password, $self->{mail} );
return PE_ERROR unless $result;
# Store password to display it to the user
$self->{reset_password} = $password;
PE_PASSWORD_OK;
}
......
......@@ -9,6 +9,7 @@ use strict;
use Lemonldap::NG::Portal::Simple;
use Lemonldap::NG::Portal::_LDAP 'ldap'; #link protected ldap
use Lemonldap::NG::Portal::UserDBLDAP; #inherits
#use Lemonldap::NG::Portal::_SMTP; #inherits
our $VERSION = '0.3';
......@@ -58,14 +59,14 @@ sub modifyPassword {
PE_OK;
}
## @apmethod int resetPasswordByMail()
# Reset the password and send a mail.
## @apmethod int resetPassword()
# Reset the password
# @return Lemonldap::NG::Portal constant
sub resetPasswordByMail {
sub resetPassword {
my $self = shift;
# Exit method if no mail
return PE_OK unless ( $self->{mail} );
# Exit method if no mail and mail_token
return PE_OK unless ( $self->{mail} && $self->{mail_token} );
unless ( $self->ldap ) {
return PE_LDAPCONNECTFAILED;
......@@ -105,10 +106,8 @@ sub resetPasswordByMail {
$self->lmLog( "pwdReset set to TRUE", 'debug' );
}
# Send new password by mail
my $result = $self->send_password( $password, $self->{mail} );
return PE_ERROR unless $result;
# Store password to display it to the user
$self->{reset_password} = $password;
PE_PASSWORD_OK;
}
......
......@@ -40,7 +40,7 @@ use Safe;
#inherits Apache::Session
#link Lemonldap::NG::Common::Apache::Session::SOAP protected globalStorage
our $VERSION = '0.90';
our $VERSION = '0.91';
use base qw(Lemonldap::NG::Common::CGI Exporter);
our @ISA;
......@@ -87,6 +87,10 @@ use constant {
PE_MALFORMEDUSER => 40,
PE_SESSIONNOTGRANTED => 41,
PE_CONFIRM => 42,
PE_MAILFORMEMPTY => 43,
PE_BADMAILTOKEN => 44,
PE_MAILERROR => 45,
PE_MAILOK => 46,
# Portal messages