scufflecloud_core/models/
organizations.rs1use std::collections::HashSet;
2use std::sync::Arc;
3
4use diesel::Selectable;
5use diesel::prelude::{AsChangeset, Associations, Identifiable, Insertable, Queryable};
6
7use crate::cedar::CedarEntity;
8use crate::chrono_ext::ChronoDateTimeExt;
9use crate::id::{Id, PrefixedId};
10use crate::models::users::{User, UserId};
11
12pub(crate) type OrganizationId = Id<Organization>;
13
14#[derive(Queryable, Selectable, Insertable, Identifiable, AsChangeset, Associations, Debug, serde::Serialize)]
15#[diesel(table_name = crate::schema::organizations)]
16#[diesel(belongs_to(User, foreign_key = owner_id))]
17#[diesel(check_for_backend(diesel::pg::Pg))]
18pub struct Organization {
19 pub id: OrganizationId,
20 pub google_customer_id: Option<String>,
21 pub google_hosted_domain: Option<String>,
22 pub name: String,
23 pub owner_id: UserId,
24}
25
26impl PrefixedId for Organization {
27 const PREFIX: &'static str = "o";
28}
29
30impl<G> CedarEntity<G> for Organization {
31 const ENTITY_TYPE: &'static str = "Organization";
32
33 fn entity_id(&self) -> cedar_policy::EntityId {
34 cedar_policy::EntityId::new(self.id.to_string_unprefixed())
35 }
36}
37
38impl From<Organization> for pb::scufflecloud::core::v1::Organization {
39 fn from(value: Organization) -> Self {
40 pb::scufflecloud::core::v1::Organization {
41 id: value.id.to_string(),
42 google_hosted_domain: value.google_hosted_domain,
43 name: value.name,
44 owner_id: value.owner_id.to_string(),
45 created_at: Some(tinc::well_known::prost::Timestamp::from(value.id.datetime())),
46 }
47 }
48}
49
50pub(crate) type ProjectId = Id<Project>;
51
52#[derive(Queryable, Selectable, Insertable, Identifiable, AsChangeset, Associations, Debug, serde::Serialize)]
53#[diesel(table_name = crate::schema::projects)]
54#[diesel(belongs_to(Organization))]
55#[diesel(check_for_backend(diesel::pg::Pg))]
56pub struct Project {
57 pub id: ProjectId,
58 pub name: String,
59 pub organization_id: OrganizationId,
60}
61
62impl PrefixedId for Project {
63 const PREFIX: &'static str = "p";
64}
65
66impl<G> CedarEntity<G> for Project {
67 const ENTITY_TYPE: &'static str = "Project";
68
69 fn entity_id(&self) -> cedar_policy::EntityId {
70 cedar_policy::EntityId::new(self.id.to_string_unprefixed())
71 }
72
73 async fn parents(&self, _global: &Arc<G>) -> Result<HashSet<cedar_policy::EntityUid>, tonic::Status> {
74 Ok(std::iter::once(CedarEntity::<G>::entity_uid(&self.organization_id)).collect())
75 }
76}
77
78impl From<Project> for pb::scufflecloud::core::v1::Project {
79 fn from(value: Project) -> Self {
80 Self {
81 id: value.id.to_string(),
82 name: value.name,
83 organization_id: value.organization_id.to_string(),
84 created_at: Some(tinc::well_known::prost::Timestamp::from(value.id.datetime())),
85 }
86 }
87}
88
89pub(crate) type PolicyId = Id<Policy>;
90
91#[derive(Queryable, Selectable, Insertable, Identifiable, AsChangeset, Associations, Debug, serde::Serialize)]
92#[diesel(table_name = crate::schema::policies)]
93#[diesel(belongs_to(Organization))]
94#[diesel(belongs_to(Project))]
95#[diesel(check_for_backend(diesel::pg::Pg))]
96pub struct Policy {
97 pub id: PolicyId,
98 pub organization_id: OrganizationId,
99 pub project_id: Option<ProjectId>,
100 pub name: String,
101 pub description: Option<String>,
102 pub policy: String,
103}
104
105impl PrefixedId for Policy {
106 const PREFIX: &'static str = "po";
107}
108
109impl<G> CedarEntity<G> for Policy {
110 const ENTITY_TYPE: &'static str = "Policy";
111
112 fn entity_id(&self) -> cedar_policy::EntityId {
113 cedar_policy::EntityId::new(self.id.to_string_unprefixed())
114 }
115
116 async fn parents(&self, _global: &Arc<G>) -> Result<HashSet<cedar_policy::EntityUid>, tonic::Status> {
117 let mut parents = HashSet::new();
118 parents.insert(CedarEntity::<G>::entity_uid(&self.organization_id));
119 if let Some(project_id) = &self.project_id {
120 parents.insert(CedarEntity::<G>::entity_uid(project_id));
121 }
122 Ok(parents)
123 }
124}
125
126pub(crate) type RoleId = Id<Role>;
127
128#[derive(Queryable, Selectable, Insertable, Identifiable, AsChangeset, Associations, Debug, serde::Serialize)]
129#[diesel(table_name = crate::schema::roles)]
130#[diesel(belongs_to(Organization))]
131#[diesel(check_for_backend(diesel::pg::Pg))]
132pub struct Role {
133 pub id: RoleId,
134 pub organization_id: OrganizationId,
135 pub name: String,
136 pub description: Option<String>,
137 pub inline_policy: Option<String>,
138}
139
140impl PrefixedId for Role {
141 const PREFIX: &'static str = "r";
142}
143
144impl<G> CedarEntity<G> for Role {
145 const ENTITY_TYPE: &'static str = "Role";
146
147 fn entity_id(&self) -> cedar_policy::EntityId {
148 cedar_policy::EntityId::new(self.id.to_string_unprefixed())
149 }
150
151 async fn parents(&self, _global: &Arc<G>) -> Result<HashSet<cedar_policy::EntityUid>, tonic::Status> {
152 Ok(std::iter::once(CedarEntity::<G>::entity_uid(&self.organization_id)).collect())
153 }
154}
155
156#[derive(Queryable, Selectable, Insertable, Identifiable, Associations, Debug)]
157#[diesel(table_name = crate::schema::role_policies)]
158#[diesel(primary_key(role_id, policy_id))]
159#[diesel(belongs_to(Role))]
160#[diesel(belongs_to(Policy))]
161#[diesel(check_for_backend(diesel::pg::Pg))]
162pub struct RolePolicy {
163 pub role_id: RoleId,
164 pub policy_id: PolicyId,
165}
166
167#[derive(Queryable, Selectable, Insertable, Identifiable, AsChangeset, Associations, Debug, serde::Serialize)]
168#[diesel(table_name = crate::schema::organization_members)]
169#[diesel(primary_key(organization_id, user_id))]
170#[diesel(belongs_to(Organization))]
171#[diesel(belongs_to(User))]
172#[diesel(check_for_backend(diesel::pg::Pg))]
173pub struct OrganizationMember {
174 pub organization_id: OrganizationId,
175 pub user_id: UserId,
176 pub invited_by_id: Option<UserId>,
177 pub inline_policy: Option<String>,
178 pub created_at: chrono::DateTime<chrono::Utc>,
179}
180
181impl<G> CedarEntity<G> for OrganizationMember {
182 const ENTITY_TYPE: &'static str = "OrganizationMember";
183
184 fn entity_id(&self) -> cedar_policy::EntityId {
185 cedar_policy::EntityId::new(format!(
186 "{}:{}",
187 self.organization_id.to_string_unprefixed(),
188 self.user_id.to_string_unprefixed()
189 ))
190 }
191
192 async fn parents(&self, _global: &Arc<G>) -> Result<HashSet<cedar_policy::EntityUid>, tonic::Status> {
193 Ok(std::iter::once(CedarEntity::<G>::entity_uid(&self.organization_id)).collect())
194 }
195}
196
197impl From<OrganizationMember> for pb::scufflecloud::core::v1::OrganizationMember {
198 fn from(value: OrganizationMember) -> Self {
199 Self {
200 organization_id: value.organization_id.to_string(),
201 user_id: value.user_id.to_string(),
202 invited_by_id: value.invited_by_id.map(|id| id.to_string()),
203 created_at: Some(value.created_at.to_prost_timestamp_utc()),
204 }
205 }
206}
207
208#[derive(Queryable, Selectable, Insertable, Identifiable, Associations, Debug)]
209#[diesel(table_name = crate::schema::organization_member_policies)]
210#[diesel(primary_key(organization_id, user_id, policy_id))]
211#[diesel(belongs_to(Organization))]
212#[diesel(belongs_to(User))]
213#[diesel(belongs_to(Policy))]
214#[diesel(check_for_backend(diesel::pg::Pg))]
215pub struct OrganizationMemberPolicy {
216 pub organization_id: OrganizationId,
217 pub user_id: UserId,
218 pub policy_id: PolicyId,
219}
220
221pub(crate) type ServiceAccountId = Id<ServiceAccount>;
222
223#[derive(Queryable, Selectable, Insertable, Identifiable, AsChangeset, Associations, Debug, serde::Serialize)]
224#[diesel(table_name = crate::schema::service_accounts)]
225#[diesel(belongs_to(Organization))]
226#[diesel(belongs_to(Project))]
227#[diesel(check_for_backend(diesel::pg::Pg))]
228pub struct ServiceAccount {
229 pub id: ServiceAccountId,
230 pub name: String,
231 pub organization_id: OrganizationId,
232 pub project_id: Option<ProjectId>,
233 pub inline_policy: Option<String>,
234}
235
236impl PrefixedId for ServiceAccount {
237 const PREFIX: &'static str = "sa";
238}
239
240impl<G> CedarEntity<G> for ServiceAccount {
241 const ENTITY_TYPE: &'static str = "ServiceAccount";
242
243 fn entity_id(&self) -> cedar_policy::EntityId {
244 cedar_policy::EntityId::new(self.id.to_string_unprefixed())
245 }
246
247 async fn parents(&self, _global: &Arc<G>) -> Result<HashSet<cedar_policy::EntityUid>, tonic::Status> {
248 let mut parents = HashSet::new();
249 parents.insert(CedarEntity::<G>::entity_uid(&self.organization_id));
250 if let Some(project_id) = &self.project_id {
251 parents.insert(CedarEntity::<G>::entity_uid(project_id));
252 }
253 Ok(parents)
254 }
255}
256
257pub(crate) type ServiceAccountTokenId = Id<ServiceAccountToken>;
258
259#[derive(Queryable, Selectable, Insertable, Identifiable, AsChangeset, Associations, Debug, serde::Serialize)]
260#[diesel(table_name = crate::schema::service_account_tokens)]
261#[diesel(belongs_to(ServiceAccount))]
262#[diesel(check_for_backend(diesel::pg::Pg))]
263pub struct ServiceAccountToken {
264 pub id: ServiceAccountTokenId,
265 pub active: bool,
266 pub service_account_id: ServiceAccountId,
267 pub token: Vec<u8>,
268 pub inline_policy: Option<String>,
269 pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
270}
271
272impl PrefixedId for ServiceAccountToken {
273 const PREFIX: &'static str = "sat";
274}
275
276impl<G> CedarEntity<G> for ServiceAccountToken {
277 const ENTITY_TYPE: &'static str = "ServiceAccountToken";
278
279 fn entity_id(&self) -> cedar_policy::EntityId {
280 cedar_policy::EntityId::new(self.id.to_string_unprefixed())
281 }
282}
283
284pub(crate) type OrganizationInvitationId = Id<OrganizationInvitation>;
285
286#[derive(Queryable, Selectable, Insertable, Identifiable, AsChangeset, Associations, Debug, serde::Serialize)]
287#[diesel(table_name = crate::schema::organization_invitations)]
288#[diesel(belongs_to(Organization))]
289#[diesel(belongs_to(User))]
290#[diesel(check_for_backend(diesel::pg::Pg))]
291pub struct OrganizationInvitation {
292 pub id: OrganizationInvitationId,
293 pub user_id: Option<UserId>,
294 pub organization_id: OrganizationId,
295 pub email: String,
296 pub invited_by_id: UserId,
297 pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
298}
299
300impl PrefixedId for OrganizationInvitation {
301 const PREFIX: &'static str = "oi";
302}
303
304impl<G> CedarEntity<G> for OrganizationInvitation {
305 const ENTITY_TYPE: &'static str = "OrganizationInvitation";
306
307 fn entity_id(&self) -> cedar_policy::EntityId {
308 cedar_policy::EntityId::new(self.id.to_string_unprefixed())
309 }
310
311 async fn parents(&self, _global: &Arc<G>) -> Result<HashSet<cedar_policy::EntityUid>, tonic::Status> {
312 Ok(std::iter::once(CedarEntity::<G>::entity_uid(&self.organization_id)).collect())
313 }
314}
315
316impl From<OrganizationInvitation> for pb::scufflecloud::core::v1::OrganizationInvitation {
317 fn from(value: OrganizationInvitation) -> Self {
318 Self {
319 id: value.id.to_string(),
320 user_id: value.user_id.map(|id| id.to_string()),
321 organization_id: value.organization_id.to_string(),
322 email: value.email,
323 invited_by_id: value.invited_by_id.to_string(),
324 expires_at: value.expires_at.map(|dt| dt.to_prost_timestamp_utc()),
325 created_at: Some(tinc::well_known::prost::Timestamp::from(value.id.datetime())),
326 }
327 }
328}