diff --git a/dev_modules/masquerade_auth/masquerade_api.e b/dev_modules/masquerade_auth/masquerade_api.e new file mode 100644 index 0000000..b7bc732 --- /dev/null +++ b/dev_modules/masquerade_auth/masquerade_api.e @@ -0,0 +1,87 @@ +note + description: "API to manage CMS User session authentication" + date: "$Date$" + revision: "$Revision$" + +class + MASQUERADE_API + +inherit + CMS_AUTH_API_I + + REFACTORING_HELPER + +create {MASQUERADE_AUTH_MODULE} + make_with_session_api + +feature {NONE} -- Initialization + + make_with_session_api (a_api: CMS_API; a_session_api: CMS_SESSION_API) + do + session_api := a_session_api + make (a_api) + end + +feature -- Access + + session_api: CMS_SESSION_API + +feature -- Status report + + has_permission_to_masquerade (a_user: detachable CMS_USER): BOOLEAN + local + v: STRING + do + if attached cms_api.setup.string_8_item_or_default ("dev.masquerade", "permission") as s then + v := s + v.left_adjust + v.right_adjust + if v.is_case_insensitive_equal_general ("none") then + elseif v.is_case_insensitive_equal_general ("all") then + Result := True + elseif v.is_case_insensitive_equal_general ("permission") then + Result := cms_api.user_has_permission (a_user, "masquerade") + else + -- no! + end + end + end + + is_authenticating (a_response: CMS_RESPONSE): BOOLEAN + do + if + a_response.is_authenticated and then + attached a_response.request.cookie (session_api.session_token) + then + Result := True + end + end + +feature -- Basic operation + + process_user_login (a_user: CMS_USER; req: WSF_REQUEST; res: WSF_RESPONSE) + do + session_api.process_user_login (a_user, req, res) + end + + process_user_logout (a_user: CMS_USER; req: WSF_REQUEST; res: WSF_RESPONSE) + do + session_api.process_user_logout (a_user, req, res) + end + +feature -- Access + +-- user_by_session_token (a_token: READABLE_STRING_32): detachable CMS_USER +-- -- Retrieve user by token `a_token', if any. +-- do +-- Result := session_auth_storage.user_by_session_token (a_token) +-- end + +-- has_user_token (a_user: CMS_USER): BOOLEAN +-- -- Has the user `a_user' and associated session token? +-- do +-- Result := session_auth_storage.has_user_token (a_user) +-- end + + +end diff --git a/dev_modules/masquerade_auth/masquerade_auth-safe.ecf b/dev_modules/masquerade_auth/masquerade_auth-safe.ecf new file mode 100644 index 0000000..7aac9ee --- /dev/null +++ b/dev_modules/masquerade_auth/masquerade_auth-safe.ecf @@ -0,0 +1,29 @@ + + + + + + /EIFGENs$ + /CVS$ + /.svn$ + + + + + + + + + + + + + + + + + + + + diff --git a/dev_modules/masquerade_auth/masquerade_auth_module.e b/dev_modules/masquerade_auth/masquerade_auth_module.e new file mode 100644 index 0000000..ca8a1c1 --- /dev/null +++ b/dev_modules/masquerade_auth/masquerade_auth_module.e @@ -0,0 +1,268 @@ +note + description: "[ + This module allows the use Session Based Authentication using Cookies to restrict access + by looking up users in the given providers. + ]" + date: "$Date$" + revision: "$Revision$" + +class + MASQUERADE_AUTH_MODULE + +inherit + CMS_AUTH_MODULE_I + rename + module_api as masquerade_api + redefine + make, + setup_hooks, + initialize, + install, + permissions, + masquerade_api, + menu_system_alter + end + + CMS_HOOK_BLOCK + + CMS_HOOK_MENU_SYSTEM_ALTER + +create + make + +feature {NONE} -- Initialization + + make + do + Precursor + version := "1.0" + description := "Service to easily log as user at development time" + package := "debug" + disable -- Disabled by default + add_dependency ({CMS_SESSION_AUTH_MODULE}) + end + +feature -- Access + + name: STRING = "masquerade_auth" + + permissions: LIST [READABLE_STRING_8] + -- List of permission ids, used by this module, and declared. + do + Result := Precursor + Result.extend ("masquerade") + end + +feature {CMS_API} -- Module Initialization + + initialize (a_api: CMS_API) + -- + do + Precursor (a_api) + + if attached {CMS_SESSION_API} a_api.module_api ({CMS_SESSION_AUTH_MODULE}) as l_session_api then + -- API initialization + create masquerade_api.make_with_session_api (a_api, l_session_api) + end + end + +feature {CMS_API} -- Module management + + install (api: CMS_API) + do + Precursor {CMS_AUTH_MODULE_I} (api) -- Mark it as installed. + end + +feature {CMS_API} -- Access: API + + masquerade_api: detachable MASQUERADE_API + -- + +feature -- Access: auth strategy + + login_title: STRING = "Masquerade" + -- Module specific login title. + + login_location: STRING = "account/auth/roc-masquerade-login" + + logout_location: STRING = "account/auth/roc-masquerade-logout" + + is_authenticating (a_response: CMS_RESPONSE): BOOLEAN + -- + do + if attached masquerade_api as l_masquerade_api then + Result := l_masquerade_api.is_authenticating (a_response) + end + end + +feature -- Access: router + + setup_router (a_router: WSF_ROUTER; a_api: CMS_API) + -- + do + if attached masquerade_api as l_masquerade_api then + a_router.handle ("/" + login_location, create {WSF_URI_AGENT_HANDLER}.make (agent handle_login (a_api, ?, ?)), a_router.methods_head_get) + a_router.handle ("/" + logout_location, create {WSF_URI_AGENT_HANDLER}.make (agent handle_logout (a_api, l_masquerade_api, ?, ?)), a_router.methods_get_post) + a_router.handle ("/" + login_location, create {WSF_URI_AGENT_HANDLER}.make (agent handle_login_with_masquerade (a_api, l_masquerade_api,?, ?)), a_router.methods_post) + end + end + +feature {NONE} -- Implementation: routes + + handle_login (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE) + local + r: CMS_RESPONSE + do + create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api) + if + attached masquerade_api as l_masquerade_api and then + l_masquerade_api.has_permission_to_masquerade (api.user) + then + if api.user_is_authenticated then + r.add_warning_message ("You are signed.") + end + r.add_block (login_block ("login", Void, r), "content") + else + r.add_error_message ("You are not allowed to use masquerade authentication!") + end + r.execute + end + + handle_logout (api: CMS_API; a_masquerade_api: MASQUERADE_API ; req: WSF_REQUEST; res: WSF_RESPONSE) local + r: CMS_RESPONSE + do + if attached api.user as l_user then + a_masquerade_api.process_user_logout (l_user, req, res) + create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api) + else + -- Not loggued in ... redirect to home + create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api) + r.set_status_code ({HTTP_CONSTANTS}.found) + end + if + attached {WSF_STRING} req.item ("destination") as p_destination and then + attached p_destination.value as v and then + v.is_valid_as_string_8 + then + r.set_redirection (v.to_string_8) + else + r.set_redirection (req.absolute_script_url ("")) + end + + r.execute + end + + handle_login_with_masquerade (api: CMS_API; a_masquerade_api: MASQUERADE_API; req: WSF_REQUEST; res: WSF_RESPONSE) + local + r: CMS_RESPONSE + do + if a_masquerade_api.has_permission_to_masquerade (api.user) then + if + attached {WSF_STRING} req.form_parameter ("username") as l_username + then + if + attached api.user_api.user_by_name (l_username.value) as l_user + then + a_masquerade_api.process_user_login (l_user, req, res) + + create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api) + if + attached {WSF_STRING} req.item ("destination") as p_destination and then + attached p_destination.value as v and then + v.is_valid_as_string_8 + then + r.set_redirection (v.to_string_8) + else + r.set_redirection ("") + end + else + create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api) + r.add_block (login_block ("login", Void, r), "content") + end + else + create {BAD_REQUEST_ERROR_CMS_RESPONSE} r.make (req, res, api) + r.add_block (login_block ("login", "Wrong username", r), "content") + end + else + create {FORBIDDEN_ERROR_CMS_RESPONSE} r.make (req, res, api) + end + r.execute + end + +feature -- Hooks configuration + + setup_hooks (a_hooks: CMS_HOOK_CORE_MANAGER) + -- Module hooks configuration. + do + Precursor (a_hooks) + a_hooks.subscribe_to_block_hook (Current) + a_hooks.subscribe_to_menu_system_alter_hook (Current) + end + +feature -- Hooks + + block_list: ITERABLE [like {CMS_BLOCK}.name] + do + Result := <<"?login">> + end + + get_block_view (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE) + do + if a_block_id.is_case_insensitive_equal_general ("login") then + a_response.add_block (login_block (a_block_id, Void, a_response), "content") + 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 + u: detachable CMS_USER + do + u := a_response.api.user + if + attached masquerade_api as l_masquerade_api and then + l_masquerade_api.has_permission_to_masquerade (u) + then + Precursor (a_menu_system, a_response) + if u /= Void then + a_menu_system.navigation_menu.extend (a_response.local_link ("Masquerade", login_location)) + end + end + end + +feature {NONE} -- Block views + + login_block (a_block_id: READABLE_STRING_8; a_err: detachable READABLE_STRING_8; a_response: CMS_RESPONSE): CMS_CONTENT_BLOCK + do + create Result.make (a_block_id, Void, login_html (a_err, a_response), Void) + end + + login_html (a_err: detachable READABLE_STRING_8; a_response: CMS_RESPONSE): STRING + local + params: CMS_DATA_QUERY_PARAMETERS + u: CMS_USER + do + create Result.make_from_string ("
") + if a_err /= Void then + Result.append ("
") + Result.append (a_err) + Result.append ("
") + end + Result.append ("
%N") + Result.append ("
") + Result.append ("") + + create params.make (0, a_response.api.user_api.users_count.as_natural_32) + across + a_response.api.user_api.recent_users (params) as ic + loop + u := ic.item + Result.append ("
  • ") + Result.append (a_response.html_encoded (a_response.api.user_display_name (u))) + Result.append ("
  • %N") + end + Result.append ("
    ") + end + +end diff --git a/examples/demo/demo.ecf b/examples/demo/demo.ecf index d58c22b..ffc4e67 100644 --- a/examples/demo/demo.ecf +++ b/examples/demo/demo.ecf @@ -31,7 +31,6 @@ - @@ -42,6 +41,7 @@ + diff --git a/examples/demo/roc.cfg b/examples/demo/roc.cfg index e8ec181..9f2d2b8 100644 --- a/examples/demo/roc.cfg +++ b/examples/demo/roc.cfg @@ -26,6 +26,7 @@ "embedded_video": { "location": "../../modules/embedded_video" }, "wikitext": { "location": "../../modules/wikitext" }, "messaging": { "location": "../../modules/messaging" }, - "comments": { "location": "../../modules/comments" } + "comments": { "location": "../../modules/comments" }, + "masquerade": { "location": "../../dev_modules/masquerade" } } } diff --git a/examples/demo/site/config/cms.ini b/examples/demo/site/config/cms.ini index 03c4f01..0414897 100644 --- a/examples/demo/site/config/cms.ini +++ b/examples/demo/site/config/cms.ini @@ -63,3 +63,7 @@ base_path=/roc-admin #theme=admin # CMS Installation, are accessible by "all", "none" or uppon "permission". (default is none) installation_access=all + +[dev] +# masquerade: all, none, permission. Default is none. +masquerade=none diff --git a/examples/demo/src/demo_cms_execution.e b/examples/demo/src/demo_cms_execution.e index 14bc6fb..71613e4 100644 --- a/examples/demo/src/demo_cms_execution.e +++ b/examples/demo/src/demo_cms_execution.e @@ -91,6 +91,9 @@ feature -- CMS modules a_setup.register_module (create {CMS_DEBUG_MODULE}.make) a_setup.register_module (create {CMS_DEMO_MODULE}.make) + -- Dev + a_setup.register_module (create {MASQUERADE_AUTH_MODULE}.make) + end end diff --git a/modules/session_auth/cms_session_api.e b/modules/session_auth/cms_session_api.e index ac02417..6def2a0 100644 --- a/modules/session_auth/cms_session_api.e +++ b/modules/session_auth/cms_session_api.e @@ -57,6 +57,18 @@ feature -- Settings session_max_age: INTEGER -- Value of the Max-Age, before the cookie expires. +feature -- Status report + + is_authenticating (a_response: CMS_RESPONSE): BOOLEAN + do + if + a_response.is_authenticated and then + attached a_response.request.cookie (session_token) + then + Result := True + end + end + feature -- Access user_by_session_token (a_token: READABLE_STRING_32): detachable CMS_USER @@ -71,6 +83,47 @@ feature -- Access Result := session_auth_storage.has_user_token (a_user) end +feature -- Basic operation + + process_user_login (a_user: CMS_USER; req: WSF_REQUEST; res: WSF_RESPONSE) + local + l_token: STRING + l_cookie: WSF_COOKIE + do + l_token := new_session_token + if has_user_token (a_user) then + update_user_session_auth (l_token, a_user) + else + new_user_session_auth (l_token, a_user) + end + create l_cookie.make (session_token, l_token) + l_cookie.set_max_age (session_max_age) + l_cookie.set_path ("/") + res.add_cookie (l_cookie) + cms_api.set_user (a_user) + cms_api.record_user_login (a_user) + end + + process_user_logout (a_user: CMS_USER; req: WSF_REQUEST; res: WSF_RESPONSE) + local + l_cookie: WSF_COOKIE + do + if + attached session_token as tok and then + attached {WSF_STRING} req.cookie (tok) as l_cookie_token + then + -- Logout Session + create l_cookie.make (tok, "") -- l_cookie_token.value) -- FIXME: unicode issue? + l_cookie.set_path ("/") + l_cookie.unset_max_age + l_cookie.set_expiration_date (create {DATE_TIME}.make_from_epoch (0)) + res.add_cookie (l_cookie) + else + -- it seems the user was not login, as there is no associated cookie! + end + cms_api.unset_user + end + feature -- Change User session new_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER;) @@ -86,4 +139,26 @@ feature -- Change User session session_auth_storage.update_user_session_auth (a_token, a_user) end +feature -- Token + + new_session_token: STRING + -- Generate token to use in a Session. + local + l_token: STRING + l_security: CMS_TOKEN_GENERATOR + 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 + + end diff --git a/modules/session_auth/cms_session_auth_module.e b/modules/session_auth/cms_session_auth_module.e index b9f4e5b..9abc865 100644 --- a/modules/session_auth/cms_session_auth_module.e +++ b/modules/session_auth/cms_session_auth_module.e @@ -100,11 +100,9 @@ feature -- Access: auth strategy -- do if - a_response.is_authenticated and then - attached session_api as l_session_api and then - attached a_response.request.cookie (l_session_api.session_token) + attached session_api as l_session_api then - Result := True + Result := l_session_api.is_authenticating (a_response) end end @@ -116,7 +114,7 @@ feature -- Access: router if attached session_api as l_session_api then a_router.handle ("/" + login_location, create {WSF_URI_AGENT_HANDLER}.make (agent handle_login (a_api, ?, ?)), a_router.methods_head_get) a_router.handle ("/" + logout_location, create {WSF_URI_AGENT_HANDLER}.make (agent handle_logout (a_api, l_session_api, ?, ?)), a_router.methods_get_post) - a_router.handle ("/" + login_location, create {WSF_URI_AGENT_HANDLER}.make (agent handle_login_with_session (a_api,session_api, ?, ?)), a_router.methods_post) + a_router.handle ("/" + login_location, create {WSF_URI_AGENT_HANDLER}.make (agent handle_login_with_session (a_api, l_session_api, ?, ?)), a_router.methods_post) end end @@ -164,39 +162,33 @@ feature {NONE} -- Implementation: routes handle_logout (api: CMS_API; a_session_api: CMS_SESSION_API ; req: WSF_REQUEST; res: WSF_RESPONSE) local r: CMS_RESPONSE - l_cookie: WSF_COOKIE - tok: STRING do - tok := a_session_api.session_token - if - attached {WSF_STRING} req.cookie (tok) as l_cookie_token and then - attached api.user as l_user - then - -- Logout Session - create l_cookie.make (tok, "") -- l_cookie_token.value) -- FIXME: unicode issue? - l_cookie.set_path ("/") - l_cookie.unset_max_age - l_cookie.set_expiration_date (create {DATE_TIME}.make_from_epoch (0)) - res.add_cookie (l_cookie) - api.unset_user - + if attached api.user as l_user then + a_session_api.process_user_logout (l_user, req, res) + create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api) + else + -- Not loggued in ... redirect to home create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api) r.set_status_code ({HTTP_CONSTANTS}.found) - r.set_redirection (req.absolute_script_url ("")) - r.execute - else - fixme (generator + ": missing else implementation in handle_logout!") end + if + attached {WSF_STRING} req.item ("destination") as p_destination and then + attached p_destination.value as v and then + v.is_valid_as_string_8 + then + r.set_redirection (v.to_string_8) + else + r.set_redirection (req.absolute_script_url ("")) + end + + r.execute end - handle_login_with_session (api: CMS_API; a_session_api: detachable CMS_SESSION_API; req: WSF_REQUEST; res: WSF_RESPONSE) + handle_login_with_session (api: CMS_API; a_session_api: CMS_SESSION_API; req: WSF_REQUEST; res: WSF_RESPONSE) local r: CMS_RESPONSE - l_token: STRING - l_cookie: WSF_COOKIE do if - attached a_session_api as l_session_api and then attached {WSF_STRING} req.form_parameter ("username") as l_username and then attached {WSF_STRING} req.form_parameter ("password") as l_password then @@ -204,18 +196,7 @@ feature {NONE} -- Implementation: routes api.user_api.is_valid_credential (l_username.value, l_password.value) and then attached api.user_api.user_by_name (l_username.value) as l_user then - l_token := generate_token - if a_session_api.has_user_token (l_user) then - l_session_api.update_user_session_auth (l_token, l_user) - else - l_session_api.new_user_session_auth (l_token, l_user) - end - create l_cookie.make (a_session_api.session_token, l_token) - l_cookie.set_max_age (a_session_api.session_max_age) - l_cookie.set_path ("/") - res.add_cookie (l_cookie) - api.set_user (l_user) - api.record_user_login (l_user) + a_session_api.process_user_login (l_user, req, res) create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api) if @@ -295,23 +276,4 @@ feature {NONE} -- Block views end end - generate_token: STRING - -- Generate token to use in a Session. - local - l_token: STRING - l_security: CMS_TOKEN_GENERATOR - 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 - end