PasswordReminder

From Request Tracker Wiki
Jump to navigation Jump to search

Password Reminders

Try the module located at http://github.com/gitpan/RT-Extension-ResetPassword

Older notes

I had a need to provide an easy way for unprivileged users to reset their password. I looked around the code base in 3.4.3 and only found a few TODO notes from JesseVincent. This is what I came up with.

(OleCraig notes: If you're looking for RT::User::SendPasswordEmail, the modified User overlay below is as close as you'll get as of 8/2006; the mention of that class in the ResetPassword function found in the stock 3.x.x User_Overlay.pm is an orphan call.)

/NoAuth/Reminder.html

Jesse provided a stub in [=$rt_dir/share/html/NoAuth/Reminder].

Copy below to [=/opt/rt3/local/html/NoAuth/Reminder.html]

I modified it to become this:

<& /Elements/Header, title => loc('Password Reminder') &>

<DIV ALIGN=CENTER>
% if ($Error) {
<& /Elements/TitleBoxStart, title => loc('Error') &>
<% $Error %>
<& /Elements/TitleBoxEnd &>
% }
<& /Elements/TitleBoxStart, width=> "40%", titleright => loc("RT [_1]",
$RT::VERSION), title => loc('Password Reminder') , contentbg=>"#cccccc" &>


% if ($user_msg eq "Nothing selected.") {
<FORM ACTION="<%$RT::WebPath%>/NoAuth/Reminder.html" METHOD=POST>
    <table>
        <tr>
            <td colspan=2>
<p>Hello.  If you are unable to remember your password, please
enter your email address.  A new one will be generated for you.</p>
            </td>
        </tr>
        <tr><td colspan=2>  </td></tr>
        <tr>
            <td>Email address:</td>
            <td><input  name="UserString"></td>
        </tr>
    </table>

<& /Elements/Submit &>
</FORM>

%} else {
<table>
%   if ($Users->Count == 1) {
<tr><td>Hello
%          print $Users->First->EmailAddress;
</td></tr>
%          my ($stat, $error) = $Users->First->ResetPassword();
<tr><td><%$error%></td></tr>
<tr><td>Please <a href="<% $RT::WebURL %>">Login</a>.</td></tr>
%   } else {
<tr><td><b>Could not find this user.</b></td></tr>
%   }
</table>
%}
<& /Elements/TitleBoxEnd &>
<& /Elements/Footer &>

<%INIT>

my ($user_msg, $Users);

my $userobj = new RT::User($RT::SystemUser);

if (! length $ARGS{'UserString'} ) {
    $user_msg = "Nothing selected.";
}
else {
    $Users = new RT::Users($RT::SystemUser);
    $Users->Limit(FIELD => 'EmailAddress', VALUE => $ARGS{'UserString'},
        OPERATOR => '=');
}



</%INIT>
<%ARGS>
$UserString => undef
$Error => undef
</%ARGS>

/Elements/Login

Copy /opt/rt3/share/html/Elements/Login to /opt/rt3/local/html/Elements/ and add below (only the + parts ;-) )

Next, I had to modify Elements/Login to reference the new Reminder.html.

--- /opt/rt3/share/html/Elements/Login  Sun Mar 27 01:01:20 2005
+++ Elements/Login      Fri Sep 30 16:19:54 2005
@@ -75,6 +75,9 @@
 <TR><TD colspan=2 align=right>
 <input type=submit Value="<&|/l&>Login</&>">
 </TD></TR>
+<TR><TD colspan=2>
+If you've forgotten your username or password, RT can <A
+href="/NoAuth/Reminder.html">send you a reminder</a>.</TD></TR>
 </TABLE>

 %# Give callbacks a chance to add more control elements
@@ -105,10 +108,6 @@
 </DIV>

 <BR>
-<!-- TODO: not yet implemented
-If you've forgotten your username or password, RT can <A
-href="/NoAuth/Reminder.html">send you a reminder</a>.
--->
 <BR>
 <BR>
 <BR>

User_Local.pm Overlay

Next, I had to create an overlay of [=RT::User::ResetPassword].

Save this as [=User_Local.pm] in /opt/rt3/local/lib/RT/

package RT::User;
no warnings qw(redefine);
use strict;

sub ResetPassword {
    my $self = shift;

    unless ( $self->CurrentUserCanModify('Password') ) {
        return ( 0, $self->loc("Permission Denied") );
    }
    my ( $status, $pass ) = $self->SetRandomPassword();

    unless ($status) {
        return ( 0, "$pass" );
    }

    my $template = RT::Template->new( $self->CurrentUser );

    if ( $self->Privileged ) {
        $template->LoadGlobalTemplate('RT_PasswordChange_Privileged');
    }
    else {
        $template->LoadGlobalTemplate('RT_PasswordChange_NonPrivileged');
    }

    unless ( $template->Id ) {
        $template->LoadGlobalTemplate('RT_PasswordChange');
    }

    unless ( $template->Id ) {
        $RT::Logger->crit( "$self tried to send "
              . $self->Name
              . " a password reminder "
              . "but couldn't find a password change template" );
    }

    my ($result, $message) = $template->Parse(
        CurrentUser => $RT::SystemUser,
        TemplateObj => $template,
        Argument    => $pass
    );

    if ( !$result) {
        $RT::Logger->warn("User tried to reset password but template obj failed to load for " . $self->CurrentUser->Name);
        return ( 0, $self->loc("Sorry, something failed and I could not change your password"));
    }

    my $MIMEObj = $template->MIMEObj;

    $MIMEObj->head->set('To', $self->EmailAddress);
    $MIMEObj->head->set('From', $RT::CorrespondAddress);

    #$->SetHeader( 'To', $self->EmailAddress );

    my $ret;
    if ( $RT::MailCommand eq 'sendmailpipe' ) {
        eval {
            open( MAIL, "|$RT::SendmailPath $RT::SendmailArguments" ) || die $!;
            print MAIL $MIMEObj->as_string;
            close(MAIL);
        };
        if ($@) {
            $RT::Logger->crit("Could not send password reset. -" . $@ );
        } else {
            $ret = 1;
        }
    }
    else {
        my @mailer_args = ($RT::MailCommand);

        local $ENV{MAILADDRESS};

        if ( $RT::MailCommand eq 'sendmail' ) {
            push @mailer_args, split(/\s+/, $RT::SendmailArguments);
        }
        elsif ( $RT::MailCommand eq 'smtp' ) {
            $ENV{MAILADDRESS} = $RT::SMTPFrom || $MIMEObj->head->get('From');
            push @mailer_args, ( Server => $RT::SMTPServer );
            push @mailer_args, ( Debug  => $RT::SMTPDebug );
        }
        else {
            push @mailer_args, $RT::MailParams;
        }

        unless ( $MIMEObj->send(@mailer_args) ) {
            $RT::Logger->crit("Could not send password reset." );
            return (0);
        }

        $ret = 1;
    }



    if ($ret) {
        return ( 1, $self->loc('New password notification sent') );
    }
    else {
        return ( 0, $self->loc('Notification could not be sent') );
    }

}

1;

RT_PasswordChange Template

Finally, I had to create a template for my overlayed [=ResetPasswrd()].

* Name: RT_PasswordChange
 * Description:  Notification of Password Change
 * Content:
 
 Subject: your {$rtname} account password reset
 
 Hello.
 
 Someone requested a password reminder for your account.  Because we keep your
 password encrypted, we don't know what your previous password was.  We have set
 a new password for your account.  The only address that received this notice
 was the email on record for your account.
 
 The new password is "{$Argument}" without the double quotes.
 
 You may login to {$rtname} at
 
   {$RT::WebURL}
 
 at your convience.
 
 If you have further questions regarding this, please respond and a ticket will
 be opened regarding your request.
 
 Thank you,
 {$RT::CorrespondAddress}
 

Conclusion

This worked for me. YMMV. If you have questions about it, you can find me in #rt on irc.perl.org. I'm HCoyote

Here's a version of Reminder.html to look nice with RT 3.6.1

<& /Elements/Callback, %ARGS, _CallbackName => 'Header' &>
<& /Elements/Header, Title => loc('Reset Your Password'), Focus => 'UserString' &>
</div>

<div id="body" class="login-body">

% if ($Error) {
<&| "/Widgets/TitleBox", title => loc('Error'), hideable => 0 &>
<% $Error %>
</&>
% }

<& /Elements/Callback, %ARGS, _CallbackName => 'BeforeForm' &>

<div id="login-box">
<&| "/Widgets/TitleBox", title => loc('Reset Your Password'), titleright => $RT::VERSION, hideable => 0 &>

% if ($user_msg eq "Nothing selected.") {
<form action="<%$RT::WebPath%>/NoAuth/Reminder.html" method="post">

<div class="input-row">
   <p>Just enter your email address below, and a new
   one will be generated and sent to you.</p><p>If you have any problems, just
   <a href="http://www.suretecsystems.com/contact"
   title="Suretec Contact Details">call us</a>.</p>
</div>

<div class="input-row">
   <span class="label">Email:</span>
   <span class="input"><input name="UserString"></span>
</div>

<div class="button-row">
   <span class="input"><input type="submit" class="button" value="Reset" /></span>
</div>
</form>

%} else {
%   if ($Users->Count == 1) {
<div class="input-row">Hello
%          print $Users->First->EmailAddress;
</div>
%          my ($stat, $error) = $Users->First->ResetPassword();
<div class="input-row"><%$error%></div>
<div class="input-row">Please <a href="<% $RT::WebURL %>">Login</a>.</div>
%   } else {
<div class="input-row"><b>Could not find this user.</b></div>
%   }
%   }
</div><!-- #login-box -->
</&>
<& /Elements/Callback, %ARGS, _CallbackName => 'AfterForm' &>
<& /Elements/Footer, Menu => 0 &>

<%INIT>

my ($user_msg, $Users);

my $userobj = new RT::User($RT::SystemUser);

if (! length $ARGS{'UserString'} ) {
   $user_msg = "Nothing selected.";
}
else {
   $Users = new RT::Users($RT::SystemUser);
   $Users->Limit(FIELD => 'EmailAddress', VALUE => $ARGS{'UserString'},
       OPERATOR => '=');
}

</%INIT>
<%ARGS>
$UserString => undef
$Error => undef
</%ARGS>


For 3.8.4 we have used:

/NoAuth/Reminder.html

% $m->callback( %ARGS, CallbackName => 'Header' );
<& /Elements/Header, Title => loc('Reset Your Password'), Focus => 'UserString' &>
</div>

<div id="body" class="login-body">

% if ($Error) {
<&| "/Widgets/TitleBox", title => loc('Error'), hideable => 0 &>
<% $Error %>
</&>
% }

% $m->callback( %ARGS, CallbackName => 'BeforeForm' );

<div id="login-box">
<&| "/Widgets/TitleBox", title => loc('Reset Your Password'), titleright => $RT::VERSION, hideable => 0 &>

% if ($user_msg eq "Nothing selected.") {
<form action="<%$RT::WebPath%>/NoAuth/Reminder.html" method="post">

<div class="input-row">
   <p>If you have any problems, just
   <a href="http://www.suretecsystems.com/contact"
   title="Suretec Contact Details">call us</a>.</p>
</div>

<div class="input-row">
   <span class="label">Email:</span>
   <span class="input"><input name="UserString"></span>
</div>

<div class="button-row">
   <span class="input"><input type="submit" class="button" value="Reset" /></span>
</div>

<div class="input-row">
    <a href="/">I've remembered, take me back.</a>
</div>

</form>

%} else {
%   if ($Users->Count == 1) {
<div class="input-row">Hello
%          print $Users->First->EmailAddress;
</div>
%          my ($stat, $error) = $Users->First->ResetPassword();
<div class="input-row"><%$error%></div>
<div class="input-row">Please <a href="<% $RT::WebURL %>">Login</a>.</div>
%   } else {
<div class="input-row"><b>Could not find this user.</b></div>
%   }
%   }
</div><!-- #login-box -->
</&>
% $m->callback( %ARGS, CallbackName => 'AfterForm' );
<& /Elements/Footer, Menu => 0 &>

<%INIT>

my ($user_msg, $Users);

my $userobj = new RT::User($RT::SystemUser);

if (! length $ARGS{'UserString'} ) {
   $user_msg = "Nothing selected.";
}
else {
   $Users = new RT::Users($RT::SystemUser);
   $Users->Limit(FIELD => 'EmailAddress', VALUE => $ARGS{'UserString'},
       OPERATOR => '=');
}

</%INIT>
<%ARGS>
$UserString => undef
$Error => undef
</%ARGS>


Add this to the Login in local

/Elements/Login

<div class="input-row">
    <a href="/NoAuth/Reminder.html">Forgotten password?</a>
</div>