On 05/14/2015 11:48 AM, Jan Cholasta wrote:
Hi,

Dne 14.5.2015 v 11:00 Tomas Babej napsal(a):
Hi,

this patch implements the domain level feature.

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

Tomas

1)

+# Create entry proclaiming Domain Level support of this master
+# This will update the supported Domain Levels during upgrade
+dn: cn=Domain Level support,cn=$FQDN,cn=masters,cn=ipa,cn=etc,$SUFFIX
+default: objectClass: top
+default: objectClass: nsContainer
+default: objectClass: ipaConfigObject
+default: objectClass: ipaSupportedDomainLevelConfig
+only: ipaMinDomainLevel: $MIN_DOMAIN_LEVEL
+only: ipaMaxDomainLevel: $MAX_DOMAIN_LEVEL

The design states that supported domain levels should be stored directly in cn=$FQDN,cn=masters,cn=ipa,cn=etc,$SUFFIX and I agree with that - there is no reason to have this information in a separate entry.

I agree, this is an error on my part - I read the design as stored in entry under cn=$FQDN,cn=masters, not in the entry itself.



2) I though we agreed to call the command domainlevel-set instead of domainlevel-raise: <https://www.redhat.com/archives/freeipa-devel/2015-May/msg00101.html>.

Fixed.



3) Domain level is just a single integer and it should be treated as such, there's no need for an LDAPObject plugin and other unnecessary complexities. The implemetation could be as simple as (from top of my head, untested):

That's right, I also considered this approach, but as far as I know you do not get the permission handling for the global DomainLevel entry otherwise.

Ludwig, I changed the path for the global entry to cn=DomainLevel.

Updated patch attached.

Tomas
From d66b08f923b150977dc88da507443d393ce82fd1 Mon Sep 17 00:00:00 2001
From: Tomas Babej <tba...@redhat.com>
Date: Thu, 14 May 2015 10:49:55 +0200
Subject: [PATCH] Add Domain Level feature

https://fedorahosted.org/freeipa/ticket/5018
---
 ACI.txt                                |   2 +
 API.txt                                |  22 ++++++
 install/share/72domainlevels.ldif      |   6 ++
 install/share/Makefile.am              |   1 +
 install/updates/72-domainlevels.update |  13 ++++
 install/updates/Makefile.am            |   1 +
 ipalib/constants.py                    |   3 +
 ipalib/errors.py                       |  16 ++++
 ipalib/plugins/domainlevel.py          | 137 +++++++++++++++++++++++++++++++++
 ipaserver/install/dsinstance.py        |   1 +
 ipaserver/install/ldapupdate.py        |   5 ++
 11 files changed, 207 insertions(+)
 create mode 100644 install/share/72domainlevels.ldif
 create mode 100644 install/updates/72-domainlevels.update
 create mode 100644 ipalib/plugins/domainlevel.py

diff --git a/ACI.txt b/ACI.txt
index 933b57cb93e833981867953b516a67484f13dca3..6791e0c8d813ba22357960bc3b34911802513f2c 100644
--- a/ACI.txt
+++ b/ACI.txt
@@ -50,6 +50,8 @@ dn: dc=ipa,dc=example
 aci: (target = "ldap:///idnsname=*,cn=dns,dc=ipa,dc=example";)(version 3.0;acl "permission:System: Remove DNS Entries";allow (delete) groupdn = "ldap:///cn=System: Remove DNS Entries,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: dc=ipa,dc=example
 aci: (targetattr = "a6record || aaaarecord || afsdbrecord || arecord || certrecord || cn || cnamerecord || dlvrecord || dnamerecord || dnsclass || dnsttl || dsrecord || hinforecord || idnsallowdynupdate || idnsallowquery || idnsallowsyncptr || idnsallowtransfer || idnsforwarders || idnsforwardpolicy || idnsname || idnssecinlinesigning || idnssoaexpire || idnssoaminimum || idnssoamname || idnssoarefresh || idnssoaretry || idnssoarname || idnssoaserial || idnsupdatepolicy || idnszoneactive || keyrecord || kxrecord || locrecord || managedby || mdrecord || minforecord || mxrecord || naptrrecord || nsec3paramrecord || nsecrecord || nsrecord || nxtrecord || ptrrecord || rrsigrecord || sigrecord || srvrecord || sshfprecord || tlsarecord || txtrecord")(target = "ldap:///idnsname=*,cn=dns,dc=ipa,dc=example";)(version 3.0;acl "permission:System: Update DNS Entries";allow (write) groupdn = "ldap:///cn=System: Update DNS Entries,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: dc=ipa,dc=example
+aci: (targetattr = "createtimestamp || entryusn || ipadomainlevel || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipadomainlevelconfig)")(version 3.0;acl "permission:System: Read Domain Level";allow (compare,read,search) userdn = "ldap:///all";;)
 dn: cn=groups,cn=accounts,dc=ipa,dc=example
 aci: (targetfilter = "(|(objectclass=ipausergroup)(objectclass=posixgroup))")(version 3.0;acl "permission:System: Add Groups";allow (add) groupdn = "ldap:///cn=System: Add Groups,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=groups,cn=accounts,dc=ipa,dc=example
diff --git a/API.txt b/API.txt
index 346e35fda536f8411f8ea8f2dc32af4caebf3fca..df7d9edbf4c9285ba2d93bc72848b903b28d0fa8 100644
--- a/API.txt
+++ b/API.txt
@@ -1283,6 +1283,28 @@ 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: domainlevel_set
+args: 0,8,3
+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: Int('ipadomainlevel', attribute=True, autofill=False, cli_name='level', minvalue=0, 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: domainlevel_show
+args: 0,4,3
+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: env
 args: 1,3,4
 arg: Str('variables*')
diff --git a/install/share/72domainlevels.ldif b/install/share/72domainlevels.ldif
new file mode 100644
index 0000000000000000000000000000000000000000..8473eebb47e1a77fa476f4a7eb1bc04b98fa0813
--- /dev/null
+++ b/install/share/72domainlevels.ldif
@@ -0,0 +1,6 @@
+dn: cn=schema
+attributeTypes: (2.16.840.1.113730.3.8.19.2.1 NAME 'ipaDomainLevel' DESC 'Domain Level value' EQUALITY numericStringMatch ORDERING caseIgnoreOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 SINGLE-VALUE X-ORIGIN 'IPA v4')
+attributeTypes: (2.16.840.1.113730.3.8.19.2.2 NAME 'ipaMinDomainLevel' DESC 'Minimal supported Domain Level value' EQUALITY numericStringMatch ORDERING caseIgnoreOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 SINGLE-VALUE X-ORIGIN 'IPA v4')
+attributeTypes: (2.16.840.1.113730.3.8.19.2.3 NAME 'ipaMaxDomainLevel' DESC 'Maximal supported Domain Level value' EQUALITY numericStringMatch ORDERING caseIgnoreOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 SINGLE-VALUE X-ORIGIN 'IPA v4')
+objectClasses:  (2.16.840.1.113730.3.8.19.1.1  NAME 'ipaDomainLevelConfig' SUP ipaConfigObject AUXILIARY DESC 'Domain Level Configuration' MUST (ipaDomainLevel) X-ORIGIN 'IPA v4')
+objectClasses:  (2.16.840.1.113730.3.8.19.1.2  NAME 'ipaSupportedDomainLevelConfig' SUP ipaConfigObject AUXILIARY DESC 'Supported Domain Level Configuration' MUST (ipaMinDomainLevel $ ipaMaxDomainLevel) X-ORIGIN 'IPA v4')
diff --git a/install/share/Makefile.am b/install/share/Makefile.am
index ca6128e2911ab5c0a773dd553f8e67eab944f120..ac4538ba9547243c28d27a196f943b5bc2bdcd93 100644
--- a/install/share/Makefile.am
+++ b/install/share/Makefile.am
@@ -21,6 +21,7 @@ app_DATA =				\
 	65ipasudo.ldif			\
 	70ipaotp.ldif			\
 	71idviews.ldif			\
+	72domainlevels.ldif			\
 	anonymous-vlv.ldif		\
 	bootstrap-template.ldif		\
 	caJarSigningCert.cfg.template	\
diff --git a/install/updates/72-domainlevels.update b/install/updates/72-domainlevels.update
new file mode 100644
index 0000000000000000000000000000000000000000..b3e14a0d3e35d1ef1a9aa9604ccb29b5b8a36f89
--- /dev/null
+++ b/install/updates/72-domainlevels.update
@@ -0,0 +1,13 @@
+# Create default Domain Level entry if it does not exist
+dn: cn=DomainLevel,cn=etc,$SUFFIX
+default: objectClass: top
+default: objectClass: nsContainer
+default: objectClass: ipaDomainLevelConfig
+default: ipaDomainLevel: 0
+
+# Create entry proclaiming Domain Level support of this master
+# This will update the supported Domain Levels during upgrade
+dn: cn=$FQDN,cn=masters,cn=ipa,cn=etc,$SUFFIX
+default: objectClass: ipaSupportedDomainLevelConfig
+only: ipaMinDomainLevel: $MIN_DOMAIN_LEVEL
+only: ipaMaxDomainLevel: $MAX_DOMAIN_LEVEL
diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am
index 0d63d9ea8d85f1add5f036e7a39f89543586d33b..b52dc2570040336031b7fe01c1cec50156bd90c3 100644
--- a/install/updates/Makefile.am
+++ b/install/updates/Makefile.am
@@ -48,6 +48,7 @@ app_DATA =				\
 	61-trusts-s4u2proxy.update	\
 	62-ranges.update		\
 	71-idviews.update		\
+	72-domainlevels.update		\
 	90-post_upgrade_plugins.update	\
 	$(NULL)
 
diff --git a/ipalib/constants.py b/ipalib/constants.py
index f1e14702ffdf5a3bd23a62b1fdd2ee3cd95d84f8..04e29d25b1b41dbb67c23d02b4a534ec1735e44c 100644
--- a/ipalib/constants.py
+++ b/ipalib/constants.py
@@ -223,3 +223,6 @@ LDAP_GENERALIZED_TIME_FORMAT = "%Y%m%d%H%M%SZ"
 
 IPA_ANCHOR_PREFIX = ':IPA:'
 SID_ANCHOR_PREFIX = ':SID:'
+
+MIN_DOMAIN_LEVEL = 0
+MAX_DOMAIN_LEVEL = 1
diff --git a/ipalib/errors.py b/ipalib/errors.py
index 89b1ef2e0dc1d7346a69fb813bd71990746c620c..63ec22269467b769d276c443f6b3dbed38cd766e 100644
--- a/ipalib/errors.py
+++ b/ipalib/errors.py
@@ -1344,6 +1344,22 @@ class EmptyResult(NotFound):
 
     errno = 4031
 
+class InvalidDomainLevelError(ExecutionError):
+    """
+    **4032** Raised when a operation could not be completed due to a invalid
+             domain level.
+
+    For example:
+
+    >>> raise InvalidDomainLevelError(reason='feature requires domain level 4')
+    Traceback (most recent call last):
+      ...
+    InvalidDomainLevelError: feature requires domain level 4
+
+    """
+
+    errno = 4032
+
 class BuiltinError(ExecutionError):
     """
     **4100** Base class for builtin execution errors (*4100 - 4199*).
diff --git a/ipalib/plugins/domainlevel.py b/ipalib/plugins/domainlevel.py
new file mode 100644
index 0000000000000000000000000000000000000000..5fbba1678e2327f6a47ba095116c3ff532b1e4b7
--- /dev/null
+++ b/ipalib/plugins/domainlevel.py
@@ -0,0 +1,137 @@
+#
+# Copyright (C) 2015  FreeIPA Contributors see COPYING for license
+#
+
+from collections import namedtuple
+
+from ipalib import _
+from ipalib import api
+from ipalib import Command
+from ipalib import errors
+from ipalib import output
+from ipalib.parameters import Int
+from ipalib.plugable import Registry
+from ipalib.plugins.baseldap import LDAPObject, LDAPUpdate, LDAPRetrieve
+
+from ipapython.dn import DN
+
+
+__doc__ = _("""
+Raise the IPA Domain Level.
+""")
+
+register = Registry()
+
+DomainLevelRange = namedtuple('DomainLevelRange', ['min', 'max'])
+
+
+@register()
+class domainlevel(LDAPObject):
+    """
+    IPA Domain Level object
+    """
+
+    object_name = _('domain level')
+    default_attributes = [
+        'ipadomainlevel',
+    ]
+
+    permission_filter_objectclasses = ['ipadomainlevelconfig']
+
+    managed_permissions = {
+        'System: Read Domain Level': {
+            'replaces_global_anonymous_aci': True,
+            'ipapermbindruletype': 'all',
+            'ipapermright': {'read', 'search', 'compare'},
+            'ipapermdefaultattr': {
+                'ipadomainlevel', 'objectclass',
+            },
+        },
+    }
+
+    label = _('Domain Level')
+    label_singular = _('Domain Level')
+
+    takes_params = (
+        Int('ipadomainlevel',
+            cli_name='level',
+            label=_('Domain Level'),
+            minvalue=0,
+        ),
+    )
+
+    def get_dn(self, *keys, **kwargs):
+        return DN(('cn', 'DomainLevel'), ('cn', 'etc'), api.env.basedn)
+
+    def get_domainlevel_range(self, master_entry):
+        try:
+            return DomainLevelRange(
+                int(master_entry['ipaMinDomainLevel'][0]),
+                int(master_entry['ipaMaxDomainLevel'][0])
+            )
+        except KeyError:
+            return DomainLevelRange(0, 0)
+
+    def get_master_entries(self):
+        """
+        Returns list of LDAPEntries representing IPA masters.
+        """
+        ldap = self.backend
+
+        container_masters = DN(
+            ('cn', 'masters'),
+            ('cn', 'ipa'),
+            ('cn', 'etc'),
+            api.env.basedn
+        )
+
+        masters, _ = ldap.find_entries(
+            filter="(cn=*)",
+            base_dn=container_masters,
+            scope=ldap.SCOPE_ONELEVEL,
+            paged_search=True,  # we need to make sure to get all of them
+        )
+
+        return masters
+
+
+@register()
+class domainlevel_set(LDAPUpdate):
+    __doc__ = _('Modify configuration options.')
+
+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+        """
+        Checks all the IPA masters for supported domain level ranges.
+
+        If the desired domain level is within the supported range of all
+        masters, it will be raised.
+
+        Domain level cannot be lowered.
+        """
+
+        assert isinstance(dn, DN)
+
+        current_value = int(ldap.get_entry(dn)['ipadomainlevel'][0])
+        desired_value = int(entry_attrs['ipadomainlevel'])
+
+        # Domain level cannot be lowered
+        if int(desired_value) < int(current_value):
+            message = _("Domain Level cannot be lowered.")
+            raise errors.InvalidDomainLevelError(message)
+
+        # Check if every master supports the desired level
+        for master in self.obj.get_master_entries():
+            supported = self.obj.get_domainlevel_range(master)
+
+            if supported.min > desired_value or supported.max < desired_value:
+                message = _("Domain Level cannot be raised to {0}, server {1} "
+                            "does not support it."
+                            .format(desired_value, master['cn'][0]))
+                raise errors.InvalidDomainLevelError(message)
+
+        return dn
+
+
+@register()
+class domainlevel_show(LDAPRetrieve):
+    __doc__ = _('Show the current domain level.')
diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
index e216edbfa2931c4ec301defc874ccc96b89b7c05..ea90d2b9840741d3cced591aaf03ec5886b78a79 100644
--- a/ipaserver/install/dsinstance.py
+++ b/ipaserver/install/dsinstance.py
@@ -61,6 +61,7 @@ IPA_SCHEMA_FILES = ("60kerberos.ldif",
                     "65ipasudo.ldif",
                     "70ipaotp.ldif",
                     "71idviews.ldif",
+                    "72domainlevels.ldif",
                     "15rfc2307bis.ldif",
                     "15rfc4876.ldif")
 
diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py
index 2ea890efc8b99fe139884811f33ece5d7dc4f949..0283770ccfae3f266ca0d3ca40299b2f8e5d85e4 100644
--- a/ipaserver/install/ldapupdate.py
+++ b/ipaserver/install/ldapupdate.py
@@ -39,6 +39,7 @@ from ipaserver.install import installutils
 from ipapython import ipautil, ipaldap
 from ipalib import errors
 from ipalib import api, create_api
+from ipalib import constants
 from ipaplatform.paths import paths
 from ipaplatform import services
 from ipapython.dn import DN
@@ -305,6 +306,10 @@ class LDAPUpdate:
             self.sub_dict["TIME"] = int(time.time())
         if not self.sub_dict.get("DOMAIN") and domain is not None:
             self.sub_dict["DOMAIN"] = domain
+        if not self.sub_dict.get("MIN_DOMAIN_LEVEL"):
+            self.sub_dict["MIN_DOMAIN_LEVEL"] = str(constants.MIN_DOMAIN_LEVEL)
+        if not self.sub_dict.get("MAX_DOMAIN_LEVEL"):
+            self.sub_dict["MAX_DOMAIN_LEVEL"] = str(constants.MAX_DOMAIN_LEVEL)
         self.api = create_api(mode=None)
         self.api.bootstrap(in_server=True, context='updates')
         self.api.finalize()
-- 
2.1.0

-- 
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