Resource Users

The Users resource is represented in the LDAP tree as user objects.

To list those LDAP objects run in a terminal:

FILTER='(|(objectClass=ucsschoolStaff)(objectClass=ucsschoolStudent)(objectClass=ucsschoolTeacher))'
univention-ldapsearch -LLL "$FILTER"

UCS@school uses the UDM to access the LDAP directory. UDM properties have different names than their associated LDAP attributes. Their values may also differ. To list the same UDM objects as above, run:

$ FILTER='(|(objectClass=ucsschoolStaff)(objectClass=ucsschoolStudent)(objectClass=ucsschoolTeacher))'
$ udm users/user list --filter "$FILTER"

Kelvin API documentation

Please see the Kelvin API documentation section Resource Users about allowed values for the attributes.

User class

The ucsschool.kelvin.client.User class has the following public attributes and methods:

class User(KelvinObject):
    def __init__(
        self,
        name: str = None,
        school: str = None,
        *,
        firstname: str = None,
        lastname: str = None,
        birthday: datetime.date = None,
        disabled: bool = False,
        email: str = None,
        expiration_date: datetime.date = None,
        kelvin_password_hashes: PasswordsHashes = None,
        password: str = None,
        record_uid: str = None,
        roles: List[str],
        schools: List[str],
        school_classes: Dict[str, List[str]] = None,
        workgroups: Dict[str, List[str]] = None,
        source_uid: str = None,
        udm_properties: Dict[str, Any] = None,
        ucsschool_roles: List[str] = None,
        dn: str = None,
        url: str = None,
        session: Session = None,
        language: str = None,
        **kwargs,
    ):
    self.name = name
    self.school = school
    self.firstname = firstname
    self.lastname = lastname
    self.birthday = birthday
    self.disabled = disabled
    self.email = email
    self.expiration_date = expiration_date
    self.kelvin_password_hashes = kelvin_password_hashes
    self.password = password
    self.record_uid = record_uid
    self.roles = roles
    self.schools = schools
    self.school_classes = school_classes or {}
    self.workgroups = workgroups or {}
    self.source_uid = source_uid
    self.udm_properties = udm_properties or {}
    self.ucsschool_roles = ucsschool_roles
    self.dn = dn
    self.url = url
    self.session = session
    if language:
        self.session.language = language

    async def reload(self) -> User:
        ...
    async def save(self) -> User:
        ...
    async def delete(self) -> None:
        ...
    def as_dict(self) -> Dict[str, Any]:
        ...

Note

The field expiration_date was added to the Kelvin REST API in version 1.5.1. The client works with prior server versions, but the attribute will not be read or set.

Note

Since the Kelvin REST API client version 2.0.0, the required argument school has the default argument None. The argument name is not required anymore.

UserResource class

ucsschool.kelvin.client.UserResource class has the following public attributes and methods:

class UserResource(KelvinResource):
    def __init__(self, session: Session, language: str = None):
        ...
    async def get(self, **kwargs) -> User:
        ...
    async def get_from_url(self, url: str) -> User:
        ...
    async def search(self, **kwargs) -> AsyncIterator[User]:
        ...

Create user

from ucsschool.kelvin.client import Session, User

async with Session(**credentials) as session:
    user = User(
        school="DEMOSCHOOL",
        schools=["DEMOSCHOOL"],
        roles=["student"],
        name="test1",
        firstname="test",
        lastname="one",
        record_uid="test1",
        source_uid="TESTID",
        session=session
    )
    await user.save()

user.dn
'uid=test1,cn=schueler,cn=users,ou=DEMOSCHOOL,dc=example,dc=com'

Note

Since version 2.0.0, all attributes except school, schools and roles can be automatically generated on the server by defining a schema. If a schema is defined for an attribute, it can be skipped. The attributes name and record_uid have to be passed in either the constructor or a schema must exist. You can find more about schemas in the [UCS@school - Handbuch zur CLI-Import Schnittstelle (german only)](https://docs.software-univention.de/ucsschool-import/5.0/de/configuration/scheme-formatting.html#formatierungsschema).

Retrieve user

from ucsschool.kelvin.client import Session, UserResource

async with Session(**credentials) as session:
    user = await UserResource(session=session).get(name="test1")

user.as_dict()

{'name': 'test1',
 'ucsschool_roles': ['student:school:DEMOSCHOOL'],
 'school': 'DEMOSCHOOL',
 'firstname': 'test',
 'lastname': 'one',
 'birthday': None,
 'disabled': False,
 'email': None,
 'expiration_date': None,
 'kelvin_password_hashes': None,
 'password': None,
 'record_uid': 'test1',
 'roles': ['student'],
 'schools': ['DEMOSCHOOL'],
 'school_classes': {},
 'workgroups': {},
 'source_uid': 'TESTID',
 'udm_properties': {},
 'dn': 'uid=test1,cn=schueler,cn=users,ou=DEMOSCHOOL,dc=example,dc=com',
 'url': 'https://master.ucs.local/ucsschool/kelvin/v1/users/test1'}

Check if user exists

from ucsschool.kelvin.client import Session, UserResource

async with Session(**credentials) as session:
    if await UserResource(session=session).exists(name="test1"):
        print("The user exists!")

Search users

The search() method allows searching for users, using a number of filters. Most (but now all) attributes support searching inexact, using an asterisk (*) as placeholder.

In the following examples the search is always limited to users of the school DEMOSCHOOL. In the 1. search all users (of the school DEMOSCHOOL) are searched, 2. users with a username starting with t, 3. users with a family name starting with tea and 4. users that have the role teacher.

from ucsschool.kelvin.client import Session, UserResource

async with Session(**credentials) as session:
    async for user in UserResource(session=session).search(school="DEMOSCHOOL"):
        print(user)

User('name'='demo_admin', dn='uid=demo_admin,cn=lehrer,cn=users,ou=DEMOSCHOOL,dc=example,dc=com')
User('name'='demo_student', dn='uid=demo_student,cn=schueler,cn=users,ou=DEMOSCHOOL,dc=example,dc=com')
User('name'='demo_teacher', dn='uid=demo_teacher,cn=lehrer,cn=users,ou=DEMOSCHOOL,dc=example,dc=com')
User('name'='test1', dn='uid=test1,cn=schueler,cn=users,ou=DEMOSCHOOL,dc=example,dc=com')

    async for user in UserResource(session=session).search(
        name="t*", school="DEMOSCHOOL"
    ):
        print(user)

User('name'='test1', dn='uid=test1,cn=schueler,cn=users,ou=DEMOSCHOOL,dc=example,dc=com')

    async for user in UserResource(session=session).search(
        lastname="tea*", school="DEMOSCHOOL"
    ):
        print(user)

User('name'='demo_teacher', dn='uid=demo_teacher,cn=lehrer,cn=users,ou=DEMOSCHOOL,dc=example,dc=com')

    async for user in UserResource(session=session).search(
        roles=["teacher"], school="DEMOSCHOOL"
    ):
        print(user)

User('name'='demo_admin', dn='uid=demo_admin,cn=lehrer,cn=users,ou=DEMOSCHOOL,dc=example,dc=com')
User('name'='demo_teacher', dn='uid=demo_teacher,cn=lehrer,cn=users,ou=DEMOSCHOOL,dc=example,dc=com')

Change user properties

Get the current user object, change some attributes and save the changes back to LDAP:

from ucsschool.kelvin.client import Session, User, UserResource

async def change_properties(username: str, **changes) -> User:
    async with Session(**credentials) as session:
        user = await UserResource(session=session).get(name=username)
        for property, value in changes.items():
            setattr(user, property, value)
        return await user.save()

user = await change_properties(
    "test1",
    firstname="newfn",
    lastname="newln",
    password="password123",
)
assert user.firstname == "newfn"
assert user.lastname == "newln"

Hint: users cannot be modified, unless their record_uid and source_uid attributes are set (as is the case with the demo_* users).

Move user

User objects support changing both school and name.

When the school attribute of a user is changed, the new value must be part of the list in the schools attribute.

In the following example both school and name are changed.

from ucsschool.kelvin.client import Session, User, UserResource

async with Session(**credentials) as session:
    user = User(
        school="DEMOSCHOOL", schools=["DEMOSCHOOL"],
        roles=["student"], name="test1", firstname="test",
        lastname="one", record_uid="test1",
        source_uid="TESTID", session=session
    )
    await user.save()
    user.dn
    'uid=test1,cn=schueler,cn=users,ou=DEMOSCHOOL,dc=example,dc=com'
    user.name = "test2"
    user.school = "DEMOSCHOOL2"
    user.schools = ["DEMOSCHOOL2"]
    await user.save()
    user.dn
    'uid=test2,cn=schueler,cn=users,ou=DEMOSCHOOL2,dc=example,dc=com'

Delete user

Get the current user object and delete it:

from ucsschool.kelvin.client import Session, User, UserResource

async with Session(**credentials) as session:
    user = await UserResource(session=session).get(name="test1")
    await user.delete()

Trying to retrieve the deleted user will raise a ucsschool.kelvin.client.NoObject exception.