UiPasswordPlugin.py 5.3 KB

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