[open-ils-commits] r16121 - in branches/rel_1_6/Open-ILS: examples examples/apache src/extras src/perlmods/OpenILS/Application src/perlmods/OpenILS/WWW src/sql/Pg src/templates src/templates/password-reset web/opac/locale/en-US xul/staff_client/server/admin (dbs)

svn at svn.open-ils.org svn at svn.open-ils.org
Sun Apr 4 00:05:19 EDT 2010


Author: dbs
Date: 2010-04-04 00:05:16 -0400 (Sun, 04 Apr 2010)
New Revision: 16121

Added:
   branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/WWW/PasswordReset.pm
   branches/rel_1_6/Open-ILS/src/templates/password-reset/
   branches/rel_1_6/Open-ILS/src/templates/password-reset/request-form.tt2
   branches/rel_1_6/Open-ILS/src/templates/password-reset/reset-form.tt2
   branches/rel_1_6/Open-ILS/src/templates/password-reset/strings.en-US
Modified:
   branches/rel_1_6/Open-ILS/examples/apache/eg.conf
   branches/rel_1_6/Open-ILS/examples/apache/eg_vhost.conf
   branches/rel_1_6/Open-ILS/examples/apache/startup.pl
   branches/rel_1_6/Open-ILS/examples/fm_IDL.xml
   branches/rel_1_6/Open-ILS/examples/opensrf.xml.example
   branches/rel_1_6/Open-ILS/src/extras/ils_events.xml
   branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Actor.pm
   branches/rel_1_6/Open-ILS/src/sql/Pg/005.schema.actors.sql
   branches/rel_1_6/Open-ILS/src/sql/Pg/950.data.seed-values.sql
   branches/rel_1_6/Open-ILS/web/opac/locale/en-US/lang.dtd
   branches/rel_1_6/Open-ILS/xul/staff_client/server/admin/org_unit_settings.xhtml
Log:
Bare-bones self-serve password reset interface.
Notifications via action/trigger infrastructure - requires "hostname"
 parameter for the event definition.
Provides localized minimal HTML reset request / reset forms.
OU settings for maximum number of concurrent reset requests per user, request
 time to live, and maximum concurrent requests for the system (this last is
 currently unused but will be the basis of throttling)

TODO:
  * Add a "Forgot your password?" link from OPAC login screen to https://<hostname>/password/<locale>
  * Implement request throttling
  * Disable access to the password forms via unencrypted HTTP
  * Add OU setting to choose "barcode + email" authentication type over "barcode or user name"
  * Add OU setting to support the option of preventing staff from using this interface to reset their passwords
  * Add Dojo spice to the sad but functional HTML forms


Modified: branches/rel_1_6/Open-ILS/examples/apache/eg.conf
===================================================================
--- branches/rel_1_6/Open-ILS/examples/apache/eg.conf	2010-04-04 01:01:08 UTC (rev 16120)
+++ branches/rel_1_6/Open-ILS/examples/apache/eg.conf	2010-04-04 04:05:16 UTC (rev 16121)
@@ -19,8 +19,8 @@
 PerlChildInitHandler OpenILS::WWW::Reporter::child_init
 PerlChildInitHandler OpenILS::WWW::SuperCat::child_init
 PerlChildInitHandler OpenILS::WWW::AddedContent::child_init
+PerlChildInitHandler OpenILS::WWW::PasswordReset::child_init
 
-
 # ----------------------------------------------------------------------------------
 # Set some defaults for our working directories
 # ----------------------------------------------------------------------------------

Modified: branches/rel_1_6/Open-ILS/examples/apache/eg_vhost.conf
===================================================================
--- branches/rel_1_6/Open-ILS/examples/apache/eg_vhost.conf	2010-04-04 01:01:08 UTC (rev 16120)
+++ branches/rel_1_6/Open-ILS/examples/apache/eg_vhost.conf	2010-04-04 04:05:16 UTC (rev 16121)
@@ -163,6 +163,16 @@
     allow from all
 </LocationMatch>
 
+# ----------------------------------------------------------------------------------
+# Self-serve password interface
+# ----------------------------------------------------------------------------------
+<Location /opac/extras/password>
+    SetHandler perl-script
+    PerlHandler OpenILS::WWW::PasswordReset::password_reset
+    Options +ExecCGI
+    PerlSendHeader On
+    allow from all
+</Location>
 
 # ----------------------------------------------------------------------------------
 # Supercat feeds

Modified: branches/rel_1_6/Open-ILS/examples/apache/startup.pl
===================================================================
--- branches/rel_1_6/Open-ILS/examples/apache/startup.pl	2010-04-04 01:01:08 UTC (rev 16120)
+++ branches/rel_1_6/Open-ILS/examples/apache/startup.pl	2010-04-04 04:05:16 UTC (rev 16121)
@@ -6,6 +6,7 @@
 use OpenILS::WWW::Proxy ('/openils/conf/opensrf_core.xml');
 use OpenILS::WWW::Vandelay qw( /openils/conf/opensrf_core.xml );
 use OpenILS::WWW::EGWeb ('/openils/conf/oils_web.xml');
+use OpenILS::WWW::PasswordReset ('/openils/conf/opensrf_core.xml');
 
 # - Uncoment the following 2 lines to make use of the IP redirection code
 # - The IP file should to contain a map with the following format:

Modified: branches/rel_1_6/Open-ILS/examples/fm_IDL.xml
===================================================================
--- branches/rel_1_6/Open-ILS/examples/fm_IDL.xml	2010-04-04 01:01:08 UTC (rev 16120)
+++ branches/rel_1_6/Open-ILS/examples/fm_IDL.xml	2010-04-04 04:05:16 UTC (rev 16121)
@@ -1056,6 +1056,18 @@
 			<link field="creator" reltype="has_a" key="id" map="" class="au"/>
 		</links>
 	</class>
+	<class id="aupr" controller="open-ils.cstore" oils_obj:fieldmapper="actor::usr_password_reset" oils_persist:tablename="actor.usr_password_reset" reporter:label="User password reset requests">
+		<fields oils_persist:primary="id" oils_persist:sequence="actor.usr_password_reset_id_seq">
+			<field reporter:label="Request ID" name="id" reporter:datatype="id"/>
+			<field reporter:label="UUID" name="uuid" reporter:datatype="text"/>
+			<field reporter:label="User" name="usr" reporter:datatype="link"/>
+			<field reporter:label="Request Time" name="request_time" reporter:datatype="timestamp"/>
+			<field reporter:label="Was Reset?" name="has_been_reset" reporter:datatype="bool"/>
+		</fields>
+		<links>
+			<link field="usr" reltype="has_a" key="id" class="au"/>
+		</links>
+	</class>
 	<class id="aus" controller="open-ils.cstore" oils_obj:fieldmapper="actor::user_setting" oils_persist:tablename="actor.usr_setting" reporter:label="User Setting">
 		<fields oils_persist:primary="id" oils_persist:sequence="actor.usr_setting_id_seq">
 			<field reporter:label="Setting ID" name="id" reporter:datatype="id" />

Modified: branches/rel_1_6/Open-ILS/examples/opensrf.xml.example
===================================================================
--- branches/rel_1_6/Open-ILS/examples/opensrf.xml.example	2010-04-04 01:01:08 UTC (rev 16120)
+++ branches/rel_1_6/Open-ILS/examples/opensrf.xml.example	2010-04-04 04:05:16 UTC (rev 16121)
@@ -19,6 +19,7 @@
             <xsl>LOCALSTATEDIR/xsl</xsl>
             <script>LOCALSTATEDIR</script>
             <script_lib>LOCALSTATEDIR</script_lib>
+            <templates>LOCALSTATEDIR/templates</templates>
         </dirs>
 
         <!-- global data visibility settings -->

Modified: branches/rel_1_6/Open-ILS/src/extras/ils_events.xml
===================================================================
--- branches/rel_1_6/Open-ILS/src/extras/ils_events.xml	2010-04-04 01:01:08 UTC (rev 16120)
+++ branches/rel_1_6/Open-ILS/src/extras/ils_events.xml	2010-04-04 04:05:16 UTC (rev 16121)
@@ -590,6 +590,12 @@
 		<desc xml:lang='en-US'>The requested config_circ_matrix_ruleset_not_found was not found</desc>
 	</event>
 
+    <event code='1700' textcode='PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS'>
+        <desc xml:lang='en-US'>There are too many active password reset request sessions for this patron.</desc>
+    </event>
+    <event code='1701' textcode='PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST'>
+        <desc xml:lang='en-US'>The user attempted to update their password using a stale or inactive password reset request session.</desc>
+    </event>
 
 	<event code='1841' textcode='ACQ_PICKLIST_NOT_FOUND'>
 		<desc xml:lang='en-US'>The requested acq.picklist was not found</desc>

Modified: branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Actor.pm
===================================================================
--- branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Actor.pm	2010-04-04 01:01:08 UTC (rev 16120)
+++ branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Actor.pm	2010-04-04 04:05:16 UTC (rev 16121)
@@ -34,6 +34,8 @@
 use OpenILS::Utils::CStoreEditor qw/:funcs/;
 use OpenILS::Utils::Penalty;
 
+use UUID::Tiny qw/:std/;
+
 sub initialize {
 	OpenILS::Application::Actor::Container->initialize();
 	OpenILS::Application::Actor::UserGroups->initialize();
@@ -3343,6 +3345,186 @@
     return {complete => 1};
 }
 
+__PACKAGE__->register_method(
+	method	=> "request_password_reset",
+	api_name	=> "open-ils.actor.patron.password_reset.request",
+	signature	=> {
+        params => [
+            { desc => 'user_id_type', type => 'string' },
+            { desc => 'user_id', type => 'string' },
+        ]
+    },
+);
+sub request_password_reset {
+    my($self, $conn, $user_id_type, $user_id) = @_;
 
+    # Check to see if password reset requests are already being throttled:
+    # 0. Check cache to see if we're in throttle mode (avoid hitting database)
+
+    my $e = new_editor(xact => 1);
+    my $user;
+
+    # Get the user, if any, depending on the input value
+    if ($user_id_type eq 'username') {
+        $user = $e->search_actor_user({usrname => $user_id})->[0];
+        if (!$user) {
+            $e->die_event;
+            return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
+        }
+    } elsif ($user_id_type eq 'barcode') {
+        my $card = $e->search_actor_card([
+            {barcode => $user_id},
+            {flesh => 1, flesh_fields => {ac => ['usr']}}])->[0];
+        if (!$card) { 
+            $e->die_event;
+            return OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
+        }
+        $user = $card->usr;
+    }
+
+    # If the user doesn't have an email address, we can't help them
+    if (!$user->email) {
+        $e->die_event;
+        return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS');
+    }
+    _reset_password_request($conn, $e, $user);
+}
+
+# Once we have the user, we can issue the password reset request
+# XXX Add a wrapper method that accepts barcode + email input
+sub _reset_password_request {
+    my ($conn, $e, $user) = @_;
+
+    # 1. Get throttle threshold and time-to-live from OU_settings
+    my $aupr_throttle = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_throttle') || 1000;
+    my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
+
+    my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl)->iso8601();
+
+    # 2. Get time of last request and number of active requests (num_active)
+    # we use the weird test of usr = -1000 to generate a FALSE condition
+    my $active_requests = $e->json_query({
+        from => 'aupr',
+        select => {
+            aupr => [
+                {
+                    column => 'uuid',
+                    transform => 'COUNT'
+                }
+            ]
+        },
+        where => {
+            has_been_reset => { '=' => { 'usr' => { '=' => -1000 } } },
+            request_time => { '>' => $threshold_time }
+        }
+    });
+
+    # 3. if (num_active > throttle_threshold) and (now - last_request < 1 minute)
+    #      ... delay - set cache - return event correspondingly ...
+    # 
+    # Otherwise, go ahead and try to get the user.
+ 
+    # Check the number of active requests for this user
+    $active_requests = $e->json_query({
+        from => 'aupr',
+        select => {
+            aupr => [
+                {
+                    column => 'usr',
+                    transform => 'COUNT'
+                }
+            ]
+        },
+        where => {
+            usr => { '=' => $user->id },
+            has_been_reset => { '=' => { 'usr' => { '=' => -1000 } } },
+            request_time => { '>' => $threshold_time }
+        }
+    });
+
+    $logger->info("User " . $user->id . " has " . $active_requests->[0]->{'usr'} . " active password reset requests.");
+
+    # if less than or equal to per-user threshold, proceed; otherwise, return event
+    my $aupr_per_user_limit = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_per_user_limit') || 3;
+    if ($active_requests->[0]->{'usr'} > $aupr_per_user_limit) {
+        $e->die_event;
+        return OpenILS::Event->new('PATRON_TOO_MANY_ACTIVE_PASSWORD_RESET_REQUESTS');
+    }
+
+    # Create the aupr object and insert into the database
+    my $reset_request = Fieldmapper::actor::usr_password_reset->new;
+    my $uuid = create_uuid_as_string(UUID_V4);
+    $reset_request->uuid($uuid);
+    $reset_request->usr($user->id);
+
+    my $aupr = $e->create_actor_usr_password_reset($reset_request) or return $e->die_event;
+    $e->commit;
+
+    # Create an event to notify user of the URL to reset their password
+
+    # Can we stuff this in the user_data param for trigger autocreate?
+    my $hostname = $U->ou_ancestor_setting_value($user->home_ou, 'lib.hostname') || 'localhost';
+
+    my $ses = OpenSRF::AppSession->create('open-ils.trigger');
+    $ses->request('open-ils.trigger.event.autocreate', 'password.reset_request', $aupr, $user->home_ou);
+
+    # Trunk only
+    # $U->create_trigger_event('password.reset_request', $aupr, $user->home_ou);
+
+    return 1;
+}
+
+__PACKAGE__->register_method(
+	method	=> "commit_password_reset",
+	api_name	=> "open-ils.actor.patron.password_reset.commit",
+	signature	=> {
+        params => [
+            { desc => 'uuid', type => 'string' },
+            { desc => 'password', type => 'string' },
+        ]
+    },
+);
+sub commit_password_reset {
+    my($self, $conn, $uuid, $password) = @_;
+
+    # Check to see if password reset requests are already being throttled:
+    # 0. Check cache to see if we're in throttle mode (avoid hitting database)
+
+    my $e = new_editor(xact => 1);
+
+    my $aupr = $e->search_actor_usr_password_reset({
+        uuid => $uuid,
+        has_been_reset => 0
+        
+    });
+
+    if (!$aupr->[0]) {
+        $e->die_event;
+        return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
+    }
+    my $user_id = $aupr->[0]->usr;
+    my $user = $e->retrieve_actor_user($user_id);
+
+    # Ensure we're still within the TTL for the request
+    my $aupr_ttl = $U->ou_ancestor_setting_value($user->home_ou, 'circ.password_reset_request_time_to_live') || 24*60*60;
+    my $threshold_time = DateTime->now(time_zone => 'local')->subtract(seconds => $aupr_ttl);
+    my $request_time = DateTime::Format::ISO8601->parse_datetime(clense_ISO8601($aupr->[0]->request_time));
+    if ($request_time < $threshold_time) {
+        $e->die_event;
+        return OpenILS::Event->new('PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST');
+    }
+
+    # All is well; update the password
+    $user->passwd($password);
+    $e->update_actor_user($user);
+
+    # And flag that this password reset request has been honoured
+    $aupr->[0]->has_been_reset('t');
+    $e->update_actor_usr_password_reset($aupr->[0]);
+    $e->commit;
+
+    return 1;
+}
+
 1;
 

Added: branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/WWW/PasswordReset.pm
===================================================================
--- branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/WWW/PasswordReset.pm	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/WWW/PasswordReset.pm	2010-04-04 04:05:16 UTC (rev 16121)
@@ -0,0 +1,205 @@
+package OpenILS::WWW::PasswordReset;
+
+# Copyright (C) 2010 Laurentian University
+# Dan Scott <dscott at laurentian.ca>
+# 
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+use strict; use warnings;
+
+use Apache2::Log;
+use Apache2::Const -compile => qw(OK REDIRECT DECLINED NOT_FOUND :log);
+use APR::Const    -compile => qw(:error SUCCESS);
+use Apache2::RequestRec ();
+use Apache2::RequestIO ();
+use Apache2::RequestUtil;
+use CGI;
+use Template;
+
+use OpenSRF::EX qw(:try);
+use OpenSRF::Utils qw/:datetime/;
+use OpenSRF::Utils::Cache;
+use OpenSRF::System;
+use OpenSRF::AppSession;
+
+use OpenILS::Utils::Fieldmapper;
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenILS::Application::AppUtils;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+
+my $log = 'OpenSRF::Utils::Logger';
+my $U = 'OpenILS::Application::AppUtils';
+
+my ($bootstrap, $actor, $templates);
+my $i18n = {};
+
+sub child_init {
+    OpenSRF::System->bootstrap_client( config_file => $bootstrap );
+    
+    my $conf = OpenSRF::Utils::SettingsClient->new();
+    my $idl = $conf->config_value("IDL");
+    Fieldmapper->import(IDL => $idl);
+    $templates = $conf->config_value("dirs", "templates");
+    $actor = OpenSRF::AppSession->create('open-ils.actor');
+    load_i18n();
+}
+
+sub password_reset {
+    my $apache = shift;
+    return Apache2::Const::DECLINED if (-e $apache->filename);
+
+    $apache->content_type('text/html');
+
+	my $cgi = new CGI;
+    my $ctx = {};
+
+    $ctx->{'uri'} = $apache->uri;
+
+    # Get our locale from the URL
+    (my $locale = $apache->path_info) =~ s{^.*?/([a-z]{2}-[A-Z]{2})/.*?$}{$1};
+    if (!$locale) {
+        $locale = 'en-US';
+    }
+
+    # If locale exists, use it; otherwise fall back to en-US
+    if (exists $i18n->{$locale}) {
+        $ctx->{'i18n'} = $i18n->{$locale};
+    } else {
+        $ctx->{'i18n'} = $i18n->{'en-US'};
+    }
+
+    my $tt = Template->new({
+        INCLUDE_PATH => $templates
+    }) || die "$Template::ERROR\n";
+
+    # Get our UUID: if no UUID, then display barcode / username / email prompt
+    (my $uuid = $apache->path_info) =~ s{^/$locale/([^/]*?)$}{$1};
+    $logger->info("Password reset: UUID = $uuid");
+
+    if (!$uuid) {
+        request_password_reset($apache, $cgi, $tt, $ctx);
+    } else {
+        reset_password($apache, $cgi, $tt, $ctx, $uuid);
+    }
+}
+
+sub reset_password {
+    my ($apache, $cgi, $tt, $ctx, $uuid) = @_;
+
+    my $password_1 = $cgi->param('pwd1');
+    my $password_2 = $cgi->param('pwd2');
+
+    $ctx->{'title'} = $ctx->{'i18n'}{'TITLE'};
+    $ctx->{'password_prompt'} = $ctx->{'i18n'}{'PASSWORD_PROMPT'};
+    $ctx->{'password_prompt2'} = $ctx->{'i18n'}{'PASSWORD_PROMPT2'};
+
+    # In case non-matching passwords slip through our funky Web interface
+    if ($password_1 and $password_2 and ($password_1 ne $password_2)) {
+        $apache->status(Apache2::Const::DECLINED);
+        $ctx->{'status'} = {
+            style => 'error',
+            msg => $ctx->{'i18n'}{'NO_MATCH'}
+        };
+        $tt->process('password-reset/reset-form.tt2', $ctx)
+            || die $tt->error();
+        return Apache2::Const::OK;
+    }
+
+    if ($password_1 and $password_2 and ($password_1 eq $password_2)) {
+        my $response = $actor->request('open-ils.actor.patron.password_reset.commit', $uuid, $password_1)->gather();
+        if (ref($response) && 
+                $response->{'textcode'} && 
+                $response->{'textcode'} eq 'PATRON_NOT_AN_ACTIVE_PASSWORD_RESET_REQUEST') {
+            $apache->status(Apache2::Const::DECLINED);
+            $ctx->{'status'} = { 
+                style => 'error',
+                msg => $ctx->{'i18n'}{'NOT_ACTIVE'}
+
+            };
+            $tt->process('password-reset/reset-form.tt2', $ctx)
+                || die $tt->error();
+            return Apache2::Const::OK;
+        }
+        $ctx->{'status'} = { 
+            style => 'success',
+            msg => $ctx->{'i18n'}{'SUCCESS'}
+        };
+    }
+
+    # Either the password change was successful, or this is their first time through
+    $tt->process('password-reset/reset-form.tt2', $ctx)
+        || die $tt->error();
+
+    return Apache2::Const::OK;
+}
+
+# Load our localized strings - lame, need to convert to Locale::Maketext
+sub load_i18n {
+    foreach my $string_bundle (glob("$templates/password-reset/strings.*")) {
+        open(I18NFH, '<', $string_bundle);
+        (my $locale = $string_bundle) =~ s/^.*\.([a-z]{2}-[A-Z]{2})$/$1/;
+        $logger->debug("Loaded locale [$locale] from file: [$string_bundle]");
+        while(<I18NFH>) {
+            my ($string_id, $string) = ($_ =~ m/^(.+?)=(.*?)$/);
+            $i18n->{$locale}{$string_id} = $string;
+        }
+        close(I18NFH);
+    }
+}
+
+sub request_password_reset {
+    my ($apache, $cgi, $tt, $ctx) = @_;
+
+    my $barcode = $cgi->param('barcode');
+    my $username = $cgi->param('username');
+    my $email = $cgi->param('email');
+
+    if (!($barcode or $username or $email)) {
+        $apache->status(Apache2::Const::OK);
+        $ctx->{'status'} = {
+            style => 'plain',
+            msg => $ctx->{'i18n'}{'IDENTIFY_YOURSELF'}
+        };
+        $tt->process('password-reset/request-form.tt2', $ctx)
+            || die $tt->error();
+        return Apache2::Const::OK;
+    } elsif ($barcode) {
+        my $response = $actor->request('open-ils.actor.patron.password_reset.request', 'barcode', $barcode)->gather();
+        $apache->status(Apache2::Const::OK);
+        $ctx->{'status'} = {
+            style => 'plain',
+            msg => $ctx->{'i18n'}{'REQUEST_SUCCESS'}
+        };
+        # Hide form
+        $tt->process('password-reset/request-form.tt2', $ctx)
+            || die $tt->error();
+        return Apache2::Const::OK;
+    } elsif ($username) {
+        my $response = $actor->request('open-ils.actor.patron.password_reset.request', 'username', $username)->gather();
+        $apache->status(Apache2::Const::OK);
+        $ctx->{'status'} = {
+            style => 'plain',
+            msg => $ctx->{'i18n'}{'REQUEST_SUCCESS'}
+        };
+        # Hide form
+        $tt->process('password-reset/request-form.tt2', $ctx)
+            || die $tt->error();
+        return Apache2::Const::OK;
+    }
+}
+
+1;
+
+# vim: et:ts=4:sw=4

Modified: branches/rel_1_6/Open-ILS/src/sql/Pg/005.schema.actors.sql
===================================================================
--- branches/rel_1_6/Open-ILS/src/sql/Pg/005.schema.actors.sql	2010-04-04 01:01:08 UTC (rev 16120)
+++ branches/rel_1_6/Open-ILS/src/sql/Pg/005.schema.actors.sql	2010-04-04 04:05:16 UTC (rev 16121)
@@ -523,5 +523,36 @@
 
 CREATE INDEX actor_usr_standing_penalty_usr_idx ON actor.usr_standing_penalty (usr);
 
+CREATE TABLE actor.usr_password_reset (
+  id SERIAL PRIMARY KEY,
+  uuid TEXT NOT NULL, 
+  usr BIGINT NOT NULL REFERENCES actor.usr(id) DEFERRABLE INITIALLY DEFERRED, 
+  request_time TIMESTAMP NOT NULL DEFAULT NOW(), 
+  has_been_reset BOOL NOT NULL DEFAULT false
+);
+COMMENT ON TABLE actor.usr_password_reset IS $$
+/*
+ * Copyright (C) 2010 Laurentian University
+ * Dan Scott <dscott at laurentian.ca>
+ *
+ * Self-serve password reset requests
+ *
+ * ****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+$$;
+CREATE UNIQUE INDEX actor_usr_password_reset_uuid_idx ON actor.usr_password_reset (uuid);
+CREATE INDEX actor_usr_password_reset_usr_idx ON actor.usr_password_reset (usr);
+CREATE INDEX actor_usr_password_reset_request_time_idx ON actor.usr_password_reset (request_time);
+CREATE INDEX actor_usr_password_reset_has_been_reset_idx ON actor.usr_password_reset (has_been_reset);
 
 COMMIT;

Modified: branches/rel_1_6/Open-ILS/src/sql/Pg/950.data.seed-values.sql
===================================================================
--- branches/rel_1_6/Open-ILS/src/sql/Pg/950.data.seed-values.sql	2010-04-04 01:01:08 UTC (rev 16120)
+++ branches/rel_1_6/Open-ILS/src/sql/Pg/950.data.seed-values.sql	2010-04-04 04:05:16 UTC (rev 16121)
@@ -1973,6 +1973,37 @@
     (5, 'usr'),
     (5, 'pickup_lib.billing_address');
 
+INSERT INTO action_trigger.hook (key,core_type,description) VALUES ('password.reset_request','aupr','Patron has requested a self-serve password reset');
+INSERT INTO action_trigger.event_definition (id, active, owner, name, hook, validator, reactor, delay, template) 
+    VALUES (15, 'f', 1, 'Password reset request notification', 'password.reset_request', 'NOOP_True', 'SendEmail', '00:00:01',
+$$
+[%- USE date -%]
+[%- user = target.usr -%]
+To: [%- params.recipient_email || user.email %]
+From: [%- params.sender_email || user.home_ou.email || default_sender %]
+Subject: [% user.home_ou.name %]: library account password reset request
 
+You have received this message because you, or somebody else, requested a reset
+of your library system password. If you did not request a reset of your library
+system password, just ignore this message and your current password will
+continue to work.
+
+If you did request a reset of your library system password, please perform
+the following steps to continue the process of resetting your password:
+
+1. Open the following link in a web browser: https://[% params.hostname %]/password-reset/en-US/[% target.uuid %]
+The browser displays a password reset form.
+
+2. Enter your new password in the password reset form in the browser. You must
+enter the password twice to ensure that you do not make a mistake. If the
+passwords match, you will then be able to log in to your library system account
+with the new password.
+
+$$);
+INSERT INTO action_trigger.environment ( event_def, path) VALUES
+    ( 15, 'usr' );
+INSERT INTO action_trigger.environment ( event_def, path) VALUES
+    ( 15, 'usr.home_ou' );
+
 SELECT SETVAL('action_trigger.event_definition_id_seq'::TEXT, 100);
 

Added: branches/rel_1_6/Open-ILS/src/templates/password-reset/request-form.tt2
===================================================================
--- branches/rel_1_6/Open-ILS/src/templates/password-reset/request-form.tt2	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/src/templates/password-reset/request-form.tt2	2010-04-04 04:05:16 UTC (rev 16121)
@@ -0,0 +1,17 @@
+<html>
+<head>
+  <title>[% i18n.REQUEST_TITLE %]</title>
+</head>
+<body>
+  <h1>[% i18n.REQUEST_TITLE %]</h1>
+<p class='[% status.style %]'>[% status.msg %]</p>
+<form method="post" action="[% uri %]">
+    <div>
+        <label for="barcode">[% i18n.BARCODE_PROMPT %] </label><input type="text" name="barcode"/></br>
+        <label for="username">[% i18n.USERNAME_PROMPT %]  </label><input type="text" name="username"/></br>
+        <!--<label for="email">[% i18n.EMAIL_PROMPT %]  </label><input type="text" name="email"/></br>-->
+        <input type="submit"/>
+    </div>
+</form>
+</body>
+</html>

Added: branches/rel_1_6/Open-ILS/src/templates/password-reset/reset-form.tt2
===================================================================
--- branches/rel_1_6/Open-ILS/src/templates/password-reset/reset-form.tt2	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/src/templates/password-reset/reset-form.tt2	2010-04-04 04:05:16 UTC (rev 16121)
@@ -0,0 +1,15 @@
+<html>
+<head>
+  <title>[% title %]</title>
+</head>
+<body>
+  <h1>[% title %]</h1>
+<p class='[% status.style %]'>[% status.msg %]</p>
+<form method="post" action="[% uri %]">
+    <div>
+        <label for="pwd1">[% password_prompt %] </label><input type="password" name="pwd1"/></br>
+        <label for="pwd2">[% password_prompt2 %]  </label><input type="password" name="pwd2"/></br>
+    </div>
+</form>
+</body>
+</html>

Added: branches/rel_1_6/Open-ILS/src/templates/password-reset/strings.en-US
===================================================================
--- branches/rel_1_6/Open-ILS/src/templates/password-reset/strings.en-US	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/src/templates/password-reset/strings.en-US	2010-04-04 04:05:16 UTC (rev 16121)
@@ -0,0 +1,13 @@
+REQUEST_TITLE=Library system password reset request form
+IDENTIFY_YOURSELF=Please enter your user name or barcode to identify your library account and request a password reset.
+REQUEST_SUCCESS=Your user name or barcode has been submitted for a password reset. If a matching account with an email address is found, you will soon receive an email at that address with further instructions for resetting your password.
+BARCODE_PROMPT=Barcode:
+USERNAME_PROMPT=User name:
+EMAIL_PROMPT=Email address associated with the account:
+NO_SESSION=Could not find the requested password reset session.
+NO_MATCH=Passwords did not match. Please try again
+NOT_ACTIVE=This was not an active password reset request. Your password has not been reset.
+SUCCESS=Password has been reset.
+TITLE=Library system password reset
+PASSWORD_PROMPT=New password: 
+PASSWORD_PROMPT2=Re-enter new password: 

Modified: branches/rel_1_6/Open-ILS/web/opac/locale/en-US/lang.dtd
===================================================================
--- branches/rel_1_6/Open-ILS/web/opac/locale/en-US/lang.dtd	2010-04-04 01:01:08 UTC (rev 16120)
+++ branches/rel_1_6/Open-ILS/web/opac/locale/en-US/lang.dtd	2010-04-04 04:05:16 UTC (rev 16121)
@@ -1653,6 +1653,12 @@
 <!ENTITY staff.server.admin.org_settings.cat.bib.alert_on_empty.desc "Alert staff when the last copy for a record is being deleted">
 <!ENTITY staff.server.admin.org_settings.patron.password.use_phone "Patron: password from phone #">
 <!ENTITY staff.server.admin.org_settings.patron.password.use_phone.desc "Use the last 4 digits of the patrons phone number as the default password when creating new users">
+<!ENTITY staff.server.admin.org_settings.circ.password_reset_request_time_to_live "Circulation: Self-serve password reset request time-to-live">
+<!ENTITY staff.server.admin.org_settings.circ.password_reset_request_time_to_live.desc "Length of time (in seconds) a self-serve password reset request should remain active.">
+<!ENTITY staff.server.admin.org_settings.circ.password_reset_request_per_user_limit "Circulation: Maximum concurrently active self-serve password reset requests per user'">
+<!ENTITY staff.server.admin.org_settings.circ.password_reset_request_per_user_limit.desc "When a user has more than this number of concurrently active self-serve password reset requests for their account, prevent the user from creating any new self-serve password reset requests until the number of active requests for the user drops back below this number.">
+<!ENTITY staff.server.admin.org_settings.circ.password_reset_request_throttle "Circulation: Maximum concurrently active self-serve password reset requests">
+<!ENTITY staff.server.admin.org_settings.circ.password_reset_request_throttle.desc "Prevent the creation of new self-serve password reset requests until the number of active requests drops back below this number.">
 <!ENTITY staff.server.admin.org_settings.circ.charge_on_damaged "Charge item price when marked damaged">
 <!ENTITY staff.server.admin.org_settings.circ.charge_on_damaged.desc "Charge item price when marked damaged">
 <!ENTITY staff.server.admin.org_settings.circ.damaged_item_processing_fee "Charge processing fee for damaged items">

Modified: branches/rel_1_6/Open-ILS/xul/staff_client/server/admin/org_unit_settings.xhtml
===================================================================
--- branches/rel_1_6/Open-ILS/xul/staff_client/server/admin/org_unit_settings.xhtml	2010-04-04 01:01:08 UTC (rev 16120)
+++ branches/rel_1_6/Open-ILS/xul/staff_client/server/admin/org_unit_settings.xhtml	2010-04-04 04:05:16 UTC (rev 16121)
@@ -142,6 +142,21 @@
                     desc : '&staff.server.admin.org_settings.patron.password.use_phone.desc;',
                     type : 'bool'
                 },
+                'circ.password_reset_request_time_to_live': {
+                    label: '&staff.server.admin.org_settings.circ.password_reset_request_time_to_live;',
+                    desc: '&staff.server.admin.org_settings.circ.password_reset_request_time_to_live.desc;',
+                    type : 'integer'
+                },
+                'circ.password_reset_request_per_user_limit': {
+                    label: '&staff.server.admin.org_settings.circ.password_reset_request_per_user_limit;',
+                    desc: '&staff.server.admin.org_settings.circ.password_reset_request_per_user_limit.desc;',
+                    type : 'integer'
+                },
+                'circ.password_reset_request_throttle': {
+                    label: '&staff.server.admin.org_settings.circ.password_reset_request_throttle;',
+                    desc: '&staff.server.admin.org_settings.circ.password_reset_request_throttle.desc;',
+                    type : 'integer'
+                },
                 'ui.circ.patron_summary.horizontal' : {
                     label : '&ui.circ.patron_summary.horizontal;',
                     desc : '&ui.circ.patron_summary.horizontal.desc;',



More information about the open-ils-commits mailing list