diff --git a/examples/notes.clj b/examples/notes.clj index 6634ce37..afa3bfe8 100755 --- a/examples/notes.clj +++ b/examples/notes.clj @@ -5,7 +5,11 @@ '[clojure.java.shell :refer [sh]] '[clojure.string :as str]) -(def debug? false) +(def debug? true) +(def user "admin") +(def password "admin") +(def base64 (-> (.getEncoder java.util.Base64) + (.encodeToString (.getBytes (str user ":" password))))) (def notes-file (io/file (System/getProperty "user.home") ".notes" "notes.txt")) (io/make-parents notes-file) @@ -36,20 +40,70 @@ (str/join " " (map html v)) :else (str v))) +(defn write-response [out session-id status headers content] + (let [cookie-header (str "Set-Cookie: notes-id=" session-id) + headers (str/join "\r\n" (conj headers cookie-header)) + response (str "HTTP/1.1 " status "\r\n" + (str headers "\r\n") + "Content-Length: " (if content (count content) + 0) + "\r\n\r\n" + (when content + (str content)))] + (when debug? (println response)) + (binding [*out* out] + (print response) + (flush)))) + ;; the home page -(defn home [] - (str - "\n" - (html - [:html - [:head - [:title "Notes"]] - [:body - [:h1 "Notes"] - [:pre (slurp notes-file)] - [:form {:action "/" :method "post"} - [:input {:type "text" :name "note"}] - [:input {:type "submit" :value "Submit"}]]]]))) +(defn home-response [out session-id] + (let [body (str + "\n" + (html + [:html + [:head + [:title "Notes"]] + [:body + [:h1 "Notes"] + [:pre (slurp notes-file)] + [:form {:action "/" :method "post"} + [:input {:type "text" :name "note"}] + [:input {:type "submit" :value "Submit"}]]]]))] + (write-response out session-id "200 OK" nil body))) + +(defn basic-auth-response [out session-id] + (write-response out session-id + "401 Unauthorized" + ["WWW-Authenticate: Basic realm=\"notes\""] + nil)) + +(def known-sessions + (atom #{})) + +(defn new-session! [] + (let [uuid (str (java.util.UUID/randomUUID))] + (swap! known-sessions conj uuid) + uuid)) + +(defn get-session-id [headers] + (if-let [cookie-header (first (filter #(str/starts-with? % "Cookie: ") headers))] + (let [parts (str/split cookie-header #"; ")] + (if-let [notes-id (first (filter #(str/starts-with? % "notes-id") parts))] + (str/replace notes-id "notes-id=" "") + (new-session!))) + (new-session!))) + +(defn basic-auth-header [headers] + (some #(str/starts-with? % "Basic-Auth: ") headers)) + +(def authenticated-sessions + (atom #{})) + +(defn authenticate! [session-id headers] + (or (contains? @authenticated-sessions session-id) + (when (some #(= % (str "Authorization: Basic " base64)) headers) + (swap! authenticated-sessions conj session-id) + true))) ;; run the server (with-open [server-socket (let [s (new ServerSocket 8080)] @@ -60,28 +114,32 @@ (let [out (io/writer (.getOutputStream client-socket)) is (.getInputStream client-socket) in (io/reader is) - response (loop [headers []] - (let [line (.readLine in)] - (if (str/blank? line) - headers - (recur (conj headers line))))) - data (let [sb (StringBuilder.)] - (loop [] - (when (.ready in) - (.append sb (char (.read in))) - (recur))) - (-> (str sb) - (java.net.URLDecoder/decode))) + [_req & headers :as response] + (loop [headers []] + (let [line (.readLine in)] + (if (str/blank? line) + headers + (recur (conj headers line))))) + session-id (get-session-id headers) + form-data (let [sb (StringBuilder.)] + (loop [] + (when (.ready in) + (.append sb (char (.read in))) + (recur))) + (-> (str sb) + (java.net.URLDecoder/decode))) _ (when debug? (println (str/join "\n" response))) - _ (when-not (str/blank? data) - (when debug? (println data)) - (let [note (str/replace data "note=" "")] + _ (when-not (str/blank? form-data) + (when debug? (println form-data)) + (let [note (str/replace form-data "note=" "")] (spit notes-file (str note "\n") :append true))) - _ (when debug? (println)) - body (home)] - (.write out (format "HTTP/1.1 %s OK\r\nContent-Length: %s\r\n\r\n%s" - 200 - (count body) - body)) - (.flush out)) + _ (when debug? (println))] + (cond + ;; if we didn't see this session before, we want the user to re-authenticate + (not (contains? @known-sessions session-id)) + (let [uuid (new-session!)] + (basic-auth-response out uuid)) + (not (authenticate! session-id headers)) + (basic-auth-response out session-id) + :else (home-response out session-id))) (recur)))