diff -aruN faf-2.6.1/src/webfaf/config.py faf-2.6.1.alma/src/webfaf/config.py --- faf-2.6.1/src/webfaf/config.py 2023-01-11 17:35:16 +++ faf-2.6.1.alma/src/webfaf/config.py 2023-05-23 09:47:50 @@ -13,6 +13,7 @@ class Config: + SAML_CONFIG_DIR = '/etc/faf/saml' DEBUG = False TESTING = False SECRET_KEY = "NOT_A_RANDOM_STRING" diff -aruN faf-2.6.1/src/webfaf/login.py faf-2.6.1.alma/src/webfaf/login.py --- faf-2.6.1/src/webfaf/login.py 2023-01-11 17:35:16 +++ faf-2.6.1.alma/src/webfaf/login.py 2023-05-23 09:50:08 @@ -1,58 +1,112 @@ import flask from openid_teams import teams from werkzeug.wrappers import Response +from urllib.parse import urlparse, urljoin from pyfaf.storage.user import User from webfaf.webfaf_main import db, oid, app from webfaf.utils import fed_raw_name +from onelogin.saml2.auth import OneLogin_Saml2_Auth -login = flask.Blueprint("login", __name__) +login = flask.Blueprint('login', __name__) -@login.route("/login/", methods=["GET"]) -@oid.loginhandler -def do_login() -> Response: - if flask.g.user is not None: - return flask.redirect(oid.get_next_url()) +def init_saml_auth(req): + saml_config_dir = app.config['SAML_CONFIG_DIR'] + return OneLogin_Saml2_Auth(req, custom_base_path=saml_config_dir) - teams_req = teams.TeamsRequest(app.config["OPENID_PRIVILEGED_TEAMS"]) - return oid.try_login("https://id.fedoraproject.org/", - ask_for=["email"], extensions=[teams_req]) +def prepare_flask_request(req): + parsed_url = urlparse(req.url) + return { + 'https': 'on' if req.scheme == 'https' else 'off', + 'http_host': req.host, + 'server_port': parsed_url.port, + 'script_name': req.script_root + req.path, + 'get_data': req.args.copy(), + 'post_data': req.form.copy(), + 'query_string': req.query_string + } -@oid.after_login -def create_or_login(resp) -> Response: - flask.session["openid"] = resp.identity_url - username = fed_raw_name(resp.identity_url) +@login.route('/login/', methods=('GET',)) +def do_login(): + """SSO authentication entry-point.""" + req = prepare_flask_request(flask.request) + comeback_url = urljoin(flask.request.host_url, '/faf/summary') + return_to = flask.request.args.get('return_to') + if return_to: + comeback_url += '?return_to={0}'.format(return_to) + auth = init_saml_auth(req) + return flask.redirect(auth.login(comeback_url)) + + +@login.route('/acs/', methods=('POST',)) +def acs(): + req = prepare_flask_request(flask.request) + auth = init_saml_auth(req) + auth.process_response() + errors = auth.get_errors() + if errors: + return flask.helpers.make_response('Error: {0}'.format(', '.join(errors)), 500) + elif not auth.is_authenticated(): + return flask.helpers.make_response('Error: authentication failed', 401) + user_attrs = auth.get_attributes() + privileged = False - # "lp" is the namespace for openid-teams - if "lp" in resp.extensions and any(group in app.config["OPENID_PRIVILEGED_TEAMS"] - for group in resp.extensions["lp"].teams): + is_admin = False + if any(group in user_attrs['groups'] for group in ('alma_retrace_admin', 'admins')): privileged = True + is_admin = True + email = user_attrs['email'][0] + username = email.split('@')[0] user = db.session.query(User).filter(User.username == username).first() if not user: # create - user = User(username=username, mail=resp.email, privileged=privileged) + user = User(username=username, mail=email, privileged=privileged, admin=is_admin) else: - user.mail = resp.email + user.mail = email user.privileged = privileged + user.admin = is_admin db.session.add(user) db.session.commit() flask.flash(u"Welcome, {0}".format(user.username)) - # This is okay: https://flask.palletsprojects.com/en/2.0.x/api/#flask.g - # pylint: disable=assigning-non-slot flask.g.user = user + flask.session['username'] = username - if flask.request.url_root == oid.get_next_url(): - return flask.redirect(flask.url_for("summary.index")) + return flask.redirect(flask.request.form['RelayState']) - return flask.redirect(oid.get_next_url()) +@login.route('/metadata/', methods=('GET',)) +def metadata(): + """ + Returns the Build System IDP provider metadata. + Returns + ------- + flask.wrappers.Response + IDP provider metadata response. + + See Also + -------- + https://en.wikipedia.org/wiki/SAML_Metadata#Identity_Provider_Metadata + """ + req = prepare_flask_request(flask.request) + auth = init_saml_auth(req) + settings = auth.get_settings() + metadata = settings.get_sp_metadata() + errors = settings.validate_metadata(metadata) + if errors: + rsp = flask.make_response(', '.join(errors), 500) + else: + rsp = flask.make_response(metadata, 200) + rsp.headers['Content-Type'] = 'text/xml' + return rsp + + @login.route("/logout/") def do_logout() -> Response: - flask.session.pop("openid", None) + flask.session.pop("username", None) flask.flash(u"You were signed out") - return flask.redirect(oid.get_next_url()) + return flask.redirect(flask.url_for("summary.index")) diff -aruN faf-2.6.1/src/webfaf/webfaf_main.py faf-2.6.1.alma/src/webfaf/webfaf_main.py --- faf-2.6.1/src/webfaf/webfaf_main.py 2023-01-11 17:35:16 +++ faf-2.6.1.alma/src/webfaf/webfaf_main.py 2023-05-23 09:53:06 @@ -170,8 +170,8 @@ @app.before_request def before_request() -> None: flask.g.user = None - if "openid" in flask.session: - username = fed_raw_name(flask.session["openid"]) + if "username" in flask.session: + username = flask.session["username"] flask.g.user = (db.session.query(User) .filter(User.username == username) .first()) diff -aruN faf-2.6.1/tests/test_webfaf/test_user.py faf-2.6.1.alma/tests/test_webfaf/test_user.py --- faf-2.6.1/tests/test_webfaf/test_user.py 2023-01-11 17:35:16 +++ faf-2.6.1.alma/tests/test_webfaf/test_user.py 2023-05-23 09:53:28 @@ -24,7 +24,7 @@ self.db.session.commit() with self.app.session_transaction() as session: - session['openid'] = 'faker1' + session['username'] = 'faker1' def test_delete_user_data(self): """