From 032cc5bdcb4b4f1ab83ea5dfcf099e57d6aae371 Mon Sep 17 00:00:00 2001 From: jvelilla Date: Fri, 5 Jun 2015 18:39:27 -0300 Subject: [PATCH] Updated CMS with Login Module. -- The module handle basic_auth (at the moment). -- Handle login, logout, register user, activate/reactivate an account, password recovery. -- Send notification emails. CMS Updates -- Added a new service: email. -- Updated Basic Auth Module to handle logout based on the browser type. -- Updated persistence layer to save and remove and query activation token and password token. -- Updated CMS_USER to handle status {active, not_active, trashed}. -- Updated MySQL scripts to be in sync with SQLite scripts --- cms-safe.ecf | 1 + cms.ecf | 1 + examples/demo/demo-safe.ecf | 1 + examples/demo/site/config/login/login.json | 5 + examples/demo/site/scripts/user.sql | 16 + .../themes/bootstrap/assets/js/roc_auth.js | 18 +- .../modules/login/templates/block_login.tpl | 32 + .../login/templates/block_new_password.tpl | 16 + .../login/templates/block_post_password.tpl | 3 + .../login/templates/block_post_reactivate.tpl | 3 + .../login/templates/block_post_register.tpl | 3 + .../login/templates/block_post_reset.tpl | 3 + .../login/templates/block_reactivate.tpl | 19 + .../login/templates/block_register.tpl | 28 + .../login/templates/block_reset_password.tpl | 28 + examples/demo/src/ewf_roc_server.e | 5 + library/model/src/user/cms_user.e | 62 ++ library/persistence/mysql/scripts/core.sql | 30 + .../mysql/scripts/create_database.sql | 163 ----- library/persistence/mysql/scripts/node.sql | 24 + library/persistence/mysql/scripts/schema.sql | 72 -- library/persistence/mysql/scripts/tables.sql | 14 - .../persistence/mysql/scripts/triggers.sql | 8 - library/persistence/mysql/scripts/user.sql | 66 ++ modules/basic_auth/basic_auth_module.e | 14 +- .../handler/basic_auth_logoff_handler.e | 55 +- modules/login/login-safe.ecf | 23 + modules/login/login_email_service.e | 45 ++ .../login/login_email_service_parameters.e | 73 ++ modules/login/login_module.e | 675 ++++++++++++++++++ src/persistence/sql/cms_storage_sql_builder.e | 4 + src/persistence/user/cms_user_storage_i.e | 37 + src/persistence/user/cms_user_storage_null.e | 31 + src/persistence/user/cms_user_storage_sql_i.e | 180 ++++- src/service/email/email_service.e | 102 +++ src/service/email/email_service_parameters.e | 20 + src/service/user/cms_user_api.e | 57 ++ 37 files changed, 1660 insertions(+), 277 deletions(-) create mode 100644 examples/demo/site/config/login/login.json create mode 100644 examples/demo/site/themes/bootstrap/modules/login/templates/block_login.tpl create mode 100644 examples/demo/site/themes/bootstrap/modules/login/templates/block_new_password.tpl create mode 100644 examples/demo/site/themes/bootstrap/modules/login/templates/block_post_password.tpl create mode 100644 examples/demo/site/themes/bootstrap/modules/login/templates/block_post_reactivate.tpl create mode 100644 examples/demo/site/themes/bootstrap/modules/login/templates/block_post_register.tpl create mode 100644 examples/demo/site/themes/bootstrap/modules/login/templates/block_post_reset.tpl create mode 100644 examples/demo/site/themes/bootstrap/modules/login/templates/block_reactivate.tpl create mode 100644 examples/demo/site/themes/bootstrap/modules/login/templates/block_register.tpl create mode 100644 examples/demo/site/themes/bootstrap/modules/login/templates/block_reset_password.tpl create mode 100644 library/persistence/mysql/scripts/core.sql delete mode 100644 library/persistence/mysql/scripts/create_database.sql create mode 100644 library/persistence/mysql/scripts/node.sql delete mode 100644 library/persistence/mysql/scripts/schema.sql delete mode 100644 library/persistence/mysql/scripts/tables.sql delete mode 100644 library/persistence/mysql/scripts/triggers.sql create mode 100644 library/persistence/mysql/scripts/user.sql create mode 100644 modules/login/login-safe.ecf create mode 100644 modules/login/login_email_service.e create mode 100644 modules/login/login_email_service_parameters.e create mode 100644 modules/login/login_module.e create mode 100644 src/service/email/email_service.e create mode 100644 src/service/email/email_service_parameters.e 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 4b9f7d6..a4bc8d3 100644 --- a/cms.ecf +++ b/cms.ecf @@ -28,6 +28,7 @@ + diff --git a/examples/demo/demo-safe.ecf b/examples/demo/demo-safe.ecf index c760a4f..a5c8f59 100644 --- a/examples/demo/demo-safe.ecf +++ b/examples/demo/demo-safe.ecf @@ -15,6 +15,7 @@ + diff --git a/examples/demo/site/config/login/login.json b/examples/demo/site/config/login/login.json new file mode 100644 index 0000000..5b44da3 --- /dev/null +++ b/examples/demo/site/config/login/login.json @@ -0,0 +1,5 @@ +{ + "email": "webmaster@example.com", + "subjet": "Thank you for regitering with us", + "smtp": "127.0.0.1" +} diff --git a/examples/demo/site/scripts/user.sql b/examples/demo/site/scripts/user.sql index 707dbc2..3868949 100644 --- a/examples/demo/site/scripts/user.sql +++ b/examples/demo/site/scripts/user.sql @@ -31,4 +31,20 @@ 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") +); + COMMIT; 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..495fdd8 --- /dev/null +++ b/examples/demo/site/themes/bootstrap/modules/login/templates/block_login.tpl @@ -0,0 +1,32 @@ +
+ {if isset="$user"} +

Logout

+ {/if} + {unless isset="$user"} +

Login or Register

+
+
+
+
+ + +
+ +
+ + +
+ + +
+
+
+
+ +
+ {/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..84b5de0 --- /dev/null +++ b/examples/demo/site/themes/bootstrap/modules/login/templates/block_new_password.tpl @@ -0,0 +1,16 @@ +
+
+
+ Require new password +
+ + + {if isset="$error_email"} + {$error_email/}
+ {/if} +
+
+ +
+
+
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..01a7960 --- /dev/null +++ b/examples/demo/site/themes/bootstrap/modules/login/templates/block_reactivate.tpl @@ -0,0 +1,19 @@ +
+
+
+ Reactivate Form +
+ + + {if isset="$error_email"} + {$error_email/}
+ {/if} +
+ {if isset="$is_active"} + {$is_active/}
+ {/if} +
+ +
+
+
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..522bde9 --- /dev/null +++ b/examples/demo/site/themes/bootstrap/modules/login/templates/block_register.tpl @@ -0,0 +1,28 @@ +
+
+
+ Register Form +
+ + + {if isset="$error_name"} + {$error_name/}
+ {/if} +
+
+ + +
+
+ + + {if isset="$error_email"} + {$error_email/}
+ {/if} +
+ + + +
+
+
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..aa2ee81 --- /dev/null +++ b/examples/demo/site/themes/bootstrap/modules/login/templates/block_reset_password.tpl @@ -0,0 +1,28 @@ +
+
+
+ Generate New Password Form +
+ + + {if isset="$error_token"} + {$error_token/}
+ {/if} +
+
+ + +
+
+ + +
+ + + {if isset="$error_password"} + {$error_password/}
+ {/if} + +
+
+
diff --git a/examples/demo/src/ewf_roc_server.e b/examples/demo/src/ewf_roc_server.e index d284825..86a7dd4 100644 --- a/examples/demo/src/ewf_roc_server.e +++ b/examples/demo/src/ewf_roc_server.e @@ -135,6 +135,11 @@ feature -- CMS setup m.enable a_setup.register_module (m) + create {LOGIN_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/create_database.sql b/library/persistence/mysql/scripts/create_database.sql deleted file mode 100644 index a78dd4e..0000000 --- a/library/persistence/mysql/scripts/create_database.sql +++ /dev/null @@ -1,163 +0,0 @@ -SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0; -SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; -SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL,ALLOW_INVALID_DATES'; - --- ----------------------------------------------------- --- Schema mydb --- ----------------------------------------------------- --- ----------------------------------------------------- --- Schema cms_dev --- ----------------------------------------------------- -CREATE SCHEMA IF NOT EXISTS `cms_dev` DEFAULT CHARACTER SET latin1 ; -USE `cms_dev` ; - --- ----------------------------------------------------- --- Table `cms_dev`.`users` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `cms_dev`.`users` ( - `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, - `username` VARCHAR(100) NOT NULL, - `password` VARCHAR(100) NOT NULL, - `salt` VARCHAR(100) NOT NULL, - `email` VARCHAR(250) NOT NULL, - `creation_date` DATETIME NULL DEFAULT NULL, - `last_login_date` DATETIME NULL DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE INDEX `username` (`username` ASC)) -ENGINE = InnoDB -AUTO_INCREMENT = 2 -DEFAULT CHARACTER SET = latin1; - - --- ----------------------------------------------------- --- Table `cms_dev`.`nodes` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `cms_dev`.`nodes` ( - `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, - `publication_date` DATE NOT NULL, - `creation_date` DATE NOT NULL, - `modification_date` DATE NOT NULL, - `title` VARCHAR(255) NOT NULL, - `summary` TEXT NOT NULL, - `content` MEDIUMTEXT NOT NULL, - `author_id` INT(10) UNSIGNED NULL DEFAULT NULL, - `version` INT(10) UNSIGNED ZEROFILL NULL DEFAULT NULL, - `editor_id` INT(10) UNSIGNED NULL DEFAULT NULL, - PRIMARY KEY (`id`), - INDEX `fk_nodes_users1_idx` (`author_id` ASC), - INDEX `fk_nodes_users2_idx` (`editor_id` ASC), - CONSTRAINT `fk_nodes_users1` - FOREIGN KEY (`author_id`) - REFERENCES `cms_dev`.`users` (`id`) - ON DELETE NO ACTION - ON UPDATE NO ACTION, - CONSTRAINT `fk_nodes_users2` - FOREIGN KEY (`editor_id`) - REFERENCES `cms_dev`.`users` (`id`) - ON DELETE NO ACTION - ON UPDATE NO ACTION) -ENGINE = InnoDB -AUTO_INCREMENT = 11 -DEFAULT CHARACTER SET = latin1; - - --- ----------------------------------------------------- --- Table `cms_dev`.`roles` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `cms_dev`.`roles` ( - `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, - `role` VARCHAR(100) NOT NULL, - PRIMARY KEY (`id`), - UNIQUE INDEX `role` (`role` ASC)) -ENGINE = InnoDB -DEFAULT CHARACTER SET = latin1; - - --- ----------------------------------------------------- --- Table `cms_dev`.`permissions` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `cms_dev`.`permissions` ( - `id` INT(11) NOT NULL AUTO_INCREMENT, - `name` VARCHAR(45) NOT NULL, - `roles_id` INT(10) UNSIGNED NOT NULL, - PRIMARY KEY (`id`), - UNIQUE INDEX `name_UNIQUE` (`name` ASC), - INDEX `fk_permissions_roles1_idx` (`roles_id` ASC), - CONSTRAINT `fk_permissions_roles1` - FOREIGN KEY (`roles_id`) - REFERENCES `cms_dev`.`roles` (`id`) - ON DELETE NO ACTION - ON UPDATE NO ACTION) -ENGINE = InnoDB -DEFAULT CHARACTER SET = latin1; - - --- ----------------------------------------------------- --- Table `cms_dev`.`profiles` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `cms_dev`.`profiles` ( - `id` INT(11) NOT NULL AUTO_INCREMENT, - `key` VARCHAR(45) NOT NULL, - `value` VARCHAR(100) NULL DEFAULT NULL, - `users_id` INT(10) UNSIGNED NOT NULL, - PRIMARY KEY (`id`), - UNIQUE INDEX `key_UNIQUE` (`key` ASC), - INDEX `fk_profiles_users1_idx` (`users_id` ASC), - CONSTRAINT `fk_profiles_users1` - FOREIGN KEY (`users_id`) - REFERENCES `cms_dev`.`users` (`id`) - ON DELETE NO ACTION - ON UPDATE NO ACTION) -ENGINE = InnoDB -DEFAULT CHARACTER SET = latin1; - - --- ----------------------------------------------------- --- Table `cms_dev`.`users_nodes` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `cms_dev`.`users_nodes` ( - `users_id` INT(10) UNSIGNED NOT NULL, - `nodes_id` INT(10) UNSIGNED NOT NULL, - PRIMARY KEY (`users_id`, `nodes_id`), - INDEX `fk_users_has_nodes_nodes1_idx` (`nodes_id` ASC), - INDEX `fk_users_has_nodes_users_idx` (`users_id` ASC), - CONSTRAINT `fk_users_has_nodes_nodes1` - FOREIGN KEY (`nodes_id`) - REFERENCES `cms_dev`.`nodes` (`id`) - ON DELETE NO ACTION - ON UPDATE NO ACTION, - CONSTRAINT `fk_users_has_nodes_users` - FOREIGN KEY (`users_id`) - REFERENCES `cms_dev`.`users` (`id`) - ON DELETE NO ACTION - ON UPDATE NO ACTION) -ENGINE = InnoDB -DEFAULT CHARACTER SET = latin1; - - --- ----------------------------------------------------- --- Table `cms_dev`.`users_roles` --- ----------------------------------------------------- -CREATE TABLE IF NOT EXISTS `cms_dev`.`users_roles` ( - `users_id` INT(10) UNSIGNED NOT NULL, - `roles_id` INT(10) UNSIGNED NOT NULL, - PRIMARY KEY (`users_id`, `roles_id`), - INDEX `fk_users_has_roles_roles1_idx` (`roles_id` ASC), - INDEX `fk_users_has_roles_users1_idx` (`users_id` ASC), - CONSTRAINT `fk_users_has_roles_roles1` - FOREIGN KEY (`roles_id`) - REFERENCES `cms_dev`.`roles` (`id`) - ON DELETE NO ACTION - ON UPDATE NO ACTION, - CONSTRAINT `fk_users_has_roles_users1` - FOREIGN KEY (`users_id`) - REFERENCES `cms_dev`.`users` (`id`) - ON DELETE NO ACTION - ON UPDATE NO ACTION) -ENGINE = InnoDB -DEFAULT CHARACTER SET = latin1; - - -SET SQL_MODE=@OLD_SQL_MODE; -SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; -SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; 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/schema.sql b/library/persistence/mysql/scripts/schema.sql deleted file mode 100644 index 382707e..0000000 --- a/library/persistence/mysql/scripts/schema.sql +++ /dev/null @@ -1,72 +0,0 @@ -SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0; -SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; -SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL,ALLOW_INVALID_DATES'; - --- ----------------------------------------------------- --- Schema mydb --- ----------------------------------------------------- --- ----------------------------------------------------- --- Schema roc_cms --- ----------------------------------------------------- -DROP SCHEMA IF EXISTS `roc_cms` ; -CREATE SCHEMA IF NOT EXISTS `roc_cms` DEFAULT CHARACTER SET latin1 ; -USE `roc_cms` ; - --- ----------------------------------------------------- --- Table `roc_cms`.`nodes` --- ----------------------------------------------------- -DROP TABLE IF EXISTS `roc_cms`.`nodes` ; - -CREATE TABLE IF NOT EXISTS `roc_cms`.`nodes` ( - `nid` INT(11) NOT NULL AUTO_INCREMENT, - `version` INT(11) NULL DEFAULT NULL, - `type` INT(11) NULL DEFAULT NULL, - `title` VARCHAR(255) NOT NULL, - `summary` TEXT NOT NULL, - `content` MEDIUMTEXT NOT NULL, - `author` INT(11) NULL DEFAULT NULL, - `publish` DATETIME NULL DEFAULT NULL, - `created` DATETIME NOT NULL, - `changed` DATETIME NOT NULL, - PRIMARY KEY (`nid`)) -ENGINE = InnoDB -DEFAULT CHARACTER SET = latin1; - - --- ----------------------------------------------------- --- Table `roc_cms`.`users` --- ----------------------------------------------------- -DROP TABLE IF EXISTS `roc_cms`.`users` ; - -CREATE TABLE IF NOT EXISTS `roc_cms`.`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) NULL DEFAULT NULL, - `created` DATETIME NOT NULL, - `signed` DATETIME NULL DEFAULT NULL, - PRIMARY KEY (`uid`), - UNIQUE INDEX `name` (`name` ASC)) -ENGINE = InnoDB -DEFAULT CHARACTER SET = latin1; - - --- ----------------------------------------------------- --- Table `roc_cms`.`users_roles` --- ----------------------------------------------------- -DROP TABLE IF EXISTS `roc_cms`.`users_roles` ; - -CREATE TABLE IF NOT EXISTS `roc_cms`.`users_roles` ( - `rid` INT(11) NOT NULL AUTO_INCREMENT, - `role` VARCHAR(100) NOT NULL, - PRIMARY KEY (`rid`), - UNIQUE INDEX `role` (`role` ASC)) -ENGINE = InnoDB -DEFAULT CHARACTER SET = latin1; - - -SET SQL_MODE=@OLD_SQL_MODE; -SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; -SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; diff --git a/library/persistence/mysql/scripts/tables.sql b/library/persistence/mysql/scripts/tables.sql deleted file mode 100644 index 4b1a4ba..0000000 --- a/library/persistence/mysql/scripts/tables.sql +++ /dev/null @@ -1,14 +0,0 @@ -DROP TABLE IF EXISTS nodes; - -CREATE TABLE nodes -( - id smallint unsigned NOT NULL auto_increment, - publication_date date NOT NULL, #When the article was published - creation_date date NOT NULL, #When the article was created - modification_date date NOT NULL, #When the article was updated - title varchar(255) NOT NULL, #Full title of the article - summary text NOT NULL, #A short summary of the articule - content mediumtext NOT NULL, #The HTML content of the article - - PRIMARY KEY (ID) -); \ No newline at end of file diff --git a/library/persistence/mysql/scripts/triggers.sql b/library/persistence/mysql/scripts/triggers.sql deleted file mode 100644 index b3e10a4..0000000 --- a/library/persistence/mysql/scripts/triggers.sql +++ /dev/null @@ -1,8 +0,0 @@ -DELIMITER $$ -CREATE TRIGGER update_editor -AFTER INSERT ON `users_nodes` FOR EACH ROW - UPDATE Nodes - SET editor_id = NEW.users_id - WHERE id = NEW.nodes_id; -$$ -DELIMITER ; \ No newline at end of file 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/login-safe.ecf b/modules/login/login-safe.ecf new file mode 100644 index 0000000..eaba1d4 --- /dev/null +++ b/modules/login/login-safe.ecf @@ -0,0 +1,23 @@ + + + + + + /.git$ + /EIFGENs$ + /.svn$ + + + + + + + + + + + + + + diff --git a/modules/login/login_email_service.e b/modules/login/login_email_service.e new file mode 100644 index 0000000..2ada59e --- /dev/null +++ b/modules/login/login_email_service.e @@ -0,0 +1,45 @@ +note + description: "Summary description for {LOGIN_EMAIL_SERVICE}." + date: "$Date$" + revision: "$Revision$" + +class + LOGIN_EMAIL_SERVICE + +inherit + EMAIL_SERVICE + redefine + initialize, + parameters + end + +create + make + +feature {NONE} -- Initialization + + initialize + do + Precursor + contact_email := parameters.contact_email + end + + parameters: LOGIN_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 + do + send_message (contact_email, a_to, parameters.contact_subject_text, a_content) + end + +end diff --git a/modules/login/login_email_service_parameters.e b/modules/login/login_email_service_parameters.e new file mode 100644 index 0000000..d356af0 --- /dev/null +++ b/modules/login/login_email_service_parameters.e @@ -0,0 +1,73 @@ +note + description: "Summary description for {LOGIN_EMAIL_SERVICE_PARAMETERS}." + date: "$Date$" + revision: "$Revision$" + +class + LOGIN_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_contact_subject: detachable READABLE_STRING_8 + do + -- 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") + if s /= Void then + l_contact_subject := 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_contact_subject /= Void then + contact_subject_text := l_contact_subject + else + contact_subject_text := "Thank you for registering with us" + end + end + +feature -- Access + + smtp_server: IMMUTABLE_STRING_8 + + admin_email: IMMUTABLE_STRING_8 + + contact_email: IMMUTABLE_STRING_8 + -- Contact email. + + contact_subject_text: IMMUTABLE_STRING_8 + +end diff --git a/modules/login/login_module.e b/modules/login/login_module.e new file mode 100644 index 0000000..32f7249 --- /dev/null +++ b/modules/login/login_module.e @@ -0,0 +1,675 @@ +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 + LOGIN_MODULE + +inherit + CMS_MODULE + redefine + register_hooks + 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 -- 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) + -- Router configuration. + do + a_router.handle_with_request_methods ("/roc-login", create {WSF_URI_AGENT_HANDLER}.make (agent handle_login (a_api, ?, ?)), a_router.methods_head_get) + a_router.handle_with_request_methods ("/roc-register", create {WSF_URI_AGENT_HANDLER}.make (agent handle_register (a_api, ?, ?)), a_router.methods_get_post) + a_router.handle_with_request_methods ("/activate/{token}", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent handle_activation (a_api, ?, ?)), a_router.methods_head_get) + a_router.handle_with_request_methods ("/reactivate", create {WSF_URI_AGENT_HANDLER}.make (agent handle_reactivation (a_api, ?, ?)), a_router.methods_get_post) + a_router.handle_with_request_methods ("/new-password", create {WSF_URI_AGENT_HANDLER}.make (agent handle_new_password (a_api, ?, ?)), a_router.methods_get_post) + a_router.handle_with_request_methods ("/reset-password", create {WSF_URI_AGENT_HANDLER}.make (agent handle_reset_password (a_api, ?, ?)), a_router.methods_get_post) + a_router.handle_with_request_methods ("/roc-logout", create {WSF_URI_AGENT_HANDLER}.make (agent handle_logout (a_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)", "roc-logout" ) + else + create lnk.make ("Login", "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) + local + vals: CMS_VALUE_TABLE + do + if + a_block_id.is_case_insensitive_equal_general ("login") and then + a_response.request.path_info.starts_with ("/roc-login") + then + 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 + 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_block_id.is_case_insensitive_equal_general ("register") and then + a_response.request.path_info.starts_with ("/roc-register") + then + 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 + elseif + a_block_id.is_case_insensitive_equal_general ("reactivate") and then + a_response.request.path_info.starts_with ("/reactivate") + then + 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 + elseif + a_block_id.is_case_insensitive_equal_general ("new_password") and then + a_response.request.path_info.starts_with ("/new-password") + then + 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 + elseif + a_block_id.is_case_insensitive_equal_general ("reset_password") and then + a_response.request.path_info.starts_with ("/reset-password") + then + 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 + end + + handle_login (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE) + local + r: CMS_RESPONSE + br: BAD_REQUEST_ERROR_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 + br: BAD_REQUEST_ERROR_CMS_RESPONSE + l_url: STRING + do + 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 + + 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: LOGIN_EMAIL_SERVICE + l_link: STRING + l_token: STRING + l_message: 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 ("/activate/") + l_link.append (l_token) + + create l_message.make_from_string (account_activation) + l_message.replace_substring_all ("$link", l_link) + + -- Send Email + create es.make (create {LOGIN_EMAIL_SERVICE_PARAMETERS}.make (api)) + write_debug_log (generator + ".handle register: send_contact_email") + es.send_contact_email (l_email.value, l_message) + + 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_id: INTEGER_64 + l_ir: INTERNAL_SERVER_ERROR_CMS_RESPONSE + l_link: CMS_LOCAL_LINK + 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 + br: BAD_REQUEST_ERROR_CMS_RESPONSE + es: LOGIN_EMAIL_SERVICE + l_user_api: CMS_USER_API + l_token: STRING + l_link: STRING + l_message: 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 ("/activate/") + l_link.append (l_token) + + create l_message.make_from_string (account_activation) + l_message.replace_substring_all ("$link", l_link) + + -- Send Email + create es.make (create {LOGIN_EMAIL_SERVICE_PARAMETERS}.make (api)) + write_debug_log (generator + ".handle register: send_contact_email") + es.send_contact_email (l_email.value, l_message) + 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 + br: BAD_REQUEST_ERROR_CMS_RESPONSE + es: LOGIN_EMAIL_SERVICE + l_user_api: CMS_USER_API + l_token: STRING + l_link: STRING + l_message: 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 ("/reset-password?token=") + l_link.append (l_token) + + create l_message.make_from_string (account_new_password) + l_message.replace_substring_all ("$link", l_link) + + -- Send Email + create es.make (create {LOGIN_EMAIL_SERVICE_PARAMETERS}.make (api)) + write_debug_log (generator + ".handle register: send_contact_email") + es.send_contact_email (l_email.value, l_message) + 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 + br: BAD_REQUEST_ERROR_CMS_RESPONSE + es: LOGIN_EMAIL_SERVICE + l_user_api: CMS_USER_API + l_link: STRING + l_message: STRING + 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} -- 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} -- Message email + + account_activation: STRING= "[ + + + + + Eiffel.org Activation + + + + + +

Thank you for registering at Eiffel.org

+ +

To complete your registration, please click on this link to activate your account:

+ +

$link

+

Thank you for joining us.

+ + + ]" + + account_new_password: STRING= "[ + + + + + Eiffel.org New Password + + + + + +

You have required a new password at Eiffel.org

+ +

To complete your request, please click on this link to genereate a new password:

+ +

$link

+ + + ]" + +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/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/user/cms_user_storage_i.e b/src/persistence/user/cms_user_storage_i.e index e31fd14..44b794a 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,27 @@ 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..8142699 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,27 @@ 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..1950668 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,108 @@ feature -- Change: roles and permissions end end + +feature -- Access: User activation + + activation_elapsed_time (a_token: READABLE_STRING_32): INTEGER_32 + -- amount of time that has passed in days since the token `a_token' was saved. + local + l_parameters: STRING_TABLE [detachable ANY] + do + error_handler.reset + write_information_log (generator + ".activation_elapsed_time") + create l_parameters.make (1) + l_parameters.put (a_token, "token") + sql_query (sql_select_activation_expiration, l_parameters) + if sql_rows_count = 1 then + Result := sql_read_integer_32 (1) + end + end + + user_id_by_activation (a_token: READABLE_STRING_32): INTEGER_64 + -- User id associatied with a token `a_token', if any. + local + l_parameters: STRING_TABLE [detachable ANY] + do + error_handler.reset + write_information_log (generator + ".user_id_by_actication") + create l_parameters.make (1) + l_parameters.put (a_token, "token") + sql_query (sql_select_userid_activation, l_parameters) + if sql_rows_count = 1 then + Result := sql_read_integer_32 (1) + end + end + +feature -- Change: User activation + + save_activation (a_token: READABLE_STRING_32; a_id: INTEGER_64) + -- + 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 +627,9 @@ feature {NONE} -- Implementation: User if attached sql_read_string (5) as l_email then Result.set_email (l_email) end + if attached sql_read_integer_32 (6) as l_status then + Result.set_status (l_status) + end else check expected_valid_user: False end end @@ -551,10 +692,11 @@ feature {NONE} -- Sql Queries: USER Select_salt_by_username: STRING = "SELECT salt FROM Users WHERE name =:name;" -- Retrieve salt by username if exists. - sql_insert_user: STRING = "INSERT INTO users (name, password, salt, email, created) VALUES (:name, :password, :salt, :email, :created);" - -- SQL Insert to add a new node. + sql_insert_user: STRING = "INSERT INTO users (name, password, salt, email, created, status) VALUES (:name, :password, :salt, :email, :created, :status);" + -- SQL Insert to add a new user. - sql_update_user: STRING = "UPDATE users SET name=:name, password=:password, salt=:salt, email=:email WHERE uid=:uid;" + sql_update_user: STRING = "UPDATE users SET name=:name, password=:password, salt=:salt, email=:email, status=:status WHERE uid=:uid;" + -- SQL update to update an existing user. feature {NONE} -- Sql Queries: USER ROLE @@ -584,4 +726,34 @@ feature {NONE} -- Sql Queries: USER ROLE select_role_permissions_by_role_id: STRING = "SELECT permission, module FROM role_permissions WHERE rid=:rid;" -- User role permissions for role id :rid; +feature {NONE} -- Sql Queries: USER ACTIVATION + + sql_insert_activation: STRING = "INSERT INTO users_activations (token, uid, created) VALUES (:token, :uid, :utc_date);" + -- SQL insert a new activation :token. + + sql_select_activation_expiration: STRING = "SELECT DATEDIFF(day,created,UTC_DATE()) FROM users_activations where token = :token;" + -- elapsed time that has passed in days since the token `a_token' was saved. + + sql_select_userid_activation: STRING = "SELECT uid FROM users_activations where token = :token;" + -- Retrieve userid given the activation token. + + Select_user_by_activation_token: STRING = "SELECT u.* FROM users as u JOIN users_activations as ua ON ua.uid = u.uid and ua.token = :token;" + -- Retrieve user by activation token if exist. + + Sql_remove_activation: STRING = "DELETE FROM users_activations WHERE token = :token;" + -- Remove activation token. + +feature {NONE} + + sql_insert_password: STRING = "INSERT INTO users_password_recovery (token, uid, created) VALUES (:token, :uid, :utc_date);" + -- SQL insert a new password recovery :token. + + Sql_remove_password: STRING = "DELETE FROM users_password_recovery WHERE token = :token;" + -- Retrieve password if exist. + + Select_user_by_password_token: STRING = "SELECT u.* FROM users as u JOIN users_password_recovery as ua ON ua.uid = u.uid and ua.token = :token;" + -- Retrieve user by password token if exist. + + + end 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