feat: Add plugin share access controls (#21124)

Extends `plugin/share/save` to accept optional discoverability and
shareTargets while uploading plugin contents, and adds
`plugin/share/updateTargets` for share-only target updates without
re-uploading.
This commit is contained in:
xl-openai
2026-05-05 20:14:18 -07:00
committed by GitHub
Unverified
parent b3d4f1a9f0
commit 5119680f85
25 changed files with 1206 additions and 2 deletions
@@ -628,6 +628,11 @@ client_request_definitions! {
serialization: global("config"),
response: v2::PluginShareSaveResponse,
},
PluginShareUpdateTargets => "plugin/share/updateTargets" {
params: v2::PluginShareUpdateTargetsParams,
serialization: global("config"),
response: v2::PluginShareUpdateTargetsResponse,
},
PluginShareList => "plugin/share/list" {
params: v2::PluginShareListParams,
serialization: global("config"),
@@ -194,6 +194,10 @@ pub struct PluginShareSaveParams {
pub plugin_path: AbsolutePathBuf,
#[ts(optional = nullable)]
pub remote_plugin_id: Option<String>,
#[ts(optional = nullable)]
pub discoverability: Option<PluginShareDiscoverability>,
#[ts(optional = nullable)]
pub share_targets: Option<Vec<PluginShareTarget>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
@@ -204,6 +208,21 @@ pub struct PluginShareSaveResponse {
pub share_url: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginShareUpdateTargetsParams {
pub remote_plugin_id: String,
pub share_targets: Vec<PluginShareTarget>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginShareUpdateTargetsResponse {
pub principals: Vec<PluginSharePrincipal>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -237,6 +256,51 @@ pub struct PluginShareListItem {
pub local_plugin_path: Option<AbsolutePathBuf>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[ts(export_to = "v2/")]
pub enum PluginShareDiscoverability {
#[serde(rename = "LISTED")]
#[ts(rename = "LISTED")]
Listed,
#[serde(rename = "UNLISTED")]
#[ts(rename = "UNLISTED")]
Unlisted,
#[serde(rename = "PRIVATE")]
#[ts(rename = "PRIVATE")]
Private,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[ts(export_to = "v2/")]
pub enum PluginSharePrincipalType {
#[serde(rename = "user")]
#[ts(rename = "user")]
User,
#[serde(rename = "group")]
#[ts(rename = "group")]
Group,
#[serde(rename = "workspace")]
#[ts(rename = "workspace")]
Workspace,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginShareTarget {
pub principal_type: PluginSharePrincipalType,
pub principal_id: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginSharePrincipal {
pub principal_type: PluginSharePrincipalType,
pub principal_id: String,
pub name: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
#[ts(rename_all = "snake_case")]
@@ -2981,11 +2981,15 @@ fn plugin_share_params_and_response_serialization_use_camel_case_fields() {
serde_json::to_value(PluginShareSaveParams {
plugin_path: plugin_path.clone(),
remote_plugin_id: None,
discoverability: None,
share_targets: None,
})
.unwrap(),
json!({
"pluginPath": plugin_path_json,
"remotePluginId": null,
"discoverability": null,
"shareTargets": null,
}),
);
@@ -2993,11 +2997,33 @@ fn plugin_share_params_and_response_serialization_use_camel_case_fields() {
serde_json::to_value(PluginShareSaveParams {
plugin_path,
remote_plugin_id: Some("plugins~Plugin_00000000000000000000000000000000".to_string(),),
discoverability: Some(PluginShareDiscoverability::Private),
share_targets: Some(vec![
PluginShareTarget {
principal_type: PluginSharePrincipalType::User,
principal_id: "user-1".to_string(),
},
PluginShareTarget {
principal_type: PluginSharePrincipalType::Workspace,
principal_id: "workspace-1".to_string(),
},
]),
})
.unwrap(),
json!({
"pluginPath": plugin_path_json,
"remotePluginId": "plugins~Plugin_00000000000000000000000000000000",
"discoverability": "PRIVATE",
"shareTargets": [
{
"principalType": "user",
"principalId": "user-1",
},
{
"principalType": "workspace",
"principalId": "workspace-1",
},
],
}),
);
@@ -3013,6 +3039,42 @@ fn plugin_share_params_and_response_serialization_use_camel_case_fields() {
}),
);
assert_eq!(
serde_json::to_value(PluginShareUpdateTargetsParams {
remote_plugin_id: "plugins~Plugin_00000000000000000000000000000000".to_string(),
share_targets: vec![PluginShareTarget {
principal_type: PluginSharePrincipalType::Group,
principal_id: "group-1".to_string(),
}],
})
.unwrap(),
json!({
"remotePluginId": "plugins~Plugin_00000000000000000000000000000000",
"shareTargets": [{
"principalType": "group",
"principalId": "group-1",
}],
}),
);
assert_eq!(
serde_json::to_value(PluginShareUpdateTargetsResponse {
principals: vec![PluginSharePrincipal {
principal_type: PluginSharePrincipalType::User,
principal_id: "user-1".to_string(),
name: "Gavin".to_string(),
}],
})
.unwrap(),
json!({
"principals": [{
"principalType": "user",
"principalId": "user-1",
"name": "Gavin",
}],
}),
);
assert_eq!(
serde_json::from_value::<PluginShareListParams>(json!({})).unwrap(),
PluginShareListParams {},