On Срд, 25 кас 2023, Ales Rozmarin via FreeIPA-users wrote:
Hi Alexander,

I create objectClasses named  'testHost' which have attribute 'host'.

As I mention up, I also test to create new attribute like this

attributeTypes: ( 2.25.36.1.2.3.4.5.1
   NAME 'customhost'
   DESC 'A hostname or identifier for a Custom host'
   EQUALITY caseIgnoreMatch
   SUBSTR caseIgnoreSubstringsMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
   SINGLE-VALUE
   X-ORIGIN 'Extending FreeIPA')
#
###############################################################################
#
objectClasses: ( 2.25.36.1.2.3.4.5.2
   NAME 'testHost'
   DESC 'An object class for Custom hosts'
   SUP person
   STRUCTURAL
   MAY (customhost)
   X-ORIGIN 'Extending FreeIPA' )

and not use default one:

attributeTypes: ( 0.9.2342.19200300.100.1.9 NAME 'host'  EQUALITY caseIgnoreMa
tch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-O
RIGIN 'RFC 4524' )

I get only error when I use default attribute host it says, invalid
'ipauserobjectclasses': user default attribute host would not be
allowed!

And that is correct because you don't have an object class that would
allow having that attribute in the list of default ones.


That why I'm not sure if I'm doing something wrong or is some
restriction that we can't display attribute 'host' in User Web UI.

You have two separate tasks here. First, if attribute exists in the
entry, it needs to be displayed. To display it, it needs to be fetched
from the LDAP entry, so the attribute needs to be specified in a list of
attributes to be fetched. Second, for storing the attribute value if it
doesn't exist, it needs to be added. For the latter you'd need an
objectclass in the entry to allow attribute to be saved.

Attributes may be present at creation time or added afterwards, based on
some conditions. Retrieval can be done differently depending on a logic.
For a basic scenario where you'd always want to retrieve an attribute
value if that one exists, you'd add the attribute name to the object'sa
'default_attributes' list.


'user' class is derived from the 'baseuser' -- there are two user
classes in IPA, one used for normal users and one used for staged users,
so we have common definitions in 'baseuser' and then inherit those by
the actual objects:

class baseuser(LDAPObject):
    """
    baseuser object.
    """
....
    object_class_config = 'ipauserobjectclasses'
    possible_objectclasses = [
        'meporiginentry', 'ipauserauthtypeclass', 'ipauser',
        'ipatokenradiusproxyuser', 'ipacertmapobject',
        'ipantuserattrs', 'ipaidpuser', 'ipapasskeyuser',
    ]
....
    default_attributes = [
        'uid', 'givenname', 'sn', 'homedirectory', 'loginshell',
        'uidnumber', 'gidnumber', 'mail', 'ou',
        'telephonenumber', 'title', 'memberof', 'nsaccountlock',
        'memberofindirect', 'ipauserauthtype', 'userclass',
        'ipatokenradiusconfiglink', 'ipatokenradiususername',
        'ipaidpconfiglink', 'ipaidpsub',
        'krbprincipalexpiration', 'usercertificate;binary',
        'krbprincipalname', 'krbcanonicalname',
        'ipacertmapdata', 'ipantlogonscript', 'ipantprofilepath',
        'ipanthomedirectory', 'ipanthomedirectorydrive',
        'ipapasskey',
    ]

The error you've got about an attribute would not be allowed comes from
'ipa config-mod' where you tried to modify list of attributes that
would be added to every single user object upon creation. That code
validates presence of the attribute against user class'
possible_objectclasses:

        for (attr, obj) in (('ipauserobjectclasses', 'user'),
                            ('ipagroupobjectclasses', 'group')):
            if attr in entry_attrs:
                if not entry_attrs[attr]:
                    raise errors.ValidationError(name=attr,
                        error=_('May not be empty'))
                objectclasses = list(set(entry_attrs[attr]).union(
                        self.api.Object[obj].possible_objectclasses))
                new_allowed_attrs = ldap.get_allowed_attributes(objectclasses,
                                        raise_on_unknown=True)
                checked_attrs = self.api.Object[obj].default_attributes

....
                for obj_attr in checked_attrs:
....
                    if obj_attr.lower() not in new_allowed_attrs:
                        raise errors.ValidationError(name=attr,
                                error=_('%(obj)s default attribute %(attr)s 
would not be allowed!') \
                                % dict(obj=obj, attr=obj_attr))


So in your case a plugin would need to declare both the attribute and
the objectclass in those lists:

------------------------
from ipaserver.user import user

user.default_attributes += ['customhost']
user.possible_objectclasses += ['testhost']
------------------------

Adding attributes and object classes to the lists maintained by 'ipa
config-mod' will only work for cases where you always have a value
assigned to those attributes. 'ipa config-mod' maintains those lists for
creating user and group objects, e.g. which object classes must be
present in every user and group created by IPA API.

Your client-side code would then need to always supply one or have a
default value that would be used by the parameter. This is not always
desirable.

For example, your parameter definition does not have a default value (I
corrected the attribute name):

user.takes_params += (
    Str('customhost*',
    cli_name='host',
    label=_('Test host'),
    ),
)

It means that you would not be able to use 'customhost' as a default
one through 'ipa config-mod' because nothing would put a default value
to this attribute in both Web UI (unless your plugin always adds one)
and in IPA API (unless you have a client-side plugin that does so).

Instead of adding the attribute to the 'ipa config-mod', you should
consider whether this attribute is indeed should always be present. If
it is, then you should think about a default value. If there should be
no default value, then don't add it to the default list.

When an attribute is sent by the client side (be it web UI or just some
IPA API caller), the server side should also add a corresponding object
class in a pre-callback. This is done automatically for
object.possible_objectclasses list.

You don't see that in 
https://github.com/abbra/freeipa-userstatus-plugin/blob/master/plugin/ipaserver/plugins/user-inetuserstatus.py
from where you started with your sample plugin because those attributes
are already allowed by existing IPA object classes in the user entry.

For a conditional add of the objectclasses we have
baseldap.add_missing_object_class method. For example, for users we do
add objectclasses depending on what additional attributes were set in
the entry. This is done with a callback that is commonly called by the
internal code. External plugins can define their own pre-callback and do
similar actions:

class baseuser_add(LDAPCreate):
    """
    Prototype command plugin to be implemented by real plugin
    """
    def pre_common_callback(self, ldap, dn, entry_attrs, attrs_list, *keys,
                            **options):
        assert isinstance(dn, DN)
        set_krbcanonicalname(entry_attrs)
        self.obj.convert_usercertificate_pre(entry_attrs)
        if entry_attrs.get('ipatokenradiususername', None):
            add_missing_object_class(ldap, u'ipatokenradiusproxyuser', dn,
                                     entry_attrs, update=False)
        if entry_attrs.get('ipauserauthtype', None):
            add_missing_object_class(ldap, u'ipauserauthtypeclass', dn,
                                     entry_attrs, update=False)
        if (
            entry_attrs.get('ipaidpconfiglink', None)
            or entry_attrs.get('ipaidpsub', None)
        ):
            add_missing_object_class(ldap, 'ipaidpuser', dn,
                                     entry_attrs, update=False)

I'd recommend looking at existing plugins when implementing a new one.

--
/ Alexander Bokovoy
Sr. Principal Software Engineer
Security / Identity Management Engineering
Red Hat Limited, Finland
_______________________________________________
FreeIPA-users mailing list -- freeipa-users@lists.fedorahosted.org
To unsubscribe send an email to freeipa-users-le...@lists.fedorahosted.org
Fedora Code of Conduct: 
https://docs.fedoraproject.org/en-US/project/code-of-conduct/
List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines
List Archives: 
https://lists.fedorahosted.org/archives/list/freeipa-users@lists.fedorahosted.org
Do not reply to spam, report it: 
https://pagure.io/fedora-infrastructure/new_issue

Reply via email to