Skip to content

Commit d0aaa66

Browse files
HTTP authentication support
1 parent 34c740c commit d0aaa66

File tree

6 files changed

+148
-45
lines changed

6 files changed

+148
-45
lines changed

ChangeLog.md

+10
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
## Changes between Neocons 1.0.0-beta1 and 1.0.0-beta2
22

3+
### HTTP Authentication support
4+
5+
Neocons now supports basic HTTP authentication. Credentials can be passed to `neocons.rest.connect` and
6+
`neocons.rest.connect!` functions as well as via `NEO4J_LOGIN` and `NEO4J_PASSWORD` environment variables
7+
(to be Heroku-friendly).
8+
9+
310
### clj-http upgraded to 0.3.4
411

512
Neocons now uses clj-http 0.3.4.
613

14+
715
### cypher/tableize and cypher/tquery
816

917
New function `cypher/tableize` transforms Cypher query responses (that list columns and row sets separately) into tables,
@@ -20,11 +28,13 @@ much like SQL queries do. The following test demonstrates how it works:
2028
`cypher/tquery` combines `cypher/query` and `cypher/tableize`: it executes Cypher queries and returns results
2129
formatted as table.
2230

31+
2332
### More Efficient nodes/connected-out
2433

2534
`clojurewerkz.neocons.rest.nodes/connected-out` implementation is now based on `nodes/multi-get` and is much more efficient for nodes
2635
with many outgoing relationships.
2736

37+
2838
### nodes/multi-get
2939

3040
`clojurewerkz.neocons.rest.nodes/multi-get` function efficiently (in a single HTTP request) fetches multiple nodes by id.

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ Neocons currently supports the following features (all via REST API, so [you can
1818
* Find shortest path or all paths between nodes
1919
* Predicates over paths, for example, if they include specific nodes/relationships
2020
* [Cypher queries](http://docs.neo4j.org/chunked/1.6/cypher-query-lang.html) (with Neo4J Server 1.6 and later)
21+
* Basic HTTP authentication, including [Heroku Neo4J add-on](https://devcenter.heroku.com/articles/neo4j) compatibility (will be part of the upcoming 1.0.0-beta2 release)
22+
* Efficient multi-get via [Cypher queries](http://docs.neo4j.org/chunked/1.6/cypher-query-lang.html)
23+
* Convenience functions for working with relationships and paths
2124

2225

2326
## Documentation & Examples

project.clj

+12-10
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
11
(defproject clojurewerkz/neocons "1.0.0-SNAPSHOT"
2-
:description "Neocons is an experimental idiomatic Clojure client for the Neo4J REST API"
2+
:description "Neocons is an experimental idiomatic Clojure client for the Neo4J REST API"
33
:license {:name "Eclipse Public License"}
4-
:min-lein-version "2.0.0"
4+
:min-lein-version "2.0.0"
55
:dependencies [[org.clojure/clojure "1.3.0"]
66
[org.clojure/data.json "0.1.2"]
77
[clj-http "0.3.4" :exclude [cheshire]]
88
[clojurewerkz/support "0.1.0-beta2"]]
9-
:test-selectors {:default (fn [v] (not (:time-consuming v)))
10-
:time-consuming (fn [v] (:time-consuming v))
11-
:focus (fn [v] (:focus v))
12-
:indexing (fn [v] (:indexing v))
13-
:cypher (fn [v] (:cypher v))
14-
:all (fn [_] true)}
9+
:test-selectors {:default (fn [m] (and (not (:time-consuming m))
10+
(not (:http-auth m))))
11+
:time-consuming :time-consuming
12+
:focus :focus
13+
:indexing :indexing
14+
:cypher :cypher
15+
:http-auth :http-auth
16+
:all (constantly true)}
1517
:source-paths ["src/clojure"]
1618
:profiles {:1.4 {:dependencies [[org.clojure/clojure "1.4.0-beta5"]]}}
1719
:aliases { "all" ["with-profile" "dev:dev,1.4"] }
1820
:repositories {"clojure-releases" "http://build.clojure.org/releases",
1921
"sonatype" {:url "http://oss.sonatype.org/content/repositories/releases",
20-
:snapshots false,
21-
:releases {:checksum :fail, :update :always}}}
22+
:snapshots false,
23+
:releases {:checksum :fail, :update :always}}}
2224
:java-source-paths ["src/java"]
2325
:warn-on-reflection true)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/sh
2+
3+
export NEO4J_LOGIN=neocons
4+
export NEO4J_PASSWORD=SEcRe7
5+
export TEST_HTTP_AUTHENTICATION=true

src/clojure/clojurewerkz/neocons/rest.clj

+46-35
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,32 @@
99
;; Implementation
1010
;;
1111

12+
(defn- env-var
13+
[^String s]
14+
(get (System/getenv) s))
15+
16+
(def ^{:private true}
17+
http-authentication-options {})
18+
1219
(defn GET
1320
[^String uri & { :as options }]
1421
(io!
15-
(http/get uri (merge options { :accept :json }))))
22+
(http/get uri (merge http-authentication-options options { :accept :json }))))
1623

1724
(defn POST
1825
[^String uri &{ :keys [body] :as options }]
1926
(io!
20-
(http/post uri (merge options { :accept :json :content-type :json :body body }))))
27+
(http/post uri (merge http-authentication-options options { :accept :json :content-type :json :body body }))))
2128

2229
(defn PUT
2330
[^String uri &{ :keys [body] :as options }]
2431
(io!
25-
(http/put uri (merge options { :accept :json :content-type :json :body body }))))
32+
(http/put uri (merge http-authentication-options options { :accept :json :content-type :json :body body }))))
2633

2734
(defn DELETE
2835
[^String uri &{ :keys [body] :as options }]
2936
(io!
30-
(http/delete uri (merge options { :accept :json }))))
37+
(http/delete uri (merge http-authentication-options options { :accept :json }))))
3138

3239

3340

@@ -41,34 +48,38 @@
4148
;; API
4249
;;
4350

44-
(defprotocol Connection
45-
(connect [uri] "Connects to given Neo4J REST API endpoint and performs service discovery")
46-
(connect! [uri] "Connects to given Neo4J REST API endpoint, performs service discovery and mutates *endpoint* state to store it"))
47-
48-
(extend-protocol Connection
49-
URI
50-
(connect [uri]
51-
(connect (.toString uri)))
52-
(connect! [uri]
53-
(connect! (.toString uri)))
54-
55-
String
56-
(connect [uri]
57-
(let [{ :keys [status body] } (GET uri)]
58-
(if (success? status)
59-
(let [payload (json/read-json body true)]
60-
(Neo4JEndpoint. (:neo4j_version payload)
61-
(:node payload)
62-
(str uri (if (.endsWith uri "/")
63-
"relationship"
64-
"/relationship"))
65-
(:node_index payload)
66-
(:relationship_index payload)
67-
(:relationship_types payload)
68-
(:batch payload)
69-
(:extensions_info payload)
70-
(:extensions payload)
71-
(:reference_node payload)
72-
(maybe-append uri "/"))))))
73-
(connect! [uri]
74-
(defonce ^{ :dynamic true } *endpoint* (connect uri))))
51+
52+
53+
(defn connect
54+
"Connects to given Neo4J REST API endpoint and performs service discovery"
55+
([uri]
56+
(let [login (env-var "NEO4J_LOGIN")
57+
password (env-var "NEO4J_PASSWORD")]
58+
(connect uri login password)))
59+
([uri login password]
60+
(let [{ :keys [status body] } (if (and login password)
61+
(GET uri :basic-auth [login password])
62+
(GET uri))]
63+
(if (success? status)
64+
(let [payload (json/read-json body true)]
65+
(alter-var-root (var http-authentication-options) (constantly { :basic-auth [login password] }))
66+
(Neo4JEndpoint. (:neo4j_version payload)
67+
(:node payload)
68+
(str uri (if (.endsWith uri "/")
69+
"relationship"
70+
"/relationship"))
71+
(:node_index payload)
72+
(:relationship_index payload)
73+
(:relationship_types payload)
74+
(:batch payload)
75+
(:extensions_info payload)
76+
(:extensions payload)
77+
(:reference_node payload)
78+
(maybe-append uri "/")))))))
79+
80+
(defn connect!
81+
"Like connect but also mutates *endpoint* state to store the connection"
82+
([uri]
83+
(alter-var-root (var *endpoint*) (fn [_] (connect uri))))
84+
([uri login password]
85+
(alter-var-root (var *endpoint*) (fn [_] (connect uri login password)))))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
(ns clojurewerkz.neocons.rest.test-basic-http-authentication
2+
(:require [clojurewerkz.neocons.rest :as neorest]
3+
[clojurewerkz.neocons.rest.nodes :as nodes]
4+
[slingshot.slingshot :as slingshot])
5+
(:import [slingshot ExceptionInfo])
6+
(:use clojure.test))
7+
8+
;; This group of tests assumes you have Nginx or Apache proxy set up at neo4j-proxy.local
9+
;; that proxies to whatever Neo4J Server installation you want to use. It is excluded from default
10+
;; test selector and thus CI. Run it using
11+
;;
12+
;; TEST_HTTP_AUTHENTICATION=true NEO4J_LOGIN=neocons NEO4J_PASSWORD=SEcRe7 lein2 test :http-auth
13+
;;
14+
;;
15+
;; Example .htpasswd file for Apache or Nginx:
16+
;;
17+
;; neocons:$apr1$u9kPE9lO$FABK3Wu7XHSFwuQiepi3M.
18+
;;
19+
;; (neocons:SEcRe7)
20+
;; you can check your HTTP authentication setup using curl like so:
21+
;;
22+
;; curl --user neocons:SEcRe7 http://neo4j-proxy.local/db/data/
23+
;; curl --user neocons:SEcRe7 http://neo4j-proxy.local/db/data/nodes/1
24+
25+
(when (get (System/getenv) "TEST_HTTP_AUTHENTICATION")
26+
(do
27+
(deftest ^{:http-auth true} test-connection-and-discovery-using-user-info-in-string-uri
28+
(try
29+
(neorest/connect! "http://neocons:incorrec7-pazzwd@neo4j-proxy.local/db/data/")
30+
(catch Exception e
31+
(let [d (.getData e)]
32+
(println d)
33+
(is (= (-> d :object :status) 401))))))
34+
35+
(deftest ^{:http-auth true} test-connection-and-discovery-using-user-info-in-string-uri
36+
(try
37+
(neorest/connect! "http://neocons:SEcRe7@neo4j-proxy.local/db/data/")
38+
(catch Exception e
39+
(let [d (.getData e)]
40+
(println d)
41+
(is (= (-> d :object :status) 401))))))
42+
43+
(let [neo4j-login (get (System/getenv) "NEO4J_LOGIN")
44+
neo4j-password (get (System/getenv) "NEO4J_PASSWORD")]
45+
(when (and neo4j-login neo4j-password)
46+
(deftest ^{:http-auth true} test-connection-and-discovery-with-http-credentials-provided-via-env-variables
47+
(neorest/connect! "http://neo4j-proxy.local/db/data/")
48+
(is (:version neorest/*endpoint*))
49+
(is (:node-uri neorest/*endpoint*))
50+
(is (:batch-uri neorest/*endpoint*))
51+
(is (:relationship-types-uri neorest/*endpoint*)))))
52+
53+
(deftest ^{:http-auth true} test-connection-and-discovery-with-provided-http-credentials
54+
(neorest/connect! "http://neo4j-proxy.local/db/data/" "neocons" "SEcRe7")
55+
(is (:version neorest/*endpoint*))
56+
(is (:node-uri neorest/*endpoint*))
57+
(is (:batch-uri neorest/*endpoint*))
58+
(is (:relationship-types-uri neorest/*endpoint*)))
59+
60+
(neorest/connect! "http://localhost:7474/db/data/" "neocons" "SEcRe7")
61+
62+
(deftest ^{:http-auth true} test-creating-and-immediately-accessing-a-node-without-properties-with-http-auth
63+
(let [created-node (nodes/create)
64+
fetched-node (nodes/get (:id created-node))]
65+
(is (= (:id created-node) (:id fetched-node)))))
66+
67+
(deftest ^{:http-auth true} test-creating-and-immediately-accessing-a-node-with-properties-with-http-auth
68+
(let [data { :key "value" }
69+
created-node (nodes/create data)
70+
fetched-node (nodes/get (:id created-node))]
71+
(is (= (:id created-node) (:id fetched-node)))
72+
(is (= (:data created-node) data))))))

0 commit comments

Comments
 (0)