rewrite service_account handler

This commit is contained in:
weisd
2025-01-21 16:17:28 +08:00
parent 296791b56b
commit 4f1cbf72c6
13 changed files with 707 additions and 456 deletions

View File

@@ -1,6 +1,6 @@
use crate::error::Error as IamError;
use crate::policy::Policy;
use crate::sys::Validator;
use crate::sys::{iam_policy_claim_name_sa, Validator, INHERITED_POLICY_TYPE};
use crate::utils;
use crate::utils::extract_claims;
use ecstore::error::{Error, Result};
@@ -158,6 +158,21 @@ impl Credentials {
.unwrap_or_default()
}
pub fn is_implied_policy(&self) -> bool {
if self.is_service_account() {
return self
.claims
.as_ref()
.map(|x| {
x.get(&iam_policy_claim_name_sa())
.map_or(false, |v| v == INHERITED_POLICY_TYPE)
})
.unwrap_or_default();
}
false
}
pub fn is_valid(&self) -> bool {
if self.status == "off" {
return false;
@@ -200,7 +215,7 @@ pub fn create_new_credentials_with_metadata(
let expiration = {
if let Some(v) = claims.get("exp") {
if let Some(expiry) = v.as_i64() {
Some(OffsetDateTime::from_unix_timestamp(expiry)?.to_offset(OffsetDateTime::now_local()?.offset()))
Some(OffsetDateTime::from_unix_timestamp(expiry)?.to_offset(OffsetDateTime::now_utc().offset()))
} else {
None
}

View File

@@ -132,3 +132,11 @@ pub fn is_err_no_such_group(err: &ecstore::error::Error) -> bool {
false
}
}
pub fn is_err_no_such_service_account(err: &ecstore::error::Error) -> bool {
if let Some(e) = err.downcast_ref::<Error>() {
matches!(e, Error::NoSuchServiceAccount(_))
} else {
false
}
}

View File

@@ -17,7 +17,7 @@ use ecstore::{
error::{Error, Result},
};
use log::{debug, warn};
use madmin::{AccountStatus, GroupDesc};
use madmin::{AccountStatus, AddOrUpdateUserReq, GroupDesc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{
@@ -483,12 +483,12 @@ where
let mut cr = ui.credentials.clone();
let current_secret_key = cr.secret_key.clone();
if !opts.secret_key.is_empty() {
if !is_secret_key_valid(&opts.secret_key) {
if let Some(secret) = opts.secret_key {
if !is_secret_key_valid(&secret) {
return Err(IamError::InvalidSecretKeyLength.into());
}
cr.secret_key = opts.secret_key;
cr.secret_key = secret;
}
if opts.name.is_some() {
@@ -554,7 +554,7 @@ where
Ok(OffsetDateTime::now_utc())
}
pub async fn policy_db_get(&self, name: &str, groups: &[String]) -> Result<Vec<String>> {
pub async fn policy_db_get(&self, name: &str, groups: &Option<Vec<String>>) -> Result<Vec<String>> {
if name.is_empty() {
return Err(Error::new(IamError::InvalidArgument));
}
@@ -562,11 +562,13 @@ where
let (mut policies, _) = self.policy_db_get_internal(name, false, false).await?;
let present = !policies.is_empty();
for group in groups.iter() {
let (gp, _) = self.policy_db_get_internal(group, true, present).await?;
gp.iter().for_each(|v| {
policies.push(v.clone());
});
if let Some(groups) = groups {
for group in groups.iter() {
let (gp, _) = self.policy_db_get_internal(group, true, present).await?;
gp.iter().for_each(|v| {
policies.push(v.clone());
});
}
}
Ok(policies)
@@ -946,7 +948,7 @@ where
m
}
pub async fn add_user(&self, access_key: &str, secret_key: &str, status: &str) -> Result<OffsetDateTime> {
pub async fn add_user(&self, access_key: &str, args: &AddOrUpdateUserReq) -> Result<OffsetDateTime> {
let users = self.cache.users.load();
if let Some(x) = users.get(access_key) {
warn!("user already exists: {:?}", x);
@@ -956,15 +958,14 @@ where
}
let status = {
match status {
val if val == AccountStatus::Enabled.as_ref() => auth::ACCOUNT_ON,
auth::ACCOUNT_ON => auth::ACCOUNT_ON,
match &args.status {
AccountStatus::Enabled => auth::ACCOUNT_ON,
_ => auth::ACCOUNT_OFF,
}
};
let user_entiry = UserIdentity::from(Credentials {
access_key: access_key.to_string(),
secret_key: secret_key.to_string(),
secret_key: args.secret_key.to_string(),
status: status.to_owned(),
..Default::default()
});

View File

@@ -27,6 +27,7 @@ use crate::store::UserType;
use ecstore::error::{Error, Result};
use ecstore::utils::crypto::base64_decode;
use ecstore::utils::crypto::base64_encode;
use madmin::AddOrUpdateUserReq;
use madmin::GroupDesc;
use serde_json::json;
use serde_json::Value;
@@ -178,9 +179,9 @@ impl<T: Store> IamSys<T> {
pub async fn new_service_account(
&self,
parent_user: &str,
groups: Vec<String>,
groups: Option<Vec<String>>,
opts: NewServiceAccountOpts,
) -> Result<OffsetDateTime> {
) -> Result<(Credentials, OffsetDateTime)> {
if parent_user.is_empty() {
return Err(IamError::InvalidArgument.into());
}
@@ -236,14 +237,15 @@ impl<T: Store> IamSys<T> {
let mut cred = create_new_credentials_with_metadata(&access_key, &secret_key, &m, &secret_key)?;
cred.parent_user = parent_user.to_owned();
cred.groups = Some(groups);
cred.groups = groups;
cred.status = ACCOUNT_ON.to_owned();
cred.name = opts.name;
cred.description = opts.description;
cred.expiration = opts.expiration;
self.store.add_service_account(cred).await
let create_at = self.store.add_service_account(cred.clone()).await?;
Ok((cred, create_at))
// TODO: notification
}
@@ -387,7 +389,7 @@ impl<T: Store> IamSys<T> {
// TODO: notification
}
pub async fn create_user(&self, access_key: &str, secret_key: &str, status: &str) -> Result<OffsetDateTime> {
pub async fn create_user(&self, access_key: &str, args: &AddOrUpdateUserReq) -> Result<OffsetDateTime> {
if !is_access_key_valid(access_key) {
return Err(IamError::InvalidAccessKeyLength.into());
}
@@ -396,11 +398,11 @@ impl<T: Store> IamSys<T> {
return Err(IamError::ContainsReservedChars.into());
}
if !is_secret_key_valid(secret_key) {
if !is_secret_key_valid(&args.secret_key) {
return Err(IamError::InvalidSecretKeyLength.into());
}
self.store.add_user(access_key, secret_key, status).await
self.store.add_user(access_key, args).await
// TODO: notification
}
@@ -470,7 +472,7 @@ impl<T: Store> IamSys<T> {
// TODO: notification
}
pub async fn policy_db_get(&self, name: &str, groups: &[String]) -> Result<Vec<String>> {
pub async fn policy_db_get(&self, name: &str, groups: &Option<Vec<String>>) -> Result<Vec<String>> {
self.store.policy_db_get(name, groups).await
}
@@ -580,7 +582,7 @@ impl<T: Store> IamSys<T> {
is_owner || combined_policy.is_allowed(&parent_args)
}
async fn get_combined_policy(&self, policies: &[String]) -> Policy {
pub async fn get_combined_policy(&self, policies: &[String]) -> Policy {
self.store.merge_policies(&policies.join(",")).await.1
}
@@ -676,7 +678,7 @@ pub struct NewServiceAccountOpts {
pub struct UpdateServiceAccountOpts {
pub session_policy: Option<Policy>,
pub secret_key: String,
pub secret_key: Option<String>,
pub name: Option<String>,
pub description: Option<String>,
pub expiration: Option<OffsetDateTime>,
@@ -702,7 +704,7 @@ pub trait Validator {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Args<'a> {
pub account: &'a str,
pub groups: &'a [String],
pub groups: &'a Option<Vec<String>>,
pub action: Action,
pub bucket: &'a str,
pub conditions: &'a HashMap<String, Vec<String>>,

View File

@@ -605,7 +605,13 @@ struct ArgsBuilder {
fn policy_is_allowed(policy: Policy, args: ArgsBuilder) -> bool {
policy.is_allowed(&Args {
account: &args.account,
groups: &args.groups,
groups: &{
if args.groups.is_empty() {
None
} else {
Some(args.groups.clone())
}
},
action: args.action.as_str().try_into().unwrap(),
bucket: &args.bucket,
conditions: &args.conditions,

View File

@@ -129,7 +129,7 @@ pub struct AddServiceAccountReq {
pub secret_key: String,
#[serde(rename = "name")]
pub name: String,
pub name: Option<String>,
#[serde(rename = "description", skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
@@ -137,3 +137,86 @@ pub struct AddServiceAccountReq {
#[serde(rename = "expiration", skip_serializing_if = "Option::is_none")]
pub expiration: Option<OffsetDateTime>,
}
impl AddServiceAccountReq {
pub fn validate(&self) -> Result<(), String> {
if self.access_key.is_empty() {
return Err("accessKey is empty".to_string());
}
if self.secret_key.is_empty() {
return Err("secretKey is empty".to_string());
}
if self.name.is_none() {
return Err("name is empty".to_string());
}
// TODO: validate
Ok(())
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Credentials<'a> {
pub access_key: &'a str,
pub secret_key: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_token: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "time::serde::rfc3339::option")]
pub expiration: Option<OffsetDateTime>,
}
#[derive(Serialize)]
pub struct AddServiceAccountResp<'a> {
pub credentials: Credentials<'a>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct InfoServiceAccountResp {
pub parent_user: String,
pub account_status: String,
pub implied_policy: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub policy: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "time::serde::rfc3339::option")]
pub expiration: Option<OffsetDateTime>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateServiceAccountReq {
#[serde(rename = "newPolicy", skip_serializing_if = "Option::is_none")]
pub new_policy: Option<String>,
#[serde(rename = "newSecretKey", skip_serializing_if = "Option::is_none")]
pub new_secret_key: Option<String>,
#[serde(rename = "newStatus", skip_serializing_if = "Option::is_none")]
pub new_status: Option<String>,
#[serde(rename = "newName", skip_serializing_if = "Option::is_none")]
pub new_name: Option<String>,
#[serde(rename = "newDescription", skip_serializing_if = "Option::is_none")]
pub new_description: Option<String>,
#[serde(rename = "newExpiration", skip_serializing_if = "Option::is_none")]
pub new_expiration: Option<OffsetDateTime>,
}
impl UpdateServiceAccountReq {
pub fn validate(&self) -> Result<(), String> {
// TODO: validate
Ok(())
}
}

View File

@@ -270,10 +270,7 @@ impl Operation for AssumeRoleHandle {
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
if let Err(_err) = iam_store
.policy_db_get(&cred.access_key, &cred.groups.unwrap_or_default())
.await
{
if let Err(_err) = iam_store.policy_db_get(&cred.access_key, &cred.groups).await {
return Err(s3_error!(InvalidArgument, "invalid policy arg"));
}

View File

@@ -1,139 +1,246 @@
use std::collections::HashMap;
use crate::admin::{handlers::check_key_valid, utils::has_space_be};
use crate::admin::{handlers::get_session_token, router::Operation};
use http::HeaderMap;
use hyper::StatusCode;
use iam::{
auth::CredentialsBuilder,
policy::action::{Action, AdminAction::ListServiceAccountsAdminAction},
error::is_err_no_such_service_account,
get_global_action_cred,
policy::Policy,
sys::{NewServiceAccountOpts, UpdateServiceAccountOpts},
};
use madmin::{
AddServiceAccountReq, AddServiceAccountResp, Credentials, InfoServiceAccountResp, ListServiceAccountsResp,
ServiceAccountInfo, UpdateServiceAccountReq,
};
use madmin::{AddServiceAccountReq, ListServiceAccountsResp, ServiceAccountInfo};
use matchit::Params;
use s3s::S3ErrorCode::InvalidRequest;
use s3s::{header::CONTENT_TYPE, s3_error, Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result};
use serde::Deserialize;
use serde_urlencoded::from_bytes;
use std::collections::HashMap;
use tracing::{debug, warn};
use crate::admin::router::Operation;
use crate::admin::{
handlers::check_key_valid,
models::service_account::{AddServiceAccountResp, Credentials, InfoServiceAccountResp},
};
pub struct AddServiceAccount {}
#[async_trait::async_trait]
impl Operation for AddServiceAccount {
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
warn!("handle AddServiceAccount ");
let Some(req_cred) = req.credentials else {
return Err(s3_error!(InvalidRequest, "get cred failed"));
};
unimplemented!()
let (cred, _owner) = check_key_valid(get_session_token(&req.headers), &req_cred.access_key).await?;
// let Some(input_cred) = req.credentials else {
// return Err(s3_error!(InvalidRequest, "get cred failed"));
// };
// let _is_owner = true; // 先按true处理后期根据请求决定。
let mut input = req.input;
let body = match input.store_all_unlimited().await {
Ok(b) => b,
Err(e) => {
warn!("get body failed, e: {:?}", e);
return Err(s3_error!(InvalidRequest, "get body failed"));
}
};
// let mut input = req.input;
// let body = match input.store_all_unlimited().await {
// Ok(b) => b,
// Err(e) => {
// warn!("get body failed, e: {:?}", e);
// return Err(s3_error!(InvalidRequest, "get body failed"));
// }
// };
let mut create_req: AddServiceAccountReq =
serde_json::from_slice(&body[..]).map_err(|e| s3_error!(InvalidRequest, "unmarshal body failed, e: {:?}", e))?;
create_req.expiration = create_req.expiration.and_then(|expire| expire.replace_millisecond(0).ok());
// let mut create_req: AddServiceAccountReq =
// serde_json::from_slice(&body[..]).map_err(|e| s3_error!(InvalidRequest, "unmarshal body failed, e: {:?}", e))?;
if has_space_be(&create_req.access_key) {
return Err(s3_error!(InvalidRequest, "access key has spaces"));
}
// create_req.expiration = create_req.expiration.and_then(|expire| expire.replace_millisecond(0).ok());
create_req
.validate()
.map_err(|e| S3Error::with_message(InvalidRequest, e.to_string()))?;
// if create_req.access_key.trim().len() != create_req.access_key.len() {
// return Err(s3_error!(InvalidRequest, "access key has spaces"));
// }
let Some(sys_cred) = get_global_action_cred() else {
return Err(s3_error!(InvalidRequest, "get sys cred failed"));
};
// let (cred, _) = check_key_valid(None, &input_cred.access_key).await.map_err(|e| {
// debug!("check key failed: {e:?}");
// s3_error!(InternalError, "check key failed")
// })?;
if sys_cred.access_key == create_req.access_key {
return Err(s3_error!(InvalidArgument, "can't create user with system access key"));
}
// // TODO check create_req validity
let mut target_user = if let Some(u) = create_req.target_user {
u
} else {
cred.access_key.clone()
};
// // 校验合法性, Name, Expiration, Description
// let target_user = if let Some(u) = create_req.target_user {
// u
// } else {
// cred.access_key
// };
// let _deny_only = true;
let req_user = cred.access_key.clone();
let mut req_parent_user = cred.access_key.clone();
let req_groups = cred.groups.clone();
let mut req_is_derived_cred = false;
// // todo 校验权限
if cred.is_owner() || cred.is_service_account() {
req_parent_user = cred.parent_user.clone();
req_is_derived_cred = true;
}
// // if !iam::is_allowed(Args {
// // account: &cred.access_key,
// // groups: &[],
// // action: Action::AdminAction(AdminAction::CreateServiceAccountAdminAction),
// // bucket: "",
// // conditions: &HashMap::new(),
// // is_owner,
// // object: "",
// // claims: &HashMap::new(),
// // deny_only,
// // })
// // .await
// // .unwrap_or(false)
// // {
// // return Err(s3_error!(AccessDenied));
// // }
// //
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
// let cred = CredentialsBuilder::new()
// .parent_user(target_user)
// .access_key(create_req.access_key)
// .secret_key(create_req.secret_key)
// .description(create_req.description.unwrap_or_default())
// .expiration(create_req.expiration)
// .session_policy({
// match create_req.policy {
// Some(p) if !p.is_empty() => {
// Some(serde_json::from_slice(p.as_bytes()).map_err(|_| s3_error!(InvalidRequest, "invalid policy"))?)
// }
// _ => None,
// }
// })
// .name(create_req.name)
// .try_build()
// .map_err(|e| s3_error!(InvalidRequest, "build cred failed, err: {:?}", e))?;
if target_user != cred.access_key {
let has_user = iam_store.get_user(&target_user).await;
if has_user.is_none() && target_user != sys_cred.access_key {
return Err(s3_error!(InvalidRequest, "target user not exist"));
}
}
// let resp = serde_json::to_vec(&AddServiceAccountResp {
// credentials: Credentials {
// access_key: &cred.access_key,
// secret_key: &cred.secret_key,
// session_token: None,
// expiration: cred.expiration,
// },
// })
// .unwrap()
// .into();
let is_svc_acc = target_user == req_user || target_user == req_parent_user;
// iam::add_service_account(cred).await.map_err(|e| {
// debug!("add cred failed: {e:?}");
// s3_error!(InternalError, "add cred failed")
// })?;
let mut taget_groups = None;
let mut opts = NewServiceAccountOpts {
access_key: create_req.access_key,
secret_key: create_req.secret_key,
name: create_req.name,
description: create_req.description,
expiration: create_req.expiration,
session_policy: create_req.policy.and_then(|p| Policy::parse_config(p.as_bytes()).ok()),
..Default::default()
};
// Ok(S3Response::new((StatusCode::OK, resp)))
if is_svc_acc {
if req_is_derived_cred {
if req_parent_user.is_empty() {
return Err(s3_error!(AccessDenied, "only derived cred can create service account"));
}
target_user = req_parent_user;
}
taget_groups = req_groups;
if let Some(claims) = cred.claims {
if opts.claims.is_none() {
opts.claims = Some(HashMap::new());
}
for (k, v) in claims.iter() {
if claims.contains_key("exp") {
continue;
}
opts.claims.as_mut().unwrap().insert(k.clone(), v.clone());
}
}
}
let (new_cred, _) = iam_store
.new_service_account(&target_user, taget_groups, opts)
.await
.map_err(|e| {
debug!("create service account failed, e: {:?}", e);
s3_error!(InternalError, "create service account failed")
})?;
let resp = AddServiceAccountResp {
credentials: Credentials {
access_key: &new_cred.access_key,
secret_key: &new_cred.secret_key,
session_token: None,
expiration: new_cred.expiration,
},
};
let body = serde_json::to_vec(&resp).map_err(|e| s3_error!(InternalError, "marshal body failed, e: {:?}", e))?;
let mut header = HeaderMap::new();
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
Ok(S3Response::with_headers((StatusCode::OK, Body::from(body)), header))
}
}
#[derive(Debug, Default, Deserialize)]
struct AccessKeyQuery {
#[serde(rename = "accessKey")]
pub access_key: String,
}
pub struct UpdateServiceAccount {}
#[async_trait::async_trait]
impl Operation for UpdateServiceAccount {
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
warn!("handle UpdateServiceAccount");
let Some(_cred) = req.credentials else { return Err(s3_error!(InvalidRequest, "get cred failed")) };
// let Some(req_cred) = req.credentials else {
// return Err(s3_error!(InvalidRequest, "get cred failed"));
// };
// return Err(s3_error!(NotImplemented));
//
// let (cred, _owner) = check_key_valid(get_session_token(&req.headers), &req_cred.access_key).await?;
todo!()
let query = {
if let Some(query) = req.uri.query() {
let input: AccessKeyQuery =
from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed1"))?;
input
} else {
AccessKeyQuery::default()
}
};
if query.access_key.is_empty() {
return Err(s3_error!(InvalidArgument, "access key is empty"));
}
let access_key = query.access_key;
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
// let svc_account = iam_store.get_service_account(&access_key).await.map_err(|e| {
// debug!("get service account failed, e: {:?}", e);
// s3_error!(InternalError, "get service account failed")
// })?;
let mut input = req.input;
let body = match input.store_all_unlimited().await {
Ok(b) => b,
Err(e) => {
warn!("get body failed, e: {:?}", e);
return Err(s3_error!(InvalidRequest, "get body failed"));
}
};
let update_req: UpdateServiceAccountReq =
serde_json::from_slice(&body[..]).map_err(|e| s3_error!(InvalidRequest, "unmarshal body failed, e: {:?}", e))?;
update_req
.validate()
.map_err(|e| S3Error::with_message(InvalidRequest, e.to_string()))?;
// TODO: is_allowed
let sp = {
if let Some(policy) = update_req.new_policy {
let sp = Policy::parse_config(policy.as_bytes()).map_err(|e| {
debug!("parse policy failed, e: {:?}", e);
s3_error!(InvalidArgument, "parse policy failed")
})?;
if sp.version.is_empty() && sp.statements.is_empty() {
None
} else {
Some(sp)
}
} else {
None
}
};
let opts = UpdateServiceAccountOpts {
secret_key: update_req.new_secret_key,
status: update_req.new_status,
name: update_req.new_name,
description: update_req.new_description,
expiration: update_req.new_expiration,
session_policy: sp,
};
let _ = iam_store.update_service_account(&access_key, opts).await.map_err(|e| {
debug!("update service account failed, e: {:?}", e);
s3_error!(InternalError, "update service account failed")
})?;
let mut header = HeaderMap::new();
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header))
}
}
@@ -143,76 +250,80 @@ impl Operation for InfoServiceAccount {
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
warn!("handle InfoServiceAccount");
// let Some(cred) = req.credentials else { return Err(s3_error!(InvalidRequest, "get cred failed")) };
let query = {
if let Some(query) = req.uri.query() {
let input: AccessKeyQuery =
from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed1"))?;
input
} else {
AccessKeyQuery::default()
}
};
// //accessKey
// let Some(ak) = req.uri.query().and_then(|x| {
// for mut x in x.split('&').map(|x| x.split('=')) {
// let Some(key) = x.next() else {
// continue;
// };
if query.access_key.is_empty() {
return Err(s3_error!(InvalidArgument, "access key is empty"));
}
// if key != "accessKey" {
// continue;
// }
let access_key = query.access_key;
// let Some(value) = x.next() else {
// continue;
// };
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
// return Some(value);
// }
let (svc_account, session_policy) = iam_store.get_service_account(&access_key).await.map_err(|e| {
debug!("get service account failed, e: {:?}", e);
s3_error!(InternalError, "get service account failed")
})?;
// None
// }) else {
// return Err(s3_error!(InvalidRequest, "access key is not exist"));
// };
// TODO: is_allowed
unimplemented!()
let implied_policy = if let Some(policy) = session_policy.as_ref() {
policy.version.is_empty() && policy.statements.is_empty()
} else {
true
};
// let (sa, _sp) = iam::get_service_account(ak).await.map_err(|e| {
// debug!("get service account failed, err: {e:?}");
// s3_error!(InternalError)
// })?;
let svc_account_policy = {
if !implied_policy {
session_policy
} else {
let policies = iam_store
.policy_db_get(&svc_account.parent_user, &svc_account.groups)
.await
.map_err(|e| {
debug!("get service account policy failed, e: {:?}", e);
s3_error!(InternalError, "get service account policy failed")
})?;
// if !iam::is_allowed(Args {
// account: &sa.access_key,
// groups: &sa.groups.unwrap_or_default()[..],
// action: Action::AdminAction(ListServiceAccountsAdminAction),
// bucket: "",
// conditions: &HashMap::new(),
// is_owner: true,
// object: "",
// claims: &HashMap::new(),
// deny_only: false,
// })
// .await
// .map_err(|_| s3_error!(InternalError))?
// {
// let req_user = &cred.access_key;
// if req_user != &sa.parent_user {
// return Err(s3_error!(AccessDenied));
// }
// }
Some(iam_store.get_combined_policy(&policies).await)
}
};
// let body = serde_json::to_vec(&InfoServiceAccountResp {
// parent_user: sa.parent_user,
// account_status: sa.status,
// implied_policy: true,
// // policy: serde_json::to_string_pretty(&sva).map_err(|_| s3_error!(InternalError, "json marshal failed"))?,
// policy: "".into(),
// name: sa.name.unwrap_or_default(),
// description: sa.description.unwrap_or_default(),
// expiration: sa.expiration,
// })
// .map_err(|_| s3_error!(InternalError, "json marshal failed"))?;
let policy = {
if let Some(policy) = svc_account_policy {
Some(serde_json::to_string(&policy).map_err(|e| {
debug!("marshal policy failed, e: {:?}", e);
s3_error!(InternalError, "marshal policy failed")
})?)
} else {
None
}
};
// Ok(S3Response::new((
// StatusCode::OK,
// crypto::encrypt_data(cred.access_key.as_bytes(), &body[..])
// .map_err(|_| s3_error!(InternalError, "encrypt data failed"))?
// .into(),
// )))
let resp = InfoServiceAccountResp {
parent_user: svc_account.parent_user,
account_status: svc_account.status,
implied_policy,
name: svc_account.name,
description: svc_account.description,
expiration: svc_account.expiration,
policy,
};
let body = serde_json::to_vec(&resp).map_err(|e| s3_error!(InternalError, "marshal body failed, e: {:?}", e))?;
let mut header = HeaderMap::new();
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
Ok(S3Response::with_headers((StatusCode::OK, Body::from(body)), header))
}
}
@@ -226,76 +337,125 @@ pub struct ListServiceAccount {}
impl Operation for ListServiceAccount {
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
warn!("handle ListServiceAccount");
unimplemented!()
// let query = {
// if let Some(query) = req.uri.query() {
// let input: ListServiceAccountQuery =
// from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?;
// input
// } else {
// ListServiceAccountQuery::default()
// }
// };
// let target_account = if let Some(user) = query.user {
// user
// } else {
// let Some(input_cred) = req.credentials else {
// return Err(s3_error!(InvalidRequest, "get cred failed"));
// };
let query = {
if let Some(query) = req.uri.query() {
let input: ListServiceAccountQuery =
from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?;
input
} else {
ListServiceAccountQuery::default()
}
};
// let (cred, _owner) = check_key_valid(None, &input_cred.access_key).await.map_err(|e| {
// debug!("check key failed: {e:?}");
// s3_error!(InternalError, "check key failed")
// })?;
let Some(input_cred) = req.credentials else {
return Err(s3_error!(InvalidRequest, "get cred failed"));
};
// if cred.parent_user.is_empty() {
// input_cred.access_key
// } else {
// cred.parent_user
// }
// };
let (cred, _owner) = check_key_valid(get_session_token(&req.headers), &input_cred.access_key)
.await
.map_err(|e| {
debug!("check key failed: {e:?}");
s3_error!(InternalError, "check key failed")
})?;
// let service_accounts = iam::list_service_accounts(&target_account).await.map_err(|e| {
// debug!("list service account failed: {e:?}");
// s3_error!(InternalError, "list service account failed")
// })?;
let target_account = if let Some(user) = query.user {
if user != input_cred.access_key {
user
} else if cred.parent_user.is_empty() {
input_cred.access_key
} else {
cred.parent_user
}
} else if cred.parent_user.is_empty() {
input_cred.access_key
} else {
cred.parent_user
};
// let accounts: Vec<ServiceAccountInfo> = service_accounts
// .into_iter()
// .map(|sa| ServiceAccountInfo {
// parent_user: sa.parent_user,
// account_status: sa.status,
// implied_policy: true, // or set according to your logic
// access_key: sa.access_key,
// name: sa.name,
// description: sa.description,
// expiration: sa.expiration,
// })
// .collect();
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
// let data = serde_json::to_vec(&ListServiceAccountsResp { accounts })
// .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal users err {}", e)))?;
let service_accounts = iam_store.list_service_accounts(&target_account).await.map_err(|e| {
debug!("list service account failed: {e:?}");
s3_error!(InternalError, "list service account failed")
})?;
// let mut header = HeaderMap::new();
// header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
let accounts: Vec<ServiceAccountInfo> = service_accounts
.into_iter()
.map(|sa| ServiceAccountInfo {
parent_user: sa.parent_user.clone(),
account_status: sa.status.clone(),
implied_policy: sa.is_implied_policy(), // or set according to your logic
access_key: sa.access_key,
name: sa.name,
description: sa.description,
expiration: sa.expiration,
})
.collect();
// Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), header))
let data = serde_json::to_vec(&ListServiceAccountsResp { accounts })
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal users err {}", e)))?;
let mut header = HeaderMap::new();
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), header))
}
}
pub struct DeleteServiceAccount {}
#[async_trait::async_trait]
impl Operation for DeleteServiceAccount {
async fn call(&self, req: S3Request<Body>, params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
warn!("handle DeleteServiceAccount");
let Some(_cred) = req.credentials else { return Err(s3_error!(InvalidRequest, "get cred failed")) };
let Some(_service_account) = params.get("accessKey") else {
return Err(s3_error!(InvalidRequest, "Invalid arguments specified."));
let Some(input_cred) = req.credentials else {
return Err(s3_error!(InvalidRequest, "get cred failed"));
};
todo!()
let (_cred, _owner) = check_key_valid(get_session_token(&req.headers), &input_cred.access_key)
.await
.map_err(|e| {
debug!("check key failed: {e:?}");
s3_error!(InternalError, "check key failed")
})?;
let query = {
if let Some(query) = req.uri.query() {
let input: AccessKeyQuery =
from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?;
input
} else {
AccessKeyQuery::default()
}
};
if query.access_key.is_empty() {
return Err(s3_error!(InvalidArgument, "access key is empty"));
}
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
let _svc_account = match iam_store.get_service_account(&query.access_key).await {
Ok((res, _)) => Some(res),
Err(err) => {
if is_err_no_such_service_account(&err) {
return Err(s3_error!(InvalidRequest, "service account not exist"));
}
None
}
};
// TODO: is_allowed
iam_store.delete_service_account(&query.access_key).await.map_err(|e| {
debug!("delete service account failed, e: {:?}", e);
s3_error!(InternalError, "delete service account failed")
})?;
let mut header = HeaderMap::new();
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header))
}
}

View File

@@ -1,3 +1,5 @@
use std::str::from_utf8;
use http::{HeaderMap, StatusCode};
use iam::get_global_action_cred;
use madmin::{AccountStatus, AddOrUpdateUserReq};
@@ -10,6 +12,7 @@ use tracing::warn;
use crate::admin::{
handlers::{check_key_valid, get_session_token},
router::Operation,
utils::has_space_be,
};
#[derive(Debug, Deserialize, Default)]
@@ -23,80 +26,86 @@ pub struct AddUser {}
#[async_trait::async_trait]
impl Operation for AddUser {
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
warn!("handle AddUser");
unimplemented!()
// let query = {
// if let Some(query) = req.uri.query() {
// let input: AddUserQuery =
// from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed1"))?;
// input
// } else {
// AddUserQuery::default()
// }
let query = {
if let Some(query) = req.uri.query() {
let input: AddUserQuery =
from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed1"))?;
input
} else {
AddUserQuery::default()
}
};
let Some(input_cred) = req.credentials else {
return Err(s3_error!(InvalidRequest, "get cred failed"));
};
let (cred, _owner) = check_key_valid(get_session_token(&req.headers), &input_cred.access_key).await?;
let ak = query.access_key.as_deref().unwrap_or_default();
if ak.is_empty() {
return Err(s3_error!(InvalidArgument, "access key is empty"));
}
let mut input = req.input;
let body = match input.store_all_unlimited().await {
Ok(b) => b,
Err(e) => {
warn!("get body failed, e: {:?}", e);
return Err(s3_error!(InvalidRequest, "get body failed"));
}
};
// let body_bytes = decrypt_data(input_cred.secret_key.expose().as_bytes(), &body)
// .map_err(|e| S3Error::with_message(S3ErrorCode::InvalidArgument, format!("decrypt_data err {}", e)))?;
let args: AddOrUpdateUserReq = serde_json::from_slice(&body)
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("unmarshal body err {}", e)))?;
warn!("add user args {:?}", args);
if args.secret_key.is_empty() {
return Err(s3_error!(InvalidArgument, "access key is empty"));
}
if let Some(sys_cred) = get_global_action_cred() {
if sys_cred.access_key == ak {
return Err(s3_error!(InvalidArgument, "can't create user with system access key"));
}
}
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
if let Some(user) = iam_store.get_user(ak).await {
if (user.credentials.is_temp() || user.credentials.is_service_account()) && cred.parent_user == ak {
return Err(s3_error!(InvalidArgument, "can't create user with service account access key"));
}
} else if has_space_be(ak) {
return Err(s3_error!(InvalidArgument, "access key has space"));
}
if from_utf8(ak.as_bytes()).is_err() {
return Err(s3_error!(InvalidArgument, "access key is not utf8"));
}
// let check_deny_only = if ak == cred.access_key {
// true
// } else {
// false
// };
// let Some(input_cred) = req.credentials else {
// return Err(s3_error!(InvalidRequest, "get cred failed"));
// };
// TODO: is_allowed
// let ak = query.access_key.as_deref().unwrap_or_default();
iam_store
.create_user(ak, &args)
.await
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("create_user err {}", e)))?;
// if ak.is_empty() {
// return Err(s3_error!(InvalidArgument, "access key is empty"));
// }
let mut header = HeaderMap::new();
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
// let mut input = req.input;
// let body = match input.store_all_unlimited().await {
// Ok(b) => b,
// Err(e) => {
// warn!("get body failed, e: {:?}", e);
// return Err(s3_error!(InvalidRequest, "get body failed"));
// }
// };
// // let body_bytes = decrypt_data(input_cred.secret_key.expose().as_bytes(), &body)
// // .map_err(|e| S3Error::with_message(S3ErrorCode::InvalidArgument, format!("decrypt_data err {}", e)))?;
// let args: AddOrUpdateUserReq = serde_json::from_slice(&body)
// .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("unmarshal body err {}", e)))?;
// warn!("add user args {:?}", args);
// if args.secret_key.is_empty() {
// return Err(s3_error!(InvalidArgument, "access key is empty"));
// }
// if let Some(sys_cred) = get_global_action_cred() {
// if sys_cred.access_key == ak {
// return Err(s3_error!(InvalidArgument, "can't create user with system access key"));
// }
// }
// if let (Some(user), true) = iam::get_user(ak)
// .await
// .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal users err {}", e)))?
// {
// if user.credentials.is_temp() || user.credentials.is_service_account() {
// return Err(s3_error!(InvalidArgument, "can't create user with service account access key"));
// }
// }
// let token = get_session_token(&req.headers);
// let (cred, _) = check_key_valid(token, &input_cred.access_key).await?;
// if (cred.is_temp() || cred.is_service_account()) && cred.parent_user == input_cred.access_key {
// return Err(s3_error!(InvalidArgument, "can't create user with service account access key"));
// }
// iam::create_user(ak, &args.secret_key, "enabled")
// .await
// .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("create_user err {}", e)))?;
// let mut header = HeaderMap::new();
// header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
// Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header))
Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header))
}
}
@@ -106,43 +115,44 @@ impl Operation for SetUserStatus {
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
warn!("handle SetUserStatus");
unimplemented!()
let query = {
if let Some(query) = req.uri.query() {
let input: AddUserQuery =
from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?;
input
} else {
AddUserQuery::default()
}
};
// let query = {
// if let Some(query) = req.uri.query() {
// let input: AddUserQuery =
// from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?;
// input
// } else {
// AddUserQuery::default()
// }
// };
let ak = query.access_key.as_deref().unwrap_or_default();
// let ak = query.access_key.as_deref().unwrap_or_default();
if ak.is_empty() {
return Err(s3_error!(InvalidArgument, "access key is empty"));
}
// if ak.is_empty() {
// return Err(s3_error!(InvalidArgument, "access key is empty"));
// }
let Some(input_cred) = req.credentials else {
return Err(s3_error!(InvalidRequest, "get cred failed"));
};
// let Some(input_cred) = req.credentials else {
// return Err(s3_error!(InvalidRequest, "get cred failed"));
// };
if input_cred.access_key == ak {
return Err(s3_error!(InvalidArgument, "can't change status of self"));
}
// if input_cred.access_key == ak {
// return Err(s3_error!(InvalidArgument, "can't change status of self"));
// }
let status = AccountStatus::try_from(query.status.as_deref().unwrap_or_default())
.map_err(|e| S3Error::with_message(S3ErrorCode::InvalidArgument, e))?;
// let status = AccountStatus::try_from(query.status.as_deref().unwrap_or_default())
// .map_err(|e| S3Error::with_message(S3ErrorCode::InvalidArgument, e))?;
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
// iam::set_user_status(ak, status)
// .await
// .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("set_user_status err {}", e)))?;
iam_store
.set_user_status(ak, status)
.await
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("set_user_status err {}", e)))?;
// let mut header = HeaderMap::new();
// header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
let mut header = HeaderMap::new();
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
// Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header))
Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header))
}
}
@@ -151,25 +161,27 @@ pub struct ListUsers {}
impl Operation for ListUsers {
async fn call(&self, _req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
warn!("handle ListUsers");
unimplemented!()
// let users = iam::list_users()
// .await
// .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, e.to_string()))?;
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
// let data = serde_json::to_vec(&users)
// .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal users err {}", e)))?;
let users = iam_store
.list_users()
.await
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, e.to_string()))?;
// // let Some(input_cred) = req.credentials else {
// // return Err(s3_error!(InvalidRequest, "get cred failed"));
// // };
let data = serde_json::to_vec(&users)
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal users err {}", e)))?;
// // let body = encrypt_data(input_cred.secret_key.expose().as_bytes(), &data)
// // .map_err(|e| S3Error::with_message(S3ErrorCode::InvalidArgument, format!("encrypt_data err {}", e)))?;
// let Some(input_cred) = req.credentials else {
// return Err(s3_error!(InvalidRequest, "get cred failed"));
// };
// let mut header = HeaderMap::new();
// header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
// let body = encrypt_data(input_cred.secret_key.expose().as_bytes(), &data)
// .map_err(|e| S3Error::with_message(S3ErrorCode::InvalidArgument, format!("encrypt_data err {}", e)))?;
// Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), header))
let mut header = HeaderMap::new();
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), header))
}
}
@@ -178,39 +190,52 @@ pub struct RemoveUser {}
impl Operation for RemoveUser {
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
warn!("handle RemoveUser");
unimplemented!()
// let query = {
// if let Some(query) = req.uri.query() {
// let input: AddUserQuery =
// from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?;
// input
// } else {
// AddUserQuery::default()
// }
// };
// let ak = query.access_key.as_deref().unwrap_or_default();
let query = {
if let Some(query) = req.uri.query() {
let input: AddUserQuery =
from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?;
input
} else {
AddUserQuery::default()
}
};
// if ak.is_empty() {
// return Err(s3_error!(InvalidArgument, "access key is empty"));
// }
let ak = query.access_key.as_deref().unwrap_or_default();
// let (is_temp, _) = iam::is_temp_user(ak)
// .await
// .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("is_temp_user err {}", e)))?;
if ak.is_empty() {
return Err(s3_error!(InvalidArgument, "access key is empty"));
}
// if is_temp {
// return Err(s3_error!(InvalidArgument, "can't remove temp user"));
// }
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
// iam::delete_user(ak, true)
// .await
// .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("delete_user err {}", e)))?;
let (is_temp, _) = iam_store
.is_temp_user(ak)
.await
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("is_temp_user err {}", e)))?;
// let mut header = HeaderMap::new();
// header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
if is_temp {
return Err(s3_error!(InvalidArgument, "can't remove temp user"));
}
// Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header))
let (cred, _owner) = check_key_valid(get_session_token(&req.headers), ak).await?;
let sys_cred = get_global_action_cred()
.ok_or_else(|| S3Error::with_message(S3ErrorCode::InternalError, "get_global_action_cred failed"))?;
if ak == sys_cred.access_key || ak == cred.access_key {
return Err(s3_error!(InvalidArgument, "can't remove self"));
}
iam_store
.delete_user(ak, true)
.await
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("delete_user err {}", e)))?;
let mut header = HeaderMap::new();
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
Ok(S3Response::with_headers((StatusCode::OK, Body::empty()), header))
}
}
@@ -219,33 +244,36 @@ pub struct GetUserInfo {}
impl Operation for GetUserInfo {
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
warn!("handle GetUserInfo");
unimplemented!()
// let query = {
// if let Some(query) = req.uri.query() {
// let input: AddUserQuery =
// from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?;
// input
// } else {
// AddUserQuery::default()
// }
// };
// let ak = query.access_key.as_deref().unwrap_or_default();
let query = {
if let Some(query) = req.uri.query() {
let input: AddUserQuery =
from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get body failed"))?;
input
} else {
AddUserQuery::default()
}
};
// if ak.is_empty() {
// return Err(s3_error!(InvalidArgument, "access key is empty"));
// }
let ak = query.access_key.as_deref().unwrap_or_default();
// let info = iam::get_user_info(ak)
// .await
// .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, e.to_string()))?;
if ak.is_empty() {
return Err(s3_error!(InvalidArgument, "access key is empty"));
}
// let data = serde_json::to_vec(&info)
// .map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal user err {}", e)))?;
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
// let mut header = HeaderMap::new();
// header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
let info = iam_store
.get_user_info(ak)
.await
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, e.to_string()))?;
// Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), header))
let data = serde_json::to_vec(&info)
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal user err {}", e)))?;
let mut header = HeaderMap::new();
header.insert(CONTENT_TYPE, "application/json".parse().unwrap());
Ok(S3Response::with_headers((StatusCode::OK, Body::from(data)), header))
}
}

View File

@@ -1,6 +1,6 @@
pub mod handlers;
pub mod models;
pub mod router;
pub mod utils;
use common::error::Result;
// use ecstore::global::{is_dist_erasure, is_erasure};

View File

@@ -1 +0,0 @@
pub mod service_account;

View File

@@ -1,51 +0,0 @@
use serde::Serialize;
use time::OffsetDateTime;
// #[derive(Deserialize, Default)]
// #[serde(rename_all = "camelCase", default)]
// pub struct AddServiceAccountReq {
// pub access_key: String,
// pub secret_key: String,
// pub policy: Option<Vec<u8>>,
// pub target_user: Option<String>,
// pub name: String,
// pub description: String,
// #[serde(with = "time::serde::rfc3339::option")]
// #[serde(skip_serializing_if = "Option::is_none")]
// pub expiration: Option<OffsetDateTime>,
// }
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Credentials<'a> {
pub access_key: &'a str,
pub secret_key: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_token: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "time::serde::rfc3339::option")]
pub expiration: Option<OffsetDateTime>,
}
#[derive(Serialize)]
pub struct AddServiceAccountResp<'a> {
pub credentials: Credentials<'a>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct InfoServiceAccountResp {
pub parent_user: String,
pub account_status: String,
pub implied_policy: bool,
pub policy: String,
#[serde(skip_serializing_if = "String::is_empty")]
pub name: String,
#[serde(skip_serializing_if = "String::is_empty")]
pub description: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "time::serde::rfc3339::option")]
pub expiration: Option<OffsetDateTime>,
}

View File

@@ -0,0 +1,3 @@
pub fn has_space_be(s: &str) -> bool {
s.trim().len() != s.len()
}