diff --git a/lumi2/ldap.py b/lumi2/ldap.py index 81ce5b0..44bf336 100644 --- a/lumi2/ldap.py +++ b/lumi2/ldap.py @@ -124,7 +124,7 @@ def _assert_is_valid_ou_dn(input_str) -> None: tokens = input_str.split(',') if len(tokens) != 3: - raise InvalidStringFormatException("Expected exactly two ',' characters.") + raise InvalidStringFormatException("Expected exactly three ',' characters.") if not tokens[0].startswith("ou="): raise InvalidStringFormatException("Expected 'ou='.") @@ -140,6 +140,114 @@ def _assert_is_valid_ou_dn(input_str) -> None: f"Expected only alphanumeric characters in organizational unit " \ f"but got: '{char}'." ) + + +def _assert_is_valid_user_dn(input_str) -> None: + """Checks whether the input string is a valid user entry DN. + + A valid user DN is a string of the following format: + 'uid=,ou=,', whereby: + (user's uid) is a substring containing only characters from the + character class [A-Za-z0-9_-.], it has minimum length 1 and must start with a + letter. + (RDN of ou holding users) must be a valid ou name as expected by + _assert_is_valid_ou_dn(). + is a substring representing an LDAP base DN as expected by + _assert_is_valid_base_dn(). + + Parameters + ---------- + input_str : string + The input string to check. + + Returns + ------- + None + + Raises + ------ + TypeError + If input_str is not of type string. + InvalidStringFormatException + If input_str is not a valid LDAP user DN string. + """ + + if not isinstance(input_str, str): + raise TypeError(f"Expected a string but got: {type(input_str)}.") + + tokens = input_str.split(',') + if len(tokens) != 4: + raise InvalidStringFormatException("Expected exactly four ',' characters.") + + if not tokens[0].startswith("uid="): + raise InvalidStringFormatException("Expected 'uid='.") + token_uid = tokens[0][4:] + if not len(token_uid): + raise InvalidStringFormatException("UID cannot be empty.") + valid_uid_chars = ascii_lowercase + ascii_uppercase + digits + "_-." + for char in token_uid: + if not char in valid_uid_chars: + raise InvalidStringFormatException(f"Invalid character in uid: '{char}'.") + if not token_uid[0] in ascii_lowercase + ascii_uppercase: + raise InvalidStringFormatException("UID must start with a letter character.") + + token_base_dn = tokens[2] + "," + tokens[3] + _assert_is_valid_base_dn(token_base_dn) + + token_ou_dn = tokens[1] + "," + token_base_dn + _assert_is_valid_ou_dn(token_ou_dn) + + +def _assert_is_valid_group_dn(input_str) -> None: + """Checks whether the input string is a valid group entry DN. + + A valid group DN is a string of the following format: + 'cn=,ou=,', whereby: + (group name) is a substring containing only characters from the + character class [A-Za-z], and it has minimum length 1. + (RDN of ou holding groups) must be a valid ou name as expected by + _assert_is_valid_ou_dn(). + is a substring representing an LDAP base DN as expected by + _assert_is_valid_base_dn(). + + Parameters + ---------- + input_str : string + The input string to check. + + Returns + ------- + None + + Raises + ------ + TypeError + If input_str is not of type string. + InvalidStringFormatException + If input_str is not a valid LDAP group DN string. + """ + + if not isinstance(input_str, str): + raise TypeError(f"Expected a string but got: {type(input_str)}.") + + tokens = input_str.split(',') + if len(tokens) != 4: + raise InvalidStringFormatException("Expected exactly four ',' characters.") + + if not tokens[0].startswith("cn="): + raise InvalidStringFormatException("Expected 'cn='.") + token_cn = tokens[0][3:] + if not len(token_cn): + raise InvalidStringFormatException("UID cannot be empty.") + for char in token_cn: + if not char in ascii_lowercase + ascii_uppercase: + raise InvalidStringFormatException(f"Invalid character in group cn: '{char}'.") + + token_base_dn = tokens[2] + "," + tokens[3] + _assert_is_valid_base_dn(token_base_dn) + + token_ou_dn = tokens[1] + "," + token_base_dn + _assert_is_valid_ou_dn(token_ou_dn) def _assert_is_valid_user_object_class(input_str) -> None: diff --git a/tests/test_ldap.py b/tests/test_ldap.py index b1d9814..0e88fc5 100644 --- a/tests/test_ldap.py +++ b/tests/test_ldap.py @@ -48,6 +48,57 @@ def test_assert_is_valid_ou_dn(): assert ll._assert_is_valid_ou_dn(valid_string) == None +def test_assert_is_valid_user_dn(): + for invalid_type in [None, 0, True, ("x",), ["x"]]: + with pytest.raises(TypeError): + ll._assert_is_valid_user_dn(invalid_type) + + for invalid_string in [ + "", " ", "uid=foo", "abc", "\u1337", + "uid=,ou=foo,dc=bar,dc=baz", "uid=foo,ou=,dc=bar,dc=baz", + "uid=foo,ou=bar,dc=,dc=baz", "uid=foo,ou=bar,dc=baz,dc=", + "uid=foo,dc=example,dc=com", "uid=1foo,ou=bar,dc=example,dc=com", + "uid=.bob,ou=foo,dc=bar,dc=baz", "uid=_foo,ou=foo,dc=bar,dc=baz", + "uid=foo,uid=bar,ou=bar,dc=com", "cn=foo,ou=bar,dc=example,dc=com", + "uid=abc%,ou=foo,dc=example,dc=com", + ]: + with pytest.raises(ll.InvalidStringFormatException): + print(invalid_string) + ll._assert_is_valid_user_dn(invalid_string) + + for valid_string in [ + "uid=foo,ou=users,dc=example,dc=com", "uid=Foo-bar.baz_,ou=users,dc=a,dc=de", + "uid=bOb_.-,ou=Users,dc=example,dc=com", "uid=foo,ou=foo,dc=bar,dc=baz", + ]: + assert ll._assert_is_valid_user_dn(valid_string) == None + + +def test_assert_is_valid_group_dn(): + for invalid_type in [None, 0, True, ("x",), ["x"]]: + with pytest.raises(TypeError): + ll._assert_is_valid_group_dn(invalid_type) + + for invalid_string in [ + "", " ", "cn=foo", "abc", "\u1337", + "cn=,ou=foo,dc=bar,dc=baz", "cn=foo,ou=,dc=bar,dc=baz", + "cn=foo,ou=bar,dc=,dc=baz", "cn=foo,ou=bar,dc=baz,dc=", + "cn=foo,dc=example,dc=com", "cn=1foo,ou=bar,dc=example,dc=com", + "cn=.bob,ou=foo,dc=bar,dc=baz", "cn=_foo,ou=foo,dc=bar,dc=baz", + "cn=foo,cn=bar,ou=bar,dc=com", + "cn=abc%,ou=foo,dc=bar,dc=baz", "cn=abc1,ou=foo,dc=bar,dc=baz", + "cn=ab-c,ou=foo,dc=bar,dc=baz", + ]: + with pytest.raises(ll.InvalidStringFormatException): + print(invalid_string) + ll._assert_is_valid_group_dn(invalid_string) + + for valid_string in [ + "cn=foo,ou=groups,dc=example,dc=com", "cn=foo,ou=bar,dc=a,dc=de", + "cn=Abc,ou=foo,dc=bar,dc=baz", + ]: + assert ll._assert_is_valid_group_dn(valid_string) == None + + def test_assert_is_valid_user_object_class(): for invalid_type in [None, 0, True, ("x",), ["x"]]: with pytest.raises(TypeError):