Skip to content
Snippets Groups Projects
Commit a5b1e107 authored by Allen Gilliland's avatar Allen Gilliland
Browse files

add new code for handling initial user creation at install time.

* new api endpoint for /api/setup/user which takes an install token and creates a new user.
* added a few setup token management functions in metabase.setup namespace.
* updated metabase.com (init) function to detect if a setup token is needed and provide a url on cmd line.
* unit tests for new api endpoint.
parent a01f1bea
No related branches found
No related tags found
No related merge requests found
......@@ -12,6 +12,7 @@
[search :as search]
[session :as session]
[setting :as setting]
[setup :as setup]
[user :as user])
(metabase.api.meta [dataset :as dataset]
[db :as db]
......@@ -42,7 +43,8 @@
(context "/result" [] (+auth result/routes))
(context "/search" [] (+auth search/routes))
(context "/session" [] session/routes)
(context "/setting" [] (+auth setting/routes))
(context "/setting" [] (+auth setting/routes))
(context "/setup" [] setup/routes)
(context "/user" [] (+auth user/routes))
(route/not-found (fn [{:keys [request-method uri]}]
{:status 404
......
(ns metabase.api.setup
(:require [compojure.core :refer [defroutes POST]]
[metabase.api.common :refer :all]
[metabase.db :refer :all]
(metabase.models [session :refer [Session]]
[user :refer [User set-user-password]])
[metabase.setup :as setup]
[metabase.util :as util]
[metabase.util.password :as password]))
;; special endpoint for creating the first user during setup
;; this endpoint both creates the user AND logs them in and returns a session id
(defendpoint POST "/user" [:as {{:keys [token first_name last_name email password] :as body} :body}]
;; check our input
(require-params token first_name last_name email password)
(check-400 (and (util/is-email? email) (password/is-complex? password)))
;; the submitted token must match our setup token
(check-403 (setup/token-match? token))
;; extra check. don't continue if there is already a user in the db.
(let [session-id (str (java.util.UUID/randomUUID))
new-user (ins User
:email email
:first_name first_name
:last_name last_name
:password (str (java.util.UUID/randomUUID)))]
;; this results in a second db call, but it avoids redundant password code so figure it's worth it
(set-user-password (:id new-user) password)
;; clear the setup token now, it's no longer needed
(setup/token-clear)
;; then we create a session right away because we want our new user logged in to continue the setup process
(ins Session
:id session-id
:user_id (:id new-user))
{:id session-id}))
(define-routes)
......@@ -9,7 +9,7 @@
[format :refer :all])
[metabase.models.user :refer [User]]
[metabase.routes :as routes]
[metabase.util :as util]
[metabase.setup :as setup]
[ring.adapter.jetty :as ring-jetty]
(ring.middleware [cookies :refer [wrap-cookies]]
[gzip :refer [wrap-gzip]]
......@@ -45,10 +45,21 @@
;; startup database. validates connection & runs any necessary migrations
(db/setup-db :auto-migrate (config/config-bool :mb-db-automigrate))
;; this is a temporary need until we finalize the code for bootstrapping the first user
(when-not (or (db/exists? User :id 1)
(db/exists? User :email "admin@admin.com")) ; possible for User 1 to have been deleted
(db/ins User :email "admin@admin.com" :first_name "admin" :last_name "admin" :password "admin" :is_superuser true))
;; run a very quick check to see if we are doing a first time installation
;; the test we are using is if there is at least 1 User in the database
(when-not (db/sel :one :fields [User :id])
(log/info "Looks like this is a new installation ... preparing setup wizard")
(let [setup-token (setup/token-create)
hostname (or (config/config-str :mb-jetty-host) "localhost")
port (config/config-int :mb-jetty-port)
setup-url (str "http://"
(or hostname "localhost")
(when-not (= 80 port) (str ":" port))
"/setup/"
setup-token)]
(log/info (str "Please use the following url to setup your Metabase installation:\n\n"
setup-url
"\n\n"))))
(log/info "Metabase Initialization COMPLETE")
true)
......
(ns metabase.setup)
(def ^:private setup-token
(atom nil))
(defn token-match?
"Function for checking if the supplied string matches our setup token.
Returns boolean `true` if supplied token matches `@setup-token`, `false` otherwise."
[token]
{:pre [(string? token)]}
(= token @setup-token))
(defn token-create
"Create and set a new `@setup-token`.
Returns the newly created token."
[]
(reset! setup-token (.toString (java.util.UUID/randomUUID))))
(defn token-clear
"Clear the `@setup-token` if it exists and reset it to nil."
[]
(reset! setup-token nil))
(ns metabase.api.setup-test
"Tests for /api/setup endpoints."
(:require [expectations :refer :all]
[metabase.db :refer :all]
[metabase.http-client :as http]
(metabase.models [session :refer [Session]]
[user :refer [User]])
[metabase.setup :as setup]
[metabase.test.util :refer [match-$ random-name expect-eval-actual-first]]
[metabase.test-data :refer :all]))
;; ## POST /api/setup/user
;; Check that we can create a new superuser via setup-token
(let [setup-token (setup/token-create)
user-name (random-name)]
(expect-eval-actual-first
(match-$ (->> (sel :one User :email (str user-name "@metabase.com"))
(:id)
(sel :one Session :user_id))
{:id $id})
(http/client :post 200 "setup/user" {:token setup-token
:first_name user-name
:last_name user-name
:email (str user-name "@metabase.com")
:password "anythingUP12!!"})))
;; Test input validations
(expect "'token' is a required param."
(http/client :post 400 "setup/user" {}))
(expect "'first_name' is a required param."
(http/client :post 400 "setup/user" {:token "anything"}))
(expect "'last_name' is a required param."
(http/client :post 400 "setup/user" {:token "anything"
:first_name "anything"}))
(expect "'email' is a required param."
(http/client :post 400 "setup/user" {:token "anything"
:first_name "anything"
:last_name "anything"}))
(expect "'password' is a required param."
(http/client :post 400 "setup/user" {:token "anything"
:first_name "anything"
:last_name "anything"
:email "anything"}))
;; valid email + complex password
(expect "Invalid Request."
(http/client :post 400 "setup/user" {:token "anything"
:first_name "anything"
:last_name "anything"
:email "anything"
:password "anything"}))
(expect "Invalid Request."
(http/client :post 400 "setup/user" {:token "anything"
:first_name "anything"
:last_name "anything"
:email "anything@email.com"
:password "anything"}))
;; token match
(expect "You don't have permissions to do that."
(http/client :post 403 "setup/user" {:token "anything"
:first_name "anything"
:last_name "anything"
:email "anything@email.com"
:password "anythingUP12!!"}))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment