├── test-resources ├── cert.binary ├── server.keystore └── in-memeory-ds-schema ├── .gitignore ├── project.clj ├── test └── clj_ldap │ └── test │ ├── start_tls.clj │ ├── server.clj │ └── client.clj ├── CHANGELOG.md ├── README.md └── src └── clj_ldap └── client.clj /test-resources/cert.binary: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerrypnz/clj-ldap/master/test-resources/cert.binary -------------------------------------------------------------------------------- /test-resources/server.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerrypnz/clj-ldap/master/test-resources/server.keystore -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | classes 4 | lib 5 | target 6 | .lein* 7 | pom.xml 8 | pom.xml.asc 9 | .nrepl-port 10 | #*# 11 | .#* 12 | .DS_Store 13 | .idea 14 | clj-ldap.iml 15 | test-resources/access 16 | test-resources/access.lck 17 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject org.clojars.pntblnk/clj-ldap "0.0.16" 2 | :description "Clojure ldap client." 3 | :url "https://github.com/pauldorman/clj-ldap" 4 | :dependencies [[org.clojure/clojure "1.8.0"] 5 | [com.unboundid/unboundid-ldapsdk "4.0.4"]] 6 | :profiles {:test {:dependencies [[lein-clojars "0.9.1"]]}} 7 | :aot [clj-ldap.client] 8 | :license {:name "Eclipse Public License - v 1.0" 9 | :url "http://www.eclipse.org/legal/epl-v10.html" 10 | :distribution :repo 11 | :comments "same as Clojure"}) 12 | -------------------------------------------------------------------------------- /test/clj_ldap/test/start_tls.clj: -------------------------------------------------------------------------------- 1 | (ns clj-ldap.test.start-tls 2 | (:require [clojure.test :refer :all] 3 | [clj-ldap.client :as ldap] 4 | [clj-ldap.test.server :as server] 5 | [clj-ldap.test.client :as client]) 6 | (:import (com.unboundid.ldap.sdk LDAPConnectionPool))) 7 | 8 | (defn- test-server 9 | "Setup server" 10 | [f] 11 | (server/start!) 12 | (f) 13 | (server/stop!)) 14 | 15 | (defn- test-data 16 | "Provide test data" 17 | [f] 18 | (try 19 | (let [conn (ldap/connect {:host {:port (server/ldapPort)}})] 20 | (#'client/add-toplevel-objects! conn) 21 | (ldap/add conn (:dn client/person-a*) (:object client/person-a*))) 22 | (catch Exception e)) 23 | (f)) 24 | 25 | (use-fixtures :once test-server) 26 | (use-fixtures :each test-data) 27 | 28 | (deftest start-tls-test 29 | (testing "Connect with :bind-dn and StartTLS returns a valid connection pool" 30 | (is (= LDAPConnectionPool 31 | (type (ldap/connect {:host {:address "localhost" 32 | :port (server/ldapPort)} 33 | :startTLS? true 34 | :bind-dn (str "cn=testa," client/base*) 35 | :password "passa"})))))) -------------------------------------------------------------------------------- /test/clj_ldap/test/server.clj: -------------------------------------------------------------------------------- 1 | (ns clj-ldap.test.server 2 | "An embedded ldap server for unit testing" 3 | (:require [clj-ldap.client :as ldap]) 4 | (:import [com.unboundid.ldap.listener 5 | InMemoryDirectoryServerConfig 6 | InMemoryDirectoryServer 7 | InMemoryListenerConfig]) 8 | (:import [com.unboundid.ldap.sdk.schema 9 | Schema]) 10 | (:import [com.unboundid.util.ssl 11 | KeyStoreKeyManager 12 | SSLUtil 13 | TrustAllTrustManager]) 14 | (:import (java.io File) 15 | (java.util.logging FileHandler Level) 16 | (com.unboundid.util MinimalLogFormatter))) 17 | 18 | ;; server will hold an InMemoryDirectoryServer instance 19 | (defonce server (atom nil)) 20 | 21 | (defn- createAccessLogger 22 | "Let the server write protocol information to test-resources/access" 23 | [] 24 | (let [logFile (File. "test-resources/access") 25 | fileHandler (FileHandler. (.getAbsolutePath logFile) true)] 26 | (.setLevel fileHandler Level/INFO) 27 | (.setFormatter fileHandler (MinimalLogFormatter. nil false false true)) 28 | fileHandler)) 29 | 30 | (defn- start-ldap-server 31 | "Setup a server listening on available LDAP and LDAPS ports chosen at random" 32 | [] 33 | (let [cfg (InMemoryDirectoryServerConfig. (into-array String ["dc=alienscience,dc=org,dc=uk"])) 34 | _ (.addAdditionalBindCredentials cfg "cn=Directory Manager" "password") 35 | _ (.setSchema cfg (Schema/getDefaultStandardSchema)) 36 | _ (.setAccessLogHandler cfg (createAccessLogger)) 37 | keystore (KeyStoreKeyManager. "test-resources/server.keystore" 38 | (char-array "password") "JKS" "server-cert") 39 | serverSSLUtil (SSLUtil. keystore (TrustAllTrustManager.)) 40 | clientSSLUtil (SSLUtil. (TrustAllTrustManager.)) 41 | _ (.setListenerConfigs cfg 42 | [(InMemoryListenerConfig/createLDAPConfig 43 | "LDAP" nil 0 44 | (.createSSLSocketFactory serverSSLUtil)) 45 | (InMemoryListenerConfig/createLDAPSConfig 46 | "LDAPS" nil 0 47 | (.createSSLServerSocketFactory serverSSLUtil) 48 | (.createSSLSocketFactory clientSSLUtil))]) 49 | ds (InMemoryDirectoryServer. cfg)] 50 | (.startListening ds) 51 | ds)) 52 | 53 | (defn stop! 54 | "Stops the embedded ldap server (listening on LDAP and LDAPS ports)" 55 | [] 56 | (if @server 57 | (do 58 | (.shutDown @server true) 59 | (reset! server nil)))) 60 | 61 | (defn ldapPort 62 | [] 63 | (.getListenPort @server "LDAP")) 64 | 65 | (defn ldapsPort 66 | [] 67 | (.getListenPort @server "LDAPS")) 68 | 69 | (defn start! 70 | "Starts an embedded ldap server on the given port and SSL" 71 | [] 72 | (stop!) 73 | (reset! server (start-ldap-server))) 74 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## [0.0.16] 6 | This release adds the following: 7 | - A lazy-seq returned by `search-all-results` using SimplePagedResults control. Thanks to @jindrichmynarz. 8 | 9 | ## [0.0.15] 10 | This release adds the following: 11 | - A fix of StartTLS support. 12 | 13 | ## [0.0.14] 14 | This release adds the following: 15 | - The :initial-connections and :max-connections options to the connect function, without deprecating the :num-connections option. 16 | 17 | ## [0.0.13] 18 | This release adds the following: 19 | - The :delete-subtree option to the delete function, which requires the support of the Subtree Delete Request Control on the server 20 | 21 | ## [0.0.12] 22 | This release adds the following: 23 | - The :proxied-auth option to most functions 24 | - The compare? function which invokes LDAP compare operation 25 | - get-connection and release-connection functions which promote proper use of the connection pool when a sequence of operations must be performed on a single connection 26 | 27 | ## [0.0.11] 28 | This release addresses a bug introduced in 0.0.10 and bumps the ldap-sdk version to 3.1.1. 29 | - Fix bug preventing use of :pre-read and :post-read options to modify and delete 30 | - Introduce test coverage of :pre-read and :post-read 31 | 32 | ## [0.0.10] 33 | ### Binary (byte-array) attribute values 34 | The behavior of the api towards binary valued attributes has been fixed. Thanks to Ray Miller for providing the 35 | changes to `create-modification`. In addition, the api now returns binary data as it was orginally submitted as opposed 36 | to base64 encoded. Base64 encoding is only needed if the client requests a text representation of the LDAP entry, such as LDIF. A byte-valued collection of attribute names (as keywords) can be provided to all search functions to instruct the api to return 37 | byte arrays as opposed to strings. 38 | 39 | ### Bind? Fix 40 | A bug was discovered and fixed in `bind?` which would leave connections in the pool with an authorization ID 41 | associated with previous BIND. Thanks to Adam Harper and Sam Umbach for debugging. The `bind?` function 42 | now acts as follows: 43 | - If passed a connection pool (which is the default behavior, `connect` returns a connection pool) then the BIND is 44 | performed with a `pool.bindAndRevertAuthentication` which returns the autherization ID back to what it was before the BIND. 45 | - If passed an individual LDAPConnection (which can be retrieved via `(.getConnection pool)`, then 46 | the BIND is performed as usual and the caller is responsible for using `(.releaseAndReAuthenticateConnection pool conn)` 47 | to release the connection back to the pool. 48 | 49 | ### Added 50 | - size-limit, time-limit and types-only options to search. 51 | - StartTLS support with the startTLS? boolean option to connect. 52 | - byte-valued option to search functions which implies byte array values are to be returned for these attributes. 53 | - controls option to search. This allows passing in arbitrary controls which have been properly instantiated via java interOp. 54 | - respf option to search. This function, if defined, will be invoked on the list of response controls, if present. 55 | - server-sort option to search which attaches a ServerSideSortRequestControl to the search operation. 56 | - Who Am I? extended request. 57 | - Unit tests for the above. 58 | 59 | ### Replaced 60 | - test.server has been rewritten to incorporate the InMemoryDirectoryServer provided in the UnboundID LDAP SDK. 61 | -------------------------------------------------------------------------------- /test/clj_ldap/test/client.clj: -------------------------------------------------------------------------------- 1 | (ns clj-ldap.test.client 2 | "Automated tests for clj-ldap" 3 | (:require [clj-ldap.client :as ldap] 4 | [clj-ldap.test.server :as server]) 5 | (:use clojure.test)) 6 | 7 | 8 | ;; Tests are run over a variety of connection types (LDAP and LDAPS for now) 9 | (def ^:dynamic *connections* nil) 10 | (def ^:dynamic *conn* nil) 11 | (def ^:dynamic *c* nil) 12 | 13 | ;; Tests concentrate on a single object class 14 | (def toplevel* "dc=alienscience,dc=org,dc=uk") 15 | (def base* (str "ou=people," toplevel*)) 16 | (def dn* (str "cn=%s," base*)) 17 | (def object-class* #{"top" "person"}) 18 | 19 | ;; Variable to catch side effects 20 | (def ^:dynamic *side-effects* nil) 21 | 22 | ;; Result of a successful write 23 | (def success* {:code 0 :name "success"}) 24 | 25 | (defn read-bytes-from-file 26 | [filename] 27 | (let [f (java.io.File. filename) 28 | ary (byte-array (.length f)) 29 | is (java.io.FileInputStream. f)] 30 | (.read is ary) 31 | (.close is) 32 | ary)) 33 | 34 | ;; People to test with 35 | (def person-a* 36 | {:dn (format dn* "testa") 37 | :object {:objectClass object-class* 38 | :cn "testa" 39 | :sn "a" 40 | :description "description a" 41 | :telephoneNumber "000000001" 42 | :userPassword "passa"}}) 43 | 44 | (def person-b* 45 | {:dn (format dn* "testb") 46 | :object {:objectClass object-class* 47 | :cn "testb" 48 | :sn "b" 49 | :description "István Orosz" 50 | :telephoneNumber ["000000002" "00000003"] 51 | :userPassword "passb"}}) 52 | 53 | (def person-c* 54 | {:dn (format dn* "André Marchand") 55 | :object {:objectClass object-class* 56 | :cn "André Marchand" 57 | :sn "Marchand" 58 | :description "description c" 59 | :telephoneNumber "000000004" 60 | :userPassword "passc"}}) 61 | 62 | (defn- connect-to-server 63 | "Opens a sequence of connection pools on the localhost server with the 64 | given ports" 65 | [port ssl-port] 66 | [ 67 | (ldap/connect {:host {:port port}}) 68 | (ldap/connect {:host {:address "localhost" 69 | :port port} 70 | :num-connections 4}) 71 | (ldap/connect {:host (str "localhost:" port)}) 72 | (ldap/connect {:ssl? true 73 | :host {:port ssl-port} 74 | :initial-connections 2}) 75 | (ldap/connect {:starTLS? true 76 | :host {:port port}}) 77 | (ldap/connect {:host {:port port} 78 | :connect-timeout 1000 79 | :timeout 5000 80 | :max-connections 2}) 81 | (ldap/connect {:host [(str "localhost:" port) 82 | {:port ssl-port}]}) 83 | (ldap/connect {:host [(str "localhost:" ssl-port) 84 | {:port ssl-port}] 85 | :ssl? true 86 | :num-connections 5 87 | :max-connections 10})]) 88 | 89 | 90 | (defn- test-server 91 | "Setup server" 92 | [f] 93 | (server/start!) 94 | (binding [*connections* (connect-to-server (server/ldapPort) (server/ldapsPort))] 95 | (f)) 96 | (server/stop!)) 97 | 98 | (defn- add-toplevel-objects! 99 | "Adds top level entries, needed for testing, to the ldap server" 100 | [connection] 101 | (ldap/add connection toplevel* 102 | {:objectClass ["top" "domain" "extensibleObject"] 103 | :dc "alienscience"}) 104 | (ldap/add connection base* 105 | {:objectClass ["top" "organizationalUnit"] 106 | :ou "people"}) 107 | (ldap/add connection 108 | (str "cn=Saul Hazledine," base*) 109 | {:objectClass ["top" "Person"] 110 | :cn "Saul Hazledine" 111 | :sn "Hazledine" 112 | :description "Creator of bugs"})) 113 | 114 | (defn- test-data 115 | "Provide test data" 116 | [f] 117 | (doseq [connection *connections*] 118 | (binding [*conn* connection] 119 | (try 120 | (add-toplevel-objects! *conn*) 121 | (ldap/add *conn* (:dn person-a*) (:object person-a*)) 122 | (ldap/add *conn* (:dn person-b*) (:object person-b*)) 123 | (catch Exception e)) 124 | (f) 125 | (try 126 | (ldap/delete *conn* toplevel* {:delete-subtree true}) 127 | (catch Exception e))))) 128 | 129 | (use-fixtures :each test-data) 130 | (use-fixtures :once test-server) 131 | 132 | (deftest test-get 133 | (is (= (ldap/get *conn* (:dn person-a*)) 134 | (assoc (:object person-a*) :dn (:dn person-a*)))) 135 | (is (= (ldap/get *conn* (:dn person-b*)) 136 | (assoc (:object person-b*) :dn (:dn person-b*)))) 137 | (is (= (ldap/get *conn* (:dn person-a*) [:cn :sn]) 138 | {:dn (:dn person-a*) 139 | :cn (-> person-a* :object :cn) 140 | :sn (-> person-a* :object :sn)}))) 141 | 142 | (deftest test-bind 143 | (if (> (-> *conn* 144 | (.getConnectionPoolStatistics) 145 | (.getMaximumAvailableConnections)) 1) 146 | (binding [*c* (ldap/get-connection *conn*)] 147 | (let [before (ldap/who-am-i *c*) 148 | _ (ldap/bind? *c* (:dn person-a*) "passa") 149 | a (ldap/who-am-i *c*) 150 | _ (ldap/release-connection *conn* *c*)] 151 | (is (= [before a] 152 | ["" (:dn person-a*)])))))) 153 | 154 | (deftest test-add-delete 155 | (is (= (ldap/add *conn* (:dn person-c*) (:object person-c*)) 156 | success*)) 157 | (is (= (ldap/get *conn* (:dn person-c*)) 158 | (assoc (:object person-c*) :dn (:dn person-c*)))) 159 | (is (= (ldap/delete *conn* (:dn person-c*)) 160 | success*)) 161 | (is (nil? (ldap/get *conn* (:dn person-c*)))) 162 | (is (= (ldap/add *conn* (str "changeNumber=1234," base*) 163 | {:objectClass ["changeLogEntry"] 164 | :changeNumber 1234 165 | :targetDN base* 166 | :changeType "modify"}) 167 | success*)) 168 | (is (= (:changeNumber (ldap/get *conn* (str "changeNumber=1234," base*))) 169 | "1234")) 170 | (is (= (ldap/delete *conn* (str "changeNumber=1234," base*) 171 | {:pre-read [:objectClass]}) 172 | {:code 0, :name "success", 173 | :pre-read {:objectClass #{"top" "changeLogEntry"}}}))) 174 | 175 | (deftest test-delete-subtree 176 | (is (= (ldap/add *conn* (:dn person-c*) (:object person-c*)) 177 | success*)) 178 | (is (= (ldap/delete *conn* base* {:delete-subtree true}) 179 | success*)) 180 | (is (nil? (ldap/get *conn* base*)))) 181 | 182 | (deftest test-modify-add 183 | (is (= (ldap/modify *conn* (:dn person-a*) 184 | {:add {:objectClass "organizationalPerson" 185 | :l "Hollywood"} 186 | :pre-read #{:objectClass :l :cn} 187 | :post-read #{:l :cn}}) 188 | {:code 0, :name "success", 189 | :pre-read {:objectClass #{"top" "person"}, :cn "testa"}, 190 | :post-read {:l "Hollywood", :cn "testa"}})) 191 | (is (= (ldap/modify 192 | *conn* (:dn person-b*) 193 | {:add {:telephoneNumber ["0000000005" "0000000006"]}}) 194 | success*)) 195 | (let [new-a (ldap/get *conn* (:dn person-a*)) 196 | new-b (ldap/get *conn* (:dn person-b*)) 197 | obj-a (:object person-a*) 198 | obj-b (:object person-b*)] 199 | (is (= (:objectClass new-a) 200 | (conj (:objectClass obj-a) "organizationalPerson"))) 201 | (is (= (:l new-a) "Hollywood")) 202 | (is (= (set (:telephoneNumber new-b)) 203 | (set (concat (:telephoneNumber obj-b) 204 | ["0000000005" "0000000006"])))))) 205 | 206 | (deftest test-modify-delete 207 | (let [b-phonenums (-> person-b* :object :telephoneNumber)] 208 | (is (= (ldap/modify *conn* (:dn person-a*) 209 | {:delete {:description :all}}) 210 | success*)) 211 | (is (= (ldap/modify *conn* (:dn person-b*) 212 | {:delete {:telephoneNumber (first b-phonenums)}}) 213 | success*)) 214 | (is (= (ldap/get *conn* (:dn person-a*)) 215 | (-> (:object person-a*) 216 | (dissoc :description) 217 | (assoc :dn (:dn person-a*))))) 218 | (is (= (ldap/get *conn* (:dn person-b*)) 219 | (-> (:object person-b*) 220 | (assoc :telephoneNumber (second b-phonenums)) 221 | (assoc :dn (:dn person-b*))))))) 222 | 223 | (deftest test-modify-replace 224 | (let [new-phonenums (-> person-b* :object :telephoneNumber) 225 | certificate-data (read-bytes-from-file 226 | "test-resources/cert.binary")] 227 | (is (= (ldap/modify *conn* (:dn person-a*) 228 | {:replace {:telephoneNumber new-phonenums}}) 229 | success*)) 230 | (is (= (ldap/get *conn* (:dn person-a*)) 231 | (-> (:object person-a*) 232 | (assoc :telephoneNumber new-phonenums) 233 | (assoc :dn (:dn person-a*))))) 234 | (is (= (ldap/modify *conn* (:dn person-a*) 235 | {:add {:objectclass ["inetOrgPerson" 236 | "organizationalPerson"] 237 | :userCertificate certificate-data}} 238 | {:proxied-auth (str "dn:" (:dn person-a*))}) 239 | success*)) 240 | (is (= (seq (:userCertificate 241 | (first (ldap/search *conn* (:dn person-a*) 242 | {:scope :base 243 | :filter "(objectclass=inetorgperson)" 244 | :attributes [:userCertificate] 245 | :byte-valued [:userCertificate]})))) 246 | (seq certificate-data))) 247 | (is (= (seq (:userCertificate 248 | (first (ldap/search *conn* (:dn person-a*) 249 | {:scope :base 250 | :byte-valued [:userCertificate]})))) 251 | (seq certificate-data))) 252 | (is (= (seq (:userCertificate (ldap/get *conn* (:dn person-a*) 253 | [:userCertificate] 254 | [:userCertificate]))) 255 | (seq certificate-data))))) 256 | 257 | (deftest test-modify-all 258 | (let [b (:object person-b*) 259 | b-phonenums (:telephoneNumber b)] 260 | (is (= (ldap/modify *conn* (:dn person-b*) 261 | {:add {:telephoneNumber "0000000005"} 262 | :delete {:telephoneNumber (second b-phonenums)} 263 | :replace {:description "desc x"}}) 264 | success*)) 265 | (let [new-b (ldap/get *conn* (:dn person-b*))] 266 | (is (= (set (:telephoneNumber new-b)) 267 | (set [(first b-phonenums) "0000000005"]))) 268 | (is (= (:description new-b) "desc x"))))) 269 | 270 | (deftest test-search 271 | (is (= (set (map :cn 272 | (ldap/search *conn* base* {:attributes [:cn]}))) 273 | (set [nil "testa" "testb" "Saul Hazledine"]))) 274 | (is (= (set (map :cn 275 | (ldap/search *conn* base* 276 | {:attributes [:cn] 277 | :filter "cn=test*" 278 | :proxied-auth (str "dn:" (:dn person-a*))}))) 279 | (set ["testa" "testb"]))) 280 | (is (= (map :cn 281 | (ldap/search *conn* base* 282 | {:filter "cn=*" 283 | :server-sort {:is-critical true 284 | :sort-keys [:cn :ascending]}})) 285 | '("Saul Hazledine" "testa" "testb"))) 286 | (is (= (count (map :cn 287 | (ldap/search *conn* base* 288 | {:attributes [:cn] :filter "cn=*" 289 | :size-limit 2}))) 290 | 2)) 291 | (is (= (:description (map :cn 292 | (ldap/search *conn* base* 293 | {:attributes [:cn] 294 | :filter "cn=István Orosz" 295 | :types-only true}))) 296 | nil)) 297 | (is (= (set (map :description 298 | (ldap/search *conn* base* 299 | {:filter "cn=testb" :types-only false}))) 300 | (set ["István Orosz"]))) 301 | (binding [*side-effects* #{}] 302 | (ldap/search! *conn* base* {:attributes [:cn :sn] :filter "cn=test*"} 303 | (fn [x] 304 | (set! *side-effects* 305 | (conj *side-effects* (dissoc x :dn))))) 306 | (is (= *side-effects* 307 | (set [{:cn "testa" :sn "a"} 308 | {:cn "testb" :sn "b"}]))))) 309 | 310 | (deftest test-search-all 311 | (let [options {:attributes [:cn] :page-size 2} 312 | lazy-results (ldap/search-all *conn* base* options) 313 | eager-results (ldap/search *conn* base* options) 314 | ->cn-set (comp set (partial map :cn))] 315 | (testing "Since search-all is lazy, not all results are fetched" 316 | (is (not (realized? lazy-results)))) 317 | (testing "Lazy and eager search eventually produce the same results" 318 | (is (->cn-set lazy-results) 319 | (->cn-set eager-results))))) 320 | 321 | (deftest test-compare? 322 | (is (= (ldap/compare? *conn* (:dn person-b*) 323 | :description "István Orosz") 324 | true)) 325 | (is (= (ldap/compare? *conn* (:dn person-a*) 326 | :description "István Orosz" 327 | {:proxied-auth (str "dn:" (:dn person-b*))}) 328 | false))) 329 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Introduction 3 | 4 | clj-ldap is a thin layer on the [unboundid sdk](http://www.unboundid.com/products/ldap-sdk/) and allows clojure programs to talk to ldap servers. This library is available on [clojars.org](http://clojars.org/search?q=clj-ldap) 5 | ```clojure 6 | :dependencies [[org.clojars.pntblnk/clj-ldap "0.0.16"]] 7 | ``` 8 | # Example 9 | 10 | ```clojure 11 | (ns example 12 | (:require [clj-ldap.client :as ldap])) 13 | 14 | (def ldap-server (ldap/connect {:host "ldap.example.com"})) 15 | 16 | (ldap/get ldap-server "cn=dude,ou=people,dc=example,dc=com") 17 | 18 | ;; Returns a map such as 19 | {:gidNumber "2000" 20 | :loginShell "/bin/bash" 21 | :objectClass #{"inetOrgPerson" "posixAccount" "shadowAccount"} 22 | :mail "dude@example.com" 23 | :sn "Dudeness" 24 | :cn "dude" 25 | :uid "dude" 26 | :homeDirectory "/home/dude"} 27 | ``` 28 | 29 | # API 30 | 31 | ## connect [options] 32 | 33 | Connects to an ldap server and returns a, thread safe, [LDAPConnectionPool](http://www.unboundid.com/products/ldap-sdk/docs/javadoc/com/unboundid/ldap/sdk/LDAPConnectionPool.html). 34 | Options is a map with the following entries: 35 | 36 | :host Either a string in the form "address:port" 37 | OR a map containing the keys, 38 | :address defaults to localhost 39 | :port defaults to 389 (or 636 for ldaps), 40 | OR a collection containing multiple hosts used for load 41 | balancing and failover. This entry is optional. 42 | :bind-dn The DN to bind as, optional 43 | :password The password to bind with, optional 44 | :num-connections Establish a fixed size connection pool. Defaults to 1. 45 | :initial-connections Establish a connection pool initially of this size with 46 | capability to grow to :max-connections. Defaults to 1. 47 | :max-connections Define maximum size of connection pool. It must be 48 | greater than or equal to the initial number of 49 | connections, defaults to value of :initial-connections. 50 | :ssl? Boolean, connect over SSL (ldaps), defaults to false 51 | :startTLS? Boolean, use startTLS over non-SSL port, defaults to false 52 | :trust-store Only trust SSL certificates that are in this 53 | JKS format file, optional, defaults to trusting all 54 | certificates 55 | :connect-timeout The timeout for making connections (milliseconds), 56 | defaults to 1 minute 57 | :timeout The timeout when waiting for a response from the server 58 | (milliseconds), defaults to 5 minutes 59 | 60 | Throws an [LDAPException](http://www.unboundid.com/products/ldap-sdk/docs/javadoc/com/unboundid/ldap/sdk/LDAPException.html) if an error occurs establishing the connection pool or authenticating to any of the servers. 61 | Some examples: 62 | ```clojure 63 | (ldap/connect {:host "ldap.example.com" 64 | :num-connections 4 65 | :bind-dn "cn=admin,dc=example,dc=com" 66 | :password "password"}) 67 | 68 | (ldap/connect {:host [{:address "ldap1.example.com" :port 1389} 69 | {:address "ldap3.example.com"} 70 | "ldap2.example.com:1389"] 71 | :startTLS? true 72 | :initial-connections 9 73 | :max-connections 18 74 | :bind-dn "cn=directory manager" 75 | :password "password"}) 76 | 77 | (ldap/connect {:host {:port 1389} 78 | :bind-dn "cn=admin,dc=example,dc=com" 79 | :password "password"}) 80 | ``` 81 | The pool can then be used as a parameter to all the functions in the library where 82 | a connection is expected. Using a pool in this manner aleviates the caller from having to get and 83 | release connections. It will still be necessary to get and release a connection if a single 84 | connection is needed to process a sequence of operations. See the following bind? example. 85 | 86 | ## bind? [connection bind-dn password] [connection-pool bind-dn password] 87 | 88 | Usage: 89 | ```clojure 90 | (ldap/bind? pool "cn=dude,ou=people,dc=example,dc=com" "somepass") 91 | 92 | (let [conn (ldap/get-connection pool) 93 | user-dn "uid=user.1,ou=people,dc=example,dc=com" 94 | user-password "password"] 95 | (try 96 | (when (ldap/bind? conn user-dn user-password) 97 | (ldap/modify conn user-dn {:replace {:description "On sabatical"}})) 98 | (finally (ldap/release-connection pool conn)))) 99 | ``` 100 | Performs a bind operation using the provided connection, bindDN and 101 | password. Returns true if successful. 102 | 103 | If an LDAPConnectionPool object is passed as the connection argument 104 | the bind attempt will have no side-effects, leaving the state of the 105 | underlying connections unchanged. 106 | 107 | When an LDAP connection object is used as the connection argument the 108 | bind? function will attempt to change the identity of that connection 109 | to that of the provided DN. Subsequent operations on that connection 110 | will be done using the bound identity. 111 | 112 | ## get [connection dn] [connection dn attributes] 113 | 114 | If successful, returns a map containing the entry for the given DN. 115 | Returns nil if the entry doesn't exist. 116 | ```clojure 117 | (ldap/get conn "cn=dude,ou=people,dc=example,dc=com") 118 | ``` 119 | Takes an optional collection that specifies which attributes will be returned from the server. 120 | ```clojure 121 | (ldap/get conn "cn=dude,ou=people,dc=example,dc=com" [:cn :sn]) 122 | ``` 123 | Throws a [LDAPException](http://www.unboundid.com/products/ldap-sdk/docs/javadoc/com/unboundid/ldap/sdk/LDAPException.html) on error. 124 | 125 | ## add [connection dn entry] 126 | 127 | Adds an entry to the connected ldap server. The entry is map of keywords to values which can be strings, sets or vectors. 128 | 129 | ```clojure 130 | (ldap/add conn "cn=dude,ou=people,dc=example,dc=com" 131 | {:objectClass #{"top" "person"} 132 | :cn "dude" 133 | :sn "a" 134 | :description "His dudeness" 135 | :telephoneNumber ["1919191910" "4323324566"]}) 136 | ``` 137 | Throws a [LDAPException](http://www.unboundid.com/products/ldap-sdk/docs/javadoc/com/unboundid/ldap/sdk/LDAPException.html) if there is an error with the request or the add failed. 138 | 139 | ## compare? [connection dn attribute assertion-value] 140 | 141 | Determines if the specified entry contains the given attribute and value. 142 | 143 | ```clojure 144 | (ldap/compare? conn "cn=dude,ou=people,dc=example,dc=com" 145 | :description "His dudeness") 146 | ``` 147 | Throws a [LDAPException](http://www.unboundid.com/products/ldap-sdk/docs/javadoc/com/unboundid/ldap/sdk/LDAPException.html) if there is an error with the request or the LDAP compare failed. 148 | 149 | ## modify [connection dn modifications] 150 | 151 | Modifies an entry in the connected ldap server. The modifications are 152 | a map in the form: 153 | ```clojure 154 | {:add 155 | {:attribute-a some-value 156 | :attribute-b [value1 value2]} 157 | :delete 158 | {:attribute-c :all 159 | :attribute-d some-value 160 | :attribute-e [value1 value2]} 161 | :replace 162 | {:attibute-d value 163 | :attribute-e [value1 value2]} 164 | :increment 165 | {:attribute-f value} 166 | :pre-read 167 | #{:attribute-a :attribute-b} 168 | :post-read 169 | #{:attribute-c :attribute-d}} 170 | ``` 171 | Where :add adds an attribute value, :delete deletes an attribute value and :replace replaces the set of values for the attribute with the ones specified. The entries :pre-read and :post-read specify attributes that have be read and returned either before or after the modifications have taken place. 172 | 173 | All the keys in the map are optional e.g: 174 | ```clojure 175 | (ldap/modify conn "cn=dude,ou=people,dc=example,dc=com" 176 | {:add {:telephoneNumber "232546265"}}) 177 | ``` 178 | The values in the map can also be set to :all when doing a delete e.g: 179 | ```clojure 180 | (ldap/modify conn "cn=dude,ou=people,dc=example,dc=com" 181 | {:delete {:telephoneNumber :all}} 182 | {:proxied-auth "dn:cn=app,dc=example,dc=com"}) 183 | ``` 184 | The values of the attributes given in :pre-read and :post-read are available in the returned map and are part of an atomic ldap operation e.g 185 | ```clojure 186 | (ldap/modify conn "uid=maxuid,ou=people,dc=example,dc=com" 187 | {:increment {:uidNumber 1} 188 | :post-read #{:uidNumber}}) 189 | ``` 190 | returns 191 | ```clojure 192 | {:code 0 193 | :name "success" 194 | :post-read {:uidNumber "2002"}} 195 | ``` 196 | The above technique can be used to maintain counters for unique ids as described by [rfc4525](http://tools.ietf.org/html/rfc4525). 197 | 198 | Throws a [LDAPException](http://www.unboundid.com/products/ldap-sdk/docs/javadoc/com/unboundid/ldap/sdk/LDAPException.html) on error. 199 | 200 | ## search [connection base] [connection base options] 201 | 202 | Runs a search on the connected ldap server, reads all the results into 203 | memory and returns the results as a sequence of maps. An introduction 204 | to ldap searching can be found in this [article](http://www.enterprisenetworkingplanet.com/netsysm/article.php/3317551/Unmasking-the-LDAP-Search-Filter.htm). 205 | 206 | Options is a map with the following optional entries: 207 | 208 | :scope The search scope, can be :base :one :sub or :subordinate, 209 | defaults to :sub 210 | :filter A string representing the search filter, 211 | defaults to "(objectclass=*)" 212 | :attributes A collection of the attributes to return, 213 | defaults to all user attributes 214 | :byte-valued A collection of attributes to return as byte arrays as 215 | opposed to Strings. 216 | :size-limit The maximum number of entries that the server should return 217 | :time-limit The maximum length of time in seconds that the server should 218 | spend processing this request 219 | :types-only Return only attribute names instead of names and values 220 | :server-sort Instruct the server to sort the results. The value of this 221 | key is a map like the following: 222 | { :is-critical ( true | false ) 223 | :sort-keys [ :cn :ascending 224 | :employeNumber :descending ... ] } 225 | At least one sort key must be provided. 226 | :proxied-auth The dn: or u: to be used as the authorization 227 | identity when processing the request. Don't forget the dn:/u: prefix. 228 | :controls Adds the provided controls for this request. 229 | :respf Applies this function to the list of response controls present. 230 | 231 | e.g 232 | ```clojure 233 | (ldap/search conn "ou=people,dc=example,dc=com") 234 | 235 | (ldap/search conn "ou=people,dc=example,dc=com" {:attributes [:cn] :sizelimit 100 236 | :proxied-auth "dn:cn=app,dc=example,dc=com"}) 237 | 238 | (ldap/search conn "dc=example,dc=com" {:filter "(uid=abc123)" :attributes [:cn :uid :userCertificate] 239 | :byte-valued [:userCertificate]}) 240 | ``` 241 | Throws a [LDAPSearchException](http://www.unboundid.com/products/ldap-sdk/docs/javadoc/com/unboundid/ldap/sdk/LDAPSearchException.html) on error. This function will not throw the exception in the event 242 | of a size limit exceeded result, instead the entries are returned. 243 | 244 | ## search! [connection base f] [connection base options f] 245 | 246 | Runs a search on the connected ldap server and executes the given 247 | function (for side effects) on each result. Does not read all the 248 | results into memory. The options argument is a map similar to that of the search 249 | function defined above. e.g 250 | ```clojure 251 | (ldap/search! conn "ou=people,dc=example,dc=com" println) 252 | 253 | (ldap/search! conn "ou=people,dc=example,dc=com" 254 | {:filter "sn=dud*"} 255 | (fn [x] 256 | (println "Hello " (:cn x)))) 257 | ``` 258 | Throws a [LDAPSearchException](http://www.unboundid.com/products/ldap-sdk/docs/javadoc/com/unboundid/ldap/sdk/LDAPSearchException.html) if an error occurs during search. Throws an [EntrySourceException](http://www.unboundid.com/products/ldap-sdk/docs/javadoc/com/unboundid/ldap/sdk/EntrySourceException.html) if there is an error obtaining search results. 259 | 260 | ## delete [connection dn] [connection dn options] 261 | 262 | Deletes the given entry in the connected ldap server. 263 | 264 | Options is a map with the following optional entries: 265 | 266 | :delete-subtree Use the Subtree Delete Control to delete entire subtree rooted at dn. 267 | :pre-read A set of attributes that should be read before deletion (will only apply to base entry if used with :delete-subtree). 268 | :proxied-auth The dn: or u: to be used as the authorization 269 | identity when processing the request. Don't forget the dn:/u: prefix. 270 | ```clojure 271 | (ldap/delete conn "cn=dude,ou=people,dc=example,dc=com") 272 | 273 | (ldap/delete conn "cn=dude,ou=people,dc=example,dc=com" 274 | {:pre-read #{:telephoneNumber}}) 275 | ``` 276 | Throws a [LDAPException](http://www.unboundid.com/products/ldap-sdk/docs/javadoc/com/unboundid/ldap/sdk/LDAPException.html) if the object does not exist or an error occurs. 277 | -------------------------------------------------------------------------------- /src/clj_ldap/client.clj: -------------------------------------------------------------------------------- 1 | (ns clj-ldap.client 2 | "LDAP client" 3 | (:refer-clojure :exclude [get]) 4 | (:require [clojure.string :as string] 5 | [clojure.pprint :refer (pprint)]) 6 | (:import [com.unboundid.ldap.sdk 7 | LDAPResult 8 | LDAPConnectionOptions 9 | LDAPConnection 10 | ResultCode 11 | LDAPConnectionPool 12 | LDAPException 13 | Attribute 14 | Entry 15 | ModificationType 16 | ModifyRequest 17 | ModifyDNRequest 18 | Modification 19 | DeleteRequest 20 | SimpleBindRequest 21 | RoundRobinServerSet 22 | SearchRequest 23 | LDAPEntrySource 24 | EntrySourceException 25 | SearchScope 26 | DereferencePolicy 27 | LDAPSearchException 28 | Control 29 | StartTLSPostConnectProcessor 30 | CompareRequest 31 | CompareResult]) 32 | (:import [com.unboundid.ldap.sdk.extensions 33 | PasswordModifyExtendedRequest 34 | PasswordModifyExtendedResult 35 | WhoAmIExtendedRequest 36 | WhoAmIExtendedResult 37 | StartTLSExtendedRequest]) 38 | (:import [com.unboundid.ldap.sdk.controls 39 | PreReadRequestControl 40 | PostReadRequestControl 41 | PreReadResponseControl 42 | PostReadResponseControl 43 | ProxiedAuthorizationV2RequestControl 44 | SimplePagedResultsControl 45 | ServerSideSortRequestControl 46 | SortKey 47 | SubtreeDeleteRequestControl]) 48 | (:import [com.unboundid.util 49 | Base64]) 50 | (:import [com.unboundid.util.ssl 51 | SSLUtil 52 | TrustAllTrustManager 53 | TrustStoreTrustManager])) 54 | 55 | ;;======== Helper functions ==================================================== 56 | 57 | (def not-nil? (complement nil?)) 58 | 59 | ;; Define get-value to return String or Bytes depending on boolean 'byte-value' 60 | (defmulti ^:private get-value (fn [attr byte-value] byte-value)) 61 | 62 | (defmethod ^:private get-value true 63 | [attr _] 64 | (if (> (.size attr) 1) 65 | (vec (.getValueByteArrays attr)) 66 | (.getValueByteArray attr))) 67 | 68 | (defmethod ^:private get-value false 69 | [attr _] 70 | (if (> (.size attr) 1) 71 | (vec (.getValues attr)) 72 | (.getValue attr))) 73 | 74 | (defn- encodeBase64 [attr] 75 | "Can be used to produce LDIF" 76 | (if (> (.size attr) 1) 77 | (map #(Base64/encode %) (get-value attr)) 78 | (Base64/encode (get-value attr)))) 79 | 80 | (defn- extract-attribute 81 | "Extracts [:name value] from the given attribute object. Converts 82 | the objectClass attribute to a set. The byte-valued collection is 83 | referenced to detemine whether to return a String or byte array" 84 | [attr byte-valued] 85 | (let [k (keyword (.getName attr)) 86 | byte-value (contains? (set byte-valued) (keyword (.getName attr)))] 87 | (cond 88 | (= :objectClass k) [k (set (get-value attr byte-value))] 89 | :else [k (get-value attr byte-value)]))) 90 | 91 | (defn- entry-as-map 92 | "Returns a closure which converts an Entry object into a map optionally 93 | adding the DN. We pass along the byte-valued collection to properly 94 | return binary data." 95 | ([byte-valued] 96 | (entry-as-map byte-valued true)) 97 | ([byte-valued dn?] 98 | (fn [entry] 99 | (let [attrs (seq (.getAttributes entry))] 100 | (if dn? 101 | (apply hash-map :dn (.getDN entry) 102 | (mapcat #(extract-attribute % byte-valued) attrs)) 103 | (apply hash-map 104 | (mapcat #(extract-attribute % byte-valued) attrs))))))) 105 | 106 | (defn- add-response-control 107 | "Adds the values contained in given response control to the given map" 108 | [m control] 109 | (condp instance? control 110 | PreReadResponseControl 111 | (update-in m [:pre-read] merge ((entry-as-map [] false) 112 | (.getEntry control))) 113 | PostReadResponseControl 114 | (update-in m [:post-read] merge ((entry-as-map [] false) 115 | (.getEntry control))) 116 | m)) 117 | 118 | (defn- add-response-controls 119 | "Adds the values contained in the given response controls to the given map" 120 | [controls m] 121 | (reduce add-response-control m (seq controls))) 122 | 123 | (defn- ldap-result 124 | "Converts an LDAPResult object into a map" 125 | [obj] 126 | (let [res (.getResultCode obj) 127 | controls (.getResponseControls obj)] 128 | (add-response-controls 129 | controls 130 | {:code (.intValue res) 131 | :name (.getName res)}))) 132 | 133 | (defn- connection-options 134 | "Returns a LDAPConnectionOptions object" 135 | [{:keys [connect-timeout timeout]}] 136 | (let [opt (LDAPConnectionOptions.)] 137 | (when connect-timeout (.setConnectTimeoutMillis opt connect-timeout)) 138 | (when timeout (.setResponseTimeoutMillis opt timeout)) 139 | opt)) 140 | 141 | (defn- create-ssl-context 142 | "Returns a SSLContext object" 143 | [{:keys [trust-store]}] 144 | (let [trust-manager (if trust-store 145 | (TrustStoreTrustManager. trust-store) 146 | (TrustAllTrustManager.)) 147 | ssl-util (SSLUtil. trust-manager)] 148 | (.createSSLContext ssl-util))) 149 | 150 | (defn- create-ssl-factory 151 | "Returns a SSLSocketFactory object" 152 | [{:keys [trust-store]}] 153 | (let [trust-manager (if trust-store 154 | (TrustStoreTrustManager. trust-store) 155 | (TrustAllTrustManager.)) 156 | ssl-util (SSLUtil. trust-manager)] 157 | (.createSSLSocketFactory ssl-util))) 158 | 159 | (defn- host-as-map 160 | "Returns a single host as a map containing an :address and an optional 161 | :port" 162 | [host] 163 | (cond 164 | (nil? host) {:address "localhost" :port 389} 165 | (string? host) (let [[address port] (string/split host #":")] 166 | {:address (if (= address "") 167 | "localhost" 168 | address) 169 | :port (if port 170 | (int (Integer. port)))}) 171 | (map? host) (merge {:address "localhost"} host) 172 | :else (throw 173 | (IllegalArgumentException. 174 | (str "Invalid host for an ldap connection : " 175 | host))))) 176 | 177 | (defn- create-connection 178 | "Create an LDAPConnection object" 179 | [{:keys [host ssl? startTLS?] 180 | :as options 181 | :or {ssl? false startTLS? false}}] 182 | (let [h (host-as-map host) 183 | host (:address h) 184 | ldap-port (or (:port h) 389) 185 | ldaps-port (or (:port h) 636) 186 | opt (connection-options options)] 187 | (cond 188 | ssl? (let [ssl (create-ssl-factory options)] 189 | (LDAPConnection. ssl opt host ldaps-port)) 190 | startTLS? (let [conn (LDAPConnection. opt host ldap-port)] 191 | (.processExtendedOperation conn (StartTLSExtendedRequest. (create-ssl-context options))) 192 | conn) 193 | :else (LDAPConnection. opt host ldap-port)))) 194 | 195 | (defn- bind-request 196 | "Returns a BindRequest object" 197 | [{:keys [bind-dn password]}] 198 | (if bind-dn 199 | (SimpleBindRequest. bind-dn password) 200 | (SimpleBindRequest.))) 201 | 202 | (defn- create-server-set 203 | "Returns a RoundRobinServerSet" 204 | [{:keys [host ssl? startTLS?] 205 | :as options 206 | :or {ssl? false startTLS? false}}] 207 | (let [hosts (map host-as-map host) 208 | addresses (into-array (map :address hosts)) 209 | opt (connection-options options)] 210 | (if ssl? 211 | (let [ssl (create-ssl-factory options) 212 | ports (int-array (map #(or (:port %) (int 636)) hosts))] 213 | (RoundRobinServerSet. addresses ports ssl opt)) 214 | (let [ports (int-array (map #(or (:port %) (int 389)) hosts))] 215 | (RoundRobinServerSet. addresses ports opt))))) 216 | 217 | (defn- connect-to-host 218 | "Connect to a single host" 219 | [{:keys [num-connections initial-connections max-connections startTLS?] 220 | :as options 221 | :or {num-connections 1 startTLS? false}}] 222 | (let [connection (create-connection options) 223 | bind-result (.bind connection (bind-request options)) 224 | pcp (if startTLS? 225 | (StartTLSPostConnectProcessor. (create-ssl-context options)) 226 | nil) 227 | initial-connections (or initial-connections num-connections) 228 | max-connections (or max-connections initial-connections)] 229 | (if (= ResultCode/SUCCESS (.getResultCode bind-result)) 230 | (LDAPConnectionPool. connection initial-connections max-connections pcp) 231 | (throw (LDAPException. bind-result))))) 232 | 233 | (defn- connect-to-hosts 234 | "Connects to multiple hosts" 235 | [{:keys [num-connections initial-connections max-connections startTLS?] 236 | :as options 237 | :or {num-connections 1 startTLS? false}}] 238 | (let [server-set (create-server-set options) 239 | bind-request (bind-request options) 240 | pcp (when startTLS? 241 | (StartTLSPostConnectProcessor. (create-ssl-context options))) 242 | initial-connections (or initial-connections num-connections) 243 | max-connections (or max-connections initial-connections)] 244 | (LDAPConnectionPool. server-set bind-request 245 | initial-connections max-connections pcp))) 246 | 247 | 248 | (defn- set-entry-kv! 249 | "Sets the given key/value pair in the given entry object" 250 | [entry-obj k v] 251 | (let [name-str (name k)] 252 | (.addAttribute entry-obj 253 | (if (coll? v) 254 | (Attribute. name-str (into-array v)) 255 | (Attribute. name-str (str v)))))) 256 | 257 | (defn- set-entry-map! 258 | "Sets the attributes in the given entry object using the given map" 259 | [entry-obj m] 260 | (doseq [[k v] m] 261 | (set-entry-kv! entry-obj k v))) 262 | 263 | (defn- byte-array? 264 | [v] 265 | (= (type v) (type (byte-array 0)))) 266 | 267 | (defn- create-modification 268 | "Creates a modification object" 269 | [modify-op attribute values] 270 | (cond 271 | (and (coll? values) (byte-array? (first values))) 272 | (Modification. modify-op attribute (into-array values)) 273 | (coll? values) 274 | (Modification. modify-op attribute (into-array String (map str values))) 275 | (= :all values) 276 | (Modification. modify-op attribute) 277 | (byte-array? values) 278 | (Modification. modify-op attribute values) 279 | :else 280 | (Modification. modify-op attribute (str values)))) 281 | 282 | (defn- modify-ops 283 | "Returns a sequence of Modification objects to do the given operation 284 | using the contents of the given map." 285 | [modify-op modify-map] 286 | (for [[k v] modify-map] 287 | (create-modification modify-op (name k) v))) 288 | 289 | (defn- add-request-controls 290 | [request options] 291 | "Adds LDAP controls to the given request" 292 | (when (contains? options :pre-read) 293 | (let [attributes (map name (options :pre-read)) 294 | pre-read-control (PreReadRequestControl. (into-array attributes))] 295 | (.addControl request pre-read-control))) 296 | (when (contains? options :post-read) 297 | (let [attributes (map name (options :post-read)) 298 | pre-read-control (PostReadRequestControl. (into-array attributes))] 299 | (.addControl request pre-read-control))) 300 | (when (contains? options :proxied-auth) 301 | (.addControl request (ProxiedAuthorizationV2RequestControl. 302 | (:proxied-auth options)))) 303 | (when (and (contains? options :delete-subtree) (= (type request) DeleteRequest)) 304 | (.addControl request (SubtreeDeleteRequestControl.)))) 305 | 306 | (defn- get-modify-request 307 | "Sets up a ModifyRequest object using the contents of the given map" 308 | [dn modifications] 309 | (let [adds (modify-ops ModificationType/ADD (modifications :add)) 310 | deletes (modify-ops ModificationType/DELETE (modifications :delete)) 311 | replacements (modify-ops ModificationType/REPLACE 312 | (modifications :replace)) 313 | increments (modify-ops ModificationType/INCREMENT 314 | (modifications :increment)) 315 | all (concat adds deletes replacements increments)] 316 | (doto (ModifyRequest. dn (into-array all)) 317 | (add-request-controls modifications)))) 318 | 319 | (defn- next-entry 320 | "Attempts to get the next entry from an LDAPEntrySource object" 321 | [source] 322 | (try 323 | (.nextEntry source) 324 | (catch EntrySourceException e 325 | (if (.mayContinueReading e) 326 | (.nextEntry source) 327 | (throw e))))) 328 | 329 | (defn- entry-seq 330 | "Returns a lazy sequence of entries from an LDAPEntrySource object" 331 | [source] 332 | (if-let [n (.nextEntry source)] 333 | (cons n (lazy-seq (entry-seq source))))) 334 | 335 | ;; Extended version of search-results function using a 336 | ;; SearchRequest that uses a SimplePagedResultsControl. 337 | ;; Allows us to read arbitrarily large result sets. 338 | (defn- search-all-results 339 | "Returns a lazy sequence of search results via paging so we don't run into 340 | size limits with the number of results." 341 | ([connection {:keys [base scope filter attributes size-limit time-limit 342 | types-only controls byte-valued 343 | cookie page-size request] 344 | :or {page-size 500} 345 | :as options}] 346 | (let [paging (SimplePagedResultsControl. page-size cookie) 347 | req (doto (or request 348 | (cond-> (SearchRequest. base scope DereferencePolicy/NEVER 349 | size-limit time-limit types-only filter attributes) 350 | (seq controls) (doto (.addControls (into-array Control controls))))) 351 | (.setControls (list paging))) 352 | response (.search connection req) 353 | results (->> response 354 | .getSearchEntries 355 | (map (entry-as-map byte-valued)) 356 | (remove empty?)) 357 | next-paging (SimplePagedResultsControl/get response) 358 | next-cookie (.getCookie next-paging)] 359 | (if (and next-paging (pos? (.getValueLength next-cookie))) 360 | (search-all-results connection 361 | (merge options {:cookie next-cookie :request request}) 362 | results) 363 | results))) 364 | ([connection options results] 365 | (lazy-seq (concat results (search-all-results connection options))))) 366 | 367 | (defn- search-results 368 | "Returns a sequence of search results for the given search criteria. 369 | Ignore a size limit exceeded exception if one occurs. If the caller 370 | provided a respf then apply the function to any response controls." 371 | [conn {:keys [base scope filter attributes size-limit time-limit types-only 372 | controls respf byte-valued]}] 373 | (try 374 | (let [req (SearchRequest. base scope DereferencePolicy/NEVER size-limit 375 | time-limit types-only filter attributes) 376 | - (and (not (empty? controls)) 377 | (.addControls req (into-array Control controls))) 378 | res (.search conn req)] 379 | (when (not-nil? respf) (respf (.getResponseControls res))) 380 | (if (> (.getEntryCount res) 0) 381 | (map (entry-as-map byte-valued) (.getSearchEntries res)))) 382 | (catch LDAPSearchException e 383 | (when (not-nil? respf) (respf (.getResponseControls e))) 384 | (if (= ResultCode/SIZE_LIMIT_EXCEEDED (.getResultCode e)) 385 | (map (entry-as-map byte-valued) (.getSearchEntries e)) 386 | (throw e))))) 387 | 388 | (defn- search-results! 389 | "Call the given function with the results of the search using 390 | the given search criteria" 391 | [pool {:keys [base scope filter attributes size-limit time-limit types-only 392 | controls respf byte-valued]} f] 393 | (let [req (SearchRequest. base scope DereferencePolicy/NEVER 394 | size-limit time-limit types-only filter attributes) 395 | - (and (not (empty? controls)) 396 | (.addControls req (into-array Control controls))) 397 | conn (.getConnection pool)] 398 | (try 399 | (with-open [source (LDAPEntrySource. conn req false)] 400 | (let [res (.getSearchResult source)] 401 | (when (not-nil? respf) (respf (.getResponseControls res))) 402 | (doseq [i (remove empty? 403 | (map (entry-as-map byte-valued) 404 | (entry-seq source)))] 405 | (f i)))) 406 | (.releaseConnection pool conn) 407 | (catch EntrySourceException e 408 | (.releaseDefunctConnection pool conn) 409 | (throw e))))) 410 | 411 | (defn- get-scope 412 | "Converts a keyword into a SearchScope object" 413 | [k] 414 | (condp = k 415 | :base SearchScope/BASE 416 | :one SearchScope/ONE 417 | :subordinate SearchScope/SUBORDINATE_SUBTREE 418 | SearchScope/SUB)) 419 | 420 | (defn- get-attributes 421 | "Converts a collection of attributes into an array" 422 | [attrs] 423 | (cond 424 | (or (nil? attrs) 425 | (empty? attrs)) (into-array java.lang.String 426 | [SearchRequest/ALL_USER_ATTRIBUTES]) 427 | :else (into-array java.lang.String 428 | (map name attrs)))) 429 | 430 | (defn- sortOrder 431 | "Convert friendly name to boolean according to ServerSideSort control" 432 | [order] 433 | (condp = order 434 | :ascending false 435 | :descending true 436 | :else false)) 437 | 438 | (defn- createServerSideSort 439 | "Create a ServerSideSortRequestControl base on the provided map with 440 | keys :is-critical and :sort-keys. The former is boolean valued while the latter 441 | is a vector: [:attr1 :ascending :attr2 :descending ... ]. 442 | Throw an exception if no sortKey is defined. If :is-critical is not defined, 443 | assume false." 444 | [{:keys [is-critical sort-keys] :or { is-critical false sort-keys []}}] 445 | (if (not (empty? sort-keys)) 446 | (let [keylist (map (fn [[k v]] (SortKey. (name k) (sortOrder v))) 447 | (apply array-map sort-keys))] 448 | (ServerSideSortRequestControl. is-critical (into-array SortKey keylist))) 449 | (throw (Exception. "Error: The search option 'server-sort' requires 450 | non-empty sort-keys")))) 451 | 452 | (defn- search-criteria 453 | "Given a map of search criteria and possibly other keys, return the same map 454 | with search criteria keys rewritten ready for passing to search functions." 455 | [base {:keys [scope filter attributes size-limit time-limit types-only 456 | proxied-auth controls respf server-sort byte-valued] 457 | :as original 458 | :or {size-limit 0 time-limit 0 types-only false byte-valued [] 459 | filter "(objectclass=*)" controls [] respf nil 460 | proxied-auth nil server-sort nil}}] 461 | (let [server-sort-control (if (not-nil? server-sort) 462 | [(createServerSideSort server-sort)] 463 | []) 464 | proxied-auth-control (if (not-nil? proxied-auth) 465 | [(ProxiedAuthorizationV2RequestControl. proxied-auth)] 466 | [])] 467 | (merge original {:base base 468 | :scope (get-scope scope) 469 | :filter filter 470 | :attributes (get-attributes attributes) 471 | :size-limit size-limit :time-limit time-limit 472 | :types-only types-only 473 | :controls (-> controls 474 | (into server-sort-control) 475 | (into proxied-auth-control))}))) 476 | 477 | ;;=========== API ============================================================== 478 | 479 | (defn connect 480 | "Connects to an ldap server and returns a thread-safe LDAPConnectionPool. 481 | Options is a map with the following entries: 482 | :host Either a string in the form \"address:port\" 483 | OR a map containing the keys, 484 | :address defaults to localhost 485 | :port defaults to 389 (or 636 for ldaps), 486 | OR a collection containing multiple hosts used for load 487 | balancing and failover. This entry is optional. 488 | :bind-dn The DN to bind as, optional 489 | :password The password to bind with, optional 490 | :num-connections Establish a fixed size connection pool. Defaults to 1. 491 | :initial-connections Establish a connection pool initially of this size with 492 | capability to grow to :max-connections. Defaults to 1. 493 | :max-connections Define maximum size of connection pool. It must be 494 | greater than or equal to the initial number of 495 | connections, defaults to value of :initial-connections. 496 | :ssl? Boolean, connect over SSL (ldaps), defaults to false 497 | :startTLS? Boolean, use startTLS over non-SSL port, defaults to false 498 | :trust-store Only trust SSL certificates that are in this 499 | JKS format file, optional, defaults to trusting all 500 | certificates 501 | :connect-timeout The timeout for making connections (milliseconds), 502 | defaults to 1 minute 503 | :timeout The timeout when waiting for a response from the server 504 | (milliseconds), defaults to 5 minutes 505 | " 506 | [options] 507 | (let [host (options :host)] 508 | (if (and (coll? host) 509 | (not (map? host))) 510 | (connect-to-hosts options) 511 | (connect-to-host options)))) 512 | 513 | (defn get-connection 514 | "Returns a connection from the LDAPConnectionPool object. This approach is 515 | only needed when a sequence of operations must be performed on a single 516 | connection. For example: get-connection, bind?, modify (as the bound user). 517 | The connection should be released back to the pool after use." 518 | [pool] 519 | (.getConnection pool)) 520 | 521 | (defn release-connection 522 | "Returns the original connection pool with the provided connection released 523 | and reauthenticated." 524 | [pool connection] 525 | (.releaseAndReAuthenticateConnection pool connection)) 526 | 527 | (defn bind? 528 | "Performs a bind operation using the provided connection, bindDN and 529 | password. Returns true if successful. 530 | 531 | When an LDAP connection object is used as the connection argument the 532 | bind? function will attempt to change the identity of that connection 533 | to that of the provided DN. Subsequent operations on that connection 534 | will be done using the bound identity. 535 | 536 | If an LDAP connection pool object is passed as the connection argument 537 | the bind attempt will have no side-effects, leaving the state of the 538 | underlying connections unchanged." 539 | [connection bind-dn password] 540 | (try 541 | (let [r (if (instance? LDAPConnectionPool connection) 542 | (.bindAndRevertAuthentication connection bind-dn password nil) 543 | (.bind connection bind-dn password))] 544 | (= ResultCode/SUCCESS (.getResultCode r))) 545 | (catch Exception _ false))) 546 | 547 | (defn close 548 | "closes the supplied connection or pool object" 549 | [conn] 550 | (.close conn)) 551 | 552 | (defn who-am-i 553 | "Return the authorization identity associated with this connection." 554 | [connection] 555 | (let [^WhoAmIExtendedResult res (.processExtendedOperation 556 | connection (WhoAmIExtendedRequest.))] 557 | (if (= ResultCode/SUCCESS (.getResultCode res)) 558 | (let [authz-id (.getAuthorizationID res)] 559 | (cond 560 | (or (= authz-id "") (= authz-id "dn:")) "" 561 | (.startsWith authz-id "dn:") (subs authz-id 3) 562 | (.startsWith authz-id "u:") (subs authz-id 2) 563 | :else authz-id))))) 564 | 565 | (defn get 566 | "If successful, returns a map containing the entry for the given DN. 567 | Returns nil if the entry doesn't exist or cannot be read. Takes an 568 | optional collection that specifies which attributes will be returned 569 | from the server." 570 | ([connection dn] 571 | (get connection dn nil)) 572 | ([connection dn attributes] 573 | (get connection dn attributes [])) 574 | ([connection dn attributes byte-valued] 575 | (if-let [result (if attributes 576 | (.getEntry connection dn 577 | (into-array java.lang.String 578 | (map name attributes))) 579 | (.getEntry connection dn))] 580 | ((entry-as-map byte-valued) result)))) 581 | 582 | (defn add 583 | "Adds an entry to the connected ldap server. The entry is assumed to be 584 | a map. The options map supports control :proxied-auth." 585 | ([connection dn entry] 586 | (add connection dn entry nil)) 587 | ([connection dn entry options] 588 | (let [entry-obj (Entry. dn)] 589 | (set-entry-map! entry-obj entry) 590 | (when options 591 | (add-request-controls entry-obj options)) 592 | (ldap-result 593 | (.add connection entry-obj))))) 594 | 595 | (defn compare? 596 | "Determine whether the specified entry contains a given attribute value. 597 | The options map supports control :proxied-auth." 598 | ([connection dn attribute assertion-value] 599 | (compare? connection dn attribute assertion-value nil)) 600 | ([connection dn attribute assertion-value options] 601 | (let [request (CompareRequest. dn (name attribute) assertion-value)] 602 | (when (and options (:proxied-auth options)) 603 | (.addControl request (ProxiedAuthorizationV2RequestControl. 604 | (:proxied-auth options)))) 605 | (.compareMatched (.compare connection request))))) 606 | 607 | (defn modify 608 | "Modifies an entry in the connected ldap server. The modifications are 609 | a map in the form: 610 | {:add 611 | {:attribute-a some-value 612 | :attribute-b [value1 value2]} 613 | :delete 614 | {:attribute-c :all 615 | :attribute-d some-value 616 | :attribute-e [value1 value2]} 617 | :replace 618 | {:attibute-d value 619 | :attribute-e [value1 value2]} 620 | :increment 621 | {:attribute-f value} 622 | :pre-read 623 | #{:attribute-a :attribute-b} 624 | :post-read 625 | #{:attribute-c :attribute-d}} 626 | 627 | Where :add adds an attribute value, :delete deletes an attribute value and 628 | :replace replaces the set of values for the attribute with the ones specified. 629 | The entries :pre-read and :post-read specify attributes that have be read and 630 | returned either before or after the modifications have taken place." 631 | ([connection dn modifications] 632 | (modify connection dn modifications nil)) 633 | ([connection dn modifications options] 634 | (let [modify-obj (get-modify-request dn modifications)] 635 | (when options 636 | (add-request-controls modify-obj options)) 637 | (ldap-result 638 | (.modify connection modify-obj))))) 639 | 640 | (defn modify-password 641 | "Creates a new password modify extended request that will attempt to change 642 | the password of the currently-authenticated user, or another user if their 643 | DN is provided and the caller has the required authorisation." 644 | ([connection new] 645 | (let [request (PasswordModifyExtendedRequest. new)] 646 | (.processExtendedOperation connection request))) 647 | 648 | ([connection old new] 649 | (let [request (PasswordModifyExtendedRequest. old new)] 650 | (.processExtendedOperation connection request))) 651 | 652 | ([connection old new dn] 653 | (let [request (PasswordModifyExtendedRequest. dn old new)] 654 | (.processExtendedOperation connection request)))) 655 | 656 | (defn modify-rdn 657 | "Modifies the RDN (Relative Distinguished Name) of an entry in the connected 658 | ldap server. 659 | 660 | The new-rdn has the form cn=foo or ou=foo. Using just foo is not sufficient. 661 | The delete-old-rdn boolean option indicates whether to delete the current 662 | RDN value from the target entry. The options map supports pre/post-read 663 | and proxied-auth controls." 664 | ([connection dn new-rdn delete-old-rdn] 665 | (modify-rdn connection dn new-rdn delete-old-rdn nil)) 666 | ([connection dn new-rdn delete-old-rdn options] 667 | (let [request (ModifyDNRequest. dn new-rdn delete-old-rdn)] 668 | (when options 669 | (add-request-controls request options)) 670 | (ldap-result 671 | (.modifyDN connection request))))) 672 | 673 | (defn delete 674 | "Deletes the given entry in the connected ldap server. Optionally takes 675 | a map that can contain: 676 | :pre-read A set of attributes that should be read before deletion 677 | (only applied to base entry if used with :delete-subtree) 678 | :proxied-auth The dn: or u: to be used as the authorization 679 | identity when processing the request. 680 | :delete-subtree If truthy, deletes the entire subtree of DN (server must 681 | support Subtree Delete Control, 1.2.840.113556.1.4.805)" 682 | ([connection dn] 683 | (delete connection dn nil)) 684 | ([connection dn options] 685 | (let [delete-obj (DeleteRequest. dn)] 686 | (when options 687 | (add-request-controls delete-obj options)) 688 | (ldap-result 689 | (.delete connection delete-obj))))) 690 | 691 | ;; For the following search functions. 692 | ;; Options is a map with the following optional entries: 693 | ;; :scope The search scope, can be :base :one :sub or :subordinate, 694 | ;; defaults to :sub 695 | ;; :filter A string representing the search filter, 696 | ;; defaults to "(objectclass=*)" 697 | ;; :attributes A collection of the attributes to return, 698 | ;; defaults to all user attributes 699 | ;; :byte-valued A collection of attributes to return as byte arrays as 700 | ;; opposed to Strings. 701 | ;; :size-limit The maximum number of entries that the server should return 702 | ;; :time-limit The maximum length of time in seconds that the server should 703 | ;; spend processing this request 704 | ;; :types-only Return only attribute names instead of names and values 705 | ;; :server-sort Instruct the server to sort the results. The value of this 706 | ;; key is a map like the following: 707 | ;; :is-critical ( true | false ) 708 | ;; :sort-keys [ :cn :ascending 709 | ;; :employeNumber :descending ... ] 710 | ;; At least one sort key must be provided. 711 | ;; :proxied-auth The dn: or u: to be used as the authorization 712 | ;; identity when processing the request. 713 | ;; :controls Adds the provided controls for this request. 714 | ;; :respf Applies this function to all response controls present. 715 | 716 | (defn search-all 717 | "Uses SimplePagedResultsControl to search on the connected ldap server, 718 | and returns the results as a lazy sequence of maps." 719 | ([connection base] 720 | (search-all connection base nil)) 721 | ([connection base options] 722 | (search-all-results connection (search-criteria base options)))) 723 | 724 | (defn search 725 | "Runs a search on the connected ldap server, reads all the results into 726 | memory and returns the results as a sequence of maps." 727 | ([connection base] 728 | (search connection base nil)) 729 | ([connection base options] 730 | (search-results connection (search-criteria base options)))) 731 | 732 | (defn search! 733 | "Runs a search on the connected ldap server and executes the given 734 | function (for side effects) on each result. Does not read all the 735 | results into memory." 736 | ([connection base f] 737 | (search! connection base nil f)) 738 | ([connection base options f] 739 | (search-results! connection (search-criteria base options) f))) 740 | -------------------------------------------------------------------------------- /test-resources/in-memeory-ds-schema: -------------------------------------------------------------------------------- 1 | ; This is a dump of the InMemeoryDirectoryServer schema 2 | ({:attributeTypes 3 | ["( 2.5.4.0 NAME 'objectClass' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 X-ORIGIN 'RFC 4512' )" 4 | "( 2.5.4.1 NAME 'aliasedObjectName' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'RFC 4512' )" 5 | "( 2.5.18.3 NAME 'creatorsName' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation X-ORIGIN 'RFC 4512' )" 6 | "( 2.5.18.1 NAME 'createTimestamp' EQUALITY generalizedTimeMatch ORDERING generalizedTimeOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation X-ORIGIN 'RFC 4512' )" 7 | "( 2.5.18.4 NAME 'modifiersName' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation X-ORIGIN 'RFC 4512' )" 8 | "( 2.5.18.2 NAME 'modifyTimestamp' EQUALITY generalizedTimeMatch ORDERING generalizedTimeOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation X-ORIGIN 'RFC 4512' )" 9 | "( 2.5.21.9 NAME 'structuralObjectClass' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation X-ORIGIN 'RFC 4512' )" 10 | "( 2.5.21.10 NAME 'governingStructureRule' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation X-ORIGIN 'RFC 4512' )" 11 | "( 2.5.18.10 NAME 'subschemaSubentry' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation X-ORIGIN 'RFC 4512' )" 12 | "( 2.5.21.6 NAME 'objectClasses' EQUALITY objectIdentifierFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.37 USAGE directoryOperation X-ORIGIN 'RFC 4512' )" 13 | "( 2.5.21.5 NAME 'attributeTypes' EQUALITY objectIdentifierFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.3 USAGE directoryOperation X-ORIGIN 'RFC 4512' )" 14 | "( 2.5.21.4 NAME 'matchingRules' EQUALITY objectIdentifierFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.30 USAGE directoryOperation X-ORIGIN 'RFC 4512' )" 15 | "( 2.5.21.8 NAME 'matchingRuleUse' EQUALITY objectIdentifierFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.31 USAGE directoryOperation X-ORIGIN 'RFC 4512' )" 16 | "( 1.3.6.1.4.1.1466.101.120.16 NAME 'ldapSyntaxes' EQUALITY objectIdentifierFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.54 USAGE directoryOperation X-ORIGIN 'RFC 4512' )" 17 | "( 2.5.21.2 NAME 'dITContentRules' EQUALITY objectIdentifierFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.16 USAGE directoryOperation X-ORIGIN 'RFC 4512' )" 18 | "( 2.5.21.1 NAME 'dITStructureRules' EQUALITY integerFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.17 USAGE directoryOperation X-ORIGIN 'RFC 4512' )" 19 | "( 2.5.21.7 NAME 'nameForms' EQUALITY objectIdentifierFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.35 USAGE directoryOperation X-ORIGIN 'RFC 4512' )" 20 | "( 1.3.6.1.4.1.1466.101.120.6 NAME 'altServer' SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 USAGE dSAOperation X-ORIGIN 'RFC 4512' )" 21 | "( 1.3.6.1.4.1.1466.101.120.5 NAME 'namingContexts' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 USAGE dSAOperation X-ORIGIN 'RFC 4512' )" 22 | "( 1.3.6.1.4.1.1466.101.120.13 NAME 'supportedControl' SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 USAGE dSAOperation X-ORIGIN 'RFC 4512' )" 23 | "( 1.3.6.1.4.1.1466.101.120.7 NAME 'supportedExtension' SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 USAGE dSAOperation X-ORIGIN 'RFC 4512' )" 24 | "( 1.3.6.1.4.1.4203.1.3.5 NAME 'supportedFeatures' EQUALITY objectIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 USAGE dSAOperation X-ORIGIN 'RFC 4512' )" 25 | "( 1.3.6.1.4.1.1466.101.120.15 NAME 'supportedLDAPVersion' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 USAGE dSAOperation X-ORIGIN 'RFC 4512' )" 26 | "( 1.3.6.1.4.1.1466.101.120.14 NAME 'supportedSASLMechanisms' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 USAGE dSAOperation X-ORIGIN 'RFC 4512' )" 27 | "( 2.5.4.41 NAME 'name' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'RFC 4519' )" 28 | "( 2.5.4.15 NAME 'businessCategory' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'RFC 4519' )" 29 | "( 2.5.4.6 NAME 'c' SUP name SYNTAX 1.3.6.1.4.1.1466.115.121.1.11 SINGLE-VALUE X-ORIGIN 'RFC 4519' )" 30 | "( 2.5.4.3 NAME 'cn' SUP name X-ORIGIN 'RFC 4519' )" 31 | "( 0.9.2342.19200300.100.1.25 NAME 'dc' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE X-ORIGIN 'RFC 4519' )" 32 | "( 2.5.4.13 NAME 'description' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'RFC 4519' )" 33 | "( 2.5.4.27 NAME 'destinationIndicator' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'RFC 4519' )" 34 | "( 2.5.4.49 NAME 'distinguishedName' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'RFC 4519' )" 35 | "( 2.5.4.46 NAME 'dnQualifier' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'RFC 4519' )" 36 | "( 2.5.4.47 NAME 'enhancedSearchGuide' SYNTAX 1.3.6.1.4.1.1466.115.121.1.21 X-ORIGIN 'RFC 4519' )" 37 | "( 2.5.4.23 NAME 'facsimileTelephoneNumber' SYNTAX 1.3.6.1.4.1.1466.115.121.1.22 X-ORIGIN 'RFC 4519' )" 38 | "( 2.5.4.44 NAME 'generationQualifier' SUP name X-ORIGIN 'RFC 4519' )" 39 | "( 2.5.4.42 NAME 'givenName' SUP name X-ORIGIN 'RFC 4519' )" 40 | "( 2.5.4.51 NAME 'houseIdentifier' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'RFC 4519' )" 41 | "( 2.5.4.43 NAME 'initials' SUP name X-ORIGIN 'RFC 4519' )" 42 | "( 2.5.4.25 NAME 'internationalISDNNumber' EQUALITY numericStringMatch SUBSTR numericStringSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 X-ORIGIN 'RFC 4519' )" 43 | "( 2.5.4.7 NAME 'l' SUP name X-ORIGIN 'RFC 4519' )" 44 | "( 2.5.4.31 NAME 'member' SUP distinguishedName X-ORIGIN 'RFC 4519' )" 45 | "( 2.5.4.10 NAME 'o' SUP name X-ORIGIN 'RFC 4519' )" 46 | "( 2.5.4.11 NAME 'ou' SUP name X-ORIGIN 'RFC 4519' )" 47 | "( 2.5.4.32 NAME 'owner' SUP distinguishedName X-ORIGIN 'RFC 4519' )" 48 | "( 2.5.4.19 NAME 'physicalDeliveryOfficeName' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'RFC 4519' )" 49 | "( 2.5.4.16 NAME 'postalAddress' EQUALITY caseIgnoreListMatch SUBSTR caseIgnoreListSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 X-ORIGIN 'RFC 4519' )" 50 | "( 2.5.4.17 NAME 'postalCode' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'RFC 4519' )" 51 | "( 2.5.4.18 NAME 'postOfficeBox' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'RFC 4519' )" 52 | "( 2.5.4.28 NAME 'preferredDeliveryMethod' SYNTAX 1.3.6.1.4.1.1466.115.121.1.14 SINGLE-VALUE X-ORIGIN 'RFC 4519' )" 53 | "( 2.5.4.26 NAME 'registeredAddress' SUP postalAddress SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 X-ORIGIN 'RFC 4519' )" 54 | "( 2.5.4.33 NAME 'roleOccupant' SUP distinguishedName X-ORIGIN 'RFC 4519' )" 55 | "( 2.5.4.14 NAME 'searchGuide' SYNTAX 1.3.6.1.4.1.1466.115.121.1.25 X-ORIGIN 'RFC 4519' )" 56 | "( 2.5.4.34 NAME 'seeAlso' SUP distinguishedName X-ORIGIN 'RFC 4519' )" 57 | "( 2.5.4.5 NAME 'serialNumber' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'RFC 4519' )" 58 | "( 2.5.4.4 NAME 'sn' SUP name X-ORIGIN 'RFC 4519' )" 59 | "( 2.5.4.8 NAME 'st' SUP name X-ORIGIN 'RFC 4519' )" 60 | "( 2.5.4.9 NAME 'street' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'RFC 4519' )" 61 | "( 2.5.4.20 NAME 'telephoneNumber' EQUALITY telephoneNumberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 X-ORIGIN 'RFC 4519' )" 62 | "( 2.5.4.22 NAME 'teletexTerminalIdentifier' SYNTAX 1.3.6.1.4.1.1466.115.121.1.51 X-ORIGIN 'RFC 4519' )" 63 | "( 2.5.4.21 NAME 'telexNumber' SYNTAX 1.3.6.1.4.1.1466.115.121.1.52 X-ORIGIN 'RFC 4519' )" 64 | "( 2.5.4.12 NAME 'title' SUP name X-ORIGIN 'RFC 4519' )" 65 | "( 0.9.2342.19200300.100.1.1 NAME 'uid' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'RFC 4519' )" 66 | "( 2.5.4.50 NAME 'uniqueMember' EQUALITY uniqueMemberMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.34 X-ORIGIN 'RFC 4519' )" 67 | "( 2.5.4.35 NAME 'userPassword' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'RFC 4519' )" 68 | "( 2.5.4.24 NAME 'x121Address' EQUALITY numericStringMatch SUBSTR numericStringSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 X-ORIGIN 'RFC 4519' )" 69 | "( 2.5.4.45 NAME 'x500UniqueIdentifier' EQUALITY bitStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.6 X-ORIGIN 'RFC 4519' )" 70 | "( 2.16.840.1.113730.3.1.1 NAME 'carLicense' DESC 'vehicle license or registration plate' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'RFC 2798' )" 71 | "( 2.16.840.1.113730.3.1.2 NAME 'departmentNumber' DESC 'identifies a department within an organization' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'RFC 2798' )" 72 | "( 2.16.840.1.113730.3.1.241 NAME 'displayName' DESC 'preferred name of a person to be used when displaying entries' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'RFC 2798' )" 73 | "( 2.16.840.1.113730.3.1.3 NAME 'employeeNumber' DESC '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 X-ORIGIN 'RFC 2798' )" 74 | "( 2.16.840.1.113730.3.1.4 NAME 'employeeType' DESC 'type of employment for a person' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'RFC 2798' )" 75 | "( 0.9.2342.19200300.100.1.60 NAME 'jpegPhoto' DESC 'a JPEG image' SYNTAX 1.3.6.1.4.1.1466.115.121.1.28 X-ORIGIN 'RFC 2798' )" 76 | "( 2.16.840.1.113730.3.1.39 NAME 'preferredLanguage' DESC '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 X-ORIGIN 'RFC 2798' )" 77 | "( 2.16.840.1.113730.3.1.40 NAME 'userSMIMECertificate' DESC 'PKCS#7 SignedData used to support S/MIME' SYNTAX 1.3.6.1.4.1.1466.115.121.1.5 X-ORIGIN 'RFC 2798' )" 78 | "( 2.16.840.1.113730.3.1.216 NAME 'userPKCS12' DESC 'PKCS #12 PFX PDU for exchange of personal identity information' SYNTAX 1.3.6.1.4.1.1466.115.121.1.5 X-ORIGIN 'RFC 2798' )" 79 | "( 2.5.4.36 NAME 'userCertificate' DESC 'X.509 user certificate' EQUALITY certificateExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.8 X-ORIGIN 'RFC 4523' )" 80 | "( 2.5.4.37 NAME 'cACertificate' DESC 'X.509 CA certificate' EQUALITY certificateExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.8 X-ORIGIN 'RFC 4523' )" 81 | "( 2.5.4.40 NAME 'crossCertificatePair' DESC 'X.509 cross certificate pair' EQUALITY certificatePairExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.10 X-ORIGIN 'RFC 4523' )" 82 | "( 2.5.4.39 NAME 'certificateRevocationList' DESC 'X.509 certificate revocation list' EQUALITY certificateListExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.9 X-ORIGIN 'RFC 4523' )" 83 | "( 2.5.4.38 NAME 'authorityRevocationList' DESC 'X.509 authority revocation list' EQUALITY certificateListExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.9 X-ORIGIN 'RFC 4523' )" 84 | "( 2.5.4.53 NAME 'deltaRevocationList' DESC 'X.509 delta revocation list' EQUALITY certificateListExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.9 X-ORIGIN 'RFC 4523' )" 85 | "( 2.5.4.52 NAME 'supportedAlgorithms' DESC 'X.509 supported algorithms' EQUALITY algorithmIdentifierMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.49 X-ORIGIN 'RFC 4523' )" 86 | "( 0.9.2342.19200300.100.1.37 NAME 'associatedDomain' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'RFC 4524' )" 87 | "( 0.9.2342.19200300.100.1.38 NAME 'associatedName' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'RFC 4524' )" 88 | "( 0.9.2342.19200300.100.1.48 NAME 'buildingName' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} X-ORIGIN 'RFC 4524' )" 89 | "( 0.9.2342.19200300.100.1.43 NAME 'co' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'RFC 4524' )" 90 | "( 0.9.2342.19200300.100.1.14 NAME 'documentAuthor' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'RFC 4524' )" 91 | "( 0.9.2342.19200300.100.1.11 NAME 'documentIdentifier' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} X-ORIGIN 'RFC 4524' )" 92 | "( 0.9.2342.19200300.100.1.15 NAME 'documentLocation' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} X-ORIGIN 'RFC 4524' )" 93 | "( 0.9.2342.19200300.100.1.56 NAME 'documentPublisher' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'RFC 4524' )" 94 | "( 0.9.2342.19200300.100.1.12 NAME 'documentTitle' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} X-ORIGIN 'RFC 4524' )" 95 | "( 0.9.2342.19200300.100.1.13 NAME 'documentVersion' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} X-ORIGIN 'RFC 4524' )" 96 | "( 0.9.2342.19200300.100.1.5 NAME 'drink' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} X-ORIGIN 'RFC 4524' )" 97 | "( 0.9.2342.19200300.100.1.20 NAME 'homePhone' EQUALITY telephoneNumberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 X-ORIGIN 'RFC 4524' )" 98 | "( 0.9.2342.19200300.100.1.39 NAME 'homePostalAddress' EQUALITY caseIgnoreListMatch SUBSTR caseIgnoreListSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 X-ORIGIN 'RFC 4524' )" 99 | "( 0.9.2342.19200300.100.1.9 NAME 'host' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} X-ORIGIN 'RFC 4524' )" 100 | "( 0.9.2342.19200300.100.1.4 NAME 'info' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{2048} X-ORIGIN 'RFC 4524' )" 101 | "( 0.9.2342.19200300.100.1.3 NAME 'mail' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} X-ORIGIN 'RFC 4524' )" 102 | "( 0.9.2342.19200300.100.1.10 NAME 'manager' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'RFC 4524' )" 103 | "( 0.9.2342.19200300.100.1.41 NAME 'mobile' EQUALITY telephoneNumberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 X-ORIGIN 'RFC 4524' )" 104 | "( 0.9.2342.19200300.100.1.45 NAME 'organizationalStatus' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} X-ORIGIN 'RFC 4524' )" 105 | "( 0.9.2342.19200300.100.1.42 NAME 'pager' EQUALITY telephoneNumberMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 X-ORIGIN 'RFC 4524' )" 106 | "( 0.9.2342.19200300.100.1.40 NAME 'personalTitle' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} X-ORIGIN 'RFC 4524' )" 107 | "( 0.9.2342.19200300.100.1.6 NAME 'roomNumber' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} X-ORIGIN 'RFC 4524' )" 108 | "( 0.9.2342.19200300.100.1.21 NAME 'secretary' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'RFC 4524' )" 109 | "( 0.9.2342.19200300.100.1.44 NAME 'uniqueIdentifier' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} X-ORIGIN 'RFC 4524' )" 110 | "( 0.9.2342.19200300.100.1.8 NAME 'userClass' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} X-ORIGIN 'RFC 4524' )" 111 | "( 0.9.2342.19200300.100.1.55 NAME 'audio' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40{250000} X-ORIGIN 'RFC 2798' )" 112 | "( 0.9.2342.19200300.100.1.7 NAME 'photo' X-ORIGIN 'RFC 2798' )" 113 | "( 1.3.6.1.4.1.250.1.57 NAME 'labeledURI' EQUALITY caseExactMatch SUBSTR caseExactSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'RFC 2798' )" 114 | "( 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 X-ORIGIN 'RFC 5020' )" 115 | "( 2.16.840.1.113730.3.1.34 NAME 'ref' DESC 'named reference - a labeledURI' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 USAGE distributedOperation X-ORIGIN 'RFC 3296' )" 116 | "( 1.3.6.1.1.4 NAME 'vendorName' EQUALITY 1.3.6.1.4.1.1466.109.114.1 SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE NO-USER-MODIFICATION USAGE dSAOperation X-ORIGIN 'RFC 3045' )" 117 | "( 1.3.6.1.1.5 NAME 'vendorVersion' EQUALITY 1.3.6.1.4.1.1466.109.114.1 SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE NO-USER-MODIFICATION USAGE dSAOperation X-ORIGIN 'RFC 3045' )" 118 | "( 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 X-ORIGIN 'RFC 4530' )" 119 | "( 1.3.6.1.4.1.453.16.2.103 NAME 'numSubordinates' DESC 'count of immediate subordinates' EQUALITY integerMatch ORDERING integerOrderingMatch SYNTAX 1.3.6.1.4.1.453.16.2.103 SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation X-ORIGIN 'draft-ietf-boreham-numsubordinates' )" 120 | "( 1.3.6.1.4.1.7628.5.4.1 NAME 'inheritable' SYNTAX BOOLEAN SINGLE-VALUE NO-USER-MODIFICATION USAGE dSAOperation X-ORIGIN 'draft-ietf-ldup-subentry' )" 121 | "( 1.3.6.1.4.1.7628.5.4.2 NAME 'blockInheritance' SYNTAX BOOLEAN SINGLE-VALUE NO-USER-MODIFICATION USAGE dSAOperation X-ORIGIN 'draft-ietf-ldup-subentry' )" 122 | "( 2.16.840.1.113730.3.1.5 NAME 'changeNumber' DESC 'a number which uniquely identifies a change made to a directory entry' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 EQUALITY integerMatch ORDERING integerOrderingMatch SINGLE-VALUE X-ORIGIN 'draft-good-ldap-changelog' )" 123 | "( 2.16.840.1.113730.3.1.6 NAME 'targetDN' DESC 'the DN of the entry which was modified' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'draft-good-ldap-changelog' )" 124 | "( 2.16.840.1.113730.3.1.7 NAME 'changeType' DESC 'the type of change made to an entry' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'draft-good-ldap-changelog' )" 125 | "( 2.16.840.1.113730.3.1.8 NAME 'changes' DESC 'a set of changes to apply to an entry' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'draft-good-ldap-changelog' )" 126 | "( 2.16.840.1.113730.3.1.9 NAME 'newRDN' DESC 'the new RDN of an entry which is the target of a modrdn operation' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'draft-good-ldap-changelog' )" 127 | "( 2.16.840.1.113730.3.1.10 NAME 'deleteOldRDN' DESC 'a flag which indicates if the old RDN should be retained as an attribute of the entry' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'draft-good-ldap-changelog' )" 128 | "( 2.16.840.1.113730.3.1.11 NAME 'newSuperior' DESC 'the new parent of an entry which is the target of a moddn operation' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'draft-good-ldap-changelog' )" 129 | "( 2.16.840.1.113730.3.1.35 NAME 'changelog' DESC 'the distinguished name of the entry which contains the set of entries comprising the server changelog' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'draft-good-ldap-changelog' )" 130 | "( 1.3.6.1.4.1.4203.1.3.3 NAME 'supportedAuthPasswordSchemes' DESC 'supported password storage schemes' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} USAGE dSAOperation X-ORIGIN 'RFC 3112' )" 131 | "( 1.3.6.1.4.1.4203.1.3.4 NAME 'authPassword' DESC 'password authentication information' EQUALITY 1.3.6.1.4.1.4203.1.2.2 SYNTAX 1.3.6.1.4.1.4203.1.1.2 X-ORIGIN 'RFC 3112' )" 132 | "( 2.16.840.1.113730.3.1.55 NAME 'aci' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 USAGE directoryOperation X-ORIGIN 'De facto standard' )"], 133 | :ldapSyntaxes 134 | ["( 1.3.6.1.4.1.1466.115.121.1.3 DESC 'Attribute Type Description' X-ORIGIN 'RFC 4517' )" 135 | "( 1.3.6.1.4.1.1466.115.121.1.6 DESC 'Bit String' X-ORIGIN 'RFC 4517')" 136 | "( 1.3.6.1.4.1.1466.115.121.1.7 DESC 'Boolean' X-ORIGIN 'RFC 4517')" 137 | "( 1.3.6.1.4.1.1466.115.121.1.11 DESC 'Country String' X-ORIGIN 'RFC 4517')" 138 | "( 1.3.6.1.4.1.1466.115.121.1.14 DESC 'Delivery Method' X-ORIGIN 'RFC 4517')" 139 | "( 1.3.6.1.4.1.1466.115.121.1.15 DESC 'Directory String' X-ORIGIN 'RFC 4517')" 140 | "( 1.3.6.1.4.1.1466.115.121.1.16 DESC 'DIT Content Rule Description' X-ORIGIN 'RFC 4517')" 141 | "( 1.3.6.1.4.1.1466.115.121.1.17 DESC 'DIT Structure Rule Description' X-ORIGIN 'RFC 4517')" 142 | "( 1.3.6.1.4.1.1466.115.121.1.12 DESC 'DN' X-ORIGIN 'RFC 4517')" 143 | "( 1.3.6.1.4.1.1466.115.121.1.21 DESC 'Enhanced Guide' X-ORIGIN 'RFC 4517')" 144 | "( 1.3.6.1.4.1.1466.115.121.1.22 DESC 'Facsimile Telephone Number' X-ORIGIN 'RFC 4517')" 145 | "( 1.3.6.1.4.1.1466.115.121.1.23 DESC 'Fax' X-ORIGIN 'RFC 4517')" 146 | "( 1.3.6.1.4.1.1466.115.121.1.24 DESC 'Generalized Time' X-ORIGIN 'RFC 4517')" 147 | "( 1.3.6.1.4.1.1466.115.121.1.25 DESC 'Guide' X-ORIGIN 'RFC 4517')" 148 | "( 1.3.6.1.4.1.1466.115.121.1.26 DESC 'IA5 String' X-ORIGIN 'RFC 4517')" 149 | "( 1.3.6.1.4.1.1466.115.121.1.27 DESC 'INTEGER' X-ORIGIN 'RFC 4517')" 150 | "( 1.3.6.1.4.1.1466.115.121.1.28 DESC 'JPEG' X-ORIGIN 'RFC 4517')" 151 | "( 1.3.6.1.4.1.1466.115.121.1.54 DESC 'LDAP Syntax Description' X-ORIGIN 'RFC 4517')" 152 | "( 1.3.6.1.4.1.1466.115.121.1.30 DESC 'Matching Rule Description' X-ORIGIN 'RFC 4517')" 153 | "( 1.3.6.1.4.1.1466.115.121.1.31 DESC 'Matching Rule Use Description' X-ORIGIN 'RFC 4517')" 154 | "( 1.3.6.1.4.1.1466.115.121.1.34 DESC 'Name And Optional UID' X-ORIGIN 'RFC 4517')" 155 | "( 1.3.6.1.4.1.1466.115.121.1.35 DESC 'Name Form Description' X-ORIGIN 'RFC 4517')" 156 | "( 1.3.6.1.4.1.1466.115.121.1.36 DESC 'Numeric String' X-ORIGIN 'RFC 4517')" 157 | "( 1.3.6.1.4.1.1466.115.121.1.37 DESC 'Object Class Description' X-ORIGIN 'RFC 4517')" 158 | "( 1.3.6.1.4.1.1466.115.121.1.40 DESC 'Octet String' X-ORIGIN 'RFC 4517')" 159 | "( 1.3.6.1.4.1.1466.115.121.1.38 DESC 'OID' X-ORIGIN 'RFC 4517')" 160 | "( 1.3.6.1.4.1.1466.115.121.1.39 DESC 'Other Mailbox' X-ORIGIN 'RFC 4517')" 161 | "( 1.3.6.1.4.1.1466.115.121.1.41 DESC 'Postal Address' X-ORIGIN 'RFC 4517')" 162 | "( 1.3.6.1.4.1.1466.115.121.1.44 DESC 'Printable String' X-ORIGIN 'RFC 4517')" 163 | "( 1.3.6.1.4.1.1466.115.121.1.58 DESC 'Substring Assertion' X-ORIGIN 'RFC 4517')" 164 | "( 1.3.6.1.4.1.1466.115.121.1.50 DESC 'Telephone Number' X-ORIGIN 'RFC 4517')" 165 | "( 1.3.6.1.4.1.1466.115.121.1.51 DESC 'Teletex Terminal Identifier' X-ORIGIN 'RFC 4517')" 166 | "( 1.3.6.1.4.1.1466.115.121.1.52 DESC 'Telex Number' X-ORIGIN 'RFC 4517')" 167 | "( 1.3.6.1.4.1.1466.115.121.1.53 DESC 'UTC Time' X-ORIGIN 'RFC 4517')"], 168 | :objectClasses 169 | ["( 2.5.6.0 NAME 'top' ABSTRACT MUST objectClass X-ORIGIN 'RFC 4512' )" 170 | "( 2.5.6.1 NAME 'alias' SUP top STRUCTURAL MUST aliasedObjectName X-ORIGIN 'RFC 4512' )" 171 | "( 1.3.6.1.4.1.1466.101.120.111 NAME 'extensibleObject' SUP top AUXILIARY X-ORIGIN 'RFC 4512' )" 172 | "( 2.5.20.1 NAME 'subschema' AUXILIARY MAY ( dITStructureRules $ nameForms $ ditContentRules $ objectClasses $ attributeTypes $ matchingRules $ matchingRuleUse ) X-ORIGIN 'RFC 4512' )" 173 | "( 2.5.6.11 NAME 'applicationProcess' SUP top STRUCTURAL MUST cn MAY ( seeAlso $ ou $ l $ description ) X-ORIGIN 'RFC 4519' )" 174 | "( 2.5.6.2 NAME 'country' SUP top STRUCTURAL MUST c MAY ( searchGuide $ description ) X-ORIGIN 'RFC 4519' )" 175 | "( 1.3.6.1.4.1.1466.344 NAME 'dcObject' SUP top AUXILIARY MUST dc X-ORIGIN 'RFC 4519' )" 176 | "( 2.5.6.14 NAME 'device' SUP top STRUCTURAL MUST cn MAY ( serialNumber $ seeAlso $ owner $ ou $ o $ l $ description ) X-ORIGIN 'RFC 4519' )" 177 | "( 2.5.6.9 NAME 'groupOfNames' SUP top STRUCTURAL MUST cn MAY ( member $ businessCategory $ seeAlso $ owner $ ou $ o $ description ) X-ORIGIN 'RFC 4519' )" 178 | "( 2.5.6.17 NAME 'groupOfUniqueNames' SUP top STRUCTURAL MUST cn MAY ( uniqueMember $ businessCategory $ seeAlso $ owner $ ou $ o $ description ) X-ORIGIN 'RFC 4519' )" 179 | "( 2.5.6.3 NAME 'locality' SUP top STRUCTURAL MAY ( street $ seeAlso $ searchGuide $ st $ l $ description ) X-ORIGIN 'RFC 4519' )" 180 | "( 2.5.6.4 NAME '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 ) X-ORIGIN 'RFC 4519' )" 181 | "( 2.5.6.6 NAME 'person' SUP top STRUCTURAL MUST ( sn $ cn ) MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) X-ORIGIN 'RFC 4519' )" 182 | "( 2.5.6.7 NAME 'organizationalPerson' SUP person STRUCTURAL MAY ( title $ x121Address $ registeredAddress $ destinationIndicator $ preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ telephoneNumber $ internationalISDNNumber $ facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOfficeName $ ou $ st $ l ) X-ORIGIN 'RFC 4519' )" 183 | "( 2.5.6.8 NAME 'organizationalRole' 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 ) X-ORIGIN 'RFC 4519' )" 184 | "( 2.5.6.5 NAME 'organizationalUnit' SUP top STRUCTURAL MUST ou MAY ( businessCategory $ description $ destinationIndicator $ facsimileTelephoneNumber $ internationalISDNNumber $ l $ physicalDeliveryOfficeName $ postalAddress $ postalCode $ postOfficeBox $ preferredDeliveryMethod $ registeredAddress $ searchGuide $ seeAlso $ st $ street $ telephoneNumber $ teletexTerminalIdentifier $ telexNumber $ userPassword $ x121Address ) X-ORIGIN 'RFC 4519' )" 185 | "( 2.5.6.10 NAME 'residentialPerson' SUP person STRUCTURAL MUST l MAY ( businessCategory $ x121Address $ registeredAddress $ destinationIndicator $ preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ telephoneNumber $ internationalISDNNumber $ facsimileTelephoneNumber $ preferredDeliveryMethod $ street $ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOfficeName $ st $ l ) X-ORIGIN 'RFC 4519' )" 186 | "( 1.3.6.1.1.3.1 NAME 'uidObject' SUP top AUXILIARY MUST uid X-ORIGIN 'RFC 4519' )" 187 | "( 2.16.840.1.113730.3.2.2 NAME 'inetOrgPerson' 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 ) X-ORIGIN 'RFC 2798' )" 188 | "( 2.5.6.21 NAME 'pkiUser' DESC 'X.509 PKI User' SUP top AUXILIARY MAY userCertificate X-ORIGIN 'RFC 4523' )" 189 | "( 2.5.6.22 NAME 'pkiCA' DESC 'X.509 PKI Certificate Authority' SUP top AUXILIARY MAY ( cACertificate $ certificateRevocationList $ authorityRevocationList $ crossCertificatePair ) X-ORIGIN 'RFC 4523' )" 190 | "( 2.5.6.19 NAME 'cRLDistributionPoint' DESC 'X.509 CRL distribution point' SUP top STRUCTURAL MUST cn MAY ( certificateRevocationList $ authorityRevocationList $ deltaRevocationList ) X-ORIGIN 'RFC 4523' )" 191 | "( 2.5.6.23 NAME 'deltaCRL' DESC 'X.509 delta CRL' SUP top AUXILIARY MAY deltaRevocationList X-ORIGIN 'RFC 4523' )" 192 | "( 2.5.6.15 NAME 'strongAuthenticationUser' DESC 'X.521 strong authentication user' SUP top AUXILIARY MUST userCertificate X-ORIGIN 'RFC 4523' )" 193 | "( 2.5.6.18 NAME 'userSecurityInformation' DESC 'X.521 user security information' SUP top AUXILIARY MAY ( supportedAlgorithms ) X-ORIGIN 'RFC 4523' )" 194 | "( 2.5.6.16 NAME 'certificationAuthority' DESC 'X.509 certificate authority' SUP top AUXILIARY MUST ( authorityRevocationList $ certificateRevocationList $ cACertificate ) MAY crossCertificatePair X-ORIGIN 'RFC 4523' )" 195 | "( 2.5.6.16.2 NAME 'certificationAuthority-V2' DESC 'X.509 certificate authority, version 2' SUP certificationAuthority AUXILIARY MAY deltaRevocationList X-ORIGIN 'RFC 4523' )" 196 | "( 0.9.2342.19200300.100.4.5 NAME 'account' SUP top STRUCTURAL MUST uid MAY ( description $ seeAlso $ l $ o $ ou $ host ) X-ORIGIN 'RFC 4524' )" 197 | "( 0.9.2342.19200300.100.4.6 NAME 'document' SUP top STRUCTURAL MUST documentIdentifier MAY ( cn $ description $ seeAlso $ l $ o $ ou $ documentTitle $ documentVersion $ documentAuthor $ documentLocation $ documentPublisher ) X-ORIGIN 'RFC 4524' )" 198 | "( 0.9.2342.19200300.100.4.9 NAME 'documentSeries' SUP top STRUCTURAL MUST cn MAY ( description $ l $ o $ ou $ seeAlso $ telephonenumber ) X-ORIGIN 'RFC 4524' )" 199 | "( 0.9.2342.19200300.100.4.13 NAME 'domain' SUP top STRUCTURAL MUST dc MAY ( userPassword $ searchGuide $ seeAlso $ businessCategory $ x121Address $ registeredAddress $ destinationIndicator $ preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ telephoneNumber $ internationaliSDNNumber $ facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $ postalAddress $ physicalDeliveryOfficeName $ st $ l $ description $ o $ associatedName ) X-ORIGIN 'RFC 4524' )" 200 | "( 0.9.2342.19200300.100.4.17 NAME 'domainRelatedObject' SUP top AUXILIARY MUST associatedDomain X-ORIGIN 'RFC 4524' )" 201 | "( 0.9.2342.19200300.100.4.18 NAME 'friendlyCountry' SUP country STRUCTURAL MUST co X-ORIGIN 'RFC 4524' )" 202 | "( 0.9.2342.19200300.100.4.14 NAME 'rFC822localPart' SUP domain STRUCTURAL MAY ( cn $ description $ destinationIndicator $ facsimileTelephoneNumber $ internationaliSDNNumber $ physicalDeliveryOfficeName $ postalAddress $ postalCode $ postOfficeBox $ preferredDeliveryMethod $ registeredAddress $ seeAlso $ sn $ street $ telephoneNumber $ teletexTerminalIdentifier $ telexNumber $ x121Address ) X-ORIGIN 'RFC 4524' )" 203 | "( 0.9.2342.19200300.100.4.7 NAME 'room' SUP top STRUCTURAL MUST cn MAY ( roomNumber $ description $ seeAlso $ telephoneNumber ) X-ORIGIN 'RFC 4524' )" 204 | "( 0.9.2342.19200300.100.4.19 NAME 'simpleSecurityObject' SUP top AUXILIARY MUST userPassword X-ORIGIN 'RFC 4524' )" 205 | "( 2.16.840.1.113730.3.2.6 NAME 'referral' DESC 'named subordinate reference object' STRUCTURAL MUST ref X-ORIGIN 'RFC 3296' )" 206 | "( 1.3.6.1.4.1.5322.13.1.1 NAME 'namedObject' SUP top STRUCTURAL MAY cn X-ORIGIN 'draft-howard-namedobject' )" 207 | "( 2.16.840.1.113719.2.142.6.1.1 NAME 'ldapSubEntry' DESC 'LDAP Subentry class, version 1' SUP top STRUCTURAL MAY ( cn ) X-ORIGIN 'draft-ietf-ldup-subentry' )" 208 | "( 1.3.6.1.4.1.7628.5.6.1.1 NAME 'inheritableLDAPSubEntry' DESC 'Inheritable LDAP Subentry class, version 1' SUP ldapSubEntry STRUCTURAL MUST ( inheritable ) MAY ( blockInheritance ) X-ORIGIN 'draft-ietf-ldup-subentry' )" 209 | "( 2.16.840.1.113730.3.2.1 NAME 'changeLogEntry' SUP top STRUCTURAL MUST ( changeNumber $ targetDN $ changeType ) MAY ( changes $ newRDN $ deleteOldRDN $ newSuperior ) X-ORIGIN 'draft-good-ldap-changelog' )" 210 | "( 1.3.6.1.4.1.4203.1.4.7 NAME 'authPasswordObject' DESC 'authentication password mix in class' AUXILIARY MAY authPassword X-ORIGIN 'RFC 3112' )"], 211 | :dn "cn=schema"}) 212 | --------------------------------------------------------------------------------