From f471fc2ced03812f74eaeedec5eb395ac7c27b27 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Thu, 20 Aug 2020 22:20:32 -0400 Subject: add lower-level client, acting like the old client --- lib/polyjuice/client.ex | 55 ++++++------ lib/polyjuice/client/low_level.ex | 174 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+), 27 deletions(-) create mode 100644 lib/polyjuice/client/low_level.ex (limited to 'lib') diff --git a/lib/polyjuice/client.ex b/lib/polyjuice/client.ex index aee93fa..81415d1 100644 --- a/lib/polyjuice/client.ex +++ b/lib/polyjuice/client.ex @@ -291,6 +291,33 @@ defmodule Polyjuice.Client do ) end + def make_login_identifier(identifier) do + case identifier do + x when is_binary(x) -> + %{ + "type" => "m.id.user", + "user" => identifier + } + + {:email, address} -> + %{ + "type" => "m.id.thirdparty", + "medium" => "email", + "address" => address + } + + {:phone, country, phone} -> + %{ + "type" => "m.id.phone", + "country" => country, + "phone" => phone + } + + x when is_map(x) -> + identifier + end + end + @doc """ Log in with a password. @@ -313,39 +340,13 @@ defmodule Polyjuice.Client do def log_in_with_password(client, identifier, password, opts \\ []) when (is_binary(identifier) or is_tuple(identifier) or is_map(identifier)) and is_binary(password) and is_list(opts) do - id = - case identifier do - x when is_binary(x) -> - %{ - "type" => "m.id.user", - "user" => identifier - } - - {:email, address} -> - %{ - "type" => "m.id.thirdparty", - "medium" => "email", - "address" => address - } - - {:phone, country, phone} -> - %{ - "type" => "m.id.phone", - "country" => country, - "phone" => phone - } - - x when is_map(x) -> - identifier - end - ret = {:ok, %{"access_token" => access_token, "user_id" => user_id, "device_id" => device_id}} = Polyjuice.Client.API.call( client, %Polyjuice.Client.Endpoint.PostLogin{ type: "m.login.password", - identifier: id, + identifier: make_login_identifier(identifier), password: password, device_id: Keyword.get(opts, :device_id), initial_device_display_name: Keyword.get(opts, :initial_device_display_name) diff --git a/lib/polyjuice/client/low_level.ex b/lib/polyjuice/client/low_level.ex new file mode 100644 index 0000000..2b22c6c --- /dev/null +++ b/lib/polyjuice/client/low_level.ex @@ -0,0 +1,174 @@ +# Copyright 2020 Hubert Chathi +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +defmodule Polyjuice.Client.LowLevel do + @moduledoc """ + A lower-level client than `Polyjuice.Client`. + + The application is responsible for taking care of all the details. For + example, after logging in, the application must create a new struct using the + access token provided. + """ + + require Logger + + @type t :: %__MODULE__{ + base_url: String.t(), + access_token: String.t(), + user_id: String.t(), + device_id: String.t(), + storage: Polyjuice.Client.Storage.t(), + test: boolean + } + + @enforce_keys [:base_url] + defstruct [ + :base_url, + :access_token, + :user_id, + :device_id, + :storage, + test: false + ] + + defimpl Polyjuice.Client.API do + def call(%{base_url: base_url, access_token: access_token, test: test}, endpoint) do + %Polyjuice.Client.Endpoint.HttpSpec{ + method: method, + headers: headers, + url: url, + body: body, + auth_required: auth_required, + stream_response: stream_response + } = Polyjuice.Client.Endpoint.Proto.http_spec(endpoint, base_url) + + Logger.debug("calling #{method} #{url}") + + if auth_required and access_token == nil do + {:error, :auth_required} + else + case :hackney.request( + # mod_esi doesn't like POST requests to a sub-path, so change POST + # to PUT when running tests + if(method == :post and test, do: :put, else: method), + url, + if access_token do + [{"Authorization", "Bearer #{access_token}"} | headers] + else + headers + end, + body, + [] + ) do + {:ok, status_code, resp_headers, client_ref} -> + Logger.debug("status code #{status_code}") + + Polyjuice.Client.Endpoint.Proto.transform_http_result( + endpoint, + status_code, + resp_headers, + if stream_response do + Polyjuice.Client.hackney_response_stream(client_ref) + else + {:ok, body} = :hackney.body(client_ref) + body + end + ) + + err -> + # anything else is an error -- return as-is + err + end + end + end + + def transaction_id(_) do + "#{Node.self()}_#{:erlang.system_time(:millisecond)}_#{:erlang.unique_integer()}" + end + + def sync_child_spec(_client, _listener, _opts \\ []) do + # do nothing + end + end + + @doc """ + Create a client. + + `opts` may contain: + - `access_token`: (required to make calls that require authorization) the + access token to use. + - `user_id`: (required by some endpoints) the ID of the user + - `device_id`: the device ID + - `storage`: (required by sync) the storage backend to use (see + `Polyjuice.Client.Storage`) + """ + @spec create(base_url :: String.t(), opts :: Keyword.t()) :: t() + def create(base_url, opts \\ []) when is_binary(base_url) do + %__MODULE__{ + base_url: + if(String.ends_with?(base_url, "/"), do: base_url, else: base_url <> "/") + |> URI.parse(), + access_token: Keyword.get(opts, :access_token), + user_id: Keyword.get(opts, :user_id), + device_id: Keyword.get(opts, :device_id), + storage: Keyword.get(opts, :storage), + test: Keyword.get(opts, :test, false) + } + end + + @doc """ + Log in with a password. + + `identifier` may be a single string (in which case it represents a username + -- either just the localpart or the full MXID), a tuple of the form + `{:email, "email@address"}`, a tuple of the form `{:phone, "country_code", + "phone_number"}`, or a map that is passed directly to the login endpoint. + + `opts` is a keyword list of options: + + - `device_id:` (string) the device ID to use + - `initial_device_display_name:` (string) the display name to use for the device + """ + @spec log_in_with_password( + client :: Polyjuice.Client.LowLevel.t(), + identifier :: String.t() | tuple() | map(), + password :: String.t(), + opts :: list() + ) :: {:ok, map()} | any + def log_in_with_password(client, identifier, password, opts \\ []) + when (is_binary(identifier) or is_tuple(identifier) or is_map(identifier)) and + is_binary(password) and is_list(opts) do + Polyjuice.Client.API.call( + client, + %Polyjuice.Client.Endpoint.PostLogin{ + type: "m.login.password", + identifier: Polyjuice.Client.make_login_identifier(identifier), + password: password, + device_id: Keyword.get(opts, :device_id), + initial_device_display_name: Keyword.get(opts, :initial_device_display_name) + } + ) + end + + @doc """ + Log out an existing session. + """ + @spec log_out(client :: Polyjuice.Client.LowLevel.t()) :: {:ok} | any + def log_out(client) do + Polyjuice.Client.API.call( + client, + %Polyjuice.Client.Endpoint.PostLogout{} + ) + end +end -- cgit v1.2.3