Skip to content

"unauthorized" test could print summary of requests made #832

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

Merged
merged 5 commits into from
Mar 30, 2022
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions nexus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ omicron-test-utils = { path = "../test-utils" }
openapiv3 = "1.0"
regex = "1.5.5"
subprocess = "0.2.8"
term = "0.7"

[dev-dependencies.openapi-lint]
git = "https://github.com/oxidecomputer/openapi-lint"
Expand Down
96 changes: 96 additions & 0 deletions nexus/tests/integration_tests/unauthorized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,47 @@ async fn test_unauthorized(cptestctx: &ControlPlaneTestContext) {

// Verify the hardcoded endpoints.
info!(log, "verifying endpoints");
print!("{}", VERIFY_HEADER);
for endpoint in &*VERIFY_ENDPOINTS {
verify_endpoint(&log, client, endpoint).await;
}
}

const VERIFY_HEADER: &str = r#"
SUMMARY OF REQUESTS MADE

KEY, USING HEADER AND EXAMPLE ROW:

+----------------------------> privileged GET (expects 200)
| (digit = last digit of 200-level
| response)
|
| +-> privileged GET (expects same as above)
| | (digit = last digit of 200-level
| | response)
| | ('-' => skipped (N/A))
^ ^
HEADER: G GET PUT POST DEL TRCE G URL
EXAMPLE: 0 3111 5555 3111 5555 5555 0 /organizations
ROW ^^^^
|||| TEST CASES FOR EACH HTTP METHOD:
+|||----------------------< authenticated, unauthorized request
+||----------------------< unauthenticated request
+|----------------------< bad authentication: no such user
+----------------------< bad authentication: invalid syntax

\__/ \__/ \__/ \__/ \__/
GET PUT etc. The test cases are repeated for each HTTP method.

The number in each cell is the last digit of the 400-level response
that was expected for this test case.

In this case, an unauthenthicated request to "GET /organizations" returned
401. All requests to "PUT /organizations" returned 405.

G GET PUT POST DEL TRCE G URL
"#;

//
// SETUP PHASE
//
Expand Down Expand Up @@ -216,6 +252,7 @@ async fn verify_endpoint(
.any(|allowed| matches!(allowed, AllowedMethod::Get));
let resource_before: Option<serde_json::Value> = if get_allowed {
info!(log, "test: privileged GET");
record_operation(WhichTest::PrivilegedGet(Some(&http::StatusCode::OK)));
Some(
NexusRequest::object_get(client, endpoint.url)
.authn_as(AuthnMode::PrivilegedUser)
Expand All @@ -227,9 +264,12 @@ async fn verify_endpoint(
)
} else {
warn!(log, "test: skipping privileged GET (method not allowed)");
record_operation(WhichTest::PrivilegedGet(None));
None
};

print!(" ");

// For each of the HTTP methods we use in the API as well as TRACE, we'll
// make several requests to this URL and verify the results.
let methods =
Expand Down Expand Up @@ -258,6 +298,7 @@ async fn verify_endpoint(
.await
.unwrap();
verify_response(&response);
record_operation(WhichTest::Unprivileged(&expected_status));

// Next, make an unauthenticated request.
info!(log, "test: unauthenticated"; "method" => ?method);
Expand All @@ -273,6 +314,7 @@ async fn verify_endpoint(
.await
.unwrap();
verify_response(&response);
record_operation(WhichTest::Unauthenticated(&expected_status));

// Now try a few requests with bogus credentials. We should get the
// same error as if we were unauthenticated. This is sort of duplicated
Expand Down Expand Up @@ -304,6 +346,7 @@ async fn verify_endpoint(
.await
.unwrap();
verify_response(&response);
record_operation(WhichTest::UnknownUser(&expected_status));

// Now try a syntactically invalid authn header.
info!(log, "test: bogus creds: bad cred syntax"; "method" => ?method);
Expand All @@ -320,6 +363,9 @@ async fn verify_endpoint(
.await
.unwrap();
verify_response(&response);
record_operation(WhichTest::InvalidHeader(&expected_status));

print!(" ");
}

// If we fetched the resource earlier, fetch it again and check the state.
Expand Down Expand Up @@ -348,7 +394,14 @@ async fn verify_endpoint(
resource_before, resource_after,
"resource changed after making a bunch of failed requests"
);
record_operation(WhichTest::PrivilegedGetCheck(Some(
&http::StatusCode::OK,
)));
} else {
record_operation(WhichTest::PrivilegedGetCheck(None));
}

println!(" {}", endpoint.url);
}

/// Verifies the body of an HTTP response for status codes 401, 403, 404, or 405
Expand Down Expand Up @@ -379,3 +432,46 @@ fn verify_response(response: &TestResponse) {
_ => unimplemented!(),
}
}

/// Describes the tests run by [`verify_endpoint()`].
enum WhichTest<'a> {
PrivilegedGet(Option<&'a http::StatusCode>),
Unprivileged(&'a http::StatusCode),
Unauthenticated(&'a http::StatusCode),
UnknownUser(&'a http::StatusCode),
InvalidHeader(&'a http::StatusCode),
PrivilegedGetCheck(Option<&'a http::StatusCode>),
}

/// Prints one cell of the giant summary table describing the successful result
/// of one HTTP request.
fn record_operation(whichtest: WhichTest<'_>) {
// Extract the status code for the test.
let status_code = match whichtest {
WhichTest::PrivilegedGet(s) | WhichTest::PrivilegedGetCheck(s) => s,
WhichTest::Unprivileged(s) => Some(s),
WhichTest::Unauthenticated(s) => Some(s),
WhichTest::UnknownUser(s) => Some(s),
WhichTest::InvalidHeader(s) => Some(s),
};

// We'll print out the third digit of the HTTP status code.
let c = match status_code {
Some(s) => s.as_str().chars().nth(2).unwrap(),
None => '-',
};

// We only get here for successful results, so they're all green. You might
// think the color is pointless, but it does help the reader make sense of
// the mess of numbers that shows up in the table for the different response
// codes.
let t = term::stdout();
if let Some(mut term) = t {
term.fg(term::color::GREEN).unwrap();
write!(term, "{}", c).unwrap();
term.reset().unwrap();
term.flush().unwrap();
} else {
print!("{}", c);
}
}
2 changes: 0 additions & 2 deletions nexus/tests/integration_tests/unauthorized_coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,6 @@ fn test_unauthorized_coverage() {
})
.collect();

println!("spec operations: {:?}", spec_operations);

// Go through each of the authz test cases and match each one against an
// OpenAPI operation.
let mut unexpected_endpoints = String::from(
Expand Down