Files
ROC/modules/auth/cms_authentication_module.e
Jocelyn Fiat f8715d54a8 Improved credential validation.
- added `CMS_USER_API.user_with_credential (...): detachable CMS_USER` that check if credential is valid, and return associated user.
  - replaced use of `is_valid_credential` by new function `user_with_credential` .
  - revisited the session auth, to allow other credential validations (other than ROC CMS auth).
  - added CMS_USER_API.credential_validations to allow authenticating with system other than ROC CMS.
Added new permission to allow by-passing the default ROC-CMS user login/register management:
  - new permission to edit its own account.
  - new permission to edit its own password.
  - new permission to view users details (mostly for user managers).
2017-10-27 12:26:21 +02:00

1226 lines
43 KiB
Plaintext

note
description: "Module Auth"
date: "$Date$"
revision: "$Revision$"
class
CMS_AUTHENTICATION_MODULE
inherit
CMS_MODULE
rename
module_api as auth_api
redefine
initialize,
setup_hooks,
permissions,
auth_api
end
CMS_WITH_MODULE_ADMINISTRATION
CMS_WITH_WEBAPI
CMS_HOOK_AUTO_REGISTER
CMS_HOOK_RESPONSE_ALTER
CMS_HOOK_VALUE_TABLE_ALTER
CMS_HOOK_BLOCK
CMS_HOOK_BLOCK_HELPER
CMS_HOOK_MENU_SYSTEM_ALTER
SHARED_EXECUTION_ENVIRONMENT
export
{NONE} all
end
REFACTORING_HELPER
SHARED_LOGGER
create
make
feature {NONE} -- Initialization
make
-- Create current module
do
version := "1.0"
description := "Authentication module"
package := "authentication"
create root_dir.make_current
cache_duration := 0
enable -- Is enabled by default
end
feature {CMS_API} -- Initialization
initialize (api: CMS_API)
-- <PRecursor>
do
create auth_api.make (api)
Precursor (api)
end
feature -- Access
name: STRING = "auth"
permissions: LIST [READABLE_STRING_8]
-- List of permission ids, used by this module, and declared.
do
Result := Precursor
Result.force ("account register")
Result.force ("account activate")
Result.force ("account reject")
Result.force ("account reactivate")
Result.force ("edit own account")
Result.force ("change own username")
Result.force ("change own password")
Result.force ("view users")
end
auth_api: detachable CMS_AUTHENTICATION_API
feature {CMS_EXECUTION} -- Administration
administration: CMS_AUTHENTICATION_MODULE_ADMINISTRATION
do
create Result.make (Current)
end
feature -- Webapi
webapi: CMS_AUTHENTICATION_MODULE_WEBAPI
do
create Result.make (Current)
end
feature -- Access: docs
root_dir: PATH
cache_duration: INTEGER
-- Caching duration
--| 0: disable
--| -1: cache always valie
--| nb: cache expires after nb seconds.
cache_disabled: BOOLEAN
do
Result := cache_duration = 0
end
feature -- Router
roc_login_location: STRING = "account/roc-login"
roc_logout_location: STRING = "account/roc-logout"
setup_router (a_router: WSF_ROUTER; a_api: CMS_API)
-- <Precursor>
do
if attached auth_api as l_auth_api then
configure_web (l_auth_api, a_router)
end
end
configure_web (a_api: CMS_AUTHENTICATION_API; a_router: WSF_ROUTER)
local
m: WSF_URI_MAPPING
do
create m.make_trailing_slash_ignored ("/account", create {WSF_URI_AGENT_HANDLER}.make (agent handle_account (a_api, ?, ?)))
a_router.map (m, a_router.methods_head_get)
create m.make_trailing_slash_ignored ("/account/edit", create {WSF_URI_AGENT_HANDLER}.make (agent handle_edit_account (a_api, ?, ?)))
a_router.map (m, a_router.methods_head_get)
a_router.handle ("/" + roc_login_location, create {WSF_URI_AGENT_HANDLER}.make (agent handle_login(a_api, ?, ?)), a_router.methods_head_get)
a_router.handle ("/" + roc_logout_location, create {WSF_URI_AGENT_HANDLER}.make (agent handle_logout(a_api, ?, ?)), a_router.methods_head_get)
a_router.handle ("/account/roc-register", create {WSF_URI_AGENT_HANDLER}.make (agent handle_register(a_api, ?, ?)), a_router.methods_get_post)
a_router.handle ("/account/activate/{token}", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent handle_activation(a_api, ?, ?)), a_router.methods_head_get)
a_router.handle ("/account/reject/{token}", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent handle_reject(a_api, ?, ?)), a_router.methods_head_get)
a_router.handle ("/account/reactivate", create {WSF_URI_AGENT_HANDLER}.make (agent handle_reactivation(a_api, ?, ?)), a_router.methods_get_post)
a_router.handle ("/account/new-password", create {WSF_URI_AGENT_HANDLER}.make (agent handle_new_password(a_api, ?, ?)), a_router.methods_get_post)
a_router.handle ("/account/reset-password", create {WSF_URI_AGENT_HANDLER}.make (agent handle_reset_password(a_api, ?, ?)), a_router.methods_get_post)
a_router.handle ("/account/change/{field}", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent handle_change_field (a_api, ?, ?)), a_router.methods_get_post)
end
feature -- Hooks configuration
setup_hooks (a_hooks: CMS_HOOK_CORE_MANAGER)
-- Module hooks configuration.
do
auto_subscribe_to_hooks (a_hooks)
a_hooks.subscribe_to_block_hook (Current)
a_hooks.subscribe_to_value_table_alter_hook (Current)
a_hooks.subscribe_to_menu_system_alter_hook (Current)
end
value_table_alter (a_value: CMS_VALUE_TABLE; a_response: CMS_RESPONSE)
-- <Precursor>
local
l_destination: detachable READABLE_STRING_GENERAL
l_url: STRING
l_url_name: READABLE_STRING_GENERAL
do
if attached {WSF_STRING} a_response.request.item ("destination") as p_destination then
l_destination := p_destination.value
else
l_destination := a_response.location
end
if l_destination.starts_with ("account/auth/") then
l_destination := Void
end
if attached a_response.user as u then
a_value.force (u, "user")
l_url_name := "site_sign_out_url"
l_url := a_response.url (roc_logout_location, Void)
else
a_value.force (Void, "user")
l_url_name := "site_sign_in_url"
l_url := a_response.url (roc_login_location, Void)
end
if l_destination /= Void and then not l_url.has_substring ("?destination") then
l_url.append ("?destination=" + percent_encoded (l_destination))
end
a_value.force (l_url, l_url_name)
end
menu_system_alter (a_menu_system: CMS_MENU_SYSTEM; a_response: CMS_RESPONSE)
-- Hook execution on collection of menu contained by `a_menu_system'
-- for related response `a_response'.
local
lnk: CMS_LOCAL_LINK
do
if attached a_response.user as u then
create lnk.make (u.name, "account")
lnk.set_weight (97)
a_menu_system.primary_menu.extend (lnk)
create lnk.make ("Logout", roc_logout_location)
else
create lnk.make ("Login", roc_login_location)
end
lnk.set_weight (98)
if
a_response.location.starts_with_general ("account/auth/")
or a_response.location.starts_with_general ("account/roc-log") -- in ou out
then
-- ignore destination
else
lnk.add_query_parameter ("destination", percent_encoded (a_response.location))
end
a_menu_system.primary_menu.extend (lnk)
-- Add the link to the taxonomy to the main menu
if a_response.has_permission ("access admin") then
create lnk.make ("Administration", a_response.api.administration_path_location (Void))
lnk.set_weight (999)
a_menu_system.navigation_menu.extend (lnk)
end
end
feature -- Handler / Constants
auth_strategy_execution_variable_name: STRING = "auth_strategy"
-- Exevc
auth_strategy (req: WSF_REQUEST): detachable READABLE_STRING_8
-- Strategy used by current authentication.
-- note: if user is authenticated..
do
if
attached {READABLE_STRING_GENERAL} req.execution_variable (auth_strategy_execution_variable_name) as s and then
s.is_valid_as_string_8
then
Result := s.to_string_8
end
end
feature -- Handler
view_account_form_id: STRING = "roccms-user-view"
edit_account_form_id: STRING = "roccms-user-edit"
handle_account (a_auth_api: CMS_AUTHENTICATION_API; req: WSF_REQUEST; res: WSF_RESPONSE)
local
r: CMS_RESPONSE
l_user: detachable CMS_USER
b: STRING
lnk: CMS_LOCAL_LINK
f: CMS_FORM
tf: WSF_FORM_TEXT_INPUT
do
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, a_auth_api.cms_api)
create b.make_empty
l_user := r.user
create f.make (r.location, view_account_form_id)
if attached smarty_template_block (Current, "account_info", a_auth_api.cms_api) as l_tpl_block then
l_tpl_block.set_weight (-10)
r.add_block (l_tpl_block, "content")
else
debug ("cms")
r.add_warning_message ("Error with block [resources_page]")
end
if l_user /= Void then
create tf.make_with_text ("username", l_user.name)
tf.set_label ("Username")
f.extend (tf)
if attached l_user.email as l_email then
create tf.make_with_text ("email", l_email.to_string_32)
tf.set_label ("Email")
f.extend (tf)
end
if attached l_user.profile_name as l_prof_name then
create tf.make_with_text ("profile_name", l_prof_name)
tf.set_label ("Profile name")
f.extend (tf)
end
create tf.make_with_text ("creation", a_auth_api.cms_api.formatted_date_time_yyyy_mm_dd (l_user.creation_date))
tf.set_label ("Creation date")
f.extend (tf)
if attached l_user.last_login_date as dt then
create tf.make_with_text ("last_login", a_auth_api.cms_api.formatted_date_time_ago (dt))
tf.set_label ("Last login")
f.extend (tf)
end
end
end
if r.is_authenticated then
create lnk.make ("View", "account/")
lnk.set_weight (1)
r.add_to_primary_tabs (lnk)
if r.has_permission ("edit own account") then
create lnk.make ("Edit", "account/edit")
lnk.set_weight (2)
r.add_to_primary_tabs (lnk)
end
end
a_auth_api.cms_api.hooks.invoke_form_alter (f, Void, r)
f.append_to_html (r.wsf_theme, b)
r.set_main_content (b)
if l_user = Void then
r.set_redirection (roc_login_location)
end
r.execute
end
handle_edit_account (a_auth_api: CMS_AUTHENTICATION_API; req: WSF_REQUEST; res: WSF_RESPONSE)
local
r: CMS_RESPONSE
l_user: detachable CMS_USER
b: STRING
lnk: CMS_LOCAL_LINK
l_form: CMS_FORM
do
if a_auth_api.cms_api.has_permission ("edit own account") then
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, a_auth_api.cms_api)
create b.make_empty
l_user := r.user
create l_form.make (r.location, edit_account_form_id)
if attached smarty_template_block (Current, "account_edit", a_auth_api.cms_api) as l_tpl_block then
l_tpl_block.set_weight (-10)
r.add_block (l_tpl_block, "content")
else
debug ("cms")
r.add_warning_message ("Error with block [resources_page]")
end
-- Build CMS form...
end
create lnk.make ("View", "account/")
lnk.set_weight (1)
r.add_to_primary_tabs (lnk)
create lnk.make ("Edit", "account/edit")
lnk.set_weight (2)
r.add_to_primary_tabs (lnk)
if
r.has_permission ("change own username") and then
attached new_change_username_form (r) as f
then
f.append_to_html (r.wsf_theme, b)
end
if attached new_change_profile_name_form (r) as f then
f.append_to_html (r.wsf_theme, b)
end
if
r.has_permission ("change own password") and then
attached new_change_password_form (r) as f
then
f.append_to_html (r.wsf_theme, b)
end
if attached new_change_email_form (r) as f then
f.append_to_html (r.wsf_theme, b)
end
l_form.append_to_html (r.wsf_theme, b)
r.set_main_content (b)
if l_user = Void then
r.set_redirection ("account")
end
r.execute
else
a_auth_api.cms_api.response_api.send_access_denied ("Can not edit your acocunt", req, res)
end
end
handle_login (a_auth_api: CMS_AUTHENTICATION_API; req: WSF_REQUEST; res: WSF_RESPONSE)
local
r: CMS_RESPONSE
do
if a_auth_api.cms_api.user_is_authenticated then
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, a_auth_api.cms_api)
r.set_redirection ("account")
r.execute
elseif attached a_auth_api.cms_api.module_by_name ("session_auth") then
-- FIXME: find better solution to support a default login system.
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, a_auth_api.cms_api)
if attached {WSF_STRING} req.item ("destination") as l_destination then
r.set_redirection ("account/auth/roc-session-login?destination=" + l_destination.url_encoded_value)
else
r.set_redirection ("account/auth/roc-session-login")
end
r.execute
elseif attached a_auth_api.cms_api.module_by_name ("basic_auth") then
-- FIXME: find better solution to support a default login system.
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, a_auth_api.cms_api)
if attached {WSF_STRING} req.item ("destination") as l_destination then
r.set_redirection ("account/auth/roc-basic-login?destination=" + l_destination.url_encoded_value)
else
r.set_redirection ("account/auth/roc-basic-login")
end
r.execute
else
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, a_auth_api.cms_api)
r.execute
end
end
handle_logout (a_auth_api: CMS_AUTHENTICATION_API; req: WSF_REQUEST; res: WSF_RESPONSE)
local
r: CMS_RESPONSE
loc: STRING
do
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, a_auth_api.cms_api)
if attached auth_strategy (req) as l_auth_strategy then
loc := l_auth_strategy
else
loc := ""
end
-- Do not try to redirect to previous page or destination!
-- if attached {WSF_STRING} req.query_parameter ("destination") as l_destination then
-- loc.append ("?destination=" + l_destination.url_encoded_value)
-- end
r.set_redirection (loc)
r.execute
end
handle_register (a_auth_api: CMS_AUTHENTICATION_API; req: WSF_REQUEST; res: WSF_RESPONSE)
local
f: CMS_FORM
r: CMS_RESPONSE
l_user_api: CMS_USER_API
u: CMS_TEMP_USER
l_exist: BOOLEAN
es: CMS_AUTHENTICATION_EMAIL_SERVICE
l_url_activate: STRING
l_url_reject: STRING
l_token: STRING
l_captcha_passed: BOOLEAN
l_email: READABLE_STRING_8
do
if
a_auth_api.cms_api.has_permission ("account register") and then
req.is_post_request_method
then
create f.make (req.percent_encoded_path_info, "roccms-user-register")
f.extend_text_field ("name", Void)
f.extend_password_field ("password", Void)
f.extend_text_field ("email", Void)
f.extend_text_field ("personal_information", Void)
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, a_auth_api.cms_api)
f.process (r)
if
attached f.last_data as fd and then not fd.has_error and then
attached fd.string_item ("name") as l_name and then
attached fd.string_item ("password") as l_password and then
attached fd.string_item ("email") as s_email and then
attached fd.string_item ("personal_information") as l_personal_information
then
if s_email.is_valid_as_string_8 then
l_email := s_email.to_string_8
l_user_api := a_auth_api.cms_api.user_api
if attached l_user_api.user_by_name (l_name) or else attached l_user_api.temp_user_by_name (l_name) then
-- Username already exist.
r.set_value ("User name already exists!", "error_name")
l_exist := True
end
if attached l_user_api.user_by_email (l_email) or else attached l_user_api.temp_user_by_email (l_email) then
-- Email already exists.
r.set_value ("An account is already associated with that email address!", "error_email")
l_exist := True
end
if attached recaptcha_secret_key (a_auth_api.cms_api) as l_recaptcha_key then
if attached {WSF_STRING} req.form_parameter ("g-recaptcha-response") as l_recaptcha_response and then is_captcha_verified (l_recaptcha_key, l_recaptcha_response.url_encoded_value) then
l_captcha_passed := True
else
--| Bad or missing captcha
l_captcha_passed := False
end
else
--| reCaptcha is not setup, so no verification
l_captcha_passed := True
end
if l_captcha_passed and then not l_exist then
-- New temp user
create u.make (l_name)
u.set_email (l_email)
u.set_password (l_password)
u.set_personal_information (l_personal_information)
a_auth_api.register_user (u, l_email, l_personal_information)
else
r.set_value (l_name, "name")
r.set_value (l_email, "email")
r.set_value (l_personal_information, "personal_information")
r.set_status_code ({HTTP_CONSTANTS}.bad_request)
end
else
r.set_value (l_name, "name")
r.set_value (l_email, "email")
r.set_value (l_personal_information, "personal_information")
r.set_status_code ({HTTP_CONSTANTS}.bad_request)
end
r.execute
else
a_auth_api.cms_api.response_api.send_bad_request ("There were issue with your application, invalid or missing values.", req, res)
end
else
a_auth_api.cms_api.response_api.send_permissions_access_denied ("You can also contact the webmaster to ask for an account.", Void, req, res)
end
end
handle_activation (a_auth_api: CMS_AUTHENTICATION_API; req: WSF_REQUEST; res: WSF_RESPONSE)
local
r: CMS_RESPONSE
l_user_api: CMS_USER_API
do
if a_auth_api.cms_api.has_permission ("account activate") then
l_user_api := a_auth_api.cms_api.user_api
if attached {WSF_STRING} req.path_parameter ("token") as l_token then
if attached {CMS_TEMP_USER} l_user_api.temp_user_by_activation_token (l_token.value) as l_temp_user then
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, a_auth_api.cms_api)
a_auth_api.activate_user (l_temp_user, l_token.value)
if
not a_auth_api.has_error and then
attached l_user_api.user_by_name (l_temp_user.name) as l_new_user
then
r.set_main_content ("<p> The account <i>" + a_auth_api.cms_api.user_html_link (l_new_user) + "</i> has been activated</p>")
else
-- Failure!!!
r.set_status_code ({HTTP_CONSTANTS}.internal_server_error)
r.set_main_content ("<p>ERROR: User activation failed for <i>" + html_encoded (l_temp_user.name) + "</i>!</p>")
if attached l_user_api.error_handler.as_single_error as err then
r.add_error_message (html_encoded (err.string_representation))
end
end
else -- the token does not exist, or it was already used.
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, a_auth_api.cms_api)
r.set_status_code ({HTTP_CONSTANTS}.bad_request)
r.set_main_content ("<p>The token <i>" + l_token.value + "</i> is not valid " + r.link ("Reactivate Account", "account/reactivate", Void) + "</p>")
end
r.execute
else
(create {INTERNAL_SERVER_ERROR_CMS_RESPONSE}.make (req, res, a_auth_api.cms_api)).execute
end
else
a_auth_api.cms_api.response_api.send_access_denied (Void, req, res)
end
end
handle_reject (a_auth_api: CMS_AUTHENTICATION_API; req: WSF_REQUEST; res: WSF_RESPONSE)
local
r: CMS_RESPONSE
es: CMS_AUTHENTICATION_EMAIL_SERVICE
l_ir: INTERNAL_SERVER_ERROR_CMS_RESPONSE
l_user_api: CMS_USER_API
do
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, a_auth_api.cms_api)
if r.has_permission ("account reject") then
if attached {WSF_STRING} req.path_parameter ("token") as l_token then
l_user_api := a_auth_api.cms_api.user_api
if attached {CMS_TEMP_USER} l_user_api.temp_user_by_activation_token (l_token.value) as l_user then
l_user_api.delete_temp_user (l_user)
r.set_main_content ("<p> The temporal account for <i>" + html_encoded (l_user.name) + "</i> has been removed</p>")
-- Send Email
if attached l_user.email as l_email then
create es.make (create {CMS_AUTHENTICATION_EMAIL_SERVICE_PARAMETERS}.make (a_auth_api.cms_api))
write_debug_log (generator + ".handle register: send_contact_activation_reject_email")
es.send_contact_activation_reject_email (l_email, l_user, req.absolute_script_url (""))
end
else
-- the token does not exist, or it was already used.
r.set_status_code ({HTTP_CONSTANTS}.bad_request)
r.set_main_content ("<p>The token <i>" + l_token.value + "</i> is not valid ")
end
r.execute
else
create l_ir.make (req, res, a_auth_api.cms_api)
l_ir.execute
end
else
a_auth_api.cms_api.response_api.send_access_denied (Void, req, res)
end
end
handle_reactivation (a_auth_api: CMS_AUTHENTICATION_API; req: WSF_REQUEST; res: WSF_RESPONSE)
local
r: CMS_RESPONSE
es: CMS_AUTHENTICATION_EMAIL_SERVICE
l_user_api: CMS_USER_API
l_token: STRING
l_url_activate: STRING
l_url_reject: STRING
l_email: READABLE_STRING_8
do
if a_auth_api.cms_api.has_permission ("account reactivate") then
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, a_auth_api.cms_api)
if req.is_post_request_method then
if attached {WSF_STRING} req.form_parameter ("email") as p_email then
if p_email.value.is_valid_as_string_8 then
l_email := p_email.value.to_string_8
l_user_api := a_auth_api.cms_api.user_api
if attached {CMS_TEMP_USER} l_user_api.temp_user_by_email (l_email) as l_user then
-- User exist create a new token and send a new email.
if l_user.is_active then
r.set_value ("The asociated user to the given email " + l_email + " , is already active", "is_active")
r.set_status_code ({HTTP_CONSTANTS}.bad_request)
else
l_token := a_auth_api.new_token
l_user_api.new_activation (l_token, l_user.id)
l_url_activate := req.absolute_script_url ("/account/activate/" + l_token)
l_url_reject := req.absolute_script_url ("/account/reject/" + l_token)
-- Send Email to webmaster
if attached l_user.personal_information as l_personal_information then
create es.make (create {CMS_AUTHENTICATION_EMAIL_SERVICE_PARAMETERS}.make (a_auth_api.cms_api))
write_debug_log (generator + ".handle register: send_register_email")
es.send_account_evaluation (l_user, l_personal_information, l_url_activate, l_url_reject, req.absolute_script_url (""))
end
end
else
r.set_value ("The email does not exist !", "error_email")
r.set_value (l_email, "email")
r.set_status_code ({HTTP_CONSTANTS}.bad_request)
end
else
r.set_value ("The email is not valid!", "error_email")
r.set_value (p_email.value, "email")
r.set_status_code ({HTTP_CONSTANTS}.bad_request)
end
end
end
r.execute
else
a_auth_api.cms_api.response_api.send_access_denied (Void, req, res)
end
end
handle_new_password (a_auth_api: CMS_AUTHENTICATION_API; req: WSF_REQUEST; res: WSF_RESPONSE)
local
r: CMS_RESPONSE
es: CMS_AUTHENTICATION_EMAIL_SERVICE
l_user_api: CMS_USER_API
l_token: STRING
l_url: STRING
l_email: READABLE_STRING_8
do
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, a_auth_api.cms_api)
if req.is_post_request_method then
l_user_api := a_auth_api.cms_api.user_api
if attached {WSF_STRING} req.form_parameter ("email") as p_email then
if p_email.value.is_valid_as_string_8 then
l_email := p_email.value.to_string_8
if attached {CMS_USER} l_user_api.user_by_email (l_email) as l_user then
-- User exist create a new token and send a new email.
l_token := a_auth_api.new_token
l_user_api.new_password (l_token, l_user.id)
l_url := req.absolute_script_url ("/account/reset-password?token=" + l_token)
-- Send Email
create es.make (create {CMS_AUTHENTICATION_EMAIL_SERVICE_PARAMETERS}.make (a_auth_api.cms_api))
write_debug_log (generator + ".handle register: send_contact_password_email")
es.send_contact_password_email (l_email, l_user, l_url, req.absolute_script_url (""))
else
r.set_value ("The email does not exist !", "error_email")
r.set_value (p_email.value, "email")
r.set_status_code ({HTTP_CONSTANTS}.bad_request)
end
else
r.set_value ("The email is not valid!", "error_email")
r.set_value (p_email.value, "email")
r.set_status_code ({HTTP_CONSTANTS}.bad_request)
end
elseif attached {WSF_STRING} req.form_parameter ("username") as l_username then
if
attached {CMS_USER} l_user_api.user_by_name (l_username.value) as l_user and then
attached l_user.email as l_user_email
then
-- User exist create a new token and send a new email.
l_token := a_auth_api.new_token
l_user_api.new_password (l_token, l_user.id)
l_url := req.absolute_script_url ("/account/reset-password?token=" + l_token)
-- Send Email
create es.make (create {CMS_AUTHENTICATION_EMAIL_SERVICE_PARAMETERS}.make (a_auth_api.cms_api))
write_debug_log (generator + ".handle register: send_contact_password_email")
es.send_contact_password_email (l_user_email, l_user, l_url, req.absolute_script_url (""))
else
r.set_value ("The username does not exist !", "error_username")
r.set_value (l_username.value, "username")
r.set_status_code ({HTTP_CONSTANTS}.bad_request)
end
end
end
r.execute
end
handle_reset_password (a_auth_api: CMS_AUTHENTICATION_API; req: WSF_REQUEST; res: WSF_RESPONSE)
local
r: CMS_RESPONSE
l_user_api: CMS_USER_API
do
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, a_auth_api.cms_api)
l_user_api := a_auth_api.cms_api.user_api
if attached {WSF_STRING} req.query_parameter ("token") as l_token then
r.set_value (l_token.value, "token")
if l_user_api.user_by_password_token (l_token.value) = Void then
r.set_value ("The token " + l_token.value + " is not valid, " + r.link ("click here", "account/new-password", Void) + " to generate a new token.", "error_token")
r.set_status_code ({HTTP_CONSTANTS}.bad_request)
end
end
if req.is_post_request_method then
if attached {WSF_STRING} req.form_parameter ("token") as l_token and then attached {WSF_STRING} req.form_parameter ("password") as l_password and then attached {WSF_STRING} req.form_parameter ("confirm_password") as l_confirm_password then
-- Does the passwords match?
if l_password.value.same_string (l_confirm_password.value) then
-- is the token valid?
if attached {CMS_USER} l_user_api.user_by_password_token (l_token.value) as l_user then
l_user.set_password (l_password.value)
l_user_api.update_user (l_user)
l_user_api.remove_password (l_token.value)
end
else
r.set_value ("Passwords Don't Match", "error_password")
r.set_value (l_token.value, "token")
r.set_status_code ({HTTP_CONSTANTS}.bad_request)
end
end
end
r.execute
end
handle_change_field (a_auth_api: CMS_AUTHENTICATION_API; req: WSF_REQUEST; res: WSF_RESPONSE)
local
r: CMS_RESPONSE
l_user_api: CMS_USER_API
f: CMS_FORM
l_fieldname: detachable READABLE_STRING_8
b: STRING
lnk: CMS_LOCAL_LINK
do
if attached {WSF_STRING} req.path_parameter ("field") as p_field then
l_fieldname := p_field.url_encoded_value
end
if l_fieldname = Void then
a_auth_api.cms_api.response_api.send_bad_request (Void, req, res)
else
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, a_auth_api.cms_api)
if r.is_authenticated then
create lnk.make ("View", "account/")
lnk.set_weight (1)
r.add_to_primary_tabs (lnk)
create lnk.make ("Edit", "account/edit")
lnk.set_weight (2)
r.add_to_primary_tabs (lnk)
end
l_user_api := a_auth_api.cms_api.user_api
if req.is_post_request_method then
if attached r.user as l_user then
if l_fieldname.is_case_insensitive_equal ("password") then
if
attached {WSF_STRING} req.form_parameter ("password") as l_password and then
attached {WSF_STRING} req.form_parameter ("confirm_password") as l_confirm_password and then
l_password.value.same_string (l_confirm_password.value)
then
-- passwords matched?
l_user.set_password (l_password.value)
l_user_api.update_user (l_user)
r.add_success_message ("Password updated.")
r.set_redirection ("account/")
r.set_redirection_delay (3)
else
r.add_error_message ("Passwords do not match!")
f := new_change_password_form (r)
r.set_main_content (f.to_html (r.wsf_theme))
end
elseif l_fieldname.is_case_insensitive_equal ("email") then
-- FIXME: find a safer workflow .. allow multiple emails, and have a primary email?
if
attached {WSF_STRING} req.form_parameter ("email") as l_email and then
attached {WSF_STRING} req.form_parameter ("confirm_email") as l_confirm_email and then
l_email.value.same_string (l_confirm_email.value) and then
l_email.value.is_valid_as_string_8
then
-- emails matched?
l_user.set_email (l_email.value.to_string_8)
l_user_api.update_user (l_user)
r.add_success_message ("Email updated.")
r.set_redirection ("account/")
r.set_redirection_delay (3)
else
r.add_error_message ("Emails do not match!")
f := new_change_email_form (r)
r.set_main_content (f.to_html (r.wsf_theme))
end
elseif l_fieldname.is_case_insensitive_equal ("profile_name") then
f := new_change_profile_name_form (r)
f.process (r)
if
attached f.last_data as fd and then
not fd.has_error and then
attached fd.string_item ("new_profile_name") as l_new_profile_name
then
check a_auth_api.cms_api.user_api.is_valid_profile_name (l_new_profile_name) end
l_user.set_profile_name (l_new_profile_name)
l_user_api.update_user (l_user)
r.add_success_message ("Profile name updated.")
r.set_redirection ("account/")
r.set_redirection_delay (3)
else
r.add_error_message ("Invalid form data!")
r.set_main_content (f.to_html (r.wsf_theme))
end
elseif l_fieldname.is_case_insensitive_equal ("username") then
if a_auth_api.cms_api.has_permission ("change own username") then
f := new_change_username_form (r)
f.process (r)
if
attached f.last_data as fd and then
not fd.has_error and then
attached fd.string_item ("new_username") as l_new_username
then
check a_auth_api.cms_api.user_api.is_valid_username (l_new_username) end
check a_auth_api.cms_api.user_api.user_by_name (l_new_username) = Void end
l_user_api.update_username (l_user, l_new_username)
r.add_success_message ("Username updated.")
r.set_redirection ("account/")
r.set_redirection_delay (3)
else
r.add_error_message ("Invalid form data!")
r.set_main_content (f.to_html (r.wsf_theme))
end
else
r.add_error_message ("You are not allowed to change your username!")
end
else
r.add_error_message ("You can not change %"" + l_fieldname + "%" information!")
end
end
else
create b.make_empty
if l_fieldname.is_case_insensitive_equal_general ("password") then
f := new_change_password_form (r)
f.append_to_html (r.wsf_theme, b)
elseif l_fieldname.is_case_insensitive_equal_general ("email") then
f := new_change_email_form (r)
f.append_to_html (r.wsf_theme, b)
elseif l_fieldname.is_case_insensitive_equal_general ("new_username") then
if a_auth_api.cms_api.has_permission ("change own username") then
f := new_change_username_form (r)
f.append_to_html (r.wsf_theme, b)
end
end
r.set_main_content (b)
end
r.execute
end
end
block_list: ITERABLE [like {CMS_BLOCK}.name]
do
Result := <<"register", "reactivate", "new_password", "reset_password">>
end
get_block_view (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
local
loc: READABLE_STRING_8
do
loc := a_response.location
if a_block_id.is_case_insensitive_equal_general ("register") and then loc.starts_with ("account/roc-register") then
get_block_view_register (a_block_id, a_response)
elseif a_block_id.is_case_insensitive_equal_general ("reactivate") and then loc.starts_with ("account/reactivate") then
get_block_view_reactivate (a_block_id, a_response)
elseif a_block_id.is_case_insensitive_equal_general ("new_password") and then loc.starts_with ("account/new-password") then
get_block_view_new_password (a_block_id, a_response)
elseif a_block_id.is_case_insensitive_equal_general ("reset_password") and then loc.starts_with ("account/reset-password") then
get_block_view_reset_password (a_block_id, a_response)
end
end
new_change_username_form (a_response: CMS_RESPONSE): CMS_FORM
local
fs: WSF_FORM_FIELD_SET
txt: WSF_FORM_TEXT_INPUT
do
create Result.make (a_response.url ("account/change/username", Void), "change-username-form")
create fs.make
fs.set_legend ("Change username")
Result.extend (fs)
create txt.make ("new_username")
txt.set_label ("Username")
txt.set_validation_action (agent (fd: WSF_FORM_DATA; api: CMS_API)
do
if
attached fd.string_item ("new_username") as l_new and then
api.user_api.is_valid_username (l_new)
then
if api.user_api.user_by_name (l_new) /= Void then
fd.report_invalid_field ("new_username", "Username is already taken!")
end
else
fd.report_invalid_field ("new_username", "Invalid username!")
end
end (?, a_response.api)
)
txt.enable_required
fs.extend (txt)
fs.extend_html_text ("<button type=%"submit%">Confirm</button>")
end
new_change_profile_name_form (a_response: CMS_RESPONSE): CMS_FORM
local
fs: WSF_FORM_FIELD_SET
txt: WSF_FORM_TEXT_INPUT
do
create Result.make (a_response.url ("account/change/profile_name", Void), "change-profile-name-form")
create fs.make
fs.set_legend ("Change profile name (i.e the name displayed on pages)")
Result.extend (fs)
create txt.make ("new_profile_name")
txt.set_label ("Profile name")
txt.set_validation_action (agent (fd: WSF_FORM_DATA; api: CMS_API)
do
if
attached fd.string_item ("new_profile_name") as l_new and then
api.user_api.is_valid_profile_name (l_new)
then
-- Ok
else
fd.report_invalid_field ("new_profile_name", "Invalid profile name!")
end
end (?, a_response.api)
)
txt.enable_required
fs.extend (txt)
fs.extend_html_text ("<button type=%"submit%">Confirm</button>")
end
new_change_password_form (a_response: CMS_RESPONSE): CMS_FORM
local
fs: WSF_FORM_FIELD_SET
pwd: WSF_FORM_PASSWORD_INPUT
do
create Result.make (a_response.url ("account/change/password", Void), "change-password-form")
create fs.make
fs.set_legend ("Change password")
Result.extend (fs)
create pwd.make ("password")
pwd.set_label ("Password")
pwd.enable_required
fs.extend (pwd)
create pwd.make ("confirm_password")
pwd.set_label ("Confirm password")
pwd.enable_required
fs.extend (pwd)
-- create but.make_with_text ("op", "Confirm")
-- fs.extend (but)
fs.extend_html_text ("<button type=%"submit%">Confirm</button>")
end
new_change_email_form (a_response: CMS_RESPONSE): CMS_FORM
local
fs: WSF_FORM_FIELD_SET
tf: WSF_FORM_EMAIL_INPUT
do
create Result.make (a_response.url ("account/change/email", Void), "change-email-form")
create fs.make
fs.set_legend ("Change email")
Result.extend (fs)
create tf.make ("email")
tf.set_label ("Email")
tf.enable_required
fs.extend (tf)
create tf.make ("confirm_email")
tf.set_label ("Confirm email")
tf.enable_required
fs.extend (tf)
fs.extend_html_text ("<button type=%"submit%">Confirm</button>")
end
feature {NONE} -- Block views
get_block_view_register (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
do
if a_response.has_permission ("account register") then
if
a_response.request.is_get_request_method
or else (
a_response.values.has ("error_name")
or else a_response.values.has ("error_email")
)
then
if attached smarty_template_block (Current, a_block_id, a_response.api) as l_tpl_block then
-- l_tpl_block.set_value (a_response.values.item ("error_name"), "error_name")
-- l_tpl_block.set_value (a_response.values.item ("error_email"), "error_email")
-- l_tpl_block.set_value (a_response.values.item ("email"), "email")
-- l_tpl_block.set_value (a_response.values.item ("name"), "name")
l_tpl_block.set_value (form_registration_application_description (a_response.api), "application_description")
if attached recaptcha_site_key (a_response.api) as l_recaptcha_site_key then
l_tpl_block.set_value (l_recaptcha_site_key, "recaptcha_site_key")
end
a_response.add_block (l_tpl_block, "content")
else
debug ("cms")
a_response.add_warning_message ("Error with block [" + a_block_id + "]")
end
end
elseif a_response.request.is_post_request_method then
if attached smarty_template_block (Current, "post_register", a_response.api) as l_tpl_block then
a_response.add_block (l_tpl_block, "content")
else
debug ("cms")
a_response.add_warning_message ("Error with block [" + a_block_id + "]")
end
end
end
end
end
get_block_view_reactivate (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
do
if a_response.request.is_get_request_method then
if attached smarty_template_block (Current, a_block_id, a_response.api) as l_tpl_block then
a_response.add_block (l_tpl_block, "content")
else
debug ("cms")
a_response.add_warning_message ("Error with block [" + a_block_id + "]")
end
end
elseif a_response.request.is_post_request_method then
if a_response.values.has ("error_email") or else a_response.values.has ("is_active") then
if attached smarty_template_block (Current, a_block_id, a_response.api) as l_tpl_block then
-- l_tpl_block.set_value (a_response.values.item ("error_email"), "error_email")
-- l_tpl_block.set_value (a_response.values.item ("email"), "email")
-- l_tpl_block.set_value (a_response.values.item ("is_active"), "is_active")
a_response.add_block (l_tpl_block, "content")
else
debug ("cms")
a_response.add_warning_message ("Error with block [" + a_block_id + "]")
end
end
else
if attached smarty_template_block (Current, "post_reactivate", a_response.api) as l_tpl_block then
a_response.add_block (l_tpl_block, "content")
else
debug ("cms")
a_response.add_warning_message ("Error with block [" + a_block_id + "]")
end
end
end
end
end
get_block_view_new_password (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
do
if a_response.request.is_get_request_method then
if attached smarty_template_block (Current, a_block_id, a_response.api) as l_tpl_block then
a_response.add_block (l_tpl_block, "content")
else
debug ("cms")
a_response.add_warning_message ("Error with block [" + a_block_id + "]")
end
end
elseif a_response.request.is_post_request_method then
if a_response.values.has ("error_email") or else a_response.values.has ("error_username") then
if attached smarty_template_block (Current, a_block_id, a_response.api) as l_tpl_block then
-- l_tpl_block.set_value (a_response.values.item ("error_email"), "error_email")
-- l_tpl_block.set_value (a_response.values.item ("email"), "email")
-- l_tpl_block.set_value (a_response.values.item ("error_username"), "error_username")
-- l_tpl_block.set_value (a_response.values.item ("username"), "username")
a_response.add_block (l_tpl_block, "content")
else
debug ("cms")
a_response.add_warning_message ("Error with block [" + a_block_id + "]")
end
end
else
if attached smarty_template_block (Current, "post_password", a_response.api) as l_tpl_block then
a_response.add_block (l_tpl_block, "content")
else
debug ("cms")
a_response.add_warning_message ("Error with block [" + a_block_id + "]")
end
end
end
end
end
get_block_view_reset_password (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
do
if a_response.request.is_get_request_method then
if attached smarty_template_block (Current, a_block_id, a_response.api) as l_tpl_block then
-- l_tpl_block.set_value (a_response.values.item ("token"), "token")
-- l_tpl_block.set_value (a_response.values.item ("error_token"), "error_token")
a_response.add_block (l_tpl_block, "content")
else
debug ("cms")
a_response.add_warning_message ("Error with block [" + a_block_id + "]")
end
end
elseif a_response.request.is_post_request_method then
if a_response.values.has ("error_token") or else a_response.values.has ("error_password") then
if attached smarty_template_block (Current, a_block_id, a_response.api) as l_tpl_block then
-- l_tpl_block.set_value (a_response.values.item ("error_token"), "error_token")
-- l_tpl_block.set_value (a_response.values.item ("error_password"), "error_password")
-- l_tpl_block.set_value (a_response.values.item ("token"), "token")
a_response.add_block (l_tpl_block, "content")
else
debug ("cms")
a_response.add_warning_message ("Error with block [" + a_block_id + "]")
end
end
else
if attached smarty_template_block (Current, "post_reset", a_response.api) as l_tpl_block then
a_response.add_block (l_tpl_block, "content")
else
debug ("cms")
a_response.add_warning_message ("Error with block [" + a_block_id + "]")
end
end
end
end
end
feature -- Access: configuration
form_registration_application_description (api: CMS_API): detachable READABLE_STRING_8
-- Get recaptcha security key.
do
if attached api.module_configuration (Current, Void) as cfg then
if attached cfg.text_item ("forms.registration.application_description") as l_desc and then not l_desc.is_whitespace then
Result := api.utf_8_encoded (l_desc)
end
end
end
recaptcha_secret_key (api: CMS_API): detachable READABLE_STRING_8
-- Get recaptcha security key.
do
if attached api.module_configuration (Current, Void) as cfg then
if attached cfg.text_item ("recaptcha.secret_key") as l_recaptcha_key and then not l_recaptcha_key.is_empty then
Result := api.utf_8_encoded (l_recaptcha_key)
end
end
end
recaptcha_site_key (api: CMS_API): detachable READABLE_STRING_8
-- Get recaptcha security key.
do
if attached api.module_configuration (Current, Void) as cfg then
if attached cfg.text_item ("recaptcha.site_key") as l_recaptcha_key and then not l_recaptcha_key.is_empty then
Result := api.utf_8_encoded (l_recaptcha_key)
end
end
end
feature -- Response Alter
response_alter (a_response: CMS_RESPONSE)
do
a_response.add_javascript_url ("https://www.google.com/recaptcha/api.js")
a_response.add_style (a_response.module_resource_url (Current, "/files/css/auth.css", Void), Void)
end
feature {NONE} -- Implementation
is_captcha_verified (a_secret, a_response: READABLE_STRING_8): BOOLEAN
local
api: RECAPTCHA_API
l_errors: STRING
do
write_debug_log (generator + ".is_captcha_verified with response: [" + a_response + "]")
create api.make (a_secret, a_response)
Result := api.verify
if not Result and then attached api.errors as l_api_errors then
create l_errors.make_empty
l_errors.append_character ('%N')
across
l_api_errors as ic
loop
l_errors.append (ic.item)
l_errors.append_character ('%N')
end
write_error_log (generator + ".is_captcha_verified api_errors [" + l_errors + "]")
end
end
note
copyright: "Copyright (c) 1984-2013, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end