Mercurial > prosody-modules
view mod_http_admin_api/mod_http_admin_api.lua @ 4362:116c88c28532
mod_http_admin_api: restructure group-related info in API
- Return the members of the group right in the get_group_by_id
call. This is an O(1) of extra work.
- Remove the groups attribute from get_user_by_name as that is
O(n) of work and rarely immediately needed.
The replacement for the group membership information in the user
is for now to use the group API and iterate; future work may fix
that.
author | Jonas Schäfer <jonas@wielicki.name> |
---|---|
date | Wed, 20 Jan 2021 15:30:29 +0100 |
parents | 7f1f3b79d991 |
children | 636d56bbad97 |
line wrap: on
line source
local usermanager = require "core.usermanager"; local json = require "util.json"; module:depends("http"); local invites = module:depends("invites"); local tokens = module:depends("tokenauth"); local mod_pep = module:depends("pep"); local group_store = module:open_store("groups"); local group_memberships = module:open_store("groups", "map"); local json_content_type = "application/json"; local www_authenticate_header = ("Bearer realm=%q"):format(module.host.."/"..module.name); local function check_credentials(request) local auth_type, auth_data = string.match(request.headers.authorization or "", "^(%S+)%s(.+)$"); if not (auth_type and auth_data) then return false; end if auth_type == "Bearer" then local token_info = tokens.get_token_info(auth_data); if not token_info or not token_info.session then return false; end return token_info.session; end return nil; end function check_auth(routes) local function check_request_auth(event) local session = check_credentials(event.request); if not session then event.response.headers.authorization = www_authenticate_header; return false, 401; elseif session.auth_scope ~= "prosody:scope:admin" then return false, 403; end event.session = session; return true; end for route, handler in pairs(routes) do routes[route] = function (event, ...) local permit, code = check_request_auth(event); if not permit then return code; end return handler(event, ...); end; end return routes; end local function token_info_to_invite_info(token_info) local additional_data = token_info.additional_data; local groups = additional_data and additional_data.groups or nil; local source = additional_data and additional_data.source or nil; return { id = token_info.token; type = token_info.type; reusable = not not token_info.reusable; inviter = token_info.inviter; jid = token_info.jid; uri = token_info.uri; landing_page = token_info.landing_page; created_at = token_info.created_at; expires = token_info.expires; groups = groups; source = source; }; end function list_invites(event) local invites_list = {}; for token, invite in invites.pending_account_invites() do --luacheck: ignore 213/token table.insert(invites_list, token_info_to_invite_info(invite)); end table.sort(invites_list, function (a, b) return a.created_at < b.created_at; end); event.response.headers["Content-Type"] = json_content_type; return json.encode_array(invites_list); end function get_invite_by_id(event, invite_id) local invite = invites.get_account_invite_info(invite_id); if not invite then return 404; end event.response.headers["Content-Type"] = json_content_type; return json.encode(token_info_to_invite_info(invite)); end function create_invite(event) local invite_options; local request = event.request; if request.body and #request.body > 0 then if request.headers.content_type ~= json_content_type then module:log("warn", "Invalid content type"); return 400; end invite_options = json.decode(event.request.body); if not invite_options then module:log("warn", "Invalid JSON"); return 400; end else invite_options = {}; end local invite; if invite_options.reusable then invite = invites.create_group(invite_options.groups, invite_options.ttl, { source = "admin_api/"..event.session.username; }); else invite = invites.create_account(nil, { source = "admin_api/"..event.session.username; groups = invite_options.groups; }); end if not invite then return 500; end event.response.headers["Content-Type"] = json_content_type; return json.encode(token_info_to_invite_info(invite)); end function delete_invite(event, invite_id) --luacheck: ignore 212/event if not invites.delete_account_invite(invite_id) then return 404; end return 200; end local function get_user_info(username) if not usermanager.user_exists(username, module.host) then return nil; end local display_name; do local pep_service = mod_pep.get_pep_service(username); local ok, _, nick_item = pep_service:get_last_item("http://jabber.org/protocol/nick", true); if ok and nick_item then display_name = nick_item:get_child_text("nick", "http://jabber.org/protocol/nick"); end end return { username = username; display_name = display_name; }; end local function get_user_groups(username) local groups; do local group_set = group_memberships:get_all(username); if group_set and next(group_set) then groups = {}; for group_id in pairs(group_set) do table.insert(groups, group_id); end end end return groups; end function list_users(event) local user_list = {}; for username in usermanager.users(module.host) do table.insert(user_list, get_user_info(username)); end event.response.headers["Content-Type"] = json_content_type; return json.encode_array(user_list); end function get_user_by_name(event, username) local property do local name, sub_path = username:match("^([^/]+)/(%w+)$"); if name then username = name; property = sub_path; end end if property == "groups" then return json.encode(get_user_groups(username)); end local user_info = get_user_info(username); if not user_info then return 404; end event.response.headers["Content-Type"] = json_content_type; return json.encode(user_info); end function delete_user(event, username) --luacheck: ignore 212/event if not usermanager.delete_user(username, module.host) then return 404; end return 200; end function list_groups(event) local group_list = {}; for group_id in group_store:users() do table.insert(group_list, { id = group_id; name = group_id; members = group_store:get(group_id); }); end event.response.headers["Content-Type"] = json_content_type; return json.encode_array(group_list); end function get_group_by_id(event, group_id) local group = group_store:get(group_id); if not group then return 404; end event.response.headers["Content-Type"] = json_content_type; return json.encode({ id = group_id; name = group_id; members = group; }); end function create_group(event) local request = event.request; if request.headers.content_type ~= json_content_type or (not request.body or #request.body == 0) then return 400; end local group = json.decode(event.request.body); if not group then return 400; end local ok = group_store:set(group.id, {}); if not ok then return 500; end event.response.headers["Content-Type"] = json_content_type; return json.encode({ id = group.id or group.name; name = group.name; }); end function delete_group(event, group_id) --luacheck: ignore 212/event if not group_id then return 400; end if not group_store:set(group_id, nil) then return 500; end return 200; end module:provides("http", { route = check_auth { ["GET /invites"] = list_invites; ["GET /invites/*"] = get_invite_by_id; ["POST /invites"] = create_invite; ["DELETE /invites/*"] = delete_invite; ["GET /users"] = list_users; ["GET /users/*"] = get_user_by_name; ["DELETE /users/*"] = delete_user; ["GET /groups"] = list_groups; ["GET /groups/*"] = get_group_by_id; ["POST /groups"] = create_group; ["DELETE /groups/*"] = delete_group; }; });