Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ docker-compose.yml
node_modules
.serena
.playwright-mcp

.DS_Store

18 changes: 17 additions & 1 deletion lib/mailgun_logger/roles/roles.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@ defmodule MailgunLogger.Roles do

@superuser_role "superuser"
@admin_role "admin"
# role met limited rechten
@member_role "member"

#########################################################

@default_actions ~w()

@admin_actions ~w(do_stuff) ++ @default_actions
#member kan enkel events checken
#w() maakt lijst van woorden splitsend op whitespace
@member_actions ~w(view_events)

@admin_actions ~w(do_stuff) ++ @member_actions ++ @default_actions

@superuser_actions ~w() ++ @admin_actions

Expand Down Expand Up @@ -55,6 +61,12 @@ defmodule MailgunLogger.Roles do
Enum.any?(roles, &can?(&1.name, action))
end

# rechten member rol
for action <- @member_actions do
action = String.to_atom(action)
def can?(@member_role, unquote(action)), do: true
end

for action <- @admin_actions do
action = String.to_atom(action)
def can?(@admin_role, unquote(action)), do: true
Expand All @@ -69,6 +81,8 @@ defmodule MailgunLogger.Roles do

def is?(%User{roles: roles}, :superuser), do: is(roles, "superuser")
def is?(%User{roles: roles}, :admin), do: is(roles, "admin")
# check user rol
def is?(%User{roles: roles}, :member), do: is(roles, "member")
def is?(_, _), do: raise("Roles.is/2 requires roles to be preloaded")

defp is(roles, role) when is_binary(role), do: Enum.map(roles, & &1.name) |> Enum.member?(role)
Expand All @@ -77,6 +91,8 @@ defmodule MailgunLogger.Roles do
def abilities(%User{roles: roles}), do: hd(roles) |> abilities()
def abilities(%Role{name: "admin"}), do: @admin_actions
def abilities(%Role{name: "superuser"}), do: @superuser_actions
# mogelijkheden vr member
def abilities(%Role{name: "member"}), do: @member_actions

def roles(%User{roles: roles}), do: Enum.map(roles, & &1.name)
end
3 changes: 2 additions & 1 deletion lib/mailgun_logger/seeder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ defmodule MailgunLogger.Seeder do

# alias MailgunLogger.UserRole

@roles [%Role{name: "superuser"}, %Role{name: "admin"}]
@roles [%Role{name: "superuser"}, %Role{name: "admin"}, %Role{name: "member"}]


def run do
Enum.each(@roles, &insert_if_new(&1))
Expand Down
12 changes: 12 additions & 0 deletions lib/mailgun_logger/users/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,18 @@ defmodule MailgunLogger.User do
put_change(changeset, :token, token)
end

@doc """
voeg rollen toe aan changeset +
bestaande rollen worden overwritten

@spec = params en :: return type
"""
@spec with_roles(Ecto.Changeset.t(), [Role.t()]) :: Ecto.Changeset.t()
def with_roles(%Ecto.Changeset{} = changeset, roles) do
put_assoc(changeset, :roles, roles)
end
# putassoc => associatie zette in changeset

@doc false
def full_name(nil), do: ""
def full_name(%User{lastname: nil, firstname: nil, email: nil}), do: ""
Expand Down
23 changes: 23 additions & 0 deletions lib/mailgun_logger/users/users.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule MailgunLogger.Users do

alias MailgunLogger.Repo
alias MailgunLogger.User
alias MailgunLogger.Roles # nodig voor rollen ophalen bij create/update user with roles


@type ecto_user() :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
Expand Down Expand Up @@ -158,6 +159,28 @@ defmodule MailgunLogger.Users do
|> Repo.update()
end

# maak nieuwe User aan met gegeven rollen
@spec create_user_with_roles(map(), [integer()]) :: ecto_user()
def create_user_with_roles(params, role_ids) do
roles = Roles.get_roles_by_id(role_ids)

%User{}
|> User.changeset(params)
|> User.with_roles(roles)
|> Repo.insert()
end

# update user en overschrijft rollen
@spec update_user_with_roles(User.t(), map(), [integer()]) :: ecto_user()
def update_user_with_roles(user, params, role_ids) do
roles = Roles.get_roles_by_id(role_ids)

user
|> User.update_changeset(params)
|> User.with_roles(roles)
|> Repo.update()
end

@spec delete_user(User.t()) :: ecto_user()
def delete_user(%User{} = user) do
Repo.delete(user)
Expand Down
75 changes: 61 additions & 14 deletions lib/mailgun_logger_web/controllers/user_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule MailgunLoggerWeb.UserController do

alias MailgunLogger.Users
alias MailgunLogger.User
alias MailgunLogger.Roles

def index(conn, _) do
users = Users.list_users()
Expand All @@ -11,42 +12,88 @@ defmodule MailgunLoggerWeb.UserController do

def new(conn, _) do
changeset = User.changeset(%User{})
render(conn, :new, changeset: changeset)
# laad alle beschikbare rollen voor het formulier
roles = Roles.list_roles()
render(conn, :new, changeset: changeset, roles: roles, selected_role_ids: [])
end

def create(conn, %{"user" => params}) do
case Users.create_user(params) do
{:ok, _} -> redirect(conn, to: Routes.user_path(conn, :index))
{:error, changeset} -> render(conn, :new, changeset: changeset)
role_ids = Map.get(params, "role_ids", [])
user_params = Map.delete(params, "role_ids")

case Users.create_user_with_roles(user_params, role_ids) do
{:ok, _} ->
redirect(conn, to: Routes.user_path(conn, :index))

{:error, changeset} ->
roles = Roles.list_roles()
render(conn, :new, changeset: changeset, roles: roles, selected_role_ids: role_ids)
end
end

def edit(conn, %{"id" => id}) do
user = Users.get_user!(id)
changeset = User.changeset(user)
render(conn, :edit, changeset: changeset, user: user)
# laad alle rollen en de huidige rollen van de gebruiker
roles = Roles.list_roles()
selected_role_ids = Enum.map(user.roles, & &1.id)
render(conn, :edit, changeset: changeset, user: user, roles: roles, selected_role_ids: selected_role_ids)
end

def update(conn, %{"id" => id, "user" => params}) do
current_user = conn.assigns.current_user
user = Users.get_user!(id)
role_ids = Map.get(params, "role_ids", [])
user_params = Map.delete(params, "role_ids")

case Users.update_user(user, params) do
{:ok, _} ->
redirect(conn, to: Routes.user_path(conn, :index))
# prevent dat user zijn eigen downgrade
if self_downgrade?(current_user, user, role_ids) do
changeset = User.changeset(user)
roles = Roles.list_roles()
selected_role_ids = Enum.map(user.roles, & &1.id)

{:error, changeset} ->
render(conn, :edit, changeset: changeset, user: user)
conn
|> put_flash(:error, "Je kan je eigen rol niet verlagen.")
|> render(:edit, changeset: changeset, user: user, roles: roles, selected_role_ids: selected_role_ids)
else
case Users.update_user_with_roles(user, user_params, role_ids) do
{:ok, _} ->
redirect(conn, to: Routes.user_path(conn, :index))

{:error, changeset} ->
roles = Roles.list_roles()
render(conn, :edit, changeset: changeset, user: user, roles: roles, selected_role_ids: role_ids)
end
end
end

def delete(conn, %{"id" => id}) do
{:ok, _} =
id
|> Users.get_user!()
|> Users.delete_user()
user = Users.get_user!(id)

# Verwijder eerst de rollen voor we de user deleten
{:ok, user} = Users.update_user_with_roles(user, %{}, [])
{:ok, _} = Users.delete_user(user)

conn
|> put_flash(:info, "user deleted successfully.")
|> redirect(to: Routes.user_path(conn, :index))
end

# check od user zijn eigen wilt downgrade
defp self_downgrade?(current_user, user, new_role_ids) do
if current_user.id == user.id do
elevated_roles = Enum.filter(user.roles, &(&1.name in ["admin", "superuser"]))
elevated_role_ids = Enum.map(elevated_roles, & &1.id)
new_role_ids_int = Enum.map(new_role_ids, &String.to_integer/1)


# true als er een verhoogde rol is die niet meer in de nieuwe rollen zit
Enum.any?(elevated_role_ids, &(&1 not in new_role_ids_int))
else

false

end

end
end
30 changes: 30 additions & 0 deletions lib/mailgun_logger_web/plugs/require_permission.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
defmodule MailgunLoggerWeb.Plugs.RequirePermission do
import Plug.Conn
import Phoenix.Controller
alias MailgunLogger.Roles
alias MailgunLoggerWeb.Router.Helpers, as: Routes


@moduledoc """
plug die checkt of huidige gebruiker de vereiste permission heeft
if false wordt gebruiker geredirect naar events page
reminder: plug (elixir) = middleware
"""

def init(action), do: action
def call(conn, action) do
user = conn.assigns[:current_user]

if user && Roles.can?(user, action) do
# user heeft toestemming, ga verder
conn
else
# geen permi -> redirect naar events
conn
|> put_flash(:error, "je hebt geen permissie tot deze pagina")
|> redirect(to: Routes.event_path(conn, :index))
|> halt()
end

end
end
31 changes: 27 additions & 4 deletions lib/mailgun_logger_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ defmodule MailgunLoggerWeb.Router do
plug(MailgunLoggerWeb.Plugs.Auth)
end

# access voor member en hoger (events bekijke)
pipeline :require_events_access do
plug(MailgunLoggerWeb.Plugs.RequirePermission, :view_events)
end


# access alleen voor admin en superuser
pipeline :require_admin_access do
plug(MailgunLoggerWeb.Plugs.RequirePermission, :do_stuff)
end

# Always except in prod
if Application.compile_env(:mailgun_logger, :env) == :dev do
forward("/sent_emails", Bamboo.SentEmailViewerPlug)
Expand Down Expand Up @@ -69,22 +80,34 @@ defmodule MailgunLoggerWeb.Router do
post("/", SetupController, :create_root)
end

# logout is toegankelijk voor alle users
# hoeft geen check voor :require_events_access
scope "/", MailgunLoggerWeb do
pipe_through([:browser, :auth])

get("/logout", AuthController, :logout)
end

# accesible voor member en boven
scope "/", MailgunLoggerWeb do
pipe_through([:browser, :auth, :require_events_access])

get("/", PageController, :index)
get("/non-affiliation", PageController, :non_affiliation)
resources("/events", EventController, only: [:index, :show])
get("/events/:id/stored_message", EventController, :stored_message)
resources("/accounts", AccountController, except: [:show])
get("/profile", ProfileController, :edit)
put("/profile", ProfileController, :update)
resources("/users", UserController, except: [:show])
end

get("/", PageController, :index)
# alleen toegankelijk voor superuser en amdin
scope "/", MailgunLoggerWeb do
pipe_through([:browser, :auth, :require_admin_access])

resources("/accounts", AccountController, except: [:show])
resources("/users", UserController, except: [:show])
post("/trigger-run", PageController, :trigger_run)
get("/stats", PageController, :stats)
get("/graphs", PageController, :graphs)
get("/non-affiliation", PageController, :non_affiliation)
end
end
20 changes: 17 additions & 3 deletions lib/mailgun_logger_web/templates/layout/app.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,16 @@
<ul>
<%= if assigns[:current_user] do %>
<li><.link href={Routes.event_path(@conn, :index)} class={active_link_class(@conn, MailgunLoggerWeb.EventController)}>Events</.link></li>
<li><.link href={Routes.page_path(@conn, :stats)} class={active_link_class(@conn, MailgunLoggerWeb.PageController, :index)}>Stats</.link></li>
<li><.link href={Routes.account_path(@conn, :index)} class={active_link_class(@conn, MailgunLoggerWeb.AccountController, :index)}>Accounts</.link></li>
<li><.link href={Routes.user_path(@conn, :index)} class={active_link_class(@conn, MailgunLoggerWeb.UserController, :index)}>Users</.link></li>

<%= if MailgunLogger.Roles.can?(@current_user, :do_stuff) do %>
<%# zichtbaar voor admin en superuser %>

<li><.link href={Routes.page_path(@conn, :stats)} class={active_link_class(@conn, MailgunLoggerWeb.PageController, :index)}>Stats</.link></li>
<li><.link href={Routes.account_path(@conn, :index)} class={active_link_class(@conn, MailgunLoggerWeb.AccountController, :index)}>Accounts</.link></li>
<li><.link href={Routes.user_path(@conn, :index)} class={active_link_class(@conn, MailgunLoggerWeb.UserController, :index)}>Users</.link></li>

<% end %>

<li class="nav-right">
<details class="nav-dropdown">
<summary class="nav-dropdown-toggle">
Expand All @@ -63,6 +70,13 @@
</div>
</header>
<main>
<%# flash meldingen (info en error) %>
<%= if Phoenix.Flash.get(@flash, :info) do %>
<div class="alert alert-info" role="info"><%= Phoenix.Flash.get(@flash, :info) %></div>
<% end %>
<%= if Phoenix.Flash.get(@flash, :error) do %>
<div class="alert alert-danger" role="alert"><%= Phoenix.Flash.get(@flash, :error) %></div>
<% end %>
<%= @inner_content %>
</main>
<footer>
Expand Down
2 changes: 1 addition & 1 deletion lib/mailgun_logger_web/templates/user/edit.html.heex
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<h1>Edit user</h1>
<%= render "form.html", action: Routes.user_path(@conn, :update, @user), changeset: @changeset, new?: false, flash: @flash %>
<%= render "form.html", action: Routes.user_path(@conn, :update, @user), changeset: @changeset, new?: false, flash: @flash, roles: @roles, selected_role_ids: @selected_role_ids %>
<br />
<.link href={Routes.user_path(@conn, :delete, @user)} method="delete" data-confirm="Are you sure?" class="btn btn-danger btn-sm">delete</.link>
25 changes: 25 additions & 0 deletions lib/mailgun_logger_web/templates/user/form.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,31 @@
options={[{"System", "system"}, {"Light", "light"}, {"Dark", "dark"}]}
/>

<%# keuze enkel visible voor admin en superuser %>
<%= if assigns[:roles] do %>
<div class="form-group">
<br>
<label style="text-decoration: underline;">Rollen</label>

<%= for role <- @roles do %>
<div>
<label for={"role-#{role.id}"} style="display: inline-flex; align-items: center; gap: 0.5rem;">
<input
type="checkbox"
name="user[role_ids][]"
value={role.id}
id={"role-#{role.id}"}
checked={role.id in @selected_role_ids}
/>
<span><%= role.name %></span>
</label>
</div>

<% end %>
</div>

<% end %>

<div class="form-group mt-5">
{submit("submit", class: "btn btn-primary")}
</div>
Expand Down
2 changes: 1 addition & 1 deletion lib/mailgun_logger_web/templates/user/new.html.heex
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
<h1>New user</h1>
<%= render "form.html", action: Routes.user_path(@conn, :create), changeset: @changeset, flash: @flash, new?: true %>
<%= render "form.html", action: Routes.user_path(@conn, :create), changeset: @changeset, flash: @flash, new?: true, roles: @roles, selected_role_ids: @selected_role_ids %>