CAT-SOOP is a flexible, programmable learning management system based on the Python programming language. https://catsoop.mit.edu
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.
 
 
 

159 lines
4.5 KiB

  1. # This file is part of CAT-SOOP
  2. # Copyright (c) 2011-2020 by The CAT-SOOP Developers <catsoop-dev@mit.edu>
  3. #
  4. # This program is free software: you can redistribute it and/or modify it under
  5. # the terms of the GNU Affero General Public License as published by the Free
  6. # Software Foundation, either version 3 of the License, or (at your option) any
  7. # later version.
  8. #
  9. # This program is distributed in the hope that it will be useful, but WITHOUT
  10. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  11. # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  12. # details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. """
  17. Simple session handling.
  18. """
  19. import os
  20. import re
  21. import time
  22. import uuid
  23. import traceback
  24. import importlib
  25. from . import util
  26. from . import cslog
  27. from . import debug_log
  28. from . import base_context
  29. importlib.reload(base_context)
  30. LOGGER = debug_log.LOGGER
  31. _nodoc = {"make_session_dir", "LOGGER"}
  32. VALID_SESSION_RE = re.compile(r"^[A-Fa-f0-9]{32}$")
  33. """
  34. Regular expression matching a valid session id name (32 hexadecimal characters)
  35. """
  36. EXPIRE = 48 * 3600
  37. """
  38. Number of seconds since last action to keep a session as valid.
  39. Defaults to 48 hours.
  40. """
  41. SESSION_DIR = os.path.join(base_context.cs_data_root, "_sessions")
  42. """
  43. The directory where sessions will be stored.
  44. """
  45. def new_session_id():
  46. """
  47. Returns a new session ID
  48. **Returns:** a string containing a new session ID
  49. """
  50. return uuid.uuid4().hex
  51. def get_session_id(environ):
  52. """
  53. Returns the appropriate session id for this request, generating a new one
  54. if necessary.
  55. As a side-effect, deletes all expired sessions.
  56. **Parameters:**
  57. * `environ`: a dictionary mapping environment variables to their values
  58. **Returns:** a tuple `(sid, new)`, where `sid` is a string containing the
  59. session ID, and `new` is a Boolean that takes value `True` if the session
  60. ID is new (just now generated), and `False` if the session ID is not new.
  61. """
  62. # clear out dead sessions first
  63. make_session_dir()
  64. now = time.time()
  65. for i in os.listdir(SESSION_DIR):
  66. fullname = os.path.join(SESSION_DIR, i)
  67. try:
  68. if os.stat(fullname).st_mtime < now - EXPIRE:
  69. os.unlink(fullname)
  70. except:
  71. pass
  72. COOKIE_REGEX = re.compile(
  73. r"(?:^|;)\s*catsoop_sid_%s\s*=\s*([^;\s]*)\s*(?:;|$)" % util.catsoop_loc_hash()
  74. )
  75. try:
  76. cookie_sid = COOKIE_REGEX.search(environ["HTTP_COOKIE"]).group(1)
  77. if VALID_SESSION_RE.match(cookie_sid) is None:
  78. LOGGER.error(
  79. "[session] cookie_sid (%s) session mismatch, generating new sid"
  80. % cookie_sid
  81. )
  82. return new_session_id(), True
  83. return cookie_sid, False
  84. except Exception as err:
  85. LOGGER.error(
  86. "[session] Error encountered retrieving session ID with regex, err=%s"
  87. % str(err)
  88. )
  89. LOGGER.error("[session] traceback=%s" % traceback.format_exc())
  90. LOGGER.error("[session] HTTP_COOKIE: %r" % environ.get("HTTP_COOKIE", None))
  91. LOGGER.error("[session] REGEX: %r" % COOKIE_REGEX)
  92. return new_session_id(), True
  93. def make_session_dir():
  94. """
  95. Create the session directory if it does not exist.
  96. """
  97. os.makedirs(SESSION_DIR, exist_ok=True)
  98. def get_session_data(context, sid):
  99. """
  100. Returns the session data associated with a given session ID
  101. **Parameters:**
  102. * `context`: the context associated with this request
  103. * `sid`: the session ID to look up
  104. **Returns:** a dictionary mapping session variables to their values
  105. """
  106. make_session_dir()
  107. fname = os.path.join(SESSION_DIR, sid)
  108. with cslog.log_lock(["_sessions", sid]):
  109. try:
  110. with open(fname, "rb") as f:
  111. out = cslog.unprep(f.read())
  112. except:
  113. out = {} # default to returning empty session
  114. return out
  115. def set_session_data(context, sid, data):
  116. """
  117. Replaces a given session's data with the dictionary provided
  118. **Parameters:**
  119. * `context`: the context associated with this request
  120. * `sid`: the session ID to replace
  121. * `data`: a dictionary mapping session variables to values
  122. **Returns:** `None`
  123. """
  124. make_session_dir()
  125. fname = os.path.join(SESSION_DIR, sid)
  126. with cslog.log_lock(["_sessions", sid]):
  127. with open(fname, "wb") as f:
  128. f.write(cslog.prep(data))