gooderp18绿色标准版
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.

404 line
12KB

  1. ##########################################################################
  2. #
  3. # pgAdmin 4 - PostgreSQL Tools
  4. #
  5. # Copyright (C) 2013 - 2020, The pgAdmin Development Team
  6. # This software is released under the PostgreSQL Licence
  7. #
  8. ##########################################################################
  9. """
  10. Implements the server-side session management.
  11. Credit/Reference: http://flask.pocoo.org/snippets/109/
  12. Modified to support both Python 2.6+ & Python 3.x
  13. """
  14. import base64
  15. import datetime
  16. import hmac
  17. import hashlib
  18. import os
  19. import random
  20. import string
  21. import time
  22. import config
  23. from uuid import uuid4
  24. from threading import Lock
  25. from flask import current_app, request, flash, redirect
  26. from flask_login import login_url
  27. from pickle import dump, load
  28. from collections import OrderedDict
  29. from flask.sessions import SessionInterface, SessionMixin
  30. from werkzeug.datastructures import CallbackDict
  31. from pgadmin.utils.ajax import make_json_response
  32. def _calc_hmac(body, secret):
  33. return base64.b64encode(
  34. hmac.new(
  35. secret.encode(), body.encode(), hashlib.sha1
  36. ).digest()
  37. ).decode()
  38. sess_lock = Lock()
  39. LAST_CHECK_SESSION_FILES = None
  40. class ManagedSession(CallbackDict, SessionMixin):
  41. def __init__(self, initial=None, sid=None, new=False, randval=None,
  42. hmac_digest=None):
  43. def on_update(self):
  44. self.modified = True
  45. CallbackDict.__init__(self, initial, on_update)
  46. self.sid = sid
  47. self.new = new
  48. self.modified = False
  49. self.randval = randval
  50. self.last_write = None
  51. self.force_write = False
  52. self.hmac_digest = hmac_digest
  53. self.permanent = True
  54. def sign(self, secret):
  55. if not self.hmac_digest:
  56. if hasattr(string, 'lowercase'):
  57. population = string.lowercase
  58. # If script is running under python3
  59. elif hasattr(string, 'ascii_lowercase'):
  60. population = string.ascii_lowercase
  61. population += string.digits
  62. self.randval = ''.join(random.sample(population, 20))
  63. self.hmac_digest = _calc_hmac(
  64. '%s:%s' % (self.sid, self.randval), secret)
  65. class SessionManager(object):
  66. def new_session(self):
  67. 'Create a new session'
  68. raise NotImplementedError
  69. def exists(self, sid):
  70. 'Does the given session-id exist?'
  71. raise NotImplementedError
  72. def remove(self, sid):
  73. 'Remove the session'
  74. raise NotImplementedError
  75. def get(self, sid, digest):
  76. 'Retrieve a managed session by session-id, checking the HMAC digest'
  77. raise NotImplementedError
  78. def put(self, session):
  79. 'Store a managed session'
  80. raise NotImplementedError
  81. class CachingSessionManager(SessionManager):
  82. def __init__(self, parent, num_to_store, skip_paths=[]):
  83. self.parent = parent
  84. self.num_to_store = num_to_store
  85. self._cache = OrderedDict()
  86. self.skip_paths = skip_paths
  87. def _normalize(self):
  88. if len(self._cache) > self.num_to_store:
  89. # Flush 20% of the cache
  90. with sess_lock:
  91. while len(self._cache) > (self.num_to_store * 0.8):
  92. self._cache.popitem(False)
  93. def new_session(self):
  94. session = self.parent.new_session()
  95. # Do not store the session if skip paths
  96. for sp in self.skip_paths:
  97. if request.path.startswith(sp):
  98. return session
  99. with sess_lock:
  100. self._cache[session.sid] = session
  101. self._normalize()
  102. return session
  103. def remove(self, sid):
  104. with sess_lock:
  105. self.parent.remove(sid)
  106. if sid in self._cache:
  107. del self._cache[sid]
  108. def exists(self, sid):
  109. with sess_lock:
  110. if sid in self._cache:
  111. return True
  112. return self.parent.exists(sid)
  113. def get(self, sid, digest):
  114. session = None
  115. with sess_lock:
  116. if sid in self._cache:
  117. session = self._cache[sid]
  118. if session and session.hmac_digest != digest:
  119. session = None
  120. # reset order in Dict
  121. del self._cache[sid]
  122. if not session:
  123. session = self.parent.get(sid, digest)
  124. # Do not store the session if skip paths
  125. for sp in self.skip_paths:
  126. if request.path.startswith(sp):
  127. return session
  128. self._cache[sid] = session
  129. self._normalize()
  130. return session
  131. def put(self, session):
  132. with sess_lock:
  133. self.parent.put(session)
  134. # Do not store the session if skip paths
  135. for sp in self.skip_paths:
  136. if request.path.startswith(sp):
  137. return
  138. if session.sid in self._cache:
  139. try:
  140. del self._cache[session.sid]
  141. except Exception:
  142. pass
  143. self._cache[session.sid] = session
  144. self._normalize()
  145. class FileBackedSessionManager(SessionManager):
  146. def __init__(self, path, secret, disk_write_delay, skip_paths=[]):
  147. self.path = path
  148. self.secret = secret
  149. self.disk_write_delay = disk_write_delay
  150. if not os.path.exists(self.path):
  151. os.makedirs(self.path)
  152. self.skip_paths = skip_paths
  153. def exists(self, sid):
  154. fname = os.path.join(self.path, sid)
  155. return os.path.exists(fname)
  156. def remove(self, sid):
  157. fname = os.path.join(self.path, sid)
  158. if os.path.exists(fname):
  159. os.unlink(fname)
  160. def new_session(self):
  161. sid = str(uuid4())
  162. fname = os.path.join(self.path, sid)
  163. while os.path.exists(fname):
  164. sid = str(uuid4())
  165. fname = os.path.join(self.path, sid)
  166. # Do not store the session if skip paths
  167. for sp in self.skip_paths:
  168. if request.path.startswith(sp):
  169. return ManagedSession(sid=sid)
  170. # touch the file
  171. with open(fname, 'wb'):
  172. return ManagedSession(sid=sid)
  173. return ManagedSession(sid=sid)
  174. def get(self, sid, digest):
  175. 'Retrieve a managed session by session-id, checking the HMAC digest'
  176. fname = os.path.join(self.path, sid)
  177. data = None
  178. hmac_digest = None
  179. randval = None
  180. if os.path.exists(fname):
  181. try:
  182. with open(fname, 'rb') as f:
  183. randval, hmac_digest, data = load(f)
  184. except Exception:
  185. pass
  186. if not data:
  187. return self.new_session()
  188. # This assumes the file is correct, if you really want to
  189. # make sure the session is good from the server side, you
  190. # can re-calculate the hmac
  191. if hmac_digest != digest:
  192. return self.new_session()
  193. return ManagedSession(
  194. data, sid=sid, randval=randval, hmac_digest=hmac_digest
  195. )
  196. def put(self, session):
  197. """Store a managed session"""
  198. current_time = time.time()
  199. if not session.hmac_digest:
  200. session.sign(self.secret)
  201. elif not session.force_write and session.last_write is not None and \
  202. (current_time - float(session.last_write)) < \
  203. self.disk_write_delay:
  204. return
  205. session.last_write = current_time
  206. session.force_write = False
  207. # Do not store the session if skip paths
  208. for sp in self.skip_paths:
  209. if request.path.startswith(sp):
  210. return
  211. fname = os.path.join(self.path, session.sid)
  212. with open(fname, 'wb') as f:
  213. dump(
  214. (session.randval, session.hmac_digest, dict(session)),
  215. f
  216. )
  217. class ManagedSessionInterface(SessionInterface):
  218. def __init__(self, manager):
  219. self.manager = manager
  220. def open_session(self, app, request):
  221. cookie_val = request.cookies.get(app.session_cookie_name)
  222. if not cookie_val or '!' not in cookie_val:
  223. return self.manager.new_session()
  224. sid, digest = cookie_val.split('!', 1)
  225. if self.manager.exists(sid):
  226. return self.manager.get(sid, digest)
  227. return self.manager.new_session()
  228. def save_session(self, app, session, response):
  229. domain = self.get_cookie_domain(app)
  230. if not session:
  231. self.manager.remove(session.sid)
  232. if session.modified:
  233. response.delete_cookie(app.session_cookie_name, domain=domain)
  234. return
  235. if not session.modified:
  236. # No need to save an unaltered session
  237. # TODO: put logic here to test if the cookie is older than N days,
  238. # if so, update the expiration date
  239. return
  240. self.manager.put(session)
  241. session.modified = False
  242. cookie_exp = self.get_expiration_time(app, session)
  243. response.set_cookie(
  244. app.session_cookie_name,
  245. '%s!%s' % (session.sid, session.hmac_digest),
  246. expires=cookie_exp, httponly=True, domain=domain
  247. )
  248. def create_session_interface(app, skip_paths=[]):
  249. return ManagedSessionInterface(
  250. CachingSessionManager(
  251. FileBackedSessionManager(
  252. app.config['SESSION_DB_PATH'],
  253. app.config['SECRET_KEY'],
  254. app.config.get('PGADMIN_SESSION_DISK_WRITE_DELAY', 10),
  255. skip_paths
  256. ),
  257. 1000,
  258. skip_paths
  259. ))
  260. def pga_unauthorised():
  261. lm = current_app.login_manager
  262. login_message = None
  263. if lm.login_message:
  264. if lm.localize_callback is not None:
  265. login_message = lm.localize_callback(lm.login_message)
  266. else:
  267. login_message = lm.login_message
  268. if not lm.login_view:
  269. # Only 401 is not enough to distinguish pgAdmin login is required.
  270. # There are other cases when we return 401. For eg. wrong password
  271. # supplied while connecting to server.
  272. # So send additional 'info' message.
  273. return make_json_response(
  274. status=401,
  275. success=0,
  276. errormsg=login_message,
  277. info='PGADMIN_LOGIN_REQUIRED'
  278. )
  279. # flash messages are only required if the request was from a
  280. # security page, otherwise it will be redirected to login page
  281. # anyway
  282. if login_message and 'security' in request.endpoint:
  283. flash(login_message, category=lm.login_message_category)
  284. return redirect(login_url(lm.login_view, request.url))
  285. def cleanup_session_files():
  286. """
  287. This function will iterate through session directory and check the last
  288. modified time, if it older than (session expiration time + 1) days then
  289. delete that file.
  290. """
  291. iterate_session_files = False
  292. global LAST_CHECK_SESSION_FILES
  293. if LAST_CHECK_SESSION_FILES is None or \
  294. datetime.datetime.now() >= LAST_CHECK_SESSION_FILES + \
  295. datetime.timedelta(hours=config.CHECK_SESSION_FILES_INTERVAL):
  296. iterate_session_files = True
  297. LAST_CHECK_SESSION_FILES = datetime.datetime.now()
  298. if iterate_session_files:
  299. for root, dirs, files in os.walk(
  300. current_app.config['SESSION_DB_PATH']):
  301. for file_name in files:
  302. absolute_file_name = os.path.join(root, file_name)
  303. st = os.stat(absolute_file_name)
  304. # Get the last modified time of the session file
  305. last_modified_time = \
  306. datetime.datetime.fromtimestamp(st.st_mtime)
  307. # Calculate session file expiry time.
  308. file_expiration_time = \
  309. last_modified_time + \
  310. current_app.permanent_session_lifetime + \
  311. datetime.timedelta(days=1)
  312. if file_expiration_time <= datetime.datetime.now() and \
  313. os.path.exists(absolute_file_name):
  314. os.unlink(absolute_file_name)
上海开阖软件有限公司 沪ICP备12045867号-1