class Answer(db.Model): """Contain the user's answers to the secret questions they've chosen.""" id = db.Column(db.Integer(), primary_key=True) answer = db.Column(db.String(256), nullable=False) user_id = db.Column(db.Integer(), db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False) question_id = db.Column(db.Integer(), db.ForeignKey('question.id', ondelete='CASCADE'), nullable=False)
class FailedAttempt(db.Model): """Represent a failed password reset attempt.""" id = db.Column(db.Integer(), primary_key=True) user_id = db.Column(db.Integer(), db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False) time = db.Column(db.DateTime, nullable=False)
class BlacklistedToken(db.Model): """Contain issued JSON web tokens.""" id = db.Column(db.Integer, primary_key=True) user_id = db.Column( db.Integer(), db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False) jti = db.Column(db.String(36), nullable=False) expires = db.Column(db.DateTime, nullable=False) @staticmethod def add_token(token): """ Add a new token to the database in the unrevoked state. :param dict token: the decoded token to blacklist """ user = User.query.filter_by(ad_guid=token['sub']).one() db_token = BlacklistedToken( jti=token['jti'], user_id=user.id, expires=datetime.fromtimestamp(token['exp']) ) db.session.add(db_token) db.session.commit() @staticmethod def is_token_revoked(token): """ Check if the token is revoked and default to True if the token is not present. :param dict token: the decoded JSON web token to check :rtype: bool :return: a boolean representing if the token is revoked """ jti = token['jti'] return bool(BlacklistedToken.query.filter_by(jti=jti).first()) @staticmethod def revoke_token(jti): """ Revoke a token for the given user. :param str jti: the GUID that identifies the token :raises werkzeug.exceptions.NotFound: if the specified token does not exist in the database """ return bool(BlacklistedToken.query.filter_by(jti=jti).first())
class Answer(db.Model): """Contain the user's answers to the secret questions they've chosen.""" id = db.Column(db.Integer(), primary_key=True) # The hashed answer should be around 120 characters, but give it plenty of room to expand in # the event the hashing algorithm is updated answer = db.Column(db.String(256), nullable=False) user_id = db.Column(db.Integer(), db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False) question_id = db.Column(db.Integer(), db.ForeignKey('question.id', ondelete='CASCADE'), nullable=False) @validates('answer') def validate_answer(self, key, answer): """ Ensure the answer is hashed. :param str key: the key/column being validated :param str answer: the answer being validated :return: the answer being validated :rtype: str :raises RuntimeError: if the answer is not hashed or isn't a string """ if not isinstance(answer, string_types): raise RuntimeError(_must_be_str.format(key)) elif not passlib.hash.sha512_crypt.identify(answer): raise RuntimeError('The answer must be stored as a SHA512 hash') return answer @staticmethod def hash_answer(answer): """ Hash the answer using the SHA512 algorithm. :param str answer: the answer to hash :return: a SHA512 hash of the string :rtype: str """ return passlib.hash.sha512_crypt.hash(answer) @staticmethod def verify_answer(input_answer, hashed_answer): """ Verify the input answer and the hashed answer stored in the database are the same. :param str input_answer: the answer to verify :param str hashed_answer: the hashed answer to verify against :return: a boolean determining if the answers match :rtype: bool """ return passlib.hash.sha512_crypt.verify(input_answer, hashed_answer) def to_json(self, include_url=True): """Represent the row as a dictionary for JSON output.""" rv = { 'id': self.id, 'user_id': self.user_id, 'question': self.question.to_json(), } if include_url: rv['url'] = url_for('api_v1.get_answer', answer_id=self.id, _external=True) return rv