diff --git a/cms-safe.ecf b/cms-safe.ecf
index d1c2b3c..1fa90e4 100644
--- a/cms-safe.ecf
+++ b/cms-safe.ecf
@@ -28,6 +28,7 @@
+
diff --git a/cms.ecf b/cms.ecf
index a41c820..3788249 100644
--- a/cms.ecf
+++ b/cms.ecf
@@ -29,6 +29,7 @@
+
diff --git a/examples/demo/demo-safe.ecf b/examples/demo/demo-safe.ecf
index f7a4714..2414c36 100644
--- a/examples/demo/demo-safe.ecf
+++ b/examples/demo/demo-safe.ecf
@@ -18,6 +18,7 @@
+
diff --git a/examples/demo/site/config/modules/login/account_activation.html b/examples/demo/site/config/modules/login/account_activation.html
new file mode 100644
index 0000000..0ab4c4f
--- /dev/null
+++ b/examples/demo/site/config/modules/login/account_activation.html
@@ -0,0 +1,18 @@
+
+
+
+
+ Activation
+
+
+
+
+
+ Thank you for registering at ROC CMS
+
+ To complete your registration, please click on this link to activate your account:
+
+
$link
+ Thank you for joining us.
+
+
diff --git a/examples/demo/site/config/modules/login/account_new_password.html b/examples/demo/site/config/modules/login/account_new_password.html
new file mode 100644
index 0000000..dfcd355
--- /dev/null
+++ b/examples/demo/site/config/modules/login/account_new_password.html
@@ -0,0 +1,17 @@
+
+
+
+
+ New Password
+
+
+
+
+
+ You have required a new password at ROC CMS
+
+ To complete your request, please click on this link to genereate a new password:
+
+
$link
+
+
\ No newline at end of file
diff --git a/examples/demo/site/config/modules/login/account_re_activation.html b/examples/demo/site/config/modules/login/account_re_activation.html
new file mode 100644
index 0000000..95aebb2
--- /dev/null
+++ b/examples/demo/site/config/modules/login/account_re_activation.html
@@ -0,0 +1,18 @@
+
+
+
+
+ New Activation
+
+
+
+
+
+ You have request a new activation token atROC CMS
+
+ To complete your registration, please click on this link to activate your account:
+
+
$link
+ Thank you for joining us.
+
+
\ No newline at end of file
diff --git a/examples/demo/site/config/modules/login/account_welcome.html b/examples/demo/site/config/modules/login/account_welcome.html
new file mode 100644
index 0000000..0ea987c
--- /dev/null
+++ b/examples/demo/site/config/modules/login/account_welcome.html
@@ -0,0 +1,13 @@
+
+
+
+
+ Welcome
+
+
+
+
+ Welcome toROC CMS
+ Thank you for joining us.
+
+
\ No newline at end of file
diff --git a/examples/demo/site/config/modules/login/login.json b/examples/demo/site/config/modules/login/login.json
new file mode 100644
index 0000000..2f3e155
--- /dev/null
+++ b/examples/demo/site/config/modules/login/login.json
@@ -0,0 +1,8 @@
+{
+ "email": "webmaster@example.com",
+ "subjet_register": "Thank you for regitering with us, activate account",
+ "subjet_activate": "New account ativation token",
+ "subjet_password": "Password Recovery!!!",
+ "subjet_oauth": "Welcome",
+ "smtp": "127.0.0.1"
+}
diff --git a/examples/demo/site/config/modules/login/oauth2_gmail.json b/examples/demo/site/config/modules/login/oauth2_gmail.json
new file mode 100644
index 0000000..8cd35ef
--- /dev/null
+++ b/examples/demo/site/config/modules/login/oauth2_gmail.json
@@ -0,0 +1,7 @@
+{
+ "api_secret":"ADD_YOUR_SECRET_KEY",
+ "api_key":"ADD_YOUR_PUBLIC_KEY",
+ "scope": "email",
+ "api_revoke":"https://accounts.google.com/o/oauth2/revoke?token=$ACCESS_TOKEN",
+ "protected_resource_url":"https://www.googleapis.com/plus/v1/people/me"
+}
diff --git a/examples/demo/site/scripts/oauth2_consumers.sql b/examples/demo/site/scripts/oauth2_consumers.sql
new file mode 100644
index 0000000..ae0d1da
--- /dev/null
+++ b/examples/demo/site/scripts/oauth2_consumers.sql
@@ -0,0 +1,18 @@
+
+CREATE TABLE `oauth2_consumers`(
+ `cid` INTEGER PRIMARY KEY NOT NULL CHECK(`cid`>=0),
+ `name` VARCHAR(255) NOT NULL,
+ `api_secret` TEXT NOT NULL,
+ `api_key` TEXT NOT NULL,
+ `scope` VARCHAR (100) NOT NULL,
+ `protected_resource_url` VARCHAR (255) NOT NULL,
+ `callback_name` VARCHAR(255) NOT NULL,
+ `extractor` VARCHAR(50) NOT NULL,
+ `authorize_url` VARCHAR (255) NOT NULL,
+ `endpoint` VARCHAR (255) NOT NULL,
+ CONSTRAINT `cid`
+ UNIQUE(`cid`),
+ CONSTRAINT `name`
+ UNIQUE(`name`)
+ );
+
diff --git a/examples/demo/site/scripts/oauth2_consumers_initialize.sql b/examples/demo/site/scripts/oauth2_consumers_initialize.sql
new file mode 100644
index 0000000..69cd233
--- /dev/null
+++ b/examples/demo/site/scripts/oauth2_consumers_initialize.sql
@@ -0,0 +1,7 @@
+ -- Change the values `TO_COMPLETE` based on your API.
+ -- API SECTET KEY AND API PUBLIC KEY
+
+INSERT INTO `oauth2_consumers` ("name", "api_secret", "api_key", "scope", "protected_resource_url", "callback_name", "extractor", "authorize_url", "endpoint")
+VALUES ("google", 'TO-COMPLETE', 'TO-COMPLETE', 'email', 'https://www.googleapis.com/plus/v1/people/me', "callback_google", "json","https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI","https://accounts.google.com/o/oauth2/token");
+INSERT INTO "oauth2_consumers" ("name", "api_secret", "api_key", "scope", "protected_resource_url", "callback_name", "extractor", "authorize_url", "endpoint" )
+VALUES ("facebook", 'TO-COMPLETE', 'TO-COMPLETE', 'email', 'https://graph.facebook.com/me', "callback_facebook","text","https://www.facebook.com/dialog/oauth?response_type=code&client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI","https://graph.facebook.com/oauth/access_token");
diff --git a/examples/demo/site/scripts/oauth2_template.sql b/examples/demo/site/scripts/oauth2_template.sql
new file mode 100644
index 0000000..f53ee67
--- /dev/null
+++ b/examples/demo/site/scripts/oauth2_template.sql
@@ -0,0 +1,10 @@
+
+CREATE TABLE :table_name (
+ `uid` INTEGER PRIMARY KEY NOT NULL CHECK(`uid`>=0),
+ `access_token` TEXT NOT NULL,
+ `created` DATETIME NOT NULL,
+ `details` TEXT NOT NULL,
+ CONSTRAINT `uid`
+ UNIQUE(`uid`)
+ );
+
diff --git a/examples/demo/site/scripts/user.sql b/examples/demo/site/scripts/user.sql
index 64c2f9e..6e286af 100644
--- a/examples/demo/site/scripts/user.sql
+++ b/examples/demo/site/scripts/user.sql
@@ -30,3 +30,19 @@ CREATE TABLE `role_permissions`(
`module` VARCHAR(255)
);
+CREATE TABLE "users_activations" (
+ "aid" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL CHECK ("aid" >= 0),
+ "token" VARCHAR(255) NOT NULL,
+ "uid" INTEGER NOT NULL CHECK ("uid" >= 0),
+ "created" DATETIME NOT NULL,
+ CONSTRAINT "token" UNIQUE ("token")
+);
+
+CREATE TABLE "users_password_recovery" (
+ "aid" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL CHECK ("aid" >= 0),
+ "token" VARCHAR(255) NOT NULL,
+ "uid" INTEGER NOT NULL CHECK ("uid" >= 0),
+ "created" DATETIME NOT NULL,
+ CONSTRAINT "token" UNIQUE ("token")
+);
+
diff --git a/examples/demo/site/themes/bootstrap/assets/js/roc_auth.js b/examples/demo/site/themes/bootstrap/assets/js/roc_auth.js
index 06f0731..75382ef 100644
--- a/examples/demo/site/themes/bootstrap/assets/js/roc_auth.js
+++ b/examples/demo/site/themes/bootstrap/assets/js/roc_auth.js
@@ -1,7 +1,8 @@
var ROC_AUTH = ROC_AUTH || { };
-var loginURL = "/login";
-var logoutURL = "/logoff";
+var loginURL = "/basic_auth_login";
+var logoutURL = "/basic_auth_logoff";
+
var userAgent = navigator.userAgent.toLowerCase();
var firstLogIn = true;
@@ -305,3 +306,16 @@ ROC_AUTH.create_form = function() {
};
+var password = document.getElementById("password")
+ , confirm_password = document.getElementById("confirm_password");
+
+ROC_AUTH.validatePassword =function(){
+ if(password.value != confirm_password.value) {
+ confirm_password.setCustomValidity("Passwords Don't Match");
+ } else {
+ confirm_password.setCustomValidity('');
+ }
+}
+
+password.onchange = ROC_AUTH.validatePassword();
+confirm_password.onkeyup = ROC_AUTH.validatePassword;
\ No newline at end of file
diff --git a/examples/demo/site/themes/bootstrap/modules/login/templates/block_login.tpl b/examples/demo/site/themes/bootstrap/modules/login/templates/block_login.tpl
new file mode 100644
index 0000000..dda41b5
--- /dev/null
+++ b/examples/demo/site/themes/bootstrap/modules/login/templates/block_login.tpl
@@ -0,0 +1,34 @@
+
+ {unless isset="$user"}
+
+
+
+
+ {/unless}
+
\ No newline at end of file
diff --git a/examples/demo/site/themes/bootstrap/modules/login/templates/block_new_password.tpl b/examples/demo/site/themes/bootstrap/modules/login/templates/block_new_password.tpl
new file mode 100644
index 0000000..ef5407f
--- /dev/null
+++ b/examples/demo/site/themes/bootstrap/modules/login/templates/block_new_password.tpl
@@ -0,0 +1,16 @@
+
diff --git a/examples/demo/site/themes/bootstrap/modules/login/templates/block_post_password.tpl b/examples/demo/site/themes/bootstrap/modules/login/templates/block_post_password.tpl
new file mode 100644
index 0000000..0ec7a7c
--- /dev/null
+++ b/examples/demo/site/themes/bootstrap/modules/login/templates/block_post_password.tpl
@@ -0,0 +1,3 @@
+
+
We have send you a new token code, check your email to generate a new password
+
diff --git a/examples/demo/site/themes/bootstrap/modules/login/templates/block_post_reactivate.tpl b/examples/demo/site/themes/bootstrap/modules/login/templates/block_post_reactivate.tpl
new file mode 100644
index 0000000..09e7206
--- /dev/null
+++ b/examples/demo/site/themes/bootstrap/modules/login/templates/block_post_reactivate.tpl
@@ -0,0 +1,3 @@
+
+
We have send you a new activation code, check your email to activate your account.
+
diff --git a/examples/demo/site/themes/bootstrap/modules/login/templates/block_post_register.tpl b/examples/demo/site/themes/bootstrap/modules/login/templates/block_post_register.tpl
new file mode 100644
index 0000000..d59f75a
--- /dev/null
+++ b/examples/demo/site/themes/bootstrap/modules/login/templates/block_post_register.tpl
@@ -0,0 +1,3 @@
+
+
Thanks for register, check your email to activate your account.
+
diff --git a/examples/demo/site/themes/bootstrap/modules/login/templates/block_post_reset.tpl b/examples/demo/site/themes/bootstrap/modules/login/templates/block_post_reset.tpl
new file mode 100644
index 0000000..9ccecfb
--- /dev/null
+++ b/examples/demo/site/themes/bootstrap/modules/login/templates/block_post_reset.tpl
@@ -0,0 +1,3 @@
+
+
You new password has been saved!
+
diff --git a/examples/demo/site/themes/bootstrap/modules/login/templates/block_reactivate.tpl b/examples/demo/site/themes/bootstrap/modules/login/templates/block_reactivate.tpl
new file mode 100644
index 0000000..bef65c0
--- /dev/null
+++ b/examples/demo/site/themes/bootstrap/modules/login/templates/block_reactivate.tpl
@@ -0,0 +1,19 @@
+
diff --git a/examples/demo/site/themes/bootstrap/modules/login/templates/block_register.tpl b/examples/demo/site/themes/bootstrap/modules/login/templates/block_register.tpl
new file mode 100644
index 0000000..58a4872
--- /dev/null
+++ b/examples/demo/site/themes/bootstrap/modules/login/templates/block_register.tpl
@@ -0,0 +1,28 @@
+
diff --git a/examples/demo/site/themes/bootstrap/modules/login/templates/block_reset_password.tpl b/examples/demo/site/themes/bootstrap/modules/login/templates/block_reset_password.tpl
new file mode 100644
index 0000000..b99289d
--- /dev/null
+++ b/examples/demo/site/themes/bootstrap/modules/login/templates/block_reset_password.tpl
@@ -0,0 +1,28 @@
+
diff --git a/examples/demo/site/themes/bootstrap/page.tpl b/examples/demo/site/themes/bootstrap/page.tpl
index 9361fde..2223976 100644
--- a/examples/demo/site/themes/bootstrap/page.tpl
+++ b/examples/demo/site/themes/bootstrap/page.tpl
@@ -61,7 +61,7 @@
{unless isempty="$page_title"}{$page_title/} {/unless}
{$page.region_content/}
-
+
diff --git a/examples/demo/src/ewf_roc_server_execution.e b/examples/demo/src/ewf_roc_server_execution.e
index 22a937a..6a48db1 100644
--- a/examples/demo/src/ewf_roc_server_execution.e
+++ b/examples/demo/src/ewf_roc_server_execution.e
@@ -59,6 +59,10 @@ feature -- CMS setup
m.enable
a_setup.register_module (m)
+ create {CMS_AUTHENTICATION_MODULE} m.make
+ m.enable
+ a_setup.register_module (m)
+
create {BASIC_AUTH_MODULE} m.make
if not a_setup.module_with_same_type_registered (m) then
m.enable
diff --git a/library/model/src/user/cms_user.e b/library/model/src/user/cms_user.e
index 5abb436..713ca65 100644
--- a/library/model/src/user/cms_user.e
+++ b/library/model/src/user/cms_user.e
@@ -27,6 +27,7 @@ feature {NONE} -- Initialization
initialize
ensure
name_set: name = a_name
+ status_not_active: status = not_active
end
make_with_id (a_id: INTEGER_64)
@@ -38,11 +39,13 @@ feature {NONE} -- Initialization
initialize
ensure
id_set: id = a_id
+ status_not_active: status = not_active
end
initialize
do
create creation_date.make_now_utc
+ mark_not_active
end
feature -- Access
@@ -71,6 +74,13 @@ feature -- Access
last_login_date: detachable DATE_TIME
-- User last login.
+ status: INTEGER
+ -- Associated status for the current user.
+ -- default: not_active
+ -- active
+ -- trashed
+
+
feature -- Roles
roles: detachable LIST [CMS_USER_ROLE]
@@ -118,6 +128,12 @@ feature -- Status report
Result := other /= Void and then id = other.id
end
+ is_active: BOOLEAN
+ -- is the current user active?
+ do
+ Result := status = {CMS_USER}.active
+ end
+
feature -- Change element
set_id (a_id: like id)
@@ -225,6 +241,52 @@ feature -- Change element: data
end
end
+feature -- Status change
+
+ mark_not_active
+ -- Set status to not_active
+ do
+ set_status (not_active)
+ ensure
+ status_not_active: status = not_active
+ end
+
+ mark_active
+ -- Set status to active.
+ do
+ set_status (active)
+ ensure
+ status_active: status = active
+ end
+
+ mark_trashed
+ -- Set status to trashed.
+ do
+ set_status (trashed)
+ ensure
+ status_trash: status = trashed
+ end
+
+ set_status (a_status: like status)
+ -- Assign `status' with `a_status'.
+ do
+ status := a_status
+ ensure
+ status_set: status = a_status
+ 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.
+
invariant
id_or_name_set: id > 0 or else not name.is_whitespace
diff --git a/library/persistence/mysql/scripts/core.sql b/library/persistence/mysql/scripts/core.sql
new file mode 100644
index 0000000..366ccbd
--- /dev/null
+++ b/library/persistence/mysql/scripts/core.sql
@@ -0,0 +1,30 @@
+BEGIN;
+
+CREATE TABLE `logs` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `category` VARCHAR(255) NOT NULL,
+ `level` int(11) NOT NULL,
+ `uid` int(11) DEFAULT NULL,
+ `message` text NOT NULL,
+ `info` text,
+ `link` text,
+ `date` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+);
+
+CREATE TABLE `custom_values` (
+ `type` VARCHAR(255) NOT NULL,
+ `name` VARCHAR(255) NOT NULL,
+ `value` VARCHAR(255) NOT NULL
+);
+
+CREATE TABLE `path_aliases` (
+ `pid` int(11) NOT NULL AUTO_INCREMENT,
+ `source` varchar(255) NOT NULL,
+ `alias` varchar(255) NOT NULL,
+ `lang` varchar(12) DEFAULT NULL,
+ PRIMARY KEY (`pid`)
+);
+
+COMMIT;
+
diff --git a/library/persistence/mysql/scripts/node.sql b/library/persistence/mysql/scripts/node.sql
new file mode 100644
index 0000000..a4a053e
--- /dev/null
+++ b/library/persistence/mysql/scripts/node.sql
@@ -0,0 +1,24 @@
+BEGIN;
+
+CREATE TABLE nodes (
+ nid INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL CHECK( nid >=0),
+ revision INTEGER,
+ type TEXT NOT NULL,
+ title VARCHAR(255) NOT NULL,
+ summary TEXT,
+ content MEDIUMTEXT NOT NULL,
+ format VARCHAR(255),
+ author INTEGER,
+ publish DATETIME,
+ created DATETIME NOT NULL,
+ changed DATETIME NOT NULL,
+ status INTEGER
+);
+
+CREATE TABLE page_nodes(
+ nid INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL CHECK( nid >=0),
+ revision INTEGER,
+ parent INTEGER
+);
+
+COMMIT;
diff --git a/library/persistence/mysql/scripts/user.sql b/library/persistence/mysql/scripts/user.sql
new file mode 100644
index 0000000..e99072a
--- /dev/null
+++ b/library/persistence/mysql/scripts/user.sql
@@ -0,0 +1,66 @@
+BEGIN;
+
+CREATE TABLE `users` (
+ `uid` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(100) NOT NULL,
+ `password` varchar(100) NOT NULL,
+ `salt` varchar(100) NOT NULL,
+ `email` varchar(250) NOT NULL,
+ `status` int(11) DEFAULT NULL,
+ `created` datetime NOT NULL,
+ `signed` datetime DEFAULT NULL,
+ CHECK (`uid` >= 0),
+ PRIMARY KEY (`uid`),
+ UNIQUE KEY `name` (`name`)
+);
+
+CREATE TABLE `roles` (
+ `rid` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(100) NOT NULL,
+ CHECK (`rid` >= 0),
+ PRIMARY KEY (`rid`),
+ UNIQUE KEY `name` (`name`)
+);
+
+
+CREATE TABLE `users_roles` (
+ `uid` int(11) NOT NULL,
+ `rid` int(11) NOT NULL,
+ CHECK (`uid` >= 0),
+ CHECK (`rid` >= 0)
+);
+
+CREATE TABLE `role_permissions` (
+ `rid` int(11) NOT NULL,
+ `permission` varchar(255) NOT NULL,
+ `module` varchar(255) DEFAULT NULL,
+ CHECK (`rid` >= 0)
+);
+
+
+CREATE TABLE `users_activations` (
+ `aid` int(11) NOT NULL AUTO_INCREMENT,
+ `token` varchar(255) NOT NULL,
+ `uid` int(11) NOT NULL,
+ `created` datetime NOT NULL,
+ CHECK (`aid` >= 0),
+ CHECK (`uid` >= 0),
+ PRIMARY KEY (`aid`),
+ UNIQUE KEY `token` (`token`)
+);
+
+
+CREATE TABLE `users_password_recovery` (
+ `aid` int(11) NOT NULL AUTO_INCREMENT,
+ `token` varchar(255) NOT NULL,
+ `uid` int(11) NOT NULL,
+ `created` datetime NOT NULL,
+ CHECK (`aid` >= 0),
+ CHECK (`uid` >= 0),
+ PRIMARY KEY (`aid`),
+ UNIQUE KEY `token` (`token`)
+);
+
+
+
+COMMIT;
\ No newline at end of file
diff --git a/modules/basic_auth/basic_auth_module.e b/modules/basic_auth/basic_auth_module.e
index 515cac7..0d9d649 100644
--- a/modules/basic_auth/basic_auth_module.e
+++ b/modules/basic_auth/basic_auth_module.e
@@ -108,14 +108,14 @@ feature -- Hooks
local
lnk: CMS_LOCAL_LINK
do
- if attached a_response.current_user (a_response.request) as u then
- create lnk.make (u.name + " (Logout)", "basic_auth_logoff?destination=" + a_response.request.request_uri)
- else
- create lnk.make ("Login", "basic_auth_login?destination=" + a_response.request.request_uri)
- end
+-- if attached a_response.current_user (a_response.request) as u then
+-- create lnk.make (u.name + " (Logout)", "basic_auth_logoff?destination=" + a_response.request.request_uri)
+-- else
+-- create lnk.make ("Login", "basic_auth_login?destination=" + a_response.request.request_uri)
+-- end
-- if not a_menu_system.primary_menu.has (lnk) then
- lnk.set_weight (99)
- a_menu_system.primary_menu.extend (lnk)
+-- lnk.set_weight (99)
+-- a_menu_system.primary_menu.extend (lnk)
-- end
end
diff --git a/modules/basic_auth/handler/basic_auth_logoff_handler.e b/modules/basic_auth/handler/basic_auth_logoff_handler.e
index 40ca330..5e8a667 100644
--- a/modules/basic_auth/handler/basic_auth_logoff_handler.e
+++ b/modules/basic_auth/handler/basic_auth_logoff_handler.e
@@ -55,21 +55,62 @@ feature -- HTTP Methods
else
create {GENERIC_VIEW_CMS_RESPONSE} l_page.make (req, res, api)
unset_current_user (req)
- l_page.set_status_code ({HTTP_STATUS_CODE}.found) -- Note: can not use {HTTP_STATUS_CODE}.unauthorized for redirection
- if attached {WSF_STRING} req.query_parameter ("destination") as l_uri then
- l_url := req.absolute_script_url (l_uri.url_encoded_value)
- else
- l_url := req.absolute_script_url ("")
- end
+ l_page.set_status_code ({HTTP_STATUS_CODE}.unauthorized) -- Note: can not use {HTTP_STATUS_CODE}.unauthorized for redirection
+ l_url := req.absolute_script_url ("")
i := l_url.substring_index ("://", 1)
if i > 0 then
-- Note: this is a hack to have the logout effective on various browser
-- (firefox requires this).
l_url.replace_substring ("://_logout_basic_auth_@", i, i + 2)
end
- l_page.set_redirection (l_url)
+ if
+ attached req.http_user_agent as l_user_agent and then
+ browser_name (l_user_agent).is_case_insensitive_equal_general ("Firefox")
+ then
+ -- Set status to refirect
+ -- and redirect to the host page.
+ l_page.set_status_code ({HTTP_STATUS_CODE}.found)
+ l_page.set_redirection (l_url)
+ end
l_page.execute
end
end
+
+ browser_name (a_user_agent: READABLE_STRING_8): READABLE_STRING_32
+ -- Browser name.
+ -- Must contain Must not contain
+ -- Firefox Firefox/xyz Seamonkey/xyz
+ -- Seamonkey Seamonkey/xyz
+ -- Chrome Chrome/xyz Chromium/xyz
+ -- Chromium Chromium/xyz
+ -- Safari Safari/xyz Chrome/xyz
+ -- Chromium/xyz
+ -- Opera OPR/xyz [1]
+ -- Opera/xyz [2]
+ -- Internet Explorer ;MSIE xyz; Internet Explorer doesn't put its name in the BrowserName/VersionNumber format
+
+ do
+ if
+ a_user_agent.has_substring ("Firefox") and then
+ not a_user_agent.has_substring ("Seamonkey")
+ then
+ Result := "Firefox"
+ elseif a_user_agent.has_substring ("Seamonkey") then
+ Result := "Seamonkey"
+ elseif a_user_agent.has_substring ("Chrome") and then not a_user_agent.has_substring ("Chromium")then
+ Result := "Chrome"
+ elseif a_user_agent.has_substring ("Chromium") then
+ Result := "Chromiun"
+ elseif a_user_agent.has_substring ("Safari") and then not (a_user_agent.has_substring ("Chrome") or else a_user_agent.has_substring ("Chromium")) then
+ Result := "Safari"
+ elseif a_user_agent.has_substring ("OPR") or else a_user_agent.has_substring ("Opera") then
+ Result := "Opera"
+ elseif a_user_agent.has_substring ("MSIE") or else a_user_agent.has_substring ("Trident")then
+ Result := "Internet Explorer"
+ else
+ Result := "Unknown"
+ end
+ end
+
end
diff --git a/modules/login/cms_authentication_constants.e b/modules/login/cms_authentication_constants.e
new file mode 100644
index 0000000..6278ed8
--- /dev/null
+++ b/modules/login/cms_authentication_constants.e
@@ -0,0 +1,13 @@
+note
+ description: "Summary description for {CMS_AUTHENTICATION_CONSTANTS}."
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_AUTHENTICATION_CONSTANTS
+
+feature -- Access
+
+ oauth_session: STRING = "EWF_ROC_OAUTH_TOKEN_"
+
+end
diff --git a/modules/login/cms_authentication_email_service_parameters.e b/modules/login/cms_authentication_email_service_parameters.e
new file mode 100644
index 0000000..20746f2
--- /dev/null
+++ b/modules/login/cms_authentication_email_service_parameters.e
@@ -0,0 +1,268 @@
+note
+ description: "Summary description for {CMS_AUTHENTICATION_EMAIL_SERVICE_PARAMETERS}."
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_AUTHENTICATION_EMAIL_SERVICE_PARAMETERS
+
+inherit
+ EMAIL_SERVICE_PARAMETERS
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_cms_api: CMS_API)
+ local
+ utf: UTF_CONVERTER
+ l_site_name: READABLE_STRING_8
+ s: detachable READABLE_STRING_32
+ l_contact_email, l_subject_register, l_subject_activate, l_subject_password, l_subject_oauth: detachable READABLE_STRING_8
+ do
+ setup := a_cms_api.setup
+ -- Use global smtp setting if any, otherwise "localhost"
+ smtp_server := utf.escaped_utf_32_string_to_utf_8_string_8 (a_cms_api.setup.text_item_or_default ("smtp", "localhost"))
+ l_site_name := utf.escaped_utf_32_string_to_utf_8_string_8 (a_cms_api.setup.site_name)
+ admin_email := a_cms_api.setup.site_email
+
+ if not admin_email.has ('<') then
+ admin_email := l_site_name + " <" + admin_email +">"
+ end
+
+ if attached {CONFIG_READER} a_cms_api.module_configuration ("login", Void) as cfg then
+ if attached cfg.text_item ("smtp") as l_smtp then
+ -- Overwrite global smtp setting if any.
+ smtp_server := utf.utf_32_string_to_utf_8_string_8 (l_smtp)
+ end
+ s := cfg.text_item ("email")
+ if s /= Void then
+ l_contact_email := utf.utf_32_string_to_utf_8_string_8 (s)
+ end
+ s := cfg.text_item ("subject_register")
+ if s /= Void then
+ l_subject_register := utf.utf_32_string_to_utf_8_string_8 (s)
+ end
+ s := cfg.text_item ("subject_activate")
+ if s /= Void then
+ l_subject_register := utf.utf_32_string_to_utf_8_string_8 (s)
+ end
+ s := cfg.text_item ("subject_password")
+ if s /= Void then
+ l_subject_register := utf.utf_32_string_to_utf_8_string_8 (s)
+ end
+ s := cfg.text_item ("subject_oauth")
+ if s /= Void then
+ l_subject_oauth := utf.utf_32_string_to_utf_8_string_8 (s)
+ end
+
+ end
+ if l_contact_email /= Void then
+ if not l_contact_email.has ('<') then
+ l_contact_email := l_site_name + " <" + l_contact_email + ">"
+ end
+ contact_email := l_contact_email
+ else
+ contact_email := admin_email
+ end
+ if l_subject_register /= Void then
+ contact_subject_register := l_subject_register
+ else
+ contact_subject_register := "Thank you for registering with us."
+ end
+
+ if l_subject_activate /= Void then
+ contact_subject_activate := l_subject_activate
+ else
+ contact_subject_activate := "New account activation token."
+ end
+ if l_subject_password /= Void then
+ contact_subject_password := l_subject_password
+ else
+ contact_subject_password := "Password Recovery."
+ end
+ if l_subject_oauth /= Void then
+ contact_subject_oauth := l_subject_oauth
+ else
+ contact_subject_oauth := "Welcome."
+ end
+
+ end
+
+feature -- Access
+
+ smtp_server: IMMUTABLE_STRING_8
+
+ admin_email: IMMUTABLE_STRING_8
+
+ contact_email: IMMUTABLE_STRING_8
+ -- Contact email.
+
+ contact_subject_register: IMMUTABLE_STRING_8
+ contact_subject_activate: IMMUTABLE_STRING_8
+ contact_subject_password: IMMUTABLE_STRING_8
+ contact_subject_oauth: IMMUTABLE_STRING_8
+
+
+
+ account_activation: STRING
+ -- Account activation template email message.
+ local
+ p: PATH
+ do
+ p := setup.environment.config_path.extended ("modules").extended ("login").extended("account_activation.html")
+ if attached read_template_file (p) as l_content then
+ Result := l_content
+ else
+ create Result.make_from_string (template_account_activation)
+ end
+ end
+
+ account_re_activation: STRING
+ -- Account re_activation template email message.
+ local
+ p: PATH
+ do
+ p := setup.environment.config_path.extended ("modules").extended ("login").extended("accunt_re_activation.html")
+ if attached read_template_file (p) as l_content then
+ Result := l_content
+ else
+ create Result.make_from_string (template_account_re_activation)
+ end
+ end
+
+ account_password: STRING
+ -- Account password template email message.
+ local
+ p: PATH
+ do
+ p := setup.environment.config_path.extended ("modules").extended ("login").extended("account_new_password.html")
+ if attached read_template_file (p) as l_content then
+ Result := l_content
+ else
+ create Result.make_from_string (template_account_new_password)
+ end
+ end
+
+ account_welcome: STRING
+ -- Account welcome template email message.
+ local
+ p: PATH
+ do
+ p := setup.environment.config_path.extended ("modules").extended ("login").extended("account_welcome.html")
+ if attached read_template_file (p) as l_content then
+ Result := l_content
+ else
+ create Result.make_from_string (template_account_welcome)
+ end
+ end
+
+feature {NONE} -- Implementation
+
+ setup: CMS_SETUP
+
+
+ read_template_file (a_path: PATH): detachable STRING
+ -- Read the content of the file at path `a_path'.
+ local
+ l_file: FILE
+ do
+ create {PLAIN_TEXT_FILE} l_file.make_with_path (a_path)
+ if l_file.exists and then l_file.is_readable then
+ l_file.open_read
+ l_file.read_stream (l_file.count)
+ Result := l_file.last_string
+ l_file.close
+ else
+ -- Error
+ end
+ end
+
+
+feature {NONE} -- Message email
+
+ template_account_activation: STRING= "[
+
+
+
+
+ Activation
+
+
+
+
+
+ Thank you for registering at ROC CMS
+
+ To complete your registration, please click on this link to activate your account:
+
+
$link
+ Thank you for joining us.
+
+
+ ]"
+
+
+ template_account_re_activation: STRING= "[
+
+
+
+
+ New Activation
+
+
+
+
+
+ You have request a new activation token atROC CMS
+
+ To complete your registration, please click on this link to activate your account:
+
+
$link
+ Thank you for joining us.
+
+
+ ]"
+
+
+
+ template_account_new_password: STRING= "[
+
+
+
+
+ New Password
+
+
+
+
+
+ You have required a new password at ROC CMS
+
+ To complete your request, please click on this link to genereate a new password:
+
+
$link
+
+
+ ]"
+
+
+ template_account_welcome: STRING= "[
+
+
+
+
+ Welcome
+
+
+
+
+
+ Welcome toROC CMS
+ Thank you for joining us.
+
+
+ ]"
+
+end
diff --git a/modules/login/cms_authentication_module.e b/modules/login/cms_authentication_module.e
new file mode 100644
index 0000000..58e1cdd
--- /dev/null
+++ b/modules/login/cms_authentication_module.e
@@ -0,0 +1,890 @@
+note
+ description: "Module Logging supporting different authentication strategies"
+ date: "$Date: 2015-05-20 06:50:50 -0300 (mi. 20 de may. de 2015) $"
+ revision: "$Revision: 97328 $"
+
+class
+ CMS_AUTHENTICATION_MODULE
+
+inherit
+ CMS_MODULE
+ rename
+ module_api as user_oauth_api
+ redefine
+ filters,
+ register_hooks,
+ initialize,
+ is_installed,
+ install,
+ user_oauth_api
+ end
+
+
+ CMS_HOOK_BLOCK
+
+ CMS_HOOK_AUTO_REGISTER
+
+ CMS_HOOK_MENU_SYSTEM_ALTER
+
+ CMS_HOOK_VALUE_TABLE_ALTER
+
+ SHARED_EXECUTION_ENVIRONMENT
+ export
+ {NONE} all
+ end
+
+ REFACTORING_HELPER
+
+ SHARED_LOGGER
+
+ CMS_REQUEST_UTIL
+
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make
+ -- Create current module
+ do
+ name := "login"
+ version := "1.0"
+ description := "Eiffel login module"
+ package := "login"
+
+ create root_dir.make_current
+ cache_duration := 0
+ end
+
+feature {CMS_API} -- Module Initialization
+
+ initialize (a_api: CMS_API)
+ --
+ local
+ l_user_auth_api: like user_oauth_api
+ l_user_auth_storage: CMS_OAUTH_20_STORAGE_I
+ do
+ Precursor (a_api)
+
+ -- Storage initialization
+ if attached {CMS_STORAGE_SQL_I} a_api.storage as l_storage_sql then
+ create {CMS_OAUTH_20_STORAGE_SQL} l_user_auth_storage.make (l_storage_sql)
+ else
+ -- FIXME: in case of NULL storage, should Current be disabled?
+ create {CMS_OAUTH_20_STORAGE_NULL} l_user_auth_storage
+ end
+
+ -- Node API initialization
+ create l_user_auth_api.make_with_storage (a_api, l_user_auth_storage)
+ user_oauth_api := l_user_auth_api
+ ensure then
+ user_oauth_api_set: user_oauth_api /= Void
+ end
+
+feature {CMS_API} -- Module management
+
+ is_installed (api: CMS_API): BOOLEAN
+ -- Is Current module installed?
+ do
+ Result := attached api.storage.custom_value ("is_initialized", "module-" + name) as v and then v.is_case_insensitive_equal_general ("yes")
+ end
+
+ install (api: CMS_API)
+ local
+ l_setup: CMS_SETUP
+ l_params: detachable STRING_TABLE [detachable ANY]
+ l_consumers: LIST [STRING]
+ do
+ l_setup := api.setup
+
+ -- Schema
+ if attached {CMS_STORAGE_SQL_I} api.storage as l_sql_storage then
+ if not l_sql_storage.sql_table_exists ("oauth2_consumers") then
+ --| Schema
+ l_sql_storage.sql_execute_file_script (l_setup.environment.path.extended ("scripts").extended ("oauth2_consumers.sql"))
+
+ if l_sql_storage.has_error then
+ api.logger.put_error ("Could not initialize database for blog module", generating_type)
+ end
+ -- TODO workaround.
+ l_sql_storage.sql_execute_file_script (l_setup.environment.path.extended ("scripts").extended ("oauth2_consumers_initialize.sql"))
+ end
+
+ -- TODO workaround, until we have an admin module
+ l_sql_storage.sql_query ("SELECT name FROM oauth2_consumers;", Void)
+ if l_sql_storage.has_error then
+ api.logger.put_error ("Could not initialize database for differnent consumerns", generating_type)
+ else
+ from
+ l_sql_storage.sql_start
+ create {ARRAYED_LIST[STRING]} l_consumers.make (2)
+ until
+ l_sql_storage.sql_after
+ loop
+ if attached l_sql_storage.sql_read_string (1) as l_name then
+ l_consumers.force ("oauth2_"+l_name)
+ end
+ l_sql_storage.sql_forth
+ end
+ across l_consumers as ic loop
+ if not l_sql_storage.sql_table_exists (ic.item) then
+ create l_params.make (1)
+ l_params.force (ic.item, "table_name")
+ l_sql_storage.sql_execute_file_script_with_params (l_setup.environment.path.extended ("scripts").extended ("oauth2_template.sql"), l_params)
+ end
+ end
+ end
+ api.storage.set_custom_value ("is_initialized", "module-" + name, "yes")
+ end
+ end
+
+feature {CMS_API} -- Access: API
+
+ user_oauth_api: detachable CMS_OAUTH_20_API
+ --
+
+feature -- Filters
+
+ filters (a_api: CMS_API): detachable LIST [WSF_FILTER]
+ -- Possibly list of Filter's module.
+ do
+ create {ARRAYED_LIST [WSF_FILTER]} Result.make (1)
+ if attached user_oauth_api as l_user_oauth_api then
+ Result.extend (create {CMS_OAUTH_20_FILTER}.make (a_api, l_user_oauth_api))
+ end
+ 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
+
+
+ setup_router (a_router: WSF_ROUTER; a_api: CMS_API)
+ --
+ do
+ if attached user_oauth_api as l_user_oauth_api then
+ configure_web (a_api, l_user_oauth_api, a_router)
+ end
+ end
+
+
+ configure_web (a_api: CMS_API; a_user_oauth_api: CMS_OAUTH_20_API; a_router: WSF_ROUTER)
+ do
+ a_router.handle ("/account/roc-login", create {WSF_URI_AGENT_HANDLER}.make (agent handle_login (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/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/roc-logout", create {WSF_URI_AGENT_HANDLER}.make (agent handle_logout (a_api, ?, ?)), a_router.methods_get_post)
+ a_router.handle ("/account/login-with-oauth/{callback}", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent handle_login_with_oauth (a_api,a_user_oauth_api, ?, ?)), a_router.methods_get_post)
+ a_router.handle ("/account/{callback}", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent handle_callback_oauth (a_api, a_user_oauth_api, ?, ?)), a_router.methods_get_post)
+ end
+
+
+feature -- Hooks configuration
+
+ register_hooks (a_response: CMS_RESPONSE)
+ -- Module hooks configuration.
+ do
+ auto_subscribe_to_hooks (a_response)
+ a_response.subscribe_to_block_hook (Current)
+ a_response.subscribe_to_value_table_alter_hook (Current)
+ end
+
+feature -- Hooks
+
+ value_table_alter (a_value: CMS_VALUE_TABLE; a_response: CMS_RESPONSE)
+ --
+ do
+ if attached current_user (a_response.request) as l_user then
+ a_value.force (l_user, "user")
+ end
+ 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.current_user (a_response.request) as u then
+ create lnk.make (u.name + " (Logout)", "account/roc-logout" )
+ else
+ create lnk.make ("Login", "account/roc-login")
+ end
+ a_menu_system.primary_menu.extend (lnk)
+ lnk.set_weight (98)
+ end
+
+ block_list: ITERABLE [like {CMS_BLOCK}.name]
+ local
+ l_string: STRING
+ do
+ Result := <<"login","register","reactivate","new_password", "reset_password">>
+ create l_string.make_empty
+ across Result as ic loop
+ l_string.append (ic.item)
+ l_string.append_character (' ')
+ end
+ write_debug_log (generator + ".block_list:" + l_string )
+ end
+
+ get_block_view (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
+ do
+ if
+ a_block_id.is_case_insensitive_equal_general ("login") and then
+ a_response.request.path_info.starts_with ("/account/roc-login")
+ then
+ get_block_view_login (a_block_id, a_response)
+ elseif
+ a_block_id.is_case_insensitive_equal_general ("register") and then
+ a_response.request.path_info.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
+ a_response.request.path_info.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
+ a_response.request.path_info.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
+ a_response.request.path_info.starts_with ("/account/reset-password")
+ then
+ get_block_view_reset_password (a_block_id, a_response)
+ end
+ end
+
+ handle_login (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
+ local
+ r: CMS_RESPONSE
+ do
+ create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
+ r.set_value ("Login", "optional_content_type")
+ r.execute
+ end
+
+ handle_logout (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
+ local
+ r: CMS_RESPONSE
+ l_url: STRING
+ l_cookie: WSF_COOKIE
+ do
+ if
+ attached {WSF_STRING} req.cookie ({CMS_AUTHENTICATION_CONSTANTS}.oauth_session) as l_cookie_token and then
+ attached {CMS_USER} current_user (req) as l_user
+ then
+ -- Logout gmail
+ create l_cookie.make ({CMS_AUTHENTICATION_CONSTANTS}.oauth_session, l_cookie_token.value)
+ l_cookie.set_path ("/")
+ l_cookie.set_max_age (-1)
+ res.add_cookie (l_cookie)
+ unset_current_user (req)
+ create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
+ r.set_status_code ({HTTP_CONSTANTS}.found)
+ r.set_redirection (req.absolute_script_url (""))
+ r.execute
+ else
+ create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
+ r.set_status_code ({HTTP_CONSTANTS}.found)
+ l_url := req.absolute_script_url ("")
+ l_url.append ("/basic_auth_logoff")
+ r.set_redirection (l_url)
+ r.execute
+ end
+ end
+
+ handle_register (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
+ local
+ r: CMS_RESPONSE
+ l_user_api: CMS_USER_API
+ u: CMS_USER
+ l_roles: LIST [CMS_USER_ROLE]
+ l_exist: BOOLEAN
+ es: CMS_AUTHENTICATON_EMAIL_SERVICE
+ l_link: STRING
+ l_token: STRING
+ do
+ create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
+ r.set_value ("Register", "optional_content_type")
+ if req.is_post_request_method then
+ if
+ attached {WSF_STRING} req.form_parameter ("name") as l_name and then
+ attached {WSF_STRING} req.form_parameter ("password") as l_password and then
+ attached {WSF_STRING} req.form_parameter ("email") as l_email
+ then
+ l_user_api := api.user_api
+
+ if attached l_user_api.user_by_name (l_name.value) then
+ -- Username already exist.
+ r.values.force ("The user name exist!", "error_name")
+ l_exist := True
+ end
+ if attached l_user_api.user_by_email (l_email.value) then
+ -- Emails already exist.
+ r.values.force ("The email exist!", "error_email")
+ l_exist := True
+ end
+
+ if not l_exist then
+ -- New user
+ create {ARRAYED_LIST [CMS_USER_ROLE]}l_roles.make (1)
+ l_roles.force (l_user_api.authenticated_user_role)
+
+ create u.make (l_name.value)
+ u.set_email (l_email.value)
+ u.set_password (l_password.value)
+ u.set_roles (l_roles)
+ l_user_api.new_user (u)
+
+ -- Create activation token
+ l_token := new_token
+ l_user_api.new_activation (l_token, u.id)
+ create l_link.make_from_string (req.server_url)
+ l_link.append ("/account/activate/")
+ l_link.append (l_token)
+
+
+ -- Send Email
+ create es.make (create {CMS_AUTHENTICATION_EMAIL_SERVICE_PARAMETERS}.make (api))
+ write_debug_log (generator + ".handle register: send_contact_email")
+ es.send_contact_email (l_email.value, l_link)
+
+ else
+ r.values.force (l_name.value, "name")
+ r.values.force (l_email.value, "email")
+ r.set_status_code ({HTTP_CONSTANTS}.bad_request)
+ end
+ end
+ end
+
+ r.execute
+ end
+
+ handle_activation (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
+ local
+ r: CMS_RESPONSE
+ l_user_api: CMS_USER_API
+ l_ir: INTERNAL_SERVER_ERROR_CMS_RESPONSE
+ do
+ l_user_api := api.user_api
+ create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
+ if attached {WSF_STRING} req.path_parameter ("token") as l_token then
+
+ if attached {CMS_USER} l_user_api.user_by_activation_token (l_token.value) as l_user then
+ -- Valid user_id
+ l_user.mark_active
+ l_user_api.update_user (l_user)
+ l_user_api.remove_activation (l_token.value)
+ r.set_value ("Account activated", "optional_content_type")
+ r.set_main_content (" Your account "+ l_user.name +" has been activated
")
+ else
+ -- the token does not exist, or it was already used.
+ r.set_status_code ({HTTP_CONSTANTS}.bad_request)
+ r.set_value ("Account not activated", "optional_content_type")
+ r.set_main_content ("The token "+ l_token.value +" is not valid Reactivate Account
" )
+
+ end
+ r.execute
+ else
+ create l_ir.make (req, res, api)
+ l_ir.execute
+ end
+ end
+
+
+ handle_reactivation (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
+ local
+ r: CMS_RESPONSE
+ es: CMS_AUTHENTICATON_EMAIL_SERVICE
+ l_user_api: CMS_USER_API
+ l_token: STRING
+ l_link: STRING
+ do
+ create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
+ if req.is_post_request_method then
+ if
+ attached {WSF_STRING} req.form_parameter ("email") as l_email
+ then
+ l_user_api := api.user_api
+ if attached {CMS_USER} l_user_api.user_by_email (l_email.value) as l_user then
+ -- User exist create a new token and send a new email.
+ if l_user.is_active then
+ r.values.force ("The asociated user to the given email " + l_email.value + " , is already active", "is_active")
+ r.set_status_code ({HTTP_CONSTANTS}.bad_request)
+ else
+ l_token := new_token
+ l_user_api.new_activation (l_token, l_user.id)
+ create l_link.make_from_string (req.server_url)
+ l_link.append ("/account/activate/")
+ l_link.append (l_token)
+
+ -- Send Email
+ create es.make (create {CMS_AUTHENTICATION_EMAIL_SERVICE_PARAMETERS}.make (api))
+ write_debug_log (generator + ".handle register: send_contact_activation_email")
+ es.send_contact_activation_email (l_email.value, l_link)
+ end
+ else
+ r.values.force ("The email does not exist or !", "error_email")
+ r.values.force (l_email.value, "email")
+ r.set_status_code ({HTTP_CONSTANTS}.bad_request)
+ end
+ end
+ end
+
+ r.execute
+ end
+
+ handle_new_password (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
+ local
+ r: CMS_RESPONSE
+ es: CMS_AUTHENTICATON_EMAIL_SERVICE
+ l_user_api: CMS_USER_API
+ l_token: STRING
+ l_link: STRING
+ do
+ create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
+ if req.is_post_request_method then
+ l_user_api := api.user_api
+ if attached {WSF_STRING} req.form_parameter ("email") as l_email then
+ if attached {CMS_USER} l_user_api.user_by_email (l_email.value) as l_user then
+ -- User exist create a new token and send a new email.
+ l_token := new_token
+ l_user_api.new_password (l_token, l_user.id)
+ create l_link.make_from_string (req.server_url)
+ l_link.append ("/account/reset-password?token=")
+ l_link.append (l_token)
+
+ -- Send Email
+ create es.make (create {CMS_AUTHENTICATION_EMAIL_SERVICE_PARAMETERS}.make (api))
+ write_debug_log (generator + ".handle register: send_contact_password_email")
+ es.send_contact_password_email (l_email.value, l_link)
+ else
+ r.values.force ("The email does not exist !", "error_email")
+ r.values.force (l_email.value, "email")
+ r.set_status_code ({HTTP_CONSTANTS}.bad_request)
+ end
+ end
+ end
+ r.execute
+ end
+
+
+ handle_reset_password (api: CMS_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, api)
+ l_user_api := api.user_api
+ if attached {WSF_STRING} req.query_parameter ("token") as l_token then
+ r.values.force (l_token.value, "token")
+ if l_user_api.user_by_password_token (l_token.value) = Void then
+ r.values.force ("The token " + l_token.value + " is not valid, click here 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.values.force ("Passwords Don't Match", "error_password")
+ r.values.force (l_token.value, "token")
+ r.set_status_code ({HTTP_CONSTANTS}.bad_request)
+ end
+ end
+ end
+ r.execute
+ end
+
+feature {NONE} -- Helpers
+
+ template_block (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE): detachable CMS_SMARTY_TEMPLATE_BLOCK
+ -- Smarty content block for `a_block_id'
+ local
+ p: detachable PATH
+ do
+ create p.make_from_string ("templates")
+ p := p.extended ("block_").appended (a_block_id).appended_with_extension ("tpl")
+ p := a_response.module_resource_path (Current, p)
+ if p /= Void then
+ if attached p.entry as e then
+ create Result.make (a_block_id, Void, p.parent, e)
+ else
+ create Result.make (a_block_id, Void, p.parent, p)
+ end
+ end
+ end
+
+feature {NONE} -- Block views
+
+ get_block_view_login (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
+ local
+ vals: CMS_VALUE_TABLE
+ do
+ if attached template_block (a_block_id, a_response) as l_tpl_block then
+ create vals.make (1)
+ -- add the variable to the block
+ value_table_alter (vals, a_response)
+ across
+ vals as ic
+ loop
+ l_tpl_block.set_value (ic.item, ic.key)
+ end
+ if
+ attached user_oauth_api as l_auth_api and then
+ attached l_auth_api.oauth2_consumers as l_list
+ then
+ l_tpl_block.set_value (l_list, "oauth_consumers")
+ 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
+ end
+
+ get_block_view_register (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
+ do
+ if a_response.request.is_get_request_method then
+ if attached template_block (a_block_id, a_response) 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_name") or else a_response.values.has ("error_email") then
+ if attached template_block (a_block_id, a_response) 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")
+ 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 template_block ("post_register", a_response) 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 template_block (a_block_id, a_response) 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 template_block (a_block_id, a_response) 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 template_block ("post_reactivate", a_response) 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 template_block (a_block_id, a_response) 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") then
+ if attached template_block (a_block_id, a_response) 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")
+ 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 template_block ("post_password", a_response) 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 template_block (a_block_id, a_response) 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 template_block (a_block_id, a_response) 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 template_block ("post_reset", a_response) 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 -- OAuth2 Login with google.
+
+ handle_login_with_oauth (api: CMS_API; a_oauth_api: CMS_OAUTH_20_API; req: WSF_REQUEST; res: WSF_RESPONSE)
+ local
+ r: CMS_RESPONSE
+ l_oauth: CMS_OAUTH_20_WORKFLOW
+ do
+ if
+ attached {WSF_STRING} req.path_parameter ("callback") as p_consumer and then
+ attached {CMS_OAUTH_20_CONSUMER} a_oauth_api.oauth_consumer_by_name (p_consumer.value) as l_consumer
+ then
+ create l_oauth.make (req.server_url, l_consumer)
+ if attached l_oauth.authorization_url as l_authorization_url then
+ create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
+ r.set_redirection (l_authorization_url)
+ r.execute
+ else
+ create {BAD_REQUEST_ERROR_CMS_RESPONSE} r.make (req, res, api)
+ r.set_main_content ("Bad request")
+ r.execute
+ end
+ else
+ create {BAD_REQUEST_ERROR_CMS_RESPONSE} r.make (req, res, api)
+ r.set_main_content ("Bad request")
+ r.execute
+ end
+ end
+
+ handle_callback_oauth (api: CMS_API; a_user_oauth_api: CMS_OAUTH_20_API; req: WSF_REQUEST; res: WSF_RESPONSE)
+ local
+ r: CMS_RESPONSE
+ l_auth: CMS_OAUTH_20_WORKFLOW
+ l_user_api: CMS_USER_API
+ l_user: CMS_USER
+ l_roles: LIST [CMS_USER_ROLE]
+ l_cookie: WSF_COOKIE
+ es: CMS_AUTHENTICATON_EMAIL_SERVICE
+ do
+ if attached {WSF_STRING} req.path_parameter ("callback") as l_callback and then
+ attached {CMS_OAUTH_20_CONSUMER} a_user_oauth_api.oauth_consumer_by_callback (l_callback.value) as l_consumer and then
+ attached {WSF_STRING} req.query_parameter ("code") as l_code
+ then
+ create l_auth.make (req.server_url, l_consumer)
+ l_auth.sign_request (l_code.value)
+ if
+ attached l_auth.access_token as l_access_token and then
+ attached l_auth.user_profile as l_user_profile
+ then
+ create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
+ -- extract user email
+ -- check if the user exist
+ l_user_api := api.user_api
+ -- 1 if the user exit put it in the context
+ if
+ attached l_auth.user_email as l_email
+ then
+ if attached {CMS_USER} l_user_api.user_by_email (l_email) as p_user then
+ -- User with email exist
+ if attached {CMS_USER} a_user_oauth_api.user_oauth2_by_id (p_user.id, l_consumer.name) then
+ -- Update oauth entry
+ a_user_oauth_api.update_user_oauth2 (l_access_token.token, l_user_profile, p_user, l_consumer.name )
+ else
+ -- create a oauth entry
+ a_user_oauth_api.new_user_oauth2 (l_access_token.token, l_user_profile, p_user, l_consumer.name )
+ end
+ create l_cookie.make ({CMS_AUTHENTICATION_CONSTANTS}.oauth_session, l_access_token.token)
+ l_cookie.set_max_age (l_access_token.expires_in)
+ l_cookie.set_path ("/")
+ res.add_cookie (l_cookie)
+ else
+
+ create {ARRAYED_LIST [CMS_USER_ROLE]}l_roles.make (1)
+ l_roles.force (l_user_api.authenticated_user_role)
+
+ -- Create a new user and oauth entry
+ create l_user.make (l_email)
+ l_user.set_email (l_email)
+ l_user.set_password (new_token) -- generate a random password.
+ l_user.set_roles (l_roles)
+ l_user.mark_active
+ l_user_api.new_user (l_user)
+
+ -- Add oauth entry
+ a_user_oauth_api.new_user_oauth2 (l_access_token.token, l_user_profile, l_user, l_consumer.name )
+ create l_cookie.make ({CMS_AUTHENTICATION_CONSTANTS}.oauth_session, l_access_token.token)
+ l_cookie.set_max_age (l_access_token.expires_in)
+ l_cookie.set_path ("/")
+ res.add_cookie (l_cookie)
+ set_current_user (req, l_user)
+
+
+ -- Send Email
+ create es.make (create {CMS_AUTHENTICATION_EMAIL_SERVICE_PARAMETERS}.make (api))
+ write_debug_log (generator + ".handle register: send_contact_welcome_email")
+ es.send_contact_welcome_email (l_email, "")
+ end
+ else
+ end
+ r.set_redirection (req.absolute_script_url (""))
+ r.execute
+ end
+
+ end
+
+ end
+
+feature {NONE} -- Token Generation
+
+ new_token: STRING
+ -- Generate a new token activation token
+ local
+ l_token: STRING
+ l_security: SECURITY_PROVIDER
+ l_encode: URL_ENCODER
+ do
+ create l_security
+ l_token := l_security.token
+ create l_encode
+ from until l_token.same_string (l_encode.encoded_string (l_token)) loop
+ -- Loop ensure that we have a security token that does not contain characters that need encoding.
+ -- We cannot simply to an encode-decode because the email sent to the user will contain an encoded token
+ -- but the user will need to use an unencoded token if activation has to be done manually.
+ l_token := l_security.token
+ end
+ Result := l_token
+ end
+
+
+
+feature {NONE} -- Implementation: date and time
+
+ http_date_format_to_date (s: READABLE_STRING_8): detachable DATE_TIME
+ local
+ d: HTTP_DATE
+ do
+ create d.make_from_string (s)
+ if not d.has_error then
+ Result := d.date_time
+ end
+ end
+
+ file_date (p: PATH): DATE_TIME
+ require
+ path_exists: (create {FILE_UTILITIES}).file_path_exists (p)
+ local
+ f: RAW_FILE
+ do
+ create f.make_with_path (p)
+ Result := timestamp_to_date (f.date)
+ end
+
+ timestamp_to_date (n: INTEGER): DATE_TIME
+ local
+ d: HTTP_DATE
+ do
+ create d.make_from_timestamp (n)
+ Result := d.date_time
+ 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
diff --git a/modules/login/cms_authenticaton_email_service.e b/modules/login/cms_authenticaton_email_service.e
new file mode 100644
index 0000000..d4a1984
--- /dev/null
+++ b/modules/login/cms_authenticaton_email_service.e
@@ -0,0 +1,88 @@
+note
+ description: "Summary description for {CMS_AUTHENTICATON_EMAIL_SERVICE}."
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_AUTHENTICATON_EMAIL_SERVICE
+
+inherit
+ EMAIL_SERVICE
+ redefine
+ initialize,
+ parameters
+ end
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ initialize
+ do
+ Precursor
+ contact_email := parameters.contact_email
+ end
+
+ parameters: CMS_AUTHENTICATION_EMAIL_SERVICE_PARAMETERS
+ -- Associated parameters.
+
+feature -- Access
+
+ contact_email: IMMUTABLE_STRING_8
+ -- contact email.
+
+feature -- Basic Operations
+
+ send_contact_email (a_to, a_content: READABLE_STRING_8)
+ -- Send successful contact message `a_token' to `a_to'.
+ require
+ attached_to: a_to /= Void
+ local
+ l_message: STRING
+ do
+ create l_message.make_from_string (parameters.account_activation)
+ l_message.replace_substring_all ("$link", a_content)
+ send_message (contact_email, a_to, parameters.contact_subject_register, l_message)
+ end
+
+
+ send_contact_activation_email (a_to, a_content: READABLE_STRING_8)
+ -- Send successful contact message `a_token' to `a_to'.
+ require
+ attached_to: a_to /= Void
+ local
+ l_message: STRING
+ do
+ create l_message.make_from_string (parameters.account_re_activation)
+ l_message.replace_substring_all ("$link", a_content)
+ send_message (contact_email, a_to, parameters.contact_subject_activate, l_message)
+ end
+
+
+ send_contact_password_email (a_to, a_content: READABLE_STRING_8)
+ -- Send successful contact message `a_token' to `a_to'.
+ require
+ attached_to: a_to /= Void
+ local
+ l_message: STRING
+ do
+ create l_message.make_from_string (parameters.account_password)
+ l_message.replace_substring_all ("$link", a_content)
+ send_message (contact_email, a_to, parameters.contact_subject_password, l_message)
+ end
+
+ send_contact_welcome_email (a_to, a_content: READABLE_STRING_8)
+ -- Send successful contact message `a_token' to `a_to'.
+ require
+ attached_to: a_to /= Void
+ local
+ l_message: STRING
+ do
+ create l_message.make_from_string (parameters.account_welcome)
+ l_message.replace_substring_all ("$link", a_content)
+ send_message (contact_email, a_to, parameters.contact_subject_oauth, l_message)
+ end
+
+
+end
diff --git a/modules/login/cms_oauth_20_api.e b/modules/login/cms_oauth_20_api.e
new file mode 100644
index 0000000..27739b2
--- /dev/null
+++ b/modules/login/cms_oauth_20_api.e
@@ -0,0 +1,95 @@
+note
+ description: "[
+ API to manage CMS User OAuth authentication.
+ ]"
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_OAUTH_20_API
+
+inherit
+ CMS_MODULE_API
+
+ REFACTORING_HELPER
+
+create {CMS_AUTHENTICATION_MODULE}
+ make_with_storage
+
+feature {NONE} -- Initialization
+
+ make_with_storage (a_api: CMS_API; a_oauth_storage: CMS_OAUTH_20_STORAGE_I)
+ -- Create an object with api `a_api' and storage `a_oauth_storage'.
+ do
+ oauth_20_storage := a_oauth_storage
+ make (a_api)
+ ensure
+ oauht_20_storage_set: oauth_20_storage = a_oauth_storage
+ end
+
+feature {CMS_MODULE} -- Access: User oauth storage.
+
+ oauth_20_storage: CMS_OAUTH_20_STORAGE_I
+ -- storage interface.
+
+feature -- Access: User Oauth20
+
+ user_oauth2_by_id (a_uid: like {CMS_USER}.id; a_consumer: READABLE_STRING_32): detachable CMS_USER
+ -- Retrieve a user by id `a_uid' for the consumer `a_consumer', if aby.
+ do
+ Result := oauth_20_storage.user_oauth2_by_id (a_uid, a_consumer)
+ end
+
+ user_oauth2_by_token (a_token: READABLE_STRING_32; a_consumer: READABLE_STRING_32): detachable CMS_USER
+ -- Retrieve a user by token `a_token' for the consumer `a_consumer'.
+ do
+ Result := oauth_20_storage.user_oauth2_by_token (a_token, a_consumer)
+ end
+
+ user_oauth2_without_consumer_by_token (a_token: READABLE_STRING_32 ): detachable CMS_USER
+ -- Retrieve a user by token `a_token' searching in all the registered consumers in the system.
+ do
+ Result := oauth_20_storage.user_oauth2_without_consumer_by_token (a_token)
+ end
+
+feature -- Access: Consumers OAuth20
+
+ oauth2_consumers: LIST [STRING]
+ -- List of Oauth_20 consumers, if any, empty in other case.
+ do
+ Result := oauth_20_storage.oauth2_consumers
+ end
+
+ oauth_consumer_by_name (a_name: READABLE_STRING_8): detachable CMS_OAUTH_20_CONSUMER
+ -- Retrieve a consumer by name `a_name', if any.
+ do
+ Result := oauth_20_storage.oauth_consumer_by_name (a_name)
+ end
+
+ oauth_consumer_by_callback (a_callback: READABLE_STRING_8): detachable CMS_OAUTH_20_CONSUMER
+ -- Retrieve a consumer by callback `a_callback', if any.
+ do
+ Result := oauth_20_storage.oauth_consumer_by_callback (a_callback)
+ end
+
+feature -- Change: User OAuth20
+
+
+ new_user_oauth2 (a_token: READABLE_STRING_32; a_user_profile: READABLE_STRING_32; a_user: CMS_USER; a_consumer: READABLE_STRING_32)
+ -- Add a new user with oauth20 using the consumer `a_consumer'.
+ require
+ has_id: a_user.has_id
+ do
+ oauth_20_storage.new_user_oauth2 (a_token, a_user_profile, a_user, a_consumer)
+ end
+
+
+ update_user_oauth2 (a_token: READABLE_STRING_32; a_user_profile: READABLE_STRING_32; a_user: CMS_USER; a_consumer_table: READABLE_STRING_32)
+ -- Updaate user `a_user' with oauth2 for the consumer `a_consumer'.
+ require
+ has_id: a_user.has_id
+ do
+ oauth_20_storage.update_user_oauth2 (a_token, a_user_profile, a_user, a_consumer_table)
+ end
+
+end
diff --git a/modules/login/cms_oauth_20_consumer.e b/modules/login/cms_oauth_20_consumer.e
new file mode 100644
index 0000000..79f3421
--- /dev/null
+++ b/modules/login/cms_oauth_20_consumer.e
@@ -0,0 +1,152 @@
+note
+ description: "Summary description for {CMS_OAUTH_CONSUMER}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_OAUTH_20_CONSUMER
+
+inherit
+
+ ANY
+ redefine
+ default_create
+ end
+
+create
+ default_create
+
+feature {NONE} -- Initialization
+
+ default_create
+ do
+ set_endpoint ("")
+ set_authorize_url ("")
+ set_extractor ("")
+ set_callback_name ("")
+ set_protected_resource_url ("")
+ set_scope ("")
+ set_api_key ("")
+ set_api_secret ("")
+ set_name ("")
+ end
+
+feature -- Access
+
+ endpoint: READABLE_STRING_32
+ -- Url that receives the access token request.
+
+ authorize_url: READABLE_STRING_32
+ --
+
+ extractor: READABLE_STRING_32
+ -- text, json
+
+
+ callback_name: READABLE_STRING_32
+ -- consumer callback name
+
+ protected_resource_url: READABLE_STRING_32
+ -- consumer resource url
+
+ scope: READABLE_STRING_32
+ -- consumer scope
+
+ api_key: READABLE_STRING_32
+ -- consumer public key
+
+ api_secret: READABLE_STRING_32
+ -- consumer secret.
+
+ name: READABLE_STRING_32
+ -- consumer name.
+
+ id: INTEGER_64
+ -- unique identifier.
+
+
+
+feature -- Element change
+
+ set_extractor (a_extractor: like extractor)
+ -- Assign `extractor' with `a_extractor'.
+ do
+ extractor := a_extractor
+ ensure
+ extractor_assigned: extractor = a_extractor
+ end
+
+ set_authorize_url (a_authorize_url: like authorize_url)
+ -- Assign `authorize_url' with `a_authorize_url'.
+ do
+ authorize_url := a_authorize_url
+ ensure
+ authorize_url_assigned: authorize_url = a_authorize_url
+ end
+
+ set_endpoint (a_endpoint: like endpoint)
+ -- Assign `endpoint' with `a_endpoint'.
+ do
+ endpoint := a_endpoint
+ ensure
+ endpoint_assigned: endpoint = a_endpoint
+ end
+
+ set_callback_name (a_callback_name: like callback_name)
+ -- Assign `callback_name' with `a_callback_name'.
+ do
+ callback_name := a_callback_name
+ ensure
+ callback_name_assigned: callback_name = a_callback_name
+ end
+
+ set_protected_resource_url (a_protected_resource_url: like protected_resource_url)
+ -- Assign `protected_resource_url' with `a_protected_resource_url'.
+ do
+ protected_resource_url := a_protected_resource_url
+ ensure
+ protected_resource_url_assigned: protected_resource_url = a_protected_resource_url
+ end
+
+ set_scope (a_scope: like scope)
+ -- Assign `scope' with `a_scope'.
+ do
+ scope := a_scope
+ ensure
+ scope_assigned: scope = a_scope
+ end
+
+ set_api_key (an_api_key: like api_key)
+ -- Assign `api_key' with `an_api_key'.
+ do
+ api_key := an_api_key
+ ensure
+ api_key_assigned: api_key = an_api_key
+ end
+
+ set_api_secret (an_api_secret: like api_secret)
+ -- Assign `api_secret' with `an_api_secret'.
+ do
+ api_secret := an_api_secret
+ ensure
+ api_secret_assigned: api_secret = an_api_secret
+ end
+
+ set_name (a_name: like name)
+ -- Assign `name' with `a_name'.
+ do
+ name := a_name
+ ensure
+ name_assigned: name = a_name
+ end
+
+ set_id (an_id: like id)
+ -- Assign `id' with `an_id'.
+ do
+ id := an_id
+ ensure
+ id_assigned: id = an_id
+ end
+
+end
diff --git a/modules/login/cms_oauth_20_workflow.e b/modules/login/cms_oauth_20_workflow.e
new file mode 100644
index 0000000..876aea4
--- /dev/null
+++ b/modules/login/cms_oauth_20_workflow.e
@@ -0,0 +1,133 @@
+note
+ description: "OAuth workflow"
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_OAUTH_20_WORKFLOW
+
+inherit
+
+ SHARED_LOGGER
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_host: READABLE_STRING_32; a_consumer: CMS_OAUTH_20_CONSUMER)
+ -- Create an object with the host `a_host'.
+ do
+ initilize (a_consumer)
+ create config.make_default (a_consumer.api_key, a_consumer.api_secret)
+ config.set_callback (a_host + "/account/"+ a_consumer.callback_name)
+ config.set_scope (a_consumer.scope)
+ --Todo create a generic OAUTH_20_GENERIC_API
+ create oauth_api.make (a_consumer.endpoint, a_consumer.authorize_url, a_consumer.extractor)
+ api_service := oauth_api.create_service (config)
+ end
+
+ initilize (a_consumer: CMS_OAUTH_20_CONSUMER)
+ do
+ --Use configuration values if any if not defaul
+ api_key := a_consumer.api_key
+ api_secret := a_consumer.api_secret
+ scope := a_consumer.scope
+ protected_resource_url := a_consumer.protected_resource_url
+ end
+
+feature -- Access
+
+ authorization_url: detachable READABLE_STRING_32
+ -- Obtain the Authorization URL.
+ do
+ -- Obtain the Authorization URL
+ write_debug_log (generator + ".authorization_url Fetching the Authorization URL..!")
+ if attached api_service.authorization_url (empty_token) as l_authorization_url then
+ write_debug_log (generator + ".authorization_url: Got the Authorization URL!")
+ write_debug_log (generator + ".authorization_url:" + l_authorization_url)
+ Result := l_authorization_url.as_string_32
+ end
+ end
+
+ sign_request (a_code: READABLE_STRING_32)
+ -- Sign request with code `a_code'.
+ --! To get the code `a_code' you need to do a request
+ --! using the authorization_url
+ local
+ request: OAUTH_REQUEST
+ do
+ -- Get the access token.
+ write_debug_log (generator + ".sign_request Fetching the access token with code [" + a_code + "]")
+ access_token := api_service.access_token_post (empty_token, create {OAUTH_VERIFIER}.make (a_code))
+ if attached access_token as l_access_token then
+ write_debug_log (generator + ".sign_request Got the Access Token [" + l_access_token.debug_output + "]")
+ -- Get the user email
+ --! at the moment the scope is mail, but we can change it to get more information.
+ create request.make ("GET", protected_resource_url)
+ request.add_header ("Authorization", "Bearer " + l_access_token.token)
+ api_service.sign_request (l_access_token, request)
+ if attached {OAUTH_RESPONSE} request.execute as l_response then
+ write_debug_log (generator + ".sign_request Sign_request response [" + l_response.status.out + "]")
+ if attached l_response.body as l_body then
+ user_profile := l_body
+ write_debug_log (generator + ".sign_request User profile [" + l_body + "]")
+ end
+ end
+ end
+ end
+
+ user_email: detachable READABLE_STRING_32
+ -- Retrieve user email if any.
+ local
+ l_json: JSON_CONFIG
+ do
+ if attached user_profile as l_profile then
+ create l_json.make_from_string (l_profile)
+ if
+ attached {JSON_ARRAY} l_json.item ("emails") as l_array and then
+ attached {JSON_OBJECT} l_array.i_th (1) as l_object and then
+ attached {JSON_STRING} l_object.item ("value") as l_email
+ then
+ Result := l_email.item
+ elseif attached {JSON_STRING} l_json.item ("email") as l_email then
+ Result := l_email.unescaped_string_32
+ end
+ end
+ end
+
+feature -- Access
+
+ access_token: detachable OAUTH_TOKEN
+ -- JSON representing the access token.
+
+ user_profile: detachable READABLE_STRING_32
+ -- JSON representing the user profiles.
+
+feature {NONE} -- Implementation
+
+ oauth_api: CMS_OAUTH_20_GENERIC_API
+ -- OAuth 2.0 Google API.
+
+ config: OAUTH_CONFIG
+ -- configuration.
+
+ api_service: OAUTH_SERVICE_I
+ -- Service.
+
+ api_key: STRING
+ -- public key.
+
+ api_secret: STRING
+ -- secret key.
+
+ scope: STRING
+ -- api scope to access protected resources.
+
+ protected_resource_url: STRING
+ -- Resource url.
+
+ empty_token: detachable OAUTH_TOKEN
+ -- fake token.
+
+end
diff --git a/modules/login/filter/cms_oauth_20_filter.e b/modules/login/filter/cms_oauth_20_filter.e
new file mode 100644
index 0000000..ef250ee
--- /dev/null
+++ b/modules/login/filter/cms_oauth_20_filter.e
@@ -0,0 +1,58 @@
+note
+ description: "Summary description for {CMS_OAUTH_20_FILTER}."
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_OAUTH_20_FILTER
+
+inherit
+ WSF_URI_TEMPLATE_HANDLER
+ CMS_HANDLER
+ rename
+ make as make_handler
+ end
+
+ WSF_FILTER
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_api: CMS_API; a_user_oauth_api: CMS_OAUTH_20_API)
+ do
+ make_handler (a_api)
+ user_oauth_api := a_user_oauth_api
+ end
+
+ user_oauth_api: CMS_OAUTH_20_API
+
+feature -- Basic operations
+
+ execute (req: WSF_REQUEST; res: WSF_RESPONSE)
+ -- Execute the filter.
+ local
+ do
+ api.logger.put_debug (generator + ".execute ", Void)
+-- if attached req.raw_header_data as l_raw_data then
+-- api.logger.put_debug (generator + ".execute " + utf.escaped_utf_32_string_to_utf_8_string_8 (l_raw_data), Void)
+-- end
+ -- A valid user
+ if
+ attached {WSF_STRING} req.cookie ({CMS_AUTHENTICATION_CONSTANTS}.oauth_session) as l_roc_auth_session_token
+ then
+ if attached {CMS_USER} user_oauth_api.user_oauth2_without_consumer_by_token (l_roc_auth_session_token.value) as l_user then
+ set_current_user (req, l_user)
+ execute_next (req, res)
+ else
+ api.logger.put_error (generator + ".execute login_valid failed for: " + l_roc_auth_session_token.value , Void)
+ execute_next (req, res)
+ end
+ else
+ api.logger.put_debug (generator + ".execute without authentication", Void)
+ execute_next (req, res)
+ end
+ end
+
+end
diff --git a/modules/login/login-safe.ecf b/modules/login/login-safe.ecf
new file mode 100644
index 0000000..36d6bff
--- /dev/null
+++ b/modules/login/login-safe.ecf
@@ -0,0 +1,30 @@
+
+
+
+
+
+ /.git$
+ /EIFGENs$
+ /.svn$
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/login/persistence/cms_oauth_20_generic_api.e b/modules/login/persistence/cms_oauth_20_generic_api.e
new file mode 100644
index 0000000..552d2ea
--- /dev/null
+++ b/modules/login/persistence/cms_oauth_20_generic_api.e
@@ -0,0 +1,94 @@
+note
+ description: "Generic OAUTH2 API"
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_OAUTH_20_GENERIC_API
+
+inherit
+
+ OAUTH_20_API
+ redefine
+ access_token_extractor,
+ access_token_verb
+ end
+
+create
+ make
+
+feature {NONE} -- Initialize
+
+ make (a_endpoint: READABLE_STRING_8; a_authorize_url: READABLE_STRING_8; a_extractor: READABLE_STRING_8)
+ do
+ endpoint := a_endpoint
+ authorize_url := a_authorize_url
+ extractor := a_extractor
+ ensure
+ endpoint_set: endpoint = a_endpoint
+ authorize_url_set: authorize_url = a_authorize_url
+ extractor_set: extractor = a_authorize_url
+ end
+
+ endpoint: READABLE_STRING_8
+ -- Url that receives the access token request.
+
+ authorize_url: READABLE_STRING_8
+ --
+
+ extractor: READABLE_STRING_8
+ -- text, json
+
+feature -- Access
+
+ access_token_extractor: ACCESS_TOKEN_EXTRACTOR
+ -- Return token extractor, by default TOKEN_EXTRACTOR_20.
+ do
+ if extractor.is_case_insensitive_equal_general ("json") then
+ create {JSON_TOKEN_EXTRACTOR} Result
+ else
+ create {TOKEN_EXTRACTOR_20} Result
+ end
+ end
+
+ access_token_verb: STRING_8
+ do
+ Result := "POST"
+ end
+
+ access_token_endpoint: STRING_8
+ -- Url that receives the access token request
+ do
+ create Result.make_from_string (endpoint)
+ end
+
+ authorization_url (config: OAUTH_CONFIG): detachable STRING_8
+ -- Url where you should redirect your users to authneticate
+ local
+ l_result: STRING_8
+ do
+ if attached config.scope as l_scope then
+ create l_result.make_from_string (authorize_url + SCOPED_AUTHORIZE_URL)
+ l_result.replace_substring_all ("$CLIENT_ID", config.api_key.as_string_8)
+ if attached config.callback as l_callback then
+ l_result.replace_substring_all ("$REDIRECT_URI", (create {OAUTH_ENCODER}).encoded_string (l_callback.as_string_8))
+ end
+ if attached config.callback as l_callback then
+ l_result.replace_substring_all ("$SCOPE", (create {OAUTH_ENCODER}).encoded_string (l_scope.as_STRING_8))
+ Result := l_result
+ end
+ else
+ create l_result.make_from_string (authorize_url + SCOPED_AUTHORIZE_URL)
+ l_result.replace_substring_all ("$CLIENT_ID", config.api_key.as_string_8)
+ if attached config.callback as l_callback then
+ l_result.replace_substring_all ("$REDIRECT_URI", (create {OAUTH_ENCODER}).encoded_string (l_callback.as_string_8))
+ end
+ end
+ end
+
+feature -- Implementation
+
+ Scoped_authorize_url: STRING = "&scope=$SCOPE";
+
+
+end
diff --git a/modules/login/persistence/cms_oauth_20_storage_i.e b/modules/login/persistence/cms_oauth_20_storage_i.e
new file mode 100644
index 0000000..2357707
--- /dev/null
+++ b/modules/login/persistence/cms_oauth_20_storage_i.e
@@ -0,0 +1,66 @@
+note
+ description: "[
+ API to handle OAUTH storage
+ ]"
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ CMS_OAUTH_20_STORAGE_I
+
+inherit
+ SHARED_LOGGER
+
+feature -- Error Handling
+
+ error_handler: ERROR_HANDLER
+ -- Error handler.
+ deferred
+ end
+
+feature -- Access: Users
+
+ user_oauth2_by_id (a_uid: like {CMS_USER}.id; a_consumer_table: READABLE_STRING_32): detachable CMS_USER
+ -- Retrieve a user by id `a_uid' for the consumer `a_consumer', if aby.
+ deferred
+ end
+
+ user_oauth2_by_token (a_token: READABLE_STRING_32; a_consumer_table: READABLE_STRING_32): detachable CMS_USER
+ -- Retrieve a user by token `a_token' for the consumer `a_consumer'.
+ deferred
+ end
+
+ user_oauth2_without_consumer_by_token (a_token: READABLE_STRING_32 ): detachable CMS_USER
+ -- Retrieve a user by token `a_token' searching in all the registered consumers in the system.
+ deferred
+ end
+
+feature -- Access: Consumers
+
+ oauth2_consumers: LIST [STRING]
+ deferred
+ end
+
+ oauth_consumer_by_name (a_name: READABLE_STRING_8): detachable CMS_OAUTH_20_CONSUMER
+ -- Retrieve a consumer by name `a_name', if any.
+ deferred
+ end
+
+ oauth_consumer_by_callback (a_callback: READABLE_STRING_8): detachable CMS_OAUTH_20_CONSUMER
+ -- Retrieve a consumer by callback `a_callback', if any.
+ deferred
+ end
+
+feature -- Change: User Oauth2
+
+ new_user_oauth2 (a_token: READABLE_STRING_32; a_user_profile: READABLE_STRING_32; a_user: CMS_USER; a_consumer_table: READABLE_STRING_32)
+ -- Add a new user with oauth2 authentication.
+ deferred
+ end
+
+ update_user_oauth2 (a_token: READABLE_STRING_32; a_user_profile: READABLE_STRING_32; a_user: CMS_USER; a_consumer_table: READABLE_STRING_32 )
+ -- Update user `a_user' with oauth2 authentication.
+ deferred
+ end
+
+end
diff --git a/modules/login/persistence/cms_oauth_20_storage_null.e b/modules/login/persistence/cms_oauth_20_storage_null.e
new file mode 100644
index 0000000..d15d092
--- /dev/null
+++ b/modules/login/persistence/cms_oauth_20_storage_null.e
@@ -0,0 +1,69 @@
+note
+ description: "Summary description for {CMS_OAUTH_20_STORAGE_NULL}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_OAUTH_20_STORAGE_NULL
+
+inherit
+
+ CMS_OAUTH_20_STORAGE_I
+
+
+feature -- Error handler
+
+ error_handler: ERROR_HANDLER
+ -- Error handler.
+ do
+ create Result.make
+ end
+
+feature -- Access: Users
+
+ user_oauth2_by_id (a_uid: like {CMS_USER}.id; a_consumer_table: READABLE_STRING_32): detachable CMS_USER
+ -- CMS User with Oauth credential by id if any.
+ do
+ end
+
+ user_oauth2_by_token (a_token: READABLE_STRING_32; a_consumer_table: READABLE_STRING_32): detachable CMS_USER
+ -- -- CMS User with Oauth credential by access token `a_token' if any.
+ do
+ end
+
+ user_oauth2_without_consumer_by_token (a_token: READABLE_STRING_32 ): detachable CMS_USER
+ do
+ end
+
+feature -- Access: Consumers
+
+ oauth2_consumers: LIST [STRING]
+ do
+ create {ARRAYED_LIST[STRING]} Result.make (0)
+ end
+
+ oauth_consumer_by_name (a_name: READABLE_STRING_8): detachable CMS_OAUTH_20_CONSUMER
+ -- Retrieve a consumer by name `a_name', if any.
+ do
+ end
+
+ oauth_consumer_by_callback (a_callback: READABLE_STRING_8): detachable CMS_OAUTH_20_CONSUMER
+ -- Retrieve a consumer by callback `a_callback', if any.
+ do
+ end
+
+feature -- Change: User Oauth2
+
+ new_user_oauth2 (a_token: READABLE_STRING_32; a_user_profile: READABLE_STRING_32; a_user: CMS_USER; a_consumer_table: READABLE_STRING_32)
+ -- Add a new user with oauth2 authentication.
+ do
+ end
+
+ update_user_oauth2 (a_token: READABLE_STRING_32; a_user_profile: READABLE_STRING_32; a_user: CMS_USER; a_consumer_table: READABLE_STRING_32 )
+ -- Update user `a_user' with oauth2 authentication.
+ do
+ end
+
+
+end
diff --git a/modules/login/persistence/cms_oauth_20_storage_sql.e b/modules/login/persistence/cms_oauth_20_storage_sql.e
new file mode 100644
index 0000000..ffbad07
--- /dev/null
+++ b/modules/login/persistence/cms_oauth_20_storage_sql.e
@@ -0,0 +1,293 @@
+note
+ description: "Summary description for {CMS_OAUTH_20_STORAGE_SQL}."
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ CMS_OAUTH_20_STORAGE_SQL
+
+inherit
+ CMS_OAUTH_20_STORAGE_I
+
+ CMS_PROXY_STORAGE_SQL
+
+ CMS_OAUTH_20_STORAGE_I
+
+ CMS_STORAGE_SQL_I
+
+ REFACTORING_HELPER
+
+create
+ make
+
+feature -- Access User Outh
+
+ user_oauth2_without_consumer_by_token (a_token: READABLE_STRING_32 ): detachable CMS_USER
+ -- Retrieve a user by token `a_token' searching in all the registered consumers in the system.
+ local
+ l_list: LIST[STRING]
+ do
+ error_handler.reset
+ write_information_log (generator + ".user_oauth2_without_consumer_by_token")
+ l_list := oauth2_consumers
+ from
+ l_list.start
+ until
+ l_list.after or attached Result
+ loop
+ if attached {CMS_USER} user_oauth2_by_token (a_token, l_list.item) as l_user then
+ Result := l_user
+ end
+ l_list.forth
+ end
+ end
+
+ user_oauth2_by_id (a_uid: like {CMS_USER}.id; a_consumer: READABLE_STRING_32): detachable CMS_USER
+ --
+ local
+ l_parameters: STRING_TABLE [detachable ANY]
+ l_string: STRING
+ do
+ error_handler.reset
+ write_information_log (generator + ".user_oauth2_by_id")
+ create l_parameters.make (1)
+ l_parameters.put (a_uid, "uid")
+ create l_string.make_from_string (select_user_oauth2_template_by_id)
+ l_string.replace_substring_all ("$table_name", sql_table_name (a_consumer))
+ sql_query (l_string, 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_oauth2_by_token (a_token: READABLE_STRING_32; a_consumer: READABLE_STRING_32): detachable CMS_USER
+ --
+ local
+ l_parameters: STRING_TABLE [detachable ANY]
+ l_string: STRING
+ do
+ error_handler.reset
+ write_information_log (generator + ".user_by_oauth2_token")
+ create l_parameters.make (1)
+ l_parameters.put (a_token, "token")
+ create l_string.make_from_string (select_user_by_oauth2_template_token)
+ l_string.replace_substring_all ("$table_name", sql_table_name (a_consumer))
+ sql_query (l_string, l_parameters)
+ if sql_rows_count = 1 then
+ Result := fetch_user
+ else
+ check no_more_than_one: sql_rows_count = 0 end
+ end
+ end
+
+
+feature --Access: Consumers
+
+ oauth2_consumers: LIST[STRING]
+ -- Return a list of consumers, or empty
+ do
+ error_handler.reset
+ create {ARRAYED_LIST[STRING]}Result.make (0)
+ write_information_log (generator + ".user_by_oauth2_token")
+ sql_query (Sql_oauth_consumers, Void)
+ if not has_error then
+ from
+ sql_start
+ until
+ sql_after
+ loop
+ if attached sql_read_string (1) as l_name then
+ Result.force (l_name)
+ end
+ sql_forth
+ end
+ end
+ end
+
+ oauth_consumer_by_name (a_name: READABLE_STRING_8): detachable CMS_OAUTH_20_CONSUMER
+ -- Retrieve a consumer by name `a_name', if any.
+ local
+ l_parameters: STRING_TABLE [detachable ANY]
+ do
+ error_handler.reset
+ write_information_log (generator + ".oauth_consumer_by_name")
+ create l_parameters.make (1)
+ l_parameters.put (a_name, "name")
+ sql_query (sql_oauth_consumer_name, l_parameters)
+ if sql_rows_count = 1 then
+ Result := fetch_consumer
+ else
+ check no_more_than_one: sql_rows_count = 0 end
+ end
+ end
+
+ oauth_consumer_by_callback (a_callback: READABLE_STRING_8): detachable CMS_OAUTH_20_CONSUMER
+ -- Retrieve a consumer by callback `a_callback', if any.
+ local
+ l_parameters: STRING_TABLE [detachable ANY]
+ do
+ error_handler.reset
+ write_information_log (generator + ".oauth_consumer_by_callback")
+ create l_parameters.make (1)
+ l_parameters.put (a_callback, "name")
+ sql_query (sql_oauth_consumer_callback, l_parameters)
+ if sql_rows_count = 1 then
+ Result := fetch_consumer
+ else
+ check no_more_than_one: sql_rows_count = 0 end
+ end
+ end
+
+feature -- Change: User OAuth
+
+ new_user_oauth2 (a_token: READABLE_STRING_32; a_user_profile: READABLE_STRING_32; a_user: CMS_USER; a_consumer: READABLE_STRING_32)
+ -- Add a new user with oauth2 authentication.
+ -- .
+ local
+ l_parameters: STRING_TABLE [detachable ANY]
+ l_string: STRING
+ do
+ error_handler.reset
+ sql_begin_transaction
+
+ write_information_log (generator + ".new_user_oauth2")
+ create l_parameters.make (4)
+ l_parameters.put (a_user.id, "uid")
+ l_parameters.put (a_token, "token")
+ l_parameters.put (a_user_profile, "profile")
+ l_parameters.put (create {DATE_TIME}.make_now_utc, "utc_date")
+
+ create l_string.make_from_string (sql_insert_oauth2_template)
+ l_string.replace_substring_all ("$table_name", sql_table_name (a_consumer))
+ sql_change (l_string, l_parameters)
+ sql_commit_transaction
+ end
+
+ update_user_oauth2 (a_token: READABLE_STRING_32; a_user_profile: READABLE_STRING_32; a_user: CMS_USER; a_consumer: READABLE_STRING_32 )
+ -- Update user `a_user' with oauth2 authentication.
+ --
+ local
+ l_parameters: STRING_TABLE [detachable ANY]
+ l_string: STRING
+ do
+ error_handler.reset
+ sql_begin_transaction
+
+ write_information_log (generator + ".new_user_oauth2")
+ create l_parameters.make (4)
+ l_parameters.put (a_user.id, "uid")
+ l_parameters.put (a_token, "token")
+ l_parameters.put (a_user_profile, "profile")
+
+ create l_string.make_from_string (sql_update_oauth2_template)
+ l_string.replace_substring_all ("$table_name", sql_table_name (a_consumer))
+ sql_change (l_string, l_parameters)
+ sql_commit_transaction
+ end
+
+feature {NONE} -- Implementation OAuth Consumer
+
+ fetch_consumer: detachable CMS_OAUTH_20_CONSUMER
+ do
+ if attached sql_read_integer_64 (1) as l_id then
+ create Result
+ Result.set_id (l_id)
+ end
+ if Result /= Void then
+ if attached sql_read_string_32 (2) as l_name then
+ Result.set_name (l_name)
+ end
+ if attached sql_read_string_32 (3) as l_api_secret then
+ Result.set_api_secret (l_api_secret)
+ end
+ if attached sql_read_string_32 (4) as l_api_key then
+ Result.set_api_key (l_api_key)
+ end
+ if attached sql_read_string_32 (5) as l_scope then
+ Result.set_scope (l_scope)
+ end
+ if attached sql_read_string_32 (6) as l_resource_url then
+ Result.set_protected_resource_url (l_resource_url)
+ end
+ if attached sql_read_string_32 (7) as l_callback_name then
+ Result.set_callback_name (l_callback_name)
+ end
+ if attached sql_read_string_32 (8) as l_extractor then
+ Result.set_extractor (l_extractor)
+ end
+ if attached sql_read_string_32 (9) as l_authorize_url then
+ Result.set_authorize_url (l_authorize_url)
+ end
+ if attached sql_read_string_32 (10) as l_endpoint then
+ Result.set_endpoint (l_endpoint)
+ end
+ end
+ end
+feature {NONE} -- Implementation: User
+
+ fetch_user: detachable CMS_USER
+ local
+ l_id: INTEGER_64
+ l_name: detachable READABLE_STRING_32
+ do
+ if attached sql_read_integer_32 (1) as i then
+ l_id := i
+ end
+ if attached sql_read_string_32 (2) as s and then not s.is_whitespace then
+ l_name := s
+ end
+
+ if l_name /= Void then
+ create Result.make (l_name)
+ if l_id > 0 then
+ Result.set_id (l_id)
+ end
+ elseif l_id > 0 then
+ create Result.make_with_id (l_id)
+ end
+
+ if Result /= Void then
+ if attached sql_read_string (3) as l_password then
+ -- FIXME: should we return the password here ???
+ Result.set_hashed_password (l_password)
+ end
+ 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
+ end
+
+feature -- {NONE} User OAuth2
+
+ sql_table_name (a_consumer: READABLE_STRING_8): STRING_8
+ do
+ Result := Sql_table_prefix.twin
+ Result.append (a_consumer)
+ end
+
+ Select_user_by_oauth2_template_token: STRING = "SELECT u.* FROM users as u JOIN $table_name as og ON og.uid = u.uid and og.access_token = :token;"
+
+ Select_user_oauth2_template_by_id: STRING = "SELECT u.* FROM users as u JOIN $table_name as og ON og.uid = u.uid and og.uid = :uid;"
+
+ Sql_insert_oauth2_template: STRING = "INSERT INTO $table_name (uid, access_token, details, created) VALUES (:uid, :token, :profile, :utc_date);"
+
+ Sql_update_oauth2_template: STRING = "UPDATE $table_name SET access_token = :token, details = :profile WHERE uid =:uid;"
+
+ Sql_oauth_consumers: STRING = "SELECT name FROM oauth2_consumers";
+
+ Sql_table_prefix: STRING = "oauth2_"
+
+feature -- {NONE} Consumer
+
+ Sql_oauth_consumer_callback: STRING ="SELECT * FROM oauth2_consumers where callback_name =:name;"
+
+ Sql_oauth_consumer_name: STRING ="SELECT * FROM oauth2_consumers where name =:name;"
+
+end
diff --git a/src/persistence/sql/cms_storage_sql_builder.e b/src/persistence/sql/cms_storage_sql_builder.e
index a1bec3b..6323b86 100644
--- a/src/persistence/sql/cms_storage_sql_builder.e
+++ b/src/persistence/sql/cms_storage_sql_builder.e
@@ -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
diff --git a/src/persistence/sql/cms_storage_sql_i.e b/src/persistence/sql/cms_storage_sql_i.e
index 6a1e903..6c779fa 100644
--- a/src/persistence/sql/cms_storage_sql_i.e
+++ b/src/persistence/sql/cms_storage_sql_i.e
@@ -76,7 +76,7 @@ feature -- Operation
i := a_sql_statement.index_of (':', i)
if i = 0 then
i := n -- exit
- else
+ elseif a_sql_statement.at (i-1).is_equal ('%'') or else a_sql_statement.at (i-1).is_equal ('%"') or else a_sql_statement.at (i-1).is_equal (' ') or else a_sql_statement.at (i-1).is_equal ('=') then
from
j := i + 1
until
@@ -124,6 +124,30 @@ feature -- Operation
feature -- Helper
+ sql_execute_file_script_with_params (a_path: PATH; a_params: detachable STRING_TABLE [detachable ANY])
+ -- Execute SQL script from `a_path' and with params `a_params'.
+ local
+ f: PLAIN_TEXT_FILE
+ sql: STRING
+ do
+ create f.make_with_path (a_path)
+ if f.exists and then f.is_access_readable then
+ create sql.make (f.count)
+ f.open_read
+ from
+ f.start
+ until
+ f.exhausted or f.end_of_file
+ loop
+ f.read_stream_thread_aware (1_024)
+ sql.append (f.last_string)
+ end
+ f.close
+ sql_execute_script_with_params (sql, a_params)
+ end
+ end
+
+
sql_execute_file_script (a_path: PATH)
-- Execute SQL script from `a_path'.
local
@@ -181,6 +205,14 @@ feature -- Helper
end
end
+ sql_execute_script_with_params (a_sql_script: STRING; a_params: detachable STRING_TABLE [detachable ANY])
+ -- Execute SQL script.
+ -- i.e: multiple SQL statements.
+ do
+ reset_error
+ sql_change (a_sql_script, a_params)
+ end
+
sql_table_exists (a_table_name: READABLE_STRING_8): BOOLEAN
-- Does table `a_table_name' exists?
do
@@ -364,6 +396,26 @@ feature {NONE} -- Implementation
loop
c := a_script[i]
inspect c
+ when '-' then
+ if i < n and then a_script[i + 1] = '-' then
+ -- Commented line "--" until New Line
+ j := a_script.index_of ('%N', i)
+ if j > 0 then
+ i := j
+ else
+ i := n
+ end
+ end
+ when '/' then
+ if i < n and then a_script[i + 1] = '*' then
+ -- Commented text "/*" until closing "*/"
+ j := a_script.substring_index ("*/", i)
+ if j > 0 then
+ i := j
+ else
+ i := n
+ end
+ end
when '`', '"', '%'' then
from
j := i
diff --git a/src/persistence/user/cms_user_storage_i.e b/src/persistence/user/cms_user_storage_i.e
index e31fd14..bd82eda 100644
--- a/src/persistence/user/cms_user_storage_i.e
+++ b/src/persistence/user/cms_user_storage_i.e
@@ -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,28 @@ feature -- Change: roles and permissions
deferred
end
+feature -- Change: User activation
+
+ save_activation (a_token: READABLE_STRING_32; a_id: INTEGER_64)
+ -- .
+ deferred
+ end
+
+ remove_activation (a_token: READABLE_STRING_32)
+ -- .
+ deferred
+ end
+
+feature -- Change: User password recovery
+
+ save_password (a_token: READABLE_STRING_32; a_id: INTEGER_64)
+ -- .
+ deferred
+ end
+
+ remove_password (a_token: READABLE_STRING_32)
+ -- .
+ deferred
+ end
+
end
diff --git a/src/persistence/user/cms_user_storage_null.e b/src/persistence/user/cms_user_storage_null.e
index 7422618..283fe5b 100644
--- a/src/persistence/user/cms_user_storage_null.e
+++ b/src/persistence/user/cms_user_storage_null.e
@@ -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,28 @@ feature -- Change: roles and permissions
do
end
+feature -- Change: User activation
+
+ save_activation (a_token: READABLE_STRING_32; a_id: INTEGER_64)
+ -- .
+ do
+ end
+
+ remove_activation (a_token: READABLE_STRING_32)
+ -- .
+ do
+ end
+
+feature -- Change: User password recovery
+
+ save_password (a_token: READABLE_STRING_32; a_id: INTEGER_64)
+ -- .
+ do
+ end
+
+ remove_password (a_token: READABLE_STRING_32)
+ -- .
+ do
+ end
+
end
diff --git a/src/persistence/user/cms_user_storage_sql_i.e b/src/persistence/user/cms_user_storage_sql_i.e
index 54c26a0..58e4723 100644
--- a/src/persistence/user/cms_user_storage_sql_i.e
+++ b/src/persistence/user/cms_user_storage_sql_i.e
@@ -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,107 @@ 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)
+ --
+ 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)
+ -- .
+ 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)
+ --
+ 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)
+ -- .
+ 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 +626,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 +691,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 +725,32 @@ 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} -- User Password Recovery
+
+ 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
diff --git a/src/service/email/email_service.e b/src/service/email/email_service.e
new file mode 100644
index 0000000..833aeb6
--- /dev/null
+++ b/src/service/email/email_service.e
@@ -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
diff --git a/src/service/email/email_service_parameters.e b/src/service/email/email_service_parameters.e
new file mode 100644
index 0000000..c7d18e1
--- /dev/null
+++ b/src/service/email/email_service_parameters.e
@@ -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
diff --git a/src/service/user/cms_user_api.e b/src/service/user/cms_user_api.e
index 5f374df..8465f91 100644
--- a/src/service/user/cms_user_api.e
+++ b/src/service/user/cms_user_api.e
@@ -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