Merge branch 'feature/admin' into 'master'

Admin

Closes #16

See merge request framasoft/mobilizon!90
This commit is contained in:
Thomas Citharel 2019-03-07 14:23:48 +01:00
commit b76b57c438
10 changed files with 137 additions and 11 deletions

View File

@ -31,3 +31,13 @@ defmodule Mobilizon.Users.Service.Tools do
|> Base.url_encode64() |> Base.url_encode64()
end end
end end
defmodule Mobilizon.Users.Guards do
@moduledoc """
Guards for users
"""
defguard is_admin(role) when is_atom(role) and role == :administrator
defguard is_moderator(role) when is_atom(role) and role in [:administrator, :moderator]
end

View File

@ -1,3 +1,11 @@
import EctoEnum
defenum(Mobilizon.Users.UserRoleEnum, :user_role_type, [
:administrator,
:moderator,
:user
])
defmodule Mobilizon.Users.User do defmodule Mobilizon.Users.User do
@moduledoc """ @moduledoc """
Represents a local user Represents a local user
@ -12,7 +20,7 @@ defmodule Mobilizon.Users.User do
field(:email, :string) field(:email, :string)
field(:password_hash, :string) field(:password_hash, :string)
field(:password, :string, virtual: true) field(:password, :string, virtual: true)
field(:role, :integer, default: 0) field(:role, Mobilizon.Users.UserRoleEnum, default: :user)
has_many(:actors, Actor) has_many(:actors, Actor)
belongs_to(:default_actor, Actor) belongs_to(:default_actor, Actor)
field(:confirmed_at, :utc_datetime) field(:confirmed_at, :utc_datetime)

View File

@ -5,19 +5,18 @@ defmodule MobilizonWeb.Context do
@behaviour Plug @behaviour Plug
import Plug.Conn import Plug.Conn
require Logger alias Mobilizon.Users.User
def init(opts) do def init(opts) do
opts opts
end end
def call(conn, _) do def call(conn, _) do
case Guardian.Plug.current_resource(conn) do with %User{} = user <- Guardian.Plug.current_resource(conn) do
put_private(conn, :absinthe, %{context: %{current_user: user}})
else
nil -> nil ->
conn conn
user ->
put_private(conn, :absinthe, %{context: %{current_user: user}})
end end
end end
end end

View File

@ -11,6 +11,7 @@ defmodule MobilizonWeb.Guardian do
alias Mobilizon.Users alias Mobilizon.Users
alias Mobilizon.Users.User alias Mobilizon.Users.User
require Logger
def subject_for_token(%User{} = user, _claims) do def subject_for_token(%User{} = user, _claims) do
{:ok, "User:" <> to_string(user.id)} {:ok, "User:" <> to_string(user.id)}
@ -21,6 +22,8 @@ defmodule MobilizonWeb.Guardian do
end end
def resource_from_claims(%{"sub" => "User:" <> uid_str}) do def resource_from_claims(%{"sub" => "User:" <> uid_str}) do
Logger.debug(fn -> "Receiving claim for user #{uid_str}" end)
try do try do
case Integer.parse(uid_str) do case Integer.parse(uid_str) do
{uid, ""} -> {uid, ""} ->
@ -39,6 +42,8 @@ defmodule MobilizonWeb.Guardian do
end end
def after_encode_and_sign(resource, claims, token, _options) do def after_encode_and_sign(resource, claims, token, _options) do
Logger.debug(fn -> "after_encode_and_sign #{inspect(claims)}" end)
with {:ok, _} <- Guardian.DB.after_encode_and_sign(resource, claims["typ"], claims, token) do with {:ok, _} <- Guardian.DB.after_encode_and_sign(resource, claims["typ"], claims, token) do
{:ok, token} {:ok, token}
end end

View File

@ -6,6 +6,7 @@ defmodule MobilizonWeb.Resolvers.User do
alias Mobilizon.Users.User alias Mobilizon.Users.User
alias Mobilizon.{Actors, Users} alias Mobilizon.{Actors, Users}
alias Mobilizon.Users.Service.{ResetPassword, Activation} alias Mobilizon.Users.Service.{ResetPassword, Activation}
import Mobilizon.Users.Guards
require Logger require Logger
@doc """ @doc """
@ -32,14 +33,20 @@ defmodule MobilizonWeb.Resolvers.User do
def list_and_count_users( def list_and_count_users(
_parent, _parent,
%{page: page, limit: limit, sort: sort, direction: direction}, %{page: page, limit: limit, sort: sort, direction: direction},
_resolution %{
) do context: %{current_user: %User{role: role}}
}
)
when is_moderator(role) do
total = Task.async(&Users.count_users/0) total = Task.async(&Users.count_users/0)
elements = Task.async(fn -> Users.list_users(page, limit, sort, direction) end) elements = Task.async(fn -> Users.list_users(page, limit, sort, direction) end)
{:ok, %{total: Task.await(total), elements: Task.await(elements)}} {:ok, %{total: Task.await(total), elements: Task.await(elements)}}
end end
def list_and_count_users(_parent, _args, _resolution),
do: {:error, "You need to have admin access to list users"}
@doc """ @doc """
Login an user. Returns a token and the user Login an user. Returns a token and the user
""" """

View File

@ -55,7 +55,7 @@
"hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, "hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"http_sign": {:hex, :http_sign, "0.1.1", "b16edb83aa282892f3271f9a048c155e772bf36e15700ab93901484c55f8dd10", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "http_sign": {:hex, :http_sign, "0.1.1", "b16edb83aa282892f3271f9a048c155e772bf36e15700ab93901484c55f8dd10", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"httpoison": {:hex, :httpoison, "1.5.0", "71ae9f304bdf7f00e9cd1823f275c955bdfc68282bc5eb5c85c3a9ade865d68e", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "httpoison": {:hex, :httpoison, "1.5.0", "71ae9f304bdf7f00e9cd1823f275c955bdfc68282bc5eb5c85c3a9ade865d68e", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"icalendar": {:git, "git@framagit.org:tcit/icalendar.git", "7090ac1f72093c6178a67e167ebaed248f60dd64", []}, "icalendar": {:git, "https://framagit.org/tcit/icalendar", "7090ac1f72093c6178a67e167ebaed248f60dd64", []},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"}, "jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},

View File

@ -0,0 +1,41 @@
defmodule Mobilizon.Repo.Migrations.MoveUserRoleToEnum do
use Ecto.Migration
alias Mobilizon.Users.UserRoleEnum
def up do
UserRoleEnum.create_type()
alter table(:users) do
add(:role_tmp, UserRoleEnum.type(), default: "user")
end
execute("UPDATE users set role_tmp = 'user' where role = 0")
execute("UPDATE users set role_tmp = 'moderator' where role = 1")
execute("UPDATE users set role_tmp = 'administrator' where role = 2")
alter table(:users) do
remove(:role)
end
rename(table(:users), :role_tmp, to: :role)
end
def down do
alter table(:users) do
add(:role_tmp, :integer, default: 0)
end
execute("UPDATE users set role_tmp = 0 where role = 'user'")
execute("UPDATE users set role_tmp = 1 where role = 'moderator'")
execute("UPDATE users set role_tmp = 2 where role = 'administrator'")
alter table(:users) do
remove(:role)
end
UserRoleEnum.drop_type()
rename(table(:users), :role_tmp, to: :role)
end
end

View File

@ -0,0 +1,28 @@
defmodule Mobilizon.Users.Service.ToolsTest do
use Mobilizon.DataCase
import Mobilizon.Factory
setup do
user = insert(:user)
moderator = insert(:user, role: :moderator)
administrator = insert(:user, role: :administrator)
{:ok, user: user, moderator: moderator, administrator: administrator}
end
describe "test guards" do
import Mobilizon.Users.Guards
test "is_moderator/1 guard", %{user: user, moderator: moderator, administrator: administrator} do
refute is_moderator(user.role)
assert is_moderator(moderator.role)
assert is_moderator(administrator.role)
end
test "is_admin/1 guard", %{user: user, moderator: moderator, administrator: administrator} do
refute is_admin(user.role)
refute is_admin(moderator.role)
assert is_admin(administrator.role)
end
end
end

View File

@ -75,8 +75,33 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do
end end
describe "Resolver: List users" do describe "Resolver: List users" do
test "list_users/3 doesn't return anything with a non moderator user", context do
insert(:user, email: "riri@example.com", role: :moderator)
user = insert(:user, email: "fifi@example.com")
insert(:user, email: "loulou@example.com", role: :administrator)
query = """
{
users {
total,
elements {
email
}
}
}
"""
res =
context.conn
|> auth_conn(user)
|> get("/api", AbsintheHelpers.query_skeleton(query, "user"))
assert hd(json_response(res, 200)["errors"])["message"] ==
"You need to have admin access to list users"
end
test "list_users/3 returns a list of users", context do test "list_users/3 returns a list of users", context do
insert(:user, email: "riri@example.com") user = insert(:user, email: "riri@example.com", role: :moderator)
insert(:user, email: "fifi@example.com") insert(:user, email: "fifi@example.com")
insert(:user, email: "loulou@example.com") insert(:user, email: "loulou@example.com")
@ -93,6 +118,7 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do
res = res =
context.conn context.conn
|> auth_conn(user)
|> get("/api", AbsintheHelpers.query_skeleton(query, "user")) |> get("/api", AbsintheHelpers.query_skeleton(query, "user"))
assert json_response(res, 200)["errors"] == nil assert json_response(res, 200)["errors"] == nil
@ -119,6 +145,7 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do
res = res =
context.conn context.conn
|> auth_conn(user)
|> get("/api", AbsintheHelpers.query_skeleton(query, "user")) |> get("/api", AbsintheHelpers.query_skeleton(query, "user"))
assert json_response(res, 200)["errors"] == nil assert json_response(res, 200)["errors"] == nil
@ -142,6 +169,7 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do
res = res =
context.conn context.conn
|> auth_conn(user)
|> get("/api", AbsintheHelpers.query_skeleton(query, "user")) |> get("/api", AbsintheHelpers.query_skeleton(query, "user"))
assert json_response(res, 200)["errors"] == nil assert json_response(res, 200)["errors"] == nil

View File

@ -9,7 +9,7 @@ defmodule Mobilizon.Factory do
%Mobilizon.Users.User{ %Mobilizon.Users.User{
password_hash: "Jane Smith", password_hash: "Jane Smith",
email: sequence(:email, &"email-#{&1}@example.com"), email: sequence(:email, &"email-#{&1}@example.com"),
role: 0, role: :user,
confirmed_at: DateTime.utc_now() |> DateTime.truncate(:second), confirmed_at: DateTime.utc_now() |> DateTime.truncate(:second),
confirmation_sent_at: nil, confirmation_sent_at: nil,
confirmation_token: nil confirmation_token: nil