From: Ivan Tashkinov Date: Mon, 18 Mar 2019 07:26:41 +0000 (+0300) Subject: [#923] Merge remote-tracking branch 'remotes/upstream/develop' into twitter_oauth X-Git-Url: https://git.squeep.com/?a=commitdiff_plain;h=2a96283efbd46c017cf9e15ef4fda3188e5e5bca;p=akkoma [#923] Merge remote-tracking branch 'remotes/upstream/develop' into twitter_oauth # Conflicts: # config/config.exs # lib/pleroma/web/auth/pleroma_authenticator.ex --- 2a96283efbd46c017cf9e15ef4fda3188e5e5bca diff --cc config/config.exs index 28867ed4c,ccdd35777..6839b489b --- a/config/config.exs +++ b/config/config.exs @@@ -370,21 -370,17 +370,32 @@@ config :auto_linker rel: false ] + config :pleroma, :ldap, + enabled: System.get_env("LDAP_ENABLED") == "true", + host: System.get_env("LDAP_HOST") || "localhost", + port: String.to_integer(System.get_env("LDAP_PORT") || "389"), + ssl: System.get_env("LDAP_SSL") == "true", + sslopts: [], + tls: System.get_env("LDAP_TLS") == "true", + tlsopts: [], + base: System.get_env("LDAP_BASE") || "dc=example,dc=com", + uid: System.get_env("LDAP_UID") || "cn" + +config :pleroma, :auth, oauth_consumer_enabled: false + +config :ueberauth, + Ueberauth, + base_path: "/oauth", + providers: [ + twitter: + {Ueberauth.Strategy.Twitter, + [callback_params: ~w[client_id redirect_uri scope scopes]]} + ] + +config :ueberauth, Ueberauth.Strategy.Twitter.OAuth, + consumer_key: System.get_env("TWITTER_CONSUMER_KEY"), + consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET") + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --cc lib/pleroma/web/auth/ldap_authenticator.ex index 000000000,88217aab8..6c65cff27 mode 000000,100644..100644 --- a/lib/pleroma/web/auth/ldap_authenticator.ex +++ b/lib/pleroma/web/auth/ldap_authenticator.ex @@@ -1,0 -1,143 +1,145 @@@ + # Pleroma: A lightweight social networking server + # Copyright © 2017-2019 Pleroma Authors + # SPDX-License-Identifier: AGPL-3.0-only + + defmodule Pleroma.Web.Auth.LDAPAuthenticator do + alias Pleroma.User + + require Logger + + @behaviour Pleroma.Web.Auth.Authenticator + + @connection_timeout 10_000 + @search_timeout 10_000 + - def get_user(%Plug.Conn{} = conn) do ++ def get_user(%Plug.Conn{} = conn, params) do + if Pleroma.Config.get([:ldap, :enabled]) do + {name, password} = - case conn.params do ++ case params do + %{"authorization" => %{"name" => name, "password" => password}} -> + {name, password} + + %{"grant_type" => "password", "username" => name, "password" => password} -> + {name, password} + end + + case ldap_user(name, password) do + %User{} = user -> + {:ok, user} + + {:error, {:ldap_connection_error, _}} -> + # When LDAP is unavailable, try default authenticator - Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn) ++ Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn, params) + + error -> + error + end + else + # Fall back to default authenticator - Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn) ++ Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn, params) + end + end + ++ def get_or_create_user_by_oauth(conn, params), do: get_user(conn, params) ++ + def handle_error(%Plug.Conn{} = _conn, error) do + error + end + + def auth_template, do: nil + + defp ldap_user(name, password) do + ldap = Pleroma.Config.get(:ldap, []) + host = Keyword.get(ldap, :host, "localhost") + port = Keyword.get(ldap, :port, 389) + ssl = Keyword.get(ldap, :ssl, false) + sslopts = Keyword.get(ldap, :sslopts, []) + + options = + [{:port, port}, {:ssl, ssl}, {:timeout, @connection_timeout}] ++ + if sslopts != [], do: [{:sslopts, sslopts}], else: [] + + case :eldap.open([to_charlist(host)], options) do + {:ok, connection} -> + try do + if Keyword.get(ldap, :tls, false) do + :application.ensure_all_started(:ssl) + + case :eldap.start_tls( + connection, + Keyword.get(ldap, :tlsopts, []), + @connection_timeout + ) do + :ok -> + :ok + + error -> + Logger.error("Could not start TLS: #{inspect(error)}") + end + end + + bind_user(connection, ldap, name, password) + after + :eldap.close(connection) + end + + {:error, error} -> + Logger.error("Could not open LDAP connection: #{inspect(error)}") + {:error, {:ldap_connection_error, error}} + end + end + + defp bind_user(connection, ldap, name, password) do + uid = Keyword.get(ldap, :uid, "cn") + base = Keyword.get(ldap, :base) + + case :eldap.simple_bind(connection, "#{uid}=#{name},#{base}", password) do + :ok -> + case User.get_by_nickname_or_email(name) do + %User{} = user -> + user + + _ -> + register_user(connection, base, uid, name, password) + end + + error -> + error + end + end + + defp register_user(connection, base, uid, name, password) do + case :eldap.search(connection, [ + {:base, to_charlist(base)}, + {:filter, :eldap.equalityMatch(to_charlist(uid), to_charlist(name))}, + {:scope, :eldap.wholeSubtree()}, + {:attributes, ['mail', 'email']}, + {:timeout, @search_timeout} + ]) do + {:ok, {:eldap_search_result, [{:eldap_entry, _, attributes}], _}} -> + with {_, [mail]} <- List.keyfind(attributes, 'mail', 0) do + params = %{ + email: :erlang.list_to_binary(mail), + name: name, + nickname: name, + password: password, + password_confirmation: password + } + + changeset = User.register_changeset(%User{}, params) + + case User.register(changeset) do + {:ok, user} -> user + error -> error + end + else + _ -> + Logger.error("Could not find LDAP attribute mail: #{inspect(attributes)}") + {:error, :ldap_registration_missing_attributes} + end + + error -> + error + end + end + end diff --cc lib/pleroma/web/auth/pleroma_authenticator.ex index 5583f41a9,94a19ad49..2e2bcfb70 --- a/lib/pleroma/web/auth/pleroma_authenticator.ex +++ b/lib/pleroma/web/auth/pleroma_authenticator.ex @@@ -8,9 -8,16 +8,16 @@@ defmodule Pleroma.Web.Auth.PleromaAuthe @behaviour Pleroma.Web.Auth.Authenticator - def get_user(%Plug.Conn{} = _conn, %{ - "authorization" => %{"name" => name, "password" => password} - }) do - def get_user(%Plug.Conn{} = conn) do ++ def get_user(%Plug.Conn{} = _conn, params) do + {name, password} = - case conn.params do ++ case params do + %{"authorization" => %{"name" => name, "password" => password}} -> + {name, password} + + %{"grant_type" => "password", "username" => name, "password" => password} -> + {name, password} + end + with {_, %User{} = user} <- {:user, User.get_by_nickname_or_email(name)}, {_, true} <- {:checkpw, Pbkdf2.checkpw(password, user.password_hash)} do {:ok, user} @@@ -20,56 -27,6 +27,54 @@@ end end - def get_user(%Plug.Conn{} = _conn, _params), do: {:error, :missing_credentials} - + def get_or_create_user_by_oauth( + %Plug.Conn{assigns: %{ueberauth_auth: %{provider: provider, uid: uid} = auth}}, + _params + ) do + user = User.get_by_auth_provider_uid(provider, uid) + + if user do + {:ok, user} + else + info = auth.info + email = info.email + nickname = info.nickname + + # TODO: FIXME: connect to existing (non-oauth) account (need a UI flow for that) / generate a random nickname? + email = + if email && User.get_by_email(email) do + nil + else + email + end + + nickname = + if nickname && User.get_by_nickname(nickname) do + nil + else + nickname + end + + new_user = + User.oauth_register_changeset( + %User{}, + %{ + auth_provider: to_string(provider), + auth_provider_uid: to_string(uid), + name: info.name, + bio: info.description, + email: email, + nickname: nickname + } + ) + + Pleroma.Repo.insert(new_user) + end + end + + def get_or_create_user_by_oauth(%Plug.Conn{} = _conn, _params), + do: {:error, :missing_credentials} + def handle_error(%Plug.Conn{} = _conn, error) do error end diff --cc lib/pleroma/web/oauth/oauth_controller.ex index d39c4a713,d151efe9e..588933d31 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@@ -175,11 -127,10 +176,10 @@@ defmodule Pleroma.Web.OAuth.OAuthContro def token_exchange( conn, - %{"grant_type" => "password", "username" => name, "password" => password} = params + %{"grant_type" => "password"} = params ) do - with %App{} = app <- get_app_from_request(conn, params), - %User{} = user <- User.get_by_nickname_or_email(name), - true <- Pbkdf2.checkpw(password, user.password_hash), - with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn)}, ++ with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn, params)}, + %App{} = app <- get_app_from_request(conn, params), {:auth_active, true} <- {:auth_active, User.auth_active?(user)}, scopes <- oauth_scopes(params, app.scopes), [] <- scopes -- app.scopes,