diff --git a/modules/drivers/mongo/src/metabase/driver/mongo/connection.clj b/modules/drivers/mongo/src/metabase/driver/mongo/connection.clj index 0b0d2a4b43863d323307f036b26d1538124c8837..999d32e2ac2583c5d4f0750a390a87ad5c4063cc 100644 --- a/modules/drivers/mongo/src/metabase/driver/mongo/connection.clj +++ b/modules/drivers/mongo/src/metabase/driver/mongo/connection.clj @@ -10,7 +10,7 @@ [metabase.util.log :as log] [metabase.util.ssh :as ssh]) (:import - (com.mongodb ConnectionString MongoClientSettings MongoClientSettings$Builder) + (com.mongodb ConnectionString MongoClientSettings MongoClientSettings$Builder MongoCredential) (com.mongodb.connection SslSettings$Builder))) (set! *warn-on-reflection* true) @@ -20,11 +20,8 @@ nil) (defn db-details->connection-string - "Generate connection string from database details. - - - `?authSource` is always prestent because we are using `dbname`. - - We let the user override options we are passing in by means of `additional-options`." - [{:keys [use-conn-uri conn-uri host port user authdb pass dbname additional-options use-srv ssl] :as _db-details}] + "Generate connection string from database details." + [{:keys [use-conn-uri conn-uri host port dbname additional-options use-srv ssl] :as _db-details}] ;; Connection string docs: ;; http://mongodb.github.io/mongo-java-driver/4.11/apidocs/mongodb-driver-core/com/mongodb/ConnectionString.html (if use-conn-uri @@ -32,14 +29,12 @@ (str (if use-srv "mongodb+srv" "mongodb") "://" - (when (seq user) (str user (when (seq pass) (str ":" pass)) "@")) + ;; credentials are added into MongoClientSettings in [[db-details->mongo-client-settings]] host (when (and (not use-srv) (some? port)) (str ":" port)) "/" dbname - "?authSource=" (if (empty? authdb) "admin" authdb) - "&appName=" config/mb-app-id-string - "&connectTimeoutMS=" (driver.u/db-connection-timeout-ms) + "?connectTimeoutMS=" (driver.u/db-connection-timeout-ms) "&serverSelectionTimeoutMS=" (driver.u/db-connection-timeout-ms) (when ssl "&ssl=true") (when (seq additional-options) (str "&" additional-options))))) @@ -65,17 +60,27 @@ (defn db-details->mongo-client-settings "Generate `MongoClientSettings` from `db-details`. `ConnectionString` is generated and applied to - `MongoClientSettings$Builder` first. Then ssl context is udated in the `builder` object. + `MongoClientSettings$Builder` first. Then credentials are set and ssl context is updated in the `builder` object. Afterwards, `MongoClientSettings` are built using `.build`." ^MongoClientSettings - [{:keys [use-conn-uri ssl] :as db-details}] + [{:keys [authdb user pass use-conn-uri ssl] :as db-details}] (let [connection-string (-> db-details db-details->connection-string ConnectionString.) builder (com.mongodb.MongoClientSettings/builder)] + (.applicationName builder config/mb-app-id-string) (.applyConnectionString builder connection-string) - (when (and ssl (not use-conn-uri)) - (maybe-add-ssl-context-to-builder! builder db-details)) + (when-not use-conn-uri + ;; NOTE: authSource connection parameter is the second argument of `createCredential`. We currently set it only + ;; when some credentials are used (ie. user is not empty), previously we did that in all cases. I've + ;; manually verified that's not necessary. + (when (seq user) + (.credential builder + (MongoCredential/createCredential user + (or (not-empty authdb) "admin") + (char-array pass)))) + (when ssl + (maybe-add-ssl-context-to-builder! builder db-details))) (.build builder))) (defn do-with-mongo-client diff --git a/modules/drivers/mongo/test/metabase/driver/mongo/connection_test.clj b/modules/drivers/mongo/test/metabase/driver/mongo/connection_test.clj index 2030d27f3a898c202e5cbbf9d019bf024bc9252c..2a7ba274549918ec5ddbd1d00a3efb22d4bb1ac4 100644 --- a/modules/drivers/mongo/test/metabase/driver/mongo/connection_test.clj +++ b/modules/drivers/mongo/test/metabase/driver/mongo/connection_test.clj @@ -2,17 +2,16 @@ (:require [clojure.string :as str] [clojure.test :refer :all] + [metabase.config :as config] [metabase.driver.mongo.connection :as mongo.connection] [metabase.driver.mongo.database :as mongo.db] [metabase.driver.mongo.util :as mongo.util] [metabase.driver.util :as driver.u] [metabase.test :as mt]) (:import - (com.mongodb ServerAddress) + (com.mongodb MongoCredential ServerAddress) (com.mongodb.client MongoDatabase))) -;;;; TODO: move some tests to db-test? - (set! *warn-on-reflection* true) (def ^:private mock-details @@ -39,7 +38,12 @@ :use-srv true}] (testing "mongo+srv connection string used when :use-srv is thruthy" (is (str/includes? (mongo.connection/db-details->connection-string db-details) - "mongodb+srv://test-user:test-pass@test-host.place.com/datadb?authSource=authdb"))) + "mongodb+srv://test-host.place.com/datadb")) + (let [settings (mongo.connection/db-details->mongo-client-settings db-details) + ^MongoCredential credential (.getCredential settings)] + (is (= "test-user" (.getUserName credential))) + (is (= "test-pass" (str/join "" (.getPassword credential)))) + (is (= "authdb" (.getSource credential))))) (testing "Only fqdn may be used with mongo+srv" (is (thrown-with-msg? Throwable #"Using DNS SRV requires a FQDN for host" @@ -139,3 +143,27 @@ (or (when (instance? java.net.ConnectException e) (throw e)) (some-> (.getCause e) recur)))))))))) + +(deftest hard-password-test + (mt/test-driver + :mongo + (testing "Passwords and usernames containing `$ : / ? # [ ] @` are usable (#38697)" + (let [s "$ : / ? # [ ] @" + settings (mongo.connection/db-details->mongo-client-settings {:user s + :pass s + :host "localhost" + :dbname "justdb"}) + ^MongoCredential credential (.getCredential settings)] + (is (= s (.getUserName credential))) + (is (= s (str/join "" (.getPassword credential)))) + (is (= "admin" (.getSource credential))))))) + +(deftest application-name-test + (mt/test-driver + :mongo + (with-redefs [config/mb-app-id-string "$ : / ? # [ ] @"] + (let [db-details {:host "test-host.place.com" + :dbname "datadb"} + settings (mongo.connection/db-details->mongo-client-settings db-details)] + (is (= "$ : / ? # [ ] @" + (.getApplicationName settings)))))))