Updated CMS with Login Module.

-- The module handle basic_auth (at the moment).
     -- Handle login, logout, register user, activate/reactivate an account, password recovery.
     -- Send notification emails.

CMS Updates
     -- Added a new service: email.
     -- Updated Basic Auth Module to handle logout based on the browser type.
     -- Updated persistence layer to save and remove and query activation token and password token.
     -- Updated CMS_USER to handle status {active, not_active, trashed}.
     -- Updated MySQL scripts to be in sync with SQLite scripts
This commit is contained in:
jvelilla
2015-06-05 18:39:27 -03:00
parent b8cfff487a
commit 032cc5bdcb
37 changed files with 1660 additions and 277 deletions

View File

@@ -36,6 +36,7 @@ feature -- Initialization
create u.make ("admin")
u.set_password ("istrator#")
u.set_email (a_setup.site_email)
u.set_status ({CMS_USER}.active)
a_storage.new_user (u)
--| Node
@@ -74,16 +75,19 @@ feature -- Initialization
create u.make ("auth")
u.set_password ("enticated#")
u.set_email (a_setup.site_email)
u.set_status ({CMS_USER}.active)
a_storage.new_user (u)
create u.make ("test")
u.set_password ("test#")
u.set_email (a_setup.site_email)
u.set_status ({CMS_USER}.active)
a_storage.new_user (u)
create u.make ("view")
u.set_password ("only#")
u.set_email (a_setup.site_email)
u.set_status ({CMS_USER}.active)
u.set_roles (l_roles)
a_storage.new_user (u)
end

View File

@@ -56,6 +56,20 @@ feature -- Access
password: Result /= Void implies (Result.hashed_password /= Void and Result.password = Void)
end
user_by_activation_token (a_token: READABLE_STRING_32): detachable CMS_USER
-- User with activation token `a_token', if any.
deferred
ensure
password: Result /= Void implies (Result.hashed_password /= Void and Result.password = Void)
end
user_by_password_token (a_token: READABLE_STRING_32): detachable CMS_USER
-- User with password token `a_token', if any.
deferred
ensure
password: Result /= Void implies (Result.hashed_password /= Void and Result.password = Void)
end
is_valid_credential (a_u, a_p: READABLE_STRING_32): BOOLEAN
-- Does account with username `a_username' and password `a_password' exist?
deferred
@@ -141,4 +155,27 @@ feature -- Change: roles and permissions
deferred
end
feature -- Change: User activation
save_activation (a_token: READABLE_STRING_32; a_id: INTEGER_64)
-- <Precursor>.
deferred
end
remove_activation (a_token: READABLE_STRING_32)
-- <Precursor>.
deferred
end
feature -- Change: User password recovery
save_password (a_token: READABLE_STRING_32; a_id: INTEGER_64)
-- <Precursor>.
deferred
end
remove_password (a_token: READABLE_STRING_32)
-- <Precursor>.
deferred
end
end

View File

@@ -34,6 +34,14 @@ feature -- Access: user
do
end
user_by_activation_token (a_token: READABLE_STRING_32): detachable CMS_USER
do
end
user_by_password_token (a_token: READABLE_STRING_32): detachable CMS_USER
do
end
is_valid_credential (l_auth_login, l_auth_password: READABLE_STRING_32): BOOLEAN
do
end
@@ -76,4 +84,27 @@ feature -- Change: roles and permissions
do
end
feature -- Change: User activation
save_activation (a_token: READABLE_STRING_32; a_id: INTEGER_64)
-- <Precursor>.
do
end
remove_activation (a_token: READABLE_STRING_32)
-- <Precursor>.
do
end
feature -- Change: User password recovery
save_password (a_token: READABLE_STRING_32; a_id: INTEGER_64)
-- <Precursor>.
do
end
remove_password (a_token: READABLE_STRING_32)
-- <Precursor>.
do
end
end

View File

@@ -62,7 +62,7 @@ feature -- Access: user
l_parameters: STRING_TABLE [detachable ANY]
do
error_handler.reset
write_information_log (generator + ".user")
write_information_log (generator + ".user_by_id")
create l_parameters.make (1)
l_parameters.put (a_id, "uid")
sql_query (select_user_by_id, l_parameters)
@@ -107,6 +107,40 @@ feature -- Access: user
end
end
user_by_activation_token (a_token: READABLE_STRING_32): detachable CMS_USER
-- User for the given activation token `a_token', if any.
local
l_parameters: STRING_TABLE [detachable ANY]
do
error_handler.reset
write_information_log (generator + ".user_by_activation_token")
create l_parameters.make (1)
l_parameters.put (a_token, "token")
sql_query (select_user_by_activation_token, l_parameters)
if sql_rows_count = 1 then
Result := fetch_user
else
check no_more_than_one: sql_rows_count = 0 end
end
end
user_by_password_token (a_token: READABLE_STRING_32): detachable CMS_USER
-- User for the given password token `a_token', if any.
local
l_parameters: STRING_TABLE [detachable ANY]
do
error_handler.reset
write_information_log (generator + ".user_by_password_token")
create l_parameters.make (1)
l_parameters.put (a_token, "token")
sql_query (select_user_by_password_token, l_parameters)
if sql_rows_count = 1 then
Result := fetch_user
else
check no_more_than_one: sql_rows_count = 0 end
end
end
is_valid_credential (l_auth_login, l_auth_password: READABLE_STRING_32): BOOLEAN
local
l_security: SECURITY_PROVIDER
@@ -155,6 +189,7 @@ feature -- Change: user
l_parameters.put (l_password_salt, "salt")
l_parameters.put (l_email, "email")
l_parameters.put (create {DATE_TIME}.make_now_utc, "created")
l_parameters.put (a_user.status, "status")
sql_change (sql_insert_user, l_parameters)
if not error_handler.has_error then
@@ -197,6 +232,7 @@ feature -- Change: user
l_parameters.put (l_password_salt, "salt")
l_parameters.put (l_email, "email")
l_parameters.put (create {DATE_TIME}.make_now_utc, "changed")
l_parameters.put (a_user.status, "status")
sql_change (sql_update_user, l_parameters)
else
@@ -441,6 +477,108 @@ feature -- Change: roles and permissions
end
end
feature -- Access: User activation
activation_elapsed_time (a_token: READABLE_STRING_32): INTEGER_32
-- amount of time that has passed in days since the token `a_token' was saved.
local
l_parameters: STRING_TABLE [detachable ANY]
do
error_handler.reset
write_information_log (generator + ".activation_elapsed_time")
create l_parameters.make (1)
l_parameters.put (a_token, "token")
sql_query (sql_select_activation_expiration, l_parameters)
if sql_rows_count = 1 then
Result := sql_read_integer_32 (1)
end
end
user_id_by_activation (a_token: READABLE_STRING_32): INTEGER_64
-- User id associatied with a token `a_token', if any.
local
l_parameters: STRING_TABLE [detachable ANY]
do
error_handler.reset
write_information_log (generator + ".user_id_by_actication")
create l_parameters.make (1)
l_parameters.put (a_token, "token")
sql_query (sql_select_userid_activation, l_parameters)
if sql_rows_count = 1 then
Result := sql_read_integer_32 (1)
end
end
feature -- Change: User activation
save_activation (a_token: READABLE_STRING_32; a_id: INTEGER_64)
-- <Precursor>
local
l_parameters: STRING_TABLE [detachable ANY]
l_utc_date: DATE_TIME
do
error_handler.reset
sql_begin_transaction
write_information_log (generator + ".save_activation")
create l_utc_date.make_now_utc
create l_parameters.make (2)
l_parameters.put (a_token, "token")
l_parameters.put (a_id, "uid")
l_parameters.put (l_utc_date, "utc_date")
sql_change (sql_insert_activation, l_parameters)
sql_commit_transaction
end
remove_activation (a_token: READABLE_STRING_32)
-- <Precursor>.
local
l_parameters: STRING_TABLE [detachable ANY]
do
error_handler.reset
sql_begin_transaction
write_information_log (generator + ".remove_activation")
create l_parameters.make (1)
l_parameters.put (a_token, "token")
sql_change (sql_remove_activation, l_parameters)
sql_commit_transaction
end
feature -- Change: User password recovery
save_password (a_token: READABLE_STRING_32; a_id: INTEGER_64)
-- <Precursor>
local
l_parameters: STRING_TABLE [detachable ANY]
l_utc_date: DATE_TIME
do
error_handler.reset
sql_begin_transaction
write_information_log (generator + ".save_password")
create l_utc_date.make_now_utc
create l_parameters.make (2)
l_parameters.put (a_token, "token")
l_parameters.put (a_id, "uid")
l_parameters.put (l_utc_date, "utc_date")
sql_change (sql_insert_password, l_parameters)
sql_commit_transaction
end
remove_password (a_token: READABLE_STRING_32)
-- <Precursor>.
local
l_parameters: STRING_TABLE [detachable ANY]
do
error_handler.reset
sql_begin_transaction
write_information_log (generator + ".remove_password")
create l_parameters.make (1)
l_parameters.put (a_token, "token")
sql_change (sql_remove_password, l_parameters)
sql_commit_transaction
end
feature {NONE} -- Implementation: User
user_salt (a_username: READABLE_STRING_32): detachable READABLE_STRING_8
@@ -489,6 +627,9 @@ feature {NONE} -- Implementation: User
if attached sql_read_string (5) as l_email then
Result.set_email (l_email)
end
if attached sql_read_integer_32 (6) as l_status then
Result.set_status (l_status)
end
else
check expected_valid_user: False end
end
@@ -551,10 +692,11 @@ feature {NONE} -- Sql Queries: USER
Select_salt_by_username: STRING = "SELECT salt FROM Users WHERE name =:name;"
-- Retrieve salt by username if exists.
sql_insert_user: STRING = "INSERT INTO users (name, password, salt, email, created) VALUES (:name, :password, :salt, :email, :created);"
-- SQL Insert to add a new node.
sql_insert_user: STRING = "INSERT INTO users (name, password, salt, email, created, status) VALUES (:name, :password, :salt, :email, :created, :status);"
-- SQL Insert to add a new user.
sql_update_user: STRING = "UPDATE users SET name=:name, password=:password, salt=:salt, email=:email WHERE uid=:uid;"
sql_update_user: STRING = "UPDATE users SET name=:name, password=:password, salt=:salt, email=:email, status=:status WHERE uid=:uid;"
-- SQL update to update an existing user.
feature {NONE} -- Sql Queries: USER ROLE
@@ -584,4 +726,34 @@ feature {NONE} -- Sql Queries: USER ROLE
select_role_permissions_by_role_id: STRING = "SELECT permission, module FROM role_permissions WHERE rid=:rid;"
-- User role permissions for role id :rid;
feature {NONE} -- Sql Queries: USER ACTIVATION
sql_insert_activation: STRING = "INSERT INTO users_activations (token, uid, created) VALUES (:token, :uid, :utc_date);"
-- SQL insert a new activation :token.
sql_select_activation_expiration: STRING = "SELECT DATEDIFF(day,created,UTC_DATE()) FROM users_activations where token = :token;"
-- elapsed time that has passed in days since the token `a_token' was saved.
sql_select_userid_activation: STRING = "SELECT uid FROM users_activations where token = :token;"
-- Retrieve userid given the activation token.
Select_user_by_activation_token: STRING = "SELECT u.* FROM users as u JOIN users_activations as ua ON ua.uid = u.uid and ua.token = :token;"
-- Retrieve user by activation token if exist.
Sql_remove_activation: STRING = "DELETE FROM users_activations WHERE token = :token;"
-- Remove activation token.
feature {NONE}
sql_insert_password: STRING = "INSERT INTO users_password_recovery (token, uid, created) VALUES (:token, :uid, :utc_date);"
-- SQL insert a new password recovery :token.
Sql_remove_password: STRING = "DELETE FROM users_password_recovery WHERE token = :token;"
-- Retrieve password if exist.
Select_user_by_password_token: STRING = "SELECT u.* FROM users as u JOIN users_password_recovery as ua ON ua.uid = u.uid and ua.token = :token;"
-- Retrieve user by password token if exist.
end

View File

@@ -0,0 +1,102 @@
note
description: "Basic Email Service"
date: "$Date: 2015-04-30 05:45:25 -0300 (ju. 30 de abr. de 2015) $"
revision: "$Revision: 97218 $"
class
EMAIL_SERVICE
inherit
SHARED_ERROR
SHARED_LOGGER
create
make
feature {NONE} -- Initialization
make (a_params: like parameters)
-- Create instance of {EMAIL_SERVICE} with smtp_server `a_params.smtp_server'.
-- Using `a_params.admin_email' as admin email.
do
parameters := a_params
initialize
end
initialize
-- Initialize service.
local
l_address_factory: INET_ADDRESS_FACTORY
do
admin_email := parameters.admin_email
-- Get local host name needed in creation of SMTP_PROTOCOL.
create l_address_factory
create smtp_protocol.make (parameters.smtp_server, l_address_factory.create_localhost.host_name)
set_successful
end
parameters: EMAIL_SERVICE_PARAMETERS
-- Associated parameters.
admin_email: IMMUTABLE_STRING_8
-- Site admin's email.
smtp_protocol: SMTP_PROTOCOL
-- SMTP protocol.
feature -- Basic Operations
send_internal_email (a_content: READABLE_STRING_GENERAL)
do
send_message (admin_email, admin_email, "Notification Contact", a_content)
end
send_email_internal_server_error (a_content: READABLE_STRING_GENERAL)
do
send_message (admin_email, admin_email, "Internal Server Error", a_content)
end
send_message (a_from_address, a_to_address: READABLE_STRING_8; a_subjet: READABLE_STRING_GENERAL; a_content: READABLE_STRING_GENERAL)
local
l_email: EMAIL
utf: UTF_CONVERTER
do
write_debug_log (generator + ".send_message: [from:" + a_from_address + ", to:" + a_to_address + ", subject:" + a_subjet + ", content:" + a_content)
create l_email.make_with_entry (a_from_address, a_to_address)
l_email.set_message (utf.escaped_utf_32_string_to_utf_8_string_8 (a_content))
l_email.add_header_entry ({EMAIL_CONSTANTS}.H_subject, utf.escaped_utf_32_string_to_utf_8_string_8 (a_subjet))
l_email.add_header_entry ("MIME-Version:", "1.0")
l_email.add_header_entry ("Content-Type", "text/html; charset=utf-8")
send_email (l_email)
end
feature {NONE} -- Implementation
send_email (a_email: EMAIL)
-- Send the email represented by `a_email'.
local
l_retried: BOOLEAN
do
if not l_retried then
write_information_log (generator + ".send_email Process send email.")
smtp_protocol.initiate_protocol
smtp_protocol.transfer (a_email)
smtp_protocol.close_protocol
write_information_log (generator + ".send_email Email sent.")
if smtp_protocol.error then
set_last_error ("smtp_protocol reported an error", generator + ".send_email")
else
set_successful
end
else
write_error_log (generator + ".send_email Email not send " + last_error_message )
end
rescue
set_last_error_from_exception (generator + ".send_email")
l_retried := True
retry
end
end

View File

@@ -0,0 +1,20 @@
note
description: "Basic Email Service customized for cms site"
author: ""
date: "$Date: 2015-01-16 07:17:14 -0300 (vi. 16 de ene. de 2015) $"
revision: "$Revision: 96467 $"
deferred class
EMAIL_SERVICE_PARAMETERS
feature -- Access
smtp_server: IMMUTABLE_STRING_8
deferred
end
admin_email: IMMUTABLE_STRING_8
deferred
end
end

View File

@@ -29,6 +29,24 @@ feature -- Access
Result := storage.user_by_name (a_username)
end
user_by_email (a_email: READABLE_STRING_32): detachable CMS_USER
-- User by email `a_email', if any.
do
Result := storage.user_by_email (a_email)
end
user_by_activation_token (a_token: READABLE_STRING_32): detachable CMS_USER
-- User by activation token `a_token'.
do
Result := storage.user_by_activation_token (a_token)
end
user_by_password_token (a_token: READABLE_STRING_32): detachable CMS_USER
-- User by password token `a_token'.
do
Result := storage.user_by_password_token (a_token)
end
feature -- Status report
is_valid_credential (a_auth_login, a_auth_password: READABLE_STRING_32): BOOLEAN
@@ -133,4 +151,43 @@ feature -- Change User
storage.update_user (a_user)
end
feature -- User Activation
new_activation (a_token: READABLE_STRING_32; a_id: INTEGER_64)
-- Save activation token `a_token', for the user with the id `a_id'.
do
storage.save_activation (a_token, a_id)
end
remove_activation (a_token: READABLE_STRING_32)
-- Remove activation token `a_token', from the storage.
do
storage.remove_activation (a_token)
end
feature -- User Password Recovery
new_password (a_token: READABLE_STRING_32; a_id: INTEGER_64)
-- Save password token `a_token', for the user with the id `a_id'.
do
storage.save_password (a_token, a_id)
end
remove_password (a_token: READABLE_STRING_32)
-- Remove password token `a_token', from the storage.
do
storage.remove_password (a_token)
end
feature -- User status
not_active: INTEGER = 0
-- The user is not active.
active: INTEGER = 1
-- The user is active
Trashed: INTEGER = -1
-- The user is trashed (soft delete), ready to be deleted/destroyed from storage.
end