Skip to content

Commit 5203125

Browse files
authored
Transfer groundControl (and admin panel) from staff to admin route (#1180)
* Create a new staff scope * Move Admin Panel requests into admin scope * Change appropriate routes into admin scope * Find-replace galore * Fix linting * Linting does not work :( * Revert "Find-replace galore" This reverts commit e77aa05. * Revert "Change appropriate routes into admin scope" This reverts commit 18dc689. * Revert "Create a new staff scope" This reverts commit 6b7e54e. * Move dangerous routes into a new scope * Fix linting * Linting works in mysterious ways * One more formatting change * Swap order of all-staff and admin-only routes This swap prevents the all-staff route, "/grading/:submissionid/:questionid", from pattern matching and overshadowing the admin-only route "/grading/:assessmentid/publish_all_grades". Thankfully, no admin routes overshadow staff routes, so a quick fix can be done here. * Update error message for grading routes * Update error messages for users * Add test cases for assets for staff Create test cases to indicate that non-admin staff can only read assets, but not create, modify, or delete them. * Update test auth to admin for assets * Update and add tests for course config routes Updates positive test auth from staff to admin, adds negative tests to ensure that non-admin staff are unable to read, update, create, or delete course configs. * Update and add tests for assessment-level routes Update the modification / deletion test auth from staff to admin, and create tests to ensure that non-admin staff are not able to delete / unpublish them * Fix sourcecast error * Revert "Fix sourcecast error" This reverts commit 831ca60. * Transfer asset routes to admin * Revert accidental formatting changes
1 parent 71192c3 commit 5203125

File tree

7 files changed

+251
-84
lines changed

7 files changed

+251
-84
lines changed

lib/cadet_web/router.ex

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ defmodule CadetWeb.Router do
2828
plug(:ensure_role, [:staff, :admin])
2929
end
3030

31+
pipeline :ensure_admin do
32+
plug(:ensure_role, [:admin])
33+
end
34+
3135
scope "/", CadetWeb do
3236
get("/.well-known/jwks.json", JWKSController, :index)
3337
end
@@ -119,11 +123,18 @@ defmodule CadetWeb.Router do
119123
get("/team/:assessmentid", TeamController, :index)
120124
end
121125

122-
# Admin pages
123-
scope "/v2/courses/:course_id/admin", CadetWeb do
124-
pipe_through([:api, :auth, :ensure_auth, :course, :ensure_staff])
126+
# Admin pages (Access: Course administrators only - these routes can cause substantial damage)
127+
@doc """
128+
NOTE: This scope must come before the routes for all staff below.
125129
126-
resources("/sourcecast", AdminSourcecastController, only: [:create, :delete])
130+
This is due to the all-staff route "/grading/:submissionid/:questionid", which would pattern match
131+
and overshadow "/grading/:assessmentid/publish_all_grades".
132+
133+
If an admin route will overshadow an all-staff route as well, a suggested better solution would be a
134+
per-route permission level check.
135+
"""
136+
scope "/v2/courses/:course_id/admin", CadetWeb do
137+
pipe_through([:api, :auth, :ensure_auth, :course, :ensure_admin])
127138

128139
get("/assets/:foldername", AdminAssetsController, :index)
129140
post("/assets/:foldername/*filename", AdminAssetsController, :upload)
@@ -133,6 +144,39 @@ defmodule CadetWeb.Router do
133144
post("/assessments/:assessmentid", AdminAssessmentsController, :update)
134145
delete("/assessments/:assessmentid", AdminAssessmentsController, :delete)
135146

147+
post(
148+
"/grading/:assessmentid/publish_all_grades",
149+
AdminGradingController,
150+
:publish_all_grades
151+
)
152+
153+
post(
154+
"/grading/:assessmentid/unpublish_all_grades",
155+
AdminGradingController,
156+
:unpublish_all_grades
157+
)
158+
159+
put("/users/:course_reg_id/role", AdminUserController, :update_role)
160+
delete("/users/:course_reg_id", AdminUserController, :delete_user)
161+
162+
put("/config", AdminCoursesController, :update_course_config)
163+
# TODO: Missing corresponding Swagger path entry
164+
get("/config/assessment_configs", AdminCoursesController, :get_assessment_configs)
165+
put("/config/assessment_configs", AdminCoursesController, :update_assessment_configs)
166+
# TODO: Missing corresponding Swagger path entry
167+
delete(
168+
"/config/assessment_config/:assessment_config_id",
169+
AdminCoursesController,
170+
:delete_assessment_config
171+
)
172+
end
173+
174+
# Admin pages (Access: All staff)
175+
scope "/v2/courses/:course_id/admin", CadetWeb do
176+
pipe_through([:api, :auth, :ensure_auth, :course, :ensure_staff])
177+
178+
resources("/sourcecast", AdminSourcecastController, only: [:create, :delete])
179+
136180
get(
137181
"/assessments/:assessmentid/popularVoteLeaderboard",
138182
AdminAssessmentsController,
@@ -148,14 +192,6 @@ defmodule CadetWeb.Router do
148192
get("/grading", AdminGradingController, :index)
149193
get("/grading/summary", AdminGradingController, :grading_summary)
150194

151-
post("/grading/:assessmentid/publish_all_grades", AdminGradingController, :publish_all_grades)
152-
153-
post(
154-
"/grading/:assessmentid/unpublish_all_grades",
155-
AdminGradingController,
156-
:unpublish_all_grades
157-
)
158-
159195
get("/grading/:submissionid", AdminGradingController, :show)
160196
post("/grading/:submissionid/unsubmit", AdminGradingController, :unsubmit)
161197
post("/grading/:submissionid/unpublish_grades", AdminGradingController, :unpublish_grades)
@@ -184,8 +220,6 @@ defmodule CadetWeb.Router do
184220

185221
# The admin route for getting total xp of a specific user
186222
get("/users/:course_reg_id/total_xp", AdminUserController, :combined_total_xp)
187-
put("/users/:course_reg_id/role", AdminUserController, :update_role)
188-
delete("/users/:course_reg_id", AdminUserController, :delete_user)
189223
get("/users/:course_reg_id/goals", AdminGoalsController, :index_goals_with_progress)
190224
post("/users/:course_reg_id/goals/:uuid/progress", AdminGoalsController, :update_progress)
191225

@@ -202,17 +236,6 @@ defmodule CadetWeb.Router do
202236
delete("/stories/:storyid", AdminStoriesController, :delete)
203237
post("/stories/:storyid", AdminStoriesController, :update)
204238

205-
put("/config", AdminCoursesController, :update_course_config)
206-
# TODO: Missing corresponding Swagger path entry
207-
get("/config/assessment_configs", AdminCoursesController, :get_assessment_configs)
208-
put("/config/assessment_configs", AdminCoursesController, :update_assessment_configs)
209-
# TODO: Missing corresponding Swagger path entry
210-
delete(
211-
"/config/assessment_config/:assessment_config_id",
212-
AdminCoursesController,
213-
:delete_assessment_config
214-
)
215-
216239
get("/teams", AdminTeamsController, :index)
217240
post("/teams", AdminTeamsController, :create)
218241
delete("/teams/:teamid", AdminTeamsController, :delete)

priv/repo/seeds.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ alias Cadet.Accounts.{
2626
# Cadet.Repo.insert!(%Cadet.Settings.Sublanguage{chapter: 1, variant: "default"})
2727

2828
if Cadet.Env.env() == :dev do
29-
number_of_students = 10
29+
number_of_students = 100_000
3030
number_of_assessments = 5
31-
number_of_questions = 3
31+
number_of_questions = 13
3232

3333
# Course
3434
admin_course =

test/cadet_web/admin_controllers/admin_assessments_controller_test.exs

Lines changed: 92 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -370,8 +370,34 @@ defmodule CadetWeb.AdminAssessmentsControllerTest do
370370
end
371371
end
372372

373-
describe "POST /, staff only" do
373+
describe "POST /, non-admin staff only" do
374374
@tag authenticate: :staff
375+
test "unauthorized", %{conn: conn} do
376+
test_cr = conn.assigns.test_cr
377+
course = test_cr.course
378+
config = insert(:assessment_config, %{course: course})
379+
380+
assessment =
381+
build(:assessment,
382+
course: course,
383+
course_id: course.id,
384+
config: config,
385+
config_id: config.id,
386+
is_published: true
387+
)
388+
389+
questions = build_list(5, :question, assessment: nil)
390+
391+
xml = XMLGenerator.generate_xml_for(assessment, questions)
392+
force_update = "false"
393+
body = %{assessment: xml, forceUpdate: force_update, assessmentConfigId: config.id}
394+
conn = post(conn, build_url(course.id), body)
395+
assert response(conn, 403) == "Forbidden"
396+
end
397+
end
398+
399+
describe "POST /, admin only" do
400+
@tag authenticate: :admin
375401
test "successful", %{conn: conn} do
376402
test_cr = conn.assigns.test_cr
377403
course = test_cr.course
@@ -429,7 +455,7 @@ defmodule CadetWeb.AdminAssessmentsControllerTest do
429455
assert expected_assessment != nil
430456
end
431457

432-
@tag authenticate: :staff
458+
@tag authenticate: :admin
433459
test "upload empty xml", %{conn: conn} do
434460
test_cr = conn.assigns.test_cr
435461
course = test_cr.course
@@ -487,6 +513,18 @@ defmodule CadetWeb.AdminAssessmentsControllerTest do
487513

488514
describe "DELETE /:assessment_id, staff only" do
489515
@tag authenticate: :staff
516+
test "unauthorized", %{conn: conn} do
517+
test_cr = conn.assigns.test_cr
518+
course = test_cr.course
519+
config = insert(:assessment_config, %{course: course})
520+
assessment = insert(:assessment, %{course: course, config: config})
521+
conn = delete(conn, build_url(course.id, assessment.id))
522+
assert response(conn, 403) == "Forbidden"
523+
end
524+
end
525+
526+
describe "DELETE /:assessment_id, admin only" do
527+
@tag authenticate: :admin
490528
test "successful", %{conn: conn} do
491529
test_cr = conn.assigns.test_cr
492530
course = test_cr.course
@@ -497,7 +535,7 @@ defmodule CadetWeb.AdminAssessmentsControllerTest do
497535
assert is_nil(Repo.get(Assessment, assessment.id))
498536
end
499537

500-
@tag authenticate: :staff
538+
@tag authenticate: :admin
501539
test "error due to different course", %{conn: conn} do
502540
test_cr = conn.assigns.test_cr
503541
course = test_cr.course
@@ -509,19 +547,6 @@ defmodule CadetWeb.AdminAssessmentsControllerTest do
509547
assert response(conn, 403) == "User not allow to delete assessments from another course"
510548
refute is_nil(Repo.get(Assessment, assessment.id))
511549
end
512-
513-
# @tag authenticate: :staff
514-
# test "error due to different course", %{conn: conn} do
515-
# test_cr = conn.assigns.test_cr
516-
# course = test_cr.course
517-
# another_course = insert(:course)
518-
# config = insert(:assessment_config, %{course: another_course})
519-
# assessment = insert(:assessment, %{course: another_course, config: config})
520-
521-
# conn = delete(conn, build_url(course.id, assessment.id))
522-
# assert response(conn, 403) == "User not allow to delete assessments from another course"
523-
# refute is_nil(Repo.get(Assessment, assessment.id))
524-
# end
525550
end
526551

527552
describe "POST /:assessment_id, unauthenticated, publish" do
@@ -544,8 +569,20 @@ defmodule CadetWeb.AdminAssessmentsControllerTest do
544569
end
545570
end
546571

547-
describe "POST /:assessment_id, staff only, publish" do
572+
describe "POST /:assessment_id, non-admin staff only, publish" do
548573
@tag authenticate: :staff
574+
test "forbidden", %{conn: conn} do
575+
test_cr = conn.assigns.test_cr
576+
course = test_cr.course
577+
config = insert(:assessment_config, %{course: course})
578+
assessment = insert(:assessment, %{course: course, config: config})
579+
conn = post(conn, build_url(course.id, assessment.id), %{isPublished: true})
580+
assert response(conn, 403) == "Forbidden"
581+
end
582+
end
583+
584+
describe "POST /:assessment_id, admin only, publish" do
585+
@tag authenticate: :admin
549586
test "successful toggle from published to unpublished", %{conn: conn} do
550587
test_cr = conn.assigns.test_cr
551588
course = test_cr.course
@@ -557,7 +594,7 @@ defmodule CadetWeb.AdminAssessmentsControllerTest do
557594
refute expected
558595
end
559596

560-
@tag authenticate: :staff
597+
@tag authenticate: :admin
561598
test "successful toggle from unpublished to published", %{conn: conn} do
562599
test_cr = conn.assigns.test_cr
563600
course = test_cr.course
@@ -608,8 +645,38 @@ defmodule CadetWeb.AdminAssessmentsControllerTest do
608645
end
609646
end
610647

611-
describe "POST /:assessment_id, staff only" do
648+
describe "POST /:assessment_id, non-admin staff only" do
612649
@tag authenticate: :staff
650+
test "forbidden", %{conn: conn} do
651+
test_cr = conn.assigns.test_cr
652+
course = test_cr.course
653+
config = insert(:assessment_config, %{course: course})
654+
assessment = insert(:assessment, %{course: course, config: config})
655+
656+
new_open_at =
657+
Timex.now()
658+
|> Timex.beginning_of_day()
659+
|> Timex.shift(days: 3)
660+
|> Timex.shift(hours: 4)
661+
662+
new_open_at_string =
663+
new_open_at
664+
|> Timex.format!("{ISO:Extended}")
665+
666+
new_close_at = Timex.shift(new_open_at, days: 7)
667+
668+
new_close_at_string =
669+
new_close_at
670+
|> Timex.format!("{ISO:Extended}")
671+
672+
new_dates = %{openAt: new_open_at_string, closeAt: new_close_at_string}
673+
conn = post(conn, build_url(course.id, assessment.id), new_dates)
674+
assert response(conn, 403) == "Forbidden"
675+
end
676+
end
677+
678+
describe "POST /:assessment_id, admin only" do
679+
@tag authenticate: :admin
613680
test "successful", %{conn: conn} do
614681
test_cr = conn.assigns.test_cr
615682
course = test_cr.course
@@ -658,7 +725,7 @@ defmodule CadetWeb.AdminAssessmentsControllerTest do
658725
assert [assessment.open_at, assessment.close_at] == [new_open_at, new_close_at]
659726
end
660727

661-
@tag authenticate: :staff
728+
@tag authenticate: :admin
662729
test "allowed to change open time of opened assessments", %{conn: conn} do
663730
test_cr = conn.assigns.test_cr
664731
course = test_cr.course
@@ -703,7 +770,7 @@ defmodule CadetWeb.AdminAssessmentsControllerTest do
703770
assert [assessment.open_at, assessment.close_at] == [new_open_at, close_at]
704771
end
705772

706-
@tag authenticate: :staff
773+
@tag authenticate: :admin
707774
test "not allowed to set close time to before open time", %{conn: conn} do
708775
test_cr = conn.assigns.test_cr
709776
course = test_cr.course
@@ -748,7 +815,7 @@ defmodule CadetWeb.AdminAssessmentsControllerTest do
748815
assert [assessment.open_at, assessment.close_at] == [open_at, close_at]
749816
end
750817

751-
@tag authenticate: :staff
818+
@tag authenticate: :admin
752819
test "successful, set close time to before current time", %{conn: conn} do
753820
test_cr = conn.assigns.test_cr
754821
course = test_cr.course
@@ -793,7 +860,7 @@ defmodule CadetWeb.AdminAssessmentsControllerTest do
793860
assert [assessment.open_at, assessment.close_at] == [open_at, new_close_at]
794861
end
795862

796-
@tag authenticate: :staff
863+
@tag authenticate: :admin
797864
test "successful, set open time to before current time", %{conn: conn} do
798865
test_cr = conn.assigns.test_cr
799866
course = test_cr.course
@@ -838,7 +905,7 @@ defmodule CadetWeb.AdminAssessmentsControllerTest do
838905
assert [assessment.open_at, assessment.close_at] == [new_open_at, close_at]
839906
end
840907

841-
@tag authenticate: :staff
908+
@tag authenticate: :admin
842909
test "successful, set hasTokenCounter and hasVotingFeatures to true", %{conn: conn} do
843910
test_cr = conn.assigns.test_cr
844911
course = test_cr.course
@@ -873,7 +940,7 @@ defmodule CadetWeb.AdminAssessmentsControllerTest do
873940
]
874941
end
875942

876-
@tag authenticate: :staff
943+
@tag authenticate: :admin
877944
test "successful, set hasTokenCounter and hasVotingFeatures to false", %{conn: conn} do
878945
test_cr = conn.assigns.test_cr
879946
course = test_cr.course

0 commit comments

Comments
 (0)