fix2 tokens
This commit is contained in:
+118
-36
@@ -119,23 +119,19 @@ async fn has_write_permission(
|
||||
kind: &str,
|
||||
slug: &str,
|
||||
) -> Result<bool, ApiError> {
|
||||
let resource = crate::security::resource_token(headers, kind, slug);
|
||||
if external_token_access_level(state, kind, slug, resource).await? >= AccessLevel::Write {
|
||||
return Ok(true);
|
||||
}
|
||||
let authorization = authorization_token(headers);
|
||||
if authorization != resource
|
||||
&& external_token_access_level(state, kind, slug, authorization).await?
|
||||
>= AccessLevel::Write
|
||||
if request_access_level(
|
||||
state,
|
||||
headers,
|
||||
kind,
|
||||
slug,
|
||||
None,
|
||||
crate::security::session_cookie_token(headers),
|
||||
)
|
||||
.await?
|
||||
>= AccessLevel::Write
|
||||
{
|
||||
return Ok(true);
|
||||
}
|
||||
let session = crate::security::session_cookie_token(headers);
|
||||
if session != resource && session != authorization {
|
||||
if account_token_access_level(state, kind, slug, session).await? >= AccessLevel::Write {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
match kind {
|
||||
"workspace" => Ok(db::find_workspace(&state.db, slug)
|
||||
.await?
|
||||
@@ -275,6 +271,7 @@ pub struct WorkspaceInfo {
|
||||
slug: String,
|
||||
title: String,
|
||||
protected: bool,
|
||||
access_level: String,
|
||||
created_at: String,
|
||||
updated_at: String,
|
||||
}
|
||||
@@ -332,6 +329,7 @@ pub struct NoteInfo {
|
||||
slug: String,
|
||||
title: String,
|
||||
protected: bool,
|
||||
access_level: String,
|
||||
note_protected: bool,
|
||||
allow_public_task_updates: bool,
|
||||
public_page_unprotected: bool,
|
||||
@@ -570,7 +568,16 @@ pub async fn workspace_info(
|
||||
workspace.is_private,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(workspace_info_from(&workspace)))
|
||||
let access_level = effective_header_access_level(
|
||||
&state,
|
||||
&headers,
|
||||
"workspace",
|
||||
&workspace.slug,
|
||||
workspace.is_private,
|
||||
workspace.password_hash.is_some(),
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(workspace_info_from(&workspace, access_level)))
|
||||
}
|
||||
|
||||
pub async fn open_workspace(
|
||||
@@ -636,8 +643,20 @@ pub async fn open_workspace(
|
||||
let start = (page - 1) * per_page;
|
||||
let notes = notes.into_iter().skip(start).take(per_page).collect();
|
||||
|
||||
let mut access_level = effective_header_access_level(
|
||||
&state,
|
||||
&headers,
|
||||
"workspace",
|
||||
&workspace.slug,
|
||||
workspace.is_private,
|
||||
workspace.password_hash.is_some(),
|
||||
)
|
||||
.await?;
|
||||
if db::verify_workspace_password(&workspace, payload.password.as_deref()) {
|
||||
access_level = AccessLevel::Write;
|
||||
}
|
||||
Ok(Json(WorkspaceOpenResponse {
|
||||
workspace: workspace_info_from(&workspace),
|
||||
workspace: workspace_info_from(&workspace, access_level),
|
||||
notes,
|
||||
pagination: ListPaginationMeta { page, per_page, total, total_pages },
|
||||
}))
|
||||
@@ -668,8 +687,9 @@ pub async fn create_note(
|
||||
{
|
||||
AccessLevel::Write
|
||||
} else {
|
||||
combined_token_access_level(
|
||||
request_access_level(
|
||||
&state,
|
||||
&headers,
|
||||
"workspace",
|
||||
&workspace_slug,
|
||||
resource_request_token(
|
||||
@@ -845,6 +865,15 @@ pub async fn note_info(
|
||||
workspace.is_private,
|
||||
)
|
||||
.await?;
|
||||
let access_level = effective_header_access_level(
|
||||
&state,
|
||||
&headers,
|
||||
"workspace",
|
||||
&workspace.slug,
|
||||
workspace.is_private,
|
||||
workspace.password_hash.is_some(),
|
||||
)
|
||||
.await?;
|
||||
let note = db::find_note(&state.db, workspace.id, ¬e_slug)
|
||||
.await?
|
||||
.ok_or_else(ApiError::not_found_note)?;
|
||||
@@ -888,6 +917,7 @@ pub async fn note_info(
|
||||
slug: note.slug,
|
||||
title: note.title,
|
||||
protected: workspace.password_hash.is_some(),
|
||||
access_level: access_level_name(access_level).into(),
|
||||
note_protected: note.protected,
|
||||
allow_public_task_updates: db::note_public_task_updates(&state.db, note.id).await?,
|
||||
public_page_unprotected: db::note_public_page_unprotected(&state.db, note.id).await?,
|
||||
@@ -1001,8 +1031,9 @@ pub async fn restore(
|
||||
{
|
||||
AccessLevel::Write
|
||||
} else {
|
||||
combined_token_access_level(
|
||||
request_access_level(
|
||||
&state,
|
||||
&headers,
|
||||
"workspace",
|
||||
&workspace_slug,
|
||||
resource_request_token(
|
||||
@@ -1105,7 +1136,7 @@ async fn external_token_access_level(
|
||||
slug: &str,
|
||||
token: Option<&str>,
|
||||
) -> Result<AccessLevel, ApiError> {
|
||||
let permission = crate::auth::share_link_permission(state, kind, slug, token)
|
||||
let permission = crate::auth::share_access_permission(state, kind, slug, token)
|
||||
.await
|
||||
.map_err(|error| ApiError::forbidden(&error.message))?;
|
||||
let level = permission_level(permission.as_deref());
|
||||
@@ -1132,17 +1163,73 @@ async fn account_token_access_level(
|
||||
Ok(permission_level(permission.as_deref()))
|
||||
}
|
||||
|
||||
async fn combined_token_access_level(
|
||||
async fn request_access_level(
|
||||
state: &SharedState,
|
||||
headers: &HeaderMap,
|
||||
kind: &str,
|
||||
slug: &str,
|
||||
access_token: Option<&str>,
|
||||
supplied_access_token: Option<&str>,
|
||||
account_token: Option<&str>,
|
||||
) -> Result<AccessLevel, ApiError> {
|
||||
Ok(std::cmp::max(
|
||||
external_token_access_level(state, kind, slug, access_token).await?,
|
||||
account_token_access_level(state, kind, slug, account_token).await?,
|
||||
))
|
||||
let mut level = account_token_access_level(state, kind, slug, account_token).await?;
|
||||
if level == AccessLevel::Write {
|
||||
return Ok(level);
|
||||
}
|
||||
|
||||
let mut checked_tokens = Vec::with_capacity(4);
|
||||
for token in [
|
||||
supplied_access_token,
|
||||
crate::security::share_session_token(headers, kind, slug),
|
||||
crate::security::resource_token(headers, kind, slug),
|
||||
authorization_token(headers),
|
||||
] {
|
||||
let Some(token) = token.map(str::trim).filter(|value| !value.is_empty()) else {
|
||||
continue;
|
||||
};
|
||||
if checked_tokens.contains(&token) {
|
||||
continue;
|
||||
}
|
||||
checked_tokens.push(token);
|
||||
level = std::cmp::max(
|
||||
level,
|
||||
external_token_access_level(state, kind, slug, Some(token)).await?,
|
||||
);
|
||||
if level == AccessLevel::Write {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(level)
|
||||
}
|
||||
|
||||
fn access_level_name(level: AccessLevel) -> &'static str {
|
||||
match level {
|
||||
AccessLevel::None => "none",
|
||||
AccessLevel::Read => "read",
|
||||
AccessLevel::Write => "write",
|
||||
}
|
||||
}
|
||||
|
||||
async fn effective_header_access_level(
|
||||
state: &SharedState,
|
||||
headers: &HeaderMap,
|
||||
kind: &str,
|
||||
slug: &str,
|
||||
is_private: i64,
|
||||
password_protected: bool,
|
||||
) -> Result<AccessLevel, ApiError> {
|
||||
let mut level = request_access_level(
|
||||
state,
|
||||
headers,
|
||||
kind,
|
||||
slug,
|
||||
None,
|
||||
bearer_token(headers),
|
||||
)
|
||||
.await?;
|
||||
if is_private == 0 && !password_protected {
|
||||
level = std::cmp::max(level, AccessLevel::Write);
|
||||
}
|
||||
Ok(level)
|
||||
}
|
||||
|
||||
fn require_write(level: AccessLevel) -> Result<(), ApiError> {
|
||||
@@ -1159,18 +1246,12 @@ async fn has_header_resource_access(
|
||||
kind: &str,
|
||||
slug: &str,
|
||||
) -> Result<bool, ApiError> {
|
||||
for token in [
|
||||
crate::security::resource_token(headers, kind, slug),
|
||||
authorization_token(headers),
|
||||
] {
|
||||
if external_token_access_level(state, kind, slug, token).await? != AccessLevel::None {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(account_token_access_level(
|
||||
Ok(request_access_level(
|
||||
state,
|
||||
headers,
|
||||
kind,
|
||||
slug,
|
||||
None,
|
||||
crate::security::session_cookie_token(headers),
|
||||
)
|
||||
.await?
|
||||
@@ -1241,7 +1322,7 @@ pub async fn authorized_workspace(
|
||||
.await?
|
||||
.ok_or_else(ApiError::not_found_workspace)?;
|
||||
let token_level =
|
||||
combined_token_access_level(state, "workspace", slug, access_token, bearer).await?;
|
||||
request_access_level(state, headers, "workspace", slug, access_token, bearer).await?;
|
||||
if workspace.is_private != 0 && token_level == AccessLevel::None {
|
||||
return Err(ApiError::not_found_workspace());
|
||||
}
|
||||
@@ -1280,11 +1361,12 @@ async fn authorized_note(
|
||||
Ok((workspace, note))
|
||||
}
|
||||
|
||||
fn workspace_info_from(workspace: &db::Workspace) -> WorkspaceInfo {
|
||||
fn workspace_info_from(workspace: &db::Workspace, access_level: AccessLevel) -> WorkspaceInfo {
|
||||
WorkspaceInfo {
|
||||
slug: workspace.slug.clone(),
|
||||
title: workspace.title.clone(),
|
||||
protected: workspace.password_hash.is_some(),
|
||||
access_level: access_level_name(access_level).into(),
|
||||
created_at: db::normalize_timestamp(&workspace.created_at),
|
||||
updated_at: db::normalize_timestamp(&workspace.updated_at),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user