diff --git a/blocks/ORB/Login.pm b/blocks/ORB/Login.pm index 7ad684a..92e65ae 100755 --- a/blocks/ORB/Login.pm +++ b/blocks/ORB/Login.pm @@ -30,6 +30,44 @@ use LWP::UserAgent; use JSON; use v5.14; +# ============================================================================ +# Utility/support functions + +## @method private $ _build_password_policy() +# Build a string describing the password policy for the current user. This +# interrogates the user's AuthMethod to determine the password policy in place +# for the user (if any), and generates a string describing it in a format +# suitable to present to users. +# +# @return A string containing the password policy enforced for the logged-in +# user. +sub _build_password_policy { + my $self = shift; + + # Anonymous user can have no policy + return '{L_LOGIN_PASSCHANGE_ERRNOUSER}' + if($self -> {"session"} -> anonymous_session()); + + my $user = $self -> {"session"} -> get_user_byid() + or return '{L_LOGIN_PASSCHANGE_ERRNOUSER}'; + + # Fetch the policy, and give up if there isn't one. + my $policy = $self -> {"session"} -> {"auth"} -> get_policy($user -> {"username"}) + or return '{L_LOGIN_POLICY_NONE}'; + + my $policystr = ""; + foreach my $name (@{$policy -> {"policy_order"}}) { + next if(!$policy -> {$name}); + + $policystr .= $self -> {"template"} -> load_template("login/policy.tem", + { "%(policy)s" => "{L_LOGIN_".uc($name)."}", + "%(value)s" => $policy -> {$name} }); + } + + return $policystr; +} + + # ============================================================================ # Emailer functions @@ -306,18 +344,27 @@ sub _validate_signin { } -## @method private $ _validate_recaptcha($response) -# Given a response code submitted as part of an operation by the user, ask the +## @method private $ _validate_recaptcha() +# Pull the reCAPTCHA response string from the posted data, and ask the # google reCAPTCHA validation service to check whether the response is valid. # -# @param response The reCAPTCHA response code submitted by the user -# @return true if the code is valid, false if is not, undef on error. +# @return true if the code is valid, undef if not - examine $self -> errstr() +# for the reason why in this case sub _validate_recaptcha { my $self = shift; - my $response = shift; $self -> clear_error(); + # Pull the reCAPTCHA response code + my ($response, $error) = $self -> validate_string("g-recaptcha-response", {"required" => 1, + "nicename" => "{L_LOGIN_RECAPTCHA}", + "minlen" => 2, + "formattest" => '^[-\w]+$', + "formatdesc" => "{L_LOGIN_ERR_BADRECAPTCHA}" + }); + return $self -> self_error($error) + if($error); + my $ua = LWP::UserAgent -> new(); my $data = { @@ -341,7 +388,7 @@ sub _validate_recaptcha { return $self -> self_error("HTTP problem: ".$resp -> status_line()); } - return 0; + return $self -> self_error("{L_LOGIN_ERR_RECAPTCHA}"); } @@ -407,23 +454,10 @@ sub _validate_signup { if($user); } - # Pull the reCAPTCHA response code - ($args -> {"recaptcha"}, $error) = $self -> validate_string("g-recaptcha-response", {"required" => 1, - "nicename" => "{L_LOGIN_RECAPTCHA}", - "minlen" => 2, - "formattest" => '^[-\w]+$', - "formatdesc" => "{L_LOGIN_ERR_BADRECAPTCHA}" - }); - - # Halt here if there are any problems. - return ($self -> {"template"} -> load_template("error/error_list.tem", {"%(message)s" => "{L_LOGIN_ERR_REGFAILED}", - "%(errors)s" => $error}), $args) - if($error); - - # Is the response valid? - return ($self -> {"template"} -> load_template("error/error_list.tem", {"%(message)s" => "{L_LOGIN_ERR_REGFAILED}", - "%(errors)s" => "{L_LOGIN_ERR_RECAPTCHA}"}), $args) - unless($self -> _validate_recaptcha($args -> {"recaptcha"})); + # Is the reCAPTCHA response valid? + return ($self -> {"template"} -> load_template("error/error_list.tem", { "%(message)s" => "{L_LOGIN_ERR_REGFAILED}", + "%(errors)s" => $self -> errstr() }), $args) + unless($self -> _validate_recaptcha()); # Get here an the user's details are okay, register the new user. @@ -498,23 +532,9 @@ sub _validate_resend { my $error; # Get the recaptcha check out of the way first - # Pull the reCAPTCHA response code - ($args -> {"recaptcha"}, $error) = $self -> validate_string("g-recaptcha-response", {"required" => 1, - "nicename" => "{L_LOGIN_RECAPTCHA}", - "minlen" => 2, - "formattest" => '^[-\w]+$', - "formatdesc" => "{L_LOGIN_ERR_BADRECAPTCHA}" - }); - - # Halt here if there are any problems. - return ($self -> {"template"} -> load_template("error/error_list.tem", {"%(message)s" => "{L_LOGIN_ERR_REGFAILED}", - "%(errors)s" => $error}), $args) - if($error); - - # Is the response valid? - return ($self -> {"template"} -> load_template("error/error_list.tem", {"%(message)s" => "{L_LOGIN_ERR_REGFAILED}", - "%(errors)s" => "{L_LOGIN_ERR_RECAPTCHA}"}), $args) - unless($self -> _validate_recaptcha($args -> {"recaptcha"})); + return ($self -> {"template"} -> load_template("error/error_list.tem", { "%(message)s" => "{L_LOGIN_RESEND_FAILED}", + "%(errors)s" => $self -> errstr() }), $args) + unless($self -> _validate_recaptcha()); # Get the email address entered by the user @@ -576,23 +596,9 @@ sub _validate_recover { my $error; # Get the recaptcha check out of the way first - # Pull the reCAPTCHA response code - ($args -> {"recaptcha"}, $error) = $self -> validate_string("g-recaptcha-response", {"required" => 1, - "nicename" => "{L_LOGIN_RECAPTCHA}", - "minlen" => 2, - "formattest" => '^[-\w]+$', - "formatdesc" => "{L_LOGIN_ERR_BADRECAPTCHA}" - }); - - # Halt here if there are any problems. - return ($self -> {"template"} -> load_template("error/error_list.tem", {"%(message)s" => "{L_LOGIN_ERR_REGFAILED}", - "%(errors)s" => $error}), $args) - if($error); - - # Is the response valid? - return ($self -> {"template"} -> load_template("error/error_list.tem", {"%(message)s" => "{L_LOGIN_ERR_REGFAILED}", - "%(errors)s" => "{L_LOGIN_ERR_RECAPTCHA}"}), $args) - unless($self -> _validate_recaptcha($args -> {"recaptcha"})); + return ($self -> {"template"} -> load_template("error/error_list.tem", { "%(message)s" => "{L_LOGIN_RECOVER_FAILED}", + "%(errors)s" => $self -> errstr() }), $args) + unless($self -> _validate_recaptcha()); # Get the email address entered by the user ($args -> {"email"}, $error) = $self -> validate_string("email", {"required" => 1, @@ -638,6 +644,67 @@ sub _validate_recover { } +## @method private @ validate_reset() +# Pull the userid and activation code out of the submitted data, and determine +# whether they are valid (and that the user's authmethod allows for resets). If +# so, reset the user's password and send and email to them with the new details. +# +# @return Two values: a reference to the user whose password has been reset +# on success, or an error message, and a reference to a hash containing +# the data entered by the user. +sub _validate_reset { + my $self = shift; + my $args = {}; + my $error; + + # Obtain the userid from the query string, if possible. + ($args -> {"uid"}, $error) = $self -> validate_numeric("uid", { "required" => 1, + "nidename" => "{L_LOGIN_UID}", + "intonly" => 1, + "min" => 2}); + return ("{L_LOGIN_ERR_NOUID}", $args) + if($error); + + my $user = $self -> {"session"} -> {"auth"} -> {"app"} -> get_user_byid($args -> {"uid"}) + or return ("{L_LOGIN_ERR_BADUID}", $args); + + # Get the reset code, should be a 64 character alphanumeric string + ($args -> {"resetcode"}, $error) = $self -> validate_string("resetcode", {"required" => 1, + "nicename" => "{L_LOGIN_RESET_CODE}", + "minlen" => 64, + "maxlen" => 64, + "formattest" => '^[a-zA-Z0-9]+$', + "formatdesc" => "{L_LOGIN_ERR_BADRECCHAR}"}); + return ($error, $args) if($error); + + # Does the reset code match the one set for the user? + return ("{L_LOGIN_ERR_BADRECCODE}", $args) + unless($user -> {"act_code"} && $user -> {"act_code"} eq $args -> {"resetcode"}); + + # Users can not recover an inactive account - they need to get a new act code + return ("{L_LOGIN_ERR_NORECINACT}", $args) + if($self -> {"session"} -> {"auth"} -> capabilities($user -> {"username"}, "activate") && + !$self -> {"session"} -> {"auth"} -> activated($user -> {"username"})); + + # double-check the authmethod supports resets, just to be on the safe side (the code should never + # get here if it does not, but better safe than sorry) + return ($self -> {"session"} -> {"auth"} -> capabilities($user -> {"username"}, "recover_message"), $args) + if(!$self -> {"session"} -> {"auth"} -> capabilities($user -> {"username"}, "recover")); + + # Okay, user is valid, authcode checks out, auth module supports resets, generate a new + # password and send it + my $newpass = $self -> {"session"} -> {"auth"} -> reset_password($user -> {"username"}); + return ($self -> {"template"} -> load_template("error/error.tem", { "%(message)s" => "{L_LOGIN_RECOVER_FAILED}", + "%(reason)s" => $self -> {"session"} -> {"auth"} -> errstr()}), $args) + if(!$newpass); + + # Get here and the user's account has been reset + $self -> _reset_email($user, $newpass); + + return($user, $args); +} + + ## @method private @ validate_passchange() # Determine whether the password change request made by the user is valid. If the # password change is valid (passwords match, pass policy, and the old password is @@ -646,13 +713,17 @@ sub _validate_recover { # @return An array of two values: A reference to the user's data on success, # or an error string if the change failed, and a reference to a hash of # arguments that passed validation. -# FIXME: OVERHAUL sub _validate_passchange { my $self = shift; my $error = ""; my $errors = ""; my $args = {}; + # Get the recaptcha check out of the way first + return ($self -> {"template"} -> load_template("error/error_list.tem", { "%(message)s" => "{L_LOGIN_PASSCHANGE_FAILED}", + "%(errors)s" => $self -> errstr() }), $args) + unless($self -> _validate_recaptcha()); + # Need to get a logged-in user before anything else is done my $user = $self -> {"session"} -> get_user_byid(); return ($self -> {"template"} -> load_template("error/error_list.tem", @@ -738,67 +809,6 @@ sub _validate_passchange { } -## @method private @ validate_reset() -# Pull the userid and activation code out of the submitted data, and determine -# whether they are valid (and that the user's authmethod allows for resets). If -# so, reset the user's password and send and email to them with the new details. -# -# @return Two values: a reference to the user whose password has been reset -# on success, or an error message, and a reference to a hash containing -# the data entered by the user. -sub _validate_reset { - my $self = shift; - my $args = {}; - my $error; - - # Obtain the userid from the query string, if possible. - ($args -> {"uid"}, $error) = $self -> validate_numeric("uid", { "required" => 1, - "nidename" => "{L_LOGIN_UID}", - "intonly" => 1, - "min" => 2}); - return ("{L_LOGIN_ERR_NOUID}", $args) - if($error); - - my $user = $self -> {"session"} -> {"auth"} -> {"app"} -> get_user_byid($args -> {"uid"}) - or return ("{L_LOGIN_ERR_BADUID}", $args); - - # Get the reset code, should be a 64 character alphanumeric string - ($args -> {"resetcode"}, $error) = $self -> validate_string("resetcode", {"required" => 1, - "nicename" => "{L_LOGIN_RESET_CODE}", - "minlen" => 64, - "maxlen" => 64, - "formattest" => '^[a-zA-Z0-9]+$', - "formatdesc" => "{L_LOGIN_ERR_BADRECCHAR}"}); - return ($error, $args) if($error); - - # Does the reset code match the one set for the user? - return ("{L_LOGIN_ERR_BADRECCODE}", $args) - unless($user -> {"act_code"} && $user -> {"act_code"} eq $args -> {"resetcode"}); - - # Users can not recover an inactive account - they need to get a new act code - return ("{L_LOGIN_ERR_NORECINACT}", $args) - if($self -> {"session"} -> {"auth"} -> capabilities($user -> {"username"}, "activate") && - !$self -> {"session"} -> {"auth"} -> activated($user -> {"username"})); - - # double-check the authmethod supports resets, just to be on the safe side (the code should never - # get here if it does not, but better safe than sorry) - return ($self -> {"session"} -> {"auth"} -> capabilities($user -> {"username"}, "recover_message"), $args) - if(!$self -> {"session"} -> {"auth"} -> capabilities($user -> {"username"}, "recover")); - - # Okay, user is valid, authcode checks out, auth module supports resets, generate a new - # password and send it - my $newpass = $self -> {"session"} -> {"auth"} -> reset_password($user -> {"username"}); - return ($self -> {"template"} -> load_template("error/error.tem", { "%(message)s" => "{L_LOGIN_RECOVER_FAILED}", - "%(reason)s" => $self -> {"session"} -> {"auth"} -> errstr()}), $args) - if(!$newpass); - - # Get here and the user's account has been reset - $self -> _reset_email($user, $newpass); - - return($user, $args); -} - - # ============================================================================ # Form generators @@ -939,39 +949,45 @@ sub _generate_recover_form { # @param error A string containing errors related to password changes, or undef. # @return An array containing the page title, content, extra header data, and # extra javascript content. -# FIXME: OVERHAUL sub _generate_passchange_form { my $self = shift; my $error = shift; - my $reasons = { 'temporary' => "{L_LOGIN_FORCECHANGE_TEMP}", - 'expired' => "{L_LOGIN_FORCECHANGE_OLD}", }; + my $reasons = { + 'temporary' => "{L_LOGIN_PASSCHANGE_TEMP}", + 'expired' => "{L_LOGIN_PASSCHANGE_OLD}", + 'manual' => "{L_LOGIN_PASSCHANGE_MANUAL}" + }; # convert the password policy to a string - my $policy = $self -> build_password_policy("login/policy.tem") || "{L_LOGIN_POLICY_NONE}"; + my $policy = $self -> _build_password_policy(); # Reason should be in the 'passchange_reason' session variable. - my $reason = $self -> {"session"} -> get_variable("passchange_reason", "temporary"); + my $reason = $self -> {"session"} -> get_variable("passchange_reason", "manual"); # Force a sane reason - $reason = 'temporary' unless($reason && $reasons -> {$reason}); + $reason = 'manual' unless($reason && $reasons -> {$reason}); # Wrap the error message in a message box if we have one. $error = $self -> {"template"} -> load_template("error/error_box.tem", {"%(message)s" => $error}) if($error); - return ("{L_LOGIN_TITLE}", - $self -> {"template"} -> load_template("login/force_password.tem", {"%(error)s" => $error, - "%(target)s" => $self -> build_url("block" => "login"), - "%(policy)s" => $policy, - "%(reason)s" => $reasons -> {$reason}, - "%(rid)s" => $reason } )); + return ("{L_LOGIN_PASSCHANGE_TITLE}", + $self -> {"template"} -> load_template("login/form_passchange.tem", + { "%(error)s" => $error, + "%(sitekey)s" => $self -> {"settings"} -> {"config"} -> {"Login:recaptcha_sitekey"}, + "%(url-target)s" => $self -> build_url(block => "login", pathinfo => [ "passchange" ]), + "%(policy)s" => $policy, + "%(reason)s" => $reasons -> {$reason}, + "%(rid)s" => $reason } ), + $self -> {"template"} -> load_template("login/signup_extrahead.tem"), + $self -> {"template"} -> load_template("login/extrajs.tem")); } # ============================================================================ # Response generators -## @method private @ generate_loggedin() +## @method private @ _generate_loggedin() # Generate the contents of a page telling the user that they have successfully logged in. # # @return An array of two values: the page title string, and the 'logged in' message. @@ -1013,7 +1029,7 @@ sub _generate_loggedin { } -## @method private @ generate_signedout() +## @method private @ _generate_signedout() # Generate the contents of a page telling the user that they have successfully logged out. # # @return An array of two values: the page title string, and the 'logged out' message @@ -1036,7 +1052,7 @@ sub _generate_signedout { } -## @method private @ generate_activated($user) +## @method private @ _generate_activated($user) # Generate the contents of a page telling the user that they have successfully activated # their account. # @@ -1059,7 +1075,7 @@ sub _generate_activated { } -## @method private @ generate_signedup() +## @method private @ _generate_signedup() # Generate the contents of a page telling the user that they have successfully created an # inactive account. # @@ -1081,7 +1097,7 @@ sub _generate_signedup { } -## @method private @ generate_resent() +## @method private @ _generate_resent() # Generate the contents of a page telling the user that a new activation code has been # sent to their email address. # @@ -1103,7 +1119,7 @@ sub _generate_resent { } -## @method private @ generate_recover() +## @method private @ _generate_recover() # Generate the contents of a page telling the user that a new password has been # sent to their email address. # @@ -1124,7 +1140,7 @@ sub _generate_recover { } -## @method private @ generate_reset() +## @method private @ _generate_reset() # Generate the contents of a page telling the user that a new password has been # sent to their email address. # @@ -1158,6 +1174,28 @@ sub _generate_reset { } +## @method private @ _generate_passchanged() +# Generate the contents of a page telling the user that they have successfully created an +# inactive account. +# +# @return An array of two values: the page title string, the 'registered' message. +sub _generate_passchanged { + my $self = shift; + + my $url = $self -> build_url(block => "login", + pathinfo => [ ]); + + return ("{L_LOGIN_PASSCHANGE_DONETITLE}", + $self -> message_box(title => "{L_LOGIN_PASSCHANGE_DONETITLE}", + type => "account", + summary => "{L_LOGIN_PASSCHANGE_SUMMARY}", + message => "{L_LOGIN_PASSCHANGE_MESSAGE}", + buttons => [ {"message" => "{L_SITE_CONTINUE}", + "colour" => "standard", + "href" => $url} ])); +} + + # ============================================================================ # API handling @@ -1338,23 +1376,28 @@ sub _handle_reset { } -# FIXME: OVERHAUL +## @method private @ _handle_passchange() +# Handle the process of showing the form they can change their password +# through, and processing submission from the form. +# +# @return An array containing the page title, content, extra header data, and +# extra javascript content. sub _handle_passchange { my $self = shift; if(defined($self -> {"cgi"} -> param("changepass"))) { - # Check the password is valid- - my ($user, $args) = $self -> validate_passchange(); + # Check the password form is valid + my ($user, $args) = $self -> _validate_passchange(); # Change failed, send back the change form if(!ref($user)) { $self -> log("passchange error", $user); return $self -> _generate_passchange_form($user); - # Change done, send back the loggedin page + # Change done, send back the loggedin page } else { $self -> log("password updated", $user); - return $self -> _generate_loggedin(); + return $self -> _generate_passchanged(); } } diff --git a/lang/en/login.lang b/lang/en/login.lang index cd7e044..36d1157 100755 --- a/lang/en/login.lang +++ b/lang/en/login.lang @@ -128,10 +128,11 @@ LOGIN_RESEND_SUMMARY = Resend successful! LOGIN_RESEND_MESSAGE = A new password and an activation link have been send to your email address.

Please check your email for a message with the subject 'Your {V_[sitename]} activation code' and follow the instructions it contains to activate your account. # Force password change -LOGIN_PASSCHANGE = Change password -LOGIN_FORCECHANGE_INTRO = Before you continue, please choose a new password to set for your account. -LOGIN_FORCECHANGE_TEMP = Your account is currently set up with a temporary password. -LOGIN_FORCECHANGE_OLD = The password on your account has expired as a result of age limits enforced by the site's password policy. +LOGIN_PASSCHANGE_TITLE = Change password +LOGIN_PASSCHANGE_INTRO = Please choose a new password using the form below. +LOGIN_PASSCHANGE_TEMP = Your account is currently set up with a temporary password. +LOGIN_PASSCHANGE_OLD = The password on your account has expired as a result of age limits enforced by the site's password policy. +LOGIN_PASSCHANGE_MANUAL = You have chosen to change the password set for your account. LOGIN_NEWPASSWORD = New password LOGIN_CONFPASS = Confirm password LOGIN_OLDPASS = Your current password @@ -143,6 +144,10 @@ LOGIN_PASSCHANGE_ERRMATCH = The new password specified does not match the confi LOGIN_PASSCHANGE_ERRSAME = The new password can not be the same as the old password. LOGIN_PASSCHANGE_ERRVALID = The specified old password is not correct. You must enter the password you used to sign in. +LOGIN_PASSCHANGE_DONETITLE = Password changed +LOGIN_PASSCHANGE_SUMMARY = Password change successful! +LOGIN_PASSCHANGE_MESSAGE = Your password has been updated successfully. The next time you log in you should enter your new password. + LOGIN_POLICY = Password policy LOGIN_POLICY_INTRO = When choosing a new password, keep in mind that: LOGIN_POLICY_NONE = No password policy is currently in place, you may use any password you want. diff --git a/templates/default/login/form_passchange.tem b/templates/default/login/form_passchange.tem new file mode 100644 index 0000000..0826082 --- /dev/null +++ b/templates/default/login/form_passchange.tem @@ -0,0 +1,40 @@ +
+%(error)s + +
+
%(reason)s {L_LOGIN_PASSCHANGE_INTRO}
+
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+ +
+
+
+
{L_LOGIN_POLICY_INTRO}
+
    +%(policy)s +
+
+
diff --git a/templates/default/login/policy.tem b/templates/default/login/policy.tem new file mode 100644 index 0000000..36c2736 --- /dev/null +++ b/templates/default/login/policy.tem @@ -0,0 +1 @@ +
  • %(policy)s