diff --git a/iam/src/auth/credentials.rs b/iam/src/auth/credentials.rs index 3f860587..ecfb0821 100644 --- a/iam/src/auth/credentials.rs +++ b/iam/src/auth/credentials.rs @@ -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 } diff --git a/iam/src/error.rs b/iam/src/error.rs index 0244f96a..f99cf89d 100644 --- a/iam/src/error.rs +++ b/iam/src/error.rs @@ -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::() { + matches!(e, Error::NoSuchServiceAccount(_)) + } else { + false + } +} diff --git a/iam/src/manager.rs b/iam/src/manager.rs index 7cabb1af..7cdb2f48 100644 --- a/iam/src/manager.rs +++ b/iam/src/manager.rs @@ -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> { + pub async fn policy_db_get(&self, name: &str, groups: &Option>) -> Result> { 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 { + pub async fn add_user(&self, access_key: &str, args: &AddOrUpdateUserReq) -> Result { 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() }); diff --git a/iam/src/sys.rs b/iam/src/sys.rs index 57d01c88..5188bab8 100644 --- a/iam/src/sys.rs +++ b/iam/src/sys.rs @@ -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 IamSys { pub async fn new_service_account( &self, parent_user: &str, - groups: Vec, + groups: Option>, opts: NewServiceAccountOpts, - ) -> Result { + ) -> Result<(Credentials, OffsetDateTime)> { if parent_user.is_empty() { return Err(IamError::InvalidArgument.into()); } @@ -236,14 +237,15 @@ impl IamSys { 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 IamSys { // TODO: notification } - pub async fn create_user(&self, access_key: &str, secret_key: &str, status: &str) -> Result { + pub async fn create_user(&self, access_key: &str, args: &AddOrUpdateUserReq) -> Result { if !is_access_key_valid(access_key) { return Err(IamError::InvalidAccessKeyLength.into()); } @@ -396,11 +398,11 @@ impl IamSys { 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 IamSys { // TODO: notification } - pub async fn policy_db_get(&self, name: &str, groups: &[String]) -> Result> { + pub async fn policy_db_get(&self, name: &str, groups: &Option>) -> Result> { self.store.policy_db_get(name, groups).await } @@ -580,7 +582,7 @@ impl IamSys { 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, - pub secret_key: String, + pub secret_key: Option, pub name: Option, pub description: Option, pub expiration: Option, @@ -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>, pub action: Action, pub bucket: &'a str, pub conditions: &'a HashMap>, diff --git a/iam/tests/policy_is_allowed.rs b/iam/tests/policy_is_allowed.rs index 8d8c600d..45ed9607 100644 --- a/iam/tests/policy_is_allowed.rs +++ b/iam/tests/policy_is_allowed.rs @@ -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, diff --git a/madmin/src/user.rs b/madmin/src/user.rs index 9f47227a..ffe402d1 100644 --- a/madmin/src/user.rs +++ b/madmin/src/user.rs @@ -129,7 +129,7 @@ pub struct AddServiceAccountReq { pub secret_key: String, #[serde(rename = "name")] - pub name: String, + pub name: Option, #[serde(rename = "description", skip_serializing_if = "Option::is_none")] pub description: Option, @@ -137,3 +137,86 @@ pub struct AddServiceAccountReq { #[serde(rename = "expiration", skip_serializing_if = "Option::is_none")] pub expiration: Option, } + +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, +} + +#[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, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "time::serde::rfc3339::option")] + pub expiration: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct UpdateServiceAccountReq { + #[serde(rename = "newPolicy", skip_serializing_if = "Option::is_none")] + pub new_policy: Option, + + #[serde(rename = "newSecretKey", skip_serializing_if = "Option::is_none")] + pub new_secret_key: Option, + + #[serde(rename = "newStatus", skip_serializing_if = "Option::is_none")] + pub new_status: Option, + + #[serde(rename = "newName", skip_serializing_if = "Option::is_none")] + pub new_name: Option, + + #[serde(rename = "newDescription", skip_serializing_if = "Option::is_none")] + pub new_description: Option, + + #[serde(rename = "newExpiration", skip_serializing_if = "Option::is_none")] + pub new_expiration: Option, +} + +impl UpdateServiceAccountReq { + pub fn validate(&self) -> Result<(), String> { + // TODO: validate + Ok(()) + } +} diff --git a/rustfs/src/admin/handlers.rs b/rustfs/src/admin/handlers.rs index 24f67a29..1b7b8ba2 100644 --- a/rustfs/src/admin/handlers.rs +++ b/rustfs/src/admin/handlers.rs @@ -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")); } diff --git a/rustfs/src/admin/handlers/service_account.rs b/rustfs/src/admin/handlers/service_account.rs index 9b850fb9..be1588cc 100644 --- a/rustfs/src/admin/handlers/service_account.rs +++ b/rustfs/src/admin/handlers/service_account.rs @@ -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, _params: Params<'_, '_>) -> S3Result> { 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, _params: Params<'_, '_>) -> S3Result> { 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, _params: Params<'_, '_>) -> S3Result> { 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, _params: Params<'_, '_>) -> S3Result> { 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 = 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 = 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, params: Params<'_, '_>) -> S3Result> { + async fn call(&self, req: S3Request, _params: Params<'_, '_>) -> S3Result> { 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)) } } diff --git a/rustfs/src/admin/handlers/user.rs b/rustfs/src/admin/handlers/user.rs index 73e7b30c..3fc98f07 100644 --- a/rustfs/src/admin/handlers/user.rs +++ b/rustfs/src/admin/handlers/user.rs @@ -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, _params: Params<'_, '_>) -> S3Result> { - 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, _params: Params<'_, '_>) -> S3Result> { 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, _params: Params<'_, '_>) -> S3Result> { 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, _params: Params<'_, '_>) -> S3Result> { 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, _params: Params<'_, '_>) -> S3Result> { 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)) } } diff --git a/rustfs/src/admin/mod.rs b/rustfs/src/admin/mod.rs index 00d2b669..4f9796d7 100644 --- a/rustfs/src/admin/mod.rs +++ b/rustfs/src/admin/mod.rs @@ -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}; diff --git a/rustfs/src/admin/models.rs b/rustfs/src/admin/models.rs deleted file mode 100644 index f84f4b7a..00000000 --- a/rustfs/src/admin/models.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod service_account; diff --git a/rustfs/src/admin/models/service_account.rs b/rustfs/src/admin/models/service_account.rs deleted file mode 100644 index 888d1e15..00000000 --- a/rustfs/src/admin/models/service_account.rs +++ /dev/null @@ -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>, -// pub target_user: Option, -// pub name: String, -// pub description: String, -// #[serde(with = "time::serde::rfc3339::option")] -// #[serde(skip_serializing_if = "Option::is_none")] -// pub expiration: Option, -// } - -#[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, -} - -#[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, -} diff --git a/rustfs/src/admin/utils.rs b/rustfs/src/admin/utils.rs new file mode 100644 index 00000000..98f3b841 --- /dev/null +++ b/rustfs/src/admin/utils.rs @@ -0,0 +1,3 @@ +pub fn has_space_be(s: &str) -> bool { + s.trim().len() != s.len() +}