| 280 | |
| 281 | |
| 282 | class FileBackedSessionManager(SessionManager): |
| 283 | |
| 284 | def __init__(self, path, secret, disk_write_delay, skip_paths=None): |
| 285 | if not secret: |
| 286 | # File-HMAC integrity collapses without a secret. Use raise, not |
| 287 | # assert: -O strips assertions in production. |
| 288 | raise RuntimeError("SECRET_KEY must be non-empty") |
| 289 | self.path = path |
| 290 | self.secret = secret |
| 291 | self.disk_write_delay = disk_write_delay |
| 292 | if not os.path.exists(self.path): |
| 293 | os.makedirs(self.path) |
| 294 | self.skip_paths = [] if skip_paths is None else skip_paths |
| 295 | |
| 296 | def exists(self, sid): |
| 297 | fname = safe_join(self.path, sid) |
| 298 | return fname is not None and os.path.exists(fname) |
| 299 | |
| 300 | def remove(self, sid): |
| 301 | fname = safe_join(self.path, sid) |
| 302 | if fname is not None and os.path.exists(fname): |
| 303 | os.unlink(fname) |
| 304 | |
| 305 | def new_session(self): |
| 306 | sid = str(uuid4()) |
| 307 | fname = safe_join(self.path, sid) |
| 308 | |
| 309 | while fname is not None and os.path.exists(fname): |
| 310 | sid = str(uuid4()) |
| 311 | fname = safe_join(self.path, sid) |
| 312 | |
| 313 | # Do not store the session if skip paths |
| 314 | for sp in self.skip_paths: |
| 315 | if request.path.startswith(sp): |
| 316 | return ManagedSession(sid=sid) |
| 317 | |
| 318 | if fname is None: |
| 319 | raise InternalServerError('Failed to create new session') |
| 320 | |
| 321 | # touch the file with mode 0o600 — see _open_session_file rationale. |
| 322 | with _open_session_file(fname): |
| 323 | return ManagedSession(sid=sid) |
| 324 | |
| 325 | return ManagedSession(sid=sid) |
| 326 | |
| 327 | def get(self, sid, digest): |
| 328 | """Retrieve a managed session by session-id, verifying integrity |
| 329 | and decrypting the body. |
| 330 | |
| 331 | File format: |
| 332 | +-------------------------------------------------------+ |
| 333 | | _HMAC_HEX_LEN bytes : hex HMAC over the ciphertext | |
| 334 | +-------------------------------------------------------+ |
| 335 | | N bytes : Fernet(pickle((randval, digest, data))) | |
| 336 | +-------------------------------------------------------+ |
| 337 | |
| 338 | The HMAC is verified against the ciphertext BEFORE Fernet decrypt |
| 339 | and BEFORE pickle.loads, so a malicious file dropped in the |