* chore(clippy): enable and fix many stricter lints These ensure that the codebase uses more idiomatic, performant, and concise language constructions. * chore: make non-Clippy compiler warnings also deny by default
295 lines
11 KiB
Rust
295 lines
11 KiB
Rust
use actix_http::StatusCode;
|
|
use actix_web::test;
|
|
use chrono::{Duration, Utc};
|
|
use common::{database::*, environment::with_test_environment_all};
|
|
|
|
use labrinth::models::pats::Scopes;
|
|
use serde_json::json;
|
|
|
|
use crate::common::api_common::AppendsOptionalPat;
|
|
|
|
pub mod common;
|
|
|
|
// Full pat test:
|
|
// - create a PAT and ensure it can be used for the scope
|
|
// - ensure access token is not returned for any PAT in GET
|
|
// - ensure PAT can be patched to change scopes
|
|
// - ensure PAT can be patched to change expiry
|
|
// - ensure expired PATs cannot be used
|
|
// - ensure PATs can be deleted
|
|
#[actix_rt::test]
|
|
pub async fn pat_full_test() {
|
|
with_test_environment_all(None, |test_env| async move {
|
|
// Create a PAT for a full test
|
|
let req = test::TestRequest::post()
|
|
.uri("/_internal/pat")
|
|
.append_pat(USER_USER_PAT)
|
|
.set_json(json!({
|
|
"scopes": Scopes::COLLECTION_CREATE, // Collection create as an easily tested example
|
|
"name": "test_pat_scopes Test",
|
|
"expires": Utc::now() + Duration::days(1),
|
|
}))
|
|
.to_request();
|
|
let resp = test_env.call(req).await;
|
|
assert_status!(&resp, StatusCode::OK);
|
|
let success: serde_json::Value = test::read_body_json(resp).await;
|
|
let id = success["id"].as_str().unwrap();
|
|
|
|
// Has access token and correct scopes
|
|
assert!(success["access_token"].as_str().is_some());
|
|
assert_eq!(
|
|
success["scopes"].as_u64().unwrap(),
|
|
Scopes::COLLECTION_CREATE.bits()
|
|
);
|
|
let access_token = success["access_token"].as_str().unwrap();
|
|
|
|
// Get PAT again
|
|
let req = test::TestRequest::get()
|
|
.append_pat(USER_USER_PAT)
|
|
.uri("/_internal/pat")
|
|
.to_request();
|
|
let resp = test_env.call(req).await;
|
|
assert_status!(&resp, StatusCode::OK);
|
|
let success: serde_json::Value = test::read_body_json(resp).await;
|
|
|
|
// Ensure access token is NOT returned for any PATs
|
|
for pat in success.as_array().unwrap() {
|
|
assert!(pat["access_token"].as_str().is_none());
|
|
}
|
|
|
|
// Create mock test for using PAT
|
|
let mock_pat_test = |token: &str| {
|
|
let token = token.to_string();
|
|
async {
|
|
// This uses a route directly instead of an api call because it doesn't relaly matter and we
|
|
// want it to succeed no matter what.
|
|
// This is an arbitrary request.
|
|
let req = test::TestRequest::post()
|
|
.uri("/v3/collection")
|
|
.append_header(("Authorization", token))
|
|
.set_json(json!({
|
|
"name": "Test Collection 1",
|
|
"description": "Test Collection Description"
|
|
}))
|
|
.to_request();
|
|
let resp = test_env.call(req).await;
|
|
resp.status().as_u16()
|
|
}
|
|
};
|
|
|
|
assert_eq!(mock_pat_test(access_token).await, 200);
|
|
|
|
// Change scopes and test again
|
|
let req = test::TestRequest::patch()
|
|
.uri(&format!("/_internal/pat/{id}"))
|
|
.append_pat(USER_USER_PAT)
|
|
.set_json(json!({
|
|
"scopes": 0,
|
|
}))
|
|
.to_request();
|
|
let resp = test_env.call(req).await;
|
|
assert_status!(&resp, StatusCode::NO_CONTENT);
|
|
assert_eq!(mock_pat_test(access_token).await, 401); // No longer works
|
|
|
|
// Change scopes back, and set expiry to the past, and test again
|
|
let req = test::TestRequest::patch()
|
|
.uri(&format!("/_internal/pat/{id}"))
|
|
.append_pat(USER_USER_PAT)
|
|
.set_json(json!({
|
|
"scopes": Scopes::COLLECTION_CREATE,
|
|
"expires": Utc::now() + Duration::seconds(1), // expires in 1 second
|
|
}))
|
|
.to_request();
|
|
let resp = test_env.call(req).await;
|
|
assert_status!(&resp, StatusCode::NO_CONTENT);
|
|
|
|
// Wait 1 second before testing again for expiry
|
|
tokio::time::sleep(Duration::seconds(1).to_std().unwrap()).await;
|
|
assert_eq!(mock_pat_test(access_token).await, 401); // No longer works
|
|
|
|
// Change everything back to normal and test again
|
|
let req = test::TestRequest::patch()
|
|
.uri(&format!("/_internal/pat/{id}"))
|
|
.append_pat(USER_USER_PAT)
|
|
.set_json(json!({
|
|
"expires": Utc::now() + Duration::days(1), // no longer expired!
|
|
}))
|
|
.to_request();
|
|
|
|
println!("PAT ID FOR TEST: {id}");
|
|
let resp = test_env.call(req).await;
|
|
assert_status!(&resp, StatusCode::NO_CONTENT);
|
|
assert_eq!(mock_pat_test(access_token).await, 200); // Works again
|
|
|
|
// Patching to a bad expiry should fail
|
|
let req = test::TestRequest::patch()
|
|
.uri(&format!("/_internal/pat/{id}"))
|
|
.append_pat(USER_USER_PAT)
|
|
.set_json(json!({
|
|
"expires": Utc::now() - Duration::days(1), // Past
|
|
}))
|
|
.to_request();
|
|
let resp = test_env.call(req).await;
|
|
assert_status!(&resp, StatusCode::BAD_REQUEST);
|
|
|
|
// Similar to above with PAT creation, patching to a bad scope should fail
|
|
for i in 0..64 {
|
|
let scope = Scopes::from_bits_truncate(1 << i);
|
|
if !Scopes::all().contains(scope) {
|
|
continue;
|
|
}
|
|
|
|
let req = test::TestRequest::patch()
|
|
.uri(&format!("/_internal/pat/{id}"))
|
|
.append_pat(USER_USER_PAT)
|
|
.set_json(json!({
|
|
"scopes": scope.bits(),
|
|
}))
|
|
.to_request();
|
|
let resp = test_env.call(req).await;
|
|
assert_eq!(
|
|
resp.status().as_u16(),
|
|
if scope.is_restricted() { 400 } else { 204 }
|
|
);
|
|
}
|
|
|
|
// Delete PAT
|
|
let req = test::TestRequest::delete()
|
|
.append_pat(USER_USER_PAT)
|
|
.uri(&format!("/_internal/pat/{id}"))
|
|
.to_request();
|
|
let resp = test_env.call(req).await;
|
|
assert_status!(&resp, StatusCode::NO_CONTENT);
|
|
})
|
|
.await;
|
|
}
|
|
|
|
// Test illegal PAT setting, both in POST and PATCH
|
|
#[actix_rt::test]
|
|
pub async fn bad_pats() {
|
|
with_test_environment_all(None, |test_env| async move {
|
|
// Creating a PAT with no name should fail
|
|
let req = test::TestRequest::post()
|
|
.uri("/_internal/pat")
|
|
.append_pat(USER_USER_PAT)
|
|
.set_json(json!({
|
|
"scopes": Scopes::COLLECTION_CREATE, // Collection create as an easily tested example
|
|
"expires": Utc::now() + Duration::days(1),
|
|
}))
|
|
.to_request();
|
|
let resp = test_env.call(req).await;
|
|
assert_status!(&resp, StatusCode::BAD_REQUEST);
|
|
|
|
// Name too short or too long should fail
|
|
for name in ["n", "this_name_is_too_long".repeat(16).as_str()] {
|
|
let req = test::TestRequest::post()
|
|
.uri("/_internal/pat")
|
|
.append_pat(USER_USER_PAT)
|
|
.set_json(json!({
|
|
"name": name,
|
|
"scopes": Scopes::COLLECTION_CREATE, // Collection create as an easily tested example
|
|
"expires": Utc::now() + Duration::days(1),
|
|
}))
|
|
.to_request();
|
|
let resp = test_env.call(req).await;
|
|
assert_status!(&resp, StatusCode::BAD_REQUEST);
|
|
}
|
|
|
|
// Creating a PAT with an expiry in the past should fail
|
|
let req = test::TestRequest::post()
|
|
.uri("/_internal/pat")
|
|
.append_pat(USER_USER_PAT)
|
|
.set_json(json!({
|
|
"scopes": Scopes::COLLECTION_CREATE, // Collection create as an easily tested example
|
|
"name": "test_pat_scopes Test",
|
|
"expires": Utc::now() - Duration::days(1),
|
|
}))
|
|
.to_request();
|
|
let resp = test_env.call(req).await;
|
|
assert_status!(&resp, StatusCode::BAD_REQUEST);
|
|
|
|
// Make a PAT with each scope, with the result varying by whether that scope is restricted
|
|
for i in 0..64 {
|
|
let scope = Scopes::from_bits_truncate(1 << i);
|
|
if !Scopes::all().contains(scope) {
|
|
continue;
|
|
}
|
|
let req = test::TestRequest::post()
|
|
.uri("/_internal/pat")
|
|
.append_pat(USER_USER_PAT)
|
|
.set_json(json!({
|
|
"scopes": scope.bits(),
|
|
"name": format!("test_pat_scopes Name {}", i),
|
|
"expires": Utc::now() + Duration::days(1),
|
|
}))
|
|
.to_request();
|
|
let resp = test_env.call(req).await;
|
|
assert_eq!(
|
|
resp.status().as_u16(),
|
|
if scope.is_restricted() { 400 } else { 200 }
|
|
);
|
|
}
|
|
|
|
// Create a 'good' PAT for patching
|
|
let req = test::TestRequest::post()
|
|
.uri("/_internal/pat")
|
|
.append_pat(USER_USER_PAT)
|
|
.set_json(json!({
|
|
"scopes": Scopes::COLLECTION_CREATE,
|
|
"name": "test_pat_scopes Test",
|
|
"expires": Utc::now() + Duration::days(1),
|
|
}))
|
|
.to_request();
|
|
let resp = test_env.call(req).await;
|
|
assert_status!(&resp, StatusCode::OK);
|
|
let success: serde_json::Value = test::read_body_json(resp).await;
|
|
let id = success["id"].as_str().unwrap();
|
|
|
|
// Patching to a bad name should fail
|
|
for name in ["n", "this_name_is_too_long".repeat(16).as_str()] {
|
|
let req = test::TestRequest::post()
|
|
.uri("/_internal/pat")
|
|
.append_pat(USER_USER_PAT)
|
|
.set_json(json!({
|
|
"name": name,
|
|
}))
|
|
.to_request();
|
|
let resp = test_env.call(req).await;
|
|
assert_status!(&resp, StatusCode::BAD_REQUEST);
|
|
}
|
|
|
|
// Patching to a bad expiry should fail
|
|
let req = test::TestRequest::patch()
|
|
.uri(&format!("/_internal/pat/{id}"))
|
|
.append_pat(USER_USER_PAT)
|
|
.set_json(json!({
|
|
"expires": Utc::now() - Duration::days(1), // Past
|
|
}))
|
|
.to_request();
|
|
let resp = test_env.call(req).await;
|
|
assert_status!(&resp, StatusCode::BAD_REQUEST);
|
|
|
|
// Similar to above with PAT creation, patching to a bad scope should fail
|
|
for i in 0..64 {
|
|
let scope = Scopes::from_bits_truncate(1 << i);
|
|
if !Scopes::all().contains(scope) {
|
|
continue;
|
|
}
|
|
|
|
let req = test::TestRequest::patch()
|
|
.uri(&format!("/_internal/pat/{id}"))
|
|
.append_pat(USER_USER_PAT)
|
|
.set_json(json!({
|
|
"scopes": scope.bits(),
|
|
}))
|
|
.to_request();
|
|
let resp = test_env.call(req).await;
|
|
assert_eq!(
|
|
resp.status().as_u16(),
|
|
if scope.is_restricted() { 400 } else { 204 }
|
|
);
|
|
}
|
|
})
|
|
.await;
|
|
}
|