Merge branch 'upstream'

This commit is contained in:
Timo Aaltonen 2014-09-11 19:17:26 +03:00
commit 767c009d48
77 changed files with 2229 additions and 1081 deletions

14
ACI.txt
View File

@ -50,6 +50,8 @@ dn: cn=groups,cn=accounts,dc=ipa,dc=example
aci: (targetattr = "member")(targetfilter = "(&(!(cn=admins))(objectclass=ipausergroup))")(version 3.0;acl "permission:System: Modify Group Membership";allow (write) groupdn = "ldap:///cn=System: Modify Group Membership,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=groups,cn=accounts,dc=ipa,dc=example
aci: (targetattr = "cn || description || gidnumber || ipauniqueid || mepmanagedby || objectclass")(targetfilter = "(|(objectclass=ipausergroup)(objectclass=posixgroup))")(version 3.0;acl "permission:System: Modify Groups";allow (write) groupdn = "ldap:///cn=System: Modify Groups,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: dc=ipa,dc=example
aci: (targetattr = "cn || gidnumber || memberuid || objectclass")(target = "ldap:///cn=groups,cn=compat,dc=ipa,dc=example")(version 3.0;acl "permission:System: Read Group Compat Tree";allow (compare,read,search) userdn = "ldap:///anyone";)
dn: cn=groups,cn=accounts,dc=ipa,dc=example
aci: (targetattr = "member || memberhost || memberof || memberuid || memberuser")(targetfilter = "(|(objectclass=ipausergroup)(objectclass=posixgroup))")(version 3.0;acl "permission:System: Read Group Membership";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=groups,cn=accounts,dc=ipa,dc=example
@ -96,6 +98,8 @@ dn: cn=computers,cn=accounts,dc=ipa,dc=example
aci: (targetattr = "ipasshpubkey")(targetfilter = "(objectclass=ipahost)")(version 3.0;acl "permission:System: Manage Host SSH Public Keys";allow (write) groupdn = "ldap:///cn=System: Manage Host SSH Public Keys,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=computers,cn=accounts,dc=ipa,dc=example
aci: (targetattr = "description || l || macaddress || nshardwareplatform || nshostlocation || nsosversion || userclass")(targetfilter = "(objectclass=ipahost)")(version 3.0;acl "permission:System: Modify Hosts";allow (write) groupdn = "ldap:///cn=System: Modify Hosts,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: dc=ipa,dc=example
aci: (targetattr = "cn || macaddress || objectclass")(target = "ldap:///cn=computers,cn=compat,dc=ipa,dc=example")(version 3.0;acl "permission:System: Read Host Compat Tree";allow (compare,read,search) userdn = "ldap:///anyone";)
dn: cn=computers,cn=accounts,dc=ipa,dc=example
aci: (targetattr = "memberof")(targetfilter = "(objectclass=ipahost)")(version 3.0;acl "permission:System: Read Host Membership";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=computers,cn=accounts,dc=ipa,dc=example
@ -126,6 +130,8 @@ dn: cn=ng,cn=alt,dc=ipa,dc=example
aci: (targetattr = "externalhost || member || memberhost || memberuser")(targetfilter = "(objectclass=ipanisnetgroup)")(version 3.0;acl "permission:System: Modify Netgroup Membership";allow (write) groupdn = "ldap:///cn=System: Modify Netgroup Membership,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=ng,cn=alt,dc=ipa,dc=example
aci: (targetattr = "description")(targetfilter = "(objectclass=ipanisnetgroup)")(version 3.0;acl "permission:System: Modify Netgroups";allow (write) groupdn = "ldap:///cn=System: Modify Netgroups,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: dc=ipa,dc=example
aci: (targetattr = "cn || membernisnetgroup || nisnetgrouptriple || objectclass")(target = "ldap:///cn=ng,cn=compat,dc=ipa,dc=example")(version 3.0;acl "permission:System: Read Netgroup Compat Tree";allow (compare,read,search) userdn = "ldap:///anyone";)
dn: cn=ng,cn=alt,dc=ipa,dc=example
aci: (targetattr = "externalhost || member || memberhost || memberof || memberuser || objectclass")(targetfilter = "(objectclass=ipanisnetgroup)")(version 3.0;acl "permission:System: Read Netgroup Membership";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=ng,cn=alt,dc=ipa,dc=example
@ -213,7 +219,7 @@ aci: (targetattr = "cmdcategory || description || externalhost || externaluser |
dn: cn=sudorules,cn=sudo,dc=ipa,dc=example
aci: (targetattr = "cmdcategory || cn || description || externalhost || externaluser || hostcategory || hostmask || ipaenabledflag || ipasudoopt || ipasudorunas || ipasudorunasextgroup || ipasudorunasextuser || ipasudorunasextusergroup || ipasudorunasgroup || ipasudorunasgroupcategory || ipasudorunasusercategory || ipauniqueid || member || memberallowcmd || memberdenycmd || memberhost || memberuser || objectclass || sudonotafter || sudonotbefore || sudoorder || usercategory")(targetfilter = "(objectclass=ipasudorule)")(version 3.0;acl "permission:System: Read Sudo Rules";allow (compare,read,search) userdn = "ldap:///all";)
dn: dc=ipa,dc=example
aci: (targetattr = "cn || description || objectclass || ou || sudocommand || sudohost || sudonotafter || sudonotbefore || sudooption || sudoorder || sudorunas || sudorunasgroup || sudorunasuser || sudouser")(target = "ldap:///ou=sudoers,dc=ipa,dc=example")(version 3.0;acl "permission:System: Read Sudoers compat tree";allow (compare,read,search) userdn = "ldap:///all";)
aci: (targetattr = "cn || description || objectclass || ou || sudocommand || sudohost || sudonotafter || sudonotbefore || sudooption || sudoorder || sudorunas || sudorunasgroup || sudorunasuser || sudouser")(target = "ldap:///ou=sudoers,dc=ipa,dc=example")(version 3.0;acl "permission:System: Read Sudoers compat tree";allow (compare,read,search) userdn = "ldap:///anyone";)
dn: cn=trusts,dc=ipa,dc=example
aci: (targetattr = "cn || ipantflatname || ipantsecurityidentifier || ipantsidblacklistincoming || ipantsidblacklistoutgoing || ipanttrusteddomainsid || ipanttrustpartner || objectclass")(version 3.0;acl "permission:System: Read Trust Information";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=trusts,dc=ipa,dc=example
@ -232,6 +238,8 @@ dn: cn=UPG Definition,cn=Definitions,cn=Managed Entries,cn=etc,dc=ipa,dc=example
aci: (targetattr = "*")(target = "ldap:///cn=UPG Definition,cn=Definitions,cn=Managed Entries,cn=etc,dc=ipa,dc=example")(version 3.0;acl "permission:System: Read UPG Definition";allow (compare,read,search) groupdn = "ldap:///cn=System: Read UPG Definition,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=users,cn=accounts,dc=ipa,dc=example
aci: (targetattr = "audio || businesscategory || carlicense || departmentnumber || destinationindicator || employeenumber || employeetype || fax || homephone || homepostaladdress || inetuserhttpurl || inetuserstatus || internationalisdnnumber || jpegphoto || l || labeleduri || mail || mobile || o || ou || pager || photo || physicaldeliveryofficename || postaladdress || postalcode || postofficebox || preferreddeliverymethod || preferredlanguage || registeredaddress || roomnumber || secretary || seealso || st || street || telephonenumber || teletexterminalidentifier || telexnumber || usercertificate || usersmimecertificate || x121address || x500uniqueidentifier")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User Addressbook Attributes";allow (compare,read,search) userdn = "ldap:///all";)
dn: dc=ipa,dc=example
aci: (targetattr = "cn || gecos || gidnumber || homedirectory || loginshell || objectclass || uid || uidnumber")(target = "ldap:///cn=users,cn=compat,dc=ipa,dc=example")(version 3.0;acl "permission:System: Read User Compat Tree";allow (compare,read,search) userdn = "ldap:///anyone";)
dn: cn=users,cn=accounts,dc=ipa,dc=example
aci: (targetattr = "ipasshpubkey || ipauniqueid || ipauserauthtype || userclass")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Read User IPA Attributes";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=users,cn=accounts,dc=ipa,dc=example
@ -252,6 +260,8 @@ dn: cn=CAcert,cn=ipa,cn=etc,dc=ipa,dc=example
aci: (targetattr = "authorityrevocationlist || cacertificate || certificaterevocationlist || cn || crosscertificatepair || objectclass")(targetfilter = "(objectclass=pkica)")(version 3.0;acl "permission:System: Read CA Certificate";allow (compare,read,search) userdn = "ldap:///anyone";)
dn: cn=ca_renewal,cn=ipa,cn=etc,dc=ipa,dc=example
aci: (targetattr = "cn || objectclass || usercertificate")(targetfilter = "(objectclass=pkiuser)")(version 3.0;acl "permission:System: Read CA Renewal Information";allow (compare,read,search) userdn = "ldap:///all";)
dn: dc=ipa,dc=example
aci: (targetattr = "creatorsname || modifiersname")(targetfilter = "(objectclass=*)")(version 3.0;acl "permission:System: Read Creator and Modifier Operational Attributes";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=dna,cn=ipa,cn=etc,dc=ipa,dc=example
aci: (targetattr = "cn || dnahostname || dnaportnum || dnaremainingvalues || dnaremotebindmethod || dnaremoteconnprotocol || dnasecureportnum || objectclass")(targetfilter = "(objectclass=dnasharedconfig)")(version 3.0;acl "permission:System: Read DNA Configuration";allow (compare,read,search) userdn = "ldap:///all";)
dn: cn=masters,cn=ipa,cn=etc,dc=ipa,dc=example
@ -260,3 +270,5 @@ dn: cn=config
aci: (targetattr = "cn || description || nsds50ruv || nsds5beginreplicarefresh || nsds5debugreplicatimeout || nsds5flags || nsds5replicaabortcleanruv || nsds5replicaautoreferral || nsds5replicabackoffmax || nsds5replicabackoffmin || nsds5replicabinddn || nsds5replicabindmethod || nsds5replicabusywaittime || nsds5replicachangecount || nsds5replicachangessentsincestartup || nsds5replicacleanruv || nsds5replicacleanruvnotified || nsds5replicacredentials || nsds5replicaenabled || nsds5replicahost || nsds5replicaid || nsds5replicalastinitend || nsds5replicalastinitstart || nsds5replicalastinitstatus || nsds5replicalastupdateend || nsds5replicalastupdatestart || nsds5replicalastupdatestatus || nsds5replicalegacyconsumer || nsds5replicaname || nsds5replicaport || nsds5replicaprotocoltimeout || nsds5replicapurgedelay || nsds5replicareferral || nsds5replicaroot || nsds5replicasessionpausetime || nsds5replicastripattrs || nsds5replicatedattributelist || nsds5replicatedattributelisttotal || nsds5replicatimeout || nsds5replicatombstonepurgeinterval || nsds5replicatransportinfo || nsds5replicatype || nsds5replicaupdateinprogress || nsds5replicaupdateschedule || nsds5task || nsds7directoryreplicasubtree || nsds7dirsynccookie || nsds7newwingroupsyncenabled || nsds7newwinusersyncenabled || nsds7windowsdomain || nsds7windowsreplicasubtree || nsruvreplicalastmodified || nsstate || objectclass || onewaysync || winsyncdirectoryfilter || winsyncinterval || winsyncmoveaction || winsyncsubtreepair || winsyncwindowsfilter")(targetfilter = "(|(objectclass=nsds5Replica)(objectclass=nsds5replicationagreement)(objectclass=nsDSWindowsReplicationAgreement)(objectClass=nsMappingTree))")(version 3.0;acl "permission:System: Read Replication Agreements";allow (compare,read,search) groupdn = "ldap:///cn=System: Read Replication Agreements,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=replication,cn=etc,dc=ipa,dc=example
aci: (targetattr = "cn || nsds5flags || nsds5replicaabortcleanruv || nsds5replicaautoreferral || nsds5replicabackoffmax || nsds5replicabackoffmin || nsds5replicabinddn || nsds5replicachangecount || nsds5replicacleanruv || nsds5replicaid || nsds5replicalegacyconsumer || nsds5replicaname || nsds5replicaprotocoltimeout || nsds5replicapurgedelay || nsds5replicareferral || nsds5replicaroot || nsds5replicatombstonepurgeinterval || nsds5replicatype || nsds5task || nsstate || objectclass")(targetfilter = "(objectclass=nsds5replica)")(version 3.0;acl "permission:System: Read Replication Information";allow (compare,read,search) userdn = "ldap:///all";)
dn: dc=ipa,dc=example
aci: (targetattr = "createtimestamp || entryusn || modifytimestamp")(targetfilter = "(objectclass=*)")(version 3.0;acl "permission:System: Read Timestamp and USN Operational Attributes";allow (compare,read,search) userdn = "ldap:///anyone";)

View File

@ -2038,7 +2038,7 @@ option: Int('ipabaserid', attribute=True, cli_name='rid_base', multivalue=False,
option: Int('ipaidrangesize', attribute=True, cli_name='range_size', multivalue=False, required=True)
option: Str('ipanttrusteddomainname', attribute=False, cli_name='dom_name', multivalue=False, required=False)
option: Str('ipanttrusteddomainsid', attribute=True, cli_name='dom_sid', multivalue=False, required=False)
option: StrEnum('iparangetype', attribute=True, cli_name='type', multivalue=False, required=False, values=(u'ipa-ad-trust-posix', u'ipa-ad-trust', u'ipa-local', u'ipa-ad-winsync', u'ipa-ipa-trust'))
option: StrEnum('iparangetype', attribute=True, cli_name='type', multivalue=False, required=False, values=(u'ipa-ad-trust-posix', u'ipa-ad-trust', u'ipa-local'))
option: Int('ipasecondarybaserid', attribute=True, cli_name='secondary_rid_base', multivalue=False, required=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
option: Str('setattr*', cli_name='setattr', exclude='webui')
@ -2063,7 +2063,7 @@ option: Int('ipabaseid', attribute=True, autofill=False, cli_name='base_id', mul
option: Int('ipabaserid', attribute=True, autofill=False, cli_name='rid_base', multivalue=False, query=True, required=False)
option: Int('ipaidrangesize', attribute=True, autofill=False, cli_name='range_size', multivalue=False, query=True, required=False)
option: Str('ipanttrusteddomainsid', attribute=True, autofill=False, cli_name='dom_sid', multivalue=False, query=True, required=False)
option: StrEnum('iparangetype', attribute=True, autofill=False, cli_name='type', multivalue=False, query=True, required=False, values=(u'ipa-ad-trust-posix', u'ipa-ad-trust', u'ipa-local', u'ipa-ad-winsync', u'ipa-ipa-trust'))
option: StrEnum('iparangetype', attribute=True, autofill=False, cli_name='type', multivalue=False, query=True, required=False, values=(u'ipa-ad-trust-posix', u'ipa-ad-trust', u'ipa-local'))
option: Int('ipasecondarybaserid', attribute=True, autofill=False, cli_name='secondary_rid_base', multivalue=False, query=True, required=False)
option: Flag('pkey_only?', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')

View File

@ -20,7 +20,7 @@
########################################################
IPA_VERSION_MAJOR=4
IPA_VERSION_MINOR=0
IPA_VERSION_RELEASE=0
IPA_VERSION_RELEASE=2
########################################################
# For 'pre' releases the version will be #

View File

@ -660,6 +660,7 @@ static int ipa_range_check_pre_op(Slapi_PBlock *pb, int modtype)
break;
case RANGE_CHECK_DIFFERENT_TYPE_IN_DOMAIN:
errmsg = "New ID range has invalid type. All ranges in the same domain must be of the same type.";
break;
default:
errmsg = "New range overlaps with existing one.";
break;

View File

@ -66,6 +66,7 @@ struct otptoken {
enum otptoken_type type;
union {
struct {
uint64_t watermark;
unsigned int step;
int offset;
} totp;
@ -123,69 +124,21 @@ static const struct berval *entry_attr_get_berval(const Slapi_Entry* e,
return slapi_value_get_berval(v);
}
static bool validate(struct otptoken *token, time_t now, ssize_t step,
uint32_t first, const uint32_t *second)
{
uint32_t tmp;
switch (token->type) {
case OTPTOKEN_TOTP:
step = (now + token->totp.offset) / token->totp.step + step;
break;
case OTPTOKEN_HOTP:
step = token->hotp.counter + step;
break;
default:
return false;
}
if (!hotp(&token->token, step, &tmp))
return false;
if (first != tmp)
return false;
if (second == NULL)
return true;
if (!hotp(&token->token, step + 1, &tmp))
return false;
return *second == tmp;
}
static bool writeback(struct otptoken *token, ssize_t step, bool sync)
static bool writeattr(const struct otptoken *token, const char *attr,
int value)
{
Slapi_Value *svals[] = { NULL, NULL };
Slapi_PBlock *pb = NULL;
Slapi_Mods *mods = NULL;
bool success = false;
const char *attr;
int value;
int ret;
switch (token->type) {
case OTPTOKEN_TOTP:
if (!sync)
return true;
attr = T("clockOffset");
value = token->totp.offset + step * token->totp.step;
break;
case OTPTOKEN_HOTP:
/* Having support for LDAP_MOD_INCREMENT could be helpful here. */
if (step < 0)
return false; /* NEVER go backwards! */
attr = H("counter");
value = token->hotp.counter + step;
break;
default:
return false;
}
/* Create the value. */
svals[0] = slapi_value_new();
if (slapi_value_set_int(svals[0], value) != 0)
goto error;
if (slapi_value_set_int(svals[0], value) != 0) {
slapi_value_free(&svals[0]);
return false;
}
/* Create the mods. */
mods = slapi_mods_new();
@ -203,26 +156,91 @@ static bool writeback(struct otptoken *token, ssize_t step, bool sync)
if (ret != LDAP_SUCCESS)
goto error;
/* Save our modifications to the object. */
switch (token->type) {
case OTPTOKEN_TOTP:
token->totp.offset = value;
break;
case OTPTOKEN_HOTP:
token->hotp.counter = value;
break;
default:
break;
}
success = true;
error:
slapi_pblock_destroy(pb);
slapi_mods_free(&mods);
return success;
}
/**
* Validate a token.
*
* If the second token code is specified, perform synchronization.
*/
static bool validate(struct otptoken *token, time_t now, ssize_t step,
uint32_t first, const uint32_t *second)
{
const char *attr;
uint32_t tmp;
/* Calculate the absolute step. */
switch (token->type) {
case OTPTOKEN_TOTP:
attr = T("watermark");
step = (now + token->totp.offset) / token->totp.step + step;
if (token->totp.watermark > 0 && step < token->totp.watermark)
return false;
break;
case OTPTOKEN_HOTP:
if (step < 0) /* NEVER go backwards! */
return false;
attr = H("counter");
step = token->hotp.counter + step;
break;
default:
return false;
}
/* Validate the first code. */
if (!hotp(&token->token, step++, &tmp))
return false;
if (first != tmp)
return false;
/* Validate the second code if specified. */
if (second != NULL) {
if (!hotp(&token->token, step++, &tmp))
return false;
if (*second != tmp)
return false;
/* Perform optional synchronization steps. */
switch (token->type) {
case OTPTOKEN_TOTP:
tmp = (step - now / token->totp.step) * token->totp.step;
if (!writeattr(token, T("clockOffset"), tmp))
return false;
break;
default:
break;
}
}
/* Write the step value. */
if (!writeattr(token, attr, step))
return false;
/* Save our modifications to the object. */
switch (token->type) {
case OTPTOKEN_TOTP:
token->totp.watermark = step;
break;
case OTPTOKEN_HOTP:
token->hotp.counter = step;
break;
default:
break;
}
return true;
}
static void otptoken_free(struct otptoken *token)
{
if (token == NULL)
@ -303,6 +321,9 @@ static struct otptoken *otptoken_new(Slapi_ComponentId *id, Slapi_Entry *entry)
/* Get offset. */
token->totp.offset = slapi_entry_attr_get_int(entry, T("clockOffset"));
/* Get watermark. */
token->totp.watermark = slapi_entry_attr_get_int(entry, T("watermark"));
/* Get step. */
token->totp.step = slapi_entry_attr_get_uint(entry, T("timeStep"));
if (token->totp.step == 0)
@ -464,15 +485,11 @@ static bool otptoken_validate(struct otptoken *token, size_t steps,
for (int i = 0; i <= steps; i++) {
/* Validate the positive step. */
if (validate(token, now, i, code, NULL))
return writeback(token, i + 1, false);
/* Counter-based tokens must NEVER validate old steps! */
if (i == 0 || token->type == OTPTOKEN_HOTP)
continue;
return true;
/* Validate the negative step. */
if (validate(token, now, 0 - i, code, NULL))
return writeback(token, 0 - i + 1, false);
return true;
}
return false;
@ -538,15 +555,11 @@ static bool otptoken_sync(struct otptoken * const *tokens, size_t steps,
for (int j = 0; tokens[j] != NULL; j++) {
/* Validate the positive step. */
if (validate(tokens[j], now, i, first_code, &second_code))
return writeback(tokens[j], i + 2, true);
/* Counter-based tokens must NEVER validate old steps! */
if (i == 0 || tokens[j]->type == OTPTOKEN_HOTP)
continue;
return true;
/* Validate the negative step. */
if (validate(tokens[j], now, 0 - i, first_code, &second_code))
return writeback(tokens[j], 0 - i + 2, true);
return true;
}
}

View File

@ -65,13 +65,14 @@ BuildRequires: m2crypto
BuildRequires: check
BuildRequires: libsss_idmap-devel
BuildRequires: libsss_nss_idmap-devel
BuildRequires: java-1.7.0-openjdk
BuildRequires: java-headless
BuildRequires: rhino
BuildRequires: libverto-devel
BuildRequires: systemd
BuildRequires: libunistring-devel
BuildRequires: python-lesscpy
BuildRequires: python-yubico
BuildRequires: python-backports-ssl_match_hostname
%description
IPA is an integrated solution to provide centrally managed Identity (machine,
@ -86,11 +87,11 @@ Group: System Environment/Base
Requires: %{name}-python = %{version}-%{release}
Requires: %{name}-client = %{version}-%{release}
Requires: %{name}-admintools = %{version}-%{release}
Requires: 389-ds-base >= 1.3.2.19
Requires: 389-ds-base >= 1.3.2.20
Requires: openldap-clients > 2.4.35-4
Requires: nss >= 3.14.3-12.0
Requires: nss-tools >= 3.14.3-12.0
Requires: krb5-server >= 1.11.5-3
Requires: krb5-server >= 1.11.5-5
Requires: krb5-pkinit-openssl
Requires: cyrus-sasl-gssapi%{?_isa}
Requires: ntp
@ -122,8 +123,8 @@ Requires: python-dns
Requires: zip
Requires: policycoreutils >= %{POLICYCOREUTILSVER}
Requires: tar
Requires(pre): certmonger >= 0.65
Requires(pre): 389-ds-base >= 1.3.2.19
Requires(pre): certmonger >= 0.75.13
Requires(pre): 389-ds-base >= 1.3.2.20
Requires: fontawesome-fonts
Requires: open-sans-fonts
@ -203,6 +204,7 @@ Requires: libsss_autofs
Requires: autofs
Requires: libnfsidmap
Requires: nfs-utils
Requires: python-backports-ssl_match_hostname
Requires(post): policycoreutils
Obsoletes: ipa-client >= 1.0

View File

@ -23,8 +23,9 @@ attributeTypes: (2.16.840.1.113730.3.8.16.1.18 NAME 'ipatokenRadiusTimeout' DESC
attributeTypes: (2.16.840.1.113730.3.8.16.1.19 NAME 'ipatokenRadiusRetries' DESC 'Number of allowed Retries' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP')
attributeTypes: (2.16.840.1.113730.3.8.16.1.20 NAME 'ipatokenUserMapAttribute' DESC 'Attribute to map from the user entry for RADIUS server authentication' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP')
attributeTypes: (2.16.840.1.113730.3.8.16.1.21 NAME 'ipatokenHOTPcounter' DESC 'HOTP counter' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP')
attributeTypes: (2.16.840.1.113730.3.8.16.1.22 NAME 'ipatokenTOTPwatermark' DESC 'TOTP watermark' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP')
objectClasses: (2.16.840.1.113730.3.8.16.2.1 NAME 'ipaToken' SUP top ABSTRACT DESC 'Abstract token class for tokens' MUST (ipatokenUniqueID) MAY (description $ managedBy $ ipatokenOwner $ ipatokenDisabled $ ipatokenNotBefore $ ipatokenNotAfter $ ipatokenVendor $ ipatokenModel $ ipatokenSerial) X-ORIGIN 'IPA OTP')
objectClasses: (2.16.840.1.113730.3.8.16.2.2 NAME 'ipatokenTOTP' SUP ipaToken STRUCTURAL DESC 'TOTP Token Type' MUST (ipatokenOTPkey $ ipatokenOTPalgorithm $ ipatokenOTPdigits $ ipatokenTOTPclockOffset $ ipatokenTOTPtimeStep) X-ORIGIN 'IPA OTP')
objectClasses: (2.16.840.1.113730.3.8.16.2.2 NAME 'ipatokenTOTP' SUP ipaToken STRUCTURAL DESC 'TOTP Token Type' MUST (ipatokenOTPkey $ ipatokenOTPalgorithm $ ipatokenOTPdigits $ ipatokenTOTPclockOffset $ ipatokenTOTPtimeStep) MAY (ipatokenTOTPwatermark) X-ORIGIN 'IPA OTP')
objectClasses: (2.16.840.1.113730.3.8.16.2.3 NAME 'ipatokenRadiusProxyUser' SUP top AUXILIARY DESC 'Radius Proxy User' MAY (ipatokenRadiusConfigLink $ ipatokenRadiusUserName) X-ORIGIN 'IPA OTP')
objectClasses: (2.16.840.1.113730.3.8.16.2.4 NAME 'ipatokenRadiusConfiguration' SUP top STRUCTURAL DESC 'Proxy Radius Configuration' MUST (cn $ ipatokenRadiusServer $ ipatokenRadiusSecret) MAY (description $ ipatokenRadiusTimeout $ ipatokenRadiusRetries $ ipatokenUserMapAttribute) X-ORIGIN 'IPA OTP')
objectClasses: (2.16.840.1.113730.3.8.16.2.5 NAME 'ipatokenHOTP' SUP ipaToken STRUCTURAL DESC 'HOTP Token Type' MUST (ipatokenOTPkey $ ipatokenOTPalgorithm $ ipatokenOTPdigits $ ipatokenHOTPcounter) X-ORIGIN 'IPA OTP')

View File

@ -25,7 +25,8 @@ import os
import krbV
from ipapython.ipa_log_manager import *
from ipaserver.install import replication, installutils, bindinstance
from ipaserver.install import (replication, installutils, bindinstance,
cainstance, certs)
from ipalib import api, errors, util
from ipalib.constants import CACERT
from ipapython import ipautil, ipaldap, version, dogtag
@ -33,15 +34,16 @@ from ipapython.dn import DN
# dict of command name and tuples of min/max num of args needed
commands = {
"list":(0, 1, "[master fqdn]", ""),
"connect":(1, 2, "<master fqdn> [other master fqdn]",
"must provide the name of the servers to connect"),
"disconnect":(1, 2, "<master fqdn> [other master fqdn]",
"must provide the name of the server to disconnect"),
"del":(1, 1, "<master fqdn>",
"must provide hostname of master to delete"),
"re-initialize":(0, 0, "", ""),
"force-sync":(0, 0, "", "")
"list": (0, 1, "[master fqdn]", ""),
"connect": (1, 2, "<master fqdn> [other master fqdn]",
"must provide the name of the servers to connect"),
"disconnect": (1, 2, "<master fqdn> [other master fqdn]",
"must provide the name of the server to disconnect"),
"del": (1, 1, "<master fqdn>",
"must provide hostname of master to delete"),
"re-initialize": (0, 0, "", ""),
"force-sync": (0, 0, "", ""),
"set-renewal-master": (0, 1, "[master fqdn]", "")
}
@ -272,7 +274,12 @@ def del_master(realm, hostname, options):
except Exception, e:
sys.exit("There were issues removing a connection: %s" % e)
# 6. And clean up the removed replica DNS entries if any.
# 6. Pick CA renewal master
ca = cainstance.CAInstance(api.env.realm, certs.NSS_DIR)
if ca.is_renewal_master(hostname):
ca.set_renewal_master(options.host)
# 7. And clean up the removed replica DNS entries if any.
try:
if bindinstance.dns_container_exists(options.host, api.env.basedn,
dm_password=options.dirman_passwd):
@ -369,6 +376,21 @@ def force_sync(realm, thishost, fromhost, dirman_passwd):
except Exception, e:
sys.exit(str(e))
def set_renewal_master(realm, replica):
if not replica:
replica = installutils.get_fqdn()
ca = cainstance.CAInstance(realm, certs.NSS_DIR)
if ca.is_renewal_master(replica):
sys.exit("%s is already the renewal master" % replica)
try:
ca.set_renewal_master(replica)
except Exception, e:
sys.exit("Failed to set renewal master to %s: %s" % (replica, e))
print "%s is now the renewal master" % replica
def main():
options, args = parse_options()
@ -433,6 +455,11 @@ def main():
replica1 = host
replica2 = args[1]
del_link(realm, replica1, replica2, dirman_passwd, options.force)
elif args[0] == 'set-renewal-master':
replica = None
if len(args) > 1:
replica = args[1]
set_renewal_master(realm, replica)
try:
main()

View File

@ -699,7 +699,6 @@ def main():
CA.configure_certmonger_renewal()
CA.import_ra_cert(dir + "/ra.p12")
CA.fix_ra_perms()
services.knownservices.httpd.restart()
# The DS instance is created before the keytab, add the SSL cert we
# generated

View File

@ -28,7 +28,7 @@ import socket
from ipapython import ipautil
from ipaserver.install import replication, dsinstance, installutils
from ipaserver.install import bindinstance
from ipaserver.install import bindinstance, cainstance, certs
from ipaserver.plugins import ldap2
from ipapython import version, ipaldap
from ipalib import api, errors, util
@ -665,6 +665,7 @@ def del_master(realm, hostname, options):
# Check that we are not leaving the installation without CA and/or DNS
this_services = []
other_services = []
ca_hostname = None
for master_cn in [m.single_value['cn'] for m in masters]:
master_dn = DN(('cn', master_cn), ('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), ipautil.realm_to_suffix(realm))
@ -679,6 +680,8 @@ def del_master(realm, hostname, options):
this_services = services_cns
else:
other_services.append(services_cns)
if ca_hostname is None and 'CA' in services_cns:
ca_hostname = master_cn
if 'CA' in this_services and not any(['CA' in o for o in other_services]):
print "Deleting this server is not allowed as it would leave your installation without a CA."
@ -688,6 +691,14 @@ def del_master(realm, hostname, options):
print "Deleting this server will leave your installation without a DNS."
if not options.force and not ipautil.user_input("Continue to delete?", False):
sys.exit("Deletion aborted")
# Pick CA renewal master
ca = cainstance.CAInstance(api.env.realm, certs.NSS_DIR)
if ca.is_renewal_master(hostname):
try:
ca.set_renewal_master(options.host)
except errors.NotFound:
ca.set_renewal_master(ca_hostname)
else:
print "Skipping calculation to determine if one or more masters would be orphaned."

View File

@ -68,7 +68,7 @@ from ipapython import sysrestore
from ipapython.ipautil import *
from ipapython import ipautil
from ipapython import dogtag
from ipalib import api, errors, util
from ipalib import api, errors, util, x509
from ipapython.config import IPAOptionParser
from ipalib.x509 import load_certificate_from_file, load_certificate_chain_from_file
from ipalib.util import validate_domain_name
@ -179,11 +179,11 @@ def parse_options():
cert_group = OptionGroup(parser, "certificate system options")
cert_group.add_option("", "--external-ca", dest="external_ca", action="store_true",
default=False, help="Generate a CSR to be signed by an external CA")
default=False, help="Generate a CSR for the IPA CA certificate to be signed by an external CA")
cert_group.add_option("", "--external_cert_file", dest="external_cert_file",
help="PEM file containing a certificate signed by the external CA")
help="File containing the IPA CA certificate signed by the external CA in PEM format")
cert_group.add_option("", "--external_ca_file", dest="external_ca_file",
help="PEM file containing the external CA chain")
help="File containing the external CA certificate chain in PEM format")
cert_group.add_option("--no-pkinit", dest="setup_pkinit", action="store_false",
default=True, help="disables pkinit setup steps")
cert_group.add_option("--dirsrv_pkcs12", dest="dirsrv_pkcs12",
@ -199,7 +199,7 @@ def parse_options():
cert_group.add_option("--pkinit_pin", dest="pkinit_pin",
help="The password of the Kerberos KDC PKCS#12 file")
cert_group.add_option("--root-ca-file", dest="root_ca_file",
help="PEM file with root CA certificate(s) to trust")
help="PEM file containing the CA certificate for the PKCS#12 files")
cert_group.add_option("--subject", action="callback", callback=subject_callback,
type="string",
help="The certificate subject base (default O=<realm-name>)")
@ -289,10 +289,6 @@ def parse_options():
if options.pkinit_pkcs12 and options.pkinit_pin is None:
parser.error("You must specify --pkinit_pin with --pkinit_pkcs12")
if options.dirsrv_pkcs12 and not options.root_ca_file:
parser.error(
"--root-ca-file must be given with the PKCS#12 options.")
if (options.external_cert_file or options.external_ca_file) and options.dirsrv_pkcs12:
parser.error(
"PKCS#12 options cannot be used with the external CA options.")
@ -513,6 +509,10 @@ def uninstall():
os.remove(ANSWER_CACHE)
except Exception:
pass
try:
os.remove(paths.ROOT_IPA_CSR)
except Exception:
pass
# ipa-client-install removes /etc/ipa/default.conf
@ -686,13 +686,21 @@ def main():
if options.external_ca:
if cainstance.is_step_one_done():
print "CA is already installed.\nRun the installer with --external_cert_file and --external_ca_file."
print ("CA is already installed.\nRun the installer with "
"--external_cert_file and --external_ca_file.")
sys.exit(1)
if ipautil.file_exists(paths.ROOT_IPA_CSR):
print ("CA CSR file %s already exists.\nIn order to continue "
"remove the file and run the installer again." %
paths.ROOT_IPA_CSR)
sys.exit(1)
elif options.external_cert_file:
if not cainstance.is_step_one_done():
# This can happen if someone passes external_ca_file without
# already having done the first stage of the CA install.
print "CA is not installed yet. To install with an external CA is a two-stage process.\nFirst run the installer with --external-ca."
print ("CA is not installed yet. To install with an external CA "
"is a two-stage process.\nFirst run the installer with "
"--external-ca.")
sys.exit(1)
# This will override any settings passed in on the cmdline
@ -901,7 +909,7 @@ def main():
if options.http_pin is None:
sys.exit("%s unlock password required" % options.http_pkcs12)
http_pkcs12_info = (options.http_pkcs12, options.http_pin)
http_cert_name = installutils.check_pkcs12(
http_ca_cert = installutils.check_pkcs12(
http_pkcs12_info, ca_file, host_name)
if options.dirsrv_pkcs12:
@ -912,7 +920,7 @@ def main():
if options.dirsrv_pin is None:
sys.exit("%s unlock password required" % options.dirsrv_pkcs12)
dirsrv_pkcs12_info = (options.dirsrv_pkcs12, options.dirsrv_pin)
dirsrv_cert_name = installutils.check_pkcs12(
dirsrv_ca_cert = installutils.check_pkcs12(
dirsrv_pkcs12_info, ca_file, host_name)
if options.pkinit_pkcs12:
@ -924,6 +932,11 @@ def main():
sys.exit("%s unlock password required" % options.pkinit_pkcs12)
pkinit_pkcs12_info = (options.pkinit_pkcs12, options.pkinit_pin)
if (options.http_pkcs12 and options.dirsrv_pkcs12 and
http_ca_cert != dirsrv_ca_cert):
sys.exit("%s and %s are not signed by the same CA certificate" %
(options.http_pkcs12, options.dirsrv_pkcs12))
if not options.dm_password:
dm_password = read_dm_password()
@ -1053,8 +1066,7 @@ def main():
ntp.create_instance()
if options.dirsrv_pkcs12:
ds = dsinstance.DsInstance(fstore=fstore,
cert_nickname=dirsrv_cert_name)
ds = dsinstance.DsInstance(fstore=fstore)
ds.create_instance(realm_name, host_name, domain_name,
dm_password, dirsrv_pkcs12_info,
idstart=options.idstart, idmax=options.idmax,
@ -1108,7 +1120,7 @@ def main():
ca.publish_ca_cert(CACERT)
else:
# Put the CA cert where other instances expect it
shutil.copy(options.root_ca_file, CACERT)
x509.write_certificate(http_ca_cert, CACERT)
os.chmod(CACERT, 0444)
# we now need to enable ssl on the ds

View File

@ -598,6 +598,11 @@ def named_enable_dnssec():
"""
Enable dnssec in named.conf
"""
if not bindinstance.named_conf_exists():
# DNS service may not be configured
root_logger.info('DNS is not configured')
return False
if not sysupgrade.get_upgrade_state('named.conf', 'dnssec_enabled'):
root_logger.info('[Enabling "dnssec-enable" configuration in DNS]')
try:
@ -672,24 +677,25 @@ def certificate_renewal_update(ca):
return False
# State not set, lets see if we are already configured
for nss_dir, nickname, ca_name, pre_command, post_command in requests:
criteria = (
('cert_storage_location', nss_dir, certmonger.NPATH),
('cert_nickname', nickname, None),
('ca_name', ca_name, None),
)
for request in requests:
nss_dir, nickname, ca_name, pre_command, post_command = request
criteria = {
'cert-database': nss_dir,
'cert-nickname': nickname,
'ca-name': ca_name,
}
request_id = certmonger.get_request_id(criteria)
if request_id is None:
break
val = certmonger.get_request_value(request_id, 'pre_certsave_command')
val = certmonger.get_request_value(request_id, 'cert-presave-command')
if val is not None:
val = val.split(' ', 1)[0]
val = os.path.basename(val)
if pre_command != val:
break
val = certmonger.get_request_value(request_id, 'post_certsave_command')
val = certmonger.get_request_value(request_id, 'post-certsave-command')
if val is not None:
val = val.split(' ', 1)[0]
val = os.path.basename(val)
@ -1074,6 +1080,7 @@ def main():
sub_dict['SUBJECT_BASE'] = subject_base
ca = cainstance.CAInstance(api.env.realm, certs.NSS_DIR)
ca.backup_config()
# migrate CRL publish dir before the location in ipa.conf is updated
ca_restart = migrate_crl_publish_dir(ca)

View File

@ -42,6 +42,9 @@ Manages the CA replication agreements of an IPA server.
\fBforce\-sync\fR
\- Immediately flush any data to be replicated from a server specified with the \-\-from option
.TP
\fBset\-renewal\-master\fR [SERVER]
\- Set CA server which handles renewal of CA subsystem certificates to SERVER
.TP
The connect and disconnect options are used to manage the replication topology. When a replica is created it is only connected with the master that created it. The connect option may be used to connect it to other existing replicas.
.TP
The disconnect option cannot be used to remove the last link of a replica. To remove a replica from the topology use the del option.

View File

@ -85,13 +85,17 @@ An unattended installation that will never prompt for user input
.SS "CERTIFICATE SYSTEM OPTIONS"
.TP
\fB\-\-external\-ca\fR
Generate a CSR to be signed by an external CA
Generate a CSR for the IPA CA certificate to be signed by an external CA.
.TP
\fB\-\-external_cert_file\fR=\fIFILE\fR
PEM file containing a certificate signed by the external CA. Must be given with \-\-external_ca_file.
File containing the IPA CA certificate signed by the external CA in PEM format. Must be given with \-\-external_ca_file.
.TP
\fB\-\-external_ca_file\fR=\fIFILE\fR
PEM file containing the external CA chain
File containing the external CA certificate chain in PEM format. Must be given with \-\-external_cert_file.
If the CA certificate chain is in PKCS#7 format you can convert it to PEM using:
openssl pkcs7 -in PKCS7_FILE -print_certs -out PEM_FILE
.TP
\fB\-\-no\-pkinit\fR
Disables pkinit setup steps
@ -114,6 +118,9 @@ The password of the Apache Server PKCS#12 file
\fB\-\-pkinit_pin\fR=\fIPKINIT_PIN\fR
The password of the Kerberos KDC PKCS#12 file
.TP
\fB\-\-root\-ca\-file\fR=\fIFILE\fR
PEM file containing the CA certificate of the CA which issued the Directory Server, Apache Server and Kerberos KDC SSL certificates. Use this option if the CA certificate is not present in the PKCS#12 files.
.TP
\fB\-\-subject\fR=\fISUBJECT\fR
The certificate subject base (default O=REALM.NAME)

View File

@ -189,7 +189,6 @@ div[name=settings].facet-group li a {
/* --- Facet error --- */
.facet-error {
padding: 2em 15em;
background-color: white;
}
@ -199,7 +198,10 @@ div[name=settings].facet-group li a {
.facet-error .error-details {
margin-top: 2em;
font-family: monospace;
}
.facet-error .error-details code {
white-space: pre;
}
/* ---- Search Facet ---- */
@ -252,15 +254,6 @@ div[name=settings].facet-group li a {
word-wrap: break-word;
}
.action-button-disabled,
.action-button-disabled:focus,
.action-button-disabled:hover {
color: gray;
cursor: default;
text-decoration: none;
outline: none;
}
.aci-attribute-table tbody {
height: 10em;
}
@ -345,7 +338,7 @@ table.scrollable tbody {
.option_widget {
list-style-type: none;
margin: 0;
padding: 0;
padding: 0 0 0 1px;
}
.option_widget.nested {
@ -365,6 +358,19 @@ table.scrollable tbody {
max-width: 150px;
}
.option_widget.columns.attribute_widget {
position: relative;
overflow-y: auto;
max-height: 36em;
}
.option_widget.columns.attribute_widget > li {
float: left;
width: 50%;
min-width: 90px;
max-width: 200px;
}
.combobox-widget-input {
position: relative;
}

View File

@ -4,7 +4,7 @@
.global-activity-indicator {
bottom: initial;
bottom: auto;
height: auto;
background-color: rgba(0, 0, 0, 0.3);
color: white;

View File

@ -247,26 +247,26 @@ define([
on_phase_error: function(error) {
window.console.error(error);
error = error || {};
var name = error.name || 'Runtime error';
var error_container = $('<div/>', {
'class': 'facet-content facet-error'
}).appendTo($('.content').empty());
'class': 'container facet-content facet-error'
}).appendTo($('.app-container .content').empty());
error_container.append('<h1>'+name+'</h1>');
var details = $('<div/>', {
'class': 'error-details'
}).appendTo(error_container);
details.append('<p> Web UI got in unrecoverable state during "'+error.phase+'" phase.</p>');
if (error.name) window.console.error(error.name);
if (error.results) {
var msg = {
message: error.results.message,
stack: error.results.stack
};
details.append('<strong>Technical details:</strong>');
details.append('<p>'+JSON.stringify(msg)+'</p>');
var msg = error.results.message;
var stack = error.results.stack.toString();
window.console.error(msg);
window.console.error(stack);
details.append('<h3>Technical details:</h3>');
details.append($('<div/>', { text: error.results.message }));
details.append($('<div/>').append($('<code/>', { text: stack })));
}
},

View File

@ -544,9 +544,17 @@ return {
aci.attributes_widget = function(spec) {
spec = spec || {};
spec.layout = spec.layout || 'columns attribute_widget';
spec.sort = spec.sort === undefined ? true : spec.sort;
var that = IPA.checkboxes_widget(spec);
/**
* Additional options which are not defined in metadata
* @property {string[]}
*/
that.custom_options = spec.custom_options || [];
that.object_type = spec.object_type;
that.skip_unmatched = spec.skip_unmatched === undefined ? false : spec.skip_unmatched;
@ -554,79 +562,93 @@ aci.attributes_widget = function(spec) {
that.create = function(container) {
that.container = container;
that.widget_create(container);
var attr_container = $('<div/>', {
'class': 'aci-attribute-table-container'
}).appendTo(container);
that.$node = that.table = $('<table/>', {
id: id,
name: that.name,
'class': 'table table-bordered table-condensed aci-attribute-table scrollable'
}).
append('<thead/>').
append('<tbody/>').
appendTo(attr_container);
var tr = $('<tr></tr>').appendTo($('thead', that.table));
var th = $('<th/>').appendTo(tr);
IPA.standalone_option({
type: "checkbox",
click: function() {
$('.aci-attribute', that.table).
prop('checked', $(this).prop('checked'));
that.value_changed.notify([], that);
that.emit('value-change', { source: that });
}
}, th);
tr.append($('<th/>', {
'class': 'aci-attribute-column',
html: text.get('@i18n:objects.aci.attribute')
}));
that.controls = $('<div/>', {
'class': 'form-inline controls'
});
that.controls.appendTo(container);
that.create_search_filter(that.controls);
that.create_add_control(that.controls);
if (that.undo) {
that.create_undo(container);
that.create_undo(that.controls);
}
if (that.object_type) {
that.populate(that.object_type);
}
that.owb_create(container);
that.create_error_link(container);
};
that.create_options = function(options) {
var tbody = $('tbody', that.table);
that.create_search_filter = function(container) {
var filter_container = $('<div/>', {
'class': 'search-filter'
});
for (var i=0; i<options.length ; i++){
var option = options[i];
var value = option.value.toLowerCase();
var tr = $('<tr/>').appendTo(tbody);
that.filter = $('<input/>', {
type: 'text',
name: 'filter',
'class': 'form-control',
placeholder: text.get('@i18n:objects.permission.filter')
}).appendTo(filter_container);
var td = $('<td/>').appendTo(tr);
var name = that.get_input_name();
var id = that._option_next_id + name;
var opt = IPA.standalone_option({
id: id,
type: 'checkbox',
name: name,
value: value,
'class': 'aci-attribute',
change: function() {
that.value_changed.notify([], that);
that.emit('value-change', { source: that });
that.filter.keyup(function(e) {
that.filter_options();
});
var find_button = IPA.action_button({
name: 'find',
icon: 'fa-search',
click: function() {
that.filter_options();
return false;
}
}).appendTo(filter_container);
filter_container.appendTo(container);
};
that.create_add_control = function(container) {
that.add_button = IPA.button({
label: '@i18n:buttons.add',
click: that.show_add_dialog
});
container.append(' ');
that.add_button.appendTo(container);
};
that.show_add_dialog = function() {
var dialog = IPA.form_dialog({
name: "add_option",
title: "@i18n:objects.permission.add_custom_attr",
fields: [
{
name: 'attr',
label: '@i18n:objects.permission.attribute',
required: true
}
}, td);
td = $('<td/>').appendTo(tr);
td.append($('<label/>',{
text: value,
'for': id
}));
option.input_node = opt[0];
that.new_option_id();
}
]
});
dialog.on_confirm = function() {
if (!dialog.validate()) return;
var attr = dialog.get_field('attr');
var value = attr.get_value()[0];
that.add_custom_option(value, false, true, true);
dialog.close();
};
dialog.open();
};
that.filter_options = function() {
$("li", that.$node).each(function() {
var item = $(this);
if(item.find('input').val().indexOf(that.filter.val()) === -1) {
item.css('display','none');
} else {
item.css('display','inline');
}
});
};
that.update = function(values) {
@ -646,14 +668,12 @@ aci.attributes_widget = function(spec) {
that.populate(that.object_type);
that.append();
that.create_options(that.options);
that.owb_create(that.container);
that.owb_update(values);
};
that.populate = function(object_type) {
$('tbody tr', that.table).remove();
if (!object_type || object_type === '') return;
var metadata = metadata_provider.get('@mo:'+object_type);
@ -666,21 +686,31 @@ aci.attributes_widget = function(spec) {
that.append = function() {
if (!that.values) return;
var unmatched = [];
for (var i=0; i<that.values.length; i++) {
if (!that.has_option(that.values[i])) {
unmatched.push(that.values[i]);
function add_unmatched(source) {
for (var i=0, l=source.length; i<l; i++) {
if (!that.has_option(source[i])) {
that.add_option(source[i], true /* suppress update */);
}
}
}
if (unmatched.length > 0 && !that.skip_unmatched) {
that.options.push.apply(that.options, that.prepare_options(unmatched));
add_unmatched(that.custom_options);
if (that.values && !that.skip_unmatched) {
add_unmatched(that.values);
}
};
that.add_custom_option = function(name, to_custom, check, update) {
var value = (name || '').toLowerCase();
if (to_custom) that.custom_options.push(value);
if (check) that.values.push(value);
if (update) that.update(that.values);
};
that.has_option = function(value) {
var o = that.options;
for (var i=0, l=o.length; i<l; i++) {
@ -689,10 +719,6 @@ aci.attributes_widget = function(spec) {
return false;
};
that.show_undo = function() {
$(that.undo_span).css('display', 'inline-block');
};
return that;
};
@ -873,6 +899,16 @@ aci.permission_target_policy = function (spec) {
var attribute_table = that.permission_target.widgets.get_widget('attrs');
var skip_unmatched_org = attribute_table.skip_unmatched;
attribute_table.object_type = type;
// UI doesn't always know what are the possible attributes.
// In case of managed permissions, one of the possible lists is in ipapermdefaultattr.
var default_attrs = that.container.fields.get_field('ipapermdefaultattr');
if (default_attrs && default_attrs.enabled) { // if managed permission
attribute_table.custom_options = default_attrs.get_value();
} else {
attribute_table.custom_options = [];
}
// skip values which don't belong to new type. Bug #2617
attribute_table.skip_unmatched = skip_unmatched || skip_unmatched_org;
attribute_field.reset();
@ -936,9 +972,7 @@ aci.permission_target_policy = function (spec) {
var widget = that.permission_target.widgets.get_widget(target_info.name);
var field = that.container.fields.get_field(target_info.name);
that.permission_target.set_row_visible(target_info.name, visible);
var managed_f = aci.managed_fields.indexOf(target_info.name) > -1;
var enabled = !(managed_f && that.managed) && visible && !that.system;
field.set_enabled(enabled);
field.set_enabled(visible);
field.set_required(visible && target_info.required);
widget.set_visible(visible);
};
@ -1007,7 +1041,9 @@ aci.permission_managed_policy = function (spec) {
var that = IPA.facet_policy();
that.post_load = function(data) {
var permtype = data.result.result.ipapermissiontype;
var result = data.result.result;
var permtype = result.ipapermissiontype;
var managed = permtype && permtype.indexOf("MANAGED") > -1;
var system = permtype && permtype.indexOf("SYSTEM") > -1 && permtype.length === 1;
var m_section = that.container.widgets.get_widget("managed");
@ -1018,7 +1054,14 @@ aci.permission_managed_policy = function (spec) {
var field = fields[i];
if (field.read_only) continue;
var managed_f = aci.managed_fields.indexOf(field.name) > -1;
field.set_enabled(!system && !(managed_f && managed));
field.set_writable(!system && !(managed_f && managed) && field.writable);
}
// Bind rule type cannot be changed if permission is in a privilege
var privileges = result.member_privilege;
if (privileges && privileges.length > 0) {
var f = that.container.fields.get_field('ipapermbindruletype');
f.set_writable(false);
}
};
@ -1080,4 +1123,4 @@ aci.register = function() {
phases.on('registration', aci.register);
return aci;
});
});

View File

@ -166,12 +166,13 @@ IPA.entity_adder_dialog = function(spec) {
function show_edit_page(entity,result) {
var pkey_name = entity.metadata.primary_key;
var pkey = result[pkey_name];
if (pkey instanceof Array) {
pkey = pkey[0];
if (!(pkey instanceof Array)) {
pkey = [pkey];
}
rpc.extract_objects(pkey);
var pkeys = that.pkey_prefix.slice(0);
pkeys.push(pkey);
pkeys.push(pkey[0]);
navigation.show_entity(that.entity.name, 'default', pkeys);
}

View File

@ -493,30 +493,24 @@ IPA.association_table_widget = function (spec) {
that.table_create(container);
that.remove_button = IPA.action_button({
that.remove_button = IPA.button_widget({
name: 'remove',
label: '@i18n:buttons.remove',
icon: 'fa-trash-o',
'class': 'action-button-disabled',
click: function() {
if (!that.remove_button.hasClass('action-button-disabled')) {
that.remove_handler();
}
return false;
}
}).appendTo(that.buttons);
enabled: false,
button_class: 'btn btn-link',
click: that.remove_handler
});
that.remove_button.create(that.buttons);
that.add_button = IPA.action_button({
that.add_button = IPA.button_widget({
name: 'add',
label: '@i18n:buttons.add',
icon: 'fa-plus',
click: function() {
if (!that.add_button.hasClass('action-button-disabled')) {
that.add_handler();
}
return false;
}
}).appendTo(that.buttons);
button_class: 'btn btn-link',
click: that.add_handler
});
that.add_button.create(that.buttons);
};
that.add_handler = function() {
@ -561,14 +555,13 @@ IPA.association_table_widget = function (spec) {
that.set_enabled = function(enabled) {
that.table_set_enabled(enabled);
if (enabled) {
if(that.add_button) {
that.add_button.removeClass('action-button-disabled');
}
} else {
$('.action-button', that.table).addClass('action-button-disabled');
if (!enabled) {
that.unselect_all();
}
if (that.add_button) {
that.add_button.set_enabled(enabled);
that.remove_button.set_enabled(false);
}
};
that.select_changed = function() {
@ -576,11 +569,7 @@ IPA.association_table_widget = function (spec) {
var values = that.get_selected_values();
if (that.remove_button) {
if (values.length === 0) {
that.remove_button.addClass('action-button-disabled');
} else {
that.remove_button.removeClass('action-button-disabled');
}
that.remove_button.set_enabled(values.length > 0);
}
};

View File

@ -1395,8 +1395,7 @@ IPA.confirm_dialog = function(spec) {
name: 'ok',
label: that.ok_label,
click: function() {
that.confirmed = true;
that.close();
that.on_confirm();
}
});
@ -1418,6 +1417,18 @@ IPA.confirm_dialog = function(spec) {
return that;
};
/**
* General form dialog with confirmation feature
* @class dialog.form_dialog
* @extends {IPA.confirm_dialog}
*/
IPA.form_dialog = function(spec) {
var that = IPA.confirm_dialog(spec);
that.create_content = that.dialog_create_content;
return that;
};
/**
* Confirm mixin
*

View File

@ -1739,42 +1739,35 @@ IPA.dns.record_type_table_widget = function(spec) {
container.addClass('dnstype-table');
that.remove_button = IPA.action_button({
that.remove_button = IPA.button_widget({
name: 'remove',
label: '@i18n:buttons.remove',
icon: 'fa-trash-o',
'class': 'action-button-disabled',
click: function() {
if (!that.remove_button.hasClass('action-button-disabled')) {
that.remove_handler();
}
return false;
}
}).appendTo(that.buttons);
enabled: false,
button_class: 'btn btn-link',
click: that.remove_handler
});
that.remove_button.create(that.buttons);
that.add_button = IPA.action_button({
that.add_button = IPA.button_widget({
name: 'add',
label: '@i18n:buttons.add',
icon: 'fa-plus',
click: function() {
if (!that.add_button.hasClass('action-button-disabled')) {
that.add_handler();
}
return false;
}
}).appendTo(that.buttons);
button_class: 'btn btn-link',
click: that.add_handler
});
that.add_button.create(that.buttons);
};
that.set_enabled = function(enabled) {
that.table_set_enabled(enabled);
if (enabled) {
if(that.add_button) {
that.add_button.removeClass('action-button-disabled');
}
} else {
$('.action-button', that.table).addClass('action-button-disabled');
if (!enabled) {
that.unselect_all();
}
if (that.add_button) {
that.add_button.set_enabled(enabled);
that.remove_button.set_enabled(false);
}
};
that.select_changed = function() {
@ -1782,11 +1775,7 @@ IPA.dns.record_type_table_widget = function(spec) {
var values = that.get_selected_values();
if (that.remove_button) {
if (values.length === 0) {
that.remove_button.addClass('action-button-disabled');
} else {
that.remove_button.removeClass('action-button-disabled');
}
that.remove_button.set_enabled(values.length > 0);
}
};

View File

@ -672,6 +672,8 @@ exp.facet = IPA.facet = function(spec, no_init) {
if (!that.dom_node) {
that.create();
} else if (!that.dom_node.parentElement) {
construct.place(that.dom_node[0], that.container_node);
}
var state = that.state.clone();
@ -728,6 +730,9 @@ exp.facet = IPA.facet = function(spec, no_init) {
*/
that.hide = function() {
that.is_shown = false;
if (that.dom_node[0].parentElement) {
that.container_node.removeChild(that.dom_node[0]);
}
that.dom_node.removeClass('active-facet');
};

View File

@ -285,6 +285,8 @@ define(['dojo/_base/declare',
if (!this.dom_node) {
this.create();
this.render_children();
} else if (!this.dom_node.parentElement) {
construct.place(this.dom_node, this.container_node);
}
dom_class.add(this.dom_node, 'active-facet');
@ -295,6 +297,9 @@ define(['dojo/_base/declare',
* Un-mark itself as active facet
*/
hide: function() {
if (this.dom_node.parentElement) {
this.container_node.removeChild(this.dom_node);
}
dom_class.remove(this.dom_node, 'active-facet');
this.emit('hide', { source: this });
},

View File

@ -450,6 +450,12 @@ field.field = IPA.field = function(spec) {
var writable = true;
function has_write(record, param) {
var rights = record.attributelevelrights[param];
var has = !!rights && rights.indexOf('w') > -1;
return has;
}
if (that.metadata) {
if (that.metadata.primary_key) {
writable = false;
@ -460,21 +466,21 @@ field.field = IPA.field = function(spec) {
}
}
if (record && record.attributelevelrights) {
if (record && record.attributelevelrights && writable) {
var rights = record.attributelevelrights[that.acl_param];
var oc_rights= record.attributelevelrights['objectclass'];
var write_oc = oc_rights && oc_rights.indexOf('w') > -1;
var write_attr = has_write(record, that.acl_param);
var write_all = has_write(record, '*');
// Some objects in LDAP may not have set proper object class and
// Some objects in LDAP may not have proper object class set and
// therefore server doesn't send proper attribute rights. Flag
// 'w_if_no_aci' should be used when we want to ensure that UI
// shows edit interface in such cases. Usable only when user can
// modify object classes.
// For all others, lack of rights means no write.
if ((!rights && !(that.flags.indexOf('w_if_no_aci') > -1 && write_oc)) ||
(rights && rights.indexOf('w') < 0)) {
writable = false;
}
var write_oc = has_write(record, 'objectclass');
var may_add_oc = !rights && write_oc && that.flags.indexOf('w_if_no_aci') > -1;
// If no rights, change writable to False:
writable = write_attr || write_all || may_add_oc;
}
that.set_writable(writable);

View File

@ -184,12 +184,7 @@ IPA.hbac.test_facet = function(spec) {
name: 'prev',
label: '@i18n:widget.prev',
icon: 'fa-chevron-left',
click: function() {
if (!that.prev_button.hasClass('action-button-disabled')) {
that.prev();
}
return false;
}
click: that.prev
}).appendTo(buttons);
buttons.append(' ');
@ -199,12 +194,7 @@ IPA.hbac.test_facet = function(spec) {
name: 'next',
label: '@i18n:widget.next',
icon: 'fa-chevron-right',
click: function() {
if (!that.next_button.hasClass('action-button-disabled')) {
that.next();
}
return false;
}
click: that.next
}).appendTo(buttons);
};
@ -535,12 +525,7 @@ IPA.hbac.test_run_facet = function(spec) {
name: 'run_test',
label: '@i18n:objects.hbactest.run_test',
icon: 'fa-gear',
click: function() {
if (!that.run_button.hasClass('action-button-disabled')) {
that.run();
}
return false;
}
click: that.run
}).appendTo(button_panel);
var result_panel = $('<div/>', {
@ -608,12 +593,7 @@ IPA.hbac.test_run_facet = function(spec) {
name: 'prev',
label: '@i18n:widget.prev',
icon: 'fa-chevron-left',
click: function() {
if (!that.prev_button.hasClass('action-button-disabled')) {
that.prev();
}
return false;
}
click: that.prev
}).appendTo(buttons);
buttons.append(' ');
@ -622,12 +602,7 @@ IPA.hbac.test_run_facet = function(spec) {
name: 'new_test',
label: '@i18n:objects.hbactest.new_test',
icon: 'fa-repeat',
click: function() {
if (!that.new_test_button.hasClass('action-button-disabled')) {
that.new_test();
}
return false;
}
click: that.new_test
}).appendTo(buttons);
};

View File

@ -710,30 +710,24 @@ IPA.sudo.options_section = function(spec) {
that.table.table_create(container);
that.remove_button = IPA.action_button({
that.remove_button = IPA.button_widget({
name: 'remove',
label: '@i18n:buttons.remove',
icon: 'fa-trash-o',
'class': 'action-button-disabled',
click: function() {
if (!that.remove_button.hasClass('action-button-disabled')) {
that.remove_handler();
}
return false;
}
}).appendTo(that.table.buttons);
enabled: false,
button_class: 'btn btn-link',
click: that.remove_handler
});
that.remove_button.create(that.table.buttons);
that.add_button = IPA.action_button({
that.add_button = IPA.button_widget({
name: 'add',
label: '@i18n:buttons.add',
icon: 'fa-plus',
click: function() {
if (!that.add_button.hasClass('action-button-disabled')) {
that.add_handler();
}
return false;
}
}).appendTo(that.table.buttons);
button_class: 'btn btn-link',
click: that.add_handler
});
that.add_button.create(that.table.buttons);
};
that.table.select_changed = function() {
@ -741,11 +735,7 @@ IPA.sudo.options_section = function(spec) {
var values = that.table.get_selected_values();
if (that.remove_button) {
if (values.length === 0) {
that.remove_button.addClass('action-button-disabled');
} else {
that.remove_button.removeClass('action-button-disabled');
}
that.remove_button.set_enabled(values.length > 0);
}
};

View File

@ -24,6 +24,7 @@
define(['dojo/_base/array',
'dojo/_base/lang',
'dojo/dom-construct',
'dojo/Evented',
'dojo/has',
'dojo/keys',
@ -43,7 +44,7 @@ define(['dojo/_base/array',
'./util',
'exports'
],
function(array, lang, Evented, has, keys, on, string, topic, builder,
function(array, lang, construct, Evented, has, keys, on, string, topic, builder,
datetime, entity_mod, IPA, $, navigation, phases, reg, rpc, text, util, exp) {
/**
@ -568,6 +569,7 @@ IPA.input_widget = function(spec) {
var changed = writable !== that.writable;
that.writable = writable;
that.update_read_only();
if (changed) {
that.emit('writable-change', { source: that, writable: writable });
@ -584,12 +586,25 @@ IPA.input_widget = function(spec) {
var changed = read_only !== that.read_only;
that.read_only = read_only;
that.update_read_only();
if (changed) {
that.emit('readonly-change', { source: that, read_only: read_only });
}
};
/**
* Update widget's HTML based on `read_only` and `writable` properties
* @protected
*/
that.update_read_only = function() {
var input = that.get_input();
if (input) {
var ro = that.is_writable();
input.prop('readOnly', !ro);
}
};
/**
* Focus input element
* @abstract
@ -635,6 +650,8 @@ IPA.input_widget = function(spec) {
that.widget_set_valid = that.set_valid;
that.widget_hide_undo = that.hide_undo;
that.widget_show_undo = that.show_undo;
that.widget_set_writable = that.set_writable;
that.widget_set_read_only = that.set_read_only;
return that;
};
@ -736,6 +753,7 @@ IPA.text_widget = function(spec) {
that.create_error_link(container);
that.set_enabled(that.enabled);
that.update_read_only();
that.update_input_group_state();
};
@ -744,18 +762,23 @@ IPA.text_widget = function(spec) {
*/
that.update = function(values) {
var value = values && values.length ? values[0] : '';
that.display_control.text(value);
that.input.val(value);
that.on_value_changed();
};
/**
* @inheritDoc
*/
that.update_read_only = function() {
if (!that.input) return;
if (!that.is_writable()) {
that.display_control.text(value);
that.display_control.css('display', '');
that.input_group.css('display', 'none');
} else {
that.input.val(value);
that.display_control.css('display', 'none');
that.input_group.css('display', '');
}
that.on_value_changed();
};
/**
@ -811,7 +834,10 @@ IPA.text_widget = function(spec) {
* visible content.
*/
that.update_input_group_state = function() {
var visible = $(':visible', that.input_group_btn).length > 0;
var children = that.input_group_btn.children();
var visible = $.grep(children, function(el, i) {
return $(el).css('display') !== 'none';
}).length > 0;
that.input_group.toggleClass('input-group', visible);
};
@ -1182,7 +1208,13 @@ IPA.multivalued_widget = function(spec) {
that.on_value_changed();
};
/** @inheritDoc */
that.update_read_only = function() {
that.update_add_link_visibility();
};
that.update_add_link_visibility = function() {
if (!that.add_link) return;
var visible = that.is_writable() && that.enabled;
if (visible) {
that.add_link.css('display', '');
@ -1191,6 +1223,40 @@ IPA.multivalued_widget = function(spec) {
}
};
that.update_row_buttons = function(row) {
var w = that.is_writable();
if (!that.enabled || !w) {
row.widget.hide_undo();
that.toggle_remove_link(row, false);
} else {
if (row.is_new || that.test_dirty_row(row)) {
row.widget.show_undo();
that.toggle_remove_link(row, false);
} else {
that.toggle_remove_link(row, w);
}
}
};
that.set_writable = function(writable) {
that.widget_set_writable(writable);
for (var i=0,l=that.rows.length; i<l; i++) {
var row = that.rows[i];
row.widget.set_writable(writable);
that.update_row_buttons(row);
}
};
that.set_read_only = function(read_only) {
that.widget_set_read_only(read_only);
for (var i=0,l=that.rows.length; i<l; i++) {
var row = that.rows[i];
row.widget.set_read_only(read_only);
that.update_row_buttons(row);
}
};
that.set_enabled = function(enabled) {
that.widget_set_enabled(enabled);
@ -1199,17 +1265,7 @@ IPA.multivalued_widget = function(spec) {
for (var i=0,l=that.rows.length; i<l; i++) {
var row = that.rows[i];
row.widget.set_enabled(enabled);
if (!enabled) {
row.widget.hide_undo();
that.toggle_remove_link(row, false);
} else {
if (row.is_new || that.test_dirty_row(row)) {
row.widget.show_undo();
} else if (that.is_writable()) {
that.toggle_remove_link(row, true);
}
}
that.update_row_buttons(row);
}
};
@ -1258,8 +1314,19 @@ IPA.option_widget_base = function(spec, that) {
that.name = spec.name;
that.label = spec.label;
that.tooltip = spec.tooltip;
that.sort = spec.sort === undefined ? false : spec.sort;
that.value_changed = that.value_changed || IPA.observer();
/**
* Value which should be check when no value supplied
* @type {string|null}
*/
that.default_value = spec.default_value || null;
/**
* Consider empty string as non-value -> enable setting default value in such case
* @type {string}
*/
that.default_on_empty = spec.default_on_empty === undefined ? true : spec.default_on_empty;
/**
@ -1378,17 +1445,32 @@ IPA.option_widget_base = function(spec, that) {
}
};
that.sort_options = function() {
var options = that.options.concat();
options.sort(function(a,b) {
if (a.value > b.value)
return 1;
if (a.value < b.value)
return -1;
return 0;
});
return options;
};
that.create_options = function(container) {
for (var i=0; i<that.options.length; i++) {
container = $(container)[0];
var options = that.options;
if (that.sort) options = that.sort_options();
for (var i=0, l=options.length; i<l; i++) {
var option_container = that.create_option_container();
var option = that.options[i];
var option = options[i];
that.create_option(option, option_container);
option_container.appendTo(container);
construct.place(option_container, container);
}
};
that.create_option_container = function() {
return $('<li/>');
return construct.create('li');
};
that._create_option = function(option, container) {
@ -1396,11 +1478,11 @@ IPA.option_widget_base = function(spec, that) {
var id = that._option_next_id + input_name;
var enabled = that.enabled && option.enabled;
var opt_cont = $('<span/>', {
var opt_cont = construct.create('span', {
"class": that.intput_type + '-cnt'
}).appendTo(container);
});
option.input_node = $('<input/>', {
option.input_node = construct.create('input', {
id: id,
type: that.input_type,
name: input_name,
@ -1408,15 +1490,16 @@ IPA.option_widget_base = function(spec, that) {
value: option.value,
title: option.tooltip || that.tooltip,
change: that.on_input_change
}).appendTo(opt_cont);
}, opt_cont);
option.label_node = $('<label/>', {
html: option.label || '',
option.label_node = construct.create('label', {
title: option.tooltip || that.tooltip,
'for': id
}).appendTo(opt_cont);
}, opt_cont);
option.label_node.textContent = option.label || '';
that.new_option_id();
construct.place(opt_cont, container);
};
that.create_option = function(option, container) {
@ -1444,7 +1527,6 @@ IPA.option_widget_base = function(spec, that) {
that.create = function(container) {
that.destroy();
that.create_options(that.$node);
var css_class = [that.css_class, 'option_widget', that.layout,
that.nested ? 'nested': ''].join(' ');
@ -1553,6 +1635,8 @@ IPA.option_widget_base = function(spec, that) {
that.update = function(values) {
var i;
var check = function(selector, uncheck) {
$(selector, that.$node).prop('checked', !uncheck);
};
@ -1562,49 +1646,48 @@ IPA.option_widget_base = function(spec, that) {
// uncheck all inputs
check(that._selector, true /*uncheck*/);
// enable/disable the inputs and their children
// they might be disabled later if not checked
var writable = !that.read_only && !!that.writable && that.enabled;
if (!that.nested) {
that.update_enabled(writable);
}
if (values && values.length > 0) {
if (that.default_on_empty && that.default_value !== null) {
for (var i=0; i<values.length; i++) {
if (values[i] === '') {
values[i] = that.default_value;
}
// use default value if none supplied
var def_used = false;
if (values && values.length > 0 && that.default_on_empty &&
that.default_value !== null) {
for (i=0; i<values.length; i++) {
if (values[i] === '') {
values[i] = that.default_value;
def_used = true;
}
}
} else if (!values || !values.length) {
var default_value = that.default_value || '';
values = [default_value];
def_used = true;
}
// check the option when option or some of its child should be
// checked
for (i=0; i<that.options.length; i++) {
var option = that.options[i];
var opt_vals = that.get_values(option);
var has_opt = array.some(values, function(val) {
return array.indexOf(opt_vals, val) > -1;
});
// check the option if it or some of its children should be checked
for (i=0; i<that.options.length; i++) {
var option = that.options[i];
var opt_vals = that.get_values(option);
var has_opt = array.some(values, function(val) {
return array.indexOf(opt_vals, val) > -1;
});
if (has_opt) {
check(that._selector+'[value="'+ option.value +'"]');
}
if (option.widget) {
option.widget.update_enabled(writable && has_opt, false);
}
if (has_opt) {
check(that._selector+'[value="'+ option.value +'"]');
}
} else {
// select default if none specified
if (that.default_value !== null) {
check(that._selector+'[value="'+that.default_value+'"]');
// default was selected instead of supplied value, hence notify
util.emit_delayed(that, 'value-change', { source: that });
} else {
// otherwise select empty
check(that._selector+'[value=""]');
// disable options without value
if (option.widget && !has_opt) {
option.widget.update_enabled(false);
}
}
// update nested
for (var j=0; j<that._child_widgets.length; j++) {
var widget = that._child_widgets[j];
widget.writable = that.writable;
@ -1612,6 +1695,11 @@ IPA.option_widget_base = function(spec, that) {
widget.enabled = that.enabled;
widget.update(values);
}
// notify if a value other than supplied was used
if (def_used) {
util.emit_delayed(that, 'value-change', { source: that });
}
}
if (that.on_value_changed) {
@ -1638,6 +1726,12 @@ IPA.option_widget_base = function(spec, that) {
}
};
that.update_read_only = function() {
// a little hack
var enabled = that.is_writable() && that.enabled;
that.update_enabled(enabled);
};
that.clear = function() {
@ -1841,6 +1935,12 @@ IPA.select_widget = function(spec) {
that.widget_create(container);
that.display_control = $('<p/>', {
name: that.name,
'class': 'form-control-static',
style: 'display: none;'
}).appendTo(container);
that.select = $('<select/>', {
name: that.name,
'class':'form-control',
@ -1897,10 +1997,11 @@ IPA.select_widget = function(spec) {
that.update = function(values) {
var old = that.save()[0];
var value = values[0];
var value = values[0] || "";
var option = $('option[value="'+value+'"]', that.select);
if (option.length) {
option.prop('selected', true);
that.display_control.text(option.text());
} else {
// default was selected instead of supplied value, hence notify
util.emit_delayed(that,'value-change', { source: that });
@ -1908,6 +2009,17 @@ IPA.select_widget = function(spec) {
that.on_value_changed();
};
that.update_read_only = function() {
if (!that.select) return;
if (!that.is_writable()) {
that.display_control.css('display', '');
that.select.css('display', 'none');
} else {
that.display_control.css('display', 'none');
that.select.css('display', '');
}
};
that.empty = function() {
$('option', that.select).remove();
};
@ -2011,14 +2123,18 @@ IPA.textarea_widget = function (spec) {
};
that.update = function(values) {
var read_only = !that.is_writable();
that.input.prop('readOnly', read_only);
var value = values && values.length ? values[0] : '';
that.input.val(value);
that.on_value_changed();
};
that.update_read_only = function() {
if (!that.input) return;
var read_only = !that.is_writable();
that.input.prop('readOnly', read_only);
};
that.clear = function() {
that.input.val('');
};
@ -3036,30 +3152,24 @@ IPA.attribute_table_widget = function(spec) {
that.create_buttons = function(container) {
that.remove_button = IPA.action_button({
that.remove_button = IPA.button_widget({
name: 'remove',
label: '@i18n:buttons.remove',
icon: 'fa-trash-o',
'class': 'action-button-disabled',
click: function() {
if (!that.remove_button.hasClass('action-button-disabled')) {
that.remove_handler();
}
return false;
}
}).appendTo(container);
enabled: false,
button_class: 'btn btn-link',
click: that.remove_handler
});
that.remove_button.create(container);
that.add_button = IPA.action_button({
that.add_button = IPA.button_widget({
name: 'add',
label: '@i18n:buttons.add',
icon: 'fa-plus',
click: function() {
if (!that.add_button.hasClass('action-button-disabled')) {
that.add_handler();
}
return false;
}
}).appendTo(container);
button_class: 'btn btn-link',
click: that.add_handler
});
that.add_button.create(container);
};
that.create = function(container) {
@ -3073,14 +3183,13 @@ IPA.attribute_table_widget = function(spec) {
that.set_enabled = function(enabled) {
that.table_set_enabled(enabled);
if (enabled) {
if(that.add_button) {
that.add_button.removeClass('action-button-disabled');
}
} else {
$('.action-button', that.table).addClass('action-button-disabled');
if (!enabled) {
that.unselect_all();
}
if (that.add_button) {
that.add_button.set_enabled(enabled);
that.remove_button.set_enabled(false);
}
};
that.select_changed = function() {
@ -3088,11 +3197,7 @@ IPA.attribute_table_widget = function(spec) {
var values = that.get_selected_values();
if (that.remove_button) {
if (values.length === 0) {
that.remove_button.addClass('action-button-disabled');
} else {
that.remove_button.removeClass('action-button-disabled');
}
that.remove_button.set_enabled(values.length > 0);
}
};
@ -3675,16 +3780,6 @@ IPA.combobox_widget = function(spec) {
that.update = function(values) {
that.close();
if (that.writable) {
that.text.css('display', 'none');
that.input.css('display', 'inline');
that.open_button.css('display', 'inline');
} else {
that.text.css('display', 'inline');
that.input.css('display', 'none');
that.open_button.css('display', 'none');
}
if (that.searchable) {
that.filter.empty();
}
@ -3711,6 +3806,19 @@ IPA.combobox_widget = function(spec) {
that.on_value_changed();
};
that.update_read_only = function() {
if (!that.input) return;
if (that.is_writable()) {
that.text.css('display', 'none');
that.input.css('display', 'inline');
that.open_button.css('display', 'inline');
} else {
that.text.css('display', 'inline');
that.input.css('display', 'none');
that.open_button.css('display', 'none');
}
};
that.set_value = function(value) {
that.text.text(value);
that.input.val(value);
@ -4113,6 +4221,12 @@ IPA.button_widget = function(spec) {
*/
that['class'] = spec['class'];
/**
* Override for button classes
* @property {string}
*/
that.button_class = spec.button_class;
/**
* Icon name
* @property {string}
@ -4126,7 +4240,7 @@ IPA.button_widget = function(spec) {
*/
that.on_click = function() {
if (that.click) {
if (that.click && that.enabled) {
that.click();
}
return false;
@ -4140,6 +4254,7 @@ IPA.button_widget = function(spec) {
title: that.tooltip,
label: that.label,
'class': that['class'],
button_class: that.button_class,
style: that.style,
icon: that.icon,
click: that.on_click
@ -4147,6 +4262,7 @@ IPA.button_widget = function(spec) {
that.container = that.button;
that.set_enabled(that.enabled);
return that.button;
};
/** @inheritDoc */

View File

@ -191,7 +191,7 @@ define(['dojo/_base/declare',
_itemsSetter: function(value) {
this._clear_items();
this.items = value;
this._render_items(this.items, this.dom_node);
this._render_items(this.items);
},
_clear_items: function() {
@ -201,9 +201,9 @@ define(['dojo/_base/declare',
}
},
_render_list: function(container) {
_render_list: function(container, nested) {
var ul = this.ul_node = construct.create('ul', {
var ul = construct.create('ul', {
'class': 'dropdown-menu'
});
if (this.right_aligned) {
@ -212,14 +212,15 @@ define(['dojo/_base/declare',
if (container) {
construct.place(ul, container);
}
if (!nested) this.ul_node = ul;
return ul;
},
_render_items: function(items, container) {
var ul = this.ul_node;
if (!container) container = this.ul_node;
array.forEach(items, function(item) {
this._render_item(item, ul);
this._render_item(item, container);
}, this);
},
@ -257,7 +258,8 @@ define(['dojo/_base/declare',
if (item.items && item.items.length > 0) {
dom_class.add(li, 'dropdown-submenu');
this._render_items(item.items, li);
var ul = this._render_list(li, true);
this._render_items(item.items, ul);
} else {
on(a, 'click', lang.hitch(this, function(event) {
this.on_item_click(event, item);

View File

@ -231,7 +231,13 @@ define(['dojo/_base/declare',
refresh: function() {
if (this.buttons_node) {
this.buttons_node.innerHTML = "";
// detach button nodes politely
// hard methods like `innerHTML=''` might have undesired
// consequences, e.g., removal of children's content in IE
var bn = this.buttons_node;
while (bn.firstChild) {
bn.removeChild(bn.firstChild);
}
}
if (this.view === 'reset') {
this.show_reset_view();

View File

@ -374,6 +374,9 @@
"enable": "Enable token"
},
"permission": {
"add_custom_attr": "Add custom attribute",
"attribute": "Attribute",
"filter": "Filter",
"identity": "Permission settings",
"managed": "Attribute breakdown",
"target": "Target"

View File

@ -63,3 +63,8 @@ addifnew:nsSaslMapPriority: 10
# Can be removed when https://fedorahosted.org/389/ticket/47457 is fixed
dn: cn=config
only:nsslapd-sasl-max-buffer-size:2097152
# Allow hashed passwords to be added by non-DM users. Without this
# setting, password migration fails
dn: cn=config
only:nsslapd-allow-hashed-passwords:on

View File

@ -11,6 +11,7 @@ default: nsAccountLock: FALSE
default: ipaUniqueID: autogenerate
dn: cn=adtrust agents,cn=sysaccounts,cn=etc,$SUFFIX
add: objectClass: nestedgroup
default: objectClass: GroupOfNames
default: objectClass: top
default: cn: adtrust agents

View File

@ -258,10 +258,10 @@ static int ipa_ldap_extended_op(LDAP *ld, const char *reqoid,
int msgid;
int ret, rc;
ret = ldap_extended_operation(ld, KEYTAB_GET_OID, control,
ret = ldap_extended_operation(ld, reqoid, control,
NULL, NULL, &msgid);
if (ret != LDAP_SUCCESS) {
fprintf(stderr, _("Operation failed! %s\n"), ldap_err2string(ret));
fprintf(stderr, _("Operation failed: %s\n"), ldap_err2string(ret));
return ret;
}
@ -270,20 +270,20 @@ static int ipa_ldap_extended_op(LDAP *ld, const char *reqoid,
tv.tv_usec = 0;
ret = ldap_result(ld, msgid, 1, &tv, &res);
if (ret == -1) {
fprintf(stderr, _("Failed to get result! %s\n"), ldap_err2string(ret));
fprintf(stderr, _("Failed to get result: %s\n"), ldap_err2string(ret));
goto done;
}
ret = ldap_parse_extended_result(ld, res, &retoid, &retdata, 0);
if (ret != LDAP_SUCCESS) {
fprintf(stderr, _("Failed to parse extended result! %s\n"),
fprintf(stderr, _("Failed to parse extended result: %s\n"),
ldap_err2string(ret));
goto done;
}
ret = ldap_parse_result(ld, res, &rc, NULL, &err, NULL, srvctrl, 0);
if (ret != LDAP_SUCCESS || rc != LDAP_SUCCESS) {
fprintf(stderr, _("Failed to parse result! %s\n"),
fprintf(stderr, _("Failed to parse result: %s\n"),
err ? err : ldap_err2string(ret));
if (ret == LDAP_SUCCESS) ret = rc;
goto done;
@ -569,7 +569,7 @@ static int ldap_get_keytab(krb5_context krbctx, bool generate, char *password,
struct krb_key_salt *es = NULL;
int num_es = 0;
struct berval *control = NULL;
LDAP *ld;
LDAP *ld = NULL;
LDAPControl **srvctrl = NULL;
BerElement *ber = NULL;
ber_tag_t rtag;
@ -917,20 +917,24 @@ int main(int argc, const char *argv[])
}
}
if (password && (retrieve == 0) && (kvno == -1)) {
if (!quiet) fprintf(stderr, _("Retrying with old method\n"));
if (retrieve == 0 && kvno == -1) {
if (!quiet) {
fprintf(stderr,
_("Retrying with pre-4.0 keytab retrieval method...\n"));
}
/* create key material */
ret = create_keys(krbctx, sprinc, password, enctypes_string, &keys, &err_msg);
if (!ret) {
if (err_msg != NULL) {
fprintf(stderr, "%s", err_msg);
}
fprintf(stderr, _("Failed to create key material\n"));
exit(8);
}
/* create key material */
ret = create_keys(krbctx, sprinc, password, enctypes_string, &keys, &err_msg);
if (!ret) {
if (err_msg != NULL) {
fprintf(stderr, "%s", err_msg);
}
kvno = ldap_set_keytab(krbctx, server, principal, uprinc, binddn, bindpw, &keys);
fprintf(stderr, _("Failed to create key material\n"));
exit(8);
}
kvno = ldap_set_keytab(krbctx, server, principal, uprinc, binddn, bindpw, &keys);
}
if (kvno == -1) {

View File

@ -359,7 +359,7 @@ def is_ipa_client_installed(on_master=False):
return installed
def configure_nsswitch_database(fstore, database, services, preserve=True,
append=True, default_value=None):
append=True, default_value=()):
"""
Edits the specified nsswitch.conf database (e.g. passwd, group, sudoers)
to use the specified service(s).
@ -390,28 +390,34 @@ def configure_nsswitch_database(fstore, database, services, preserve=True,
opts = conf.parse(f)
raw_database_entry = conf.findOpts(opts, 'option', database)[1]
if not raw_database_entry:
# If there is no database entry, database is not present in
# the nsswitch.conf. Set the list of services to the
# default list, if passed.
configured_services = ' '.join(default_value or [])
else:
configured_services = raw_database_entry['value'].strip()
if append:
new_services = ' ' + configured_services + ' ' + ' '.join(services)
# Detect the list of already configured services
if not raw_database_entry:
# If there is no database entry, database is not present in
# the nsswitch.conf. Set the list of services to the
# default list, if passed.
configured_services = list(default_value)
else:
new_services = ' ' + ' '.join(services) + ' ' + configured_services
configured_services = raw_database_entry['value'].strip().split()
# Make sure no service is added if already mentioned in the list
added_services = [s for s in services
if s not in configured_services]
# Prepend / append the list of new services
if append:
new_value = ' ' + ' '.join(configured_services + added_services)
else:
new_value = ' ' + ' '.join(added_services + configured_services)
else:
# Preserve not set, let's rewrite existing configuration
new_services = ' ' + ' '.join(services)
new_value = ' ' + ' '.join(services)
# Set new services as sources for database
opts = [{'name': database,
'type':'option',
'action':'set',
'value': new_services
'value': new_value
},
{'name':'empty',
'type':'empty'
@ -491,7 +497,8 @@ def uninstall(options, env):
"Failed to remove IPA CA from /etc/pki/nssdb: %s", str(e))
# Always start certmonger. We can't untrack something if it isn't
# running
# running. Note that this is legacy code to untrack any certificates
# that were created by previous versions of this installer.
messagebus = services.knownservices.messagebus
try:
messagebus.start()
@ -506,7 +513,7 @@ def uninstall(options, env):
try:
certmonger.stop_tracking(paths.NSS_DB_DIR, nickname=client_nss_nickname)
except (CalledProcessError, RuntimeError), e:
except RuntimeError, e:
root_logger.error("%s failed to stop tracking certificate: %s",
cmonger.service_name, str(e))
@ -1065,69 +1072,6 @@ def configure_krb5_conf(cli_realm, cli_domain, cli_server, cli_kdc, dnsok,
return 0
def configure_certmonger(fstore, subject_base, cli_realm, hostname, options,
remote_env):
started = True
principal = 'host/%s@%s' % (hostname, cli_realm)
messagebus = services.knownservices.messagebus
try:
messagebus.start()
except Exception, e:
log_service_error(messagebus.service_name, 'start', e)
# Ensure that certmonger has been started at least once to generate the
# cas files in /var/lib/certmonger/cas.
cmonger = services.knownservices.certmonger
try:
cmonger.restart()
except Exception, e:
log_service_error(cmonger.service_name, 'restart', e)
if options.hostname:
# It needs to be stopped if we touch them
try:
cmonger.stop()
except Exception, e:
log_service_error(cmonger.service_name, 'stop', e)
# If the hostname is explicitly set then we need to tell certmonger
# which principal name to use when requesting certs.
certmonger.add_principal_to_cas(principal)
try:
cmonger.restart()
except Exception, e:
log_service_error(cmonger.service_name, 'restart', e)
root_logger.warning(
"Automatic certificate management will not be available")
started = False
try:
cmonger.enable()
except Exception, e:
root_logger.error(
"Failed to configure automatic startup of the %s daemon: %s",
cmonger.service_name, str(e))
root_logger.warning(
"Automatic certificate management will not be available")
# Request our host cert
if remote_env['enable_ra']:
if started:
client_nss_nickname = client_nss_nickname_format % hostname
subject = DN(('CN', hostname), subject_base)
try:
run(["ipa-getcert", "request", "-d", paths.NSS_DB_DIR,
"-n", client_nss_nickname, "-N", str(subject),
"-K", principal])
except Exception:
root_logger.error("%s request for host certificate failed",
cmonger.service_name)
else:
root_logger.warning(
"A RA is not configured on the server. "
"Not requesting host certificate.")
def configure_sssd_conf(fstore, cli_realm, cli_domain, cli_server, options, client_domain, client_hostname):
try:
sssdconfig = SSSDConfig.SSSDConfig()
@ -2119,7 +2063,7 @@ def install(options, env, fstore, statestore):
# Create the discovery instance
ds = ipadiscovery.IPADiscovery()
ret = ds.search(domain=options.domain, servers=options.server, hostname=hostname, ca_cert_path=get_cert_path(options.ca_cert_file))
ret = ds.search(domain=options.domain, servers=options.server, realm=options.realm_name, hostname=hostname, ca_cert_path=get_cert_path(options.ca_cert_file))
if options.server and ret != 0:
# There is no point to continue with installation as server list was
@ -2635,8 +2579,6 @@ def install(options, env, fstore, statestore):
if not options.on_master:
client_dns(cli_server[0], hostname, options.dns_updates)
configure_certmonger(fstore, subject_base, cli_realm, hostname,
options, remote_env)
update_ssh_keys(cli_server[0], hostname, services.knownservices.sshd.get_config_dir(), options.create_sshfp)

View File

@ -139,7 +139,7 @@ class IPADiscovery(object):
domain = domain[p+1:]
return (None, None)
def search(self, domain = "", servers = "", hostname=None, ca_cert_path=None):
def search(self, domain="", servers="", realm=None, hostname=None, ca_cert_path=None):
"""
Use DNS discovery to identify valid IPA servers.
@ -218,13 +218,21 @@ class IPADiscovery(object):
#search for kerberos
root_logger.debug("[Kerberos realm search]")
krb_realm, kdc = self.ipadnssearchkrb(self.domain)
if not servers and not krb_realm:
if realm:
root_logger.debug("Kerberos realm forced")
self.realm = realm
self.realm_source = 'Forced'
else:
realm = self.ipadnssearchkrbrealm()
self.realm = realm
self.realm_source = (
'Discovered Kerberos DNS records from %s' % self.domain)
if not servers and not realm:
return REALM_NOT_FOUND
self.realm = krb_realm
self.kdc = kdc
self.realm_source = self.kdc_source = (
self.kdc = self.ipadnssearchkrbkdc()
self.kdc_source = (
'Discovered Kerberos DNS records from %s' % self.domain)
# We may have received multiple servers corresponding to the domain
@ -335,6 +343,10 @@ class IPADiscovery(object):
no_schema=True, decode_attrs=False)
try:
lh.do_simple_bind(DN(), '')
# get IPA base DN
root_logger.debug("Search LDAP server for IPA base DN")
basedn = get_ipa_basedn(lh)
except errors.ACIError:
root_logger.debug("LDAP Error: Anonymous access not allowed")
return [NO_ACCESS_TO_LDAP]
@ -350,10 +362,6 @@ class IPADiscovery(object):
else:
return [UNKNOWN_ERROR]
# get IPA base DN
root_logger.debug("Search LDAP server for IPA base DN")
basedn = get_ipa_basedn(lh)
if basedn is None:
root_logger.debug("The server is not an IPA server")
return [NOT_IPA_SERVER]
@ -452,11 +460,12 @@ class IPADiscovery(object):
return servers
def ipadnssearchkrb(self, tdomain):
def ipadnssearchkrbrealm(self, domain=None):
realm = None
kdc = None
if not domain:
domain = self.domain
# now, check for a Kerberos realm the local host or domain is in
qname = "_kerberos." + tdomain
qname = "_kerberos." + domain
root_logger.debug("Search DNS for TXT record of %s", qname)
@ -472,18 +481,21 @@ class IPADiscovery(object):
realm = answer.strings[0]
if realm:
break
return realm
if realm:
# now fetch server information for the realm
domain = realm.lower()
def ipadnssearchkrbkdc(self, domain=None):
kdc = None
kdc = self.ipadns_search_srv(domain, '_kerberos._udp', 88,
break_on_first=False)
if not domain:
domain = self.domain
if kdc:
kdc = ','.join(kdc)
else:
root_logger.debug("SRV record for KDC not found! Realm: %s, SRV record: %s" % (realm, qname))
kdc = None
kdc = self.ipadns_search_srv(domain, '_kerberos._udp', 88,
break_on_first=False)
return realm, kdc
if kdc:
kdc = ','.join(kdc)
else:
root_logger.debug("SRV record for KDC not found! Domain: %s" % domain)
kdc = None
return kdc

View File

@ -584,6 +584,12 @@ class InvalidSessionPassword(SessionError):
errno = 1201
format= _('Principal %(principal)s cannot be authenticated: %(message)s')
class PasswordExpired(InvalidSessionPassword):
"""
**1202** Raised when we cannot obtain a TGT for a principal because the password is expired.
"""
errno = 1202
##############################################################################
# 2000 - 2999: Authorization errors
class AuthorizationError(PublicError):
@ -811,6 +817,22 @@ class DeprecationError(InvocationError):
errno = 3015
format = _("Command '%(name)s' has been deprecated")
class NotAForestRootError(InvocationError):
"""
**3016** Raised when an attempt to establish trust is done against non-root domain
Forest root domain has the same name as the forest itself
For example:
>>> raise NotAForestRootError(forest='example.test', domain='jointops.test')
Traceback (most recent call last):
...
NotAForestRootError: Domain 'jointops.test' is not a root domain for forest 'example.test'
"""
errno = 3016
format = _("Domain '%(domain)s' is not a root domain for forest '%(forest)s'")
##############################################################################
# 4000 - 4999: Execution errors
@ -1113,19 +1135,19 @@ class DefaultGroupError(ExecutionError):
class DNSNotARecordError(ExecutionError):
"""
**4019** Raised when a hostname is not a DNS A record
**4019** Raised when a hostname is not a DNS A/AAAA record
For example:
>>> raise DNSNotARecordError()
Traceback (most recent call last):
...
DNSNotARecordError: Host does not have corresponding DNS A record
DNSNotARecordError: Host does not have corresponding DNS A/AAAA record
"""
errno = 4019
format = _('Host does not have corresponding DNS A record')
format = _('Host does not have corresponding DNS A/AAAA record')
class ManagedGroupError(ExecutionError):
"""

View File

@ -210,6 +210,10 @@ def get_effective_rights(ldap, dn, attrs=None):
rights = rights[0].split(', ')
for r in rights:
(k,v) = r.split(':')
if v == 'none':
# the string "none" means "no rights found"
# see https://fedorahosted.org/freeipa/ticket/4359
v = u''
rdict[k.strip().lower()] = v
return rdict
@ -236,9 +240,13 @@ def entry_from_entry(entry, newentry):
def entry_to_dict(entry, **options):
if options.get('raw', False):
result = {}
for attr, value in entry.raw.iteritems():
if entry.conn.get_type(attr) is not str:
value = list(value)
for attr in entry.iterkeys():
if attr.lower() == 'attributelevelrights':
value = entry[attr]
elif entry.conn.get_type(attr) is str:
value = entry.raw[attr]
else:
value = list(entry.raw[attr])
for (i, v) in enumerate(value):
try:
value[i] = v.decode('utf-8')

View File

@ -209,11 +209,11 @@ EXAMPLES:
authoritative (e.g. sub.example.com) will be routed to the global forwarder.
Global forwarding configuration can be overridden per-zone.
""") + _("""
Semantics of forwarding in IPA matches BIND sematics and depends on type
of the zone:
Semantics of forwarding in IPA matches BIND semantics and depends on the type
of zone:
* Master zone: local BIND replies authoritatively to queries for data in
the given zone (including authoritative NXDOMAIN answers) and forwarding
affects only queries for names bellow zone cuts (NS records) of locally
affects only queries for names below zone cuts (NS records) of locally
served zones.
* Forward zone: forward zone contains no authoritative data. BIND forwards
@ -489,6 +489,14 @@ def _hostname_validator(ugettext, value):
return None
def _no_wildcard_validator(ugettext, value):
"""Disallow usage of wildcards as RFC 4592 section 4 recommends
"""
assert isinstance(value, DNSName)
if value.is_wild():
return _('should not be a wildcard domain name (RFC 4592 section 4)')
return None
def is_forward_record(zone, str_address):
addr = netaddr.IPAddress(str_address)
if addr.version == 4:
@ -1731,6 +1739,7 @@ class DNSZoneBase(LDAPObject):
takes_params = (
DNSNameParam('idnsname',
_no_wildcard_validator, # RFC 4592 section 4
only_absolute=True,
cli_name='name',
label=_('Zone name'),
@ -2619,6 +2628,19 @@ class dnsrecord(LDAPObject):
error=unicode(_('out-of-zone data: record name must '
'be a subdomain of the zone or a '
'relative name')))
# dissallowed wildcard (RFC 4592 section 4)
no_wildcard_rtypes = ['DNAME', 'DS', 'NS']
if (keys[-1].is_wild() and
any(entry_attrs.get('%srecord' % r.lower())
for r in no_wildcard_rtypes)
):
raise errors.ValidationError(
name='idnsname',
error=(_('owner of %(types)s records '
'should not be a wildcard domain name (RFC 4592 section 4)') %
{'types': ', '.join(no_wildcard_rtypes)}
)
)
def _ptrrecord_pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
assert isinstance(dn, DN)
@ -3374,6 +3396,13 @@ class dnsrecord_mod(LDAPUpdate):
if del_all:
result = self.obj.methods.delentry(*keys,
version=options['version'])
# we need to modify delete result to match mod output type
# only one value is expected, not a list
if client_has_capability(options['version'], 'primary_key_types'):
assert len(result['value']) == 1
result['value'] = result['value'][0]
# indicate that entry was deleted
context.dnsrecord_entry_mods[(keys[0], keys[1])] = None

View File

@ -202,6 +202,16 @@ class group(LDAPObject):
],
'default_privileges': {'Group Administrators'},
},
'System: Read Group Compat Tree': {
'non_object': True,
'ipapermbindruletype': 'anonymous',
'ipapermlocation': api.env.basedn,
'ipapermtarget': DN('cn=groups', 'cn=compat', api.env.basedn),
'ipapermright': {'read', 'search', 'compare'},
'ipapermdefaultattr': {
'objectclass', 'cn', 'memberuid', 'gidnumber',
},
},
}
label = _('User Groups')
@ -522,7 +532,7 @@ class group_remove_member(LDAPRemoveMember):
def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
assert isinstance(dn, DN)
if keys[0] in PROTECTED_GROUPS:
if keys[0] in PROTECTED_GROUPS and 'user' in options:
protected_group_name = keys[0]
result = api.Command.group_show(protected_group_name)
users_left = set(result['result'].get('member_user', []))

View File

@ -368,6 +368,16 @@ class host(LDAPObject):
'ipapermdefaultattr': {'userpassword'},
'default_privileges': {'Host Administrators', 'Host Enrollment'},
},
'System: Read Host Compat Tree': {
'non_object': True,
'ipapermbindruletype': 'anonymous',
'ipapermlocation': api.env.basedn,
'ipapermtarget': DN('cn=computers', 'cn=compat', api.env.basedn),
'ipapermright': {'read', 'search', 'compare'},
'ipapermdefaultattr': {
'objectclass', 'cn', 'macaddress',
},
},
}
label = _('Hosts')

View File

@ -185,13 +185,14 @@ class idrange(LDAPObject):
label = _('ID Ranges')
label_singular = _('ID Range')
# The commented range types are planned but not yet supported
range_types = {
u'ipa-local': unicode(_('local domain range')),
u'ipa-ad-winsync': unicode(_('Active Directory winsync range')),
# u'ipa-ad-winsync': unicode(_('Active Directory winsync range')),
u'ipa-ad-trust': unicode(_('Active Directory domain range')),
u'ipa-ad-trust-posix': unicode(_('Active Directory trust range with '
'POSIX attributes')),
u'ipa-ipa-trust': unicode(_('IPA trust range')),
# u'ipa-ipa-trust': unicode(_('IPA trust range')),
}
takes_params = (

View File

@ -518,6 +518,9 @@ class i18n_messages(Command):
"enable": "Enable token",
},
"permission": {
"add_custom_attr": _("Add custom attribute"),
"attribute": _("Attribute"),
"filter": _("Filter"),
"identity": _("Permission settings"),
"managed": _("Attribute breakdown"),
"target": _("Target"),

View File

@ -160,6 +160,16 @@ class netgroup(LDAPObject):
],
'default_privileges': {'Netgroups Administrators'},
},
'System: Read Netgroup Compat Tree': {
'non_object': True,
'ipapermbindruletype': 'anonymous',
'ipapermlocation': api.env.basedn,
'ipapermtarget': DN('cn=ng', 'cn=compat', api.env.basedn),
'ipapermright': {'read', 'search', 'compare'},
'ipapermdefaultattr': {
'objectclass', 'cn', 'membernisnetgroup', 'nisnetgrouptriple',
},
},
}
label = _('Netgroups')

View File

@ -21,7 +21,7 @@ from ipalib.plugins.baseldap import DN, LDAPObject, LDAPAddMember, LDAPRemoveMem
from ipalib.plugins.baseldap import LDAPCreate, LDAPDelete, LDAPUpdate, LDAPSearch, LDAPRetrieve
from ipalib import api, Int, Str, Bool, DateTime, Flag, Bytes, IntEnum, StrEnum, Password, _, ngettext
from ipalib.plugable import Registry
from ipalib.errors import PasswordMismatch, ConversionError, LastMemberError, NotFound
from ipalib.errors import PasswordMismatch, ConversionError, LastMemberError, NotFound, ValidationError
from ipalib.request import context
from ipalib.frontend import Local
@ -103,6 +103,11 @@ def _normalize_owner(userobj, entry_attrs):
if owner is not None:
entry_attrs['ipatokenowner'] = userobj.get_dn(owner)
def _check_interval(not_before, not_after):
if not_before and not_after:
return not_before <= not_after
return True
@register()
class otptoken(LDAPObject):
@ -254,6 +259,11 @@ class otptoken_add(LDAPCreate):
entry_attrs['ipatokenuniqueid'] = str(uuid.uuid4())
dn = DN("ipatokenuniqueid=%s" % entry_attrs['ipatokenuniqueid'], dn)
if not _check_interval(options.get('ipatokennotbefore', None),
options.get('ipatokennotafter', None)):
raise ValidationError(name='not_after',
error='is before the validity start')
# Set the object class and defaults for specific token types
entry_attrs['objectclass'] = otptoken.object_class + ['ipatoken' + options['type']]
for ttype, tattrs in TOKEN_TYPES.items():
@ -336,6 +346,25 @@ class otptoken_mod(LDAPUpdate):
msg_summary = _('Modified OTP token "%(value)s"')
def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
notafter_set = True
notbefore = options.get('ipatokennotbefore', None)
notafter = options.get('ipatokennotafter', None)
# notbefore xor notafter, exactly one of them is not None
if bool(notbefore) ^ bool(notafter):
result = self.api.Command.otptoken_show(keys[-1])['result']
if notbefore is None:
notbefore = result.get('ipatokennotbefore', [None])[0]
if notafter is None:
notafter_set = False
notafter = result.get('ipatokennotafter', [None])[0]
if not _check_interval(notbefore, notafter):
if notafter_set:
raise ValidationError(name='not_after',
error='is before the validity start')
else:
raise ValidationError(name='not_before',
error='is after the validity end')
_normalize_owner(self.api.Object.user, entry_attrs)
return dn

View File

@ -296,8 +296,9 @@ class permission(baseldap.LDAPObject):
DNParam(
'ipapermtarget?',
cli_name='target',
label=_('ACI target DN'),
flags={'no_option'}
label=_('Target DN'),
doc=_('Optional DN to apply the permission to '
'(must be in the subtree, but may not yet exist)'),
),
Str('memberof*',

View File

@ -166,7 +166,7 @@ class sudorule(LDAPObject):
'non_object': True,
'ipapermlocation': api.env.basedn,
'ipapermtarget': DN('ou=sudoers', api.env.basedn),
'ipapermbindruletype': 'all',
'ipapermbindruletype': 'anonymous',
'ipapermright': {'read', 'search', 'compare'},
'ipapermdefaultattr': {
'objectclass', 'cn', 'ou',

View File

@ -435,7 +435,7 @@ sides.
),
Password('realm_passwd?',
cli_name='password',
label=_("Active directory domain administrator's password"),
label=_("Active Directory domain administrator's password"),
confirm=False,
),
Str('realm_server?',
@ -511,6 +511,30 @@ sides.
return result
def interactive_prompt_callback(self, kw):
"""
Also ensure that realm_admin is prompted for if --admin or
--trust-secret is not specified when 'ipa trust-add' is run on the
system.
Also ensure that realm_passwd is prompted for if --password or
--trust-secret is not specified when 'ipa trust-add' is run on the
system.
"""
trust_secret = kw.get('trust_secret')
realm_admin = kw.get('realm_admin')
realm_passwd = kw.get('realm_passwd')
if trust_secret is None:
if realm_admin is None:
kw['realm_admin'] = self.prompt_param(
self.params['realm_admin'])
if realm_passwd is None:
kw['realm_passwd'] = self.Backend.textui.prompt_password(
self.params['realm_passwd'].label, confirm=False)
def validate_options(self, *keys, **options):
if not _bindings_installed:
raise errors.NotFound(
@ -721,11 +745,10 @@ sides.
ret['summary'] = self.msg_summary_existing % ret
return ret
# 2. We don't have access to the remote domain and trustdom password
# is provided. Do the work on our side and inform what to do on remote
# side.
if 'trust_secret' in options:
if options.get('trust_secret'):
result = self.trustinstance.join_ad_ipa_half(
keys[-1],
self.realm_server,
@ -740,8 +763,11 @@ sides.
if dn:
ret['summary'] = self.msg_summary_existing % ret
return ret
raise errors.ValidationError(name=_('AD Trust setup'),
error=_('Not enough arguments specified to perform trust setup'))
else:
raise errors.ValidationError(
name=_('AD Trust setup'),
error=_('Not enough arguments specified to perform trust '
'setup'))
@register()
class trust_del(LDAPDelete):

View File

@ -424,6 +424,17 @@ class user(LDAPObject):
],
'default_privileges': {'User Administrators'},
},
'System: Read User Compat Tree': {
'non_object': True,
'ipapermbindruletype': 'anonymous',
'ipapermlocation': api.env.basedn,
'ipapermtarget': DN('cn=users', 'cn=compat', api.env.basedn),
'ipapermright': {'read', 'search', 'compare'},
'ipapermdefaultattr': {
'objectclass', 'uid', 'cn', 'gecos', 'gidnumber', 'uidnumber',
'homedirectory', 'loginshell',
},
},
}
label = _('Users')
@ -780,23 +791,21 @@ class user_add(LDAPCreate):
if 'manager' in entry_attrs:
entry_attrs['manager'] = self.obj._normalize_manager(entry_attrs['manager'])
if ('objectclass' in entry_attrs
and 'userclass' in entry_attrs
and 'ipauser' not in entry_attrs['objectclass']):
if 'userclass' in entry_attrs and \
'ipauser' not in entry_attrs['objectclass']:
entry_attrs['objectclass'].append('ipauser')
if 'ipatokenradiusconfiglink' in entry_attrs:
cl = entry_attrs['ipatokenradiusconfiglink']
if cl:
if 'objectclass' not in entry_attrs:
_entry = ldap.get_entry(dn, ['objectclass'])
entry_attrs['objectclass'] = _entry['objectclass']
if 'ipauserauthtype' in entry_attrs and \
'ipauserauthtypeclass' not in entry_attrs['objectclass']:
entry_attrs['objectclass'].append('ipauserauthtypeclass')
if 'ipatokenradiusproxyuser' not in entry_attrs['objectclass']:
entry_attrs['objectclass'].append('ipatokenradiusproxyuser')
rcl = entry_attrs.get('ipatokenradiusconfiglink', None)
if rcl:
if 'ipatokenradiusproxyuser' not in entry_attrs['objectclass']:
entry_attrs['objectclass'].append('ipatokenradiusproxyuser')
answer = self.api.Object['radiusproxy'].get_dn_if_exists(cl)
entry_attrs['ipatokenradiusconfiglink'] = answer
answer = self.api.Object['radiusproxy'].get_dn_if_exists(rcl)
entry_attrs['ipatokenradiusconfiglink'] = answer
return dn

View File

@ -96,19 +96,30 @@ def find_modules_in_dir(src_dir):
def validate_host_dns(log, fqdn):
"""
See if the hostname has a DNS A record.
See if the hostname has a DNS A/AAAA record.
"""
try:
answers = resolver.query(fqdn, rdatatype.A)
log.debug(
'IPA: found %d records for %s: %s' % (len(answers), fqdn,
'IPA: found %d A records for %s: %s' % (len(answers), fqdn,
' '.join(str(answer) for answer in answers))
)
except DNSException, e:
log.debug(
'IPA: DNS A record lookup failed for %s' % fqdn
)
raise errors.DNSNotARecordError()
# A record not found, try to find AAAA record
try:
answers = resolver.query(fqdn, rdatatype.AAAA)
log.debug(
'IPA: found %d AAAA records for %s: %s' % (len(answers), fqdn,
' '.join(str(answer) for answer in answers))
)
except DNSException, e:
log.debug(
'IPA: DNS AAAA record lookup failed for %s' % fqdn
)
raise errors.DNSNotARecordError()
def has_soa_or_ns_record(domain):

View File

@ -1,4 +1,5 @@
# Authors: Rob Crittenden <rcritten@redhat.com>
# David Kupka <dkupka@redhat.com>
#
# Copyright (C) 2010 Red Hat
# see file 'COPYING' for use and warranty information
@ -23,136 +24,203 @@
import os
import sys
import re
import time
import dbus
from ipapython import ipautil
from ipapython import dogtag
from ipaplatform.paths import paths
from ipaplatform import services
from ipapython.ipa_log_manager import root_logger
REQUEST_DIR=paths.CERTMONGER_REQUESTS_DIR
CA_DIR=paths.CERTMONGER_CAS_DIR
REQUEST_DIR = paths.CERTMONGER_REQUESTS_DIR
CA_DIR = paths.CERTMONGER_CAS_DIR
# Normalizer types for critera in get_request_id()
NPATH = 1
DBUS_CM_PATH = '/org/fedorahosted/certmonger'
DBUS_CM_IF = 'org.fedorahosted.certmonger'
DBUS_CM_REQUEST_IF = 'org.fedorahosted.certmonger.request'
DBUS_CM_CA_IF = 'org.fedorahosted.certmonger.ca'
DBUS_PROPERTY_IF = 'org.freedesktop.DBus.Properties'
def find_request_value(filename, directive):
class _cm_dbus_object(object):
"""
Return a value from a certmonger request file for the requested directive
It tries to do this a number of times because sometimes there is a delay
when ipa-getcert returns and the file is fully updated, particularly
when doing a request. Generating a CSR is fast but not instantaneous.
Auxiliary class for convenient DBus object handling.
"""
tries = 1
value = None
found = False
while value is None and tries <= 5:
tries=tries + 1
time.sleep(1)
fp = open(filename, 'r')
lines = fp.readlines()
fp.close()
def __init__(self, bus, object_path, object_dbus_interface,
parent_dbus_interface=None, property_interface=False):
"""
bus - DBus bus object, result of dbus.SystemBus() or dbus.SessionBus()
Object is accesible over this DBus bus instance.
object_path - path to requested object on DBus bus
object_dbus_interface
parent_dbus_interface
property_interface - create DBus property interface? True or False
"""
if bus is None or object_path is None or object_dbus_interface is None:
raise RuntimeError(
"bus, object_path and dbus_interface must not be None.")
if parent_dbus_interface is None:
parent_dbus_interface = object_dbus_interface
self.bus = bus
self.path = object_path
self.obj_dbus_if = object_dbus_interface
self.parent_dbus_if = parent_dbus_interface
self.obj = bus.get_object(parent_dbus_interface, object_path)
self.obj_if = dbus.Interface(self.obj, object_dbus_interface)
if property_interface:
self.prop_if = dbus.Interface(self.obj, DBUS_PROPERTY_IF)
for line in lines:
if found:
# A value can span multiple lines. If it does then it has a
# leading space.
if not line.startswith(' '):
# We hit the next directive, return now
return value
else:
value = value + line[1:]
def _start_certmonger():
"""
Start certmonger daemon. If it's already running systemctl just ignores
the command.
"""
if not services.knownservices.certmonger.is_running():
try:
services.knownservices.certmonger.start()
except Exception, e:
root_logger.error('Failed to start certmonger: %s' % e)
raise
def _connect_to_certmonger():
"""
Start certmonger daemon and connect to it via DBus.
"""
try:
_start_certmonger()
except (KeyboardInterrupt, OSError), e:
root_logger.error('Failed to start certmonger: %s' % e)
raise
try:
bus = dbus.SystemBus()
cm = _cm_dbus_object(bus, DBUS_CM_PATH, DBUS_CM_IF)
except dbus.DBusException, e:
root_logger.error("Failed to access certmonger over DBus: %s", e)
raise
return cm
def _get_requests(criteria=dict()):
"""
Get all requests that matches the provided criteria.
"""
if not isinstance(criteria, dict):
raise TypeError('"criteria" must be dict.')
cm = _connect_to_certmonger()
requests = []
requests_paths = []
if 'nickname' in criteria:
request_path = cm.obj_if.find_request_by_nickname(criteria['nickname'])
if request_path:
requests_paths = [request_path]
else:
requests_paths = cm.obj_if.get_requests()
for request_path in requests_paths:
request = _cm_dbus_object(cm.bus, request_path, DBUS_CM_REQUEST_IF,
DBUS_CM_IF, True)
for criterion in criteria:
if criterion == 'ca-name':
ca_path = request.obj_if.get_ca()
ca = _cm_dbus_object(cm.bus, ca_path, DBUS_CM_CA_IF,
DBUS_CM_IF)
value = ca.obj_if.get_nickname()
else:
if line.startswith(directive + '='):
found = True
value = line[len(directive)+1:]
value = request.prop_if.Get(DBUS_CM_REQUEST_IF, criterion)
if value != criteria[criterion]:
break
else:
requests.append(request)
return requests
def _get_request(criteria):
"""
Find request that matches criteria.
If 'nickname' is specified other criteria are ignored because 'nickname'
uniquely identify single request.
When multiple or none request matches specified criteria RuntimeError is
raised.
"""
requests = _get_requests(criteria)
if len(requests) == 0:
return None
elif len(requests) == 1:
return requests[0]
else:
raise RuntimeError("Criteria expected to be met by 1 request, got %s."
% len(requests))
return value
def get_request_value(request_id, directive):
"""
There is no guarantee that the request_id will match the filename
in the certmonger requests directory, so open each one to find the
request_id.
Get property of request.
"""
fileList=os.listdir(REQUEST_DIR)
for file in fileList:
value = find_request_value('%s/%s' % (REQUEST_DIR, file), 'id')
if value is not None and value.rstrip() == request_id:
return find_request_value('%s/%s' % (REQUEST_DIR, file), directive)
try:
request = _get_request(dict(nickname=request_id))
except RuntimeError, e:
root_logger.error('Failed to get request: %s' % e)
raise
if request:
return request.prop_if.Get(DBUS_CM_REQUEST_IF, directive)
else:
return None
return None
def get_request_id(criteria):
"""
If you don't know the certmonger request_id then try to find it by looking
through all the request files. An alternative would be to parse the
ipa-getcert list output but this seems cleaner.
through all the requests.
criteria is a tuple of key/value/type to search for. The more specific
criteria is a tuple of key/value to search for. The more specific
the better. An error is raised if multiple request_ids are returned for
the same criteria.
None is returned if none of the criteria match.
"""
assert type(criteria) is tuple
try:
request = _get_request(criteria)
except RuntimeError, e:
root_logger.error('Failed to get request: %s' % e)
raise
reqid=None
fileList=os.listdir(REQUEST_DIR)
for file in fileList:
match = True
for (key, value, valtype) in criteria:
rv = find_request_value('%s/%s' % (REQUEST_DIR, file), key)
if rv and valtype == NPATH:
rv = os.path.abspath(rv)
if rv is None or rv.rstrip() != value:
match = False
break
if match and reqid is not None:
raise RuntimeError('multiple certmonger requests match the criteria')
if match:
reqid = find_request_value('%s/%s' % (REQUEST_DIR, file), 'id').rstrip()
if request:
return request.prop_if.Get(DBUS_CM_REQUEST_IF, 'nickname')
else:
return None
return reqid
def get_requests_for_dir(dir):
"""
Return a list containing the request ids for a given NSS database
directory.
"""
reqid=[]
fileList=os.listdir(REQUEST_DIR)
for file in fileList:
rv = find_request_value(os.path.join(REQUEST_DIR, file),
'cert_storage_location')
if rv is None:
continue
rv = os.path.abspath(rv).rstrip()
if rv != dir:
continue
id = find_request_value(os.path.join(REQUEST_DIR, file), 'id')
if id is not None:
reqid.append(id.rstrip())
reqid = []
criteria = {'cert-storage': 'NSSDB', 'key-storage': 'NSSDB',
'cert-database': dir, 'key-database': dir, }
requests = _get_requests(criteria)
for request in requests:
reqid.append(request.prop_if.Get(DBUS_CM_REQUEST_IF, 'nickname'))
return reqid
def add_request_value(request_id, directive, value):
"""
Add a new directive to a certmonger request file.
The certmonger service MUST be stopped in order for this to work.
"""
fileList=os.listdir(REQUEST_DIR)
for file in fileList:
id = find_request_value('%s/%s' % (REQUEST_DIR, file), 'id')
if id is not None and id.rstrip() == request_id:
current_value = find_request_value('%s/%s' % (REQUEST_DIR, file), directive)
if not current_value:
fp = open('%s/%s' % (REQUEST_DIR, file), 'a')
fp.write('%s=%s\n' % (directive, value))
fp.close()
try:
request = _get_request({'nickname': request_id})
except RuntimeError, e:
root_logger.error('Failed to get request: %s' % e)
raise
if request:
request.obj_if.modify({directive: value})
return
def add_principal(request_id, principal):
"""
@ -161,7 +229,8 @@ def add_principal(request_id, principal):
When an existing certificate is added via start-tracking it won't have
a principal.
"""
return add_request_value(request_id, 'template_principal', principal)
add_request_value(request_id, 'template-principal', [principal])
def add_subject(request_id, subject):
"""
@ -171,47 +240,29 @@ def add_subject(request_id, subject):
When an existing certificate is added via start-tracking it won't have
a subject_template set.
"""
return add_request_value(request_id, 'template_subject', subject)
add_request_value(request_id, 'template-subject', subject)
def request_cert(nssdb, nickname, subject, principal, passwd_fname=None):
"""
Execute certmonger to request a server certificate
Execute certmonger to request a server certificate.
"""
args = [paths.IPA_GETCERT,
'request',
'-d', nssdb,
'-n', nickname,
'-N', subject,
'-K', principal,
]
cm = _connect_to_certmonger()
request_parameters = dict(KEY_STORAGE='NSSDB', CERT_STORAGE='NSSDB',
CERT_LOCATION=nssdb, CERT_NICKNAME=nickname,
SUBJECT=subject, PRINCIPAL=principal,)
if passwd_fname:
args.append('-p')
args.append(os.path.abspath(passwd_fname))
(stdout, stderr, returncode) = ipautil.run(args)
# FIXME: should be some error handling around this
m = re.match('New signing request "(\d+)" added', stdout)
request_id = m.group(1)
return request_id
request_parameters['KEY_PIN_FILE'] = passwd_fname
result = cm.obj_if.add_request(request_parameters)
try:
if result[0]:
request = _cm_dbus_object(cm.bus, result[1], DBUS_CM_REQUEST_IF,
DBUS_CM_IF, True)
except TypeError:
root_logger.error('Failed to get create new request.')
raise
return request.obj_if.get_nickname()
def cert_exists(nickname, secdir):
"""
See if a nickname exists in an NSS database.
Returns True/False
This isn't very sophisticated in that it doesn't differentiate between
a database that doesn't exist and a nickname that doesn't exist within
the database.
"""
args = [paths.CERTUTIL, "-L",
"-d", os.path.abspath(secdir),
"-n", nickname
]
(stdout, stderr, rc) = ipautil.run(args, raiseonerr=False)
if rc == 0:
return True
else:
return False
def start_tracking(nickname, secdir, password_file=None, command=None):
"""
@ -222,61 +273,54 @@ def start_tracking(nickname, secdir, password_file=None, command=None):
certmonger to run when it renews a certificate. This command must
reside in /usr/lib/ipa/certmonger to work with SELinux.
Returns the stdout, stderr and returncode from running ipa-getcert
This assumes that certmonger is already running.
Returns True or False
"""
if not cert_exists(nickname, os.path.abspath(secdir)):
raise RuntimeError('Nickname "%s" doesn\'t exist in NSS database "%s"' % (nickname, secdir))
args = [paths.IPA_GETCERT, "start-tracking",
"-d", os.path.abspath(secdir),
"-n", nickname]
if password_file:
args.append("-p")
args.append(os.path.abspath(password_file))
cm = _connect_to_certmonger()
params = {'TRACK': True}
params['cert-nickname'] = nickname
params['cert-database'] = os.path.abspath(secdir)
params['cert-storage'] = 'NSSDB'
params['key-nickname'] = nickname
params['key-database'] = os.path.abspath(secdir)
params['key-storage'] = 'NSSDB'
if command:
args.append("-C")
args.append(command)
params['cert-postsave-command'] = command
if password_file:
params['KEY_PIN_FILE'] = os.path.abspath(password_file)
result = cm.obj_if.add_request(params)
try:
if result[0]:
request = _cm_dbus_object(cm.bus, result[1], DBUS_CM_REQUEST_IF,
DBUS_CM_IF, True)
except TypeError, e:
root_logger.error('Failed to add new request.')
raise
return request.prop_if.Get(DBUS_CM_REQUEST_IF, 'nickname')
(stdout, stderr, returncode) = ipautil.run(args)
return (stdout, stderr, returncode)
def stop_tracking(secdir, request_id=None, nickname=None):
"""
Stop tracking the current request using either the request_id or nickname.
This assumes that the certmonger service is running.
Returns True or False
"""
if request_id is None and nickname is None:
raise RuntimeError('Both request_id and nickname are missing.')
if nickname:
# Using the nickname find the certmonger request_id
criteria = (('cert_storage_location', os.path.abspath(secdir), NPATH),('cert_nickname', nickname, None))
try:
request_id = get_request_id(criteria)
if request_id is None:
return ('', '', 0)
except RuntimeError:
# This means that multiple requests matched, skip it for now
# Fall back to trying to stop tracking using nickname
pass
args = [paths.GETCERT,
'stop-tracking',
]
criteria = {'cert-database': secdir}
if request_id:
args.append('-i')
args.append(request_id)
else:
args.append('-n')
args.append(nickname)
args.append('-d')
args.append(os.path.abspath(secdir))
criteria['nickname'] = request_id
if nickname:
criteria['cert-nickname'] = nickname
try:
request = _get_request(criteria)
except RuntimeError, e:
root_logger.error('Failed to get request: %s' % e)
raise
if request:
cm = _connect_to_certmonger()
cm.obj_if.remove_request(request.path)
(stdout, stderr, returncode) = ipautil.run(args)
return (stdout, stderr, returncode)
def _find_IPA_ca():
"""
@ -286,13 +330,10 @@ def _find_IPA_ca():
We can use find_request_value because the ca files have the
same file format.
"""
fileList=os.listdir(CA_DIR)
for file in fileList:
value = find_request_value('%s/%s' % (CA_DIR, file), 'id')
if value is not None and value.strip() == 'IPA':
return '%s/%s' % (CA_DIR, file)
cm = _connect_to_certmonger()
ca_path = cm.obj_if.find_ca_by_nickname('IPA')
return _cm_dbus_object(cm.bus, ca_path, DBUS_CM_CA_IF, DBUS_CM_IF, True)
return None
def add_principal_to_cas(principal):
"""
@ -302,58 +343,27 @@ def add_principal_to_cas(principal):
/usr/libexec/certmonger/ipa-submit.
We also need to restore this on uninstall.
The certmonger service MUST be stopped in order for this to work.
"""
cafile = _find_IPA_ca()
if cafile is None:
return
ca = _find_IPA_ca()
if ca:
ext_helper = ca.prop_if.Get(DBUS_CM_CA_IF, 'external-helper')
if ext_helper and ext_helper.find('-k') == -1:
ext_helper = '%s -k %s' % (ext_helper.strip(), principal)
ca.prop_if.Set(DBUS_CM_CA_IF, 'external-helper', ext_helper)
update = False
fp = open(cafile, 'r')
lines = fp.readlines()
fp.close()
for i in xrange(len(lines)):
if lines[i].startswith('ca_external_helper') and \
lines[i].find('-k') == -1:
lines[i] = '%s -k %s\n' % (lines[i].strip(), principal)
update = True
if update:
fp = open(cafile, 'w')
for line in lines:
fp.write(line)
fp.close()
def remove_principal_from_cas():
"""
Remove any -k principal options from the ipa_submit helper.
The certmonger service MUST be stopped in order for this to work.
"""
cafile = _find_IPA_ca()
if cafile is None:
return
ca = _find_IPA_ca()
if ca:
ext_helper = ca.prop_if.Get(DBUS_CM_CA_IF, 'external-helper')
if ext_helper and ext_helper.find('-k'):
ext_helper = ext_helper.strip()[0]
ca.prop_if.Set(DBUS_CM_CA_IF, 'external-helper', ext_helper)
update = False
fp = open(cafile, 'r')
lines = fp.readlines()
fp.close()
for i in xrange(len(lines)):
if lines[i].startswith('ca_external_helper') and \
lines[i].find('-k') > 0:
lines[i] = lines[i].strip().split(' ')[0] + '\n'
update = True
if update:
fp = open(cafile, 'w')
for line in lines:
fp.write(line)
fp.close()
# Routines specific to renewing dogtag CA certificates
def get_pin(token, dogtag_constants=None):
"""
Dogtag stores its NSS pin in a file formatted as token:PIN.
@ -369,6 +379,7 @@ def get_pin(token, dogtag_constants=None):
return pin.strip()
return None
def dogtag_start_tracking(ca, nickname, pin, pinfile, secdir, pre_command,
post_command, profile=None):
"""
@ -383,52 +394,46 @@ def dogtag_start_tracking(ca, nickname, pin, pinfile, secdir, pre_command,
post_command is the script to execute after a renewal is done.
Both commands can be None.
Returns the stdout, stderr and returncode from running ipa-getcert
This assumes that certmonger is already running.
"""
if not cert_exists(nickname, os.path.abspath(secdir)):
raise RuntimeError('Nickname "%s" doesn\'t exist in NSS database "%s"' % (nickname, secdir))
args = [paths.GETCERT, "start-tracking",
"-d", os.path.abspath(secdir),
"-n", nickname,
"-c", ca,
]
cm = _connect_to_certmonger()
certmonger_cmd_template = paths.CERTMONGER_COMMAND_TEMPLATE
if pre_command is not None:
params = {'TRACK': True}
params['cert-nickname'] = nickname
params['cert-database'] = os.path.abspath(secdir)
params['cert-storage'] = 'NSSDB'
params['key-nickname'] = nickname
params['key-database'] = os.path.abspath(secdir)
params['key-storage'] = 'NSSDB'
ca_path = cm.obj_if.find_ca_by_nickname(ca)
if ca_path:
params['ca'] = ca_path
if pin:
params['KEY_PIN'] = pin
if pinfile:
params['KEY_PIN_FILE'] = os.path.abspath(pinfile)
if pre_command:
if not os.path.isabs(pre_command):
if sys.maxsize > 2**32L:
libpath = 'lib64'
else:
libpath = 'lib'
pre_command = paths.CERTMONGER_COMMAND_TEMPLATE % (libpath, pre_command)
args.append("-B")
args.append(pre_command)
if post_command is not None:
pre_command = certmonger_cmd_template % (libpath, pre_command)
params['cert-presave-command'] = pre_command
if post_command:
if not os.path.isabs(post_command):
if sys.maxsize > 2**32L:
libpath = 'lib64'
else:
libpath = 'lib'
post_command = paths.CERTMONGER_COMMAND_TEMPLATE % (libpath, post_command)
args.append("-C")
args.append(post_command)
if pinfile:
args.append("-p")
args.append(pinfile)
else:
args.append("-P")
args.append(pin)
post_command = certmonger_cmd_template % (libpath, post_command)
params['cert-postsave-command'] = post_command
if profile:
args.append("-T")
args.append(profile)
params['ca-profile'] = profile
cm.obj_if.add_request(params)
(stdout, stderr, returncode) = ipautil.run(args, nolog=[pin])
def check_state(dirs):
"""
@ -446,8 +451,11 @@ def check_state(dirs):
return reqids
if __name__ == '__main__':
request_id = request_cert(paths.HTTPD_ALIAS_DIR, "Test", "cn=tiger.example.com,O=IPA", "HTTP/tiger.example.com@EXAMPLE.COM")
request_id = request_cert(paths.HTTPD_ALIAS_DIR, "Test",
"cn=tiger.example.com,O=IPA",
"HTTP/tiger.example.com@EXAMPLE.COM")
csr = get_request_value(request_id, 'csr')
print csr
stop_tracking(request_id)

View File

@ -589,7 +589,11 @@ class DomainValidator(object):
try:
result = netrc.finddc(domain=domain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_GC | nbt.NBT_SERVER_CLOSEST)
except RuntimeError, e:
finddc_error = e
try:
# If search of closest GC failed, attempt to find any one
result = netrc.finddc(domain=domain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_GC)
except RuntimeError, e:
finddc_error = e
if not self._domains:
self._domains = self.get_trusted_domains()
@ -703,16 +707,19 @@ class TrustDomainInstance(object):
binding_template=lambda x,y,z: u'%s:%s[%s]' % (x, y, z)
return [binding_template(t, remote_host, o) for t in transports for o in options]
def retrieve_anonymously(self, remote_host, discover_srv=False):
def retrieve_anonymously(self, remote_host, discover_srv=False, search_pdc=False):
"""
When retrieving DC information anonymously, we can't get SID of the domain
"""
netrc = net.Net(creds=self.creds, lp=self.parm)
flags = nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE
if search_pdc:
flags = flags | nbt.NBT_SERVER_PDC
try:
if discover_srv:
result = netrc.finddc(domain=remote_host, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS)
result = netrc.finddc(domain=remote_host, flags=flags)
else:
result = netrc.finddc(address=remote_host, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS)
result = netrc.finddc(address=remote_host, flags=flags)
except RuntimeError, e:
raise assess_dcerpc_exception(message=str(e))
@ -723,6 +730,7 @@ class TrustDomainInstance(object):
self.info['dns_forest'] = unicode(result.forest)
self.info['guid'] = unicode(result.domain_uuid)
self.info['dc'] = unicode(result.pdc_dns_name)
self.info['is_pdc'] = (result.server_type & nbt.NBT_SERVER_PDC) != 0
# Netlogon response doesn't contain SID of the domain.
# We need to do rootDSE search with LDAP_SERVER_EXTENDED_DN_OID control to reveal the SID
@ -771,6 +779,13 @@ class TrustDomainInstance(object):
self.info['sid'] = unicode(result.sid)
self.info['dc'] = remote_host
try:
result = self._pipe.QueryInfoPolicy2(self._policy_handle, lsa.LSA_POLICY_INFO_ROLE)
except RuntimeError, (num, message):
raise assess_dcerpc_exception(num=num, message=message)
self.info['is_pdc'] = (result.role == lsa.LSA_ROLE_PRIMARY)
def generate_auth(self, trustdom_secret):
def arcfour_encrypt(key, data):
c = RC4.RC4(key)
@ -886,7 +901,7 @@ class TrustDomainInstance(object):
info.sid = security.dom_sid(another_domain.info['sid'])
info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
info.trust_attributes = 0
try:
dname = lsa.String()
@ -903,8 +918,6 @@ class TrustDomainInstance(object):
except RuntimeError, (num, message):
raise assess_dcerpc_exception(num=num, message=message)
self.update_ftinfo(another_domain)
# We should use proper trustdom handle in order to modify the
# trust settings. Samba insists this has to be done with LSA
# OpenTrustedDomain* calls, it is not enough to have a handle
@ -923,6 +936,15 @@ class TrustDomainInstance(object):
# server as that one doesn't support AES encryption types
pass
try:
info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
self._pipe.SetInformationTrustedDomain(trustdom_handle, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX, info)
except RuntimeError, e:
root_logger.error('unable to set trust to transitive: %s' % (str(e)))
pass
if self.info['is_pdc']:
self.update_ftinfo(another_domain)
def verify_trust(self, another_domain):
def retrieve_netlogon_info_2(domain, function_code, data):
try:
@ -1017,7 +1039,7 @@ def fetch_domains(api, mydomain, trustdomain, creds=None):
result = []
for t in domains.array:
if ((t.trust_attributes & trust_attributes['NETR_TRUST_ATTRIBUTE_WITHIN_FOREST']) and
if (not (t.trust_flags & trust_flags['NETR_TRUST_FLAG_PRIMARY']) and
(t.trust_flags & trust_flags['NETR_TRUST_FLAG_IN_FOREST'])):
res = dict()
res['cn'] = unicode(t.dns_name)
@ -1066,9 +1088,9 @@ class TrustDomainJoins(object):
rd.creds.set_anonymous()
rd.creds.set_workstation(self.local_domain.hostname)
if realm_server is None:
rd.retrieve_anonymously(realm, discover_srv=True)
rd.retrieve_anonymously(realm, discover_srv=True, search_pdc=True)
else:
rd.retrieve_anonymously(realm_server, discover_srv=False)
rd.retrieve_anonymously(realm_server, discover_srv=False, search_pdc=True)
rd.read_only = True
if realm_admin and realm_passwd:
if 'name' in rd.info:
@ -1129,6 +1151,9 @@ class TrustDomainJoins(object):
realm_passwd
)
if self.remote_domain.info['dns_domain'] != self.remote_domain.info['dns_forest']:
raise errors.NotAForestRootError(forest=self.remote_domain.info['dns_forest'], domain=self.remote_domain.info['dns_domain'])
if not self.remote_domain.read_only:
trustdom_pass = samba.generate_random_password(128, 128)
self.get_realmdomains()
@ -1145,5 +1170,8 @@ class TrustDomainJoins(object):
if not(isinstance(self.remote_domain, TrustDomainInstance)):
self.populate_remote_domain(realm, realm_server, realm_passwd=None)
if self.remote_domain.info['dns_domain'] != self.remote_domain.info['dns_forest']:
raise errors.NotAForestRootError(forest=self.remote_domain.info['dns_forest'], domain=self.remote_domain.info['dns_domain'])
self.local_domain.establish_trust(self.remote_domain, trustdom_passwd)
return dict(local=self.local_domain, remote=self.remote_domain, verified=False)

View File

@ -408,6 +408,7 @@ class ADTRUSTInstance(service.Service):
conf_fd = open(self.smb_conf, "w")
conf_fd.write('### Added by IPA Installer ###\n')
conf_fd.write('[global]\n')
conf_fd.write('debug pid = yes\n')
conf_fd.write('config backend = registry\n')
conf_fd.close()
@ -496,28 +497,31 @@ class ADTRUSTInstance(service.Service):
def __setup_principal(self):
try:
api.Command.service_add(unicode(self.cifs_principal))
# Add the principal to the 'adtrust agents' group
# as 389-ds only operates with GroupOfNames, we have to use
# the principal's proper dn as defined in self.cifs_agent
try:
current = self.admin_conn.get_entry(self.smb_dn)
members = current.get('member', [])
if not(self.cifs_agent in members):
current["member"] = members + [self.cifs_agent]
self.admin_conn.update_entry(current)
except errors.NotFound:
entry = self.admin_conn.make_entry(
self.smb_dn,
objectclass=["top", "GroupOfNames"],
cn=[self.smb_dn['cn']],
member=[self.cifs_agent],
)
self.admin_conn.add_entry(entry)
except Exception:
except errors.DuplicateEntry:
# CIFS principal already exists, it is not the first time
# adtrustinstance is managed
# That's fine, we we'll re-extract the key again.
pass
except Exception, e:
self.print_msg("Cannot add CIFS service: %s" % e)
# Add the principal to the 'adtrust agents' group
# as 389-ds only operates with GroupOfNames, we have to use
# the principal's proper dn as defined in self.cifs_agent
try:
current = self.admin_conn.get_entry(self.smb_dn)
members = current.get('member', [])
if not(self.cifs_agent in members):
current["member"] = members + [self.cifs_agent]
self.admin_conn.update_entry(current)
except errors.NotFound:
entry = self.admin_conn.make_entry(
self.smb_dn,
objectclass=["top", "GroupOfNames"],
cn=[self.smb_dn['cn']],
member=[self.cifs_agent],
)
self.admin_conn.add_entry(entry)
self.clean_samba_keytab()

View File

@ -318,13 +318,13 @@ def stop_tracking_certificates(dogtag_constants):
try:
certmonger.stop_tracking(
dogtag_constants.ALIAS_DIR, nickname=nickname)
except (ipautil.CalledProcessError, RuntimeError), e:
except RuntimeError, e:
root_logger.error(
"certmonger failed to stop tracking certificate: %s" % str(e))
try:
certmonger.stop_tracking(paths.HTTPD_ALIAS_DIR, nickname='ipaCert')
except (ipautil.CalledProcessError, RuntimeError), e:
except RuntimeError, e:
root_logger.error(
"certmonger failed to stop tracking certificate: %s" % str(e))
cmonger.stop()
@ -449,6 +449,7 @@ class CAInstance(service.Service):
self.step("creating pki-ca instance", self.create_instance)
self.step("configuring certificate server instance", self.__configure_instance)
self.step("stopping certificate server instance to update CS.cfg", self.__stop)
self.step("backing up CS.cfg", self.backup_config)
self.step("disabling nonces", self.__disable_nonce)
self.step("set up CRL publishing", self.__enable_crl_publish)
self.step("starting certificate server instance", self.__start)
@ -583,9 +584,25 @@ class CAInstance(service.Service):
config.set("CA", "pki_external_csr_path", self.csr_file)
elif self.external == 2:
cert = x509.load_certificate_from_file(self.cert_file)
cert_file = tempfile.NamedTemporaryFile()
x509.write_certificate(cert.der_data, cert_file.name)
cert_file.flush()
cert_chain, stderr, rc = ipautil.run(
[paths.OPENSSL, 'crl2pkcs7',
'-certfile', self.cert_chain_file,
'-nocrl'])
# Dogtag chokes on the header and footer, remove them
# https://bugzilla.redhat.com/show_bug.cgi?id=1127838
cert_chain = re.search(
r'(?<=-----BEGIN PKCS7-----).*?(?=-----END PKCS7-----)',
cert_chain, re.DOTALL).group(0)
cert_chain_file = ipautil.write_tmp_file(cert_chain)
config.set("CA", "pki_external", "True")
config.set("CA", "pki_external_ca_cert_path", self.cert_file)
config.set("CA", "pki_external_ca_cert_chain_path", self.cert_chain_file)
config.set("CA", "pki_external_ca_cert_path", cert_file.name)
config.set("CA", "pki_external_ca_cert_chain_path", cert_chain_file.name)
config.set("CA", "pki_external_step_two", "True")
# Generate configuration file
@ -602,6 +619,7 @@ class CAInstance(service.Service):
'Contents of pkispawn configuration file (%s):\n%s' %
(cfg_file, ipautil.nolog_replace(f.read(), nolog)))
self.backup_state('installed', True)
try:
ipautil.run(args, nolog=nolog)
except ipautil.CalledProcessError, e:
@ -646,6 +664,7 @@ class CAInstance(service.Service):
'-redirect', 'logs=/var/log/pki-ca',
'-enable_proxy'
]
self.backup_state('installed', True)
ipautil.run(args, env={'PKI_HOSTNAME':self.fqdn})
def __enable(self):
@ -717,10 +736,15 @@ class CAInstance(service.Service):
args.append("-ext_csr_file")
args.append(self.csr_file)
elif self.external == 2:
cert = x509.load_certificate_from_file(self.cert_file)
cert_file = tempfile.NamedTemporaryFile()
x509.write_certificate(cert.der_data, cert_file.name)
cert_file.flush()
args.append("-external")
args.append("true")
args.append("-ext_ca_cert_file")
args.append(self.cert_file)
args.append(cert_file.name)
args.append("-ext_ca_cert_chain_file")
args.append(self.cert_chain_file)
else:
@ -788,6 +812,12 @@ class CAInstance(service.Service):
root_logger.debug(traceback.format_exc())
root_logger.critical("Failed to restart the certificate server. See the installation log for details.")
def backup_config(self):
try:
backup_config(self.dogtag_constants)
except Exception, e:
root_logger.warning("Failed to backup CS.cfg: %s", e)
def __disable_nonce(self):
# Turn off Nonces
update_result = installutils.update_file(
@ -1320,6 +1350,8 @@ class CAInstance(service.Service):
if not enabled is None and not enabled:
self.disable()
# Just eat this state if it exists
installed = self.restore_state("installed")
try:
if self.dogtag_constants.DOGTAG_VERSION >= 10:
ipautil.run([paths.PKIDESTROY, "-i",
@ -1355,9 +1387,12 @@ class CAInstance(service.Service):
# remove CRL files
root_logger.info("Remove old CRL files")
for f in get_crl_files():
root_logger.debug("Remove %s", f)
installutils.remove_file(f)
try:
for f in get_crl_files():
root_logger.debug("Remove %s", f)
installutils.remove_file(f)
except OSError, e:
root_logger.warning("Error while removing old CRL files: %s" % e)
# remove CRL directory
root_logger.info("Remove CRL directory")
@ -1417,7 +1452,7 @@ class CAInstance(service.Service):
secdir=paths.HTTPD_ALIAS_DIR,
pre_command=None,
post_command='renew_ra_cert')
except (ipautil.CalledProcessError, RuntimeError), e:
except RuntimeError, e:
root_logger.error(
"certmonger failed to start tracking certificate: %s" % e)
@ -1445,7 +1480,7 @@ class CAInstance(service.Service):
secdir=self.dogtag_constants.ALIAS_DIR,
pre_command='stop_pkicad',
post_command='renew_ca_cert "%s"' % nickname)
except (ipautil.CalledProcessError, RuntimeError), e:
except RuntimeError, e:
root_logger.error(
"certmonger failed to start tracking certificate: %s" % e)
@ -1465,7 +1500,7 @@ class CAInstance(service.Service):
secdir=self.dogtag_constants.ALIAS_DIR,
pre_command=None,
post_command=None)
except (ipautil.CalledProcessError, RuntimeError), e:
except RuntimeError, e:
root_logger.error(
"certmonger failed to start tracking certificate: %s" % e)
@ -1596,12 +1631,15 @@ class CAInstance(service.Service):
return True
return False
def is_renewal_master(self):
def is_renewal_master(self, fqdn=None):
if fqdn is None:
fqdn = api.env.host
if not self.admin_conn:
self.ldap_connect()
dn = DN(('cn', 'CA'), ('cn', api.env.host), ('cn', 'masters'),
('cn', 'ipa'), ('cn', 'etc'), api.env.basedn)
dn = DN(('cn', 'CA'), ('cn', fqdn), ('cn', 'masters'), ('cn', 'ipa'),
('cn', 'etc'), api.env.basedn)
filter = '(ipaConfigString=caRenewalMaster)'
try:
self.admin_conn.get_entries(base_dn=dn, filter=filter,
@ -1611,6 +1649,38 @@ class CAInstance(service.Service):
return True
def set_renewal_master(self, fqdn=None):
if fqdn is None:
fqdn = api.env.host
if not self.admin_conn:
self.ldap_connect()
base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
api.env.basedn)
filter = '(&(cn=CA)(ipaConfigString=caRenewalMaster))'
try:
entries = self.admin_conn.get_entries(
base_dn=base_dn, filter=filter, attrs_list=['ipaConfigString'])
except errors.NotFound:
entries = []
dn = DN(('cn', 'CA'), ('cn', fqdn), base_dn)
master_entry = self.admin_conn.get_entry(dn, ['ipaConfigString'])
for entry in entries:
if master_entry is not None and entry.dn == master_entry.dn:
master_entry = None
continue
entry['ipaConfigString'] = [x for x in entry['ipaConfigString']
if x.lower() != 'carenewalmaster']
self.admin_conn.update_entry(entry)
if master_entry is not None:
master_entry['ipaConfigString'].append('caRenewalMaster')
self.admin_conn.update_entry(master_entry)
def replica_ca_install_check(config):
if not config.setup_ca:
@ -1740,6 +1810,16 @@ def install_replica_ca(config, postinstall=False):
return ca
def backup_config(dogtag_constants=None):
"""
Create a backup copy of CS.cfg
"""
if dogtag_constants is None:
dogtag_constants = dogtag.configured_constants()
shutil.copy(dogtag_constants.CS_CFG_PATH,
dogtag_constants.CS_CFG_PATH + '.ipabkp')
def update_cert_config(nickname, cert, dogtag_constants=None):
"""
When renewing a CA subsystem certificate the configuration file
@ -1761,6 +1841,10 @@ def update_cert_config(nickname, cert, dogtag_constants=None):
with stopped_service(dogtag_constants.SERVICE_NAME,
instance_name=dogtag_constants.PKI_INSTANCE_NAME):
try:
backup_config(dogtag_constants)
except Exception, e:
syslog.syslog(syslog.LOG_ERR, "Failed to backup CS.cfg: %s" % e)
installutils.set_directive(dogtag.configured_constants().CS_CFG_PATH,
directives[nickname],

View File

@ -209,9 +209,21 @@ class NSSDatabase(object):
raise RuntimeError(
"Setting trust on %s failed" % root_nickname)
def get_cert(self, nickname, pem=False):
args = ['-L', '-n', nickname]
if pem:
args.append('-a')
else:
args.append('-r')
try:
cert, err, returncode = self.run_certutil(args)
except ipautil.CalledProcessError:
raise RuntimeError("Failed to get %s" % nickname)
return cert
def export_pem_cert(self, nickname, location):
"""Export the given cert to PEM file in the given location"""
cert, err, returncode = self.run_certutil(["-L", "-n", nickname, "-a"])
cert = self.get_cert(nickname)
with open(location, "w+") as fd:
fd.write(cert)
os.chmod(location, 0444)
@ -510,46 +522,26 @@ class CertDB(object):
else:
libpath = 'lib'
command = paths.CERTMONGER_COMMAND_TEMPLATE % (libpath, command)
cmonger = services.knownservices.certmonger
cmonger.enable()
services.knownservices.messagebus.start()
cmonger.start()
try:
(stdout, stderr, rc) = certmonger.start_tracking(nickname, self.secdir, password_file, command)
except (ipautil.CalledProcessError, RuntimeError), e:
request_id = certmonger.start_tracking(nickname, self.secdir, password_file, command)
except RuntimeError, e:
root_logger.error("certmonger failed starting to track certificate: %s" % str(e))
return
cmonger.stop()
cert = self.get_cert_from_db(nickname)
nsscert = x509.load_certificate(cert, dbdir=self.secdir)
subject = str(nsscert.subject)
m = re.match('New tracking request "(\d+)" added', stdout)
if not m:
root_logger.error('Didn\'t get new %s request, got %s' % (cmonger.service_name, stdout))
raise RuntimeError('%s did not issue new tracking request for \'%s\' in \'%s\'. Use \'ipa-getcert list\' to list existing certificates.' % (cmonger.service_name, nickname, self.secdir))
request_id = m.group(1)
certmonger.add_principal(request_id, principal)
certmonger.add_subject(request_id, subject)
cmonger.start()
def untrack_server_cert(self, nickname):
"""
Tell certmonger to stop tracking the given certificate nickname.
"""
# Always start certmonger. We can't untrack something if it isn't
# running
cmonger = services.knownservices.certmonger
services.knownservices.messagebus.start()
cmonger.start()
try:
certmonger.stop_tracking(self.secdir, nickname=nickname)
except (ipautil.CalledProcessError, RuntimeError), e:
except RuntimeError, e:
root_logger.error("certmonger failed to stop tracking certificate: %s" % str(e))
cmonger.stop()
def create_server_cert(self, nickname, hostname, other_certdb=None, subject=None):
"""

View File

@ -221,14 +221,14 @@ info: IPA V2.0
class DsInstance(service.Service):
def __init__(self, realm_name=None, domain_name=None, dm_password=None,
fstore=None, cert_nickname='Server-Cert'):
fstore=None):
service.Service.__init__(self, "dirsrv",
service_desc="directory server",
dm_password=dm_password,
ldapi=False,
autobind=service.DISABLED
)
self.nickname = cert_nickname
self.nickname = 'Server-Cert'
self.dm_password = dm_password
self.realm = realm_name
self.sub_dict = None
@ -632,24 +632,23 @@ class DsInstance(service.Service):
raise RuntimeError("Could not find a suitable server cert in import in %s" % self.pkcs12_info[0])
# We only handle one server cert
nickname = server_certs[0][0]
self.dercert = dsdb.get_cert_from_db(nickname, pem=False)
self.nickname = server_certs[0][0]
self.dercert = dsdb.get_cert_from_db(self.nickname, pem=False)
else:
nickname = self.nickname
cadb = certs.CertDB(self.realm, host_name=self.fqdn, subject_base=self.subject_base)
# FIXME, need to set this nickname in the RA plugin
cadb.export_ca_cert('ipaCert', False)
dsdb.create_from_cacert(cadb.cacert_fname, passwd=None)
self.dercert = dsdb.create_server_cert(
nickname, self.fqdn, cadb)
self.nickname, self.fqdn, cadb)
dsdb.create_pin_file()
self.cacert_name = dsdb.cacert_name
if self.ca_is_configured:
dsdb.track_server_cert(
nickname, self.principal, dsdb.passwd_fname,
self.nickname, self.principal, dsdb.passwd_fname,
'restart_dirsrv %s' % self.serverid)
conn = ipaldap.IPAdmin(self.fqdn)
@ -670,7 +669,7 @@ class DsInstance(service.Service):
DN(('cn', 'RSA'), ('cn', 'encryption'), ('cn', 'config')),
objectclass=["top", "nsEncryptionModule"],
cn=["RSA"],
nsSSLPersonalitySSL=[nickname],
nsSSLPersonalitySSL=[self.nickname],
nsSSLToken=["internal (software)"],
nsSSLActivation=["on"],
)

View File

@ -487,7 +487,7 @@ def get_server_ip_address(host_name, fstore, unattended, options):
hosts_record = record_in_hosts(ip_address)
if hosts_record is None:
if ip_add_to_hosts:
if ip_add_to_hosts or options.setup_dns:
print "Adding ["+ip_address+" "+host_name+"] to your /etc/hosts file"
fstore.backup_file(paths.HOSTS)
add_record_to_hosts(ip_address, host_name)
@ -734,8 +734,6 @@ def check_pkcs12(pkcs12_info, ca_file, hostname):
This is used for files given to --*_pkcs12 to ipa-server-install and
ipa-replica-prepare.
Return a (server cert name, CA cert names) tuple
"""
pkcs12_filename, pkcs12_passwd = pkcs12_info
root_logger.debug('Checking PKCS#12 certificate %s', pkcs12_filename)
@ -746,13 +744,18 @@ def check_pkcs12(pkcs12_info, ca_file, hostname):
# Import the CA cert first so it has a known nickname
# (if it's present in the PKCS#12 it won't be overwritten)
ca_cert_name = 'The Root CA'
try:
nssdb.import_pem_cert(ca_cert_name, "CT,C,C", ca_file)
except (ValueError, RuntimeError) as e:
raise ScriptError(str(e))
if ca_file:
try:
nssdb.import_pem_cert(ca_cert_name, "CT,C,C", ca_file)
except (ValueError, RuntimeError) as e:
raise ScriptError(str(e))
# Import everything in the PKCS#12
nssdb.import_pkcs12(pkcs12_filename, db_pwd_file.name, pkcs12_passwd)
try:
nssdb.import_pkcs12(
pkcs12_filename, db_pwd_file.name, pkcs12_passwd)
except RuntimeError as e:
raise ScriptError(str(e))
# Check we have exactly one server cert (one with a private key)
server_certs = nssdb.find_server_certs()
@ -767,21 +770,23 @@ def check_pkcs12(pkcs12_info, ca_file, hostname):
# Check we have the whole cert chain & the CA is in it
trust_chain = nssdb.get_trust_chain(server_cert_name)
while trust_chain:
if trust_chain[0] == ca_cert_name:
break
trust_chain = trust_chain[1:]
else:
if len(trust_chain) < 2:
if ca_file:
raise ScriptError(
'%s is not signed by %s, or the full certificate chain is '
'not present in the PKCS#12 file' %
(pkcs12_filename, ca_file))
else:
raise ScriptError(
'The full certificate chain is not present in %s' %
pkcs12_filename)
if ca_file and trust_chain[-2] != ca_cert_name:
raise ScriptError(
'%s is not signed by %s, or the full certificate chain is not '
'present in the PKCS#12 file' % (pkcs12_filename, ca_file))
if len(trust_chain) != 2:
raise ScriptError(
'trust chain of the server certificate in %s contains %s '
'certificates, expected 2' %
(pkcs12_filename, len(trust_chain)))
'%s is not signed by %s' % (pkcs12_filename, ca_file))
ca_cert_name = trust_chain[-2]
# Check server validity
nssdb.trust_root_cert(ca_cert_name)
try:
nssdb.verify_server_cert_validity(server_cert_name, hostname)
except ValueError as e:
@ -789,8 +794,7 @@ def check_pkcs12(pkcs12_info, ca_file, hostname):
'The server certificate in %s is not valid: %s' %
(pkcs12_filename, e))
return server_cert_name
return nssdb.get_cert(ca_cert_name)
@contextmanager
def private_ccache(path=None):

View File

@ -139,7 +139,7 @@ class ReplicaPrepare(admintool.AdminTool):
"could not find directory instance: %s" % config_dir)
def check_pkcs12(self, pkcs12_file, pkcs12_pin):
installutils.check_pkcs12(
return installutils.check_pkcs12(
pkcs12_info=(pkcs12_file, pkcs12_pin),
ca_file=CACERT,
hostname=self.replica_fqdn)
@ -221,7 +221,8 @@ class ReplicaPrepare(admintool.AdminTool):
if options.http_pin is None:
raise admintool.ScriptError(
"%s unlock password required" % options.http_pkcs12)
self.check_pkcs12(options.http_pkcs12, options.http_pin)
http_ca_cert = self.check_pkcs12(
options.http_pkcs12, options.http_pin)
if options.dirsrv_pkcs12:
if options.dirsrv_pin is None:
@ -231,7 +232,8 @@ class ReplicaPrepare(admintool.AdminTool):
if options.dirsrv_pin is None:
raise admintool.ScriptError(
"%s unlock password required" % options.dirsrv_pkcs12)
self.check_pkcs12(options.dirsrv_pkcs12, options.dirsrv_pin)
dirsrv_ca_cert = self.check_pkcs12(
options.dirsrv_pkcs12, options.dirsrv_pin)
if options.pkinit_pkcs12:
if options.pkinit_pin is None:
@ -242,6 +244,12 @@ class ReplicaPrepare(admintool.AdminTool):
raise admintool.ScriptError(
"%s unlock password required" % options.pkinit_pkcs12)
if (options.http_pkcs12 and options.dirsrv_pkcs12 and
http_ca_cert != dirsrv_ca_cert):
raise admintool.ScriptError(
"%s and %s are not signed by the same CA certificate" %
(options.http_pkcs12, options.dirsrv_pkcs12))
if (not ipautil.file_exists(
dogtag.configured_constants().CS_CFG_PATH) and
options.dirsrv_pin is None):

View File

@ -154,7 +154,7 @@ class ServerCertInstall(admintool.AdminTool):
os.chown(os.path.join(dirname, 'secmod.db'), 0, pent.pw_gid)
def import_cert(self, dirname, pkcs12_passwd, old_cert, principal, command):
server_cert = installutils.check_pkcs12(
installutils.check_pkcs12(
pkcs12_info=(self.pkcs12_fname, pkcs12_passwd),
ca_file=CACERT,
hostname=api.env.host)
@ -166,6 +166,7 @@ class ServerCertInstall(admintool.AdminTool):
cdb.delete_cert(old_cert)
cdb.import_pkcs12(self.pkcs12_fname, pkcs12_passwd)
server_cert = cdb.find_server_certs()[0][0]
if api.env.enable_ra:
cdb.track_server_cert(server_cert, principal, cdb.passwd_fname,

View File

@ -52,10 +52,10 @@ class update_ca_renewal_master(PostUpdate):
self.debug("found CA renewal master %s", entries[0].dn[1].value)
return (False, False, [])
criteria = (
('cert_storage_location', paths.HTTPD_ALIAS_DIR, certmonger.NPATH),
('cert_nickname', 'ipaCert', None),
)
criteria = {
'cert-database': paths.HTTPD_ALIAS_DIR,
'cert-nickname': 'ipaCert',
}
request_id = certmonger.get_request_id(criteria)
if request_id is not None:
self.debug("found certmonger request for ipaCert")

View File

@ -61,6 +61,8 @@ class update_dnszones(PostUpdate):
def execute(self, **options):
ldap = self.obj.backend
if not dns_container_exists(ldap):
return (False, False, [])
try:
zones = api.Command.dnszone_find(all=True)['result']
@ -153,6 +155,8 @@ class update_check_forwardzones(PreSchemaUpdate):
# no upgrade is needed
return (False, False, [])
ldap = self.obj.backend
if not dns_container_exists(ldap): # No DNS installed
return (False, False, [])
result = ldap.schema.get_obj(_ldap.schema.models.ObjectClass, 'idnsforwardzone')
if result is None:
sysupgrade.set_upgrade_state('dns', 'update_to_forward_zones', True)

View File

@ -96,6 +96,24 @@ from ipaserver.install.plugins.baseupdate import PostUpdate
register = Registry()
NONOBJECT_PERMISSIONS = {
'System: Read Timestamp and USN Operational Attributes': {
'ipapermlocation': api.env.basedn,
'ipapermtargetfilter': {'(objectclass=*)'},
'ipapermbindruletype': 'anonymous',
'ipapermright': {'read', 'search', 'compare'},
'ipapermdefaultattr': {
'createtimestamp', 'modifytimestamp', 'entryusn',
},
},
'System: Read Creator and Modifier Operational Attributes': {
'ipapermlocation': api.env.basedn,
'ipapermtargetfilter': {'(objectclass=*)'},
'ipapermbindruletype': 'all',
'ipapermright': {'read', 'search', 'compare'},
'ipapermdefaultattr': {
'creatorsname', 'modifiersname',
},
},
'System: Read IPA Masters': {
'replaces_global_anonymous_aci': True,
'ipapermlocation': DN('cn=masters,cn=ipa,cn=etc', api.env.basedn),

View File

@ -178,15 +178,24 @@ class ldap2(LDAPClient, CrudBackend):
# ignore when trying to unbind multiple times
pass
def find_entries(self, filter=None, attrs_list=None, base_dn=None,
scope=_ldap.SCOPE_SUBTREE, time_limit=None,
size_limit=None, search_refs=False, paged_search=False):
if time_limit is None or size_limit is None:
config = self.get_ipa_config()
if time_limit is None:
time_limit = config.get('ipasearchtimelimit', [None])[0]
if size_limit is None:
size_limit = config.get('ipasearchrecordslimit', [None])[0]
def _get_limits():
"""Get configured global limits, caching them for more calls"""
if not _lims:
config = self.get_ipa_config()
_lims['time'] = config.get('ipasearchtimelimit', [None])[0]
_lims['size'] = config.get('ipasearchrecordslimit', [None])[0]
return _lims
_lims = {}
if time_limit is None:
time_limit = _get_limits()['time']
if size_limit is None:
size_limit = _get_limits()['size']
has_memberindirect = False
has_memberofindirect = False
@ -207,6 +216,20 @@ class ldap2(LDAPClient, CrudBackend):
search_refs=search_refs, paged_search=paged_search)
if has_memberindirect or has_memberofindirect:
# For the memberof searches, we want to apply the global limit
# if it's larger than the requested one, so decreasing limits on
# the individual query only affects the query itself.
# See https://fedorahosted.org/freeipa/ticket/4398
def _max_with_none(a, b):
"""Maximum of a and b, treating None as infinity"""
if a is None or b is None:
return None
else:
return max(a, b)
time_limit = _max_with_none(time_limit, _get_limits()['time'])
size_limit = _max_with_none(size_limit, _get_limits()['size'])
for entry in res:
if has_memberindirect:
self._process_memberindirect(

View File

@ -39,7 +39,7 @@ from ipalib.capabilities import VERSION_WITHOUT_CAPABILITIES
from ipalib.backend import Executioner
from ipalib.errors import (PublicError, InternalError, CommandError, JSONError,
CCacheError, RefererError, InvalidSessionPassword, NotFound, ACIError,
ExecutionError)
ExecutionError, PasswordExpired)
from ipalib.request import context, destroy_context
from ipalib.rpc import (xml_dumps, xml_loads,
json_encode_binary, json_decode_binary)
@ -944,37 +944,12 @@ class login_password(Backend, KerberosSession, HTTP_Status):
# Get the ccache we'll use and attempt to get credentials in it with user,password
ipa_ccache_name = get_ipa_ccache_name()
reason = 'invalid-password'
try:
self.kinit(user, self.api.env.realm, password, ipa_ccache_name)
except InvalidSessionPassword, e:
# Ok, now why is this bad. Is the password simply bad or is the
# password expired?
try:
dn = DN(('uid', user),
self.api.env.container_user,
self.api.env.basedn)
conn = ldap2(shared_instance=False,
ldap_uri=self.api.env.ldap_uri)
conn.connect(bind_dn=dn, bind_pw=password)
# password is ok, must be expired, lets double-check
entry_attrs = conn.get_entry(dn,
['krbpasswordexpiration'])
if 'krbpasswordexpiration' in entry_attrs:
expiration = entry_attrs['krbpasswordexpiration'][0]
if expiration <= datetime.datetime.utcnow():
reason = 'password-expired'
except Exception:
# It doesn't really matter how we got here but the user's
# password is not accepted or the user is unknown.
pass
finally:
if conn.isconnected():
conn.destroy_connection()
return self.unauthorized(environ, start_response, str(e), reason)
except PasswordExpired as e:
return self.unauthorized(environ, start_response, str(e), 'password-expired')
except InvalidSessionPassword as e:
return self.unauthorized(environ, start_response, str(e), 'invalid-password')
return self.finalize_kerberos_acquisition('login_password', ipa_ccache_name, environ, start_response)
@ -1001,7 +976,8 @@ class login_password(Backend, KerberosSession, HTTP_Status):
(stdout, stderr, returncode) = ipautil.run(
[paths.KINIT, principal, '-T', armor_path],
env={'KRB5CCNAME': ccache_name}, stdin=password, raiseonerr=False)
env={'KRB5CCNAME': ccache_name, 'LC_ALL': 'C'},
stdin=password, raiseonerr=False)
self.debug('kinit: principal=%s returncode=%s, stderr="%s"',
principal, returncode, stderr)
@ -1013,6 +989,8 @@ class login_password(Backend, KerberosSession, HTTP_Status):
raiseonerr=False)
if returncode != 0:
if stderr.strip() == 'kinit: Cannot read password while getting initial credentials':
raise PasswordExpired(principal=principal, message=unicode(stderr))
raise InvalidSessionPassword(principal=principal, message=unicode(stderr))
class change_password(Backend, HTTP_Status):

View File

@ -86,10 +86,8 @@ class test_ipagetkeytab(cmdline_test):
"-k", self.keytabname,
]
(out, err, rc) = ipautil.run(new_args, stdin=None, raiseonerr=False)
assert err == (
'Failed to parse result! PrincipalName not found.\n\n'
'Failed to get keytab\n'
), err
assert 'Failed to parse result: PrincipalName not found.\n' in err, err
assert rc > 0, rc
def test_2_run(self):
"""

View File

@ -315,15 +315,7 @@ def configure_dns_for_trust(master, ad):
kinit_admin(master)
if is_subdomain(master.domain.name, ad.domain.name):
master.run_command(['ipa', 'dnszone-add', ad.domain.name,
'--name-server', ad.hostname,
'--admin-email', 'hostmaster@%s' % ad.domain.name,
'--forwarder', ad.ip,
'--forward-policy', 'only',
'--ip-address', ad.ip,
'--force'])
elif is_subdomain(ad.domain.name, master.domain.name):
if is_subdomain(ad.domain.name, master.domain.name):
master.run_command(['ipa', 'dnsrecord-add', master.domain.name,
'%s.%s' % (ad.shortname, ad.netbios),
'--a-ip-address', ad.ip])
@ -336,13 +328,10 @@ def configure_dns_for_trust(master, ad):
master.run_command(['ipa', 'dnszone-mod', master.domain.name,
'--allow-transfer', ad.ip])
else:
master.run_command(['ipa', 'dnszone-add', ad.domain.name,
'--name-server', ad.hostname,
'--admin-email', 'hostmaster@%s' % ad.domain.name,
master.run_command(['ipa', 'dnsforwardzone-add', ad.domain.name,
'--forwarder', ad.ip,
'--forward-policy', 'only',
'--ip-address', ad.ip,
'--force'])
])
def establish_trust_with_ad(master, ad, extra_args=()):

View File

@ -17,6 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import nose
import re
from ipatests.test_integration.base import IntegrationTest
@ -29,6 +30,7 @@ class ADTrustBase(IntegrationTest):
topology = 'line'
num_ad_domains = 1
optional_extra_roles = ['ad_subdomain']
@classmethod
def install(cls):
@ -38,6 +40,14 @@ class ADTrustBase(IntegrationTest):
cls.check_sid_generation()
cls.configure_dns_and_time()
# Determine whether the subdomain AD is available
try:
child_ad = cls.host_by_role(cls.optional_extra_roles[0])
cls.ad_subdomain = '.'.join(
child_ad.hostname.split('.')[1:])
except LookupError:
cls.ad_subdomain = None
@classmethod
def install_adtrust(cls):
"""Test adtrust support installation"""
@ -64,16 +74,32 @@ class ADTrustBase(IntegrationTest):
tasks.configure_dns_for_trust(cls.master, cls.ad)
tasks.sync_time(cls.master, cls.ad)
class TestBasicADTrust(ADTrustBase):
"""Basic Integration test for Active Directory"""
def test_establish_trust(self):
"""Tests establishing trust with Active Directory"""
tasks.establish_trust_with_ad(self.master, self.ad,
extra_args=['--range-type', 'ipa-ad-trust'])
def test_all_trustdomains_found(self):
"""
Tests that all trustdomains can be found.
"""
if self.ad_subdomain is None:
raise nose.SkipTest('AD subdomain is not available.')
result = self.master.run_command(['ipa',
'trustdomain-find',
self.ad.domain.name])
# Check that both trustdomains appear in the result
assert self.ad.domain.name in result.stdout_text
assert self.ad_subdomain in result.stdout_text
class TestBasicADTrust(ADTrustBase):
"""Basic Integration test for Active Directory"""
def test_range_properties_in_nonposix_trust(self):
"""Check the properties of the created range"""
@ -111,7 +137,7 @@ class TestBasicADTrust(ADTrustBase):
class TestPosixADTrust(ADTrustBase):
"""Integration test for Active Directory with POSIX support"""
def test_establish_trust_with_posix_attributes(self):
def test_establish_trust(self):
# Not specifying the --range-type directly, it should be detected
tasks.establish_trust_with_ad(self.master, self.ad)

View File

@ -33,11 +33,11 @@ DATA = {
('textbox', 'aciname', PKEY),
('combobox', 'group', 'editors'),
('combobox', 'memberof', 'ipausers'),
('table', 'attrs', 'audio'),
('table', 'attrs', 'businesscategory'),
('checkbox', 'attrs', 'audio'),
('checkbox', 'attrs', 'businesscategory'),
],
'mod': [
('table', 'attrs', 'businesscategory'),
('checkbox', 'attrs', 'businesscategory'),
],
}

View File

@ -61,11 +61,11 @@ PERMISSION_DATA = {
('checkbox', 'ipapermright', 'write'),
('checkbox', 'ipapermright', 'read'),
('selectbox', 'type', 'user'),
('table', 'attrs', 'audio'),
('table', 'attrs', 'cn'),
('checkbox', 'attrs', 'audio'),
('checkbox', 'attrs', 'cn'),
],
'mod': [
('table', 'attrs', 'carlicense'),
('checkbox', 'attrs', 'carlicense'),
],
}

View File

@ -30,11 +30,11 @@ DATA = {
'pkey': PKEY,
'add': [
('textbox', 'aciname', PKEY),
('table', 'attrs', 'audio'),
('table', 'attrs', 'businesscategory'),
('checkbox', 'attrs', 'audio'),
('checkbox', 'attrs', 'businesscategory'),
],
'mod': [
('table', 'attrs', 'businesscategory'),
('checkbox', 'attrs', 'businesscategory'),
],
}

View File

@ -368,9 +368,9 @@ class UI_driver(object):
self.wait_for_request(n=2)
# reset password if needed
newpw_tb = self.find("//input[@type='password'][@name='new_password']", 'xpath', auth, strict=True)
verify_tb = self.find("//input[@type='password'][@name='verify_password']", 'xpath', auth, strict=True)
if newpw_tb.is_displayed():
newpw_tb = self.find("//input[@type='password'][@name='new_password']", 'xpath', auth)
verify_tb = self.find("//input[@type='password'][@name='verify_password']", 'xpath', auth)
if newpw_tb and newpw_tb.is_displayed():
newpw_tb.send_keys(new_password)
verify_tb.send_keys(new_password)
verify_tb.send_keys(Keys.RETURN)
@ -609,7 +609,7 @@ class UI_driver(object):
if not dialog:
dialog = self.get_dialog(strict=True)
s = ".rcue-dialog-buttons button[name=%s]" % name
s = ".rcue-dialog-buttons button[name='%s']" % name
self._button_click(s, dialog, name)
def action_button_click(self, name, parent):
@ -634,10 +634,8 @@ class UI_driver(object):
def _button_click(self, selector, parent, name=''):
btn = self.find(selector, By.CSS_SELECTOR, parent, strict=True)
disabled = 'ui-state-disabled' in btn.get_attribute("class").split() or \
btn.get_attribute("disabled")
ActionChains(self.driver).move_to_element(btn).perform()
disabled = btn.get_attribute("disabled")
assert btn.is_displayed(), 'Button is not displayed: %s' % name
assert not disabled, 'Invalid button state: disabled. Button: %s' % name
btn.click()
@ -940,13 +938,18 @@ class UI_driver(object):
parent = self.get_form()
s = self.get_table_selector(table_name)
s += " tbody td input[value='%s']+label" % pkey
label = self.find(s, By.CSS_SELECTOR, parent, strict=True)
input_s = s + " tbody td input[value='%s']" % pkey
checkbox = self.find(input_s, By.CSS_SELECTOR, parent, strict=True)
checkbox_id = checkbox.get_attribute('id')
label_s = s + " tbody td label[for='%s']" % checkbox_id
print label_s
label = self.find(label_s, By.CSS_SELECTOR, parent, strict=True)
try:
ActionChains(self.driver).move_to_element(label).click().perform()
except WebDriverException as e:
assert False, 'Can\'t click on checkbox label: %s \n%s' % (s, e)
self.wait()
assert checkbox.is_selected(), 'Record was not checked: %s' % input_s
self.wait()
def get_record_value(self, pkey, column, parent=None, table_name=None):
@ -1011,7 +1014,7 @@ class UI_driver(object):
if table_name and parent:
s = self.get_table_selector(table_name)
table = self.find(s, By.CSS_SELECTOR, parent, strict=True)
self.action_button_click('remove', table)
self.button_click('remove', table)
else:
self.facet_button_click('remove')
if fields:
@ -1323,7 +1326,7 @@ class UI_driver(object):
parent = self.get_form()
s = self.get_table_selector(name)
table = self.find(s, By.CSS_SELECTOR, parent, strict=True)
s = "a[name=%s].button" % 'add'
s = ".btn[name=%s]" % 'add'
btn = self.find(s, By.CSS_SELECTOR, table, strict=True)
btn.click()
self.wait()
@ -1339,6 +1342,7 @@ class UI_driver(object):
self.switch_to_facet(facet)
self.facet_button_click('add')
self.wait()
self.wait_for_request()
for key in pkeys:
@ -1364,7 +1368,7 @@ class UI_driver(object):
s = self.get_table_selector(table_name)
table = self.find(s, By.CSS_SELECTOR, parent, strict=True)
s = "a[name=%s].button" % 'add'
s = "button[name='%s']" % 'add'
btn = self.find(s, By.CSS_SELECTOR, table, strict=True)
btn.click()
self.wait_for_request(0.4)
@ -1372,6 +1376,7 @@ class UI_driver(object):
for key in pkeys:
self.select_record(key, table_name='available')
self.button_click('add')
self.wait()
self.dialog_button_click('add')
self.wait_for_request(n=2)
@ -1550,21 +1555,6 @@ class UI_driver(object):
"""
assert expected == current, "Rows don't match. Expected: %d, Got: %d" % (expected, current)
def assert_action_button_enabled(self, name, context_selector=None, enabled=True):
"""
Assert that action-button is enabled or disabled
"""
s = ""
if context_selector:
s = context_selector
s += "a[name=%s]" % name
facet = self.get_facet()
btn = self.find(s, By.CSS_SELECTOR, facet, strict=True)
cls = 'action-button-disabled'
valid = enabled ^ self.has_class(btn, cls)
assert btn.is_displayed(), 'Button is not displayed'
assert valid, 'Button has incorrect enabled state.'
def assert_button_enabled(self, name, context_selector=None, enabled=True):
"""
Assert that button is enabled or disabled (expects that element will be
@ -1591,7 +1581,7 @@ class UI_driver(object):
Assert that button in table is enabled/disabled
"""
s = "table[name='%s'] " % table_name
self.assert_action_button_enabled(name, s, enabled)
self.assert_button_enabled(name, s, enabled)
def assert_facet(self, entity, facet=None):
"""

View File

@ -21,8 +21,13 @@
Test the `ipalib.plugins.baseldap` module.
"""
import ldap
from ipapython.dn import DN
from ipapython import ipaldap
from ipalib import errors
from ipalib.plugins import baseldap
from ipatests.util import assert_deepequal
def test_exc_wrapper():
@ -157,3 +162,47 @@ def test_exc_callback_registration():
messages = []
subclass_instance.test_fail()
assert messages == ['Base exc_callback', 'Subclass registered callback']
def test_entry_to_dict():
class FakeAttributeType(object):
def __init__(self, name, syntax):
self.names = (name,)
self.syntax = syntax
class FakeSchema(object):
def get_obj(self, type, name):
if type != ldap.schema.AttributeType:
return
if name == 'binaryattr':
return FakeAttributeType(name, '1.3.6.1.4.1.1466.115.121.1.40')
elif name == 'textattr':
return FakeAttributeType(name, '1.3.6.1.4.1.1466.115.121.1.15')
elif name == 'dnattr':
return FakeAttributeType(name, '1.3.6.1.4.1.1466.115.121.1.12')
class FakeIPASimpleLDAPObject(ipaldap.IPASimpleLDAPObject):
def __init__(self):
super(FakeIPASimpleLDAPObject, self).__init__('ldap://test', False)
self._schema = FakeSchema()
self._has_schema = True
conn = FakeIPASimpleLDAPObject()
rights = {'nothing': 'is'}
entry = ipaldap.LDAPEntry(
conn,
DN('cn=test'),
textattr=[u'text'],
dnattr=[DN('cn=test')],
binaryattr=['\xffabcd'],
attributelevelrights=rights)
the_dict = {
u'dn': u'cn=test',
u'textattr': [u'text'],
u'dnattr': [u'cn=test'],
u'binaryattr': ['\xffabcd'],
u'attributelevelrights': rights}
assert_deepequal(
baseldap.entry_to_dict(entry, all=True, raw=True),
the_dict)

View File

@ -263,6 +263,7 @@ zone_findtest_forward = u'forward.find.test.'
zone_findtest_forward_dnsname = DNSName(zone_findtest_forward)
zone_findtest_forward_dn = DN(('idnsname', zone_findtest_forward), api.env.container_dns, api.env.basedn)
zone_fw_wildcard = u'*.wildcardforwardzone.test.'
class test_dns(Declarative):
@ -289,7 +290,8 @@ class test_dns(Declarative):
revzone3_classless1, revzone3_classless2,
idnzone1, revidnzone1, zone_findtest_master],
{'continue': True}),
('dnsforwardzone_del', [fwzone1, zone_findtest_forward],
('dnsforwardzone_del', [fwzone1, zone_findtest_forward,
zone_fw_wildcard],
{'continue': True}),
('dnsconfig_mod', [], {'idnsforwarders' : None,
'idnsforwardpolicy' : None,
@ -1378,10 +1380,10 @@ class test_dns(Declarative):
dict(
desc='Delete record %r in zone %r' % (tlsa, zone1),
command=('dnsrecord_del', [zone1, tlsa], {'del_all': True}),
desc='Remove record using dnsrecord-mod %r in zone %r' % (tlsa, zone1),
command=('dnsrecord_mod', [zone1, tlsa], {'tlsarecord': ''}),
expected={
'value': [tlsa_dnsname],
'value': tlsa_dnsname,
'summary': u'Deleted record "%s"' % tlsa,
'result': {'failed': []},
},
@ -2735,6 +2737,39 @@ class test_dns(Declarative):
),
dict(
desc='Try to add NS record to wildcard owner %r in zone %r' % (wildcard_rec1, zone1),
command=('dnsrecord_add', [zone1, wildcard_rec1], {'nsrecord': zone2_ns, 'force': True}),
expected=errors.ValidationError(
name='idnsname',
error=(u'owner of DNAME, DS, NS records '
'should not be a wildcard domain name (RFC 4592 section 4)')
)
),
dict(
desc='Try to add DNAME record to wildcard owner %r in zone %r' % (wildcard_rec1, zone1),
command=('dnsrecord_add', [zone1, wildcard_rec1], {'dnamerecord': u'dname.test.'}),
expected=errors.ValidationError(
name='idnsname',
error=(u'owner of DNAME, DS, NS records '
'should not be a wildcard domain name (RFC 4592 section 4)')
)
),
dict(
desc='Try to add DS record to wildcard owner %r in zone %r' % (wildcard_rec1, zone1),
command=('dnsrecord_add', [zone1, wildcard_rec1], {'dsrecord': u'0 0 0 00'}),
expected=errors.ValidationError(
name='idnsname',
error=(u'owner of DNAME, DS, NS records '
'should not be a wildcard domain name (RFC 4592 section 4)')
)
),
dict(
desc='Add A denormalized record in zone %r' % (idnzone1),
command=('dnsrecord_add', [idnzone1, u'gro\xdf'], {'arecord': u'172.16.0.1'}),
@ -2743,6 +2778,16 @@ class test_dns(Declarative):
),
dict(
desc='Try to create forward zone %r with wildcard domain name' % zone_fw_wildcard,
command=(
'dnsforwardzone_add', [zone_fw_wildcard], {'idnsforwardpolicy': u'none'}
),
expected=errors.ValidationError(name='name',
error=u'should not be a wildcard domain name (RFC 4592 section 4)')
),
dict(
desc='Try to create forward zone %r without forwarders with default "(first)" policy' % fwzone1,
command=(

View File

@ -1009,5 +1009,72 @@ class test_group(Declarative):
value=[user1],
),
),
]
class test_group_remove_group_from_protected_group(Declarative):
cleanup_commands = [
('group_del', [group1], {}),
]
tests = [
# Test scenario from ticket #4448
# https://fedorahosted.org/freeipa/ticket/4448
dict(
desc='Add group %s' % group1,
command=('group_add', [group1], dict(description=u'Test desc 1')),
expected=dict(
value=group1,
summary=u'Added group "%s"' % group1,
result=dict(
cn=[group1],
description=[u'Test desc 1'],
objectclass=objectclasses.posixgroup,
gidnumber=[fuzzy_digits],
ipauniqueid=[fuzzy_uuid],
dn=get_group_dn(group1),
),
),
),
dict(
desc='Add %s group to admins group' % group1,
command=('group_add_member', [u'admins'], dict(group=group1)),
expected=dict(
completed=1,
failed=dict(
member=dict(
group=tuple(),
user=tuple(),
),
),
result=dict(
dn=get_group_dn('admins'),
member_user=[u'admin'],
member_group=[group1],
gidnumber=[fuzzy_digits],
cn=[u'admins'],
description=[u'Account administrators group'],
),
),
),
dict(
desc='Remove %s group from admins group' % group1,
command=('group_remove_member', [u'admins'], dict(group=group1)),
expected=dict(
completed=1,
failed=dict(
member=dict(
group=tuple(),
user=tuple(),
),
),
result=dict(
dn=get_group_dn(u'admins'),
cn=[u'admins'],
gidnumber=[fuzzy_digits],
member_user=[u'admin'],
description=[u'Account administrators group'],
),
),
),
]

View File

@ -27,6 +27,7 @@ import tempfile
from ipapython import ipautil
from ipalib import api, errors, x509
from ipapython.dn import DN
from ipapython.dnsutil import DNSName
from nose.tools import raises, assert_raises
from nose.plugins.skip import SkipTest
from ipatests.test_xmlrpc.xmlrpc_test import (Declarative, XMLRPC_test,
@ -56,6 +57,82 @@ dn4 = DN(('fqdn',fqdn4),('cn','computers'),('cn','accounts'),
api.env.basedn)
invalidfqdn1 = u'foo_bar.lab.%s' % api.env.domain
# DNS integration tests
dnszone = u'zone-ipv6host.test'
dnszone_absolute = dnszone + '.'
dnszone_dnsname = DNSName(dnszone_absolute)
dnszone_dn = DN(('idnsname', dnszone_absolute), api.env.container_dns, api.env.basedn)
dnszone_ns = u'ns1.%s' % dnszone_absolute
dnszone_ns_dnsname = DNSName(dnszone_ns)
dnszone_rname = u'root.%s' % dnszone_absolute
dnszone_rname_dnsname = DNSName(dnszone_rname)
dnszone_ip = u'172.16.29.1'
revzone = u'29.16.172.in-addr.arpa.'
revzone_dnsname = DNSName(revzone)
revzone_ip = u'172.16.29.0'
revzone_ipprefix = u'172.16.29.'
revzone_dn = DN(('idnsname', revzone), api.env.container_dns, api.env.basedn)
revipv6zone = u'0.0.0.0.1.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.'
revipv6zone_dnsname = DNSName(revipv6zone)
revipv6zone_ip = u'2001:db8:1::'
revipv6zone_ipprefix = u'2001:db8:1:'
revipv6zone_dn = DN(('idnsname', revipv6zone), api.env.container_dns, api.env.basedn)
arec = u'172.16.29.22'
aaaarec = u'2001:db8:1::beef'
arec2 = u'172.16.29.33'
aaaarec2 = u'2001:db8:1::dead'
ipv6only = u'testipv6onlyhost'
ipv6only_dnsname = DNSName(ipv6only)
ipv6only_dn = DN(('idnsname', ipv6only), dnszone_dn)
ipv6only_host_fqdn = u'%s.%s' % (ipv6only, dnszone)
ipv6only_host_dn = DN(('fqdn',ipv6only_host_fqdn),('cn','computers'),('cn','accounts'),
api.env.basedn)
ipv4only = u'testipv4onlyhost'
ipv4only_dnsname = DNSName(ipv4only)
ipv4only_dn = DN(('idnsname', ipv4only), dnszone_dn)
ipv4only_host_fqdn = u'%s.%s' % (ipv4only, dnszone)
ipv4only_host_dn = DN(('fqdn',ipv4only_host_fqdn),('cn','computers'),('cn','accounts'),
api.env.basedn)
ipv46both = u'testipv4and6host'
ipv46both_dnsname = DNSName(ipv46both)
ipv46both_dn = DN(('idnsname', ipv46both), dnszone_dn)
ipv46both_host_fqdn = u'%s.%s' % (ipv46both, dnszone)
ipv46both_host_dn = DN(('fqdn',ipv46both_host_fqdn),('cn','computers'),('cn','accounts'),
api.env.basedn)
ipv4_fromip = u'withipv4addr'
ipv4_fromip_ip = u'172.16.29.40'
ipv4_fromip_arec = ipv4_fromip_ip
ipv4_fromip_dnsname = DNSName(ipv4_fromip)
ipv4_fromip_dn = DN(('idnsname', ipv4_fromip), dnszone_dn)
ipv4_fromip_host_fqdn = u'%s.%s' % (ipv4_fromip, dnszone)
ipv4_fromip_host_dn = DN(('fqdn',ipv4_fromip_host_fqdn),('cn','computers'),('cn','accounts'),
api.env.basedn)
ipv4_fromip_ptr = u'40'
ipv4_fromip_ptrrec = ipv4_fromip_host_fqdn + '.'
ipv4_fromip_ptr_dnsname = DNSName(ipv4_fromip_ptr)
ipv4_fromip_ptr_dn = DN(('idnsname', ipv4_fromip_ptr), revzone_dn)
ipv6_fromip = u'withipv6addr'
ipv6_fromip_ipv6 = u'2001:db8:1::9'
ipv6_fromip_aaaarec = ipv6_fromip_ipv6
ipv6_fromip_dnsname = DNSName(ipv6_fromip)
ipv6_fromip_dn = DN(('idnsname', ipv6_fromip), dnszone_dn)
ipv6_fromip_host_fqdn = u'%s.%s' % (ipv6_fromip, dnszone)
ipv6_fromip_host_dn = DN(('fqdn',ipv6_fromip_host_fqdn),('cn','computers'),('cn','accounts'),
api.env.basedn)
ipv6_fromip_ptr = u'9.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0'
ipv6_fromip_ptrrec = ipv6_fromip_host_fqdn + '.'
ipv6_fromip_ptr_dnsname = DNSName(ipv6_fromip_ptr)
ipv6_fromip_ptr_dn = DN(('idnsname', ipv6_fromip_ptr), revipv6zone_dn)
sshpubkey = u'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDGAX3xAeLeaJggwTqMjxNwa6XHBUAikXPGMzEpVrlLDCZtv00djsFTBi38PkgxBJVkgRWMrcBsr/35lq7P6w8KGIwA8GI48Z0qBS2NBMJ2u9WQ2hjLN6GdMlo77O0uJY3251p12pCVIS/bHRSq8kHO2No8g7KA9fGGcagPfQH+ee3t7HUkpbQkFTmbPPN++r3V8oVUk5LxbryB3UIIVzNmcSIn3JrXynlvui4MixvrtX6zx+O/bBo68o8/eZD26QrahVbA09fivrn/4h3TM019Eu/c2jOdckfU3cHUV/3Tno5d6JicibyaoDDK7S/yjdn5jhaz8MSEayQvFkZkiF0L public key test'
sshpubkeyfp = u'13:67:6B:BF:4E:A2:05:8E:AE:25:8B:A1:31:DE:6F:1B public key test (ssh-rsa)'
@ -684,7 +761,7 @@ class test_host(Declarative):
desc='Try to add host not in DNS %r without force' % fqdn2,
command=('host_add', [fqdn2], {}),
expected=errors.DNSNotARecordError(
reason=u'Host does not have corresponding DNS A record'),
reason=u'Host does not have corresponding DNS A/AAAA record'),
),
@ -958,3 +1035,374 @@ class test_host_false_pwd_change(XMLRPC_test):
# verify that it's gone
with assert_raises(errors.NotFound):
api.Command['host_show'](self.fqdn1)
class test_host_dns(Declarative):
cleanup_commands = [
('host_del', [ipv6only_host_fqdn], {}),
('host_del', [ipv4only_host_fqdn], {}),
('host_del', [ipv46both_host_fqdn], {}),
('host_del', [ipv4_fromip_host_fqdn], {}),
('host_del', [ipv6_fromip_host_fqdn], {}),
('dnszone_del', [dnszone], {}),
('dnszone_del', [revzone], {}),
('dnszone_del', [revipv6zone], {}),
]
tests = [
dict(
desc='Create zone %r' % dnszone,
command=(
'dnszone_add', [dnszone], {
'idnssoamname': dnszone_ns,
'idnssoarname': dnszone_rname,
'ip_address' : dnszone_ip,
}
),
expected={
'value': dnszone_dnsname,
'summary': None,
'result': {
'dn': dnszone_dn,
'idnsname': [dnszone_dnsname],
'idnszoneactive': [u'TRUE'],
'idnssoamname': [dnszone_ns_dnsname],
'nsrecord': [dnszone_ns],
'idnssoarname': [dnszone_rname_dnsname],
'idnssoaserial': [fuzzy_digits],
'idnssoarefresh': [fuzzy_digits],
'idnssoaretry': [fuzzy_digits],
'idnssoaexpire': [fuzzy_digits],
'idnssoaminimum': [fuzzy_digits],
'idnsallowdynupdate': [u'FALSE'],
'idnsupdatepolicy': [u'grant %(realm)s krb5-self * A; '
u'grant %(realm)s krb5-self * AAAA; '
u'grant %(realm)s krb5-self * SSHFP;'
% dict(realm=api.env.realm)],
'idnsallowtransfer': [u'none;'],
'idnsallowquery': [u'any;'],
'objectclass': objectclasses.dnszone,
},
},
),
dict(
desc='Create reverse zone %r' % revzone,
command=(
'dnszone_add', [revzone], {
'idnssoamname': dnszone_ns,
'idnssoarname': dnszone_rname,
}
),
expected={
'value': revzone_dnsname,
'summary': None,
'result': {
'dn': revzone_dn,
'idnsname': [revzone_dnsname],
'idnszoneactive': [u'TRUE'],
'idnssoamname': [dnszone_ns_dnsname],
'nsrecord': [dnszone_ns],
'idnssoarname': [dnszone_rname_dnsname],
'idnssoaserial': [fuzzy_digits],
'idnssoarefresh': [fuzzy_digits],
'idnssoaretry': [fuzzy_digits],
'idnssoaexpire': [fuzzy_digits],
'idnssoaminimum': [fuzzy_digits],
'idnsallowdynupdate': [u'FALSE'],
'idnsupdatepolicy': [u'grant %(realm)s krb5-subdomain %(zone)s PTR;'
% dict(realm=api.env.realm, zone=revzone)],
'idnsallowtransfer': [u'none;'],
'idnsallowquery': [u'any;'],
'objectclass': objectclasses.dnszone,
},
},
),
dict(
desc='Create reverse zone %r' % revipv6zone,
command=(
'dnszone_add', [revipv6zone], {
'idnssoamname': dnszone_ns,
'idnssoarname': dnszone_rname,
}
),
expected={
'value': revipv6zone_dnsname,
'summary': None,
'result': {
'dn': revipv6zone_dn,
'idnsname': [revipv6zone_dnsname],
'idnszoneactive': [u'TRUE'],
'idnssoamname': [dnszone_ns_dnsname],
'nsrecord': [dnszone_ns],
'idnssoarname': [dnszone_rname_dnsname],
'idnssoaserial': [fuzzy_digits],
'idnssoarefresh': [fuzzy_digits],
'idnssoaretry': [fuzzy_digits],
'idnssoaexpire': [fuzzy_digits],
'idnssoaminimum': [fuzzy_digits],
'idnsallowdynupdate': [u'FALSE'],
'idnsupdatepolicy': [u'grant %(realm)s krb5-subdomain %(zone)s PTR;'
% dict(realm=api.env.realm, zone=revipv6zone)],
'idnsallowtransfer': [u'none;'],
'idnsallowquery': [u'any;'],
'objectclass': objectclasses.dnszone,
},
},
),
dict(
desc='Add A record to %r in zone %r' % (ipv6only, dnszone),
command=('dnsrecord_add', [dnszone, ipv6only], {'arecord': arec}),
expected={
'value': ipv6only_dnsname,
'summary': None,
'result': {
'dn': ipv6only_dn,
'idnsname': [ipv6only_dnsname],
'arecord': [arec],
'objectclass': objectclasses.dnsrecord,
},
},
),
dict(
desc='Add A record to %r in zone %r' % (ipv4only, dnszone),
command=('dnsrecord_add', [dnszone, ipv4only], {'aaaarecord': aaaarec}),
expected={
'value': ipv4only_dnsname,
'summary': None,
'result': {
'dn': ipv4only_dn,
'idnsname': [ipv4only_dnsname],
'aaaarecord': [aaaarec],
'objectclass': objectclasses.dnsrecord,
},
},
),
dict(
desc='Add A record to %r in zone %r' % (ipv46both, dnszone),
command=('dnsrecord_add', [dnszone, ipv46both], {'arecord': arec2,
'aaaarecord': aaaarec}
),
expected={
'value': ipv46both_dnsname,
'summary': None,
'result': {
'dn': ipv46both_dn,
'idnsname': [ipv46both_dnsname],
'arecord': [arec2],
'aaaarecord': [aaaarec],
'objectclass': objectclasses.dnsrecord,
},
},
),
dict(
desc='Create %r (AAAA record exists)' % ipv6only_host_fqdn,
command=('host_add', [ipv6only_host_fqdn],
dict(
description=u'Test host 5',
l=u'Undisclosed location 5',
),
),
expected=dict(
value=ipv6only_host_fqdn,
summary=u'Added host "%s"' % ipv6only_host_fqdn,
result=dict(
dn=ipv6only_host_dn,
fqdn=[ipv6only_host_fqdn],
description=[u'Test host 5'],
l=[u'Undisclosed location 5'],
krbprincipalname=[u'host/%s@%s' % (ipv6only_host_fqdn, api.env.realm)],
objectclass=objectclasses.host,
ipauniqueid=[fuzzy_uuid],
managedby_host=[ipv6only_host_fqdn],
has_keytab=False,
has_password=False,
),
),
),
dict(
desc='Create %r (A record exists)' % ipv4only_host_fqdn,
command=('host_add', [ipv4only_host_fqdn],
dict(
description=u'Test host 6',
l=u'Undisclosed location 6',
),
),
expected=dict(
value=ipv4only_host_fqdn,
summary=u'Added host "%s"' % ipv4only_host_fqdn,
result=dict(
dn=ipv4only_host_dn,
fqdn=[ipv4only_host_fqdn],
description=[u'Test host 6'],
l=[u'Undisclosed location 6'],
krbprincipalname=[u'host/%s@%s' % (ipv4only_host_fqdn, api.env.realm)],
objectclass=objectclasses.host,
ipauniqueid=[fuzzy_uuid],
managedby_host=[ipv4only_host_fqdn],
has_keytab=False,
has_password=False,
),
),
),
dict(
desc='Create %r (A and AAAA records exist)' % ipv46both_host_fqdn,
command=('host_add', [ipv46both_host_fqdn],
dict(
description=u'Test host 7',
l=u'Undisclosed location 7',
),
),
expected=dict(
value=ipv46both_host_fqdn,
summary=u'Added host "%s"' % ipv46both_host_fqdn,
result=dict(
dn=ipv46both_host_dn,
fqdn=[ipv46both_host_fqdn],
description=[u'Test host 7'],
l=[u'Undisclosed location 7'],
krbprincipalname=[u'host/%s@%s' % (ipv46both_host_fqdn, api.env.realm)],
objectclass=objectclasses.host,
ipauniqueid=[fuzzy_uuid],
managedby_host=[ipv46both_host_fqdn],
has_keytab=False,
has_password=False,
),
),
),
dict(
desc='Create %r with --from-ip option' % ipv4_fromip_host_fqdn,
command=('host_add', [ipv4_fromip_host_fqdn],
dict(
description=u'Test host 8',
l=u'Undisclosed location 8',
ip_address=ipv4_fromip_ip,
),
),
expected=dict(
value=ipv4_fromip_host_fqdn,
summary=u'Added host "%s"' % ipv4_fromip_host_fqdn,
result=dict(
dn=ipv4_fromip_host_dn,
fqdn=[ipv4_fromip_host_fqdn],
description=[u'Test host 8'],
l=[u'Undisclosed location 8'],
krbprincipalname=[u'host/%s@%s' % (ipv4_fromip_host_fqdn, api.env.realm)],
objectclass=objectclasses.host,
ipauniqueid=[fuzzy_uuid],
managedby_host=[ipv4_fromip_host_fqdn],
has_keytab=False,
has_password=False,
),
),
),
dict(
desc='Check if A record was created for host %r' % ipv4_fromip_host_fqdn,
command=('dnsrecord_show', [dnszone, ipv4_fromip], {}
),
expected=dict(
value=ipv4_fromip_dnsname,
summary=None,
result=dict(
dn=ipv4_fromip_dn,
idnsname=[ipv4_fromip_dnsname],
arecord=[ipv4_fromip_arec],
),
),
),
dict(
desc='Check if PTR record was created for host %r' % ipv4_fromip_host_fqdn,
command=('dnsrecord_show', [revzone, ipv4_fromip_ptr], {}
),
expected=dict(
value=ipv4_fromip_ptr_dnsname,
summary=None,
result=dict(
dn=ipv4_fromip_ptr_dn,
idnsname=[ipv4_fromip_ptr_dnsname],
ptrrecord=[ipv4_fromip_ptrrec],
),
),
),
dict(
desc='Create %r with --from-ip option (IPv6)' % ipv6_fromip_host_fqdn,
command=('host_add', [ipv6_fromip_host_fqdn],
dict(
description=u'Test host 9',
l=u'Undisclosed location 9',
ip_address=ipv6_fromip_ipv6,
),
),
expected=dict(
value=ipv6_fromip_host_fqdn,
summary=u'Added host "%s"' % ipv6_fromip_host_fqdn,
result=dict(
dn=ipv6_fromip_host_dn,
fqdn=[ipv6_fromip_host_fqdn],
description=[u'Test host 9'],
l=[u'Undisclosed location 9'],
krbprincipalname=[u'host/%s@%s' % (ipv6_fromip_host_fqdn, api.env.realm)],
objectclass=objectclasses.host,
ipauniqueid=[fuzzy_uuid],
managedby_host=[ipv6_fromip_host_fqdn],
has_keytab=False,
has_password=False,
),
),
),
dict(
desc='Check if AAAA record was created for host %r' % ipv6_fromip_host_fqdn,
command=('dnsrecord_show', [dnszone, ipv6_fromip], {}
),
expected=dict(
value=ipv6_fromip_dnsname,
summary=None,
result=dict(
dn=ipv6_fromip_dn,
idnsname=[ipv6_fromip_dnsname],
aaaarecord=[ipv6_fromip_aaaarec],
),
),
),
dict(
desc='Check if PTR record was created for host %r' % ipv6_fromip_host_fqdn,
command=('dnsrecord_show', [revipv6zone, ipv6_fromip_ptr], {}
),
expected=dict(
value=ipv6_fromip_ptr_dnsname,
summary=None,
result=dict(
dn=ipv6_fromip_ptr_dn,
idnsname=[ipv6_fromip_ptr_dnsname],
ptrrecord=[ipv6_fromip_ptrrec],
),
),
),
]

View File

@ -2851,9 +2851,9 @@ class test_permission_legacy(Declarative):
command=('permission_find', [],
{'ipapermlocation': api.env.basedn}),
expected=dict(
count=15,
count=16,
truncated=False,
summary=u'15 permissions matched',
summary=u'16 permissions matched',
result=lambda s: True,
),
),

View File

@ -71,7 +71,17 @@ class test_realmdomains(Declarative):
u'(version 3.0;acl '
u'"permission:System: Read Realm Domains";'
u'allow (compare,read,search) '
u'userdn = "ldap:///all";)'
u'userdn = "ldap:///all";)',
u'(targetattr = "associateddomain")'
u'(targetfilter = "(objectclass=domainrelatedobject)")'
u'(version 3.0;acl '
u'"permission:System: Modify Realm Domains";'
u'allow (write) groupdn = "ldap:///%s";)' %
DN('cn=System: Modify Realm Domains',
api.env.container_permission,
api.env.basedn),
],
),
),