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";) 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 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";) 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 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";) 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 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";) 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 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";) 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 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";) 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 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";) 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 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";) 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 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";) 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 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 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";) 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 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 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";) 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 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";) 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 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";) 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 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";) 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 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";) 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 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";) 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 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";) 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 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";) 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 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";) 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: 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('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: 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: 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: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
option: Str('setattr*', cli_name='setattr', 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('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: 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: 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: 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('pkey_only?', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui') option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')

View File

@ -20,7 +20,7 @@
######################################################## ########################################################
IPA_VERSION_MAJOR=4 IPA_VERSION_MAJOR=4
IPA_VERSION_MINOR=0 IPA_VERSION_MINOR=0
IPA_VERSION_RELEASE=0 IPA_VERSION_RELEASE=2
######################################################## ########################################################
# For 'pre' releases the version will be # # 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; break;
case RANGE_CHECK_DIFFERENT_TYPE_IN_DOMAIN: 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."; errmsg = "New ID range has invalid type. All ranges in the same domain must be of the same type.";
break;
default: default:
errmsg = "New range overlaps with existing one."; errmsg = "New range overlaps with existing one.";
break; break;

View File

@ -66,6 +66,7 @@ struct otptoken {
enum otptoken_type type; enum otptoken_type type;
union { union {
struct { struct {
uint64_t watermark;
unsigned int step; unsigned int step;
int offset; int offset;
} totp; } totp;
@ -123,69 +124,21 @@ static const struct berval *entry_attr_get_berval(const Slapi_Entry* e,
return slapi_value_get_berval(v); return slapi_value_get_berval(v);
} }
static bool validate(struct otptoken *token, time_t now, ssize_t step, static bool writeattr(const struct otptoken *token, const char *attr,
uint32_t first, const uint32_t *second) int value)
{
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)
{ {
Slapi_Value *svals[] = { NULL, NULL }; Slapi_Value *svals[] = { NULL, NULL };
Slapi_PBlock *pb = NULL; Slapi_PBlock *pb = NULL;
Slapi_Mods *mods = NULL; Slapi_Mods *mods = NULL;
bool success = false; bool success = false;
const char *attr;
int value;
int ret; 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. */ /* Create the value. */
svals[0] = slapi_value_new(); svals[0] = slapi_value_new();
if (slapi_value_set_int(svals[0], value) != 0) if (slapi_value_set_int(svals[0], value) != 0) {
goto error; slapi_value_free(&svals[0]);
return false;
}
/* Create the mods. */ /* Create the mods. */
mods = slapi_mods_new(); mods = slapi_mods_new();
@ -203,26 +156,91 @@ static bool writeback(struct otptoken *token, ssize_t step, bool sync)
if (ret != LDAP_SUCCESS) if (ret != LDAP_SUCCESS)
goto error; 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; success = true;
error: error:
slapi_pblock_destroy(pb); slapi_pblock_destroy(pb);
slapi_mods_free(&mods); slapi_mods_free(&mods);
return success; 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) static void otptoken_free(struct otptoken *token)
{ {
if (token == NULL) if (token == NULL)
@ -303,6 +321,9 @@ static struct otptoken *otptoken_new(Slapi_ComponentId *id, Slapi_Entry *entry)
/* Get offset. */ /* Get offset. */
token->totp.offset = slapi_entry_attr_get_int(entry, T("clockOffset")); 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. */ /* Get step. */
token->totp.step = slapi_entry_attr_get_uint(entry, T("timeStep")); token->totp.step = slapi_entry_attr_get_uint(entry, T("timeStep"));
if (token->totp.step == 0) 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++) { for (int i = 0; i <= steps; i++) {
/* Validate the positive step. */ /* Validate the positive step. */
if (validate(token, now, i, code, NULL)) if (validate(token, now, i, code, NULL))
return writeback(token, i + 1, false); return true;
/* Counter-based tokens must NEVER validate old steps! */
if (i == 0 || token->type == OTPTOKEN_HOTP)
continue;
/* Validate the negative step. */ /* Validate the negative step. */
if (validate(token, now, 0 - i, code, NULL)) if (validate(token, now, 0 - i, code, NULL))
return writeback(token, 0 - i + 1, false); return true;
} }
return false; 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++) { for (int j = 0; tokens[j] != NULL; j++) {
/* Validate the positive step. */ /* Validate the positive step. */
if (validate(tokens[j], now, i, first_code, &second_code)) if (validate(tokens[j], now, i, first_code, &second_code))
return writeback(tokens[j], i + 2, true); return true;
/* Counter-based tokens must NEVER validate old steps! */
if (i == 0 || tokens[j]->type == OTPTOKEN_HOTP)
continue;
/* Validate the negative step. */ /* Validate the negative step. */
if (validate(tokens[j], now, 0 - i, first_code, &second_code)) 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: check
BuildRequires: libsss_idmap-devel BuildRequires: libsss_idmap-devel
BuildRequires: libsss_nss_idmap-devel BuildRequires: libsss_nss_idmap-devel
BuildRequires: java-1.7.0-openjdk BuildRequires: java-headless
BuildRequires: rhino BuildRequires: rhino
BuildRequires: libverto-devel BuildRequires: libverto-devel
BuildRequires: systemd BuildRequires: systemd
BuildRequires: libunistring-devel BuildRequires: libunistring-devel
BuildRequires: python-lesscpy BuildRequires: python-lesscpy
BuildRequires: python-yubico BuildRequires: python-yubico
BuildRequires: python-backports-ssl_match_hostname
%description %description
IPA is an integrated solution to provide centrally managed Identity (machine, 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}-python = %{version}-%{release}
Requires: %{name}-client = %{version}-%{release} Requires: %{name}-client = %{version}-%{release}
Requires: %{name}-admintools = %{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: openldap-clients > 2.4.35-4
Requires: nss >= 3.14.3-12.0 Requires: nss >= 3.14.3-12.0
Requires: nss-tools >= 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: krb5-pkinit-openssl
Requires: cyrus-sasl-gssapi%{?_isa} Requires: cyrus-sasl-gssapi%{?_isa}
Requires: ntp Requires: ntp
@ -122,8 +123,8 @@ Requires: python-dns
Requires: zip Requires: zip
Requires: policycoreutils >= %{POLICYCOREUTILSVER} Requires: policycoreutils >= %{POLICYCOREUTILSVER}
Requires: tar Requires: tar
Requires(pre): certmonger >= 0.65 Requires(pre): certmonger >= 0.75.13
Requires(pre): 389-ds-base >= 1.3.2.19 Requires(pre): 389-ds-base >= 1.3.2.20
Requires: fontawesome-fonts Requires: fontawesome-fonts
Requires: open-sans-fonts Requires: open-sans-fonts
@ -203,6 +204,7 @@ Requires: libsss_autofs
Requires: autofs Requires: autofs
Requires: libnfsidmap Requires: libnfsidmap
Requires: nfs-utils Requires: nfs-utils
Requires: python-backports-ssl_match_hostname
Requires(post): policycoreutils Requires(post): policycoreutils
Obsoletes: ipa-client >= 1.0 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.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.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.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.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.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.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') 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 import krbV
from ipapython.ipa_log_manager import * 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 import api, errors, util
from ipalib.constants import CACERT from ipalib.constants import CACERT
from ipapython import ipautil, ipaldap, version, dogtag 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 # dict of command name and tuples of min/max num of args needed
commands = { commands = {
"list":(0, 1, "[master fqdn]", ""), "list": (0, 1, "[master fqdn]", ""),
"connect":(1, 2, "<master fqdn> [other master fqdn]", "connect": (1, 2, "<master fqdn> [other master fqdn]",
"must provide the name of the servers to connect"), "must provide the name of the servers to connect"),
"disconnect":(1, 2, "<master fqdn> [other master fqdn]", "disconnect": (1, 2, "<master fqdn> [other master fqdn]",
"must provide the name of the server to disconnect"), "must provide the name of the server to disconnect"),
"del":(1, 1, "<master fqdn>", "del": (1, 1, "<master fqdn>",
"must provide hostname of master to delete"), "must provide hostname of master to delete"),
"re-initialize":(0, 0, "", ""), "re-initialize": (0, 0, "", ""),
"force-sync":(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: except Exception, e:
sys.exit("There were issues removing a connection: %s" % 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: try:
if bindinstance.dns_container_exists(options.host, api.env.basedn, if bindinstance.dns_container_exists(options.host, api.env.basedn,
dm_password=options.dirman_passwd): dm_password=options.dirman_passwd):
@ -369,6 +376,21 @@ def force_sync(realm, thishost, fromhost, dirman_passwd):
except Exception, e: except Exception, e:
sys.exit(str(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(): def main():
options, args = parse_options() options, args = parse_options()
@ -433,6 +455,11 @@ def main():
replica1 = host replica1 = host
replica2 = args[1] replica2 = args[1]
del_link(realm, replica1, replica2, dirman_passwd, options.force) 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: try:
main() main()

View File

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

View File

@ -28,7 +28,7 @@ import socket
from ipapython import ipautil from ipapython import ipautil
from ipaserver.install import replication, dsinstance, installutils 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 ipaserver.plugins import ldap2
from ipapython import version, ipaldap from ipapython import version, ipaldap
from ipalib import api, errors, util 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 # Check that we are not leaving the installation without CA and/or DNS
this_services = [] this_services = []
other_services = [] other_services = []
ca_hostname = None
for master_cn in [m.single_value['cn'] for m in masters]: 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)) 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 this_services = services_cns
else: else:
other_services.append(services_cns) 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]): 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." 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." print "Deleting this server will leave your installation without a DNS."
if not options.force and not ipautil.user_input("Continue to delete?", False): if not options.force and not ipautil.user_input("Continue to delete?", False):
sys.exit("Deletion aborted") 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: else:
print "Skipping calculation to determine if one or more masters would be orphaned." 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.ipautil import *
from ipapython import ipautil from ipapython import ipautil
from ipapython import dogtag from ipapython import dogtag
from ipalib import api, errors, util from ipalib import api, errors, util, x509
from ipapython.config import IPAOptionParser from ipapython.config import IPAOptionParser
from ipalib.x509 import load_certificate_from_file, load_certificate_chain_from_file from ipalib.x509 import load_certificate_from_file, load_certificate_chain_from_file
from ipalib.util import validate_domain_name from ipalib.util import validate_domain_name
@ -179,11 +179,11 @@ def parse_options():
cert_group = OptionGroup(parser, "certificate system options") cert_group = OptionGroup(parser, "certificate system options")
cert_group.add_option("", "--external-ca", dest="external_ca", action="store_true", 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", 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", 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", cert_group.add_option("--no-pkinit", dest="setup_pkinit", action="store_false",
default=True, help="disables pkinit setup steps") default=True, help="disables pkinit setup steps")
cert_group.add_option("--dirsrv_pkcs12", dest="dirsrv_pkcs12", 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", cert_group.add_option("--pkinit_pin", dest="pkinit_pin",
help="The password of the Kerberos KDC PKCS#12 file") help="The password of the Kerberos KDC PKCS#12 file")
cert_group.add_option("--root-ca-file", dest="root_ca_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, cert_group.add_option("--subject", action="callback", callback=subject_callback,
type="string", type="string",
help="The certificate subject base (default O=<realm-name>)") 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: if options.pkinit_pkcs12 and options.pkinit_pin is None:
parser.error("You must specify --pkinit_pin with --pkinit_pkcs12") 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: if (options.external_cert_file or options.external_ca_file) and options.dirsrv_pkcs12:
parser.error( parser.error(
"PKCS#12 options cannot be used with the external CA options.") "PKCS#12 options cannot be used with the external CA options.")
@ -513,6 +509,10 @@ def uninstall():
os.remove(ANSWER_CACHE) os.remove(ANSWER_CACHE)
except Exception: except Exception:
pass pass
try:
os.remove(paths.ROOT_IPA_CSR)
except Exception:
pass
# ipa-client-install removes /etc/ipa/default.conf # ipa-client-install removes /etc/ipa/default.conf
@ -686,13 +686,21 @@ def main():
if options.external_ca: if options.external_ca:
if cainstance.is_step_one_done(): 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) sys.exit(1)
elif options.external_cert_file: elif options.external_cert_file:
if not cainstance.is_step_one_done(): if not cainstance.is_step_one_done():
# This can happen if someone passes external_ca_file without # This can happen if someone passes external_ca_file without
# already having done the first stage of the CA install. # 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) sys.exit(1)
# This will override any settings passed in on the cmdline # This will override any settings passed in on the cmdline
@ -901,7 +909,7 @@ def main():
if options.http_pin is None: if options.http_pin is None:
sys.exit("%s unlock password required" % options.http_pkcs12) sys.exit("%s unlock password required" % options.http_pkcs12)
http_pkcs12_info = (options.http_pkcs12, options.http_pin) 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) http_pkcs12_info, ca_file, host_name)
if options.dirsrv_pkcs12: if options.dirsrv_pkcs12:
@ -912,7 +920,7 @@ def main():
if options.dirsrv_pin is None: if options.dirsrv_pin is None:
sys.exit("%s unlock password required" % options.dirsrv_pkcs12) sys.exit("%s unlock password required" % options.dirsrv_pkcs12)
dirsrv_pkcs12_info = (options.dirsrv_pkcs12, options.dirsrv_pin) 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) dirsrv_pkcs12_info, ca_file, host_name)
if options.pkinit_pkcs12: if options.pkinit_pkcs12:
@ -924,6 +932,11 @@ def main():
sys.exit("%s unlock password required" % options.pkinit_pkcs12) sys.exit("%s unlock password required" % options.pkinit_pkcs12)
pkinit_pkcs12_info = (options.pkinit_pkcs12, options.pkinit_pin) 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: if not options.dm_password:
dm_password = read_dm_password() dm_password = read_dm_password()
@ -1053,8 +1066,7 @@ def main():
ntp.create_instance() ntp.create_instance()
if options.dirsrv_pkcs12: if options.dirsrv_pkcs12:
ds = dsinstance.DsInstance(fstore=fstore, ds = dsinstance.DsInstance(fstore=fstore)
cert_nickname=dirsrv_cert_name)
ds.create_instance(realm_name, host_name, domain_name, ds.create_instance(realm_name, host_name, domain_name,
dm_password, dirsrv_pkcs12_info, dm_password, dirsrv_pkcs12_info,
idstart=options.idstart, idmax=options.idmax, idstart=options.idstart, idmax=options.idmax,
@ -1108,7 +1120,7 @@ def main():
ca.publish_ca_cert(CACERT) ca.publish_ca_cert(CACERT)
else: else:
# Put the CA cert where other instances expect it # 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) os.chmod(CACERT, 0444)
# we now need to enable ssl on the ds # we now need to enable ssl on the ds

View File

@ -598,6 +598,11 @@ def named_enable_dnssec():
""" """
Enable dnssec in named.conf 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'): if not sysupgrade.get_upgrade_state('named.conf', 'dnssec_enabled'):
root_logger.info('[Enabling "dnssec-enable" configuration in DNS]') root_logger.info('[Enabling "dnssec-enable" configuration in DNS]')
try: try:
@ -672,24 +677,25 @@ def certificate_renewal_update(ca):
return False return False
# State not set, lets see if we are already configured # State not set, lets see if we are already configured
for nss_dir, nickname, ca_name, pre_command, post_command in requests: for request in requests:
criteria = ( nss_dir, nickname, ca_name, pre_command, post_command = request
('cert_storage_location', nss_dir, certmonger.NPATH), criteria = {
('cert_nickname', nickname, None), 'cert-database': nss_dir,
('ca_name', ca_name, None), 'cert-nickname': nickname,
) 'ca-name': ca_name,
}
request_id = certmonger.get_request_id(criteria) request_id = certmonger.get_request_id(criteria)
if request_id is None: if request_id is None:
break 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: if val is not None:
val = val.split(' ', 1)[0] val = val.split(' ', 1)[0]
val = os.path.basename(val) val = os.path.basename(val)
if pre_command != val: if pre_command != val:
break 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: if val is not None:
val = val.split(' ', 1)[0] val = val.split(' ', 1)[0]
val = os.path.basename(val) val = os.path.basename(val)
@ -1074,6 +1080,7 @@ def main():
sub_dict['SUBJECT_BASE'] = subject_base sub_dict['SUBJECT_BASE'] = subject_base
ca = cainstance.CAInstance(api.env.realm, certs.NSS_DIR) ca = cainstance.CAInstance(api.env.realm, certs.NSS_DIR)
ca.backup_config()
# migrate CRL publish dir before the location in ipa.conf is updated # migrate CRL publish dir before the location in ipa.conf is updated
ca_restart = migrate_crl_publish_dir(ca) 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 \fBforce\-sync\fR
\- Immediately flush any data to be replicated from a server specified with the \-\-from option \- Immediately flush any data to be replicated from a server specified with the \-\-from option
.TP .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. 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 .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. 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" .SS "CERTIFICATE SYSTEM OPTIONS"
.TP .TP
\fB\-\-external\-ca\fR \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 .TP
\fB\-\-external_cert_file\fR=\fIFILE\fR \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 .TP
\fB\-\-external_ca_file\fR=\fIFILE\fR \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 .TP
\fB\-\-no\-pkinit\fR \fB\-\-no\-pkinit\fR
Disables pkinit setup steps Disables pkinit setup steps
@ -114,6 +118,9 @@ The password of the Apache Server PKCS#12 file
\fB\-\-pkinit_pin\fR=\fIPKINIT_PIN\fR \fB\-\-pkinit_pin\fR=\fIPKINIT_PIN\fR
The password of the Kerberos KDC PKCS#12 file The password of the Kerberos KDC PKCS#12 file
.TP .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 \fB\-\-subject\fR=\fISUBJECT\fR
The certificate subject base (default O=REALM.NAME) 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 --- */
.facet-error { .facet-error {
padding: 2em 15em;
background-color: white; background-color: white;
} }
@ -199,7 +198,10 @@ div[name=settings].facet-group li a {
.facet-error .error-details { .facet-error .error-details {
margin-top: 2em; margin-top: 2em;
font-family: monospace; }
.facet-error .error-details code {
white-space: pre;
} }
/* ---- Search Facet ---- */ /* ---- Search Facet ---- */
@ -252,15 +254,6 @@ div[name=settings].facet-group li a {
word-wrap: break-word; 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 { .aci-attribute-table tbody {
height: 10em; height: 10em;
} }
@ -345,7 +338,7 @@ table.scrollable tbody {
.option_widget { .option_widget {
list-style-type: none; list-style-type: none;
margin: 0; margin: 0;
padding: 0; padding: 0 0 0 1px;
} }
.option_widget.nested { .option_widget.nested {
@ -365,6 +358,19 @@ table.scrollable tbody {
max-width: 150px; 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 { .combobox-widget-input {
position: relative; position: relative;
} }

View File

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

View File

@ -247,26 +247,26 @@ define([
on_phase_error: function(error) { on_phase_error: function(error) {
window.console.error(error);
error = error || {}; error = error || {};
var name = error.name || 'Runtime error'; var name = error.name || 'Runtime error';
var error_container = $('<div/>', { var error_container = $('<div/>', {
'class': 'facet-content facet-error' 'class': 'container facet-content facet-error'
}).appendTo($('.content').empty()); }).appendTo($('.app-container .content').empty());
error_container.append('<h1>'+name+'</h1>'); error_container.append('<h1>'+name+'</h1>');
var details = $('<div/>', { var details = $('<div/>', {
'class': 'error-details' 'class': 'error-details'
}).appendTo(error_container); }).appendTo(error_container);
details.append('<p> Web UI got in unrecoverable state during "'+error.phase+'" phase.</p>'); 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) { if (error.results) {
var msg = { var msg = error.results.message;
message: error.results.message, var stack = error.results.stack.toString();
stack: error.results.stack window.console.error(msg);
}; window.console.error(stack);
details.append('<strong>Technical details:</strong>'); details.append('<h3>Technical details:</h3>');
details.append('<p>'+JSON.stringify(msg)+'</p>'); 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) { aci.attributes_widget = function(spec) {
spec = spec || {}; spec = spec || {};
spec.layout = spec.layout || 'columns attribute_widget';
spec.sort = spec.sort === undefined ? true : spec.sort;
var that = IPA.checkboxes_widget(spec); 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.object_type = spec.object_type;
that.skip_unmatched = spec.skip_unmatched === undefined ? false : spec.skip_unmatched; 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.create = function(container) {
that.container = container; that.container = container;
that.widget_create(container);
var attr_container = $('<div/>', { that.controls = $('<div/>', {
'class': 'aci-attribute-table-container' 'class': 'form-inline controls'
}).appendTo(container); });
that.controls.appendTo(container);
that.$node = that.table = $('<table/>', { that.create_search_filter(that.controls);
id: id, that.create_add_control(that.controls);
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')
}));
if (that.undo) { if (that.undo) {
that.create_undo(container); that.create_undo(that.controls);
} }
if (that.object_type) { that.owb_create(container);
that.populate(that.object_type);
}
that.create_error_link(container); that.create_error_link(container);
}; };
that.create_options = function(options) { that.create_search_filter = function(container) {
var tbody = $('tbody', that.table); var filter_container = $('<div/>', {
'class': 'search-filter'
});
for (var i=0; i<options.length ; i++){ that.filter = $('<input/>', {
var option = options[i]; type: 'text',
var value = option.value.toLowerCase(); name: 'filter',
var tr = $('<tr/>').appendTo(tbody); 'class': 'form-control',
placeholder: text.get('@i18n:objects.permission.filter')
}).appendTo(filter_container);
var td = $('<td/>').appendTo(tr); that.filter.keyup(function(e) {
var name = that.get_input_name(); that.filter_options();
var id = that._option_next_id + name; });
var opt = IPA.standalone_option({
id: id, var find_button = IPA.action_button({
type: 'checkbox', name: 'find',
name: name, icon: 'fa-search',
value: value, click: function() {
'class': 'aci-attribute', that.filter_options();
change: function() { return false;
that.value_changed.notify([], that); }
that.emit('value-change', { source: that }); }).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/>',{ dialog.on_confirm = function() {
text: value, if (!dialog.validate()) return;
'for': id var attr = dialog.get_field('attr');
})); var value = attr.get_value()[0];
option.input_node = opt[0]; that.add_custom_option(value, false, true, true);
that.new_option_id(); 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) { that.update = function(values) {
@ -646,14 +668,12 @@ aci.attributes_widget = function(spec) {
that.populate(that.object_type); that.populate(that.object_type);
that.append(); that.append();
that.create_options(that.options); that.owb_create(that.container);
that.owb_update(values); that.owb_update(values);
}; };
that.populate = function(object_type) { that.populate = function(object_type) {
$('tbody tr', that.table).remove();
if (!object_type || object_type === '') return; if (!object_type || object_type === '') return;
var metadata = metadata_provider.get('@mo:'+object_type); var metadata = metadata_provider.get('@mo:'+object_type);
@ -666,21 +686,31 @@ aci.attributes_widget = function(spec) {
that.append = function() { that.append = function() {
if (!that.values) return;
var unmatched = []; var unmatched = [];
for (var i=0; i<that.values.length; i++) { function add_unmatched(source) {
if (!that.has_option(that.values[i])) { for (var i=0, l=source.length; i<l; i++) {
unmatched.push(that.values[i]); if (!that.has_option(source[i])) {
that.add_option(source[i], true /* suppress update */);
}
} }
} }
if (unmatched.length > 0 && !that.skip_unmatched) { add_unmatched(that.custom_options);
that.options.push.apply(that.options, that.prepare_options(unmatched));
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) { that.has_option = function(value) {
var o = that.options; var o = that.options;
for (var i=0, l=o.length; i<l; i++) { for (var i=0, l=o.length; i<l; i++) {
@ -689,10 +719,6 @@ aci.attributes_widget = function(spec) {
return false; return false;
}; };
that.show_undo = function() {
$(that.undo_span).css('display', 'inline-block');
};
return that; return that;
}; };
@ -873,6 +899,16 @@ aci.permission_target_policy = function (spec) {
var attribute_table = that.permission_target.widgets.get_widget('attrs'); var attribute_table = that.permission_target.widgets.get_widget('attrs');
var skip_unmatched_org = attribute_table.skip_unmatched; var skip_unmatched_org = attribute_table.skip_unmatched;
attribute_table.object_type = type; 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 // skip values which don't belong to new type. Bug #2617
attribute_table.skip_unmatched = skip_unmatched || skip_unmatched_org; attribute_table.skip_unmatched = skip_unmatched || skip_unmatched_org;
attribute_field.reset(); 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 widget = that.permission_target.widgets.get_widget(target_info.name);
var field = that.container.fields.get_field(target_info.name); var field = that.container.fields.get_field(target_info.name);
that.permission_target.set_row_visible(target_info.name, visible); that.permission_target.set_row_visible(target_info.name, visible);
var managed_f = aci.managed_fields.indexOf(target_info.name) > -1; field.set_enabled(visible);
var enabled = !(managed_f && that.managed) && visible && !that.system;
field.set_enabled(enabled);
field.set_required(visible && target_info.required); field.set_required(visible && target_info.required);
widget.set_visible(visible); widget.set_visible(visible);
}; };
@ -1007,7 +1041,9 @@ aci.permission_managed_policy = function (spec) {
var that = IPA.facet_policy(); var that = IPA.facet_policy();
that.post_load = function(data) { 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 managed = permtype && permtype.indexOf("MANAGED") > -1;
var system = permtype && permtype.indexOf("SYSTEM") > -1 && permtype.length === 1; var system = permtype && permtype.indexOf("SYSTEM") > -1 && permtype.length === 1;
var m_section = that.container.widgets.get_widget("managed"); var m_section = that.container.widgets.get_widget("managed");
@ -1018,7 +1054,14 @@ aci.permission_managed_policy = function (spec) {
var field = fields[i]; var field = fields[i];
if (field.read_only) continue; if (field.read_only) continue;
var managed_f = aci.managed_fields.indexOf(field.name) > -1; 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);
} }
}; };

View File

@ -166,12 +166,13 @@ IPA.entity_adder_dialog = function(spec) {
function show_edit_page(entity,result) { function show_edit_page(entity,result) {
var pkey_name = entity.metadata.primary_key; var pkey_name = entity.metadata.primary_key;
var pkey = result[pkey_name]; var pkey = result[pkey_name];
if (pkey instanceof Array) { if (!(pkey instanceof Array)) {
pkey = pkey[0]; pkey = [pkey];
} }
rpc.extract_objects(pkey);
var pkeys = that.pkey_prefix.slice(0); var pkeys = that.pkey_prefix.slice(0);
pkeys.push(pkey); pkeys.push(pkey[0]);
navigation.show_entity(that.entity.name, 'default', pkeys); 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.table_create(container);
that.remove_button = IPA.action_button({ that.remove_button = IPA.button_widget({
name: 'remove', name: 'remove',
label: '@i18n:buttons.remove', label: '@i18n:buttons.remove',
icon: 'fa-trash-o', icon: 'fa-trash-o',
'class': 'action-button-disabled', enabled: false,
click: function() { button_class: 'btn btn-link',
if (!that.remove_button.hasClass('action-button-disabled')) { click: that.remove_handler
that.remove_handler(); });
} that.remove_button.create(that.buttons);
return false;
}
}).appendTo(that.buttons);
that.add_button = IPA.action_button({ that.add_button = IPA.button_widget({
name: 'add', name: 'add',
label: '@i18n:buttons.add', label: '@i18n:buttons.add',
icon: 'fa-plus', icon: 'fa-plus',
click: function() { button_class: 'btn btn-link',
if (!that.add_button.hasClass('action-button-disabled')) { click: that.add_handler
that.add_handler(); });
} that.add_button.create(that.buttons);
return false;
}
}).appendTo(that.buttons);
}; };
that.add_handler = function() { that.add_handler = function() {
@ -561,14 +555,13 @@ IPA.association_table_widget = function (spec) {
that.set_enabled = function(enabled) { that.set_enabled = function(enabled) {
that.table_set_enabled(enabled); that.table_set_enabled(enabled);
if (enabled) { if (!enabled) {
if(that.add_button) {
that.add_button.removeClass('action-button-disabled');
}
} else {
$('.action-button', that.table).addClass('action-button-disabled');
that.unselect_all(); that.unselect_all();
} }
if (that.add_button) {
that.add_button.set_enabled(enabled);
that.remove_button.set_enabled(false);
}
}; };
that.select_changed = function() { that.select_changed = function() {
@ -576,11 +569,7 @@ IPA.association_table_widget = function (spec) {
var values = that.get_selected_values(); var values = that.get_selected_values();
if (that.remove_button) { if (that.remove_button) {
if (values.length === 0) { that.remove_button.set_enabled(values.length > 0);
that.remove_button.addClass('action-button-disabled');
} else {
that.remove_button.removeClass('action-button-disabled');
}
} }
}; };

View File

@ -1395,8 +1395,7 @@ IPA.confirm_dialog = function(spec) {
name: 'ok', name: 'ok',
label: that.ok_label, label: that.ok_label,
click: function() { click: function() {
that.confirmed = true; that.on_confirm();
that.close();
} }
}); });
@ -1418,6 +1417,18 @@ IPA.confirm_dialog = function(spec) {
return that; 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 * Confirm mixin
* *

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,6 +24,7 @@
define(['dojo/_base/array', define(['dojo/_base/array',
'dojo/_base/lang', 'dojo/_base/lang',
'dojo/dom-construct',
'dojo/Evented', 'dojo/Evented',
'dojo/has', 'dojo/has',
'dojo/keys', 'dojo/keys',
@ -43,7 +44,7 @@ define(['dojo/_base/array',
'./util', './util',
'exports' '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) { 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; var changed = writable !== that.writable;
that.writable = writable; that.writable = writable;
that.update_read_only();
if (changed) { if (changed) {
that.emit('writable-change', { source: that, writable: writable }); that.emit('writable-change', { source: that, writable: writable });
@ -584,12 +586,25 @@ IPA.input_widget = function(spec) {
var changed = read_only !== that.read_only; var changed = read_only !== that.read_only;
that.read_only = read_only; that.read_only = read_only;
that.update_read_only();
if (changed) { if (changed) {
that.emit('readonly-change', { source: that, read_only: read_only }); 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 * Focus input element
* @abstract * @abstract
@ -635,6 +650,8 @@ IPA.input_widget = function(spec) {
that.widget_set_valid = that.set_valid; that.widget_set_valid = that.set_valid;
that.widget_hide_undo = that.hide_undo; that.widget_hide_undo = that.hide_undo;
that.widget_show_undo = that.show_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; return that;
}; };
@ -736,6 +753,7 @@ IPA.text_widget = function(spec) {
that.create_error_link(container); that.create_error_link(container);
that.set_enabled(that.enabled); that.set_enabled(that.enabled);
that.update_read_only();
that.update_input_group_state(); that.update_input_group_state();
}; };
@ -744,18 +762,23 @@ IPA.text_widget = function(spec) {
*/ */
that.update = function(values) { that.update = function(values) {
var value = values && values.length ? values[0] : ''; 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()) { if (!that.is_writable()) {
that.display_control.text(value);
that.display_control.css('display', ''); that.display_control.css('display', '');
that.input_group.css('display', 'none'); that.input_group.css('display', 'none');
} else { } else {
that.input.val(value);
that.display_control.css('display', 'none'); that.display_control.css('display', 'none');
that.input_group.css('display', ''); that.input_group.css('display', '');
} }
that.on_value_changed();
}; };
/** /**
@ -811,7 +834,10 @@ IPA.text_widget = function(spec) {
* visible content. * visible content.
*/ */
that.update_input_group_state = function() { 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); that.input_group.toggleClass('input-group', visible);
}; };
@ -1182,7 +1208,13 @@ IPA.multivalued_widget = function(spec) {
that.on_value_changed(); that.on_value_changed();
}; };
/** @inheritDoc */
that.update_read_only = function() {
that.update_add_link_visibility();
};
that.update_add_link_visibility = function() { that.update_add_link_visibility = function() {
if (!that.add_link) return;
var visible = that.is_writable() && that.enabled; var visible = that.is_writable() && that.enabled;
if (visible) { if (visible) {
that.add_link.css('display', ''); 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.set_enabled = function(enabled) {
that.widget_set_enabled(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++) { for (var i=0,l=that.rows.length; i<l; i++) {
var row = that.rows[i]; var row = that.rows[i];
row.widget.set_enabled(enabled); row.widget.set_enabled(enabled);
that.update_row_buttons(row);
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);
}
}
} }
}; };
@ -1258,8 +1314,19 @@ IPA.option_widget_base = function(spec, that) {
that.name = spec.name; that.name = spec.name;
that.label = spec.label; that.label = spec.label;
that.tooltip = spec.tooltip; that.tooltip = spec.tooltip;
that.sort = spec.sort === undefined ? false : spec.sort;
that.value_changed = that.value_changed || IPA.observer(); 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; 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; 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) { 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_container = that.create_option_container();
var option = that.options[i]; var option = options[i];
that.create_option(option, option_container); that.create_option(option, option_container);
option_container.appendTo(container); construct.place(option_container, container);
} }
}; };
that.create_option_container = function() { that.create_option_container = function() {
return $('<li/>'); return construct.create('li');
}; };
that._create_option = function(option, container) { 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 id = that._option_next_id + input_name;
var enabled = that.enabled && option.enabled; var enabled = that.enabled && option.enabled;
var opt_cont = $('<span/>', { var opt_cont = construct.create('span', {
"class": that.intput_type + '-cnt' "class": that.intput_type + '-cnt'
}).appendTo(container); });
option.input_node = $('<input/>', { option.input_node = construct.create('input', {
id: id, id: id,
type: that.input_type, type: that.input_type,
name: input_name, name: input_name,
@ -1408,15 +1490,16 @@ IPA.option_widget_base = function(spec, that) {
value: option.value, value: option.value,
title: option.tooltip || that.tooltip, title: option.tooltip || that.tooltip,
change: that.on_input_change change: that.on_input_change
}).appendTo(opt_cont); }, opt_cont);
option.label_node = $('<label/>', { option.label_node = construct.create('label', {
html: option.label || '',
title: option.tooltip || that.tooltip, title: option.tooltip || that.tooltip,
'for': id 'for': id
}).appendTo(opt_cont); }, opt_cont);
option.label_node.textContent = option.label || '';
that.new_option_id(); that.new_option_id();
construct.place(opt_cont, container);
}; };
that.create_option = function(option, container) { that.create_option = function(option, container) {
@ -1444,7 +1527,6 @@ IPA.option_widget_base = function(spec, that) {
that.create = function(container) { that.create = function(container) {
that.destroy(); that.destroy();
that.create_options(that.$node);
var css_class = [that.css_class, 'option_widget', that.layout, var css_class = [that.css_class, 'option_widget', that.layout,
that.nested ? 'nested': ''].join(' '); that.nested ? 'nested': ''].join(' ');
@ -1553,6 +1635,8 @@ IPA.option_widget_base = function(spec, that) {
that.update = function(values) { that.update = function(values) {
var i;
var check = function(selector, uncheck) { var check = function(selector, uncheck) {
$(selector, that.$node).prop('checked', !uncheck); $(selector, that.$node).prop('checked', !uncheck);
}; };
@ -1562,49 +1646,48 @@ IPA.option_widget_base = function(spec, that) {
// uncheck all inputs // uncheck all inputs
check(that._selector, true /*uncheck*/); 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; var writable = !that.read_only && !!that.writable && that.enabled;
if (!that.nested) { if (!that.nested) {
that.update_enabled(writable); that.update_enabled(writable);
} }
if (values && values.length > 0) { // use default value if none supplied
var def_used = false;
if (that.default_on_empty && that.default_value !== null) { if (values && values.length > 0 && that.default_on_empty &&
for (var i=0; i<values.length; i++) { that.default_value !== null) {
if (values[i] === '') { for (i=0; i<values.length; i++) {
values[i] = that.default_value; 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 // check the option if it or some of its children should be checked
// checked for (i=0; i<that.options.length; i++) {
for (i=0; i<that.options.length; i++) { var option = that.options[i];
var option = that.options[i]; var opt_vals = that.get_values(option);
var opt_vals = that.get_values(option); var has_opt = array.some(values, function(val) {
var has_opt = array.some(values, function(val) { return array.indexOf(opt_vals, val) > -1;
return array.indexOf(opt_vals, val) > -1; });
});
if (has_opt) { if (has_opt) {
check(that._selector+'[value="'+ option.value +'"]'); check(that._selector+'[value="'+ option.value +'"]');
}
if (option.widget) {
option.widget.update_enabled(writable && has_opt, false);
}
} }
} else {
// select default if none specified // disable options without value
if (that.default_value !== null) { if (option.widget && !has_opt) {
check(that._selector+'[value="'+that.default_value+'"]'); option.widget.update_enabled(false);
// 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=""]');
} }
} }
// update nested
for (var j=0; j<that._child_widgets.length; j++) { for (var j=0; j<that._child_widgets.length; j++) {
var widget = that._child_widgets[j]; var widget = that._child_widgets[j];
widget.writable = that.writable; widget.writable = that.writable;
@ -1612,6 +1695,11 @@ IPA.option_widget_base = function(spec, that) {
widget.enabled = that.enabled; widget.enabled = that.enabled;
widget.update(values); 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) { 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() { that.clear = function() {
@ -1841,6 +1935,12 @@ IPA.select_widget = function(spec) {
that.widget_create(container); that.widget_create(container);
that.display_control = $('<p/>', {
name: that.name,
'class': 'form-control-static',
style: 'display: none;'
}).appendTo(container);
that.select = $('<select/>', { that.select = $('<select/>', {
name: that.name, name: that.name,
'class':'form-control', 'class':'form-control',
@ -1897,10 +1997,11 @@ IPA.select_widget = function(spec) {
that.update = function(values) { that.update = function(values) {
var old = that.save()[0]; var old = that.save()[0];
var value = values[0]; var value = values[0] || "";
var option = $('option[value="'+value+'"]', that.select); var option = $('option[value="'+value+'"]', that.select);
if (option.length) { if (option.length) {
option.prop('selected', true); option.prop('selected', true);
that.display_control.text(option.text());
} else { } else {
// default was selected instead of supplied value, hence notify // default was selected instead of supplied value, hence notify
util.emit_delayed(that,'value-change', { source: that }); util.emit_delayed(that,'value-change', { source: that });
@ -1908,6 +2009,17 @@ IPA.select_widget = function(spec) {
that.on_value_changed(); 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() { that.empty = function() {
$('option', that.select).remove(); $('option', that.select).remove();
}; };
@ -2011,14 +2123,18 @@ IPA.textarea_widget = function (spec) {
}; };
that.update = function(values) { that.update = function(values) {
var read_only = !that.is_writable();
that.input.prop('readOnly', read_only);
var value = values && values.length ? values[0] : ''; var value = values && values.length ? values[0] : '';
that.input.val(value); that.input.val(value);
that.on_value_changed(); 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.clear = function() {
that.input.val(''); that.input.val('');
}; };
@ -3036,30 +3152,24 @@ IPA.attribute_table_widget = function(spec) {
that.create_buttons = function(container) { that.create_buttons = function(container) {
that.remove_button = IPA.action_button({ that.remove_button = IPA.button_widget({
name: 'remove', name: 'remove',
label: '@i18n:buttons.remove', label: '@i18n:buttons.remove',
icon: 'fa-trash-o', icon: 'fa-trash-o',
'class': 'action-button-disabled', enabled: false,
click: function() { button_class: 'btn btn-link',
if (!that.remove_button.hasClass('action-button-disabled')) { click: that.remove_handler
that.remove_handler(); });
} that.remove_button.create(container);
return false;
}
}).appendTo(container);
that.add_button = IPA.action_button({ that.add_button = IPA.button_widget({
name: 'add', name: 'add',
label: '@i18n:buttons.add', label: '@i18n:buttons.add',
icon: 'fa-plus', icon: 'fa-plus',
click: function() { button_class: 'btn btn-link',
if (!that.add_button.hasClass('action-button-disabled')) { click: that.add_handler
that.add_handler(); });
} that.add_button.create(container);
return false;
}
}).appendTo(container);
}; };
that.create = function(container) { that.create = function(container) {
@ -3073,14 +3183,13 @@ IPA.attribute_table_widget = function(spec) {
that.set_enabled = function(enabled) { that.set_enabled = function(enabled) {
that.table_set_enabled(enabled); that.table_set_enabled(enabled);
if (enabled) { if (!enabled) {
if(that.add_button) {
that.add_button.removeClass('action-button-disabled');
}
} else {
$('.action-button', that.table).addClass('action-button-disabled');
that.unselect_all(); that.unselect_all();
} }
if (that.add_button) {
that.add_button.set_enabled(enabled);
that.remove_button.set_enabled(false);
}
}; };
that.select_changed = function() { that.select_changed = function() {
@ -3088,11 +3197,7 @@ IPA.attribute_table_widget = function(spec) {
var values = that.get_selected_values(); var values = that.get_selected_values();
if (that.remove_button) { if (that.remove_button) {
if (values.length === 0) { that.remove_button.set_enabled(values.length > 0);
that.remove_button.addClass('action-button-disabled');
} else {
that.remove_button.removeClass('action-button-disabled');
}
} }
}; };
@ -3675,16 +3780,6 @@ IPA.combobox_widget = function(spec) {
that.update = function(values) { that.update = function(values) {
that.close(); 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) { if (that.searchable) {
that.filter.empty(); that.filter.empty();
} }
@ -3711,6 +3806,19 @@ IPA.combobox_widget = function(spec) {
that.on_value_changed(); 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.set_value = function(value) {
that.text.text(value); that.text.text(value);
that.input.val(value); that.input.val(value);
@ -4113,6 +4221,12 @@ IPA.button_widget = function(spec) {
*/ */
that['class'] = spec['class']; that['class'] = spec['class'];
/**
* Override for button classes
* @property {string}
*/
that.button_class = spec.button_class;
/** /**
* Icon name * Icon name
* @property {string} * @property {string}
@ -4126,7 +4240,7 @@ IPA.button_widget = function(spec) {
*/ */
that.on_click = function() { that.on_click = function() {
if (that.click) { if (that.click && that.enabled) {
that.click(); that.click();
} }
return false; return false;
@ -4140,6 +4254,7 @@ IPA.button_widget = function(spec) {
title: that.tooltip, title: that.tooltip,
label: that.label, label: that.label,
'class': that['class'], 'class': that['class'],
button_class: that.button_class,
style: that.style, style: that.style,
icon: that.icon, icon: that.icon,
click: that.on_click click: that.on_click
@ -4147,6 +4262,7 @@ IPA.button_widget = function(spec) {
that.container = that.button; that.container = that.button;
that.set_enabled(that.enabled); that.set_enabled(that.enabled);
return that.button;
}; };
/** @inheritDoc */ /** @inheritDoc */

View File

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

View File

@ -231,7 +231,13 @@ define(['dojo/_base/declare',
refresh: function() { refresh: function() {
if (this.buttons_node) { 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') { if (this.view === 'reset') {
this.show_reset_view(); this.show_reset_view();

View File

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

View File

@ -63,3 +63,8 @@ addifnew:nsSaslMapPriority: 10
# Can be removed when https://fedorahosted.org/389/ticket/47457 is fixed # Can be removed when https://fedorahosted.org/389/ticket/47457 is fixed
dn: cn=config dn: cn=config
only:nsslapd-sasl-max-buffer-size:2097152 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 default: ipaUniqueID: autogenerate
dn: cn=adtrust agents,cn=sysaccounts,cn=etc,$SUFFIX dn: cn=adtrust agents,cn=sysaccounts,cn=etc,$SUFFIX
add: objectClass: nestedgroup
default: objectClass: GroupOfNames default: objectClass: GroupOfNames
default: objectClass: top default: objectClass: top
default: cn: adtrust agents default: cn: adtrust agents

View File

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

View File

@ -359,7 +359,7 @@ def is_ipa_client_installed(on_master=False):
return installed return installed
def configure_nsswitch_database(fstore, database, services, preserve=True, 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) Edits the specified nsswitch.conf database (e.g. passwd, group, sudoers)
to use the specified service(s). to use the specified service(s).
@ -390,28 +390,34 @@ def configure_nsswitch_database(fstore, database, services, preserve=True,
opts = conf.parse(f) opts = conf.parse(f)
raw_database_entry = conf.findOpts(opts, 'option', database)[1] raw_database_entry = conf.findOpts(opts, 'option', database)[1]
if not raw_database_entry: # Detect the list of already configured services
# If there is no database entry, database is not present in if not raw_database_entry:
# the nsswitch.conf. Set the list of services to the # If there is no database entry, database is not present in
# default list, if passed. # the nsswitch.conf. Set the list of services to the
configured_services = ' '.join(default_value or []) # default list, if passed.
else: configured_services = list(default_value)
configured_services = raw_database_entry['value'].strip()
if append:
new_services = ' ' + configured_services + ' ' + ' '.join(services)
else: 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: else:
# Preserve not set, let's rewrite existing configuration # Preserve not set, let's rewrite existing configuration
new_services = ' ' + ' '.join(services) new_value = ' ' + ' '.join(services)
# Set new services as sources for database # Set new services as sources for database
opts = [{'name': database, opts = [{'name': database,
'type':'option', 'type':'option',
'action':'set', 'action':'set',
'value': new_services 'value': new_value
}, },
{'name':'empty', {'name':'empty',
'type':'empty' 'type':'empty'
@ -491,7 +497,8 @@ def uninstall(options, env):
"Failed to remove IPA CA from /etc/pki/nssdb: %s", str(e)) "Failed to remove IPA CA from /etc/pki/nssdb: %s", str(e))
# Always start certmonger. We can't untrack something if it isn't # 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 messagebus = services.knownservices.messagebus
try: try:
messagebus.start() messagebus.start()
@ -506,7 +513,7 @@ def uninstall(options, env):
try: try:
certmonger.stop_tracking(paths.NSS_DB_DIR, nickname=client_nss_nickname) 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", root_logger.error("%s failed to stop tracking certificate: %s",
cmonger.service_name, str(e)) cmonger.service_name, str(e))
@ -1065,69 +1072,6 @@ def configure_krb5_conf(cli_realm, cli_domain, cli_server, cli_kdc, dnsok,
return 0 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): def configure_sssd_conf(fstore, cli_realm, cli_domain, cli_server, options, client_domain, client_hostname):
try: try:
sssdconfig = SSSDConfig.SSSDConfig() sssdconfig = SSSDConfig.SSSDConfig()
@ -2119,7 +2063,7 @@ def install(options, env, fstore, statestore):
# Create the discovery instance # Create the discovery instance
ds = ipadiscovery.IPADiscovery() 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: if options.server and ret != 0:
# There is no point to continue with installation as server list was # 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: if not options.on_master:
client_dns(cli_server[0], hostname, options.dns_updates) 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) 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:] domain = domain[p+1:]
return (None, None) 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. Use DNS discovery to identify valid IPA servers.
@ -218,13 +218,21 @@ class IPADiscovery(object):
#search for kerberos #search for kerberos
root_logger.debug("[Kerberos realm search]") root_logger.debug("[Kerberos realm search]")
krb_realm, kdc = self.ipadnssearchkrb(self.domain) if realm:
if not servers and not krb_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 return REALM_NOT_FOUND
self.realm = krb_realm self.kdc = self.ipadnssearchkrbkdc()
self.kdc = kdc self.kdc_source = (
self.realm_source = self.kdc_source = (
'Discovered Kerberos DNS records from %s' % self.domain) 'Discovered Kerberos DNS records from %s' % self.domain)
# We may have received multiple servers corresponding to the domain # We may have received multiple servers corresponding to the domain
@ -335,6 +343,10 @@ class IPADiscovery(object):
no_schema=True, decode_attrs=False) no_schema=True, decode_attrs=False)
try: try:
lh.do_simple_bind(DN(), '') 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: except errors.ACIError:
root_logger.debug("LDAP Error: Anonymous access not allowed") root_logger.debug("LDAP Error: Anonymous access not allowed")
return [NO_ACCESS_TO_LDAP] return [NO_ACCESS_TO_LDAP]
@ -350,10 +362,6 @@ class IPADiscovery(object):
else: else:
return [UNKNOWN_ERROR] 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: if basedn is None:
root_logger.debug("The server is not an IPA server") root_logger.debug("The server is not an IPA server")
return [NOT_IPA_SERVER] return [NOT_IPA_SERVER]
@ -452,11 +460,12 @@ class IPADiscovery(object):
return servers return servers
def ipadnssearchkrb(self, tdomain): def ipadnssearchkrbrealm(self, domain=None):
realm = None realm = None
kdc = None if not domain:
domain = self.domain
# now, check for a Kerberos realm the local host or domain is in # 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) root_logger.debug("Search DNS for TXT record of %s", qname)
@ -472,18 +481,21 @@ class IPADiscovery(object):
realm = answer.strings[0] realm = answer.strings[0]
if realm: if realm:
break break
return realm
if realm: def ipadnssearchkrbkdc(self, domain=None):
# now fetch server information for the realm kdc = None
domain = realm.lower()
kdc = self.ipadns_search_srv(domain, '_kerberos._udp', 88, if not domain:
break_on_first=False) domain = self.domain
if kdc: kdc = self.ipadns_search_srv(domain, '_kerberos._udp', 88,
kdc = ','.join(kdc) break_on_first=False)
else:
root_logger.debug("SRV record for KDC not found! Realm: %s, SRV record: %s" % (realm, qname))
kdc = None
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 errno = 1201
format= _('Principal %(principal)s cannot be authenticated: %(message)s') 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 # 2000 - 2999: Authorization errors
class AuthorizationError(PublicError): class AuthorizationError(PublicError):
@ -811,6 +817,22 @@ class DeprecationError(InvocationError):
errno = 3015 errno = 3015
format = _("Command '%(name)s' has been deprecated") 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 # 4000 - 4999: Execution errors
@ -1113,19 +1135,19 @@ class DefaultGroupError(ExecutionError):
class DNSNotARecordError(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: For example:
>>> raise DNSNotARecordError() >>> raise DNSNotARecordError()
Traceback (most recent call last): 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 errno = 4019
format = _('Host does not have corresponding DNS A record') format = _('Host does not have corresponding DNS A/AAAA record')
class ManagedGroupError(ExecutionError): class ManagedGroupError(ExecutionError):
""" """

View File

@ -210,6 +210,10 @@ def get_effective_rights(ldap, dn, attrs=None):
rights = rights[0].split(', ') rights = rights[0].split(', ')
for r in rights: for r in rights:
(k,v) = r.split(':') (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 rdict[k.strip().lower()] = v
return rdict return rdict
@ -236,9 +240,13 @@ def entry_from_entry(entry, newentry):
def entry_to_dict(entry, **options): def entry_to_dict(entry, **options):
if options.get('raw', False): if options.get('raw', False):
result = {} result = {}
for attr, value in entry.raw.iteritems(): for attr in entry.iterkeys():
if entry.conn.get_type(attr) is not str: if attr.lower() == 'attributelevelrights':
value = list(value) 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): for (i, v) in enumerate(value):
try: try:
value[i] = v.decode('utf-8') 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. authoritative (e.g. sub.example.com) will be routed to the global forwarder.
Global forwarding configuration can be overridden per-zone. Global forwarding configuration can be overridden per-zone.
""") + _(""" """) + _("""
Semantics of forwarding in IPA matches BIND sematics and depends on type Semantics of forwarding in IPA matches BIND semantics and depends on the type
of the zone: of zone:
* Master zone: local BIND replies authoritatively to queries for data in * Master zone: local BIND replies authoritatively to queries for data in
the given zone (including authoritative NXDOMAIN answers) and forwarding 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. served zones.
* Forward zone: forward zone contains no authoritative data. BIND forwards * Forward zone: forward zone contains no authoritative data. BIND forwards
@ -489,6 +489,14 @@ def _hostname_validator(ugettext, value):
return None 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): def is_forward_record(zone, str_address):
addr = netaddr.IPAddress(str_address) addr = netaddr.IPAddress(str_address)
if addr.version == 4: if addr.version == 4:
@ -1731,6 +1739,7 @@ class DNSZoneBase(LDAPObject):
takes_params = ( takes_params = (
DNSNameParam('idnsname', DNSNameParam('idnsname',
_no_wildcard_validator, # RFC 4592 section 4
only_absolute=True, only_absolute=True,
cli_name='name', cli_name='name',
label=_('Zone name'), label=_('Zone name'),
@ -2619,6 +2628,19 @@ class dnsrecord(LDAPObject):
error=unicode(_('out-of-zone data: record name must ' error=unicode(_('out-of-zone data: record name must '
'be a subdomain of the zone or a ' 'be a subdomain of the zone or a '
'relative name'))) '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): def _ptrrecord_pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
assert isinstance(dn, DN) assert isinstance(dn, DN)
@ -3374,6 +3396,13 @@ class dnsrecord_mod(LDAPUpdate):
if del_all: if del_all:
result = self.obj.methods.delentry(*keys, result = self.obj.methods.delentry(*keys,
version=options['version']) 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 # indicate that entry was deleted
context.dnsrecord_entry_mods[(keys[0], keys[1])] = None context.dnsrecord_entry_mods[(keys[0], keys[1])] = None

View File

@ -202,6 +202,16 @@ class group(LDAPObject):
], ],
'default_privileges': {'Group Administrators'}, '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') label = _('User Groups')
@ -522,7 +532,7 @@ class group_remove_member(LDAPRemoveMember):
def pre_callback(self, ldap, dn, found, not_found, *keys, **options): def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
assert isinstance(dn, DN) 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] protected_group_name = keys[0]
result = api.Command.group_show(protected_group_name) result = api.Command.group_show(protected_group_name)
users_left = set(result['result'].get('member_user', [])) users_left = set(result['result'].get('member_user', []))

View File

@ -368,6 +368,16 @@ class host(LDAPObject):
'ipapermdefaultattr': {'userpassword'}, 'ipapermdefaultattr': {'userpassword'},
'default_privileges': {'Host Administrators', 'Host Enrollment'}, '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') label = _('Hosts')

View File

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

View File

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

View File

@ -160,6 +160,16 @@ class netgroup(LDAPObject):
], ],
'default_privileges': {'Netgroups Administrators'}, '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') 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.plugins.baseldap import LDAPCreate, LDAPDelete, LDAPUpdate, LDAPSearch, LDAPRetrieve
from ipalib import api, Int, Str, Bool, DateTime, Flag, Bytes, IntEnum, StrEnum, Password, _, ngettext from ipalib import api, Int, Str, Bool, DateTime, Flag, Bytes, IntEnum, StrEnum, Password, _, ngettext
from ipalib.plugable import Registry 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.request import context
from ipalib.frontend import Local from ipalib.frontend import Local
@ -103,6 +103,11 @@ def _normalize_owner(userobj, entry_attrs):
if owner is not None: if owner is not None:
entry_attrs['ipatokenowner'] = userobj.get_dn(owner) 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() @register()
class otptoken(LDAPObject): class otptoken(LDAPObject):
@ -254,6 +259,11 @@ class otptoken_add(LDAPCreate):
entry_attrs['ipatokenuniqueid'] = str(uuid.uuid4()) entry_attrs['ipatokenuniqueid'] = str(uuid.uuid4())
dn = DN("ipatokenuniqueid=%s" % entry_attrs['ipatokenuniqueid'], dn) 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 # Set the object class and defaults for specific token types
entry_attrs['objectclass'] = otptoken.object_class + ['ipatoken' + options['type']] entry_attrs['objectclass'] = otptoken.object_class + ['ipatoken' + options['type']]
for ttype, tattrs in TOKEN_TYPES.items(): for ttype, tattrs in TOKEN_TYPES.items():
@ -336,6 +346,25 @@ class otptoken_mod(LDAPUpdate):
msg_summary = _('Modified OTP token "%(value)s"') msg_summary = _('Modified OTP token "%(value)s"')
def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): 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) _normalize_owner(self.api.Object.user, entry_attrs)
return dn return dn

View File

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

View File

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

View File

@ -435,7 +435,7 @@ sides.
), ),
Password('realm_passwd?', Password('realm_passwd?',
cli_name='password', cli_name='password',
label=_("Active directory domain administrator's password"), label=_("Active Directory domain administrator's password"),
confirm=False, confirm=False,
), ),
Str('realm_server?', Str('realm_server?',
@ -511,6 +511,30 @@ sides.
return result 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): def validate_options(self, *keys, **options):
if not _bindings_installed: if not _bindings_installed:
raise errors.NotFound( raise errors.NotFound(
@ -721,11 +745,10 @@ sides.
ret['summary'] = self.msg_summary_existing % ret ret['summary'] = self.msg_summary_existing % ret
return ret return ret
# 2. We don't have access to the remote domain and trustdom password # 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 # is provided. Do the work on our side and inform what to do on remote
# side. # side.
if 'trust_secret' in options: if options.get('trust_secret'):
result = self.trustinstance.join_ad_ipa_half( result = self.trustinstance.join_ad_ipa_half(
keys[-1], keys[-1],
self.realm_server, self.realm_server,
@ -740,8 +763,11 @@ sides.
if dn: if dn:
ret['summary'] = self.msg_summary_existing % ret ret['summary'] = self.msg_summary_existing % ret
return ret return ret
raise errors.ValidationError(name=_('AD Trust setup'), else:
error=_('Not enough arguments specified to perform trust setup')) raise errors.ValidationError(
name=_('AD Trust setup'),
error=_('Not enough arguments specified to perform trust '
'setup'))
@register() @register()
class trust_del(LDAPDelete): class trust_del(LDAPDelete):

View File

@ -424,6 +424,17 @@ class user(LDAPObject):
], ],
'default_privileges': {'User Administrators'}, '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') label = _('Users')
@ -780,23 +791,21 @@ class user_add(LDAPCreate):
if 'manager' in entry_attrs: if 'manager' in entry_attrs:
entry_attrs['manager'] = self.obj._normalize_manager(entry_attrs['manager']) entry_attrs['manager'] = self.obj._normalize_manager(entry_attrs['manager'])
if ('objectclass' in entry_attrs if 'userclass' in entry_attrs and \
and 'userclass' in entry_attrs 'ipauser' not in entry_attrs['objectclass']:
and 'ipauser' not in entry_attrs['objectclass']):
entry_attrs['objectclass'].append('ipauser') entry_attrs['objectclass'].append('ipauser')
if 'ipatokenradiusconfiglink' in entry_attrs: if 'ipauserauthtype' in entry_attrs and \
cl = entry_attrs['ipatokenradiusconfiglink'] 'ipauserauthtypeclass' not in entry_attrs['objectclass']:
if cl: entry_attrs['objectclass'].append('ipauserauthtypeclass')
if 'objectclass' not in entry_attrs:
_entry = ldap.get_entry(dn, ['objectclass'])
entry_attrs['objectclass'] = _entry['objectclass']
if 'ipatokenradiusproxyuser' not in entry_attrs['objectclass']: rcl = entry_attrs.get('ipatokenradiusconfiglink', None)
entry_attrs['objectclass'].append('ipatokenradiusproxyuser') if rcl:
if 'ipatokenradiusproxyuser' not in entry_attrs['objectclass']:
entry_attrs['objectclass'].append('ipatokenradiusproxyuser')
answer = self.api.Object['radiusproxy'].get_dn_if_exists(cl) answer = self.api.Object['radiusproxy'].get_dn_if_exists(rcl)
entry_attrs['ipatokenradiusconfiglink'] = answer entry_attrs['ipatokenradiusconfiglink'] = answer
return dn return dn

View File

@ -96,19 +96,30 @@ def find_modules_in_dir(src_dir):
def validate_host_dns(log, fqdn): 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: try:
answers = resolver.query(fqdn, rdatatype.A) answers = resolver.query(fqdn, rdatatype.A)
log.debug( 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)) ' '.join(str(answer) for answer in answers))
) )
except DNSException, e: except DNSException, e:
log.debug( log.debug(
'IPA: DNS A record lookup failed for %s' % fqdn '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): def has_soa_or_ns_record(domain):

View File

@ -1,4 +1,5 @@
# Authors: Rob Crittenden <rcritten@redhat.com> # Authors: Rob Crittenden <rcritten@redhat.com>
# David Kupka <dkupka@redhat.com>
# #
# Copyright (C) 2010 Red Hat # Copyright (C) 2010 Red Hat
# see file 'COPYING' for use and warranty information # see file 'COPYING' for use and warranty information
@ -23,136 +24,203 @@
import os import os
import sys import sys
import re
import time import time
import dbus
from ipapython import ipautil from ipapython import ipautil
from ipapython import dogtag from ipapython import dogtag
from ipaplatform.paths import paths from ipaplatform.paths import paths
from ipaplatform import services
from ipapython.ipa_log_manager import root_logger
REQUEST_DIR=paths.CERTMONGER_REQUESTS_DIR REQUEST_DIR = paths.CERTMONGER_REQUESTS_DIR
CA_DIR=paths.CERTMONGER_CAS_DIR CA_DIR = paths.CERTMONGER_CAS_DIR
# Normalizer types for critera in get_request_id() DBUS_CM_PATH = '/org/fedorahosted/certmonger'
NPATH = 1 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 Auxiliary class for convenient DBus object handling.
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.
""" """
tries = 1 def __init__(self, bus, object_path, object_dbus_interface,
value = None parent_dbus_interface=None, property_interface=False):
found = False """
while value is None and tries <= 5: bus - DBus bus object, result of dbus.SystemBus() or dbus.SessionBus()
tries=tries + 1 Object is accesible over this DBus bus instance.
time.sleep(1) object_path - path to requested object on DBus bus
fp = open(filename, 'r') object_dbus_interface
lines = fp.readlines() parent_dbus_interface
fp.close() 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: def _start_certmonger():
# A value can span multiple lines. If it does then it has a """
# leading space. Start certmonger daemon. If it's already running systemctl just ignores
if not line.startswith(' '): the command.
# We hit the next directive, return now """
return value if not services.knownservices.certmonger.is_running():
else: try:
value = value + line[1:] 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: else:
if line.startswith(directive + '='): value = request.prop_if.Get(DBUS_CM_REQUEST_IF, criterion)
found = True if value != criteria[criterion]:
value = line[len(directive)+1:] 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): def get_request_value(request_id, directive):
""" """
There is no guarantee that the request_id will match the filename Get property of request.
in the certmonger requests directory, so open each one to find the
request_id.
""" """
fileList=os.listdir(REQUEST_DIR) try:
for file in fileList: request = _get_request(dict(nickname=request_id))
value = find_request_value('%s/%s' % (REQUEST_DIR, file), 'id') except RuntimeError, e:
if value is not None and value.rstrip() == request_id: root_logger.error('Failed to get request: %s' % e)
return find_request_value('%s/%s' % (REQUEST_DIR, file), directive) raise
if request:
return request.prop_if.Get(DBUS_CM_REQUEST_IF, directive)
else:
return None
return None
def get_request_id(criteria): def get_request_id(criteria):
""" """
If you don't know the certmonger request_id then try to find it by looking 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 through all the requests.
ipa-getcert list output but this seems cleaner.
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 better. An error is raised if multiple request_ids are returned for
the same criteria. the same criteria.
None is returned if none of the criteria match. 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 if request:
fileList=os.listdir(REQUEST_DIR) return request.prop_if.Get(DBUS_CM_REQUEST_IF, 'nickname')
for file in fileList: else:
match = True return None
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()
return reqid
def get_requests_for_dir(dir): def get_requests_for_dir(dir):
""" """
Return a list containing the request ids for a given NSS database Return a list containing the request ids for a given NSS database
directory. directory.
""" """
reqid=[] reqid = []
fileList=os.listdir(REQUEST_DIR) criteria = {'cert-storage': 'NSSDB', 'key-storage': 'NSSDB',
for file in fileList: 'cert-database': dir, 'key-database': dir, }
rv = find_request_value(os.path.join(REQUEST_DIR, file), requests = _get_requests(criteria)
'cert_storage_location') for request in requests:
if rv is None: reqid.append(request.prop_if.Get(DBUS_CM_REQUEST_IF, 'nickname'))
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())
return reqid return reqid
def add_request_value(request_id, directive, value): def add_request_value(request_id, directive, value):
""" """
Add a new directive to a certmonger request file. 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) try:
for file in fileList: request = _get_request({'nickname': request_id})
id = find_request_value('%s/%s' % (REQUEST_DIR, file), 'id') except RuntimeError, e:
if id is not None and id.rstrip() == request_id: root_logger.error('Failed to get request: %s' % e)
current_value = find_request_value('%s/%s' % (REQUEST_DIR, file), directive) raise
if not current_value: if request:
fp = open('%s/%s' % (REQUEST_DIR, file), 'a') request.obj_if.modify({directive: value})
fp.write('%s=%s\n' % (directive, value))
fp.close()
return
def add_principal(request_id, principal): 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 When an existing certificate is added via start-tracking it won't have
a principal. 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): 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 When an existing certificate is added via start-tracking it won't have
a subject_template set. 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): 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, cm = _connect_to_certmonger()
'request', request_parameters = dict(KEY_STORAGE='NSSDB', CERT_STORAGE='NSSDB',
'-d', nssdb, CERT_LOCATION=nssdb, CERT_NICKNAME=nickname,
'-n', nickname, SUBJECT=subject, PRINCIPAL=principal,)
'-N', subject,
'-K', principal,
]
if passwd_fname: if passwd_fname:
args.append('-p') request_parameters['KEY_PIN_FILE'] = passwd_fname
args.append(os.path.abspath(passwd_fname)) result = cm.obj_if.add_request(request_parameters)
(stdout, stderr, returncode) = ipautil.run(args) try:
# FIXME: should be some error handling around this if result[0]:
m = re.match('New signing request "(\d+)" added', stdout) request = _cm_dbus_object(cm.bus, result[1], DBUS_CM_REQUEST_IF,
request_id = m.group(1) DBUS_CM_IF, True)
return request_id 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): 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 certmonger to run when it renews a certificate. This command must
reside in /usr/lib/ipa/certmonger to work with SELinux. reside in /usr/lib/ipa/certmonger to work with SELinux.
Returns the stdout, stderr and returncode from running ipa-getcert Returns True or False
This assumes that certmonger is already running.
""" """
if not cert_exists(nickname, os.path.abspath(secdir)): cm = _connect_to_certmonger()
raise RuntimeError('Nickname "%s" doesn\'t exist in NSS database "%s"' % (nickname, secdir)) params = {'TRACK': True}
args = [paths.IPA_GETCERT, "start-tracking", params['cert-nickname'] = nickname
"-d", os.path.abspath(secdir), params['cert-database'] = os.path.abspath(secdir)
"-n", nickname] params['cert-storage'] = 'NSSDB'
if password_file: params['key-nickname'] = nickname
args.append("-p") params['key-database'] = os.path.abspath(secdir)
args.append(os.path.abspath(password_file)) params['key-storage'] = 'NSSDB'
if command: if command:
args.append("-C") params['cert-postsave-command'] = command
args.append(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): def stop_tracking(secdir, request_id=None, nickname=None):
""" """
Stop tracking the current request using either the request_id or nickname. 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: if request_id is None and nickname is None:
raise RuntimeError('Both request_id and nickname are missing.') 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, criteria = {'cert-database': secdir}
'stop-tracking',
]
if request_id: if request_id:
args.append('-i') criteria['nickname'] = request_id
args.append(request_id) if nickname:
else: criteria['cert-nickname'] = nickname
args.append('-n') try:
args.append(nickname) request = _get_request(criteria)
args.append('-d') except RuntimeError, e:
args.append(os.path.abspath(secdir)) 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(): def _find_IPA_ca():
""" """
@ -286,13 +330,10 @@ def _find_IPA_ca():
We can use find_request_value because the ca files have the We can use find_request_value because the ca files have the
same file format. same file format.
""" """
fileList=os.listdir(CA_DIR) cm = _connect_to_certmonger()
for file in fileList: ca_path = cm.obj_if.find_ca_by_nickname('IPA')
value = find_request_value('%s/%s' % (CA_DIR, file), 'id') return _cm_dbus_object(cm.bus, ca_path, DBUS_CM_CA_IF, DBUS_CM_IF, True)
if value is not None and value.strip() == 'IPA':
return '%s/%s' % (CA_DIR, file)
return None
def add_principal_to_cas(principal): def add_principal_to_cas(principal):
""" """
@ -302,58 +343,27 @@ def add_principal_to_cas(principal):
/usr/libexec/certmonger/ipa-submit. /usr/libexec/certmonger/ipa-submit.
We also need to restore this on uninstall. We also need to restore this on uninstall.
The certmonger service MUST be stopped in order for this to work.
""" """
cafile = _find_IPA_ca() ca = _find_IPA_ca()
if cafile is None: if ca:
return 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(): def remove_principal_from_cas():
""" """
Remove any -k principal options from the ipa_submit helper. 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() ca = _find_IPA_ca()
if cafile is None: if ca:
return 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): def get_pin(token, dogtag_constants=None):
""" """
Dogtag stores its NSS pin in a file formatted as token:PIN. 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 pin.strip()
return None return None
def dogtag_start_tracking(ca, nickname, pin, pinfile, secdir, pre_command, def dogtag_start_tracking(ca, nickname, pin, pinfile, secdir, pre_command,
post_command, profile=None): 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. post_command is the script to execute after a renewal is done.
Both commands can be None. 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", cm = _connect_to_certmonger()
"-d", os.path.abspath(secdir), certmonger_cmd_template = paths.CERTMONGER_COMMAND_TEMPLATE
"-n", nickname,
"-c", ca,
]
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 not os.path.isabs(pre_command):
if sys.maxsize > 2**32L: if sys.maxsize > 2**32L:
libpath = 'lib64' libpath = 'lib64'
else: else:
libpath = 'lib' libpath = 'lib'
pre_command = paths.CERTMONGER_COMMAND_TEMPLATE % (libpath, pre_command) pre_command = certmonger_cmd_template % (libpath, pre_command)
args.append("-B") params['cert-presave-command'] = pre_command
args.append(pre_command) if post_command:
if post_command is not None:
if not os.path.isabs(post_command): if not os.path.isabs(post_command):
if sys.maxsize > 2**32L: if sys.maxsize > 2**32L:
libpath = 'lib64' libpath = 'lib64'
else: else:
libpath = 'lib' libpath = 'lib'
post_command = paths.CERTMONGER_COMMAND_TEMPLATE % (libpath, post_command) post_command = certmonger_cmd_template % (libpath, post_command)
args.append("-C") params['cert-postsave-command'] = post_command
args.append(post_command)
if pinfile:
args.append("-p")
args.append(pinfile)
else:
args.append("-P")
args.append(pin)
if profile: if profile:
args.append("-T") params['ca-profile'] = profile
args.append(profile)
cm.obj_if.add_request(params)
(stdout, stderr, returncode) = ipautil.run(args, nolog=[pin])
def check_state(dirs): def check_state(dirs):
""" """
@ -446,8 +451,11 @@ def check_state(dirs):
return reqids return reqids
if __name__ == '__main__': 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') csr = get_request_value(request_id, 'csr')
print csr print csr
stop_tracking(request_id) stop_tracking(request_id)

View File

@ -589,7 +589,11 @@ class DomainValidator(object):
try: try:
result = netrc.finddc(domain=domain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_GC | nbt.NBT_SERVER_CLOSEST) result = netrc.finddc(domain=domain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_GC | nbt.NBT_SERVER_CLOSEST)
except RuntimeError, e: 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: if not self._domains:
self._domains = self.get_trusted_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) 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] 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 When retrieving DC information anonymously, we can't get SID of the domain
""" """
netrc = net.Net(creds=self.creds, lp=self.parm) 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: try:
if discover_srv: 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: 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: except RuntimeError, e:
raise assess_dcerpc_exception(message=str(e)) raise assess_dcerpc_exception(message=str(e))
@ -723,6 +730,7 @@ class TrustDomainInstance(object):
self.info['dns_forest'] = unicode(result.forest) self.info['dns_forest'] = unicode(result.forest)
self.info['guid'] = unicode(result.domain_uuid) self.info['guid'] = unicode(result.domain_uuid)
self.info['dc'] = unicode(result.pdc_dns_name) 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. # 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 # 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['sid'] = unicode(result.sid)
self.info['dc'] = remote_host 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 generate_auth(self, trustdom_secret):
def arcfour_encrypt(key, data): def arcfour_encrypt(key, data):
c = RC4.RC4(key) c = RC4.RC4(key)
@ -886,7 +901,7 @@ class TrustDomainInstance(object):
info.sid = security.dom_sid(another_domain.info['sid']) info.sid = security.dom_sid(another_domain.info['sid'])
info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND
info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE info.trust_attributes = 0
try: try:
dname = lsa.String() dname = lsa.String()
@ -903,8 +918,6 @@ class TrustDomainInstance(object):
except RuntimeError, (num, message): except RuntimeError, (num, message):
raise assess_dcerpc_exception(num=num, message=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 # We should use proper trustdom handle in order to modify the
# trust settings. Samba insists this has to be done with LSA # trust settings. Samba insists this has to be done with LSA
# OpenTrustedDomain* calls, it is not enough to have a handle # 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 # server as that one doesn't support AES encryption types
pass 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 verify_trust(self, another_domain):
def retrieve_netlogon_info_2(domain, function_code, data): def retrieve_netlogon_info_2(domain, function_code, data):
try: try:
@ -1017,7 +1039,7 @@ def fetch_domains(api, mydomain, trustdomain, creds=None):
result = [] result = []
for t in domains.array: 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'])): (t.trust_flags & trust_flags['NETR_TRUST_FLAG_IN_FOREST'])):
res = dict() res = dict()
res['cn'] = unicode(t.dns_name) res['cn'] = unicode(t.dns_name)
@ -1066,9 +1088,9 @@ class TrustDomainJoins(object):
rd.creds.set_anonymous() rd.creds.set_anonymous()
rd.creds.set_workstation(self.local_domain.hostname) rd.creds.set_workstation(self.local_domain.hostname)
if realm_server is None: if realm_server is None:
rd.retrieve_anonymously(realm, discover_srv=True) rd.retrieve_anonymously(realm, discover_srv=True, search_pdc=True)
else: else:
rd.retrieve_anonymously(realm_server, discover_srv=False) rd.retrieve_anonymously(realm_server, discover_srv=False, search_pdc=True)
rd.read_only = True rd.read_only = True
if realm_admin and realm_passwd: if realm_admin and realm_passwd:
if 'name' in rd.info: if 'name' in rd.info:
@ -1129,6 +1151,9 @@ class TrustDomainJoins(object):
realm_passwd 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: if not self.remote_domain.read_only:
trustdom_pass = samba.generate_random_password(128, 128) trustdom_pass = samba.generate_random_password(128, 128)
self.get_realmdomains() self.get_realmdomains()
@ -1145,5 +1170,8 @@ class TrustDomainJoins(object):
if not(isinstance(self.remote_domain, TrustDomainInstance)): if not(isinstance(self.remote_domain, TrustDomainInstance)):
self.populate_remote_domain(realm, realm_server, realm_passwd=None) 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) self.local_domain.establish_trust(self.remote_domain, trustdom_passwd)
return dict(local=self.local_domain, remote=self.remote_domain, verified=False) 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 = open(self.smb_conf, "w")
conf_fd.write('### Added by IPA Installer ###\n') conf_fd.write('### Added by IPA Installer ###\n')
conf_fd.write('[global]\n') conf_fd.write('[global]\n')
conf_fd.write('debug pid = yes\n')
conf_fd.write('config backend = registry\n') conf_fd.write('config backend = registry\n')
conf_fd.close() conf_fd.close()
@ -496,28 +497,31 @@ class ADTRUSTInstance(service.Service):
def __setup_principal(self): def __setup_principal(self):
try: try:
api.Command.service_add(unicode(self.cifs_principal)) api.Command.service_add(unicode(self.cifs_principal))
# Add the principal to the 'adtrust agents' group except errors.DuplicateEntry:
# 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:
# CIFS principal already exists, it is not the first time # CIFS principal already exists, it is not the first time
# adtrustinstance is managed # adtrustinstance is managed
# That's fine, we we'll re-extract the key again. # That's fine, we we'll re-extract the key again.
pass 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() self.clean_samba_keytab()

View File

@ -318,13 +318,13 @@ def stop_tracking_certificates(dogtag_constants):
try: try:
certmonger.stop_tracking( certmonger.stop_tracking(
dogtag_constants.ALIAS_DIR, nickname=nickname) dogtag_constants.ALIAS_DIR, nickname=nickname)
except (ipautil.CalledProcessError, RuntimeError), e: except RuntimeError, e:
root_logger.error( root_logger.error(
"certmonger failed to stop tracking certificate: %s" % str(e)) "certmonger failed to stop tracking certificate: %s" % str(e))
try: try:
certmonger.stop_tracking(paths.HTTPD_ALIAS_DIR, nickname='ipaCert') certmonger.stop_tracking(paths.HTTPD_ALIAS_DIR, nickname='ipaCert')
except (ipautil.CalledProcessError, RuntimeError), e: except RuntimeError, e:
root_logger.error( root_logger.error(
"certmonger failed to stop tracking certificate: %s" % str(e)) "certmonger failed to stop tracking certificate: %s" % str(e))
cmonger.stop() cmonger.stop()
@ -449,6 +449,7 @@ class CAInstance(service.Service):
self.step("creating pki-ca instance", self.create_instance) self.step("creating pki-ca instance", self.create_instance)
self.step("configuring certificate server instance", self.__configure_instance) self.step("configuring certificate server instance", self.__configure_instance)
self.step("stopping certificate server instance to update CS.cfg", self.__stop) 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("disabling nonces", self.__disable_nonce)
self.step("set up CRL publishing", self.__enable_crl_publish) self.step("set up CRL publishing", self.__enable_crl_publish)
self.step("starting certificate server instance", self.__start) 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) config.set("CA", "pki_external_csr_path", self.csr_file)
elif self.external == 2: 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", "True")
config.set("CA", "pki_external_ca_cert_path", self.cert_file) config.set("CA", "pki_external_ca_cert_path", cert_file.name)
config.set("CA", "pki_external_ca_cert_chain_path", self.cert_chain_file) config.set("CA", "pki_external_ca_cert_chain_path", cert_chain_file.name)
config.set("CA", "pki_external_step_two", "True") config.set("CA", "pki_external_step_two", "True")
# Generate configuration file # Generate configuration file
@ -602,6 +619,7 @@ class CAInstance(service.Service):
'Contents of pkispawn configuration file (%s):\n%s' % 'Contents of pkispawn configuration file (%s):\n%s' %
(cfg_file, ipautil.nolog_replace(f.read(), nolog))) (cfg_file, ipautil.nolog_replace(f.read(), nolog)))
self.backup_state('installed', True)
try: try:
ipautil.run(args, nolog=nolog) ipautil.run(args, nolog=nolog)
except ipautil.CalledProcessError, e: except ipautil.CalledProcessError, e:
@ -646,6 +664,7 @@ class CAInstance(service.Service):
'-redirect', 'logs=/var/log/pki-ca', '-redirect', 'logs=/var/log/pki-ca',
'-enable_proxy' '-enable_proxy'
] ]
self.backup_state('installed', True)
ipautil.run(args, env={'PKI_HOSTNAME':self.fqdn}) ipautil.run(args, env={'PKI_HOSTNAME':self.fqdn})
def __enable(self): def __enable(self):
@ -717,10 +736,15 @@ class CAInstance(service.Service):
args.append("-ext_csr_file") args.append("-ext_csr_file")
args.append(self.csr_file) args.append(self.csr_file)
elif self.external == 2: 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("-external")
args.append("true") args.append("true")
args.append("-ext_ca_cert_file") 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("-ext_ca_cert_chain_file")
args.append(self.cert_chain_file) args.append(self.cert_chain_file)
else: else:
@ -788,6 +812,12 @@ class CAInstance(service.Service):
root_logger.debug(traceback.format_exc()) root_logger.debug(traceback.format_exc())
root_logger.critical("Failed to restart the certificate server. See the installation log for details.") 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): def __disable_nonce(self):
# Turn off Nonces # Turn off Nonces
update_result = installutils.update_file( update_result = installutils.update_file(
@ -1320,6 +1350,8 @@ class CAInstance(service.Service):
if not enabled is None and not enabled: if not enabled is None and not enabled:
self.disable() self.disable()
# Just eat this state if it exists
installed = self.restore_state("installed")
try: try:
if self.dogtag_constants.DOGTAG_VERSION >= 10: if self.dogtag_constants.DOGTAG_VERSION >= 10:
ipautil.run([paths.PKIDESTROY, "-i", ipautil.run([paths.PKIDESTROY, "-i",
@ -1355,9 +1387,12 @@ class CAInstance(service.Service):
# remove CRL files # remove CRL files
root_logger.info("Remove old CRL files") root_logger.info("Remove old CRL files")
for f in get_crl_files(): try:
root_logger.debug("Remove %s", f) for f in get_crl_files():
installutils.remove_file(f) 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 # remove CRL directory
root_logger.info("Remove CRL directory") root_logger.info("Remove CRL directory")
@ -1417,7 +1452,7 @@ class CAInstance(service.Service):
secdir=paths.HTTPD_ALIAS_DIR, secdir=paths.HTTPD_ALIAS_DIR,
pre_command=None, pre_command=None,
post_command='renew_ra_cert') post_command='renew_ra_cert')
except (ipautil.CalledProcessError, RuntimeError), e: except RuntimeError, e:
root_logger.error( root_logger.error(
"certmonger failed to start tracking certificate: %s" % e) "certmonger failed to start tracking certificate: %s" % e)
@ -1445,7 +1480,7 @@ class CAInstance(service.Service):
secdir=self.dogtag_constants.ALIAS_DIR, secdir=self.dogtag_constants.ALIAS_DIR,
pre_command='stop_pkicad', pre_command='stop_pkicad',
post_command='renew_ca_cert "%s"' % nickname) post_command='renew_ca_cert "%s"' % nickname)
except (ipautil.CalledProcessError, RuntimeError), e: except RuntimeError, e:
root_logger.error( root_logger.error(
"certmonger failed to start tracking certificate: %s" % e) "certmonger failed to start tracking certificate: %s" % e)
@ -1465,7 +1500,7 @@ class CAInstance(service.Service):
secdir=self.dogtag_constants.ALIAS_DIR, secdir=self.dogtag_constants.ALIAS_DIR,
pre_command=None, pre_command=None,
post_command=None) post_command=None)
except (ipautil.CalledProcessError, RuntimeError), e: except RuntimeError, e:
root_logger.error( root_logger.error(
"certmonger failed to start tracking certificate: %s" % e) "certmonger failed to start tracking certificate: %s" % e)
@ -1596,12 +1631,15 @@ class CAInstance(service.Service):
return True return True
return False 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: if not self.admin_conn:
self.ldap_connect() self.ldap_connect()
dn = DN(('cn', 'CA'), ('cn', api.env.host), ('cn', 'masters'), dn = DN(('cn', 'CA'), ('cn', fqdn), ('cn', 'masters'), ('cn', 'ipa'),
('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) ('cn', 'etc'), api.env.basedn)
filter = '(ipaConfigString=caRenewalMaster)' filter = '(ipaConfigString=caRenewalMaster)'
try: try:
self.admin_conn.get_entries(base_dn=dn, filter=filter, self.admin_conn.get_entries(base_dn=dn, filter=filter,
@ -1611,6 +1649,38 @@ class CAInstance(service.Service):
return True 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): def replica_ca_install_check(config):
if not config.setup_ca: if not config.setup_ca:
@ -1740,6 +1810,16 @@ def install_replica_ca(config, postinstall=False):
return ca 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): def update_cert_config(nickname, cert, dogtag_constants=None):
""" """
When renewing a CA subsystem certificate the configuration file 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, with stopped_service(dogtag_constants.SERVICE_NAME,
instance_name=dogtag_constants.PKI_INSTANCE_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, installutils.set_directive(dogtag.configured_constants().CS_CFG_PATH,
directives[nickname], directives[nickname],

View File

@ -209,9 +209,21 @@ class NSSDatabase(object):
raise RuntimeError( raise RuntimeError(
"Setting trust on %s failed" % root_nickname) "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): def export_pem_cert(self, nickname, location):
"""Export the given cert to PEM file in the given 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: with open(location, "w+") as fd:
fd.write(cert) fd.write(cert)
os.chmod(location, 0444) os.chmod(location, 0444)
@ -510,46 +522,26 @@ class CertDB(object):
else: else:
libpath = 'lib' libpath = 'lib'
command = paths.CERTMONGER_COMMAND_TEMPLATE % (libpath, command) command = paths.CERTMONGER_COMMAND_TEMPLATE % (libpath, command)
cmonger = services.knownservices.certmonger
cmonger.enable()
services.knownservices.messagebus.start()
cmonger.start()
try: try:
(stdout, stderr, rc) = certmonger.start_tracking(nickname, self.secdir, password_file, command) request_id = certmonger.start_tracking(nickname, self.secdir, password_file, command)
except (ipautil.CalledProcessError, RuntimeError), e: except RuntimeError, e:
root_logger.error("certmonger failed starting to track certificate: %s" % str(e)) root_logger.error("certmonger failed starting to track certificate: %s" % str(e))
return return
cmonger.stop()
cert = self.get_cert_from_db(nickname) cert = self.get_cert_from_db(nickname)
nsscert = x509.load_certificate(cert, dbdir=self.secdir) nsscert = x509.load_certificate(cert, dbdir=self.secdir)
subject = str(nsscert.subject) 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_principal(request_id, principal)
certmonger.add_subject(request_id, subject) certmonger.add_subject(request_id, subject)
cmonger.start()
def untrack_server_cert(self, nickname): def untrack_server_cert(self, nickname):
""" """
Tell certmonger to stop tracking the given certificate 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: try:
certmonger.stop_tracking(self.secdir, nickname=nickname) 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)) 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): 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): class DsInstance(service.Service):
def __init__(self, realm_name=None, domain_name=None, dm_password=None, 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.Service.__init__(self, "dirsrv",
service_desc="directory server", service_desc="directory server",
dm_password=dm_password, dm_password=dm_password,
ldapi=False, ldapi=False,
autobind=service.DISABLED autobind=service.DISABLED
) )
self.nickname = cert_nickname self.nickname = 'Server-Cert'
self.dm_password = dm_password self.dm_password = dm_password
self.realm = realm_name self.realm = realm_name
self.sub_dict = None 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]) raise RuntimeError("Could not find a suitable server cert in import in %s" % self.pkcs12_info[0])
# We only handle one server cert # We only handle one server cert
nickname = server_certs[0][0] self.nickname = server_certs[0][0]
self.dercert = dsdb.get_cert_from_db(nickname, pem=False) self.dercert = dsdb.get_cert_from_db(self.nickname, pem=False)
else: else:
nickname = self.nickname
cadb = certs.CertDB(self.realm, host_name=self.fqdn, subject_base=self.subject_base) cadb = certs.CertDB(self.realm, host_name=self.fqdn, subject_base=self.subject_base)
# FIXME, need to set this nickname in the RA plugin # FIXME, need to set this nickname in the RA plugin
cadb.export_ca_cert('ipaCert', False) cadb.export_ca_cert('ipaCert', False)
dsdb.create_from_cacert(cadb.cacert_fname, passwd=None) dsdb.create_from_cacert(cadb.cacert_fname, passwd=None)
self.dercert = dsdb.create_server_cert( self.dercert = dsdb.create_server_cert(
nickname, self.fqdn, cadb) self.nickname, self.fqdn, cadb)
dsdb.create_pin_file() dsdb.create_pin_file()
self.cacert_name = dsdb.cacert_name self.cacert_name = dsdb.cacert_name
if self.ca_is_configured: if self.ca_is_configured:
dsdb.track_server_cert( dsdb.track_server_cert(
nickname, self.principal, dsdb.passwd_fname, self.nickname, self.principal, dsdb.passwd_fname,
'restart_dirsrv %s' % self.serverid) 'restart_dirsrv %s' % self.serverid)
conn = ipaldap.IPAdmin(self.fqdn) conn = ipaldap.IPAdmin(self.fqdn)
@ -670,7 +669,7 @@ class DsInstance(service.Service):
DN(('cn', 'RSA'), ('cn', 'encryption'), ('cn', 'config')), DN(('cn', 'RSA'), ('cn', 'encryption'), ('cn', 'config')),
objectclass=["top", "nsEncryptionModule"], objectclass=["top", "nsEncryptionModule"],
cn=["RSA"], cn=["RSA"],
nsSSLPersonalitySSL=[nickname], nsSSLPersonalitySSL=[self.nickname],
nsSSLToken=["internal (software)"], nsSSLToken=["internal (software)"],
nsSSLActivation=["on"], 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) hosts_record = record_in_hosts(ip_address)
if hosts_record is None: 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" print "Adding ["+ip_address+" "+host_name+"] to your /etc/hosts file"
fstore.backup_file(paths.HOSTS) fstore.backup_file(paths.HOSTS)
add_record_to_hosts(ip_address, host_name) 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 This is used for files given to --*_pkcs12 to ipa-server-install and
ipa-replica-prepare. ipa-replica-prepare.
Return a (server cert name, CA cert names) tuple
""" """
pkcs12_filename, pkcs12_passwd = pkcs12_info pkcs12_filename, pkcs12_passwd = pkcs12_info
root_logger.debug('Checking PKCS#12 certificate %s', pkcs12_filename) 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 # Import the CA cert first so it has a known nickname
# (if it's present in the PKCS#12 it won't be overwritten) # (if it's present in the PKCS#12 it won't be overwritten)
ca_cert_name = 'The Root CA' ca_cert_name = 'The Root CA'
try: if ca_file:
nssdb.import_pem_cert(ca_cert_name, "CT,C,C", ca_file) try:
except (ValueError, RuntimeError) as e: nssdb.import_pem_cert(ca_cert_name, "CT,C,C", ca_file)
raise ScriptError(str(e)) except (ValueError, RuntimeError) as e:
raise ScriptError(str(e))
# Import everything in the PKCS#12 # 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) # Check we have exactly one server cert (one with a private key)
server_certs = nssdb.find_server_certs() 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 # Check we have the whole cert chain & the CA is in it
trust_chain = nssdb.get_trust_chain(server_cert_name) trust_chain = nssdb.get_trust_chain(server_cert_name)
while trust_chain: if len(trust_chain) < 2:
if trust_chain[0] == ca_cert_name: if ca_file:
break raise ScriptError(
trust_chain = trust_chain[1:] '%s is not signed by %s, or the full certificate chain is '
else: '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( raise ScriptError(
'%s is not signed by %s, or the full certificate chain is not ' '%s is not signed by %s' % (pkcs12_filename, ca_file))
'present in the PKCS#12 file' % (pkcs12_filename, ca_file)) ca_cert_name = trust_chain[-2]
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)))
# Check server validity # Check server validity
nssdb.trust_root_cert(ca_cert_name)
try: try:
nssdb.verify_server_cert_validity(server_cert_name, hostname) nssdb.verify_server_cert_validity(server_cert_name, hostname)
except ValueError as e: 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' % 'The server certificate in %s is not valid: %s' %
(pkcs12_filename, e)) (pkcs12_filename, e))
return server_cert_name return nssdb.get_cert(ca_cert_name)
@contextmanager @contextmanager
def private_ccache(path=None): def private_ccache(path=None):

View File

@ -139,7 +139,7 @@ class ReplicaPrepare(admintool.AdminTool):
"could not find directory instance: %s" % config_dir) "could not find directory instance: %s" % config_dir)
def check_pkcs12(self, pkcs12_file, pkcs12_pin): def check_pkcs12(self, pkcs12_file, pkcs12_pin):
installutils.check_pkcs12( return installutils.check_pkcs12(
pkcs12_info=(pkcs12_file, pkcs12_pin), pkcs12_info=(pkcs12_file, pkcs12_pin),
ca_file=CACERT, ca_file=CACERT,
hostname=self.replica_fqdn) hostname=self.replica_fqdn)
@ -221,7 +221,8 @@ class ReplicaPrepare(admintool.AdminTool):
if options.http_pin is None: if options.http_pin is None:
raise admintool.ScriptError( raise admintool.ScriptError(
"%s unlock password required" % options.http_pkcs12) "%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_pkcs12:
if options.dirsrv_pin is None: if options.dirsrv_pin is None:
@ -231,7 +232,8 @@ class ReplicaPrepare(admintool.AdminTool):
if options.dirsrv_pin is None: if options.dirsrv_pin is None:
raise admintool.ScriptError( raise admintool.ScriptError(
"%s unlock password required" % options.dirsrv_pkcs12) "%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_pkcs12:
if options.pkinit_pin is None: if options.pkinit_pin is None:
@ -242,6 +244,12 @@ class ReplicaPrepare(admintool.AdminTool):
raise admintool.ScriptError( raise admintool.ScriptError(
"%s unlock password required" % options.pkinit_pkcs12) "%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( if (not ipautil.file_exists(
dogtag.configured_constants().CS_CFG_PATH) and dogtag.configured_constants().CS_CFG_PATH) and
options.dirsrv_pin is None): 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) os.chown(os.path.join(dirname, 'secmod.db'), 0, pent.pw_gid)
def import_cert(self, dirname, pkcs12_passwd, old_cert, principal, command): 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), pkcs12_info=(self.pkcs12_fname, pkcs12_passwd),
ca_file=CACERT, ca_file=CACERT,
hostname=api.env.host) hostname=api.env.host)
@ -166,6 +166,7 @@ class ServerCertInstall(admintool.AdminTool):
cdb.delete_cert(old_cert) cdb.delete_cert(old_cert)
cdb.import_pkcs12(self.pkcs12_fname, pkcs12_passwd) cdb.import_pkcs12(self.pkcs12_fname, pkcs12_passwd)
server_cert = cdb.find_server_certs()[0][0]
if api.env.enable_ra: if api.env.enable_ra:
cdb.track_server_cert(server_cert, principal, cdb.passwd_fname, 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) self.debug("found CA renewal master %s", entries[0].dn[1].value)
return (False, False, []) return (False, False, [])
criteria = ( criteria = {
('cert_storage_location', paths.HTTPD_ALIAS_DIR, certmonger.NPATH), 'cert-database': paths.HTTPD_ALIAS_DIR,
('cert_nickname', 'ipaCert', None), 'cert-nickname': 'ipaCert',
) }
request_id = certmonger.get_request_id(criteria) request_id = certmonger.get_request_id(criteria)
if request_id is not None: if request_id is not None:
self.debug("found certmonger request for ipaCert") self.debug("found certmonger request for ipaCert")

View File

@ -61,6 +61,8 @@ class update_dnszones(PostUpdate):
def execute(self, **options): def execute(self, **options):
ldap = self.obj.backend ldap = self.obj.backend
if not dns_container_exists(ldap):
return (False, False, [])
try: try:
zones = api.Command.dnszone_find(all=True)['result'] zones = api.Command.dnszone_find(all=True)['result']
@ -153,6 +155,8 @@ class update_check_forwardzones(PreSchemaUpdate):
# no upgrade is needed # no upgrade is needed
return (False, False, []) return (False, False, [])
ldap = self.obj.backend 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') result = ldap.schema.get_obj(_ldap.schema.models.ObjectClass, 'idnsforwardzone')
if result is None: if result is None:
sysupgrade.set_upgrade_state('dns', 'update_to_forward_zones', True) 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() register = Registry()
NONOBJECT_PERMISSIONS = { 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': { 'System: Read IPA Masters': {
'replaces_global_anonymous_aci': True, 'replaces_global_anonymous_aci': True,
'ipapermlocation': DN('cn=masters,cn=ipa,cn=etc', api.env.basedn), '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 # ignore when trying to unbind multiple times
pass pass
def find_entries(self, filter=None, attrs_list=None, base_dn=None, def find_entries(self, filter=None, attrs_list=None, base_dn=None,
scope=_ldap.SCOPE_SUBTREE, time_limit=None, scope=_ldap.SCOPE_SUBTREE, time_limit=None,
size_limit=None, search_refs=False, paged_search=False): size_limit=None, search_refs=False, paged_search=False):
if time_limit is None or size_limit is None:
config = self.get_ipa_config() def _get_limits():
if time_limit is None: """Get configured global limits, caching them for more calls"""
time_limit = config.get('ipasearchtimelimit', [None])[0] if not _lims:
if size_limit is None: config = self.get_ipa_config()
size_limit = config.get('ipasearchrecordslimit', [None])[0] _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_memberindirect = False
has_memberofindirect = False has_memberofindirect = False
@ -207,6 +216,20 @@ class ldap2(LDAPClient, CrudBackend):
search_refs=search_refs, paged_search=paged_search) search_refs=search_refs, paged_search=paged_search)
if has_memberindirect or has_memberofindirect: 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: for entry in res:
if has_memberindirect: if has_memberindirect:
self._process_memberindirect( self._process_memberindirect(

View File

@ -39,7 +39,7 @@ from ipalib.capabilities import VERSION_WITHOUT_CAPABILITIES
from ipalib.backend import Executioner from ipalib.backend import Executioner
from ipalib.errors import (PublicError, InternalError, CommandError, JSONError, from ipalib.errors import (PublicError, InternalError, CommandError, JSONError,
CCacheError, RefererError, InvalidSessionPassword, NotFound, ACIError, CCacheError, RefererError, InvalidSessionPassword, NotFound, ACIError,
ExecutionError) ExecutionError, PasswordExpired)
from ipalib.request import context, destroy_context from ipalib.request import context, destroy_context
from ipalib.rpc import (xml_dumps, xml_loads, from ipalib.rpc import (xml_dumps, xml_loads,
json_encode_binary, json_decode_binary) 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 # Get the ccache we'll use and attempt to get credentials in it with user,password
ipa_ccache_name = get_ipa_ccache_name() ipa_ccache_name = get_ipa_ccache_name()
reason = 'invalid-password'
try: try:
self.kinit(user, self.api.env.realm, password, ipa_ccache_name) self.kinit(user, self.api.env.realm, password, ipa_ccache_name)
except InvalidSessionPassword, e: except PasswordExpired as e:
# Ok, now why is this bad. Is the password simply bad or is the return self.unauthorized(environ, start_response, str(e), 'password-expired')
# password expired? except InvalidSessionPassword as e:
try: return self.unauthorized(environ, start_response, str(e), 'invalid-password')
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)
return self.finalize_kerberos_acquisition('login_password', ipa_ccache_name, environ, start_response) 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( (stdout, stderr, returncode) = ipautil.run(
[paths.KINIT, principal, '-T', armor_path], [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"', self.debug('kinit: principal=%s returncode=%s, stderr="%s"',
principal, returncode, stderr) principal, returncode, stderr)
@ -1013,6 +989,8 @@ class login_password(Backend, KerberosSession, HTTP_Status):
raiseonerr=False) raiseonerr=False)
if returncode != 0: 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)) raise InvalidSessionPassword(principal=principal, message=unicode(stderr))
class change_password(Backend, HTTP_Status): class change_password(Backend, HTTP_Status):

View File

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

View File

@ -315,15 +315,7 @@ def configure_dns_for_trust(master, ad):
kinit_admin(master) kinit_admin(master)
if is_subdomain(master.domain.name, ad.domain.name): if is_subdomain(ad.domain.name, master.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):
master.run_command(['ipa', 'dnsrecord-add', master.domain.name, master.run_command(['ipa', 'dnsrecord-add', master.domain.name,
'%s.%s' % (ad.shortname, ad.netbios), '%s.%s' % (ad.shortname, ad.netbios),
'--a-ip-address', ad.ip]) '--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, master.run_command(['ipa', 'dnszone-mod', master.domain.name,
'--allow-transfer', ad.ip]) '--allow-transfer', ad.ip])
else: else:
master.run_command(['ipa', 'dnszone-add', ad.domain.name, master.run_command(['ipa', 'dnsforwardzone-add', ad.domain.name,
'--name-server', ad.hostname,
'--admin-email', 'hostmaster@%s' % ad.domain.name,
'--forwarder', ad.ip, '--forwarder', ad.ip,
'--forward-policy', 'only', '--forward-policy', 'only',
'--ip-address', ad.ip, ])
'--force'])
def establish_trust_with_ad(master, ad, extra_args=()): 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 # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import nose
import re import re
from ipatests.test_integration.base import IntegrationTest from ipatests.test_integration.base import IntegrationTest
@ -29,6 +30,7 @@ class ADTrustBase(IntegrationTest):
topology = 'line' topology = 'line'
num_ad_domains = 1 num_ad_domains = 1
optional_extra_roles = ['ad_subdomain']
@classmethod @classmethod
def install(cls): def install(cls):
@ -38,6 +40,14 @@ class ADTrustBase(IntegrationTest):
cls.check_sid_generation() cls.check_sid_generation()
cls.configure_dns_and_time() 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 @classmethod
def install_adtrust(cls): def install_adtrust(cls):
"""Test adtrust support installation""" """Test adtrust support installation"""
@ -64,16 +74,32 @@ class ADTrustBase(IntegrationTest):
tasks.configure_dns_for_trust(cls.master, cls.ad) tasks.configure_dns_for_trust(cls.master, cls.ad)
tasks.sync_time(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): def test_establish_trust(self):
"""Tests establishing trust with Active Directory""" """Tests establishing trust with Active Directory"""
tasks.establish_trust_with_ad(self.master, self.ad, tasks.establish_trust_with_ad(self.master, self.ad,
extra_args=['--range-type', 'ipa-ad-trust']) 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): def test_range_properties_in_nonposix_trust(self):
"""Check the properties of the created range""" """Check the properties of the created range"""
@ -111,7 +137,7 @@ class TestBasicADTrust(ADTrustBase):
class TestPosixADTrust(ADTrustBase): class TestPosixADTrust(ADTrustBase):
"""Integration test for Active Directory with POSIX support""" """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 # Not specifying the --range-type directly, it should be detected
tasks.establish_trust_with_ad(self.master, self.ad) tasks.establish_trust_with_ad(self.master, self.ad)

View File

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

View File

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

View File

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

View File

@ -368,9 +368,9 @@ class UI_driver(object):
self.wait_for_request(n=2) self.wait_for_request(n=2)
# reset password if needed # reset password if needed
newpw_tb = self.find("//input[@type='password'][@name='new_password']", 'xpath', auth, strict=True) newpw_tb = self.find("//input[@type='password'][@name='new_password']", 'xpath', auth)
verify_tb = self.find("//input[@type='password'][@name='verify_password']", 'xpath', auth, strict=True) verify_tb = self.find("//input[@type='password'][@name='verify_password']", 'xpath', auth)
if newpw_tb.is_displayed(): if newpw_tb and newpw_tb.is_displayed():
newpw_tb.send_keys(new_password) newpw_tb.send_keys(new_password)
verify_tb.send_keys(new_password) verify_tb.send_keys(new_password)
verify_tb.send_keys(Keys.RETURN) verify_tb.send_keys(Keys.RETURN)
@ -609,7 +609,7 @@ class UI_driver(object):
if not dialog: if not dialog:
dialog = self.get_dialog(strict=True) 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) self._button_click(s, dialog, name)
def action_button_click(self, name, parent): def action_button_click(self, name, parent):
@ -634,10 +634,8 @@ class UI_driver(object):
def _button_click(self, selector, parent, name=''): def _button_click(self, selector, parent, name=''):
btn = self.find(selector, By.CSS_SELECTOR, parent, strict=True) btn = self.find(selector, By.CSS_SELECTOR, parent, strict=True)
ActionChains(self.driver).move_to_element(btn).perform()
disabled = 'ui-state-disabled' in btn.get_attribute("class").split() or \ disabled = btn.get_attribute("disabled")
btn.get_attribute("disabled")
assert btn.is_displayed(), 'Button is not displayed: %s' % name assert btn.is_displayed(), 'Button is not displayed: %s' % name
assert not disabled, 'Invalid button state: disabled. Button: %s' % name assert not disabled, 'Invalid button state: disabled. Button: %s' % name
btn.click() btn.click()
@ -940,13 +938,18 @@ class UI_driver(object):
parent = self.get_form() parent = self.get_form()
s = self.get_table_selector(table_name) s = self.get_table_selector(table_name)
s += " tbody td input[value='%s']+label" % pkey input_s = s + " tbody td input[value='%s']" % pkey
label = self.find(s, By.CSS_SELECTOR, parent, strict=True) 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: try:
ActionChains(self.driver).move_to_element(label).click().perform() ActionChains(self.driver).move_to_element(label).click().perform()
except WebDriverException as e: except WebDriverException as e:
assert False, 'Can\'t click on checkbox label: %s \n%s' % (s, 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() self.wait()
def get_record_value(self, pkey, column, parent=None, table_name=None): 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: if table_name and parent:
s = self.get_table_selector(table_name) s = self.get_table_selector(table_name)
table = self.find(s, By.CSS_SELECTOR, parent, strict=True) table = self.find(s, By.CSS_SELECTOR, parent, strict=True)
self.action_button_click('remove', table) self.button_click('remove', table)
else: else:
self.facet_button_click('remove') self.facet_button_click('remove')
if fields: if fields:
@ -1323,7 +1326,7 @@ class UI_driver(object):
parent = self.get_form() parent = self.get_form()
s = self.get_table_selector(name) s = self.get_table_selector(name)
table = self.find(s, By.CSS_SELECTOR, parent, strict=True) 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 = self.find(s, By.CSS_SELECTOR, table, strict=True)
btn.click() btn.click()
self.wait() self.wait()
@ -1339,6 +1342,7 @@ class UI_driver(object):
self.switch_to_facet(facet) self.switch_to_facet(facet)
self.facet_button_click('add') self.facet_button_click('add')
self.wait()
self.wait_for_request() self.wait_for_request()
for key in pkeys: for key in pkeys:
@ -1364,7 +1368,7 @@ class UI_driver(object):
s = self.get_table_selector(table_name) s = self.get_table_selector(table_name)
table = self.find(s, By.CSS_SELECTOR, parent, strict=True) 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 = self.find(s, By.CSS_SELECTOR, table, strict=True)
btn.click() btn.click()
self.wait_for_request(0.4) self.wait_for_request(0.4)
@ -1372,6 +1376,7 @@ class UI_driver(object):
for key in pkeys: for key in pkeys:
self.select_record(key, table_name='available') self.select_record(key, table_name='available')
self.button_click('add') self.button_click('add')
self.wait()
self.dialog_button_click('add') self.dialog_button_click('add')
self.wait_for_request(n=2) 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) 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): def assert_button_enabled(self, name, context_selector=None, enabled=True):
""" """
Assert that button is enabled or disabled (expects that element will be 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 Assert that button in table is enabled/disabled
""" """
s = "table[name='%s'] " % table_name 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): def assert_facet(self, entity, facet=None):
""" """

View File

@ -21,8 +21,13 @@
Test the `ipalib.plugins.baseldap` module. Test the `ipalib.plugins.baseldap` module.
""" """
import ldap
from ipapython.dn import DN
from ipapython import ipaldap
from ipalib import errors from ipalib import errors
from ipalib.plugins import baseldap from ipalib.plugins import baseldap
from ipatests.util import assert_deepequal
def test_exc_wrapper(): def test_exc_wrapper():
@ -157,3 +162,47 @@ def test_exc_callback_registration():
messages = [] messages = []
subclass_instance.test_fail() subclass_instance.test_fail()
assert messages == ['Base exc_callback', 'Subclass registered callback'] 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_dnsname = DNSName(zone_findtest_forward)
zone_findtest_forward_dn = DN(('idnsname', zone_findtest_forward), api.env.container_dns, api.env.basedn) 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): class test_dns(Declarative):
@ -289,7 +290,8 @@ class test_dns(Declarative):
revzone3_classless1, revzone3_classless2, revzone3_classless1, revzone3_classless2,
idnzone1, revidnzone1, zone_findtest_master], idnzone1, revidnzone1, zone_findtest_master],
{'continue': True}), {'continue': True}),
('dnsforwardzone_del', [fwzone1, zone_findtest_forward], ('dnsforwardzone_del', [fwzone1, zone_findtest_forward,
zone_fw_wildcard],
{'continue': True}), {'continue': True}),
('dnsconfig_mod', [], {'idnsforwarders' : None, ('dnsconfig_mod', [], {'idnsforwarders' : None,
'idnsforwardpolicy' : None, 'idnsforwardpolicy' : None,
@ -1378,10 +1380,10 @@ class test_dns(Declarative):
dict( dict(
desc='Delete record %r in zone %r' % (tlsa, zone1), desc='Remove record using dnsrecord-mod %r in zone %r' % (tlsa, zone1),
command=('dnsrecord_del', [zone1, tlsa], {'del_all': True}), command=('dnsrecord_mod', [zone1, tlsa], {'tlsarecord': ''}),
expected={ expected={
'value': [tlsa_dnsname], 'value': tlsa_dnsname,
'summary': u'Deleted record "%s"' % tlsa, 'summary': u'Deleted record "%s"' % tlsa,
'result': {'failed': []}, '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( dict(
desc='Add A denormalized record in zone %r' % (idnzone1), desc='Add A denormalized record in zone %r' % (idnzone1),
command=('dnsrecord_add', [idnzone1, u'gro\xdf'], {'arecord': u'172.16.0.1'}), 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( dict(
desc='Try to create forward zone %r without forwarders with default "(first)" policy' % fwzone1, desc='Try to create forward zone %r without forwarders with default "(first)" policy' % fwzone1,
command=( command=(

View File

@ -1009,5 +1009,72 @@ class test_group(Declarative):
value=[user1], 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 ipapython import ipautil
from ipalib import api, errors, x509 from ipalib import api, errors, x509
from ipapython.dn import DN from ipapython.dn import DN
from ipapython.dnsutil import DNSName
from nose.tools import raises, assert_raises from nose.tools import raises, assert_raises
from nose.plugins.skip import SkipTest from nose.plugins.skip import SkipTest
from ipatests.test_xmlrpc.xmlrpc_test import (Declarative, XMLRPC_test, 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) api.env.basedn)
invalidfqdn1 = u'foo_bar.lab.%s' % api.env.domain 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' 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)' 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, desc='Try to add host not in DNS %r without force' % fqdn2,
command=('host_add', [fqdn2], {}), command=('host_add', [fqdn2], {}),
expected=errors.DNSNotARecordError( 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 # verify that it's gone
with assert_raises(errors.NotFound): with assert_raises(errors.NotFound):
api.Command['host_show'](self.fqdn1) 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', [], command=('permission_find', [],
{'ipapermlocation': api.env.basedn}), {'ipapermlocation': api.env.basedn}),
expected=dict( expected=dict(
count=15, count=16,
truncated=False, truncated=False,
summary=u'15 permissions matched', summary=u'16 permissions matched',
result=lambda s: True, result=lambda s: True,
), ),
), ),

View File

@ -71,7 +71,17 @@ class test_realmdomains(Declarative):
u'(version 3.0;acl ' u'(version 3.0;acl '
u'"permission:System: Read Realm Domains";' u'"permission:System: Read Realm Domains";'
u'allow (compare,read,search) ' 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),
], ],
), ),
), ),