Schema
######

An LDAP server store information about *types* it can handle in its **schema**. The schema includes all information needed by a client to correctly performs
LDAP operations. Let's examine an LDAP server schema::

    >>> server.schema
    DSA Schema from: cn=schema
      Attribute types:{'ipaNTTrustForestTrustInfo': Attribute type: 2.16.840.1.113730.3.8.11.17
      Short name: ipaNTTrustForestTrustInfo
      Description: Forest trust information for a trusted domain object
      Equality rule: octetStringMatch
      Syntax: 1.3.6.1.4.1.1466.115.121.1.40 [('1.3.6.1.4.1.1466.115.121.1.40', 'LDAP_SYNTAX', 'Octet String', 'RFC4517')]
      'ntUserCreateNewAccount': Attribute type: 2.16.840.1.113730.3.1.42
      Short name: ntUserCreateNewAccount
      Description: Netscape defined attribute type
      Single Value: True
      Syntax: 1.3.6.1.4.1.1466.115.121.1.15 [('1.3.6.1.4.1.1466.115.121.1.15', 'LDAP_SYNTAX', 'Directory String', 'RFC4517')]
      Extensions:
        X-ORIGIN: Netscape NT Synchronization
      'passwordGraceUserTime': Attribute type: 2.16.840.1.113730.3.1.998
      Short name: passwordGraceUserTime, pwdGraceUserTime
      Description: Netscape defined password policy attribute type
      Single Value: True
      Usage: Directory operation
      Syntax: 1.3.6.1.4.1.1466.115.121.1.15 [('1.3.6.1.4.1.1466.115.121.1.15', 'LDAP_SYNTAX', 'Directory String', 'RFC4517')]
      Extensions:
        X-ORIGIN: Netscape Directory Server
      'nsslapd-ldapilisten': Attribute type: 2.16.840.1.113730.3.1.2229
      Short name: nsslapd-ldapilisten
      Description: Netscape defined attribute type
      Single Value: True
      Syntax: 1.3.6.1.4.1.1466.115.121.1.15 [('1.3.6.1.4.1.1466.115.121.1.15', 'LDAP_SYNTAX', 'Directory String', 'RFC4517')]
      Extensions:
        X-ORIGIN: Netscape Directory Server
      'bootParameter': Attribute type: 1.3.6.1.1.1.1.23
      Short name: bootParameter
      Description: Standard LDAP attribute type
      Syntax: 1.3.6.1.4.1.1466.115.121.1.26 [('1.3.6.1.4.1.1466.115.121.1.26', 'LDAP_SYNTAX', 'IA5 String', 'RFC4517')]
      Extensions:
        X-ORIGIN: RFC 2307

      <...long list of descriptors...>


The schema is a very long list that describes what kind of data types the LDAP server understands. It also specifies
what attributes can be stored in each class. Some classes are containers for other entries (either container or leaf)
and are used to build the hierarchy of the DIT. Container entries can have attributes too.
One important specification in the schema is if the attribute is *multi-valued* or not. A multi-valued attribute can store one or more values.
Every LDAP server must at least support the standard LDAP3 schema but can have additional custom classes and attributes.
The schema defines also the *syntaxes* and the *matching rules* of the different kind of data types stored in the LDAP.

.. note::
   Object classes and attributes are independent objects. An attribute is not a "child" of a class neither a
   class is a "parent" of any attribute. Classes and attributes are linked in the schema with the ``MAY`` and ``MUST`` options
   of the object class definition that specify what attributes an entry can contain and which of them are mandatory.

.. note::
   There are 3 different types of object classes: **ABSTRACT** (used only when defining the class hierarchy), **STRUCTURAL** (used to
   create concrete entries) and **AUXILIARY** (used to add additional attributes to an entry). Only one structural class can be used
   in an entry, while many auxiliary classes can be added to the same entry. Adding an object class to an entry simply means
   that the attributes defined in that object class can be stored in that entry.

If the ldap3 library is aware of the schema used by the LDAP server it will try to automatically convert data retrieved by the Search
operation to their representation. An integer will be returned as an int, a generalizedDate as a datetime object and so on.
If you don't read the schema all the values are returned as bytes and unicode strings. You can control this behaviour with
the ``get_info`` parameter of the Server object and the ``check_names`` parameter of the Connection object.

The schema can be extended by the user, but the LDAP RFCs don't specify how this operation must be performed, so each LDAP server has its
own method of adding classes and attributes to the schema.

Operational attributes
----------------------
The LDAP server store *operational* information on each entry. This information is used by the internal mechanism of the server and *can* be made
available to the user via an **operational attribute** that can usually be read but not written.

To request all operational attribute in a search you can use the ``+`` (PLUS) character as an attribute name. Keep in mind that the server
may not return some operational attribute if they are not explicitly requested (because they may take a long time or many resources to be computed),
so if you need a specific attribute is better to request it explicitly.

Some server may not return attribute information in the schema. In this case the ldap3 library is not aware of them. This can lead to some erratic
behaviour, especially in the Abstraction Layer of ldap3.

In this case you can tell ldap3 to not check for a specific attribute::

    from ldap3 import get_config_parameter, set_config_parameter
    attrs = get_config_parameter('ATTRIBUTES_EXCLUDED_FROM_CHECK')
    attrs.extend(['memberOf', 'entryUUID', 'pwdChangedTime'])  # # all the missing attributes you need
    set_config_parameter('ATTRIBUTES_EXCLUDED_FROM_CHECK', attrs)

Now the missing attributes can be used in searches.

Then, if you're using the Abstraction Layer you must instruct the ObjectDef to query for those attributes too. For example, if you
want to query *inetOrgPerson* in a Reader Cursor of the Abstraction Layer::

    from ldap3 import Connection, ObjectDef, Reader
    c = Connection('my_server', 'my_user', 'my_password')
    c.bind()
    person = ObjectDef('inetOrgPerson', c)  # read the object class hierarchy schema from the server
    person += ['memberOf', 'entryUUID', 'pwdChangedTime'] # this creates the missing AttrDef in the ObjectDef
    r = Reader(c, person, 'my_base')
    r.serch()

when you query the ``r`` cursor you'll get back the missing attributes too.
