caps and delete
This commit is contained in:
@@ -7,7 +7,7 @@ use base64::engine::general_purpose::STANDARD as BASE64;
|
||||
use base64::Engine;
|
||||
use common::{acquire_db_lock, body_to_vec, TestApp};
|
||||
use diesel::prelude::*;
|
||||
use papercrate::models::{ApiToken, ApiTokenCapability};
|
||||
use papercrate::models::{ApiCapability, ApiToken};
|
||||
use papercrate::routes::webdav;
|
||||
use papercrate::schema::api_tokens;
|
||||
use serde::Deserialize;
|
||||
@@ -15,13 +15,46 @@ use serde_json::json;
|
||||
use tower::ServiceExt;
|
||||
use uuid::Uuid;
|
||||
|
||||
const LEGACY_WEBDAV_CAPS: &[&str] = &[
|
||||
"documents:edit",
|
||||
"documents:read",
|
||||
"documents:upload",
|
||||
"documents:write",
|
||||
"folders:edit",
|
||||
"folders:read",
|
||||
"folders:write",
|
||||
"webdav:read",
|
||||
];
|
||||
|
||||
const OWNER_CAPS: &[&str] = &[
|
||||
"correspondents:edit",
|
||||
"correspondents:read",
|
||||
"correspondents:write",
|
||||
"documents:edit",
|
||||
"documents:read",
|
||||
"documents:upload",
|
||||
"documents:write",
|
||||
"folders:edit",
|
||||
"folders:read",
|
||||
"folders:write",
|
||||
"profile:read",
|
||||
"profile:write",
|
||||
"tags:edit",
|
||||
"tags:read",
|
||||
"tags:write",
|
||||
"webdav:read",
|
||||
"webdav:write",
|
||||
"capability_sets:read",
|
||||
"capability_sets:write",
|
||||
];
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct TokenInfo {
|
||||
id: Uuid,
|
||||
label: Option<String>,
|
||||
last_used_at: Option<String>,
|
||||
revoked_at: Option<String>,
|
||||
capabilities: Vec<ApiTokenCapability>,
|
||||
capabilities: Vec<ApiCapability>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -55,11 +88,16 @@ async fn api_token_crud_flow() -> Result<()> {
|
||||
app.insert_user(username, password, "admin").await?;
|
||||
let access_token = app.login_token(username, password).await?;
|
||||
|
||||
let created = create_token(&app, &access_token, json!({ "label": "dav" })).await?;
|
||||
let created = create_token(
|
||||
&app,
|
||||
&access_token,
|
||||
json!({ "label": "dav", "capabilities": LEGACY_WEBDAV_CAPS }),
|
||||
)
|
||||
.await?;
|
||||
let token_id = created.info.id;
|
||||
assert_eq!(created.info.label.as_deref(), Some("dav"));
|
||||
assert!(created.info.last_used_at.is_none());
|
||||
assert_eq!(created.info.capabilities, vec![ApiTokenCapability::Webdav]);
|
||||
assert_capabilities(&created.info.capabilities, LEGACY_WEBDAV_CAPS);
|
||||
|
||||
let regenerated = regenerate_token(&app, &access_token, token_id).await?;
|
||||
assert_eq!(regenerated.info.id, token_id);
|
||||
@@ -70,12 +108,10 @@ async fn api_token_crud_flow() -> Result<()> {
|
||||
&app,
|
||||
&access_token,
|
||||
token_id,
|
||||
json!({ "capabilities": ["webdav", "api"] }),
|
||||
json!({ "capabilities": OWNER_CAPS }),
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(updated.capabilities.len(), 2);
|
||||
assert!(updated.capabilities.contains(&ApiTokenCapability::Webdav));
|
||||
assert!(updated.capabilities.contains(&ApiTokenCapability::Api));
|
||||
assert_capabilities(&updated.capabilities, OWNER_CAPS);
|
||||
|
||||
let listed = list_tokens(&app, &access_token).await?;
|
||||
assert_eq!(listed.len(), 1);
|
||||
@@ -119,7 +155,12 @@ async fn webdav_basic_auth_uses_api_tokens() -> Result<()> {
|
||||
app.insert_user(username, password, "admin").await?;
|
||||
let access_token = app.login_token(username, password).await?;
|
||||
|
||||
let created = create_token(&app, &access_token, json!({ "label": "webdav" })).await?;
|
||||
let created = create_token(
|
||||
&app,
|
||||
&access_token,
|
||||
json!({ "label": "webdav", "capabilities": LEGACY_WEBDAV_CAPS }),
|
||||
)
|
||||
.await?;
|
||||
let token_id = created.info.id;
|
||||
|
||||
let router = webdav::create_router().with_state(app.state.clone());
|
||||
@@ -187,6 +228,50 @@ async fn webdav_basic_auth_uses_api_tokens() -> Result<()> {
|
||||
let response = router.clone().oneshot(success_request).await?;
|
||||
assert_eq!(response.status(), StatusCode::MULTI_STATUS);
|
||||
|
||||
// Token without webdav_read cannot authenticate.
|
||||
let limited_token = create_token(
|
||||
&app,
|
||||
&access_token,
|
||||
json!({
|
||||
"label": "limited",
|
||||
"capabilities": ["documents:read"]
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let limited_header = format!(
|
||||
"Basic {}",
|
||||
BASE64.encode(format!("{}:{}", username, limited_token.token))
|
||||
);
|
||||
|
||||
let limited_request = Request::builder()
|
||||
.method(propfind.clone())
|
||||
.uri("/")
|
||||
.header(header::AUTHORIZATION, limited_header.clone())
|
||||
.header("depth", "0")
|
||||
.body(Body::empty())?;
|
||||
let limited_response = router.clone().oneshot(limited_request).await?;
|
||||
assert_eq!(limited_response.status(), StatusCode::UNAUTHORIZED);
|
||||
|
||||
let _ = app
|
||||
.patch_json(
|
||||
&format!("/api/profile/api-tokens/{}", limited_token.info.id),
|
||||
&json!({
|
||||
"capabilities": ["documents:read", "webdav:read"]
|
||||
}),
|
||||
Some(&access_token),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let upgraded_request = Request::builder()
|
||||
.method(propfind.clone())
|
||||
.uri("/")
|
||||
.header(header::AUTHORIZATION, limited_header)
|
||||
.header("depth", "0")
|
||||
.body(Body::empty())?;
|
||||
let upgraded_response = router.clone().oneshot(upgraded_request).await?;
|
||||
assert_eq!(upgraded_response.status(), StatusCode::MULTI_STATUS);
|
||||
|
||||
delete_token(&app, &access_token, token_id).await?;
|
||||
|
||||
let failure_request = Request::builder()
|
||||
@@ -269,6 +354,19 @@ async fn delete_token(app: &TestApp, access_token: &str, token_id: Uuid) -> Resu
|
||||
assert_eq!(response.status(), StatusCode::NO_CONTENT);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn assert_capabilities(actual: &[ApiCapability], expected: &[&str]) {
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for capability in expected {
|
||||
assert!(
|
||||
actual
|
||||
.iter()
|
||||
.any(|candidate| candidate.as_str() == *capability),
|
||||
"capability '{}' not present",
|
||||
capability
|
||||
);
|
||||
}
|
||||
}
|
||||
async fn exchange_token(app: &TestApp, api_token: &str) -> Result<LoginResponseView> {
|
||||
let response = app
|
||||
.post_json(
|
||||
|
||||
Reference in New Issue
Block a user