123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499 |
- # -*- coding: utf-8 -*-
- """
- (c) 2014-2017 - Copyright Red Hat Inc
- Authors:
- Pierre-Yves Chibon <pingou@pingoured.fr>
- Farhaan Bukhsh <farhaan.bukhsh@gmail.com>
- """
- from __future__ import unicode_literals
- import datetime
- import logging
- import flask
- import six
- from sqlalchemy.exc import SQLAlchemyError
- from six.moves.urllib.parse import urljoin
- import pagure.login_forms as forms
- import pagure.lib.login
- import pagure.lib.model as model
- import pagure.lib.notify
- import pagure.lib.query
- from pagure.utils import login_required
- from pagure.lib.login import generate_hashed_value, check_password
- from pagure.ui import UI_NS
- _log = logging.getLogger(__name__)
- @UI_NS.route("/user/new/", methods=["GET", "POST"])
- @UI_NS.route("/user/new", methods=["GET", "POST"])
- def new_user():
- """ Create a new user.
- """
- form = forms.NewUserForm()
- if form.validate_on_submit():
- username = form.user.data
- if pagure.lib.query.search_user(flask.g.session, username=username):
- flask.flash("Username already taken.", "error")
- return flask.redirect(flask.request.url)
- email = form.email_address.data
- if pagure.lib.query.search_user(flask.g.session, email=email):
- flask.flash("Email address already taken.", "error")
- return flask.redirect(flask.request.url)
- form.password.data = generate_hashed_value(form.password.data)
- token = pagure.lib.login.id_generator(40)
- user = model.User()
- user.token = token
- form.populate_obj(obj=user)
- user.default_email = form.email_address.data
- flask.g.session.add(user)
- flask.g.session.flush()
- try:
- pagure.lib.query.add_email_to_user(
- flask.g.session, user, form.email_address.data
- )
- flask.g.session.commit()
- send_confirmation_email(user)
- flask.flash(
- "User created, please check your email to activate the "
- "account"
- )
- except pagure.exceptions.PagureException as err:
- flask.flash(str(err), "error")
- _log.exception(err)
- except SQLAlchemyError: # pragma: no cover
- flask.g.session.rollback()
- flask.flash("Could not create user.")
- _log.exception("Could not create user.")
- return flask.redirect(flask.url_for("auth_login"))
- return flask.render_template("login/user_new.html", form=form)
- @UI_NS.route("/dologin", methods=["POST"])
- def do_login():
- """ Log in the user.
- """
- logout()
- form = forms.LoginForm()
- next_url = flask.request.form.get("next_url")
- if not next_url or next_url == "None":
- next_url = flask.url_for("ui_ns.index")
- if form.validate_on_submit():
- username = form.username.data
- user_obj = pagure.lib.query.search_user(
- flask.g.session, username=username
- )
- if not user_obj:
- flask.flash("Username or password invalid.", "error")
- return flask.redirect(flask.url_for("auth_login"))
- try:
- password_checks = check_password(
- form.password.data,
- user_obj.password,
- seed=pagure.config.config.get("PASSWORD_SEED", None),
- )
- except pagure.exceptions.PagureException as err:
- _log.exception(err)
- flask.flash("Username or password of invalid format.", "error")
- return flask.redirect(flask.url_for("auth_login"))
- if not password_checks:
- flask.flash("Username or password invalid.", "error")
- return flask.redirect(flask.url_for("auth_login"))
- elif user_obj.token:
- flask.flash(
- "Invalid user, did you confirm the creation with the url "
- "provided by email?",
- "error",
- )
- return flask.redirect(flask.url_for("auth_login"))
- else:
- password = user_obj.password
- if not isinstance(password, six.text_type):
- password = password.decode("utf-8")
- if not password.startswith("$2$"):
- user_obj.password = generate_hashed_value(form.password.data)
- flask.g.session.add(user_obj)
- flask.g.session.flush()
- visit_key = pagure.lib.login.id_generator(40)
- now = datetime.datetime.utcnow()
- expiry = now + datetime.timedelta(days=30)
- session = model.PagureUserVisit(
- user_id=user_obj.id,
- user_ip=flask.request.remote_addr,
- visit_key=visit_key,
- expiry=expiry,
- )
- flask.g.session.add(session)
- try:
- flask.g.session.commit()
- flask.g.fas_user = user_obj
- flask.g.fas_session_id = visit_key
- flask.g.fas_user.login_time = now
- flask.flash("Welcome %s" % user_obj.username)
- except SQLAlchemyError as err: # pragma: no cover
- flask.flash(
- "Could not set the session in the db, "
- "please report this error to an admin",
- "error",
- )
- _log.exception(err)
- return flask.redirect(next_url)
- else:
- flask.flash("Insufficient information provided", "error")
- return flask.redirect(flask.url_for("auth_login"))
- @UI_NS.route("/confirm/<token>/")
- @UI_NS.route("/confirm/<token>")
- def confirm_user(token):
- """ Confirm a user account.
- """
- user_obj = pagure.lib.query.search_user(flask.g.session, token=token)
- if not user_obj:
- flask.flash("No user associated with this token.", "error")
- else:
- user_obj.token = None
- flask.g.session.add(user_obj)
- try:
- flask.g.session.commit()
- flask.flash("Email confirmed, account activated")
- return flask.redirect(flask.url_for("auth_login"))
- except SQLAlchemyError as err: # pragma: no cover
- flask.flash(
- "Could not set the account as active in the db, "
- "please report this error to an admin",
- "error",
- )
- _log.exception(err)
- return flask.redirect(flask.url_for("ui_ns.index"))
- @UI_NS.route("/password/lost/", methods=["GET", "POST"])
- @UI_NS.route("/password/lost", methods=["GET", "POST"])
- def lost_password():
- """ Method to allow a user to change his/her password assuming the email
- is not compromised.
- """
- form = forms.LostPasswordForm()
- if form.validate_on_submit():
- username = form.username.data
- user_obj = pagure.lib.query.search_user(
- flask.g.session, username=username
- )
- if not user_obj:
- flask.flash("Username invalid.", "error")
- return flask.redirect(flask.url_for("auth_login"))
- elif user_obj.token:
- current_time = datetime.datetime.utcnow()
- invalid_period = user_obj.updated_on + datetime.timedelta(
- minutes=3
- )
- if current_time < invalid_period:
- flask.flash(
- "An email was sent to you less than 3 minutes ago, "
- "did you check your spam folder? Otherwise, "
- "try again after some time.",
- "error",
- )
- return flask.redirect(flask.url_for("auth_login"))
- token = pagure.lib.login.id_generator(40)
- user_obj.token = token
- flask.g.session.add(user_obj)
- try:
- flask.g.session.commit()
- send_lostpassword_email(user_obj)
- flask.flash("Check your email to finish changing your password")
- except SQLAlchemyError: # pragma: no cover
- flask.g.session.rollback()
- flask.flash(
- "Could not set the token allowing changing a password.",
- "error",
- )
- _log.exception("Password lost change - Error setting token.")
- return flask.redirect(flask.url_for("auth_login"))
- return flask.render_template("login/password_change.html", form=form)
- @UI_NS.route("/password/reset/<token>/", methods=["GET", "POST"])
- @UI_NS.route("/password/reset/<token>", methods=["GET", "POST"])
- def reset_password(token):
- """ Method to allow a user to reset his/her password.
- """
- form = forms.ResetPasswordForm()
- user_obj = pagure.lib.query.search_user(flask.g.session, token=token)
- if not user_obj:
- flask.flash("No user associated with this token.", "error")
- return flask.redirect(flask.url_for("auth_login"))
- elif not user_obj.token:
- flask.flash(
- "Invalid user, this user never asked for a password change",
- "error",
- )
- return flask.redirect(flask.url_for("auth_login"))
- if form.validate_on_submit():
- user_obj.password = generate_hashed_value(form.password.data)
- user_obj.token = None
- flask.g.session.add(user_obj)
- try:
- flask.g.session.commit()
- flask.flash("Password changed")
- except SQLAlchemyError: # pragma: no cover
- flask.g.session.rollback()
- flask.flash("Could not set the new password.", "error")
- _log.exception("Password lost change - Error setting password.")
- return flask.redirect(flask.url_for("auth_login"))
- return flask.render_template(
- "login/password_reset.html", form=form, token=token
- )
- #
- # Methods specific to local login.
- #
- @UI_NS.route("/password/change/", methods=["GET", "POST"])
- @UI_NS.route("/password/change", methods=["GET", "POST"])
- @login_required
- def change_password():
- """ Method to change the password for local auth users.
- """
- form = forms.ChangePasswordForm()
- user_obj = pagure.lib.query.search_user(
- flask.g.session, username=flask.g.fas_user.username
- )
- if not user_obj:
- flask.abort(404, "User not found")
- if form.validate_on_submit():
- try:
- password_checks = check_password(
- form.old_password.data,
- user_obj.password,
- seed=pagure.config.config.get("PASSWORD_SEED", None),
- )
- except pagure.exceptions.PagureException as err:
- _log.exception(err)
- flask.flash(
- "Could not update your password, either user or password "
- "could not be checked",
- "error",
- )
- return flask.redirect(flask.url_for("auth_login"))
- if password_checks:
- user_obj.password = generate_hashed_value(form.password.data)
- flask.g.session.add(user_obj)
- else:
- flask.flash(
- "Could not update your password, either user or password "
- "could not be checked",
- "error",
- )
- return flask.redirect(flask.url_for("auth_login"))
- try:
- flask.g.session.commit()
- flask.flash("Password changed")
- except SQLAlchemyError: # pragma: no cover
- flask.g.session.rollback()
- flask.flash("Could not set the new password.", "error")
- _log.exception("Password change - Error setting new password.")
- return flask.redirect(flask.url_for("auth_login"))
- return flask.render_template("login/password_recover.html", form=form)
- def send_confirmation_email(user):
- """ Sends the confirmation email asking the user to confirm its email
- address.
- """
- if not user.emails:
- return
- url = pagure.config.config.get("APP_URL", flask.request.url_root)
- url = urljoin(
- url or flask.request.url_root,
- flask.url_for("ui_ns.confirm_user", token=user.token),
- )
- message = """ Dear %(username)s,
- Thank you for registering on pagure at %(url)s.
- To finish your registration, please click on the following link or copy/paste
- it in your browser:
- %(url)s
- You account will not be activated until you finish this step.
- Sincerely,
- Your pagure admin.
- """ % (
- {"username": user.username, "url": url}
- )
- pagure.lib.notify.send_email(
- text=message,
- subject="Confirm your user account",
- to_mail=user.emails[0].email,
- )
- def send_lostpassword_email(user):
- """ Sends the email with the information on how to reset his/her password
- to the user.
- """
- if not user.emails:
- return
- url = pagure.config.config.get("APP_URL", flask.request.url_root)
- url = urljoin(
- url or flask.request.url_root,
- flask.url_for("ui_ns.reset_password", token=user.token),
- )
- message = """ Dear %(username)s,
- The IP address %(ip)s has requested a password change for this account.
- If you wish to change your password, please click on the following link or
- copy/paste it in your browser:
- %(url)s
- If you did not request this change, please inform an admin immediately!
- Sincerely,
- Your pagure admin.
- """ % (
- {
- "username": user.username,
- "url": url,
- "ip": flask.request.remote_addr,
- }
- )
- pagure.lib.notify.send_email(
- text=message,
- subject="Confirm your password change",
- to_mail=user.emails[0].email,
- )
- def logout():
- """ Log the user out by expiring the user's session.
- """
- flask.g.fas_session_id = None
- flask.g.fas_user = None
- def _check_session_cookie():
- """ Set the user into flask.g if the user is logged in.
- """
- if not hasattr(flask.g, "session") or not flask.g.session:
- flask.g.session = pagure.lib.query.create_session(
- flask.current_app.config["DB_URL"]
- )
- cookie_name = pagure.config.config.get("SESSION_COOKIE_NAME", "pagure")
- cookie_name = "%s_local_cookie" % cookie_name
- session_id = None
- user = None
- login_time = None
- if cookie_name and cookie_name in flask.request.cookies:
- sessionid = flask.request.cookies.get(cookie_name)
- visit_session = pagure.lib.login.get_session_by_visitkey(
- flask.g.session, sessionid
- )
- if visit_session and visit_session.user:
- now = datetime.datetime.now()
- if now > visit_session.expiry:
- flask.flash("Session timed-out", "error")
- elif (
- pagure.config.config.get("CHECK_SESSION_IP", True)
- and visit_session.user_ip != flask.request.remote_addr
- ):
- flask.flash("Session expired", "error")
- else:
- new_expiry = now + datetime.timedelta(days=30)
- session_id = visit_session.visit_key
- user = visit_session.user
- login_time = visit_session.created
- visit_session.expiry = new_expiry
- flask.g.session.add(visit_session)
- try:
- flask.g.session.commit()
- except SQLAlchemyError as err: # pragma: no cover
- flask.flash(
- "Could not prolong the session in the db, "
- "please report this error to an admin",
- "error",
- )
- _log.exception(err)
- flask.g.fas_session_id = session_id
- if user:
- flask.g.fas_user = user
- flask.g.fas_user.email = user.default_email
- flask.g.authenticated = pagure.utils.authenticated()
- flask.g.fas_user.login_time = login_time
- def _send_session_cookie(response):
- """ Set the session cookie if the user is authenticated. """
- cookie_name = pagure.config.config.get("SESSION_COOKIE_NAME", "pagure")
- secure = pagure.config.config.get("SESSION_COOKIE_SECURE", True)
- response.set_cookie(
- key="%s_local_cookie" % cookie_name,
- value=flask.g.get("fas_session_id") or "",
- secure=secure,
- httponly=True,
- )
- return response
|