Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Protect Routes #292

Merged
merged 45 commits into from
Jul 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
6e6b893
Initial role based routing
joaodiaslobo Jul 5, 2023
582f36e
Add membership status to conn assigns to retrieve role and protect ro…
joaodiaslobo Jul 6, 2023
80b03ce
Add organization parameter to department routes
joaodiaslobo Jul 8, 2023
75bafae
Initial role based routing
joaodiaslobo Jul 5, 2023
feebe5a
Add membership status to conn assigns to retrieve role and protect ro…
joaodiaslobo Jul 6, 2023
237d1ab
Add organization parameter to department routes
joaodiaslobo Jul 8, 2023
75dff21
Merge branch 'jl/protect-routes' of github.com:cesium/atomic into jl/…
joaodiaslobo Jul 9, 2023
a849e37
Fix issues regarding previous rebase
joaodiaslobo Jul 9, 2023
3a558fb
Check for parameters mismatch and trigger 404
joaodiaslobo Jul 9, 2023
075b5c0
Add organization parameter to partnership routes
joaodiaslobo Jul 10, 2023
f07ca2b
Change organization routes id parameter's name
joaodiaslobo Jul 10, 2023
4956113
Change membership routes and detect id mismatches
joaodiaslobo Jul 11, 2023
9c43972
Remove duplicate routes
joaodiaslobo Jul 11, 2023
756263e
Add organization_id param to activity routes
joaodiaslobo Jul 14, 2023
d650934
Filter activities by organization
joaodiaslobo Jul 14, 2023
b9539d5
Add organization parameter to speaker routes
joaodiaslobo Jul 14, 2023
a475878
Change board routes and check for id mismatches
joaodiaslobo Jul 14, 2023
3cb3da0
Initial role based routing
joaodiaslobo Jul 5, 2023
31d0e56
Add membership status to conn assigns to retrieve role and protect ro…
joaodiaslobo Jul 6, 2023
f7a275b
Add organization parameter to department routes
joaodiaslobo Jul 8, 2023
b528b79
Initial role based routing
joaodiaslobo Jul 5, 2023
7dcd54a
Add membership status to conn assigns to retrieve role and protect ro…
joaodiaslobo Jul 6, 2023
02423dc
Add organization parameter to department routes
joaodiaslobo Jul 8, 2023
6f51255
Fix issues regarding previous rebase
joaodiaslobo Jul 9, 2023
43fc3f9
Check for parameters mismatch and trigger 404
joaodiaslobo Jul 9, 2023
015b71e
Add organization parameter to partnership routes
joaodiaslobo Jul 10, 2023
161f226
Change organization routes id parameter's name
joaodiaslobo Jul 10, 2023
8f6d8cd
Change membership routes and detect id mismatches
joaodiaslobo Jul 11, 2023
a4205fc
Remove duplicate routes
joaodiaslobo Jul 11, 2023
bfad6e5
Add organization_id param to activity routes
joaodiaslobo Jul 14, 2023
c35bfb3
Filter activities by organization
joaodiaslobo Jul 14, 2023
7dc2c24
Add organization parameter to speaker routes
joaodiaslobo Jul 14, 2023
aaf5d8c
Change board routes and check for id mismatches
joaodiaslobo Jul 14, 2023
a318a7b
Merge branch 'jl/protect-routes' of github.com:cesium/atomic into jl/…
joaodiaslobo Jul 14, 2023
cba4774
Run formatter
joaodiaslobo Jul 14, 2023
1c1187a
Solve warnings
joaodiaslobo Jul 14, 2023
b4ce9fc
Update routes and add authorization
joaodiaslobo Jul 17, 2023
e94d326
Keep organization state trough routes
joaodiaslobo Jul 19, 2023
b155ca7
Fix tests
MarioRodrigues10 Jul 23, 2023
a7f3908
Fix warnings
MarioRodrigues10 Jul 23, 2023
0825507
Add some improvements
MarioRodrigues10 Jul 25, 2023
290bcbe
Add field default_organization_id
MarioRodrigues10 Jul 26, 2023
5edc4eb
Solve minor root.html.heex problem
MarioRodrigues10 Jul 26, 2023
f27aafb
Implement suggestions
MarioRodrigues10 Jul 27, 2023
b710af3
Fix function
MarioRodrigues10 Jul 28, 2023
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
9 changes: 9 additions & 0 deletions lib/atomic/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,9 @@ defmodule Atomic.Accounts do
"""
def get_user_by_session_token(token) do
{:ok, query} = UserToken.verify_session_token_query(token)

Repo.one(query)
|> Repo.preload(:organizations)
end

@doc """
Expand Down Expand Up @@ -424,4 +426,11 @@ defmodule Atomic.Accounts do
|> Course.changeset(attrs)
|> Repo.insert()
end

@doc """
Gets the user's organizations
"""
def get_user_organizations(user) do
Repo.all(Ecto.assoc(user, :organizations))
end
end
3 changes: 2 additions & 1 deletion lib/atomic/accounts/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ defmodule Atomic.Accounts.User do
alias Atomic.Uploaders.ProfilePicture

@required_fields ~w(email password role name)a
@optional_fields ~w(course_id)a
@optional_fields ~w(course_id default_organization_id)a

@roles ~w(admin student)a

Expand All @@ -20,6 +20,7 @@ defmodule Atomic.Accounts.User do
field :password, :string, virtual: true, redact: true
field :hashed_password, :string, redact: true
field :confirmed_at, :naive_datetime
belongs_to :default_organization, Organization

belongs_to :course, Course
field :profile_picture, ProfilePicture.Type
Expand Down
28 changes: 28 additions & 0 deletions lib/atomic/activities.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ defmodule Atomic.Activities do
|> Repo.all()
end

def list_activities_by_organization_id(organization_id, opts) do
Activity
|> apply_filters(opts)
|> join(:inner, [a], d in assoc(a, :departments))
|> where([a, d], d.organization_id == ^organization_id)
|> select([a, _d], a)
|> Repo.all()
end

@doc """
Gets a single activity.

Expand All @@ -41,6 +50,12 @@ defmodule Atomic.Activities do
|> Repo.preload(preloads)
end

def get_activity_organizations!(activity, _preloads \\ []) do
departments = Map.get(activity, :departments, [])

Enum.map(departments, & &1.organization_id)
end

alias Atomic.Activities.Enrollment

def is_participating?(activity_id, user_id) do
Expand Down Expand Up @@ -389,6 +404,19 @@ defmodule Atomic.Activities do
Repo.all(Speaker)
end

@doc """
Returns the list of speakers belonging to an organization.

## Examples

iex> list_speakers_by_organization_id(99d7c9e5-4212-4f59-a097-28aaa33c2621)
[%Speaker{}, ...]

"""
def list_speakers_by_organization_id(id) do
Repo.all(from s in Speaker, where: s.organization_id == ^id)
end

def get_speakers(nil), do: []

def get_speakers(ids) do
Expand Down
11 changes: 9 additions & 2 deletions lib/atomic/activities/speaker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,25 @@ defmodule Atomic.Activities.Speaker do
use Atomic.Schema
alias Atomic.Activities.Activity
alias Atomic.Activities.ActivitySpeaker
alias Atomic.Organizations.Organization

@required_fields ~w(name bio organization_id)a

schema "speakers" do
field :bio, :string
field :name, :string

many_to_many :activities, Activity, join_through: ActivitySpeaker, on_replace: :delete

belongs_to :organization, Organization, on_replace: :delete_if_exists

timestamps()
end

@doc false
def changeset(speaker, attrs) do
speaker
|> cast(attrs, [:name, :bio])
|> validate_required([:name, :bio])
|> cast(attrs, @required_fields)
|> validate_required(@required_fields)
end
end
13 changes: 13 additions & 0 deletions lib/atomic/departments.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ defmodule Atomic.Departments do
Repo.all(Department)
end

@doc """
Returns the list of departments belonging to an organization.

## Examples

iex> list_departments_by_organization_id(99d7c9e5-4212-4f59-a097-28aaa33c2621)
[%Department{}, ...]

"""
def list_departments_by_organization_id(id) do
Repo.all(from d in Department, where: d.organization_id == ^id)
end

def get_departments(nil), do: []

def get_departments(ids) do
Expand Down
31 changes: 25 additions & 6 deletions lib/atomic/organizations.ex
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,15 @@ defmodule Atomic.Organizations do
|> Repo.exists?()
end

def get_role(user_id, organization_id) do
membership =
Membership
|> where([m], m.user_id == ^user_id and m.organization_id == ^organization_id)
|> Repo.one()

membership[:role]
end

@doc """
Gets a single membership.

Expand Down Expand Up @@ -247,12 +256,22 @@ defmodule Atomic.Organizations do

"""
def roles_less_than_or_equal(role) do
case role do
:follower -> []
:member -> [:member]
:admin -> [:member, :admin]
:owner -> [:member, :admin, :owner]
end
list = [:follower, :member, :admin, :owner]
Enum.drop_while(list, fn elem -> elem != role end)
end

@doc """
Returns all roles bigger or equal to the given role.

## Examples

iex> roles_bigger_than_or_equal(:member)
[:member, :admin, :owner]

"""
def roles_bigger_than_or_equal(role) do
list = [:follower, :member, :admin, :owner]
Enum.drop_while(list, fn elem -> elem != role end)
end

@doc """
Expand Down
13 changes: 13 additions & 0 deletions lib/atomic/partnerships.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ defmodule Atomic.Partnerships do
Repo.all(Partner)
end

@doc """
Returns the list of partnerships belonging to an organization.

## Examples

iex> list_partnerships_by_organization_id(99d7c9e5-4212-4f59-a097-28aaa33c2621)
[%Partner{}, ...]

"""
def list_partnerships_by_organization_id(id) do
Repo.all(from p in Partner, where: p.organization_id == ^id)
end

@doc """
Gets a single partner.

Expand Down
2 changes: 1 addition & 1 deletion lib/atomic/partnerships/partner.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ defmodule Atomic.Partnerships.Partner do
alias Atomic.Organizations.Organization
alias Atomic.Uploaders

@required_fields ~w(name description)a
@required_fields ~w(name description organization_id)a

@optional_fields []

Expand Down
32 changes: 25 additions & 7 deletions lib/atomic_web/controllers/user_auth.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ defmodule AtomicWeb.UserAuth do
import Phoenix.Controller

alias Atomic.Accounts
alias Atomic.Organizations
alias AtomicWeb.Router.Helpers, as: Routes

# Make the remember me cookie valid for 60 days.
Expand All @@ -31,12 +32,27 @@ defmodule AtomicWeb.UserAuth do
token = Accounts.generate_user_session_token(user)
user_return_to = get_session(conn, :user_return_to)

conn
|> renew_session()
|> put_session(:user_token, token)
|> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}")
|> maybe_write_remember_me_cookie(token, params)
|> redirect(to: user_return_to || signed_in_path(conn))
case user.default_organization_id do
nil ->
conn
|> renew_session()
|> put_session(:user_token, token)
|> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}")
|> maybe_write_remember_me_cookie(token, params)
|> redirect(to: user_return_to || signed_in_path(conn))

_ ->
conn
|> renew_session()
|> put_session(:user_token, token)
|> put_session(
:current_organization,
Organizations.get_organization!(user.default_organization_id)
)
|> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}")
|> maybe_write_remember_me_cookie(token, params)
|> redirect(to: user_return_to || signed_in_path(conn))
end
end

defp maybe_write_remember_me_cookie(conn, token, %{"remember_me" => "true"}) do
Expand Down Expand Up @@ -94,7 +110,9 @@ defmodule AtomicWeb.UserAuth do
def fetch_current_user(conn, _opts) do
{user_token, conn} = ensure_user_token(conn)
user = user_token && Accounts.get_user_by_session_token(user_token)
assign(conn, :current_user, user)

conn
|> assign(:current_user, user)
end

defp ensure_user_token(conn) do
Expand Down
4 changes: 4 additions & 0 deletions lib/atomic_web/exceptions.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
defmodule AtomicWeb.MismatchError do
defexception message: "The provided parameters have no relation in the database.",
plug_status: 404
end
22 changes: 14 additions & 8 deletions lib/atomic_web/live/activity_live/edit.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ defmodule AtomicWeb.ActivityLive.Edit do
end

@impl true
def handle_params(%{"id" => id} = _params, _url, socket) do
{:noreply,
socket
|> assign(:page_title, gettext("Edit Activity"))
|> assign(
:activity,
Activities.get_activity!(id, [:activity_sessions, :speakers, :departments])
)}
def handle_params(%{"organization_id" => organization_id, "id" => id} = _params, _url, socket) do
activity =
Activities.get_activity!(id, preloads: [:activity_sessions, :departments, :speakers])

organizations = Activities.get_activity_organizations!(activity)

if organization_id in organizations do
{:noreply,
socket
|> assign(:page_title, gettext("Edit Activity"))
|> assign(:activity, activity)}
else
raise AtomicWeb.MismatchError
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you raising an exception on an handle_params function?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what he is doing is raising an exception making the current request fail with 404 status, which automatically shows the 404 page. Smart

Copy link
Member

@ruilopesm ruilopesm Jul 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I see. Why not wrap this logic inside a function and call it on a Plug implemented at the route level? That way, we could also use that function when deciding to display (or not) the "Edit" button on the show of an activity. Agreed? 🙏

Edit: This was supposed to be a reply to the already created thread 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that is a good idea. Will clean up the code quite a bit

end
end
end
2 changes: 1 addition & 1 deletion lib/atomic_web/live/activity_live/edit.html.heex
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<div id="atomic-edit" phx-update="ignore">
<.live_component module={AtomicWeb.ActivityLive.FormComponent} id={@activity.id} title={@page_title} action={@live_action} activity={@activity} return_to={Routes.activity_show_path(@socket, :show, @activity)} />
<.live_component module={AtomicWeb.ActivityLive.FormComponent} id={@activity.id} organization={@current_organization} title={@page_title} action={@live_action} activity={@activity} return_to={Routes.activity_show_path(@socket, :show, @current_organization, @activity)} />
</div>
20 changes: 15 additions & 5 deletions lib/atomic_web/live/activity_live/form_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,35 @@ defmodule AtomicWeb.ActivityLive.FormComponent do

@impl true
def mount(socket) do
departments = Departments.list_departments()
speakers = Activities.list_speakers()

{:ok,
socket
|> assign(:departments, departments)
|> assign(:speakers, speakers)}
|> assign(:departments, [])
|> assign(:speakers, [])}
end

@impl true
def update(%{activity: activity} = assigns, socket) do
departments = Departments.list_departments_by_organization_id(assigns.organization.id)
speakers = Activities.list_speakers_by_organization_id(assigns.organization.id)
changeset = Activities.change_activity(activity)

{:ok,
socket
|> assign(assigns)
|> assign(:departments, departments)
|> assign(:speakers, speakers)
|> assign(:changeset, changeset)}
end

@impl true
def update(%{"organization_id" => organization_id}, socket) do
departments = Departments.list_departments_by_organization_id(organization_id)

{:ok,
socket
|> assign(:departments, departments)}
end

@impl true
def handle_event("validate", %{"activity" => activity_params}, socket) do
changeset =
Expand Down
10 changes: 5 additions & 5 deletions lib/atomic_web/live/activity_live/index.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ defmodule AtomicWeb.ActivityLive.Index do
alias Atomic.Activities.Activity

@impl true
def mount(_params, _session, socket) do
{:ok, assign(socket, :activities, list_activities())}
def mount(params, _session, socket) do
{:ok, assign(socket, :activities, list_activities(params["organization_id"]))}
end

@impl true
Expand Down Expand Up @@ -37,10 +37,10 @@ defmodule AtomicWeb.ActivityLive.Index do
activity = Activities.get_activity!(id)
{:ok, _} = Activities.delete_activity(activity)

{:noreply, assign(socket, :activies, list_activities())}
{:noreply, assign(socket, :activies, list_activities(socket.assigns.current_organization.id))}
end

defp list_activities do
Activities.list_activities(preloads: [:activity_sessions])
defp list_activities(organization_id) do
Activities.list_activities_by_organization_id(organization_id, preloads: [:activity_sessions])
end
end
4 changes: 2 additions & 2 deletions lib/atomic_web/live/activity_live/index.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@
<td><%= activity.maximum_entries %></td>

<td>
<span><%= live_redirect("Show", to: Routes.activity_show_path(@socket, :show, activity)) %></span>
<span><%= live_redirect("Show", to: Routes.activity_show_path(@socket, :show, @current_organization, activity)) %></span>
</td>
</tr>
<% end %>
</tbody>
</table>

<span><%= live_patch("New Activity", to: Routes.activity_new_path(@socket, :new)) %></span>
<span><%= live_patch("New Activity", to: Routes.activity_new_path(@socket, :new, @current_organization)) %></span>
2 changes: 1 addition & 1 deletion lib/atomic_web/live/activity_live/new.html.heex
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<div id="atomic-new" phx-update="ignore">
<.live_component module={AtomicWeb.ActivityLive.FormComponent} id={:new} title={@page_title} action={@live_action} activity={@activity} return_to={Routes.activity_index_path(@socket, :index)} />
<.live_component module={AtomicWeb.ActivityLive.FormComponent} id={:new} organization={@current_organization} title={@page_title} action={@live_action} activity={@activity} return_to={Routes.activity_index_path(@socket, :index, @current_organization)} />
</div>
Loading