Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • uffd/uffd
  • rixx/uffd
  • thies/uffd
  • leona/uffd
  • enbewe/uffd
  • strifel/uffd
  • thies/uffd-2
7 results
Show changes
Showing
with 1389 additions and 1995 deletions
uid=testuser,ou=users,dc=example,dc=com
uid=testadmin,ou=users,dc=example,dc=com
uid=newuser,ou=users,dc=example,dc=com
uid=newuser1,ou=users,dc=example,dc=com
uid=newuser2,ou=users,dc=example,dc=com
uid=newuser3,ou=users,dc=example,dc=com
uid=newuser4,ou=users,dc=example,dc=com
uid=newuser5,ou=users,dc=example,dc=com
uid=newuser6,ou=users,dc=example,dc=com
uid=newuser7,ou=users,dc=example,dc=com
uid=newuser8,ou=users,dc=example,dc=com
uid=newuser9,ou=users,dc=example,dc=com
uid=newuser10,ou=users,dc=example,dc=com
uid=newuser11,ou=users,dc=example,dc=com
uid=newuser12,ou=users,dc=example,dc=com
uid=test,ou=postfix,dc=example,dc=com
uid=test1,ou=postfix,dc=example,dc=com
version: 1
dn: cn=users,ou=groups,dc=example,dc=com
changetype: modify
add: uniqueMember
uniqueMember: uid=testuser,ou=users,dc=example,dc=com
uniqueMember: uid=testadmin,ou=users,dc=example,dc=com
dn: cn=uffd_access,ou=groups,dc=example,dc=com
changetype: modify
add: uniqueMember
uniqueMember: uid=testuser,ou=users,dc=example,dc=com
uniqueMember: uid=testadmin,ou=users,dc=example,dc=com
dn: cn=uffd_admin,ou=groups,dc=example,dc=com
changetype: modify
add: uniqueMember
uniqueMember: uid=testadmin,ou=users,dc=example,dc=com
{
"entries": [
{
"dn": "uid=testuser,ou=users,dc=example,dc=com",
"raw": {
"cn": [
"Test User"
],
"createTimestamp": [
"20200101000000Z"
],
"creatorsName": [
"cn=admin,dc=example,dc=com"
],
"displayName": [
"Test User"
],
"entryDN": [
"uid=testuser,ou=users,dc=example,dc=com"
],
"entryUUID": [
"75e62c6a-03c2-11eb-adc1-0242ac120002"
],
"gidNumber": [
"20001"
],
"givenName": [
"Test User"
],
"hasSubordinates": [
"FALSE"
],
"homeDirectory": [
"/home/testuser"
],
"mail": [
"testuser@example.com"
],
"memberOf": [
"cn=uffd_access,ou=groups,dc=example,dc=com",
"cn=users,ou=groups,dc=example,dc=com"
],
"modifiersName": [
"cn=admin,dc=example,dc=com"
],
"modifyTimestamp": [
"20200101000000Z"
],
"objectClass": [
"top",
"inetOrgPerson",
"organizationalPerson",
"person",
"posixAccount"
],
"sn": [
" "
],
"structuralObjectClass": [
"inetOrgPerson"
],
"subschemaSubentry": [
"cn=Subschema"
],
"uid": [
"testuser"
],
"uidNumber": [
"10000"
],
"userPassword": [
"userpassword"
]
}
},
{
"dn": "uid=testadmin,ou=users,dc=example,dc=com",
"raw": {
"cn": [
"Test Admin"
],
"createTimestamp": [
"20200101000000Z"
],
"creatorsName": [
"cn=admin,dc=example,dc=com"
],
"displayName": [
"Test Admin"
],
"entryDN": [
"uid=testadmin,ou=users,dc=example,dc=com"
],
"entryUUID": [
"678c8470-03c2-11eb-adc1-0242ac120002"
],
"gidNumber": [
"20001"
],
"givenName": [
"Test Admin"
],
"hasSubordinates": [
"FALSE"
],
"homeDirectory": [
"/home/testadmin"
],
"mail": [
"testadmin@example.com"
],
"memberOf": [
"cn=users,ou=groups,dc=example,dc=com",
"cn=uffd_access,ou=groups,dc=example,dc=com",
"cn=uffd_admin,ou=groups,dc=example,dc=com"
],
"modifiersName": [
"cn=admin,dc=example,dc=com"
],
"modifyTimestamp": [
"20200101000000Z"
],
"objectClass": [
"top",
"inetOrgPerson",
"organizationalPerson",
"person",
"posixAccount"
],
"sn": [
" "
],
"structuralObjectClass": [
"inetOrgPerson"
],
"subschemaSubentry": [
"cn=Subschema"
],
"uid": [
"testadmin"
],
"uidNumber": [
"10001"
],
"userPassword": [
"adminpassword"
]
}
},
{
"dn": "cn=users,ou=groups,dc=example,dc=com",
"raw": {
"cn": [
"users"
],
"createTimestamp": [
"20200101000000Z"
],
"creatorsName": [
"cn=admin,dc=example,dc=com"
],
"description": [
"Base group for all users"
],
"entryDN": [
"cn=users,ou=groups,dc=example,dc=com"
],
"entryUUID": [
"1aec0e8c-03c3-11eb-adc1-0242ac120002"
],
"gidNumber": [
"20001"
],
"hasSubordinates": [
"FALSE"
],
"modifiersName": [
"cn=admin,dc=example,dc=com"
],
"modifyTimestamp": [
"20200101000000Z"
],
"objectClass": [
"posixGroup",
"groupOfUniqueNames",
"top"
],
"structuralObjectClass": [
"groupOfUniqueNames"
],
"subschemaSubentry": [
"cn=Subschema"
],
"uniqueMember": [
"cn=dummy,ou=system,dc=example,dc=com",
"uid=testuser,ou=users,dc=example,dc=com",
"uid=testadmin,ou=users,dc=example,dc=com"
]
}
},
{
"dn": "cn=uffd_access,ou=groups,dc=example,dc=com",
"raw": {
"cn": [
"uffd_access"
],
"createTimestamp": [
"20200101000000Z"
],
"creatorsName": [
"cn=admin,dc=example,dc=com"
],
"description": [
"User access to uffd selfservice"
],
"entryDN": [
"cn=uffd_access,ou=groups,dc=example,dc=com"
],
"entryUUID": [
"4fc8dd60-03c3-11eb-adc1-0242ac120002"
],
"gidNumber": [
"20002"
],
"hasSubordinates": [
"FALSE"
],
"modifiersName": [
"cn=admin,dc=example,dc=com"
],
"modifyTimestamp": [
"20200101000000Z"
],
"objectClass": [
"posixGroup",
"groupOfUniqueNames",
"top"
],
"structuralObjectClass": [
"groupOfUniqueNames"
],
"subschemaSubentry": [
"cn=Subschema"
],
"uniqueMember": [
"cn=dummy,ou=system,dc=example,dc=com",
"uid=testuser,ou=users,dc=example,dc=com",
"uid=testadmin,ou=users,dc=example,dc=com"
]
}
},
{
"dn": "cn=uffd_admin,ou=groups,dc=example,dc=com",
"raw": {
"cn": [
"uffd_admin"
],
"createTimestamp": [
"20200101000000Z"
],
"creatorsName": [
"cn=admin,dc=example,dc=com"
],
"description": [
"Admin access to uffd selfservice"
],
"entryDN": [
"cn=uffd_admin,ou=groups,dc=example,dc=com"
],
"entryUUID": [
"b5d869d6-03c3-11eb-adc1-0242ac120002"
],
"gidNumber": [
"20003"
],
"hasSubordinates": [
"FALSE"
],
"modifiersName": [
"cn=admin,dc=example,dc=com"
],
"modifyTimestamp": [
"20200101000000Z"
],
"objectClass": [
"posixGroup",
"groupOfUniqueNames",
"top"
],
"structuralObjectClass": [
"groupOfUniqueNames"
],
"subschemaSubentry": [
"cn=Subschema"
],
"uniqueMember": [
"cn=dummy,ou=system,dc=example,dc=com",
"uid=testadmin,ou=users,dc=example,dc=com"
]
}
},
{
"dn": "uid=test,ou=postfix,dc=example,dc=com",
"raw": {
"createTimestamp": [
"20200101000000Z"
],
"creatorsName": [
"cn=admin,dc=example,dc=com"
],
"entryDN": [
"uid=test,ou=postfix,dc=example,dc=com"
],
"entryUUID": [
"926e5273-a545-4dfe-8f20-d1eeaf41d796"
],
"hasSubordinates": [
"FALSE"
],
"mailacceptinggeneralid": [
"test1@example.com",
"test2@example.com"
],
"maildrop": [
"testuser@mail.example.com"
],
"modifiersName": [
"cn=admin,dc=example,dc=com"
],
"modifyTimestamp": [
"20200101000000Z"
],
"objectClass": [
"top",
"postfixVirtual"
],
"structuralObjectClass": [
"postfixVirtual"
],
"subschemaSubentry": [
"cn=Subschema"
],
"uid": [
"test"
]
}
}
]
}
{
"raw": {
"altServer": [],
"configContext": [
"cn=config"
],
"entryDN": [
""
],
"namingContexts": [
"dc=example,dc=com"
],
"objectClass": [
"top",
"OpenLDAProotDSE"
],
"structuralObjectClass": [
"OpenLDAProotDSE"
],
"subschemaSubentry": [
"cn=Subschema"
],
"supportedCapabilities": [],
"supportedControl": [
"2.16.840.1.113730.3.4.18",
"2.16.840.1.113730.3.4.2",
"1.3.6.1.4.1.4203.1.10.1",
"1.3.6.1.1.22",
"1.2.840.113556.1.4.319",
"1.2.826.0.1.3344810.2.3",
"1.3.6.1.1.13.2",
"1.3.6.1.1.13.1",
"1.3.6.1.1.12"
],
"supportedExtension": [
"1.3.6.1.4.1.1466.20037",
"1.3.6.1.4.1.4203.1.11.1",
"1.3.6.1.4.1.4203.1.11.3",
"1.3.6.1.1.8"
],
"supportedFeatures": [
"1.3.6.1.1.14",
"1.3.6.1.4.1.4203.1.5.1",
"1.3.6.1.4.1.4203.1.5.2",
"1.3.6.1.4.1.4203.1.5.3",
"1.3.6.1.4.1.4203.1.5.4",
"1.3.6.1.4.1.4203.1.5.5"
],
"supportedLDAPVersion": [
"3"
],
"supportedSASLMechanisms": [
"DIGEST-MD5",
"CRAM-MD5",
"NTLM"
],
"vendorName": [],
"vendorVersion": []
},
"type": "DsaInfo"
}
{
"raw": {
"attributeTypes": [
"( 2.5.4.0 NAME 'objectClass' DESC 'RFC4512: object classes of the entity' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )",
"( 2.5.21.9 NAME 'structuralObjectClass' DESC 'RFC4512: structural object class of entry' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
"( 2.5.18.1 NAME 'createTimestamp' DESC 'RFC4512: time which object was created' EQUALITY generalizedTimeMatch ORDERING generalizedTimeOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
"( 2.5.18.2 NAME 'modifyTimestamp' DESC 'RFC4512: time which object was last modified' EQUALITY generalizedTimeMatch ORDERING generalizedTimeOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
"( 2.5.18.3 NAME 'creatorsName' DESC 'RFC4512: name of creator' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
"( 2.5.18.4 NAME 'modifiersName' DESC 'RFC4512: name of last modifier' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
"( 2.5.18.9 NAME 'hasSubordinates' DESC 'X.501: entry has children' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
"( 2.5.18.10 NAME 'subschemaSubentry' DESC 'RFC4512: name of controlling subschema entry' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
"( 1.3.6.1.1.20 NAME 'entryDN' DESC 'DN of the entry' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
"( 1.3.6.1.1.16.4 NAME 'entryUUID' DESC 'UUID of the entry' EQUALITY UUIDMatch ORDERING UUIDOrderingMatch SYNTAX 1.3.6.1.1.16.1 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
"( 1.3.6.1.4.1.1466.101.120.6 NAME 'altServer' DESC 'RFC4512: alternative servers' SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 USAGE dSAOperation )",
"( 1.3.6.1.4.1.1466.101.120.5 NAME 'namingContexts' DESC 'RFC4512: naming contexts' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 USAGE dSAOperation )",
"( 1.3.6.1.4.1.1466.101.120.13 NAME 'supportedControl' DESC 'RFC4512: supported controls' SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 USAGE dSAOperation )",
"( 1.3.6.1.4.1.1466.101.120.7 NAME 'supportedExtension' DESC 'RFC4512: supported extended operations' SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 USAGE dSAOperation )",
"( 1.3.6.1.4.1.1466.101.120.15 NAME 'supportedLDAPVersion' DESC 'RFC4512: supported LDAP versions' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 USAGE dSAOperation )",
"( 1.3.6.1.4.1.1466.101.120.14 NAME 'supportedSASLMechanisms' DESC 'RFC4512: supported SASL mechanisms' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 USAGE dSAOperation )",
"( 1.3.6.1.4.1.4203.1.3.5 NAME 'supportedFeatures' DESC 'RFC4512: features supported by the server' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 USAGE dSAOperation )",
"( 1.3.6.1.1.4 NAME 'vendorName' DESC 'RFC3045: name of implementation vendor' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE NO-USER-MODIFICATION USAGE dSAOperation )",
"( 1.3.6.1.1.5 NAME 'vendorVersion' DESC 'RFC3045: version of implementation' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE NO-USER-MODIFICATION USAGE dSAOperation )",
"( 2.5.21.4 NAME 'matchingRules' DESC 'RFC4512: matching rules' EQUALITY objectIdentifierFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.30 USAGE directoryOperation )",
"( 2.5.21.5 NAME 'attributeTypes' DESC 'RFC4512: attribute types' EQUALITY objectIdentifierFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.3 USAGE directoryOperation )",
"( 2.5.21.6 NAME 'objectClasses' DESC 'RFC4512: object classes' EQUALITY objectIdentifierFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.37 USAGE directoryOperation )",
"( 2.5.21.8 NAME 'matchingRuleUse' DESC 'RFC4512: matching rule uses' EQUALITY objectIdentifierFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.31 USAGE directoryOperation )",
"( 1.3.6.1.4.1.1466.101.120.16 NAME 'ldapSyntaxes' DESC 'RFC4512: LDAP syntaxes' EQUALITY objectIdentifierFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.54 USAGE directoryOperation )",
"( 2.5.4.1 NAME ( 'aliasedObjectName' 'aliasedEntryName' ) DESC 'RFC4512: name of aliased object' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE )",
"( 2.16.840.1.113730.3.1.34 NAME 'ref' DESC 'RFC3296: subordinate referral URL' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 USAGE distributedOperation )",
"( 1.3.6.1.4.1.1466.101.119.3 NAME 'entryTtl' DESC 'RFC2589: entry time-to-live' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE NO-USER-MODIFICATION USAGE dSAOperation )",
"( 1.3.6.1.4.1.1466.101.119.4 NAME 'dynamicSubtrees' DESC 'RFC2589: dynamic subtrees' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 NO-USER-MODIFICATION USAGE dSAOperation )",
"( 2.5.4.49 NAME 'distinguishedName' DESC 'RFC4519: common supertype of DN attributes' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )",
"( 2.5.4.41 NAME 'name' DESC 'RFC4519: common supertype of name attributes' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32768} )",
"( 2.5.4.3 NAME ( 'cn' 'commonName' ) DESC 'RFC4519: common name(s) for which the entity is known by' SUP name )",
"( 0.9.2342.19200300.100.1.1 NAME ( 'uid' 'userid' ) DESC 'RFC4519: user identifier' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )",
"( 1.3.6.1.1.1.1.0 NAME 'uidNumber' DESC 'RFC2307: An integer uniquely identifying a user in an administrative domain' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.1 NAME 'gidNumber' DESC 'RFC2307: An integer uniquely identifying a group in an administrative domain' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 2.5.4.35 NAME 'userPassword' DESC 'RFC4519/2307: password of user' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40{128} )",
"( 1.3.6.1.4.1.250.1.57 NAME 'labeledURI' DESC 'RFC2079: Uniform Resource Identifier with optional label' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 2.5.4.13 NAME 'description' DESC 'RFC4519: descriptive information' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} )",
"( 2.5.4.34 NAME 'seeAlso' DESC 'RFC4519: DN of related object' SUP distinguishedName )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.78 NAME 'olcConfigFile' DESC 'File for slapd configuration directives' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.79 NAME 'olcConfigDir' DESC 'Directory for slapd configuration backend' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.1 NAME 'olcAccess' DESC 'Access Control List' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORDERED 'VALUES' )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.86 NAME 'olcAddContentAcl' DESC 'Check ACLs against content of Add ops' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.2 NAME 'olcAllows' DESC 'Allowed set of deprecated features' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.3 NAME 'olcArgsFile' DESC 'File for slapd command line options' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.5 NAME 'olcAttributeOptions' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.4 NAME 'olcAttributeTypes' DESC 'OpenLDAP attributeTypes' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORDERED 'VALUES' )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.6 NAME 'olcAuthIDRewrite' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORDERED 'VALUES' )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.7 NAME 'olcAuthzPolicy' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.8 NAME 'olcAuthzRegexp' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORDERED 'VALUES' )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.9 NAME 'olcBackend' DESC 'A type of backend' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORDERED 'SIBLINGS' )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.10 NAME 'olcConcurrency' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.11 NAME 'olcConnMaxPending' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.12 NAME 'olcConnMaxPendingAuth' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.13 NAME 'olcDatabase' DESC 'The backend type for a database instance' SUP olcBackend SINGLE-VALUE X-ORDERED 'SIBLINGS' )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.14 NAME 'olcDefaultSearchBase' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.15 NAME 'olcDisallows' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.16 NAME 'olcDitContentRules' DESC 'OpenLDAP DIT content rules' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORDERED 'VALUES' )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.0.20 NAME 'olcExtraAttrs' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.17 NAME 'olcGentleHUP' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.0.17 NAME 'olcHidden' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.18 NAME 'olcIdleTimeout' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.19 NAME 'olcInclude' SUP labeledURI )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.20 NAME 'olcIndexSubstrIfMinLen' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.21 NAME 'olcIndexSubstrIfMaxLen' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.22 NAME 'olcIndexSubstrAnyLen' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.23 NAME 'olcIndexSubstrAnyStep' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.84 NAME 'olcIndexIntLen' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.0.4 NAME 'olcLastMod' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.85 NAME 'olcLdapSyntaxes' DESC 'OpenLDAP ldapSyntax' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORDERED 'VALUES' )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.0.5 NAME 'olcLimits' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORDERED 'VALUES' )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.93 NAME 'olcListenerThreads' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.26 NAME 'olcLocalSSF' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.27 NAME 'olcLogFile' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.28 NAME 'olcLogLevel' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.0.6 NAME 'olcMaxDerefDepth' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.0.16 NAME 'olcMirrorMode' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.30 NAME 'olcModuleLoad' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORDERED 'VALUES' )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.31 NAME 'olcModulePath' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.0.18 NAME 'olcMonitoring' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.32 NAME 'olcObjectClasses' DESC 'OpenLDAP object classes' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORDERED 'VALUES' )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.33 NAME 'olcObjectIdentifier' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORDERED 'VALUES' )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.34 NAME 'olcOverlay' SUP olcDatabase SINGLE-VALUE X-ORDERED 'SIBLINGS' )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.35 NAME 'olcPasswordCryptSaltFormat' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.36 NAME 'olcPasswordHash' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.37 NAME 'olcPidFile' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.38 NAME 'olcPlugin' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.39 NAME 'olcPluginLogFile' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.40 NAME 'olcReadOnly' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.41 NAME 'olcReferral' SUP labeledURI SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.0.7 NAME 'olcReplica' SUP labeledURI EQUALITY caseIgnoreMatch X-ORDERED 'VALUES' )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.43 NAME 'olcReplicaArgsFile' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.44 NAME 'olcReplicaPidFile' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.45 NAME 'olcReplicationInterval' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.46 NAME 'olcReplogFile' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.47 NAME 'olcRequires' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.48 NAME 'olcRestrict' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.49 NAME 'olcReverseLookup' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.0.8 NAME 'olcRootDN' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.51 NAME 'olcRootDSE' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.0.9 NAME 'olcRootPW' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.89 NAME 'olcSaslAuxprops' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.53 NAME 'olcSaslHost' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.54 NAME 'olcSaslRealm' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.56 NAME 'olcSaslSecProps' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.58 NAME 'olcSchemaDN' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.59 NAME 'olcSecurity' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.81 NAME 'olcServerID' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.60 NAME 'olcSizeLimit' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.61 NAME 'olcSockbufMaxIncoming' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.62 NAME 'olcSockbufMaxIncomingAuth' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.83 NAME 'olcSortVals' DESC 'Attributes whose values will always be sorted' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.0.15 NAME 'olcSubordinate' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.0.10 NAME 'olcSuffix' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.0.19 NAME 'olcSyncUseSubentry' DESC 'Store sync context in a subentry' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.0.11 NAME 'olcSyncrepl' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORDERED 'VALUES' )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.90 NAME 'olcTCPBuffer' DESC 'Custom TCP buffer size' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.66 NAME 'olcThreads' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.67 NAME 'olcTimeLimit' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.68 NAME 'olcTLSCACertificateFile' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.69 NAME 'olcTLSCACertificatePath' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.70 NAME 'olcTLSCertificateFile' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.71 NAME 'olcTLSCertificateKeyFile' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.72 NAME 'olcTLSCipherSuite' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.73 NAME 'olcTLSCRLCheck' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.82 NAME 'olcTLSCRLFile' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.74 NAME 'olcTLSRandFile' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.75 NAME 'olcTLSVerifyClient' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.77 NAME 'olcTLSDHParamFile' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.87 NAME 'olcTLSProtocolMin' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.80 NAME 'olcToolThreads' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.0.12 NAME 'olcUpdateDN' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.0.13 NAME 'olcUpdateRef' SUP labeledURI EQUALITY caseIgnoreMatch )",
"( 1.3.6.1.4.1.4203.1.12.2.3.0.88 NAME 'olcWriteTimeout' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.0.1 NAME 'olcDbDirectory' DESC 'Directory for database content' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.1.2 NAME 'olcDbCheckpoint' DESC 'Database checkpoint interval in kbytes and minutes' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.1.4 NAME 'olcDbNoSync' DESC 'Disable synchronous database writes' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.12.3 NAME 'olcDbEnvFlags' DESC 'Database environment flags' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.0.2 NAME 'olcDbIndex' DESC 'Attribute index parameters' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.12.1 NAME 'olcDbMaxReaders' DESC 'Maximum number of threads that may access the DB concurrently' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.12.2 NAME 'olcDbMaxSize' DESC 'Maximum size of DB in bytes' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.0.3 NAME 'olcDbMode' DESC 'Unix permissions of database files' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.12.5 NAME 'olcDbRtxnSize' DESC 'Number of entries to process in one read transaction' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.2.1.9 NAME 'olcDbSearchStack' DESC 'Depth of search stack in IDLs' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.2.840.113556.1.2.102 NAME 'memberOf' DESC 'Group that the entry belongs to' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 USAGE dSAOperation X-ORIGIN 'iPlanet Delegated Administrator' )",
"( 1.3.6.1.4.1.4203.1.12.2.3.3.18.0 NAME 'olcMemberOfDN' DESC 'DN to be used as modifiersName' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.3.18.1 NAME 'olcMemberOfDangling' DESC 'Behavior with respect to dangling members, constrained to ignore, drop, error' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.3.18.2 NAME 'olcMemberOfRefInt' DESC 'Take care of referential integrity' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.3.18.3 NAME 'olcMemberOfGroupOC' DESC 'Group objectClass' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.3.18.4 NAME 'olcMemberOfMemberAD' DESC 'member attribute' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.3.18.5 NAME 'olcMemberOfMemberOfAD' DESC 'memberOf attribute' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.3.18.7 NAME 'olcMemberOfDanglingError' DESC 'Error code returned in case of dangling back reference' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.3.10.1 NAME 'olcUniqueBase' DESC 'Subtree for uniqueness searches' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.3.10.2 NAME 'olcUniqueIgnore' DESC 'Attributes for which uniqueness shall not be enforced' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.3.6.1.4.1.4203.1.12.2.3.3.10.3 NAME 'olcUniqueAttribute' DESC 'Attributes for which uniqueness shall be enforced' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.3.6.1.4.1.4203.1.12.2.3.3.10.4 NAME 'olcUniqueStrict' DESC 'Enforce uniqueness of null values' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.3.10.5 NAME 'olcUniqueURI' DESC 'List of keywords and LDAP URIs for a uniqueness domain' EQUALITY caseExactMatch ORDERING caseExactOrderingMatch SUBSTR caseExactSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.3.6.1.4.1.4203.1.12.2.3.3.11.1 NAME 'olcRefintAttribute' DESC 'Attributes for referential integrity' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.3.6.1.4.1.4203.1.12.2.3.3.11.2 NAME 'olcRefintNothing' DESC 'Replacement DN to supply when needed' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE )",
"( 1.3.6.1.4.1.4203.1.12.2.3.3.11.3 NAME 'olcRefintModifiersName' DESC 'The DN to use as modifiersName' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE )",
"( 2.5.4.2 NAME 'knowledgeInformation' DESC 'RFC2256: knowledge information' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32768} )",
"( 2.5.4.4 NAME ( 'sn' 'surname' ) DESC 'RFC2256: last (family) name(s) for which the entity is known by' SUP name )",
"( 2.5.4.5 NAME 'serialNumber' DESC 'RFC2256: serial number of the entity' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44{64} )",
"( 2.5.4.6 NAME ( 'c' 'countryName' ) DESC 'RFC4519: two-letter ISO-3166 country code' SUP name SYNTAX 1.3.6.1.4.1.1466.115.121.1.11 SINGLE-VALUE )",
"( 2.5.4.7 NAME ( 'l' 'localityName' ) DESC 'RFC2256: locality which this object resides in' SUP name )",
"( 2.5.4.8 NAME ( 'st' 'stateOrProvinceName' ) DESC 'RFC2256: state or province which this object resides in' SUP name )",
"( 2.5.4.9 NAME ( 'street' 'streetAddress' ) DESC 'RFC2256: street address of this object' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )",
"( 2.5.4.10 NAME ( 'o' 'organizationName' ) DESC 'RFC2256: organization this object belongs to' SUP name )",
"( 2.5.4.11 NAME ( 'ou' 'organizationalUnitName' ) DESC 'RFC2256: organizational unit this object belongs to' SUP name )",
"( 2.5.4.12 NAME 'title' DESC 'RFC2256: title associated with the entity' SUP name )",
"( 2.5.4.14 NAME 'searchGuide' DESC 'RFC2256: search guide, deprecated by enhancedSearchGuide' SYNTAX 1.3.6.1.4.1.1466.115.121.1.25 )",
"( 2.5.4.15 NAME 'businessCategory' DESC 'RFC2256: business category' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )",
"( 2.5.4.16 NAME 'postalAddress' DESC 'RFC2256: postal address' EQUALITY caseIgnoreListMatch SUBSTR caseIgnoreListSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )",
"( 2.5.4.17 NAME 'postalCode' DESC 'RFC2256: postal code' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{40} )",
"( 2.5.4.18 NAME 'postOfficeBox' DESC 'RFC2256: Post Office Box' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{40} )",
"( 2.5.4.19 NAME 'physicalDeliveryOfficeName' DESC 'RFC2256: Physical Delivery Office Name' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )",
"( 2.5.4.20 NAME 'telephoneNumber' DESC 'RFC2256: Telephone Number' EQUALITY telephoneNumberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.50{32} )",
"( 2.5.4.21 NAME 'telexNumber' DESC 'RFC2256: Telex Number' SYNTAX 1.3.6.1.4.1.1466.115.121.1.52 )",
"( 2.5.4.22 NAME 'teletexTerminalIdentifier' DESC 'RFC2256: Teletex Terminal Identifier' SYNTAX 1.3.6.1.4.1.1466.115.121.1.51 )",
"( 2.5.4.23 NAME ( 'facsimileTelephoneNumber' 'fax' ) DESC 'RFC2256: Facsimile (Fax) Telephone Number' SYNTAX 1.3.6.1.4.1.1466.115.121.1.22 )",
"( 2.5.4.24 NAME 'x121Address' DESC 'RFC2256: X.121 Address' EQUALITY numericStringMatch SUBSTR numericStringSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.36{15} )",
"( 2.5.4.25 NAME 'internationaliSDNNumber' DESC 'RFC2256: international ISDN number' EQUALITY numericStringMatch SUBSTR numericStringSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.36{16} )",
"( 2.5.4.26 NAME 'registeredAddress' DESC 'RFC2256: registered postal address' SUP postalAddress SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )",
"( 2.5.4.27 NAME 'destinationIndicator' DESC 'RFC2256: destination indicator' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44{128} )",
"( 2.5.4.28 NAME 'preferredDeliveryMethod' DESC 'RFC2256: preferred delivery method' SYNTAX 1.3.6.1.4.1.1466.115.121.1.14 SINGLE-VALUE )",
"( 2.5.4.29 NAME 'presentationAddress' DESC 'RFC2256: presentation address' EQUALITY presentationAddressMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.43 SINGLE-VALUE )",
"( 2.5.4.30 NAME 'supportedApplicationContext' DESC 'RFC2256: supported application context' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )",
"( 2.5.4.31 NAME 'member' DESC 'RFC2256: member of a group' SUP distinguishedName )",
"( 2.5.4.32 NAME 'owner' DESC 'RFC2256: owner (of the object)' SUP distinguishedName )",
"( 2.5.4.33 NAME 'roleOccupant' DESC 'RFC2256: occupant of role' SUP distinguishedName )",
"( 2.5.4.36 NAME 'userCertificate' DESC 'RFC2256: X.509 user certificate, use ;binary' EQUALITY certificateExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.8 )",
"( 2.5.4.37 NAME 'cACertificate' DESC 'RFC2256: X.509 CA certificate, use ;binary' EQUALITY certificateExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.8 )",
"( 2.5.4.38 NAME 'authorityRevocationList' DESC 'RFC2256: X.509 authority revocation list, use ;binary' SYNTAX 1.3.6.1.4.1.1466.115.121.1.9 )",
"( 2.5.4.39 NAME 'certificateRevocationList' DESC 'RFC2256: X.509 certificate revocation list, use ;binary' SYNTAX 1.3.6.1.4.1.1466.115.121.1.9 )",
"( 2.5.4.40 NAME 'crossCertificatePair' DESC 'RFC2256: X.509 cross certificate pair, use ;binary' SYNTAX 1.3.6.1.4.1.1466.115.121.1.10 )",
"( 2.5.4.42 NAME ( 'givenName' 'gn' ) DESC 'RFC2256: first name(s) for which the entity is known by' SUP name )",
"( 2.5.4.43 NAME 'initials' DESC 'RFC2256: initials of some or all of names, but not the surname(s).' SUP name )",
"( 2.5.4.44 NAME 'generationQualifier' DESC 'RFC2256: name qualifier indicating a generation' SUP name )",
"( 2.5.4.45 NAME 'x500UniqueIdentifier' DESC 'RFC2256: X.500 unique identifier' EQUALITY bitStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.6 )",
"( 2.5.4.46 NAME 'dnQualifier' DESC 'RFC2256: DN qualifier' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 )",
"( 2.5.4.47 NAME 'enhancedSearchGuide' DESC 'RFC2256: enhanced search guide' SYNTAX 1.3.6.1.4.1.1466.115.121.1.21 )",
"( 2.5.4.48 NAME 'protocolInformation' DESC 'RFC2256: protocol information' EQUALITY protocolInformationMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.42 )",
"( 2.5.4.50 NAME 'uniqueMember' DESC 'RFC2256: unique member of a group' EQUALITY uniqueMemberMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.34 )",
"( 2.5.4.51 NAME 'houseIdentifier' DESC 'RFC2256: house identifier' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32768} )",
"( 2.5.4.52 NAME 'supportedAlgorithms' DESC 'RFC2256: supported algorithms' SYNTAX 1.3.6.1.4.1.1466.115.121.1.49 )",
"( 2.5.4.53 NAME 'deltaRevocationList' DESC 'RFC2256: delta revocation list; use ;binary' SYNTAX 1.3.6.1.4.1.1466.115.121.1.9 )",
"( 2.5.4.54 NAME 'dmdName' DESC 'RFC2256: name of DMD' SUP name )",
"( 2.5.4.65 NAME 'pseudonym' DESC 'X.520(4th): pseudonym for the object' SUP name )",
"( 0.9.2342.19200300.100.1.3 NAME ( 'mail' 'rfc822Mailbox' ) DESC 'RFC1274: RFC822 Mailbox' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )",
"( 0.9.2342.19200300.100.1.25 NAME ( 'dc' 'domainComponent' ) DESC 'RFC1274/2247: domain component' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )",
"( 0.9.2342.19200300.100.1.37 NAME 'associatedDomain' DESC 'RFC1274: domain associated with object' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
"( 1.2.840.113549.1.9.1 NAME ( 'email' 'emailAddress' 'pkcs9email' ) DESC 'RFC3280: legacy attribute for email addresses in DNs' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} )",
"( 0.9.2342.19200300.100.1.2 NAME 'textEncodedORAddress' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )",
"( 0.9.2342.19200300.100.1.4 NAME 'info' DESC 'RFC1274: general information' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{2048} )",
"( 0.9.2342.19200300.100.1.5 NAME ( 'drink' 'favouriteDrink' ) DESC 'RFC1274: favorite drink' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )",
"( 0.9.2342.19200300.100.1.6 NAME 'roomNumber' DESC 'RFC1274: room number' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )",
"( 0.9.2342.19200300.100.1.7 NAME 'photo' DESC 'RFC1274: photo (G3 fax)' SYNTAX 1.3.6.1.4.1.1466.115.121.1.23{25000} )",
"( 0.9.2342.19200300.100.1.8 NAME 'userClass' DESC 'RFC1274: category of user' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )",
"( 0.9.2342.19200300.100.1.9 NAME 'host' DESC 'RFC1274: host computer' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )",
"( 0.9.2342.19200300.100.1.10 NAME 'manager' DESC 'RFC1274: DN of manager' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )",
"( 0.9.2342.19200300.100.1.11 NAME 'documentIdentifier' DESC 'RFC1274: unique identifier of document' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )",
"( 0.9.2342.19200300.100.1.12 NAME 'documentTitle' DESC 'RFC1274: title of document' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )",
"( 0.9.2342.19200300.100.1.13 NAME 'documentVersion' DESC 'RFC1274: version of document' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )",
"( 0.9.2342.19200300.100.1.14 NAME 'documentAuthor' DESC 'RFC1274: DN of author of document' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )",
"( 0.9.2342.19200300.100.1.15 NAME 'documentLocation' DESC 'RFC1274: location of document original' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )",
"( 0.9.2342.19200300.100.1.20 NAME ( 'homePhone' 'homeTelephoneNumber' ) DESC 'RFC1274: home telephone number' EQUALITY telephoneNumberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 )",
"( 0.9.2342.19200300.100.1.21 NAME 'secretary' DESC 'RFC1274: DN of secretary' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )",
"( 0.9.2342.19200300.100.1.22 NAME 'otherMailbox' SYNTAX 1.3.6.1.4.1.1466.115.121.1.39 )",
"( 0.9.2342.19200300.100.1.26 NAME 'aRecord' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
"( 0.9.2342.19200300.100.1.27 NAME 'mDRecord' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
"( 0.9.2342.19200300.100.1.28 NAME 'mXRecord' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
"( 0.9.2342.19200300.100.1.29 NAME 'nSRecord' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
"( 0.9.2342.19200300.100.1.30 NAME 'sOARecord' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
"( 0.9.2342.19200300.100.1.31 NAME 'cNAMERecord' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
"( 0.9.2342.19200300.100.1.38 NAME 'associatedName' DESC 'RFC1274: DN of entry associated with domain' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )",
"( 0.9.2342.19200300.100.1.39 NAME 'homePostalAddress' DESC 'RFC1274: home postal address' EQUALITY caseIgnoreListMatch SUBSTR caseIgnoreListSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )",
"( 0.9.2342.19200300.100.1.40 NAME 'personalTitle' DESC 'RFC1274: personal title' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )",
"( 0.9.2342.19200300.100.1.41 NAME ( 'mobile' 'mobileTelephoneNumber' ) DESC 'RFC1274: mobile telephone number' EQUALITY telephoneNumberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 )",
"( 0.9.2342.19200300.100.1.42 NAME ( 'pager' 'pagerTelephoneNumber' ) DESC 'RFC1274: pager telephone number' EQUALITY telephoneNumberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 )",
"( 0.9.2342.19200300.100.1.43 NAME ( 'co' 'friendlyCountryName' ) DESC 'RFC1274: friendly country name' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 0.9.2342.19200300.100.1.44 NAME 'uniqueIdentifier' DESC 'RFC1274: unique identifer' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )",
"( 0.9.2342.19200300.100.1.45 NAME 'organizationalStatus' DESC 'RFC1274: organizational status' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )",
"( 0.9.2342.19200300.100.1.46 NAME 'janetMailbox' DESC 'RFC1274: Janet mailbox' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )",
"( 0.9.2342.19200300.100.1.47 NAME 'mailPreferenceOption' DESC 'RFC1274: mail preference option' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )",
"( 0.9.2342.19200300.100.1.48 NAME 'buildingName' DESC 'RFC1274: name of building' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )",
"( 0.9.2342.19200300.100.1.49 NAME 'dSAQuality' DESC 'RFC1274: DSA Quality' SYNTAX 1.3.6.1.4.1.1466.115.121.1.19 SINGLE-VALUE )",
"( 0.9.2342.19200300.100.1.50 NAME 'singleLevelQuality' DESC 'RFC1274: Single Level Quality' SYNTAX 1.3.6.1.4.1.1466.115.121.1.13 SINGLE-VALUE )",
"( 0.9.2342.19200300.100.1.51 NAME 'subtreeMinimumQuality' DESC 'RFC1274: Subtree Mininum Quality' SYNTAX 1.3.6.1.4.1.1466.115.121.1.13 SINGLE-VALUE )",
"( 0.9.2342.19200300.100.1.52 NAME 'subtreeMaximumQuality' DESC 'RFC1274: Subtree Maximun Quality' SYNTAX 1.3.6.1.4.1.1466.115.121.1.13 SINGLE-VALUE )",
"( 0.9.2342.19200300.100.1.53 NAME 'personalSignature' DESC 'RFC1274: Personal Signature (G3 fax)' SYNTAX 1.3.6.1.4.1.1466.115.121.1.23 )",
"( 0.9.2342.19200300.100.1.54 NAME 'dITRedirect' DESC 'RFC1274: DIT Redirect' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )",
"( 0.9.2342.19200300.100.1.55 NAME 'audio' DESC 'RFC1274: audio (u-law)' SYNTAX 1.3.6.1.4.1.1466.115.121.1.4{25000} )",
"( 0.9.2342.19200300.100.1.56 NAME 'documentPublisher' DESC 'RFC1274: publisher of document' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.3.6.1.1.1.1.2 NAME 'gecos' DESC 'The GECOS field; the common name' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.3 NAME 'homeDirectory' DESC 'The absolute path to the home directory' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.4 NAME 'loginShell' DESC 'The path to the login shell' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.5 NAME 'shadowLastChange' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.6 NAME 'shadowMin' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.7 NAME 'shadowMax' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.8 NAME 'shadowWarning' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.9 NAME 'shadowInactive' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.10 NAME 'shadowExpire' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.11 NAME 'shadowFlag' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.12 NAME 'memberUid' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
"( 1.3.6.1.1.1.1.13 NAME 'memberNisNetgroup' EQUALITY caseExactIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
"( 1.3.6.1.1.1.1.14 NAME 'nisNetgroupTriple' DESC 'Netgroup triple' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
"( 1.3.6.1.1.1.1.15 NAME 'ipServicePort' DESC 'Service port number' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.16 NAME 'ipServiceProtocol' DESC 'Service protocol name' SUP name )",
"( 1.3.6.1.1.1.1.17 NAME 'ipProtocolNumber' DESC 'IP protocol number' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.18 NAME 'oncRpcNumber' DESC 'ONC RPC number' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.19 NAME 'ipHostNumber' DESC 'IPv4 addresses as a dotted decimal omitting leading zeros or IPv6 addresses as defined in RFC2373' SUP name )",
"( 1.3.6.1.1.1.1.20 NAME 'ipNetworkNumber' DESC 'IP network as a dotted decimal, eg. 192.168, omitting leading zeros' SUP name SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.21 NAME 'ipNetmaskNumber' DESC 'IP netmask as a dotted decimal, eg. 255.255.255.0, omitting leading zeros' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.22 NAME 'macAddress' DESC 'MAC address in maximal, colon separated hex notation, eg. 00:00:92:90:ee:e2' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
"( 1.3.6.1.1.1.1.23 NAME 'bootParameter' DESC 'rpc.bootparamd parameter' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
"( 1.3.6.1.1.1.1.24 NAME 'bootFile' DESC 'Boot image name' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
"( 1.3.6.1.1.1.1.26 NAME 'nisMapName' DESC 'Name of a A generic NIS map' SUP name )",
"( 1.3.6.1.1.1.1.27 NAME 'nisMapEntry' DESC 'A generic NIS entry' EQUALITY caseExactIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.28 NAME 'nisPublicKey' DESC 'NIS public key' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.29 NAME 'nisSecretKey' DESC 'NIS secret key' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.30 NAME 'nisDomain' DESC 'NIS domain' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
"( 1.3.6.1.1.1.1.31 NAME 'automountMapName' DESC 'automount Map Name' EQUALITY caseExactIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.32 NAME 'automountKey' DESC 'Automount Key value' EQUALITY caseExactIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )",
"( 1.3.6.1.1.1.1.33 NAME 'automountInformation' DESC 'Automount information' EQUALITY caseExactIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )",
"( 2.16.840.1.113730.3.1.1 NAME 'carLicense' DESC 'RFC2798: vehicle license or registration plate' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 2.16.840.1.113730.3.1.2 NAME 'departmentNumber' DESC 'RFC2798: identifies a department within an organization' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 2.16.840.1.113730.3.1.241 NAME 'displayName' DESC 'RFC2798: preferred name to be used when displaying entries' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 2.16.840.1.113730.3.1.3 NAME 'employeeNumber' DESC 'RFC2798: numerically identifies an employee within an organization' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 2.16.840.1.113730.3.1.4 NAME 'employeeType' DESC 'RFC2798: type of employment for a person' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 0.9.2342.19200300.100.1.60 NAME 'jpegPhoto' DESC 'RFC2798: a JPEG image' SYNTAX 1.3.6.1.4.1.1466.115.121.1.28 )",
"( 2.16.840.1.113730.3.1.39 NAME 'preferredLanguage' DESC 'RFC2798: preferred written or spoken language for a person' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )",
"( 2.16.840.1.113730.3.1.40 NAME 'userSMIMECertificate' DESC 'RFC2798: PKCS#7 SignedData used to support S/MIME' SYNTAX 1.3.6.1.4.1.1466.115.121.1.5 )",
"( 2.16.840.1.113730.3.1.216 NAME 'userPKCS12' DESC 'RFC2798: personal identity information, a PKCS #12 PFX' SYNTAX 1.3.6.1.4.1.1466.115.121.1.5 )",
"( 1.3.6.1.4.1.4203.666.1.200 NAME 'mailacceptinggeneralid' DESC 'Postfix mail local address alias attribute' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )",
"( 1.3.6.1.4.1.4203.666.1.201 NAME 'maildrop' DESC 'Postfix mail final destination attribute' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )"
],
"cn": [
"Subschema"
],
"createTimestamp": [
"20200930024551Z"
],
"dITContentRules": [],
"dITStructureRules": [],
"entryDN": [
"cn=Subschema"
],
"ldapSyntaxes": [
"( 1.3.6.1.4.1.1466.115.121.1.4 DESC 'Audio' X-NOT-HUMAN-READABLE 'TRUE' )",
"( 1.3.6.1.4.1.1466.115.121.1.5 DESC 'Binary' X-NOT-HUMAN-READABLE 'TRUE' )",
"( 1.3.6.1.4.1.1466.115.121.1.6 DESC 'Bit String' )",
"( 1.3.6.1.4.1.1466.115.121.1.7 DESC 'Boolean' )",
"( 1.3.6.1.4.1.1466.115.121.1.8 DESC 'Certificate' X-BINARY-TRANSFER-REQUIRED 'TRUE' X-NOT-HUMAN-READABLE 'TRUE' )",
"( 1.3.6.1.4.1.1466.115.121.1.9 DESC 'Certificate List' X-BINARY-TRANSFER-REQUIRED 'TRUE' X-NOT-HUMAN-READABLE 'TRUE' )",
"( 1.3.6.1.4.1.1466.115.121.1.10 DESC 'Certificate Pair' X-BINARY-TRANSFER-REQUIRED 'TRUE' X-NOT-HUMAN-READABLE 'TRUE' )",
"( 1.3.6.1.4.1.4203.666.11.10.2.1 DESC 'X.509 AttributeCertificate' X-BINARY-TRANSFER-REQUIRED 'TRUE' X-NOT-HUMAN-READABLE 'TRUE' )",
"( 1.3.6.1.4.1.1466.115.121.1.12 DESC 'Distinguished Name' )",
"( 1.2.36.79672281.1.5.0 DESC 'RDN' )",
"( 1.3.6.1.4.1.1466.115.121.1.14 DESC 'Delivery Method' )",
"( 1.3.6.1.4.1.1466.115.121.1.15 DESC 'Directory String' )",
"( 1.3.6.1.4.1.1466.115.121.1.22 DESC 'Facsimile Telephone Number' )",
"( 1.3.6.1.4.1.1466.115.121.1.24 DESC 'Generalized Time' )",
"( 1.3.6.1.4.1.1466.115.121.1.26 DESC 'IA5 String' )",
"( 1.3.6.1.4.1.1466.115.121.1.27 DESC 'Integer' )",
"( 1.3.6.1.4.1.1466.115.121.1.28 DESC 'JPEG' X-NOT-HUMAN-READABLE 'TRUE' )",
"( 1.3.6.1.4.1.1466.115.121.1.34 DESC 'Name And Optional UID' )",
"( 1.3.6.1.4.1.1466.115.121.1.36 DESC 'Numeric String' )",
"( 1.3.6.1.4.1.1466.115.121.1.38 DESC 'OID' )",
"( 1.3.6.1.4.1.1466.115.121.1.39 DESC 'Other Mailbox' )",
"( 1.3.6.1.4.1.1466.115.121.1.40 DESC 'Octet String' )",
"( 1.3.6.1.4.1.1466.115.121.1.41 DESC 'Postal Address' )",
"( 1.3.6.1.4.1.1466.115.121.1.44 DESC 'Printable String' )",
"( 1.3.6.1.4.1.1466.115.121.1.11 DESC 'Country String' )",
"( 1.3.6.1.4.1.1466.115.121.1.45 DESC 'SubtreeSpecification' )",
"( 1.3.6.1.4.1.1466.115.121.1.49 DESC 'Supported Algorithm' X-BINARY-TRANSFER-REQUIRED 'TRUE' X-NOT-HUMAN-READABLE 'TRUE' )",
"( 1.3.6.1.4.1.1466.115.121.1.50 DESC 'Telephone Number' )",
"( 1.3.6.1.4.1.1466.115.121.1.52 DESC 'Telex Number' )",
"( 1.3.6.1.1.1.0.0 DESC 'RFC2307 NIS Netgroup Triple' )",
"( 1.3.6.1.1.1.0.1 DESC 'RFC2307 Boot Parameter' )",
"( 1.3.6.1.1.16.1 DESC 'UUID' )"
],
"matchingRuleUse": [
"( 1.2.840.113556.1.4.804 NAME 'integerBitOrMatch' APPLIES ( supportedLDAPVersion $ entryTtl $ uidNumber $ gidNumber $ olcConcurrency $ olcConnMaxPending $ olcConnMaxPendingAuth $ olcIdleTimeout $ olcIndexSubstrIfMinLen $ olcIndexSubstrIfMaxLen $ olcIndexSubstrAnyLen $ olcIndexSubstrAnyStep $ olcIndexIntLen $ olcListenerThreads $ olcLocalSSF $ olcMaxDerefDepth $ olcReplicationInterval $ olcSockbufMaxIncoming $ olcSockbufMaxIncomingAuth $ olcThreads $ olcToolThreads $ olcWriteTimeout $ olcDbMaxReaders $ olcDbMaxSize $ olcDbRtxnSize $ olcDbSearchStack $ mailPreferenceOption $ shadowLastChange $ shadowMin $ shadowMax $ shadowWarning $ shadowInactive $ shadowExpire $ shadowFlag $ ipServicePort $ ipProtocolNumber $ oncRpcNumber ) )",
"( 1.2.840.113556.1.4.803 NAME 'integerBitAndMatch' APPLIES ( supportedLDAPVersion $ entryTtl $ uidNumber $ gidNumber $ olcConcurrency $ olcConnMaxPending $ olcConnMaxPendingAuth $ olcIdleTimeout $ olcIndexSubstrIfMinLen $ olcIndexSubstrIfMaxLen $ olcIndexSubstrAnyLen $ olcIndexSubstrAnyStep $ olcIndexIntLen $ olcListenerThreads $ olcLocalSSF $ olcMaxDerefDepth $ olcReplicationInterval $ olcSockbufMaxIncoming $ olcSockbufMaxIncomingAuth $ olcThreads $ olcToolThreads $ olcWriteTimeout $ olcDbMaxReaders $ olcDbMaxSize $ olcDbRtxnSize $ olcDbSearchStack $ mailPreferenceOption $ shadowLastChange $ shadowMin $ shadowMax $ shadowWarning $ shadowInactive $ shadowExpire $ shadowFlag $ ipServicePort $ ipProtocolNumber $ oncRpcNumber ) )",
"( 1.3.6.1.4.1.1466.109.114.2 NAME 'caseIgnoreIA5Match' APPLIES ( altServer $ c $ mail $ dc $ associatedDomain $ email $ aRecord $ mDRecord $ mXRecord $ nSRecord $ sOARecord $ cNAMERecord $ janetMailbox $ gecos $ homeDirectory $ loginShell $ memberUid $ memberNisNetgroup $ nisNetgroupTriple $ ipNetmaskNumber $ macAddress $ bootParameter $ bootFile $ nisMapEntry $ nisDomain $ automountMapName $ automountKey $ automountInformation $ mailacceptinggeneralid $ maildrop ) )",
"( 1.3.6.1.4.1.1466.109.114.1 NAME 'caseExactIA5Match' APPLIES ( altServer $ c $ mail $ dc $ associatedDomain $ email $ aRecord $ mDRecord $ mXRecord $ nSRecord $ sOARecord $ cNAMERecord $ janetMailbox $ gecos $ homeDirectory $ loginShell $ memberUid $ memberNisNetgroup $ nisNetgroupTriple $ ipNetmaskNumber $ macAddress $ bootParameter $ bootFile $ nisMapEntry $ nisDomain $ automountMapName $ automountKey $ automountInformation $ mailacceptinggeneralid $ maildrop ) )",
"( 2.5.13.38 NAME 'certificateListExactMatch' APPLIES ( authorityRevocationList $ certificateRevocationList $ deltaRevocationList ) )",
"( 2.5.13.34 NAME 'certificateExactMatch' APPLIES ( userCertificate $ cACertificate ) )",
"( 2.5.13.30 NAME 'objectIdentifierFirstComponentMatch' APPLIES ( supportedControl $ supportedExtension $ supportedFeatures $ ldapSyntaxes $ supportedApplicationContext ) )",
"( 2.5.13.29 NAME 'integerFirstComponentMatch' APPLIES ( supportedLDAPVersion $ entryTtl $ uidNumber $ gidNumber $ olcConcurrency $ olcConnMaxPending $ olcConnMaxPendingAuth $ olcIdleTimeout $ olcIndexSubstrIfMinLen $ olcIndexSubstrIfMaxLen $ olcIndexSubstrAnyLen $ olcIndexSubstrAnyStep $ olcIndexIntLen $ olcListenerThreads $ olcLocalSSF $ olcMaxDerefDepth $ olcReplicationInterval $ olcSockbufMaxIncoming $ olcSockbufMaxIncomingAuth $ olcThreads $ olcToolThreads $ olcWriteTimeout $ olcDbMaxReaders $ olcDbMaxSize $ olcDbRtxnSize $ olcDbSearchStack $ mailPreferenceOption $ shadowLastChange $ shadowMin $ shadowMax $ shadowWarning $ shadowInactive $ shadowExpire $ shadowFlag $ ipServicePort $ ipProtocolNumber $ oncRpcNumber ) )",
"( 2.5.13.28 NAME 'generalizedTimeOrderingMatch' APPLIES ( createTimestamp $ modifyTimestamp ) )",
"( 2.5.13.27 NAME 'generalizedTimeMatch' APPLIES ( createTimestamp $ modifyTimestamp ) )",
"( 2.5.13.24 NAME 'protocolInformationMatch' APPLIES protocolInformation )",
"( 2.5.13.23 NAME 'uniqueMemberMatch' APPLIES uniqueMember )",
"( 2.5.13.22 NAME 'presentationAddressMatch' APPLIES presentationAddress )",
"( 2.5.13.20 NAME 'telephoneNumberMatch' APPLIES ( telephoneNumber $ homePhone $ mobile $ pager ) )",
"( 2.5.13.18 NAME 'octetStringOrderingMatch' APPLIES ( userPassword $ nisPublicKey $ nisSecretKey ) )",
"( 2.5.13.17 NAME 'octetStringMatch' APPLIES ( userPassword $ nisPublicKey $ nisSecretKey ) )",
"( 2.5.13.16 NAME 'bitStringMatch' APPLIES x500UniqueIdentifier )",
"( 2.5.13.15 NAME 'integerOrderingMatch' APPLIES ( supportedLDAPVersion $ entryTtl $ uidNumber $ gidNumber $ olcConcurrency $ olcConnMaxPending $ olcConnMaxPendingAuth $ olcIdleTimeout $ olcIndexSubstrIfMinLen $ olcIndexSubstrIfMaxLen $ olcIndexSubstrAnyLen $ olcIndexSubstrAnyStep $ olcIndexIntLen $ olcListenerThreads $ olcLocalSSF $ olcMaxDerefDepth $ olcReplicationInterval $ olcSockbufMaxIncoming $ olcSockbufMaxIncomingAuth $ olcThreads $ olcToolThreads $ olcWriteTimeout $ olcDbMaxReaders $ olcDbMaxSize $ olcDbRtxnSize $ olcDbSearchStack $ mailPreferenceOption $ shadowLastChange $ shadowMin $ shadowMax $ shadowWarning $ shadowInactive $ shadowExpire $ shadowFlag $ ipServicePort $ ipProtocolNumber $ oncRpcNumber ) )",
"( 2.5.13.14 NAME 'integerMatch' APPLIES ( supportedLDAPVersion $ entryTtl $ uidNumber $ gidNumber $ olcConcurrency $ olcConnMaxPending $ olcConnMaxPendingAuth $ olcIdleTimeout $ olcIndexSubstrIfMinLen $ olcIndexSubstrIfMaxLen $ olcIndexSubstrAnyLen $ olcIndexSubstrAnyStep $ olcIndexIntLen $ olcListenerThreads $ olcLocalSSF $ olcMaxDerefDepth $ olcReplicationInterval $ olcSockbufMaxIncoming $ olcSockbufMaxIncomingAuth $ olcThreads $ olcToolThreads $ olcWriteTimeout $ olcDbMaxReaders $ olcDbMaxSize $ olcDbRtxnSize $ olcDbSearchStack $ mailPreferenceOption $ shadowLastChange $ shadowMin $ shadowMax $ shadowWarning $ shadowInactive $ shadowExpire $ shadowFlag $ ipServicePort $ ipProtocolNumber $ oncRpcNumber ) )",
"( 2.5.13.13 NAME 'booleanMatch' APPLIES ( hasSubordinates $ olcAddContentAcl $ olcGentleHUP $ olcHidden $ olcLastMod $ olcMirrorMode $ olcMonitoring $ olcReadOnly $ olcReverseLookup $ olcSyncUseSubentry $ olcDbNoSync $ olcMemberOfRefInt $ olcUniqueStrict ) )",
"( 2.5.13.11 NAME 'caseIgnoreListMatch' APPLIES ( postalAddress $ registeredAddress $ homePostalAddress ) )",
"( 2.5.13.9 NAME 'numericStringOrderingMatch' APPLIES ( x121Address $ internationaliSDNNumber ) )",
"( 2.5.13.8 NAME 'numericStringMatch' APPLIES ( x121Address $ internationaliSDNNumber ) )",
"( 2.5.13.7 NAME 'caseExactSubstringsMatch' APPLIES ( serialNumber $ c $ telephoneNumber $ destinationIndicator $ dnQualifier $ homePhone $ mobile $ pager ) )",
"( 2.5.13.6 NAME 'caseExactOrderingMatch' APPLIES ( supportedSASLMechanisms $ vendorName $ vendorVersion $ ref $ name $ cn $ uid $ labeledURI $ description $ olcConfigFile $ olcConfigDir $ olcAccess $ olcAllows $ olcArgsFile $ olcAttributeOptions $ olcAttributeTypes $ olcAuthIDRewrite $ olcAuthzPolicy $ olcAuthzRegexp $ olcBackend $ olcDatabase $ olcDisallows $ olcDitContentRules $ olcExtraAttrs $ olcInclude $ olcLdapSyntaxes $ olcLimits $ olcLogFile $ olcLogLevel $ olcModuleLoad $ olcModulePath $ olcObjectClasses $ olcObjectIdentifier $ olcOverlay $ olcPasswordCryptSaltFormat $ olcPasswordHash $ olcPidFile $ olcPlugin $ olcPluginLogFile $ olcReferral $ olcReplica $ olcReplicaArgsFile $ olcReplicaPidFile $ olcReplogFile $ olcRequires $ olcRestrict $ olcRootDSE $ olcRootPW $ olcSaslAuxprops $ olcSaslHost $ olcSaslRealm $ olcSaslSecProps $ olcSecurity $ olcServerID $ olcSizeLimit $ olcSortVals $ olcSubordinate $ olcSyncrepl $ olcTCPBuffer $ olcTimeLimit $ olcTLSCACertificateFile $ olcTLSCACertificatePath $ olcTLSCertificateFile $ olcTLSCertificateKeyFile $ olcTLSCipherSuite $ olcTLSCRLCheck $ olcTLSCRLFile $ olcTLSRandFile $ olcTLSVerifyClient $ olcTLSDHParamFile $ olcTLSProtocolMin $ olcUpdateRef $ olcDbDirectory $ olcDbCheckpoint $ olcDbEnvFlags $ olcDbIndex $ olcDbMode $ olcMemberOfDangling $ olcMemberOfGroupOC $ olcMemberOfMemberAD $ olcMemberOfMemberOfAD $ olcMemberOfDanglingError $ olcUniqueIgnore $ olcUniqueAttribute $ olcUniqueURI $ olcRefintAttribute $ knowledgeInformation $ sn $ serialNumber $ c $ l $ st $ street $ o $ ou $ title $ businessCategory $ postalCode $ postOfficeBox $ physicalDeliveryOfficeName $ telephoneNumber $ destinationIndicator $ givenName $ initials $ generationQualifier $ dnQualifier $ houseIdentifier $ dmdName $ pseudonym $ textEncodedORAddress $ info $ drink $ roomNumber $ userClass $ host $ documentIdentifier $ documentTitle $ documentVersion $ documentLocation $ homePhone $ personalTitle $ mobile $ pager $ co $ uniqueIdentifier $ organizationalStatus $ buildingName $ documentPublisher $ ipServiceProtocol $ ipHostNumber $ ipNetworkNumber $ nisMapName $ carLicense $ departmentNumber $ displayName $ employeeNumber $ employeeType $ preferredLanguage ) )",
"( 2.5.13.5 NAME 'caseExactMatch' APPLIES ( supportedSASLMechanisms $ vendorName $ vendorVersion $ ref $ name $ cn $ uid $ labeledURI $ description $ olcConfigFile $ olcConfigDir $ olcAccess $ olcAllows $ olcArgsFile $ olcAttributeOptions $ olcAttributeTypes $ olcAuthIDRewrite $ olcAuthzPolicy $ olcAuthzRegexp $ olcBackend $ olcDatabase $ olcDisallows $ olcDitContentRules $ olcExtraAttrs $ olcInclude $ olcLdapSyntaxes $ olcLimits $ olcLogFile $ olcLogLevel $ olcModuleLoad $ olcModulePath $ olcObjectClasses $ olcObjectIdentifier $ olcOverlay $ olcPasswordCryptSaltFormat $ olcPasswordHash $ olcPidFile $ olcPlugin $ olcPluginLogFile $ olcReferral $ olcReplica $ olcReplicaArgsFile $ olcReplicaPidFile $ olcReplogFile $ olcRequires $ olcRestrict $ olcRootDSE $ olcRootPW $ olcSaslAuxprops $ olcSaslHost $ olcSaslRealm $ olcSaslSecProps $ olcSecurity $ olcServerID $ olcSizeLimit $ olcSortVals $ olcSubordinate $ olcSyncrepl $ olcTCPBuffer $ olcTimeLimit $ olcTLSCACertificateFile $ olcTLSCACertificatePath $ olcTLSCertificateFile $ olcTLSCertificateKeyFile $ olcTLSCipherSuite $ olcTLSCRLCheck $ olcTLSCRLFile $ olcTLSRandFile $ olcTLSVerifyClient $ olcTLSDHParamFile $ olcTLSProtocolMin $ olcUpdateRef $ olcDbDirectory $ olcDbCheckpoint $ olcDbEnvFlags $ olcDbIndex $ olcDbMode $ olcMemberOfDangling $ olcMemberOfGroupOC $ olcMemberOfMemberAD $ olcMemberOfMemberOfAD $ olcMemberOfDanglingError $ olcUniqueIgnore $ olcUniqueAttribute $ olcUniqueURI $ olcRefintAttribute $ knowledgeInformation $ sn $ serialNumber $ c $ l $ st $ street $ o $ ou $ title $ businessCategory $ postalCode $ postOfficeBox $ physicalDeliveryOfficeName $ telephoneNumber $ destinationIndicator $ givenName $ initials $ generationQualifier $ dnQualifier $ houseIdentifier $ dmdName $ pseudonym $ textEncodedORAddress $ info $ drink $ roomNumber $ userClass $ host $ documentIdentifier $ documentTitle $ documentVersion $ documentLocation $ homePhone $ personalTitle $ mobile $ pager $ co $ uniqueIdentifier $ organizationalStatus $ buildingName $ documentPublisher $ ipServiceProtocol $ ipHostNumber $ ipNetworkNumber $ nisMapName $ carLicense $ departmentNumber $ displayName $ employeeNumber $ employeeType $ preferredLanguage ) )",
"( 2.5.13.4 NAME 'caseIgnoreSubstringsMatch' APPLIES ( serialNumber $ c $ telephoneNumber $ destinationIndicator $ dnQualifier $ homePhone $ mobile $ pager ) )",
"( 2.5.13.3 NAME 'caseIgnoreOrderingMatch' APPLIES ( supportedSASLMechanisms $ vendorName $ vendorVersion $ ref $ name $ cn $ uid $ labeledURI $ description $ olcConfigFile $ olcConfigDir $ olcAccess $ olcAllows $ olcArgsFile $ olcAttributeOptions $ olcAttributeTypes $ olcAuthIDRewrite $ olcAuthzPolicy $ olcAuthzRegexp $ olcBackend $ olcDatabase $ olcDisallows $ olcDitContentRules $ olcExtraAttrs $ olcInclude $ olcLdapSyntaxes $ olcLimits $ olcLogFile $ olcLogLevel $ olcModuleLoad $ olcModulePath $ olcObjectClasses $ olcObjectIdentifier $ olcOverlay $ olcPasswordCryptSaltFormat $ olcPasswordHash $ olcPidFile $ olcPlugin $ olcPluginLogFile $ olcReferral $ olcReplica $ olcReplicaArgsFile $ olcReplicaPidFile $ olcReplogFile $ olcRequires $ olcRestrict $ olcRootDSE $ olcRootPW $ olcSaslAuxprops $ olcSaslHost $ olcSaslRealm $ olcSaslSecProps $ olcSecurity $ olcServerID $ olcSizeLimit $ olcSortVals $ olcSubordinate $ olcSyncrepl $ olcTCPBuffer $ olcTimeLimit $ olcTLSCACertificateFile $ olcTLSCACertificatePath $ olcTLSCertificateFile $ olcTLSCertificateKeyFile $ olcTLSCipherSuite $ olcTLSCRLCheck $ olcTLSCRLFile $ olcTLSRandFile $ olcTLSVerifyClient $ olcTLSDHParamFile $ olcTLSProtocolMin $ olcUpdateRef $ olcDbDirectory $ olcDbCheckpoint $ olcDbEnvFlags $ olcDbIndex $ olcDbMode $ olcMemberOfDangling $ olcMemberOfGroupOC $ olcMemberOfMemberAD $ olcMemberOfMemberOfAD $ olcMemberOfDanglingError $ olcUniqueIgnore $ olcUniqueAttribute $ olcUniqueURI $ olcRefintAttribute $ knowledgeInformation $ sn $ serialNumber $ c $ l $ st $ street $ o $ ou $ title $ businessCategory $ postalCode $ postOfficeBox $ physicalDeliveryOfficeName $ telephoneNumber $ destinationIndicator $ givenName $ initials $ generationQualifier $ dnQualifier $ houseIdentifier $ dmdName $ pseudonym $ textEncodedORAddress $ info $ drink $ roomNumber $ userClass $ host $ documentIdentifier $ documentTitle $ documentVersion $ documentLocation $ homePhone $ personalTitle $ mobile $ pager $ co $ uniqueIdentifier $ organizationalStatus $ buildingName $ documentPublisher $ ipServiceProtocol $ ipHostNumber $ ipNetworkNumber $ nisMapName $ carLicense $ departmentNumber $ displayName $ employeeNumber $ employeeType $ preferredLanguage ) )",
"( 2.5.13.2 NAME 'caseIgnoreMatch' APPLIES ( supportedSASLMechanisms $ vendorName $ vendorVersion $ ref $ name $ cn $ uid $ labeledURI $ description $ olcConfigFile $ olcConfigDir $ olcAccess $ olcAllows $ olcArgsFile $ olcAttributeOptions $ olcAttributeTypes $ olcAuthIDRewrite $ olcAuthzPolicy $ olcAuthzRegexp $ olcBackend $ olcDatabase $ olcDisallows $ olcDitContentRules $ olcExtraAttrs $ olcInclude $ olcLdapSyntaxes $ olcLimits $ olcLogFile $ olcLogLevel $ olcModuleLoad $ olcModulePath $ olcObjectClasses $ olcObjectIdentifier $ olcOverlay $ olcPasswordCryptSaltFormat $ olcPasswordHash $ olcPidFile $ olcPlugin $ olcPluginLogFile $ olcReferral $ olcReplica $ olcReplicaArgsFile $ olcReplicaPidFile $ olcReplogFile $ olcRequires $ olcRestrict $ olcRootDSE $ olcRootPW $ olcSaslAuxprops $ olcSaslHost $ olcSaslRealm $ olcSaslSecProps $ olcSecurity $ olcServerID $ olcSizeLimit $ olcSortVals $ olcSubordinate $ olcSyncrepl $ olcTCPBuffer $ olcTimeLimit $ olcTLSCACertificateFile $ olcTLSCACertificatePath $ olcTLSCertificateFile $ olcTLSCertificateKeyFile $ olcTLSCipherSuite $ olcTLSCRLCheck $ olcTLSCRLFile $ olcTLSRandFile $ olcTLSVerifyClient $ olcTLSDHParamFile $ olcTLSProtocolMin $ olcUpdateRef $ olcDbDirectory $ olcDbCheckpoint $ olcDbEnvFlags $ olcDbIndex $ olcDbMode $ olcMemberOfDangling $ olcMemberOfGroupOC $ olcMemberOfMemberAD $ olcMemberOfMemberOfAD $ olcMemberOfDanglingError $ olcUniqueIgnore $ olcUniqueAttribute $ olcUniqueURI $ olcRefintAttribute $ knowledgeInformation $ sn $ serialNumber $ c $ l $ st $ street $ o $ ou $ title $ businessCategory $ postalCode $ postOfficeBox $ physicalDeliveryOfficeName $ telephoneNumber $ destinationIndicator $ givenName $ initials $ generationQualifier $ dnQualifier $ houseIdentifier $ dmdName $ pseudonym $ textEncodedORAddress $ info $ drink $ roomNumber $ userClass $ host $ documentIdentifier $ documentTitle $ documentVersion $ documentLocation $ homePhone $ personalTitle $ mobile $ pager $ co $ uniqueIdentifier $ organizationalStatus $ buildingName $ documentPublisher $ ipServiceProtocol $ ipHostNumber $ ipNetworkNumber $ nisMapName $ carLicense $ departmentNumber $ displayName $ employeeNumber $ employeeType $ preferredLanguage ) )",
"( 2.5.13.1 NAME 'distinguishedNameMatch' APPLIES ( creatorsName $ modifiersName $ subschemaSubentry $ entryDN $ namingContexts $ aliasedObjectName $ dynamicSubtrees $ distinguishedName $ seeAlso $ olcDefaultSearchBase $ olcRootDN $ olcSchemaDN $ olcSuffix $ olcUpdateDN $ memberOf $ olcMemberOfDN $ olcUniqueBase $ olcRefintNothing $ olcRefintModifiersName $ member $ owner $ roleOccupant $ manager $ documentAuthor $ secretary $ associatedName $ dITRedirect ) )",
"( 2.5.13.0 NAME 'objectIdentifierMatch' APPLIES ( supportedControl $ supportedExtension $ supportedFeatures $ supportedApplicationContext ) )"
],
"matchingRules": [
"( 1.3.6.1.1.16.3 NAME 'UUIDOrderingMatch' SYNTAX 1.3.6.1.1.16.1 )",
"( 1.3.6.1.1.16.2 NAME 'UUIDMatch' SYNTAX 1.3.6.1.1.16.1 )",
"( 1.2.840.113556.1.4.804 NAME 'integerBitOrMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )",
"( 1.2.840.113556.1.4.803 NAME 'integerBitAndMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )",
"( 1.3.6.1.4.1.4203.1.2.1 NAME 'caseExactIA5SubstringsMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
"( 1.3.6.1.4.1.1466.109.114.3 NAME 'caseIgnoreIA5SubstringsMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
"( 1.3.6.1.4.1.1466.109.114.2 NAME 'caseIgnoreIA5Match' SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
"( 1.3.6.1.4.1.1466.109.114.1 NAME 'caseExactIA5Match' SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )",
"( 2.5.13.38 NAME 'certificateListExactMatch' SYNTAX 1.3.6.1.1.15.5 )",
"( 2.5.13.34 NAME 'certificateExactMatch' SYNTAX 1.3.6.1.1.15.1 )",
"( 2.5.13.30 NAME 'objectIdentifierFirstComponentMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )",
"( 2.5.13.29 NAME 'integerFirstComponentMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )",
"( 2.5.13.28 NAME 'generalizedTimeOrderingMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 )",
"( 2.5.13.27 NAME 'generalizedTimeMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 )",
"( 2.5.13.23 NAME 'uniqueMemberMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.34 )",
"( 2.5.13.21 NAME 'telephoneNumberSubstringsMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.58 )",
"( 2.5.13.20 NAME 'telephoneNumberMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 )",
"( 2.5.13.19 NAME 'octetStringSubstringsMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )",
"( 2.5.13.18 NAME 'octetStringOrderingMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )",
"( 2.5.13.17 NAME 'octetStringMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )",
"( 2.5.13.16 NAME 'bitStringMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.6 )",
"( 2.5.13.15 NAME 'integerOrderingMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )",
"( 2.5.13.14 NAME 'integerMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )",
"( 2.5.13.13 NAME 'booleanMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 )",
"( 2.5.13.11 NAME 'caseIgnoreListMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 )",
"( 2.5.13.10 NAME 'numericStringSubstringsMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.58 )",
"( 2.5.13.9 NAME 'numericStringOrderingMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 )",
"( 2.5.13.8 NAME 'numericStringMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 )",
"( 2.5.13.7 NAME 'caseExactSubstringsMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.58 )",
"( 2.5.13.6 NAME 'caseExactOrderingMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 2.5.13.5 NAME 'caseExactMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 2.5.13.4 NAME 'caseIgnoreSubstringsMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.58 )",
"( 2.5.13.3 NAME 'caseIgnoreOrderingMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 2.5.13.2 NAME 'caseIgnoreMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
"( 1.2.36.79672281.1.13.3 NAME 'rdnMatch' SYNTAX 1.2.36.79672281.1.5.0 )",
"( 2.5.13.1 NAME 'distinguishedNameMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )",
"( 2.5.13.0 NAME 'objectIdentifierMatch' SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )"
],
"modifyTimestamp": [
"20200930024551Z"
],
"nameForms": [],
"objectClass": [
"top",
"subentry",
"subschema",
"extensibleObject"
],
"objectClasses": [
"( 2.5.6.0 NAME 'top' DESC 'top of the superclass chain' ABSTRACT MUST objectClass )",
"( 1.3.6.1.4.1.1466.101.120.111 NAME 'extensibleObject' DESC 'RFC4512: extensible object' SUP top AUXILIARY )",
"( 2.5.6.1 NAME 'alias' DESC 'RFC4512: an alias' SUP top STRUCTURAL MUST aliasedObjectName )",
"( 2.16.840.1.113730.3.2.6 NAME 'referral' DESC 'namedref: named subordinate referral' SUP top STRUCTURAL MUST ref )",
"( 1.3.6.1.4.1.4203.1.4.1 NAME ( 'OpenLDAProotDSE' 'LDAProotDSE' ) DESC 'OpenLDAP Root DSE object' SUP top STRUCTURAL MAY cn )",
"( 2.5.17.0 NAME 'subentry' DESC 'RFC3672: subentry' SUP top STRUCTURAL MUST ( cn $ subtreeSpecification ) )",
"( 2.5.20.1 NAME 'subschema' DESC 'RFC4512: controlling subschema (sub)entry' AUXILIARY MAY ( dITStructureRules $ nameForms $ dITContentRules $ objectClasses $ attributeTypes $ matchingRules $ matchingRuleUse ) )",
"( 1.3.6.1.4.1.1466.101.119.2 NAME 'dynamicObject' DESC 'RFC2589: Dynamic Object' SUP top AUXILIARY )",
"( 1.3.6.1.4.1.4203.1.12.2.4.0.0 NAME 'olcConfig' DESC 'OpenLDAP configuration object' SUP top ABSTRACT )",
"( 1.3.6.1.4.1.4203.1.12.2.4.0.1 NAME 'olcGlobal' DESC 'OpenLDAP Global configuration options' SUP olcConfig STRUCTURAL MAY ( cn $ olcConfigFile $ olcConfigDir $ olcAllows $ olcArgsFile $ olcAttributeOptions $ olcAuthIDRewrite $ olcAuthzPolicy $ olcAuthzRegexp $ olcConcurrency $ olcConnMaxPending $ olcConnMaxPendingAuth $ olcDisallows $ olcGentleHUP $ olcIdleTimeout $ olcIndexSubstrIfMaxLen $ olcIndexSubstrIfMinLen $ olcIndexSubstrAnyLen $ olcIndexSubstrAnyStep $ olcIndexIntLen $ olcListenerThreads $ olcLocalSSF $ olcLogFile $ olcLogLevel $ olcPasswordCryptSaltFormat $ olcPasswordHash $ olcPidFile $ olcPluginLogFile $ olcReadOnly $ olcReferral $ olcReplogFile $ olcRequires $ olcRestrict $ olcReverseLookup $ olcRootDSE $ olcSaslAuxprops $ olcSaslHost $ olcSaslRealm $ olcSaslSecProps $ olcSecurity $ olcServerID $ olcSizeLimit $ olcSockbufMaxIncoming $ olcSockbufMaxIncomingAuth $ olcTCPBuffer $ olcThreads $ olcTimeLimit $ olcTLSCACertificateFile $ olcTLSCACertificatePath $ olcTLSCertificateFile $ olcTLSCertificateKeyFile $ olcTLSCipherSuite $ olcTLSCRLCheck $ olcTLSRandFile $ olcTLSVerifyClient $ olcTLSDHParamFile $ olcTLSCRLFile $ olcTLSProtocolMin $ olcToolThreads $ olcWriteTimeout $ olcObjectIdentifier $ olcAttributeTypes $ olcObjectClasses $ olcDitContentRules $ olcLdapSyntaxes ) )",
"( 1.3.6.1.4.1.4203.1.12.2.4.0.2 NAME 'olcSchemaConfig' DESC 'OpenLDAP schema object' SUP olcConfig STRUCTURAL MAY ( cn $ olcObjectIdentifier $ olcLdapSyntaxes $ olcAttributeTypes $ olcObjectClasses $ olcDitContentRules ) )",
"( 1.3.6.1.4.1.4203.1.12.2.4.0.3 NAME 'olcBackendConfig' DESC 'OpenLDAP Backend-specific options' SUP olcConfig STRUCTURAL MUST olcBackend )",
"( 1.3.6.1.4.1.4203.1.12.2.4.0.4 NAME 'olcDatabaseConfig' DESC 'OpenLDAP Database-specific options' SUP olcConfig STRUCTURAL MUST olcDatabase MAY ( olcHidden $ olcSuffix $ olcSubordinate $ olcAccess $ olcAddContentAcl $ olcLastMod $ olcLimits $ olcMaxDerefDepth $ olcPlugin $ olcReadOnly $ olcReplica $ olcReplicaArgsFile $ olcReplicaPidFile $ olcReplicationInterval $ olcReplogFile $ olcRequires $ olcRestrict $ olcRootDN $ olcRootPW $ olcSchemaDN $ olcSecurity $ olcSizeLimit $ olcSyncUseSubentry $ olcSyncrepl $ olcTimeLimit $ olcUpdateDN $ olcUpdateRef $ olcMirrorMode $ olcMonitoring $ olcExtraAttrs ) )",
"( 1.3.6.1.4.1.4203.1.12.2.4.0.5 NAME 'olcOverlayConfig' DESC 'OpenLDAP Overlay-specific options' SUP olcConfig STRUCTURAL MUST olcOverlay )",
"( 1.3.6.1.4.1.4203.1.12.2.4.0.6 NAME 'olcIncludeFile' DESC 'OpenLDAP configuration include file' SUP olcConfig STRUCTURAL MUST olcInclude MAY ( cn $ olcRootDSE ) )",
"( 1.3.6.1.4.1.4203.1.12.2.4.0.7 NAME 'olcFrontendConfig' DESC 'OpenLDAP frontend configuration' AUXILIARY MAY ( olcDefaultSearchBase $ olcPasswordHash $ olcSortVals ) )",
"( 1.3.6.1.4.1.4203.1.12.2.4.0.8 NAME 'olcModuleList' DESC 'OpenLDAP dynamic module info' SUP olcConfig STRUCTURAL MAY ( cn $ olcModulePath $ olcModuleLoad ) )",
"( 1.3.6.1.4.1.4203.1.12.2.4.2.2.1 NAME 'olcLdifConfig' DESC 'LDIF backend configuration' SUP olcDatabaseConfig STRUCTURAL MUST olcDbDirectory )",
"( 1.3.6.1.4.1.4203.1.12.2.4.2.12.1 NAME 'olcMdbConfig' DESC 'MDB backend configuration' SUP olcDatabaseConfig STRUCTURAL MUST olcDbDirectory MAY ( olcDbCheckpoint $ olcDbEnvFlags $ olcDbNoSync $ olcDbIndex $ olcDbMaxReaders $ olcDbMaxSize $ olcDbMode $ olcDbSearchStack $ olcDbRtxnSize ) )",
"( 1.3.6.1.4.1.4203.1.12.2.4.3.18.1 NAME 'olcMemberOf' DESC 'Member-of configuration' SUP olcOverlayConfig STRUCTURAL MAY ( olcMemberOfDN $ olcMemberOfDangling $ olcMemberOfDanglingError $ olcMemberOfRefInt $ olcMemberOfGroupOC $ olcMemberOfMemberAD $ olcMemberOfMemberOfAD ) )",
"( 1.3.6.1.4.1.4203.1.12.2.4.3.10.1 NAME 'olcUniqueConfig' DESC 'Attribute value uniqueness configuration' SUP olcOverlayConfig STRUCTURAL MAY ( olcUniqueBase $ olcUniqueIgnore $ olcUniqueAttribute $ olcUniqueStrict $ olcUniqueURI ) )",
"( 1.3.6.1.4.1.4203.1.12.2.4.3.11.1 NAME 'olcRefintConfig' DESC 'Referential integrity configuration' SUP olcOverlayConfig STRUCTURAL MAY ( olcRefintAttribute $ olcRefintNothing $ olcRefintModifiersName ) )",
"( 2.5.6.2 NAME 'country' DESC 'RFC2256: a country' SUP top STRUCTURAL MUST c MAY ( searchGuide $ description ) )",
"( 2.5.6.3 NAME 'locality' DESC 'RFC2256: a locality' SUP top STRUCTURAL MAY ( street $ seeAlso $ searchGuide $ st $ l $ description ) )",
"( 2.5.6.4 NAME 'organization' DESC 'RFC2256: an organization' SUP top STRUCTURAL MUST o MAY ( userPassword $ searchGuide $ seeAlso $ businessCategory $ x121Address $ registeredAddress $ destinationIndicator $ preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ telephoneNumber $ internationaliSDNNumber $ facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOfficeName $ st $ l $ description ) )",
"( 2.5.6.5 NAME 'organizationalUnit' DESC 'RFC2256: an organizational unit' SUP top STRUCTURAL MUST ou MAY ( userPassword $ searchGuide $ seeAlso $ businessCategory $ x121Address $ registeredAddress $ destinationIndicator $ preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ telephoneNumber $ internationaliSDNNumber $ facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOfficeName $ st $ l $ description ) )",
"( 2.5.6.6 NAME 'person' DESC 'RFC2256: a person' SUP top STRUCTURAL MUST ( sn $ cn ) MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) )",
"( 2.5.6.7 NAME 'organizationalPerson' DESC 'RFC2256: an organizational person' SUP person STRUCTURAL MAY ( title $ x121Address $ registeredAddress $ destinationIndicator $ preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ telephoneNumber $ internationaliSDNNumber $ facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOfficeName $ ou $ st $ l ) )",
"( 2.5.6.8 NAME 'organizationalRole' DESC 'RFC2256: an organizational role' SUP top STRUCTURAL MUST cn MAY ( x121Address $ registeredAddress $ destinationIndicator $ preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ telephoneNumber $ internationaliSDNNumber $ facsimileTelephoneNumber $ seeAlso $ roleOccupant $ preferredDeliveryMethod $ street $ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOfficeName $ ou $ st $ l $ description ) )",
"( 2.5.6.9 NAME 'groupOfNames' DESC 'RFC2256: a group of names (DNs)' SUP top STRUCTURAL MUST ( member $ cn ) MAY ( businessCategory $ seeAlso $ owner $ ou $ o $ description ) )",
"( 2.5.6.10 NAME 'residentialPerson' DESC 'RFC2256: an residential person' SUP person STRUCTURAL MUST l MAY ( businessCategory $ x121Address $ registeredAddress $ destinationIndicator $ preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ telephoneNumber $ internationaliSDNNumber $ facsimileTelephoneNumber $ preferredDeliveryMethod $ street $ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOfficeName $ st $ l ) )",
"( 2.5.6.11 NAME 'applicationProcess' DESC 'RFC2256: an application process' SUP top STRUCTURAL MUST cn MAY ( seeAlso $ ou $ l $ description ) )",
"( 2.5.6.12 NAME 'applicationEntity' DESC 'RFC2256: an application entity' SUP top STRUCTURAL MUST ( presentationAddress $ cn ) MAY ( supportedApplicationContext $ seeAlso $ ou $ o $ l $ description ) )",
"( 2.5.6.13 NAME 'dSA' DESC 'RFC2256: a directory system agent (a server)' SUP applicationEntity STRUCTURAL MAY knowledgeInformation )",
"( 2.5.6.14 NAME 'device' DESC 'RFC2256: a device' SUP top STRUCTURAL MUST cn MAY ( serialNumber $ seeAlso $ owner $ ou $ o $ l $ description ) )",
"( 2.5.6.15 NAME 'strongAuthenticationUser' DESC 'RFC2256: a strong authentication user' SUP top AUXILIARY MUST userCertificate )",
"( 2.5.6.16 NAME 'certificationAuthority' DESC 'RFC2256: a certificate authority' SUP top AUXILIARY MUST ( authorityRevocationList $ certificateRevocationList $ cACertificate ) MAY crossCertificatePair )",
"( 2.5.6.17 NAME 'groupOfUniqueNames' DESC 'RFC2256: a group of unique names (DN and Unique Identifier)' SUP top STRUCTURAL MUST ( uniqueMember $ cn ) MAY ( businessCategory $ seeAlso $ owner $ ou $ o $ description ) )",
"( 2.5.6.18 NAME 'userSecurityInformation' DESC 'RFC2256: a user security information' SUP top AUXILIARY MAY supportedAlgorithms )",
"( 2.5.6.16.2 NAME 'certificationAuthority-V2' SUP certificationAuthority AUXILIARY MAY deltaRevocationList )",
"( 2.5.6.19 NAME 'cRLDistributionPoint' SUP top STRUCTURAL MUST cn MAY ( certificateRevocationList $ authorityRevocationList $ deltaRevocationList ) )",
"( 2.5.6.20 NAME 'dmd' SUP top STRUCTURAL MUST dmdName MAY ( userPassword $ searchGuide $ seeAlso $ businessCategory $ x121Address $ registeredAddress $ destinationIndicator $ preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ telephoneNumber $ internationaliSDNNumber $ facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOfficeName $ st $ l $ description ) )",
"( 2.5.6.21 NAME 'pkiUser' DESC 'RFC2587: a PKI user' SUP top AUXILIARY MAY userCertificate )",
"( 2.5.6.22 NAME 'pkiCA' DESC 'RFC2587: PKI certificate authority' SUP top AUXILIARY MAY ( authorityRevocationList $ certificateRevocationList $ cACertificate $ crossCertificatePair ) )",
"( 2.5.6.23 NAME 'deltaCRL' DESC 'RFC2587: PKI user' SUP top AUXILIARY MAY deltaRevocationList )",
"( 1.3.6.1.4.1.250.3.15 NAME 'labeledURIObject' DESC 'RFC2079: object that contains the URI attribute type' SUP top AUXILIARY MAY labeledURI )",
"( 0.9.2342.19200300.100.4.19 NAME 'simpleSecurityObject' DESC 'RFC1274: simple security object' SUP top AUXILIARY MUST userPassword )",
"( 1.3.6.1.4.1.1466.344 NAME 'dcObject' DESC 'RFC2247: domain component object' SUP top AUXILIARY MUST dc )",
"( 1.3.6.1.1.3.1 NAME 'uidObject' DESC 'RFC2377: uid object' SUP top AUXILIARY MUST uid )",
"( 0.9.2342.19200300.100.4.4 NAME ( 'pilotPerson' 'newPilotPerson' ) SUP person STRUCTURAL MAY ( userid $ textEncodedORAddress $ rfc822Mailbox $ favouriteDrink $ roomNumber $ userClass $ homeTelephoneNumber $ homePostalAddress $ secretary $ personalTitle $ preferredDeliveryMethod $ businessCategory $ janetMailbox $ otherMailbox $ mobileTelephoneNumber $ pagerTelephoneNumber $ organizationalStatus $ mailPreferenceOption $ personalSignature ) )",
"( 0.9.2342.19200300.100.4.5 NAME 'account' SUP top STRUCTURAL MUST userid MAY ( description $ seeAlso $ localityName $ organizationName $ organizationalUnitName $ host ) )",
"( 0.9.2342.19200300.100.4.6 NAME 'document' SUP top STRUCTURAL MUST documentIdentifier MAY ( commonName $ description $ seeAlso $ localityName $ organizationName $ organizationalUnitName $ documentTitle $ documentVersion $ documentAuthor $ documentLocation $ documentPublisher ) )",
"( 0.9.2342.19200300.100.4.7 NAME 'room' SUP top STRUCTURAL MUST commonName MAY ( roomNumber $ description $ seeAlso $ telephoneNumber ) )",
"( 0.9.2342.19200300.100.4.9 NAME 'documentSeries' SUP top STRUCTURAL MUST commonName MAY ( description $ seeAlso $ telephonenumber $ localityName $ organizationName $ organizationalUnitName ) )",
"( 0.9.2342.19200300.100.4.13 NAME 'domain' SUP top STRUCTURAL MUST domainComponent MAY ( associatedName $ organizationName $ description $ businessCategory $ seeAlso $ searchGuide $ userPassword $ localityName $ stateOrProvinceName $ streetAddress $ physicalDeliveryOfficeName $ postalAddress $ postalCode $ postOfficeBox $ streetAddress $ facsimileTelephoneNumber $ internationalISDNNumber $ telephoneNumber $ teletexTerminalIdentifier $ telexNumber $ preferredDeliveryMethod $ destinationIndicator $ registeredAddress $ x121Address ) )",
"( 0.9.2342.19200300.100.4.14 NAME 'RFC822localPart' SUP domain STRUCTURAL MAY ( commonName $ surname $ description $ seeAlso $ telephoneNumber $ physicalDeliveryOfficeName $ postalAddress $ postalCode $ postOfficeBox $ streetAddress $ facsimileTelephoneNumber $ internationalISDNNumber $ telephoneNumber $ teletexTerminalIdentifier $ telexNumber $ preferredDeliveryMethod $ destinationIndicator $ registeredAddress $ x121Address ) )",
"( 0.9.2342.19200300.100.4.15 NAME 'dNSDomain' SUP domain STRUCTURAL MAY ( ARecord $ MDRecord $ MXRecord $ NSRecord $ SOARecord $ CNAMERecord ) )",
"( 0.9.2342.19200300.100.4.17 NAME 'domainRelatedObject' DESC 'RFC1274: an object related to an domain' SUP top AUXILIARY MUST associatedDomain )",
"( 0.9.2342.19200300.100.4.18 NAME 'friendlyCountry' SUP country STRUCTURAL MUST friendlyCountryName )",
"( 0.9.2342.19200300.100.4.20 NAME 'pilotOrganization' SUP ( organization $ organizationalUnit ) STRUCTURAL MAY buildingName )",
"( 0.9.2342.19200300.100.4.21 NAME 'pilotDSA' SUP dsa STRUCTURAL MAY dSAQuality )",
"( 0.9.2342.19200300.100.4.22 NAME 'qualityLabelledData' SUP top AUXILIARY MUST dsaQuality MAY ( subtreeMinimumQuality $ subtreeMaximumQuality ) )",
"( 1.3.6.1.1.1.2.0 NAME 'posixAccount' DESC 'Abstraction of an account with POSIX attributes' SUP top AUXILIARY MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory ) MAY ( userPassword $ loginShell $ gecos $ description ) )",
"( 1.3.6.1.1.1.2.1 NAME 'shadowAccount' DESC 'Additional attributes for shadow passwords' SUP top AUXILIARY MUST uid MAY ( userPassword $ description $ shadowLastChange $ shadowMin $ shadowMax $ shadowWarning $ shadowInactive $ shadowExpire $ shadowFlag ) )",
"( 1.3.6.1.1.1.2.2 NAME 'posixGroup' DESC 'Abstraction of a group of accounts' SUP top AUXILIARY MUST gidNumber MAY ( userPassword $ memberUid $ description ) )",
"( 1.3.6.1.1.1.2.3 NAME 'ipService' DESC 'Abstraction an Internet Protocol service. Maps an IP port and protocol (such as tcp or udp) to one or more names; the distinguished value of the cn attribute denotes the services canonical name' SUP top STRUCTURAL MUST ( cn $ ipServicePort $ ipServiceProtocol ) MAY description )",
"( 1.3.6.1.1.1.2.4 NAME 'ipProtocol' DESC 'Abstraction of an IP protocol. Maps a protocol number to one or more names. The distinguished value of the cn attribute denotes the protocols canonical name' SUP top STRUCTURAL MUST ( cn $ ipProtocolNumber ) MAY description )",
"( 1.3.6.1.1.1.2.5 NAME 'oncRpc' DESC 'Abstraction of an Open Network Computing (ONC) [RFC1057] Remote Procedure Call (RPC) binding. This class maps an ONC RPC number to a name. The distinguished value of the cn attribute denotes the RPC services canonical name' SUP top STRUCTURAL MUST ( cn $ oncRpcNumber ) MAY description )",
"( 1.3.6.1.1.1.2.6 NAME 'ipHost' DESC 'Abstraction of a host, an IP device. The distinguished value of the cn attribute denotes the hosts canonical name. Device SHOULD be used as a structural class' SUP top AUXILIARY MUST ( cn $ ipHostNumber ) MAY ( userPassword $ l $ description $ manager ) )",
"( 1.3.6.1.1.1.2.7 NAME 'ipNetwork' DESC 'Abstraction of a network. The distinguished value of the cn attribute denotes the networks canonical name' SUP top STRUCTURAL MUST ipNetworkNumber MAY ( cn $ ipNetmaskNumber $ l $ description $ manager ) )",
"( 1.3.6.1.1.1.2.8 NAME 'nisNetgroup' DESC 'Abstraction of a netgroup. May refer to other netgroups' SUP top STRUCTURAL MUST cn MAY ( nisNetgroupTriple $ memberNisNetgroup $ description ) )",
"( 1.3.6.1.1.1.2.9 NAME 'nisMap' DESC 'A generic abstraction of a NIS map' SUP top STRUCTURAL MUST nisMapName MAY description )",
"( 1.3.6.1.1.1.2.10 NAME 'nisObject' DESC 'An entry in a NIS map' SUP top STRUCTURAL MUST ( cn $ nisMapEntry $ nisMapName ) MAY description )",
"( 1.3.6.1.1.1.2.11 NAME 'ieee802Device' DESC 'A device with a MAC address; device SHOULD be used as a structural class' SUP top AUXILIARY MAY macAddress )",
"( 1.3.6.1.1.1.2.12 NAME 'bootableDevice' DESC 'A device with boot parameters; device SHOULD be used as a structural class' SUP top AUXILIARY MAY ( bootFile $ bootParameter ) )",
"( 1.3.6.1.1.1.2.14 NAME 'nisKeyObject' DESC 'An object with a public and secret key' SUP top AUXILIARY MUST ( cn $ nisPublicKey $ nisSecretKey ) MAY ( uidNumber $ description ) )",
"( 1.3.6.1.1.1.2.15 NAME 'nisDomainObject' DESC 'Associates a NIS domain with a naming context' SUP top AUXILIARY MUST nisDomain )",
"( 1.3.6.1.1.1.2.16 NAME 'automountMap' SUP top STRUCTURAL MUST automountMapName MAY description )",
"( 1.3.6.1.1.1.2.17 NAME 'automount' DESC 'Automount information' SUP top STRUCTURAL MUST ( automountKey $ automountInformation ) MAY description )",
"( 1.3.6.1.4.1.5322.13.1.1 NAME 'namedObject' SUP top STRUCTURAL MAY cn )",
"( 2.16.840.1.113730.3.2.2 NAME 'inetOrgPerson' DESC 'RFC2798: Internet Organizational Person' SUP organizationalPerson STRUCTURAL MAY ( audio $ businessCategory $ carLicense $ departmentNumber $ displayName $ employeeNumber $ employeeType $ givenName $ homePhone $ homePostalAddress $ initials $ jpegPhoto $ labeledURI $ mail $ manager $ mobile $ o $ pager $ photo $ roomNumber $ secretary $ uid $ userCertificate $ x500uniqueIdentifier $ preferredLanguage $ userSMIMECertificate $ userPKCS12 ) )",
"( 1.3.6.1.4.1.4203.666.1.100 NAME 'postfixVirtual' DESC 'Postfix virtual map class' SUP top STRUCTURAL MUST uid MAY ( mailacceptinggeneralid $ maildrop ) )"
],
"structuralObjectClass": [
"subentry"
],
"subschemaSubentry": [
"cn=Subschema"
]
},
"schema_entry": "cn=Subschema",
"type": "SchemaInfo"
}
\ No newline at end of file
......@@ -2,7 +2,7 @@ import unittest
from flask import Flask, Blueprint, session, url_for
from uffd.csrf import csrf_bp, csrf_protect
from uffd.csrf import bp as csrf_bp, csrf_protect
uid_counter = 0
......
import unittest
import datetime
import time
from flask import url_for, session, request
# These imports are required, because otherwise we get circular imports?!
from uffd import ldap, user
from uffd.user.models import User
from uffd.mfa.models import MFAMethod, MFAType, RecoveryCodeMethod, TOTPMethod, WebauthnMethod, _hotp
from uffd import create_app, db
from utils import dump, UffdTestCase
class TestMfaPrimitives(unittest.TestCase):
def test_hotp(self):
self.assertEqual(_hotp(5555555, b'\xae\xa3T\x05\x89\xd6\xb76\xf61r\x92\xcc\xb5WZ\xe6)\x05q'), '458290')
self.assertEqual(_hotp(5555555, b'\xae\xa3T\x05\x89\xd6\xb76\xf61r\x92\xcc\xb5WZ\xe6)\x05q', digits=8), '20458290')
for digits in range(1, 10):
self.assertEqual(len(_hotp(1, b'abcd', digits=digits)), digits)
self.assertEqual(_hotp(1234, b''), '161024')
self.assertEqual(_hotp(0, b'\x04\x8fM\xcc\x7f\x82\x9c$a\x1b\xb3'), '279354')
self.assertEqual(_hotp(2**64-1, b'abcde'), '899292')
def get_fido2_test_cred(self):
try:
from fido2.ctap2 import AttestedCredentialData
except ImportError:
self.skipTest('fido2 could not be imported')
# Example public key from webauthn spec 6.5.1.1
return AttestedCredentialData(bytes.fromhex('00000000000000000000000000000000'+'0040'+'053cbcc9d37a61d3bac87cdcc77ee326256def08ab15775d3a720332e4101d14fae95aeee3bc9698781812e143c0597dc6e180595683d501891e9dd030454c0a'+'A501020326200121582065eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d2258201e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c'))
class TestMfaMethodModels(UffdTestCase):
def test_common_attributes(self):
method = MFAMethod(user=self.get_user(), name='testname')
self.assertTrue(method.created <= datetime.datetime.now())
self.assertEqual(method.name, 'testname')
self.assertEqual(method.user.loginname, 'testuser')
method.user = self.get_admin()
self.assertEqual(method.user.loginname, 'testadmin')
def test_recovery_code_method(self):
method = RecoveryCodeMethod(user=self.get_user())
db.session.add(method)
db.session.commit()
db.session = db.create_scoped_session() # Ensure the next query does not return the cached method object
_method = RecoveryCodeMethod.query.get(method.id)
self.assertFalse(hasattr(_method, 'code'))
self.assertFalse(_method.verify(''))
self.assertFalse(_method.verify('A'*8))
self.assertTrue(_method.verify(method.code))
def test_totp_method_attributes(self):
method = TOTPMethod(user=self.get_user(), name='testname')
self.assertEqual(method.name, 'testname')
# Restore method with key parameter
_method = TOTPMethod(user=self.get_user(), key=method.key, name='testname')
self.assertEqual(_method.name, 'testname')
self.assertEqual(method.raw_key, _method.raw_key)
self.assertEqual(method.issuer, _method.issuer)
self.assertEqual(method.accountname, _method.accountname)
self.assertEqual(method.key_uri, _method.key_uri)
db.session.add(method)
db.session.commit()
db.session = db.create_scoped_session() # Ensure the next query does not return the cached method object
# Restore method from db
_method = TOTPMethod.query.get(method.id)
self.assertEqual(_method.name, 'testname')
self.assertEqual(method.raw_key, _method.raw_key)
self.assertEqual(method.issuer, _method.issuer)
self.assertEqual(method.accountname, _method.accountname)
self.assertEqual(method.key_uri, _method.key_uri)
def test_totp_method_verify(self):
method = TOTPMethod(user=self.get_user())
counter = int(time.time()/30)
self.assertFalse(method.verify(''))
self.assertFalse(method.verify(_hotp(counter-2, method.raw_key)))
self.assertTrue(method.verify(_hotp(counter, method.raw_key)))
self.assertFalse(method.verify(_hotp(counter+2, method.raw_key)))
def test_webauthn_method(self):
data = get_fido2_test_cred(self)
method = WebauthnMethod(user=self.get_user(), cred=data, name='testname')
self.assertEqual(method.name, 'testname')
db.session.add(method)
db.session.commit()
db.session = db.create_scoped_session() # Ensure the next query does not return the cached method object
_method = WebauthnMethod.query.get(method.id)
self.assertEqual(_method.name, 'testname')
self.assertEqual(bytes(method.cred), bytes(_method.cred))
self.assertEqual(data.credential_id, _method.cred.credential_id)
self.assertEqual(data.public_key, _method.cred.public_key)
# We only test (de-)serialization here, as everything else is currently implemented in the views
class TestMfaViews(UffdTestCase):
def setUp(self):
super().setUp()
db.session.add(RecoveryCodeMethod(user=self.get_admin()))
db.session.add(TOTPMethod(user=self.get_admin(), name='Admin Phone'))
# We don't want to skip all tests only because fido2 is not installed!
#db.session.add(WebauthnMethod(user=get_testadmin(), cred=get_fido2_test_cred(self), name='Admin FIDO2 dongle'))
db.session.commit()
def add_recovery_codes(self, count=10):
user = self.get_user()
for _ in range(count):
db.session.add(RecoveryCodeMethod(user=user))
db.session.commit()
def add_totp(self):
db.session.add(TOTPMethod(user=self.get_user(), name='My phone'))
db.session.commit()
def add_webauthn(self):
db.session.add(WebauthnMethod(user=self.get_user(), cred=get_fido2_test_cred(self), name='My FIDO2 dongle'))
db.session.commit()
def test_setup_disabled(self):
self.login_as('user')
r = self.client.get(path=url_for('mfa.setup'), follow_redirects=True)
dump('mfa_setup_disabled', r)
self.assertEqual(r.status_code, 200)
def test_setup_recovery_codes(self):
self.login_as('user')
self.add_recovery_codes()
r = self.client.get(path=url_for('mfa.setup'), follow_redirects=True)
dump('mfa_setup_only_recovery_codes', r)
self.assertEqual(r.status_code, 200)
def test_setup_enabled(self):
self.login_as('user')
self.add_recovery_codes()
self.add_totp()
self.add_webauthn()
r = self.client.get(path=url_for('mfa.setup'), follow_redirects=True)
dump('mfa_setup_enabled', r)
self.assertEqual(r.status_code, 200)
def test_setup_few_recovery_codes(self):
self.login_as('user')
self.add_totp()
self.add_recovery_codes(1)
r = self.client.get(path=url_for('mfa.setup'), follow_redirects=True)
dump('mfa_setup_few_recovery_codes', r)
self.assertEqual(r.status_code, 200)
def test_setup_no_recovery_codes(self):
self.login_as('user')
self.add_totp()
r = self.client.get(path=url_for('mfa.setup'), follow_redirects=True)
dump('mfa_setup_no_recovery_codes', r)
self.assertEqual(r.status_code, 200)
def test_disable(self):
self.login_as('user')
self.add_recovery_codes()
self.add_totp()
admin_methods = len(MFAMethod.query.filter_by(dn=self.get_admin().dn).all())
r = self.client.get(path=url_for('mfa.disable'), follow_redirects=True)
dump('mfa_disable', r)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('mfa.disable_confirm'), follow_redirects=True)
dump('mfa_disable_submit', r)
self.assertEqual(r.status_code, 200)
self.assertEqual(len(MFAMethod.query.filter_by(dn=request.user.dn).all()), 0)
self.assertEqual(len(MFAMethod.query.filter_by(dn=self.get_admin().dn).all()), admin_methods)
def test_disable_recovery_only(self):
self.login_as('user')
self.add_recovery_codes()
admin_methods = len(MFAMethod.query.filter_by(dn=self.get_admin().dn).all())
self.assertNotEqual(len(MFAMethod.query.filter_by(dn=request.user.dn).all()), 0)
r = self.client.get(path=url_for('mfa.disable'), follow_redirects=True)
dump('mfa_disable_recovery_only', r)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('mfa.disable_confirm'), follow_redirects=True)
dump('mfa_disable_recovery_only_submit', r)
self.assertEqual(r.status_code, 200)
self.assertEqual(len(MFAMethod.query.filter_by(dn=request.user.dn).all()), 0)
self.assertEqual(len(MFAMethod.query.filter_by(dn=self.get_admin().dn).all()), admin_methods)
def test_admin_disable(self):
for method in MFAMethod.query.filter_by(dn=self.get_admin().dn).all():
if not isinstance(method, RecoveryCodeMethod):
db.session.delete(method)
db.session.commit()
self.add_recovery_codes()
self.add_totp()
self.login_as('admin')
self.assertIsNotNone(request.user)
admin_methods = len(MFAMethod.query.filter_by(dn=self.get_admin().dn).all())
r = self.client.get(path=url_for('mfa.admin_disable', uid=self.get_user().uid), follow_redirects=True)
dump('mfa_admin_disable', r)
self.assertEqual(r.status_code, 200)
self.assertEqual(len(MFAMethod.query.filter_by(dn=self.get_user().dn).all()), 0)
self.assertEqual(len(MFAMethod.query.filter_by(dn=self.get_admin().dn).all()), admin_methods)
def test_setup_recovery(self):
self.login_as('user')
self.assertEqual(len(RecoveryCodeMethod.query.filter_by(dn=request.user.dn).all()), 0)
r = self.client.post(path=url_for('mfa.setup_recovery'), follow_redirects=True)
dump('mfa_setup_recovery', r)
self.assertEqual(r.status_code, 200)
methods = RecoveryCodeMethod.query.filter_by(dn=request.user.dn).all()
self.assertNotEqual(len(methods), 0)
r = self.client.post(path=url_for('mfa.setup_recovery'), follow_redirects=True)
dump('mfa_setup_recovery_reset', r)
self.assertEqual(r.status_code, 200)
self.assertEqual(len(RecoveryCodeMethod.query.filter_by(id=methods[0].id).all()), 0)
self.assertNotEqual(len(methods), 0)
def test_setup_totp(self):
self.login_as('user')
self.add_recovery_codes()
r = self.client.get(path=url_for('mfa.setup_totp', name='My TOTP Authenticator'), follow_redirects=True)
dump('mfa_setup_totp', r)
self.assertEqual(r.status_code, 200)
self.assertNotEqual(len(session.get('mfa_totp_key', '')), 0)
def test_setup_totp_without_recovery(self):
self.login_as('user')
r = self.client.get(path=url_for('mfa.setup_totp', name='My TOTP Authenticator'), follow_redirects=True)
dump('mfa_setup_totp_without_recovery', r)
self.assertEqual(r.status_code, 200)
def test_setup_totp_finish(self):
self.login_as('user')
self.add_recovery_codes()
self.assertEqual(len(TOTPMethod.query.filter_by(dn=request.user.dn).all()), 0)
r = self.client.get(path=url_for('mfa.setup_totp', name='My TOTP Authenticator'), follow_redirects=True)
method = TOTPMethod(request.user, key=session.get('mfa_totp_key', ''))
code = _hotp(int(time.time()/30), method.raw_key)
r = self.client.post(path=url_for('mfa.setup_totp_finish', name='My TOTP Authenticator'), data={'code': code}, follow_redirects=True)
dump('mfa_setup_totp_finish', r)
self.assertEqual(r.status_code, 200)
self.assertEqual(len(TOTPMethod.query.filter_by(dn=request.user.dn).all()), 1)
def test_setup_totp_finish_without_recovery(self):
self.login_as('user')
self.assertEqual(len(TOTPMethod.query.filter_by(dn=request.user.dn).all()), 0)
r = self.client.get(path=url_for('mfa.setup_totp', name='My TOTP Authenticator'), follow_redirects=True)
method = TOTPMethod(request.user, key=session.get('mfa_totp_key', ''))
code = _hotp(int(time.time()/30), method.raw_key)
r = self.client.post(path=url_for('mfa.setup_totp_finish', name='My TOTP Authenticator'), data={'code': code}, follow_redirects=True)
dump('mfa_setup_totp_finish_without_recovery', r)
self.assertEqual(r.status_code, 200)
self.assertEqual(len(TOTPMethod.query.filter_by(dn=request.user.dn).all()), 0)
def test_setup_totp_finish_wrong_code(self):
self.login_as('user')
self.add_recovery_codes()
self.assertEqual(len(TOTPMethod.query.filter_by(dn=request.user.dn).all()), 0)
r = self.client.get(path=url_for('mfa.setup_totp', name='My TOTP Authenticator'), follow_redirects=True)
method = TOTPMethod(request.user, key=session.get('mfa_totp_key', ''))
code = _hotp(int(time.time()/30), method.raw_key)
code = str(int(code[0])+1)[-1] + code[1:]
r = self.client.post(path=url_for('mfa.setup_totp_finish', name='My TOTP Authenticator'), data={'code': code}, follow_redirects=True)
dump('mfa_setup_totp_finish_wrong_code', r)
self.assertEqual(r.status_code, 200)
self.assertEqual(len(TOTPMethod.query.filter_by(dn=request.user.dn).all()), 0)
def test_setup_totp_finish_empty_code(self):
self.login_as('user')
self.add_recovery_codes()
self.assertEqual(len(TOTPMethod.query.filter_by(dn=request.user.dn).all()), 0)
r = self.client.get(path=url_for('mfa.setup_totp', name='My TOTP Authenticator'), follow_redirects=True)
r = self.client.post(path=url_for('mfa.setup_totp_finish', name='My TOTP Authenticator'), data={'code': ''}, follow_redirects=True)
dump('mfa_setup_totp_finish_empty_code', r)
self.assertEqual(r.status_code, 200)
self.assertEqual(len(TOTPMethod.query.filter_by(dn=request.user.dn).all()), 0)
def test_delete_totp(self):
self.login_as('user')
self.add_recovery_codes()
self.add_totp()
method = TOTPMethod(request.user, name='test')
db.session.add(method)
db.session.commit()
self.assertEqual(len(TOTPMethod.query.filter_by(dn=request.user.dn).all()), 2)
r = self.client.get(path=url_for('mfa.delete_totp', id=method.id), follow_redirects=True)
dump('mfa_delete_totp', r)
self.assertEqual(r.status_code, 200)
self.assertEqual(len(TOTPMethod.query.filter_by(id=method.id).all()), 0)
self.assertEqual(len(TOTPMethod.query.filter_by(dn=request.user.dn).all()), 1)
# TODO: webauthn setup tests
def test_auth_integration(self):
self.add_recovery_codes()
self.add_totp()
db.session.commit()
self.assertIsNone(request.user)
r = self.login_as('user')
dump('mfa_auth_redirected', r)
self.assertEqual(r.status_code, 200)
self.assertIn(b'/mfa/auth', r.data)
self.assertIsNone(request.user)
r = self.client.get(path=url_for('mfa.auth'), follow_redirects=False)
dump('mfa_auth', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(request.user)
def test_auth_disabled(self):
self.assertIsNone(request.user)
self.login_as('user')
r = self.client.get(path=url_for('mfa.auth', ref='/redirecttarget'), follow_redirects=False)
self.assertEqual(r.status_code, 302)
self.assertTrue(r.location.endswith('/redirecttarget'))
self.assertIsNotNone(request.user)
def test_auth_recovery_only(self):
self.add_recovery_codes()
self.assertIsNone(request.user)
self.login_as('user')
r = self.client.get(path=url_for('mfa.auth', ref='/redirecttarget'), follow_redirects=False)
self.assertEqual(r.status_code, 302)
self.assertTrue(r.location.endswith('/redirecttarget'))
self.assertIsNotNone(request.user)
def test_auth_recovery_code(self):
self.add_recovery_codes()
self.add_totp()
method = RecoveryCodeMethod(user=self.get_user())
db.session.add(method)
db.session.commit()
method_id = method.id
self.login_as('user')
r = self.client.get(path=url_for('mfa.auth'), follow_redirects=False)
dump('mfa_auth_recovery_code', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(request.user)
r = self.client.post(path=url_for('mfa.auth_finish', ref='/redirecttarget'), data={'code': method.code})
self.assertEqual(r.status_code, 302)
self.assertTrue(r.location.endswith('/redirecttarget'))
self.assertIsNotNone(request.user)
self.assertEqual(len(RecoveryCodeMethod.query.filter_by(id=method_id).all()), 0)
def test_auth_totp_code(self):
self.add_recovery_codes()
self.add_totp()
method = TOTPMethod(user=self.get_user(), name='testname')
raw_key = method.raw_key
db.session.add(method)
db.session.commit()
self.login_as('user')
r = self.client.get(path=url_for('mfa.auth'), follow_redirects=False)
dump('mfa_auth_totp_code', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(request.user)
code = _hotp(int(time.time()/30), raw_key)
r = self.client.post(path=url_for('mfa.auth_finish'), data={'code': code}, follow_redirects=True)
dump('mfa_auth_totp_code_submit', r)
self.assertEqual(r.status_code, 200)
self.assertIsNotNone(request.user)
def test_auth_empty_code(self):
self.add_recovery_codes()
self.add_totp()
self.login_as('user')
r = self.client.get(path=url_for('mfa.auth'), follow_redirects=False)
self.assertEqual(r.status_code, 200)
self.assertIsNone(request.user)
r = self.client.post(path=url_for('mfa.auth_finish'), data={'code': ''}, follow_redirects=True)
dump('mfa_auth_empty_code', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(request.user)
def test_auth_invalid_code(self):
self.add_recovery_codes()
self.add_totp()
method = TOTPMethod(user=self.get_user(), name='testname')
raw_key = method.raw_key
db.session.add(method)
db.session.commit()
self.login_as('user')
r = self.client.get(path=url_for('mfa.auth'), follow_redirects=False)
self.assertEqual(r.status_code, 200)
self.assertIsNone(request.user)
code = _hotp(int(time.time()/30), raw_key)
code = str(int(code[0])+1)[-1] + code[1:]
r = self.client.post(path=url_for('mfa.auth_finish'), data={'code': code}, follow_redirects=True)
dump('mfa_auth_invalid_code', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(request.user)
def test_auth_ratelimit(self):
self.add_recovery_codes()
self.add_totp()
method = TOTPMethod(user=self.get_user(), name='testname')
raw_key = method.raw_key
db.session.add(method)
db.session.commit()
self.login_as('user')
self.assertIsNone(request.user)
code = _hotp(int(time.time()/30), raw_key)
inv_code = str(int(code[0])+1)[-1] + code[1:]
for i in range(20):
r = self.client.post(path=url_for('mfa.auth_finish'), data={'code': inv_code}, follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertIsNone(request.user)
r = self.client.post(path=url_for('mfa.auth_finish'), data={'code': code}, follow_redirects=True)
dump('mfa_auth_ratelimit', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(request.user)
# TODO: webauthn auth tests
class TestMfaViewsOL(TestMfaViews):
use_openldap = True
import datetime
from urllib.parse import urlparse, parse_qs
from flask import url_for, session
# These imports are required, because otherwise we get circular imports?!
from uffd import ldap, user
from uffd.user.models import User
from uffd.session.models import DeviceLoginConfirmation
from uffd.oauth2.models import OAuth2Client, OAuth2DeviceLoginInitiation
from uffd import create_app, db, ldap
from utils import dump, UffdTestCase
class TestOAuth2Client(UffdTestCase):
def setUpApp(self):
self.app.config['OAUTH2_CLIENTS'] = {
'test': {'client_secret': 'testsecret', 'redirect_uris': ['http://localhost:5009/callback', 'http://localhost:5009/callback2']},
'test1': {'client_secret': 'testsecret1', 'redirect_uris': ['http://localhost:5008/callback'], 'required_group': 'users'},
}
def test_from_id(self):
client = OAuth2Client.from_id('test')
self.assertEqual(client.client_id, 'test')
self.assertEqual(client.client_secret, 'testsecret')
self.assertEqual(client.redirect_uris, ['http://localhost:5009/callback', 'http://localhost:5009/callback2'])
self.assertEqual(client.default_redirect_uri, 'http://localhost:5009/callback')
self.assertEqual(client.default_scopes, ['profile'])
self.assertEqual(client.client_type, 'confidential')
client = OAuth2Client.from_id('test1')
self.assertEqual(client.client_id, 'test1')
self.assertEqual(client.required_group, 'users')
def test_access_allowed(self):
user = self.get_user() # has 'users' and 'uffd_access' group
admin = self.get_admin() # has 'users', 'uffd_access' and 'uffd_admin' group
client = OAuth2Client('test', '', [''], ['uffd_admin', ['users', 'notagroup']])
self.assertFalse(client.access_allowed(user))
self.assertTrue(client.access_allowed(admin))
# More required_group values are tested by TestUserModel.test_has_permission
class TestViews(UffdTestCase):
def setUpApp(self):
self.app.config['OAUTH2_CLIENTS'] = {
'test': {'client_secret': 'testsecret', 'redirect_uris': ['http://localhost:5009/callback', 'http://localhost:5009/callback2']},
'test1': {'client_secret': 'testsecret1', 'redirect_uris': ['http://localhost:5008/callback'], 'required_group': 'uffd_admin'},
}
def assert_authorization(self, r):
while True:
if r.status_code != 302 or r.location.startswith('http://localhost:5009/callback'):
break
r = self.client.get(r.location, follow_redirects=False)
self.assertEqual(r.status_code, 302)
self.assertTrue(r.location.startswith('http://localhost:5009/callback'))
args = parse_qs(urlparse(r.location).query)
self.assertEqual(args['state'], ['teststate'])
code = args['code'][0]
r = self.client.post(path=url_for('oauth2.token'),
data={'grant_type': 'authorization_code', 'code': code, 'redirect_uri': 'http://localhost:5009/callback', 'client_id': 'test', 'client_secret': 'testsecret'}, follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.content_type, 'application/json')
self.assertEqual(r.json['token_type'], 'Bearer')
self.assertEqual(r.json['scope'], 'profile')
token = r.json['access_token']
r = self.client.get(path=url_for('oauth2.userinfo'), headers=[('Authorization', 'Bearer %s'%token)], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.content_type, 'application/json')
user = self.get_user()
self.assertEqual(r.json['id'], user.uid)
self.assertEqual(r.json['name'], user.displayname)
self.assertEqual(r.json['nickname'], user.loginname)
self.assertEqual(r.json['email'], user.mail)
self.assertTrue(r.json.get('groups'))
def test_authorization(self):
self.login_as('user')
r = self.client.get(path=url_for('oauth2.authorize', response_type='code', client_id='test', state='teststate', redirect_uri='http://localhost:5009/callback'), follow_redirects=False)
self.assert_authorization(r)
def test_authorization_devicelogin_start(self):
ref = url_for('oauth2.authorize', response_type='code', client_id='test', state='teststate', redirect_uri='http://localhost:5009/callback')
r = self.client.get(path=url_for('session.devicelogin_start', ref=ref), follow_redirects=True)
# check response
initiation = OAuth2DeviceLoginInitiation.query.filter_by(id=session['devicelogin_id'], secret=session['devicelogin_secret']).one()
self.assertEqual(r.status_code, 200)
self.assertFalse(initiation.expired)
self.assertEqual(initiation.oauth2_client_id, 'test')
self.assertIsNotNone(initiation.description)
def test_authorization_devicelogin_auth(self):
with self.client.session_transaction() as _session:
initiation = OAuth2DeviceLoginInitiation(oauth2_client_id='test')
db.session.add(initiation)
confirmation = DeviceLoginConfirmation(initiation=initiation, user=self.get_user())
db.session.add(confirmation)
db.session.commit()
_session['devicelogin_id'] = initiation.id
_session['devicelogin_secret'] = initiation.secret
code = confirmation.code
self.client.get(path='/')
ref = url_for('oauth2.authorize', response_type='code', client_id='test', state='teststate', redirect_uri='http://localhost:5009/callback')
r = self.client.post(path=url_for('session.devicelogin_submit', ref=ref), data={'confirmation-code': code}, follow_redirects=False)
self.assert_authorization(r)
import unittest
from uffd.password_hash import *
class TestPasswordHashRegistry(unittest.TestCase):
def test(self):
registry = PasswordHashRegistry()
@registry.register
class TestPasswordHash:
METHOD_NAME = 'test'
def __init__(self, value, **kwargs):
self.value = value
self.kwargs = kwargs
@registry.register
class Test2PasswordHash:
METHOD_NAME = 'test2'
result = registry.parse('{test}data', key='value')
self.assertIsInstance(result, TestPasswordHash)
self.assertEqual(result.value, '{test}data')
self.assertEqual(result.kwargs, {'key': 'value'})
with self.assertRaises(ValueError):
registry.parse('{invalid}data')
with self.assertRaises(ValueError):
registry.parse('invalid')
with self.assertRaises(ValueError):
registry.parse('{invalid')
class TestPasswordHash(unittest.TestCase):
def setUp(self):
class TestPasswordHash(PasswordHash):
@classmethod
def from_password(cls, password):
cls(build_value(cls.METHOD_NAME, password))
def verify(self, password):
return self.data == password
class TestPasswordHash1(TestPasswordHash):
METHOD_NAME = 'test1'
class TestPasswordHash2(TestPasswordHash):
METHOD_NAME = 'test2'
self.TestPasswordHash1 = TestPasswordHash1
self.TestPasswordHash2 = TestPasswordHash2
def test(self):
obj = self.TestPasswordHash1('{test1}data')
self.assertEqual(obj.value, '{test1}data')
self.assertEqual(obj.data, 'data')
self.assertIs(obj.target_cls, self.TestPasswordHash1)
self.assertFalse(obj.needs_rehash)
def test_invalid(self):
with self.assertRaises(ValueError):
self.TestPasswordHash1('invalid')
with self.assertRaises(ValueError):
self.TestPasswordHash1('{invalid}data')
with self.assertRaises(ValueError):
self.TestPasswordHash1('{test2}data')
def test_target_cls(self):
obj = self.TestPasswordHash1('{test1}data', target_cls=self.TestPasswordHash1)
self.assertEqual(obj.value, '{test1}data')
self.assertEqual(obj.data, 'data')
self.assertIs(obj.target_cls, self.TestPasswordHash1)
self.assertFalse(obj.needs_rehash)
obj = self.TestPasswordHash1('{test1}data', target_cls=self.TestPasswordHash2)
self.assertEqual(obj.value, '{test1}data')
self.assertEqual(obj.data, 'data')
self.assertIs(obj.target_cls, self.TestPasswordHash2)
self.assertTrue(obj.needs_rehash)
obj = self.TestPasswordHash1('{test1}data', target_cls=PasswordHash)
self.assertEqual(obj.value, '{test1}data')
self.assertEqual(obj.data, 'data')
self.assertIs(obj.target_cls, PasswordHash)
self.assertFalse(obj.needs_rehash)
class TestPlaintextPasswordHash(unittest.TestCase):
def test_verify(self):
obj = PlaintextPasswordHash('{plain}password')
self.assertTrue(obj.verify('password'))
self.assertFalse(obj.verify('notpassword'))
def test_from_password(self):
obj = PlaintextPasswordHash.from_password('password')
self.assertEqual(obj.value, '{plain}password')
self.assertTrue(obj.verify('password'))
self.assertFalse(obj.verify('notpassword'))
class TestHashlibPasswordHash(unittest.TestCase):
def test_verify(self):
obj = SHA512PasswordHash('{sha512}sQnzu7wkTrgkQZF+0G1hi5AI3Qmzvv0bXgc5THBqi7mAsdd4Xll27ASbRt9fEyavWi6m0QP9B8lThf+rDKy8hg==')
self.assertTrue(obj.verify('password'))
self.assertFalse(obj.verify('notpassword'))
def test_from_password(self):
obj = SHA512PasswordHash.from_password('password')
self.assertIsNotNone(obj.value)
self.assertTrue(obj.value.startswith('{sha512}'))
self.assertTrue(obj.verify('password'))
self.assertFalse(obj.verify('notpassword'))
class TestSaltedHashlibPasswordHash(unittest.TestCase):
def test_verify(self):
obj = SaltedSHA512PasswordHash('{ssha512}dOeDLmVpHJThhHeag10Hm2g4T7s3SBE6rGHcXUolXJHVufY4qT782rwZ/0XE6cuLcBZ0KpnwmUzRpAEtZBdv+JYEEtZQs/uC')
self.assertTrue(obj.verify('password'))
self.assertFalse(obj.verify('notpassword'))
def test_from_password(self):
obj = SaltedSHA512PasswordHash.from_password('password')
self.assertIsNotNone(obj.value)
self.assertTrue(obj.value.startswith('{ssha512}'))
self.assertTrue(obj.verify('password'))
self.assertFalse(obj.verify('notpassword'))
class TestCryptPasswordHash(unittest.TestCase):
def test_verify(self):
obj = CryptPasswordHash('{crypt}$5$UbTTMBH9NRurlQcX$bUiUTyedvmArlVt.62ZLRV80e2v3DjcBp/tSDkP2imD')
self.assertTrue(obj.verify('password'))
self.assertFalse(obj.verify('notpassword'))
def test_from_password(self):
obj = CryptPasswordHash.from_password('password')
self.assertIsNotNone(obj.value)
self.assertTrue(obj.value.startswith('{crypt}'))
self.assertTrue(obj.verify('password'))
self.assertFalse(obj.verify('notpassword'))
class TestArgon2PasswordHash(unittest.TestCase):
def test_verify(self):
obj = Argon2PasswordHash('{argon2}$argon2id$v=19$m=102400,t=2,p=8$Jc8LpCgPLjwlN/7efHLvwQ$ZqSg3CFb2/hBb3X8hOq4aw')
self.assertTrue(obj.verify('password'))
self.assertFalse(obj.verify('notpassword'))
obj = Argon2PasswordHash('{argon2}$invalid$')
self.assertFalse(obj.verify('password'))
def test_from_password(self):
obj = Argon2PasswordHash.from_password('password')
self.assertIsNotNone(obj.value)
self.assertTrue(obj.value.startswith('{argon2}'))
self.assertTrue(obj.verify('password'))
self.assertFalse(obj.verify('notpassword'))
def test_needs_rehash(self):
obj = Argon2PasswordHash('{argon2}$argon2id$v=19$m=102400,t=2,p=8$Jc8LpCgPLjwlN/7efHLvwQ$ZqSg3CFb2/hBb3X8hOq4aw')
self.assertFalse(obj.needs_rehash)
obj = Argon2PasswordHash('{argon2}$argon2id$v=19$m=102400,t=2,p=8$Jc8LpCgPLjwlN/7efHLvwQ$ZqSg3CFb2/hBb3X8hOq4aw', target_cls=PlaintextPasswordHash)
self.assertTrue(obj.needs_rehash)
obj = Argon2PasswordHash('{argon2}$argon2d$v=19$m=102400,t=2,p=8$kshPgLU1+h72l/Z8QWh8Ig$tYerKCe/5I2BCPKu8hCl2w')
self.assertTrue(obj.needs_rehash)
obj = Argon2PasswordHash('{argon2}$argon2id$v=19$m=102400,t=1,p=8$aa6i4vg/szKX5xHVGFaAeQ$v6j0ltuVqQaZlmuepaVJ1A')
self.assertTrue(obj.needs_rehash)
class TestInvalidPasswordHash(unittest.TestCase):
def test(self):
obj = InvalidPasswordHash('test')
self.assertEqual(obj.value, 'test')
self.assertFalse(obj.verify('test'))
self.assertTrue(obj.needs_rehash)
self.assertFalse(obj)
obj = InvalidPasswordHash(None)
self.assertIsNone(obj.value)
self.assertFalse(obj.verify('test'))
self.assertTrue(obj.needs_rehash)
self.assertFalse(obj)
class TestPasswordWrapper(unittest.TestCase):
def setUp(self):
class Test:
password_hash = None
password = PasswordHashAttribute('password_hash', PlaintextPasswordHash)
self.test = Test()
def test_get_none(self):
self.test.password_hash = None
obj = self.test.password
self.assertIsInstance(obj, InvalidPasswordHash)
self.assertEqual(obj.value, None)
self.assertTrue(obj.needs_rehash)
def test_get_valid(self):
self.test.password_hash = '{plain}password'
obj = self.test.password
self.assertIsInstance(obj, PlaintextPasswordHash)
self.assertEqual(obj.value, '{plain}password')
self.assertFalse(obj.needs_rehash)
def test_get_needs_rehash(self):
self.test.password_hash = '{ssha512}dOeDLmVpHJThhHeag10Hm2g4T7s3SBE6rGHcXUolXJHVufY4qT782rwZ/0XE6cuLcBZ0KpnwmUzRpAEtZBdv+JYEEtZQs/uC'
obj = self.test.password
self.assertIsInstance(obj, SaltedSHA512PasswordHash)
self.assertEqual(obj.value, '{ssha512}dOeDLmVpHJThhHeag10Hm2g4T7s3SBE6rGHcXUolXJHVufY4qT782rwZ/0XE6cuLcBZ0KpnwmUzRpAEtZBdv+JYEEtZQs/uC')
self.assertTrue(obj.needs_rehash)
def test_set(self):
self.test.password = 'password'
self.assertEqual(self.test.password_hash, '{plain}password')
def test_set_none(self):
self.test.password = None
self.assertIsNone(self.test.password_hash)
import time
from uffd.models.ratelimit import get_addrkey, format_delay, Ratelimit
from flask import Flask, Blueprint, session, url_for
from uffd.ratelimit import get_addrkey, format_delay, Ratelimit, RatelimitEvent
from utils import UffdTestCase
from tests.utils import UffdTestCase
class TestRatelimit(UffdTestCase):
def test_limiting(self):
......@@ -48,19 +44,3 @@ class TestRatelimit(UffdTestCase):
self.assertIsInstance(format_delay(120), str)
self.assertIsInstance(format_delay(3600), str)
self.assertIsInstance(format_delay(4000), str)
def test_cleanup(self):
ratelimit = Ratelimit('test', 1, 1)
ratelimit.log('')
ratelimit.log('1')
ratelimit.log('2')
ratelimit.log('3')
ratelimit.log('4')
time.sleep(1)
ratelimit.log('5')
self.assertEqual(RatelimitEvent.query.filter(RatelimitEvent.name == 'test').count(), 6)
ratelimit.cleanup()
self.assertEqual(RatelimitEvent.query.filter(RatelimitEvent.name == 'test').count(), 1)
time.sleep(1)
ratelimit.cleanup()
self.assertEqual(RatelimitEvent.query.filter(RatelimitEvent.name == 'test').count(), 0)
from uffd.remailer import remailer
from tests.utils import UffdTestCase
USER_ID = 1234
SERVICE1_ID = 4223
SERVICE2_ID = 3242
ADDR_V1_S1 = 'v1-WzQyMjMsMTIzNF0.MeO6bHGTgIyPvvq2r3xriokLMCU@remailer.example.com'
ADDR_V1_S2 = 'v1-WzMyNDIsMTIzNF0.p2a_RkJc0oHBc9u4_S8G9METflA@remailer.example.com'
ADDR_V2_S1 = 'v2-lm2demrtfqytemzulu-ghr3u3drsoaizd567k3k67dlrkeqwmbf@remailer.example.com'
ADDR_V2_S2 = 'v2-lmztenbsfqytemzulu-u5tl6rscltjidqlt3o4p2lyg6targ7sq@remailer.example.com'
class TestRemailer(UffdTestCase):
def test_is_remailer_domain(self):
self.app.config['REMAILER_DOMAIN'] = 'remailer.example.com'
self.assertTrue(remailer.is_remailer_domain('remailer.example.com'))
self.assertTrue(remailer.is_remailer_domain('REMAILER.EXAMPLE.COM'))
self.assertTrue(remailer.is_remailer_domain(' remailer.example.com '))
self.assertFalse(remailer.is_remailer_domain('other.remailer.example.com'))
self.assertFalse(remailer.is_remailer_domain('example.com'))
self.app.config['REMAILER_OLD_DOMAINS'] = [' OTHER.remailer.example.com ']
self.assertTrue(remailer.is_remailer_domain(' OTHER.remailer.example.com '))
self.assertTrue(remailer.is_remailer_domain('remailer.example.com'))
self.assertTrue(remailer.is_remailer_domain('other.remailer.example.com'))
self.assertFalse(remailer.is_remailer_domain('example.com'))
def test_build_v1_address(self):
self.app.config['REMAILER_DOMAIN'] = 'remailer.example.com'
self.assertEqual(remailer.build_v1_address(SERVICE1_ID, USER_ID), ADDR_V1_S1)
self.assertEqual(remailer.build_v1_address(SERVICE2_ID, USER_ID), ADDR_V1_S2)
long_addr = remailer.build_v1_address(1000, 1000000)
self.assertLessEqual(len(long_addr.split('@')[0]), 64)
self.assertLessEqual(len(long_addr), 256)
self.app.config['REMAILER_OLD_DOMAINS'] = ['old.remailer.example.com']
self.assertEqual(remailer.build_v1_address(SERVICE1_ID, USER_ID), ADDR_V1_S1)
self.app.config['REMAILER_SECRET_KEY'] = self.app.config['SECRET_KEY']
self.assertEqual(remailer.build_v1_address(SERVICE1_ID, USER_ID), ADDR_V1_S1)
self.app.config['REMAILER_SECRET_KEY'] = 'REMAILER-DEBUGKEY'
self.assertNotEqual(remailer.build_v1_address(SERVICE1_ID, USER_ID), ADDR_V1_S1)
def test_build_v2_address(self):
self.app.config['REMAILER_DOMAIN'] = 'remailer.example.com'
self.assertEqual(remailer.build_v2_address(SERVICE1_ID, USER_ID), ADDR_V2_S1)
self.assertEqual(remailer.build_v2_address(SERVICE2_ID, USER_ID), ADDR_V2_S2)
long_addr = remailer.build_v2_address(1000, 1000000)
self.assertLessEqual(len(long_addr.split('@')[0]), 64)
self.assertLessEqual(len(long_addr), 256)
self.app.config['REMAILER_OLD_DOMAINS'] = ['old.remailer.example.com']
self.assertEqual(remailer.build_v2_address(SERVICE1_ID, USER_ID), ADDR_V2_S1)
self.app.config['REMAILER_SECRET_KEY'] = self.app.config['SECRET_KEY']
self.assertEqual(remailer.build_v2_address(SERVICE1_ID, USER_ID), ADDR_V2_S1)
self.app.config['REMAILER_SECRET_KEY'] = 'REMAILER-DEBUGKEY'
self.assertNotEqual(remailer.build_v2_address(SERVICE1_ID, USER_ID), ADDR_V2_S1)
def test_parse_address(self):
# REMAILER_DOMAIN behaviour
self.app.config['REMAILER_DOMAIN'] = None
self.assertIsNone(remailer.parse_address(ADDR_V1_S2))
self.assertIsNone(remailer.parse_address(ADDR_V2_S2))
self.assertIsNone(remailer.parse_address('foo@example.com'))
self.app.config['REMAILER_DOMAIN'] = 'remailer.example.com'
self.assertEqual(remailer.parse_address(ADDR_V1_S2), (SERVICE2_ID, USER_ID))
self.assertEqual(remailer.parse_address(ADDR_V2_S2), (SERVICE2_ID, USER_ID))
self.assertIsNone(remailer.parse_address('foo@example.com'))
self.assertIsNone(remailer.parse_address('foo@remailer.example.com'))
self.assertIsNone(remailer.parse_address('v1-foo@remailer.example.com'))
self.assertIsNone(remailer.parse_address('v2-foo@remailer.example.com'))
self.assertIsNone(remailer.parse_address('v2-foo-bar@remailer.example.com'))
self.app.config['REMAILER_DOMAIN'] = 'new-remailer.example.com'
self.assertIsNone(remailer.parse_address(ADDR_V1_S2))
self.assertIsNone(remailer.parse_address(ADDR_V2_S2))
self.app.config['REMAILER_OLD_DOMAINS'] = ['remailer.example.com']
self.assertEqual(remailer.parse_address(ADDR_V1_S2), (SERVICE2_ID, USER_ID))
self.assertEqual(remailer.parse_address(ADDR_V2_S2), (SERVICE2_ID, USER_ID))
# REMAILER_SECRET_KEY behaviour
self.app.config['REMAILER_DOMAIN'] = 'remailer.example.com'
self.app.config['REMAILER_OLD_DOMAINS'] = []
self.assertEqual(remailer.parse_address(ADDR_V1_S2), (SERVICE2_ID, USER_ID))
self.assertEqual(remailer.parse_address(ADDR_V2_S2), (SERVICE2_ID, USER_ID))
self.app.config['REMAILER_SECRET_KEY'] = self.app.config['SECRET_KEY']
self.assertEqual(remailer.parse_address(ADDR_V1_S2), (SERVICE2_ID, USER_ID))
self.assertEqual(remailer.parse_address(ADDR_V2_S2), (SERVICE2_ID, USER_ID))
self.app.config['REMAILER_SECRET_KEY'] = 'REMAILER-DEBUGKEY'
self.assertIsNone(remailer.parse_address(ADDR_V1_S2))
self.assertIsNone(remailer.parse_address(ADDR_V2_S2))
import datetime
import unittest
from flask import url_for, request
# These imports are required, because otherwise we get circular imports?!
from uffd import ldap, user
from uffd.selfservice.models import MailToken, PasswordToken
from uffd.user.models import User
from uffd.role.models import Role
from uffd import create_app, db
from utils import dump, UffdTestCase
class TestSelfservice(UffdTestCase):
def test_index(self):
self.login_as('user')
r = self.client.get(path=url_for('selfservice.index'))
dump('selfservice_index', r)
self.assertEqual(r.status_code, 200)
user = request.user
self.assertIn(user.displayname.encode(), r.data)
self.assertIn(user.loginname.encode(), r.data)
self.assertIn(user.mail.encode(), r.data)
def test_update_displayname(self):
self.login_as('user')
user = request.user
r = self.client.post(path=url_for('selfservice.update_profile'),
data={'displayname': 'New Display Name', 'mail': user.mail},
follow_redirects=True)
dump('update_displayname', r)
self.assertEqual(r.status_code, 200)
_user = request.user
self.assertEqual(_user.displayname, 'New Display Name')
def test_update_displayname_invalid(self):
self.login_as('user')
user = request.user
r = self.client.post(path=url_for('selfservice.update_profile'),
data={'displayname': '', 'mail': user.mail},
follow_redirects=True)
dump('update_displayname_invalid', r)
self.assertEqual(r.status_code, 200)
_user = request.user
self.assertNotEqual(_user.displayname, '')
def test_update_mail(self):
self.login_as('user')
user = request.user
r = self.client.post(path=url_for('selfservice.update_profile'),
data={'displayname': user.displayname, 'mail': 'newemail@example.com'},
follow_redirects=True)
dump('update_mail', r)
self.assertEqual(r.status_code, 200)
_user = request.user
self.assertNotEqual(_user.mail, 'newemail@example.com')
token = MailToken.query.filter(MailToken.loginname == user.loginname).first()
self.assertEqual(token.newmail, 'newemail@example.com')
self.assertIn(token.token, str(self.app.last_mail.get_content()))
r = self.client.get(path=url_for('selfservice.token_mail', token=token.token), follow_redirects=True)
self.assertEqual(r.status_code, 200)
_user = request.user
self.assertEqual(_user.mail, 'newemail@example.com')
def test_update_mail_sendfailure(self):
self.app.config['MAIL_SKIP_SEND'] = 'fail'
self.login_as('user')
user = request.user
r = self.client.post(path=url_for('selfservice.update_profile'),
data={'displayname': user.displayname, 'mail': 'newemail@example.com'},
follow_redirects=True)
dump('update_mail_sendfailure', r)
self.assertEqual(r.status_code, 200)
_user = request.user
self.assertNotEqual(_user.mail, 'newemail@example.com')
# Maybe also check that there is no new token in the db
def test_change_password(self):
self.login_as('user')
user = request.user
r = self.client.post(path=url_for('selfservice.change_password'),
data={'password1': 'newpassword', 'password2': 'newpassword'},
follow_redirects=True)
dump('change_password', r)
self.assertEqual(r.status_code, 200)
_user = request.user
self.assertTrue(ldap.test_user_bind(_user.dn, 'newpassword'))
def test_change_password_invalid(self):
self.login_as('user')
user = request.user
r = self.client.post(path=url_for('selfservice.change_password'),
data={'password1': 'shortpw', 'password2': 'shortpw'},
follow_redirects=True)
dump('change_password_invalid', r)
self.assertEqual(r.status_code, 200)
_user = request.user
self.assertFalse(ldap.test_user_bind(_user.dn, 'shortpw'))
self.assertTrue(ldap.test_user_bind(_user.dn, 'userpassword'))
def test_change_password_mismatch(self):
self.login_as('user')
user = request.user
r = self.client.post(path=url_for('selfservice.change_password'),
data={'password1': 'newpassword1', 'password2': 'newpassword2'},
follow_redirects=True)
dump('change_password_mismatch', r)
self.assertEqual(r.status_code, 200)
_user = request.user
self.assertFalse(ldap.test_user_bind(_user.dn, 'newpassword1'))
self.assertFalse(ldap.test_user_bind(_user.dn, 'newpassword2'))
self.assertTrue(ldap.test_user_bind(_user.dn, 'userpassword'))
def test_leave_role(self):
if self.use_userconnection:
self.skipTest('Leaving roles is not possible in user mode')
role1 = Role(name='testrole1')
role2 = Role(name='testrole2')
db.session.add(role1)
db.session.add(role2)
self.get_user().roles = [role1, role2]
db.session.commit()
roleid = role1.id
self.login_as('user')
r = self.client.post(path=url_for('selfservice.leave_role', roleid=roleid), follow_redirects=True)
dump('leave_role', r)
self.assertEqual(r.status_code, 200)
_user = self.get_user()
self.assertEqual(len(_user.roles), 1)
self.assertEqual(list(_user.roles)[0].name, 'testrole2')
def test_token_mail_emptydb(self):
self.login_as('user')
user = request.user
r = self.client.get(path=url_for('selfservice.token_mail', token='A'*128), follow_redirects=True)
dump('token_mail_emptydb', r)
self.assertEqual(r.status_code, 200)
_user = request.user
self.assertEqual(_user.mail, user.mail)
def test_token_mail_invalid(self):
self.login_as('user')
user = request.user
db.session.add(MailToken(loginname=user.loginname, newmail='newusermail@example.com'))
db.session.commit()
r = self.client.get(path=url_for('selfservice.token_mail', token='A'*128), follow_redirects=True)
dump('token_mail_invalid', r)
self.assertEqual(r.status_code, 200)
_user = request.user
self.assertEqual(_user.mail, user.mail)
@unittest.skip('See #26')
def test_token_mail_wrong_user(self):
self.login_as('user')
user = request.user
admin_user = self.get_admin()
db.session.add(MailToken(loginname=user.loginname, newmail='newusermail@example.com'))
admin_token = MailToken(loginname='testadmin', newmail='newadminmail@example.com')
db.session.add(admin_token)
db.session.commit()
r = self.client.get(path=url_for('selfservice.token_mail', token=admin_token.token), follow_redirects=True)
dump('token_mail_wrong_user', r)
self.assertEqual(r.status_code, 200)
_user = request.user
_admin_user = self.get_admin()
self.assertEqual(_user.mail, user.mail)
self.assertEqual(_admin_user.mail, admin_user.mail)
def test_token_mail_expired(self):
self.login_as('user')
user = request.user
token = MailToken(loginname=user.loginname, newmail='newusermail@example.com',
created=(datetime.datetime.now() - datetime.timedelta(days=10)))
db.session.add(token)
db.session.commit()
r = self.client.get(path=url_for('selfservice.token_mail', token=token.token), follow_redirects=True)
dump('token_mail_expired', r)
self.assertEqual(r.status_code, 200)
_user = request.user
self.assertEqual(_user.mail, user.mail)
tokens = MailToken.query.filter(MailToken.loginname == user.loginname).all()
self.assertEqual(len(tokens), 0)
def test_forgot_password(self):
if self.use_userconnection:
self.skipTest('Password Reset is not possible in user mode')
user = self.get_user()
r = self.client.get(path=url_for('selfservice.forgot_password'))
dump('forgot_password', r)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('selfservice.forgot_password'),
data={'loginname': user.loginname, 'mail': user.mail}, follow_redirects=True)
dump('forgot_password_submit', r)
self.assertEqual(r.status_code, 200)
token = PasswordToken.query.filter(PasswordToken.loginname == user.loginname).first()
self.assertIsNotNone(token)
self.assertIn(token.token, str(self.app.last_mail.get_content()))
def test_forgot_password_wrong_user(self):
if self.use_userconnection:
self.skipTest('Password Reset is not possible in user mode')
user = self.get_user()
r = self.client.get(path=url_for('selfservice.forgot_password'))
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('selfservice.forgot_password'),
data={'loginname': 'not_a_user', 'mail': user.mail}, follow_redirects=True)
dump('forgot_password_submit_wrong_user', r)
self.assertEqual(r.status_code, 200)
self.assertFalse(hasattr(self.app, 'last_mail'))
self.assertEqual(len(PasswordToken.query.all()), 0)
def test_forgot_password_wrong_email(self):
if self.use_userconnection:
self.skipTest('Password Reset is not possible in user mode')
user = self.get_user()
r = self.client.get(path=url_for('selfservice.forgot_password'), follow_redirects=True)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('selfservice.forgot_password'),
data={'loginname': user.loginname, 'mail': 'not_an_email@example.com'}, follow_redirects=True)
dump('forgot_password_submit_wrong_email', r)
self.assertEqual(r.status_code, 200)
self.assertFalse(hasattr(self.app, 'last_mail'))
self.assertEqual(len(PasswordToken.query.all()), 0)
# Regression test for #31
def test_forgot_password_invalid_user(self):
if self.use_userconnection:
self.skipTest('Password Reset is not possible in user mode')
r = self.client.post(path=url_for('selfservice.forgot_password'),
data={'loginname': '=', 'mail': 'test@example.com'}, follow_redirects=True)
dump('forgot_password_submit_invalid_user', r)
self.assertEqual(r.status_code, 200)
self.assertFalse(hasattr(self.app, 'last_mail'))
self.assertEqual(len(PasswordToken.query.all()), 0)
def test_token_password(self):
if self.use_userconnection:
self.skipTest('Password Token is not possible in user mode')
user = self.get_user()
token = PasswordToken(loginname=user.loginname)
db.session.add(token)
db.session.commit()
r = self.client.get(path=url_for('selfservice.token_password', token=token.token), follow_redirects=True)
dump('token_password', r)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('selfservice.token_password', token=token.token),
data={'password1': 'newpassword', 'password2': 'newpassword'}, follow_redirects=True)
dump('token_password_submit', r)
self.assertEqual(r.status_code, 200)
self.assertTrue(ldap.test_user_bind(user.dn, 'newpassword'))
def test_token_password_emptydb(self):
if self.use_userconnection:
self.skipTest('Password Token is not possible in user mode')
user = self.get_user()
r = self.client.get(path=url_for('selfservice.token_password', token='A'*128), follow_redirects=True)
dump('token_password_emptydb', r)
self.assertEqual(r.status_code, 200)
self.assertIn(b'Token expired, please try again', r.data)
r = self.client.post(path=url_for('selfservice.token_password', token='A'*128),
data={'password1': 'newpassword', 'password2': 'newpassword'}, follow_redirects=True)
dump('token_password_emptydb_submit', r)
self.assertEqual(r.status_code, 200)
self.assertIn(b'Token expired, please try again', r.data)
self.assertTrue(ldap.test_user_bind(user.dn, 'userpassword'))
def test_token_password_invalid(self):
if self.use_userconnection:
self.skipTest('Password Token is not possible in user mode')
user = self.get_user()
token = PasswordToken(loginname=user.loginname)
db.session.add(token)
db.session.commit()
r = self.client.get(path=url_for('selfservice.token_password', token='A'*128), follow_redirects=True)
dump('token_password_invalid', r)
self.assertEqual(r.status_code, 200)
self.assertIn(b'Token expired, please try again', r.data)
r = self.client.post(path=url_for('selfservice.token_password', token='A'*128),
data={'password1': 'newpassword', 'password2': 'newpassword'}, follow_redirects=True)
dump('token_password_invalid_submit', r)
self.assertEqual(r.status_code, 200)
self.assertIn(b'Token expired, please try again', r.data)
self.assertTrue(ldap.test_user_bind(user.dn, 'userpassword'))
def test_token_password_expired(self):
if self.use_userconnection:
self.skipTest('Password Token is not possible in user mode')
user = self.get_user()
token = PasswordToken(loginname=user.loginname,
created=(datetime.datetime.now() - datetime.timedelta(days=10)))
db.session.add(token)
db.session.commit()
r = self.client.get(path=url_for('selfservice.token_password', token=token.token), follow_redirects=True)
dump('token_password_invalid_expired', r)
self.assertEqual(r.status_code, 200)
self.assertIn(b'Token expired, please try again', r.data)
r = self.client.post(path=url_for('selfservice.token_password', token=token.token),
data={'password1': 'newpassword', 'password2': 'newpassword'}, follow_redirects=True)
dump('token_password_invalid_expired_submit', r)
self.assertEqual(r.status_code, 200)
self.assertIn(b'Token expired, please try again', r.data)
self.assertTrue(ldap.test_user_bind(user.dn, 'userpassword'))
def test_token_password_different_passwords(self):
if self.use_userconnection:
self.skipTest('Password Token is not possible in user mode')
user = self.get_user()
token = PasswordToken(loginname=user.loginname)
db.session.add(token)
db.session.commit()
r = self.client.get(path=url_for('selfservice.token_password', token=token.token), follow_redirects=True)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('selfservice.token_password', token=token.token),
data={'password1': 'newpassword', 'password2': 'differentpassword'}, follow_redirects=True)
dump('token_password_different_passwords_submit', r)
self.assertEqual(r.status_code, 200)
self.assertTrue(ldap.test_user_bind(user.dn, 'userpassword'))
class TestSelfserviceOL(TestSelfservice):
use_openldap = True
class TestSelfserviceOLUser(TestSelfserviceOL):
use_userconnection = True
import datetime
import unittest
from flask import url_for
# These imports are required, because otherwise we get circular imports?!
from uffd import ldap, user
from utils import dump, UffdTestCase
class TestServices(UffdTestCase):
def setUpApp(self):
self.app.config['SERVICES'] = [
{
'title': 'Service Title',
'subtitle': 'Service Subtitle',
'description': 'Short description of the service as plain text',
'url': 'https://example.com/',
'logo_url': '/static/fairy-dust-color.png',
'required_group': 'users',
'permission_levels': [
{'name': 'Moderator', 'required_group': 'moderators'},
{'name': 'Admin', 'required_group': 'uffd_admin'},
],
'confidential': True,
'groups': [
{'name': 'Group "crew_crew"', 'required_group': 'users'},
{'name': 'Group "crew_logistik"', 'required_group': 'uffd_admin'},
],
'infos': [
{'title': 'Documentation', 'html': '<p>Some information about the service as html</p>', 'required_group': 'users'},
],
'links': [
{'title': 'Link to an external site', 'url': '#', 'required_group': 'users'},
],
},
{
'title': 'Minimal Service Title',
}
]
self.app.config['SERVICES_PUBLIC'] = True
def test_index(self):
r = self.client.get(path=url_for('services.index'))
dump('services_index_public', r)
self.assertEqual(r.status_code, 200)
self.assertNotIn(b'https://example.com/', r.data)
self.login_as('user')
r = self.client.get(path=url_for('services.index'))
dump('services_index', r)
self.assertEqual(r.status_code, 200)
self.assertIn(b'https://example.com/', r.data)
import unittest
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from uffd.tasks import CleanupTask
class TestCleanupTask(unittest.TestCase):
def test(self):
app = Flask(__name__)
app.testing = True
app.debug = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
db = SQLAlchemy(app)
cleanup_task = CleanupTask()
@cleanup_task.delete_by_attribute('delete_me')
class TestModel(db.Model):
id = db.Column(db.Integer(), primary_key=True, autoincrement=True)
delete_me = db.Column(db.Boolean(), default=False, nullable=False)
with app.test_request_context():
db.create_all()
db.session.add(TestModel(delete_me=True))
db.session.add(TestModel(delete_me=True))
db.session.add(TestModel(delete_me=True))
db.session.add(TestModel(delete_me=False))
db.session.add(TestModel(delete_me=False))
db.session.commit()
db.session.expire_all()
self.assertEqual(TestModel.query.count(), 5)
with app.test_request_context():
cleanup_task.run()
db.session.commit()
db.session.expire_all()
with app.test_request_context():
self.assertEqual(TestModel.query.count(), 2)
from uffd.utils import nopad_b32decode, nopad_b32encode, nopad_urlsafe_b64decode, nopad_urlsafe_b64encode
from tests.utils import UffdTestCase
class TestUtils(UffdTestCase):
def test_nopad_b32(self):
for n in range(0, 32):
self.assertEqual(b'X'*n, nopad_b32decode(nopad_b32encode(b'X'*n)))
def test_nopad_b64(self):
for n in range(0, 32):
self.assertEqual(b'X'*n, nopad_urlsafe_b64decode(nopad_urlsafe_b64encode(b'X'*n)))
import os
import tempfile
import shutil
import unittest
from flask import request, url_for
from flask import url_for
import flask_migrate
from uffd import create_app, db
from uffd.user.models import User, Group
from uffd.models import User, Group, Mail
def dump(basename, resp):
basename = basename.replace('.', '_').replace('/', '_')
......@@ -20,101 +19,149 @@ def dump(basename, resp):
f.write(resp.data)
def db_flush():
db.session = db.create_scoped_session()
if hasattr(request, 'ldap_connection'):
del request.ldap_session
db.session.rollback()
db.session.expire_all()
class UffdTestCase(unittest.TestCase):
use_openldap = False
use_userconnection = False
def get_user(self):
return User.query.get(self.test_data.get('user').get('dn'))
def get_admin(self):
return User.query.get(self.test_data.get('admin').get('dn'))
def get_admin_group(self):
return Group.query.get(self.test_data.get('group_uffd_admin').get('dn'))
def get_access_group(self):
return Group.query.get(self.test_data.get('group_uffd_access').get('dn'))
def get_users_group(self):
return Group.query.get(self.test_data.get('group_users').get('dn'))
def login_as(self, user, ref=None):
return self.client.post(path=url_for('session.login', ref=ref),
data={'loginname': self.test_data.get(user).get('loginname'),
'password': self.test_data.get(user).get('password')}, follow_redirects=True)
class AppTestCase(unittest.TestCase):
DISABLE_SQLITE_MEMORY_DB = False
def setUp(self):
self.dir = tempfile.mkdtemp()
# It would be far better to create a minimal app here, but since the
# session module depends on almost everything else, that is not really feasable
config = {
'TESTING': True,
'DEBUG': True,
'SQLALCHEMY_DATABASE_URI': 'sqlite:///%s/db.sqlite'%self.dir,
'SQLALCHEMY_DATABASE_URI': 'sqlite:///:memory:',
'SECRET_KEY': 'DEBUGKEY',
'LDAP_SERVICE_MOCK': True,
'MAIL_SKIP_SEND': True,
'SELF_SIGNUP': True,
'ENABLE_INVITE': True,
'ENABLE_PASSWORDRESET': True
}
if self.use_openldap:
if not os.environ.get('UNITTEST_OPENLDAP'):
self.skipTest('OPENLDAP_TESTING not set')
config['LDAP_SERVICE_MOCK'] = False
config['LDAP_SERVICE_URL'] = 'ldap://localhost'
if self.use_userconnection:
config['LDAP_SERVICE_USER_BIND'] = True
config['SELF_SIGNUP'] = False
config['ENABLE_INVITE'] = False
config['ENABLE_PASSWORDRESET'] = False
config['ENABLE_ROLESELFSERVICE'] = False
else:
config['LDAP_SERVICE_BIND_DN'] = 'cn=uffd,ou=system,dc=example,dc=com'
config['LDAP_SERVICE_BIND_PASSWORD'] = 'uffd-ldap-password'
os.system("ldapdelete -c -D 'cn=uffd,ou=system,dc=example,dc=com' -w '{}' -H '{}' -f tests/openldap_ldifs/ldap_server_entries_cleanup.ldif > /dev/null 2>&1".format(config['LDAP_SERVICE_BIND_PASSWORD'], config['LDAP_SERVICE_URL']))
os.system("ldapadd -c -D 'cn=uffd,ou=system,dc=example,dc=com' -w '{}' -H '{}' -f tests/openldap_ldifs/ldap_server_entries_add.ldif".format(config['LDAP_SERVICE_BIND_PASSWORD'], config['LDAP_SERVICE_URL']))
os.system("ldapmodify -c -D 'cn=uffd,ou=system,dc=example,dc=com' -w '{}' -H '{}' -f tests/openldap_ldifs/ldap_server_entries_modify.ldif".format(config['LDAP_SERVICE_BIND_PASSWORD'], config['LDAP_SERVICE_URL']))
#os.system("/usr/sbin/slapcat -n 1 -l /dev/stdout")
self.test_data = {
'admin': {
'loginname': 'testadmin',
'dn': 'uid=testadmin,ou=users,dc=example,dc=com',
'password': 'adminpassword'
},
'user': {
'loginname': 'testuser',
'dn': 'uid=testuser,ou=users,dc=example,dc=com',
'password': 'userpassword'
},
'group_uffd_access': {
'dn': 'cn=uffd_access,ou=groups,dc=example,dc=com'
},
'group_uffd_admin': {
'dn': 'cn=uffd_admin,ou=groups,dc=example,dc=com'
},
'group_users': {
'dn': 'cn=users,ou=groups,dc=example,dc=com'
}
}
if self.DISABLE_SQLITE_MEMORY_DB:
try:
os.remove('/tmp/uffd-migration-test-db.sqlite3')
except FileNotFoundError:
pass
config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/uffd-migration-test-db.sqlite3'
if os.environ.get('TEST_WITH_MYSQL'):
import MySQLdb
conn = MySQLdb.connect(user='root', unix_socket='/var/run/mysqld/mysqld.sock')
cur = conn.cursor()
try:
cur.execute('DROP DATABASE uffd_tests')
except:
pass
cur.execute('CREATE DATABASE uffd_tests CHARACTER SET utf8mb4 COLLATE utf8mb4_nopad_bin')
conn.close()
config['SQLALCHEMY_DATABASE_URI'] = 'mysql+mysqldb:///uffd_tests?unix_socket=/var/run/mysqld/mysqld.sock&charset=utf8mb4'
self.app = create_app(config)
self.setUpApp()
def setUpApp(self):
pass
def tearDown(self):
if self.DISABLE_SQLITE_MEMORY_DB:
try:
os.remove('/tmp/uffd-migration-test-db.sqlite3')
except FileNotFoundError:
pass
class MigrationTestCase(AppTestCase):
DISABLE_SQLITE_MEMORY_DB = True
REVISION = None
def setUp(self):
super().setUp()
self.request_context = self.app.test_request_context()
self.request_context.__enter__()
if self.REVISION:
flask_migrate.upgrade(revision=self.REVISION + '-1')
def upgrade(self, revision='+1'):
db.session.commit()
flask_migrate.upgrade(revision=revision)
def downgrade(self, revision='-1'):
db.session.commit()
flask_migrate.downgrade(revision=revision)
def tearDown(self):
db.session.rollback()
self.request_context.__exit__(None, None, None)
super().tearDown()
class ModelTestCase(AppTestCase):
def setUp(self):
super().setUp()
self.request_context = self.app.test_request_context()
self.request_context.__enter__()
db.create_all()
db.session.commit()
def tearDown(self):
db.session.rollback()
self.request_context.__exit__(None, None, None)
super().tearDown()
class UffdTestCase(AppTestCase):
def setUp(self):
super().setUp()
self.client = self.app.test_client()
self.client.__enter__()
# Just do some request so that we can use url_for
self.client.get(path='/')
db.create_all()
def setUpApp(self):
# This reflects the old LDAP example data
users_group = Group(name='users', unix_gid=20001, description='Base group for all users')
db.session.add(users_group)
access_group = Group(name='uffd_access', unix_gid=20002, description='Access to Single-Sign-On and Selfservice')
db.session.add(access_group)
admin_group = Group(name='uffd_admin', unix_gid=20003, description='Admin access to uffd')
db.session.add(admin_group)
testuser = User(loginname='testuser', unix_uid=10000, password='userpassword', primary_email_address='test@example.com', displayname='Test User', groups=[users_group, access_group])
db.session.add(testuser)
testadmin = User(loginname='testadmin', unix_uid=10001, password='adminpassword', primary_email_address='admin@example.com', displayname='Test Admin', groups=[users_group, access_group, admin_group])
db.session.add(testadmin)
testmail = Mail(uid='test', receivers=['test1@example.com', 'test2@example.com'], destinations=['testuser@mail.example.com'])
db.session.add(testmail)
self.setUpDB()
db.session.commit()
def setUpDB(self):
pass
def tearDown(self):
self.client.__exit__(None, None, None)
shutil.rmtree(self.dir)
super().tearDown()
def get_user(self):
return User.query.filter_by(loginname='testuser').one_or_none()
def get_admin(self):
return User.query.filter_by(loginname='testadmin').one_or_none()
def get_admin_group(self):
return Group.query.filter_by(name='uffd_admin').one_or_none()
def get_access_group(self):
return Group.query.filter_by(name='uffd_access').one_or_none()
def get_users_group(self):
return Group.query.filter_by(name='users').one_or_none()
def get_mail(self):
return Mail.query.filter_by(uid='test').one_or_none()
def login_as(self, user, ref=None):
# It is currently not possible to login while already logged in as another
# user, so make sure that we are not logged in first
self.client.get(path=url_for('session.logout'), follow_redirects=True)
loginname = None
password = None
if user == 'user':
loginname = 'testuser'
password = 'userpassword'
elif user == 'admin':
loginname = 'testadmin'
password = 'adminpassword'
return self.client.post(path=url_for('session.login', ref=ref),
data={'loginname': loginname, 'password': password}, follow_redirects=True)
import base64
from flask import url_for
from uffd.password_hash import PlaintextPasswordHash
from uffd.remailer import remailer
from uffd.database import db
from uffd.models import APIClient, Service, User, RemailerMode
from uffd.views.api import apikey_required
from tests.utils import UffdTestCase, db_flush
def basic_auth(username, password):
return ('Authorization', 'Basic ' + base64.b64encode(f'{username}:{password}'.encode()).decode())
class TestAPIAuth(UffdTestCase):
def setUpApp(self):
@self.app.route('/test/endpoint1')
@apikey_required()
def testendpoint1():
return 'OK', 200
@self.app.route('/test/endpoint2')
@apikey_required('users')
def testendpoint2():
return 'OK', 200
def setUpDB(self):
db.session.add(APIClient(service=Service(name='test1'), auth_username='test1', auth_password='testsecret1', perm_users=True))
db.session.add(APIClient(service=Service(name='test2'), auth_username='test2', auth_password='testsecret2'))
def test_basic(self):
r = self.client.get(path=url_for('testendpoint1'), headers=[basic_auth('test1', 'testsecret1')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
r = self.client.get(path=url_for('testendpoint2'), headers=[basic_auth('test1', 'testsecret1')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
r = self.client.get(path=url_for('testendpoint1'), headers=[basic_auth('test2', 'testsecret2')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
def test_basic_invalid_credentials(self):
r = self.client.get(path=url_for('testendpoint1'), headers=[basic_auth('test-none', 'testsecret-none')], follow_redirects=True)
self.assertEqual(r.status_code, 401)
r = self.client.get(path=url_for('testendpoint1'), headers=[basic_auth('test1', 'testsecret2')], follow_redirects=True)
self.assertEqual(r.status_code, 401)
def test_basic_missing_scope(self):
r = self.client.get(path=url_for('testendpoint2'), headers=[basic_auth('test2', 'testsecret2')], follow_redirects=True)
self.assertEqual(r.status_code, 403)
def test_no_auth(self):
r = self.client.get(path=url_for('testendpoint1'), follow_redirects=True)
self.assertEqual(r.status_code, 401)
def test_auth_password_rehash(self):
db.session.add(APIClient(service=Service(name='test3'), auth_username='test3', auth_password=PlaintextPasswordHash.from_password('testsecret3')))
db.session.commit()
self.assertIsInstance(APIClient.query.filter_by(auth_username='test3').one().auth_password, PlaintextPasswordHash)
r = self.client.get(path=url_for('testendpoint1'), headers=[basic_auth('test3', 'testsecret3')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
api_client = APIClient.query.filter_by(auth_username='test3').one()
self.assertIsInstance(api_client.auth_password, APIClient.auth_password.method_cls)
self.assertTrue(api_client.auth_password.verify('testsecret3'))
r = self.client.get(path=url_for('testendpoint1'), headers=[basic_auth('test3', 'testsecret3')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
class TestAPIGetmails(UffdTestCase):
def setUpDB(self):
db.session.add(APIClient(service=Service(name='test'), auth_username='test', auth_password='test', perm_mail_aliases=True))
def test_lookup(self):
r = self.client.get(path=url_for('api.getmails', receive_address='test1@example.com'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, [{'name': 'test', 'receive_addresses': ['test1@example.com', 'test2@example.com'], 'destination_addresses': ['testuser@mail.example.com']}])
r = self.client.get(path=url_for('api.getmails', receive_address='test2@example.com'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, [{'name': 'test', 'receive_addresses': ['test1@example.com', 'test2@example.com'], 'destination_addresses': ['testuser@mail.example.com']}])
def test_lookup_notfound(self):
r = self.client.get(path=url_for('api.getmails', receive_address='test3@example.com'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, [])
def test_lookup_case_folding(self):
r = self.client.get(path=url_for('api.getmails', receive_address='Test1@example.com'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, [{'name': 'test', 'receive_addresses': ['test1@example.com', 'test2@example.com'], 'destination_addresses': ['testuser@mail.example.com']}])
class TestAPICheckPassword(UffdTestCase):
def setUpDB(self):
db.session.add(APIClient(service=Service(name='test'), auth_username='test', auth_password='test', perm_checkpassword=True))
def test(self):
r = self.client.post(path=url_for('api.checkpassword'), data={'loginname': 'testuser', 'password': 'userpassword'}, headers=[basic_auth('test', 'test')])
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json['loginname'], 'testuser')
def test_password_rehash(self):
self.get_user().password = PlaintextPasswordHash.from_password('userpassword')
db.session.commit()
self.assertIsInstance(self.get_user().password, PlaintextPasswordHash)
db_flush()
r = self.client.post(path=url_for('api.checkpassword'), data={'loginname': 'testuser', 'password': 'userpassword'}, headers=[basic_auth('test', 'test')])
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json['loginname'], 'testuser')
self.assertIsInstance(self.get_user().password, User.password.method_cls)
self.assertTrue(self.get_user().password.verify('userpassword'))
def test_wrong_password(self):
r = self.client.post(path=url_for('api.checkpassword'), data={'loginname': 'testuser', 'password': 'wrongpassword'}, headers=[basic_auth('test', 'test')])
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, None)
def test_deactivated(self):
self.get_user().is_deactivated = True
db.session.commit()
r = self.client.post(path=url_for('api.checkpassword'), data={'loginname': 'testuser', 'password': 'userpassword'}, headers=[basic_auth('test', 'test')])
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, None)
class TestAPIGetusers(UffdTestCase):
def setUpDB(self):
db.session.add(APIClient(service=Service(name='test'), auth_username='test', auth_password='test', perm_users=True))
def fix_result(self, result):
result.sort(key=lambda user: user['id'])
for user in result:
user['groups'].sort()
return result
def test_all(self):
r = self.client.get(path=url_for('api.getusers'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'displayname': 'Test User', 'email': 'test@example.com', 'id': 10000, 'loginname': 'testuser', 'groups': ['uffd_access', 'users']},
{'displayname': 'Test Admin', 'email': 'admin@example.com', 'id': 10001, 'loginname': 'testadmin', 'groups': ['uffd_access', 'uffd_admin', 'users']}
])
def test_id(self):
r = self.client.get(path=url_for('api.getusers', id=10000), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'displayname': 'Test User', 'email': 'test@example.com', 'id': 10000, 'loginname': 'testuser', 'groups': ['uffd_access', 'users']},
])
def test_id_empty(self):
r = self.client.get(path=url_for('api.getusers', id=0), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, [])
def test_with_remailer(self):
service = Service.query.filter_by(name='test').one()
service.remailer_mode = RemailerMode.ENABLED_V1
db.session.commit()
user = self.get_user()
self.app.config['REMAILER_DOMAIN'] = 'remailer.example.com'
r = self.client.get(path=url_for('api.getusers', id=10000), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
service = Service.query.filter_by(name='test').one()
self.assertEqual(self.fix_result(r.json), [
{'displayname': 'Test User', 'email': remailer.build_v1_address(service.id, user.id), 'id': 10000, 'loginname': 'testuser', 'groups': ['uffd_access', 'users']},
])
def test_loginname(self):
r = self.client.get(path=url_for('api.getusers', loginname='testuser'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'displayname': 'Test User', 'email': 'test@example.com', 'id': 10000, 'loginname': 'testuser', 'groups': ['uffd_access', 'users']},
])
def test_loginname_empty(self):
r = self.client.get(path=url_for('api.getusers', loginname='notauser'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, [])
def test_email(self):
r = self.client.get(path=url_for('api.getusers', email='admin@example.com'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'displayname': 'Test Admin', 'email': 'admin@example.com', 'id': 10001, 'loginname': 'testadmin', 'groups': ['uffd_access', 'uffd_admin', 'users']}
])
def test_email_with_remailer(self):
service = Service.query.filter_by(name='test').one()
service.remailer_mode = RemailerMode.ENABLED_V1
db.session.commit()
user = self.get_admin()
self.app.config['REMAILER_DOMAIN'] = 'remailer.example.com'
r = self.client.get(path=url_for('api.getusers', email=remailer.build_v1_address(service.id, user.id)), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
service = Service.query.filter_by(name='test').one()
self.assertEqual(self.fix_result(r.json), [
{'displayname': 'Test Admin', 'email': remailer.build_v1_address(service.id, user.id), 'id': 10001, 'loginname': 'testadmin', 'groups': ['uffd_access', 'uffd_admin', 'users']}
])
def test_email_empty(self):
r = self.client.get(path=url_for('api.getusers', email='foo@bar'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, [])
def test_group(self):
r = self.client.get(path=url_for('api.getusers', group='uffd_admin'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'displayname': 'Test Admin', 'email': 'admin@example.com', 'id': 10001, 'loginname': 'testadmin', 'groups': ['uffd_access', 'uffd_admin', 'users']}
])
def test_group_empty(self):
r = self.client.get(path=url_for('api.getusers', group='notagroup'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, [])
def test_deactivated(self):
self.get_user().is_deactivated = True
db.session.commit()
r = self.client.get(path=url_for('api.getusers'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'displayname': 'Test User', 'email': 'test@example.com', 'id': 10000, 'loginname': 'testuser', 'groups': ['uffd_access', 'users']},
{'displayname': 'Test Admin', 'email': 'admin@example.com', 'id': 10001, 'loginname': 'testadmin', 'groups': ['uffd_access', 'uffd_admin', 'users']}
])
Service.query.filter_by(name='test').first().hide_deactivated_users = True
db.session.commit()
r = self.client.get(path=url_for('api.getusers'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'displayname': 'Test Admin', 'email': 'admin@example.com', 'id': 10001, 'loginname': 'testadmin', 'groups': ['uffd_access', 'uffd_admin', 'users']}
])
class TestAPIGetgroups(UffdTestCase):
def setUpDB(self):
db.session.add(APIClient(service=Service(name='test'), auth_username='test', auth_password='test', perm_users=True))
def fix_result(self, result):
result.sort(key=lambda group: group['id'])
for group in result:
group['members'].sort()
return result
def test_all(self):
r = self.client.get(path=url_for('api.getgroups'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'id': 20001, 'members': ['testadmin', 'testuser'], 'name': 'users'},
{'id': 20002, 'members': ['testadmin', 'testuser'], 'name': 'uffd_access'},
{'id': 20003, 'members': ['testadmin'], 'name': 'uffd_admin'}
])
def test_all_deactivated_members(self):
self.get_user().is_deactivated = True
db.session.commit()
r = self.client.get(path=url_for('api.getgroups'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'id': 20001, 'members': ['testadmin', 'testuser'], 'name': 'users'},
{'id': 20002, 'members': ['testadmin', 'testuser'], 'name': 'uffd_access'},
{'id': 20003, 'members': ['testadmin'], 'name': 'uffd_admin'}
])
Service.query.filter_by(name='test').first().hide_deactivated_users = True
db.session.commit()
r = self.client.get(path=url_for('api.getgroups'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'id': 20001, 'members': ['testadmin'], 'name': 'users'},
{'id': 20002, 'members': ['testadmin'], 'name': 'uffd_access'},
{'id': 20003, 'members': ['testadmin'], 'name': 'uffd_admin'}
])
def test_id(self):
r = self.client.get(path=url_for('api.getgroups', id=20002), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'id': 20002, 'members': ['testadmin', 'testuser'], 'name': 'uffd_access'},
])
def test_id_empty(self):
r = self.client.get(path=url_for('api.getgroups', id=0), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, [])
def test_name(self):
r = self.client.get(path=url_for('api.getgroups', name='uffd_admin'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'id': 20003, 'members': ['testadmin'], 'name': 'uffd_admin'}
])
def test_name_empty(self):
r = self.client.get(path=url_for('api.getgroups', name='notagroup'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, [])
def test_member(self):
r = self.client.get(path=url_for('api.getgroups', member='testuser'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'id': 20001, 'members': ['testadmin', 'testuser'], 'name': 'users'},
{'id': 20002, 'members': ['testadmin', 'testuser'], 'name': 'uffd_access'},
])
def test_member_empty(self):
r = self.client.get(path=url_for('api.getgroups', member='notauser'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, [])
def test_member_deactivated(self):
self.get_user().is_deactivated = True
db.session.commit()
r = self.client.get(path=url_for('api.getgroups', member='testuser'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [
{'id': 20001, 'members': ['testadmin', 'testuser'], 'name': 'users'},
{'id': 20002, 'members': ['testadmin', 'testuser'], 'name': 'uffd_access'},
])
Service.query.filter_by(name='test').first().hide_deactivated_users = True
db.session.commit()
r = self.client.get(path=url_for('api.getgroups', member='testuser'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(self.fix_result(r.json), [])
class TestAPIRemailerResolve(UffdTestCase):
def setUpDB(self):
db.session.add(APIClient(service=Service(name='test'), auth_username='test', auth_password='test', perm_remailer=True))
db.session.add(Service(name='service1'))
db.session.add(Service(name='service2', remailer_mode=RemailerMode.ENABLED_V1))
def test(self):
self.app.config['REMAILER_DOMAIN'] = 'remailer.example.com'
service = Service.query.filter_by(name='service2').one()
r = self.client.get(path=url_for('api.resolve_remailer', orig_address=remailer.build_v1_address(service.id, self.get_user().id)), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, {'address': self.get_user().primary_email.address})
r = self.client.get(path=url_for('api.resolve_remailer', orig_address='foo@bar'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json, {'address': None})
def test_invalid(self):
r = self.client.get(path=url_for('api.resolve_remailer', orig_address=['foo', 'bar']), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 400)
r = self.client.get(path=url_for('api.resolve_remailer'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 400)
r = self.client.get(path=url_for('api.resolve_remailer', foo='bar'), headers=[basic_auth('test', 'test')], follow_redirects=True)
self.assertEqual(r.status_code, 400)
class TestAPIMetricsPrometheus(UffdTestCase):
def setUpDB(self):
db.session.add(APIClient(service=Service(name='test'), auth_username='test', auth_password='test', perm_metrics=True))
def test(self):
r = self.client.get(path=url_for('api.prometheus_metrics'), headers=[basic_auth('test', 'test')])
self.assertEqual(r.status_code, 200)
self.assertTrue("uffd_version_info" in r.data.decode())
import unittest
import datetime
import time
from flask import url_for, session, current_app
from flask import url_for, current_app
# These imports are required, because otherwise we get circular imports?!
from uffd import user
from uffd.ldap import ldap
from uffd.database import db
from uffd.models import Invite, InviteGrant, InviteSignup, Role, RoleGroup
from uffd import create_app, db
from uffd.invite.models import Invite, InviteGrant, InviteSignup
from uffd.user.models import User, Group
from uffd.role.models import Role, RoleGroup
from uffd.session.views import login_get_user
from utils import dump, UffdTestCase, db_flush
class TestInviteModel(UffdTestCase):
def test_expire(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60))
self.assertFalse(invite.expired)
self.assertTrue(invite.active)
invite.valid_until = datetime.datetime.now() - datetime.timedelta(seconds=60)
self.assertTrue(invite.expired)
self.assertFalse(invite.active)
def test_void(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), single_use=False)
self.assertFalse(invite.voided)
self.assertTrue(invite.active)
invite.used = True
self.assertFalse(invite.voided)
self.assertTrue(invite.active)
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), single_use=True)
self.assertFalse(invite.voided)
self.assertTrue(invite.active)
invite.used = True
self.assertTrue(invite.voided)
self.assertFalse(invite.active)
def test_permitted(self):
role = Role(name='testrole')
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, roles=[role])
self.assertTrue(invite.permitted)
self.assertTrue(invite.active)
invite.creator_dn = 'uid=doesnotexist,ou=users,dc=example,dc=com'
self.assertFalse(invite.permitted)
self.assertFalse(invite.active)
invite.creator_dn = self.test_data.get('admin').get('dn')
self.assertTrue(invite.permitted)
self.assertTrue(invite.active)
invite.creator_dn = self.test_data.get('user').get('dn')
self.assertFalse(invite.permitted)
self.assertFalse(invite.active)
role.moderator_group_dn = self.test_data.get('group_uffd_access').get('dn')
current_app.config['ACL_SIGNUP_GROUP'] = 'uffd_access'
self.assertTrue(invite.permitted)
self.assertTrue(invite.active)
role.moderator_group_dn = None
self.assertFalse(invite.permitted)
self.assertFalse(invite.active)
role.moderator_group_dn = self.test_data.get('group_uffd_access').get('dn')
current_app.config['ACL_SIGNUP_GROUP'] = 'uffd_admin'
self.assertFalse(invite.permitted)
self.assertFalse(invite.active)
def test_disable(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60))
self.assertTrue(invite.active)
invite.disable()
self.assertFalse(invite.active)
def test_reset_disabled(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60))
invite.disable()
self.assertFalse(invite.active)
invite.reset()
self.assertTrue(invite.active)
def test_reset_expired(self):
invite = Invite(valid_until=datetime.datetime.now() - datetime.timedelta(seconds=60))
self.assertFalse(invite.active)
invite.reset()
self.assertFalse(invite.active)
def test_reset_single_use(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), single_use=False)
invite.used = True
invite.disable()
self.assertFalse(invite.active)
invite.reset()
self.assertTrue(invite.active)
def test_short_token(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60))
db.session.add(invite)
db.session.commit()
self.assertTrue(len(invite.short_token) <= len(invite.token)/3)
class TestInviteGrantModel(UffdTestCase):
def test_success(self):
user = self.get_user()
group0 = self.get_access_group()
role0 = Role(name='baserole', groups={group0: RoleGroup(group=group0)})
db.session.add(role0)
user.roles.add(role0)
user.update_groups()
group1 = self.get_admin_group()
role1 = Role(name='testrole1', groups={group1: RoleGroup(group=group1)})
db.session.add(role1)
role2 = Role(name='testrole2')
db.session.add(role2)
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), roles=[role1, role2])
self.assertIn(role0, user.roles)
self.assertNotIn(role1, user.roles)
self.assertNotIn(role2, user.roles)
self.assertIn(group0, user.groups)
self.assertNotIn(group1, user.groups)
self.assertFalse(invite.used)
grant = InviteGrant(invite=invite, user=user)
success, msg = grant.apply()
self.assertTrue(success)
self.assertIn(role0, user.roles)
self.assertIn(role1, user.roles)
self.assertIn(role2, user.roles)
self.assertIn(group0, user.groups)
self.assertIn(group1, user.groups)
self.assertTrue(invite.used)
db.session.commit()
ldap.session.commit()
db_flush()
user = self.get_user()
self.assertIn('baserole', [role.name for role in user.roles_effective])
self.assertIn('testrole1', [role.name for role in user.roles])
self.assertIn('testrole2', [role.name for role in user.roles])
self.assertIn(self.test_data.get('group_uffd_access').get('dn'), [group.dn for group in user.groups])
self.assertIn(self.test_data.get('group_uffd_admin').get('dn'), [group.dn for group in user.groups])
def test_inactive(self):
user = self.get_user()
group = self.get_admin_group()
role = Role(name='testrole1', groups={group: RoleGroup(group=group)})
db.session.add(role)
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), roles=[role], single_use=True, used=True)
self.assertFalse(invite.active)
grant = InviteGrant(invite=invite, user=user)
success, msg = grant.apply()
self.assertFalse(success)
self.assertIsInstance(msg, str)
self.assertNotIn(role, user.roles)
self.assertNotIn(group, user.groups)
def test_no_roles(self):
user = self.get_user()
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60))
self.assertTrue(invite.active)
grant = InviteGrant(invite=invite, user=user)
success, msg = grant.apply()
self.assertFalse(success)
self.assertIsInstance(msg, str)
def test_no_new_roles(self):
user = self.get_user()
role = Role(name='testrole1')
db.session.add(role)
user.roles.add(role)
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), roles=[role])
self.assertTrue(invite.active)
grant = InviteGrant(invite=invite, user=user)
success, msg = grant.apply()
self.assertFalse(success)
self.assertIsInstance(msg, str)
class TestInviteSignupModel(UffdTestCase):
def create_base_roles(self):
baserole = Role(name='base', is_default=True)
baserole.groups[self.get_access_group()] = RoleGroup()
baserole.groups[self.get_users_group()] = RoleGroup()
db.session.add(baserole)
db.session.commit()
def test_success(self):
self.create_base_roles()
base_role = Role.query.filter_by(name='base').one()
base_group1 = self.get_access_group()
base_group2 = self.get_users_group()
group = self.get_admin_group()
role1 = Role(name='testrole1', groups={group: RoleGroup(group=group)})
db.session.add(role1)
role2 = Role(name='testrole2')
db.session.add(role2)
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), roles=[role1, role2], allow_signup=True)
signup = InviteSignup(invite=invite, loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
self.assertFalse(invite.used)
valid, msg = signup.validate()
self.assertTrue(valid)
self.assertFalse(invite.used)
user, msg = signup.finish('notsecret')
self.assertIsInstance(user, User)
self.assertTrue(invite.used)
self.assertEqual(user.loginname, 'newuser')
self.assertEqual(user.displayname, 'New User')
self.assertEqual(user.mail, 'test@example.com')
self.assertEqual(signup.user.dn, user.dn)
self.assertIn(base_role, user.roles_effective)
self.assertIn(role1, user.roles)
self.assertIn(role2, user.roles)
self.assertIn(base_group1, user.groups)
self.assertIn(base_group2, user.groups)
self.assertIn(group, user.groups)
db.session.commit()
ldap.session.commit()
db_flush()
self.assertEqual(len(User.query.filter_by(loginname='newuser').all()), 1)
def test_success_no_roles(self):
self.create_base_roles()
base_role = Role.query.filter_by(name='base').one()
base_group1 = self.get_access_group()
base_group2 = self.get_users_group()
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
signup = InviteSignup(invite=invite, loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
self.assertFalse(invite.used)
valid, msg = signup.validate()
self.assertTrue(valid)
self.assertFalse(invite.used)
user, msg = signup.finish('notsecret')
self.assertIsInstance(user, User)
self.assertTrue(invite.used)
self.assertEqual(user.loginname, 'newuser')
self.assertEqual(user.displayname, 'New User')
self.assertEqual(user.mail, 'test@example.com')
self.assertEqual(signup.user.dn, user.dn)
self.assertIn(base_role, user.roles_effective)
self.assertEqual(len(user.roles_effective), 1)
self.assertIn(base_group1, user.groups)
self.assertIn(base_group2, user.groups)
self.assertEqual(len(user.groups), 2)
db.session.commit()
ldap.session.commit()
db_flush()
self.assertEqual(len(User.query.filter_by(loginname='newuser').all()), 1)
def test_inactive(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, single_use=True, used=True)
self.assertFalse(invite.active)
signup = InviteSignup(invite=invite, loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
valid, msg = signup.validate()
self.assertFalse(valid)
self.assertIsInstance(msg, str)
user, msg = signup.finish('notsecret')
self.assertIsNone(user)
self.assertIsInstance(msg, str)
def test_invalid(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
self.assertTrue(invite.active)
signup = InviteSignup(invite=invite, loginname='', displayname='New User', mail='test@example.com', password='notsecret')
valid, msg = signup.validate()
self.assertFalse(valid)
self.assertIsInstance(msg, str)
def test_invalid2(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
self.assertTrue(invite.active)
signup = InviteSignup(invite=invite, loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
user, msg = signup.finish('wrongpassword')
self.assertIsNone(user)
self.assertIsInstance(msg, str)
def test_no_signup(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=False)
self.assertTrue(invite.active)
signup = InviteSignup(invite=invite, loginname='newuser', displayname='New User', mail='test@example.com', password='notsecret')
valid, msg = signup.validate()
self.assertFalse(valid)
self.assertIsInstance(msg, str)
user, msg = signup.finish('notsecret')
self.assertIsNone(user)
self.assertIsInstance(msg, str)
from tests.utils import dump, UffdTestCase, db_flush
class TestInviteAdminViews(UffdTestCase):
def setUpApp(self):
......@@ -286,8 +13,8 @@ class TestInviteAdminViews(UffdTestCase):
self.app.last_mail = None
def test_index(self):
valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
valid_until_expired = datetime.datetime.now() - datetime.timedelta(seconds=60)
valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
valid_until_expired = datetime.datetime.utcnow() - datetime.timedelta(seconds=60)
user1 = self.get_user()
user2 = self.get_admin()
role1 = Role(name='testrole1')
......@@ -295,16 +22,18 @@ class TestInviteAdminViews(UffdTestCase):
role2 = Role(name='testrole2')
db.session.add(role2)
# All possible states
db.session.add(Invite(valid_until=valid_until, single_use=False))
db.session.add(Invite(valid_until=valid_until, single_use=True, used=False))
db.session.add(Invite(valid_until=valid_until, single_use=True, used=True, signups=[InviteSignup(user=user1)]))
db.session.add(Invite(valid_until=valid_until_expired))
db.session.add(Invite(valid_until=valid_until, disabled=True))
db.session.add(Invite(valid_until=valid_until, single_use=False, creator=self.get_admin()))
db.session.add(Invite(valid_until=valid_until, single_use=True, used=False, creator=self.get_admin()))
invite = Invite(valid_until=valid_until, single_use=True, used=True, creator=self.get_admin())
invite.signups = [InviteSignup(user=user1)]
db.session.add(invite)
db.session.add(Invite(valid_until=valid_until_expired, creator=self.get_admin()))
db.session.add(Invite(valid_until=valid_until, disabled=True, creator=self.get_admin()))
# Different permissions
db.session.add(Invite(valid_until=valid_until, allow_signup=True))
db.session.add(Invite(valid_until=valid_until, allow_signup=False))
db.session.add(Invite(valid_until=valid_until, allow_signup=True, roles=[role1], grants=[InviteGrant(user=user2)]))
db.session.add(Invite(valid_until=valid_until, allow_signup=False, roles=[role1, role2]))
db.session.add(Invite(valid_until=valid_until, allow_signup=True, creator=self.get_admin()))
db.session.add(Invite(valid_until=valid_until, allow_signup=False, creator=self.get_admin()))
db.session.add(Invite(valid_until=valid_until, allow_signup=True, roles=[role1], grants=[InviteGrant(user=user2)], creator=self.get_admin()))
db.session.add(Invite(valid_until=valid_until, allow_signup=False, roles=[role1, role2], creator=self.get_admin()))
db.session.commit()
self.login_as('admin')
r = self.client.get(path=url_for('invite.index'), follow_redirects=True)
......@@ -326,15 +55,14 @@ class TestInviteAdminViews(UffdTestCase):
self.login_as('user')
r = self.client.get(path=url_for('invite.index'), follow_redirects=True)
dump('invite_index_noaccess', r)
self.assertEqual(r.status_code, 200)
self.assertIn('Access denied'.encode(), r.data)
self.assertEqual(r.status_code, 403)
def test_index_signupperm(self):
current_app.config['ACL_SIGNUP_GROUP'] = 'uffd_access'
valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
invite1 = Invite(valid_until=valid_until, allow_signup=True, creator_dn=self.test_data.get('admin').get('dn'))
valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
invite1 = Invite(valid_until=valid_until, allow_signup=True, creator=self.get_admin())
db.session.add(invite1)
invite2 = Invite(valid_until=valid_until, allow_signup=True, creator_dn=self.test_data.get('user').get('dn'))
invite2 = Invite(valid_until=valid_until, allow_signup=True, creator=self.get_user())
db.session.add(invite2)
invite3 = Invite(valid_until=valid_until, allow_signup=True)
db.session.add(invite3)
......@@ -345,7 +73,6 @@ class TestInviteAdminViews(UffdTestCase):
self.login_as('user')
r = self.client.get(path=url_for('invite.index'), follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertNotIn('Access denied'.encode(), r.data)
self.assertNotIn(token1.encode(), r.data)
self.assertIn(token2.encode(), r.data)
self.assertNotIn(token3.encode(), r.data)
......@@ -353,16 +80,15 @@ class TestInviteAdminViews(UffdTestCase):
def test_index_rolemod(self):
role1 = Role(name='testrole1')
db.session.add(role1)
role2 = Role(name='testrole2', moderator_group_dn=self.test_data.get('group_uffd_access').get('dn'))
role2 = Role(name='testrole2', moderator_group=self.get_access_group())
db.session.add(role2)
valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
db.session.add(Invite(valid_until=valid_until, roles=[role1]))
db.session.add(Invite(valid_until=valid_until, roles=[role2]))
db.session.commit()
self.login_as('user')
r = self.client.get(path=url_for('invite.index'), follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.assertNotIn('Access denied'.encode(), r.data)
self.assertNotIn('testrole1'.encode(), r.data)
self.assertIn('testrole2'.encode(), r.data)
......@@ -375,10 +101,10 @@ class TestInviteAdminViews(UffdTestCase):
r = self.client.get(path=url_for('invite.new'), follow_redirects=True)
dump('invite_new', r)
self.assertEqual(r.status_code, 200)
valid_until = (datetime.datetime.now() + datetime.timedelta(seconds=60)).isoformat()
valid_until_input = (datetime.datetime.now() + datetime.timedelta(seconds=60)).isoformat(timespec='minutes')
self.assertListEqual(Invite.query.all(), [])
r = self.client.post(path=url_for('invite.new_submit'),
data={'single-use': '1', 'valid-until': valid_until,
data={'single-use': '1', 'valid-until': valid_until_input,
'allow-signup': '1', 'role-%d'%role_id: '1'}, follow_redirects=True)
dump('invite_new_submit', r)
invite = Invite.query.one()
......@@ -395,9 +121,9 @@ class TestInviteAdminViews(UffdTestCase):
db.session.add(role)
db.session.commit()
role_id = role.id
valid_until = (datetime.datetime.now() + datetime.timedelta(seconds=60)).isoformat()
valid_until_input = (datetime.datetime.now() + datetime.timedelta(seconds=60)).isoformat(timespec='minutes')
r = self.client.post(path=url_for('invite.new_submit'),
data={'single-use': '1', 'valid-until': valid_until,
data={'single-use': '1', 'valid-until': valid_until_input,
'allow-signup': '1', 'role-%d'%role_id: '1'}, follow_redirects=True)
dump('invite_new_noperm', r)
self.assertIsNone(Invite.query.first())
......@@ -405,17 +131,17 @@ class TestInviteAdminViews(UffdTestCase):
def test_new_empty(self):
current_app.config['ACL_SIGNUP_GROUP'] = 'uffd_access'
self.login_as('user')
valid_until = (datetime.datetime.now() + datetime.timedelta(seconds=60)).isoformat()
valid_until_input = (datetime.datetime.now() + datetime.timedelta(seconds=60)).isoformat(timespec='minutes')
r = self.client.post(path=url_for('invite.new_submit'),
data={'single-use': '1', 'valid-until': valid_until,
data={'single-use': '1', 'valid-until': valid_until_input,
'allow-signup': '0'}, follow_redirects=True)
dump('invite_new_empty', r)
self.assertIsNone(Invite.query.first())
def test_disable(self):
self.login_as('admin')
valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
invite = Invite(valid_until=valid_until)
valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
invite = Invite(valid_until=valid_until, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
id = invite.id
......@@ -427,8 +153,8 @@ class TestInviteAdminViews(UffdTestCase):
def test_disable_own(self):
current_app.config['ACL_SIGNUP_GROUP'] = 'uffd_access'
self.login_as('user')
valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
invite = Invite(valid_until=valid_until, creator_dn=self.test_data.get('user').get('dn'))
valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
invite = Invite(valid_until=valid_until, creator=self.get_user())
db.session.add(invite)
db.session.commit()
id = invite.id
......@@ -439,10 +165,10 @@ class TestInviteAdminViews(UffdTestCase):
def test_disable_rolemod(self):
self.login_as('user')
valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
role = Role(name='testrole', moderator_group_dn=self.test_data.get('group_uffd_access').get('dn'))
valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
role = Role(name='testrole', moderator_group=self.get_access_group())
db.session.add(role)
invite = Invite(valid_until=valid_until, creator_dn=self.test_data.get('admin').get('dn'), roles=[role])
invite = Invite(valid_until=valid_until, creator=self.get_admin(), roles=[role])
db.session.add(invite)
db.session.commit()
id = invite.id
......@@ -452,11 +178,11 @@ class TestInviteAdminViews(UffdTestCase):
def test_disable_noperm(self):
self.login_as('user')
valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
db.session.add(Role(name='testrole1', moderator_group_dn=self.test_data.get('group_uffd_access').get('dn')))
role = Role(name='testrole2', moderator_group_dn=self.test_data.get('group_uffd_admin').get('dn'))
valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
db.session.add(Role(name='testrole1', moderator_group=self.get_access_group()))
role = Role(name='testrole2', moderator_group=self.get_admin_group())
db.session.add(role)
invite = Invite(valid_until=valid_until, creator_dn=self.test_data.get('admin').get('dn'), roles=[role])
invite = Invite(valid_until=valid_until, creator=self.get_admin(), roles=[role])
db.session.add(invite)
db.session.commit()
id = invite.id
......@@ -466,8 +192,8 @@ class TestInviteAdminViews(UffdTestCase):
def test_reset_disabled(self):
self.login_as('admin')
valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
invite = Invite(valid_until=valid_until, disabled=True)
valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
invite = Invite(valid_until=valid_until, disabled=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
id = invite.id
......@@ -478,8 +204,8 @@ class TestInviteAdminViews(UffdTestCase):
def test_reset_voided(self):
self.login_as('admin')
valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
invite = Invite(valid_until=valid_until, single_use=True, used=True)
valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
invite = Invite(valid_until=valid_until, single_use=True, used=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
id = invite.id
......@@ -491,8 +217,8 @@ class TestInviteAdminViews(UffdTestCase):
def test_reset_own(self):
current_app.config['ACL_SIGNUP_GROUP'] = 'uffd_access'
self.login_as('user')
valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
invite = Invite(valid_until=valid_until, disabled=True, creator_dn=self.test_data.get('user').get('dn'))
valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
invite = Invite(valid_until=valid_until, disabled=True, creator=self.get_user())
db.session.add(invite)
db.session.commit()
id = invite.id
......@@ -503,10 +229,10 @@ class TestInviteAdminViews(UffdTestCase):
def test_reset_foreign(self):
self.login_as('user')
valid_until = datetime.datetime.now() + datetime.timedelta(seconds=60)
role = Role(name='testrole', moderator_group_dn=self.test_data.get('group_uffd_access').get('dn'))
valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
role = Role(name='testrole', moderator_group=self.get_access_group())
db.session.add(role)
invite = Invite(valid_until=valid_until, disabled=True, creator_dn=self.test_data.get('admin').get('dn'), roles=[role])
invite = Invite(valid_until=valid_until, disabled=True, creator=self.get_admin(), roles=[role])
db.session.add(invite)
db.session.commit()
id = invite.id
......@@ -520,30 +246,30 @@ class TestInviteUseViews(UffdTestCase):
self.app.last_mail = None
def test_use(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, roles=[Role(name='testrole1'), Role(name='testrole2')])
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, roles=[Role(name='testrole1'), Role(name='testrole2')])
db.session.add(invite)
db.session.commit()
token = invite.token
r = self.client.get(path=url_for('invite.use', token=token), follow_redirects=True)
r = self.client.get(path=url_for('invite.use', invite_id=invite.id, token=token), follow_redirects=True)
dump('invite_use', r)
self.assertEqual(r.status_code, 200)
def test_use_loggedin(self):
self.login_as('user')
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, roles=[Role(name='testrole1'), Role(name='testrole2')])
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, roles=[Role(name='testrole1'), Role(name='testrole2')])
db.session.add(invite)
db.session.commit()
token = invite.token
r = self.client.get(path=url_for('invite.use', token=token), follow_redirects=True)
r = self.client.get(path=url_for('invite.use', invite_id=invite.id, token=token), follow_redirects=True)
dump('invite_use_loggedin', r)
self.assertEqual(r.status_code, 200)
def test_use_inactive(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), disabled=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), disabled=True)
db.session.add(invite)
db.session.commit()
token = invite.token
r = self.client.get(path=url_for('invite.use', token=token), follow_redirects=True)
r = self.client.get(path=url_for('invite.use', invite_id=invite.id, token=token), follow_redirects=True)
dump('invite_use_inactive', r)
self.assertEqual(r.status_code, 200)
......@@ -554,17 +280,17 @@ class TestInviteUseViews(UffdTestCase):
group0 = self.get_access_group()
role0 = Role(name='baserole', groups={group0: RoleGroup(group=group0)})
db.session.add(role0)
user.roles.add(role0)
user.roles.append(role0)
user.update_groups()
group1 = self.get_admin_group()
role1 = Role(name='testrole1', groups={group1: RoleGroup(group=group1)})
db.session.add(role1)
role2 = Role(name='testrole2')
db.session.add(role2)
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), roles=[role1, role2])
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), roles=[role1, role2], creator=self.get_admin())
db.session.add(invite)
db.session.commit()
ldap.session.commit()
invite_id = invite.id
token = invite.token
self.assertIn(role0, user.roles)
self.assertNotIn(role1, user.roles)
......@@ -573,7 +299,7 @@ class TestInviteUseViews(UffdTestCase):
self.assertNotIn(group1, user.groups)
self.assertFalse(invite.used)
self.login_as('user')
r = self.client.post(path=url_for('invite.grant', token=token), follow_redirects=True)
r = self.client.post(path=url_for('invite.grant', invite_id=invite_id, token=token), follow_redirects=True)
dump('invite_grant', r)
self.assertEqual(r.status_code, 200)
db_flush()
......@@ -583,27 +309,29 @@ class TestInviteUseViews(UffdTestCase):
self.assertIn('baserole', [role.name for role in user.roles])
self.assertIn('testrole1', [role.name for role in user.roles])
self.assertIn('testrole2', [role.name for role in user.roles])
self.assertIn(self.test_data.get('group_uffd_access').get('dn'), [group.dn for group in user.groups])
self.assertIn(self.test_data.get('group_uffd_admin').get('dn'), [group.dn for group in user.groups])
self.assertIn(self.get_access_group(), user.groups)
self.assertIn(self.get_admin_group(), user.groups)
def test_grant_invalid_invite(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), disabled=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), disabled=True)
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
self.login_as('user')
r = self.client.post(path=url_for('invite.grant', token=token), follow_redirects=True)
r = self.client.post(path=url_for('invite.grant', invite_id=invite_id, token=token), follow_redirects=True)
dump('invite_grant_invalid_invite', r)
self.assertEqual(r.status_code, 200)
self.assertFalse(Invite.query.filter_by(token=token).first().used)
def test_grant_no_roles(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60))
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60))
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
self.login_as('user')
r = self.client.post(path=url_for('invite.grant', token=token), follow_redirects=True)
r = self.client.post(path=url_for('invite.grant', invite_id=invite_id, token=token), follow_redirects=True)
dump('invite_grant_no_roles', r)
self.assertEqual(r.status_code, 200)
self.assertFalse(Invite.query.filter_by(token=token).first().used)
......@@ -612,13 +340,14 @@ class TestInviteUseViews(UffdTestCase):
user = self.get_user()
role = Role(name='testrole')
db.session.add(role)
user.roles.add(role)
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), roles=[role])
user.roles.append(role)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), roles=[role])
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
self.login_as('user')
r = self.client.post(path=url_for('invite.grant', token=token), follow_redirects=True)
r = self.client.post(path=url_for('invite.grant', invite_id=invite_id, token=token), follow_redirects=True)
dump('invite_grant_no_new_roles', r)
self.assertEqual(r.status_code, 200)
self.assertFalse(Invite.query.filter_by(token=token).first().used)
......@@ -633,11 +362,12 @@ class TestInviteUseViews(UffdTestCase):
db.session.add(role1)
role2 = Role(name='testrole2')
db.session.add(role2)
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), roles=[role1, role2], allow_signup=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), roles=[role1, role2], allow_signup=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
r = self.client.get(path=url_for('invite.signup_start', token=token), follow_redirects=True)
r = self.client.get(path=url_for('invite.signup_start', invite_id=invite_id, token=token), follow_redirects=True)
dump('invite_signup_start', r)
self.assertEqual(r.status_code, 200)
r = self.client.post(path=url_for('invite.signup_submit', token=token),
......@@ -650,40 +380,40 @@ class TestInviteUseViews(UffdTestCase):
self.assertEqual(signup.displayname, 'New User')
self.assertEqual(signup.mail, 'test@example.com')
self.assertIn(signup.token, str(self.app.last_mail.get_content()))
self.assertTrue(signup.check_password('notsecret'))
self.assertTrue(signup.password.verify('notsecret'))
self.assertTrue(signup.validate()[0])
def test_signup_invalid_invite(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, disabled=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, disabled=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
r = self.client.get(path=url_for('invite.signup_start', token=invite.token), follow_redirects=True)
r = self.client.get(path=url_for('invite.signup_start', invite_id=invite.id, token=invite.token), follow_redirects=True)
dump('invite_signup_invalid_invite', r)
self.assertEqual(r.status_code, 200)
def test_signup_nosignup(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=False)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=False, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
r = self.client.get(path=url_for('invite.signup_start', token=invite.token), follow_redirects=True)
r = self.client.get(path=url_for('invite.signup_start', invite_id=invite.id, token=invite.token), follow_redirects=True)
dump('invite_signup_nosignup', r)
self.assertEqual(r.status_code, 200)
def test_signup_wrongpassword(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
r = self.client.post(path=url_for('invite.signup_submit', token=invite.token),
r = self.client.post(path=url_for('invite.signup_submit', invite_id=invite.id, token=invite.token),
data={'loginname': 'newuser', 'displayname': 'New User', 'mail': 'test@example.com',
'password1': 'notsecret', 'password2': 'notthesame'}, follow_redirects=True)
dump('invite_signup_wrongpassword', r)
self.assertEqual(r.status_code, 200)
def test_signup_invalid(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
r = self.client.post(path=url_for('invite.signup_submit', token=invite.token),
r = self.client.post(path=url_for('invite.signup_submit', invite_id=invite.id, token=invite.token),
data={'loginname': '', 'displayname': 'New User', 'mail': 'test@example.com',
'password1': 'notsecret', 'password2': 'notsecret'}, follow_redirects=True)
dump('invite_signup_invalid', r)
......@@ -691,27 +421,28 @@ class TestInviteUseViews(UffdTestCase):
def test_signup_mailerror(self):
self.app.config['MAIL_SKIP_SEND'] = 'fail'
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
r = self.client.post(path=url_for('invite.signup_submit', token=invite.token),
r = self.client.post(path=url_for('invite.signup_submit', invite_id=invite.id, token=invite.token),
data={'loginname': 'newuser', 'displayname': 'New User', 'mail': 'test@example.com',
'password1': 'notsecret', 'password2': 'notsecret'}, follow_redirects=True)
dump('invite_signup_mailerror', r)
self.assertEqual(r.status_code, 200)
def test_signup_hostlimit(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
for i in range(20):
r = self.client.post(path=url_for('invite.signup_submit', token=token),
r = self.client.post(path=url_for('invite.signup_submit', invite_id=invite_id, token=token),
data={'loginname': 'newuser%d'%i, 'displayname': 'New User', 'mail': 'test%d@example.com'%i,
'password1': 'notsecret', 'password2': 'notsecret'}, follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.app.last_mail = None
r = self.client.post(path=url_for('invite.signup_submit', token=token),
r = self.client.post(path=url_for('invite.signup_submit', invite_id=invite_id, token=token),
data={'loginname': 'newuser', 'displayname': 'New User', 'mail': 'test@example.com',
'password1': 'notsecret', 'password2': 'notsecret'}, follow_redirects=True)
dump('invite_signup_hostlimit', r)
......@@ -720,17 +451,18 @@ class TestInviteUseViews(UffdTestCase):
self.assertIsNone(self.app.last_mail)
def test_signup_mailimit(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
for i in range(3):
r = self.client.post(path=url_for('invite.signup_submit', token=token),
r = self.client.post(path=url_for('invite.signup_submit', invite_id=invite_id, token=token),
data={'loginname': 'newuser%d'%i, 'displayname': 'New User', 'mail': 'test@example.com',
'password1': 'notsecret', 'password2': 'notsecret'}, follow_redirects=True)
self.assertEqual(r.status_code, 200)
self.app.last_mail = None
r = self.client.post(path=url_for('invite.signup_submit', token=token),
r = self.client.post(path=url_for('invite.signup_submit', invite_id=invite_id, token=token),
data={'loginname': 'newuser', 'displayname': 'New User', 'mail': 'test@example.com',
'password1': 'notsecret', 'password2': 'notsecret'}, follow_redirects=True)
dump('invite_signup_maillimit', r)
......@@ -739,75 +471,79 @@ class TestInviteUseViews(UffdTestCase):
self.assertIsNone(self.app.last_mail)
def test_signup_check(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
r = self.client.post(path=url_for('invite.signup_check', token=token), follow_redirects=True,
r = self.client.post(path=url_for('invite.signup_check', invite_id=invite_id, token=token), follow_redirects=True,
data={'loginname': 'newuser'})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.content_type, 'application/json')
self.assertEqual(r.json['status'], 'ok')
def test_signup_check_invalid(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
r = self.client.post(path=url_for('invite.signup_check', token=token), follow_redirects=True,
r = self.client.post(path=url_for('invite.signup_check', invite_id=invite_id, token=token), follow_redirects=True,
data={'loginname': ''})
print(r.data)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.content_type, 'application/json')
self.assertEqual(r.json['status'], 'invalid')
def test_signup_check_exists(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
r = self.client.post(path=url_for('invite.signup_check', token=token), follow_redirects=True,
r = self.client.post(path=url_for('invite.signup_check', invite_id=invite_id, token=token), follow_redirects=True,
data={'loginname': 'testuser'})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.content_type, 'application/json')
self.assertEqual(r.json['status'], 'exists')
def test_signup_check_nosignup(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=False)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=False, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
r = self.client.post(path=url_for('invite.signup_check', token=token), follow_redirects=True,
r = self.client.post(path=url_for('invite.signup_check', invite_id=invite_id, token=token), follow_redirects=True,
data={'loginname': 'testuser'})
self.assertEqual(r.status_code, 403)
self.assertEqual(r.content_type, 'application/json')
self.assertEqual(r.json['status'], 'error')
def test_signup_check_error(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True, disabled=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, disabled=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
r = self.client.post(path=url_for('invite.signup_check', token=token), follow_redirects=True,
r = self.client.post(path=url_for('invite.signup_check', invite_id=invite_id, token=token), follow_redirects=True,
data={'loginname': 'testuser'})
self.assertEqual(r.status_code, 403)
self.assertEqual(r.content_type, 'application/json')
self.assertEqual(r.json['status'], 'error')
def test_signup_check_ratelimited(self):
invite = Invite(valid_until=datetime.datetime.now() + datetime.timedelta(seconds=60), allow_signup=True)
invite = Invite(valid_until=datetime.datetime.utcnow() + datetime.timedelta(seconds=60), allow_signup=True, creator=self.get_admin())
db.session.add(invite)
db.session.commit()
invite_id = invite.id
token = invite.token
for i in range(20):
r = self.client.post(path=url_for('invite.signup_check', token=token), follow_redirects=True,
r = self.client.post(path=url_for('invite.signup_check', invite_id=invite_id, token=token), follow_redirects=True,
data={'loginname': 'testuser'})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.content_type, 'application/json')
r = self.client.post(path=url_for('invite.signup_check', token=token), follow_redirects=True,
r = self.client.post(path=url_for('invite.signup_check', invite_id=invite_id, token=token), follow_redirects=True,
data={'loginname': 'testuser'})
self.assertEqual(r.status_code, 200)
self.assertEqual(r.content_type, 'application/json')
self.assertEqual(r.json['status'], 'ratelimited')
class TestInviteUseViewsOL(TestInviteUseViews):
use_openldap = True
import datetime
import time
import unittest
from flask import url_for, session
from flask import url_for
# These imports are required, because otherwise we get circular imports?!
from uffd.ldap import ldap
from uffd import user
from uffd.database import db
from uffd.models import Mail
from uffd.mail.models import Mail
from uffd import create_app, db
from utils import dump, UffdTestCase
def get_mail():
return Mail.query.get('uid=test,ou=postfix,dc=example,dc=com')
from tests.utils import dump, UffdTestCase
class TestMailViews(UffdTestCase):
def setUp(self):
......@@ -27,15 +18,15 @@ class TestMailViews(UffdTestCase):
self.assertEqual(r.status_code, 200)
def test_index_empty(self):
ldap.session.delete(get_mail())
ldap.session.commit()
self.assertIsNone(get_mail())
db.session.delete(self.get_mail())
db.session.commit()
self.assertIsNone(self.get_mail())
r = self.client.get(path=url_for('mail.index'), follow_redirects=True)
dump('mail_index_empty', r)
self.assertEqual(r.status_code, 200)
def test_show(self):
r = self.client.get(path=url_for('mail.show', uid=get_mail().uid), follow_redirects=True)
r = self.client.get(path=url_for('mail.show', mai_id=self.get_mail().id), follow_redirects=True)
dump('mail_show', r)
self.assertEqual(r.status_code, 200)
......@@ -45,17 +36,17 @@ class TestMailViews(UffdTestCase):
self.assertEqual(r.status_code, 200)
def test_update(self):
m = get_mail()
m = self.get_mail()
self.assertIsNotNone(m)
self.assertEqual(m.uid, 'test')
self.assertEqual(sorted(m.receivers), ['test1@example.com', 'test2@example.com'])
self.assertEqual(sorted(m.destinations), ['testuser@mail.example.com'])
r = self.client.post(path=url_for('mail.update', uid=m.uid),
r = self.client.post(path=url_for('mail.update', mail_id=m.id),
data={'mail-uid': 'test1', 'mail-receivers': 'foo@bar.com\ntest@bar.com',
'mail-destinations': 'testuser@mail.example.com\ntestadmin@mail.example.com'}, follow_redirects=True)
dump('mail_update', r)
self.assertEqual(r.status_code, 200)
m = get_mail()
m = self.get_mail()
self.assertIsNotNone(m)
self.assertEqual(m.uid, 'test')
self.assertEqual(sorted(m.receivers), ['foo@bar.com', 'test@bar.com'])
......@@ -67,30 +58,27 @@ class TestMailViews(UffdTestCase):
'mail-destinations': 'testuser@mail.example.com\ntestadmin@mail.example.com'}, follow_redirects=True)
dump('mail_create', r)
self.assertEqual(r.status_code, 200)
m = Mail.query.get('uid=test1,ou=postfix,dc=example,dc=com')
m = Mail.query.filter_by(uid='test1').one()
self.assertEqual(m.uid, 'test1')
self.assertEqual(sorted(m.receivers), ['foo@bar.com', 'test@bar.com'])
self.assertEqual(sorted(m.destinations), ['testadmin@mail.example.com', 'testuser@mail.example.com'])
@unittest.skip('We do not catch LDAP errors at the moment!') # TODO: Not sure if necessary
@unittest.skip('We do not catch DB errors at the moment!') # TODO
def test_create_error(self):
r = self.client.post(path=url_for('mail.update'),
data={'mail-uid': 'test', 'mail-receivers': 'foo@bar.com\ntest@bar.com',
'mail-destinations': 'testuser@mail.example.com\ntestadmin@mail.example.com'}, follow_redirects=True)
dump('mail_create_error', r)
self.assertEqual(r.status_code, 200)
m = get_mail()
m = self.get_mail()
self.assertIsNotNone(m)
self.assertEqual(m.uid, 'test')
self.assertEqual(sorted(m.receivers), ['test1@example.com', 'test2@example.com'])
self.assertEqual(sorted(m.destinations), ['testuser@mail.example.com'])
def test_delete(self):
self.assertIsNotNone(get_mail())
r = self.client.get(path=url_for('mail.delete', uid=get_mail().uid), follow_redirects=True)
self.assertIsNotNone(self.get_mail())
r = self.client.get(path=url_for('mail.delete', mail_id=self.get_mail().id), follow_redirects=True)
dump('mail_delete', r)
self.assertEqual(r.status_code, 200)
self.assertIsNone(get_mail())
class TestMailViewsOL(TestMailViews):
use_openldap = True
self.assertIsNone(self.get_mail())