Merge branch 'feature/admin' into 'master'
Admin Closes #16 See merge request framasoft/mobilizon!90
This commit is contained in:
commit
b76b57c438
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
"""
|
"""
|
||||||
|
2
mix.lock
2
mix.lock
@ -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"},
|
||||||
|
@ -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
|
28
test/mobilizon/users/service/tools.exs
Normal file
28
test/mobilizon/users/service/tools.exs
Normal 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
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user