Skip to content

Commit b66ab3b

Browse files
begedinjoshsmith
authored andcommitted
Add ProjectUserPolicy and tests
1 parent 526fa3a commit b66ab3b

File tree

3 files changed

+224
-2
lines changed

3 files changed

+224
-2
lines changed

lib/code_corps/helpers/policy.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ defmodule CodeCorps.Helpers.Policy do
88

99
alias CodeCorps.{
1010
Organization, OrganizationMembership,
11-
Project, Repo, StripeConnectAccount,
11+
Project, ProjectUser, Repo, StripeConnectAccount,
1212
TaskSkill, Task, User, UserTask
1313
}
1414
alias Ecto.Changeset
@@ -46,9 +46,10 @@ defmodule CodeCorps.Helpers.Policy do
4646
4747
Returns `:string`
4848
"""
49-
@spec get_role(nil | OrganizationMembership.t | Changeset.t) :: String.t | nil
49+
@spec get_role(nil | OrganizationMembership.t | ProjectUser.t | Changeset.t) :: String.t | nil
5050
def get_role(nil), do: nil
5151
def get_role(%OrganizationMembership{role: role}), do: role
52+
def get_role(%ProjectUser{role: role}), do: role
5253
def get_role(%Changeset{} = changeset), do: changeset |> Changeset.get_field(:role)
5354

5455
@doc """
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
defmodule CodeCorps.ProjectUserPolicyTest do
2+
use CodeCorps.PolicyCase
3+
4+
import CodeCorps.ProjectUserPolicy, only: [create?: 2, update?: 2, delete?: 2]
5+
import CodeCorps.ProjectUser, only: [create_pending_changeset: 2, update_changeset: 2]
6+
7+
alias CodeCorps.ProjectUser
8+
9+
describe "create?/2" do
10+
test "returns true when user is creating their own membership" do
11+
user = insert(:user)
12+
changeset = %ProjectUser{} |> create_pending_changeset(%{user_id: user.id})
13+
14+
assert create?(user, changeset)
15+
end
16+
17+
test "returns false for normal user, creating someone else's membership" do
18+
user = insert(:user)
19+
changeset = %ProjectUser{} |> create_pending_changeset(%{user_id: "someone_else"})
20+
21+
refute create?(user, changeset)
22+
end
23+
end
24+
25+
describe "update?/2" do
26+
test "returns false when user is non-member" do
27+
user = insert(:user)
28+
project_user = insert(:project_user)
29+
30+
changeset = project_user |> update_changeset(%{})
31+
32+
refute update?(user, changeset)
33+
end
34+
35+
test "returns false when user is pending" do
36+
user = insert(:user)
37+
project = insert(:project)
38+
insert(:project_user, role: "pending", user: user, project: project)
39+
40+
project_user = insert(:project_user, project: project)
41+
42+
changeset = project_user |> update_changeset(%{})
43+
44+
refute update?(user, changeset)
45+
end
46+
47+
test "returns false when user is contributor" do
48+
user = insert(:user)
49+
project = insert(:project)
50+
insert(:project_user, role: "contributor", user: user, project: project)
51+
52+
project_user = insert(:project_user, project: project)
53+
54+
changeset = project_user |> update_changeset(%{})
55+
56+
refute update?(user, changeset)
57+
end
58+
59+
test "returns true when user is admin, approving a pending membership" do
60+
user = insert(:user)
61+
project = insert(:project)
62+
insert(:project_user, role: "admin", user: user, project: project)
63+
64+
project_user = insert(:project_user, project: project, role: "pending")
65+
66+
changeset = project_user |> update_changeset(%{role: "contributor"})
67+
68+
assert update?(user, changeset)
69+
end
70+
71+
test "returns false when user is admin, doing something other than approving a pending membership" do
72+
user = insert(:user)
73+
project = insert(:project)
74+
insert(:project_user, role: "admin", user: user, project: project)
75+
76+
project_user = insert(:project_user, project: project, role: "contributor")
77+
78+
changeset = project_user |> update_changeset(%{})
79+
80+
refute update?(user, changeset)
81+
end
82+
83+
test "returns true when user is owner and is changing a role other than owner" do
84+
user = insert(:user)
85+
project = insert(:project)
86+
insert(:project_user, role: "owner", user: user, project: project)
87+
88+
project_user = insert(:project_user, project: project, role: "admin")
89+
90+
changeset = project_user |> update_changeset(%{})
91+
92+
assert update?(user, changeset)
93+
end
94+
95+
test "returns false when user is owner and is changing another owner" do
96+
user = insert(:user)
97+
project = insert(:project)
98+
insert(:project_user, role: "owner", user: user, project: project)
99+
100+
project_user = insert(:project_user, project: project, role: "owner")
101+
102+
changeset = project_user |> update_changeset(%{})
103+
104+
refute update?(user, changeset)
105+
end
106+
end
107+
108+
describe "delete?/2" do
109+
test "returns true when contributor is deleting their own membership" do
110+
user = insert(:user)
111+
project = insert(:project)
112+
113+
project_user = insert(:project_user, project: project, user: user, role: "contributor")
114+
115+
assert delete?(user, project_user)
116+
end
117+
118+
test "returns true when admin is deleting a pending membership" do
119+
user = insert(:user)
120+
project = insert(:project)
121+
insert(:project_user, role: "admin", user: user, project: project)
122+
123+
project_user = insert(:project_user, project: project, role: "pending")
124+
125+
assert delete?(user, project_user)
126+
end
127+
128+
test "returns true when admin is deleting a contributor" do
129+
user = insert(:user)
130+
project = insert(:project)
131+
insert(:project_user, role: "admin", user: user, project: project)
132+
133+
project_user = insert(:project_user, project: project, role: "contributor")
134+
135+
assert delete?(user, project_user)
136+
end
137+
138+
test "returns false when admin is deleting another admin" do
139+
user = insert(:user)
140+
project = insert(:project)
141+
insert(:project_user, role: "admin", user: user, project: project)
142+
143+
project_user = insert(:project_user, project: project, role: "admin")
144+
145+
refute delete?(user, project_user)
146+
end
147+
148+
test "returns false when admin is deleting an owner" do
149+
user = insert(:user)
150+
project = insert(:project)
151+
insert(:project_user, role: "admin", user: user, project: project)
152+
153+
project_user = insert(:project_user, project: project, role: "owner")
154+
155+
refute delete?(user, project_user)
156+
end
157+
158+
test "returns true when owner is deleting an admin" do
159+
user = insert(:user)
160+
project = insert(:project)
161+
insert(:project_user, role: "owner", user: user, project: project)
162+
163+
project_user = insert(:project_user, project: project, role: "admin")
164+
165+
assert delete?(user, project_user)
166+
end
167+
end
168+
end
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
defmodule CodeCorps.ProjectUserPolicy do
2+
@moduledoc """
3+
Handles `User` authorization of actions on `ProjectUser` records
4+
"""
5+
import CodeCorps.Helpers.Policy, only: [get_role: 1]
6+
7+
alias CodeCorps.{ProjectUser, Repo, User}
8+
alias Ecto.Changeset
9+
10+
@spec create?(User.t, Ecto.Changeset.t) :: boolean
11+
def create?(%User{id: id}, %Changeset{changes: %{user_id: user_id}}), do: id == user_id
12+
def create?(%User{}, %Changeset{}), do: false
13+
14+
@spec update?(User.t, Ecto.Changeset.t) :: boolean
15+
def update?(%User{} = user, %Changeset{data: %ProjectUser{} = record} = changeset) do
16+
user_membership = record |> get_project_membership(user)
17+
18+
user_role = user_membership |> get_role
19+
old_role = record |> get_role
20+
new_role = changeset |> get_role
21+
22+
case [user_role, old_role, new_role] do
23+
# Non-member, pending and contributors can't do anything
24+
[nil, _, _] -> false
25+
["pending", _, _] -> false
26+
["contributor", _, _] -> false
27+
# Admins can only approve pending memberships and nothing else
28+
["admin", "pending", "contributor"] -> true
29+
["admin", _, _] -> false
30+
# Owners can do everything expect changing other owners
31+
["owner", "owner", _] -> false
32+
["owner", _, _] -> true
33+
# No other role change is allowed
34+
[_, _, _] -> false
35+
end
36+
end
37+
38+
@spec delete?(User.t, ProjectUser.t) :: boolean
39+
def delete?(%User{} = user, %ProjectUser{} = record) do
40+
record |> get_project_membership(user) |> do_delete?(record)
41+
end
42+
43+
defp do_delete?(%ProjectUser{} = user_m, %ProjectUser{} = current_m) when user_m == current_m, do: true
44+
defp do_delete?(%ProjectUser{role: "owner"}, %ProjectUser{}), do: true
45+
defp do_delete?(%ProjectUser{role: "admin"}, %ProjectUser{role: role}) when role in ~w(pending contributor), do: true
46+
defp do_delete?(_, _), do: false
47+
48+
defp get_project_membership(%ProjectUser{user_id: nil}, %User{id: nil}), do: nil
49+
defp get_project_membership(%ProjectUser{user_id: m_id} = membership, %User{id: u_id}) when m_id == u_id, do: membership
50+
defp get_project_membership(%ProjectUser{project_id: project_id}, %User{id: user_id}) do
51+
ProjectUser |> Repo.get_by(project_id: project_id, user_id: user_id)
52+
end
53+
end

0 commit comments

Comments
 (0)