Skip to main content

security_client_rs/
roles.rs

1//! Kibana role administration. Role routes are global even on a scoped client.
2use std::collections::BTreeMap;
3
4use serde::{Deserialize, Serialize};
5use serde_json::{Map, Value};
6
7use crate::{
8    Kibana, Scope,
9    http::{Empty, Method},
10    request::endpoint,
11};
12
13#[derive(Clone, Debug, Serialize, Deserialize)]
14#[non_exhaustive]
15pub struct KibanaPrivilege {
16    pub spaces: Vec<String>,
17    #[serde(default)]
18    pub base: Vec<String>,
19    #[serde(default)]
20    pub feature: BTreeMap<String, Vec<String>>,
21}
22
23impl KibanaPrivilege {
24    /// Grants base privileges such as `read` or `all` in the given spaces.
25    pub fn new(spaces: Vec<String>, base: Vec<String>) -> Self {
26        Self {
27            spaces,
28            base,
29            feature: BTreeMap::new(),
30        }
31    }
32}
33
34/// Writable role privileges and metadata for [`Roles::put`].
35#[derive(Clone, Debug, Serialize, Deserialize)]
36#[non_exhaustive]
37pub struct RoleDefinition {
38    /// Elasticsearch cluster, index and run-as privileges, as in the Elasticsearch role API.
39    pub elasticsearch: Value,
40    #[serde(default)]
41    pub kibana: Vec<KibanaPrivilege>,
42    #[serde(default, skip_serializing_if = "Option::is_none")]
43    pub description: Option<String>,
44    #[serde(default, skip_serializing_if = "Option::is_none")]
45    pub metadata: Option<Map<String, Value>>,
46}
47
48impl RoleDefinition {
49    pub fn new(elasticsearch: Value, kibana: Vec<KibanaPrivilege>) -> Self {
50        Self {
51            elasticsearch,
52            kibana,
53            description: None,
54            metadata: None,
55        }
56    }
57}
58
59/// A role returned by Kibana, including read-only metadata.
60/// Submit its [`definition`](Self::definition) to [`Roles::put`], with its name
61/// as the path argument, to avoid resubmitting response-only fields.
62#[derive(Clone, Debug, Serialize, Deserialize)]
63#[non_exhaustive]
64pub struct Role {
65    pub name: String,
66    #[serde(flatten)]
67    pub definition: RoleDefinition,
68    /// Read-only fields such as `transient_metadata`.
69    #[serde(flatten)]
70    pub extra: Map<String, Value>,
71}
72
73#[derive(Clone, Copy, Debug)]
74pub struct Roles<'a>(pub(crate) &'a Kibana);
75
76impl<'a> Roles<'a> {
77    pub fn list(&self) -> ListRoles<'a> {
78        ListRoles(
79            self.0
80                .request(Method::GET, Scope::Global, &["api", "security", "role"]),
81        )
82    }
83
84    pub fn get(&self, name: &str) -> GetRole<'a> {
85        GetRole(self.0.request(
86            Method::GET,
87            Scope::Global,
88            &["api", "security", "role", name],
89        ))
90    }
91
92    /// Creates or replaces a role from a [`RoleDefinition`] or equivalent JSON.
93    pub fn put<B: Serialize + ?Sized>(&self, name: &str, role: &B) -> PutRole<'a> {
94        PutRole(
95            self.0
96                .request(
97                    Method::PUT,
98                    Scope::Global,
99                    &["api", "security", "role", name],
100                )
101                .json(role),
102        )
103    }
104
105    pub fn delete(&self, name: &str) -> DeleteRole<'a> {
106        DeleteRole(self.0.request(
107            Method::DELETE,
108            Scope::Global,
109            &["api", "security", "role", name],
110        ))
111    }
112}
113
114endpoint! {
115    /// `GET /api/security/role`
116    ListRoles => Vec<Role>
117}
118
119endpoint! {
120    /// `GET /api/security/role/{name}`
121    GetRole => Role
122}
123
124endpoint! {
125    /// `PUT /api/security/role/{name}`
126    PutRole => Empty
127}
128
129impl PutRole<'_> {
130    /// Fails with HTTP 409 instead of replacing an existing role.
131    pub fn create_only(self, enabled: bool) -> Self {
132        Self(self.0.param("createOnly", enabled))
133    }
134}
135
136endpoint! {
137    /// `DELETE /api/security/role/{name}`
138    DeleteRole => Empty
139}