diff --git a/src/modules/core/cms_core_module_webapi.e b/src/modules/core/cms_core_module_webapi.e deleted file mode 100644 index 1ef9f39..0000000 --- a/src/modules/core/cms_core_module_webapi.e +++ /dev/null @@ -1,188 +0,0 @@ -note - description: "Summary description for {CMS_CORE_MODULE_WEBAPI}." - author: "" - date: "$Date$" - revision: "$Revision$" - -class - CMS_CORE_MODULE_WEBAPI - -inherit - CMS_MODULE_WEBAPI [CMS_CORE_MODULE] - redefine - permissions - end - -create - make - -feature -- Security - - permissions: LIST [READABLE_STRING_8] - -- List of permission ids, used by this module, and declared. - do - Result := Precursor - Result.force ("admin users") - end - -feature {NONE} -- Router/administration - - setup_webapi_router (a_router: WSF_ROUTER; a_api: CMS_API) - -- - do - a_router.handle ("", create {WSF_URI_AGENT_HANDLER}.make (agent handle_root (?, ?, a_api)), a_router.methods_get) - a_router.handle ("/access_token", create {WSF_URI_AGENT_HANDLER}.make (agent do_post_access_token (?, ?, a_api)), a_router.methods_post) - - a_router.handle ("/user/{uid}", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent do_get_user (?, ?, a_api)), a_router.methods_get) - end - -feature -- Request handling - - handle_root (req: WSF_REQUEST; res: WSF_RESPONSE; api: CMS_API) - local - rep: HM_WEBAPI_RESPONSE - do - rep := new_webapi_response (req, res, api) - rep.add_field ("site_name", api.setup.site_name) - if attached api.user as u then - add_user_links_to (u, rep) - end - rep.add_self (req.percent_encoded_path_info) - rep.execute - end - -feature -- Access token - - do_get_access_token (req: WSF_REQUEST; res: WSF_RESPONSE; api: CMS_API) - local - rep: HM_WEBAPI_RESPONSE --- l_access_token: detachable READABLE_STRING_32 - do - if - attached api.user as l_user and then - attached api.user_api.user_profile_item ("access_token", l_user) as l_access_token - then --- l_access_token := new_key (40) --- api.user_api.save_user_profile_item (l_user, "access_token", l_access_token) - - rep := new_webapi_response (req, res, api) - rep.add_field ("access_token", l_access_token) - rep.add_self (req.percent_encoded_path_info) - add_user_links_to (l_user, rep) - if attached {WSF_STRING} req.item ("destination") as dest then - rep.set_redirection (dest.url_encoded_value) - end - rep.execute - else - send_access_denied (Void, req, res, api) - end - end - - do_post_access_token (req: WSF_REQUEST; res: WSF_RESPONSE; api: CMS_API) - local - m: WSF_PAGE_RESPONSE - l_access_token: detachable READABLE_STRING_32 - do - if attached api.user as l_user then - l_access_token := api.user_api.user_profile_item ("access_token", l_user) - - l_access_token := new_key (40) - --- if l_access_token /= Void then --- l_access_token := "Updated-" + (create {UUID_GENERATOR}).generate_uuid.out --- else --- l_access_token := "New-" + (create {UUID_GENERATOR}).generate_uuid.out --- end - api.user_api.save_user_profile_item (l_user, "access_token", l_access_token) - if attached {WSF_STRING} req.item ("destination") as dest then - res.redirect_now (dest.value.to_string_8) - else - create m.make_with_body ("Ok") - res.send (m) - end - else - send_access_denied (Void, req, res, api) - end - end - -feature -- Users - - do_get_user (req: WSF_REQUEST; res: WSF_RESPONSE; api: CMS_API) - local - rep: HM_WEBAPI_RESPONSE - do - if attached api.user as u then - rep := new_webapi_response (req, res, api) - rep.add_field ("uid", u.id.out) - - rep.add_field ("name", u.name) - if attached u.email as l_email then - rep.add_field ("email", l_email) - end - if attached u.profile_name as l_profile_name then - rep.add_field ("profile_name", l_profile_name) - end - add_user_links_to (u, rep) - rep.execute - else - -- FIXME: use specific Web API response! - send_access_denied (Void, req, res, api) - end - end - -feature -- Helpers - - new_webapi_response (req: WSF_REQUEST; res: WSF_RESPONSE; api: CMS_API): HM_WEBAPI_RESPONSE - do --- create {MD_WEBAPI_RESPONSE} Result.make (req, res, api) - create {JSON_WEBAPI_RESPONSE} Result.make (req, res, api) - end - - send_access_denied (m: detachable READABLE_STRING_GENERAL; req: WSF_REQUEST; res: WSF_RESPONSE; api: CMS_API) - local - rep: HM_WEBAPI_RESPONSE - do - rep := new_webapi_response (req, res, api) - if m /= Void then - rep.add_field ("error", m) - else - rep.add_field ("error", "Access denied") - end - rep.execute - end - - add_user_links_to (u: CMS_USER; rep: HM_WEBAPI_RESPONSE) - do - rep.add_link ("account", "user/" + u.id.out, rep.api.webapi_path ("/user/" + u.id.out)) - end - - new_key (len: INTEGER): STRING_8 - local - rand: RANDOM - n: INTEGER - v: NATURAL_32 - do - create rand.set_seed ((create {DATE_TIME}.make_now_utc).seconds) - rand.start - create Result.make (len) - from - n := 1 - until - n = len - loop - rand.forth - v := (rand.item \\ 16).to_natural_32 - check 0 <= v and v <= 15 end - if v < 9 then - Result.append_code (48 + v) -- 48 '0' - else - Result.append_code (97 + v - 9) -- 97 'a' - end - n := n + 1 - 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 diff --git a/src/modules/core/cms_user_api.e b/src/modules/core/cms_user_api.e index 8e20d12..72f0b22 100644 --- a/src/modules/core/cms_user_api.e +++ b/src/modules/core/cms_user_api.e @@ -118,6 +118,16 @@ feature -- Access: user Result := user_storage.user_by_name (a_username) end + user_by_id_or_name (a_uid: READABLE_STRING_GENERAL): detachable CMS_USER + -- User by id or name `a_uid`, if any. + do + if a_uid.is_integer_64 then + Result := user_by_id (a_uid.to_integer_64) + else + Result := user_by_name (a_uid) + end + end + user_by_email (a_email: READABLE_STRING_GENERAL): detachable CMS_USER -- User by email `a_email', if any. do diff --git a/src/modules/core/cms_user_profile_api.e b/src/modules/core/cms_user_profile_api.e index 366c603..c4684cd 100644 --- a/src/modules/core/cms_user_profile_api.e +++ b/src/modules/core/cms_user_profile_api.e @@ -54,6 +54,15 @@ feature -- Access: profile Result := user_profile_storage.user_profile_item (a_user, a_item_name) end + users_with_profile_item (a_item_name: READABLE_STRING_GENERAL; a_value: detachable READABLE_STRING_GENERAL): detachable LIST [CMS_USER] + -- Users having a profile item `a_item_name:a_value`. + -- Note: if `a_value` is Void, return users having a profile item named `a_item_name`. + require + not a_item_name.is_whitespace + do + Result := user_profile_storage.users_with_profile_item (a_item_name, a_value) + end + feature -- Change: profile save_user_profile (a_user: CMS_USER; a_user_profile: CMS_USER_PROFILE) diff --git a/src/modules/core/handler/user/cms_user_handler.e b/src/modules/core/handler/user/cms_user_handler.e index af74713..49e64d9 100644 --- a/src/modules/core/handler/user/cms_user_handler.e +++ b/src/modules/core/handler/user/cms_user_handler.e @@ -82,7 +82,6 @@ feature -- HTTP Methods -- local l_user: detachable CMS_USER - l_uid: INTEGER_64 do if api.has_permission ("view user") then -- Display existing node diff --git a/src/modules/core/persistence/user_profile/cms_user_profile_storage_i.e b/src/modules/core/persistence/user_profile/cms_user_profile_storage_i.e index 291b81c..21ead62 100644 --- a/src/modules/core/persistence/user_profile/cms_user_profile_storage_i.e +++ b/src/modules/core/persistence/user_profile/cms_user_profile_storage_i.e @@ -31,6 +31,12 @@ feature -- Access end end + users_with_profile_item (a_item_name: READABLE_STRING_GENERAL; a_value: detachable READABLE_STRING_GENERAL): detachable LIST [CMS_USER] + -- Users having a profile item `a_item_name:a_value`. + -- Note: if `a_value` is Void, return users having a profile item named `a_item_name`. + deferred + end + feature -- Change save_user_profile (a_user: CMS_USER; a_profile: CMS_USER_PROFILE) @@ -54,4 +60,7 @@ feature -- Change save_user_profile (a_user, pf) 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 diff --git a/src/modules/core/persistence/user_profile/cms_user_profile_storage_null.e b/src/modules/core/persistence/user_profile/cms_user_profile_storage_null.e index ccdad85..19c92b5 100644 --- a/src/modules/core/persistence/user_profile/cms_user_profile_storage_null.e +++ b/src/modules/core/persistence/user_profile/cms_user_profile_storage_null.e @@ -24,6 +24,11 @@ feature -- Access do end + users_with_profile_item (a_item_name: READABLE_STRING_GENERAL; a_value: detachable READABLE_STRING_GENERAL): detachable LIST [CMS_USER] + -- + do + end + feature -- Change save_user_profile (a_user: CMS_USER; a_profile: CMS_USER_PROFILE) @@ -31,4 +36,7 @@ feature -- Change do 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 diff --git a/src/modules/core/persistence/user_profile/cms_user_profile_storage_sql.e b/src/modules/core/persistence/user_profile/cms_user_profile_storage_sql.e index 5b14e94..d4ea036 100644 --- a/src/modules/core/persistence/user_profile/cms_user_profile_storage_sql.e +++ b/src/modules/core/persistence/user_profile/cms_user_profile_storage_sql.e @@ -66,13 +66,63 @@ feature -- Access sql_finalize end + users_with_profile_item (a_item_name: READABLE_STRING_GENERAL; a_value: detachable READABLE_STRING_GENERAL): detachable LIST [CMS_USER] + -- Users having a profile item `a_item_name:a_value`. + -- Note: if `a_value` is Void, return users having a profile item named `a_item_name`. + local + l_parameters: STRING_TABLE [detachable ANY] + l_uids: ARRAYED_LIST [INTEGER_64] + do + reset_error + create l_parameters.make (2) + l_parameters.put (a_item_name, "key") + if a_value = Void then + sql_query (sql_select_users_with_profile_item_named, l_parameters) + else + l_parameters.put (a_value, "value") + sql_query (sql_select_users_with_profile_item, l_parameters) + end + if not has_error then + create l_uids.make (0) + from + sql_start + until + sql_after or has_error + loop + if + attached sql_read_integer_64 (1) as l_uid and then + l_uid > 0 + then + l_uids.force (l_uid) + end + sql_forth + end + end + sql_finalize + if + not has_error and + l_uids /= Void and + attached api as l_cms_api + then + create {ARRAYED_LIST [CMS_USER]} Result.make (l_uids.count) + across + l_uids as ic + loop + if attached l_cms_api.user_api.user_by_id (ic.item) as u then + Result.force (u) + else + check known_user: False end + end + end + end + end + feature -- Change save_user_profile_item (a_user: CMS_USER; a_item_name: READABLE_STRING_GENERAL; a_item_value: READABLE_STRING_GENERAL) -- Save user profile item `a_item_name:a_item_value` for `a_user'. local l_parameters: STRING_TABLE [detachable ANY] - p: detachable CMS_USER_PROFILE do create l_parameters.make (3) l_parameters.put (a_user.id, "uid") @@ -142,6 +192,12 @@ feature {NONE} -- Queries sql_select_user_profile_item: STRING = "SELECT key, value FROM user_profiles WHERE uid=:uid AND key=:key" -- user profile items for :uid; + sql_select_users_with_profile_item: STRING = "SELECT uid FROM user_profiles WHERE key=:key and value=:value" + -- users with profile item named :key and value :value; + + sql_select_users_with_profile_item_named: STRING = "SELECT uid FROM user_profiles WHERE key=:key" + -- users with profile item named :key; + sql_insert_user_profile_item: STRING = "INSERT INTO user_profiles (uid, key, value) VALUES (:uid, :key, :value);" -- new user profile item for :uid; @@ -149,5 +205,8 @@ feature {NONE} -- Queries -- user profile items for :uid; +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 diff --git a/src/modules/core/webapi/cms_core_access_token_webapi_auth_filter.e b/src/modules/core/webapi/cms_core_access_token_webapi_auth_filter.e new file mode 100644 index 0000000..19d7956 --- /dev/null +++ b/src/modules/core/webapi/cms_core_access_token_webapi_auth_filter.e @@ -0,0 +1,52 @@ +note + description: "Summary description for {CMS_CORE_ACCESS_TOKEN_WEBAPI_AUTH_FILTER}." + author: "" + date: "$Date$" + revision: "$Revision$" + +class + CMS_CORE_ACCESS_TOKEN_WEBAPI_AUTH_FILTER + +inherit + WSF_FILTER + +create + make + +feature {NONE} -- Initialization + + make (a_api: CMS_API) + -- Initialize Current handler with `a_api'. + do + api := a_api + end + +feature -- API Service + + api: CMS_API + +feature -- Basic operations + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute the filter. + local + tok: READABLE_STRING_GENERAL + do + if + attached req.http_authorization as l_auth and then + l_auth.starts_with_general ("Bearer ") + then + tok := l_auth.substring (8, l_auth.count) + if attached api.user_api.users_with_profile_item ("access_token", tok) as lst then + if lst.count = 1 then + api.set_user (lst.first) + end + end + end + execute_next (req, res) + 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 diff --git a/src/modules/core/webapi/cms_core_module_webapi.e b/src/modules/core/webapi/cms_core_module_webapi.e new file mode 100644 index 0000000..4504a16 --- /dev/null +++ b/src/modules/core/webapi/cms_core_module_webapi.e @@ -0,0 +1,92 @@ +note + description: "Summary description for {CMS_CORE_MODULE_WEBAPI}." + author: "" + date: "$Date$" + revision: "$Revision$" + +class + CMS_CORE_MODULE_WEBAPI + +inherit + CMS_MODULE_WEBAPI [CMS_CORE_MODULE] + redefine + permissions, + filters + end + +create + make + +feature -- Security + + permissions: LIST [READABLE_STRING_8] + -- List of permission ids, used by this module, and declared. + do + Result := Precursor + Result.force ("admin users") + Result.force ("view users") + end + +feature {NONE} -- Router/administration + + setup_webapi_router (a_router: WSF_ROUTER; a_api: CMS_API) + -- + local + l_root: CMS_CORE_WEBAPI_ROOT_HANDLER + do + create l_root.make (a_api) + a_router.handle ("", l_root, a_router.methods_get) + a_router.handle ("/", l_root, a_router.methods_get) + a_router.handle ("/user/{uid}/access_token", create {CMS_CORE_WEBAPI_ACCESS_TOKEN_HANDLER}.make (a_api), a_router.methods_get_post) + a_router.handle ("/user/{uid}", create {CMS_CORE_WEBAPI_USER_HANDLER}.make (a_api), a_router.methods_get) + end + +feature -- Access: filter + + filters (a_api: CMS_API): detachable LIST [WSF_FILTER] + -- Possibly list of Filter's module. + do + create {ARRAYED_LIST [WSF_FILTER]} Result.make (1) + Result.extend (create {CMS_CORE_ACCESS_TOKEN_WEBAPI_AUTH_FILTER}.make (a_api)) + end + +--feature -- Helpers + +-- new_webapi_response (req: WSF_REQUEST; res: WSF_RESPONSE; api: CMS_API): HM_WEBAPI_RESPONSE +-- do +---- create {MD_WEBAPI_RESPONSE} Result.make (req, res, api) +-- create {JSON_WEBAPI_RESPONSE} Result.make (req, res, api) +-- end + +-- new_wepapi_error_response (msg: detachable READABLE_STRING_GENERAL; req: WSF_REQUEST; res: WSF_RESPONSE; api: CMS_API): HM_WEBAPI_RESPONSE +-- do +-- Result := new_webapi_response (req, res, api) +-- if msg /= Void then +-- Result.add_string_field ("error", msg) +-- else +-- Result.add_string_field ("error", "True") +-- end +-- end + +-- send_access_denied (m: detachable READABLE_STRING_GENERAL; req: WSF_REQUEST; res: WSF_RESPONSE; api: CMS_API) +-- local +-- rep: HM_WEBAPI_RESPONSE +-- do +-- rep := new_webapi_response (req, res, api) +-- if m /= Void then +-- rep.add_string_field ("error", m) +-- else +-- rep.add_string_field ("error", "Access denied") +-- end +-- rep.execute +-- end + +-- add_user_links_to (u: CMS_USER; rep: HM_WEBAPI_RESPONSE) +-- do +-- rep.add_link ("account", "user/" + u.id.out, rep.api.webapi_path ("/user/" + u.id.out)) +-- 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 diff --git a/src/modules/core/webapi/cms_core_webapi_access_token_handler.e b/src/modules/core/webapi/cms_core_webapi_access_token_handler.e new file mode 100644 index 0000000..bcc2764 --- /dev/null +++ b/src/modules/core/webapi/cms_core_webapi_access_token_handler.e @@ -0,0 +1,177 @@ +note + description: "Summary description for {CMS_CORE_WEBAPI_ACCESS_TOKEN_HANDLER}." + date: "$Date$" + revision: "$Revision$" + +class + CMS_CORE_WEBAPI_ACCESS_TOKEN_HANDLER + +inherit + CMS_WEBAPI_HANDLER + + WSF_URI_TEMPLATE_HANDLER + +create + make + +feature -- Execution + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute handler for `req' and respond in `res'. + local + l_uid: READABLE_STRING_GENERAL + do + if attached {WSF_STRING} req.path_parameter ("uid") as p_uid then + l_uid := p_uid.value + if req.is_post_request_method then + post_access_token (l_uid, req, res) + elseif req.is_get_request_method then + get_access_token (l_uid, req, res) + else + send_bad_request (Void, req, res) + end + else + send_bad_request ("Missing {uid} parameter", req, res) + end + end + +feature -- Helper + + user_by_uid (a_uid: READABLE_STRING_GENERAL): detachable CMS_USER + do + Result := api.user_api.user_by_id_or_name (a_uid) + end + +feature -- Request execution + + get_access_token (a_uid: READABLE_STRING_GENERAL; req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute handler for `req' and respond in `res'. + local + rep: HM_WEBAPI_RESPONSE + do + if attached user_by_uid (a_uid) as l_user then + if attached api.user as u then + if u.same_as (l_user) or api.user_api.is_admin_user (u) then + rep := new_access_token_webapi_response (l_user, user_access_token (l_user), req, res) + if attached {WSF_STRING} req.item ("destination") as dest then + rep.set_redirection (dest.url_encoded_value) + end + rep.execute + else + -- Only admin, or current user can see its access_token! + send_access_denied (Void, req, res) + end + else + send_access_denied (Void, req, res) + end + else + send_not_found ("User not found", req, res) + end + end + + post_access_token (a_uid: READABLE_STRING_GENERAL; req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute handler for `req' and respond in `res'. + local + l_access_token: detachable READABLE_STRING_32 + rep: like new_webapi_response + do + if attached user_by_uid (a_uid) as l_user then + if attached api.user as u then + if u.same_as (l_user) or api.user_api.is_admin_user (u) then + if attached req.path_parameter ("application") then + + end + -- l_access_token := user_access_token (l_user) + l_access_token := new_key (40) + + -- if l_access_token /= Void then + -- l_access_token := "Updated-" + (create {UUID_GENERATOR}).generate_uuid.out + -- else + -- l_access_token := "New-" + (create {UUID_GENERATOR}).generate_uuid.out + -- end + set_user_access_token (l_user, l_access_token) + + rep := new_access_token_webapi_response (l_user, l_access_token, req, res) + if attached {WSF_STRING} req.item ("destination") as dest then + rep.set_redirection (dest.url_encoded_value) + end + rep.execute + else + -- Only admin, or current user can create the user access_token! + send_access_denied (Void, req, res) + end + else + send_access_denied (Void, req, res) + end + else + send_not_found ("User not found", req, res) + end + end + +feature {NONE} -- Implementation + + user_access_token (a_user: CMS_USER): READABLE_STRING_8 + do + if + attached api.user_api.user_profile_item ("access_token", a_user) as l_access_token and then + not l_access_token.is_whitespace and then + l_access_token.is_valid_as_string_8 + then + Result := l_access_token.to_string_8 + else + Result := new_key (40) + end + end + + set_user_access_token (a_user: CMS_USER; a_access_token: READABLE_STRING_GENERAL) + do + api.user_api.save_user_profile_item (a_user, "access_token", a_access_token) + end + + new_access_token_webapi_response (a_user: CMS_USER; a_access_token: READABLE_STRING_GENERAL; req: WSF_REQUEST; res: WSF_RESPONSE): like new_webapi_response + local + tb: STRING_TABLE [detachable ANY] + do + Result := new_webapi_response (req, res) + Result.add_string_field ("access_token", a_access_token) + create tb.make_equal (3) + tb.force (a_user.name, "name") + tb.force (a_user.id, "uid") + Result.add_table_iterator_field ("user", tb) + +-- Result.add_string_field ("username", a_user.name) +-- Result.add_integer_64_field ("uid", a_user.id) + Result.add_self (req.percent_encoded_path_info) + add_user_links_to (a_user, Result) + end + + new_key (len: INTEGER): STRING_8 + local + rand: RANDOM + n: INTEGER + v: NATURAL_32 + do + create rand.set_seed ((create {DATE_TIME}.make_now_utc).seconds) + rand.start + create Result.make (len) + from + n := 1 + until + n = len + loop + rand.forth + v := (rand.item \\ 16).to_natural_32 + check 0 <= v and v <= 15 end + if v < 9 then + Result.append_code (48 + v) -- 48 '0' + else + Result.append_code (97 + v - 9) -- 97 'a' + end + n := n + 1 + 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 diff --git a/src/modules/core/webapi/cms_core_webapi_root_handler.e b/src/modules/core/webapi/cms_core_webapi_root_handler.e new file mode 100644 index 0000000..8565d4b --- /dev/null +++ b/src/modules/core/webapi/cms_core_webapi_root_handler.e @@ -0,0 +1,36 @@ +note + description: "Summary description for {CMS_CORE_WEBAPI_ROOT_HANDLER}." + date: "$Date$" + revision: "$Revision$" + +class + CMS_CORE_WEBAPI_ROOT_HANDLER + +inherit + CMS_WEBAPI_HANDLER + + WSF_URI_HANDLER + +create + make + +feature -- Execution + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute handler for `req' and respond in `res'. + local + rep: HM_WEBAPI_RESPONSE + do + rep := new_webapi_response (req, res) + rep.add_string_field ("site_name", api.setup.site_name) + if attached api.user as u then + add_user_links_to (u, rep) + end + rep.add_self (req.percent_encoded_path_info) + rep.execute + 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 diff --git a/src/modules/core/webapi/cms_core_webapi_user_handler.e b/src/modules/core/webapi/cms_core_webapi_user_handler.e new file mode 100644 index 0000000..0ec4177 --- /dev/null +++ b/src/modules/core/webapi/cms_core_webapi_user_handler.e @@ -0,0 +1,80 @@ +note + description: "Summary description for {CMS_CORE_WEBAPI_USER_HANDLER}." + date: "$Date$" + revision: "$Revision$" + +class + CMS_CORE_WEBAPI_USER_HANDLER + +inherit + CMS_WEBAPI_HANDLER + + WSF_URI_TEMPLATE_HANDLER + +create + make + +feature -- Execution + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute handler for `req' and respond in `res'. + do + if req.is_get_request_method then + execute_get (req, res) + else + send_bad_request (Void, req, res) + end + end + + execute_get (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute handler for `req' and respond in `res'. + local + rep: HM_WEBAPI_RESPONSE + l_user: detachable CMS_USER + do + if attached api.user as u then + if attached {WSF_STRING} req.path_parameter ("uid") as p_uid then + if p_uid.is_integer then + l_user := api.user_api.user_by_id (p_uid.integer_value) + else + l_user := api.user_api.user_by_name (p_uid.value) + end +-- if l_user = Void and p_uid.is_case_insensitive_equal ("me") then +-- l_user := u +-- end + if l_user /= Void then + if l_user.same_as (u) or api.has_permissions (<<"admin users", "view users">>) then + rep := new_webapi_response (req, res) + rep.add_string_field ("uid", u.id.out) + + rep.add_string_field ("name", u.name) + if attached u.email as l_email then + rep.add_string_field ("email", l_email) + end + if attached u.profile_name as l_profile_name then + rep.add_string_field ("profile_name", l_profile_name) + end + add_user_links_to (u, rep) + else + rep := new_wepapi_error_response ("denied", req, res) + rep.set_status_code ({HTTP_STATUS_CODE}.user_access_denied) + end + else + rep := new_wepapi_error_response ("Not found", req, res) + rep.set_status_code ({HTTP_STATUS_CODE}.not_found) + end + else + rep := new_wepapi_error_response ("Bad request", req, res) + rep.set_status_code ({HTTP_STATUS_CODE}.bad_request) + end + rep.execute + else + -- FIXME: use specific Web API response! + send_access_denied (Void, req, res) + 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 diff --git a/src/service/cms_execution.e b/src/service/cms_execution.e index 1b49ce8..ca8fe20 100644 --- a/src/service/cms_execution.e +++ b/src/service/cms_execution.e @@ -19,6 +19,7 @@ inherit execute_default, filter_execute, initialize, + initialize_filter, initialize_router end @@ -61,6 +62,13 @@ feature {NONE} -- Initialization create {WSF_MAINTENANCE_FILTER} filter end + initialize_filter + -- Initialize `filter`. + do + create_filter + -- setup_filter: delayed to `initialize_execution`. + end + initialize_router -- Initialize `router`. do @@ -147,11 +155,12 @@ feature -- Settings: router configure_api_file_handler (l_router) end - setup_router_for_webapi + setup_router_and_filter_for_webapi local l_api: like api l_router: like router l_module: CMS_MODULE + f, l_filter, l_last_filter: WSF_FILTER do l_api := api l_router := router @@ -161,6 +170,20 @@ feature -- Settings: router -- Configure root of api handler. l_router.set_base_url (l_api.webapi_path (Void)) + -- Find insertion location for new filter + -- i.e just before the CMS_EXECUTION filter. + from + l_filter := filter + until + l_last_filter /= Void or not attached l_filter.next as l_next_filter + loop + if l_next_filter.next = Void then + l_last_filter := l_next_filter + else + l_filter := l_next_filter + end + end + -- Include routes from modules. across modules as ic @@ -172,25 +195,52 @@ feature -- Settings: router attached l_webapi.module_webapi as wapi then wapi.setup_router (l_router, l_api) + if attached wapi.filters (l_api) as l_filters then + across + l_filters as f_ic + loop + f := f_ic.item + l_filter.set_next (f) + f.set_next (l_last_filter) + end + end end end end - setup_router_for_administration + setup_router_and_filter_for_administration -- local l_api: like api l_router: like router l_module: CMS_MODULE + f, l_filter, l_last_filter: WSF_FILTER do l_api := api l_router := router - l_api.logger.put_debug (generator + ".setup_router_for_administration", Void) + l_api.logger.put_debug (generator + ".setup_router_and_filter_for_administration", Void) -- Configure root of api handler. l_router.set_base_url (l_api.administration_path (Void)) + -- Apply normal filters + setup_filter + + -- Find insertion location for new filter + -- i.e just before the CMS_EXECUTION filter. + from + l_filter := filter + until + l_last_filter /= Void or not attached l_filter.next as l_next_filter + loop + if l_next_filter.next = Void then + l_last_filter := l_next_filter + else + l_filter := l_next_filter + end + end + -- Include routes from modules. across modules as ic @@ -200,15 +250,24 @@ feature -- Settings: router l_module.is_initialized then if - attached {CMS_ADMINISTRABLE} l_module as l_administration and then + attached {CMS_WITH_MODULE_ADMINISTRATION} l_module as l_administration and then attached l_administration.module_administration as adm then adm.setup_router (l_router, l_api) - elseif - attached {CMS_WITH_WEBAPI} l_module as l_wapi and then - attached l_wapi.module_webapi as wapi - then - wapi.setup_router (l_router, l_api) + if attached adm.filters (l_api) as l_filters then + across + l_filters as f_ic + loop + f := f_ic.item + l_filter.set_next (f) + f.set_next (l_last_filter) + end + end +-- elseif +-- attached {CMS_WITH_WEBAPI} l_module as l_wapi and then +-- attached l_wapi.module_webapi as wapi +-- then +-- wapi.setup_router (l_router, l_api) end end end @@ -283,6 +342,7 @@ feature -- Request execution do api.switch_to_site_mode api.initialize_execution + setup_filter setup_router end @@ -291,7 +351,7 @@ feature -- Request execution do api.switch_to_webapi_mode api.initialize_execution - setup_router_for_webapi + setup_router_and_filter_for_webapi end initialize_administration_execution @@ -299,7 +359,7 @@ feature -- Request execution do api.switch_to_administration_mode api.initialize_execution - setup_router_for_administration + setup_router_and_filter_for_administration end execute @@ -341,6 +401,36 @@ feature -- Filters -- f.set_next (l_filter) -- l_filter := f +-- -- Include filters from modules +-- across +-- modules as ic +-- loop +-- l_module := ic.item +-- if +-- l_module.is_enabled and then +-- attached l_module.filters (l_api) as l_m_filters +-- then +-- across l_m_filters as f_ic loop +-- f := f_ic.item +-- l_filter.append (f) +---- f.set_next (l_filter) +---- l_filter := f +-- end +-- end +-- end + + filter := l_filter + end + + setup_filter + -- Setup `filter'. + local + f, l_filter: detachable WSF_FILTER + l_module: CMS_MODULE + l_api: like api + do + l_api := api + l_filter := filter -- Include filters from modules across modules as ic @@ -352,20 +442,15 @@ feature -- Filters then across l_m_filters as f_ic loop f := f_ic.item - f.set_next (l_filter) - l_filter := f + l_filter.append (f) +-- f.set_next (l_filter) +-- l_filter := f end end end - filter := l_filter end - setup_filter - -- Setup `filter'. - do - end - feature -- Execution handle_favicon (req: WSF_REQUEST; res: WSF_RESPONSE) diff --git a/src/service/response/hm_webapi_response.e b/src/service/response/hm_webapi_response.e index c20609d..f01d43a 100644 --- a/src/service/response/hm_webapi_response.e +++ b/src/service/response/hm_webapi_response.e @@ -15,11 +15,27 @@ feature -- Element change deferred end - add_field (a_name: READABLE_STRING_GENERAL; a_value: READABLE_STRING_GENERAL) + add_string_field (a_name: READABLE_STRING_GENERAL; a_value: READABLE_STRING_GENERAL) deferred end - add_link (rel: READABLE_STRING_8; a_attname: READABLE_STRING_8 ; a_att_href: READABLE_STRING_8) + add_boolean_field (a_name: READABLE_STRING_GENERAL; a_value: BOOLEAN) + deferred + end + + add_integer_64_field (a_name: READABLE_STRING_GENERAL; a_value: INTEGER_64) + deferred + end + + add_iterator_field (a_name: READABLE_STRING_GENERAL; a_value: ITERABLE [detachable ANY]) + deferred + end + + add_table_iterator_field (a_name: READABLE_STRING_GENERAL; a_value: TABLE_ITERABLE [detachable ANY, READABLE_STRING_GENERAL]) + deferred + end + + add_link (rel: READABLE_STRING_8; a_attname: READABLE_STRING_8; a_att_href: READABLE_STRING_8) deferred end diff --git a/src/service/response/json_webapi_response.e b/src/service/response/json_webapi_response.e index f857a68..54cea7f 100644 --- a/src/service/response/json_webapi_response.e +++ b/src/service/response/json_webapi_response.e @@ -31,14 +31,53 @@ feature -- Element change add_self (a_href: READABLE_STRING_8) do - add_field ("self", a_href) + add_string_field ("self", a_href) end - add_field (a_name: READABLE_STRING_GENERAL; a_value: READABLE_STRING_GENERAL) +feature -- Fields + +-- add_field (a_name: READABLE_STRING_GENERAL; a_value: detachable ANY) +-- do +-- resource.put (new_resource_item (a_value), a_name) +-- end + + add_string_field (a_name: READABLE_STRING_GENERAL; a_value: READABLE_STRING_GENERAL) do resource.put_string (a_value, a_name) end + add_boolean_field (a_name: READABLE_STRING_GENERAL; a_value: BOOLEAN) + do + resource.put_boolean (a_value, a_name) + end + + add_integer_64_field (a_name: READABLE_STRING_GENERAL; a_value: INTEGER_64) + do + resource.put_integer (a_value, a_name) + end + + add_natural_64_field (a_name: READABLE_STRING_GENERAL; a_value: NATURAL_64) + do + resource.put_natural (a_value, a_name) + end + + add_real_64_field (a_name: READABLE_STRING_GENERAL; a_value: REAL_64) + do + resource.put_real (a_value, a_name) + end + + add_iterator_field (a_name: READABLE_STRING_GENERAL; a_value: ITERABLE [detachable ANY]) + do + resource.put (new_resource_item (a_value), a_name) + end + + add_table_iterator_field (a_name: READABLE_STRING_GENERAL; a_value: TABLE_ITERABLE [detachable ANY, READABLE_STRING_GENERAL]) + do + resource.put (new_resource_item (a_value), a_name) + end + +feature -- Links + add_link (rel: READABLE_STRING_8; a_attname: READABLE_STRING_8 ; a_att_href: READABLE_STRING_8) local lnks: JSON_OBJECT @@ -66,6 +105,68 @@ feature -- Execution response.send (m) end +feature {NONE} -- Implementation factory + + new_resource_item (a_value: detachable ANY): JSON_VALUE + local + l_serializer: JSON_REFLECTOR_SERIALIZER + ctx: JSON_SERIALIZER_CONTEXT + do + create {JSON_NULL} Result + + create l_serializer + create ctx + ctx.set_default_serializer (l_serializer) + ctx.set_is_type_name_included (False) + ctx.register_serializer (create {TABLE_ITERABLE_JSON_SERIALIZER [detachable ANY, READABLE_STRING_GENERAL]}, {TABLE_ITERABLE [detachable ANY, READABLE_STRING_GENERAL]}) + ctx.register_serializer (create {ITERABLE_JSON_SERIALIZER [detachable ANY]}, {ITERABLE [detachable ANY]}) + Result := l_serializer.to_json (a_value, ctx) +-- if a_value = Void then +-- create {JSON_NULL} Result +-- elseif attached {READABLE_STRING_GENERAL} a_value as s then +-- create {JSON_STRING} Result.make_from_string_general (s) +-- elseif attached {BOOLEAN} a_value as b then +-- create {JSON_BOOLEAN} Result.make (b) +-- elseif attached {NUMERIC} a_value as num then +---- if attached {INTEGER_64} num as i64 then +---- add_integer_64_field (a_name, i64) +---- elseif attached {INTEGER_32} num as i32 then +---- add_integer_64_field (a_name, i32.as_integer_64) +---- elseif attached {INTEGER_16} num as i16 then +---- add_integer_64_field (a_name, i16.as_integer_64) +---- elseif attached {INTEGER_8} num as i8 then +---- add_integer_64_field (a_name, i8.as_integer_64) +---- elseif attached {NATURAL_64} num as n64 then +---- add_natural_64_field (a_name, n64) +---- elseif attached {NATURAL_32} num as n32 then +---- add_natural_64_field (a_name, n32.as_natural_64) +---- elseif attached {NATURAL_16} num as n16 then +---- add_natural_64_field (a_name, n16.as_natural_64) +---- elseif attached {NATURAL_8} num as n8 then +---- add_natural_64_field (a_name, n8.as_natural_64) +---- elseif attached {REAL_64} num as r64 then +---- add_real_64_field (a_name, r64) +---- elseif attached {REAL_32} num as r32 then +---- add_real_64_field (a_name, r32.to_double +---- else +---- check is_basic_numeric_type: False end +---- add_string_field (a_name, num.out) +---- end +-- elseif attached {CHARACTER_8} a_value as ch8 then +---- add_string_field (a_name, ch8.out) +-- elseif attached {CHARACTER_32} a_value as ch32 then +---- add_string_field (a_name, ch32.out) +-- elseif attached {POINTER} a_value as ptr then +---- add_string_field (a_name, ptr.out) +-- elseif attached {ITERABLE [detachable ANY]} a_value as arr then +---- add_iterator_field (a_name, arr) +-- elseif attached {TABLE_ITERABLE [detachable ANY, READABLE_STRING_GENERAL]} a_value as tb then +---- add_table_iterator_field (a_name, tb) +-- else +-- check is_supported_type: False end +-- end + end + invariant note diff --git a/src/service/cms_module_webapi.e b/src/service/webapi/cms_module_webapi.e similarity index 100% rename from src/service/cms_module_webapi.e rename to src/service/webapi/cms_module_webapi.e diff --git a/src/service/webapi/cms_webapi_handler.e b/src/service/webapi/cms_webapi_handler.e new file mode 100644 index 0000000..6d9a4b5 --- /dev/null +++ b/src/service/webapi/cms_webapi_handler.e @@ -0,0 +1,100 @@ +note + description: "[ + Objects that ... + ]" + author: "$Author$" + date: "$Date$" + revision: "$Revision$" + +deferred class + CMS_WEBAPI_HANDLER + +inherit + WSF_HANDLER + + CMS_API_ACCESS + + CMS_ENCODERS + + REFACTORING_HELPER + +feature {NONE} -- Initialization + + make (a_api: CMS_API) + -- Initialize Current handler with `a_api'. + do + api := a_api + end + +feature -- API Service + + api: CMS_API + +feature -- Factory + + new_webapi_response (req: WSF_REQUEST; res: WSF_RESPONSE): HM_WEBAPI_RESPONSE + do +-- create {MD_WEBAPI_RESPONSE} Result.make (req, res, api) + create {JSON_WEBAPI_RESPONSE} Result.make (req, res, api) + end + + new_wepapi_error_response (msg: detachable READABLE_STRING_GENERAL; req: WSF_REQUEST; res: WSF_RESPONSE): HM_WEBAPI_RESPONSE + do + Result := new_webapi_response (req, res) + if msg /= Void then + Result.add_string_field ("error", msg) + else + Result.add_string_field ("error", "True") + end + end + + send_not_found (m: detachable READABLE_STRING_GENERAL; req: WSF_REQUEST; res: WSF_RESPONSE) + local + rep: HM_WEBAPI_RESPONSE + do + if m /= Void then + rep := new_wepapi_error_response (m, req, res) + else + rep := new_wepapi_error_response ("Not found", req, res) + end + rep.set_status_code ({HTTP_STATUS_CODE}.not_found) + rep.execute + end + + send_access_denied (m: detachable READABLE_STRING_GENERAL; req: WSF_REQUEST; res: WSF_RESPONSE) + local + rep: HM_WEBAPI_RESPONSE + do + if m /= Void then + rep := new_wepapi_error_response (m, req, res) + else + rep := new_wepapi_error_response ("Access denied", req, res) + end + rep.set_status_code ({HTTP_STATUS_CODE}.user_access_denied) + rep.execute + end + + send_bad_request (m: detachable READABLE_STRING_GENERAL; req: WSF_REQUEST; res: WSF_RESPONSE) + local + rep: HM_WEBAPI_RESPONSE + do + if m /= Void then + rep := new_wepapi_error_response (m, req, res) + else + rep := new_wepapi_error_response ("Bad request", req, res) + end + rep.set_status_code ({HTTP_STATUS_CODE}.bad_request) + rep.execute + end + +feature {NONE} -- Builder + + add_user_links_to (u: CMS_USER; rep: HM_WEBAPI_RESPONSE) + do + rep.add_link ("account", "user/" + u.id.out, rep.api.webapi_path ("/user/" + u.id.out)) + 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 diff --git a/src/service/cms_with_webapi.e b/src/service/webapi/cms_with_webapi.e similarity index 100% rename from src/service/cms_with_webapi.e rename to src/service/webapi/cms_with_webapi.e