UiPasswordPlugin.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import string
  2. import random
  3. import time
  4. import json
  5. import re
  6. import os
  7. from Config import config
  8. from Plugin import PluginManager
  9. from util import helper
  10. plugin_dir = os.path.dirname(__file__)
  11. if "sessions" not in locals().keys(): # To keep sessions between module reloads
  12. sessions = {}
  13. whitelisted_client_ids = {}
  14. def showPasswordAdvice(password):
  15. error_msgs = []
  16. if not password or not isinstance(password, str):
  17. error_msgs.append("You have enabled <b>UiPassword</b> plugin, but you forgot to set a password!")
  18. elif len(password) < 8:
  19. error_msgs.append("You are using a very short UI password!")
  20. return error_msgs
  21. @PluginManager.registerTo("UiRequest")
  22. class UiRequestPlugin(object):
  23. sessions = sessions
  24. whitelisted_client_ids = whitelisted_client_ids
  25. last_cleanup = time.time()
  26. def getClientId(self):
  27. return self.env["REMOTE_ADDR"] + " - " + self.env["HTTP_USER_AGENT"]
  28. def whitelistClientId(self, session_id=None):
  29. if not session_id:
  30. session_id = self.getCookies().get("session_id")
  31. client_id = self.getClientId()
  32. if client_id in self.whitelisted_client_ids:
  33. self.whitelisted_client_ids[client_id]["updated"] = time.time()
  34. return False
  35. self.whitelisted_client_ids[client_id] = {
  36. "added": time.time(),
  37. "updated": time.time(),
  38. "session_id": session_id
  39. }
  40. def route(self, path):
  41. # Restict Ui access by ip
  42. if config.ui_restrict and self.env['REMOTE_ADDR'] not in config.ui_restrict:
  43. return self.error403(details=False)
  44. if path.endswith("favicon.ico"):
  45. return self.actionFile("src/Ui/media/img/favicon.ico")
  46. else:
  47. if config.ui_password:
  48. if time.time() - self.last_cleanup > 60 * 60: # Cleanup expired sessions every hour
  49. self.sessionCleanup()
  50. # Validate session
  51. session_id = self.getCookies().get("session_id")
  52. if session_id not in self.sessions and self.getClientId() not in self.whitelisted_client_ids:
  53. # Invalid session id and not whitelisted ip: display login
  54. return self.actionLogin()
  55. return super(UiRequestPlugin, self).route(path)
  56. def actionWrapper(self, path, *args, **kwargs):
  57. if config.ui_password and self.isWrapperNecessary(path):
  58. session_id = self.getCookies().get("session_id")
  59. if session_id not in self.sessions:
  60. # We only accept cookie based auth on wrapper
  61. return self.actionLogin()
  62. else:
  63. self.whitelistClientId()
  64. return super().actionWrapper(path, *args, **kwargs)
  65. # Action: Login
  66. @helper.encodeResponse
  67. def actionLogin(self):
  68. template = open(plugin_dir + "/login.html").read()
  69. self.sendHeader()
  70. posted = self.getPosted()
  71. if posted: # Validate http posted data
  72. if self.sessionCheckPassword(posted.get("password")):
  73. # Valid password, create session
  74. session_id = self.randomString(26)
  75. self.sessions[session_id] = {
  76. "added": time.time(),
  77. "keep": posted.get("keep")
  78. }
  79. self.whitelistClientId(session_id)
  80. # Redirect to homepage or referer
  81. url = self.env.get("HTTP_REFERER", "")
  82. if not url or re.sub(r"\?.*", "", url).endswith("/Login"):
  83. url = "/" + config.homepage
  84. cookie_header = ('Set-Cookie', "session_id=%s;path=/;max-age=2592000;" % session_id) # Max age = 30 days
  85. self.start_response('301 Redirect', [('Location', url), cookie_header])
  86. yield "Redirecting..."
  87. else:
  88. # Invalid password, show login form again
  89. template = template.replace("{result}", "bad_password")
  90. yield template
  91. def randomString(self, nchars):
  92. return ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(nchars))
  93. def sessionCheckPassword(self, password):
  94. return password == config.ui_password
  95. def sessionDelete(self, session_id):
  96. del self.sessions[session_id]
  97. for client_id in list(self.whitelisted_client_ids):
  98. if self.whitelisted_client_ids[client_id]["session_id"] == session_id:
  99. del self.whitelisted_client_ids[client_id]
  100. def sessionCleanup(self):
  101. self.last_cleanup = time.time()
  102. for session_id, session in list(self.sessions.items()):
  103. if session["keep"] and time.time() - session["added"] > 60 * 60 * 24 * 60: # Max 60days for keep sessions
  104. self.sessionDelete(session_id)
  105. elif not session["keep"] and time.time() - session["added"] > 60 * 60 * 24: # Max 24h for non-keep sessions
  106. self.sessionDelete(session_id)
  107. # Action: Display sessions
  108. @helper.encodeResponse
  109. def actionSessions(self):
  110. self.sendHeader()
  111. yield "<pre>"
  112. yield json.dumps(self.sessions, indent=4)
  113. yield "\r\n"
  114. yield json.dumps(self.whitelisted_client_ids, indent=4)
  115. # Action: Logout
  116. @helper.encodeResponse
  117. def actionLogout(self):
  118. # Session id has to passed as get parameter or called without referer to avoid remote logout
  119. session_id = self.getCookies().get("session_id")
  120. if not self.env.get("HTTP_REFERER") or session_id == self.get.get("session_id"):
  121. if session_id in self.sessions:
  122. self.sessionDelete(session_id)
  123. self.start_response('301 Redirect', [
  124. ('Location', "/"),
  125. ('Set-Cookie', "session_id=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT")
  126. ])
  127. yield "Redirecting..."
  128. else:
  129. self.sendHeader()
  130. yield "Error: Invalid session id"
  131. @PluginManager.registerTo("ConfigPlugin")
  132. class ConfigPlugin(object):
  133. def createArguments(self):
  134. group = self.parser.add_argument_group("UiPassword plugin")
  135. group.add_argument('--ui_password', help='Password to access UiServer', default=None, metavar="password")
  136. return super(ConfigPlugin, self).createArguments()
  137. from Translate import translate as lang
  138. @PluginManager.registerTo("UiWebsocket")
  139. class UiWebsocketPlugin(object):
  140. def actionUiLogout(self, to):
  141. permissions = self.getPermissions(to)
  142. if "ADMIN" not in permissions:
  143. return self.response(to, "You don't have permission to run this command")
  144. session_id = self.request.getCookies().get("session_id", "")
  145. self.cmd("redirect", '/Logout?session_id=%s' % session_id)
  146. def addHomepageNotifications(self):
  147. error_msgs = showPasswordAdvice(config.ui_password)
  148. for msg in error_msgs:
  149. self.site.notifications.append(["error", lang[msg]])
  150. return super(UiWebsocketPlugin, self).addHomepageNotifications()