Permalink
Browse files

Added search

  • Loading branch information...
1 parent a201fa6 commit d55711fdcaa0854c35db7bf178646aeac309a609 @alienscience committed Feb 9, 2011
Showing with 184 additions and 6 deletions.
  1. +42 −1 README.md
  2. +121 −4 src/clj_ldap/client.clj
  3. +21 −1 test/clj_ldap/test/client.clj
View
@@ -95,7 +95,48 @@ All the keys in the map are optional e.g:
(ldap/modify conn "cn=dude,ou=people,dc=example,dc=com"
{:add {:telephoneNumber "232546265"}})
-
+
+## search [connection base] [connection base options]
+
+Runs a search on the connected ldap server, reads all the results into
+memory and returns the results as a sequence of maps.
+
+Options is a map with the following optional entries:
+ :scope The search scope, can be :base :one or :sub,
+ defaults to :sub
+ :filter A string describing the search filter,
+ defaults to "(objectclass=*)"
+ :attributes A collection of the attributes to return,
+ defaults to all user attributes
+e.g
+ (ldap/search conn "ou=people,dc=example,dc=com")
+
+ (ldap/search conn "ou=people,dc=example,dc=com" {:attributes [:cn]})
+
+## search! [connection base f] [connection base options f]
+
+Runs a search on the connected ldap server and executes the given
+function (for side effects) on each result. Does not read all the
+results into memory.
+
+Options is a map with the following optional entries:
+ :scope The search scope, can be :base :one or :sub,
+ defaults to :sub
+ :filter A string describing the search filter,
+ defaults to "(objectclass=*)"
+ :attributes A collection of the attributes to return,
+ defaults to all user attributes
+ :queue-size The size of the internal queue used to store results before
+ they passed to the function, the default is 100
+
+e.g
+ (ldap/search! conn "ou=people,dc=example,dc=com" println)
+
+ (ldap/search! conn "ou=people,dc=example,dc=com"
+ {:filter "sn=dud*"}
+ (fn [x]
+ (println "Hello " (:cn x))))
+
## delete [connection dn]
Deletes the entry with the given DN on the connected ldap server.
View
@@ -17,7 +17,11 @@
Modification
DeleteRequest
SimpleBindRequest
- RoundRobinServerSet])
+ RoundRobinServerSet
+ SearchRequest
+ LDAPEntrySource
+ EntrySourceException
+ SearchScope])
(:import [com.unboundid.util.ssl
SSLUtil
TrustAllTrustManager
@@ -124,6 +128,12 @@
(> (.size attr) 1) [k (vec (.getValues attr))]
:else [k (.getValue attr)])))
+(defn- entry-as-map
+ "Converts an Entry object into a map"
+ [entry]
+ (let [attrs (seq (.getAttributes entry))]
+ (apply hash-map
+ (mapcat extract-attribute attrs))))
(defn- set-entry-kv!
"Sets the given key/value pair in the given entry object"
@@ -165,6 +175,77 @@
(modifications :replace))]
(ModifyRequest. dn (into-array (concat adds deletes replacements)))))
+
+(defn- next-entry
+ "Attempts to get the next entry from an LDAPEntrySource object"
+ [source]
+ (try
+ (.nextEntry source)
+ (catch EntrySourceException e
+ (if (.mayContinueReading e)
+ (.nextEntry source)
+ (throw e)))))
+
+(defn- entry-seq
+ "Returns a lazy sequence of entries from an LDAPEntrySource object"
+ [source]
+ (if-let [n (.nextEntry source)]
+ (cons n (lazy-seq (entry-seq source)))))
+
+(defn- search-results
+ "Returns a sequence of search results for the given search criteria."
+ [connection {:keys [base scope filter attributes]}]
+ (let [res (.search connection base scope filter attributes)]
+ (if (> (.getEntryCount res) 0)
+ (remove empty?
+ (map entry-as-map (.getSearchEntries res))))))
+
+(defn- search-results!
+ "Call the given function with the results of the search using
+ the given search criteria"
+ [pool {:keys [base scope filter attributes]} queue-size f]
+ (let [request (SearchRequest. base scope filter attributes)
+ conn (.getConnection pool)]
+ (try
+ (with-open [source (LDAPEntrySource. conn request false)]
+ (doseq [i (remove empty?
+ (map entry-as-map (entry-seq source)))]
+ (f i)))
+ (.releaseConnection pool conn)
+ (catch EntrySourceException e
+ (.releaseDefunctConnection pool conn)
+ (throw e)))))
+
+
+(defn- get-scope
+ "Converts a keyword into a SearchScope object"
+ [k]
+ (condp = k
+ :base SearchScope/BASE
+ :one SearchScope/ONE
+ SearchScope/SUB))
+
+(defn- get-attributes
+ "Converts a collection of attributes into an array"
+ [attrs]
+ (cond
+ (or (nil? attrs)
+ (empty? attrs)) (into-array java.lang.String
+ [SearchRequest/ALL_USER_ATTRIBUTES])
+ :else (into-array java.lang.String
+ (map name attrs))))
+
+(defn- search-criteria
+ "Returns a map of search criteria from the given base and options"
+ [base options]
+ (let [scope (get-scope (:scope options))
+ filter (or (:filter options) "(objectclass=*)")
+ attributes (get-attributes (:attributes options))]
+ {:base base
+ :scope scope
+ :filter filter
+ :attributes attributes}))
+
;;=========== API ==============================================================
(defn connect
@@ -200,9 +281,7 @@
Returns nil if the entry doesn't exist or cannot be read."
[connection dn]
(if-let [result (.getEntry connection dn)]
- (let [attrs (seq (.getAttributes result))]
- (apply hash-map
- (mapcat extract-attribute attrs)))))
+ (entry-as-map result)))
(defn add
"Adds an entry to the connected ldap server. The entry is assumed to be
@@ -241,4 +320,42 @@
(.delete connection delete-obj))))
+(defn search
+ "Runs a search on the connected ldap server, reads all the results into
+ memory and returns the results as a sequence of maps.
+
+ Options is a map with the following optional entries:
+ :scope The search scope, can be :base :one or :sub,
+ defaults to :sub
+ :filter A string describing the search filter,
+ defaults to \"(objectclass=*)\"
+ :attributes A collection of the attributes to return,
+ defaults to all user attributes"
+ ([connection base]
+ (search connection base nil))
+ ([connection base options]
+ (search-results connection (search-criteria base options))))
+
+(defn search!
+ "Runs a search on the connected ldap server and executes the given
+ function (for side effects) on each result. Does not read all the
+ results into memory.
+
+ Options is a map with the following optional entries:
+ :scope The search scope, can be :base :one or :sub,
+ defaults to :sub
+ :filter A string describing the search filter,
+ defaults to \"(objectclass=*)\"
+ :attributes A collection of the attributes to return,
+ defaults to all user attributes
+ :queue-size The size of the internal queue used to store results before
+ they passed to the function, the default is 100"
+ ([connection base f]
+ (search! connection base nil f))
+ ([connection base options f]
+ (let [queue-size (or (:queue-size options) 100)]
+ (search-results! connection
+ (search-criteria base options)
+ queue-size
+ f))))
@@ -14,9 +14,13 @@
(def *conn* nil)
;; Tests concentrate on a single object class
-(def dn* "cn=%s,ou=people,dc=alienscience,dc=org,dc=uk")
+(def base* "ou=people,dc=alienscience,dc=org,dc=uk")
+(def dn* (str "cn=%s," base*))
(def object-class* #{"top" "person"})
+;; Variable to catch side effects
+(def *side-effects* nil)
+
;; Result of a successful write
(def success* [0 "success"])
@@ -167,3 +171,19 @@
(set [(first b-phonenums) "0000000005"])))
(is (= (:description new-b) "desc x")))))
+
+(deftest test-search
+ (is (= (set (ldap/search *conn* base*
+ {:attributes [:cn]}))
+ (set [{:cn "testa"} {:cn "testb"} {:cn "Saul Hazledine"}])))
+ (is (= (set (ldap/search *conn* base*
+ {:attributes [:cn] :filter "cn=test*"}))
+ (set [{:cn "testa"} {:cn "testb"}])))
+ (binding [*side-effects* #{}]
+ (ldap/search! *conn* base* {:attributes [:cn :sn] :filter "cn=test*"}
+ (fn [x]
+ (set! *side-effects*
+ (conj *side-effects* x))))
+ (is (= *side-effects*
+ (set [{:cn "testa" :sn "a"}
+ {:cn "testb" :sn "b"}])))))

0 comments on commit d55711f

Please sign in to comment.