You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

mod_muc_kick_participant.lua 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. -- http endpoint to kick participants, access is based on provided jwt token
  2. -- the correct jigasi we fined based on the display name and the number provided
  3. -- Copyright (C) 2023-present 8x8, Inc.
  4. local util = module:require "util";
  5. local async_handler_wrapper = util.async_handler_wrapper;
  6. local is_sip_jigasi = util.is_sip_jigasi;
  7. local starts_with = util.starts_with;
  8. local formdecode = require "util.http".formdecode;
  9. local urlencode = require "util.http".urlencode;
  10. local jid = require "util.jid";
  11. local json = require 'cjson.safe';
  12. local muc_domain_prefix = module:get_option_string("muc_mapper_domain_prefix", "conference");
  13. local muc_domain_base = module:get_option_string("muc_mapper_domain_base");
  14. if not muc_domain_base then
  15. module:log("warn", "No 'muc_domain_base' option set, disabling kick check endpoint.");
  16. return ;
  17. end
  18. local json_content_type = "application/json";
  19. local token_util = module:require "token/util".new(module);
  20. local asapKeyServer = module:get_option_string('prosody_password_public_key_repo_url', '');
  21. if asapKeyServer == '' then
  22. module:log('warn', 'No "prosody_password_public_key_repo_url" option set, disabling kick endpoint.');
  23. return ;
  24. end
  25. token_util:set_asap_key_server(asapKeyServer);
  26. --- Verifies the token
  27. -- @param token the token we received
  28. -- @param room_address the full room address jid
  29. -- @return true if values are ok or false otherwise
  30. function verify_token(token, room_address)
  31. if token == nil then
  32. module:log("warn", "no token provided for %s", room_address);
  33. return false;
  34. end
  35. local session = {};
  36. session.auth_token = token;
  37. local verified, reason, msg = token_util:process_and_verify_token(session);
  38. if not verified then
  39. module:log("warn", "not a valid token %s %s for %s", tostring(reason), tostring(msg), room_address);
  40. return false;
  41. end
  42. return true;
  43. end
  44. -- Validates the request by checking for required url param room and
  45. -- validates the token provided with the request
  46. -- @param request - The request to validate.
  47. -- @return [error_code, room]
  48. local function validate_and_get_room(request)
  49. if not request.url.query then
  50. module:log("warn", "No query");
  51. return 400, nil;
  52. end
  53. local params = formdecode(request.url.query);
  54. local room_name = urlencode(params.room) or "";
  55. local subdomain = urlencode(params.prefix) or "";
  56. if not room_name then
  57. module:log("warn", "Missing room param for %s", room_name);
  58. return 400, nil;
  59. end
  60. local room_address = jid.join(room_name, muc_domain_prefix.."."..muc_domain_base);
  61. if subdomain and subdomain ~= "" then
  62. room_address = "["..subdomain.."]"..room_address;
  63. end
  64. -- verify access
  65. local token = request.headers["authorization"]
  66. if token and starts_with(token,'Bearer ') then
  67. token = token:sub(8,#token)
  68. end
  69. if not verify_token(token, room_address) then
  70. return 403, nil;
  71. end
  72. local room = get_room_from_jid(room_address);
  73. if not room then
  74. module:log("warn", "No room found for %s", room_address);
  75. return 404, nil;
  76. else
  77. return 200, room;
  78. end
  79. end
  80. function handle_kick_participant (event)
  81. local request = event.request;
  82. if request.headers.content_type ~= json_content_type
  83. or (not request.body or #request.body == 0) then
  84. module:log("warn", "Wrong content type: %s", request.headers.content_type);
  85. return { status_code = 400; }
  86. end
  87. local params, error = json.decode(request.body);
  88. if not params then
  89. module:log("warn", "Missing params error:%s", error);
  90. return { status_code = 400; }
  91. end
  92. local number = params["number"];
  93. local participantId = params["participantId"];
  94. if (not number and not participantId) or (number and participantId) then
  95. module:log("warn", "Invalid parameters: exactly one of 'number' or 'participantId' must be provided.");
  96. return { status_code = 400; };
  97. end
  98. local error_code, room = validate_and_get_room(request);
  99. if error_code and error_code ~= 200 then
  100. module:log("error", "Error validating %s", error_code);
  101. return { error_code = 400; }
  102. end
  103. if not room then
  104. return { status_code = 404; }
  105. end
  106. for _, occupant in room:each_occupant() do
  107. local pr = occupant:get_presence();
  108. if is_participant_match(pr, number, participantId) then
  109. room:set_role(true, occupant.nick, nil);
  110. module:log('info', 'Occupant kicked %s from %s', occupant.nick, room.jid);
  111. return { status_code = 200; }
  112. end
  113. end
  114. -- not found participant to kick
  115. return { status_code = 404; };
  116. end
  117. function is_participant_match(pr, number, participantId)
  118. if number then
  119. local displayName = pr:get_child_text('nick', 'http://jabber.org/protocol/nick');
  120. return is_sip_jigasi(pr) and displayName and starts_with(displayName, number);
  121. elseif participantId then
  122. local from = pr.attr.from;
  123. local _, _, from_resource = jid.split(from);
  124. if from_resource then
  125. return from_resource == participantId;
  126. end
  127. end
  128. return false;
  129. end
  130. module:log("info","Adding http handler for /kick-participant on %s", module.host);
  131. module:depends("http");
  132. module:provides("http", {
  133. default_path = "/";
  134. route = {
  135. ["PUT kick-participant"] = function (event) return async_handler_wrapper(event, handle_kick_participant) end;
  136. };
  137. });