diff --git a/SOURCES/faf-almalinux_support.patch b/SOURCES/faf-almalinux_support.patch
new file mode 100644
index 0000000..c96c8f5
--- /dev/null
+++ b/SOURCES/faf-almalinux_support.patch
@@ -0,0 +1,480 @@
+diff -Naur faf-git-2884.9521ad3.orig/config/faf.conf.in faf-git-2884.9521ad3.almalinux/config/faf.conf.in
+--- faf-git-2884.9521ad3.orig/config/faf.conf.in 2021-01-27 00:52:48.000000000 +0300
++++ faf-git-2884.9521ad3.almalinux/config/faf.conf.in 2021-02-09 19:19:57.612625137 +0300
+@@ -32,9 +32,9 @@
+ Directory = @localstatedir@/spool/faf
+ CreateComponents = False
+ # attachments accepted by this server
+-# allowed values: fedora-bugzilla rhel-bugzilla centos-mantisb comment email url
++# allowed values: fedora-bugzilla rhel-bugzilla centos-mantisb almalinux-mantisb comment email url
+ # or * to allow all attachments
+-AcceptAttachments = fedora-bugzilla rhel-bugzilla centos-mantisbt
++AcceptAttachments = fedora-bugzilla rhel-bugzilla centos-mantisbt almalinux-mantisb
+
+ # Allow uReports without affected package - meaning that crashing code was
+ # not packaged
+diff -Naur faf-git-2884.9521ad3.orig/config/plugins/almalinux.conf faf-git-2884.9521ad3.almalinux/config/plugins/almalinux.conf
+--- faf-git-2884.9521ad3.orig/config/plugins/almalinux.conf 1970-01-01 03:00:00.000000000 +0300
++++ faf-git-2884.9521ad3.almalinux/config/plugins/almalinux.conf 2021-02-09 19:13:08.081175514 +0300
+@@ -0,0 +1,8 @@
++[AlmaLinux]
++repo-urls=https://repo.almalinux.org/almalinux/$releasever/AppStream/Source/
++ https://repo.almalinux.org/almalinux/$releasever/BaseOS/Source/
++ https://repo.almalinux.org/almalinux/$releasever/PowerTools/Source/
++ https://repo.almalinux.org/almalinux/$releasever/HighAvailability/Source/
++
++inactive-releases=
++active-releases=8
+diff -Naur faf-git-2884.9521ad3.orig/config/plugins/almalinuxmantisbt.conf faf-git-2884.9521ad3.almalinux/config/plugins/almalinuxmantisbt.conf
+--- faf-git-2884.9521ad3.orig/config/plugins/almalinuxmantisbt.conf 1970-01-01 03:00:00.000000000 +0300
++++ faf-git-2884.9521ad3.almalinux/config/plugins/almalinuxmantisbt.conf 2021-02-09 19:13:57.932844040 +0300
+@@ -0,0 +1,6 @@
++[almalinux-mantisbt]
++api_url=https://bugs.almalinux.org/api/soap/mantisconnect.php?wsdl
++web_url=https://bugs.almalinux.org/view.php?id=
++user=
++password=
++abbr=ALMALINUX
+diff -Naur faf-git-2884.9521ad3.orig/config/plugins/Makefile.am faf-git-2884.9521ad3.almalinux/config/plugins/Makefile.am
+--- faf-git-2884.9521ad3.orig/config/plugins/Makefile.am 2021-01-27 00:52:48.000000000 +0300
++++ faf-git-2884.9521ad3.almalinux/config/plugins/Makefile.am 2021-02-09 19:36:15.210749908 +0300
+@@ -1,4 +1,6 @@
+ pluginconf_DATA = \
++ almalinux.conf \
++ almalinuxmantisbt.conf \
+ centos.conf \
+ clonebz.conf \
+ centosmantisbt.conf \
+diff -Naur faf-git-2884.9521ad3.orig/src/pyfaf/actions/Makefile.am faf-git-2884.9521ad3.almalinux/src/pyfaf/actions/Makefile.am
+--- faf-git-2884.9521ad3.orig/src/pyfaf/actions/Makefile.am 2021-01-27 00:52:48.000000000 +0300
++++ faf-git-2884.9521ad3.almalinux/src/pyfaf/actions/Makefile.am 2021-02-09 19:22:53.265070056 +0300
+@@ -6,6 +6,7 @@
+ archlist.py \
+ assign_release_to_builds.py \
+ attach_centos_bugs.py \
++ attach_almalinux_bugs.py \
+ bugtrackerlist.py \
+ pull_abrt_bugs.py \
+ pull_bug.py \
+diff -Naur faf-git-2884.9521ad3.orig/src/pyfaf/bugtrackers/Makefile.am faf-git-2884.9521ad3.almalinux/src/pyfaf/bugtrackers/Makefile.am
+--- faf-git-2884.9521ad3.orig/src/pyfaf/bugtrackers/Makefile.am 2021-01-27 00:52:48.000000000 +0300
++++ faf-git-2884.9521ad3.almalinux/src/pyfaf/bugtrackers/Makefile.am 2021-02-09 19:23:25.949966773 +0300
+@@ -4,6 +4,7 @@
+ fedorabz.py \
+ mantisbt.py \
+ centosmantisbt.py \
++ almalinuxmantisbt.py \
+ rhelbz.py
+
+ bugtrackersdir = $(pythondir)/pyfaf/bugtrackers
+diff -Naur faf-git-2884.9521ad3.orig/src/pyfaf/opsys/Makefile.am faf-git-2884.9521ad3.almalinux/src/pyfaf/opsys/Makefile.am
+--- faf-git-2884.9521ad3.orig/src/pyfaf/opsys/Makefile.am 2021-01-27 00:52:48.000000000 +0300
++++ faf-git-2884.9521ad3.almalinux/src/pyfaf/opsys/Makefile.am 2021-02-09 19:20:43.440480316 +0300
+@@ -1,6 +1,7 @@
+ opsys_PYTHON = \
+ __init__.py \
+ centos.py \
++ almalinux.py \
+ fedora.py
+
+ opsysdir = $(pythondir)/pyfaf/opsys
+diff -Naur faf-git-2884.9521ad3.orig/src/pyfaf/actions/attach_almalinux_bugs.py faf-git-2884.9521ad3.almalinux/src/pyfaf/actions/attach_almalinux_bugs.py
+--- faf-git-2884.9521ad3.orig/src/pyfaf/actions/attach_almalinux_bugs.py 1970-01-01 03:00:00.000000000 +0300
++++ faf-git-2884.9521ad3.almalinux/src/pyfaf/actions/attach_almalinux_bugs.py 2021-02-09 19:18:08.530180952 +0300
+@@ -0,0 +1,71 @@
++# Copyright (C) 2015 ABRT Team
++# Copyright (C) 2015 Red Hat, Inc.
++#
++# This file is part of faf.
++#
++# faf is free software: you can redistribute it and/or modify
++# it under the terms of the GNU General Public License as published by
++# the Free Software Foundation, either version 3 of the License, or
++# (at your option) any later version.
++#
++# faf is distributed in the hope that it will be useful,
++# but WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++# GNU General Public License for more details.
++#
++# You should have received a copy of the GNU General Public License
++# along with faf. If not, see .
++
++from pyfaf.actions import Action
++from pyfaf.bugtrackers import bugtrackers
++from pyfaf.queries import (get_mantis_bug,
++ get_report,
++ get_bugtracker_by_name)
++from pyfaf.storage import ReportMantis
++
++
++class AttachAlmaLinuxBugs(Action):
++ """
++ Looks up all bugs created using ABRT on AlmaLinux MantisBT and attaches them
++ to reports using the bthash from URL. This is a workaround until proper
++ attaching is supported by the ABRT client.
++ """
++ name = "attach-almalinux-bugs"
++
++ def run(self, cmdline, db) -> None:
++ mbt = bugtrackers["almalinux-mantisbt"]
++ db_tracker = get_bugtracker_by_name(db, "almalinux-mantisbt")
++ for bug_id in mbt.list_bugs():
++ self.log_info("Processing Mantis issue #{0}".format(bug_id))
++ bug = mbt.mc.mc_issue_get(mbt.user, mbt.password, bug_id)
++ bug_dict = mbt.preprocess_bug(bug)
++ if bug_dict and bug_dict.get("url", False):
++ url = bug_dict["url"]
++ report_hash = url.split("/")[-1]
++ db_report = get_report(db, report_hash)
++ if db_report is None:
++ self.log_info("Report with hash {0} not found."
++ .format(report_hash))
++ continue
++ db_mantisbug = get_mantis_bug(db, bug_id, db_tracker.id)
++ if db_mantisbug is None:
++ self.log_info("Downloading bug to storage...")
++ db_mantisbug = mbt.download_bug_to_storage(db, bug_id)
++ if db_mantisbug is None:
++ self.log_info("Failed to download bug.")
++ continue
++
++ db_rm = (db.session.query(ReportMantis)
++ .filter(ReportMantis.report == db_report)
++ .filter(ReportMantis.mantisbug == db_mantisbug)
++ .first())
++ if db_rm is None:
++ db_rm = ReportMantis()
++ db_rm.mantisbug = db_mantisbug
++ db_rm.report = db_report
++ db.session.add(db_rm)
++ db.session.flush()
++ self.log_info("Associated issue #{0} with report #{1}."
++ .format(bug_id, db_report.id))
++ else:
++ self.log_info("Bug {0} is not a valid ABRT issue.".format(bug_id))
+diff -Naur faf-git-2884.9521ad3.orig/src/pyfaf/bugtrackers/almalinuxmantisbt.py faf-git-2884.9521ad3.almalinux/src/pyfaf/bugtrackers/almalinuxmantisbt.py
+--- faf-git-2884.9521ad3.orig/src/pyfaf/bugtrackers/almalinuxmantisbt.py 1970-01-01 03:00:00.000000000 +0300
++++ faf-git-2884.9521ad3.almalinux/src/pyfaf/bugtrackers/almalinuxmantisbt.py 2021-02-09 19:18:51.643896419 +0300
+@@ -0,0 +1,27 @@
++# Copyright (C) 2014 ABRT Team
++# Copyright (C) 2014 Red Hat, Inc.
++#
++# This file is part of faf.
++#
++# faf is free software: you can redistribute it and/or modify
++# it under the terms of the GNU General Public License as published by
++# the Free Software Foundation, either version 3 of the License, or
++# (at your option) any later version.
++#
++# faf is distributed in the hope that it will be useful,
++# but WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++# GNU General Public License for more details.
++#
++# You should have received a copy of the GNU General Public License
++# along with faf. If not, see .
++
++from pyfaf.bugtrackers import mantisbt
++
++__all__ = ["AlmaLinuxMantis"]
++
++# see https://github.com/abrt/faf/issues/695
++# pylint: disable=abstract-method
++
++class AlmaLinuxMantis(mantisbt.Mantis):
++ name = "almalinux-mantisbt"
+diff -Naur faf-git-2884.9521ad3.orig/src/pyfaf/opsys/almalinux.py faf-git-2884.9521ad3.almalinux/src/pyfaf/opsys/almalinux.py
+--- faf-git-2884.9521ad3.orig/src/pyfaf/opsys/almalinux.py 1970-01-01 03:00:00.000000000 +0300
++++ faf-git-2884.9521ad3.almalinux/src/pyfaf/opsys/almalinux.py 2021-02-09 19:16:54.251671666 +0300
+@@ -0,0 +1,245 @@
++# Copyright (C) 2013 ABRT Team
++# Copyright (C) 2013 Red Hat, Inc.
++#
++# This file is part of faf.
++#
++# faf is free software: you can redistribute it and/or modify
++# it under the terms of the GNU General Public License as published by
++# the Free Software Foundation, either version 3 of the License, or
++# (at your option) any later version.
++#
++# faf is distributed in the hope that it will be useful,
++# but WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++# GNU General Public License for more details.
++#
++# You should have received a copy of the GNU General Public License
++# along with faf. If not, see .
++from __future__ import absolute_import
++
++import re
++
++from typing import Dict, List
++
++from pyfaf.opsys import System
++from pyfaf.checker import DictChecker, IntChecker, ListChecker, StringChecker
++from pyfaf.common import FafError, log
++from pyfaf.queries import (get_arch_by_name,
++ get_opsys_by_name,
++ get_package_by_nevra,
++ get_reportpackage,
++ get_unknown_package)
++from pyfaf.storage import (Arch,
++ Build,
++ OpSys,
++ Package,
++ ReportPackage,
++ ReportUnknownPackage,
++ column_len)
++from pyfaf.repos import repo_types
++from pyfaf.utils.parse import str2bool, words2list
++from pyfaf.storage.custom_types import to_semver
++
++__all__ = ["AlmaLinux"]
++
++# see https://github.com/abrt/faf/issues/695
++# pylint: disable=abstract-method
++
++class AlmaLinux(System):
++ name = "almalinux"
++ nice_name = "AlmaLinux"
++
++ packages_checker = ListChecker(
++ DictChecker({
++ "name": StringChecker(pattern=r"^[a-zA-Z0-9_\-\.\+~]+$",
++ maxlen=column_len(Package,
++ "name")),
++ "epoch": IntChecker(minval=0),
++ "version": StringChecker(pattern=r"^[a-zA-Z0-9_\.\+~^]+$",
++ maxlen=column_len(Build, "version")),
++ "release": StringChecker(pattern=r"^[a-zA-Z0-9_\.\+]+$",
++ maxlen=column_len(Build, "release")),
++ "architecture": StringChecker(pattern=r"^[a-zA-Z0-9_]+$",
++ maxlen=column_len(Arch, "name")),
++ }), minlen=0
++ )
++
++ ureport_checker = DictChecker({
++ # no need to check name, version and architecture twice
++ # the toplevel checker already did it
++ # "name": StringChecker(allowed=[CentOS.name])
++ # "version": StringChecker()
++ # "architecture": StringChecker()
++ })
++
++ pkg_roles = ["affected", "related", "selinux_policy"]
++
++ @classmethod
++ def install(cls, db, logger=None) -> None:
++ if logger is None:
++ logger = log.getChild(cls.__name__)
++
++ logger.info("Adding AlmaLinux")
++ new = OpSys()
++ new.name = cls.nice_name
++ db.session.add(new)
++ db.session.flush()
++
++ @classmethod
++ def installed(cls, db) -> bool:
++ return bool(get_opsys_by_name(db, cls.nice_name))
++
++ def __init__(self) -> None:
++ super().__init__()
++ self.eol = None
++ self.repo_urls = []
++ self.allow_unpackaged = None
++ self.inactive_releases = None
++ self.active_releases = None
++ self.load_config_to_self("repo_urls", ["almalinux.repo-urls"], [],
++ callback=words2list)
++ self.load_config_to_self("allow_unpackaged",
++ ["ureport.allow-unpackaged"], False,
++ callback=str2bool)
++ self.load_config_to_self("inactive_releases", ["almalinux.inactive-releases"])
++ self.load_config_to_self("active_releases", ["almalinux.active-releases"])
++
++ def _save_packages(self, db, db_report, packages, count=1) -> None:
++ for package in packages:
++ role = "RELATED"
++ if "package_role" in package:
++ if package["package_role"] == "affected":
++ role = "CRASHED"
++ elif package["package_role"] == "selinux_policy":
++ role = "SELINUX_POLICY"
++
++ db_package = get_package_by_nevra(db,
++ name=package["name"],
++ epoch=package["epoch"],
++ version=package["version"],
++ release=package["release"],
++ arch=package["architecture"])
++ if db_package is None:
++ self.log_warn("Package {0}-{1}:{2}-{3}.{4} not found in "
++ "storage".format(package["name"],
++ package["epoch"],
++ package["version"],
++ package["release"],
++ package["architecture"]))
++
++ db_unknown_pkg = get_unknown_package(db,
++ db_report,
++ role,
++ package["name"],
++ package["epoch"],
++ package["version"],
++ package["release"],
++ package["architecture"])
++ if db_unknown_pkg is None:
++ db_arch = get_arch_by_name(db, package["architecture"])
++ if db_arch is None:
++ continue
++
++ db_unknown_pkg = ReportUnknownPackage()
++ db_unknown_pkg.report = db_report
++ db_unknown_pkg.name = package["name"]
++ db_unknown_pkg.epoch = package["epoch"]
++ db_unknown_pkg.version = package["version"]
++ db_unknown_pkg.release = package["release"]
++ db_unknown_pkg.semver = to_semver(package["version"])
++ db_unknown_pkg.semrel = to_semver(package["release"])
++ db_unknown_pkg.arch = db_arch
++ db_unknown_pkg.type = role
++ db_unknown_pkg.count = 0
++ db.session.add(db_unknown_pkg)
++
++ db_unknown_pkg.count += count
++ continue
++
++ db_reportpackage = get_reportpackage(db, db_report, db_package)
++ if db_reportpackage is None:
++ db_reportpackage = ReportPackage()
++ db_reportpackage.report = db_report
++ db_reportpackage.installed_package = db_package
++ db_reportpackage.count = 0
++ db_reportpackage.type = role
++ db.session.add(db_reportpackage)
++
++ db_reportpackage.count += count
++
++ def validate_ureport(self, ureport) -> bool:
++ AlmaLinux.ureport_checker.check(ureport)
++ return True
++
++ def validate_packages(self, packages) -> bool:
++ AlmaLinux.packages_checker.check(packages)
++ affected = False
++ for package in packages:
++ if "package_role" in package:
++ if package["package_role"] not in AlmaLinux.pkg_roles:
++ raise FafError("Only the following package roles are allowed: "
++ "{0}".format(", ".join(AlmaLinux.pkg_roles)))
++ if package["package_role"] == "affected":
++ affected = True
++
++ if not(affected or self.allow_unpackaged):
++ raise FafError("uReport must contain affected package")
++
++ return True
++
++ def save_ureport(self, db, db_report, ureport, packages, flush=False, count=1) -> None:
++ self._save_packages(db, db_report, packages, count=count)
++
++ if flush:
++ db.session.flush()
++
++ def get_releases(self) -> Dict[str, Dict[str, str]]:
++ result = {}
++
++ for release in re.findall(r"[\w\.]+", self.inactive_releases):
++ result[release] = {"status": "EOL"}
++ for release in re.findall(r"[\w\.]+", self.active_releases):
++ result[release] = {"status": "ACTIVE"}
++
++ return result
++
++ def get_components(self, release) -> List[str]:
++ if not self.repo_urls:
++ self.log_info("No repository URLs were found.")
++ return []
++
++ urls = [repo.replace("$releasever", release) for repo in self.repo_urls]
++ components = []
++ if "dnf" in repo_types:
++ from pyfaf.repos.dnf import Dnf
++ dnf = Dnf(self.name, *urls)
++ components.extend(list(set(pkg["name"]
++ for pkg in dnf.list_packages(["src"]))))
++ else:
++ raise FafError("No repo type available")
++ return components
++
++ #def get_component_acls(self, component, release=None):
++ # return {}
++
++ def get_build_candidates(self, db) -> List[Build]:
++ return (db.session.query(Build)
++ .filter(Build.release.like("%%.el%%"))
++ .all())
++
++ def check_pkgname_match(self, packages, parser) -> None:
++ for package in packages:
++ if ("package_role" not in package or
++ package["package_role"].lower() != "affected"):
++ continue
++
++ nvra = "{0}-{1}-{2}.{3}".format(package["name"],
++ package["version"],
++ package["release"],
++ package["architecture"])
++
++ match = parser.match(nvra)
++ if match is not None:
++ return True
++
++ return False
+diff -Naur faf-git-2884.9521ad3.orig/src/pyfaf/ureport.py faf-git-2884.9521ad3.almalinux/src/pyfaf/ureport.py
+--- faf-git-2884.9521ad3.orig/src/pyfaf/ureport.py 2021-01-27 00:52:48.000000000 +0300
++++ faf-git-2884.9521ad3.almalinux/src/pyfaf/ureport.py 2021-02-09 19:24:28.761661372 +0300
+@@ -487,6 +487,42 @@
+ log.error("Failed to fetch bug #{0} from '{1}'"
+ .format(bug_id, atype))
+
++ elif atype == "almalinux-mantisbt":
++ bug_id = int(attachment["data"])
++
++ reportbug = (db.session.query(ReportMantis)
++ .filter(
++ (ReportMantis.report_id == report.id) &
++ (ReportMantis.mantisbug_id == bug_id))
++ .first())
++
++ if reportbug:
++ log.debug("Skipping existing attachment")
++ return
++
++ db_tracker = get_bugtracker_by_name(db, "almalinux-mantisbt")
++ bug = get_mantis_bug(db, bug_id, db_tracker.id)
++ if not bug and atype in bugtrackers:
++ # download from bugtracker identified by atype
++ tracker = bugtrackers[atype]
++
++ if not tracker.installed(db):
++ raise FafError("Bugtracker used in this attachment"
++ " is not installed")
++
++ bug = tracker.download_bug_to_storage(db, bug_id)
++
++ if bug:
++ new = ReportMantis()
++ new.report = report
++ new.mantisbug = bug
++ db.session.add(new)
++ db.session.flush()
++ else:
++ log.error("Failed to fetch bug #{0} from '{1}'"
++ .format(bug_id, atype))
++
+ elif atype == "comment":
+ comment = ReportComment()
+ comment.report = report
diff --git a/SOURCES/faf-saml-auth.patch b/SOURCES/faf-saml-auth.patch
new file mode 100644
index 0000000..f332430
--- /dev/null
+++ b/SOURCES/faf-saml-auth.patch
@@ -0,0 +1,264 @@
+diff -crB src.orig/webfaf/config.py src/webfaf/config.py
+*** src.orig/webfaf/config.py 2021-02-11 11:31:10.000000000 +0200
+--- src/webfaf/config.py 2021-02-11 11:43:27.000000000 +0200
+***************
+*** 10,15 ****
+--- 10,16 ----
+
+
+ class Config(object):
++ SAML_CONFIG_DIR = '/etc/faf/saml'
+ DEBUG = False
+ TESTING = False
+ SECRET_KEY = 'NOT_A_RANDOM_STRING'
+diff -crB src.orig/webfaf/login.py src/webfaf/login.py
+*** src.orig/webfaf/login.py 2021-02-11 11:31:10.000000000 +0200
+--- src/webfaf/login.py 2021-02-11 11:44:30.000000000 +0200
+***************
+*** 1,56 ****
+ import flask
+ from openid_teams import teams
+ from werkzeug.wrappers import Response
+
+ from pyfaf.storage.user import User
+ from webfaf.webfaf_main import db, oid, app
+ from webfaf.utils import fed_raw_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())
+!
+! teams_req = teams.TeamsRequest(app.config["OPENID_PRIVILEGED_TEAMS"])
+! return oid.try_login("https://id.fedoraproject.org/",
+! ask_for=["email"], extensions=[teams_req])
+!
+!
+! @oid.after_login
+! def create_or_login(resp) -> Response:
+! flask.session["openid"] = resp.identity_url
+! username = fed_raw_name(resp.identity_url)
+
+ 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):
+ privileged = True
+
+ user = db.session.query(User).filter(User.username == username).first()
+ if not user: # create
+! user = User(username=username, mail=resp.email, privileged=privileged)
+ else:
+! user.mail = resp.email
+ user.privileged = privileged
+
+ db.session.add(user)
+ db.session.commit()
+ flask.flash(u"Welcome, {0}".format(user.username))
+ flask.g.user = user
+
+! if flask.request.url_root == oid.get_next_url():
+! return flask.redirect(flask.url_for("summary.index"))
+
+! return flask.redirect(oid.get_next_url())
+
+
+ @login.route("/logout/")
+ def do_logout() -> Response:
+! flask.session.pop("openid", None)
+ flask.flash(u"You were signed out")
+! return flask.redirect(oid.get_next_url())
+--- 1,113 ----
+ 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__)
+
+
+! def init_saml_auth(req):
+! saml_config_dir = app.config['SAML_CONFIG_DIR']
+! return OneLogin_Saml2_Auth(req, custom_base_path=saml_config_dir)
+!
+!
+! 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
+! }
+!
+!
+! @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
+! 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=email, privileged=privileged, admin=is_admin)
+ else:
+! 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))
+ flask.g.user = user
++ flask.session['username'] = username
+
+! return flask.redirect(flask.request.form['RelayState'])
+
+!
+! @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("username", None)
+ flask.flash(u"You were signed out")
+! return flask.redirect(flask.url_for("summary.index"))
+!
+diff -crB src.orig/webfaf/templates/base.html src/webfaf/templates/base.html
+*** src.orig/webfaf/templates/base.html 2021-02-11 11:31:12.000000000 +0200
+--- src/webfaf/templates/base.html 2021-02-11 13:09:20.000000000 +0200
+***************
+*** 71,77 ****
+
+ {% if config['OPENID_ENABLED'] %}
+
+-
+ {% if g.user %}
+ -
+
+--- 71,76 ----
+diff -crB src.orig/webfaf/webfaf_main.py src/webfaf/webfaf_main.py
+*** src.orig/webfaf/webfaf_main.py 2021-02-11 11:31:15.000000000 +0200
+--- src/webfaf/webfaf_main.py 2021-02-10 19:55:36.000000000 +0200
+***************
+*** 14,20 ****
+ import munch
+ from ratelimitingfilter import RateLimitingFilter
+ from werkzeug.local import LocalProxy
+! from werkzeug.middleware.proxy_fix import ProxyFix
+
+ from pyfaf.storage.user import User
+ from pyfaf.storage import OpSysComponent, Report
+--- 14,23 ----
+ import munch
+ from ratelimitingfilter import RateLimitingFilter
+ from werkzeug.local import LocalProxy
+! try:
+! from werkzeug.middleware.proxy_fix import ProxyFix
+! except ModuleNotFoundError:
+! from werkzeug.contrib.fixers import ProxyFix
+
+ from pyfaf.storage.user import User
+ from pyfaf.storage import OpSysComponent, Report
+***************
+*** 167,174 ****
+ @app.before_request
+ def before_request() -> None:
+ flask.g.user = None
+! if "openid" in flask.session:
+! username = fed_raw_name(flask.session["openid"])
+ flask.g.user = (db.session.query(User)
+ .filter(User.username == username)
+ .first())
+--- 170,177 ----
+ @app.before_request
+ def before_request() -> None:
+ flask.g.user = None
+! if "username" in flask.session:
+! username = flask.session["username"]
+ flask.g.user = (db.session.query(User)
+ .filter(User.username == username)
+ .first())
+diff -crB tests.orig/test_webfaf/test_user.py tests/test_webfaf/test_user.py
+*** tests.orig/test_webfaf/test_user.py 2021-02-11 11:31:18.000000000 +0200
+--- tests/test_webfaf/test_user.py 2021-02-11 11:55:50.000000000 +0200
+***************
+*** 24,30 ****
+ self.db.session.commit()
+
+ with self.app.session_transaction() as session:
+! session['openid'] = 'faker1'
+
+ def test_delete_user_data(self):
+ """
+--- 24,30 ----
+ self.db.session.commit()
+
+ with self.app.session_transaction() as session:
+! session['username'] = 'faker1'
+
+ def test_delete_user_data(self):
+ """
+
diff --git a/SPECS/faf.spec b/SPECS/faf.spec
index 9db98e5..cf187e4 100644
--- a/SPECS/faf.spec
+++ b/SPECS/faf.spec
@@ -1,10 +1,12 @@
Name: faf
Version: 2.6.1
-Release: 1%{?dist}
+Release: 1%{?dist}.alma
Summary: Software Problem Analysis Tool
License: GPLv3+
URL: https://github.com/abrt/faf/
Source0: https://github.com/abrt/%{name}/archive/%{version}/%{name}-%{version}.tar.gz
+Patch0: faf-almalinux_support.patch
+Patch1: faf-saml-auth.patch
BuildArch: noarch
@@ -155,6 +157,14 @@ Requires: %{name}-dnf = %{version}
%description opsys-centos
A plugin for %{name} implementing support for CentOS operating system.
+%package opsys-almalinux
+Summary: %{name}'s AlmaLinux operating system plugin
+Requires: %{name} = %{version}
+Requires: %{name}-dnf = %{version}
+
+%description opsys-almalinux
+A plugin for %{name} implementing support for AlmaLinux operating system.
+
%package opsys-fedora
Summary: %{name}'s Fedora operating system plugin
Requires: %{name} = %{version}
@@ -366,6 +376,14 @@ Requires: %{name}-bugtracker-centos-mantis = %{version}
%description action-attach-centos-bugs
A plugin for %{name} implementing attaching of bugs from CentOS Mantis bugtracker
+%package action-attach-almalinux-bugs
+Summary: %{name}'s attach-almalinux-bugs plugin
+Requires: %{name} = %{version}
+Requires: %{name}-bugtracker-almalinux-mantis = %{version}
+
+%description action-attach-almalinux-bugs
+A plugin for %{name} implementing attaching of bugs from AlmaLinux Mantis bugtracker
+
%package action-fedmsg-notify
Summary: %{name}'s fedmsg-notify plugin
Requires: %{name} = %{version}
@@ -492,6 +510,14 @@ Requires: %{name}-bugtracker-mantis = %{version}
%description bugtracker-centos-mantis
A plugin adding support for Mantis used by CentOS
+%package bugtracker-almalinux-mantis
+Summary: %{name}'s Mantis support for AlmaLinux
+Requires: %{name} = %{version}
+Requires: %{name}-bugtracker-mantis = %{version}
+
+%description bugtracker-almalinux-mantis
+A plugin adding support for Mantis used by AlmaLinux
+
%package fedmsg
Summary: %{name}'s Fedora Messaging support
Requires: %{name} = %{version}
@@ -543,6 +569,10 @@ systemd services for the Celery task queue.
%prep
%setup -q
+# AlmaLinux support patches
+%patch0 -p1
+%patch1 -p0
+
NOCONFIGURE=1 ./autogen.sh
%build
@@ -935,6 +965,11 @@ fi
%{python3_sitelib}/pyfaf/opsys/centos.py
%{python3_sitelib}/pyfaf/opsys/__pycache__/centos.*.pyc
+%files opsys-almalinux
+%config(noreplace) %{_sysconfdir}/faf/plugins/almalinux.conf
+%{python3_sitelib}/pyfaf/opsys/almalinux.py
+%{python3_sitelib}/pyfaf/opsys/__pycache__/almalinux.*.pyc
+
%files opsys-fedora
%config(noreplace) %{_sysconfdir}/faf/plugins/fedora.conf
%{python3_sitelib}/pyfaf/opsys/fedora.py
@@ -1091,6 +1126,10 @@ fi
%{python3_sitelib}/pyfaf/actions/attach_centos_bugs.py
%{python3_sitelib}/pyfaf/actions/__pycache__/attach_centos_bugs.*.pyc
+%files action-attach-almalinux-bugs
+%{python3_sitelib}/pyfaf/actions/attach_almalinux_bugs.py
+%{python3_sitelib}/pyfaf/actions/__pycache__/attach_almalinux_bugs.*.pyc
+
%files action-fedmsg-notify
%{python3_sitelib}/pyfaf/actions/fedmsg_notify.py
%{python3_sitelib}/pyfaf/actions/__pycache__/fedmsg_notify.*.pyc
@@ -1177,6 +1216,11 @@ fi
%{python3_sitelib}/pyfaf/bugtrackers/__pycache__/centosmantisbt.*.pyc
%config(noreplace) %{_sysconfdir}/faf/plugins/centosmantisbt.conf
+%files bugtracker-almalinux-mantis
+%{python3_sitelib}/pyfaf/bugtrackers/almalinuxmantisbt.py
+%{python3_sitelib}/pyfaf/bugtrackers/__pycache__/almalinuxmantisbt.*.pyc
+%config(noreplace) %{_sysconfdir}/faf/plugins/almalinuxmantisbt.conf
+
%files fedmsg
%config(noreplace) %{_sysconfdir}/faf/plugins/fedmsg.conf