https://fedorahosted.org/freeipa/ticket/2008

Patches attached.

From 9a936740da7cdacec150acc92a45041a98ce7cb3 Mon Sep 17 00:00:00 2001
From: Martin Basti <mba...@redhat.com>
Date: Wed, 4 May 2016 17:33:52 +0200
Subject: [PATCH 1/4] DNS Locations: Always create DNS related privileges

DNS privileges are important for handling DNS locations which can be
created without DNS servers in IPA topology. We will also need this
privileges presented for future feature 'External DNS support'

https://fedorahosted.org/freeipa/ticket/2008
---
 install/share/delegation.ldif        | 16 ++++++++++++++++
 install/share/dns.ldif               | 16 ----------------
 install/updates/37-locations.update  |  0
 install/updates/40-delegation.update | 16 ++++++++++++++++
 ipalib/plugins/location.py           |  1 +
 5 files changed, 33 insertions(+), 16 deletions(-)
 create mode 100644 install/updates/37-locations.update
 create mode 100644 ipalib/plugins/location.py

diff --git a/install/share/delegation.ldif b/install/share/delegation.ldif
index 067b4d26a8be8f4d1b699c15b027ed7f260ddb5b..064078306560528842fa76176152ac594db077c8 100644
--- a/install/share/delegation.ldif
+++ b/install/share/delegation.ldif
@@ -80,6 +80,22 @@ objectClass: nestedgroup
 cn: Delegation Administrator
 description: Role administration
 
+dn: cn=DNS Administrators,cn=privileges,cn=pbac,$SUFFIX
+changetype: add
+objectClass: top
+objectClass: groupofnames
+objectClass: nestedgroup
+cn: DNS Administrators
+description: DNS Administrators
+
+dn: cn=DNS Servers,cn=privileges,cn=pbac,$SUFFIX
+changetype: add
+objectClass: top
+objectClass: groupofnames
+objectClass: nestedgroup
+cn: DNS Servers
+description: DNS Servers
+
 dn: cn=Service Administrators,cn=privileges,cn=pbac,$SUFFIX
 changetype: add
 objectClass: top
diff --git a/install/share/dns.ldif b/install/share/dns.ldif
index 42b41a8d706a8a3fd826320aff6c9333264128fc..54a452bcd8715240905166e6ab18f722983be00f 100644
--- a/install/share/dns.ldif
+++ b/install/share/dns.ldif
@@ -10,19 +10,3 @@ aci: (targetattr = "*")(version 3.0; acl "Allow read access"; allow (read,search
 aci: (target = "ldap:///idnsname=*,cn=dns,$SUFFIX";)(version 3.0;acl "Add DNS entries in a zone";allow (add) userattr = "parent[1].managedby#GROUPDN";)
 aci: (target = "ldap:///idnsname=*,cn=dns,$SUFFIX";)(version 3.0;acl "Remove DNS entries from a zone";allow (delete) userattr = "parent[1].managedby#GROUPDN";)
 aci: (targetattr = "a6record || aaaarecord || afsdbrecord || aplrecord || arecord || certrecord || cn || cnamerecord || dhcidrecord || dlvrecord || dnamerecord || dnsclass || dnsttl || dsrecord || hinforecord || hiprecord || idnsallowdynupdate || idnsallowquery || idnsallowsyncptr || idnsallowtransfer || idnsforwarders || idnsforwardpolicy || idnsname || idnssecinlinesigning || idnssoaexpire || idnssoaminimum || idnssoamname || idnssoarefresh || idnssoaretry || idnssoarname || idnssoaserial || idnsupdatepolicy || idnszoneactive || ipseckeyrecord || keyrecord || kxrecord || locrecord || mdrecord || minforecord || mxrecord || naptrrecord || nsecrecord || nsec3paramrecord || nsrecord || nxtrecord || ptrrecord || rprecord || rrsigrecord || sigrecord || spfrecord || srvrecord || sshfprecord || tlsarecord || txtrecord || unknownrecord ")(target = "ldap:///idnsname=*,cn=dns,$SUFFIX";)(version 3.0;acl "Update DNS entries in a zone";allow (write) userattr = "parent[0,1].managedby#GROUPDN";)
-
-dn: cn=DNS Administrators,cn=privileges,cn=pbac,$SUFFIX
-changetype: add
-objectClass: top
-objectClass: groupofnames
-objectClass: nestedgroup
-cn: DNS Administrators
-description: DNS Administrators
-
-dn: cn=DNS Servers,cn=privileges,cn=pbac,$SUFFIX
-changetype: add
-objectClass: top
-objectClass: groupofnames
-objectClass: nestedgroup
-cn: DNS Servers
-description: DNS Servers
diff --git a/install/updates/37-locations.update b/install/updates/37-locations.update
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/install/updates/40-delegation.update b/install/updates/40-delegation.update
index f0431b92d707b17607fe873efbfe2fcccd3efce1..259cbdbdab9eef69e29dba117db36a9e3e0c5f66 100644
--- a/install/updates/40-delegation.update
+++ b/install/updates/40-delegation.update
@@ -274,3 +274,19 @@ default:objectClass: groupofnames
 default:objectClass: top
 default:cn: Vault Administrators
 default:description: Vault Administrators
+
+
+# Locations - always create DNS related privileges
+dn: cn=DNS Administrators,cn=privileges,cn=pbac,$SUFFIX
+default:objectClass: top
+default:objectClass: groupofnames
+default:objectClass: nestedgroup
+default:cn: DNS Administrators
+default:description: DNS Administrators
+
+dn: cn=DNS Servers,cn=privileges,cn=pbac,$SUFFIX
+default:objectClass: top
+default:objectClass: groupofnames
+default:objectClass: nestedgroup
+default:cn: DNS Servers
+default:description: DNS Servers
diff --git a/ipalib/plugins/location.py b/ipalib/plugins/location.py
new file mode 100644
index 0000000000000000000000000000000000000000..8090bb1637c4d826b9a746a82b98ece903e321cc
--- /dev/null
+++ b/ipalib/plugins/location.py
@@ -0,0 +1 @@
+__author__ = 'mbasti'
-- 
2.5.5

From a7766da5fd1a72884308d4206c9cde262f5c8d35 Mon Sep 17 00:00:00 2001
From: Martin Basti <mba...@redhat.com>
Date: Thu, 5 May 2016 11:12:00 +0200
Subject: [PATCH 2/4] DNS Locations: add new attributes and objectclasses

http://www.freeipa.org/page/V4/DNS_Location_Mechanism

https://fedorahosted.org/freeipa/ticket/2008
---
 install/share/60ipadns.ldif | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/install/share/60ipadns.ldif b/install/share/60ipadns.ldif
index e0ed0ab869cea0478d9640bb509c6267abed1a01..31c2f71f8566d04a05709f1359b20e6fa51921ce 100644
--- a/install/share/60ipadns.ldif
+++ b/install/share/60ipadns.ldif
@@ -70,9 +70,13 @@ attributeTypes: ( 2.16.840.1.113730.3.8.5.25 NAME 'idnsSecKeyRevoke' DESC 'DNSKE
 attributeTypes: ( 2.16.840.1.113730.3.8.5.26 NAME 'idnsSecKeySep' DESC 'DNSKEY SEP flag (equivalent to bit 15): RFC 4035' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'IPA v4.1' )
 attributeTypes: ( 2.16.840.1.113730.3.8.5.27 NAME 'idnsSecAlgorithm' DESC 'DNSKEY algorithm: string used as mnemonic' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE X-ORIGIN 'IPA v4.1' )
 attributeTypes: ( 2.16.840.1.113730.3.8.5.28 NAME 'idnsSecKeyRef' DESC 'PKCS#11 URI of the key' EQUALITY caseExactMatch SINGLE-VALUE SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.1' )
+attributeTypes: ( 2.16.840.1.113730.3.8.5.32 NAME 'ipaLocation' DESC 'Reference to IPA location' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'IPA v4.4' )
+attributeTypes: ( 2.16.840.1.113730.3.8.5.33 NAME 'ipaLocationWeight' DESC 'Weight for the server in IPA location' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA v4.4' )
 objectClasses: ( 2.16.840.1.113730.3.8.6.0 NAME 'idnsRecord' DESC 'dns Record, usually a host' SUP top STRUCTURAL MUST idnsName MAY ( cn $ idnsAllowDynUpdate $ dNSTTL $ dNSClass $ aRecord $ aAAARecord $ a6Record $ nSRecord $ cNAMERecord $ pTRRecord $ sRVRecord $ tXTRecord $ mXRecord $ mDRecord $ hInfoRecord $ mInfoRecord $ aFSDBRecord $ SigRecord $ KeyRecord $ LocRecord $ nXTRecord $ nAPTRRecord $ kXRecord $ certRecord $ dNameRecord $ dSRecord $ sSHFPRecord $ rRSIGRecord $ nSECRecord $ DLVRecord $ TLSARecord $ UnknownRecord $ RPRecord $ APLRecord $ IPSECKEYRecord $ DHCIDRecord $ HIPRecord $ SPFRecord ) )
 objectClasses: ( 2.16.840.1.113730.3.8.6.1 NAME 'idnsZone' DESC 'Zone class' SUP idnsRecord STRUCTURAL MUST ( idnsZoneActive $ idnsSOAmName $ idnsSOArName $ idnsSOAserial $ idnsSOArefresh $ idnsSOAretry $ idnsSOAexpire $ idnsSOAminimum ) MAY ( idnsUpdatePolicy $ idnsAllowQuery $ idnsAllowTransfer $ idnsAllowSyncPTR $ idnsForwardPolicy $ idnsForwarders $ idnsSecInlineSigning $ nSEC3PARAMRecord ) )
 objectClasses: ( 2.16.840.1.113730.3.8.6.2 NAME 'idnsConfigObject' DESC 'DNS global config options' STRUCTURAL MAY ( idnsForwardPolicy $ idnsForwarders $ idnsAllowSyncPTR $ idnsZoneRefresh $ idnsPersistentSearch ) )
 objectClasses: ( 2.16.840.1.113730.3.8.12.18 NAME 'ipaDNSZone' SUP top AUXILIARY MUST idnsName MAY managedBy X-ORIGIN 'IPA v3' )
 objectClasses: ( 2.16.840.1.113730.3.8.6.3 NAME 'idnsForwardZone' DESC 'Forward Zone class' SUP top STRUCTURAL MUST ( idnsName $ idnsZoneActive ) MAY ( idnsForwarders $ idnsForwardPolicy ) )
 objectClasses: ( 2.16.840.1.113730.3.8.6.4 NAME 'idnsSecKey' DESC 'DNSSEC key metadata' STRUCTURAL MUST ( idnsSecKeyRef $ idnsSecKeyCreated $ idnsSecAlgorithm ) MAY ( idnsSecKeyPublish $ idnsSecKeyActivate $ idnsSecKeyInactive $ idnsSecKeyDelete $ idnsSecKeyZone $ idnsSecKeyRevoke $ idnsSecKeySep $ cn ) X-ORIGIN 'IPA v4.1' )
+objectClasses: ( 2.16.840.1.113730.3.8.6.7 NAME 'ipaLocationObject' DESC 'Object for storing IPA server location' AUXILIARY MUST ( idnsName ) MAY ( description ) X-ORIGIN 'IPA v4.4' )
+objectClasses: ( 2.16.840.1.113730.3.8.6.8 NAME 'ipaLocationMember' DESC 'Member object of IPA location' AUXILIARY MAY ( ipaLocation $ ipaLocationWeight ) X-ORIGIN 'IPA v4.4' )
-- 
2.5.5

From 407b935ecd6df0ed98c6df6d45a575229ef3cd09 Mon Sep 17 00:00:00 2001
From: Martin Basti <mba...@redhat.com>
Date: Thu, 5 May 2016 11:13:07 +0200
Subject: [PATCH 3/4] DNS Locations: Add location-* commands

Added location-{add,mod,del,find,show} commands. Command are just
prototypes and does not provide any information about server (will be
done later)

http://www.freeipa.org/page/V4/DNS_Location_Mechanism

https://fedorahosted.org/freeipa/ticket/2008
---
 ACI.txt                               |   8 ++
 API.txt                               |  59 ++++++++++++++
 VERSION                               |   4 +-
 install/share/bootstrap-template.ldif |   6 ++
 install/updates/37-locations.update   |   4 +
 install/updates/Makefile.am           |   1 +
 ipalib/constants.py                   |   1 +
 ipalib/plugins/location.py            | 142 +++++++++++++++++++++++++++++++++-
 8 files changed, 222 insertions(+), 3 deletions(-)

diff --git a/ACI.txt b/ACI.txt
index 24cb332ce6e10c82a5bfab76d084fb6c0277800d..8f75ec4985ae93b9b0dd06217fc87696043b3587 100644
--- a/ACI.txt
+++ b/ACI.txt
@@ -158,6 +158,14 @@ dn: cn=IPA.EXAMPLE,cn=kerberos,dc=ipa,dc=example
 aci: (targetattr = "createtimestamp || entryusn || krbdefaultencsalttypes || krbmaxrenewableage || krbmaxticketlife || krbsupportedencsalttypes || modifytimestamp || objectclass")(targetfilter = "(objectclass=krbticketpolicyaux)")(version 3.0;acl "permission:System: Read Default Kerberos Ticket Policy";allow (compare,read,search) groupdn = "ldap:///cn=System: Read Default Kerberos Ticket Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=users,cn=accounts,dc=ipa,dc=example
 aci: (targetattr = "krbmaxrenewableage || krbmaxticketlife")(targetfilter = "(objectclass=krbticketpolicyaux)")(version 3.0;acl "permission:System: Read User Kerberos Ticket Policy";allow (compare,read,search) groupdn = "ldap:///cn=System: Read User Kerberos Ticket Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=locations,cn=etc,dc=ipa,dc=example
+aci: (targetfilter = "(objectclass=ipaLocationObject)")(version 3.0;acl "permission:System: Add IPA Locations";allow (add) groupdn = "ldap:///cn=System: Add IPA Locations,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=locations,cn=etc,dc=ipa,dc=example
+aci: (targetattr = "description")(targetfilter = "(objectclass=ipaLocationObject)")(version 3.0;acl "permission:System: Modify IPA Locations";allow (write) groupdn = "ldap:///cn=System: Modify IPA Locations,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=locations,cn=etc,dc=ipa,dc=example
+aci: (targetattr = "createtimestamp || description || entryusn || idnsname || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipaLocationObject)")(version 3.0;acl "permission:System: Read IPA Locations";allow (compare,read,search) groupdn = "ldap:///cn=System: Read IPA Locations,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=locations,cn=etc,dc=ipa,dc=example
+aci: (targetfilter = "(objectclass=ipaLocationObject)")(version 3.0;acl "permission:System: Remove IPA Locations";allow (delete) groupdn = "ldap:///cn=System: Remove IPA Locations,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=ng,cn=alt,dc=ipa,dc=example
 aci: (targetfilter = "(objectclass=ipanisnetgroup)")(version 3.0;acl "permission:System: Add Netgroups";allow (add) groupdn = "ldap:///cn=System: Add Netgroups,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=ng,cn=alt,dc=ipa,dc=example
diff --git a/API.txt b/API.txt
index 3598b08198cae536754259f7463669052efa3f86..5d187c4cc8fd27c384f9254f5db5ac54ea5e23bf 100644
--- a/API.txt
+++ b/API.txt
@@ -2727,6 +2727,65 @@ option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
+command: location_add
+args: 1,6,3
+arg: DNSNameParam('idnsname', attribute=True, cli_name='name', multivalue=False, only_relative=True, primary_key=True, required=True)
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('description', attribute=True, cli_name='description', multivalue=False, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
+command: location_del
+args: 1,2,3
+arg: DNSNameParam('idnsname', attribute=True, cli_name='name', multivalue=True, only_relative=True, primary_key=True, query=True, required=True)
+option: Flag('continue', autofill=True, cli_name='continue', default=False)
+option: Str('version?', exclude='webui')
+output: Output('result', <type 'dict'>, None)
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: ListOfPrimaryKeys('value', None, None)
+command: location_find
+args: 1,8,4
+arg: Str('criteria?', noextrawhitespace=False)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('description', attribute=True, autofill=False, cli_name='description', multivalue=False, query=True, required=False)
+option: DNSNameParam('idnsname', attribute=True, autofill=False, cli_name='name', multivalue=False, only_relative=True, primary_key=True, query=True, required=False)
+option: Flag('pkey_only?', autofill=True, default=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Int('sizelimit?', autofill=False, minvalue=0)
+option: Int('timelimit?', autofill=False, minvalue=0)
+option: Str('version?', exclude='webui')
+output: Output('count', <type 'int'>, None)
+output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list of LDAP entries', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('truncated', <type 'bool'>, None)
+command: location_mod
+args: 1,8,3
+arg: DNSNameParam('idnsname', attribute=True, cli_name='name', multivalue=False, only_relative=True, primary_key=True, query=True, required=True)
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('delattr*', cli_name='delattr', exclude='webui')
+option: Str('description', attribute=True, autofill=False, cli_name='description', multivalue=False, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
+command: location_show
+args: 1,4,3
+arg: DNSNameParam('idnsname', attribute=True, cli_name='name', multivalue=False, only_relative=True, primary_key=True, query=True, required=True)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
 command: migrate_ds
 args: 2,20,4
 arg: Str('ldapuri', cli_name='ldap_uri')
diff --git a/VERSION b/VERSION
index aedebd185821d42fa48608f4c5fdf9ff510ace3f..7e3def151e9986454509a580515b9d34dc220a60 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=165
-# Last change: mbasti - limit ipamaxusernamelength value to 255
+IPA_API_VERSION_MINOR=166
+# Last change: mbasti - location-* commands
diff --git a/install/share/bootstrap-template.ldif b/install/share/bootstrap-template.ldif
index 628a8e2e0f5483b9f6f565b0c7d11eb000a5912d..83be4399508a905f8eae7e2f59140a6b4051b661 100644
--- a/install/share/bootstrap-template.ldif
+++ b/install/share/bootstrap-template.ldif
@@ -119,6 +119,12 @@ objectClass: nsContainer
 objectClass: top
 cn: etc
 
+dn: cn=locations,cn=etc,$SUFFIX
+changetype: add
+objectClass: nsContainer
+objectClass: top
+cn: locations
+
 dn: cn=sysaccounts,cn=etc,$SUFFIX
 changetype: add
 objectClass: nsContainer
diff --git a/install/updates/37-locations.update b/install/updates/37-locations.update
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..cf47e6d6296af830a76aad2c9b9f5a6ea5d9f3a1 100644
--- a/install/updates/37-locations.update
+++ b/install/updates/37-locations.update
@@ -0,0 +1,4 @@
+dn: cn=locations,cn=etc,$SUFFIX
+default: objectClass: nsContainer
+default: objectClass: top
+default: cn: locations
diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am
index 3edc21473d676bd282e9ea2b88769c097fb8a63a..737a8bbbd1a4915a6aefec2d273b90bb3ca31710 100644
--- a/install/updates/Makefile.am
+++ b/install/updates/Makefile.am
@@ -28,6 +28,7 @@ app_DATA =				\
 	25-referint.update		\
 	30-provisioning.update		\
 	30-s4u2proxy.update		\
+	37-locations.update		\
 	40-delegation.update		\
 	40-realm_domains.update		\
 	40-replication.update		\
diff --git a/ipalib/constants.py b/ipalib/constants.py
index 021f18cd366b821427bdbfcc5e354d2047ef39b1..d1c9ccf68d01ef1dc032559ca8a353eede7a0e09 100644
--- a/ipalib/constants.py
+++ b/ipalib/constants.py
@@ -121,6 +121,7 @@ DEFAULT_CONFIG = (
     ('container_certprofile', DN(('cn', 'certprofiles'), ('cn', 'ca'))),
     ('container_topology', DN(('cn', 'topology'), ('cn', 'ipa'), ('cn', 'etc'))),
     ('container_caacl', DN(('cn', 'caacls'), ('cn', 'ca'))),
+    ('container_locations', DN(('cn', 'locations'), ('cn', 'etc'))),
 
     # Ports, hosts, and URIs:
     ('xmlrpc_uri', 'http://localhost:8888/ipa/xml'),
diff --git a/ipalib/plugins/location.py b/ipalib/plugins/location.py
index 8090bb1637c4d826b9a746a82b98ece903e321cc..d52d2baeb8bfb2fddeac40b281268622d47c6aeb 100644
--- a/ipalib/plugins/location.py
+++ b/ipalib/plugins/location.py
@@ -1 +1,141 @@
-__author__ = 'mbasti'
+#
+# Copyright (C) 2016  FreeIPA Contributors see COPYING for license
+#
+
+from __future__ import absolute_import
+
+from ipalib import (
+    _,
+    ngettext,
+    api,
+    Str,
+    DNSNameParam
+)
+from ipalib.plugable import Registry
+from ipalib.plugins.baseldap import (
+    LDAPCreate,
+    LDAPSearch,
+    LDAPRetrieve,
+    LDAPDelete,
+    LDAPObject,
+    LDAPUpdate,
+)
+
+__doc__ = _("""
+IPA locations
+""") + _("""
+Manipulate with DNS locations
+""") + _("""
+EXAMPLES:
+""") + _("""
+  Find all locations:
+    ipa location-find
+""") + _("""
+  Show specific location:
+    ipa location-show location
+""") + _("""
+  Add location:
+    ipa location-add location --description 'My location'
+""") + _("""
+  Delete location:
+    ipa location-del location
+""")
+
+register = Registry()
+
+
+@register()
+class location(LDAPObject):
+    """
+    IPA locations
+    """
+    container_dn = api.env.container_locations
+    object_name = _('location')
+    object_name_plural = _('locations')
+    object_class = ['ipaLocationObject']
+    search_attributes = ['idnsName']
+    default_attributes = [
+        'idnsname', 'description'
+    ]
+    label = _('IPA Locations')
+    label_singular = _('IPA Location')
+
+    permission_filter_objectclasses = ['ipaLocationObject']
+    managed_permissions = {
+        'System: Read IPA Locations': {
+            'ipapermright': {'read', 'search', 'compare'},
+            'ipapermdefaultattr': {
+                'objectclass', 'idnsname', 'description',
+            },
+            'default_privileges': {'DNS Administrators'},
+        },
+        'System: Add IPA Locations': {
+            'ipapermright': {'add'},
+            'default_privileges': {'DNS Administrators'},
+        },
+        'System: Remove IPA Locations': {
+            'ipapermright': {'delete'},
+            'default_privileges': {'DNS Administrators'},
+        },
+        'System: Modify IPA Locations': {
+            'ipapermright': {'write'},
+            'ipapermdefaultattr': {
+                'description',
+            },
+            'default_privileges': {'DNS Administrators'},
+        },
+    }
+
+    takes_params = (
+        DNSNameParam(
+            'idnsname',
+            cli_name='name',
+            primary_key=True,
+            label=_('Location name'),
+            doc=_('IPA location name'),
+            # dns name must be relative, we will put it into middle of
+            # location domain name for location records
+            only_relative=True,
+        ),
+        Str(
+            'description?',
+            label=_('Description'),
+            doc=_('IPA Location description'),
+        ),
+    )
+
+
+@register()
+class location_add(LDAPCreate):
+    __doc__ = _('Add a new IPA location.')
+
+    msg_summary = _('Added IPA location "%(value)s"')
+
+
+@register()
+class location_del(LDAPDelete):
+    __doc__ = _('Delete an IPA location.')
+
+    msg_summary = _('Deleted IPA location "%(value)s"')
+
+
+@register()
+class location_mod(LDAPUpdate):
+    __doc__ = _('Modify information about an IPA location .')
+
+    msg_summary = _('Modified IPA location "%(value)s"')
+
+
+@register()
+class location_find(LDAPSearch):
+    __doc__ = _('Search for IPA locations.')
+
+    msg_summary = ngettext(
+        '%(count)d IPA location matched',
+        '%(count)d IPA locations matched', 0
+    )
+
+
+@register()
+class location_show(LDAPRetrieve):
+    __doc__ = _('Display information about an IPA location.')
-- 
2.5.5

From 323d19dc4d7a9dcab6b2bff7c2433c3271fe4c86 Mon Sep 17 00:00:00 2001
From: Martin Basti <mba...@redhat.com>
Date: Thu, 5 May 2016 16:07:20 +0200
Subject: [PATCH 4/4] DNS Locations: API tests

Tests for location-* commands

https://fedorahosted.org/freeipa/ticket/2008
---
 ipatests/test_xmlrpc/test_location_plugin.py    | 113 +++++++++++++++++++++++
 ipatests/test_xmlrpc/tracker/location_plugin.py | 116 ++++++++++++++++++++++++
 2 files changed, 229 insertions(+)
 create mode 100644 ipatests/test_xmlrpc/test_location_plugin.py
 create mode 100644 ipatests/test_xmlrpc/tracker/location_plugin.py

diff --git a/ipatests/test_xmlrpc/test_location_plugin.py b/ipatests/test_xmlrpc/test_location_plugin.py
new file mode 100644
index 0000000000000000000000000000000000000000..45755edfbbc19fcf53f76cd1cec7da7a25776bc0
--- /dev/null
+++ b/ipatests/test_xmlrpc/test_location_plugin.py
@@ -0,0 +1,113 @@
+#
+# Copyright (C) 2016  FreeIPA Contributors see COPYING for license
+#
+from __future__ import absolute_import
+
+import pytest
+
+from ipalib import errors
+from ipatests.test_xmlrpc.tracker.location_plugin import LocationTracker
+from ipatests.test_xmlrpc.xmlrpc_test import (
+    XMLRPC_test,
+    raises_exact,
+)
+
+
+@pytest.fixture(scope='class')
+def location1(request):
+    tracker = LocationTracker(u'location1')
+    return tracker.make_fixture(request)
+
+
+@pytest.fixture(scope='class')
+def location_invalid(request):
+    tracker = LocationTracker(u'invalid..location')
+    return tracker
+
+
+@pytest.fixture(scope='class')
+def location_absolute(request):
+    tracker = LocationTracker(u'invalid.absolute.')
+    return tracker.make_fixture(request)
+
+
+@pytest.mark.tier1
+class TestNonexistentIPALocation(XMLRPC_test):
+    def test_retrieve_nonexistent(self, location1):
+        location1.ensure_missing()
+        command = location1.make_retrieve_command()
+        with raises_exact(errors.NotFound(
+                reason=u'%s: location not found' % location1.idnsname)):
+            command()
+
+    def test_update_nonexistent(self, location1):
+        location1.ensure_missing()
+        command = location1.make_update_command(updates=dict(
+            description=u'Nope'))
+        with raises_exact(errors.NotFound(
+                reason=u'%s: location not found' % location1.idnsname)):
+            command()
+
+    def test_delete_nonexistent(self, location1):
+        location1.ensure_missing()
+        command = location1.make_delete_command()
+        with raises_exact(errors.NotFound(
+                reason=u'%s: location not found' % location1.idnsname)):
+            command()
+
+@pytest.mark.tier1
+class TestInvalidIPALocations(XMLRPC_test):
+    def test_invalid_name(self, location_invalid):
+        command = location_invalid.make_create_command()
+        with raises_exact(errors.ConversionError(
+                name=u'name',
+                error=u"empty DNS label")):
+            command()
+
+    def test_invalid_absolute(self, location_absolute):
+        command = location_absolute.make_create_command()
+        with raises_exact(errors.ValidationError(
+                name=u'name', error=u'must be relative')):
+            command()
+
+
+@pytest.mark.tier1
+class TestCRUD(XMLRPC_test):
+    def test_create_duplicate(self, location1):
+        location1.ensure_exists()
+        command = location1.make_create_command(force=True)
+        with raises_exact(errors.DuplicateEntry(
+                message=u'location with name "%s" already exists' %
+                        location1.idnsname)):
+            command()
+
+    def test_retrieve_simple(self, location1):
+        location1.retrieve()
+
+    def test_retrieve_all(self, location1):
+        location1.retrieve(all=True)
+
+    def test_search_simple(self, location1):
+        location1.find()
+
+    def test_search_all(self, location1):
+        location1.find(all=True)
+
+    def test_update_simple(self, location1):
+        location1.update(dict(
+                description=u'Updated description',
+            ),
+            expected_updates=dict(
+                description=[u'Updated description'],
+            ))
+        location1.retrieve()
+
+    def test_try_rename(self, location1):
+        location1.ensure_exists()
+        command = location1.make_update_command(
+            updates=dict(setattr=u'idnsname=changed'))
+        with raises_exact(errors.NotAllowedOnRDN()):
+            command()
+
+    def test_delete_location(self, location1):
+        location1.delete()
diff --git a/ipatests/test_xmlrpc/tracker/location_plugin.py b/ipatests/test_xmlrpc/tracker/location_plugin.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c2959cc258ea31bd331cf0a18dd0c3765ea493e
--- /dev/null
+++ b/ipatests/test_xmlrpc/tracker/location_plugin.py
@@ -0,0 +1,116 @@
+#
+# Copyright (C) 2016  FreeIPA Contributors see COPYING for license
+#
+from __future__ import absolute_import
+
+from ipapython.dn import DN
+from ipapython.dnsutil import DNSName
+from ipatests.util import assert_deepequal
+from ipatests.test_xmlrpc.tracker.base import Tracker
+
+
+class LocationTracker(Tracker):
+    """Tracker for IPA Location tests"""
+    retrieve_keys = {'idnsname', 'description', 'dn'}
+    retrieve_all_keys = retrieve_keys | {'objectclass'}
+    create_keys = retrieve_keys | {'objectclass'}
+    find_keys = retrieve_keys
+    find_all_keys = retrieve_all_keys
+    update_keys = {'idnsname', 'description'}
+
+    def __init__(self, name, description=u"Location description"):
+        super(LocationTracker, self).__init__(default_version=None)
+        # ugly hack to allow testing invalid inputs
+        try:
+            self.idnsname_obj = DNSName(name)
+        except Exception:
+            self.idnsname_obj = u"this should never happen, you found a bug"
+
+        self.idnsname = name
+        self.description = description
+        self.dn = DN(('idnsname', name), 'cn=locations', 'cn=etc',
+                     self.api.env.basedn)
+
+    def make_create_command(self, force=None):
+        """Make function that creates this location using location-add"""
+        return self.make_command(
+            'location_add', self.idnsname, description=self.description,
+        )
+
+    def make_delete_command(self):
+        """Make function that removes this location using location-del"""
+        return self.make_command('location_del', self.idnsname)
+
+    def make_retrieve_command(self, all=False, raw=False):
+        """Make function that retrieves this location using location-show"""
+        return self.make_command(
+            'location_show', self.idnsname, all=all, raw=raw
+        )
+
+    def make_find_command(self, *args, **kwargs):
+        """Make function that finds locations using location-find"""
+        return self.make_command('location_find', *args, **kwargs)
+
+    def make_update_command(self, updates):
+        """Make function that modifies the location using location-mod"""
+        return self.make_command('location_mod', self.idnsname, **updates)
+
+    def track_create(self):
+        """Update expected state for location creation"""
+
+        self.attrs = dict(
+            dn=self.dn,
+            idnsname=[self.idnsname_obj],
+            description=[self.description],
+            objectclass=[u'ipaLocationObject'],
+        )
+        self.exists = True
+
+    def check_create(self, result):
+        """Check `location-add` command result"""
+        assert_deepequal(dict(
+            value=self.idnsname_obj,
+            summary=u'Added IPA location "{loc}"'.format(loc=self.idnsname),
+            result=self.filter_attrs(self.create_keys)
+        ), result)
+
+    def check_delete(self, result):
+        """Check `location-del` command result"""
+        assert_deepequal(dict(
+            value=[self.idnsname_obj],
+            summary=u'Deleted IPA location "{loc}"'.format(loc=self.idnsname),
+            result=dict(failed=[]),
+        ), result)
+
+    def check_retrieve(self, result, all=False, raw=False):
+        """Check `location-show` command result"""
+        if all:
+            expected = self.filter_attrs(self.retrieve_all_keys)
+        else:
+            expected = self.filter_attrs(self.retrieve_keys)
+        assert_deepequal(dict(
+            value=self.idnsname_obj,
+            summary=None,
+            result=expected,
+        ), result)
+
+    def check_find(self, result, all=False, raw=False):
+        """Check `location-find` command result"""
+        if all:
+            expected = self.filter_attrs(self.find_all_keys)
+        else:
+            expected = self.filter_attrs(self.find_keys)
+        assert_deepequal(dict(
+            count=1,
+            truncated=False,
+            summary=u'1 IPA location matched',
+            result=[expected],
+        ), result)
+
+    def check_update(self, result, extra_keys=()):
+        """Check `location-update` command result"""
+        assert_deepequal(dict(
+            value=self.idnsname_obj,
+            summary=u'Modified IPA location "{loc}"'.format(loc=self.idnsname),
+            result=self.filter_attrs(self.update_keys | set(extra_keys))
+        ), result)
-- 
2.5.5

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to