def get_timed_signer_url(namespace): """ This gives a basic signing/verifying object. The namespace makes sure signed tokens can't be used in a different area. Like using a forgot-password-token as a session cookie. Basic usage: .. code-block:: python _signer = None TOKEN_VALID_DAYS = 10 def setup(): global _signer _signer = get_timed_signer_url("session cookie") def create_token(obj): return _signer.dumps(obj) def parse_token(token): # This might raise an exception in case # of an invalid token, or an expired token. return _signer.loads(token, max_age=TOKEN_VALID_DAYS*24*3600) For more details see http://pythonhosted.org/itsdangerous/#itsdangerous.URLSafeTimedSerializer """ assert __itsda_secret is not None return itsdangerous.URLSafeTimedSerializer(__itsda_secret, salt=namespace)
def claim(self): email = context.form.get('email') email_pattern = r'(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)' if not email: raise HTTPBadRequest() if not re.match(email_pattern, email): raise HTTPStatus('701 Invalid email format.') if DBSession.query(Member.email).filter(Member.email == email).count(): raise HTTPStatus( '601 The requested email address is already registered.') # FIXME: Token should be put in payload serializer = \ itsdangerous.URLSafeTimedSerializer(settings.activation.secret) token = serializer.dumps(email) DBSession.add( ActivationEmail(to=email, subject='Activate your NueMD Coder account', body={ 'activation_token': token, 'activation_url': settings.activation.url })) return dict(email=email)
def unsubscribe(request, token): signer = itsdangerous.URLSafeTimedSerializer( secret_key=settings.SECRET_KEY) max_age = 60 * 60 * 24 * 7 # Seven days till the link expires try: subscription_id = signer.loads(token, max_age=max_age) get_object_or_404(Subscription, pk=subscription_id).delete() context = { 'status': 'success', } except itsdangerous.SignatureExpired: context = {'status': 'failure', 'reason': 'expired'} logger.warning('Unsubscribe link expired') except itsdangerous.BadTimeSignature as e: logger.error('Wrong signature format [{}]: {}'.format(e, token)) context = {'status': 'failure', 'reason': 'tampered'} except itsdangerous.BadSignature as e: context = {'status': 'failure', 'reason': 'tampered'} logger.error( 'Tampering with unsubscribe link detected [{}]: {}'.format( e, token)) encoded_payload = e.payload if encoded_payload is not None: try: decoded_payload = signer.load_payload(encoded_payload) logger.error('Payload was {}'.format(decoded_payload)) except itsdangerous.BadData: pass return render(request, 'creator/subscription_unsubscribe.html', context=context)
def extract_session_from_cookie (cookie_header, secret_key): parsed_cookie = SimpleCookie (cookie_header) if not 'session' in parsed_cookie: return {} cookie_interface = flask.sessions.SecureCookieSessionInterface() # This code matches what Flask does for encoding serializer = itsdangerous.URLSafeTimedSerializer( secret_key, salt=cookie_interface.salt, serializer=cookie_interface.serializer, signer_kwargs=dict( key_derivation=cookie_interface.key_derivation, digest_method=cookie_interface.digest_method )) try: cookie_value = parsed_cookie['session'].value return serializer.loads(cookie_value) except itsdangerous.exc.BadSignature as e: # If the signature is wrong, we can still decode the cookie. # We try to do it properly with the actual key though, because exception # handling is slightly slow in Python and doing it successfully is therefore # faster. if e.payload is not None: try: return serializer.load_payload(e.payload) except itsdangerous.exc.BadData: pass return {}
def generate_link(request, user): xom = request.registry['xom'] serializer = itsdangerous.URLSafeTimedSerializer(xom.config.secret) result = {'hash_type': 'sha256', 'salt': newsalt(), 'username': user.name} result['token'] = generate_token(result, get_pwhash(user)) value = serializer.dumps(result) return request.route_url('passwd_reset', token=value)
def reset(password_reset): safe = itsdangerous.URLSafeTimedSerializer(settings.SECRET_KEY) with db.session.begin(subtransactions=True): user_ = db.session.query(User).filter( User.email == password_reset["email"]).one_or_none() NoSuchUser.require_condition( user_, "The user with {email} email does not exist", email=password_reset["email"]) token = safe.dumps(user_.email) email.template.send( "password_reset", body={ "from_": "*****@*****.**", "to": [user_.email], "subject": "Password reset required", "variables": { "name": user_.get_short_name(), "password_reset_url": password_reset["url_template"].format(token=token), }, "tag": "password_reset", }, ) return connexion.NoContent, 200
def confirm_self_deletion(token): """Confirm user deletion.""" s = itsdangerous.URLSafeTimedSerializer(flask.current_app.config.get("SECRET_KEY")) try: # Get email from token, overwrite the one from login if applicable email = s.loads(token, salt="email-delete", max_age=604800) # Check that the email is registered on the current user: if email not in [email.email for email in flask_login.current_user.emails]: msg = "The email for user to be deleted is not registered on your account." flask.current_app.logger.warning( f"{msg} email: {email}: user: {flask_login.current_user}" ) raise ddserr.UserDeletionError(message=msg) # Get row from deletion requests table deletion_request_row = models.DeletionRequest.query.filter( models.DeletionRequest.email == email ).first() except itsdangerous.exc.SignatureExpired as exc: email = db_tools.remove_user_self_deletion_request(flask_login.current_user) raise ddserr.UserDeletionError( message=f"Deletion request for {email} has expired. Please login to the DDS and request deletion anew." ) from exc except (itsdangerous.exc.BadSignature, itsdangerous.exc.BadTimeSignature) as exc: raise ddserr.UserDeletionError( message="Confirmation link is invalid. No action has been performed." ) from exc except sqlalchemy.exc.SQLAlchemyError as sqlerr: raise ddserr.DatabaseError(message=sqlerr) from sqlerr # Check if the user and the deletion request exists if deletion_request_row: try: user = user_schemas.UserSchema().load({"email": email}) _ = db_tools.remove_user_self_deletion_request(user) DeleteUser.delete_user(user=user) except sqlalchemy.exc.SQLAlchemyError as sqlerr: raise ddserr.UserDeletionError( message=f"User deletion request for {user.username} / {user.primary_email.email} failed due to database error: {sqlerr}", alt_message=f"Deletion request for user {user.username} registered with {user.primary_email.email} failed for technical reasons. Please contact the unit for technical support!", ) except sqlalchemy.exc.OperationalError as err: raise ddserr.DatabaseError(message=str(err), alt_message="Unexpected database error.") flask.session.clear() return flask.make_response( flask.render_template("user/userdeleted.html", username=user.username, initial=True) ) else: return flask.make_response( flask.render_template("user/userdeleted.html", username=email, initial=False) )
def get_token(self, tag): """Return a token that can be used to validate or reset a user. The tag is a plain string that is used as a namespace. It must also be passed to from_token.""" serialzer = itsdangerous.URLSafeTimedSerializer( current_app.config['SECRET_KEY']) return serialzer.dumps(self.id, salt=tag)
def from_token(token, expires=3600): auth_s = itsdangerous.URLSafeTimedSerializer(app.config['SECRET_KEY']) try: data = auth_s.loads(token, max_age=expires) except Exception as e: return None return User.query.get(data['id'])
def from_token(cls, token, tag, max_age=None): """Given a token, return the user for that token.""" serialzer = itsdangerous.URLSafeTimedSerializer( current_app.config['SECRET_KEY']) try: id = serialzer.loads(token, max_age=max_age, salt=tag) except itsdangerous.BadData: return None return cls.query.get(int(id))
def get_signing_serializer_modified(self, app): if not secret_key: return None signer_kwargs = dict( key_derivation=self.key_derivation, digest_method=self.digest_method ) return itsdangerous.URLSafeTimedSerializer(secret_key, salt=self.salt, serializer=self.serializer, signer_kwargs=signer_kwargs)
def factory( app, defauts, secret, audience='http://localhost:8080', prefix='/persona', ): serializer = itsdangerous.URLSafeTimedSerializer(secret) @bobo.subroute(prefix) def routes(request): return Routes(request, audience, prefix, serializer) persona_app = bobo.Application(bobo_resources = [routes]) def run_app(env, start): request = webob.Request(env) token = request.cookies.get(TOKEN) old_email = email = None if token: try: email = serializer.loads(token) except itsdangerous.BadTimeSignature: pass # just don't log them in else: old_email = env.get('REMOTE_USER') env['REMOTE_USER'] = email try: if env['PATH_INFO'].startswith(prefix): return persona_app(env, start) else: needs_login = [] def start_response(status, headers, exc_info=None): if status.startswith("401 "): needs_login.append(0) else: start(status, headers, exc_info) it = app(env, start_response) if needs_login: return bobo.redirect( prefix+'/login.html?came_from='+env['PATH_INFO'] )(env, start) else: return it finally: if email: if old_email: env['REMOTE_USER'] = old_email else: del env['REMOTE_USER'] return run_app
def confirm_token(token: str, expiration: int=3600) -> str: serializer = itsdangerous.URLSafeTimedSerializer(flask.current_app.config['SECRET_KEY']) try: email = serializer.loads( token, salt=flask.current_app.config['SECURITY_PASSWORD_SALT'], max_age=expiration ) return email except: return ""
def _make_serializer(self) -> itsdangerous.URLSafeTimedSerializer: """Returns URL Safe Timed Serializer for signing payloads. :return: Serializer for dumping and loading into a URL safe string. :rtype: :class:`itsdangerous.URLSafeTimedSerializer` """ return itsdangerous.URLSafeTimedSerializer( secret_key=self.secret, salt=self.salt, signer_kwargs={ 'key_derivation': 'hmac', 'digest_method': 'SHA1' })
def create_token(entity, contents=None, field=None, salt=b'itsdangerous'): assert isinstance(entity, SecureEntity) if callable(contents): contents = contents(entity) if not contents and field: contents = getattr(entity, field) return itsdangerous.URLSafeTimedSerializer( secret_key=entity.get_token_secret_key(), salt=salt).dumps(contents)
def unsubscribe(token): safe = itsdangerous.URLSafeTimedSerializer(settings.SECRET_KEY) token_data = safe.loads(token) with db.session.begin(subtransactions=True): # Create an unsubscribe unsubscribe_ = Unsubscribe(**token_data) db.session.add(unsubscribe_) return flask.render_template("unsubscribe.html"), 200
def deserialize_url_time_sensitive_value(token, salt): serializer = itsd.URLSafeTimedSerializer(os.environ["SECRET_KEY"]) try: decoded_payload = serializer.loads(token, salt=salt, max_age=86400) return decoded_payload except itsd.BadSignature as e: if e.payload is not None: try: decoded_payload = serializer.load_payload(e.payload) return None except itsd.BadData: return None
def from_config(config: typing.Dict[str, typing.Any], secret_key: typing.Union[str, bytes], state_storage: dict = None) -> Authl: """ Generate an Authl handler set from provided configuration directives. Arguments: :param dict config: a configuration dictionary. See the individual handlers' from_config functions to see possible configuration values. :param std secret_key: a signing key used to keep authentication secrets. :param dict state_storage: a dict-like object that will store persistent state for methods that need it Handlers will be enabled based on truthy values of the following keys EMAIL_FROM / EMAIL_SENDMAIL -- enable the EmailAddress handler MASTODON_NAME -- enable the Mastodon handler INDIEAUTH_CLIENT_ID -- enable the IndieAuth handler INDIELOGIN_CLIENT_ID -- enable the IndieLogin handler TWITTER_CLIENT_KEY -- enable the Twitter handler TEST_ENABLED -- enable the test/loopback handler """ serializer = itsdangerous.URLSafeTimedSerializer(secret_key) instance = Authl() if config.get('EMAIL_FROM') or config.get('EMAIL_SENDMAIL'): from .handlers import email_addr instance.add_handler(email_addr.from_config(config, serializer)) if config.get('FEDIVERSE_NAME') or config.get('MASTODON_NAME'): from .handlers import fediverse instance.add_handler(fediverse.from_config(config, serializer)) if config.get('INDIEAUTH_CLIENT_ID'): from .handlers import indieauth instance.add_handler(indieauth.from_config(config, serializer)) if config.get('INDIELOGIN_CLIENT_ID'): from .handlers import indielogin instance.add_handler(indielogin.from_config(config, serializer)) if config.get('TWITTER_CLIENT_KEY'): from .handlers import twitter instance.add_handler(twitter.from_config(config, state_storage)) if config.get('TEST_ENABLED'): from .handlers import test_handler instance.add_handler(test_handler.TestHandler()) return instance
def edit_project(projectid): MAX_TOKEN_AGE = 3600 isp = ISP.query.filter_by(id=projectid, is_disabled=False).first_or_404() sess_token = session.get('edit_tokens', {}).get(isp.id) if 'token' in request.args: s = itsdangerous.URLSafeTimedSerializer(current_app.secret_key, salt='edit') try: r = s.loads(request.args['token'], max_age=MAX_TOKEN_AGE, return_timestamp=True) except: abort(403) if r[0] != isp.id: abort(403) tokens = session.setdefault('edit_tokens', {}) session.modified = True # ITS A TARP tokens[r[0]] = r[1] # refresh page, without the token in the url return redirect(url_for('.edit_project', projectid=r[0])) elif (sess_token is None or (datetime.utcnow() - sess_token).total_seconds() > MAX_TOKEN_AGE): return redirect(url_for('.gen_edit_token', projectid=isp.id)) if isp.is_local: form = forms.ProjectForm.edit_json(isp) if form.validate_on_submit(): isp.name = form.name.data isp.shortname = form.shortname.data or None isp.json = form.to_json(isp.json) isp.tech_email = form.tech_email.data db.session.add(isp) db.session.commit() flash(_(u'Project modified'), 'info') return redirect(url_for('.project', projectid=isp.id)) return render_template('edit_project_form.html', form=form) else: form = forms.ProjectJSONForm(obj=isp) if form.validate_on_submit(): isp.tech_email = form.tech_email.data url = utils.make_ispjson_url(form.json_url.data) isp.json_url = url db.session.add(isp) db.session.commit() flash(_(u'Project modified'), 'info') return redirect(url_for('.project', projectid=isp.id)) return render_template('edit_project_json_form.html', form=form)
def get_token_info(request, token): xom = request.registry['xom'] serializer = itsdangerous.URLSafeTimedSerializer(xom.config.secret) value = serializer.loads(token, max_age=24 * 60 * 60) user = xom.model.get_user(value['username']) if user is None: raise ValueError("The user '%s' doesn't exist anymore." % value['username']) if value['token'] != generate_token(value, get_pwhash(user)): raise ValueError( "The password has already been changed since the link was requested." ) return value
def authenticate(self, cookie_key, cookies): signer_kwargs = dict(key_derivation=self.key_derivation, digest_method=self.digest_method) serializer = itsdangerous.URLSafeTimedSerializer( app.secret_key, salt=self.salt, serializer=self.serializer, signer_kwargs=signer_kwargs) morsel_val = cookies.get(cookie_key) if not morsel_val: return {} assert morsel_val.key == cookie_key session_data = serializer.loads(morsel_val.value) return session_data
def from_env() -> "App": config = environ.to_config(Config) app = flask.Flask( __name__, static_folder=os.path.join(os.getcwd(), config.static_path), ) # 16MB app.config["MAX_CONTENT_LENGTH"] = 16 * 1024 * 1024 app.secret_key = config.secret_key return App( config=config, db=playhouse.sqlite_ext.SqliteExtDatabase(None), serializer=itsdangerous.URLSafeTimedSerializer(config.secret_key), app=app, )
def _make_serializer(self) -> itsdangerous.URLSafeTimedSerializer: """Returns URL Safe Timed Serializer for signing payloads. :return: Serializer for dumping and loading into a URL safe string. :rtype: :class:`itsdangerous.URLSafeTimedSerializer` """ # TODO: Throw exception if secret / salt not set. return itsdangerous.URLSafeTimedSerializer( secret_key=self.secret, salt=self.salt, signer_kwargs={ "key_derivation": "hmac", "digest_method": "SHA1" }, )
def get_user_from_reset_token(token): serializer = itsdangerous.URLSafeTimedSerializer(app.secret_key) try: data = serializer.loads(token, max_age=86400) except itsdangerous.BadData: return ('invalid token', None) (email, password_hash) = data user = User.objects.filter(email=email).first() if user and md5(user.password.encode('utf-8')) == password_hash: return (None, user) return ('invalid token', None)
def verify_token(token, entity_getter, salt=b'itsdangerous', max_age=None): data = peak_token_data(token) if not data: return None, None entity = entity_getter(data) if not entity: return None, data try: itsdangerous.URLSafeTimedSerializer(entity.get_token_secret_key(), salt).loads(token, max_age=max_age) except (itsdangerous.BadSignature, itsdangerous.SignatureExpired): return None, None return entity, data
def __call__(self, args): serializer = \ itsdangerous.URLSafeTimedSerializer(settings.activation.secret) token = serializer.dumps(args.email) email = ActivationEmail( to=args.email, subject='Activate your Cucumber account', body={ 'activation_token': token, 'activation_url': settings.activation.url } ) email.to = args.email email.do_({})
def instrument_log_mobile_file_upload(instrument_id: int, token: str): try: instrument = get_instrument(instrument_id) except InstrumentDoesNotExistError: return flask.abort(404) serializer = itsdangerous.URLSafeTimedSerializer(flask.current_app.config['SECRET_KEY'], salt='instrument-log-mobile-upload') try: user_id, instrument_id = serializer.loads(token, max_age=15 * 60) except itsdangerous.BadSignature: return flask.abort(400) try: user = get_user(user_id) except UserDoesNotExistError: return flask.abort(403) if not instrument.users_can_create_log_entries and user not in instrument.responsible_users: return flask.abort(403) if user.is_readonly: return flask.abort(403) return flask.render_template('mobile_upload.html', user_id=get_user(user_id), instrument=instrument)
def post_instrument_log_mobile_file_upload(instrument_id: int, token: str): try: instrument = get_instrument(instrument_id) except InstrumentDoesNotExistError: return flask.abort(404) serializer = itsdangerous.URLSafeTimedSerializer(flask.current_app.config['SECRET_KEY'], salt='instrument-log-mobile-upload') try: user_id, instrument_id = serializer.loads(token, max_age=15 * 60) except itsdangerous.BadSignature: return flask.abort(400) try: user = get_user(user_id) except UserDoesNotExistError: return flask.abort(403) if not instrument.users_can_create_log_entries and user not in instrument.responsible_users: return flask.abort(403) if user.is_readonly: return flask.abort(403) files = flask.request.files.getlist('file_input') if not files: return flask.redirect( flask.url_for( '.instrument_log_mobile_file_upload', instrument_id=instrument_id, token=token ) ) if files: category_ids = [] log_entry = create_instrument_log_entry( instrument_id=instrument_id, user_id=user_id, content='', category_ids=category_ids ) for file_storage in files: create_instrument_log_file_attachment( instrument_log_entry_id=log_entry.id, file_name=file_storage.filename, content=file_storage.stream.read() ) return flask.render_template('mobile_upload_success.html')
def reset_confirm(password_reset_confirm): safe = itsdangerous.URLSafeTimedSerializer(settings.SECRET_KEY) with InvalidToken.handle_errors( "The specified token is invalid", exception_class=(itsdangerous.BadSignature, itsdangerous.BadTimeSignature)): email = safe.loads(password_reset_confirm["token"]) with db.session.begin(subtransactions=True): user_ = db.session.query(User).filter( User.email == email).one_or_none() NoSuchUser.require_condition( user_, "The user with {email} email does not exist", email=email) user_.password = guard.encrypt_password( password_reset_confirm["new_password"]) db.session.add(user_) return connexion.NoContent, 200
def gen_edit_token(projectid): isp = ISP.query.filter_by(id=projectid, is_disabled=False).first_or_404() form = forms.RequestEditToken() if form.validate_on_submit(): # validated if form.tech_email.data == isp.tech_email: s = itsdangerous.URLSafeTimedSerializer(current_app.secret_key, salt='edit') token = s.dumps(isp.id) msg = Message("Edit request of your ISP", sender=current_app.config['EMAIL_SENDER']) msg.body = """ Hello, You are receiving this message because your are listed as technical contact for "%s" on the FFDN ISP database. Someone asked to edit your ISP's data in our database. If it's not you, please ignore this message. To proceed to the editing form, please click on the following link: %s?token=%s Note: the link is only valid for one hour from the moment we send you this email. Thanks, The FFDN ISP Database team https://db.ffdn.org """.strip() % (isp.complete_name, url_for('.edit_project', projectid=isp.id, _external=True), token) msg.add_recipient(isp.tech_email) mail.send(msg) # if the email provided is not the correct one, we still redirect flash( _(u'If you provided the correct email adress, ' 'you must will receive a message shortly (check your spam folder)' ), 'info') return redirect(url_for('.project', projectid=isp.id)) return render_template('gen_edit_token.html', form=form)