Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
OliverLok committed Jan 2, 2024
2 parents 0755898 + 12534b5 commit 4d20a14
Show file tree
Hide file tree
Showing 98 changed files with 1,842 additions and 327 deletions.
20 changes: 11 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ go run cmd/version/main.go
## Configuration

This project uses configuration options to set the behavior of its executables.
All executables use the same configuration infrastructure
All executables that use autograder resources use the same configuration infrastructure
and can therefore be configured the same way and with the same options.

To see all the available options,
Expand All @@ -63,7 +63,7 @@ use the `cmd/list-options` executable.
./bin/list-options
```

Options can be set on the command line using the `-c`/`--config` flag.
Options can be set on the command-line using the `-c`/`--config` flag.
For example:
```
./bin/logs-example --config log.level=debug
Expand All @@ -84,14 +84,16 @@ and defaults to `autograder`.

### Loading Options

When an autograder executable is run,
it will automatically look for config files in two locations:
- `<work dir>/config/config.json`
- `<work dir>/config/secrets.json`
Configurations will be loaded in the following order (later options override earlier ones):
0. The command-line options are checked for `BASE_DIR`.
1. Load options from environmental variables.
2. Options are loaded from `WORK_DIR/config` (config.json then secrets.json).
3. Options are loaded from the current working directory (config.json then secrets.json).
4. Options are loaded from any files specified with `--config-path` (ordered by appearance).
5. Options are loaded from the command-line (`--config` / `-c`).

Use these files to set persistent options.

To load other config (JSON) files, use the `--config-path` flag.
The base directory (`dirs.base`) can ONLY be set via the command-line or environmental variables.
This prevents cycles from the base directory changing and loading new options.

### Key Configuration Options

Expand Down
14 changes: 14 additions & 0 deletions _docs/assignments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Autograder Assignments

## LMS Syncing Mechanics

When syncing assignment information from an LMS,
only fields not set in the assignment config will be brought over from the LMS.

Matching autograder assignments with LMS assignments are first done via the LMS id set in the assignment's config.
If no LMS is is set, then an attempt is made to match assignments via their name (if the name is not empty).
A name match is made only if an autograder assignment matches one and only one LMS assignment.

## Duplicate Assignments

Assignments in the same course may not share the same ID, name, or LMS ID.
2 changes: 1 addition & 1 deletion _tests/COURSE101/HW0/assignment.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
"static-files": [
"grader.py"
],
"image": "autograder.python"
"image": "eriqaugustine/autograder.python"
}
2 changes: 1 addition & 1 deletion _tests/course-languages/cpp/assignment.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"path": "https://github.com/nlohmann/json/releases/download/v3.11.3/json.hpp"
}
],
"image": "autograder.base",
"image": "eriqaugustine/autograder.base",
"invocation": ["bash", "./grader.sh"],
"post-submission-files-ops": [
["cp", "input/assignment.cpp", "work/assignment.cpp"]
Expand Down
2 changes: 1 addition & 1 deletion _tests/course-languages/java/assignment.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"grader.sh",
"Grader.java"
],
"image": "autograder.base",
"image": "eriqaugustine/autograder.base",
"invocation": ["bash", "./grader.sh"],
"post-static-docker-commands": [
"RUN apt-get update",
Expand Down
2 changes: 1 addition & 1 deletion _tests/course-with-zero-limit/HW0/assignment.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"id": "hw0",
"name": "Homework 0",
"image": "autograder.python"
"image": "eriqaugustine/autograder.python"
}
8 changes: 4 additions & 4 deletions api/admin/update_course.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,30 @@ func HandleUpdateCourse(request *UpdateCourseRequest) (*UpdateCourseResponse, *c
if (request.Clear) {
err := db.ClearCourse(request.Course);
if (err != nil) {
return nil, core.NewInternalError("-701", &request.APIRequestCourseUserContext,
return nil, core.NewInternalError("-201", &request.APIRequestCourseUserContext,
"Failed to clear course.").Err(err);
}
}

if (request.Source != "") {
spec, err := common.ParseFileSpec(request.Source);
if (err != nil) {
return nil, core.NewBadCourseRequestError("-702", &request.APIRequestCourseUserContext,
return nil, core.NewBadCourseRequestError("-202", &request.APIRequestCourseUserContext,
"Source FileSpec is not formatted properly.").Err(err);
}

request.Course.Source = spec;

err = db.SaveCourse(request.Course);
if (err != nil) {
return nil, core.NewInternalError("-703", &request.APIRequestCourseUserContext,
return nil, core.NewInternalError("-203", &request.APIRequestCourseUserContext,
"Failed to save course.").Err(err);
}
}

updated, err := procedures.UpdateCourse(request.Course);
if (err != nil) {
return nil, core.NewInternalError("-704", &request.APIRequestCourseUserContext,
return nil, core.NewInternalError("-204", &request.APIRequestCourseUserContext,
"Failed to update course.").Err(err);
}

Expand Down
6 changes: 3 additions & 3 deletions api/core/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import (
func (this *APIRequestCourseUserContext) Auth() (*model.User, *APIError) {
user, err := db.GetUser(this.Course, this.UserEmail);
if (err != nil) {
return nil, NewAuthBadRequestError("-201", this, "Cannot Get User").Err(err);
return nil, NewAuthBadRequestError("-012", this, "Cannot Get User").Err(err);
}

if (user == nil) {
return nil, NewAuthBadRequestError("-202", this, "Unknown User");
return nil, NewAuthBadRequestError("-013", this, "Unknown User");
}

if (config.NO_AUTH.Get()) {
Expand All @@ -27,7 +27,7 @@ func (this *APIRequestCourseUserContext) Auth() (*model.User, *APIError) {
}

if (!user.CheckPassword(this.UserPass)) {
return nil, NewAuthBadRequestError("-203", this, "Bad Password");
return nil, NewAuthBadRequestError("-014", this, "Bad Password");
}

return user, nil;
Expand Down
22 changes: 11 additions & 11 deletions api/core/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,25 @@ func TestAuth(test *testing.T) {
{"[email protected]", "student", false, ""},
{"[email protected]", "other", false, ""},

{"Z", "student", false, "-202"},
{"[email protected]", "student", false, "-202"},
{"[email protected]", "student", false, "-202"},
{"student", "student", false, "-202"},
{"Z", "student", false, "-013"},
{"[email protected]", "student", false, "-013"},
{"[email protected]", "student", false, "-013"},
{"student", "student", false, "-013"},

{"[email protected]", "", false, "-203"},
{"[email protected]", "Zstudent", false, "-203"},
{"[email protected]", "studentZ", false, "-203"},
{"[email protected]", "", false, "-014"},
{"[email protected]", "Zstudent", false, "-014"},
{"[email protected]", "studentZ", false, "-014"},

{"[email protected]", "owner", true, ""},
{"[email protected]", "admin", true, ""},
{"[email protected]", "grader", true, ""},
{"[email protected]", "student", true, ""},
{"[email protected]", "other", true, ""},

{"Z", "student", true, "-202"},
{"[email protected]", "student", true, "-202"},
{"[email protected]", "student", true, "-202"},
{"student", "student", true, "-202"},
{"Z", "student", true, "-013"},
{"[email protected]", "student", true, "-013"},
{"[email protected]", "student", true, "-013"},
{"student", "student", true, "-013"},

{"[email protected]", "", true, ""},
{"[email protected]", "Zstudent", true, ""},
Expand Down
24 changes: 12 additions & 12 deletions api/core/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,25 +71,25 @@ func (this *APIRequestCourseUserContext) Validate(request any, endpoint string)
}

if (this.CourseID == "") {
return NewBadRequestError("-301", &this.APIRequest, "No course ID specified.");
return NewBadRequestError("-015", &this.APIRequest, "No course ID specified.");
}

if (this.UserEmail == "") {
return NewBadRequestError("-302", &this.APIRequest, "No user email specified.");
return NewBadRequestError("-016", &this.APIRequest, "No user email specified.");
}

if (this.UserPass == "") {
return NewBadRequestError("-303", &this.APIRequest, "No user password specified.");
return NewBadRequestError("-017", &this.APIRequest, "No user password specified.");
}

var err error;
this.Course, err = db.GetCourse(this.CourseID);
if (err != nil) {
return NewInternalError("-318", this, "Unable to get course").Err(err);
return NewInternalError("-032", this, "Unable to get course").Err(err);
}

if (this.Course == nil) {
return NewBadRequestError("-304", &this.APIRequest, fmt.Sprintf("Could not find course: '%s'.", this.CourseID)).
return NewBadRequestError("-018", &this.APIRequest, fmt.Sprintf("Could not find course: '%s'.", this.CourseID)).
Add("course-id", this.CourseID);
}

Expand All @@ -100,11 +100,11 @@ func (this *APIRequestCourseUserContext) Validate(request any, endpoint string)

minRole, foundRole := getMaxRole(request);
if (!foundRole) {
return NewInternalError("-305", this, "No role found for request. All request structs require a minimum role.");
return NewInternalError("-019", this, "No role found for request. All request structs require a minimum role.");
}

if (this.User.Role < minRole) {
return NewBadPermissionsError("-306", this, minRole, "Base API Request");
return NewBadPermissionsError("-020", this, minRole, "Base API Request");
}

return nil;
Expand All @@ -118,12 +118,12 @@ func (this *APIRequestAssignmentContext) Validate(request any, endpoint string)
}

if (this.AssignmentID == "") {
return NewBadRequestError("-307", &this.APIRequest, "No assignment ID specified.");
return NewBadRequestError("-021", &this.APIRequest, "No assignment ID specified.");
}

this.Assignment = this.Course.GetAssignment(this.AssignmentID);
if (this.Assignment == nil) {
return NewBadRequestError("-308", &this.APIRequest, fmt.Sprintf("Could not find assignment: '%s'.", this.AssignmentID)).
return NewBadRequestError("-022", &this.APIRequest, fmt.Sprintf("Could not find assignment: '%s'.", this.AssignmentID)).
Add("course-id", this.CourseID).Add("assignment-id", this.AssignmentID);
}

Expand All @@ -135,7 +135,7 @@ func (this *APIRequestAssignmentContext) Validate(request any, endpoint string)
func ValidateAPIRequest(request *http.Request, apiRequest any, endpoint string) *APIError {
reflectPointer := reflect.ValueOf(apiRequest);
if (reflectPointer.Kind() != reflect.Pointer) {
return NewBareInternalError("-309", endpoint, "ValidateAPIRequest() must be called with a pointer.").
return NewBareInternalError("-023", endpoint, "ValidateAPIRequest() must be called with a pointer.").
Add("kind", reflectPointer.Kind().String());
}

Expand All @@ -146,7 +146,7 @@ func ValidateAPIRequest(request *http.Request, apiRequest any, endpoint string)
}

if (!foundRequestStruct) {
return NewBareInternalError("-310", endpoint, "Request is not any kind of known API request.");
return NewBareInternalError("-024", endpoint, "Request is not any kind of known API request.");
}

// Check for any special field types that we know how to populate.
Expand Down Expand Up @@ -185,7 +185,7 @@ func validateRequestStruct(request any, endpoint string) (bool, *APIError) {

reflectValue := reflect.ValueOf(request).Elem();
if (reflectValue.Kind() != reflect.Struct) {
return false, NewBareInternalError("-317", endpoint, "Request's type must be a struct.").
return false, NewBareInternalError("-031", endpoint, "Request's type must be a struct.").
Add("kind", reflectValue.Kind().String());
}

Expand Down
18 changes: 9 additions & 9 deletions api/core/request_fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func checkRequestTargetUser(endpoint string, apiRequest any, fieldIndex int) *AP
jsonName := util.JSONFieldName(fieldType);

if (field.Email == "") {
return NewBadRequestError("-320", &courseContext.APIRequest,
return NewBadRequestError("-034", &courseContext.APIRequest,
fmt.Sprintf("Field '%s' requires a non-empty string, empty or null provided.", jsonName)).
Add("struct-name", structName).Add("field-name", fieldType.Name).Add("json-name", jsonName);
}
Expand Down Expand Up @@ -173,7 +173,7 @@ func checkRequestTargetUserSelfOrRole(endpoint string, apiRequest any, fieldInde

// Operations not on self require higher permissions.
if ((field.Email != courseContext.User.Email) && (courseContext.User.Role < minRole)) {
return NewBadPermissionsError("-319", courseContext, minRole, "Non-Self Target User");
return NewBadPermissionsError("-033", courseContext, minRole, "Non-Self Target User");
}

user := users[field.Email];
Expand All @@ -198,19 +198,19 @@ func checkRequestPostFiles(request *http.Request, endpoint string, apiRequest an
fieldType := reflectValue.Type().Field(fieldIndex);

if (!fieldType.IsExported()) {
return NewBareInternalError("-314", endpoint, "A POSTFiles field must be exported.").
return NewBareInternalError("-028", endpoint, "A POSTFiles field must be exported.").
Add("struct-name", structName).Add("field-name", fieldType.Name);
}

postFiles, err := storeRequestFiles(request);

if (err != nil) {
return NewBareInternalError("-315", endpoint, "Failed to store files from POST.").Err(err).
return NewBareInternalError("-029", endpoint, "Failed to store files from POST.").Err(err).
Add("struct-name", structName).Add("field-name", fieldType.Name);
}

if (postFiles == nil) {
return NewBareBadRequestError("-316", endpoint, "Endpoint requires files to be provided in POST body, no files found.").
return NewBareBadRequestError("-030", endpoint, "Endpoint requires files to be provided in POST body, no files found.").
Add("struct-name", structName).Add("field-name", fieldType.Name);
}

Expand All @@ -230,7 +230,7 @@ func checkRequestNonEmptyString(endpoint string, apiRequest any, fieldIndex int)

value := fieldValue.Interface().(NonEmptyString);
if (value == "") {
return NewBareBadRequestError("-318", endpoint,
return NewBareBadRequestError("-032", endpoint,
fmt.Sprintf("Field '%s' requires a non-empty string, empty or null provided.", jsonName)).
Add("struct-name", structName).Add("field-name", fieldType.Name).Add("json-name", jsonName);
}
Expand Down Expand Up @@ -327,22 +327,22 @@ func baseCheckRequestUsersField(endpoint string, apiRequest any, fieldIndex int)
courseContextValue := reflectValue.FieldByName("APIRequestCourseUserContext");
if (!courseContextValue.IsValid() || courseContextValue.IsZero()) {
return nil, nil,
NewBareInternalError("-311", endpoint, "A request with type requiring users must embed APIRequestCourseUserContext").
NewBareInternalError("-025", endpoint, "A request with type requiring users must embed APIRequestCourseUserContext").
Add("request", apiRequest).
Add("struct-name", structName).Add("field-name", fieldType.Name).Add("field-type", fieldName);
}
courseContext := courseContextValue.Interface().(APIRequestCourseUserContext);

if (!fieldType.IsExported()) {
return nil, nil,
NewInternalError("-312", &courseContext, "Field must be exported.").
NewInternalError("-026", &courseContext, "Field must be exported.").
Add("struct-name", structName).Add("field-name", fieldType.Name).Add("field-type", fieldName);
}

users, err := db.GetUsers(courseContext.Course);
if (err != nil) {
return nil, nil,
NewInternalError("-313", &courseContext, "Failed to fetch embeded users.").Err(err).
NewInternalError("-027", &courseContext, "Failed to fetch embeded users.").Err(err).
Add("struct-name", structName).Add("field-name", fieldType.Name).Add("field-type", fieldName);
}

Expand Down
Loading

0 comments on commit 4d20a14

Please sign in to comment.