Skip to main content

core_api/host/
permissions.rs

1//! OS authorization for one computer's Host and the services it runs; independent
2//! of any Space.
3//!
4//! Each installed package declares requirements per OS. The same PermissionKey on
5//! another process is a separate grant, because the operating system attributes
6//! the call to the process that makes it. The snapshot therefore keeps one row
7//! per declaring component, including stopped services, and does not merge those
8//! rows. Merely reading this snapshot MUST NOT request OS authorization. Local
9//! Network has no general passive status API: a probe result is historical
10//! evidence, never a silently refreshed system setting.
11use serde::{Deserialize, Serialize};
12use std::collections::BTreeMap;
13
14pub type PlatformPermissionRequirements = BTreeMap<String, Vec<PermissionRequirement>>;
15
16/// Automation is authorized per target application, not as one global switch.
17#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
18#[serde(tag = "id", rename_all = "camelCase", deny_unknown_fields)]
19pub enum PermissionKey {
20    LocalNetwork {},
21    Bluetooth {},
22    Microphone {},
23    Reminders {},
24    Automation {
25        #[serde(rename = "targetBundleId")]
26        target_bundle_id: String,
27    },
28}
29
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31#[serde(rename_all = "camelCase", deny_unknown_fields)]
32pub struct PermissionRequirement {
33    pub permission: PermissionKey,
34    /// Human-readable affected capability; denial need not disable the service.
35    pub feature: String,
36    pub reason: String,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40#[serde(rename_all = "camelCase", deny_unknown_fields)]
41pub struct PermissionUse {
42    pub component_id: String,
43    pub component_name: String,
44    pub feature: String,
45    pub reason: String,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
49#[serde(rename_all = "camelCase")]
50pub enum PermissionState {
51    Unknown,
52    NotDetermined,
53    Granted,
54    Denied,
55    Restricted,
56    Unsupported,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
60#[serde(rename_all = "camelCase")]
61pub enum PermissionEvidence {
62    /// A passive OS authorization query, not hardware availability.
63    System,
64    /// A previous explicit request/probe; observedAtMs is mandatory.
65    Probe,
66    Unavailable,
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
70#[serde(rename_all = "camelCase")]
71pub enum PermissionAction {
72    Request,
73    OpenSettings,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
77#[serde(rename_all = "camelCase", deny_unknown_fields)]
78pub struct HostPermission {
79    pub permission: PermissionKey,
80    pub state: PermissionState,
81    pub evidence: PermissionEvidence,
82    pub observed_at_ms: Option<i64>,
83    pub uses: Vec<PermissionUse>,
84    /// Mechanisms supported by this Host, NOT authority for a remote caller.
85    pub supported_actions: Vec<PermissionAction>,
86    pub error: Option<String>,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
90#[serde(rename_all = "camelCase", deny_unknown_fields)]
91pub struct PermissionDeclarationError {
92    pub component_id: String,
93    pub message: String,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
97#[serde(rename_all = "camelCase", deny_unknown_fields)]
98pub struct HostPermissions {
99    pub host_id: String,
100    pub subject_id: String,
101    pub subject_name: String,
102    pub host_version: String,
103    pub platform: String,
104    pub observed_at_ms: i64,
105    pub permissions: Vec<HostPermission>,
106    /// Missing, malformed or unsupported declarations must remain visible.
107    /// An empty permission list is not proof that the inventory is complete.
108    pub declaration_errors: Vec<PermissionDeclarationError>,
109}
110
111/// Local-control mutation. A Hub may later relay `GET /v1/permissions`; it must
112/// not relay this request, and it does not store or decide the grant. The server
113/// must verify the local control credential and Host identity. This DTO has no
114/// caller-controlled isLocal flag or arbitrary Settings URL. The component
115/// selects an installed service (or "host"); the key selects its permission.
116/// Older callers may omit the component only when ownership is unambiguous.
117#[derive(Debug, Clone, Serialize, Deserialize)]
118#[serde(rename_all = "camelCase", deny_unknown_fields)]
119pub struct HostPermissionRequest {
120    pub host_id: String,
121    #[serde(default, skip_serializing_if = "Option::is_none")]
122    pub component_id: Option<String>,
123    pub permission: PermissionKey,
124}
125
126pub const HOST_PERMISSIONS_PATH: &str = "/v1/permissions";
127pub const HOST_PERMISSION_REQUEST_PATH: &str = "/internal/permissions/request";
128pub const HOST_PERMISSION_SETTINGS_PATH: &str = "/internal/permissions/open-settings";