Added auth_api: CMS_AUTHENTICATION_API, and for now moved registration instructions inside.

Added authentication module webapi, to provide registration via webapi.
Improved the roles display by providing table of permissions if asked.
Added various links in primary tabs to navigate back to roles or users, depending on the page.
Added datetime to-from string converters in CMS_ENCODERS.
Start removing CMS_ADMINISTRABLE.
Added permission to use simple core access token.
Added webapi for users: list, new, register.
This commit is contained in:
Jocelyn Fiat
2017-09-12 23:07:45 +02:00
parent b83a050a1d
commit e04138c89e
27 changed files with 799 additions and 208 deletions

View File

@@ -52,6 +52,27 @@ feature -- Encoders
Result := percent_encoder.percent_encoded_string (a_string)
end
date_time_to_string (dt: DATE_TIME): STRING_8
-- Date time `dt` converted to standard output (using RFC1123)
local
hd: HTTP_DATE
do
create hd.make_from_date_time (dt)
Result := hd.rfc1123_string
end
date_time_from_string (s: READABLE_STRING_GENERAL): detachable DATE_TIME
-- Date time from string `s`, if valid.
local
hd: HTTP_DATE
do
create hd.make_from_string (s)
check not hd.has_error end
if not hd.has_error then
Result := hd.date_time
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)"

View File

@@ -74,12 +74,24 @@ feature -- Conversion
feature -- Basic operation
process (a_response: CMS_RESPONSE_I)
do
if attached {CMS_RESPONSE} a_response as rep_cms then
process_cms_response (rep_cms)
else
-- FIXME: check webapi for hook need!
process_form (a_response.request, Void, Void)
end
end
feature -- CMS response
prepare (a_response: CMS_RESPONSE)
do
a_response.api.hooks.invoke_form_alter (Current, Void, a_response)
end
process (a_response: CMS_RESPONSE)
process_cms_response (a_response: CMS_RESPONSE)
do
process_form (a_response.request, agent on_prepared (a_response, ?), agent on_processed (a_response, ?))
end
@@ -96,6 +108,41 @@ feature -- Basic operation
end
end
feature -- Webapi processing
process_webapi_response ()
do
end
feature -- Helpers
extend_text_field (a_name: READABLE_STRING_8; a_text: detachable READABLE_STRING_GENERAL)
-- Extend new text field `a_name` with value `a_text`.
local
tf: WSF_FORM_TEXT_INPUT
do
if a_text /= Void then
create tf.make_with_text (a_name, a_text.as_string_32)
else
create tf.make (a_name)
end
extend (tf)
end
extend_password_field (a_name: READABLE_STRING_8; a_text: detachable READABLE_STRING_GENERAL)
-- Extend new password field `a_name` with value `a_text`.
local
tf: WSF_FORM_PASSWORD_INPUT
do
if a_text /= Void then
create tf.make_with_text (a_name, a_text.as_string_32)
else
create tf.make (a_name)
end
extend (tf)
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)"

View File

@@ -85,7 +85,7 @@ feature {CMS_API} -- Module management
create u.make ("admin")
u.set_password ("istrator#")
u.set_email (a_api.setup.site_email)
u.set_status ({CMS_USER}.active)
u.mark_active
a_api.user_api.new_user (u)
--| Node
@@ -130,6 +130,7 @@ feature -- Security
Result.force ("import core")
Result.force ("admin path_alias")
Result.force ("edit path_alias")
Result.force ("use access_token")
end
feature {CMS_EXECUTION} -- Administration

View File

@@ -329,7 +329,7 @@ feature -- User roles.
loop
lst := ic.item.permissions
if
attached {CMS_ADMINISTRABLE} ic.item as adm and then
attached {CMS_WITH_MODULE_ADMINISTRATION} ic.item as adm and then
attached adm.module_administration.permissions as adm_permissions and then
not adm_permissions.is_empty
then

View File

@@ -119,17 +119,18 @@ feature -- Process Edit
ti.set_is_readonly (True)
fs.extend (ti)
end
create l_new_access_token_form.make (api.webapi_path ("access_token"), Void)
l_new_access_token_form.set_method_post
if l_access_token /= Void then
l_new_access_token_form.extend (create {WSF_FORM_SUBMIT_INPUT}.make_with_text ("access_token_op", "Refresh Access Token"))
else
l_new_access_token_form.extend (create {WSF_FORM_SUBMIT_INPUT}.make_with_text ("access_token_op", "Create Access Token"))
if api.user_has_permission (a_user, "use access_token") then
create l_new_access_token_form.make (api.webapi_path ("access_token"), Void)
l_new_access_token_form.set_method_post
if l_access_token /= Void then
l_new_access_token_form.extend (create {WSF_FORM_SUBMIT_INPUT}.make_with_text ("access_token_op", "Refresh Access Token"))
else
l_new_access_token_form.extend (create {WSF_FORM_SUBMIT_INPUT}.make_with_text ("access_token_op", "Create Access Token"))
end
l_new_access_token_form.extend (create {WSF_FORM_HIDDEN_INPUT}.make_with_text ("destination", request.percent_encoded_path_info))
a_form.put_widget_after_form (l_new_access_token_form)
a_form.extend (fs)
end
l_new_access_token_form.extend (create {WSF_FORM_HIDDEN_INPUT}.make_with_text ("destination", request.percent_encoded_path_info))
a_form.put_widget_after_form (l_new_access_token_form)
a_form.extend (fs)
end
end
end

View File

@@ -18,6 +18,7 @@ feature -- Basic operations
-- Execute the filter.
local
tok: READABLE_STRING_GENERAL
u: CMS_USER
do
if
attached req.http_authorization as l_auth and then
@@ -26,7 +27,10 @@ feature -- Basic operations
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)
u := lst.first
if api.user_has_permission (u, "use access_token") then
api.set_user (u)
end
end
end
end

View File

@@ -39,6 +39,7 @@ feature {NONE} -- Router/administration
a_router.handle ("/", l_root, a_router.methods_get)
a_router.handle ("/user/{uid}/access_token", create {CMS_ACCESS_TOKEN_WEBAPI_HANDLER}.make (a_api), a_router.methods_get_post)
a_router.handle ("/user/{uid}", create {CMS_USER_WEBAPI_HANDLER}.make (a_api), a_router.methods_get)
a_router.handle ("/user/", create {CMS_USERS_WEBAPI_HANDLER}.make (a_api), a_router.methods_get_post)
end
feature -- Access: filter

View File

@@ -21,6 +21,8 @@ feature -- Execution
do
if req.is_get_request_method then
execute_get (req, res)
-- elseif req.is_post_request_method then
-- execute_post (req, res)
else
send_bad_request (Void, req, res)
end
@@ -45,16 +47,24 @@ feature -- Execution
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 ("uid", l_user.id.out)
rep.add_string_field ("name", l_user.name)
if attached l_user.email as l_email then
rep.add_string_field ("email", l_email)
end
if attached u.profile_name as l_profile_name then
if not l_user.is_active then
rep.add_boolean_field ("is_active", False)
end
if attached l_user.profile_name as l_profile_name then
rep.add_string_field ("profile_name", l_profile_name)
end
add_user_links_to (u, rep)
if attached l_user.creation_date as dt then
rep.add_string_field ("creation_date", date_time_to_string (dt))
end
if attached l_user.last_login_date as dt then
rep.add_string_field ("last_login_date", date_time_to_string (dt))
end
add_user_links_to (l_user, rep)
else
rep := new_wepapi_error_response ("denied", req, res)
rep.set_status_code ({HTTP_STATUS_CODE}.user_access_denied)
@@ -74,6 +84,39 @@ feature -- Execution
end
end
-- execute_post (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 and then api.has_permission ("admin users") 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
-- 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)"

View File

@@ -0,0 +1,175 @@
note
description: "Summary description for {CMS_USERS_WEBAPI_HANDLER}."
date: "$Date$"
revision: "$Revision$"
class
CMS_USERS_WEBAPI_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'.
do
if req.is_get_request_method then
execute_get (req, res)
elseif req.is_post_request_method then
execute_post (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
l_params: CMS_DATA_QUERY_PARAMETERS
tb: STRING_TABLE [detachable ANY]
arr: ARRAYED_LIST [STRING_TABLE [detachable ANY]]
l_full: BOOLEAN
nb: INTEGER
do
if api.has_permissions (<<"admin users", "view users">>) then
if attached req.query_parameter ("full") as p and then p.is_case_insensitive_equal ("yes") then
l_full := True
end
rep := new_webapi_response (req, res)
nb := api.user_api.users_count
rep.add_integer_64_field ("users_count", nb)
create l_params.make (0, nb.to_natural_32)
create arr.make (nb)
across
api.user_api.recent_users (l_params) as ic
loop
l_user := ic.item
create tb.make_caseless (5)
tb.force (api.webapi_path ("user/" + l_user.id.out), "href")
tb.force (l_user.id.out, "uid")
tb.force (l_user.name, "name")
if attached l_user.profile_name as pn then
tb.force (pn, "profile_name")
end
if not l_user.is_active then
tb.force (False, "is_active")
end
if l_full then
if l_user.has_email then
tb.force (l_user.email, "email")
end
if attached l_user.creation_date as dt then
tb.force (date_time_to_string (dt), "creation_date")
end
if attached l_user.last_login_date as dt then
tb.force (date_time_to_string (dt), "last_login_date")
end
end
arr.force (tb)
end
rep.add_iterator_field ("users", arr)
rep.add_self (req.percent_encoded_path_info)
rep.execute
else
send_access_denied (Void, req, res)
end
end
execute_post (req: WSF_REQUEST; res: WSF_RESPONSE)
-- Execute handler for `req' and respond in `res'.
local
rep: HM_WEBAPI_RESPONSE
l_user: detachable CMS_USER
f: WSF_FORM
tf: WSF_FORM_TEXT_INPUT
err: STRING_32
do
if api.has_permission ("admin users") then
create f.make (req.percent_encoded_path_info, "new-user")
create tf.make ("username"); f.extend (tf)
create tf.make ("password"); f.extend (tf)
create tf.make ("email"); f.extend (tf)
create tf.make ("profile_name"); f.extend (tf)
f.process (req, Void, Void)
if attached f.last_data as fd then
create err.make_empty
if not fd.has_error and then attached fd.string_item ("username") as l_name then
if api.user_api.user_by_id_or_name (l_name) /= Void then
err.append ("Username already used!%N")
fd.report_invalid_field ("username", "Username already used!")
else
create l_user.make (l_name)
if attached fd.string_item ("password") as l_passwd then
l_user.set_password (l_passwd)
else
err.append ("Missing password!%N")
fd.report_invalid_field ("username", "Missing password!")
end
if attached fd.string_item ("email") as l_email then
if l_email.is_valid_as_string_8 then
l_user.set_email (l_email.to_string_8)
else
err.append ("Invalid email address!%N")
end
end
if attached fd.string_item ("profile_name") as l_profile_name then
l_user.set_profile_name (l_profile_name)
end
end
end
if fd.has_error then
-- Error !
if attached fd.errors as lst then
create err.make_empty
across
lst as ic
loop
if attached ic.item.field as l_field then
err.append (l_field.name + ": ")
end
if attached ic.item.message as msg then
err.append (msg)
end
end
else
-- Keep `err`.
end
elseif l_user = Void then
err := "Invalid new user request!"
else
err := Void
l_user.mark_active
api.user_api.new_user (l_user)
if api.user_api.has_error then
err := "Could not create user!"
end
end
end
if l_user = Void or else err /= Void then
rep := new_wepapi_error_response (err, req, res)
else
rep := new_webapi_response (req, res)
rep.add_string_field ("uid", l_user.id.out)
add_user_links_to (l_user, rep)
end
rep.add_self (req.percent_encoded_path_info)
rep.execute
else
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

View File

@@ -527,7 +527,7 @@ feature -- CMS links
require
u_with_name: not u.name.is_whitespace
do
Result := link (user_display_name (u), "user/" + u.id.out, Void)
Result := link (real_user_display_name (u), "user/" + u.id.out, Void)
end
feature -- Helpers: URLs
@@ -784,6 +784,11 @@ feature -- Logging
end
end
log_debug (a_category: READABLE_STRING_8; a_message: READABLE_STRING_8; a_link: detachable CMS_LINK)
do
log (a_category, a_message, {CMS_LOG}.level_debug, a_link)
end
feature -- Internationalization (i18n)
translation (a_text: READABLE_STRING_GENERAL; opts: detachable CMS_API_OPTIONS): STRING_32
@@ -1059,7 +1064,7 @@ feature {CMS_EXECUTION} -- Hooks
loop
l_module := ic.item
if is_administration_mode then
if attached {CMS_ADMINISTRABLE} l_module as adm then
if attached {CMS_WITH_MODULE_ADMINISTRATION} l_module as adm then
l_module := adm.module_administration
else
l_module := Void

View File

@@ -194,6 +194,7 @@ feature -- Import
end
else
user_api.new_user (u)
-- FIXME: check what status to use...
a_import_ctx.log ("New user %"" + u.name + "%" -> " + u.id.out + " .")
end
end

View File

@@ -6,7 +6,7 @@ note
deferred class
CMS_WITH_WEBAPI
feature -- Administration
feature -- Webapi
module_webapi: like webapi
-- Associated web api module.