summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPierre de Lacroix <pierre@pdelacroix.com>2021-03-03 18:39:41 +0100
committerPierre de Lacroix <pierre@pdelacroix.com>2021-03-03 18:39:41 +0100
commit4ee4fcebc5f718750c2d80fea651459502a8963d (patch)
treee61a1f5e12fd53eb957306ee249cc7c8bc0dfa57
parentfix Polyjuice.Client.LowLevel.t (diff)
parenthotfix to squash (diff)
Merge branch 'post_register' into kazarma
-rw-r--r--lib/polyjuice/client.ex106
-rw-r--r--lib/polyjuice/client/endpoint/post_register.ex101
-rw-r--r--lib/polyjuice/client/low_level.ex40
-rw-r--r--test/polyjuice/client/endpoint/post_register_test.exs92
-rw-r--r--test/polyjuice/client/low_level_test.exs72
-rw-r--r--test/polyjuice/client_test.exs167
6 files changed, 578 insertions, 0 deletions
diff --git a/lib/polyjuice/client.ex b/lib/polyjuice/client.ex
index 361914d..f027fd0 100644
--- a/lib/polyjuice/client.ex
+++ b/lib/polyjuice/client.ex
@@ -723,6 +723,112 @@ defmodule Polyjuice.Client do
end
end
+ @type register_opts() :: [
+ kind: :guest | :user,
+ username: String.t() | nil,
+ password: String.t() | nil,
+ device_id: String.t() | nil,
+ initial_device_display_name: String.t() | nil,
+ inhibit_login: boolean()
+ ]
+
+ @doc """
+ Register a user.
+
+ `opts` is a keyword list of options:
+
+ - `username:` (string) the basis for the localpart of the desired Matrix ID
+ - `password:` (string) the desired password for the account
+ - `device_id:` (string) the device ID to use
+ - `initial_device_display_name:` (string) the display name to use for the device
+ - `inhibit_login:` (boolean) don't login after successful register
+ - `kind:` (atom) kind of account to register. Defaults to user. One of: ["guest", "user"]
+ """
+ @spec register(
+ client :: Polyjuice.Client.t(),
+ opts :: register_opts()
+ ) :: {:ok, map()} | any
+ def register(%Polyjuice.Client{} = client, opts \\ []) do
+ case Polyjuice.Client.API.call(
+ client,
+ %Polyjuice.Client.Endpoint.PostRegister{
+ auth: %{type: "m.login.dummy"},
+ username: Keyword.get(opts, :username),
+ password: Keyword.get(opts, :password),
+ kind: Keyword.get(opts, :kind),
+ device_id: Keyword.get(opts, :device_id),
+ initial_device_display_name: Keyword.get(opts, :initial_device_display_name),
+ inhibit_login: Keyword.get(opts, :inhibit_login, false)
+ }
+ ) do
+ ret =
+ {:ok,
+ %{"access_token" => access_token, "user_id" => user_id, "device_id" => device_id} =
+ http_ret} ->
+ GenServer.cast(
+ client.pid,
+ {:set, %{access_token: access_token, user_id: user_id, device_id: device_id}}
+ )
+
+ # do not send logged_in event if the inhibit_login option is set to true
+ unless Keyword.get(opts, :inhibit_login) == true do
+ if client.opts.storage do
+ Polyjuice.Client.Storage.kv_put(
+ client.opts.storage,
+ "ca.uhoreg.polyjuice",
+ "access_token",
+ access_token
+ )
+
+ Polyjuice.Client.Storage.kv_put(
+ client.opts.storage,
+ "ca.uhoreg.polyjuice",
+ "user_id",
+ user_id
+ )
+
+ Polyjuice.Client.Storage.kv_put(
+ client.opts.storage,
+ "ca.uhoreg.polyjuice",
+ "device_id",
+ device_id
+ )
+ end
+
+ if client.opts.handler do
+ Polyjuice.Client.Handler.handle(
+ client.opts.handler,
+ :logged_in,
+ {user_id, device_id, Map.drop(http_ret, ["user_id", "device_id"])}
+ )
+ end
+
+ if client.opts.sync && client.opts.handler do
+ # make sure we don't already have a sync process running
+ kill_sync(client.id)
+
+ supervisor_name = process_name(client.id, :supervisor)
+
+ DynamicSupervisor.start_child(
+ supervisor_name,
+ sync_child_spec(
+ client.base_url,
+ client.id,
+ client.pid,
+ client.hackney_opts,
+ client.opts
+ )
+ )
+ end
+ end
+
+ ret
+
+ ret ->
+ ret
+ end
+ end
+
@doc false
def kill_sync(id) do
supervisor_name = process_name(id, :supervisor)
diff --git a/lib/polyjuice/client/endpoint/post_register.ex b/lib/polyjuice/client/endpoint/post_register.ex
new file mode 100644
index 0000000..df10a8c
--- /dev/null
+++ b/lib/polyjuice/client/endpoint/post_register.ex
@@ -0,0 +1,101 @@
+# Copyright 2019 Hubert Chathi <hubert@uhoreg.ca>
+#
+# 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.Endpoint.PostRegister do
+ @moduledoc """
+ Register a user
+
+ https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-register
+ """
+
+ @type t :: %__MODULE__{
+ kind: :guest | :user,
+ auth: %{type: String.t(), session: String.t() | nil},
+ username: String.t() | nil,
+ password: String.t() | nil,
+ device_id: String.t() | nil,
+ initial_device_display_name: String.t() | nil,
+ inhibit_login: boolean()
+ }
+
+ @enforce_keys [:auth]
+ defstruct [
+ :kind,
+ :auth,
+ :username,
+ :password,
+ :device_id,
+ :initial_device_display_name,
+ :inhibit_login
+ ]
+
+ defimpl Polyjuice.Client.Endpoint.Proto do
+ def http_spec(%Polyjuice.Client.Endpoint.PostRegister{
+ kind: kind,
+ auth: auth,
+ username: username,
+ password: password,
+ device_id: device_id,
+ initial_device_display_name: initial_device_display_name,
+ inhibit_login: inhibit_login
+ }) do
+ body =
+ [
+ if(auth != nil, do: [{"auth", auth}], else: []),
+ if(username != nil, do: [{"username", username}], else: []),
+ if(password != nil, do: [{"password", password}], else: []),
+ if(device_id != nil, do: [{"device_id", device_id}], else: []),
+ if(inhibit_login != nil,
+ do: [{"inhibit_login", Atom.to_string(inhibit_login)}],
+ else: []
+ ),
+ if(initial_device_display_name != nil,
+ do: [{"initial_device_display_name", initial_device_display_name}],
+ else: []
+ )
+ ]
+ |> Enum.concat()
+ |> Map.new()
+ |> Jason.encode_to_iodata!()
+
+ query = [
+ kind:
+ if kind == nil do
+ "user"
+ else
+ Atom.to_string(kind)
+ end
+ ]
+
+ Polyjuice.Client.Endpoint.HttpSpec.post(
+ :r0,
+ "register",
+ body: body,
+ query: query,
+ auth_required: false
+ )
+ end
+
+ def transform_http_result(req, status_code, resp_headers, body) do
+ Polyjuice.Client.Endpoint.parse_response(req, status_code, resp_headers, body)
+ end
+ end
+
+ # defimpl Polyjuice.Client.Endpoint.BodyParser do
+ # def parse(_req, parsed) do
+ # {:ok, Map.get(parsed, "user_id"), Map.get(parsed, "access_token"),
+ # Map.get(parsed, "device_id")}
+ # end
+ # end
+end
diff --git a/lib/polyjuice/client/low_level.ex b/lib/polyjuice/client/low_level.ex
index 8f963e7..c4329c0 100644
--- a/lib/polyjuice/client/low_level.ex
+++ b/lib/polyjuice/client/low_level.ex
@@ -212,4 +212,44 @@ defmodule Polyjuice.Client.LowLevel do
%Polyjuice.Client.Endpoint.PostLogout{}
)
end
+
+ @type register_opts() :: [
+ kind: :guest | :user,
+ username: String.t() | nil,
+ password: String.t() | nil,
+ device_id: String.t() | nil,
+ initial_device_display_name: String.t() | nil,
+ inhibit_login: boolean()
+ ]
+
+ @doc """
+ Register a user.
+
+ `opts` is a keyword list of options:
+
+ - `username:` (string) the basis for the localpart of the desired Matrix ID
+ - `password:` (string) the desired password for the account
+ - `device_id:` (string) the device ID to use
+ - `initial_device_display_name:` (string) the display name to use for the device
+ - `inhibit_login:` (boolean) don't login after successful register
+ - `kind:` (atom) kind of account to register. Defaults to user. One of: ["guest", "user"]
+ """
+ @spec register(
+ client :: Polyjuice.Client.LowLevel.t(),
+ opts :: register_opts()
+ ) :: {:ok, map()} | any
+ def register(client, opts \\ []) do
+ Polyjuice.Client.API.call(
+ client,
+ %Polyjuice.Client.Endpoint.PostRegister{
+ kind: Keyword.get(opts, :kind, :user),
+ auth: %{type: "m.login.dummy"},
+ username: Keyword.get(opts, :username),
+ password: Keyword.get(opts, :password),
+ device_id: Keyword.get(opts, :device_id),
+ initial_device_display_name: Keyword.get(opts, :initial_device_display_name),
+ inhibit_login: Keyword.get(opts, :inhibit_login, false)
+ }
+ )
+ end
end
diff --git a/test/polyjuice/client/endpoint/post_register_test.exs b/test/polyjuice/client/endpoint/post_register_test.exs
new file mode 100644
index 0000000..a7eda85
--- /dev/null
+++ b/test/polyjuice/client/endpoint/post_register_test.exs
@@ -0,0 +1,92 @@
+# Copyright 2019 Hubert Chathi <hubert@uhoreg.ca>
+#
+# 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.Endpoint.PostRegisterTest do
+ use ExUnit.Case
+
+ test "POST register" do
+ endpoint = %Polyjuice.Client.Endpoint.PostRegister{
+ kind: :user,
+ auth: %{
+ "type" => "m.login.dummy"
+ },
+ username: "alice",
+ password: "12345",
+ device_id: "ABCDEF",
+ initial_device_display_name: "test_device",
+ inhibit_login: false
+ }
+
+ http_spec = Polyjuice.Client.Endpoint.Proto.http_spec(endpoint)
+
+ assert %{http_spec | body: nil} == %Polyjuice.Client.Endpoint.HttpSpec{
+ auth_required: false,
+ body: nil,
+ headers: [
+ {"Accept", "application/json"},
+ {"Accept-Encoding", "gzip, deflate"},
+ {"Content-Type", "application/json"}
+ ],
+ method: :post,
+ query: [kind: "user"],
+ path: "_matrix/client/r0/register"
+ }
+
+ assert Jason.decode!(http_spec.body) == %{
+ "password" => "12345",
+ "auth" => %{"type" => "m.login.dummy"},
+ "device_id" => "ABCDEF",
+ "inhibit_login" => "false",
+ "initial_device_display_name" => "test_device",
+ "username" => "alice"
+ }
+
+ assert Polyjuice.Client.Endpoint.Proto.transform_http_result(
+ endpoint,
+ 200,
+ [{"Content-Type", "application/json"}],
+ ~s(
+ {"user_id":"@alice:example.com",
+ "access_token":"1234567890",
+ "device_id":"ABCDEF",
+ "well_known":
+ {"m.homeserver":
+ {"base_url":"https://example.com"},
+ "m.identity_server":
+ {"base_url":"https://example.com"}
+ }
+ }
+ )
+ ) == {
+ :ok,
+ %{
+ "access_token" => "1234567890",
+ "device_id" => "ABCDEF",
+ "user_id" => "@alice:example.com",
+ "well_known" => %{
+ "m.homeserver" => %{"base_url" => "https://example.com"},
+ "m.identity_server" => %{"base_url" => "https://example.com"}
+ }
+ }
+ }
+
+ assert Polyjuice.Client.Endpoint.Proto.transform_http_result(
+ endpoint,
+ 500,
+ [],
+ "Aaah!"
+ ) ==
+ {:error, 500, %{"body" => "Aaah!", "errcode" => "CA_UHOREG_POLYJUICE_BAD_RESPONSE"}}
+ end
+end
diff --git a/test/polyjuice/client/low_level_test.exs b/test/polyjuice/client/low_level_test.exs
index 7840e83..2c4b942 100644
--- a/test/polyjuice/client/low_level_test.exs
+++ b/test/polyjuice/client/low_level_test.exs
@@ -171,4 +171,76 @@ defmodule Polyjuice.Client.LowLevelTest do
File.rm_rf(tmpdir)
end
end
+
+ test "register" do
+ {:ok, tmpdir} = TestUtil.mktmpdir("register-")
+
+ try do
+ tmpdir_charlist = to_charlist(tmpdir)
+
+ :inets.start()
+
+ {:ok, httpd_pid} =
+ :inets.start(
+ :httpd,
+ port: 0,
+ server_name: 'sync.test',
+ server_root: tmpdir_charlist,
+ document_root: tmpdir_charlist,
+ bind_address: {127, 0, 0, 1},
+ modules: [:mod_esi],
+ erl_script_alias: {'', [Polyjuice.ClientTest.Httpd]}
+ )
+
+ port = :httpd.info(httpd_pid) |> Keyword.fetch!(:port)
+
+ client =
+ Polyjuice.Client.LowLevel.create(
+ "http://127.0.0.1:#{port}/Elixir.Polyjuice.ClientTest.Httpd/",
+ test: true
+ )
+
+ opts = [username: "alice", password: "password", device_id: "foo"]
+
+ assert Polyjuice.Client.LowLevel.register(
+ client,
+ opts
+ ) ==
+ {:ok,
+ %{
+ "access_token" => "m.id.user_login",
+ "device_id" => "foo",
+ "user_id" => "@alice:example.org"
+ }}
+
+ # Test error when registering if username is taken
+
+ {:ok, httpd_pid} =
+ :inets.start(
+ :httpd,
+ port: 0,
+ server_name: 'sync.test',
+ server_root: tmpdir_charlist,
+ document_root: tmpdir_charlist,
+ bind_address: {127, 0, 0, 1},
+ modules: [:mod_esi],
+ erl_script_alias: {'', [Polyjuice.ClientTest.Httpd.UserAlreadyTaken]}
+ )
+
+ port = :httpd.info(httpd_pid) |> Keyword.fetch!(:port)
+
+ client =
+ Polyjuice.Client.LowLevel.create(
+ "http://127.0.0.1:#{port}/Elixir.Polyjuice.ClientTest.Httpd.UserAlreadyTaken/",
+ test: true
+ )
+
+ assert {:error, 400, %{"errcode" => "M_USER_IN_USE", "error" => _}} =
+ Polyjuice.Client.LowLevel.register(client, opts)
+
+ :inets.stop(:httpd, httpd_pid)
+ after
+ File.rm_rf(tmpdir)
+ end
+ end
end
diff --git a/test/polyjuice/client_test.exs b/test/polyjuice/client_test.exs
index aad44a3..a87fa4a 100644
--- a/test/polyjuice/client_test.exs
+++ b/test/polyjuice/client_test.exs
@@ -44,6 +44,9 @@ defmodule Polyjuice.ClientTest do
"client/r0/logout" ->
handle_logout(session_id, env, input)
+ "client/r0/register" ->
+ handle_register(session_id, env, input)
+
_ ->
:mod_esi.deliver(
session_id,
@@ -70,6 +73,43 @@ defmodule Polyjuice.ClientTest do
'Content-Type: application/json\r\n\r\n{}'
)
end
+
+ defp handle_register(session_id, _env, {_, input}) do
+ inhibit_login = to_string(input) |> Jason.decode!() |> Map.get("inhibit_login")
+ username = to_string(input) |> Jason.decode!() |> Map.get("username")
+
+ response =
+ if inhibit_login == "true" do
+ 'Content-Type: application/json\r\n\r\n{"user_id":"@#{username}:example.org"}'
+ else
+ 'Content-Type: application/json\r\n\r\n{"user_id":"@#{username}:example.org","access_token":"m.id.user_login","device_id":"foo"}'
+ end
+
+ :mod_esi.deliver(
+ session_id,
+ response
+ )
+ end
+ end
+
+ defmodule Httpd.LoggedOut do
+ # just tells the user that they were logged out, no matter what
+ def _matrix(session_id, _env, _input) do
+ :mod_esi.deliver(
+ session_id,
+ 'Status: 401 Unauthorized\r\nContent-Type: application/json\r\n\r\n{"errcode":"M_UNKNOWN_TOKEN","error":"Unknown token"}'
+ )
+ end
+ end
+
+ defmodule Httpd.UserAlreadyTaken do
+ # just tells the user that the user_id is already taken, no matter what
+ def _matrix(session_id, _env, _input) do
+ :mod_esi.deliver(
+ session_id,
+ 'Status: 400 Unauthorized\r\nContent-Type: application/json\r\n\r\n{"errcode":"M_USER_IN_USE","error":"User ID already taken."}'
+ )
+ end
end
test "transaction_id is unique" do
@@ -430,4 +470,131 @@ defmodule Polyjuice.ClientTest do
assert Polyjuice.Client.API.get_user_and_device(client) == {"@alice:example.org", "DEVICEID"}
end
+
+ test "register" do
+ {:ok, tmpdir} = TestUtil.mktmpdir("register-")
+
+ storage = Polyjuice.Client.Storage.Ets.open()
+
+ try do
+ tmpdir_charlist = to_charlist(tmpdir)
+
+ :inets.start()
+
+ {:ok, httpd_pid} =
+ :inets.start(
+ :httpd,
+ port: 0,
+ server_name: 'sync.test',
+ server_root: tmpdir_charlist,
+ document_root: tmpdir_charlist,
+ bind_address: {127, 0, 0, 1},
+ modules: [:mod_esi],
+ erl_script_alias: {'', [Polyjuice.ClientTest.Httpd]}
+ )
+
+ port = :httpd.info(httpd_pid) |> Keyword.fetch!(:port)
+
+ {:ok, client_pid} =
+ Polyjuice.Client.start_link(
+ "http://127.0.0.1:#{port}/Elixir.Polyjuice.ClientTest.Httpd/",
+ access_token: nil,
+ sync: false,
+ storage: storage,
+ handler: self(),
+ test: true
+ )
+
+ client = Polyjuice.Client.get_client(client_pid)
+
+ opts = [username: "alice", password: "password", device_id: "foo"]
+
+ assert Polyjuice.Client.register(client, opts) ==
+ {:ok,
+ %{
+ "access_token" => "m.id.user_login",
+ "device_id" => "foo",
+ "user_id" => "@alice:example.org"
+ }}
+
+ assert GenServer.call(client.pid, :get_state) == %{
+ access_token: "m.id.user_login",
+ user_id: "@alice:example.org",
+ device_id: "foo"
+ }
+
+ assert_receive({:polyjuice_client, :logged_in, {"@alice:example.org", "foo", _}})
+
+ assert Polyjuice.Client.Storage.kv_get(storage, "ca.uhoreg.polyjuice", "access_token") ==
+ "m.id.user_login"
+
+ assert Polyjuice.Client.Storage.kv_get(storage, "ca.uhoreg.polyjuice", "user_id") ==
+ "@alice:example.org"
+
+ assert Polyjuice.Client.Storage.kv_get(storage, "ca.uhoreg.polyjuice", "device_id") == "foo"
+
+ Polyjuice.Client.log_out(client)
+
+ # Test inhibit_login: :true
+ opts = [
+ username: "bob",
+ password: "password",
+ device_id: "foo",
+ inhibit_login: true
+ ]
+
+ assert Polyjuice.Client.register(client, opts) ==
+ {:ok,
+ %{
+ "user_id" => "@bob:example.org"
+ }}
+
+ assert Polyjuice.Client.Storage.kv_get(storage, "ca.uhoreg.polyjuice", "access_token") ==
+ nil
+
+ assert Polyjuice.Client.Storage.kv_get(storage, "ca.uhoreg.polyjuice", "user_id") == nil
+ assert Polyjuice.Client.Storage.kv_get(storage, "ca.uhoreg.polyjuice", "device_id") == nil
+
+ assert GenServer.call(client.pid, :get_state) |> Map.get(:access_token) == nil
+
+ # Test error when registering if username is taken
+ {:ok, httpd_pid} =
+ :inets.start(
+ :httpd,
+ port: 0,
+ server_name: 'sync.test',
+ server_root: tmpdir_charlist,
+ document_root: tmpdir_charlist,
+ bind_address: {127, 0, 0, 1},
+ modules: [:mod_esi],
+ erl_script_alias: {'', [Polyjuice.ClientTest.Httpd.UserAlreadyTaken]}
+ )
+
+ port = :httpd.info(httpd_pid) |> Keyword.fetch!(:port)
+
+ {:ok, client_pid} =
+ Polyjuice.Client.start_link(
+ "http://127.0.0.1:#{port}/Elixir.Polyjuice.ClientTest.Httpd.UserAlreadyTaken/",
+ access_token: nil,
+ sync: false,
+ storage: storage,
+ handler: self(),
+ test: true
+ )
+
+ client = Polyjuice.Client.get_client(client_pid)
+
+ opts = [username: "alice", password: "password", device_id: "foo"]
+
+ assert {:error, 400, %{"errcode" => "M_USER_IN_USE", "error" => _}} =
+ Polyjuice.Client.register(client, opts)
+
+ Polyjuice.Client.API.stop(client)
+
+ :inets.stop(:httpd, httpd_pid)
+ after
+ Polyjuice.Client.Storage.close(storage)
+ File.rm_rf(tmpdir)
+ end
+ end
end