Improved credential validation.

- added `CMS_USER_API.user_with_credential (...): detachable CMS_USER` that check if credential is valid, and return associated user.
  - replaced use of `is_valid_credential` by new function `user_with_credential` .
  - revisited the session auth, to allow other credential validations (other than ROC CMS auth).
  - added CMS_USER_API.credential_validations to allow authenticating with system other than ROC CMS.
Added new permission to allow by-passing the default ROC-CMS user login/register management:
  - new permission to edit its own account.
  - new permission to edit its own password.
  - new permission to view users details (mostly for user managers).
This commit is contained in:
Jocelyn Fiat
2017-10-27 12:26:21 +02:00
parent 49b9ba3f86
commit f8715d54a8
15 changed files with 256 additions and 134 deletions

View File

@@ -23,12 +23,18 @@ feature -- Access
feature -- Element change feature -- Element change
set_personal_information (a_personal_information: like personal_information) set_personal_information (a_personal_information: detachable READABLE_STRING_GENERAL)
-- Assign `personal_information' with `a_personal_information'. -- Assign `personal_information` with `a_personal_information`.
do do
personal_information := a_personal_information if a_personal_information = Void then
personal_information := Void
else
personal_information := a_personal_information.as_string_32
end
ensure ensure
personal_information_assigned: personal_information = a_personal_information personal_information_assigned: a_personal_information /= Void
implies (attached personal_information as inf and then
a_personal_information.same_string (inf))
end end
set_salt (a_salt: like salt) set_salt (a_salt: like salt)

View File

@@ -15,7 +15,7 @@ create {CMS_AUTHENTICATION_MODULE}
feature -- Token Generation feature -- Token Generation
register_user (u: CMS_TEMP_USER; a_email: READABLE_STRING_8; a_personal_information: READABLE_STRING_8) register_user (u: CMS_TEMP_USER; a_email: READABLE_STRING_8; a_personal_information: READABLE_STRING_GENERAL)
local local
l_user_api: CMS_USER_API l_user_api: CMS_USER_API
l_url_activate: STRING l_url_activate: STRING
@@ -24,7 +24,11 @@ feature -- Token Generation
es: CMS_AUTHENTICATION_EMAIL_SERVICE es: CMS_AUTHENTICATION_EMAIL_SERVICE
do do
l_user_api := cms_api.user_api l_user_api := cms_api.user_api
-- New temp user
u.set_personal_information (a_personal_information)
l_user_api.new_temp_user (u) l_user_api.new_temp_user (u)
-- Create activation token -- Create activation token
l_token := new_token l_token := new_token
l_user_api.new_activation (l_token, u.id) l_user_api.new_activation (l_token, u.id)

View File

@@ -79,8 +79,10 @@ feature -- Access
Result.force ("account activate") Result.force ("account activate")
Result.force ("account reject") Result.force ("account reject")
Result.force ("account reactivate") Result.force ("account reactivate")
Result.force ("edit own account")
Result.force ("change own username") Result.force ("change own username")
Result.force ("view user") Result.force ("change own password")
Result.force ("view users")
end end
auth_api: detachable CMS_AUTHENTICATION_API auth_api: detachable CMS_AUTHENTICATION_API
@@ -303,10 +305,12 @@ feature -- Handler
lnk.set_weight (1) lnk.set_weight (1)
r.add_to_primary_tabs (lnk) r.add_to_primary_tabs (lnk)
if r.has_permission ("edit own account") then
create lnk.make ("Edit", "account/edit") create lnk.make ("Edit", "account/edit")
lnk.set_weight (2) lnk.set_weight (2)
r.add_to_primary_tabs (lnk) r.add_to_primary_tabs (lnk)
end end
end
a_auth_api.cms_api.hooks.invoke_form_alter (f, Void, r) a_auth_api.cms_api.hooks.invoke_form_alter (f, Void, r)
f.append_to_html (r.wsf_theme, b) f.append_to_html (r.wsf_theme, b)
@@ -327,6 +331,7 @@ feature -- Handler
lnk: CMS_LOCAL_LINK lnk: CMS_LOCAL_LINK
l_form: CMS_FORM l_form: CMS_FORM
do do
if a_auth_api.cms_api.has_permission ("edit own account") then
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, a_auth_api.cms_api) create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, a_auth_api.cms_api)
create b.make_empty create b.make_empty
l_user := r.user l_user := r.user
@@ -357,7 +362,11 @@ feature -- Handler
if attached new_change_profile_name_form (r) as f then if attached new_change_profile_name_form (r) as f then
f.append_to_html (r.wsf_theme, b) f.append_to_html (r.wsf_theme, b)
end end
if attached new_change_password_form (r) as f then
if
r.has_permission ("change own password") and then
attached new_change_password_form (r) as f
then
f.append_to_html (r.wsf_theme, b) f.append_to_html (r.wsf_theme, b)
end end
if attached new_change_email_form (r) as f then if attached new_change_email_form (r) as f then
@@ -372,6 +381,9 @@ feature -- Handler
r.set_redirection ("account") r.set_redirection ("account")
end end
r.execute r.execute
else
a_auth_api.cms_api.response_api.send_access_denied ("Can not edit your acocunt", req, res)
end
end end
handle_login (a_auth_api: CMS_AUTHENTICATION_API; req: WSF_REQUEST; res: WSF_RESPONSE) handle_login (a_auth_api: CMS_AUTHENTICATION_API; req: WSF_REQUEST; res: WSF_RESPONSE)
@@ -486,14 +498,12 @@ feature -- Handler
--| reCaptcha is not setup, so no verification --| reCaptcha is not setup, so no verification
l_captcha_passed := True l_captcha_passed := True
end end
if not l_exist then if l_captcha_passed and then not l_exist then
-- New temp user -- New temp user
create u.make (l_name) create u.make (l_name)
u.set_email (l_email) u.set_email (l_email)
u.set_password (l_password) u.set_password (l_password)
u.set_personal_information (l_personal_information) u.set_personal_information (l_personal_information)
l_user_api.new_temp_user (u)
a_auth_api.register_user (u, l_email, l_personal_information) a_auth_api.register_user (u, l_email, l_personal_information)
else else
r.set_value (l_name, "name") r.set_value (l_name, "name")

View File

@@ -35,8 +35,7 @@ feature -- Basic operations
attached l_auth.password as l_auth_password attached l_auth.password as l_auth_password
then then
if if
api.user_api.is_valid_credential (l_auth_login, l_auth_password) and then attached api.user_api.user_with_credential (l_auth_login, l_auth_password) as l_user
attached api.user_api.user_by_name (l_auth_login) as l_user
then then
if api.user_has_permission (l_user, {CMS_BASIC_AUTH_MODULE}.perm_use_basic_auth) then if api.user_has_permission (l_user, {CMS_BASIC_AUTH_MODULE}.perm_use_basic_auth) then
debug ("refactor_fixme") debug ("refactor_fixme")

View File

@@ -26,8 +26,7 @@ feature -- Basic operations
attached l_auth.password as l_auth_password attached l_auth.password as l_auth_password
then then
if if
api.user_api.is_valid_credential (l_auth_login, l_auth_password) and then attached api.user_api.user_with_credential (l_auth_login, l_auth_password) as l_user
attached api.user_api.user_by_name (l_auth_login) as l_user
then then
if api.user_has_permission (l_user, {CMS_BASIC_AUTH_MODULE}.perm_use_basic_auth) then if api.user_has_permission (l_user, {CMS_BASIC_AUTH_MODULE}.perm_use_basic_auth) then
api.set_user (l_user) api.set_user (l_user)

View File

@@ -197,38 +197,17 @@ feature {NONE} -- Implementation: routes
then then
l_username_or_email := p_username.value l_username_or_email := p_username.value
l_password := p_password.value l_password := p_password.value
l_user := api.user_api.user_by_name (l_username_or_email) l_user := api.user_api.user_with_credential (l_username_or_email, l_password)
if l_user = Void then if l_user /= Void then
l_user := api.user_api.user_by_email (l_username_or_email) if attached {CMS_TEMP_USER} l_user as l_temp_user then
end
if l_user = Void then
l_tmp_user := api.user_api.temp_user_by_name (l_username_or_email)
if l_tmp_user = Void then
l_tmp_user := api.user_api.temp_user_by_email (l_username_or_email)
end
if
l_tmp_user /= Void and then
api.user_api.is_valid_temp_user_credential (l_tmp_user.name, l_password)
then
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api) create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
if attached smarty_template_login_block (req, Current, "login", api) as l_tpl_block then if attached smarty_template_login_block (req, Current, "login", api) as l_tpl_block then
l_tpl_block.set_value (l_username_or_email, "username") l_tpl_block.set_value (l_username_or_email, "username")
l_tpl_block.set_value ("Error: Inactive account (or not yet validated)!", "error") l_tpl_block.set_value ("Error: the account is inactive, or not yet validated!", "error")
r.add_block (l_tpl_block, "content") r.add_block (l_tpl_block, "content")
end end
else else
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
if attached smarty_template_login_block (req, Current, "login", api) as l_tpl_block then
l_tpl_block.set_value (l_username_or_email, "username")
l_tpl_block.set_value ("Wrong username or password ", "error")
r.add_block (l_tpl_block, "content")
end
end
else
l_username := l_user.name
if api.user_api.is_valid_credential (l_username, l_password) then
a_session_api.process_user_login (l_user, req, res) a_session_api.process_user_login (l_user, req, res)
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api) create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
if if
attached {WSF_STRING} req.item ("destination") as p_destination and then attached {WSF_STRING} req.item ("destination") as p_destination and then
@@ -239,6 +218,7 @@ feature {NONE} -- Implementation: routes
else else
r.set_redirection ("") r.set_redirection ("")
end end
end
else else
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api) create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
if attached smarty_template_login_block (req, Current, "login", api) as l_tpl_block then if attached smarty_template_login_block (req, Current, "login", api) as l_tpl_block then
@@ -247,7 +227,6 @@ feature {NONE} -- Implementation: routes
r.add_block (l_tpl_block, "content") r.add_block (l_tpl_block, "content")
end end
end end
end
r.execute r.execute
else else
create {BAD_REQUEST_ERROR_CMS_RESPONSE} r.make (req, res, api) create {BAD_REQUEST_ERROR_CMS_RESPONSE} r.make (req, res, api)

View File

@@ -131,6 +131,7 @@ feature -- Security
Result.force ("admin path_alias") Result.force ("admin path_alias")
Result.force ("edit path_alias") Result.force ("edit path_alias")
Result.force ("use access_token") Result.force ("use access_token")
Result.force ("view users")
end end
feature {CMS_EXECUTION} -- Administration feature {CMS_EXECUTION} -- Administration
@@ -180,7 +181,6 @@ feature -- Hook
end end
end end
note note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others" copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"

View File

@@ -29,6 +29,8 @@ feature {NONE} -- Initialization
Precursor {CMS_MODULE_API} Precursor {CMS_MODULE_API}
Precursor {CMS_USER_PROFILE_API} Precursor {CMS_USER_PROFILE_API}
user_storage := storage user_storage := storage
create credential_validations.make_caseless (1)
register_credential_validation (create {CMS_USER_CREDENTIAL_CORE_VALIDATION}.make (cms_api, user_storage))
end end
feature -- Storage feature -- Storage
@@ -233,12 +235,35 @@ feature -- Change User
error_handler.append (user_storage.error_handler) error_handler.append (user_storage.error_handler)
end end
feature -- Credential validation
register_credential_validation (a_validation: CMS_USER_CREDENTIAL_VALIDATION)
do
credential_validations.force (a_validation, a_validation.id)
end
credential_validations: STRING_TABLE [CMS_USER_CREDENTIAL_VALIDATION]
-- Credential validation items, used by `user_validating_credential`.
feature -- Status report feature -- Status report
is_valid_credential (a_auth_login, a_auth_password: READABLE_STRING_GENERAL): BOOLEAN user_with_credential (a_user_identifier, a_password: READABLE_STRING_GENERAL): detachable CMS_USER
-- Is the credentials `a_auth_login' and `a_auth_password' valid? -- User validating the credential `a_user_identifier` and `a_password`, if any.
-- note: can be used to check if credentials are valid.
do do
Result := user_storage.is_valid_credential (a_auth_login, a_auth_password) across
credential_validations as ic
until
Result /= Void
loop
Result := ic.item.user_with_credential (a_user_identifier, a_password)
end
end
is_valid_credential (a_user_identifier: READABLE_STRING_GENERAL; a_password: READABLE_STRING_GENERAL): BOOLEAN
-- Is the credentials `a_user_identifier' and `a_password' valid?
do
Result := user_with_credential (a_user_identifier, a_password) /= Void
end end
user_has_permission (a_user: detachable CMS_USER; a_permission: detachable READABLE_STRING_GENERAL): BOOLEAN user_has_permission (a_user: detachable CMS_USER; a_permission: detachable READABLE_STRING_GENERAL): BOOLEAN
@@ -501,10 +526,17 @@ feature -- User status
feature -- Access - Temp User feature -- Access - Temp User
is_valid_temp_user_credential (a_auth_login, a_auth_password: READABLE_STRING_GENERAL): BOOLEAN temp_user_with_credential (a_user_identifier, a_password: READABLE_STRING_GENERAL): detachable CMS_USER
-- Is the credentials `a_auth_login' and `a_auth_password' valid? -- Temporary user validating the credential `a_user_identifier` and `a_password`, if any.
-- note: can be used to check if credentials are valid.
do do
Result := user_storage.is_valid_temp_user_credential (a_auth_login, a_auth_password) Result := user_storage.temp_user_with_credential (a_user_identifier, a_password)
end
is_valid_temp_user_credential (a_user_name: READABLE_STRING_GENERAL; a_password: READABLE_STRING_GENERAL): BOOLEAN
-- Is the credentials `a_user_name' and `a_password' valid?
do
Result := temp_user_with_credential (a_user_name, a_password) /= Void
end end
temp_users_count: INTEGER temp_users_count: INTEGER

View File

@@ -0,0 +1,60 @@
note
description: "Summary description for {CMS_USER_CREDENTIAL_CORE_VALIDATION}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
CMS_USER_CREDENTIAL_CORE_VALIDATION
inherit
CMS_USER_CREDENTIAL_VALIDATION
create
make
feature {NONE} -- Creation
make (a_api: CMS_API; a_storage: CMS_USER_STORAGE_I)
do
user_storage := a_storage
cms_api := a_api
end
cms_api: CMS_API
feature -- Access
id: STRING = "core"
user_storage: CMS_USER_STORAGE_I
feature -- Status report
user_with_credential (a_user_identifier: READABLE_STRING_GENERAL; a_password: READABLE_STRING_GENERAL): detachable CMS_USER
-- User validating credentials `a_user_identifier` and `a_password`, if any.
do
-- Check by username, by email
-- and also check temp user...
Result := user_storage.user_with_credential (a_user_identifier, a_password)
if Result = Void and then a_user_identifier.has ('@') then
-- Try with email
if attached user_storage.user_by_email (a_user_identifier) as u then
Result := user_storage.user_with_credential (u.name, a_password)
end
end
if Result = Void then
Result := user_storage.temp_user_with_credential (a_user_identifier, a_password)
if Result = Void and then a_user_identifier.has ('@') then
-- Try with email
if attached user_storage.temp_user_by_email (a_user_identifier) as u then
Result := user_storage.temp_user_with_credential (u.name, a_password)
end
end
end
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,28 @@
note
description: "[
User credential validation.
This provides a simple way to add new source for credentials or users management.
]"
date: "$Date$"
revision: "$Revision$"
deferred class
CMS_USER_CREDENTIAL_VALIDATION
feature -- Access
id: STRING
deferred
end
feature -- Status report
user_with_credential (a_user_identifier: READABLE_STRING_GENERAL; a_password: READABLE_STRING_GENERAL): detachable CMS_USER
-- User validating credential `a_user_identifier` and `a_password`, if any.
deferred
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -83,9 +83,9 @@ feature -- HTTP Methods
local local
l_user: detachable CMS_USER l_user: detachable CMS_USER
do do
if api.has_permission ("view user") then if api.has_permission ("view users") then
-- Display existing node
l_user := user_path_parameter (req) l_user := user_path_parameter (req)
-- Display existing node
if if
l_user /= Void l_user /= Void
then then

View File

@@ -52,7 +52,7 @@ feature -- Process
attached associated_user as l_user attached associated_user as l_user
then then
if if
api.has_permission ("view user") api.has_permission ("view users")
or l_user.same_as (user) -- Same user or l_user.same_as (user) -- Same user
then then
f := new_view_form (l_user, request.request_uri, "view-user") f := new_view_form (l_user, request.request_uri, "view-user")

View File

@@ -70,11 +70,18 @@ feature -- Access
password: Result /= Void implies (Result.hashed_password /= Void and Result.password = Void) password: Result /= Void implies (Result.hashed_password /= Void and Result.password = Void)
end end
is_valid_credential (a_u, a_p: READABLE_STRING_GENERAL): BOOLEAN user_with_credential (a_user_name, a_password: READABLE_STRING_GENERAL): detachable CMS_USER
-- Does account with username `a_username' and password `a_password' exist? -- User validating the credential `a_user_name` and `a_password`, if any.
-- note: can be used to check if credentials are valid.
deferred deferred
end end
is_valid_credential (a_username, a_password: READABLE_STRING_GENERAL): BOOLEAN
-- Does account with username `a_username' and password `a_password' exist?
do
Result := user_with_credential (a_username, a_password) /= Void
end
users_count: INTEGER users_count: INTEGER
-- Number of users -- Number of users
deferred deferred
@@ -212,11 +219,18 @@ feature -- Change: User password recovery
feature -- Access: Temp Users feature -- Access: Temp Users
is_valid_temp_user_credential (a_u, a_p: READABLE_STRING_GENERAL): BOOLEAN temp_user_with_credential (a_user_identifier, a_password: READABLE_STRING_GENERAL): detachable CMS_TEMP_USER
-- Does temp account with username `a_username' and password `a_password' exist? -- Temp user validating the credential `a_user_identifier` and `a_password`, if any.
-- note: can be used to check if credentials are valid.
deferred deferred
end end
is_valid_temp_user_credential (a_username, a_password: READABLE_STRING_GENERAL): BOOLEAN
-- Does temp account with username `a_username' and password `a_password' exist?
do
Result := temp_user_with_credential (a_username, a_password) /= Void
end
temp_users_count: INTEGER temp_users_count: INTEGER
-- Number of pending users -- Number of pending users
--! to be accepted or rejected --! to be accepted or rejected

View File

@@ -41,7 +41,7 @@ feature -- Access: user
do do
end end
is_valid_credential (l_auth_login, l_auth_password: READABLE_STRING_GENERAL): BOOLEAN user_with_credential (a_user_name, a_password: READABLE_STRING_GENERAL): detachable CMS_USER
do do
end end
@@ -147,7 +147,8 @@ feature -- Change: User password recovery
feature -- Access: Users feature -- Access: Users
is_valid_temp_user_credential (l_auth_login, l_auth_password: READABLE_STRING_GENERAL): BOOLEAN temp_user_with_credential (a_user_name, a_password: READABLE_STRING_GENERAL): detachable CMS_TEMP_USER
-- <Precursor>
do do
end end

View File

@@ -149,23 +149,17 @@ feature -- Access: user
sql_finalize_query (select_user_by_password_token) sql_finalize_query (select_user_by_password_token)
end end
is_valid_credential (a_auth_login, a_auth_password: READABLE_STRING_GENERAL): BOOLEAN user_with_credential (a_user_name, a_password: READABLE_STRING_GENERAL): detachable CMS_USER
local
l_security: SECURITY_PROVIDER
do do
if attached user_salt (a_auth_login) as l_hash then if
if attached user_by_name (a_auth_login) as l_user then attached user_by_name (a_user_name) as l_user and then
create l_security attached user_salt (a_user_name) as l_hash
then
if if
attached l_user.hashed_password as l_hashed_password and then attached l_user.hashed_password as l_hashed_password and then
l_security.password_hash (a_auth_password, l_hash).is_case_insensitive_equal (l_hashed_password) (create {SECURITY_PROVIDER}).password_hash (a_password, l_hash).is_case_insensitive_equal (l_hashed_password)
then then
Result := True Result := l_user
else
write_information_log (generator + ".is_valid_credential User: wrong username or password" )
end
else
write_information_log (generator + ".is_valid_credential User:" + a_auth_login + "does not exist" )
end end
end end
end end
@@ -1064,23 +1058,19 @@ feature {NONE} -- User Password Recovery
feature -- Acess: Temp users feature -- Acess: Temp users
is_valid_temp_user_credential (a_auth_login, a_auth_password: READABLE_STRING_GENERAL): BOOLEAN temp_user_with_credential (a_user_name, a_password: READABLE_STRING_GENERAL): detachable CMS_TEMP_USER
local -- Temp user validating the credential `a_user_name` and `a_password`, if any.
l_security: SECURITY_PROVIDER -- note: can be used to check if credentials are valid.
do do
if attached temp_user_salt (a_auth_login) as l_hash then if
if attached temp_user_by_name (a_auth_login) as l_user then attached temp_user_by_name (a_user_name) as l_user and then
create l_security attached temp_user_salt (a_user_name) as l_hash
then
if if
attached l_user.hashed_password as l_hashed_password and then attached l_user.hashed_password as l_hashed_password and then
l_security.password_hash (a_auth_password, l_hash).is_case_insensitive_equal (l_hashed_password) (create {SECURITY_PROVIDER}).password_hash (a_password, l_hash).is_case_insensitive_equal (l_hashed_password)
then then
Result := True Result := l_user
else
write_information_log (generator + ".is_valid_temp_user_credential User: wrong username or password" )
end
else
write_information_log (generator + ".is_valid_temp_user_credential User:" + a_auth_login + "does not exist" )
end end
end end
end end