Hi,

the following set of patches implements the ID view creation and
management of views and ID overrides in IPA.

Pending questions:
1.) The patch 253 implements basic managed permissions for ID views and
ID overrides. Do we want to have a separate permission for assigning ID
views?
2.) Performance: idview-apply command takes ~100 seconds for hostgroups
with 1000 member hosts. I am able to lower that by 20-30% using raw ldap
calls, is bypassing the framework worth the performance gain? We'll lose
the possiblity to hook on exception callbacks.

The commands also need more helpful documentation, I am working on that.

-- 
Tomas Babej
Associate Software Engineer | Red Hat | Identity Management
RHCE | Brno Site | IRC: tbabej | freeipa.org 

>From 00289142b3eff9474cbb6672c17abad1c6b63005 Mon Sep 17 00:00:00 2001
From: Tomas Babej <tba...@redhat.com>
Date: Thu, 31 Jul 2014 11:57:53 +0200
Subject: [PATCH] idviews: Add necessary schema for the ID views

---
 install/share/60basev2.ldif        | 4 +++-
 install/share/71idviews.ldif       | 5 +++++
 install/share/Makefile.am          | 1 +
 install/share/copy-schema-to-ca.py | 1 +
 ipaserver/install/dsinstance.py    | 1 +
 5 files changed, 11 insertions(+), 1 deletion(-)
 create mode 100644 install/share/71idviews.ldif

diff --git a/install/share/60basev2.ldif b/install/share/60basev2.ldif
index 044acc413984b78ef71da8f24f86ffca611d5d43..60174caf19b6d463729b66cabf0c64b9b798955d 100644
--- a/install/share/60basev2.ldif
+++ b/install/share/60basev2.ldif
@@ -12,8 +12,10 @@ attributeTypes: (2.16.840.1.113730.3.8.3.18 NAME 'managedBy' DESC 'DNs of entrie
 attributeTypes: (2.16.840.1.113730.3.8.3.24 NAME 'ipaEntitlementId' DESC 'Entitlement Unique identifier' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
 # ipaKrbAuthzData added here. Even though it is a v3 attribute it is updating
 # a v2 objectClass so needs to be here.
+# Same for the ipaAssignedIDView.
 attributeTypes: (2.16.840.1.113730.3.8.11.37 NAME 'ipaKrbAuthzData' DESC 'type of PAC preferred by a service' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v3' )
-objectClasses: (2.16.840.1.113730.3.8.4.1 NAME 'ipaHost' AUXILIARY MUST ( fqdn ) MAY ( userPassword $ ipaClientVersion $ enrolledBy $ memberOf $ userClass ) X-ORIGIN 'IPA v2' )
+attributeTypes: (2.16.840.1.113730.3.8.12.32 NAME 'ipaAssignedIDView' DESC 'DN of view assigned to this particular host' SUP distinguishedName EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'IPA v4' )
+objectClasses: (2.16.840.1.113730.3.8.4.1 NAME 'ipaHost' AUXILIARY MUST ( fqdn ) MAY ( userPassword $ ipaClientVersion $ enrolledBy $ memberOf $ userClass $ ipaAssignedIDView ) X-ORIGIN 'IPA v2' )
 objectClasses: (2.16.840.1.113730.3.8.4.12 NAME 'ipaObject' DESC 'IPA objectclass' AUXILIARY MUST ( ipaUniqueID ) X-ORIGIN 'IPA v2' )
 objectClasses: (2.16.840.1.113730.3.8.4.14 NAME 'ipaEntitlement' DESC 'IPA Entitlement object' AUXILIARY MUST ( ipaEntitlementId ) MAY ( userPKCS12 $ userCertificate ) X-ORIGIN 'IPA v2' )
 objectClasses: (2.16.840.1.113730.3.8.4.15 NAME 'ipaPermission' DESC 'IPA Permission objectclass' AUXILIARY MAY ( ipaPermissionType ) X-ORIGIN 'IPA v2' )
diff --git a/install/share/71idviews.ldif b/install/share/71idviews.ldif
new file mode 100644
index 0000000000000000000000000000000000000000..bb9dec551aa1952000ea17eea2da767ab9d0621d
--- /dev/null
+++ b/install/share/71idviews.ldif
@@ -0,0 +1,5 @@
+dn: cn=schema
+attributeTypes: (2.16.840.1.113730.3.8.11.62 NAME 'ipaAnchorUUID' DESC 'Unique Anchor Identifier' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA v4')
+objectClasses: (2.16.840.1.113730.3.8.12.29 NAME 'ipaOverrideAnchor' SUP top STRUCTURAL MUST ( cn $ description $ ipaAnchorUUID ) X-ORIGIN 'IPA v4' )
+objectClasses: (2.16.840.1.113730.3.8.12.30 NAME 'ipaUserOverride' DESC 'Override for User Attributes' SUP ipaOverrideAnchor STRUCTURAL MAY ( uid $ uidNumber $ gidNumber $ homeDirectory $ loginShell $ gecos ) X-ORIGIN 'IPA v4' )
+objectClasses: (2.16.840.1.113730.3.8.12.31 NAME 'ipaGroupOverride' DESC 'Override for Group Attributes' SUP ipaOverrideAnchor STRUCTURAL MAY ( gidNumber ) X-ORIGIN 'IPA v4' )
diff --git a/install/share/Makefile.am b/install/share/Makefile.am
index 7d5b67a78020e8445277162ec585fd908be17de4..e72623ad7ada2763ef44d44fa9ab436f21e97eb5 100644
--- a/install/share/Makefile.am
+++ b/install/share/Makefile.am
@@ -19,6 +19,7 @@ app_DATA =				\
 	65ipacertstore.ldif		\
 	65ipasudo.ldif			\
 	70ipaotp.ldif			\
+	71idviews.ldif			\
 	anonymous-vlv.ldif		\
 	bootstrap-template.ldif		\
 	caJarSigningCert.cfg.template	\
diff --git a/install/share/copy-schema-to-ca.py b/install/share/copy-schema-to-ca.py
index fc53fe4cb52486cc618bec77aabe8283ad5eadbc..c795bf6d47b13da95dfb1c518bb3b45c0c173521 100755
--- a/install/share/copy-schema-to-ca.py
+++ b/install/share/copy-schema-to-ca.py
@@ -34,6 +34,7 @@ SCHEMA_FILENAMES = (
     "65ipacertstore.ldif",
     "65ipasudo.ldif",
     "70ipaotp.ldif",
+    "71idviews.ldif",
     "05rfc2247.ldif",
 )
 
diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
index 242e04d99f7096ad34c9e9d7ac32a03a4add90c6..c2480eed891bdd6bdba042858ef2f0aa2914b5e7 100644
--- a/ipaserver/install/dsinstance.py
+++ b/ipaserver/install/dsinstance.py
@@ -61,6 +61,7 @@ IPA_SCHEMA_FILES = ("60kerberos.ldif",
                     "65ipacertstore.ldif",
                     "65ipasudo.ldif",
                     "70ipaotp.ldif",
+                    "71idviews.ldif",
                     "15rfc2307bis.ldif",
                     "15rfc4876.ldif")
 
-- 
1.9.3

>From 2a66a82994de99d1a247f5701a3d5836682664b9 Mon Sep 17 00:00:00 2001
From: Tomas Babej <tba...@redhat.com>
Date: Thu, 31 Jul 2014 11:52:04 +0200
Subject: [PATCH] idviews: Create container for ID views under cn=accounts

Part of: https://fedorahosted.org/freeipa/ticket/3979
---
 install/updates/71-idviews.update | 4 ++++
 install/updates/Makefile.am       | 1 +
 ipalib/constants.py               | 1 +
 3 files changed, 6 insertions(+)
 create mode 100644 install/updates/71-idviews.update

diff --git a/install/updates/71-idviews.update b/install/updates/71-idviews.update
new file mode 100644
index 0000000000000000000000000000000000000000..6ec928c32db638b0ad7908ccd7837e632a7247a8
--- /dev/null
+++ b/install/updates/71-idviews.update
@@ -0,0 +1,4 @@
+dn: cn=views,cn=accounts,$SUFFIX
+default: objectClass: top
+default: objectClass: nsContainer
+default: cn: views
diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am
index f26eaeee0d02ec05202fa159525ba8adcdeb3928..c35094f3f95e78152e5f0e55f481eb289d763147 100644
--- a/install/updates/Makefile.am
+++ b/install/updates/Makefile.am
@@ -41,6 +41,7 @@ app_DATA =				\
 	60-trusts.update		\
 	61-trusts-s4u2proxy.update	\
 	62-ranges.update		\
+	71-idviews.update		\
 	$(NULL)
 
 EXTRA_DIST =				\
diff --git a/ipalib/constants.py b/ipalib/constants.py
index 8ae545526f3533253791ae629db469a002ea9ef0..8fb49748a16831655d17d98238f190ac0e2cb1b5 100644
--- a/ipalib/constants.py
+++ b/ipalib/constants.py
@@ -113,6 +113,7 @@ DEFAULT_CONFIG = (
     ('container_realm_domains', DN(('cn', 'Realm Domains'), ('cn', 'ipa'), ('cn', 'etc'))),
     ('container_otp', DN(('cn', 'otp'))),
     ('container_radiusproxy', DN(('cn', 'radiusproxy'))),
+    ('container_views', DN(('cn', 'views'), ('cn', 'accounts'))),
 
     # Ports, hosts, and URIs:
     ('xmlrpc_uri', 'http://localhost:8888/ipa/xml'),
-- 
1.9.3

>From 5d43d349f076cb9c4d2e40365a5d6a429a2c96ad Mon Sep 17 00:00:00 2001
From: Tomas Babej <tba...@redhat.com>
Date: Thu, 31 Jul 2014 12:08:05 +0200
Subject: [PATCH] idviews: Add ipaAssignedIDVIew reference to the host object

Part of: https://fedorahosted.org/freeipa/ticket/3979
---
 ACI.txt                | 4 ++--
 API.txt                | 9 ++++++---
 ipalib/plugins/host.py | 9 ++++++---
 3 files changed, 14 insertions(+), 8 deletions(-)

diff --git a/ACI.txt b/ACI.txt
index 7d158936318a4c81a91673d3bf769be788887547..99cd0d73b0120a4a2620ec686e1a46b55dc71cb3 100644
--- a/ACI.txt
+++ b/ACI.txt
@@ -95,11 +95,11 @@ aci: (targetattr = "krblastpwdchange || krbprincipalkey")(targetfilter = "(objec
 dn: cn=computers,cn=accounts,dc=ipa,dc=example
 aci: (targetattr = "ipasshpubkey")(targetfilter = "(objectclass=ipahost)")(version 3.0;acl "permission:System: Manage Host SSH Public Keys";allow (write) groupdn = "ldap:///cn=System: Manage Host SSH Public Keys,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=computers,cn=accounts,dc=ipa,dc=example
-aci: (targetattr = "description || l || macaddress || nshardwareplatform || nshostlocation || nsosversion || userclass")(targetfilter = "(objectclass=ipahost)")(version 3.0;acl "permission:System: Modify Hosts";allow (write) groupdn = "ldap:///cn=System: Modify Hosts,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+aci: (targetattr = "description || ipaassignedidview || 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: cn=computers,cn=accounts,dc=ipa,dc=example
 aci: (targetattr = "memberof")(targetfilter = "(objectclass=ipahost)")(version 3.0;acl "permission:System: Read Host Membership";allow (compare,read,search) userdn = "ldap:///all";;)
 dn: cn=computers,cn=accounts,dc=ipa,dc=example
-aci: (targetattr = "cn || description || enrolledby || fqdn || ipaclientversion || ipakrbauthzdata || ipasshpubkey || ipauniqueid || krbcanonicalname || krblastpwdchange || krbpasswordexpiration || krbprincipalaliases || krbprincipalexpiration || krbprincipalname || l || macaddress || managedby || nshardwareplatform || nshostlocation || nsosversion || objectclass || serverhostname || usercertificate || userclass")(targetfilter = "(objectclass=ipahost)")(version 3.0;acl "permission:System: Read Hosts";allow (compare,read,search) userdn = "ldap:///all";;)
+aci: (targetattr = "cn || description || enrolledby || fqdn || ipaassignedidview || ipaclientversion || ipakrbauthzdata || ipasshpubkey || ipauniqueid || krbcanonicalname || krblastpwdchange || krbpasswordexpiration || krbprincipalaliases || krbprincipalexpiration || krbprincipalname || l || macaddress || managedby || nshardwareplatform || nshostlocation || nsosversion || objectclass || serverhostname || usercertificate || userclass")(targetfilter = "(objectclass=ipahost)")(version 3.0;acl "permission:System: Read Hosts";allow (compare,read,search) userdn = "ldap:///all";;)
 dn: cn=computers,cn=accounts,dc=ipa,dc=example
 aci: (targetfilter = "(objectclass=ipahost)")(version 3.0;acl "permission:System: Remove Hosts";allow (delete) groupdn = "ldap:///cn=System: Remove Hosts,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=hostgroups,cn=accounts,dc=ipa,dc=example
diff --git a/API.txt b/API.txt
index d731881eea39fb0ed2210d1a6b04739f6cd7f29b..7670aa75b4063217e98889de233c7e10d7ba5e67 100644
--- a/API.txt
+++ b/API.txt
@@ -1780,13 +1780,14 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Output('value', <type 'bool'>, None)
 output: Output('warning', (<type 'list'>, <type 'tuple'>, <type 'NoneType'>), None)
 command: host_add
-args: 1,22,3
+args: 1,23,3
 arg: Str('fqdn', attribute=True, cli_name='hostname', multivalue=False, 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='desc', multivalue=False, required=False)
 option: Flag('force', autofill=True, default=False)
 option: Str('ip_address?')
+option: DNParam('ipaassignedidview', attribute=True, cli_name='ipaassignedidview', multivalue=False, required=False)
 option: Bool('ipakrbokasdelegate', attribute=False, cli_name='ok_as_delegate', multivalue=False, required=False)
 option: Bool('ipakrbrequirespreauth', attribute=False, cli_name='requires_pre_auth', multivalue=False, required=False)
 option: Str('ipasshpubkey', attribute=True, cli_name='sshpubkey', csv=True, multivalue=True, required=False)
@@ -1834,7 +1835,7 @@ output: Output('result', <type 'bool'>, None)
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: host_find
-args: 1,33,4
+args: 1,34,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='desc', multivalue=False, query=True, required=False)
@@ -1845,6 +1846,7 @@ option: Str('in_hostgroup*', cli_name='in_hostgroups', csv=True)
 option: Str('in_netgroup*', cli_name='in_netgroups', csv=True)
 option: Str('in_role*', cli_name='in_roles', csv=True)
 option: Str('in_sudorule*', cli_name='in_sudorules', csv=True)
+option: DNParam('ipaassignedidview', attribute=True, autofill=False, cli_name='ipaassignedidview', multivalue=False, query=True, required=False)
 option: Str('l', attribute=True, autofill=False, cli_name='locality', multivalue=False, query=True, required=False)
 option: Str('macaddress', attribute=True, autofill=False, cli_name='macaddress', csv=True, multivalue=True, pattern='^([a-fA-F0-9]{2}[:|\\-]?){5}[a-fA-F0-9]{2}$', query=True, required=False)
 option: Str('man_by_host*', cli_name='man_by_hosts', csv=True)
@@ -1874,12 +1876,13 @@ output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Output('truncated', <type 'bool'>, None)
 command: host_mod
-args: 1,23,3
+args: 1,24,3
 arg: Str('fqdn', attribute=True, cli_name='hostname', multivalue=False, 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='desc', multivalue=False, required=False)
+option: DNParam('ipaassignedidview', attribute=True, autofill=False, cli_name='ipaassignedidview', multivalue=False, required=False)
 option: Bool('ipakrbokasdelegate', attribute=False, autofill=False, cli_name='ok_as_delegate', multivalue=False, required=False)
 option: Bool('ipakrbrequirespreauth', attribute=False, autofill=False, cli_name='requires_pre_auth', multivalue=False, required=False)
 option: Str('ipasshpubkey', attribute=True, autofill=False, cli_name='sshpubkey', csv=True, multivalue=True, required=False)
diff --git a/ipalib/plugins/host.py b/ipalib/plugins/host.py
index ee858ad273c86404c2a9e7ad90500f4477e052e1..cb9a18d805bf4d15bf836dc551dae8729af57c7d 100644
--- a/ipalib/plugins/host.py
+++ b/ipalib/plugins/host.py
@@ -27,7 +27,7 @@ import netaddr
 import string
 
 from ipalib import api, errors, util
-from ipalib import Str, Flag, Bytes
+from ipalib import Str, Flag, Bytes, DNParam
 from ipalib.plugable import Registry
 from ipalib.plugins.baseldap import *
 from ipalib.plugins.service import (split_principal, validate_certificate,
@@ -276,7 +276,7 @@ class host(LDAPObject):
                 'krbprincipalname', 'l', 'macaddress', 'nshardwareplatform',
                 'nshostlocation', 'nsosversion', 'objectclass',
                 'serverhostname', 'usercertificate', 'userclass',
-                'enrolledby', 'managedby',
+                'enrolledby', 'managedby', 'ipaassignedidview',
                 'krbprincipalname', 'krbcanonicalname', 'krbprincipalaliases',
                 'krbprincipalexpiration', 'krbpasswordexpiration',
                 'krblastpwdchange',
@@ -342,7 +342,7 @@ class host(LDAPObject):
             'ipapermright': {'write'},
             'ipapermdefaultattr': {
                 'description', 'l', 'nshardwareplatform', 'nshostlocation',
-                'nsosversion', 'macaddress', 'userclass',
+                'nsosversion', 'macaddress', 'userclass', 'ipaassignedidview',
             },
             'replaces': [
                 '(targetattr = "description || l || nshostlocation || nshardwareplatform || nsosversion")(target = "ldap:///fqdn=*,cn=computers,cn=accounts,$SUFFIX";)(version 3.0;acl "permission:Modify Hosts";allow (write) groupdn = "ldap:///cn=Modify Hosts,cn=permissions,cn=pbac,$SUFFIX";)',
@@ -449,6 +449,9 @@ class host(LDAPObject):
             doc=_('Host category (semantics placed on this attribute are for '
                   'local interpretation)'),
         ),
+        DNParam('ipaassignedidview?',
+            flags=['no_option'],
+        ),
     ) + ticket_flags_params
 
     def get_dn(self, *keys, **options):
-- 
1.9.3

>From 3edf9e39558a8adc9d4402bef5a9895cda19d1ab Mon Sep 17 00:00:00 2001
From: Tomas Babej <tba...@redhat.com>
Date: Fri, 1 Aug 2014 12:06:36 +0200
Subject: [PATCH] ipalib: Remove redundant and star imports from host plugin

Also fixes incorrect error catching for UnicodeDecodeError.

Part of: https://fedorahosted.org/freeipa/ticket/3979
---
 ipalib/plugins/host.py | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/ipalib/plugins/host.py b/ipalib/plugins/host.py
index cb9a18d805bf4d15bf836dc551dae8729af57c7d..1d359edcc5b61c5c811197050a81b46e85d8b270 100644
--- a/ipalib/plugins/host.py
+++ b/ipalib/plugins/host.py
@@ -18,18 +18,17 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-import platform
-import os
-import sys
 from nss.error import NSPRError
-import nss.nss as nss
-import netaddr
 import string
 
 from ipalib import api, errors, util
 from ipalib import Str, Flag, Bytes, DNParam
 from ipalib.plugable import Registry
-from ipalib.plugins.baseldap import *
+from ipalib.plugins.baseldap import (LDAPQuery, LDAPObject, LDAPCreate,
+                                     LDAPDelete, LDAPUpdate, LDAPSearch,
+                                     LDAPRetrieve, LDAPAddMember,
+                                     LDAPRemoveMember, host_is_master,
+                                     pkey_to_value)
 from ipalib.plugins.service import (split_principal, validate_certificate,
     set_certificate_attrs, ticket_flags_params, update_krbticketflags,
     set_kerberos_attrs)
@@ -38,6 +37,7 @@ from ipalib.plugins.dns import (dns_container_exists, _record_types,
         get_reverse_zone)
 from ipalib import _, ngettext
 from ipalib import x509
+from ipalib import output
 from ipalib.request import context
 from ipalib.util import (normalize_sshpubkey, validate_sshpubkey_no_options,
     convert_sshpubkey_post, validate_hostname)
@@ -139,13 +139,13 @@ def update_sshfp_record(zone, record, entry_attrs):
     for pubkey in pubkeys:
         try:
             sshfp = SSHPublicKey(pubkey).fingerprint_dns_sha1()
-        except ValueError, UnicodeDecodeError:
+        except (ValueError, UnicodeDecodeError):
             continue
         if sshfp is not None:
             sshfps.append(sshfp)
         try:
             sshfp = SSHPublicKey(pubkey).fingerprint_dns_sha256()
-        except ValueError, UnicodeDecodeError:
+        except (ValueError, UnicodeDecodeError):
             continue
         if sshfp is not None:
             sshfps.append(sshfp)
-- 
1.9.3

>From a24446dfd8b80925e86ed26705e7e3408331d422 Mon Sep 17 00:00:00 2001
From: Tomas Babej <tba...@redhat.com>
Date: Fri, 1 Aug 2014 12:08:15 +0200
Subject: [PATCH] ipalib: PEP8 fixes for host plugin

Part of: https://fedorahosted.org/freeipa/ticket/3979
---
 ipalib/plugins/host.py | 37 +++++++++++++++++++++----------------
 1 file changed, 21 insertions(+), 16 deletions(-)

diff --git a/ipalib/plugins/host.py b/ipalib/plugins/host.py
index 1d359edcc5b61c5c811197050a81b46e85d8b270..996f43ba7fc62dc25d7253f0f416437601db8cee 100644
--- a/ipalib/plugins/host.py
+++ b/ipalib/plugins/host.py
@@ -108,7 +108,8 @@ register = Registry()
 
 # Characters to be used by random password generator
 # The set was chosen to avoid the need for escaping the characters by user
-host_pwd_chars=string.digits + string.ascii_letters + '_,.@+-='
+host_pwd_chars = string.digits + string.ascii_letters + '_,.@+-='
+
 
 def remove_fwd_ptr(ipaddr, host, domain, recordtype):
     api.log.debug('deleting ipaddr %s' % ipaddr)
@@ -118,24 +119,25 @@ def remove_fwd_ptr(ipaddr, host, domain, recordtype):
         # in case domain is in FQDN form with a trailing dot, we needn't add
         # another one, in case it has no trailing dot, dnsrecord-del will
         # normalize the entry
-        delkw = { 'ptrrecord' : "%s.%s" % (host, domain) }
+        delkw = {'ptrrecord': "%s.%s" % (host, domain)}
 
         api.Command['dnsrecord_del'](revzone, revname, **delkw)
     except errors.NotFound:
         pass
 
     try:
-        delkw = { recordtype : ipaddr }
+        delkw = {recordtype: ipaddr}
         api.Command['dnsrecord_del'](domain, host, **delkw)
     except errors.NotFound:
         pass
 
+
 def update_sshfp_record(zone, record, entry_attrs):
     if 'ipasshpubkey' not in entry_attrs:
         return
 
     pubkeys = entry_attrs['ipasshpubkey'] or ()
-    sshfps=[]
+    sshfps = []
     for pubkey in pubkeys:
         try:
             sshfp = SSHPublicKey(pubkey).fingerprint_dns_sha1()
@@ -143,6 +145,7 @@ def update_sshfp_record(zone, record, entry_attrs):
             continue
         if sshfp is not None:
             sshfps.append(sshfp)
+
         try:
             sshfp = SSHPublicKey(pubkey).fingerprint_dns_sha256()
         except (ValueError, UnicodeDecodeError):
@@ -200,16 +203,18 @@ host_output_params = (
     ),
 )
 
+
 def validate_ipaddr(ugettext, ipaddr):
     """
     Verify that we have either an IPv4 or IPv6 address.
     """
     try:
-        ip = CheckedIPAddress(ipaddr, match_local=False)
+        CheckedIPAddress(ipaddr, match_local=False)
     except Exception, e:
         return unicode(e)
     return None
 
+
 def normalize_hostname(hostname):
     """Use common fqdn form without the trailing dot"""
     if hostname.endswith(u'.'):
@@ -217,6 +222,7 @@ def normalize_hostname(hostname):
     hostname = hostname.lower()
     return hostname
 
+
 def _hostname_validator(ugettext, value):
     try:
         validate_hostname(value)
@@ -226,6 +232,7 @@ def _hostname_validator(ugettext, value):
 
     return None
 
+
 @register()
 class host(LDAPObject):
     """
@@ -431,7 +438,8 @@ class host(LDAPObject):
         Str('macaddress*',
             normalizer=lambda value: value.upper(),
             pattern='^([a-fA-F0-9]{2}[:|\-]?){5}[a-fA-F0-9]{2}$',
-            pattern_errmsg='Must be of the form HH:HH:HH:HH:HH:HH, where each H is a hexadecimal character.',
+            pattern_errmsg=('Must be of the form HH:HH:HH:HH:HH:HH, where '
+                            'each H is a hexadecimal character.'),
             csv=True,
             label=_('MAC address'),
             doc=_('Hardware MAC address(es) on this host'),
@@ -507,7 +515,6 @@ class host(LDAPObject):
                 entry_attrs['memberofindirect'].remove(member)
 
 
-
 @register()
 class host_add(LDAPCreate):
     __doc__ = _('Add a new host.')
@@ -627,7 +634,6 @@ class host_add(LDAPCreate):
         return dn
 
 
-
 @register()
 class host_del(LDAPDelete):
     __doc__ = _('Delete a host.')
@@ -693,7 +699,8 @@ class host_del(LDAPDelete):
                                    domain, 'aaaarecord')
                 else:
                     # Try to delete all other record types too
-                    _attribute_types = [str('%srecord' % t.lower()) for t in _record_types]
+                    _attribute_types = [str('%srecord' % t.lower())
+                                        for t in _record_types]
                     for attr in _attribute_types:
                         if attr not in ['arecord', 'aaaarecord'] and attr in record:
                             for i in xrange(len(record[attr])):
@@ -739,7 +746,6 @@ class host_del(LDAPDelete):
         return dn
 
 
-
 @register()
 class host_mod(LDAPUpdate):
     __doc__ = _('Modify information about a host.')
@@ -769,7 +775,9 @@ class host_mod(LDAPUpdate):
             entry = {}
             self.obj.get_password_attributes(ldap, dn, entry)
             if not entry['has_password'] and entry['has_keytab']:
-                raise errors.ValidationError(name='password', error=_('Password cannot be set on enrolled host.'))
+                raise errors.ValidationError(
+                    name='password',
+                    error=_('Password cannot be set on enrolled host.'))
 
         # Once a principal name is set it cannot be changed
         if 'cn' in entry_attrs:
@@ -887,7 +895,6 @@ class host_mod(LDAPUpdate):
         return dn
 
 
-
 @register()
 class host_find(LDAPSearch):
     __doc__ = _('Search for hosts.')
@@ -942,7 +949,8 @@ class host_find(LDAPSearch):
             for target_hosts, filter_op in ((hosts, ldap.MATCH_ANY),
                                             (not_hosts, ldap.MATCH_NONE)):
                 hosts_avas = [DN(host)[0][0] for host in target_hosts]
-                hosts_filters = [ldap.make_filter_from_attr(ava.attr, ava.value) for ava in hosts_avas]
+                hosts_filters = [ldap.make_filter_from_attr(ava.attr, ava.value)
+                                 for ava in hosts_avas]
                 hosts_filter = ldap.combine_filters(hosts_filters, filter_op)
 
                 filter = ldap.combine_filters(
@@ -972,7 +980,6 @@ class host_find(LDAPSearch):
         return truncated
 
 
-
 @register()
 class host_show(LDAPRetrieve):
     __doc__ = _('Display information about a host.')
@@ -1020,7 +1027,6 @@ class host_show(LDAPRetrieve):
             return super(host_show, self).forward(*keys, **options)
 
 
-
 @register()
 class host_disable(LDAPQuery):
     __doc__ = _('Disable the Kerberos key, SSL certificate and all services of a host.')
@@ -1132,7 +1138,6 @@ class host_add_managedby(LDAPAddMember):
         return (completed, dn)
 
 
-
 @register()
 class host_remove_managedby(LDAPRemoveMember):
     __doc__ = _('Remove hosts that can manage this host.')
-- 
1.9.3

>From 416f073f500bdae4194023e421b1ae28fde1fe3c Mon Sep 17 00:00:00 2001
From: Tomas Babej <tba...@redhat.com>
Date: Thu, 31 Jul 2014 12:52:19 +0200
Subject: [PATCH] idviews: Create basic idview plugin structure

Part of: https://fedorahosted.org/freeipa/ticket/3979
---
 API.txt                   | 140 ++++++++++++++++++++++++++++++++++
 ipalib/plugins/idviews.py | 189 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 329 insertions(+)
 create mode 100644 ipalib/plugins/idviews.py

diff --git a/API.txt b/API.txt
index 7670aa75b4063217e98889de233c7e10d7ba5e67..60bf16db258afd61d265b8a8e3b38a023caa738a 100644
--- a/API.txt
+++ b/API.txt
@@ -2031,6 +2031,86 @@ command: i18n_messages
 args: 0,1,1
 option: Str('version?', exclude='webui')
 output: Output('texts', <type 'dict'>, None)
+command: idoverride_add
+args: 2,11,3
+arg: Str('idviewcn', cli_name='idview', multivalue=False, primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='override', multivalue=False, primary_key=True, required=False)
+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='desc', multivalue=False, required=True)
+option: Int('gidnumber', attribute=True, cli_name='gid', minvalue=1, multivalue=False, required=False)
+option: Str('homedirectory', attribute=True, cli_name='homedir', multivalue=False, required=False)
+option: Str('ipaanchoruuid', attribute=True, cli_name='anchor', multivalue=False, required=True)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: Str('uid', attribute=True, cli_name='login', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', required=True)
+option: Int('uidnumber', attribute=True, cli_name='uid', minvalue=1, multivalue=False, required=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: idoverride_del
+args: 2,2,3
+arg: Str('idviewcn', cli_name='idview', multivalue=False, primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='override', multivalue=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: idoverride_find
+args: 2,13,4
+arg: Str('idviewcn', cli_name='idview', multivalue=False, primary_key=True, query=True, required=True)
+arg: Str('criteria?', noextrawhitespace=False)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('cn', attribute=True, autofill=False, cli_name='override', multivalue=False, primary_key=True, query=True, required=False)
+option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, query=True, required=False)
+option: Int('gidnumber', attribute=True, autofill=False, cli_name='gid', minvalue=1, multivalue=False, query=True, required=False)
+option: Str('homedirectory', attribute=True, autofill=False, cli_name='homedir', multivalue=False, query=True, required=False)
+option: Str('ipaanchoruuid', attribute=True, autofill=False, cli_name='anchor', multivalue=False, query=True, required=False)
+option: Flag('pkey_only?', autofill=True, default=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Int('sizelimit?', autofill=False, minvalue=0)
+option: Int('timelimit?', autofill=False, minvalue=0)
+option: Str('uid', attribute=True, autofill=False, cli_name='login', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', query=True, required=False)
+option: Int('uidnumber', attribute=True, autofill=False, cli_name='uid', minvalue=1, multivalue=False, query=True, required=False)
+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: idoverride_mod
+args: 2,14,3
+arg: Str('idviewcn', cli_name='idview', multivalue=False, primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='override', multivalue=False, 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='desc', multivalue=False, required=False)
+option: Int('gidnumber', attribute=True, autofill=False, cli_name='gid', minvalue=1, multivalue=False, required=False)
+option: Str('homedirectory', attribute=True, autofill=False, cli_name='homedir', multivalue=False, required=False)
+option: Str('ipaanchoruuid', attribute=True, autofill=False, cli_name='anchor', multivalue=False, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('rename', cli_name='rename', multivalue=False, primary_key=True, required=False)
+option: Flag('rights', autofill=True, default=False)
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: Str('uid', attribute=True, autofill=False, cli_name='login', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', required=False)
+option: Int('uidnumber', attribute=True, autofill=False, cli_name='uid', minvalue=1, multivalue=False, required=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: idoverride_show
+args: 2,4,3
+arg: Str('idviewcn', cli_name='idview', multivalue=False, primary_key=True, query=True, required=True)
+arg: Str('cn', attribute=True, cli_name='override', multivalue=False, 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: idrange_add
 args: 1,12,3
 arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, required=True)
@@ -2106,6 +2186,66 @@ 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: idview_add
+args: 1,6,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, required=False)
+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='desc', 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: idview_del
+args: 1,2,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=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: idview_find
+args: 1,8,4
+arg: Str('criteria?', noextrawhitespace=False)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('cn', attribute=True, autofill=False, cli_name='name', multivalue=False, primary_key=True, query=True, required=False)
+option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, query=True, required=False)
+option: Flag('pkey_only?', autofill=True, default=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+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: idview_mod
+args: 1,9,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, 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='desc', multivalue=False, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('rename', cli_name='rename', multivalue=False, primary_key=True, required=False)
+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: idview_show
+args: 1,4,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, 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: json_metadata
 args: 2,4,3
 arg: Str('objname?')
diff --git a/ipalib/plugins/idviews.py b/ipalib/plugins/idviews.py
new file mode 100644
index 0000000000000000000000000000000000000000..60a54922364426a5ae32d9bcf1d749a4ec5dc6e5
--- /dev/null
+++ b/ipalib/plugins/idviews.py
@@ -0,0 +1,189 @@
+# Authors:
+#   Alexander Bokovoy <aboko...@redhat.com>
+#   Tomas Babej <tba...@redhat.com>
+#
+# Copyright (C) 2014  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from ipalib.plugins.baseldap import (LDAPObject, LDAPCreate,
+                                     LDAPDelete, LDAPUpdate, LDAPSearch,
+                                     LDAPRetrieve)
+from ipalib import api, Str, Int, _, ngettext
+from ipalib.plugable import Registry
+
+
+__doc__ = _("""
+ID views
+Manage ID views
+IPA allows to override certain properties of users and groups per each host.
+This functionality is primary used to allow migration from older systems or
+other Identity Management solutions.
+""")
+
+register = Registry()
+
+
+@register()
+class idview(LDAPObject):
+    """
+    ID view object.
+    """
+
+    container_dn = api.env.container_views
+    object_name = _('ID view')
+    object_name_plural = _('ID views')
+    object_class = ['nsContainer', 'top']
+    default_attributes = ['cn', 'description']
+    rdn_is_primary_key = True
+
+    label = _('ID views')
+    label_singular = _('ID view')
+
+    takes_params = (
+        Str('cn',
+            cli_name='name',
+            label=_('ID View Name'),
+            primary_key=True,
+            flags=('optional_create'),
+        ),
+        Str('description?',
+            cli_name='desc',
+            label=_('Description'),
+        ),
+    )
+
+
+@register()
+class idview_add(LDAPCreate):
+    __doc__ = _('Add a new ID View.')
+    mgs_summary = _('Added ID view "%(value)s"')
+
+
+@register()
+class idview_del(LDAPDelete):
+    __doc__ = _('Delete an ID view.')
+    msg_summary = _('Deleted ID view "%(value)s"')
+
+
+@register()
+class idview_mod(LDAPUpdate):
+    __doc__ = _('Modify an ID view.')
+    msg_summary = _('Modified an ID view "%(value)s"')
+
+
+@register()
+class idview_find(LDAPSearch):
+    __doc__ = _('Search for an ID view.')
+    msg_summary = ngettext('%(count)d ID view matched',
+                           '%(count)d ID views matched', 0)
+
+
+@register()
+class idview_show(LDAPRetrieve):
+    __doc__ = _('Display information about an ID view.')
+
+
+@register()
+class idoverride(LDAPObject):
+    """
+    ID override object.
+    """
+
+    parent_object = 'idview'
+    container_dn = api.env.container_views
+
+    object_name = _('ID override')
+    object_name_plural = _('ID overrides')
+    object_class = ['ipaOverrideAnchor', 'top']
+    default_attributes = [
+        'cn', 'description', 'ipaAnchorUUID', 'gidNumber',
+        'homeDirectory', 'uidNumber', 'uid',
+    ]
+    rdn_is_primary_key = True
+
+    label = _('ID overrides')
+    label_singular = _('ID override')
+
+    takes_params = (
+        Str('cn',
+            cli_name='override',
+            label=_('ID override name'),
+            primary_key=True,
+            flags=('optional_create'),
+        ),
+        Str('description',
+            cli_name='desc',
+            label=_('Description'),
+        ),
+        Str('ipaanchoruuid',
+            cli_name='anchor',
+            label=_('Anchor to override'),
+        ),
+        Str('uid',
+            pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$',
+            pattern_errmsg='may only include letters, numbers, _, -, . and $',
+            maxlength=255,
+            cli_name='login',
+            label=_('User login'),
+            normalizer=lambda value: value.lower(),
+        ),
+        Int('uidnumber?',
+            cli_name='uid',
+            label=_('UID'),
+            doc=_('User ID Number'),
+            minvalue=1,
+        ),
+        Int('gidnumber?',
+            cli_name='gid',
+            label=_('GID'),
+            doc=_('Group ID Number'),
+            minvalue=1,
+        ),
+        Str('homedirectory?',
+            cli_name='homedir',
+            label=_('Home directory'),
+        ),
+    )
+
+
+@register()
+class idoverride_add(LDAPCreate):
+    __doc__ = _('Add a new ID override.')
+    mgs_summary = _('Added ID override "%(value)s"')
+
+
+@register()
+class idoverride_del(LDAPDelete):
+    __doc__ = _('Delete an ID override.')
+    msg_summary = _('Deleted ID override "%(value)s"')
+
+
+@register()
+class idoverride_mod(LDAPUpdate):
+    __doc__ = _('Modify an ID override.')
+    msg_summary = _('Modified an ID override "%(value)s"')
+
+
+@register()
+class idoverride_find(LDAPSearch):
+    __doc__ = _('Search for an ID override.')
+    msg_summary = ngettext('%(count)d ID override matched',
+                           '%(count)d ID overrides matched', 0)
+
+
+@register()
+class idoverride_show(LDAPRetrieve):
+    __doc__ = _('Display information about an ID override.')
-- 
1.9.3

>From 0d1bd1eacb773899bb8263b0f938838c7bb02222 Mon Sep 17 00:00:00 2001
From: Tomas Babej <tba...@redhat.com>
Date: Thu, 31 Jul 2014 13:13:29 +0200
Subject: [PATCH] idvies: Add managed permissions for idview and idoverride
 objects

Part of: https://fedorahosted.org/freeipa/ticket/3979
---
 ACI.txt                   |  4 ++++
 ipalib/plugins/idviews.py | 23 +++++++++++++++++++++++
 2 files changed, 27 insertions(+)

diff --git a/ACI.txt b/ACI.txt
index 99cd0d73b0120a4a2620ec686e1a46b55dc71cb3..ebd0ac60d220761efcffd0e35307db4461491d3b 100644
--- a/ACI.txt
+++ b/ACI.txt
@@ -114,8 +114,12 @@ dn: cn=hostgroups,cn=accounts,dc=ipa,dc=example
 aci: (targetattr = "businesscategory || cn || description || ipauniqueid || o || objectclass || ou || owner || seealso")(targetfilter = "(objectclass=ipahostgroup)")(version 3.0;acl "permission:System: Read Hostgroups";allow (compare,read,search) userdn = "ldap:///all";;)
 dn: cn=hostgroups,cn=accounts,dc=ipa,dc=example
 aci: (targetfilter = "(objectclass=ipahostgroup)")(version 3.0;acl "permission:System: Remove Hostgroups";allow (delete) groupdn = "ldap:///cn=System: Remove Hostgroups,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=views,cn=accounts,dc=ipa,dc=example
+aci: (targetattr = "cn || description || gidnumber || homedirectory || ipaanchoruuid || objectclass || uid || uidnumber")(targetfilter = "(objectclass=ipaOverrideAnchor)")(version 3.0;acl "permission:System: Read ID Overrides";allow (compare,read,search) userdn = "ldap:///all";;)
 dn: cn=ranges,cn=etc,dc=ipa,dc=example
 aci: (targetattr = "cn || ipabaseid || ipabaserid || ipaidrangesize || ipanttrusteddomainsid || iparangetype || ipasecondarybaserid || objectclass")(targetfilter = "(objectclass=ipaidrange)")(version 3.0;acl "permission:System: Read ID Ranges";allow (compare,read,search) userdn = "ldap:///all";;)
+dn: cn=views,cn=accounts,dc=ipa,dc=example
+aci: (targetattr = "cn || description || objectclass")(targetfilter = "(objectclass=nsContainer)")(version 3.0;acl "permission:System: Read ID Views";allow (compare,read,search) userdn = "ldap:///all";;)
 dn: cn=IPA.EXAMPLE,cn=kerberos,dc=ipa,dc=example
 aci: (targetattr = "krbdefaultencsalttypes || krbmaxrenewableage || krbmaxticketlife || krbsupportedencsalttypes || 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
diff --git a/ipalib/plugins/idviews.py b/ipalib/plugins/idviews.py
index 60a54922364426a5ae32d9bcf1d749a4ec5dc6e5..9aa35944390c618f455ee91c4cef72225b59772e 100644
--- a/ipalib/plugins/idviews.py
+++ b/ipalib/plugins/idviews.py
@@ -65,6 +65,17 @@ class idview(LDAPObject):
         ),
     )
 
+    permission_filter_objectclasses = ['nsContainer']
+    managed_permissions = {
+        'System: Read ID Views': {
+            'ipapermbindruletype': 'all',
+            'ipapermright': {'read', 'search', 'compare'},
+            'ipapermdefaultattr': {
+                'cn', 'description', 'objectClass',
+            },
+        },
+    }
+
 
 @register()
 class idview_add(LDAPCreate):
@@ -158,6 +169,18 @@ class idoverride(LDAPObject):
         ),
     )
 
+    permission_filter_objectclasses = ['ipaOverrideAnchor']
+    managed_permissions = {
+        'System: Read ID Overrides': {
+            'ipapermbindruletype': 'all',
+            'ipapermright': {'read', 'search', 'compare'},
+            'ipapermdefaultattr': {
+                'cn', 'objectClass', 'ipaAnchorUUID', 'uidNumber', 'gidNumber',
+                'description', 'homeDirectory', 'uid',
+            },
+        },
+    }
+
 
 @register()
 class idoverride_add(LDAPCreate):
-- 
1.9.3

>From c8f69cdf7f436004ffc4b52c184cc846fda2b5cf Mon Sep 17 00:00:00 2001
From: Tomas Babej <tba...@redhat.com>
Date: Thu, 31 Jul 2014 13:52:24 +0200
Subject: [PATCH] idviews: Set proper objectclass for the ID override objects

Part of: https://fedorahosted.org/freeipa/ticket/3979
---
 ipalib/plugins/idviews.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 63 insertions(+), 1 deletion(-)

diff --git a/ipalib/plugins/idviews.py b/ipalib/plugins/idviews.py
index 9aa35944390c618f455ee91c4cef72225b59772e..b0a13ea2b7e668bb3e570e1351ed92b88cc1c5e5 100644
--- a/ipalib/plugins/idviews.py
+++ b/ipalib/plugins/idviews.py
@@ -21,9 +21,10 @@
 from ipalib.plugins.baseldap import (LDAPObject, LDAPCreate,
                                      LDAPDelete, LDAPUpdate, LDAPSearch,
                                      LDAPRetrieve)
-from ipalib import api, Str, Int, _, ngettext
+from ipalib import api, Str, Int, _, ngettext, errors
 from ipalib.plugable import Registry
 
+from ipapython.dn import DN
 
 __doc__ = _("""
 ID views
@@ -181,12 +182,68 @@ class idoverride(LDAPObject):
         },
     }
 
+    useroverride_attrs = ('uidnumber', 'homedirectory', 'uid')
+    groupoverride_attrs = ('gidnumber',)
+
+    def set_proper_objectclass(self, dn, entry_attrs, options, adding=False):
+        """
+        Sets the proper object class (ipaUserOverride or ipaGroupOverride
+        depending on the options passed.
+
+        Makes sure that attributes one idoverride object does not contain
+        attributes from both classes.
+        """
+
+        # If we're adding new object, we cannot obtain list of objectclasses
+        # from LDAP since the object does not exist yet
+        if not adding:
+            ldap = self.backend
+            objectclasses = ldap.get_entry(dn, ['objectclass'])['objectclass']
+        else:
+            objectclasses = []
+
+        is_override_objectclass_set = ('ipaUserOverride' in objectclasses or
+                                       'ipaGroupOverride' in objectclasses)
+
+        new_group_attrs = set(options.keys()) & set(self.groupoverride_attrs)
+        new_user_attrs = set(options.keys()) & set(self.useroverride_attrs)
+
+        overlap_error = errors.ValidationError(
+            name='attributes',
+            error=_("You cannot set both user and group related parameters in "
+                    "one override, since anchor refers to exactly one object."))
+
+        if is_override_objectclass_set:
+            # If the objectclass is set, just check if we're not trying to set
+            # attributes allowed by the other objectclass
+            if 'ipaUserOverride' in objectclasses and new_group_attrs:
+                raise overlap_error
+            elif 'ipaGroupOverride' in objectclasses and new_user_attrs:
+                raise overlap_error
+        else:
+            # No override objectclass set, that means we're setting the user
+            # or group attributes for the first time now
+            if new_user_attrs and new_group_attrs:
+                raise overlap_error
+            elif new_user_attrs:
+                entry_attrs['objectclass'].append('ipauseroverride')
+            elif new_group_attrs:
+                entry_attrs['objectclass'].append('ipagroupoverride')
+
+            # Note that if no user / group override attribute was specified,
+            # no override objectclass was added. This is desired outcome.
+
 
 @register()
 class idoverride_add(LDAPCreate):
     __doc__ = _('Add a new ID override.')
     mgs_summary = _('Added ID override "%(value)s"')
 
+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+        assert isinstance(dn, DN)
+        self.obj.set_proper_objectclass(dn, entry_attrs, options, adding=True)
+        return dn
+
 
 @register()
 class idoverride_del(LDAPDelete):
@@ -199,6 +256,11 @@ class idoverride_mod(LDAPUpdate):
     __doc__ = _('Modify an ID override.')
     msg_summary = _('Modified an ID override "%(value)s"')
 
+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+        assert isinstance(dn, DN)
+        self.obj.set_proper_objectclass(dn, entry_attrs, options)
+        return dn
+
 
 @register()
 class idoverride_find(LDAPSearch):
-- 
1.9.3

>From 50323546083d05a0381c2a4e39658eba2636b928 Mon Sep 17 00:00:00 2001
From: Tomas Babej <tba...@redhat.com>
Date: Thu, 31 Jul 2014 14:18:31 +0200
Subject: [PATCH] hostgroup: Add helper that returns all members of a hostgroup

Part of: https://fedorahosted.org/freeipa/ticket/3979
---
 ipalib/plugins/hostgroup.py | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/ipalib/plugins/hostgroup.py b/ipalib/plugins/hostgroup.py
index 0e86e99202bb69e8c7a82ecaff09e9383403bb09..04891cc962d1380a0ac7faeda95ff3b38e82e212 100644
--- a/ipalib/plugins/hostgroup.py
+++ b/ipalib/plugins/hostgroup.py
@@ -54,6 +54,14 @@ EXAMPLES:
    ipa hostgroup-del baltimore
 """)
 
+
+def get_complete_hostgroup_member_list(hostgroup):
+    result = api.Command['hostgroup_show'](hostgroup)['result']
+    direct = list(result.get('member_host', []))
+    indirect = list(result.get('memberindirect_host', []))
+    return direct + indirect
+
+
 register = Registry()
 
 @register()
-- 
1.9.3

>From 5829b740dd597e87be92bb0f4a718f281f8a88ab Mon Sep 17 00:00:00 2001
From: Tomas Babej <tba...@redhat.com>
Date: Fri, 1 Aug 2014 12:14:35 +0200
Subject: [PATCH] hostgroup: Remove redundant and star imports

Part of: https://fedorahosted.org/freeipa/ticket/3979
---
 ipalib/plugins/hostgroup.py | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/ipalib/plugins/hostgroup.py b/ipalib/plugins/hostgroup.py
index 04891cc962d1380a0ac7faeda95ff3b38e82e212..276d1ef9758283d37a131fbbd4298466e897eb51 100644
--- a/ipalib/plugins/hostgroup.py
+++ b/ipalib/plugins/hostgroup.py
@@ -19,8 +19,11 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from ipalib.plugable import Registry
-from ipalib.plugins.baseldap import *
-from ipalib import api, Int, _, ngettext, errors
+from ipalib.plugins.baseldap import (LDAPObject, LDAPCreate, LDAPRetrieve,
+                                     LDAPDelete, LDAPUpdate, LDAPSearch,
+                                     LDAPAddMember, LDAPRemoveMember,
+                                     entry_from_entry, wait_for_value)
+from ipalib import Str, api, _, ngettext, errors
 from ipalib.plugins.netgroup import NETGROUP_PATTERN, NETGROUP_PATTERN_ERRMSG
 from ipapython.dn import DN
 
-- 
1.9.3

>From 5900539bdcd449d9bb53899a2feb5e28823fe736 Mon Sep 17 00:00:00 2001
From: Tomas Babej <tba...@redhat.com>
Date: Fri, 1 Aug 2014 12:15:07 +0200
Subject: [PATCH] hostgroup: Selected PEP8 fixes for the hostgroup plugin

Part of: https://fedorahosted.org/freeipa/ticket/3979
---
 ipalib/plugins/hostgroup.py | 15 ++++-----------
 1 file changed, 4 insertions(+), 11 deletions(-)

diff --git a/ipalib/plugins/hostgroup.py b/ipalib/plugins/hostgroup.py
index 276d1ef9758283d37a131fbbd4298466e897eb51..4007940fb4267203c5c3c3ec1dbcba101c0d944a 100644
--- a/ipalib/plugins/hostgroup.py
+++ b/ipalib/plugins/hostgroup.py
@@ -67,6 +67,7 @@ def get_complete_hostgroup_member_list(hostgroup):
 
 register = Registry()
 
+
 @register()
 class hostgroup(LDAPObject):
     """
@@ -177,7 +178,6 @@ class hostgroup(LDAPObject):
                 entry_attrs['memberof'].remove(member)
 
 
-
 @register()
 class hostgroup_add(LDAPCreate):
     __doc__ = _('Add a new hostgroup.')
@@ -197,9 +197,9 @@ class hostgroup_add(LDAPCreate):
             # when enabled, a managed netgroup is created for every hostgroup
             # make sure that the netgroup can be created
             api.Object['netgroup'].get_dn_if_exists(keys[-1])
-            raise errors.DuplicateEntry(message=unicode(_(\
-                    u'netgroup with name "%s" already exists. ' \
-                    u'Hostgroups and netgroups share a common namespace'\
+            raise errors.DuplicateEntry(message=unicode(_(
+                    u'netgroup with name "%s" already exists. '
+                    u'Hostgroups and netgroups share a common namespace'
                     ) % keys[-1]))
         except errors.NotFound:
             pass
@@ -217,8 +217,6 @@ class hostgroup_add(LDAPCreate):
         return dn
 
 
-
-
 @register()
 class hostgroup_del(LDAPDelete):
     __doc__ = _('Delete a hostgroup.')
@@ -226,7 +224,6 @@ class hostgroup_del(LDAPDelete):
     msg_summary = _('Deleted hostgroup "%(value)s"')
 
 
-
 @register()
 class hostgroup_mod(LDAPUpdate):
     __doc__ = _('Modify a hostgroup.')
@@ -239,7 +236,6 @@ class hostgroup_mod(LDAPUpdate):
         return dn
 
 
-
 @register()
 class hostgroup_find(LDAPSearch):
     __doc__ = _('Search for hostgroups.')
@@ -257,7 +253,6 @@ class hostgroup_find(LDAPSearch):
         return truncated
 
 
-
 @register()
 class hostgroup_show(LDAPRetrieve):
     __doc__ = _('Display information about a hostgroup.')
@@ -268,7 +263,6 @@ class hostgroup_show(LDAPRetrieve):
         return dn
 
 
-
 @register()
 class hostgroup_add_member(LDAPAddMember):
     __doc__ = _('Add members to a hostgroup.')
@@ -279,7 +273,6 @@ class hostgroup_add_member(LDAPAddMember):
         return (completed, dn)
 
 
-
 @register()
 class hostgroup_remove_member(LDAPRemoveMember):
     __doc__ = _('Remove members from a hostgroup.')
-- 
1.9.3

>From 0004a1768277f99d53be34993b77cb5be207dd58 Mon Sep 17 00:00:00 2001
From: Tomas Babej <tba...@redhat.com>
Date: Thu, 31 Jul 2014 14:09:05 +0200
Subject: [PATCH] idviews: Add ipa idview-apply and idview-unapply commands

Part of: https://fedorahosted.org/freeipa/ticket/3979
---
 API.txt                   |  19 +++++
 ipalib/plugins/idviews.py | 182 +++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 198 insertions(+), 3 deletions(-)

diff --git a/API.txt b/API.txt
index 60bf16db258afd61d265b8a8e3b38a023caa738a..8f1b5364de8d5865990755fdbafb0c9bc6404fc8 100644
--- a/API.txt
+++ b/API.txt
@@ -2198,6 +2198,16 @@ 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: idview_apply
+args: 1,3,4
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+option: Str('host*', cli_name='hosts')
+option: Str('hostgroup*', cli_name='hostgroups')
+option: Str('version?', exclude='webui')
+output: Output('completed', <type 'int'>, None)
+output: Output('failed', <type 'dict'>, None)
+output: Output('succeeded', <type 'dict'>, None)
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 command: idview_del
 args: 1,2,3
 arg: Str('cn', attribute=True, cli_name='name', multivalue=True, primary_key=True, query=True, required=True)
@@ -2246,6 +2256,15 @@ 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: idview_unapply
+args: 0,3,4
+option: Str('host*', cli_name='hosts')
+option: Str('hostgroup*', cli_name='hostgroups')
+option: Str('version?', exclude='webui')
+output: Output('completed', <type 'int'>, None)
+output: Output('failed', <type 'dict'>, None)
+output: Output('succeeded', <type 'dict'>, None)
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 command: json_metadata
 args: 2,4,3
 arg: Str('objname?')
diff --git a/ipalib/plugins/idviews.py b/ipalib/plugins/idviews.py
index b0a13ea2b7e668bb3e570e1351ed92b88cc1c5e5..3739e5637f5119668f9847a5d20e74c2235a3e8d 100644
--- a/ipalib/plugins/idviews.py
+++ b/ipalib/plugins/idviews.py
@@ -18,10 +18,11 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from ipalib.plugins.baseldap import (LDAPObject, LDAPCreate,
+from ipalib.plugins.baseldap import (LDAPQuery, LDAPObject, LDAPCreate,
                                      LDAPDelete, LDAPUpdate, LDAPSearch,
-                                     LDAPRetrieve)
-from ipalib import api, Str, Int, _, ngettext, errors
+                                     LDAPRetrieve, global_output_params)
+from ipalib.plugins.hostgroup import get_complete_hostgroup_member_list
+from ipalib import api, Str, Int, _, ngettext, errors, output
 from ipalib.plugable import Registry
 
 from ipapython.dn import DN
@@ -109,6 +110,181 @@ class idview_show(LDAPRetrieve):
 
 
 @register()
+class idview_apply(LDAPQuery):
+    __doc__ = _('Applies ID view to specified hosts or current members of '
+                'specified hostgroups. If any other ID view is applied to '
+                'the host, it is overriden.')
+
+    member_count_out = (_('ID view applied to %i host.'),
+                        _('ID view applied to %i hosts.'))
+
+    msg_summary = 'Applied ID view "%(value)s"'
+
+    takes_options = (
+        Str('host*',
+            cli_name='hosts',
+            doc=_('Hosts to apply the ID view to.'),
+            label=_('hosts'),
+        ),
+        Str('hostgroup*',
+            cli_name='hostgroups',
+            doc=_('Hostgroups to whose hosts apply the ID view to. Please note '
+                  'that view is not applied automatically to any hosts added '
+                  'to the hostgroup after running idview-apply command.'),
+            label=_('hostgroups'),
+        ),
+    )
+
+    has_output = (
+        output.summary,
+        output.Output('succeeded',
+            type=dict,
+            doc=_('Hosts that ID view was applied to.'),
+        ),
+        output.Output('failed',
+            type=dict,
+            doc=_('Hosts or hostgroups that ID view could not be applied to.'),
+        ),
+        output.Output('completed',
+            type=int,
+            doc=_('Number of hosts the ID view was applied to:'),
+        ),
+    )
+
+    has_output_params = global_output_params
+
+    def execute(self, *keys, **options):
+        view = keys[-1] if keys else None
+
+        # Test if idview actually exists, if it does not, NotFound is raised
+        if not options.get('clear_view', False):
+            result = self.api.Command['idview_show'](view)['result']
+            view_dn = result['dn']
+            assert isinstance(view_dn, DN)
+        else:
+            # In case we are removing assigned view, we modify the host setting
+            # the ipaAssignedIDView to None
+            view_dn = None
+
+        completed = 0
+        succeeded = {'host': []}
+        failed = {
+            'host': [],
+            'hostgroup': [],
+            }
+
+        # Generate a list of all hosts to apply the view to
+        hosts_to_apply = list(options.get('host', []))
+
+        for hostgroup in options.get('hostgroup', ()):
+            try:
+                hosts_to_apply += get_complete_hostgroup_member_list(hostgroup)
+            except errors.NotFound:
+                failed['hostgroup'].append((hostgroup, "not found"))
+            except errors.PublicError as e:
+                failed['hostgroup'].append((hostgroup, str(e)))
+
+        for host in hosts_to_apply:
+            try:
+                options = {'ipaassignedidview': view_dn,
+                           'no_members': True}
+
+                # Run the command through _exc_wrapper so that any exception
+                # callbacks defined can be called
+                result = self._exc_wrapper(
+                    keys,
+                    options,
+                    self.api.Command['host_mod'])(host, **options)
+
+                # If no exception was raised, view assigment went well
+                completed = completed + 1
+                succeeded['host'].append(host)
+            except errors.EmptyModlist:
+                # If view was already applied, do not complain
+                pass
+            except errors.NotFound:
+                failed['host'].append((host, "not found"))
+            except errors.PublicError as e:
+                failed['host'].append((host, str(e)))
+
+        # Wrap dictionary containing failures in another dictionary under key
+        # 'memberhost', since that is output parameter in global_output_params
+        # and thus we get nice output in the CLI
+        failed = {'memberhost': failed}
+
+        # Sort the list of affected hosts
+        succeeded['host'].sort()
+
+        # Provide a fallback message if no host was affected
+        if not succeeded['host']:
+            succeeded['host'] = unicode(_('No host was affected.'))
+
+        # Note that we're returning the list of affected hosts even if they
+        # were passed via referencing a hostgroup. This is desired, since we
+        # want to stress the fact that view is applied on all the current
+        # member hosts of the hostgroup and not tied with the hostgroup itself.
+
+        return dict(
+            summary=unicode(_(self.msg_summary % {'value': view})),
+            succeeded=succeeded,
+            completed=completed,
+            failed=failed,
+        )
+
+
+@register()
+class idview_unapply(idview_apply):
+    __doc__ = _('Clears ID view from specified hosts or current members of '
+                'specified hostgroups.')
+
+    member_count_out = (_('ID view cleared from %i host.'),
+                        _('ID view cleared from %i hosts.'))
+
+    msg_summary = 'Cleared ID views'
+
+    takes_options = (
+        Str('host*',
+            cli_name='hosts',
+            doc=_('Hosts to clear (any) ID view from.'),
+            label=_('hosts'),
+        ),
+        Str('hostgroup*',
+            cli_name='hostgroups',
+            doc=_('Hostgroups whose hosts should have ID views cleared. Note '
+                  'that view is not cleared automatically from any host added '
+                  'to the hostgroup after running idview-unapply command.'),
+            label=_('hostgroups'),
+        ),
+    )
+
+    has_output = (
+        output.summary,
+        output.Output('succeeded',
+            type=dict,
+            doc=_('Hosts that ID view was cleared from.'),
+        ),
+        output.Output('failed',
+            type=dict,
+            doc=_('Hosts or hostgroups that ID view could not be cleared '
+                  'from.'),
+        ),
+        output.Output('completed',
+            type=int,
+            doc=_('Number of hosts that had a ID view was unset:'),
+        ),
+    )
+
+    # Take no arguments, since ID View reference is not needed to clear
+    # the hosts
+    def get_args(self):
+        return ()
+
+    def execute(self, *keys, **options):
+        options['clear_view'] = True
+        return super(idview_unapply, self).execute(*keys, **options)
+
+
+@register()
 class idoverride(LDAPObject):
     """
     ID override object.
-- 
1.9.3

>From c5f75cc65e00dedd73ebb91d9a628998164942af Mon Sep 17 00:00:00 2001
From: Tomas Babej <tba...@redhat.com>
Date: Thu, 31 Jul 2014 14:22:59 +0200
Subject: [PATCH] idviews: Extend idview-show command to display assigned
 idoverrides and hosts

Part of: https://fedorahosted.org/freeipa/ticket/3979
---
 API.txt                   |  3 +-
 ipalib/plugins/idviews.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 81 insertions(+), 2 deletions(-)

diff --git a/API.txt b/API.txt
index 8f1b5364de8d5865990755fdbafb0c9bc6404fc8..cb619411255c293311b84a760d3cfc27767fb224 100644
--- a/API.txt
+++ b/API.txt
@@ -2247,11 +2247,12 @@ output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDA
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: idview_show
-args: 1,4,3
+args: 1,5,3
 arg: Str('cn', attribute=True, cli_name='name', multivalue=False, 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: Flag('show_hosts?', autofill=True, cli_name='show_hosts', 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)
diff --git a/ipalib/plugins/idviews.py b/ipalib/plugins/idviews.py
index 3739e5637f5119668f9847a5d20e74c2235a3e8d..47fcfdf175f19d16738f81e28b81b7662b80ae64 100644
--- a/ipalib/plugins/idviews.py
+++ b/ipalib/plugins/idviews.py
@@ -22,7 +22,7 @@ from ipalib.plugins.baseldap import (LDAPQuery, LDAPObject, LDAPCreate,
                                      LDAPDelete, LDAPUpdate, LDAPSearch,
                                      LDAPRetrieve, global_output_params)
 from ipalib.plugins.hostgroup import get_complete_hostgroup_member_list
-from ipalib import api, Str, Int, _, ngettext, errors, output
+from ipalib import api, Str, Int, Flag, _, ngettext, errors, output
 from ipalib.plugable import Registry
 
 from ipapython.dn import DN
@@ -108,6 +108,84 @@ class idview_find(LDAPSearch):
 class idview_show(LDAPRetrieve):
     __doc__ = _('Display information about an ID view.')
 
+    takes_options = LDAPRetrieve.takes_options + (
+        Flag('show_hosts?',
+             cli_name='show_hosts',
+             doc=_('Enumerate all the hosts the view applies to.'),
+        ),
+    )
+
+    has_output_params = global_output_params + (
+        Str('userviews',
+            label=_('User object overrides'),
+            ),
+        Str('groupviews',
+            label=_('Group object overrides'),
+            ),
+        Str('applicablehosts',
+            label=_('Hosts the view applies to')
+        ),
+    )
+
+    def show_id_overrides(self, dn, entry_attrs):
+        ldap = self.obj.backend
+
+        try:
+            (userviews, truncated) = ldap.find_entries(
+                filter="objectclass=ipaUserOverride",
+                attrs_list=['cn'],
+                base_dn=dn,
+                scope=ldap.SCOPE_ONELEVEL,
+                paged_search=True)
+
+            entry_attrs['userviews'] = [view.single_value.get('cn')
+                                        for view in userviews]
+        except errors.NotFound:
+            pass
+
+        try:
+            (groupviews, truncated) = ldap.find_entries(
+                filter="objectclass=ipaGroupOverride",
+                attrs_list=['cn'],
+                base_dn=dn,
+                scope=ldap.SCOPE_ONELEVEL,
+                paged_search=True)
+
+            entry_attrs['groupviews'] = [view.single_value.get('cn')
+                                         for view in groupviews]
+        except errors.NotFound:
+            pass
+
+    def enumerate_hosts(self, dn, entry_attrs):
+        ldap = self.obj.backend
+
+        filter_params = {
+            'ipaAssignedIDView': dn,
+            'objectClass': 'ipaHost',
+        }
+
+        try:
+            (hosts, truncated) = ldap.find_entries(
+                filter=ldap.make_filter(filter_params, rules=ldap.MATCH_ALL),
+                attrs_list=['cn'],
+                base_dn=api.env.container_host + api.env.basedn,
+                scope=ldap.SCOPE_ONELEVEL,
+                paged_search=True)
+
+            entry_attrs['applicablehosts'] = [host.single_value.get('cn')
+                                              for host in hosts]
+        except errors.NotFound:
+            pass
+
+    def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+        assert isinstance(dn, DN)
+        self.show_id_overrides(dn, entry_attrs)
+
+        if options.get('show_hosts', False):
+            self.enumerate_hosts(dn, entry_attrs)
+
+        return dn
+
 
 @register()
 class idview_apply(LDAPQuery):
-- 
1.9.3

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to