1use itertools::Itertools;
2
3use unitycatalog_common::models::credentials::v1::{
4 CreateCredentialRequest, Credential, DeleteCredentialRequest, GetCredentialRequest,
5 ListCredentialsRequest, ListCredentialsResponse, UpdateCredentialRequest,
6};
7use unitycatalog_common::models::{ObjectLabel, ResourceIdent, ResourceName, ResourceRef};
8
9use super::{RequestContext, SecuredAction};
10pub use crate::codegen::credentials::CredentialHandler;
11use crate::policy::{Permission, Policy, process_resources};
12use crate::store::ResourceStore;
13use crate::{Error, Result};
14
15#[async_trait::async_trait]
16pub trait CredentialHandlerExt: Send + Sync + 'static {
17 async fn get_credential_internal(&self, request: GetCredentialRequest) -> Result<Credential>;
24}
25
26fn has_secret(cred: &Credential) -> bool {
32 cred.azure_service_principal.is_set()
33 || cred.azure_managed_identity.is_set()
34 || cred.azure_storage_key.is_set()
35 || cred.aws_iam_role.is_set()
36 || cred.databricks_gcp_service_account.is_set()
37}
38
39#[async_trait::async_trait]
40impl<T: ResourceStore + Policy<RequestContext>> CredentialHandler<RequestContext> for T {
41 #[tracing::instrument(skip(self, context))]
42 async fn list_credentials(
43 &self,
44 request: ListCredentialsRequest,
45 context: RequestContext,
46 ) -> Result<ListCredentialsResponse> {
47 self.check_required(&request, &context).await?;
48 let (mut resources, next_page_token) = self
49 .list(
50 &ObjectLabel::Credential,
51 None,
52 request.max_results.map(|v| v as usize),
53 request.page_token,
54 )
55 .await?;
56 process_resources(self, &context, &Permission::Read, &mut resources).await?;
57 Ok(ListCredentialsResponse {
58 credentials: resources.into_iter().map(|r| r.try_into()).try_collect()?,
59 next_page_token,
60 ..Default::default()
61 })
62 }
63 #[tracing::instrument(skip(self, context), fields(resource_name))]
64 async fn create_credential(
65 &self,
66 request: CreateCredentialRequest,
67 context: RequestContext,
68 ) -> Result<Credential> {
69 tracing::Span::current().record("resource_name", &request.name);
70 self.check_required(&request, &context).await?;
71 let cred = Credential {
75 name: request.name.clone(),
76 full_name: request.name,
77 comment: request.comment,
78 purpose: request.purpose,
79 read_only: request.read_only.unwrap_or(false),
80 used_for_managed_storage: false,
81 id: None,
82 created_at: None,
83 updated_at: None,
84 azure_managed_identity: request.azure_managed_identity,
85 azure_service_principal: request.azure_service_principal,
86 azure_storage_key: request.azure_storage_key,
87 aws_iam_role: request.aws_iam_role,
88 databricks_gcp_service_account: request.databricks_gcp_service_account,
89 owner: None,
90 created_by: None,
91 updated_by: None,
92 ..Default::default()
93 };
94 if !has_secret(&cred) {
95 return Err(Error::invalid_argument("No credentials provided"));
96 }
97 Ok(self.create(cred.into()).await?.0.try_into()?)
100 }
101
102 #[tracing::instrument(skip(self, context), fields(resource_name))]
103 async fn get_credential(
104 &self,
105 request: GetCredentialRequest,
106 context: RequestContext,
107 ) -> Result<Credential> {
108 tracing::Span::current().record("resource_name", &request.name);
109 self.check_required(&request, &context).await?;
110 self.get_credential_internal(request).await
111 }
112
113 #[tracing::instrument(skip(self, context), fields(resource_name))]
114 async fn update_credential(
115 &self,
116 request: UpdateCredentialRequest,
117 context: RequestContext,
118 ) -> Result<Credential> {
119 tracing::Span::current().record("resource_name", &request.name);
120 self.check_required(&request, &context).await?;
121 let ident = ResourceIdent::credential(ResourceName::new([request.name.as_str()]));
124 let curr: Credential = self.get(&ident).await?.0.try_into()?;
125 let cred = Credential {
126 name: request.name.clone(),
127 full_name: request.name,
128 comment: request.comment,
129 purpose: curr.purpose,
130 read_only: request.read_only.unwrap_or(false),
131 used_for_managed_storage: false,
132 id: None,
133 created_at: None,
134 updated_at: None,
135 azure_managed_identity: request.azure_managed_identity,
136 azure_service_principal: request.azure_service_principal,
137 azure_storage_key: request.azure_storage_key,
138 aws_iam_role: request.aws_iam_role,
139 databricks_gcp_service_account: request.databricks_gcp_service_account,
140 owner: None,
141 created_by: None,
142 updated_by: None,
143 ..Default::default()
144 };
145 if !has_secret(&cred) {
146 return Err(Error::invalid_argument("No credentials provided"));
147 }
148 Ok(self.update(&ident, cred.into()).await?.0.try_into()?)
149 }
150
151 #[tracing::instrument(skip(self, context), fields(resource_name))]
152 async fn delete_credential(
153 &self,
154 request: DeleteCredentialRequest,
155 context: RequestContext,
156 ) -> Result<()> {
157 tracing::Span::current().record("resource_name", &request.name);
158 self.check_required(&request, &context).await?;
159 Ok(self.delete(&request.resource()).await?)
162 }
163}
164
165#[async_trait::async_trait]
166impl<T: ResourceStore + Policy<RequestContext>> CredentialHandlerExt for T {
167 async fn get_credential_internal(&self, request: GetCredentialRequest) -> Result<Credential> {
168 Ok(self
172 .get_with_secrets(&request.resource())
173 .await?
174 .0
175 .try_into()?)
176 }
177}
178
179impl SecuredAction for CreateCredentialRequest {
180 fn resource(&self) -> ResourceIdent {
181 ResourceIdent::credential(ResourceName::new([self.name.as_str()]))
182 }
183
184 fn permission(&self) -> &'static Permission {
185 &Permission::Create
186 }
187}
188
189impl SecuredAction for ListCredentialsRequest {
190 fn resource(&self) -> ResourceIdent {
191 ResourceIdent::credential(ResourceRef::Undefined)
192 }
193
194 fn permission(&self) -> &'static Permission {
195 &Permission::Read
196 }
197}
198
199impl SecuredAction for GetCredentialRequest {
200 fn resource(&self) -> ResourceIdent {
201 ResourceIdent::credential(ResourceName::new([self.name.as_str()]))
202 }
203
204 fn permission(&self) -> &'static Permission {
205 &Permission::Read
206 }
207}
208
209impl SecuredAction for UpdateCredentialRequest {
210 fn resource(&self) -> ResourceIdent {
211 ResourceIdent::credential(ResourceName::new([self.name.as_str()]))
212 }
213
214 fn permission(&self) -> &'static Permission {
215 &Permission::Manage
216 }
217}
218
219impl SecuredAction for DeleteCredentialRequest {
220 fn resource(&self) -> ResourceIdent {
221 ResourceIdent::credential(ResourceName::new([self.name.as_str()]))
222 }
223
224 fn permission(&self) -> &'static Permission {
225 &Permission::Manage
226 }
227}