diff --git a/SOURCES/0022-rpcserver-fallback-to-non-armored-kinit-in-case-of-trusted-domains_rhbz#1914821.patch b/SOURCES/0022-rpcserver-fallback-to-non-armored-kinit-in-case-of-trusted-domains_rhbz#1914821.patch
new file mode 100644
index 0000000..e382b47
--- /dev/null
+++ b/SOURCES/0022-rpcserver-fallback-to-non-armored-kinit-in-case-of-trusted-domains_rhbz#1914821.patch
@@ -0,0 +1,236 @@
+From 1441b999d3fe9b4e59fe942294d13480ecee7d94 Mon Sep 17 00:00:00 2001
+From: Alexander Bokovoy <abokovoy@redhat.com>
+Date: Wed, 28 Oct 2020 17:46:56 +0200
+Subject: [PATCH] rpcserver: fallback to non-armored kinit in case of trusted
+ domains
+
+MIT Kerberos implements FAST negotiation as specified in RFC 6806
+section 11. The implementation relies on the caller to provide a hint
+whether FAST armoring must be used.
+
+FAST armor can only be used when both client and KDC have a shared
+secret. When KDC is from a trusted domain, there is no way to have a
+shared secret between a generic Kerberos client and that KDC.
+
+[MS-KILE] section 3.2.5.4 'Using FAST When the Realm Supports FAST'
+allows KILE clients (Kerberos clients) to have local settings that
+direct it to enforce use of FAST. This is equal to the current
+implementation of 'kinit' utility in MIT Kerberos requiring to use FAST
+if armor cache (option '-T') is provided.
+
+[MS-KILE] section 3.3.5.7.4 defines a way for a computer from a
+different realm to use compound identity TGS-REQ to create FAST TGS-REQ
+explicitly armored with the computer's TGT. However, this method is not
+available to IPA framework as we don't have access to the IPA server's
+host key. In addition, 'kinit' utility does not support this method.
+
+Active Directory has a policy to force use of FAST when client
+advertizes its use. Since we cannot know in advance whether a principal
+to obtain initial credentials for belongs to our realm or to a trusted
+one due to enterprise principal canonicalization, we have to try to
+kinit. Right now we fail unconditionally if FAST couldn't be used and
+libkrb5 communication with a KDC from the user realm (e.g. from a
+trusted forest) causes enforcement of a FAST.
+
+In the latter case, as we cannot use FAST anyway, try to kinit again
+without advertizing FAST. This works even in the situations when FAST
+enforcement is enabled on Active Directory side: if client doesn't
+advertize FAST capability, it is not required. Additionally, FAST cannot
+be used for any practical need for a trusted domain's users yet.
+
+Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
+Reviewed-By: Rob Crittenden <rcritten@redhat.com>
+---
+ ipalib/errors.py                        |  6 ++
+ ipaserver/rpcserver.py                  | 94 ++++++++++++++++---------
+ ipatests/test_integration/test_trust.py | 21 ++++++
+ 3 files changed, 86 insertions(+), 35 deletions(-)
+
+diff --git a/ipalib/errors.py b/ipalib/errors.py
+index 1b17ca7ed..fa51e15c0 100644
+--- a/ipalib/errors.py
++++ b/ipalib/errors.py
+@@ -245,6 +245,12 @@ class PluginModuleError(PrivateError):
+     format = '%(name)s is not a valid plugin module'
+ 
+ 
++class KrbPrincipalWrongFAST(PrivateError):
++    """
++    Raised when it is not possible to use our FAST armor for kinit
++    """
++    format = '%(principal)s cannot use Anonymous PKINIT as a FAST armor'
++
+ ##############################################################################
+ # Public errors:
+ 
+diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py
+index 181295471..ed775170e 100644
+--- a/ipaserver/rpcserver.py
++++ b/ipaserver/rpcserver.py
+@@ -46,9 +46,11 @@ from ipalib.capabilities import VERSION_WITHOUT_CAPABILITIES
+ from ipalib.frontend import Local
+ from ipalib.install.kinit import kinit_armor, kinit_password
+ from ipalib.backend import Executioner
+-from ipalib.errors import (PublicError, InternalError, JSONError,
++from ipalib.errors import (
++    PublicError, InternalError, JSONError,
+     CCacheError, RefererError, InvalidSessionPassword, NotFound, ACIError,
+-    ExecutionError, PasswordExpired, KrbPrincipalExpired, UserLocked)
++    ExecutionError, PasswordExpired, KrbPrincipalExpired, KrbPrincipalWrongFAST,
++    UserLocked)
+ from ipalib.request import context, destroy_context
+ from ipalib.rpc import (xml_dumps, xml_loads,
+     json_encode_binary, json_decode_binary)
+@@ -957,6 +959,34 @@ class login_password(Backend, KerberosSession):
+         self.api.Backend.wsgi_dispatch.mount(self, self.key)
+ 
+     def __call__(self, environ, start_response):
++        def attempt_kinit(user_principal, password,
++                          ipa_ccache_name, use_armor=True):
++            try:
++                # try to remove in case an old file was there
++                os.unlink(ipa_ccache_name)
++            except OSError:
++                pass
++            try:
++                self.kinit(user_principal, password,
++                           ipa_ccache_name, use_armor=use_armor)
++            except PasswordExpired as e:
++                return self.unauthorized(environ, start_response,
++                                         str(e), 'password-expired')
++            except InvalidSessionPassword as e:
++                return self.unauthorized(environ, start_response,
++                                         str(e), 'invalid-password')
++            except KrbPrincipalExpired as e:
++                return self.unauthorized(environ,
++                                         start_response,
++                                         str(e),
++                                         'krbprincipal-expired')
++            except UserLocked as e:
++                return self.unauthorized(environ,
++                                         start_response,
++                                         str(e),
++                                         'user-locked')
++            return None
++
+         logger.debug('WSGI login_password.__call__:')
+ 
+         # Get the user and password parameters from the request
+@@ -1007,26 +1037,14 @@ class login_password(Backend, KerberosSession):
+         ipa_ccache_name = os.path.join(paths.IPA_CCACHES,
+                                        'kinit_{}'.format(os.getpid()))
+         try:
+-            # try to remove in case an old file was there
+-            os.unlink(ipa_ccache_name)
+-        except OSError:
+-            pass
+-        try:
+-            self.kinit(user_principal, password, ipa_ccache_name)
+-        except PasswordExpired as e:
+-            return self.unauthorized(environ, start_response, str(e), 'password-expired')
+-        except InvalidSessionPassword as e:
+-            return self.unauthorized(environ, start_response, str(e), 'invalid-password')
+-        except KrbPrincipalExpired as e:
+-            return self.unauthorized(environ,
+-                                     start_response,
+-                                     str(e),
+-                                     'krbprincipal-expired')
+-        except UserLocked as e:
+-            return self.unauthorized(environ,
+-                                     start_response,
+-                                     str(e),
+-                                     'user-locked')
++            result = attempt_kinit(user_principal, password,
++                                   ipa_ccache_name, use_armor=True)
++        except KrbPrincipalWrongFAST:
++            result = attempt_kinit(user_principal, password,
++                                   ipa_ccache_name, use_armor=False)
++
++        if result is not None:
++            return result
+ 
+         result = self.finalize_kerberos_acquisition('login_password',
+                                                     ipa_ccache_name, environ,
+@@ -1038,21 +1056,24 @@ class login_password(Backend, KerberosSession):
+             pass
+         return result
+ 
+-    def kinit(self, principal, password, ccache_name):
+-        # get anonymous ccache as an armor for FAST to enable OTP auth
+-        armor_path = os.path.join(paths.IPA_CCACHES,
+-                                  "armor_{}".format(os.getpid()))
++    def kinit(self, principal, password, ccache_name, use_armor=True):
++        if use_armor:
++            # get anonymous ccache as an armor for FAST to enable OTP auth
++            armor_path = os.path.join(paths.IPA_CCACHES,
++                                      "armor_{}".format(os.getpid()))
+ 
+-        logger.debug('Obtaining armor in ccache %s', armor_path)
++            logger.debug('Obtaining armor in ccache %s', armor_path)
+ 
+-        try:
+-            kinit_armor(
+-                armor_path,
+-                pkinit_anchors=[paths.KDC_CERT, paths.KDC_CA_BUNDLE_PEM],
+-            )
+-        except RuntimeError as e:
+-            logger.error("Failed to obtain armor cache")
+-            # We try to continue w/o armor, 2FA will be impacted
++            try:
++                kinit_armor(
++                    armor_path,
++                    pkinit_anchors=[paths.KDC_CERT, paths.KDC_CA_BUNDLE_PEM],
++                )
++            except RuntimeError as e:
++                logger.error("Failed to obtain armor cache")
++                # We try to continue w/o armor, 2FA will be impacted
++                armor_path = None
++        else:
+             armor_path = None
+ 
+         try:
+@@ -1080,6 +1101,9 @@ class login_password(Backend, KerberosSession):
+                   'while getting initial credentials') in str(e):
+                 raise UserLocked(principal=principal,
+                                  message=unicode(e))
++            elif ('kinit: Error constructing AP-REQ armor: '
++                  'Matching credential not found') in str(e):
++                raise KrbPrincipalWrongFAST(principal=principal)
+             raise InvalidSessionPassword(principal=principal,
+                                          message=unicode(e))
+ 
+diff --git a/ipatests/test_integration/test_trust.py b/ipatests/test_integration/test_trust.py
+index a6a055c2a..bec918a31 100644
+--- a/ipatests/test_integration/test_trust.py
++++ b/ipatests/test_integration/test_trust.py
+@@ -175,6 +175,27 @@ class TestTrust(BaseTestTrust):
+         tasks.kdestroy_all(self.master)
+         tasks.kinit_admin(self.master)
+ 
++    def test_password_login_as_aduser(self):
++        """Test if AD user can login with password to Web UI"""
++        ad_admin = 'Administrator@%s' % self.ad_domain
++
++        tasks.kdestroy_all(self.master)
++        user_and_password = ('user=%s&password=%s' %
++                             (ad_admin, self.master.config.ad_admin_password))
++        host = self.master.hostname
++        cmd_args = [
++            paths.BIN_CURL,
++            '-v',
++            '-H', 'referer:https://{}/ipa'.format(host),
++            '-H', 'Content-Type:application/x-www-form-urlencoded',
++            '-H', 'Accept:text/plain',
++            '--cacert', paths.IPA_CA_CRT,
++            '--data', user_and_password,
++            'https://{}/ipa/session/login_password'.format(host)]
++        result = self.master.run_command(cmd_args)
++        assert "Set-Cookie: ipa_session=MagBearerToken" in result.stdout_text
++        tasks.kinit_admin(self.master)
++
+     def test_ipauser_authentication_with_nonposix_trust(self):
+         ipauser = u'tuser'
+         original_passwd = 'Secret123'
+-- 
+2.29.2
+
diff --git a/SOURCES/0023-pylint-remove-unused-variable_rhbz#1914821.patch b/SOURCES/0023-pylint-remove-unused-variable_rhbz#1914821.patch
new file mode 100644
index 0000000..91596b6
--- /dev/null
+++ b/SOURCES/0023-pylint-remove-unused-variable_rhbz#1914821.patch
@@ -0,0 +1,27 @@
+From 12de9ee69f12f7c0021ea98e9c1163db7d59e5d3 Mon Sep 17 00:00:00 2001
+From: Alexander Bokovoy <abokovoy@redhat.com>
+Date: Wed, 28 Oct 2020 19:37:11 +0200
+Subject: [PATCH] pylint: remove unused variable
+
+Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
+Reviewed-By: Rob Crittenden <rcritten@redhat.com>
+---
+ ipaserver/rpcserver.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py
+index 27850e867..181295471 100644
+--- a/ipaserver/rpcserver.py
++++ b/ipaserver/rpcserver.py
+@@ -972,7 +972,7 @@ class login_password(Backend, KerberosSession):
+ 
+         try:
+             query_dict = parse_qs(query_string)
+-        except Exception as e:
++        except Exception:
+             return self.bad_request(environ, start_response, "cannot parse query data")
+ 
+         user = query_dict.get('user', None)
+-- 
+2.29.2
+
diff --git a/SOURCES/0024-wgi-plugins.py-ignore-empty-plugin-directories_rhbz#1895910.patch b/SOURCES/0024-wgi-plugins.py-ignore-empty-plugin-directories_rhbz#1895910.patch
new file mode 100644
index 0000000..432aa61
--- /dev/null
+++ b/SOURCES/0024-wgi-plugins.py-ignore-empty-plugin-directories_rhbz#1895910.patch
@@ -0,0 +1,121 @@
+From 29262465edf034d521c165e3854e28835d86b98d Mon Sep 17 00:00:00 2001
+From: Alexander Bokovoy <abokovoy@redhat.com>
+Date: Fri, 6 Nov 2020 09:53:35 +0200
+Subject: [PATCH] wgi/plugins.py: ignore empty plugin directories
+
+Dynamic plugin registry returns as a plugin any folder within the
+plugins directory. Web UI then attempts to load for each plugin 'foo' a
+JavaScript file named 'foo/foo.js'. The problem is that if 'foo/foo.js'
+does not exist, Web UI breaks and it is impossible to recover until the
+empty folder is removed or 'foo/foo.js' (even empty) is created at the
+server side.
+
+Check that 'foo/foo.js' actual exists when including a plugin into the
+registry.
+
+Test the registry generator by creating fake plugins and removing them
+during the test.
+
+Fixes: https://pagure.io/freeipa/issue/8567
+
+Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
+Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
+---
+ install/wsgi/plugins.py                   |  5 +-
+ ipatests/test_ipaserver/test_jsplugins.py | 68 +++++++++++++++++++++++
+ 2 files changed, 72 insertions(+), 1 deletion(-)
+ create mode 100644 ipatests/test_ipaserver/test_jsplugins.py
+
+diff --git a/install/wsgi/plugins.py b/install/wsgi/plugins.py
+index f80cfb9fe..4c43e7f87 100644
+--- a/install/wsgi/plugins.py
++++ b/install/wsgi/plugins.py
+@@ -36,7 +36,10 @@ def get_plugin_index():
+ 
+     dirs = os.listdir(paths.IPA_JS_PLUGINS_DIR)
+     index = 'define([],function(){return['
+-    index += ','.join("'"+x+"'" for x in dirs)
++    for x in dirs:
++        p = os.path.join(paths.IPA_JS_PLUGINS_DIR, x, x + '.js')
++        if os.path.exists(p):
++            index += "'" + x + "',"
+     index += '];});'
+     return index.encode('utf-8')
+ 
+diff --git a/ipatests/test_ipaserver/test_jsplugins.py b/ipatests/test_ipaserver/test_jsplugins.py
+new file mode 100644
+index 000000000..354e6992c
+--- /dev/null
++++ b/ipatests/test_ipaserver/test_jsplugins.py
+@@ -0,0 +1,68 @@
++# Copyright (C) 2020  FreeIPA Contributors see COPYING for license
++
++import os
++import pytest
++
++from ipatests.test_ipaserver.httptest import Unauthorized_HTTP_test
++from ipatests.util import assert_equal, assert_not_equal
++from ipaplatform.paths import paths
++
++
++@pytest.mark.tier1
++class test_jsplugins(Unauthorized_HTTP_test):
++    app_uri = '/ipa/ui/js/freeipa/plugins.js'
++    jsplugins = (('foo', 'foo.js'), ('bar', ''))
++    content_type = 'application/javascript'
++
++    def test_jsplugins(self):
++        empty_response = "define([],function(){return[];});"
++
++        # Step 1: make sure default response has no additional plugins
++        response = self.send_request(method='GET')
++        assert_equal(response.status, 200)
++        response_data = response.read().decode(encoding='utf-8')
++        assert_equal(response_data, empty_response)
++
++        # Step 2: add fake plugins
++        try:
++            for (d, f) in self.jsplugins:
++                dir = os.path.join(paths.IPA_JS_PLUGINS_DIR, d)
++                if not os.path.exists(dir):
++                    os.mkdir(dir, 0o755)
++                if f:
++                    with open(os.path.join(dir, f), 'w') as js:
++                        js.write("/* test js plugin */")
++
++        except OSError as e:
++            pytest.skip(
++                'Cannot set up test JS plugin: %s' % e
++            )
++
++        # Step 3: query plugins to see if our plugins exist
++        response = self.send_request(method='GET')
++        assert_equal(response.status, 200)
++        response_data = response.read().decode(encoding='utf-8')
++        assert_not_equal(response_data, empty_response)
++        for (d, f) in self.jsplugins:
++            if f:
++                assert "'" + d + "'" in response_data
++            else:
++                assert "'" + d + "'" not in response_data
++
++        # Step 4: remove fake plugins
++        try:
++            for (d, f) in self.jsplugins:
++                dir = os.path.join(paths.IPA_JS_PLUGINS_DIR, d)
++                file = os.path.join(dir, f)
++                if f and os.path.exists(file):
++                    os.unlink(file)
++                if os.path.exists(dir):
++                    os.rmdir(dir)
++        except OSError:
++            pass
++
++        # Step 5: make sure default response has no additional plugins
++        response = self.send_request(method='GET')
++        assert_equal(response.status, 200)
++        response_data = response.read().decode(encoding='utf-8')
++        assert_equal(response_data, empty_response)
+-- 
+2.29.2
+
diff --git a/SOURCES/0025-ipatests-support-subordinate-upn-suffixes_rhbz#1914823.patch b/SOURCES/0025-ipatests-support-subordinate-upn-suffixes_rhbz#1914823.patch
new file mode 100644
index 0000000..1ffa594
--- /dev/null
+++ b/SOURCES/0025-ipatests-support-subordinate-upn-suffixes_rhbz#1914823.patch
@@ -0,0 +1,76 @@
+From d5cca835d5439331c05475d0ad2f993ac6f8b615 Mon Sep 17 00:00:00 2001
+From: Sudhir Menon <sumenon@redhat.com>
+Date: Wed, 11 Nov 2020 14:55:32 +0530
+Subject: [PATCH] ipatests: support subordinate upn suffixes
+
+This test adds new UPN Suffix on the AD side
+within the ad.test subtree i.e new.ad.test and this
+UPN is then assigned to aduser and then try to
+kinit using aduser along with the UPN set, to ensure
+that the kinit succeeds
+
+Signed-off-by: Sudhir Menon <sumenon@redhat.com>
+Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
+---
+ ipatests/test_integration/test_trust.py | 45 +++++++++++++++++++++++++
+ 1 file changed, 45 insertions(+)
+
+diff --git a/ipatests/test_integration/test_trust.py b/ipatests/test_integration/test_trust.py
+index 7e4dbcc6e..31349ced7 100644
+--- a/ipatests/test_integration/test_trust.py
++++ b/ipatests/test_integration/test_trust.py
+@@ -245,6 +245,51 @@ class TestTrust(BaseTestTrust):
+         self.master.run_command(['kinit', '-C', '-E', self.upn_principal],
+                                 stdin_text=self.upn_password)
+ 
++    def test_subordinate_suffix(self):
++        """Test subordinate UPN Suffixes"""
++        tasks.configure_dns_for_trust(self.master, self.ad)
++        tasks.establish_trust_with_ad(
++            self.master, self.ad_domain,
++            extra_args=['--range-type', 'ipa-ad-trust'])
++        # Clear all UPN Suffixes
++        ps_cmd = "Get-ADForest | Set-ADForest -UPNSuffixes $null"
++        self.ad.run_command(["powershell", "-c", ps_cmd])
++        result = self.master.run_command(["ipa", "trust-show", self.ad_domain])
++        assert (
++            "ipantadditionalsuffixes: {}".format(self.upn_suffix)
++            not in result.stdout_text
++        )
++        # Run Get-ADForest
++        ps_cmd1 = "Get-ADForest"
++        self.ad.run_command(["powershell", "-c", ps_cmd1])
++        # Add new UPN for AD
++        ps_cmd2 = (
++            'Get-ADForest | Set-ADForest -UPNSuffixes '
++            '@{add="new.ad.test", "upn.dom"}'
++        )
++        self.ad.run_command(["powershell", "-c", ps_cmd2])
++        self.ad.run_command(["powershell", "-c", ps_cmd1])
++        self.master.run_command(
++            ["ipa", "trust-fetch-domains", self.ad_domain],
++            raiseonerr=False)
++        self.master.run_command(["ipa", "trust-show", self.ad_domain])
++        # Set UPN for the aduser
++        ps_cmd3 = (
++            'set-aduser -UserPrincipalName '
++            'Administrator@new.ad.test -Identity Administrator'
++        )
++        self.ad.run_command(["powershell", "-c", ps_cmd3])
++        # kinit to IPA using AD user Administrator@new.ad.test
++        result = self.master.run_command(
++            ["getent", "passwd", "Administrator@new.ad.test"]
++        )
++        assert result.returncode == 0
++        self.master.run_command(
++            ["kinit", "-E", "Administrator@new.ad.test"],
++            stdin_text="Secret123",
++        )
++        tasks.kdestroy_all(self.master)
++
+     def test_remove_nonposix_trust(self):
+         self.remove_trust(self.ad)
+         tasks.unconfigure_dns_for_trust(self.master, self.ad)
+-- 
+2.29.2
+
diff --git a/SOURCES/0026-ipa-kdb-support-subordinate-superior-UPN-suffixes_rhbz#1914823.patch b/SOURCES/0026-ipa-kdb-support-subordinate-superior-UPN-suffixes_rhbz#1914823.patch
new file mode 100644
index 0000000..19adf2b
--- /dev/null
+++ b/SOURCES/0026-ipa-kdb-support-subordinate-superior-UPN-suffixes_rhbz#1914823.patch
@@ -0,0 +1,114 @@
+From 1f0702bf9231a4898a2d58325fc51c71fea25047 Mon Sep 17 00:00:00 2001
+From: Alexander Bokovoy <abokovoy@redhat.com>
+Date: Fri, 23 Oct 2020 18:45:09 +0300
+Subject: [PATCH] ipa-kdb: support subordinate/superior UPN suffixes
+
+[MS-ADTS] 6.1.6.9.3.2 requires msDS-TrustForestTrustInfo attribute of
+trusted domain information in Active Directory to conform certain rules.
+One side-effect of those rules is that list of UPN suffixes reported
+through the netr_DsRGetForestTrustInformation function is dynamically
+filtered to deduplicate subordinate suffixes.
+
+It means that if list of UPN suffixes contains the following top level
+names (TLNs):
+
+  fabrikam.com
+  sub.fabrikam.com
+
+then netr_DsRGetForestTrustInformation would only return 'fabrikam.com'
+as the TLN, fully filtering 'sub.fabrikam.com'.
+
+IPA KDB driver used exact comparison of the UPN suffixes so any
+subordinate had to be specified exactly.
+
+Modify logic so that if exact check does not succeed, we validate a
+realm to test being a subordinate of the known UPN suffixes. The
+subordinate check is done by making sure UPN suffix is at the end of the
+test realm and is immediately preceded with a dot.
+
+Because the function to check suffixes potentially called for every
+Kerberos principal, precalculate and cache length for each UPN suffix at
+the time we retrieve the list of them.
+
+Fixes: https://pagure.io/freeipa/issue/8554
+
+Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
+Reviewed-By: Rob Crittenden <rcritten@redhat.com>
+Reviewed-By: Robbie Harwood <rharwood@redhat.com>
+Reviewed-By: Rob Crittenden <rcritten@redhat.com>
+Reviewed-By: Robbie Harwood <rharwood@redhat.com>
+---
+ daemons/ipa-kdb/ipa_kdb_mspac.c         | 30 +++++++++++++++++++++++++
+ daemons/ipa-kdb/ipa_kdb_mspac_private.h |  1 +
+ 2 files changed, 31 insertions(+)
+
+diff --git a/daemons/ipa-kdb/ipa_kdb_mspac.c b/daemons/ipa-kdb/ipa_kdb_mspac.c
+index 29dadc183..692f542c9 100644
+--- a/daemons/ipa-kdb/ipa_kdb_mspac.c
++++ b/daemons/ipa-kdb/ipa_kdb_mspac.c
+@@ -2393,6 +2393,7 @@ void ipadb_mspac_struct_free(struct ipadb_mspac **mspac)
+                     free((*mspac)->trusts[i].upn_suffixes[j]);
+                 }
+                 free((*mspac)->trusts[i].upn_suffixes);
++                free((*mspac)->trusts[i].upn_suffixes_len);
+             }
+         }
+         free((*mspac)->trusts);
+@@ -2603,6 +2604,24 @@ krb5_error_code ipadb_mspac_get_trusted_domains(struct ipadb_context *ipactx)
+             }
+         }
+ 
++        t[n].upn_suffixes_len = NULL;
++        if (t[n].upn_suffixes != NULL) {
++            size_t len = 0;
++
++            for (; t[n].upn_suffixes[len] != NULL; len++);
++
++            if (len != 0) {
++                t[n].upn_suffixes_len = calloc(n, sizeof(size_t));
++                if (t[n].upn_suffixes_len == NULL) {
++                    ret = ENOMEM;
++                    goto done;
++                }
++                for (i = 0; i < len; i++) {
++                    t[n].upn_suffixes_len[i] = strlen(t[n].upn_suffixes[i]);
++                }
++            }
++        }
++
+         ret = ipadb_ldap_attr_to_strlist(lc, le, "ipaNTSIDBlacklistIncoming",
+                                          &sid_blacklist_incoming);
+ 
+@@ -2972,6 +2991,17 @@ krb5_error_code ipadb_is_princ_from_trusted_realm(krb5_context kcontext,
+ 				result = strncasecmp(test_realm,
+ 						     ipactx->mspac->trusts[i].upn_suffixes[j],
+ 						     size) == 0;
++				if (!result) {
++					/* if UPN suffix did not match exactly, find if it is
++					 * superior to the test_realm, e.g. if test_realm ends
++					 * with the UPN suffix prefixed with dot*/
++					size_t len = ipactx->mspac->trusts[i].upn_suffixes_len[j];
++					if ((size > len) && (test_realm[size - len - 1] == '.')) {
++						result = strncasecmp(test_realm + (size - len),
++								     ipactx->mspac->trusts[i].upn_suffixes[j],
++								     len) == 0;
++					}
++				}
+ 				if (result)
+ 					break;
+ 			}
+diff --git a/daemons/ipa-kdb/ipa_kdb_mspac_private.h b/daemons/ipa-kdb/ipa_kdb_mspac_private.h
+index 30382d2ee..b21aa163f 100644
+--- a/daemons/ipa-kdb/ipa_kdb_mspac_private.h
++++ b/daemons/ipa-kdb/ipa_kdb_mspac_private.h
+@@ -48,6 +48,7 @@ struct ipadb_adtrusts {
+     struct ipadb_adtrusts *parent;
+     char *parent_name;
+     char **upn_suffixes;
++    size_t *upn_suffixes_len;
+ };
+ 
+ int string_to_sid(const char *str, struct dom_sid *sid);
+-- 
+2.29.2
+
diff --git a/SOURCES/0027-ad-trust-accept-subordinate-domains-of-the-forest-trust-root_rhbz#1914823.patch b/SOURCES/0027-ad-trust-accept-subordinate-domains-of-the-forest-trust-root_rhbz#1914823.patch
new file mode 100644
index 0000000..f8be726
--- /dev/null
+++ b/SOURCES/0027-ad-trust-accept-subordinate-domains-of-the-forest-trust-root_rhbz#1914823.patch
@@ -0,0 +1,57 @@
+From 6b224e57672e3f73f93bb9eddd9031e945529a1e Mon Sep 17 00:00:00 2001
+From: Alexander Bokovoy <abokovoy@redhat.com>
+Date: Tue, 24 Nov 2020 16:03:36 +0200
+Subject: [PATCH] ad trust: accept subordinate domains of the forest trust root
+
+Commit 8b6d1ab854387840f7526d6d59ddc7102231957f added support for
+subordinate UPN suffixes but missed the case where subordinate UPN is a
+subdomain of the forest root domain and not mentioned in the UPN
+suffixes list.
+
+Correct this situation by applying the same check to the trusted domain
+name as well.
+
+Fixes: https://pagure.io/freeipa/issue/8554
+Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
+Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
+---
+ daemons/ipa-kdb/ipa_kdb_mspac.c | 12 +++++++++++-
+ 1 file changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/daemons/ipa-kdb/ipa_kdb_mspac.c b/daemons/ipa-kdb/ipa_kdb_mspac.c
+index f2bd60e11..c6ac593ca 100644
+--- a/daemons/ipa-kdb/ipa_kdb_mspac.c
++++ b/daemons/ipa-kdb/ipa_kdb_mspac.c
+@@ -2976,10 +2976,20 @@ krb5_error_code ipadb_is_princ_from_trusted_realm(krb5_context kcontext,
+ 
+ 	/* Iterate through list of trusts and check if input realm belongs to any of the trust */
+ 	for(i = 0 ; i < ipactx->mspac->num_trusts ; i++) {
++		size_t len = 0;
+ 		result = strncasecmp(test_realm,
+ 				     ipactx->mspac->trusts[i].domain_name,
+ 				     size) == 0;
+ 
++		if (!result) {
++			len = strlen(ipactx->mspac->trusts[i].domain_name);
++			if ((size > len) && (test_realm[size - len - 1] == '.')) {
++				result = strncasecmp(test_realm + (size - len),
++						     ipactx->mspac->trusts[i].domain_name,
++						     len) == 0;
++			}
++		}
++
+                 if (!result && (ipactx->mspac->trusts[i].flat_name != NULL)) {
+ 			result = strncasecmp(test_realm,
+ 					     ipactx->mspac->trusts[i].flat_name,
+@@ -2995,7 +3005,7 @@ krb5_error_code ipadb_is_princ_from_trusted_realm(krb5_context kcontext,
+ 					/* if UPN suffix did not match exactly, find if it is
+ 					 * superior to the test_realm, e.g. if test_realm ends
+ 					 * with the UPN suffix prefixed with dot*/
+-					size_t len = ipactx->mspac->trusts[i].upn_suffixes_len[j];
++					len = ipactx->mspac->trusts[i].upn_suffixes_len[j];
+ 					if ((size > len) && (test_realm[size - len - 1] == '.')) {
+ 						result = strncasecmp(test_realm + (size - len),
+ 								     ipactx->mspac->trusts[i].upn_suffixes[j],
+-- 
+2.29.2
+
diff --git a/SPECS/ipa.spec b/SPECS/ipa.spec
index 0bd2c8f..54eed1a 100644
--- a/SPECS/ipa.spec
+++ b/SPECS/ipa.spec
@@ -149,7 +149,7 @@
 
 Name:           %{package_name}
 Version:        %{IPA_VERSION}
-Release:        13%{?dist}
+Release:        14%{?dist}
 Summary:        The Identity, Policy and Audit system
 
 License:        GPLv3+
@@ -185,6 +185,12 @@ Patch0018:      0018-dogtaginstance.py-add-debug-to-pkispawn_rhbz#1879604.patch
 Patch0019:      0019-SELinux-add-dedicated-policy-for-ipa-pki-retrieve-key-ipatests-enhance-TestSubCAkeyReplication_rhbz#1870202.patch
 Patch0020:      0020-SELinux-do-not-double-define-node_t-and-pki_tomcat_c_rhbz#1870202.patch
 Patch0021:      0021-Fix-nsslapd-db-lock-tuning-of-BDB-backend_rhbz#1882472.patch
+Patch0022:      0022-rpcserver-fallback-to-non-armored-kinit-in-case-of-trusted-domains_rhbz#1914821.patch
+Patch0023:      0023-pylint-remove-unused-variable_rhbz#1914821.patch
+Patch0024:      0024-wgi-plugins.py-ignore-empty-plugin-directories_rhbz#1895910.patch
+Patch0025:      0025-ipatests-support-subordinate-upn-suffixes_rhbz#1914823.patch
+Patch0026:      0026-ipa-kdb-support-subordinate-superior-UPN-suffixes_rhbz#1914823.patch
+Patch0027:      0027-ad-trust-accept-subordinate-domains-of-the-forest-trust-root_rhbz#1914823.patch
 Patch1001:      1001-Change-branding-to-IPA-and-Identity-Management.patch
 Patch1002:      1002-4.8.0-Remove-csrgen.patch
 Patch1003:      1003-Revert-WebUI-use-python3-rjsmin-to-minify-JavaScript.patch
@@ -862,7 +868,6 @@ export PATH=/usr/bin:/usr/sbin:$PATH
 
 export PYTHON=%{__python3}
 %configure --with-vendor-suffix=-%{release} \
-           --with-ipaplatform=rhel \
            %{enable_server_option} \
            %{with_ipatests_option} \
            %{linter_options}
@@ -1536,6 +1541,20 @@ fi
 
 
 %changelog
+* Tue Jan 12 2021 Rafael Jeffman <rjeffman@redhat.com> - 4.8.7-14
+- wgi/plugins.py: ignore empty plugin directories
+  Resolves: RHBZ#1895910
+- rpcserver: fallback to non-armored kinit in case of trusted domains
+  Resolves: RHBZ#1914821
+- pylint: remove unused variable
+  Resolves: RHBZ#1914821
+- ipa-kdb: support subordinate/superior UPN suffixes
+  Resolves: RHBZ#1914823
+- ad trust: accept subordinate domains of the forest trust root
+  Resolves: RHBZ#1914823
+- ipatests: support subordinate upn suffixes
+  Resolves: RHBZ#1914823
+
 * Thu Oct 08 2020 Thomas Woerner <twoerner@redhat.com> - 4.8.7-13
 - Fix nsslapd-db-lock tuning of BDB backend
   Resolves: RHBZ#1882472