From a3314aeff7c0fbc4d3661d0c0f75e0c60e8c89ac Mon Sep 17 00:00:00 2001
From: Cam Saul <cam@geotip.com>
Date: Mon, 6 Jul 2015 20:09:47 -0700
Subject: [PATCH] Strip DB details from API calls for non-admins

---
 src/metabase/api/routes.clj            |  13 +-
 src/metabase/core.clj                  |  52 +++----
 src/metabase/middleware/auth.clj       |  88 ++++++------
 src/metabase/middleware/format.clj     |  12 +-
 src/metabase/models/common.clj         |  10 +-
 src/metabase/models/database.clj       |  43 +++---
 src/metabase/models/interface.clj      |  14 ++
 test/metabase/api/meta/db_test.clj     |  30 ++--
 test/metabase/api/meta/field_test.clj  |   1 -
 test/metabase/api/meta/table_test.clj  |   7 +-
 test/metabase/middleware/auth_test.clj | 184 +++++++++++--------------
 test/metabase/task_test.clj            |   8 +-
 12 files changed, 229 insertions(+), 233 deletions(-)
 create mode 100644 src/metabase/models/interface.clj

diff --git a/src/metabase/api/routes.clj b/src/metabase/api/routes.clj
index 470f277d078..51413b17f15 100644
--- a/src/metabase/api/routes.clj
+++ b/src/metabase/api/routes.clj
@@ -15,18 +15,13 @@
                                [table :as table])
             [metabase.middleware.auth :as auth]))
 
-(defn- +apikey
+(def ^:private +apikey
   "Wrap API-ROUTES so they may only be accessed with proper apikey credentials."
-  [api-routes]
-  (-> api-routes
-      auth/enforce-apikey))
+  auth/enforce-api-key)
 
-(defn- +auth
+(def ^:private +auth
   "Wrap API-ROUTES so they may only be accessed with proper authentiaction credentials."
-  [api-routes]
-  (-> api-routes
-      auth/bind-current-user
-      auth/enforce-authentication))
+  auth/enforce-authentication)
 
 (defroutes routes
   (context "/card"         [] (+auth card/routes))
diff --git a/src/metabase/core.clj b/src/metabase/core.clj
index 2e269bcd1f7..5584404dcff 100644
--- a/src/metabase/core.clj
+++ b/src/metabase/core.clj
@@ -1,19 +1,9 @@
+;; -*- comment-column: 35; -*-
 (ns metabase.core
   (:gen-class)
   (:require [clojure.tools.logging :as log]
             [clojure.java.browse :refer [browse-url]]
             [colorize.core :as color]
-            [medley.core :as medley]
-            [metabase.config :as config]
-            [metabase.db :as db]
-            (metabase.middleware [auth :as auth]
-                                 [log-api-call :refer :all]
-                                 [format :refer :all])
-            [metabase.models.setting :refer [defsetting]]
-            [metabase.models.user :refer [User]]
-            [metabase.routes :as routes]
-            [metabase.setup :as setup]
-            [metabase.task :as task]
             [ring.adapter.jetty :as ring-jetty]
             (ring.middleware [cookies :refer [wrap-cookies]]
                              [gzip :refer [wrap-gzip]]
@@ -21,7 +11,18 @@
                                            wrap-json-body]]
                              [keyword-params :refer [wrap-keyword-params]]
                              [params :refer [wrap-params]]
-                             [session :refer [wrap-session]])))
+                             [session :refer [wrap-session]])
+            [medley.core :as medley]
+            (metabase [config :as config]
+                      [db :as db]
+                      [routes :as routes]
+                      [setup :as setup]
+                      [task :as task])
+            (metabase.middleware [auth :as auth]
+                                 [log-api-call :refer :all]
+                                 [format :refer :all])
+            (metabase.models [setting :refer [defsetting]]
+                             [user :refer [User]])))
 
 ;; ## CONFIG
 
@@ -32,17 +33,19 @@
   "The primary entry point to the HTTP server"
   (-> routes/routes
       (log-api-call :request :response)
-      format-response         ; [METABASE] Do formatting before converting to JSON so serializer doesn't barf
-      (wrap-json-body         ; extracts json POST body and makes it avaliable on request
+      format-response              ; [METABASE] Do formatting before converting to JSON so serializer doesn't barf
+      (wrap-json-body              ; extracts json POST body and makes it avaliable on request
         {:keywords? true})
-      wrap-json-response      ; middleware to automatically serialize suitable objects as JSON in responses
-      wrap-keyword-params     ; converts string keys in :params to keyword keys
-      wrap-params             ; parses GET and POST params as :query-params/:form-params and both as :params
-      auth/wrap-apikey        ; looks for a Metabase API Key on the request and assocs as :metabase-apikey
-      auth/wrap-sessionid     ; looks for a Metabase sessionid and assocs as :metabase-sessionid
-      wrap-cookies            ; Parses cookies in the request map and assocs as :cookies
-      wrap-session            ; reads in current HTTP session and sets :session/key
-      wrap-gzip))             ; GZIP response if client can handle it
+      wrap-json-response           ; middleware to automatically serialize suitable objects as JSON in responses
+      wrap-keyword-params          ; converts string keys in :params to keyword keys
+      wrap-params                  ; parses GET and POST params as :query-params/:form-params and both as :params
+      auth/bind-current-user       ; Binds *current-user* and *current-user-id* if :metabase-user-id is non-nil
+      auth/wrap-current-user-id    ; looks for :metabase-session-id and sets :metabase-user-id if Session ID is valid
+      auth/wrap-api-key            ; looks for a Metabase API Key on the request and assocs as :metabase-api-key
+      auth/wrap-session-id         ; looks for a Metabase Session ID and assoc as :metabase-session-id
+      wrap-cookies                 ; Parses cookies in the request map and assocs as :cookies
+      wrap-session                 ; reads in current HTTP session and sets :session/key
+      wrap-gzip))                  ; GZIP response if client can handle it
 
 (defn- -init-create-setup-token
   "Create and set a new setup token, and open the setup URL on the user's system."
@@ -57,10 +60,7 @@
                          setup-token)]
     (log/info (color/green "Please use the following url to setup your Metabase installation:\n\n"
                            setup-url
-                           "\n\n"))
-    ;; Attempt to browse URL on user's system; this will just fail silently if we can't do it
-    ;(browse-url setup-url)
-    ))
+                           "\n\n"))))
 
 
 (defn init
diff --git a/src/metabase/middleware/auth.clj b/src/metabase/middleware/auth.clj
index 080a10fc3ee..441ec9a16b5 100644
--- a/src/metabase/middleware/auth.clj
+++ b/src/metabase/middleware/auth.clj
@@ -1,6 +1,6 @@
 (ns metabase.middleware.auth
   "Middleware for dealing with authentication and session management."
-  (:require [korma.core :as korma]
+  (:require [korma.core :as k]
             [metabase.config :as config]
             [metabase.db :refer [sel]]
             [metabase.api.common :refer [*current-user* *current-user-id*]]
@@ -8,16 +8,16 @@
                              [user :refer [User current-user-fields]])))
 
 
-(def metabase-session-cookie "metabase.SESSION_ID")
-(def metabase-session-header "x-metabase-session")
-(def metabase-apikey-header "x-metabase-apikey")
+(def ^:const metabase-session-cookie "metabase.SESSION_ID")
+(def ^:const metabase-session-header "x-metabase-session")
+(def ^:const metabase-api-key-header "x-metabase-apikey")
 
-(def response-unauthentic {:status 401 :body "Unauthenticated"})
-(def response-forbidden {:status 403 :body "Forbidden"})
+(def ^:const response-unauthentic {:status 401 :body "Unauthenticated"})
+(def ^:const response-forbidden {:status 403 :body "Forbidden"})
 
 
-(defn wrap-sessionid
-  "Middleware that sets the :metabase-sessionid keyword on the request if a session id can be found.
+(defn wrap-session-id
+  "Middleware that sets the `:metabase-session-id` keyword on the request if a session id can be found.
 
    We first check the request :cookies for `metabase.SESSION_ID`, then if no cookie is found we look in the
    http headers for `X-METABASE-SESSION`.  If neither is found then then no keyword is bound to the request."
@@ -25,32 +25,34 @@
   (fn [{:keys [cookies headers] :as request}]
     (if-let [session-id (or (get-in cookies [metabase-session-cookie :value]) (headers metabase-session-header))]
       ;; alternatively we could always associate the keyword and just let it be nil if there is no value
-      (handler (assoc request :metabase-sessionid session-id))
+      (handler (assoc request :metabase-session-id session-id))
       (handler request))))
 
+(defn wrap-current-user-id
+  "Add `:metabase-user-id` to the request if a valid session token was passed."
+  [handler]
+  (fn [{:keys [metabase-session-id] :as request}]
+    ;; TODO - what kind of validations can we do on the sessionid to make sure it's safe to handle?  str?  alphanumeric?
+    (handler (or (when metabase-session-id
+                   (when-let [session (first (k/select Session
+                                                       ;; NOTE: we join with the User table and ensure user.is_active = true
+                                                       (k/with User (k/where {:is_active true}))
+                                                       (k/fields :created_at :user_id)
+                                                       (k/where {:id metabase-session-id})))]
+                     (let [session-age-ms (- (System/currentTimeMillis) (.getTime ^java.util.Date (get session :created_at (java.util.Date. 0))))]
+                       ;; If the session exists and is not expired (max-session-age > session-age) then validation is good
+                       (when (and session (> (config/config-int :max-session-age) (quot session-age-ms 60000)))
+                         (assoc request :metabase-user-id (:user_id session))))))
+                 request))))
 
-(defn enforce-authentication
-  "Middleware that enforces authentication of the client, cancelling the request processing if auth fails.
-
-   Authentication is determined by validating the :metabase-sessionid on the request against the db session list.
-   If the session is valid then we associate a :metabase-userid on the request and carry on, but if the validation
-   fails then we return an HTTP 401 response indicating that the client is not authentic.
 
-   NOTE: we are purposely not associating the full current user object here so that we can be modular."
+(defn enforce-authentication
+  "Middleware that returns a 401 response if REQUEST has no associated `:metabase-user-id`."
   [handler]
-  (fn [{:keys [metabase-sessionid] :as request}]
-    ;; TODO - what kind of validations can we do on the sessionid to make sure it's safe to handle?  str?  alphanumeric?
-    (let [session (first (korma/select Session
-                           ;; NOTE: we join with the User table and ensure user.is_active = true
-                           (korma/with User (korma/where {:is_active true}))
-                           (korma/fields :created_at :user_id)
-                           (korma/where {:id metabase-sessionid})))
-          session-age-ms (- (System/currentTimeMillis) (.getTime ^java.util.Date (get session :created_at (java.util.Date. 0))))]
-      ;; If the session exists and is not expired (max-session-age > session-age) then validation is good
-      (if (and session (> (config/config-int :max-session-age) (quot session-age-ms 60000)))
-        (handler (assoc request :metabase-userid (:user_id session)))
-        ;; default response is 401
-        response-unauthentic))))
+  (fn [{:keys [metabase-user-id] :as request}]
+    (if metabase-user-id
+      (handler request)
+      response-unauthentic)))
 
 
 (defmacro sel-current-user [current-user-id]
@@ -65,35 +67,35 @@
    *current-user*    delay that returns current user (or nil) from DB"
   [handler]
   (fn [request]
-    (let [current-user-id (:metabase-userid request)]
+    (if-let [current-user-id (:metabase-user-id request)]
       (binding [*current-user-id* current-user-id
-                *current-user* (if-not current-user-id (atom nil)
-                                                       (delay (sel-current-user current-user-id)))]
-        (handler request)))))
+                *current-user*    (delay (sel-current-user current-user-id))]
+        (handler request))
+      (handler request))))
 
 
-(defn wrap-apikey
-  "Middleware that sets the :metabase-apikey keyword on the request if a valid API Key can be found.
+(defn wrap-api-key
+  "Middleware that sets the :metabase-api-key keyword on the request if a valid API Key can be found.
 
-   We check the request headers for `X-METABASE-APIKEY` and if it's not found then then no keyword is bound to the request."
+   We check the request headers for `X-METABASE-API-KEY` and if it's not found then then no keyword is bound to the request."
   [handler]
   (fn [{:keys [headers] :as request}]
-    (if-let [api-key (headers metabase-apikey-header)]
-      (handler (assoc request :metabase-apikey api-key))
+    (if-let [api-key (headers metabase-api-key-header)]
+      (handler (assoc request :metabase-api-key api-key))
       (handler request))))
 
 
-(defn enforce-apikey
+(defn enforce-api-key
   "Middleware that enforces validation of the client via API Key, cancelling the request processing if the check fails.
 
-   Validation is handled by first checking for the presence of the :metabase-apikey on the request.  If the api key
+   Validation is handled by first checking for the presence of the :metabase-api-key on the request.  If the api key
    is available then we validate it by checking it against the configured :mb-api-key value set in our global config.
 
-   If the request :metabase-apikey matches the configured :mb-api-key value then the request continues, otherwise we
+   If the request :metabase-api-key matches the configured :mb-api-key value then the request continues, otherwise we
    reject the request and return a 403 Forbidden response."
   [handler]
-  (fn [{:keys [metabase-apikey] :as request}]
-    (if (= (config/config-str :mb-api-key) metabase-apikey)
+  (fn [{:keys [metabase-api-key] :as request}]
+    (if (= (config/config-str :mb-api-key) metabase-api-key)
       (handler request)
       ;; default response is 403
       response-forbidden)))
diff --git a/src/metabase/middleware/format.clj b/src/metabase/middleware/format.clj
index f6a372d76a1..673c11ad00a 100644
--- a/src/metabase/middleware/format.clj
+++ b/src/metabase/middleware/format.clj
@@ -3,6 +3,7 @@
             (cheshire factory
                       [generate :refer [add-encoder encode-str]])
             [medley.core :refer [filter-vals map-vals]]
+            [metabase.models.interface :refer [api-serialize]]
             [metabase.util :as util]))
 
 (declare -format-response)
@@ -45,11 +46,14 @@
   [m]
   (filter-vals #(not (or (delay? %)
                          (fn? %)))
-               m))
+               ;; Convert typed maps such as metabase.models.database/DatabaseInstance to plain maps because empty, which is used internally by filter-vals,
+               ;; will fail otherwise
+               (into {} m)))
 
 (defn- -format-response [obj]
   (cond
-    (map? obj)  (->> (remove-fns-and-delays obj)   ; recurse over all vals in the map
-                     (map-vals -format-response))
-    (coll? obj) (map -format-response obj)        ; recurse over all items in the collection
+    (map? obj)  (->> (api-serialize obj)
+                     remove-fns-and-delays
+                     (map-vals -format-response)) ; recurse over all vals in the map
+    (coll? obj) (map -format-response obj) ; recurse over all items in the collection
     :else       obj))
diff --git a/src/metabase/models/common.clj b/src/metabase/models/common.clj
index 50a16eddf06..adb7a0a0980 100644
--- a/src/metabase/models/common.clj
+++ b/src/metabase/models/common.clj
@@ -3,7 +3,7 @@
             [metabase.api.common :refer [*current-user* *current-user-id* check]]
             [metabase.util :as u]))
 
-(def timezones
+(def ^:const timezones
   ["GMT"
    "UTC"
    "US/Alaska"
@@ -17,11 +17,11 @@
 
 ;;; ALLEN'S PERMISSIONS IMPLEMENTATION
 
-(def perms-none 0)
-(def perms-read 1)
-(def perms-readwrite 2)
+(def ^:const perms-none 0)
+(def ^:const perms-read 1)
+(def ^:const perms-readwrite 2)
 
-(def permissions
+(def ^:const permissions
   [{:id perms-none :name "None"},
    {:id perms-read :name "Read Only"},
    {:id perms-readwrite :name "Read & Write"}])
diff --git a/src/metabase/models/database.clj b/src/metabase/models/database.clj
index 680d57ed30f..bd43d73ee43 100644
--- a/src/metabase/models/database.clj
+++ b/src/metabase/models/database.clj
@@ -2,7 +2,8 @@
   (:require [korma.core :refer :all]
             [metabase.api.common :refer [*current-user*]]
             [metabase.db :refer :all]
-            [metabase.models.common :refer [assoc-permissions-sets]]))
+            (metabase.models [common :refer [assoc-permissions-sets]]
+                             [interface :refer :all])))
 
 
 (defentity Database
@@ -13,33 +14,23 @@
   (assoc :hydration-keys #{:database
                            :db}))
 
-(defmethod post-select Database [_ db]
-  (assoc db
-         :can_read     (delay true)
-         :can_write    (delay (:is_superuser @*current-user*))))
-
- {:created_at #inst "2015-06-30T19:51:45.294000000-00:00",
-   :engine :h2,
-   :id 3,
-   :details
-   {:db
-    "file:/Users/camsaul/metabase/target/Test_Database;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=-1"},
-   :updated_at #inst "2015-06-30T19:51:45.294000000-00:00",
-   :name "Test Database",
-   :organization_id nil,
-   :description nil}
+(defrecord DatabaseInstance []
+  ;; preserve normal IFn behavior so things like ((sel :one Database) :id) work correctly
+  clojure.lang.IFn
+  (invoke [this k]
+    (get this k))
 
-{:description nil,
-   :organization_id nil,
-   :name "Test Database",
-   :updated_at #inst "2015-06-30T19:51:45.294000000-00:00",
-   :details
-   {:db
-    "file:/Users/camsaul/metabase/target/Test_Database;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=-1"},
-   :id 3,
-   :engine "h2",
-   :created_at #inst "2015-06-30T19:51:45.294000000-00:00"}
+  IModelInstanceApiSerialze
+  (api-serialize [this]
+    ;; If current user isn't an admin strip out DB details which may include things like password
+    (cond-> this
+      (not (:is_superuser @*current-user*)) (dissoc :details))))
 
+(defmethod post-select Database [_ db]
+  (map->DatabaseInstance
+   (assoc db
+          :can_read  (delay true)
+          :can_write (delay (:is_superuser @*current-user*)))))
 
 (defmethod pre-cascade-delete Database [_ {:keys [id] :as database}]
   (cascade-delete 'metabase.models.table/Table :db_id id))
diff --git a/src/metabase/models/interface.clj b/src/metabase/models/interface.clj
new file mode 100644
index 00000000000..1887ea2ffe2
--- /dev/null
+++ b/src/metabase/models/interface.clj
@@ -0,0 +1,14 @@
+(ns metabase.models.interface)
+
+(defprotocol IModelInstanceApiSerialze
+  (api-serialize [this]
+    "Called on all objects being written out by the API. Default implementations return THIS as-is, but models can provide
+     custom methods to strip sensitive data, from non-admins, etc."))
+
+(extend-protocol IModelInstanceApiSerialze
+  Object
+  (api-serialize [this]
+    this)
+  nil
+  (api-serialize [_]
+    nil))
diff --git a/test/metabase/api/meta/db_test.clj b/test/metabase/api/meta/db_test.clj
index 736efb3dc7e..4ef82a691b6 100644
--- a/test/metabase/api/meta/db_test.clj
+++ b/test/metabase/api/meta/db_test.clj
@@ -40,18 +40,31 @@
 ;; # DB LIFECYCLE ENDPOINTS
 
 ;; ## GET /api/meta/db/:id
+;; regular users *should not* see DB details
 (expect
     (match-$ (db)
-      {:created_at $
-       :engine "h2"
-       :id $
-       :details $
-       :updated_at $
-       :name "Test Database"
+      {:created_at      $
+       :engine          "h2"
+       :id              $
+       :updated_at      $
+       :name            "Test Database"
        :organization_id nil
-       :description nil})
+       :description     nil})
   ((user->client :rasta) :get 200 (format "meta/db/%d" (db-id))))
 
+;; superusers *should* see DB details
+(expect
+    (match-$ (db)
+      {:created_at      $
+       :engine          "h2"
+       :id              $
+       :details         $
+       :updated_at      $
+       :name            "Test Database"
+       :organization_id nil
+       :description     nil})
+  ((user->client :crowberto) :get 200 (format "meta/db/%d" (db-id))))
+
 ;; ## POST /api/meta/db
 ;; Check that we can create a Database
 (let [db-name (random-name)]
@@ -106,6 +119,7 @@
 
 ;; ## GET /api/meta/db
 ;; Test that we can get all the DBs for an Org, ordered by name
+;; Database details *should not* come back for Rasta since she's not a superuser
 (let [db-name (str "A" (random-name))] ; make sure this name comes before "Test Database"
   (expect-eval-actual-first
       (set (filter identity
@@ -115,7 +129,6 @@
                                {:created_at      $
                                 :engine          (name $engine)
                                 :id              $
-                                :details         $
                                 :updated_at      $
                                 :name            "Test Database"
                                 :organization_id nil
@@ -125,7 +138,6 @@
                              {:created_at      $
                               :engine          "postgres"
                               :id              $
-                              :details         {:host "localhost", :port 5432, :dbname "fakedb", :user "cam"}
                               :updated_at      $
                               :name            $
                               :organization_id nil
diff --git a/test/metabase/api/meta/field_test.clj b/test/metabase/api/meta/field_test.clj
index 09134c7bf6f..f60cc814da4 100644
--- a/test/metabase/api/meta/field_test.clj
+++ b/test/metabase/api/meta/field_test.clj
@@ -22,7 +22,6 @@
                        {:created_at $
                         :engine "h2"
                         :id $
-                        :details $
                         :updated_at $
                         :name "Test Database"
                         :organization_id nil
diff --git a/test/metabase/api/meta/table_test.clj b/test/metabase/api/meta/table_test.clj
index 4bb229203a3..0ed48e7a5bf 100644
--- a/test/metabase/api/meta/table_test.clj
+++ b/test/metabase/api/meta/table_test.clj
@@ -64,7 +64,6 @@
              {:created_at $
               :engine "h2"
               :id $
-              :details $
               :updated_at $
               :name "Test Database"
               :organization_id nil
@@ -120,7 +119,6 @@
              {:created_at $
               :engine "h2"
               :id $
-              :details $
               :updated_at $
               :name "Test Database"
               :organization_id nil
@@ -193,7 +191,6 @@
              {:created_at $
               :engine "h2"
               :id $
-              :details $
               :updated_at $
               :name "Test Database"
               :organization_id nil
@@ -293,7 +290,6 @@
              {:created_at $
               :engine "h2"
               :id $
-              :details $
               :updated_at $
               :name "Test Database"
               :organization_id nil
@@ -444,8 +440,7 @@
                                   :updated_at $,
                                   :id $,
                                   :engine "h2",
-                                  :created_at $
-                                  :details $})})})
+                                  :created_at $})})})
       :destination (match-$ users-id-field
                      {:id $
                       :table_id $
diff --git a/test/metabase/middleware/auth_test.clj b/test/metabase/middleware/auth_test.clj
index 871143d14a4..58ac400655c 100644
--- a/test/metabase/middleware/auth_test.clj
+++ b/test/metabase/middleware/auth_test.clj
@@ -1,188 +1,172 @@
 (ns metabase.middleware.auth-test
   (:require [expectations :refer :all]
-            [korma.core :as korma]
+            [korma.core :as k]
             [ring.mock.request :as mock]
             [metabase.api.common :refer [*current-user-id* *current-user*]]
             [metabase.middleware.auth :refer :all]
             [metabase.models.session :refer [Session]]
             [metabase.test.data :refer :all]
             [metabase.test.data.users :refer :all]
-            [metabase.util :as util]))
+            [metabase.util :as u]))
 
-;;  ===========================  TEST wrap-sessionid middleware  ===========================
+;;  ===========================  TEST wrap-session-id middleware  ===========================
 
 ;; create a simple example of our middleware wrapped around a handler that simply returns the request
 ;; this works in this case because the only impact our middleware has is on the request
-(def wrapped-handler
-  (wrap-sessionid (fn [req] req)))
+(def ^:private wrapped-handler
+  (wrap-session-id identity))
 
 
-;; no sessionid in the request
-(expect
-  {}
+;; no session-id in the request
+(expect nil
   (-> (wrapped-handler (mock/request :get "/anyurl") )
-      (select-keys [:metabase-sessionid])))
+      :metabase-session-id))
 
 
-;; extract sessionid from header
-(expect
-  {:metabase-sessionid "foobar"}
+;; extract session-id from header
+(expect "foobar"
   (-> (wrapped-handler (mock/header (mock/request :get "/anyurl") metabase-session-header "foobar"))
-      (select-keys [:metabase-sessionid])))
+      :metabase-session-id))
 
 
-;; extract sessionid from cookie
-(expect
-  {:metabase-sessionid "cookie-session"}
+;; extract session-id from cookie
+(expect "cookie-session"
   (-> (wrapped-handler (assoc (mock/request :get "/anyurl") :cookies {metabase-session-cookie {:value "cookie-session"}}))
-      (select-keys [:metabase-sessionid])))
+      :metabase-session-id))
 
 
-;; if both header and cookie sessionids exist, then we expect the cookie to take precedence
-(expect
-  {:metabase-sessionid "cookie-session"}
+;; if both header and cookie session-ids exist, then we expect the cookie to take precedence
+(expect "cookie-session"
   (-> (wrapped-handler (-> (mock/header (mock/request :get "/anyurl") metabase-session-header "foobar")
                            (assoc :cookies {metabase-session-cookie {:value "cookie-session"}})))
-      (select-keys [:metabase-sessionid])))
+      :metabase-session-id))
 
 
 ;;  ===========================  TEST enforce-authentication middleware  ===========================
 
 
 ;; create a simple example of our middleware wrapped around a handler that simply returns the request
-(def auth-enforced-handler
-  (enforce-authentication (fn [req] req)))
+(def ^:private auth-enforced-handler
+  (wrap-current-user-id (enforce-authentication identity)))
 
 
-(defn request-with-sessionid
-  "Creates a mock Ring request with the given sessionid applied"
-  [sessionid]
+(defn- request-with-session-id
+  "Creates a mock Ring request with the given session-id applied"
+  [session-id]
   (-> (mock/request :get "/anyurl")
-      (assoc :metabase-sessionid sessionid)))
+      (assoc :metabase-session-id session-id)))
 
 
-;; no sessionid in the request
-(expect
-  {:status (:status response-unauthentic)
-   :body (:body response-unauthentic)}
+;; no session-id in the request
+(expect response-unauthentic
   (auth-enforced-handler (mock/request :get "/anyurl")))
 
+(defn- random-session-id []
+  {:post [(string? %)]}
+  (.toString (java.util.UUID/randomUUID)))
 
-;; valid sessionid
-(let [sessionid (.toString (java.util.UUID/randomUUID))]
-  (assert sessionid)
-  ;; validate that we are authenticated
-  (expect-let [res (korma/insert Session (korma/values {:id sessionid :user_id (user->id :rasta) :created_at (util/new-sql-timestamp)}))]
-    {:metabase-userid (user->id :rasta)}
-    (-> (auth-enforced-handler (request-with-sessionid sessionid))
-        (select-keys [:metabase-userid]))))
+;; valid session ID
+(expect (user->id :rasta)
+  (let [session-id (random-session-id)]
+    (k/insert Session (k/values {:id session-id, :user_id (user->id :rasta), :created_at (u/new-sql-timestamp)}))
+    (-> (auth-enforced-handler (request-with-session-id session-id))
+        :metabase-user-id)))
 
 
-;; expired sessionid
-(let [sessionid (.toString (java.util.UUID/randomUUID))]
-  (assert sessionid)
-  ;; create a new session (specifically created some time in the past so it's EXPIRED)
-  ;; should fail due to session expiration
-  (expect-let [res (korma/insert Session (korma/values {:id sessionid :user_id (user->id :rasta) :created_at (java.sql.Timestamp. 0)}))]
-    {:status (:status response-unauthentic)
-     :body (:body response-unauthentic)}
-    (auth-enforced-handler (request-with-sessionid sessionid))))
+;; expired session-id
+;; create a new session (specifically created some time in the past so it's EXPIRED)
+;; should fail due to session expiration
+(expect response-unauthentic
+  (let [session-id (random-session-id)]
+    (k/insert Session (k/values {:id session-id, :user_id (user->id :rasta), :created_at (java.sql.Timestamp. 0)}))
+    (auth-enforced-handler (request-with-session-id session-id))))
 
 
-;; inactive user sessionid
-(let [sessionid (.toString (java.util.UUID/randomUUID))]
-  (assert sessionid)
-  ;; create a new session (specifically created some time in the past so it's EXPIRED)
-  ;; should fail due to inactive user
-  ;; NOTE that :trashbird is our INACTIVE test user
-  (expect-let [res (korma/insert Session (korma/values {:id sessionid :user_id (user->id :trashbird) :created_at (util/new-sql-timestamp)}))]
-    {:status (:status response-unauthentic)
-     :body (:body response-unauthentic)}
-    (auth-enforced-handler (request-with-sessionid sessionid))))
+;; inactive user session-id
+;; create a new session (specifically created some time in the past so it's EXPIRED)
+;; should fail due to inactive user
+;; NOTE that :trashbird is our INACTIVE test user
+(expect response-unauthentic
+  (let [session-id (random-session-id)]
+    (k/insert Session (k/values {:id session-id, :user_id (user->id :trashbird), :created_at (u/new-sql-timestamp)}))
+    (auth-enforced-handler (request-with-session-id session-id))))
 
 
 ;;  ===========================  TEST bind-current-user middleware  ===========================
 
 
 ;; create a simple example of our middleware wrapped around a handler that simply returns our bound variables for users
-(def user-bound-handler
-  (bind-current-user (fn [req] {:userid *current-user-id*
-                                :user (select-keys @*current-user* [:id :email])})))
+(def ^:private user-bound-handler
+  (bind-current-user (fn [_] {:user-id *current-user-id*
+                              :user    (select-keys @*current-user* [:id :email])})))
 
-
-(defn request-with-userid
-  "Creates a mock Ring request with the given userid applied"
-  [userid]
+(defn- request-with-user-id
+  "Creates a mock Ring request with the given user-id applied"
+  [user-id]
   (-> (mock/request :get "/anyurl")
-      (assoc :metabase-userid userid)))
+      (assoc :metabase-user-id user-id)))
 
 
 ;; with valid user-id
 (expect
-  {:userid (user->id :rasta)
-   :user {:id (user->id :rasta)
-          :email (:email (fetch-user :rasta))}}
-  (user-bound-handler (request-with-userid (user->id :rasta))))
+    {:user-id (user->id :rasta)
+     :user    {:id    (user->id :rasta)
+               :email (:email (fetch-user :rasta))}}
+  (user-bound-handler (request-with-user-id (user->id :rasta))))
 
 ;; with invalid user-id (not sure how this could ever happen, but lets test it anyways)
 (expect
-  {:userid 0
-   :user {}}
-  (user-bound-handler (request-with-userid 0)))
+    {:user-id 0
+     :user    {}}
+  (user-bound-handler (request-with-user-id 0)))
 
 
-;;  ===========================  TEST wrap-apikey middleware  ===========================
+;;  ===========================  TEST wrap-api-key middleware  ===========================
 
 ;; create a simple example of our middleware wrapped around a handler that simply returns the request
 ;; this works in this case because the only impact our middleware has is on the request
-(def wrapped-apikey-handler
-  (wrap-apikey (fn [req] req)))
+(def ^:private wrapped-api-key-handler
+  (wrap-api-key identity))
 
 
 ;; no apikey in the request
-(expect
-  {}
-  (-> (wrapped-apikey-handler (mock/request :get "/anyurl") )
-      (select-keys [:metabase-sessionid])))
+(expect nil
+  (-> (wrapped-api-key-handler (mock/request :get "/anyurl") )
+      :metabase-session-id))
 
 
 ;; extract apikey from header
-(expect
-  {:metabase-apikey "foobar"}
-  (-> (wrapped-apikey-handler (mock/header (mock/request :get "/anyurl") metabase-apikey-header "foobar"))
-      (select-keys [:metabase-apikey])))
+(expect "foobar"
+  (-> (wrapped-api-key-handler (mock/header (mock/request :get "/anyurl") metabase-api-key-header "foobar"))
+      :metabase-api-key))
 
 
-;;  ===========================  TEST enforce-apikey middleware  ===========================
+;;  ===========================  TEST enforce-api-key middleware  ===========================
 
 
 ;; create a simple example of our middleware wrapped around a handler that simply returns the request
-(def apikey-enforced-handler
-  (enforce-apikey (fn [req] {:success true})))
+(def ^:private api-key-enforced-handler
+  (enforce-api-key (constantly {:success true})))
 
 
-(defn request-with-apikey
+(defn- request-with-api-key
   "Creates a mock Ring request with the given apikey applied"
-  [apikey]
+  [api-key]
   (-> (mock/request :get "/anyurl")
-      (assoc :metabase-apikey apikey)))
+      (assoc :metabase-api-key api-key)))
 
 
 ;; no apikey in the request, expect 403
-(expect
-  {:status (:status response-forbidden)
-   :body (:body response-forbidden)}
-  (apikey-enforced-handler (mock/request :get "/anyurl")))
+(expect response-forbidden
+  (api-key-enforced-handler (mock/request :get "/anyurl")))
 
 
 ;; valid apikey, expect 200
 (expect
-  {:success true}
-  (apikey-enforced-handler (request-with-apikey "test-api-key")))
+    {:success true}
+  (api-key-enforced-handler (request-with-api-key "test-api-key")))
 
 
 ;; invalid apikey, expect 403
-(expect
-  {:status (:status response-forbidden)
-   :body (:body response-forbidden)}
-  (apikey-enforced-handler (request-with-apikey "foobar")))
+(expect response-forbidden
+  (api-key-enforced-handler (request-with-api-key "foobar")))
diff --git a/test/metabase/task_test.clj b/test/metabase/task_test.clj
index 5e1b817e390..a6e07ddfc41 100644
--- a/test/metabase/task_test.clj
+++ b/test/metabase/task_test.clj
@@ -67,16 +67,16 @@
          :restarted]
   [(do
      (stop-task-runner!)
-     (with-redefs [metabase.task/hourly-task-delay (constantly 100)
+     (with-redefs [metabase.task/hourly-task-delay (constantly 200)
                    metabase.task/hourly-tasks-hook mock-hourly-tasks-hook]
        (add-hook! #'hourly-tasks-hook inc-task-test-atom-counter-by-system-hour)
        (reset! task-test-atom-counter 0)
        (start-task-runner!)
 
        [@task-test-atom-counter      ; should be 0, since not enough time has elaspsed for the hook to be executed
-        (do (Thread/sleep 150)
-            @task-test-atom-counter) ; should have been called once (~50ms ago)
-        (do (Thread/sleep 200)
+        (do (Thread/sleep 300)
+            @task-test-atom-counter) ; should have been called once (~100ms ago)
+        (do (Thread/sleep 400)
             @task-test-atom-counter) ; should have been called two more times
         (do (stop-task-runner!)
             :stopped)]))
-- 
GitLab