From 053cc000cdde56e48a42434fea848a2e884ebe62 Mon Sep 17 00:00:00 2001 From: Jocelyn Fiat Date: Fri, 25 Aug 2017 09:20:17 +0200 Subject: [PATCH] Updated webapi system. Added specific webapi handler classes for root, user, access token, ... Added new queries to user profile api. Reviewed filter setup depending on the mode (site, admin, webapi). --- src/modules/core/cms_core_module_webapi.e | 188 ------------------ src/modules/core/cms_user_api.e | 10 + src/modules/core/cms_user_profile_api.e | 9 + .../core/handler/user/cms_user_handler.e | 1 - .../user_profile/cms_user_profile_storage_i.e | 9 + .../cms_user_profile_storage_null.e | 8 + .../cms_user_profile_storage_sql.e | 61 +++++- ...cms_core_access_token_webapi_auth_filter.e | 52 +++++ .../core/webapi/cms_core_module_webapi.e | 92 +++++++++ .../cms_core_webapi_access_token_handler.e | 177 +++++++++++++++++ .../webapi/cms_core_webapi_root_handler.e | 36 ++++ .../webapi/cms_core_webapi_user_handler.e | 80 ++++++++ src/service/cms_execution.e | 123 ++++++++++-- src/service/response/hm_webapi_response.e | 20 +- src/service/response/json_webapi_response.e | 105 +++++++++- src/service/{ => webapi}/cms_module_webapi.e | 0 src/service/webapi/cms_webapi_handler.e | 100 ++++++++++ src/service/{ => webapi}/cms_with_webapi.e | 0 18 files changed, 858 insertions(+), 213 deletions(-) delete mode 100644 src/modules/core/cms_core_module_webapi.e create mode 100644 src/modules/core/webapi/cms_core_access_token_webapi_auth_filter.e create mode 100644 src/modules/core/webapi/cms_core_module_webapi.e create mode 100644 src/modules/core/webapi/cms_core_webapi_access_token_handler.e create mode 100644 src/modules/core/webapi/cms_core_webapi_root_handler.e create mode 100644 src/modules/core/webapi/cms_core_webapi_user_handler.e rename src/service/{ => webapi}/cms_module_webapi.e (100%) create mode 100644 src/service/webapi/cms_webapi_handler.e rename src/service/{ => webapi}/cms_with_webapi.e (100%) 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