faf/SOURCES/faf-saml-auth.patch

179 lines
6.4 KiB
Diff

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):
"""