From f01d497401fa1a47cdb0ebf381721925e7b8fd39 Mon Sep 17 00:00:00 2001 From: JD Robertson <44848933+JD-Robertson@users.noreply.github.com> Date: Mon, 6 Nov 2023 13:44:59 -0600 Subject: [PATCH] Add Support for the PLAIN authMechanism (#223) * Add support for LDAP authentication (#5) * Add support for LDAP authentication * Unit test for url parser changes * Mention PLAIN auth support in the Readme (#6) * Mention PLAIN auth support in the Readme * tweak wording * Update readme (#7) --- README.md | 5 +++++ lib/mongo/auth.ex | 4 ++++ lib/mongo/auth/plain.ex | 31 +++++++++++++++++++++++++++++++ lib/mongo/url_parser.ex | 1 + test/mongo/url_parser_test.exs | 20 ++++++++++++++++++++ 5 files changed, 61 insertions(+) create mode 100644 lib/mongo/auth/plain.ex diff --git a/README.md b/README.md index cba10f40..d6baf0c7 100644 --- a/README.md +++ b/README.md @@ -705,6 +705,11 @@ separated in sub folders and module namespaces. ## Auth Mechanisms For versions of Mongo 3.0 and greater, the auth mechanism defaults to SCRAM. + +If connecting to MongoDB Enterprise Edition or MongoDB Atlas, the [PLAIN](https://www.mongodb.com/docs/manual/tutorial/authenticate-nativeldap-activedirectory/) +auth mechanism is supported for LDAP authentication. The GSSAPI auth mechanism used for Kerberos authentication +is not currently supported. + If you'd like to use [MONGODB-X509](https://www.mongodb.com/docs/v6.0/tutorial/configure-x509-client-authentication/) authentication, you can specify that as a `start_link` option. diff --git a/lib/mongo/auth.ex b/lib/mongo/auth.ex index ce6afcd3..6505f8e0 100644 --- a/lib/mongo/auth.ex +++ b/lib/mongo/auth.ex @@ -39,6 +39,10 @@ defmodule Mongo.Auth do Mongo.Auth.X509 end + defp mechanism(%{wire_version: version, auth_mechanism: :plain}) when version >= 3 do + Mongo.Auth.PLAIN + end + defp mechanism(%{wire_version: version}) when version >= 3 do Mongo.Auth.SCRAM end diff --git a/lib/mongo/auth/plain.ex b/lib/mongo/auth/plain.ex new file mode 100644 index 00000000..1702a49c --- /dev/null +++ b/lib/mongo/auth/plain.ex @@ -0,0 +1,31 @@ +defmodule Mongo.Auth.PLAIN do + @moduledoc false + alias Mongo.MongoDBConnection.Utils + + def auth({nil, nil}, _db, _s) do + :ok + end + + def auth({username, password}, _db, s) do + auth_payload = build_auth_payload(username, password) + message = [saslStart: 1, mechanism: "PLAIN", payload: auth_payload] + + case Utils.command(-3, message, s) do + {:ok, _flags, %{"ok" => ok, "done" => true}} when ok == 1 -> + :ok + + {:ok, _flags, %{"ok" => ok, "errmsg" => reason, "code" => code}} when ok == 0 -> + {:error, Mongo.Error.exception(message: "auth failed for user #{username}: #{reason}", code: code)} + + error -> + error + end + end + + defp build_auth_payload(username, password) do + # https://www.ietf.org/rfc/rfc4616.txt + # Null separate listed of authorization ID (blank), username, password. These are sent as raw UTF-8. + payload = "\0#{username}\0#{password}" + %BSON.Binary{binary: payload} + end +end diff --git a/lib/mongo/url_parser.ex b/lib/mongo/url_parser.ex index 02208b25..0fe9a5e6 100644 --- a/lib/mongo/url_parser.ex +++ b/lib/mongo/url_parser.ex @@ -113,6 +113,7 @@ defmodule Mongo.UrlParser do defp decode_percent(:username, value), do: URI.decode_www_form(value) defp decode_percent(:password, value), do: URI.decode_www_form(value) + defp decode_percent(:auth_source, value), do: URI.decode_www_form(value) defp decode_percent(_other, value), do: value defp parse_query_options(opts, %{"options" => options}) when is_binary(options) do diff --git a/test/mongo/url_parser_test.exs b/test/mongo/url_parser_test.exs index 36c1a911..b6115285 100644 --- a/test/mongo/url_parser_test.exs +++ b/test/mongo/url_parser_test.exs @@ -199,5 +199,25 @@ defmodule Mongo.UrlParserTest do username = Keyword.get(opts, :username) assert username == real_username end + + test "external auth source " do + encoded_external_auth_source = URI.encode_www_form("$external") + url = "mongodb://user:password@seed1.domain.com:27017,seed2.domain.com:27017,seed3.domain.com:27017/db_name?replicaSet=set-name&authMechanism=PLAIN&authSource=#{encoded_external_auth_source}&tls=true" + + assert UrlParser.parse_url(url: url) |> Keyword.drop([:pw_safe]) == [ + password: "*****", + username: "user", + database: "db_name", + tls: true, + auth_source: "$external", + auth_mechanism: :plain, + set_name: "set-name", + seeds: [ + "seed1.domain.com:27017", + "seed2.domain.com:27017", + "seed3.domain.com:27017" + ] + ] + end end end