Permalink
Browse files

HTTP authentication support

  • Loading branch information...
1 parent 34c740c commit d0aaa667225113821730864140721c67ab3fb8a4 @michaelklishin committed Mar 29, 2012
View
@@ -1,9 +1,17 @@
## Changes between Neocons 1.0.0-beta1 and 1.0.0-beta2
+### HTTP Authentication support
+
+Neocons now supports basic HTTP authentication. Credentials can be passed to `neocons.rest.connect` and
+`neocons.rest.connect!` functions as well as via `NEO4J_LOGIN` and `NEO4J_PASSWORD` environment variables
+(to be Heroku-friendly).
+
+
### clj-http upgraded to 0.3.4
Neocons now uses clj-http 0.3.4.
+
### cypher/tableize and cypher/tquery
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:
`cypher/tquery` combines `cypher/query` and `cypher/tableize`: it executes Cypher queries and returns results
formatted as table.
+
### More Efficient nodes/connected-out
`clojurewerkz.neocons.rest.nodes/connected-out` implementation is now based on `nodes/multi-get` and is much more efficient for nodes
with many outgoing relationships.
+
### nodes/multi-get
`clojurewerkz.neocons.rest.nodes/multi-get` function efficiently (in a single HTTP request) fetches multiple nodes by id.
View
@@ -18,6 +18,9 @@ Neocons currently supports the following features (all via REST API, so [you can
* Find shortest path or all paths between nodes
* Predicates over paths, for example, if they include specific nodes/relationships
* [Cypher queries](http://docs.neo4j.org/chunked/1.6/cypher-query-lang.html) (with Neo4J Server 1.6 and later)
+ * 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)
+ * Efficient multi-get via [Cypher queries](http://docs.neo4j.org/chunked/1.6/cypher-query-lang.html)
+ * Convenience functions for working with relationships and paths
## Documentation & Examples
View
@@ -1,23 +1,25 @@
(defproject clojurewerkz/neocons "1.0.0-SNAPSHOT"
- :description "Neocons is an experimental idiomatic Clojure client for the Neo4J REST API"
+ :description "Neocons is an experimental idiomatic Clojure client for the Neo4J REST API"
:license {:name "Eclipse Public License"}
- :min-lein-version "2.0.0"
+ :min-lein-version "2.0.0"
:dependencies [[org.clojure/clojure "1.3.0"]
[org.clojure/data.json "0.1.2"]
[clj-http "0.3.4" :exclude [cheshire]]
[clojurewerkz/support "0.1.0-beta2"]]
- :test-selectors {:default (fn [v] (not (:time-consuming v)))
- :time-consuming (fn [v] (:time-consuming v))
- :focus (fn [v] (:focus v))
- :indexing (fn [v] (:indexing v))
- :cypher (fn [v] (:cypher v))
- :all (fn [_] true)}
+ :test-selectors {:default (fn [m] (and (not (:time-consuming m))
+ (not (:http-auth m))))
+ :time-consuming :time-consuming
+ :focus :focus
+ :indexing :indexing
+ :cypher :cypher
+ :http-auth :http-auth
+ :all (constantly true)}
:source-paths ["src/clojure"]
:profiles {:1.4 {:dependencies [[org.clojure/clojure "1.4.0-beta5"]]}}
:aliases { "all" ["with-profile" "dev:dev,1.4"] }
:repositories {"clojure-releases" "http://build.clojure.org/releases",
"sonatype" {:url "http://oss.sonatype.org/content/repositories/releases",
- :snapshots false,
- :releases {:checksum :fail, :update :always}}}
+ :snapshots false,
+ :releases {:checksum :fail, :update :always}}}
:java-source-paths ["src/java"]
:warn-on-reflection true)
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+export NEO4J_LOGIN=neocons
+export NEO4J_PASSWORD=SEcRe7
+export TEST_HTTP_AUTHENTICATION=true
@@ -9,25 +9,32 @@
;; Implementation
;;
+(defn- env-var
+ [^String s]
+ (get (System/getenv) s))
+
+(def ^{:private true}
+ http-authentication-options {})
+
(defn GET
[^String uri & { :as options }]
(io!
- (http/get uri (merge options { :accept :json }))))
+ (http/get uri (merge http-authentication-options options { :accept :json }))))
(defn POST
[^String uri &{ :keys [body] :as options }]
(io!
- (http/post uri (merge options { :accept :json :content-type :json :body body }))))
+ (http/post uri (merge http-authentication-options options { :accept :json :content-type :json :body body }))))
(defn PUT
[^String uri &{ :keys [body] :as options }]
(io!
- (http/put uri (merge options { :accept :json :content-type :json :body body }))))
+ (http/put uri (merge http-authentication-options options { :accept :json :content-type :json :body body }))))
(defn DELETE
[^String uri &{ :keys [body] :as options }]
(io!
- (http/delete uri (merge options { :accept :json }))))
+ (http/delete uri (merge http-authentication-options options { :accept :json }))))
@@ -41,34 +48,38 @@
;; API
;;
-(defprotocol Connection
- (connect [uri] "Connects to given Neo4J REST API endpoint and performs service discovery")
- (connect! [uri] "Connects to given Neo4J REST API endpoint, performs service discovery and mutates *endpoint* state to store it"))
-
-(extend-protocol Connection
- URI
- (connect [uri]
- (connect (.toString uri)))
- (connect! [uri]
- (connect! (.toString uri)))
-
- String
- (connect [uri]
- (let [{ :keys [status body] } (GET uri)]
- (if (success? status)
- (let [payload (json/read-json body true)]
- (Neo4JEndpoint. (:neo4j_version payload)
- (:node payload)
- (str uri (if (.endsWith uri "/")
- "relationship"
- "/relationship"))
- (:node_index payload)
- (:relationship_index payload)
- (:relationship_types payload)
- (:batch payload)
- (:extensions_info payload)
- (:extensions payload)
- (:reference_node payload)
- (maybe-append uri "/"))))))
- (connect! [uri]
- (defonce ^{ :dynamic true } *endpoint* (connect uri))))
+
+
+(defn connect
+ "Connects to given Neo4J REST API endpoint and performs service discovery"
+ ([uri]
+ (let [login (env-var "NEO4J_LOGIN")
+ password (env-var "NEO4J_PASSWORD")]
+ (connect uri login password)))
+ ([uri login password]
+ (let [{ :keys [status body] } (if (and login password)
+ (GET uri :basic-auth [login password])
+ (GET uri))]
+ (if (success? status)
+ (let [payload (json/read-json body true)]
+ (alter-var-root (var http-authentication-options) (constantly { :basic-auth [login password] }))
+ (Neo4JEndpoint. (:neo4j_version payload)
+ (:node payload)
+ (str uri (if (.endsWith uri "/")
+ "relationship"
+ "/relationship"))
+ (:node_index payload)
+ (:relationship_index payload)
+ (:relationship_types payload)
+ (:batch payload)
+ (:extensions_info payload)
+ (:extensions payload)
+ (:reference_node payload)
+ (maybe-append uri "/")))))))
+
+(defn connect!
+ "Like connect but also mutates *endpoint* state to store the connection"
+ ([uri]
+ (alter-var-root (var *endpoint*) (fn [_] (connect uri))))
+ ([uri login password]
+ (alter-var-root (var *endpoint*) (fn [_] (connect uri login password)))))
@@ -0,0 +1,72 @@
+(ns clojurewerkz.neocons.rest.test-basic-http-authentication
+ (:require [clojurewerkz.neocons.rest :as neorest]
+ [clojurewerkz.neocons.rest.nodes :as nodes]
+ [slingshot.slingshot :as slingshot])
+ (:import [slingshot ExceptionInfo])
+ (:use clojure.test))
+
+;; This group of tests assumes you have Nginx or Apache proxy set up at neo4j-proxy.local
+;; that proxies to whatever Neo4J Server installation you want to use. It is excluded from default
+;; test selector and thus CI. Run it using
+;;
+;; TEST_HTTP_AUTHENTICATION=true NEO4J_LOGIN=neocons NEO4J_PASSWORD=SEcRe7 lein2 test :http-auth
+;;
+;;
+;; Example .htpasswd file for Apache or Nginx:
+;;
+;; neocons:$apr1$u9kPE9lO$FABK3Wu7XHSFwuQiepi3M.
+;;
+;; (neocons:SEcRe7)
+;; you can check your HTTP authentication setup using curl like so:
+;;
+;; curl --user neocons:SEcRe7 http://neo4j-proxy.local/db/data/
+;; curl --user neocons:SEcRe7 http://neo4j-proxy.local/db/data/nodes/1
+
+(when (get (System/getenv) "TEST_HTTP_AUTHENTICATION")
+ (do
+ (deftest ^{:http-auth true} test-connection-and-discovery-using-user-info-in-string-uri
+ (try
+ (neorest/connect! "http://neocons:incorrec7-pazzwd@neo4j-proxy.local/db/data/")
+ (catch Exception e
+ (let [d (.getData e)]
+ (println d)
+ (is (= (-> d :object :status) 401))))))
+
+ (deftest ^{:http-auth true} test-connection-and-discovery-using-user-info-in-string-uri
+ (try
+ (neorest/connect! "http://neocons:SEcRe7@neo4j-proxy.local/db/data/")
+ (catch Exception e
+ (let [d (.getData e)]
+ (println d)
+ (is (= (-> d :object :status) 401))))))
+
+ (let [neo4j-login (get (System/getenv) "NEO4J_LOGIN")
+ neo4j-password (get (System/getenv) "NEO4J_PASSWORD")]
+ (when (and neo4j-login neo4j-password)
+ (deftest ^{:http-auth true} test-connection-and-discovery-with-http-credentials-provided-via-env-variables
+ (neorest/connect! "http://neo4j-proxy.local/db/data/")
+ (is (:version neorest/*endpoint*))
+ (is (:node-uri neorest/*endpoint*))
+ (is (:batch-uri neorest/*endpoint*))
+ (is (:relationship-types-uri neorest/*endpoint*)))))
+
+ (deftest ^{:http-auth true} test-connection-and-discovery-with-provided-http-credentials
+ (neorest/connect! "http://neo4j-proxy.local/db/data/" "neocons" "SEcRe7")
+ (is (:version neorest/*endpoint*))
+ (is (:node-uri neorest/*endpoint*))
+ (is (:batch-uri neorest/*endpoint*))
+ (is (:relationship-types-uri neorest/*endpoint*)))
+
+ (neorest/connect! "http://localhost:7474/db/data/" "neocons" "SEcRe7")
+
+ (deftest ^{:http-auth true} test-creating-and-immediately-accessing-a-node-without-properties-with-http-auth
+ (let [created-node (nodes/create)
+ fetched-node (nodes/get (:id created-node))]
+ (is (= (:id created-node) (:id fetched-node)))))
+
+ (deftest ^{:http-auth true} test-creating-and-immediately-accessing-a-node-with-properties-with-http-auth
+ (let [data { :key "value" }
+ created-node (nodes/create data)
+ fetched-node (nodes/get (:id created-node))]
+ (is (= (:id created-node) (:id fetched-node)))
+ (is (= (:data created-node) data))))))

0 comments on commit d0aaa66

Please sign in to comment.