Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b4badd451 | ||
|
|
e2d7602c1d | ||
|
|
a7369944d8 | ||
|
|
2385d1d90a | ||
|
|
5e2e4664ae | ||
|
|
aaf57185a2 | ||
|
|
05a298f564 | ||
|
|
ade5093b08 | ||
|
|
a5762621ef | ||
|
|
d40fd28783 | ||
|
|
6483f33389 | ||
|
|
1316cd8b22 | ||
|
|
f962a0c283 | ||
|
|
1ada42e3bc | ||
|
|
b59baf31bc | ||
|
|
98c6e96e8d | ||
|
|
e77b0b08d7 | ||
|
|
b31631ad69 | ||
|
|
f33266c2ba | ||
|
|
991849cf58 | ||
|
|
cbf2614d8a | ||
|
|
585cebb1a9 | ||
|
|
3675bd1d7a | ||
|
|
de64d6724e | ||
|
|
4f15804270 | ||
|
|
15f454f6f8 | ||
|
|
56c8270997 | ||
|
|
c39c2ee80d | ||
|
|
afa94c7995 | ||
|
|
50b4d9ab3f | ||
|
|
857713c5a9 | ||
|
|
4984ff210a | ||
|
|
ff4152539b | ||
|
|
206e08d811 | ||
|
|
a6a6781284 | ||
|
|
3e90842b3d |
No files matched your search
@@ -155,6 +155,7 @@ install/custodia/ipa-custodia-dmldap
|
||||
install/custodia/ipa-custodia-pki-tomcat
|
||||
install/custodia/ipa-custodia-pki-tomcat-wrapped
|
||||
install/custodia/ipa-custodia-ra-agent
|
||||
install/oddjob/org.freeipa.server.config-enable-sid
|
||||
install/oddjob/org.freeipa.server.trust-enable-agent
|
||||
install/oddjob/com.redhat.idm.trust-fetch-domains
|
||||
install/oddjob/etc/oddjobd.conf.d/ipa-server.conf
|
||||
|
||||
+9
-2
@@ -23,6 +23,8 @@ PYTHON_SCRIPT_SUBDIRS = \
|
||||
$(top_builddir)/install/tools \
|
||||
$(NULL)
|
||||
|
||||
PRCI_DEFINITIONS_DIR = $(top_srcdir)/ipatests/prci_definitions
|
||||
|
||||
AZURE_PYTHON_SCRIPT_SUBDIR = $(top_builddir)/ipatests/azure
|
||||
|
||||
IPA_PLACEHOLDERS = freeipa ipa ipaserver ipatests
|
||||
@@ -336,6 +338,8 @@ endif # WITH_RPMLINT
|
||||
# There are Jinja yaml templates, which differ from reqular ones. These
|
||||
# files should be placed on skip list (YAML_TEMPLATE_FILES), otherwise
|
||||
# safe_load fails.
|
||||
# Also check PRCI definitions yaml files jobs format and content with
|
||||
# prci_checker script
|
||||
.PHONY: yamllint
|
||||
yamllint:
|
||||
YAML_TEMPLATE_FILES="\
|
||||
@@ -357,7 +361,10 @@ yamllint:
|
||||
echo $${YAML}; \
|
||||
$(PYTHON) -c "import yaml; f = open('$${YAML}'); yaml.safe_load(f); f.close()" || { echo Your YAML file: $${YAML} has a wrong syntax or this is a Jinja template. In the latter clause, consider to add your YAML file to the YAML_TEMPLATE_FILES list in Makefile.am.; exit 1; } \
|
||||
done; \
|
||||
echo "-----------"
|
||||
echo -e "\nCheck PRCI definitions";
|
||||
@echo "-----------"
|
||||
$(PYTHON) $(PRCI_DEFINITIONS_DIR)/prci_checker.py -d $(PRCI_DEFINITIONS_DIR) -s $(PRCI_DEFINITIONS_DIR)/prci_jobs_spec.yaml;
|
||||
@echo "-----------"
|
||||
|
||||
# Build & lint documentation.
|
||||
#
|
||||
@@ -389,7 +396,7 @@ pylint: $(GENERATED_PYTHON_FILES) ipasetup.py python_scripts
|
||||
-name '*~' -o \
|
||||
-name '*.py' -print -o \
|
||||
-type f -exec grep -qsm1 '^#!.*\bpython' '{}' \; -print`; \
|
||||
FILES=`echo -e "$${FILES}\n$(AZURE_PYTHON_SCRIPT_SUBDIR)"`; \
|
||||
FILES=`echo -e "$${FILES}\n$(AZURE_PYTHON_SCRIPT_SUBDIR)\n$(PRCI_DEFINITIONS_DIR)"`; \
|
||||
echo -e "Pylint on $(PYTHON) is running over files:\n$${FILES}\nPlease wait ...\n"; \
|
||||
$(PYTHON) -m pylint --version; \
|
||||
PYTHONPATH=$(top_srcdir) $(PYTHON) -m pylint \
|
||||
|
||||
+2
-2
@@ -21,7 +21,7 @@
|
||||
########################################################
|
||||
define(IPA_VERSION_MAJOR, 4)
|
||||
define(IPA_VERSION_MINOR, 9)
|
||||
define(IPA_VERSION_RELEASE, 10)
|
||||
define(IPA_VERSION_RELEASE, 11)
|
||||
|
||||
########################################################
|
||||
# For 'pre' releases the version will be #
|
||||
@@ -49,7 +49,7 @@ define(IPA_VERSION_PRE_RELEASE,)
|
||||
# This option works only with GNU m4: #
|
||||
# it requires esyscmd m4 macro. #
|
||||
########################################################
|
||||
define(IPA_VERSION_IS_GIT_SNAPSHOT, no)
|
||||
define(IPA_VERSION_IS_GIT_SNAPSHOT, yes)
|
||||
|
||||
########################################################
|
||||
# git development branch: #
|
||||
|
||||
@@ -359,7 +359,8 @@ static int ipagraceperiod_preop(Slapi_PBlock *pb)
|
||||
Slapi_ValueSet *values = NULL;
|
||||
long grace_limit = 0;
|
||||
int grace_user_time;
|
||||
char *pwd_expiration = NULL;
|
||||
char *tmpstr = NULL;
|
||||
time_t pwd_expiration;
|
||||
int pwresponse_requested = 0;
|
||||
Slapi_PBlock *pbtm = NULL;
|
||||
Slapi_Mods *smods = NULL;
|
||||
@@ -414,12 +415,17 @@ static int ipagraceperiod_preop(Slapi_PBlock *pb)
|
||||
}
|
||||
slapi_value_free(&objectclass);
|
||||
|
||||
pwd_expiration = slapi_entry_attr_get_charptr(target_entry, "krbPasswordExpiration");
|
||||
if (pwd_expiration == NULL) {
|
||||
tmpstr = slapi_entry_attr_get_charptr(target_entry, "krbPasswordExpiration");
|
||||
if (tmpstr == NULL) {
|
||||
/* No expiration means nothing to do */
|
||||
LOG_TRACE("No krbPasswordExpiration for %s, nothing to do\n", dn);
|
||||
goto done;
|
||||
}
|
||||
pwd_expiration = ipapwd_gentime_to_time_t(tmpstr);
|
||||
if (pwd_expiration > time(NULL)) {
|
||||
/* Not expired, nothing to see here */
|
||||
goto done;
|
||||
}
|
||||
|
||||
ldrc = ipagraceperiod_getpolicy(target_entry, &policy_entry,
|
||||
&values, &actual_type_name,
|
||||
@@ -473,7 +479,7 @@ static int ipagraceperiod_preop(Slapi_PBlock *pb)
|
||||
if (pwresponse_requested) {
|
||||
slapi_pwpolicy_make_response_control(pb, -1, grace_limit - grace_user_time , -1);
|
||||
}
|
||||
} else if ((grace_limit > 0) && (grace_user_time >= grace_limit)) {
|
||||
} else if (grace_user_time >= grace_limit) {
|
||||
LOG_TRACE("%s password is expired and out of grace limit\n", dn);
|
||||
errstr = "Password is expired.\n";
|
||||
ret = LDAP_INVALID_CREDENTIALS;
|
||||
|
||||
+2
-1
@@ -35,4 +35,5 @@ distclean: clean
|
||||
venv:
|
||||
$(PYTHON) -m venv $(VENVDIR)
|
||||
$(VENVDIR)/bin/python3 -m pip install -U pip setuptools
|
||||
$(VENVDIR)/bin/python3 -m pip install -U -r ./requirements.txt
|
||||
$(VENVDIR)/bin/python3 -m pip install -U -r ./requirements.txt \
|
||||
-c constraints.txt
|
||||
@@ -0,0 +1 @@
|
||||
docutils < 0.19
|
||||
@@ -98,6 +98,7 @@ dist_app_DATA = \
|
||||
kdcproxy.wsgi \
|
||||
ipakrb5.aug \
|
||||
ipa.conf.template \
|
||||
ipa-wsgi.conf.template \
|
||||
ipa-kdc-proxy.conf.template \
|
||||
ipa-pki-proxy.conf.template \
|
||||
ipa-rewrite.conf.template \
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
#
|
||||
# VERSION 32 - DO NOT REMOVE THIS LINE
|
||||
#
|
||||
# This file may be overwritten on upgrades.
|
||||
#
|
||||
|
||||
# FIXME: WSGISocketPrefix is a server-scope directive. The mod_wsgi package
|
||||
# should really be fixed by adding this its /etc/httpd/conf.d/wsgi.conf:
|
||||
WSGISocketPrefix $WSGI_PREFIX_DIR
|
||||
|
||||
# Configure mod_wsgi handler for /ipa
|
||||
WSGIDaemonProcess ipa processes=$WSGI_PROCESSES threads=1 maximum-requests=500 \
|
||||
user=ipaapi group=ipaapi display-name=%{GROUP} socket-timeout=2147483647 \
|
||||
lang=C.UTF-8 locale=C.UTF-8
|
||||
WSGIImportScript /usr/share/ipa/wsgi.py process-group=ipa application-group=ipa
|
||||
|
||||
<VirtualHost *:80>
|
||||
ServerName $FQDN
|
||||
ServerAlias ipa-ca.$DOMAIN
|
||||
|
||||
# Logging
|
||||
CustomLog /var/log/httpd/ipa-access.log combined
|
||||
ErrorLog /var/log/httpd/ipa-error.log
|
||||
|
||||
IncludeOptional $HTTPD_IPA_PKI_PROXY_CONF
|
||||
Include $HTTPD_IPA_REWRITE_CONF
|
||||
Include $HTTPD_IPA_CONF
|
||||
</VirtualHost>
|
||||
|
||||
# the below section is only need if you have above 3 lines and want SSL
|
||||
<VirtualHost *:443>
|
||||
ServerName $FQDN
|
||||
ServerAlias ipa-ca.$DOMAIN
|
||||
|
||||
# Logging
|
||||
CustomLog /var/log/httpd/ipa-access.log combined
|
||||
ErrorLog /var/log/httpd/ipa-error.log
|
||||
|
||||
# SSL
|
||||
SSLEngine on
|
||||
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
|
||||
SSLCACertificateFile /etc/ipa/ca.crt
|
||||
SSLCertificateKeyFile /var/lib/ipa/private/httpd.key
|
||||
SSLCertificateFile /var/lib/ipa/certs/httpd.crt
|
||||
SSLCipherSuite PROFILE=SYSTEM
|
||||
SSLProxyCipherSuite PROFILE=SYSTEM
|
||||
|
||||
#
|
||||
IncludeOptional $HTTPD_IPA_KDCPROXY_CONF_SYMLINK
|
||||
IncludeOptional $HTTPD_IPA_PKI_PROXY_CONF
|
||||
Include $HTTPD_IPA_REWRITE_CONF
|
||||
Include $HTTPD_IPA_CONF
|
||||
</VirtualHost>
|
||||
@@ -0,0 +1,15 @@
|
||||
#
|
||||
# VERSION 32 - DO NOT REMOVE THIS LINE
|
||||
#
|
||||
# This file may be overwritten on upgrades.
|
||||
#
|
||||
|
||||
# FIXME: WSGISocketPrefix is a server-scope directive. The mod_wsgi package
|
||||
# should really be fixed by adding this its /etc/httpd/conf.d/wsgi.conf:
|
||||
WSGISocketPrefix $WSGI_PREFIX_DIR
|
||||
|
||||
# Configure mod_wsgi handler for /ipa
|
||||
WSGIDaemonProcess ipa processes=$WSGI_PROCESSES threads=1 maximum-requests=500 \
|
||||
user=ipaapi group=ipaapi display-name=%{GROUP} socket-timeout=2147483647 \
|
||||
lang=C.UTF-8 locale=C.UTF-8
|
||||
|
||||
@@ -35,14 +35,7 @@ AddOutputFilterByType DEFLATE text/html text/plain text/xml \
|
||||
application/javascript application/json text/css \
|
||||
application/x-font-ttf
|
||||
|
||||
# FIXME: WSGISocketPrefix is a server-scope directive. The mod_wsgi package
|
||||
# should really be fixed by adding this its /etc/httpd/conf.d/wsgi.conf:
|
||||
WSGISocketPrefix $WSGI_PREFIX_DIR
|
||||
|
||||
# Configure mod_wsgi handler for /ipa
|
||||
WSGIDaemonProcess ipa processes=$WSGI_PROCESSES threads=1 maximum-requests=500 \
|
||||
user=ipaapi group=ipaapi display-name=%{GROUP} socket-timeout=2147483647 \
|
||||
lang=C.UTF-8 locale=C.UTF-8
|
||||
WSGIScriptAlias /ipa /usr/share/ipa/wsgi.py process-group=ipa \
|
||||
application-group=%{GLOBAL}
|
||||
WSGIScriptReloading Off
|
||||
|
||||
@@ -98,6 +98,9 @@ Configure SSSD as data source for subid.
|
||||
\fB\-\-skip\-conncheck\fR
|
||||
Skip connection check to remote master
|
||||
.TP
|
||||
\fB\-\-skip\-mem\-check\fR
|
||||
Skip checking for minimum required memory
|
||||
.TP
|
||||
\fB\-d\fR, \fB\-\-debug
|
||||
Enable debug logging when more verbose output is needed
|
||||
.TP
|
||||
|
||||
@@ -83,6 +83,9 @@ Do not configure OpenSSH server.
|
||||
\fB\-\-subid\fR
|
||||
Configure SSSD as data source for subid.
|
||||
.TP
|
||||
\fB\-\-skip\-mem\-check\fR
|
||||
Skip checking for minimum required memory
|
||||
.TP
|
||||
\fB\-d\fR, \fB\-\-debug\fR
|
||||
Enable debug logging when more verbose output is needed.
|
||||
.TP
|
||||
|
||||
@@ -318,6 +318,7 @@ define([
|
||||
$type: 'text',
|
||||
name: 'pagination_size',
|
||||
label: '@i18n:customization.table_pagination',
|
||||
required: true,
|
||||
validators: ['positive_integer']
|
||||
}
|
||||
]
|
||||
|
||||
@@ -72,6 +72,9 @@ return {
|
||||
{
|
||||
name: 'cospriority',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'passwordgracelimit'
|
||||
}
|
||||
]
|
||||
}]
|
||||
|
||||
@@ -318,6 +318,11 @@ return {
|
||||
label: '@mo-param:pwpolicy:krbpwdlockoutduration:label',
|
||||
read_only: true,
|
||||
measurement_unit: 'seconds'
|
||||
},
|
||||
{
|
||||
name: 'passwordgracelimit',
|
||||
label: '@mo-param:pwpolicy:passwordgracelimit:label',
|
||||
read_only: true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
+12
-2
@@ -18,7 +18,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Custom message (debug, info, wraning) classes passed through RPC.
|
||||
Custom message (debug, info, warning) classes passed through RPC.
|
||||
|
||||
These are added to the "messages" entry in a RPC response, and printed to the
|
||||
user as log messages.
|
||||
@@ -27,7 +27,7 @@ Each message class has a unique numeric "errno" attribute from the 10000-10999
|
||||
range, so that it does not clash with PublicError numbers.
|
||||
|
||||
Messages also have the 'type' argument, set to one of 'debug', 'info',
|
||||
'warning', 'error'. This determines the severity of themessage.
|
||||
'warning', 'error'. This determines the severity of the message.
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
@@ -496,6 +496,16 @@ class LightweightCACertificateNotAvailable(PublicMessage):
|
||||
format = _("The certificate for %(ca)s is not available on this server.")
|
||||
|
||||
|
||||
class MissingTargetAttributesinPermission(PublicMessage):
|
||||
"""
|
||||
**13032** A permission was added with no target attributes
|
||||
"""
|
||||
errno = 13032
|
||||
type = "warning"
|
||||
format = _("The permission has %(right)s rights but no attributes "
|
||||
"are set.")
|
||||
|
||||
|
||||
def iter_messages(variables, base):
|
||||
"""Return a tuple with all subclasses
|
||||
"""
|
||||
|
||||
@@ -45,11 +45,14 @@ class BasePathNamespace:
|
||||
GSSAPI_SESSION_KEY = "/etc/httpd/alias/ipasession.key"
|
||||
OLD_KRA_AGENT_PEM = "/etc/httpd/alias/kra-agent.pem"
|
||||
HTTPD_CONF_D_DIR = "/etc/httpd/conf.d/"
|
||||
HTTPD_IPA_DIR = "/etc/httpd/ipa"
|
||||
HTTPD_IPA_KDCPROXY_CONF = "/etc/ipa/kdcproxy/ipa-kdc-proxy.conf"
|
||||
HTTPD_IPA_KDCPROXY_CONF_SYMLINK = "/etc/httpd/conf.d/ipa-kdc-proxy.conf"
|
||||
HTTPD_IPA_PKI_PROXY_CONF = "/etc/httpd/conf.d/ipa-pki-proxy.conf"
|
||||
HTTPD_IPA_REWRITE_CONF = "/etc/httpd/conf.d/ipa-rewrite.conf"
|
||||
HTTPD_IPA_CONF = "/etc/httpd/conf.d/ipa.conf"
|
||||
HTTPD_IPA_KDCPROXY_CONF_SYMLINK = "/etc/httpd/ipa/ipa-kdc-proxy.conf"
|
||||
HTTPD_IPA_PKI_PROXY_CONF = "/etc/httpd/ipa/ipa-pki-proxy.conf"
|
||||
HTTPD_IPA_REWRITE_CONF = "/etc/httpd/ipa/ipa-rewrite.conf"
|
||||
HTTPD_IPA_CONF = "/etc/httpd/ipa/ipa.conf"
|
||||
HTTPD_IPA_SITE_CONF = "/etc/httpd/conf.d/vhost-ipa.conf"
|
||||
HTTPD_IPA_WSGI_CONF = "/etc/httpd/ipa/ipa-wsgi.conf"
|
||||
HTTPD_NSS_CONF = "/etc/httpd/conf.d/nss.conf"
|
||||
HTTPD_SSL_CONF = "/etc/httpd/conf.d/ssl.conf"
|
||||
HTTPD_SSL_SITE_CONF = "/etc/httpd/conf.d/ssl.conf"
|
||||
|
||||
@@ -20,10 +20,13 @@ class DebianPathNamespace(BasePathNamespace):
|
||||
ETC_HTTPD_DIR = "/etc/apache2"
|
||||
HTTPD_ALIAS_DIR = "/etc/apache2/ipa"
|
||||
HTTPD_CONF_D_DIR = "/etc/apache2/conf-enabled/"
|
||||
HTTPD_IPA_KDCPROXY_CONF_SYMLINK = "/etc/apache2/conf-enabled/ipa-kdc-proxy.conf"
|
||||
HTTPD_IPA_PKI_PROXY_CONF = "/etc/apache2/conf-enabled/ipa-pki-proxy.conf"
|
||||
HTTPD_IPA_REWRITE_CONF = "/etc/apache2/conf-available/ipa-rewrite.conf"
|
||||
HTTPD_IPA_CONF = "/etc/apache2/conf-enabled/ipa.conf"
|
||||
HTTPD_IPA_DIR = "/etc/apache2/ipa/"
|
||||
HTTPD_IPA_KDCPROXY_CONF_SYMLINK = "/etc/apache2/ipa/ipa-kdc-proxy.conf"
|
||||
HTTPD_IPA_PKI_PROXY_CONF = "/etc/apache2/ipa/ipa-pki-proxy.conf"
|
||||
HTTPD_IPA_REWRITE_CONF = "/etc/apache2/ipa/ipa-rewrite.conf"
|
||||
HTTPD_IPA_CONF = "/etc/apache2/ipa/ipa.conf"
|
||||
HTTPD_IPA_WSGI_CONF = "/etc/apache2/ipa/ipa-wsgi.conf"
|
||||
HTTPD_IPA_SITE_CONF = "/etc/apache2/sites-available/ipa.conf"
|
||||
HTTPD_NSS_CONF = "/etc/apache2/mods-available/nss.conf"
|
||||
HTTPD_SSL_CONF = "/etc/apache2/mods-available/ssl.conf"
|
||||
HTTPD_SSL_SITE_CONF = "/etc/apache2/sites-available/default-ssl.conf"
|
||||
@@ -79,8 +82,9 @@ class DebianPathNamespace(BasePathNamespace):
|
||||
UPDATE_CA_TRUST = "/usr/sbin/update-ca-certificates"
|
||||
BIND_LDAP_DNS_IPA_WORKDIR = "/var/cache/bind/dyndb-ldap/ipa/"
|
||||
BIND_LDAP_DNS_ZONE_WORKDIR = "/var/cache/bind/dyndb-ldap/ipa/master/"
|
||||
BIND_LDAP_SO = "ldap.so"
|
||||
LIBARCH = "/{0}".format(MULTIARCH)
|
||||
LIBSOFTHSM2_SO = "/usr/lib/softhsm/libsofthsm2.so"
|
||||
LIBSOFTHSM2_SO = "/usr/lib/{0}/softhsm/libsofthsm2.so".format(MULTIARCH)
|
||||
PAM_KRB5_SO = "/usr/lib/{0}/security/pam_krb5.so".format(MULTIARCH)
|
||||
LIB_SYSTEMD_SYSTEMD_DIR = "/lib/systemd/system/"
|
||||
LIBEXEC_CERTMONGER_DIR = "/usr/lib/certmonger"
|
||||
|
||||
@@ -17,10 +17,13 @@ class SusePathNamespace(BasePathNamespace):
|
||||
HTTPD_ALIAS_DIR = "/etc/apache2/ipa"
|
||||
GSSAPI_SESSION_KEY = "/etc/apache2/ipa/ipasession.key"
|
||||
HTTPD_CONF_D_DIR = "/etc/apache2/conf.d/"
|
||||
HTTPD_IPA_KDCPROXY_CONF_SYMLINK = "/etc/apache2/conf.d/ipa-kdc-proxy.conf"
|
||||
HTTPD_IPA_PKI_PROXY_CONF = "/etc/apache2/conf.d/ipa-pki-proxy.conf"
|
||||
HTTPD_IPA_REWRITE_CONF = "/etc/apache2/conf.d/ipa-rewrite.conf"
|
||||
HTTPD_IPA_CONF = "/etc/apache2/conf.d/ipa.conf"
|
||||
HTTPD_IPA_DIR = "/etc/apache2/ipa/"
|
||||
HTTPD_IPA_KDCPROXY_CONF_SYMLINK = "/etc/apache2/ipa/ipa-kdc-proxy.conf"
|
||||
HTTPD_IPA_PKI_PROXY_CONF = "/etc/apache2/ipa/ipa-pki-proxy.conf"
|
||||
HTTPD_IPA_REWRITE_CONF = "/etc/apache2/ipa/ipa-rewrite.conf"
|
||||
HTTPD_IPA_CONF = "/etc/apache2/ipa/ipa.conf"
|
||||
HTTPD_IPA_WSGI_CONF = "/etc/apache2/ipa/ipa-wsgi.conf"
|
||||
HTTPD_IPA_SITE_CONF = "/etc/apache2/conf.d/ipa-vhost.conf"
|
||||
HTTPD_NSS_CONF = "/etc/apache2/conf.d/nss.conf"
|
||||
HTTPD_SSL_CONF = "/etc/apache2/conf.d/ssl.conf"
|
||||
HTTPD_SSL_SITE_CONF = "/etc/apache2/conf.d/ssl.conf"
|
||||
|
||||
@@ -45,7 +45,7 @@ class SuseTaskNamespace(RedHatTaskNamespace):
|
||||
return False # FIXME: Implement after libexec move
|
||||
|
||||
def modify_nsswitch_pam_stack(self, sssd, mkhomedir, statestore,
|
||||
sudo=True):
|
||||
sudo=True, subid=False):
|
||||
# pylint: disable=ipa-forbidden-import
|
||||
from ipalib import sysrestore # FixMe: break import cycle
|
||||
# pylint: enable=ipa-forbidden-import
|
||||
|
||||
@@ -144,6 +144,47 @@ class DNSResolver(dns.resolver.Resolver):
|
||||
nameservers.remove(ipv4_loopback)
|
||||
self.nameservers = nameservers
|
||||
|
||||
@dns.resolver.Resolver.nameservers.setter
|
||||
def nameservers(self, nameservers):
|
||||
"""
|
||||
*nameservers*, a ``list`` of nameservers with optional ports:
|
||||
"SERVER_IP port PORT_NUMBER".
|
||||
|
||||
Overloads dns.resolver.Resolver.nameservers setter to split off ports
|
||||
into nameserver_ports after setting nameservers successfully with the
|
||||
setter in dns.resolver.Resolver.
|
||||
"""
|
||||
# Get nameserver_ports if it is already set
|
||||
if hasattr(self, "nameserver_ports"):
|
||||
nameserver_ports = self.nameserver_ports
|
||||
else:
|
||||
nameserver_ports = {}
|
||||
|
||||
# Check nameserver items in list and split out converted port number
|
||||
# into nameserver_ports: { nameserver: port }
|
||||
if isinstance(nameservers, list):
|
||||
_nameservers = []
|
||||
for nameserver in nameservers:
|
||||
splits = nameserver.split()
|
||||
if len(splits) == 3 and splits[1] == "port":
|
||||
nameserver = splits[0]
|
||||
try:
|
||||
port = int(splits[2])
|
||||
if port < 0 or port > 65535:
|
||||
raise ValueError()
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
"invalid nameserver: %s is not a valid port" %
|
||||
splits[2])
|
||||
nameserver_ports[nameserver] = port
|
||||
_nameservers.append(nameserver)
|
||||
nameservers = _nameservers
|
||||
|
||||
# Call dns.resolver.Resolver.nameservers setter
|
||||
dns.resolver.Resolver.nameservers.__set__(self, nameservers)
|
||||
# Set nameserver_ports after successfull call to setter
|
||||
self.nameserver_ports = nameserver_ports
|
||||
|
||||
|
||||
class DNSZoneAlreadyExists(dns.exception.DNSException):
|
||||
supp_kwargs = {'zone', 'ns'}
|
||||
|
||||
@@ -87,13 +87,14 @@ class HTTPInstance(service.Service):
|
||||
|
||||
def create_instance(self, realm, fqdn, domain_name, dm_password=None,
|
||||
pkcs12_info=None,
|
||||
subject_base=None, auto_redirect=True, ca_file=None,
|
||||
ca_is_configured=None, promote=False,
|
||||
subject_base=None, auto_redirect=True, ui_vhost=False,
|
||||
ca_file=None, ca_is_configured=None, promote=False,
|
||||
master_fqdn=None):
|
||||
self.fqdn = fqdn
|
||||
self.realm = realm
|
||||
self.domain = domain_name
|
||||
self.dm_password = dm_password
|
||||
self.ui_vhost = ui_vhost
|
||||
self.suffix = ipautil.realm_to_suffix(self.realm)
|
||||
self.pkcs12_info = pkcs12_info
|
||||
self.cert = None
|
||||
@@ -112,6 +113,10 @@ class HTTPInstance(service.Service):
|
||||
IPA_CCACHES=paths.IPA_CCACHES,
|
||||
WSGI_PREFIX_DIR=paths.WSGI_PREFIX_DIR,
|
||||
WSGI_PROCESSES=constants.WSGI_PROCESSES,
|
||||
HTTPD_IPA_KDCPROXY_CONF_SYMLINK=paths.HTTPD_IPA_KDCPROXY_CONF_SYMLINK,
|
||||
HTTPD_IPA_PKI_PROXY_CONF=paths.HTTPD_IPA_PKI_PROXY_CONF,
|
||||
HTTPD_IPA_REWRITE_CONF=paths.HTTPD_IPA_REWRITE_CONF,
|
||||
HTTPD_IPA_CONF=paths.HTTPD_IPA_CONF
|
||||
)
|
||||
self.ca_file = ca_file
|
||||
if ca_is_configured is not None:
|
||||
@@ -198,6 +203,14 @@ class HTTPInstance(service.Service):
|
||||
# https://pagure.io/freeipa/issue/7662
|
||||
tasks.restore_context(session_dir)
|
||||
|
||||
# create /etc/httpd/ipa
|
||||
if not os.path.isdir(paths.HTTPD_IPA_DIR):
|
||||
os.makedirs(paths.HTTPD_IPA_DIR)
|
||||
# Must be world-readable / executable
|
||||
os.chmod(paths.HTTPD_IPA_DIR, 0o755)
|
||||
# Restore SELinux context of /etc/httpd/ipa
|
||||
tasks.restore_context(paths.HTTPD_IPA_DIR)
|
||||
|
||||
target_fname = paths.HTTPD_IPA_CONF
|
||||
http_txt = ipautil.template_file(
|
||||
os.path.join(paths.USR_SHARE_IPA_DIR,
|
||||
@@ -209,6 +222,29 @@ class HTTPInstance(service.Service):
|
||||
http_fd.close()
|
||||
os.chmod(target_fname, 0o644)
|
||||
|
||||
target_fname = paths.HTTPD_IPA_WSGI_CONF
|
||||
http_txt = ipautil.template_file(
|
||||
os.path.join(paths.USR_SHARE_IPA_DIR,
|
||||
"ipa-wsgi.conf.template"),
|
||||
self.sub_dict)
|
||||
self.fstore.backup_file(paths.HTTPD_IPA_CONF)
|
||||
http_fd = open(target_fname, "w")
|
||||
http_fd.write(http_txt)
|
||||
http_fd.close()
|
||||
os.chmod(target_fname, 0o644)
|
||||
|
||||
|
||||
target_fname = paths.HTTPD_IPA_SITE_CONF
|
||||
http_txt = ipautil.template_file(
|
||||
os.path.join(paths.USR_SHARE_IPA_DIR,
|
||||
"ipa-vhost.conf.template"),
|
||||
self.sub_dict)
|
||||
self.fstore.backup_file(paths.HTTPD_IPA_SITE_CONF)
|
||||
http_fd = open(target_fname, "w")
|
||||
http_fd.write(http_txt)
|
||||
http_fd.close()
|
||||
os.chmod(target_fname, 0o644)
|
||||
|
||||
target_fname = paths.HTTPD_IPA_REWRITE_CONF
|
||||
http_txt = ipautil.template_file(
|
||||
os.path.join(paths.USR_SHARE_IPA_DIR,
|
||||
@@ -539,8 +575,9 @@ class HTTPInstance(service.Service):
|
||||
ca_iface.Set('org.fedorahosted.certmonger.ca',
|
||||
'external-helper', helper)
|
||||
|
||||
for f in [paths.HTTPD_IPA_CONF, paths.HTTPD_SSL_CONF,
|
||||
paths.HTTPD_SSL_SITE_CONF, paths.HTTPD_NSS_CONF]:
|
||||
for f in [paths.HTTPD_IPA_CONF, paths.HTTPD_IPA_WSGI_CONF,
|
||||
paths.HTTPD_SSL_CONF, paths.HTTPD_SSL_SITE_CONF,
|
||||
paths.HTTPD_NSS_CONF]:
|
||||
try:
|
||||
self.fstore.restore_file(f)
|
||||
except ValueError as error:
|
||||
@@ -554,6 +591,7 @@ class HTTPInstance(service.Service):
|
||||
paths.HTTPD_PASSWD_FILE_FMT.format(host=api.env.host),
|
||||
paths.HTTPD_IPA_REWRITE_CONF,
|
||||
paths.HTTPD_IPA_CONF,
|
||||
paths.HTTPD_IPA_WSGI_CONF,
|
||||
paths.HTTPD_IPA_PKI_PROXY_CONF,
|
||||
paths.HTTPD_IPA_KDCPROXY_CONF_SYMLINK,
|
||||
paths.HTTPD_IPA_KDCPROXY_CONF,
|
||||
|
||||
@@ -1132,7 +1132,8 @@ def check_available_memory(ca=False):
|
||||
if available < minimum_suggested:
|
||||
raise ScriptError(
|
||||
"Less than the minimum 1.2GB of RAM is available, "
|
||||
"%.2fGB available" % (available / (1024 * 1024 * 1024))
|
||||
"%.2fGB available. Use --skip-mem-check to suppress this check."
|
||||
% (available / (1024 * 1024 * 1024))
|
||||
)
|
||||
|
||||
def load_external_cert(files, ca_subject):
|
||||
|
||||
@@ -154,6 +154,8 @@ class Backup(admintool.AdminTool):
|
||||
paths.HTTPD_CERT_FILE,
|
||||
paths.HTTPD_KEY_FILE,
|
||||
paths.HTTPD_IPA_CONF,
|
||||
paths.HTTPD_IPA_SITE_CONF,
|
||||
paths.HTTPD_WSGI_CONF,
|
||||
paths.SSHD_CONFIG,
|
||||
paths.SSHD_IPA_CONFIG,
|
||||
paths.SSH_CONFIG,
|
||||
|
||||
@@ -1152,7 +1152,7 @@ class ReplicationManager:
|
||||
except (ValueError, TypeError, KeyError):
|
||||
end = 0
|
||||
# incremental update is done if inprogress is false and end >= start
|
||||
done = inprogress and inprogress.lower() == 'false' and start <= end
|
||||
done = inprogress is not None and not inprogress and start <= end
|
||||
logger.info("Replication Update in progress: %s: status: %s: "
|
||||
"start: %d: end: %d",
|
||||
inprogress, status, start, end)
|
||||
|
||||
@@ -317,6 +317,12 @@ class ServerInstallInterface(ServerCertificateInstallInterface,
|
||||
)
|
||||
no_ui_redirect = enroll_only(no_ui_redirect)
|
||||
|
||||
ui_vhost = knob(
|
||||
None,
|
||||
description="Use virtual host for Web UI",
|
||||
)
|
||||
ui_vhost = enroll_only(ui_vhost)
|
||||
|
||||
dirsrv_config_file = knob(
|
||||
str, None,
|
||||
description="The path to LDIF file that will be used to modify "
|
||||
|
||||
@@ -943,12 +943,14 @@ def install(installer):
|
||||
realm_name, host_name, domain_name, dm_password,
|
||||
pkcs12_info=http_pkcs12_info, subject_base=options.subject_base,
|
||||
auto_redirect=not options.no_ui_redirect,
|
||||
ui_vhost=options.ui_vhost,
|
||||
ca_is_configured=setup_ca)
|
||||
else:
|
||||
http.create_instance(
|
||||
realm_name, host_name, domain_name, dm_password,
|
||||
subject_base=options.subject_base,
|
||||
auto_redirect=not options.no_ui_redirect,
|
||||
ui_vhost=options.ui_vhost,
|
||||
ca_is_configured=setup_ca)
|
||||
|
||||
ca.set_subject_base_in_config(options.subject_base)
|
||||
|
||||
@@ -150,7 +150,7 @@ def install_ca_cert(ldap, base_dn, realm, cafile, destfile=paths.IPA_CA_CRT):
|
||||
return destfile
|
||||
|
||||
|
||||
def install_http(config, auto_redirect, ca_is_configured, ca_file,
|
||||
def install_http(config, auto_redirect, ui_vhost, ca_is_configured, ca_file,
|
||||
pkcs12_info=None, fstore=None):
|
||||
# if we have a pkcs12 file, create the cert db from
|
||||
# that. Otherwise the ds setup will create the CA
|
||||
@@ -163,7 +163,7 @@ def install_http(config, auto_redirect, ca_is_configured, ca_file,
|
||||
http.create_instance(
|
||||
config.realm_name, config.host_name, config.domain_name,
|
||||
config.dirman_password, pkcs12_info,
|
||||
auto_redirect=auto_redirect, ca_file=ca_file,
|
||||
auto_redirect=auto_redirect, ui_vhost=ui_vhost, ca_file=ca_file,
|
||||
ca_is_configured=ca_is_configured, promote=True,
|
||||
subject_base=config.subject_base, master_fqdn=config.master_host_name)
|
||||
|
||||
@@ -1315,6 +1315,7 @@ def install(installer):
|
||||
install_http(
|
||||
config,
|
||||
auto_redirect=not options.no_ui_redirect,
|
||||
ui_vhost=options.ui_vhost,
|
||||
pkcs12_info=http_pkcs12_info,
|
||||
ca_is_configured=ca_enabled,
|
||||
ca_file=cafile,
|
||||
|
||||
@@ -152,6 +152,19 @@ def find_autoredirect(fqdn):
|
||||
return False
|
||||
return True
|
||||
|
||||
def find_uivhost(fqdn):
|
||||
"""
|
||||
When upgrading we need to see if the UI virtual host usage
|
||||
was disabled during install time (or afterward). So sift through the
|
||||
configuration file and see if we can determine the status.
|
||||
|
||||
Returns True if use_vhost is enabled, False otherwise
|
||||
"""
|
||||
filename = paths.HTTPD_IPA_SITE_CONF
|
||||
if os.path.exists(filename):
|
||||
return True
|
||||
return False
|
||||
|
||||
def find_version(filename):
|
||||
"""Find the version of a configuration file
|
||||
|
||||
@@ -1677,10 +1690,12 @@ def upgrade_configuration():
|
||||
fix_permissions()
|
||||
|
||||
auto_redirect = find_autoredirect(fqdn)
|
||||
use_vhost = find_usevhost(fqdn)
|
||||
sub_dict = dict(
|
||||
REALM=api.env.realm,
|
||||
FQDN=fqdn,
|
||||
AUTOREDIR='' if auto_redirect else '#',
|
||||
UI_VHOST='' if use_vhost else '#',
|
||||
CRL_PUBLISH_PATH=paths.PKI_CA_PUBLISH_DIR,
|
||||
DOGTAG_PORT=8009,
|
||||
CLONE='#',
|
||||
@@ -1694,6 +1709,10 @@ def upgrade_configuration():
|
||||
IPA_CUSTODIA_SOCKET=paths.IPA_CUSTODIA_SOCKET,
|
||||
KDCPROXY_CONFIG=paths.KDCPROXY_CONFIG,
|
||||
DOMAIN=api.env.domain,
|
||||
HTTPD_IPA_KDCPROXY_CONF_SYMLINK=paths.HTTPD_IPA_KDCPROXY_CONF_SYMLINK,
|
||||
HTTPD_IPA_PKI_PROXY_CONF=paths.HTTPD_IPA_PKI_PROXY_CONF,
|
||||
HTTPD_IPA_REWRITE_CONF=paths.HTTPD_IPA_REWRITE_CONF,
|
||||
HTTPD_IPA_CONF=paths.HTTPD_IPA_CONF
|
||||
)
|
||||
|
||||
subject_base = find_subject_base()
|
||||
@@ -1730,6 +1749,12 @@ def upgrade_configuration():
|
||||
upgrade_file(sub_dict, paths.HTTPD_IPA_CONF,
|
||||
os.path.join(paths.USR_SHARE_IPA_DIR,
|
||||
"ipa.conf.template"))
|
||||
upgrade_file(sub_dict, paths.HTTPD_IPA_SITE_CONF,
|
||||
os.path.join(paths.USR_SHARE_IPA_DIR,
|
||||
"ipa-vhost.conf.template"))
|
||||
upgrade_file(sub_dict, paths.HTTPD_IPA_WSGI_CONF,
|
||||
os.path.join(paths.USR_SHARE_IPA_DIR,
|
||||
"ipa-wsgi.conf.template"))
|
||||
upgrade_file(sub_dict, paths.HTTPD_IPA_REWRITE_CONF,
|
||||
os.path.join(paths.USR_SHARE_IPA_DIR,
|
||||
"ipa-rewrite.conf.template"))
|
||||
@@ -1885,9 +1910,12 @@ def upgrade_configuration():
|
||||
custodia = custodiainstance.CustodiaInstance(api.env.host, api.env.realm)
|
||||
custodia.upgrade_instance()
|
||||
|
||||
# Don't include schema upgrades in restart consideration, see
|
||||
# https://pagure.io/freeipa/issue/9204
|
||||
ca_upgrade_schema(ca)
|
||||
|
||||
ca_restart = any([
|
||||
ca_restart,
|
||||
ca_upgrade_schema(ca),
|
||||
certificate_renewal_update(ca, kra, ds, http),
|
||||
ca_enable_pkix(ca),
|
||||
ca_configure_profiles_acl(ca),
|
||||
|
||||
@@ -28,6 +28,7 @@ from .privilege import validate_permission_to_privilege
|
||||
from ipalib import errors
|
||||
from ipalib.parameters import Str, StrEnum, DNParam, Flag
|
||||
from ipalib import api, _, ngettext
|
||||
from ipalib import messages
|
||||
from ipalib.plugable import Registry
|
||||
from ipalib.capabilities import client_has_capability
|
||||
from ipalib.aci import ACI
|
||||
@@ -553,6 +554,7 @@ class permission(baseldap.LDAPObject):
|
||||
def make_aci(self, entry):
|
||||
"""Make an ACI string from the given permission entry"""
|
||||
|
||||
msg = None
|
||||
aci_parts = []
|
||||
name = entry.single_value['cn']
|
||||
|
||||
@@ -560,6 +562,18 @@ class permission(baseldap.LDAPObject):
|
||||
attrs = self.get_effective_attrs(entry)
|
||||
if attrs:
|
||||
aci_parts.append("(targetattr = \"%s\")" % ' || '.join(attrs))
|
||||
else:
|
||||
# If we are re-parsing an entry for validation then
|
||||
# the effective attrs may be empty.
|
||||
if entry.get('attrs') is None:
|
||||
rights = set(['read', 'write', 'search', 'compare'])
|
||||
permrights = set(entry['ipapermright'])
|
||||
for right in ('add', 'delete'):
|
||||
permrights.discard(right)
|
||||
if permrights and permrights.issubset(rights):
|
||||
msg = messages.MissingTargetAttributesinPermission(
|
||||
right=', '.join(sorted(entry['ipapermright']))
|
||||
)
|
||||
|
||||
# target
|
||||
ipapermtarget = entry.single_value.get('ipapermtarget')
|
||||
@@ -608,12 +622,12 @@ class permission(baseldap.LDAPObject):
|
||||
aci_parts.append('(version 3.0;acl "permission:%s";allow (%s) %s;)' % (
|
||||
name, ','.join(sorted(entry['ipapermright'])), bindrule))
|
||||
|
||||
return ''.join(aci_parts)
|
||||
return ''.join(aci_parts), msg
|
||||
|
||||
def add_aci(self, permission_entry):
|
||||
"""Add the ACI coresponding to the given permission entry"""
|
||||
ldap = self.api.Backend.ldap2
|
||||
acistring = self.make_aci(permission_entry)
|
||||
acistring, _msg = self.make_aci(permission_entry)
|
||||
location = permission_entry.single_value.get('ipapermlocation',
|
||||
self.api.env.basedn)
|
||||
|
||||
@@ -641,7 +655,7 @@ class permission(baseldap.LDAPObject):
|
||||
- entry
|
||||
- removed ACI string, or None if none existed previously
|
||||
"""
|
||||
new_acistring = self.make_aci(permission_entry)
|
||||
new_acistring, _msg = self.make_aci(permission_entry)
|
||||
return self._replace_aci(permission_entry, old_name, new_acistring)
|
||||
|
||||
def _replace_aci(self, permission_entry, old_name=None, new_acistring=None):
|
||||
@@ -672,6 +686,25 @@ class permission(baseldap.LDAPObject):
|
||||
logger.debug('No changes to ACI')
|
||||
return acientry, acistring
|
||||
|
||||
def check_attrs(self, result, *keys, **options):
|
||||
"""Re-build the ACI to determine if there are rights that
|
||||
only work when there are attributes defined."""
|
||||
ldap = self.backend
|
||||
|
||||
dn = self.get_dn(*keys, **options)
|
||||
entry = ldap.make_entry(dn, result['result'])
|
||||
for old_name, new_name in _DEPRECATED_OPTION_ALIASES.items():
|
||||
if old_name in entry:
|
||||
entry[new_name] = entry[old_name]
|
||||
del entry[old_name]
|
||||
_acistring, msg = self.make_aci(entry)
|
||||
if msg:
|
||||
messages.add_message(
|
||||
options['version'],
|
||||
result,
|
||||
msg,
|
||||
)
|
||||
|
||||
def _get_aci_entry_and_string(self, permission_entry, name=None,
|
||||
notfound_ok=False, cached_acientry=None):
|
||||
"""Get the entry and ACI corresponding to the permission entry
|
||||
@@ -776,7 +809,7 @@ class permission(baseldap.LDAPObject):
|
||||
target_entry['ipapermlocation'] = [self.api.env.basedn]
|
||||
|
||||
# Make sure we're not losing *any info* by the upgrade
|
||||
new_acistring = self.make_aci(target_entry)
|
||||
new_acistring, _msg = self.make_aci(target_entry)
|
||||
if not ACI(new_acistring).isequal(aci):
|
||||
raise ValueError('Cannot convert ACI, %r != %r' % (new_acistring,
|
||||
acistring))
|
||||
@@ -992,7 +1025,9 @@ class permission_add(baseldap.LDAPCreate):
|
||||
# the whole command, not just the callbacks
|
||||
def execute(self, *keys, **options):
|
||||
self.obj.preprocess_options(options, merge_targetfilter=True)
|
||||
return super(permission_add, self).execute(*keys, **options)
|
||||
result = super(permission_add, self).execute(*keys, **options)
|
||||
self.obj.check_attrs(result, *keys, **options)
|
||||
return result
|
||||
|
||||
def get_args(self):
|
||||
for arg in super(permission_add, self).get_args():
|
||||
@@ -1094,7 +1129,9 @@ class permission_mod(baseldap.LDAPUpdate):
|
||||
def execute(self, *keys, **options):
|
||||
context.filter_ops = self.obj.preprocess_options(
|
||||
options, return_filter_ops=True)
|
||||
return super(permission_mod, self).execute(*keys, **options)
|
||||
result = super(permission_mod, self).execute(*keys, **options)
|
||||
self.obj.check_attrs(result, *keys, **options)
|
||||
return result
|
||||
|
||||
def get_options(self):
|
||||
for opt in super(permission_mod, self).get_options():
|
||||
|
||||
@@ -56,6 +56,7 @@ from .idviews import remove_ipaobject_overrides
|
||||
from ipalib.plugable import Registry
|
||||
from .baseldap import (
|
||||
LDAPObject,
|
||||
pkey_to_unicode,
|
||||
pkey_to_value,
|
||||
LDAPCreate,
|
||||
LDAPSearch,
|
||||
@@ -701,6 +702,7 @@ class user_del(baseuser_del):
|
||||
__doc__ = _('Delete a user.')
|
||||
|
||||
msg_summary = _('Deleted user "%(value)s"')
|
||||
msg_summary_preserved = _('Preserved user "%(value)s"')
|
||||
|
||||
takes_options = baseuser_del.takes_options + (
|
||||
Bool('preserve?',
|
||||
@@ -831,6 +833,8 @@ class user_del(baseuser_del):
|
||||
failed.append(pkey_to_value(pkey, options))
|
||||
|
||||
val = dict(result=dict(failed=failed), value=preserved)
|
||||
val['summary'] = self.msg_summary_preserved % dict(
|
||||
value=pkey_to_unicode(preserved))
|
||||
return val
|
||||
else:
|
||||
return super(user_del, self).execute(*keys, **options)
|
||||
|
||||
@@ -45,15 +45,16 @@ vms:
|
||||
- test_integration/test_external_ca.py::TestExternalCAInstall
|
||||
|
||||
- vm_jobs:
|
||||
- container_job: InstallDNSSECFirst
|
||||
containers:
|
||||
replicas: 1
|
||||
resources:
|
||||
replica:
|
||||
mem_limit: "2400m"
|
||||
memswap_limit: "3500m"
|
||||
tests:
|
||||
- test_integration/test_dnssec.py::TestInstallDNSSECFirst
|
||||
# Temporary disable - see https://pagure.io/freeipa/issue/9216
|
||||
# - container_job: InstallDNSSECFirst
|
||||
# containers:
|
||||
# replicas: 1
|
||||
# resources:
|
||||
# replica:
|
||||
# mem_limit: "2400m"
|
||||
# memswap_limit: "3500m"
|
||||
# tests:
|
||||
# - test_integration/test_dnssec.py::TestInstallDNSSECFirst
|
||||
|
||||
- container_job: simple_replication
|
||||
containers:
|
||||
@@ -69,14 +70,6 @@ vms:
|
||||
tests:
|
||||
- test_integration/test_service_permissions.py
|
||||
|
||||
- container_job: netgroup
|
||||
tests:
|
||||
- test_integration/test_netgroup.py
|
||||
|
||||
- container_job: external_ca_ExternalCAProfileScenarios
|
||||
tests:
|
||||
- test_integration/test_external_ca.py::TestExternalCAProfileScenarios
|
||||
|
||||
- vm_jobs:
|
||||
- container_job: sudo
|
||||
containers:
|
||||
@@ -137,6 +130,15 @@ vms:
|
||||
tests:
|
||||
- test_integration/test_membermanager.py
|
||||
|
||||
- container_job: netgroup
|
||||
tests:
|
||||
- test_integration/test_netgroup.py
|
||||
|
||||
- container_job: external_ca_ExternalCAProfileScenarios
|
||||
tests:
|
||||
- test_integration/test_external_ca.py::TestExternalCAProfileScenarios
|
||||
|
||||
|
||||
- vm_jobs:
|
||||
- container_job: ServerReplicaCALessToCAFull
|
||||
tests:
|
||||
|
||||
@@ -12,7 +12,7 @@ from jinja2 import Template
|
||||
from typing import NamedTuple, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import List, Tuple, Union
|
||||
from typing import List, Tuple, Union, Dict
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
|
||||
|
||||
@@ -77,14 +77,19 @@ class Container:
|
||||
return self._ipv6
|
||||
|
||||
def execute(
|
||||
self, args: Union[str, List[str]], raiseonerr: bool = True
|
||||
self,
|
||||
args: Union[str, List[str]],
|
||||
raiseonerr: bool = True,
|
||||
env: Union[Dict[str, str], List[str], None] = None,
|
||||
) -> ExecRunReturn:
|
||||
"""
|
||||
Exec an arbitrary command within container
|
||||
"""
|
||||
dcont = self.dclient.containers.get(self.name)
|
||||
logging.info("%s: run: %s", dcont.name, args)
|
||||
result: ExecRunReturn = dcont.exec_run(args, demux=True)
|
||||
result: ExecRunReturn = dcont.exec_run(
|
||||
args, demux=True, environment=env
|
||||
)
|
||||
if result.output[0] is not None:
|
||||
logging.info("%s: %s", dcont.name, result.output[0])
|
||||
logging.info("%s: result: %s", dcont.name, result.exit_code)
|
||||
@@ -127,13 +132,13 @@ class ContainersGroup:
|
||||
for c in range(1, self.scale + 1)
|
||||
]
|
||||
|
||||
def execute_all(self, args):
|
||||
def execute_all(self, args, env=None):
|
||||
"""
|
||||
Sequentially exec an arbitrary command within every container of group
|
||||
"""
|
||||
results = []
|
||||
for cont in self.containers:
|
||||
results.append(cont.execute(args))
|
||||
results.append(cont.execute(args, env=env))
|
||||
return results
|
||||
|
||||
def ips(self):
|
||||
@@ -199,7 +204,8 @@ class ContainersGroup:
|
||||
cont.execute(cmd)
|
||||
|
||||
cmd = ["hostnamectl", "set-hostname", cont.hostname]
|
||||
cont.execute(cmd)
|
||||
# default timeout (25s) maybe not enough
|
||||
cont.execute(cmd, env={"SYSTEMD_BUS_TIMEOUT": "50"})
|
||||
|
||||
def setup_resolvconf(self):
|
||||
"""
|
||||
@@ -331,7 +337,7 @@ class Controller(Container):
|
||||
|
||||
return self._master_container
|
||||
|
||||
def execute(self, args):
|
||||
def execute(self, args, env=None):
|
||||
"""
|
||||
Execute a command on controller (either master or local machine)
|
||||
"""
|
||||
@@ -339,7 +345,7 @@ class Controller(Container):
|
||||
proc = subprocess.run(args, check=True, capture_output=True)
|
||||
return [proc.stdout.decode().rstrip().strip("'")]
|
||||
|
||||
return self.master_container.execute(args)
|
||||
return self.master_container.execute(args, env=env)
|
||||
|
||||
def setup_hosts(self):
|
||||
"""
|
||||
|
||||
@@ -4,6 +4,11 @@ steps:
|
||||
env | sort
|
||||
displayName: Print Host Enviroment
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
sudo apt list --installed
|
||||
displayName: Show Host's installed packages
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
sudo apt-get update
|
||||
@@ -16,6 +21,21 @@ steps:
|
||||
python3-docker
|
||||
displayName: Install Host's tests requirements
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
sudo systemctl
|
||||
displayName: Show Host's systemd status
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
# most of the time systemd killed hostnamed with SIGKILL on timeout
|
||||
# kill without waiting for graceful termination
|
||||
sudo systemctl kill -s SIGKILL azsecd ||:
|
||||
sudo systemctl disable --now azsecmond ||:
|
||||
sudo systemctl disable --now azsecd ||:
|
||||
sudo systemctl disable --now clamav-freshclam ||:
|
||||
displayName: Disable azsec services (clamav)
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
printf "AppArmor status\n"
|
||||
@@ -52,6 +72,16 @@ steps:
|
||||
|
||||
- template: setup-test-environment.yml
|
||||
|
||||
- script: |
|
||||
set -eu
|
||||
sudo top -b -o +%MEM n 1
|
||||
displayName: Show Host's top
|
||||
|
||||
- script: |
|
||||
set -eu
|
||||
sudo ps -auxf
|
||||
displayName: Show Host's processes
|
||||
|
||||
- template: run-test.yml
|
||||
|
||||
- script: |
|
||||
|
||||
@@ -309,4 +309,4 @@ jobs:
|
||||
test_suite: test_integration/test_subids.py
|
||||
template: *ci-ipa-4-9-latest
|
||||
timeout: 3600
|
||||
topology: *master_1repl
|
||||
topology: *master_1repl_1client
|
||||
@@ -1748,7 +1748,7 @@ jobs:
|
||||
test_suite: test_integration/test_subids.py
|
||||
template: *ci-ipa-4-9-latest
|
||||
timeout: 3600
|
||||
topology: *master_1repl
|
||||
topology: *master_1repl_1client
|
||||
|
||||
fedora-latest-ipa-4-9/test_custom_plugins:
|
||||
requires: [fedora-latest-ipa-4-9/build]
|
||||
@@ -1771,5 +1771,5 @@ jobs:
|
||||
build_url: '{fedora-latest-ipa-4-9/build_url}'
|
||||
test_suite: test_integration/test_idp.py
|
||||
template: *ci-ipa-4-9-latest
|
||||
timeout: 3600
|
||||
topology: *master_1repl
|
||||
timeout: 5000
|
||||
topology: *master_2repl_1client
|
||||
@@ -1887,7 +1887,7 @@ jobs:
|
||||
test_suite: test_integration/test_subids.py
|
||||
template: *ci-ipa-4-9-latest
|
||||
timeout: 3600
|
||||
topology: *master_1repl
|
||||
topology: *master_1repl_1client
|
||||
|
||||
fedora-latest-ipa-4-9/test_custom_plugins:
|
||||
requires: [fedora-latest-ipa-4-9/build]
|
||||
@@ -1912,5 +1912,5 @@ jobs:
|
||||
selinux_enforcing: True
|
||||
test_suite: test_integration/test_idp.py
|
||||
template: *ci-ipa-4-9-latest
|
||||
timeout: 3600
|
||||
topology: *master_1repl
|
||||
timeout: 5000
|
||||
topology: *master_2repl_1client
|
||||
@@ -1748,7 +1748,7 @@ jobs:
|
||||
test_suite: test_integration/test_subids.py
|
||||
template: *ci-ipa-4-9-previous
|
||||
timeout: 3600
|
||||
topology: *master_1repl
|
||||
topology: *master_1repl_1client
|
||||
|
||||
fedora-previous-ipa-4-9/test_custom_plugins:
|
||||
requires: [fedora-previous-ipa-4-9/build]
|
||||
@@ -1771,5 +1771,5 @@ jobs:
|
||||
build_url: '{fedora-previous-ipa-4-9/build_url}'
|
||||
test_suite: test_integration/test_idp.py
|
||||
template: *ci-ipa-4-9-previous
|
||||
timeout: 3600
|
||||
topology: *master_1repl
|
||||
timeout: 5000
|
||||
topology: *master_2repl_1client
|
||||
@@ -0,0 +1,433 @@
|
||||
#! /usr/bin/python3
|
||||
import os
|
||||
import glob
|
||||
import sys
|
||||
import argparse
|
||||
from argparse import RawTextHelpFormatter
|
||||
import yaml
|
||||
|
||||
# Get default DIR from script location
|
||||
DEFAULT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
# Default jobs specification file name and path
|
||||
JOBS_SPEC_YAML = "prci_jobs_spec.yaml"
|
||||
JOBS_SPEC_PATH = os.path.join(DEFAULT_DIR, JOBS_SPEC_YAML)
|
||||
# Files to ignore on check
|
||||
IGNORE_FILES = {JOBS_SPEC_YAML, "temp_commit.yaml"}
|
||||
|
||||
|
||||
def load_yaml(path):
|
||||
"""Load YAML file into Python object."""
|
||||
with open(path, "r") as file_data:
|
||||
data = yaml.safe_load(file_data)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def print_error(msg):
|
||||
"""Helper function to print error messages"""
|
||||
print("ERROR: " + msg)
|
||||
|
||||
|
||||
def print_warning(msg):
|
||||
"""Helper function to print warning messages"""
|
||||
print("WARNING: " + msg)
|
||||
|
||||
|
||||
def print_hint(msg):
|
||||
"""Helper function to print hint messages"""
|
||||
print("HINT: " + msg)
|
||||
|
||||
|
||||
def print_field_error(
|
||||
jobname, fieldname=None, expected_value=None, custom_msg=None
|
||||
):
|
||||
"""Helper function to print field errors"""
|
||||
msg = f"In job '{jobname}':\n"
|
||||
if custom_msg:
|
||||
msg += f" {custom_msg}"
|
||||
elif fieldname and expected_value:
|
||||
msg += (
|
||||
f' Job field "{fieldname}" should be defined as: '
|
||||
f'"{fieldname}: {expected_value}"'
|
||||
)
|
||||
else:
|
||||
msg = f"In job '{jobname}'."
|
||||
print_error(msg)
|
||||
|
||||
|
||||
def check_jobs(filename, jobs_def, topologies, current_spec, supported_classes):
|
||||
"""
|
||||
Check if given job definition file has all jobs correctly defined according
|
||||
to specification file.
|
||||
|
||||
:param filename: file name of the definition file to be checked
|
||||
:param jobs_def: definition file jobs as a dict object
|
||||
:param topologies: list of dicts of predefined topologies
|
||||
:param jobs_spec: PRCI specification file containing correct definitions
|
||||
:param supported_classes: List of supported test-run classes
|
||||
|
||||
:returns: Boolean with the checks result
|
||||
"""
|
||||
correct_fields = True
|
||||
|
||||
try:
|
||||
job_prefix = current_spec["job_prefix"]
|
||||
except KeyError as e:
|
||||
print_error(
|
||||
"Specification file has bad format "
|
||||
f"and '{filename}' could not be analyzed.\n"
|
||||
f" KeyError: {e} in '{filename}'"
|
||||
)
|
||||
return False
|
||||
|
||||
requires = [f"{job_prefix}build"]
|
||||
build_url = f"{{{job_prefix}build_url}}"
|
||||
|
||||
# Get template from build job
|
||||
build_job_name = job_prefix + "build"
|
||||
build_job = jobs_def.get(build_job_name)
|
||||
if not build_job:
|
||||
print_error(
|
||||
" Build job is not defined or has incorrect name.\n"
|
||||
f" Name should be: '{build_job_name}'"
|
||||
)
|
||||
return False
|
||||
build_args = build_job["job"]["args"]
|
||||
template = build_args["template"]
|
||||
|
||||
copr = build_args.get("copr")
|
||||
copr_defined = current_spec.get("copr_defined", False)
|
||||
|
||||
update_packages = current_spec.get("update_packages", False)
|
||||
selinux = current_spec.get("selinux_enforcing", False)
|
||||
enable_testing_repo = current_spec.get("enable_testing_repo", False)
|
||||
|
||||
for job_name, params in jobs_def.items():
|
||||
# Checks for all kind of jobs
|
||||
args = params.get("job").get("args")
|
||||
if not job_name.startswith(job_prefix):
|
||||
msg = f"Job name should start with prefix '{job_prefix}'"
|
||||
print_field_error(job_name, custom_msg=msg)
|
||||
correct_fields = False
|
||||
if args.get("template") != template:
|
||||
print_field_error(job_name, "template", template)
|
||||
correct_fields = False
|
||||
if "timeout" not in args:
|
||||
msg = "'timeout' field should be defined in args section"
|
||||
print_field_error(job_name, custom_msg=msg)
|
||||
if args.get("topology") not in topologies:
|
||||
msg = (
|
||||
"'topology' field should be defined with one of the "
|
||||
"pre-defined topologies"
|
||||
)
|
||||
print_field_error(job_name, custom_msg=msg)
|
||||
correct_fields = False
|
||||
if args.get("enable_testing_repo", False) != enable_testing_repo:
|
||||
if enable_testing_repo:
|
||||
print_field_error(
|
||||
job_name, "enable_testing_repo", enable_testing_repo
|
||||
)
|
||||
else:
|
||||
msg = (
|
||||
"'enable_testing_repo' field should be set to false or not"
|
||||
" defined"
|
||||
)
|
||||
print_field_error(job_name, custom_msg=msg)
|
||||
correct_fields = False
|
||||
|
||||
# Checks for build job
|
||||
if job_name == build_job_name:
|
||||
if copr_defined and not copr:
|
||||
msg = "'copr' field should be defined for the build job"
|
||||
print_field_error(job_name, custom_msg=msg)
|
||||
correct_fields = False
|
||||
elif not copr_defined and copr:
|
||||
msg = "'copr' field should NOT be defined for the build job"
|
||||
print_field_error(job_name, custom_msg=msg)
|
||||
correct_fields = False
|
||||
if params.get("job").get("class") != "Build":
|
||||
print_field_error(job_name, "class", "Build")
|
||||
correct_fields = False
|
||||
continue
|
||||
|
||||
# Checks only for non-build jobs
|
||||
if params.get("requires") != requires:
|
||||
print_field_error(job_name, "requires", requires)
|
||||
correct_fields = False
|
||||
if params.get("job").get("class") not in supported_classes:
|
||||
msg = (
|
||||
"'class' field should be defined with one of the "
|
||||
f"supported: {supported_classes}"
|
||||
)
|
||||
print_field_error(job_name, custom_msg=msg)
|
||||
correct_fields = False
|
||||
if args.get("build_url") != build_url:
|
||||
print_field_error(job_name, "build_url", f"'{build_url}'")
|
||||
correct_fields = False
|
||||
if "test_suite" not in args:
|
||||
msg = "'test_suite' field should be defined in args section"
|
||||
print_field_error(job_name, custom_msg=msg)
|
||||
correct_fields = False
|
||||
# Check template field against build target
|
||||
if args.get("template") != template:
|
||||
print_field_error(job_name, "template", template)
|
||||
correct_fields = False
|
||||
if "timeout" not in args:
|
||||
msg = "'timeout' field should be defined in args section"
|
||||
print_field_error(job_name, custom_msg=msg)
|
||||
correct_fields = False
|
||||
# If build target has a copr repo, check that the job also defines it
|
||||
if args.get("copr") != copr:
|
||||
if copr and copr_defined:
|
||||
print_field_error(job_name, "copr", copr)
|
||||
elif not copr and not copr_defined:
|
||||
msg = "'copr' field should not be defined"
|
||||
print_field_error(job_name, custom_msg=msg)
|
||||
correct_fields = False
|
||||
if args.get("update_packages", False) != update_packages:
|
||||
if update_packages:
|
||||
print_field_error(job_name, "update_packages", update_packages)
|
||||
else:
|
||||
msg = (
|
||||
"'update_packages' field should be set to false or not"
|
||||
" defined"
|
||||
)
|
||||
print_field_error(job_name, custom_msg=msg)
|
||||
correct_fields = False
|
||||
if args.get("selinux_enforcing", False) != selinux:
|
||||
if selinux:
|
||||
print_field_error(job_name, "selinux_enforcing", selinux)
|
||||
else:
|
||||
msg = (
|
||||
"'selinux_enforcing' field should be set to false or not"
|
||||
" defined"
|
||||
)
|
||||
print_field_error(job_name, custom_msg=msg)
|
||||
correct_fields = False
|
||||
|
||||
return correct_fields
|
||||
|
||||
|
||||
def process_def_file(file, jobs_spec, supported_classes):
|
||||
"""Function to process PRCI definition file
|
||||
|
||||
:param file: name of the definition file to be
|
||||
processed (extension included)
|
||||
:param jobs_spec: PRCI specification file containing correct definitions
|
||||
:param supported_classes: List of supported test-run classes
|
||||
|
||||
:returns: Boolean with the checks result, filename,
|
||||
and number of jobs in the definition
|
||||
file (-1 when error / warning)
|
||||
"""
|
||||
# File base name without extension
|
||||
filename = os.path.splitext(os.path.basename(file))[0]
|
||||
try:
|
||||
def_suite = load_yaml(file)
|
||||
except FileNotFoundError as e:
|
||||
print(e)
|
||||
print_error(f"File '{file}' was not found.")
|
||||
sys.exit(1)
|
||||
except yaml.composer.ComposerError as e:
|
||||
print_error(str(e))
|
||||
print_hint(
|
||||
"You probably defined a wrong alias "
|
||||
"in the newly added or modified job."
|
||||
)
|
||||
sys.exit(1)
|
||||
except yaml.YAMLError as e:
|
||||
print(e)
|
||||
print_error(f"Error loading YAML definition file {file}")
|
||||
sys.exit(1)
|
||||
|
||||
# Get spec for file to be analyzed
|
||||
current_spec = jobs_spec.get(filename)
|
||||
if current_spec is None:
|
||||
print_warning(
|
||||
f"'{filename}' file is not defined in the PRCI "
|
||||
"specification file and "
|
||||
"could not be analyzed."
|
||||
)
|
||||
return True, "", -1
|
||||
|
||||
jobs_def = def_suite.get("jobs")
|
||||
if jobs_def is None:
|
||||
print_error(
|
||||
f"'{filename}' file doesn't have a jobs section following "
|
||||
"the format."
|
||||
)
|
||||
return False, "", -1
|
||||
|
||||
# Get list of pre-defined topologies
|
||||
topologies_def = def_suite.get("topologies")
|
||||
if topologies_def is None:
|
||||
print_error(
|
||||
f"'{filename}' file doesn't have a topologies section following "
|
||||
"the format."
|
||||
)
|
||||
return False, "", -1
|
||||
topologies = list(topologies_def.values())
|
||||
|
||||
# Print file to be analyzed and its number of jobs
|
||||
n_jobs = len(jobs_def)
|
||||
print("[File] " + filename + " [Jobs] " + str(n_jobs))
|
||||
|
||||
result = check_jobs(
|
||||
filename, jobs_def, topologies, current_spec, supported_classes
|
||||
)
|
||||
return result, filename, n_jobs
|
||||
|
||||
|
||||
def process_spec_file(filepath):
|
||||
"""Function to process jobs specification file
|
||||
|
||||
:param filepath: Filepath for spec file
|
||||
|
||||
:returns: Definition specification dict, supported classes and
|
||||
list of files that should contain the same number of jobs
|
||||
"""
|
||||
try:
|
||||
spec_root = load_yaml(filepath)
|
||||
except FileNotFoundError as e:
|
||||
print(e)
|
||||
print_error(f"Jobs specification file '{filepath}' not found.")
|
||||
sys.exit(1)
|
||||
except yaml.YAMLError as e:
|
||||
print(e)
|
||||
print_error(f"Error loading YAML specification file '{filepath}'")
|
||||
sys.exit(1)
|
||||
|
||||
jobs_spec = spec_root.get("prci_job_spec")
|
||||
if not jobs_spec:
|
||||
print_error(
|
||||
f"Specification definition not found in spec file '{filepath}'\n"
|
||||
" Key 'prci_job_spec' is not present."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
supported_classes = spec_root.get("classes")
|
||||
if not supported_classes:
|
||||
print_error(
|
||||
f"Supported classes not defined in spec file '{filepath}'\n"
|
||||
" Key 'classes' is not present."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
f_fixed_jobs = spec_root.get("fixed_n_jobs")
|
||||
|
||||
return jobs_spec, supported_classes, f_fixed_jobs
|
||||
|
||||
|
||||
def check_n_jobs(defs_n_jobs):
|
||||
"""
|
||||
Function to check if definition files have the same number of jobs
|
||||
|
||||
:param defs_n_jobs: Dict of definition filenames as keys and number
|
||||
of jobs as values
|
||||
|
||||
:returns: Boolean, if definitions have the same number of jobs
|
||||
"""
|
||||
if not defs_n_jobs: # Spec not defined to check num of jobs
|
||||
return True
|
||||
elif len(set(defs_n_jobs.values())) == 1:
|
||||
return True
|
||||
else:
|
||||
print_error(
|
||||
"Following PRCI definitions should have the same number of jobs:"
|
||||
f" {list(defs_n_jobs.keys())}"
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def parse_arguments(description):
|
||||
"""Parse and return arguments if specified"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description=description, formatter_class=RawTextHelpFormatter
|
||||
)
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
group.add_argument(
|
||||
"-f", "--file", help="Specify YAML definition file to be analyzed"
|
||||
)
|
||||
group.add_argument(
|
||||
"-d",
|
||||
"--defs",
|
||||
default=DEFAULT_DIR,
|
||||
help="Specify directory for definition files to be analyzed",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--spec",
|
||||
default=JOBS_SPEC_PATH,
|
||||
help="Specify path for specification file",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Checker script for prci definition files.\n
|
||||
This script checks whether jobs in a prci definition file have the correct
|
||||
naming format, requirements, and arguments, which are defined in the
|
||||
specification file.
|
||||
|
||||
If no defition file, definition directory or spec file is specified,
|
||||
script will look for them in its own dir location.
|
||||
|
||||
Examples of the usage for the tool:\n
|
||||
# Check all yaml definition files in default dir\n
|
||||
python3 prci_checker.py\n
|
||||
# Check only specified file\n
|
||||
python3 prci_checker.py -f gating.yaml\n
|
||||
# Check with custom path for spec file\n
|
||||
python3 prci_checker.py -s ../../alternative_spec.yaml
|
||||
# Check with custom path for spec file\n
|
||||
python3 prci_checker.py -d ./definitions
|
||||
"""
|
||||
args = parse_arguments(main.__doc__)
|
||||
|
||||
print("BEGINNING PRCI JOB DEFINITIONS CHECKS")
|
||||
|
||||
# Get data from jobs specification file
|
||||
jobs_spec, supported_classes, f_fixed_jobs = process_spec_file(args.spec)
|
||||
|
||||
if args.file:
|
||||
result = process_def_file(args.file, jobs_spec, supported_classes)
|
||||
else:
|
||||
# Get all yaml files in default dir, except those in IGNORE_FILES
|
||||
def_files_dir = os.path.join(args.defs, "*.y*ml")
|
||||
defs_files = glob.glob(def_files_dir)
|
||||
ignore_files_paths = {
|
||||
os.path.join(args.defs, ignore_file) for ignore_file in IGNORE_FILES
|
||||
}
|
||||
defs_files = set(defs_files) - ignore_files_paths
|
||||
if not defs_files:
|
||||
print_warning(
|
||||
"No yaml job definition files found to analyze "
|
||||
"in specified directory."
|
||||
)
|
||||
return
|
||||
|
||||
result = True
|
||||
defs_n_jobs = {}
|
||||
|
||||
for def_file in defs_files:
|
||||
result_file, filename, n_jobs = process_def_file(
|
||||
def_file, jobs_spec, supported_classes
|
||||
)
|
||||
if not result_file:
|
||||
result = False
|
||||
continue
|
||||
if n_jobs > -1 and f_fixed_jobs and filename in f_fixed_jobs:
|
||||
defs_n_jobs[filename] = n_jobs
|
||||
|
||||
result = result and check_n_jobs(defs_n_jobs)
|
||||
|
||||
if not result:
|
||||
print("CHECKS FINISHED WITH ERRORS")
|
||||
sys.exit(1)
|
||||
|
||||
print("CHECKS FINISHED SUCCESSFULLY")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,32 @@
|
||||
# Specification file for PRCI definitions used by prci_checker script
|
||||
|
||||
# List of supported test-run classes for non-build jobs
|
||||
classes:
|
||||
- "RunPytest"
|
||||
- "RunPytest2"
|
||||
- "RunPytest3"
|
||||
- "RunWebuiTests"
|
||||
- "RunADTests"
|
||||
|
||||
# (Optional) Definition files that should contain the same number of jobs
|
||||
fixed_n_jobs:
|
||||
- nightly_ipa-4-9_latest.yaml
|
||||
- nightly_ipa-4-9_latest_selinux.yaml
|
||||
- nightly_ipa-4-9_latest_previous.yaml
|
||||
|
||||
# Info specific to prci definition files
|
||||
# 'job_prefix' field is mandatory
|
||||
# 'update_packages', 'selinux_enforcing', 'enable_testing_repo' and
|
||||
# 'copr_defined' are optional boolean fields (if not specified,
|
||||
# false value is assumed).
|
||||
# New definitions specifications may be added anytime
|
||||
prci_job_spec:
|
||||
gating:
|
||||
job_prefix: 'fedora-latest-ipa-4-9/'
|
||||
nightly_ipa-4-9_latest:
|
||||
job_prefix: 'fedora-latest-ipa-4-9/'
|
||||
nightly_ipa-4-9_latest_selinux:
|
||||
job_prefix: 'fedora-latest-ipa-4-9/'
|
||||
selinux_enforcing: True
|
||||
nightly_ipa-4-9_previous:
|
||||
job_prefix: 'fedora-previous-ipa-4-9/'
|
||||
@@ -1,6 +1,8 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import time
|
||||
import pytest
|
||||
import re
|
||||
|
||||
import textwrap
|
||||
from ipaplatform.paths import paths
|
||||
@@ -22,12 +24,12 @@ driver.get(verification_uri)
|
||||
try:
|
||||
element = WebDriverWait(driver, 90).until(
|
||||
EC.presence_of_element_located((By.ID, "username")))
|
||||
driver.find_element_by_id("username").send_keys("testuser1")
|
||||
driver.find_element_by_id("password").send_keys("{passwd}")
|
||||
driver.find_element_by_id("kc-login").click()
|
||||
driver.find_element(By.ID, "username").send_keys("testuser1")
|
||||
driver.find_element(By.ID, "password").send_keys("{passwd}")
|
||||
driver.find_element(By.ID, "kc-login").click()
|
||||
element = WebDriverWait(driver, 90).until(
|
||||
EC.presence_of_element_located((By.ID, "kc-login")))
|
||||
driver.find_element_by_id("kc-login").click()
|
||||
driver.find_element(By.ID, "kc-login").click()
|
||||
assert "Device Login Successful" in driver.page_source
|
||||
finally:
|
||||
now = datetime.now().strftime("%M-%S")
|
||||
@@ -39,18 +41,12 @@ finally:
|
||||
def add_user_code(host, verification_uri):
|
||||
contents = user_code_script.format(uri=verification_uri,
|
||||
passwd=host.config.admin_password)
|
||||
host.put_file_contents("/tmp/add_user_code.py", contents)
|
||||
tasks.run_repeatedly(
|
||||
host, ['python3', '/tmp/add_user_code.py'])
|
||||
|
||||
|
||||
def get_verification_uri(host, since, keycloak_server_name):
|
||||
command = textwrap.dedent("""
|
||||
journalctl -u ipa-otpd\\* --since="%s" | grep "user_code:" | awk '{ print substr($7,2,9) }'""" % since) # noqa: E501
|
||||
user_code = host.run_command(command).stdout_text.rstrip("\r\n")
|
||||
uri = ("https://{0}:8443/auth/realms/master/device?user_code={1}".format(
|
||||
keycloak_server_name, user_code))
|
||||
return uri
|
||||
try:
|
||||
host.put_file_contents("/tmp/add_user_code.py", contents)
|
||||
tasks.run_repeatedly(
|
||||
host, ['python3', '/tmp/add_user_code.py'])
|
||||
finally:
|
||||
host.run_command(["rm", "-f", "/tmp/add_user_code.py"])
|
||||
|
||||
|
||||
def kinit_idp(host, user, keycloak_server):
|
||||
@@ -58,11 +54,14 @@ def kinit_idp(host, user, keycloak_server):
|
||||
tasks.kdestroy_all(host)
|
||||
# create armor for FAST
|
||||
host.run_command(["kinit", "-n", "-c", ARMOR])
|
||||
since = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
cmd = ["kinit", "-T", ARMOR, user]
|
||||
|
||||
with host.spawn_expect(cmd, default_timeout=100) as e:
|
||||
e.expect('Authenticate at .+: ')
|
||||
uri = get_verification_uri(host, since, keycloak_server.hostname)
|
||||
e.expect('Authenticate at (.+) and press ENTER.:')
|
||||
prompt = e.get_last_output()
|
||||
uri = re.search(r'Authenticate at (.*?) and press ENTER.:', prompt
|
||||
).group(1)
|
||||
time.sleep(15)
|
||||
if uri:
|
||||
add_user_code(keycloak_server, uri)
|
||||
e.sendline('\n')
|
||||
@@ -74,21 +73,27 @@ def kinit_idp(host, user, keycloak_server):
|
||||
|
||||
class TestIDPKeycloak(IntegrationTest):
|
||||
|
||||
num_replicas = 1
|
||||
num_replicas = 2
|
||||
topology = 'line'
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
tasks.install_master(cls.master, setup_dns=True)
|
||||
tasks.install_client(cls.master, cls.replicas[0])
|
||||
content = cls.master.get_file_contents(paths.IPA_DEFAULT_CONF,
|
||||
encoding='utf-8')
|
||||
new_content = content + "\noidc_child_debug_level = 10"
|
||||
cls.master.put_file_contents(paths.IPA_DEFAULT_CONF, new_content)
|
||||
cls.client = cls.replicas[0]
|
||||
cls.replica = cls.replicas[1]
|
||||
tasks.install_master(cls.master, extra_args=['--no-dnssec-validation'])
|
||||
tasks.install_client(cls.master, cls.replicas[0],
|
||||
extra_args=["--mkhomedir"])
|
||||
tasks.install_replica(cls.master, cls.replicas[1])
|
||||
for host in [cls.master, cls.replicas[0], cls.replicas[1]]:
|
||||
content = host.get_file_contents(paths.IPA_DEFAULT_CONF,
|
||||
encoding='utf-8')
|
||||
new_content = content + "\noidc_child_debug_level = 10"
|
||||
host.put_file_contents(paths.IPA_DEFAULT_CONF, new_content)
|
||||
with tasks.remote_sssd_config(cls.master) as sssd_config:
|
||||
sssd_config.edit_domain(
|
||||
cls.master.domain, 'krb5_auth_timeout', 1100)
|
||||
tasks.clear_sssd_cache(cls.master)
|
||||
tasks.clear_sssd_cache(cls.replicas[0])
|
||||
tasks.kinit_admin(cls.master)
|
||||
cls.master.run_command(["ipa", "config-mod", "--user-auth-type=idp",
|
||||
"--user-auth-type=password"])
|
||||
@@ -97,20 +102,207 @@ class TestIDPKeycloak(IntegrationTest):
|
||||
cls.replicas[0].run_command(xvfb)
|
||||
|
||||
def test_auth_keycloak_idp(self):
|
||||
keycloak_srv = self.replicas[0]
|
||||
create_quarkus.setup_keycloakserver(keycloak_srv)
|
||||
"""
|
||||
Test case to check that OAuth 2.0 Device
|
||||
Authorization Grant is working as
|
||||
expected for user configured with external idp.
|
||||
"""
|
||||
create_quarkus.setup_keycloakserver(self.client)
|
||||
time.sleep(60)
|
||||
create_quarkus.setup_keycloak_client(keycloak_srv)
|
||||
create_quarkus.setup_keycloak_client(self.client)
|
||||
tasks.kinit_admin(self.master)
|
||||
cmd = ["ipa", "idp-add", "keycloak", "--provider=keycloak",
|
||||
cmd = ["ipa", "idp-add", "keycloakidp", "--provider=keycloak",
|
||||
"--client-id=ipa_oidc_client", "--org=master",
|
||||
"--base-url={0}:8443/auth".format(keycloak_srv.hostname)]
|
||||
"--base-url={0}:8443/auth".format(self.client.hostname)]
|
||||
self.master.run_command(cmd, stdin_text="{0}\n{0}".format(
|
||||
keycloak_srv.config.admin_password))
|
||||
self.client.config.admin_password))
|
||||
tasks.user_add(self.master, 'keycloakuser',
|
||||
extra_args=["--user-auth-type=idp",
|
||||
"--idp-user-id=testuser1@ipa.test",
|
||||
"--idp=keycloak"]
|
||||
"--idp=keycloakidp"]
|
||||
)
|
||||
list_user = self.master.run_command(
|
||||
["ipa", "user-find", "--idp-user-id=testuser1@ipa.test"]
|
||||
)
|
||||
assert "keycloakuser" in list_user.stdout_text
|
||||
list_by_idp = self.master.run_command(["ipa", "user-find",
|
||||
"--idp=keycloakidp"]
|
||||
)
|
||||
assert "keycloakuser" in list_by_idp.stdout_text
|
||||
list_by_user = self.master.run_command(
|
||||
["ipa", "user-find", "--idp-user-id=testuser1@ipa.test", "--all"]
|
||||
)
|
||||
assert "keycloakidp" in list_by_user.stdout_text
|
||||
tasks.clear_sssd_cache(self.master)
|
||||
kinit_idp(self.master, 'keycloakuser', keycloak_srv)
|
||||
kinit_idp(self.master, 'keycloakuser', keycloak_server=self.client)
|
||||
|
||||
@pytest.fixture
|
||||
def hbac_setup_teardown(self):
|
||||
# allow sshd only on given host
|
||||
tasks.kinit_admin(self.master)
|
||||
self.master.run_command(["ipa", "hbacrule-disable", "allow_all"])
|
||||
self.master.run_command(["ipa", "hbacrule-add", "rule1"])
|
||||
self.master.run_command(["ipa", "hbacrule-add-user", "rule1",
|
||||
"--users=keycloakuser"]
|
||||
)
|
||||
self.master.run_command(["ipa", "hbacrule-add-host", "rule1",
|
||||
"--hosts", self.replica.hostname])
|
||||
self.master.run_command(["ipa", "hbacrule-add-service", "rule1",
|
||||
"--hbacsvcs=sshd"]
|
||||
)
|
||||
tasks.clear_sssd_cache(self.master)
|
||||
tasks.clear_sssd_cache(self.replica)
|
||||
yield
|
||||
|
||||
# cleanup
|
||||
tasks.kinit_admin(self.master)
|
||||
self.master.run_command(["ipa", "hbacrule-enable", "allow_all"])
|
||||
self.master.run_command(["ipa", "hbacrule-del", "rule1"])
|
||||
|
||||
def test_auth_hbac(self, hbac_setup_teardown):
|
||||
"""
|
||||
Test case to check that hbacrule is working as
|
||||
expected for user configured with external idp.
|
||||
"""
|
||||
kinit_idp(self.master, 'keycloakuser', keycloak_server=self.client)
|
||||
ssh_cmd = "ssh -q -K -l keycloakuser {0} whoami"
|
||||
valid_ssh = self.master.run_command(
|
||||
ssh_cmd.format(self.replica.hostname))
|
||||
assert "keycloakuser" in valid_ssh.stdout_text
|
||||
negative_ssh = self.master.run_command(
|
||||
ssh_cmd.format(self.master.hostname), raiseonerr=False
|
||||
)
|
||||
assert negative_ssh.returncode == 255
|
||||
|
||||
def test_auth_sudo_idp(self):
|
||||
"""
|
||||
Test case to check that sudorule is working as
|
||||
expected for user configured with external idp.
|
||||
"""
|
||||
tasks.kdestroy_all(self.master)
|
||||
tasks.kinit_admin(self.master)
|
||||
# rule: keycloakuser are allowed to execute yum on
|
||||
# the client machine as root.
|
||||
cmdlist = [
|
||||
["ipa", "sudocmd-add", "/usr/bin/yum"],
|
||||
["ipa", "sudorule-add", "sudorule"],
|
||||
['ipa', 'sudorule-add-user', '--users=keycloakuser',
|
||||
'sudorule'],
|
||||
['ipa', 'sudorule-add-host', '--hosts',
|
||||
self.client.hostname, 'sudorule'],
|
||||
['ipa', 'sudorule-add-runasuser',
|
||||
'--users=root', 'sudorule'],
|
||||
['ipa', 'sudorule-add-allow-command',
|
||||
'--sudocmds=/usr/bin/yum', 'sudorule'],
|
||||
['ipa', 'sudorule-show', 'sudorule', '--all'],
|
||||
['ipa', 'sudorule-add-option',
|
||||
'sudorule', '--sudooption', "!authenticate"]
|
||||
]
|
||||
for cmd in cmdlist:
|
||||
self.master.run_command(cmd)
|
||||
tasks.clear_sssd_cache(self.master)
|
||||
tasks.clear_sssd_cache(self.client)
|
||||
try:
|
||||
cmd = 'sudo -ll -U keycloakuser'
|
||||
test = self.client.run_command(cmd).stdout_text
|
||||
assert "User keycloakuser may run the following commands" in test
|
||||
assert "/usr/bin/yum" in test
|
||||
kinit_idp(self.client, 'keycloakuser', self.client)
|
||||
test_sudo = 'su -c "sudo yum list wget" keycloakuser'
|
||||
self.client.run_command(test_sudo)
|
||||
list_fail = self.master.run_command(cmd).stdout_text
|
||||
assert "User keycloakuser is not allowed to run sudo" in list_fail
|
||||
finally:
|
||||
tasks.kinit_admin(self.master)
|
||||
self.master.run_command(['ipa', 'sudorule-del', 'sudorule'])
|
||||
self.master.run_command(["ipa", "sudocmd-del", "/usr/bin/yum"])
|
||||
|
||||
def test_auth_replica(self):
|
||||
"""
|
||||
Test case to check that OAuth 2.0 Device
|
||||
Authorization is working as expected on replica.
|
||||
"""
|
||||
tasks.clear_sssd_cache(self.master)
|
||||
tasks.clear_sssd_cache(self.replica)
|
||||
tasks.kinit_admin(self.replica)
|
||||
list_user = self.master.run_command(
|
||||
["ipa", "user-find", "--idp-user-id=testuser1@ipa.test"]
|
||||
)
|
||||
assert "keycloakuser" in list_user.stdout_text
|
||||
list_by_idp = self.replica.run_command(["ipa", "user-find",
|
||||
"--idp=keycloakidp"]
|
||||
)
|
||||
assert "keycloakuser" in list_by_idp.stdout_text
|
||||
list_by_user = self.replica.run_command(
|
||||
["ipa", "user-find", "--idp-user-id=testuser1@ipa.test", "--all"]
|
||||
)
|
||||
assert "keycloakidp" in list_by_user.stdout_text
|
||||
kinit_idp(self.replica, 'keycloakuser', keycloak_server=self.client)
|
||||
|
||||
def test_idp_with_services(self):
|
||||
"""
|
||||
Test case to check that services can be configured
|
||||
auth indicator as idp.
|
||||
"""
|
||||
tasks.clear_sssd_cache(self.master)
|
||||
tasks.kinit_admin(self.master)
|
||||
domain = self.master.domain.name.upper()
|
||||
services = [
|
||||
"DNS/{0}@{1}".format(self.master.hostname, domain),
|
||||
"HTTP/{0}@{1}".format(self.client.hostname, domain),
|
||||
"dogtag/{0}@{1}".format(self.master.hostname, domain),
|
||||
"ipa-dnskeysyncd/{0}@{1}".format(self.master.hostname, domain)
|
||||
]
|
||||
try:
|
||||
for service in services:
|
||||
test = self.master.run_command(["ipa", "service-mod", service,
|
||||
"--auth-ind=idp"]
|
||||
)
|
||||
assert "Authentication Indicators: idp" in test.stdout_text
|
||||
finally:
|
||||
for service in services:
|
||||
self.master.run_command(["ipa", "service-mod", service,
|
||||
"--auth-ind="])
|
||||
|
||||
def test_idp_backup_restore(self):
|
||||
"""
|
||||
Test case to check that after restore data is retrieved
|
||||
with related idp configuration.
|
||||
"""
|
||||
tasks.kinit_admin(self.master)
|
||||
user = "backupuser"
|
||||
cmd = ["ipa", "idp-add", "testidp", "--provider=keycloak",
|
||||
"--client-id=ipa_oidc_client", "--org=master",
|
||||
"--base-url={0}:8443/auth".format(self.client.hostname)]
|
||||
self.master.run_command(cmd, stdin_text="{0}\n{0}".format(
|
||||
self.client.config.admin_password))
|
||||
|
||||
tasks.user_add(self.master, user,
|
||||
extra_args=["--user-auth-type=idp",
|
||||
"--idp-user-id=testuser1@ipa.test",
|
||||
"--idp=testidp"]
|
||||
)
|
||||
|
||||
backup_path = tasks.get_backup_dir(self.master)
|
||||
# change data after backup
|
||||
self.master.run_command(['ipa', 'user-del', user])
|
||||
self.master.run_command(['ipa', 'idp-del', 'testidp'])
|
||||
dirman_password = self.master.config.dirman_password
|
||||
self.master.run_command(['ipa-restore', backup_path],
|
||||
stdin_text=dirman_password + '\nyes')
|
||||
try:
|
||||
list_user = self.master.run_command(
|
||||
['ipa', 'user-show', 'backupuser', '--all']
|
||||
).stdout_text
|
||||
assert "External IdP configuration: testidp" in list_user
|
||||
assert "User authentication types: idp" in list_user
|
||||
assert ("External IdP user identifier: "
|
||||
"testuser1@ipa.test") in list_user
|
||||
list_idp = self.master.run_command(['ipa', 'idp-find', 'testidp'])
|
||||
assert "testidp" in list_idp.stdout_text
|
||||
kinit_idp(self.master, user, self.client)
|
||||
finally:
|
||||
tasks.kdestroy_all(self.master)
|
||||
tasks.kinit_admin(self.master)
|
||||
self.master.run_command(["rm", "-rf", backup_path])
|
||||
self.master.run_command(["ipa", "idp-del", "testidp"])
|
||||
@@ -881,7 +881,7 @@ class TestInstallMaster(IntegrationTest):
|
||||
"""
|
||||
# check process count in httpd conf file i.e expected string
|
||||
exp = b'WSGIDaemonProcess ipa processes=%d' % constants.WSGI_PROCESSES
|
||||
httpd_conf = self.master.get_file_contents(paths.HTTPD_IPA_CONF)
|
||||
httpd_conf = self.master.get_file_contents(paths.HTTPD_IPA_WSGI_CONF)
|
||||
assert exp in httpd_conf
|
||||
|
||||
# check the process count
|
||||
@@ -1206,7 +1206,7 @@ class TestInstallMaster(IntegrationTest):
|
||||
|
||||
try:
|
||||
cmd = ['ipa-server-install', '--hostname', new_hostname]
|
||||
with self.master.spawn_expect(cmd) as e:
|
||||
with self.master.spawn_expect(cmd, default_timeout=100) as e:
|
||||
e.expect_exact('Do you want to configure integrated '
|
||||
'DNS (BIND)? [no]: ')
|
||||
e.sendline('no')
|
||||
@@ -1417,7 +1417,7 @@ class TestInstallMasterDNS(IntegrationTest):
|
||||
"""
|
||||
cmd = ['ipa-server-install']
|
||||
netbios = create_netbios_name(self.master)
|
||||
with self.master.spawn_expect(cmd) as e:
|
||||
with self.master.spawn_expect(cmd, default_timeout=100) as e:
|
||||
e.expect_exact('Do you want to configure integrated '
|
||||
'DNS (BIND)? [no]: ')
|
||||
e.sendline('yes')
|
||||
|
||||
@@ -340,6 +340,31 @@ class TestIpaHealthCheck(IntegrationTest):
|
||||
assert returncode == 0
|
||||
assert output == "No issues found."
|
||||
|
||||
def test_ipa_healthcheck_fips_enabled(self):
|
||||
"""
|
||||
Test if FIPS is enabled and the check exists.
|
||||
|
||||
https://pagure.io/freeipa/issue/8951
|
||||
"""
|
||||
returncode, check = run_healthcheck(self.master,
|
||||
source="ipahealthcheck.meta.core",
|
||||
check="MetaCheck",
|
||||
output_type="json",
|
||||
failures_only=False)
|
||||
assert returncode == 0
|
||||
|
||||
cmd = self.master.run_command(['fips-mode-setup', '--is-enabled'],
|
||||
raiseonerr=False)
|
||||
returncode = cmd.returncode
|
||||
|
||||
# If this produces IndexError, the check does not exist
|
||||
if check[0]["kw"]["fips"] == "disabled":
|
||||
assert returncode == 2
|
||||
elif check[0]["kw"]["fips"] == "enabled":
|
||||
assert returncode == 0
|
||||
else:
|
||||
assert returncode == 1
|
||||
|
||||
def test_ipa_healthcheck_after_certupdate(self):
|
||||
"""
|
||||
Verify that ipa-certupdate hasn't messed up tracking
|
||||
@@ -507,6 +532,45 @@ class TestIpaHealthCheck(IntegrationTest):
|
||||
assert additional_msg in check["kw"]["msg"] or \
|
||||
additional_msg == check["kw"]["error"]
|
||||
|
||||
def test_ipahealthcheck_ca_not_configured(self):
|
||||
"""
|
||||
Test if the healthcheck ignores pki-tomcat errors
|
||||
when CA is not configured on the machine
|
||||
Related: https://github.com/freeipa/freeipa-healthcheck/issues/201
|
||||
"""
|
||||
# uninstall replica installed by class' install method
|
||||
tasks.uninstall_replica(self.master, self.replicas[0])
|
||||
|
||||
# install it again without CA
|
||||
tasks.install_replica(self.master,
|
||||
self.replicas[0],
|
||||
setup_ca=False,
|
||||
setup_dns=True,
|
||||
extra_args=['--no-dnssec-validation']
|
||||
)
|
||||
|
||||
# Init a user on replica to assign a DNA range
|
||||
tasks.kinit_admin(self.replicas[0])
|
||||
tasks.user_add(
|
||||
self.replicas[0], 'ipauser1', first='Test', last='User',
|
||||
)
|
||||
|
||||
returncode, data = run_healthcheck(self.replicas[0],
|
||||
failures_only=True)
|
||||
assert returncode == 0
|
||||
assert len(data) == 0
|
||||
|
||||
# restore the replica original configuration
|
||||
tasks.user_del(self.replicas[0], 'ipauser1')
|
||||
tasks.uninstall_replica(self.master, self.replicas[0])
|
||||
tasks.install_replica(
|
||||
self.master,
|
||||
self.replicas[0],
|
||||
setup_dns=True,
|
||||
extra_args=['--no-dnssec-validation']
|
||||
)
|
||||
|
||||
|
||||
def test_source_ipahealthcheck_meta_core_metacheck(self):
|
||||
"""
|
||||
Testcase checks behaviour of check MetaCheck in source
|
||||
@@ -2843,3 +2907,65 @@ class TestIpaHealthCheckWithExternalCA(IntegrationTest):
|
||||
assert check["kw"]["msg"] == error_msg
|
||||
else:
|
||||
assert error_reason in check["kw"]["msg"]
|
||||
|
||||
|
||||
class TestIpaHealthCheckSingleMaster(IntegrationTest):
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
# Nota Bene: The ipa server is not installed
|
||||
tasks.install_packages(cls.master, HEALTHCHECK_PKG)
|
||||
|
||||
def test_ipahealthcheck_mismatching_certificates_subject(self):
|
||||
"""
|
||||
Test if healthcheck uses cert subject base from IPA and not from
|
||||
REALM. This prevents false-positive errors when the subject base is
|
||||
customized.
|
||||
|
||||
Related: https://github.com/freeipa/freeipa-healthcheck/issues/253
|
||||
"""
|
||||
# install master with custom cert subject base
|
||||
tasks.install_master(
|
||||
self.master,
|
||||
setup_dns=True,
|
||||
extra_args=[
|
||||
'--no-dnssec-validation',
|
||||
'--subject-base=O=LINUX.IS.GREAT,C=EU'
|
||||
]
|
||||
)
|
||||
try:
|
||||
returncode, data = run_healthcheck(
|
||||
self.master,
|
||||
source="ipahealthcheck.ipa.certs",
|
||||
check="IPADogtagCertsMatchCheck",
|
||||
failures_only=True)
|
||||
|
||||
assert returncode == 0
|
||||
assert len(data) == 0
|
||||
finally:
|
||||
# uninstall server for the next step
|
||||
tasks.uninstall_master(self.master)
|
||||
|
||||
# install master with custom CA certificate subject DN
|
||||
tasks.install_master(
|
||||
self.master,
|
||||
setup_dns=True,
|
||||
extra_args=[
|
||||
'--no-dnssec-validation',
|
||||
'--ca-subject=CN=Healthcheck test,O=LINUX.IS.GREAT'
|
||||
]
|
||||
)
|
||||
|
||||
try:
|
||||
returncode, data = run_healthcheck(
|
||||
self.master,
|
||||
source="ipahealthcheck.ipa.certs",
|
||||
check="IPADogtagCertsMatchCheck",
|
||||
failures_only=True)
|
||||
|
||||
assert returncode == 0
|
||||
assert len(data) == 0
|
||||
|
||||
finally:
|
||||
# cleanup
|
||||
tasks.uninstall_master(self.master)
|
||||
@@ -36,7 +36,7 @@ class TestPWPolicy(IntegrationTest):
|
||||
cls.master.run_command(['ipa', 'group-add-member', POLICY,
|
||||
'--users', USER])
|
||||
cls.master.run_command(['ipa', 'pwpolicy-add', POLICY,
|
||||
'--priority', '1'])
|
||||
'--priority', '1', '--gracelimit', '-1'])
|
||||
cls.master.run_command(['ipa', 'passwd', USER],
|
||||
stdin_text='{password}\n{password}\n'.format(
|
||||
password=PASSWORD
|
||||
@@ -265,7 +265,6 @@ class TestPWPolicy(IntegrationTest):
|
||||
|
||||
def test_graceperiod_expired(self):
|
||||
"""Test the LDAP bind grace period"""
|
||||
str(self.master.domain.basedn)
|
||||
dn = "uid={user},cn=users,cn=accounts,{base_dn}".format(
|
||||
user=USER, base_dn=str(self.master.domain.basedn))
|
||||
|
||||
@@ -308,7 +307,6 @@ class TestPWPolicy(IntegrationTest):
|
||||
|
||||
def test_graceperiod_not_replicated(self):
|
||||
"""Test that the grace period is reset on password reset"""
|
||||
str(self.master.domain.basedn)
|
||||
dn = "uid={user},cn=users,cn=accounts,{base_dn}".format(
|
||||
user=USER, base_dn=str(self.master.domain.basedn))
|
||||
|
||||
@@ -341,3 +339,54 @@ class TestPWPolicy(IntegrationTest):
|
||||
)
|
||||
assert 'passwordgraceusertime: 0' in result.stdout_text.lower()
|
||||
self.reset_password(self.master)
|
||||
|
||||
def test_graceperiod_zero(self):
|
||||
"""Test the LDAP bind with zero grace period"""
|
||||
dn = "uid={user},cn=users,cn=accounts,{base_dn}".format(
|
||||
user=USER, base_dn=str(self.master.domain.basedn))
|
||||
|
||||
self.master.run_command(
|
||||
["ipa", "pwpolicy-mod", POLICY, "--gracelimit", "0", ],
|
||||
)
|
||||
|
||||
# Resetting the password will mark it as expired
|
||||
self.reset_password(self.master)
|
||||
|
||||
# Now grace is done and binds should fail.
|
||||
result = self.master.run_command(
|
||||
["ldapsearch", "-e", "ppolicy", "-D", dn,
|
||||
"-w", PASSWORD, "-b", dn], raiseonerr=False
|
||||
)
|
||||
assert result.returncode == 49
|
||||
|
||||
assert 'Password is expired' in result.stderr_text
|
||||
assert 'Password expired, 0 grace logins remain' in result.stderr_text
|
||||
|
||||
def test_graceperiod_disabled(self):
|
||||
"""Test the LDAP bind with grace period disabled (-1)"""
|
||||
str(self.master.domain.basedn)
|
||||
dn = "uid={user},cn=users,cn=accounts,{base_dn}".format(
|
||||
user=USER, base_dn=str(self.master.domain.basedn))
|
||||
|
||||
# This can fail if gracelimit is already -1 so ignore it
|
||||
self.master.run_command(
|
||||
["ipa", "pwpolicy-mod", POLICY, "--gracelimit", "-1",],
|
||||
raiseonerr=False,
|
||||
)
|
||||
|
||||
# Ensure the password is expired
|
||||
self.reset_password(self.master)
|
||||
|
||||
result = self.kinit_as_user(self.master, PASSWORD, PASSWORD)
|
||||
|
||||
for _i in range(0, 10):
|
||||
result = self.master.run_command(
|
||||
["ldapsearch", "-e", "ppolicy", "-D", dn,
|
||||
"-w", PASSWORD, "-b", dn]
|
||||
)
|
||||
|
||||
# With graceperiod disabled it should not increment
|
||||
result = tasks.ldapsearch_dm(
|
||||
self.master, dn, ['passwordgraceusertime',],
|
||||
)
|
||||
assert 'passwordgraceusertime: 0' in result.stdout_text.lower()
|
||||
@@ -17,6 +17,7 @@ from ipatests.test_integration.base import IntegrationTest
|
||||
|
||||
class TestSubordinateId(IntegrationTest):
|
||||
num_replicas = 0
|
||||
num_clients = 1
|
||||
topology = "star"
|
||||
|
||||
def _parse_result(self, result):
|
||||
@@ -268,3 +269,40 @@ class TestSubordinateId(IntegrationTest):
|
||||
f"--subuid={subuid}"])
|
||||
owner = self._parse_result(result)["owner"]
|
||||
assert owner == uid
|
||||
|
||||
def test_nsswitch_doesnot_contain_subid_entry(self):
|
||||
"""
|
||||
This testcase checks that when ipa-client-install
|
||||
is installed without subid option, the nsswitch.conf
|
||||
does not contain subid entry or does not use sss as
|
||||
source for subid
|
||||
"""
|
||||
cmd = self.clients[0].run_command(
|
||||
["grep", "^subid", "/etc/nsswitch.conf"],
|
||||
raiseonerr=False
|
||||
)
|
||||
# a source is defined for the subid database.
|
||||
# Ensure it is not "sss"
|
||||
if cmd.returncode == 0:
|
||||
assert 'sss' not in cmd.stdout_text
|
||||
else:
|
||||
# grep command returncode 1 means no matching line
|
||||
# was found = no source is defined for the subid database,
|
||||
# which is valid other return codes would
|
||||
# mean an error occurred
|
||||
assert cmd.returncode == 1
|
||||
|
||||
def test_nsswitch_is_updated_with_subid_entry(self):
|
||||
"""
|
||||
This test case checks that when ipa-client-install
|
||||
is installed with --subid option, the nsswitch.conf
|
||||
file is modified with the entry 'subid: sss'
|
||||
"""
|
||||
tasks.uninstall_client(self.clients[0])
|
||||
tasks.install_client(self.master, self.clients[0],
|
||||
extra_args=['--subid'])
|
||||
cmd = self.clients[0].run_command(
|
||||
["grep", "^subid", "/etc/nsswitch.conf"]
|
||||
)
|
||||
subid = cmd.stdout_text.split()
|
||||
assert ['subid:', 'sss'] == subid
|
||||
@@ -101,3 +101,43 @@ class TestSortURI:
|
||||
assert dnsutil.sort_prio_weight([h3, h2, h1]) == [h1, h2, h3]
|
||||
assert dnsutil.sort_prio_weight([h3, h3, h3]) == [h3]
|
||||
assert dnsutil.sort_prio_weight([h2, h2, h1, h1]) == [h1, h2]
|
||||
|
||||
|
||||
class TestDNSResolver:
|
||||
def test_nameservers(self):
|
||||
res = dnsutil.DNSResolver()
|
||||
res.nameservers = ["4.4.4.4", "8.8.8.8"]
|
||||
assert res.nameservers == ["4.4.4.4", "8.8.8.8"]
|
||||
|
||||
def test_nameservers_with_ports(self):
|
||||
res = dnsutil.DNSResolver()
|
||||
res.nameservers = ["4.4.4.4 port 53", "8.8.8.8 port 8053"]
|
||||
assert res.nameservers == ["4.4.4.4", "8.8.8.8"]
|
||||
assert res.nameserver_ports == {"4.4.4.4": 53, "8.8.8.8": 8053}
|
||||
|
||||
res.nameservers = ["4.4.4.4 port 53", "8.8.8.8 port 8053"]
|
||||
assert res.nameservers == ["4.4.4.4", "8.8.8.8"]
|
||||
assert res.nameserver_ports == {"4.4.4.4": 53, "8.8.8.8": 8053}
|
||||
|
||||
def test_nameservers_with_bad_ports(self):
|
||||
res = dnsutil.DNSResolver()
|
||||
try:
|
||||
res.nameservers = ["4.4.4.4 port a"]
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
pytest.fail("No fail on bad port a")
|
||||
|
||||
try:
|
||||
res.nameservers = ["4.4.4.4 port -1"]
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
pytest.fail("No fail on bad port -1")
|
||||
|
||||
try:
|
||||
res.nameservers = ["4.4.4.4 port 65536"]
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
pytest.fail("No fail on bad port 65536")
|
||||
@@ -214,8 +214,8 @@ class TestAJPSecretUpgrade:
|
||||
as mocked_file:
|
||||
dogtag.secure_ajp_connector()
|
||||
if rewrite:
|
||||
newdata = mocked_file().write.call_args.args
|
||||
f = BytesIO(newdata[0])
|
||||
newdata = mocked_file().write.call_args
|
||||
f = BytesIO(newdata[0][0])
|
||||
server_xml = myparse(f)
|
||||
doc = server_xml.getroot()
|
||||
connectors = doc.xpath('//Connector[@protocol="AJP/1.3"]')
|
||||
|
||||
@@ -11,6 +11,11 @@ from ipatests.test_webui.ui_driver import screenshot
|
||||
import pytest
|
||||
import re
|
||||
|
||||
try:
|
||||
from selenium.webdriver.common.by import By
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.tier1
|
||||
class TestMiscCases(UI_driver):
|
||||
@@ -26,3 +31,17 @@ class TestMiscCases(UI_driver):
|
||||
ver_re = re.compile('version: .*')
|
||||
assert re.search(ver_re, about_text), 'Version not found'
|
||||
self.dialog_button_click('ok')
|
||||
|
||||
@screenshot
|
||||
def test_customization_pagination_input_required(self):
|
||||
"""Test if 'pagination size' is required when submitting the form."""
|
||||
self.init_app()
|
||||
|
||||
self.profile_menu_action('configuration')
|
||||
self.fill_input('pagination_size', '')
|
||||
self.dialog_button_click('save')
|
||||
|
||||
pagination_size_elem = self.find(
|
||||
".widget[name='pagination_size']", By.CSS_SELECTOR)
|
||||
|
||||
self.assert_field_validation_required(parent=pagination_size_elem)
|
||||
@@ -152,6 +152,18 @@ class test_old_permission(Declarative):
|
||||
expected=dict(
|
||||
value=permission1,
|
||||
summary=u'Added permission "%s"' % permission1,
|
||||
messages=(
|
||||
{
|
||||
'message': ('The permission has write rights but no '
|
||||
'attributes are set.'),
|
||||
'code': 13032,
|
||||
'type': 'warning',
|
||||
'name': 'MissingTargetAttributesinPermission',
|
||||
'data': {
|
||||
'right': 'write',
|
||||
}
|
||||
},
|
||||
),
|
||||
result=dict(
|
||||
dn=permission1_dn,
|
||||
cn=[permission1],
|
||||
@@ -493,6 +505,18 @@ class test_old_permission(Declarative):
|
||||
expected=dict(
|
||||
value=permission2,
|
||||
summary=u'Added permission "%s"' % permission2,
|
||||
messages=(
|
||||
{
|
||||
'message': ('The permission has write rights but no '
|
||||
'attributes are set.'),
|
||||
'code': 13032,
|
||||
'type': 'warning',
|
||||
'name': 'MissingTargetAttributesinPermission',
|
||||
'data': {
|
||||
'right': 'write',
|
||||
}
|
||||
},
|
||||
),
|
||||
result=dict(
|
||||
dn=permission2_dn,
|
||||
cn=[permission2],
|
||||
@@ -801,6 +825,18 @@ class test_old_permission(Declarative):
|
||||
expected=dict(
|
||||
value=permission1,
|
||||
summary=u'Modified permission "%s"' % permission1,
|
||||
messages=(
|
||||
{
|
||||
'message': ('The permission has read rights but no '
|
||||
'attributes are set.'),
|
||||
'code': 13032,
|
||||
'type': 'warning',
|
||||
'name': 'MissingTargetAttributesinPermission',
|
||||
'data': {
|
||||
'right': 'read',
|
||||
}
|
||||
},
|
||||
),
|
||||
result=dict(
|
||||
dn=permission1_dn,
|
||||
cn=[permission1],
|
||||
@@ -921,6 +957,18 @@ class test_old_permission(Declarative):
|
||||
expected=dict(
|
||||
value=permission1_renamed,
|
||||
summary=u'Modified permission "%s"' % permission1_renamed,
|
||||
messages=(
|
||||
{
|
||||
'message': ('The permission has write rights but no '
|
||||
'attributes are set.'),
|
||||
'code': 13032,
|
||||
'type': 'warning',
|
||||
'name': 'MissingTargetAttributesinPermission',
|
||||
'data': {
|
||||
'right': 'write',
|
||||
}
|
||||
},
|
||||
),
|
||||
result={
|
||||
'dn': permission1_renamed_ucase_dn,
|
||||
'cn': [permission1_renamed_ucase],
|
||||
@@ -947,6 +995,18 @@ class test_old_permission(Declarative):
|
||||
expected=dict(
|
||||
value=permission1_renamed_ucase,
|
||||
summary=u'Modified permission "%s"' % permission1_renamed_ucase,
|
||||
messages=(
|
||||
{
|
||||
'message': ('The permission has write rights but no '
|
||||
'attributes are set.'),
|
||||
'code': 13032,
|
||||
'type': 'warning',
|
||||
'name': 'MissingTargetAttributesinPermission',
|
||||
'data': {
|
||||
'right': 'write',
|
||||
}
|
||||
},
|
||||
),
|
||||
result=dict(
|
||||
dn=permission1_renamed_ucase_dn,
|
||||
cn=[permission1_renamed_ucase],
|
||||
@@ -1175,6 +1235,18 @@ class test_old_permission(Declarative):
|
||||
expected=dict(
|
||||
value=permission1,
|
||||
summary=u'Added permission "%s"' % permission1,
|
||||
messages=(
|
||||
{
|
||||
'message': ('The permission has write rights but no '
|
||||
'attributes are set.'),
|
||||
'code': 13032,
|
||||
'type': 'warning',
|
||||
'name': 'MissingTargetAttributesinPermission',
|
||||
'data': {
|
||||
'right': 'write',
|
||||
}
|
||||
},
|
||||
),
|
||||
result=dict(
|
||||
dn=permission1_dn,
|
||||
cn=[permission1],
|
||||
@@ -1206,6 +1278,18 @@ class test_old_permission(Declarative):
|
||||
expected=dict(
|
||||
value=permission1,
|
||||
summary=u'Modified permission "%s"' % permission1,
|
||||
messages=(
|
||||
{
|
||||
'message': ('The permission has write rights but no '
|
||||
'attributes are set.'),
|
||||
'code': 13032,
|
||||
'type': 'warning',
|
||||
'name': 'MissingTargetAttributesinPermission',
|
||||
'data': {
|
||||
'right': 'write',
|
||||
}
|
||||
},
|
||||
),
|
||||
result=dict(
|
||||
dn=permission1_dn,
|
||||
cn=[permission1],
|
||||
@@ -1229,6 +1313,18 @@ class test_old_permission(Declarative):
|
||||
),
|
||||
expected=dict(
|
||||
summary=u'Modified permission "%s"' % permission1,
|
||||
messages=(
|
||||
{
|
||||
'message': ('The permission has write rights but no '
|
||||
'attributes are set.'),
|
||||
'code': 13032,
|
||||
'type': 'warning',
|
||||
'name': 'MissingTargetAttributesinPermission',
|
||||
'data': {
|
||||
'right': 'write',
|
||||
}
|
||||
},
|
||||
),
|
||||
value=permission1,
|
||||
result=dict(
|
||||
dn=permission1_dn,
|
||||
@@ -1266,6 +1362,18 @@ class test_old_permission(Declarative):
|
||||
expected=dict(
|
||||
value=permission1,
|
||||
summary=u'Added permission "%s"' % permission1,
|
||||
messages=(
|
||||
{
|
||||
'message': ('The permission has write rights but no '
|
||||
'attributes are set.'),
|
||||
'code': 13032,
|
||||
'type': 'warning',
|
||||
'name': 'MissingTargetAttributesinPermission',
|
||||
'data': {
|
||||
'right': 'write',
|
||||
}
|
||||
},
|
||||
),
|
||||
result=dict(
|
||||
dn=permission1_dn,
|
||||
cn=[permission1],
|
||||
|
||||
@@ -3167,6 +3167,18 @@ class test_permission_bindtype(Declarative):
|
||||
expected=dict(
|
||||
value=permission1,
|
||||
summary=u'Added permission "%s"' % permission1,
|
||||
messages=(
|
||||
{
|
||||
'message': ('The permission has write rights but no '
|
||||
'attributes are set.'),
|
||||
'code': 13032,
|
||||
'type': 'warning',
|
||||
'name': 'MissingTargetAttributesinPermission',
|
||||
'data': {
|
||||
'right': 'write',
|
||||
}
|
||||
},
|
||||
),
|
||||
result=dict(
|
||||
dn=permission1_dn,
|
||||
cn=[permission1],
|
||||
@@ -3228,6 +3240,18 @@ class test_permission_bindtype(Declarative):
|
||||
expected=dict(
|
||||
value=permission1,
|
||||
summary=u'Modified permission "%s"' % permission1,
|
||||
messages=(
|
||||
{
|
||||
'message': ('The permission has write rights but no '
|
||||
'attributes are set.'),
|
||||
'code': 13032,
|
||||
'type': 'warning',
|
||||
'name': 'MissingTargetAttributesinPermission',
|
||||
'data': {
|
||||
'right': 'write',
|
||||
}
|
||||
},
|
||||
),
|
||||
result=dict(
|
||||
dn=permission1_dn,
|
||||
cn=[permission1],
|
||||
@@ -3319,6 +3343,18 @@ class test_permission_bindtype(Declarative):
|
||||
expected=dict(
|
||||
value=permission1,
|
||||
summary=u'Modified permission "%s"' % permission1,
|
||||
messages=(
|
||||
{
|
||||
'message': ('The permission has write rights but no '
|
||||
'attributes are set.'),
|
||||
'code': 13032,
|
||||
'type': 'warning',
|
||||
'name': 'MissingTargetAttributesinPermission',
|
||||
'data': {
|
||||
'right': 'write',
|
||||
}
|
||||
},
|
||||
),
|
||||
result=dict(
|
||||
dn=permission1_renamed_dn,
|
||||
cn=[permission1_renamed],
|
||||
@@ -3350,6 +3386,18 @@ class test_permission_bindtype(Declarative):
|
||||
expected=dict(
|
||||
value=permission1_renamed,
|
||||
summary=u'Modified permission "%s"' % permission1_renamed,
|
||||
messages=(
|
||||
{
|
||||
'message': ('The permission has write rights but no '
|
||||
'attributes are set.'),
|
||||
'code': 13032,
|
||||
'type': 'warning',
|
||||
'name': 'MissingTargetAttributesinPermission',
|
||||
'data': {
|
||||
'right': 'write',
|
||||
}
|
||||
},
|
||||
),
|
||||
result=dict(
|
||||
dn=permission1_renamed_dn,
|
||||
cn=[permission1_renamed],
|
||||
@@ -3379,6 +3427,18 @@ class test_permission_bindtype(Declarative):
|
||||
expected=dict(
|
||||
value=permission1_renamed,
|
||||
summary=u'Modified permission "%s"' % permission1_renamed,
|
||||
messages=(
|
||||
{
|
||||
'message': ('The permission has write rights but no '
|
||||
'attributes are set.'),
|
||||
'code': 13032,
|
||||
'type': 'warning',
|
||||
'name': 'MissingTargetAttributesinPermission',
|
||||
'data': {
|
||||
'right': 'write',
|
||||
}
|
||||
},
|
||||
),
|
||||
result=dict(
|
||||
dn=permission1_dn,
|
||||
cn=[permission1],
|
||||
@@ -4010,6 +4070,18 @@ class test_permission_filters(Declarative):
|
||||
expected=dict(
|
||||
value=permission1,
|
||||
summary=u'Added permission "%s"' % permission1,
|
||||
messages=(
|
||||
{
|
||||
'message': ('The permission has write rights but no '
|
||||
'attributes are set.'),
|
||||
'code': 13032,
|
||||
'type': 'warning',
|
||||
'name': 'MissingTargetAttributesinPermission',
|
||||
'data': {
|
||||
'right': 'write',
|
||||
}
|
||||
},
|
||||
),
|
||||
result=dict(
|
||||
dn=permission1_dn,
|
||||
cn=[permission1],
|
||||
@@ -4054,6 +4126,18 @@ class test_permission_filters(Declarative):
|
||||
expected=dict(
|
||||
value=permission1,
|
||||
summary=u'Modified permission "%s"' % permission1,
|
||||
messages=(
|
||||
{
|
||||
'message': ('The permission has write rights but no '
|
||||
'attributes are set.'),
|
||||
'code': 13032,
|
||||
'type': 'warning',
|
||||
'name': 'MissingTargetAttributesinPermission',
|
||||
'data': {
|
||||
'right': 'write',
|
||||
}
|
||||
},
|
||||
),
|
||||
result=dict(
|
||||
dn=permission1_dn,
|
||||
cn=[permission1],
|
||||
@@ -4093,6 +4177,18 @@ class test_permission_filters(Declarative):
|
||||
expected=dict(
|
||||
value=permission1,
|
||||
summary=u'Modified permission "%s"' % permission1,
|
||||
messages=(
|
||||
{
|
||||
'message': ('The permission has write rights but no '
|
||||
'attributes are set.'),
|
||||
'code': 13032,
|
||||
'type': 'warning',
|
||||
'name': 'MissingTargetAttributesinPermission',
|
||||
'data': {
|
||||
'right': 'write',
|
||||
}
|
||||
},
|
||||
),
|
||||
result=dict(
|
||||
dn=permission1_dn,
|
||||
cn=[permission1],
|
||||
@@ -4132,6 +4228,18 @@ class test_permission_filters(Declarative):
|
||||
expected=dict(
|
||||
value=permission1,
|
||||
summary=u'Modified permission "%s"' % permission1,
|
||||
messages=(
|
||||
{
|
||||
'message': ('The permission has write rights but no '
|
||||
'attributes are set.'),
|
||||
'code': 13032,
|
||||
'type': 'warning',
|
||||
'name': 'MissingTargetAttributesinPermission',
|
||||
'data': {
|
||||
'right': 'write',
|
||||
}
|
||||
},
|
||||
),
|
||||
result=dict(
|
||||
dn=permission1_dn,
|
||||
cn=[permission1],
|
||||
@@ -4172,6 +4280,18 @@ class test_permission_filters(Declarative):
|
||||
expected=dict(
|
||||
value=permission1,
|
||||
summary=u'Modified permission "%s"' % permission1,
|
||||
messages=(
|
||||
{
|
||||
'message': ('The permission has write rights but no '
|
||||
'attributes are set.'),
|
||||
'code': 13032,
|
||||
'type': 'warning',
|
||||
'name': 'MissingTargetAttributesinPermission',
|
||||
'data': {
|
||||
'right': 'write',
|
||||
}
|
||||
},
|
||||
),
|
||||
result=dict(
|
||||
dn=permission1_dn,
|
||||
cn=[permission1],
|
||||
@@ -4205,6 +4325,18 @@ class test_permission_filters(Declarative):
|
||||
expected=dict(
|
||||
value=permission1,
|
||||
summary=u'Modified permission "%s"' % permission1,
|
||||
messages=(
|
||||
{
|
||||
'message': ('The permission has write rights but no '
|
||||
'attributes are set.'),
|
||||
'code': 13032,
|
||||
'type': 'warning',
|
||||
'name': 'MissingTargetAttributesinPermission',
|
||||
'data': {
|
||||
'right': 'write',
|
||||
}
|
||||
},
|
||||
),
|
||||
result=dict(
|
||||
dn=permission1_dn,
|
||||
cn=[permission1],
|
||||
@@ -4254,6 +4386,18 @@ class test_permission_filters(Declarative):
|
||||
expected=dict(
|
||||
value=permission1,
|
||||
summary=u'Added permission "%s"' % permission1,
|
||||
messages=(
|
||||
{
|
||||
'message': ('The permission has write rights but no '
|
||||
'attributes are set.'),
|
||||
'code': 13032,
|
||||
'type': 'warning',
|
||||
'name': 'MissingTargetAttributesinPermission',
|
||||
'data': {
|
||||
'right': 'write',
|
||||
}
|
||||
},
|
||||
),
|
||||
result=dict(
|
||||
dn=permission1_dn,
|
||||
cn=[permission1],
|
||||
|
||||
@@ -255,6 +255,18 @@ class test_privilege(Declarative):
|
||||
expected=dict(
|
||||
value=permission2,
|
||||
summary=u'Added permission "%s"' % permission2,
|
||||
messages=(
|
||||
{
|
||||
'message': ('The permission has write rights but no '
|
||||
'attributes are set.'),
|
||||
'code': 13032,
|
||||
'type': 'warning',
|
||||
'name': 'MissingTargetAttributesinPermission',
|
||||
'data': {
|
||||
'right': 'write',
|
||||
}
|
||||
},
|
||||
),
|
||||
result=dict(
|
||||
dn=permission2_dn,
|
||||
cn=[permission2],
|
||||
|
||||
@@ -479,11 +479,12 @@ class TestActive(XMLRPC_test):
|
||||
|
||||
def test_delete_preserve(self, user):
|
||||
user.ensure_exists()
|
||||
user.track_delete()
|
||||
user.track_delete(preserve=True)
|
||||
command = user.make_delete_command(no_preserve=False, preserve=True)
|
||||
result = command()
|
||||
user.check_delete(result)
|
||||
|
||||
user.track_delete(preserve=False)
|
||||
command = user.make_delete_command()
|
||||
result = command()
|
||||
user.check_delete(result)
|
||||
@@ -622,6 +623,7 @@ class TestCustomAttr(XMLRPC_test):
|
||||
assert 'BusinessCat' in result['result'][u'businesscategory']
|
||||
|
||||
# delete the user with --preserve
|
||||
user_customattr.track_delete(preserve=True)
|
||||
command = user_customattr.make_delete_command(no_preserve=False,
|
||||
preserve=True)
|
||||
result = command()
|
||||
@@ -763,6 +765,7 @@ class TestGroups(XMLRPC_test):
|
||||
result = command()
|
||||
group.check_retrieve(result)
|
||||
|
||||
user.track_delete(preserve=True)
|
||||
command = user.make_delete_command(no_preserve=False, preserve=True)
|
||||
result = command()
|
||||
user.check_delete(result)
|
||||
|
||||
@@ -125,7 +125,9 @@ def user_npg(request, group):
|
||||
del tracker.attrs['mepmanagedentry']
|
||||
tracker.attrs.update(
|
||||
description=[], memberof_group=[group.cn],
|
||||
objectclass=objectclasses.user_base + [u'ipantuserattrs'],
|
||||
objectclass=fuzzy_set_optional_oc(
|
||||
objectclasses.user_base, 'ipantuserattrs'
|
||||
),
|
||||
)
|
||||
return tracker.make_fixture(request)
|
||||
|
||||
@@ -139,7 +141,9 @@ def user_npg2(request, group):
|
||||
del tracker.attrs['mepmanagedentry']
|
||||
tracker.attrs.update(
|
||||
gidnumber=[u'1000'], description=[], memberof_group=[group.cn],
|
||||
objectclass=objectclasses.user_base + [u'ipantuserattrs'],
|
||||
objectclass=fuzzy_set_optional_oc(
|
||||
objectclasses.user_base, 'ipantuserattrs'
|
||||
),
|
||||
)
|
||||
return tracker.make_fixture(request)
|
||||
|
||||
@@ -151,8 +155,9 @@ def user_radius(request, xmlrpc_setup):
|
||||
sn=u'radiususer1',
|
||||
ipatokenradiususername=u'radiususer')
|
||||
tracker.track_create()
|
||||
tracker.attrs.update(
|
||||
objectclass=objectclasses.user + [u'ipatokenradiusproxyuser']
|
||||
tracker.attrs.update(objectclass=fuzzy_set_optional_oc(
|
||||
objectclasses.user + [u'ipatokenradiusproxyuser'],
|
||||
'ipantuserattrs'),
|
||||
)
|
||||
return tracker.make_fixture(request)
|
||||
|
||||
@@ -647,7 +652,8 @@ class TestCreate(XMLRPC_test):
|
||||
testuser.attrs.update(gidnumber=[u'1000'])
|
||||
testuser.attrs.update(
|
||||
description=[],
|
||||
objectclass=objectclasses.user_base + [u'ipantuserattrs']
|
||||
objectclass=fuzzy_set_optional_oc(
|
||||
objectclasses.user_base, 'ipantuserattrs'),
|
||||
)
|
||||
command = testuser.make_create_command()
|
||||
result = command()
|
||||
@@ -865,7 +871,9 @@ class TestUserWithUPGDisabled(XMLRPC_test):
|
||||
testuser.attrs.update(gidnumber=[u'1000'])
|
||||
testuser.attrs.update(
|
||||
description=[],
|
||||
objectclass=objectclasses.user_base + [u'ipantuserattrs'],
|
||||
objectclass=fuzzy_set_optional_oc(
|
||||
objectclasses.user_base, 'ipantuserattrs'
|
||||
),
|
||||
)
|
||||
command = testuser.make_create_command()
|
||||
result = command()
|
||||
|
||||
@@ -273,9 +273,14 @@ class UserTracker(CertmapdataMixin, KerberosAliasMixin, Tracker):
|
||||
|
||||
def check_delete(self, result):
|
||||
""" Check 'user-del' command result """
|
||||
if u'preserved' in self.attrs and self.attrs[u'preserved']:
|
||||
summary = u'Preserved user "%s"' % self.uid
|
||||
else:
|
||||
summary = u'Deleted user "%s"' % self.uid
|
||||
|
||||
assert_deepequal(dict(
|
||||
value=[self.uid],
|
||||
summary=u'Deleted user "%s"' % self.uid,
|
||||
summary=summary,
|
||||
result=dict(failed=[]),
|
||||
), result)
|
||||
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ def generate_aci_lines(api):
|
||||
anonymous_read_aci=None,
|
||||
is_new=True,
|
||||
)
|
||||
aci = perm_plugin.make_aci(entry)
|
||||
aci, _msg = perm_plugin.make_aci(entry)
|
||||
yield 'dn: %s\n' % entry.single_value['ipapermlocation']
|
||||
yield f'aci: {aci}\n'
|
||||
|
||||
|
||||
Reference in new issue
Block a user