Skip to content
Merged
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
88 changes: 88 additions & 0 deletions lib/cadet/accounts/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ defmodule Cadet.Accounts do

alias Cadet.Accounts.{Query, User, CourseRegistration}
alias Cadet.Auth.Provider
alias Cadet.Assessments.{Answer, Submission}

@doc """
Register new User entity using Cadet.Accounts.Form.Registration
Expand Down Expand Up @@ -109,4 +110,91 @@ defmodule Cadet.Accounts do
{:error, changeset} -> {:error, {:internal_server_error, full_error_messages(changeset)}}
end
end

@update_role_roles ~w(admin)a
def update_role(
_admin_course_reg = %CourseRegistration{
id: admin_course_reg_id,
course_id: admin_course_id,
role: admin_role
},
role,
coursereg_id
) do
with {:validate_role, true} <- {:validate_role, admin_role in @update_role_roles},
{:validate_not_self, true} <- {:validate_not_self, admin_course_reg_id != coursereg_id},
{:get_cr, user_course_reg} when not is_nil(user_course_reg) <-
{:get_cr, CourseRegistration |> where(id: ^coursereg_id) |> Repo.one()},
{:validate_same_course, true} <-
{:validate_same_course, user_course_reg.course_id == admin_course_id},
{:update_db, {:ok, _} = result} <-
{:update_db,
user_course_reg |> CourseRegistration.changeset(%{role: role}) |> Repo.update()} do
result
else
{:validate_role, false} ->
{:error, {:forbidden, "User is not permitted to change others' roles"}}

{:validate_not_self, false} ->
{:error, {:bad_request, "Admin not allowed to downgrade own role"}}

{:get_cr, _} ->
{:error, {:bad_request, "User course registration does not exist"}}

{:validate_same_course, false} ->
{:error, {:forbidden, "Wrong course"}}

{:update_db, {:error, changeset}} ->
{:error, {:bad_request, full_error_messages(changeset)}}
end
end

@delete_user_roles ~w(admin)a
def delete_user(
_admin_course_reg = %CourseRegistration{
id: admin_course_reg_id,
course_id: admin_course_id,
role: admin_role
},
coursereg_id
) do
with {:validate_role, true} <- {:validate_role, admin_role in @delete_user_roles},
{:validate_not_self, true} <- {:validate_not_self, admin_course_reg_id != coursereg_id},
{:get_cr, user_course_reg} when not is_nil(user_course_reg) <-
{:get_cr, CourseRegistration |> where(id: ^coursereg_id) |> Repo.one()},
{:prevent_delete_admin, true} <- {:prevent_delete_admin, user_course_reg.role != :admin},
{:validate_same_course, true} <-
{:validate_same_course, user_course_reg.course_id == admin_course_id} do
# TODO: Handle deletions of achievement entries, etc. too

# Delete submissions and answers before deleting user
Submission
|> where(student_id: ^user_course_reg.id)
|> Repo.all()
|> Enum.each(fn x ->
Answer
|> where(submission_id: ^x.id)
|> Repo.delete_all()

Repo.delete(x)
end)

Repo.delete(user_course_reg)
else
{:validate_role, false} ->
{:error, {:forbidden, "User is not permitted to delete other users"}}

{:validate_not_self, false} ->
{:error, {:bad_request, "Admin not allowed to delete ownself from course"}}

{:get_cr, _} ->
{:error, {:bad_request, "User course registration does not exist"}}

{:prevent_delete_admin, false} ->
{:error, {:bad_request, "Admins cannot be deleted"}}

{:validate_same_course, false} ->
{:error, {:forbidden, "Wrong course"}}
end
end
end
74 changes: 74 additions & 0 deletions lib/cadet_web/admin_controllers/admin_user_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,30 @@ defmodule CadetWeb.AdminUserController do
render(conn, "users.json", users: users)
end

def update_role(conn, %{"role" => role, "crId" => coursereg_id}) do
case Accounts.update_role(conn.assigns.course_reg, role, coursereg_id) do
{:ok, %{}} ->
text(conn, "OK")

{:error, {status, message}} ->
conn
|> put_status(status)
|> text(message)
end
end

def delete_user(conn, %{"crId" => coursereg_id}) do
case Accounts.delete_user(conn.assigns.course_reg, coursereg_id) do
{:ok, %{}} ->
text(conn, "OK")

{:error, {status, message}} ->
conn
|> put_status(status)
|> text(message)
end
end

swagger_path :index do
get("/v2/courses/{course_id}/admin/users")

Expand All @@ -24,6 +48,56 @@ defmodule CadetWeb.AdminUserController do
response(401, "Unauthorised")
end

swagger_path :update_role do
put("/v2/courses/{course_id}/admin/users/role")

summary("Updates the role of the given user in the the course")
security([%{JWT: []}])
consumes("application/json")

parameters do
course_id(:path, :integer, "Course ID", required: true)
role(:body, :role, "The new role", required: true)

crId(:body, :integer, "The course registration of the user whose role is to be updated",
required: true
)
end

response(200, "OK")

response(
400,
"Bad Request. User course registration does not exist or admin not allowed to downgrade own role"
)

response(403, "Forbidden. User is in different course, or you are not an admin")
end

swagger_path :delete_user do
delete("/v2/courses/{course_id}/admin/users")

summary("Deletes a user from a course")
consumes("application/json")

parameters do
course_id(:path, :integer, "Course ID", required: true)

crId(:body, :integer, "The course registration of the user whose role is to be updated",
required: true
)
end

response(200, "OK")

response(
400,
"Bad Request. User course registration does not exist or admin not allowed to delete ownself from course or admins cannot be deleted"
)

response(403, "Forbidden. User is in different course, or you are not an admin")
end

def swagger_definitions do
%{
AdminUserInfo:
Expand Down
1 change: 1 addition & 0 deletions lib/cadet_web/admin_views/admin_user_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ defmodule CadetWeb.AdminUserView do
crId: cr.id,
course_id: cr.course_id,
name: cr.user.name,
username: cr.user.username,
role: cr.role,
group:
case cr.group do
Expand Down
2 changes: 2 additions & 0 deletions lib/cadet_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ defmodule CadetWeb.Router do
)

get("/users", AdminUserController, :index)
put("/users/role", AdminUserController, :update_role)
delete("/users", AdminUserController, :delete_user)
post("/users/:userid/goals/:uuid/progress", AdminGoalsController, :update_progress)

put("/achievements", AdminAchievementsController, :bulk_update)
Expand Down
1 change: 1 addition & 0 deletions lib/cadet_web/views/user_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ defmodule CadetWeb.UserView do

_ ->
%{
crId: latest.id,
courseId: latest.course_id,
role: latest.role,
group:
Expand Down
123 changes: 123 additions & 0 deletions test/cadet/accounts/accounts_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,127 @@ defmodule Cadet.AccountsTest do
assert length(all_stu_in_c1g2) == 0
end
end

describe "update_role" do
setup do
c1 = insert(:course, %{course_name: "c1"})
c2 = insert(:course, %{course_name: "c2"})
admin1 = insert(:course_registration, %{course: c1, role: :admin})
staff1 = insert(:course_registration, %{course: c1, role: :staff})
student1 = insert(:course_registration, %{course: c1, role: :student})
student2 = insert(:course_registration, %{course: c2, role: :student})

{:ok, %{a1: admin1, s1: student1, s2: student2, st1: staff1}}
end

test "successful when admin is admin of the course the user is in (student)", %{
a1: admin1,
s1: %{id: coursereg_id}
} do
{:ok, updated_coursereg} = Accounts.update_role(admin1, "student", coursereg_id)
assert updated_coursereg.role == :student
end

test "successful when admin is admin of the course the user is in (staff)", %{
a1: admin1,
s1: %{id: coursereg_id}
} do
{:ok, updated_coursereg} = Accounts.update_role(admin1, "staff", coursereg_id)
assert updated_coursereg.role == :staff
end

test "successful when admin is admin of the course the user is in (admin)", %{
a1: admin1,
s1: %{id: coursereg_id}
} do
{:ok, updated_coursereg} = Accounts.update_role(admin1, "admin", coursereg_id)
assert updated_coursereg.role == :admin
end

test "fails when admin tries to downgrade own role", %{a1: %{id: coursereg_id} = admin1} do
assert {:error, {:bad_request, "Admin not allowed to downgrade own role"}} ==
Accounts.update_role(admin1, "staff", coursereg_id)
end

test "fails when user course registration does not exist", %{
a1: admin1,
s2: %{id: coursereg_id}
} do
assert {:error, {:bad_request, "User course registration does not exist"}} ==
Accounts.update_role(admin1, "staff", coursereg_id + 1)
end

test "admin is not admin of the course the user is in", %{a1: admin1, s2: %{id: coursereg_id}} do
assert {:error, {:forbidden, "Wrong course"}} ==
Accounts.update_role(admin1, "staff", coursereg_id)
end

test "invalid role provided", %{a1: admin1, s1: %{id: coursereg_id}} do
assert {:error, {:bad_request, "role is invalid"}} ==
Accounts.update_role(admin1, "invalidrole", coursereg_id)
end

test "fails when staff makes changes", %{st1: staff1, s1: %{id: coursereg_id}} do
assert {:error, {:forbidden, "User is not permitted to change others' roles"}} ==
Accounts.update_role(staff1, "staff", coursereg_id)
end
end

describe "delete_user" do
setup do
c1 = insert(:course, %{course_name: "c1"})
c2 = insert(:course, %{course_name: "c2"})
admin1 = insert(:course_registration, %{course: c1, role: :admin})
admin2 = insert(:course_registration, %{course: c1, role: :admin})
staff1 = insert(:course_registration, %{course: c1, role: :staff})
student1 = insert(:course_registration, %{course: c1, role: :student})
student2 = insert(:course_registration, %{course: c2, role: :student})

{:ok, %{a1: admin1, a2: admin2, s1: student1, s2: student2, st1: staff1}}
end

test "successful when admin is admin of the course the user is in (student)", %{
a1: admin1,
s1: %{id: coursereg_id}
} do
{:ok, deleted_entry} = Accounts.delete_user(admin1, coursereg_id)
assert deleted_entry.id == coursereg_id
end

test "successful when admin is admin of the course the user is in (staff)", %{
a1: admin1,
st1: %{id: coursereg_id}
} do
{:ok, deleted_entry} = Accounts.delete_user(admin1, coursereg_id)
assert deleted_entry.id == coursereg_id
end

test "fails when staff tries to delete user", %{st1: staff1, s1: %{id: coursereg_id}} do
assert {:error, {:forbidden, "User is not permitted to delete other users"}} ==
Accounts.delete_user(staff1, coursereg_id)
end

test "fails when deleting own self", %{a1: %{id: coursereg_id} = admin1} do
assert {:error, {:bad_request, "Admin not allowed to delete ownself from course"}} ==
Accounts.delete_user(admin1, coursereg_id)
end

test "fails when user course registration does not exist", %{
a1: admin1,
s2: %{id: coursereg_id}
} do
assert {:error, {:bad_request, "User course registration does not exist"}} ==
Accounts.delete_user(admin1, coursereg_id + 1)
end

test "fails when deleting an admin", %{a1: admin1, a2: %{id: coursereg_id}} do
assert {:error, {:bad_request, "Admins cannot be deleted"}} ==
Accounts.delete_user(admin1, coursereg_id)
end

test "fails when deleting a user from another course", %{a1: admin1, s2: %{id: coursereg_id}} do
assert {:error, {:forbidden, "Wrong course"}} ==
Accounts.delete_user(admin1, coursereg_id)
end
end
end
Loading