/contrib/matrix_sso_helper.py

https://github.com/poljar/weechat-matrix · Python · 137 lines · 84 code · 34 blank · 19 comment · 13 complexity · 34b4c169869120fea1772e881e11e5a0 MD5 · raw file

  1. #!/usr/bin/env -S python3 -u
  2. # Copyright 2019 The Matrix.org Foundation CIC
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import asyncio
  16. import argparse
  17. import socket
  18. import json
  19. from random import choice
  20. from aiohttp import web
  21. # The browsers ban some known ports, the dynamic port range doesn't contain any
  22. # banned ports, so we use that.
  23. port_range = range(49152, 65535)
  24. shutdown_task = None
  25. def to_weechat(message):
  26. print(json.dumps(message))
  27. async def get_token(request):
  28. global shutdown_task
  29. async def shutdown():
  30. await asyncio.sleep(1)
  31. raise KeyboardInterrupt
  32. token = request.query.get("loginToken")
  33. if not token:
  34. raise KeyboardInterrupt
  35. message = {
  36. "type": "token",
  37. "loginToken": token
  38. }
  39. # Send the token to weechat.
  40. to_weechat(message)
  41. # Initiate a shutdown.
  42. shutdown_task = asyncio.ensure_future(shutdown())
  43. # Respond to the browser.
  44. return web.Response(text="Continuing in Weechat.")
  45. def bind_socket(port=None):
  46. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  47. if port is not None and port != 0:
  48. sock.bind(("localhost", port))
  49. return sock
  50. while True:
  51. port = choice(port_range)
  52. try:
  53. sock.bind(("localhost", port))
  54. except OSError:
  55. continue
  56. return sock
  57. async def wait_for_shutdown_task(_):
  58. if not shutdown_task:
  59. return
  60. try:
  61. await shutdown_task
  62. except KeyboardInterrupt:
  63. pass
  64. def main():
  65. parser = argparse.ArgumentParser(
  66. description="Start a web server that waits for a SSO token to be "
  67. "passed with a GET request"
  68. )
  69. parser.add_argument(
  70. "-p", "--port",
  71. help=("the port that the web server will be listening on, if 0 a "
  72. "random port should be chosen"
  73. ),
  74. type=int,
  75. default=0
  76. )
  77. args = parser.parse_args()
  78. app = web.Application()
  79. app.add_routes([web.get('/', get_token)])
  80. if not 0 <= args.port <= 65535:
  81. raise ValueError("Port needs to be 0-65535")
  82. try:
  83. sock = bind_socket(args.port)
  84. except OSError as e:
  85. message = {
  86. "type": "error",
  87. "message": str(e),
  88. "code": e.errno
  89. }
  90. to_weechat(message)
  91. return
  92. host, port = sock.getsockname()
  93. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  94. message = {
  95. "type": "redirectUrl",
  96. "host": host,
  97. "port": port
  98. }
  99. to_weechat(message)
  100. app.on_shutdown.append(wait_for_shutdown_task)
  101. web.run_app(app, sock=sock, handle_signals=True, print=None)
  102. if __name__ == "__main__":
  103. main()