Merge branch 'main' into nugine/fmt

This commit is contained in:
weisd
2024-10-12 00:54:22 +08:00
committed by GitHub
25 changed files with 1937 additions and 206 deletions
+28 -14
View File
@@ -1,6 +1,7 @@
name: e2e
name: E2E
on:
workflow_dispatch:
push:
pull_request:
branches: [ "main" ]
@@ -9,14 +10,34 @@ env:
CARGO_TERM_COLOR: always
jobs:
build:
skip-check:
permissions:
actions: write
contents: read
runs-on: ubuntu-latest
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
concurrent_skipping: 'same_content_newer'
cancel_others: true
paths_ignore: '["*.md"]'
e2e:
# See this url if required matrix jobs are hanging
# https://github.com/fkirc/skip-duplicate-actions#how-to-use-skip-check-with-required-matrix-jobs
needs: skip-check
if: needs.skip-check.outputs.should_skip != 'true'
timeout-minutes: 30
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- stable
- nightly
# - nightly
steps:
- name: cache protoc bin
@@ -58,14 +79,15 @@ jobs:
chmod +x /usr/local/bin/flatc
rm -rf Linux.flatc.binary.g++-13.zip
- uses: actions/checkout@v4
- name: toolchain
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
# components: rustfmt, clippy
- name: checkout
uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- name: run fs start
working-directory: .
@@ -79,11 +101,3 @@ jobs:
- name: e2e test
run: cargo test -p e2e_test --lib
- name: cache cargo
uses: actions/cache@v4
env:
cache-name: cache-cargo
with:
path: ~/.cargo
key: ${{ runner.os }}-build-${{ env.cache-name }}-v0.0.1
+33 -16
View File
@@ -9,7 +9,27 @@ env:
CARGO_TERM_COLOR: always
jobs:
skip-check:
permissions:
actions: write
contents: read
runs-on: ubuntu-latest
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
concurrent_skipping: 'same_content_newer'
cancel_others: true
paths_ignore: '["*.md"]'
build:
# See this url if required matrix jobs are hanging
# https://github.com/fkirc/skip-duplicate-actions#how-to-use-skip-check-with-required-matrix-jobs
needs: skip-check
if: needs.skip-check.outputs.should_skip != 'true'
runs-on: ubuntu-latest
strategy:
matrix:
@@ -57,24 +77,21 @@ jobs:
chmod +x /usr/local/bin/flatc
rm -rf Linux.flatc.binary.g++-13.zip
- name: toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
# components: rustfmt, clippy
- uses: actions/checkout@v4
- name: cargo build
run: cargo build
- name: toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- name: cargo fmt
run: cargo fmt --all --check
- name: cargo clippy
run: cargo clippy -- -D warnings
- name: cargo test
run: cargo test --all --exclude e2e_test
- name: cache cargo
uses: actions/cache@v4
env:
cache-name: cache-cargo
with:
path: ~/.cargo
key: ${{ runner.os }}-build-${{ env.cache-name }}-v0.0.1
Generated
+24 -24
View File
@@ -318,9 +318,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.18"
version = "4.5.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3"
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
dependencies = [
"clap_builder",
"clap_derive",
@@ -328,9 +328,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.18"
version = "4.5.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b"
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
dependencies = [
"anstream",
"anstyle",
@@ -578,9 +578,9 @@ dependencies = [
[[package]]
name = "futures"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [
"futures-channel",
"futures-core",
@@ -593,9 +593,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
"futures-sink",
@@ -603,15 +603,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-executor"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
dependencies = [
"futures-core",
"futures-task",
@@ -620,15 +620,15 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-macro"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
@@ -637,21 +637,21 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]]
name = "futures-task"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-util"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-channel",
"futures-core",
@@ -1362,9 +1362,9 @@ dependencies = [
[[package]]
name = "prost-build"
version = "0.13.1"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bb182580f71dd070f88d01ce3de9f4da5021db7115d2e1c3605a754153b77c1"
checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15"
dependencies = [
"bytes",
"heck",
+3 -3
View File
@@ -20,10 +20,10 @@ version = "0.0.1"
async-trait = "0.1.83"
backon = "1.2.0"
bytes = "1.7.2"
clap = { version = "4.5.18", features = ["derive"] }
clap = { version = "4.5.20", features = ["derive"] }
ecstore = { path = "./ecstore" }
flatbuffers = "24.3.25"
futures = "0.3.30"
futures = "0.3.31"
futures-util = "0.3.30"
common = { path = "./common/common" }
hyper = "1.4.1"
@@ -41,7 +41,7 @@ netif = "0.1.6"
pin-project-lite = "0.2"
# pin-utils = "0.1.0"
prost = "0.13.3"
prost-build = "0.13.1"
prost-build = "0.13.3"
prost-types = "0.13.3"
protobuf = "3.6"
protos = { path = "./common/protos" }
+1 -1
View File
@@ -50,7 +50,7 @@ fn main() -> Result<(), AnyError> {
.protoc_arg("--experimental_allow_proto3_optional")
.compile_well_known_types(true)
.emit_rerun_if_changed(false)
.compile(proto_files, &[proto_dir.clone()])
.compile_protos(proto_files, &[proto_dir.clone()])
.map_err(|e| format!("Failed to generate protobuf file: {e}."))?;
// protos/gen/mod.rs
+13 -1
View File
@@ -1,4 +1,6 @@
#[derive(Debug, thiserror::Error)]
use crate::error::Error;
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum BucketMetadataError {
#[error("tagging not found")]
TaggingNotFound,
@@ -17,3 +19,13 @@ pub enum BucketMetadataError {
#[error("bucket remote target not found")]
BucketRemoteTargetNotFound,
}
impl BucketMetadataError {
pub fn is(&self, err: &Error) -> bool {
if let Some(e) = err.downcast_ref::<BucketMetadataError>() {
e == self
} else {
false
}
}
}
+1 -14
View File
@@ -6,10 +6,8 @@ use super::{
use byteorder::{BigEndian, ByteOrder, LittleEndian};
use rmp_serde::Serializer as rmpSerializer;
use serde::Serializer;
use serde::{Deserialize, Deserializer, Serialize};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::Display;
use std::str::FromStr;
use time::OffsetDateTime;
use tracing::{error, warn};
@@ -381,17 +379,6 @@ async fn read_bucket_metadata(api: &ECStore, bucket: &str) -> Result<BucketMetad
Ok(bm)
}
fn _deserialize_from_str<'de, S, D>(_deserializer: D) -> core::result::Result<S, D::Error>
where
S: FromStr,
S::Err: Display,
D: Deserializer<'de>,
{
// let s: String = Deserialize::deserialize(deserializer)?;
// S::from_str(&s).map_err(de::Error::custom)
unimplemented!()
}
fn _write_time<S>(t: &OffsetDateTime, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
+48 -5
View File
@@ -2,7 +2,7 @@ use std::collections::HashSet;
use std::{collections::HashMap, sync::Arc};
use crate::bucket::error::BucketMetadataError;
use crate::bucket::metadata::load_bucket_metadata_parse;
use crate::bucket::metadata::{load_bucket_metadata_parse, BUCKET_LIFECYCLE_CONFIG};
use crate::bucket::utils::is_meta_bucketname;
use crate::config;
use crate::config::error::ConfigError;
@@ -143,15 +143,42 @@ impl BucketMetadataSys {
}
}
// async fn reset(&mut self) {
// let mut map = self.metadata_map.write().await;
// map.clear();
// }
async fn _reset(&mut self) {
let mut map = self.metadata_map.write().await;
map.clear();
}
pub async fn update(&mut self, bucket: &str, config_file: &str, data: Vec<u8>) -> Result<OffsetDateTime> {
self.update_and_parse(bucket, config_file, data, true).await
}
pub async fn delete(&mut self, bucket: &str, config_file: &str) -> Result<OffsetDateTime> {
if config_file == BUCKET_LIFECYCLE_CONFIG {
let meta = match self.get_config_from_disk(bucket).await {
Ok(res) => res,
Err(err) => {
if !config::error::is_not_found(&err) {
return Err(err);
} else {
BucketMetadata::new(bucket)
}
}
};
if !meta.lifecycle_config_xml.is_empty() {
let cfg = Lifecycle::unmarshal(&meta.lifecycle_config_xml)?;
for _v in cfg.rules.iter() {
// TODO: FIXME:
break;
}
}
// TODO: other lifecycle handle
}
self.update_and_parse(bucket, config_file, Vec::new(), false).await
}
async fn update_and_parse(&mut self, bucket: &str, config_file: &str, data: Vec<u8>, parse: bool) -> Result<OffsetDateTime> {
let layer = new_object_layer_fn();
let lock = layer.read().await;
@@ -201,6 +228,22 @@ impl BucketMetadataSys {
Ok(())
}
pub async fn get_config_from_disk(&self, bucket: &str) -> Result<BucketMetadata> {
if self.api.as_ref().is_none() {
return Err(Error::msg("errBucketMetadataNotInitialized"));
}
if is_meta_bucketname(bucket) {
return Err(Error::msg("errInvalidArgument"));
}
if let Some(api) = self.api.as_ref() {
load_bucket_metadata(&api, bucket).await
} else {
Err(Error::msg("errBucketMetadataNotInitialized"))
}
}
pub async fn get_config(&self, bucket: &str) -> Result<(BucketMetadata, bool)> {
if let Some(api) = self.api.as_ref() {
let has_bm = {
+3 -2
View File
@@ -1,11 +1,12 @@
mod encryption;
mod error;
pub mod error;
mod event;
mod lifecycle;
pub mod metadata;
mod metadata_sys;
mod objectlock;
mod policy;
pub mod policy;
pub mod policy_sys;
mod quota;
mod replication;
pub mod tags;
+399 -9
View File
@@ -1,80 +1,286 @@
use crate::{bucket::policy::condition::keyname::ALL_SUPPORT_KEYS, utils};
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::{
collections::{HashMap, HashSet},
vec,
};
use super::condition::{
key::{Key, KeySet},
keyname::{KeyName, COMMOM_KEYS},
};
#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)]
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
pub struct ActionSet(HashSet<Action>);
impl ActionSet {}
impl ActionSet {
pub fn as_ref(&self) -> &HashSet<Action> {
&self.0
}
pub fn is_match(&self, act: &Action) -> bool {
for item in self.0.iter() {
if item.is_match(act) {
return true;
}
if item == &Action::GetObjectVersion {
if act == &Action::GetObjectVersion {
return true;
}
}
}
false
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
// 定义Action枚举类型
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default, Hash)]
pub enum Action {
#[serde(rename = "s3:AbortMultipartUpload")]
AbortMultipartUpload,
#[serde(rename = "s3:CreateBucket")]
CreateBucket,
#[serde(rename = "s3:DeleteBucket")]
DeleteBucket,
#[serde(rename = "s3:ForceDeleteBucket")]
ForceDeleteBucket,
#[serde(rename = "s3:DeleteBucketPolicy")]
DeleteBucketPolicy,
#[serde(rename = "s3:DeleteBucketCors")]
DeleteBucketCors,
#[serde(rename = "s3:DeleteObject")]
DeleteObject,
#[serde(rename = "s3:GetBucketLocation")]
GetBucketLocation,
#[serde(rename = "s3:GetBucketNotification")]
GetBucketNotification,
#[serde(rename = "s3:GetBucketPolicy")]
GetBucketPolicy,
#[serde(rename = "s3:GetBucketCors")]
GetBucketCors,
#[serde(rename = "s3:GetObject")]
GetObject,
#[serde(rename = "s3:GetObjectAttributes")]
GetObjectAttributes,
#[serde(rename = "s3:HeadBucket")]
HeadBucket,
#[serde(rename = "s3:ListAllMyBuckets")]
ListAllMyBuckets,
#[serde(rename = "s3:ListBucket")]
ListBucket,
#[serde(rename = "s3:GetBucketPolicyStatus")]
GetBucketPolicyStatus,
#[serde(rename = "s3:ListBucketVersions")]
ListBucketVersions,
#[serde(rename = "s3:ListBucketMultipartUploads")]
ListBucketMultipartUploads,
#[serde(rename = "s3:ListenNotification")]
ListenNotification,
#[serde(rename = "s3:ListenBucketNotification")]
ListenBucketNotification,
#[serde(rename = "s3:ListMultipartUploadParts")]
ListMultipartUploadParts,
PutBucketLifecycle,
GetBucketLifecycle,
#[serde(rename = "s3:PutLifecycleConfiguration")]
PutLifecycleConfiguration,
#[serde(rename = "s3:GetLifecycleConfiguration")]
GetLifecycleConfiguration,
#[serde(rename = "s3:PutBucketNotification")]
PutBucketNotification,
#[serde(rename = "s3:PutBucketPolicy")]
PutBucketPolicy,
#[serde(rename = "s3:PutBucketCors")]
PutBucketCors,
#[serde(rename = "s3:PutObject")]
PutObject,
#[serde(rename = "s3:DeleteObjectVersion")]
DeleteObjectVersion,
#[serde(rename = "s3:DeleteObjectVersionTagging")]
DeleteObjectVersionTagging,
#[serde(rename = "s3:GetObjectVersion")]
GetObjectVersion,
#[serde(rename = "s3:GetObjectVersionAttributes")]
GetObjectVersionAttributes,
#[serde(rename = "s3:GetObjectVersionTagging")]
GetObjectVersionTagging,
#[serde(rename = "s3:PutObjectVersionTagging")]
PutObjectVersionTagging,
#[serde(rename = "s3:BypassGovernanceRetention")]
BypassGovernanceRetention,
#[serde(rename = "s3:PutObjectRetention")]
PutObjectRetention,
#[serde(rename = "s3:GetObjectRetention")]
GetObjectRetention,
#[serde(rename = "s3:GetObjectLegalHold")]
GetObjectLegalHold,
#[serde(rename = "s3:PutObjectLegalHold")]
PutObjectLegalHold,
#[serde(rename = "s3:GetBucketObjectLockConfiguration")]
GetBucketObjectLockConfiguration,
#[serde(rename = "s3:PutBucketObjectLockConfiguration")]
PutBucketObjectLockConfiguration,
#[serde(rename = "s3:GetBucketTagging")]
GetBucketTagging,
#[serde(rename = "s3:PutBucketTagging")]
PutBucketTagging,
#[serde(rename = "s3:GetObjectTagging")]
GetObjectTagging,
#[serde(rename = "s3:PutObjectTagging")]
PutObjectTagging,
#[serde(rename = "s3:DeleteObjectTagging")]
DeleteObjectTagging,
#[serde(rename = "s3:PutBucketEncryption")]
PutBucketEncryption,
#[serde(rename = "s3:GetBucketEncryption")]
GetBucketEncryption,
#[serde(rename = "s3:PutBucketVersioning")]
PutBucketVersioning,
#[serde(rename = "s3:GetBucketVersioning")]
GetBucketVersioning,
#[serde(rename = "s3:PutReplicationConfiguration")]
PutReplicationConfiguration,
#[serde(rename = "s3:GetReplicationConfiguration")]
GetReplicationConfiguration,
#[serde(rename = "s3:ReplicateObject")]
ReplicateObject,
#[serde(rename = "s3:ReplicateDelete")]
ReplicateDelete,
#[serde(rename = "s3:ReplicateTags")]
ReplicateTags,
#[serde(rename = "s3:GetObjectVersionForReplication")]
GetObjectVersionForReplication,
#[serde(rename = "s3:RestoreObject")]
RestoreObject,
#[serde(rename = "s3:ResetBucketReplicationState")]
ResetBucketReplicationState,
#[serde(rename = "s3:PutObjectFanOut")]
PutObjectFanOut,
#[default]
#[serde(rename = "s3:*")]
AllActions,
}
lazy_static! {
#[derive(Debug)]
static ref SUPPORT_OBJCET_ACTIONS: HashSet<Action> = {
let mut h = HashSet::new();
h.insert(Action::AllActions);
h.insert(Action::AbortMultipartUpload);
h.insert(Action::DeleteObject);
h.insert(Action::GetObject);
h.insert(Action::ListMultipartUploadParts);
h.insert(Action::PutObject);
h.insert(Action::BypassGovernanceRetention);
h.insert(Action::PutObjectRetention);
h.insert(Action::GetObjectRetention);
h.insert(Action::PutObjectLegalHold);
h.insert(Action::GetObjectLegalHold);
h.insert(Action::GetObjectTagging);
h.insert(Action::PutObjectTagging);
h.insert(Action::DeleteObjectTagging);
h.insert(Action::GetObjectVersion);
h.insert(Action::GetObjectVersionTagging);
h.insert(Action::DeleteObjectVersion);
h.insert(Action::DeleteObjectVersionTagging);
h.insert(Action::PutObjectVersionTagging);
h.insert(Action::ReplicateObject);
h.insert(Action::ReplicateDelete);
h.insert(Action::ReplicateTags);
h.insert(Action::GetObjectVersionForReplication);
h.insert(Action::RestoreObject);
h.insert(Action::ResetBucketReplicationState);
h.insert(Action::PutObjectFanOut);
h.insert(Action::GetObjectAttributes);
h.insert(Action::GetObjectVersionAttributes);
h
};
}
impl Action {
// 将字符串转换为Action枚举
fn from_str(s: &str) -> Option<Self> {
pub fn is_object_action(&self) -> bool {
for act in SUPPORT_OBJCET_ACTIONS.iter() {
if self.is_match(act) {
return true;
}
}
false
}
pub fn is_match(&self, a: &Action) -> bool {
utils::wildcard::match_pattern(&self.clone().as_str(), &a.clone().as_str())
}
fn as_str(self) -> &'static str {
match self {
Action::AbortMultipartUpload => "s3:AbortMultipartUpload",
Action::CreateBucket => "s3:CreateBucket",
Action::DeleteBucket => "s3:DeleteBucket",
Action::ForceDeleteBucket => "s3:ForceDeleteBucket",
Action::DeleteBucketPolicy => "s3:DeleteBucketPolicy",
Action::DeleteBucketCors => "s3:DeleteBucketCors",
Action::DeleteObject => "s3:DeleteObject",
Action::GetBucketLocation => "s3:GetBucketLocation",
Action::GetBucketNotification => "s3:GetBucketNotification",
Action::GetBucketPolicy => "s3:GetBucketPolicy",
Action::GetBucketCors => "s3:GetBucketCors",
Action::GetObject => "s3:GetObject",
Action::GetObjectAttributes => "s3:GetObjectAttributes",
Action::HeadBucket => "s3:HeadBucket",
Action::ListAllMyBuckets => "s3:ListAllMyBuckets",
Action::ListBucket => "s3:ListBucket",
Action::GetBucketPolicyStatus => "s3:GetBucketPolicyStatus",
Action::ListBucketVersions => "s3:ListBucketVersions",
Action::ListBucketMultipartUploads => "s3:ListBucketMultipartUploads",
Action::ListenNotification => "s3:ListenNotification",
Action::ListenBucketNotification => "s3:ListenBucketNotification",
Action::ListMultipartUploadParts => "s3:ListMultipartUploadParts",
Action::PutLifecycleConfiguration => "s3:PutLifecycleConfiguration",
Action::GetLifecycleConfiguration => "s3:GetLifecycleConfiguration",
Action::PutBucketNotification => "s3:PutBucketNotification",
Action::PutBucketPolicy => "s3:PutBucketPolicy",
Action::PutBucketCors => "s3:PutBucketCors",
Action::PutObject => "s3:PutObject",
Action::DeleteObjectVersion => "s3:DeleteObjectVersion",
Action::DeleteObjectVersionTagging => "s3:DeleteObjectVersionTagging",
Action::GetObjectVersion => "s3:GetObjectVersion",
Action::GetObjectVersionAttributes => "s3:GetObjectVersionAttributes",
Action::GetObjectVersionTagging => "s3:GetObjectVersionTagging",
Action::PutObjectVersionTagging => "s3:PutObjectVersionTagging",
Action::BypassGovernanceRetention => "s3:BypassGovernanceRetention",
Action::PutObjectRetention => "s3:PutObjectRetention",
Action::GetObjectRetention => "s3:GetObjectRetention",
Action::GetObjectLegalHold => "s3:GetObjectLegalHold",
Action::PutObjectLegalHold => "s3:PutObjectLegalHold",
Action::GetBucketObjectLockConfiguration => "s3:GetBucketObjectLockConfiguration",
Action::PutBucketObjectLockConfiguration => "s3:PutBucketObjectLockConfiguration",
Action::GetBucketTagging => "s3:GetBucketTagging",
Action::PutBucketTagging => "s3:PutBucketTagging",
Action::GetObjectTagging => "s3:GetObjectTagging",
Action::PutObjectTagging => "s3:PutObjectTagging",
Action::DeleteObjectTagging => "s3:DeleteObjectTagging",
Action::PutBucketEncryption => "s3:PutEncryptionConfiguration",
Action::GetBucketEncryption => "s3:GetEncryptionConfiguration",
Action::PutBucketVersioning => "s3:PutBucketVersioning",
Action::GetBucketVersioning => "s3:GetBucketVersioning",
Action::PutReplicationConfiguration => "s3:GetReplicationConfiguration",
Action::GetReplicationConfiguration => "s3:PutReplicationConfiguration",
Action::ReplicateObject => "s3:ReplicateObject",
Action::ReplicateDelete => "s3:ReplicateDelete",
Action::ReplicateTags => "s3:ReplicateTags",
Action::GetObjectVersionForReplication => "s3:GetObjectVersionForReplication",
Action::RestoreObject => "s3:RestoreObject",
Action::ResetBucketReplicationState => "s3:ResetBucketReplicationState",
Action::PutObjectFanOut => "s3:PutObjectFanOut",
Action::AllActions => "s3:*",
}
}
fn _from_str(s: &str) -> Option<Self> {
match s {
"s3:AbortMultipartUpload" => Some(Action::AbortMultipartUpload),
"s3:CreateBucket" => Some(Action::CreateBucket),
@@ -98,8 +304,8 @@ impl Action {
"s3:ListenNotification" => Some(Action::ListenNotification),
"s3:ListenBucketNotification" => Some(Action::ListenBucketNotification),
"s3:ListMultipartUploadParts" => Some(Action::ListMultipartUploadParts),
"s3:PutLifecycleConfiguration" => Some(Action::PutBucketLifecycle),
"s3:GetLifecycleConfiguration" => Some(Action::GetBucketLifecycle),
"s3:PutLifecycleConfiguration" => Some(Action::PutLifecycleConfiguration),
"s3:GetLifecycleConfiguration" => Some(Action::GetLifecycleConfiguration),
"s3:PutBucketNotification" => Some(Action::PutBucketNotification),
"s3:PutBucketPolicy" => Some(Action::PutBucketPolicy),
"s3:PutBucketCors" => Some(Action::PutBucketCors),
@@ -140,3 +346,187 @@ impl Action {
}
}
}
pub struct ActionConditionKeyMap(HashMap<Action, KeySet>);
impl ActionConditionKeyMap {
pub fn lookup(&self, action: &Action) -> KeySet {
let common_keys: Vec<Key> = COMMOM_KEYS.iter().map(|v| v.to_key()).collect();
let mut merged_keys = KeySet::from_keys(&common_keys);
for (act, key) in self.0.iter() {
if action.is_match(act) {
merged_keys.merge(key);
}
}
merged_keys
}
}
lazy_static! {
pub static ref IAMActionConditionKeyMap: ActionConditionKeyMap = create_action_condition_key_map();
}
fn create_action_condition_key_map() -> ActionConditionKeyMap {
let common_keys: Vec<Key> = COMMOM_KEYS.iter().map(|v| v.to_key()).collect();
let all_support_keys: Vec<Key> = ALL_SUPPORT_KEYS.iter().map(|v| v.to_key()).collect();
let mut map = HashMap::new();
map.insert(Action::AllActions, KeySet::from_keys(&all_support_keys));
map.insert(Action::AbortMultipartUpload, KeySet::from_keys(&common_keys));
map.insert(Action::CreateBucket, KeySet::from_keys(&common_keys));
let mut delete_obj_keys = common_keys.clone();
delete_obj_keys.push(KeyName::S3VersionID.to_key());
map.insert(Action::DeleteObject, KeySet::from_keys(&delete_obj_keys));
map.insert(Action::GetBucketLocation, KeySet::from_keys(&common_keys));
map.insert(Action::GetBucketPolicyStatus, KeySet::from_keys(&common_keys));
let mut get_obj_keys = common_keys.clone();
get_obj_keys.extend(vec![
KeyName::S3XAmzServerSideEncryption.to_key(),
KeyName::S3XAmzServerSideEncryptionCustomerAlgorithm.to_key(),
KeyName::S3XAmzServerSideEncryptionAwsKmsKeyID.to_key(),
KeyName::S3VersionID.to_key(),
KeyName::ExistingObjectTag.to_key(),
]);
map.insert(Action::DeleteObject, KeySet::from_keys(&get_obj_keys));
map.insert(Action::HeadBucket, KeySet::from_keys(&common_keys));
let mut get_obj_attr_keys = common_keys.clone();
get_obj_attr_keys.push(KeyName::ExistingObjectTag.to_key());
map.insert(Action::DeleteObject, KeySet::from_keys(&get_obj_attr_keys));
let mut get_obj_ver_attr_keys = common_keys.clone();
get_obj_ver_attr_keys.extend(vec![KeyName::S3VersionID.to_key(), KeyName::ExistingObjectTag.to_key()]);
map.insert(Action::DeleteObject, KeySet::from_keys(&get_obj_ver_attr_keys));
map.insert(Action::ListAllMyBuckets, KeySet::from_keys(&common_keys));
let mut list_bucket_keys = common_keys.clone();
list_bucket_keys.extend(vec![
KeyName::S3Prefix.to_key(),
KeyName::S3Delimiter.to_key(),
KeyName::S3MaxKeys.to_key(),
]);
map.insert(Action::ListBucket, KeySet::from_keys(&list_bucket_keys));
map.insert(Action::ListBucketVersions, KeySet::from_keys(&list_bucket_keys));
map.insert(Action::ListBucketMultipartUploads, KeySet::from_keys(&common_keys));
map.insert(Action::ListenNotification, KeySet::from_keys(&common_keys));
map.insert(Action::ListenBucketNotification, KeySet::from_keys(&common_keys));
map.insert(Action::ListMultipartUploadParts, KeySet::from_keys(&common_keys));
let mut put_obj_keys = common_keys.clone();
put_obj_keys.extend(vec![
KeyName::S3XAmzCopySource.to_key(),
KeyName::S3XAmzServerSideEncryption.to_key(),
KeyName::S3XAmzServerSideEncryptionCustomerAlgorithm.to_key(),
KeyName::S3XAmzServerSideEncryptionAwsKmsKeyID.to_key(),
KeyName::S3XAmzMetadataDirective.to_key(),
KeyName::S3XAmzStorageClass.to_key(),
KeyName::S3VersionID.to_key(),
KeyName::S3ObjectLockRetainUntilDate.to_key(),
KeyName::S3ObjectLockMode.to_key(),
KeyName::S3ObjectLockLegalHold.to_key(),
KeyName::RequestObjectTagKeys.to_key(),
KeyName::RequestObjectTag.to_key(),
]);
map.insert(Action::PutObject, KeySet::from_keys(&put_obj_keys));
let mut put_obj_retention_keys = common_keys.clone();
put_obj_retention_keys.extend(vec![
KeyName::S3XAmzServerSideEncryption.to_key(),
KeyName::S3XAmzServerSideEncryptionCustomerAlgorithm.to_key(),
KeyName::S3XAmzServerSideEncryptionAwsKmsKeyID.to_key(),
KeyName::S3ObjectLockRemainingRetentionDays.to_key(),
KeyName::S3ObjectLockRetainUntilDate.to_key(),
KeyName::S3ObjectLockMode.to_key(),
KeyName::S3VersionID.to_key(),
]);
map.insert(Action::PutObjectRetention, KeySet::from_keys(&put_obj_retention_keys));
let mut get_obj_retention_keys = common_keys.clone();
get_obj_retention_keys.extend(vec![
KeyName::S3XAmzServerSideEncryption.to_key(),
KeyName::S3XAmzServerSideEncryptionCustomerAlgorithm.to_key(),
KeyName::S3XAmzServerSideEncryptionAwsKmsKeyID.to_key(),
KeyName::S3VersionID.to_key(),
]);
map.insert(Action::GetObjectRetention, KeySet::from_keys(&get_obj_retention_keys));
let mut put_obj_hold_keys = common_keys.clone();
put_obj_hold_keys.extend(vec![
KeyName::S3XAmzServerSideEncryption.to_key(),
KeyName::S3XAmzServerSideEncryptionCustomerAlgorithm.to_key(),
KeyName::S3XAmzServerSideEncryptionAwsKmsKeyID.to_key(),
KeyName::S3ObjectLockLegalHold.to_key(),
KeyName::S3VersionID.to_key(),
]);
map.insert(Action::PutObjectLegalHold, KeySet::from_keys(&put_obj_hold_keys));
map.insert(Action::GetObjectLegalHold, KeySet::from_keys(&common_keys));
let mut bypass_governance_retention_keys = common_keys.clone();
bypass_governance_retention_keys.extend(vec![
KeyName::S3VersionID.to_key(),
KeyName::S3ObjectLockRemainingRetentionDays.to_key(),
KeyName::S3ObjectLockRetainUntilDate.to_key(),
KeyName::S3ObjectLockMode.to_key(),
KeyName::S3ObjectLockLegalHold.to_key(),
KeyName::RequestObjectTagKeys.to_key(),
KeyName::RequestObjectTag.to_key(),
]);
map.insert(Action::BypassGovernanceRetention, KeySet::from_keys(&bypass_governance_retention_keys));
map.insert(Action::GetBucketObjectLockConfiguration, KeySet::from_keys(&common_keys));
map.insert(Action::PutBucketObjectLockConfiguration, KeySet::from_keys(&common_keys));
map.insert(Action::GetBucketTagging, KeySet::from_keys(&common_keys));
let mut put_bucket_tagging_keys = common_keys.clone();
put_bucket_tagging_keys.extend(vec![KeyName::RequestObjectTagKeys.to_key(), KeyName::RequestObjectTag.to_key()]);
map.insert(Action::PutBucketTagging, KeySet::from_keys(&put_bucket_tagging_keys));
let mut put_object_tagging_keys = common_keys.clone();
put_object_tagging_keys.extend(vec![
KeyName::S3VersionID.to_key(),
KeyName::ExistingObjectTag.to_key(),
KeyName::RequestObjectTagKeys.to_key(),
KeyName::RequestObjectTag.to_key(),
]);
map.insert(Action::PutObjectTagging, KeySet::from_keys(&put_object_tagging_keys));
let mut get_object_tagging_keys = common_keys.clone();
get_object_tagging_keys.extend(vec![KeyName::S3VersionID.to_key(), KeyName::ExistingObjectTag.to_key()]);
map.insert(Action::GetObjectTagging, KeySet::from_keys(&get_object_tagging_keys));
map.insert(Action::DeleteObjectTagging, KeySet::from_keys(&get_object_tagging_keys));
map.insert(Action::PutObjectVersionTagging, KeySet::from_keys(&put_object_tagging_keys));
map.insert(Action::GetObjectVersionTagging, KeySet::from_keys(&get_object_tagging_keys));
map.insert(Action::GetObjectVersion, KeySet::from_keys(&get_object_tagging_keys));
let mut delete_object_version_keys = common_keys.clone();
delete_object_version_keys.extend(vec![KeyName::S3VersionID.to_key()]);
map.insert(Action::DeleteObjectVersion, KeySet::from_keys(&delete_object_version_keys));
map.insert(Action::DeleteObjectVersionTagging, KeySet::from_keys(&get_object_tagging_keys));
map.insert(Action::GetReplicationConfiguration, KeySet::from_keys(&common_keys));
map.insert(Action::PutReplicationConfiguration, KeySet::from_keys(&common_keys));
map.insert(Action::ReplicateObject, KeySet::from_keys(&get_object_tagging_keys));
map.insert(Action::ReplicateDelete, KeySet::from_keys(&get_object_tagging_keys));
map.insert(Action::ReplicateTags, KeySet::from_keys(&get_object_tagging_keys));
map.insert(Action::GetObjectVersionForReplication, KeySet::from_keys(&get_object_tagging_keys));
map.insert(Action::RestoreObject, KeySet::from_keys(&common_keys));
map.insert(Action::ResetBucketReplicationState, KeySet::from_keys(&common_keys));
map.insert(Action::PutObjectFanOut, KeySet::from_keys(&common_keys));
ActionConditionKeyMap(map)
}
+233 -23
View File
@@ -1,57 +1,267 @@
use crate::error::Result;
use rmp_serde::Serializer as rmpSerializer;
use crate::error::{Error, Result};
// use rmp_serde::Serializer as rmpSerializer;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use super::{
action::{Action, ActionSet},
action::{Action, ActionSet, IAMActionConditionKeyMap},
condition::function::Functions,
effect::Effect,
principal::Principal,
resource::ResourceSet,
};
const DEFAULT_VERSION: &str = "2012-10-17";
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
pub struct BucketPolicyArgs {
account_name: String,
groups: Vec<String>,
action: Action,
bucket_name: String,
condition_values: HashMap<String, Vec<String>>,
is_owner: bool,
object_name: String,
pub account_name: String,
pub groups: Vec<String>,
pub action: Action,
pub bucket_name: String,
pub condition_values: HashMap<String, Vec<String>>,
pub is_owner: bool,
pub object_name: String,
}
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq, Eq)]
// #[serde(rename_all = "PascalCase", default)]
pub struct BPStatement {
sid: String,
effect: Effect,
principal: Principal,
actions: ActionSet,
#[serde(rename = "Sid")]
pub sid: String,
#[serde(rename = "Effect")]
pub effect: Effect,
#[serde(rename = "Principal")]
pub principal: Principal,
#[serde(rename = "Action")]
pub actions: ActionSet,
#[serde(rename = "NotAction", default)]
pub not_actions: ActionSet,
#[serde(rename = "Resource")]
pub resources: ResourceSet,
#[serde(rename = "Condition", default)]
pub conditions: Functions,
}
not_actions: Option<ActionSet>,
resources: ResourceSet,
conditions: Option<Functions>,
impl BPStatement {
// pub fn equals(&self, other: &BPStatement) -> bool {
// if self.effect != other.effect {
// return false;
// }
// if !self.principal.equals(other.principal) {
// return false;
// }
// if !self.actions.equals(other.actions) {
// return false;
// }
// if !self.not_actions.equals(other.not_actions) {
// return false;
// }
// if !self.resources.equals(other.resources) {
// return false;
// }
// if !self.conditions.equals(other.conditions) {
// return false;
// }
// true
// }
pub fn validate(&self, bucket: &str) -> Result<()> {
self.is_valid()?;
self.resources.validate_bucket(bucket)
}
pub fn is_valid(&self) -> Result<()> {
if !self.effect.is_valid() {
return Err(Error::msg(format!("invalid Effect {:?}", self.effect)));
}
if !self.principal.is_valid() {
return Err(Error::msg(format!("invalid Principal {:?}", self.principal)));
}
if self.actions.is_empty() && self.not_actions.is_empty() {
return Err(Error::msg("Action must not be empty"));
}
if self.resources.as_ref().is_empty() {
return Err(Error::msg("Resource must not be empty"));
}
for act in self.actions.as_ref() {
if act.is_object_action() {
if !self.resources.object_resource_exists() {
return Err(Error::msg(format!(
"unsupported object Resource found {:?} for action {:?}",
self.resources, act
)));
}
} else {
if !self.resources.bucket_resource_exists() {
return Err(Error::msg(format!(
"unsupported bucket Resource found {:?} for action {:?}",
self.resources, act
)));
}
}
let key_diff = self.conditions.keys().difference(&IAMActionConditionKeyMap.lookup(act));
if !key_diff.is_empty() {
return Err(Error::msg(format!(
"unsupported condition keys '{:?}' used for action '{:?}'",
key_diff, act
)));
}
}
Ok(())
}
fn is_allowed(&self, args: &BucketPolicyArgs) -> bool {
let check = || -> bool {
if !self.principal.is_match(&args.account_name) {
return false;
}
if (!self.actions.is_match(&args.action) && !self.actions.is_empty()) || self.not_actions.is_match(&args.action) {
return false;
}
let mut resource = args.bucket_name.clone();
if !args.object_name.is_empty() {
if !args.object_name.starts_with("/") {
resource.push_str("/");
}
resource.push_str(&args.object_name);
}
if !self.resources.is_match(&resource, &args.condition_values) {
return false;
}
self.conditions.evaluate(&args.condition_values)
};
self.effect.is_allowed(check())
}
}
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
// #[serde(rename_all = "PascalCase", default)]
pub struct BucketPolicy {
#[serde(rename = "ID", default)]
pub id: String,
#[serde(rename = "Version")]
pub version: String,
#[serde(rename = "Statement")]
pub statements: Vec<BPStatement>,
}
impl BucketPolicy {
pub fn marshal_msg(&self) -> Result<Vec<u8>> {
let mut buf = Vec::new();
pub fn is_allowed(&self, args: &BucketPolicyArgs) -> bool {
for statement in self.statements.iter() {
if statement.effect == Effect::Deny {
if !statement.is_allowed(args) {
return false;
}
}
}
self.serialize(&mut rmpSerializer::new(&mut buf).with_struct_map())?;
if args.is_owner {
return true;
}
for statement in self.statements.iter() {
if statement.effect == Effect::Allow {
if statement.is_allowed(args) {
return true;
}
}
}
false
}
pub fn validate(&self, bucket: &str) -> Result<()> {
self.is_valid()?;
for statement in self.statements.iter() {
statement.validate(bucket)?;
}
Ok(())
}
pub fn is_valid(&self) -> Result<()> {
if self.version.as_str() != DEFAULT_VERSION && self.version.is_empty() {
return Err(Error::msg(format!("invalid version {}", self.version)));
}
for statement in self.statements.iter() {
if let Err(err) = statement.is_valid() {
return Err(err);
}
}
Ok(())
}
pub fn is_empty(&self) -> bool {
self.statements.is_empty()
}
pub fn marshal_msg(&self) -> Result<String> {
let buf = serde_json::to_string(self)?;
Ok(buf)
// let mut buf = Vec::new();
// self.serialize(&mut rmpSerializer::new(&mut buf).with_struct_map())?;
// Ok(buf)
}
pub fn unmarshal(buf: &[u8]) -> Result<Self> {
let t: BucketPolicy = rmp_serde::from_slice(buf)?;
Ok(t)
let mut p = serde_json::from_slice::<BucketPolicy>(&buf)?;
p.drop_duplicate_statements();
Ok(p)
// let t: BucketPolicy = rmp_serde::from_slice(buf)?;
// Ok(t)
}
fn drop_duplicate_statements(&mut self) {
let mut dups = HashMap::new();
for v in self.statements.iter() {
if let Ok(data) = serde_json::to_string(self) {
dups.insert(data, v);
}
}
let mut news = Vec::new();
for (_, v) in dups {
news.push(v.clone());
}
self.statements = news;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bucket_policy() {
let json = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Resource\":[\"arn:aws:s3:::dada\"],\"Sid\":\"\"},{\"Action\":[\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListMultipartUploadParts\",\"s3:PutObject\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Resource\":[\"arn:aws:s3:::dada/*\"],\"Sid\":\"sdf\"}]}";
let a = BucketPolicy::unmarshal(json.to_string().as_bytes()).unwrap();
println!("{:?}", a);
let j = a.marshal_msg();
println!("{:?}", j);
println!("{:?}", json);
}
}
+266 -26
View File
@@ -1,23 +1,26 @@
use super::name::Name;
use serde::{Deserialize, Serialize};
use super::{
key::{Key, KeySet},
keyname::KeyName,
name::Name,
};
use serde::{
de::{MapAccess, Visitor},
ser::SerializeMap,
Deserialize, Serialize,
};
use std::{
collections::{HashMap, HashSet},
fmt::Debug,
fmt::{self, Debug, Display},
marker::PhantomData,
};
#[derive(Debug, Clone)]
pub struct Key {
name: String,
variable: String,
}
// 定义ValueSet类型
pub type ValueSet = HashSet<String>;
// 定义Function trait
pub trait FunctionApi {
pub trait FunctionApi: 'static + Send + Sync {
// evaluate方法
fn evaluate(&self, values: &HashMap<Key, ValueSet>) -> bool;
fn evaluate(&self, values: &HashMap<String, Vec<String>>) -> bool;
// key方法
fn key(&self) -> Key;
@@ -31,28 +34,265 @@ pub trait FunctionApi {
// to_map方法
fn to_map(&self) -> HashMap<Key, ValueSet>;
// clone方法
fn clone(&self) -> Box<Function>;
fn clone_box(&self) -> Box<dyn FunctionApi>;
}
// impl Debug for dyn Function {
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// write!(f, "{:?}", self.to_string())
// #[derive(Debug, Deserialize, Serialize, Clone)]
// pub enum Function {
// Test(TestFunction),
// }
// impl FunctionApi for Function {
// // evaluate方法
// fn evaluate(&self, values: &HashMap<String, Vec<String>>) -> bool {
// match self {
// Function::Test(f) => f.evaluate(values),
// }
// }
// // key方法
// fn key(&self) -> Key {
// match self {
// Function::Test(f) => f.key(),
// }
// }
// // name方法
// fn name(&self) -> Name {
// match self {
// Function::Test(f) => f.name(),
// }
// }
// // String方法
// fn to_string(&self) -> String {
// match self {
// Function::Test(f) => f.to_string(),
// }
// }
// // to_map方法
// fn to_map(&self) -> HashMap<Key, ValueSet> {
// match self {
// Function::Test(f) => f.to_map(),
// }
// }
// fn clone_box(&self) -> Box<dyn FunctionApi> {
// match self {
// Function::Test(f) => f.clone_box(),
// }
// }
// }
// 定义Functions类型
#[derive(Default)]
pub struct Functions(Vec<Box<dyn FunctionApi>>);
impl Functions {
pub fn evaluate(&self, values: &HashMap<String, Vec<String>>) -> bool {
for f in self.0.iter() {
if f.evaluate(values) {
return true;
}
}
false
}
pub fn keys(&self) -> KeySet {
let mut set = KeySet::new();
for f in self.0.iter() {
set.add(f.key())
}
set
}
}
impl Debug for Functions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let funs: Vec<String> = self.0.iter().map(|v| v.to_string()).collect();
f.debug_list().entries(funs.iter()).finish()
}
}
impl Display for Functions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let funs: Vec<String> = self.0.iter().map(|v| v.to_string()).collect();
write!(f, "{:?}", funs)
}
}
impl Clone for Functions {
fn clone(&self) -> Self {
let mut list = Vec::new();
for v in self.0.iter() {
list.push(v.clone_box())
}
Functions(list)
}
}
impl PartialEq for Functions {
fn eq(&self, other: &Self) -> bool {
if self.0.len() != other.0.len() {
return false;
}
for v in self.0.iter() {
let s = v.to_string();
let mut found = false;
for o in other.0.iter() {
if s == o.to_string() {
found = true;
break;
}
}
if !found {
return false;
}
}
true
}
}
impl Eq for Functions {}
type FunctionsMap = HashMap<String, HashMap<String, ValueSet>>;
impl Serialize for Functions {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut nm: FunctionsMap = HashMap::new();
for f in self.0.iter() {
let fname = f.name().to_string();
if !nm.contains_key(&fname) {
nm.insert(fname.clone(), HashMap::new());
}
for (k, v) in f.to_map() {
if let Some(hm) = nm.get_mut(&fname) {
hm.insert(k.to_string(), v);
}
}
}
let mut map = serializer.serialize_map(Some(nm.len()))?;
for (k, v) in nm.iter() {
map.serialize_entry(k, v)?;
}
map.end()
}
}
struct MyMapVisitor {
marker: PhantomData<fn() -> FunctionsMap>,
}
impl MyMapVisitor {
fn new() -> Self {
MyMapVisitor { marker: PhantomData }
}
}
// This is the trait that Deserializers are going to be driving. There
// is one method for each type of data that our type knows how to
// deserialize from. There are many other methods that are not
// implemented here, for example deserializing from integers or strings.
// By default those methods will return an error, which makes sense
// because we cannot deserialize a MyMap from an integer or string.
impl<'de> Visitor<'de> for MyMapVisitor {
// The type that our Visitor is going to produce.
type Value = FunctionsMap;
// Format a message stating what data this Visitor expects to receive.
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a very special map")
}
// Deserialize MyMap from an abstract "map" provided by the
// Deserializer. The MapAccess input is a callback provided by
// the Deserializer to let us see each entry in the map.
fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut map = FunctionsMap::with_capacity(access.size_hint().unwrap_or(0));
// While there are entries remaining in the input, add them
// into our map.
while let Some((key, value)) = access.next_entry()? {
map.insert(key, value);
}
Ok(map)
}
}
// This is the trait that informs Serde how to deserialize MyMap.
impl<'de> Deserialize<'de> for Functions {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
// Instantiate our Visitor and ask the Deserializer to drive
// it over the input data, resulting in an instance of MyMap.
let _map = deserializer.deserialize_map(MyMapVisitor::new())?;
// TODO: FIXME: create functions from name
Ok(Functions(Vec::new()))
}
}
// impl<'de> Deserialize<'de> for Functions {
// fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
// where
// D: serde::Deserializer<'de>,
// {
// todo!()
// }
// }
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
enum Function {
#[default]
Test,
}
pub struct TestFunction {}
// 定义Functions类型
#[derive(Deserialize, Serialize, Default, Clone)]
pub struct Functions(Vec<Function>);
impl FunctionApi for TestFunction {
// evaluate方法
fn evaluate(&self, _values: &HashMap<String, Vec<String>>) -> bool {
true
}
impl Debug for Functions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Functions").field(&self.0).finish()
// key方法
fn key(&self) -> Key {
Key {
name: KeyName::JWTPrefUsername,
variable: "".to_string(),
}
}
// name方法
fn name(&self) -> Name {
Name::StringEquals
}
// String方法
fn to_string(&self) -> String {
Name::StringEquals.to_string()
}
// to_map方法
fn to_map(&self) -> HashMap<Key, ValueSet> {
HashMap::new()
}
fn clone_box(&self) -> Box<dyn FunctionApi> {
Box::new(self.clone())
}
}
+159
View File
@@ -0,0 +1,159 @@
use super::keyname::{KeyName, ALL_SUPPORT_KEYS};
use crate::error::Error;
use serde::{Deserialize, Serialize};
use std::{collections::HashSet, fmt, str::FromStr};
// 定义Key结构体
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Key {
pub name: KeyName,
pub variable: String,
}
impl Key {
pub fn new(name: KeyName, variable: String) -> Self {
Key { name, variable }
}
// IsValid - checks if key is valid or not.
fn is_valid(&self) -> bool {
ALL_SUPPORT_KEYS.iter().any(|supported| self.name == *supported)
}
// Is - checks if this key has the same key name or not.
pub fn is(&self, name: &KeyName) -> bool {
self.name == *name
}
pub fn to_string(&self) -> String {
if !self.variable.is_empty() {
format!("{}/{}", self.name.as_str(), self.variable)
} else {
self.name.to_string()
}
}
// VarName - returns variable key name, such as "${aws:username}"
pub fn var_name(&self) -> String {
self.name.var_name()
}
// Name - returns key name which is stripped value of prefixes "aws:" and "s3:"
pub fn name(&self) -> String {
if !self.variable.is_empty() {
format!("{}{}", self.name.name(), self.variable)
} else {
self.name.name().to_string()
}
}
}
impl FromStr for Key {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (name, variable) = if let Some(pos) = s.find('/') {
(&s[..pos], &s[pos + 1..])
} else {
(s, "")
};
let keyname = KeyName::from_str(name)?;
let key = Key {
name: keyname,
variable: variable.to_string(),
};
if key.is_valid() {
Ok(key)
} else {
Err(Error::msg(format!("invalid condition key '{}'", s)))
}
}
}
impl Serialize for Key {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.to_string().as_str())
}
}
impl<'de> Deserialize<'de> for Key {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
Key::from_str(s.as_str()).map_err(serde::de::Error::custom)
}
}
impl fmt::Display for Key {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_string())
}
}
#[derive(Debug, Default)]
pub struct KeySet(HashSet<Key>);
impl KeySet {
pub fn new() -> Self {
KeySet(HashSet::new())
}
// Add - add a key to key set
pub fn add(&mut self, key: Key) {
self.0.insert(key);
}
// Merge merges two key sets, duplicates are overwritten
pub fn merge(&mut self, other: &KeySet) {
for key in &other.0 {
self.add(key.clone());
}
}
// Match matches the input key name with current keySet
pub fn match_key(&self, key: &Key) -> bool {
self.0.contains(key)
}
// Difference - returns a key set contains difference of two keys
pub fn difference(&self, other: &KeySet) -> KeySet {
let mut result = KeySet::default();
for key in &self.0 {
if !other.match_key(key) {
result.add(key.clone());
}
}
result
}
// IsEmpty - returns whether key set is empty or not
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
// ToSlice - returns slice of keys
fn to_slice(&self) -> Vec<Key> {
self.0.iter().cloned().collect()
}
// NewKeySet - returns new KeySet contains given keys
pub fn from_keys(keys: &Vec<Key>) -> KeySet {
let mut set = KeySet::default();
for key in keys {
set.add(key.clone());
}
set
}
}
impl fmt::Display for KeySet {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.to_slice())
}
}
@@ -1,3 +1,4 @@
pub mod function;
pub mod key;
pub mod keyname;
pub mod name;
+42 -35
View File
@@ -29,40 +29,47 @@ pub enum Name {
ForAnyValue,
}
// 实现Display trait用于打印
impl std::fmt::Display for Name {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{}",
match *self {
Name::StringEquals => "StringEquals",
Name::StringNotEquals => "StringNotEquals",
Name::StringEqualsIgnoreCase => "StringEqualsIgnoreCase",
Name::StringNotEqualsIgnoreCase => "StringNotEqualsIgnoreCase",
Name::StringLike => "StringLike",
Name::StringNotLike => "StringNotLike",
Name::BinaryEquals => "BinaryEquals",
Name::IpAddress => "IpAddress",
Name::NotIpAddress => "NotIpAddress",
Name::Null => "Null",
Name::Bool => "Bool",
Name::NumericEquals => "NumericEquals",
Name::NumericNotEquals => "NumericNotEquals",
Name::NumericLessThan => "NumericLessThan",
Name::NumericLessThanEquals => "NumericLessThanEquals",
Name::NumericGreaterThan => "NumericGreaterThan",
Name::NumericGreaterThanIfExists => "NumericGreaterThanIfExists",
Name::NumericGreaterThanEquals => "NumericGreaterThanEquals",
Name::DateEquals => "DateEquals",
Name::DateNotEquals => "DateNotEquals",
Name::DateLessThan => "DateLessThan",
Name::DateLessThanEquals => "DateLessThanEquals",
Name::DateGreaterThan => "DateGreaterThan",
Name::DateGreaterThanEquals => "DateGreaterThanEquals",
Name::ForAllValues => "ForAllValues",
Name::ForAnyValue => "ForAnyValue",
}
)
impl Name {
pub fn as_str(&self) -> &'static str {
match self {
Name::StringEquals => "StringEquals",
Name::StringNotEquals => "StringNotEquals",
Name::StringEqualsIgnoreCase => "StringEqualsIgnoreCase",
Name::StringNotEqualsIgnoreCase => "StringNotEqualsIgnoreCase",
Name::StringLike => "StringLike",
Name::StringNotLike => "StringNotLike",
Name::BinaryEquals => "BinaryEquals",
Name::IpAddress => "IpAddress",
Name::NotIpAddress => "NotIpAddress",
Name::Null => "Null",
Name::Bool => "Bool",
Name::NumericEquals => "NumericEquals",
Name::NumericNotEquals => "NumericNotEquals",
Name::NumericLessThan => "NumericLessThan",
Name::NumericLessThanEquals => "NumericLessThanEquals",
Name::NumericGreaterThan => "NumericGreaterThan",
Name::NumericGreaterThanIfExists => "NumericGreaterThanIfExists",
Name::NumericGreaterThanEquals => "NumericGreaterThanEquals",
Name::DateEquals => "DateEquals",
Name::DateNotEquals => "DateNotEquals",
Name::DateLessThan => "DateLessThan",
Name::DateLessThanEquals => "DateLessThanEquals",
Name::DateGreaterThan => "DateGreaterThan",
Name::DateGreaterThanEquals => "DateGreaterThanEquals",
Name::ForAllValues => "ForAllValues",
Name::ForAnyValue => "ForAnyValue",
}
}
}
// impl ToString for Name {
// fn to_string(&self) -> String {
// self.as_str().to_string()
// }
// }
impl std::fmt::Display for Name {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
+18
View File
@@ -2,12 +2,30 @@ use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Default)]
#[serde(rename_all = "PascalCase")]
pub enum Effect {
#[default]
Allow,
Deny,
}
impl Effect {
pub fn is_allowed(self, b: bool) -> bool {
if self == Effect::Allow {
b
} else {
!b
}
}
pub fn is_valid(self) -> bool {
match self {
Effect::Allow => true,
Effect::Deny => true,
}
}
}
// 实现从字符串解析Effect的功能
impl FromStr for Effect {
type Err = ();
+19 -1
View File
@@ -1,7 +1,25 @@
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
use crate::utils;
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq)]
#[serde(rename_all = "PascalCase", default)]
pub struct Principal {
#[serde(rename = "AWS")]
aws: HashSet<String>,
}
impl Principal {
pub fn is_valid(&self) -> bool {
!self.aws.is_empty()
}
pub fn is_match(&self, parincipal: &str) -> bool {
for pattern in self.aws.iter() {
if utils::wildcard::match_simple(&pattern, parincipal) {
return true;
}
}
return false;
}
}
+237 -11
View File
@@ -1,5 +1,13 @@
use crate::error::{Error, Result};
use crate::{
bucket::policy::condition::keyname::COMMOM_KEYS,
utils::{self, wildcard},
};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::{
collections::{HashMap, HashSet},
str::FromStr,
};
// 定义ResourceARNType枚举类型
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize, Default)]
@@ -10,16 +18,234 @@ pub enum ResourceARNType {
ResourceARNKMS,
}
// // 定义资源ARN前缀
// const RESOURCE_ARN_PREFIX: &str = "arn:aws:s3:::";
// const RESOURCE_ARN_KMS_PREFIX: &str = "arn:rustfs:kms::::";
// 定义Resource结构体
#[derive(Debug, Deserialize, Serialize, Default, PartialEq, Eq, Hash, Clone)]
pub struct Resource {
pattern: String,
r#type: ResourceARNType,
impl ResourceARNType {
pub fn to_string(&self) -> String {
match self {
ResourceARNType::UnknownARN => "".to_string(),
ResourceARNType::ResourceARNS3 => RESOURCE_ARN_PREFIX.to_string(),
ResourceARNType::ResourceARNKMS => RESOURCE_ARN_KMS_PREFIX.to_string(),
}
}
}
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
// 定义资源ARN前缀
const RESOURCE_ARN_PREFIX: &str = "arn:aws:s3:::";
const RESOURCE_ARN_KMS_PREFIX: &str = "arn:rustfs:kms::::";
// 定义Resource结构体
#[derive(Debug, Default, PartialEq, Eq, Hash, Clone)]
pub struct Resource {
pattern: String,
rtype: ResourceARNType,
}
impl Resource {
pub fn validate_bucket(&self, bucket: &str) -> Result<()> {
self.validate()?;
if !wildcard::match_pattern(&self.pattern, bucket)
&& !wildcard::match_as_pattern_prefix(&self.pattern, format!("{}/", bucket).as_str())
{
return Err(Error::msg("bucket name does not match"));
}
Ok(())
}
pub fn validate(&self) -> Result<()> {
if !self.is_valid() {
Err(Error::msg("invalid resource"))
} else {
Ok(())
}
}
pub fn is_valid(&self) -> bool {
if self.rtype == ResourceARNType::UnknownARN {
return false;
}
if self.is_s3() {
if self.pattern.starts_with("/") {
return false;
}
}
if self.is_kms() {
if self.pattern.as_bytes().iter().any(|&v| v == b'/' || v == b'\\' || v == b'.') {
return false;
}
}
!self.pattern.is_empty()
}
pub fn is_s3(&self) -> bool {
self.rtype == ResourceARNType::ResourceARNS3
}
pub fn is_kms(&self) -> bool {
self.rtype == ResourceARNType::ResourceARNKMS
}
pub fn is_bucket_pattern(&self) -> bool {
!self.pattern.contains("/") || self.pattern.eq("*")
}
pub fn is_object_pattern(&self) -> bool {
self.pattern.contains("/") || self.pattern.contains("*")
}
pub fn is_match(&self, res: &str, condition_values: &HashMap<String, Vec<String>>) -> bool {
let mut pattern = res.to_string();
if !condition_values.is_empty() {
for key in COMMOM_KEYS.iter() {
if let Some(vals) = condition_values.get(key.name()) {
if let Some(v0) = vals.get(0) {
pattern = pattern.replace(key.name(), &v0);
}
}
}
}
let cp = utils::path::clean(res);
if cp != "." && cp == pattern {
return true;
}
wildcard::match_pattern(&pattern, res)
}
pub fn to_string(&self) -> String {
format!("{}{}", self.rtype.to_string(), self.pattern)
}
}
impl FromStr for Resource {
type Err = serde_json::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with(RESOURCE_ARN_PREFIX) {
let pattern = {
if let Some(val) = s.strip_prefix(RESOURCE_ARN_PREFIX) {
val.to_string()
} else {
s.to_string()
}
};
Ok(Self {
rtype: ResourceARNType::ResourceARNS3,
pattern,
})
} else if s.starts_with(RESOURCE_ARN_KMS_PREFIX) {
let pattern = {
if let Some(val) = s.strip_prefix(RESOURCE_ARN_KMS_PREFIX) {
val.to_string()
} else {
s.to_string()
}
};
Ok(Self {
rtype: ResourceARNType::ResourceARNS3,
pattern,
})
} else {
Ok(Self {
rtype: ResourceARNType::UnknownARN,
pattern: "".to_string(),
})
}
}
}
impl Serialize for Resource {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.to_string().as_str())
}
}
impl<'de> Deserialize<'de> for Resource {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = Resource;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("string resource")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match Resource::from_str(value) {
Ok(res) => Ok(res),
Err(_) => Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(value), &self)),
}
}
}
deserializer.deserialize_any(Visitor)
}
}
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(transparent)]
pub struct ResourceSet(HashSet<Resource>);
impl ResourceSet {
pub fn validate_bucket(&self, bucket: &str) -> Result<()> {
for res in self.0.iter() {
if let Err(err) = res.validate_bucket(bucket) {
return Err(err);
}
}
Ok(())
}
pub fn as_ref(&self) -> &HashSet<Resource> {
&self.0
}
pub fn is_match(&self, res: &str, condition_values: &HashMap<String, Vec<String>>) -> bool {
for item in self.0.iter() {
if item.is_match(res, condition_values) {
return true;
}
}
false
}
pub fn object_resource_exists(&self) -> bool {
for res in self.0.iter() {
if res.is_object_pattern() {
return true;
}
}
false
}
pub fn bucket_resource_exists(&self) -> bool {
for res in self.0.iter() {
if res.is_bucket_pattern() {
return true;
}
}
false
}
}
// impl Serialize for ResourceSet {
// fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
// where
// S: serde::Serializer,
// {
// let ress: Vec<Resource> = self.0.iter().cloned().collect();
// serializer.collect_seq(ress)
// }
// }
// impl<'de> Deserialize<'de> for ResourceSet {
// fn deserialize<D>(deserializer: D) -> Result<ResourceSet, D::Error>
// where
// D: Deserializer<'de>,
// {
// let vec: Vec<Resource> = Deserialize::deserialize(deserializer)?;
// let ha: HashSet<Resource> = vec.into_iter().collect();
// Ok(ResourceSet(ha))
// }
// }
+33
View File
@@ -0,0 +1,33 @@
use super::{
error::BucketMetadataError,
get_bucket_metadata_sys,
policy::bucket_policy::{BucketPolicy, BucketPolicyArgs},
};
use crate::error::Result;
use tracing::warn;
pub struct PolicySys {}
impl PolicySys {
pub async fn is_allowed(args: &BucketPolicyArgs) -> bool {
match Self::get(&args.bucket_name).await {
Ok(cfg) => return cfg.is_allowed(args),
Err(err) => {
if !BucketMetadataError::BucketPolicyNotFound.is(&err) {
warn!("config get err {:?}", err);
}
()
}
}
args.is_owner
}
pub async fn get(bucket: &str) -> Result<BucketPolicy> {
let bucket_meta_sys_lock = get_bucket_metadata_sys().await;
let bucket_meta_sys = bucket_meta_sys_lock.write().await;
let (cfg, _) = bucket_meta_sys.get_bucket_policy(bucket).await?;
Ok(cfg)
}
}
+6 -6
View File
@@ -1643,7 +1643,7 @@ mod test {
#[tokio::test]
async fn test_make_volume() {
let p = "./testv";
let p = "./testv0";
fs::create_dir_all(&p).await.unwrap();
let ep = match Endpoint::try_from(p) {
@@ -1662,18 +1662,18 @@ mod test {
println!("ppp :{:?}", &tmpp);
let volumes = vec!["a", "b", "c"];
let volumes = vec!["a123", "b123", "c123"];
disk.make_volumes(volumes.clone()).await.unwrap();
disk.make_volumes(volumes.clone()).await.unwrap();
fs::remove_dir_all(&p).await.unwrap();
let _ = fs::remove_dir_all(&p).await;
}
#[tokio::test]
async fn test_delete_volume() {
let p = "./testv";
let p = "./testv1";
fs::create_dir_all(&p).await.unwrap();
let ep = match Endpoint::try_from(p) {
@@ -1692,12 +1692,12 @@ mod test {
println!("ppp :{:?}", &tmpp);
let volumes = vec!["a", "b", "c"];
let volumes = vec!["a123", "b123", "c123"];
disk.make_volumes(volumes.clone()).await.unwrap();
disk.delete_volume("a").await.unwrap();
fs::remove_dir_all(&p).await.unwrap();
let _ = fs::remove_dir_all(&p).await;
}
}
+2 -2
View File
@@ -127,10 +127,10 @@ mod test {
#[test]
fn test_check_local_server_addr() {
let test_cases = [
(":54321", Ok(())),
// (":54321", Ok(())),
("localhost:54321", Ok(())),
("0.0.0.0:9000", Ok(())),
(":0", Ok(())),
// (":0", Ok(())),
("localhost", Err(Error::from_string("invalid socket address"))),
("", Err(Error::from_string("invalid socket address"))),
(
+161
View File
@@ -37,3 +37,164 @@ pub fn retain_slash(s: &str) -> String {
format!("{}{}", s, SLASH_SEPARATOR)
}
}
pub struct LazyBuf {
s: String,
buf: Option<Vec<u8>>,
w: usize,
}
impl LazyBuf {
pub fn new(s: String) -> Self {
LazyBuf { s, buf: None, w: 0 }
}
pub fn index(&self, i: usize) -> u8 {
if let Some(ref buf) = self.buf {
buf[i]
} else {
self.s.as_bytes()[i]
}
}
pub fn append(&mut self, c: u8) {
if self.buf.is_none() {
if self.w < self.s.len() && self.s.as_bytes()[self.w] == c {
self.w += 1;
return;
}
let mut new_buf = vec![0; self.s.len()];
new_buf[..self.w].copy_from_slice(&self.s.as_bytes()[..self.w]);
self.buf = Some(new_buf);
}
if let Some(ref mut buf) = self.buf {
buf[self.w] = c;
self.w += 1;
}
}
pub fn string(&self) -> String {
if let Some(ref buf) = self.buf {
String::from_utf8(buf[..self.w].to_vec()).unwrap()
} else {
self.s[..self.w].to_string()
}
}
}
pub fn clean(path: &str) -> String {
if path.is_empty() {
return ".".to_string();
}
let rooted = path.starts_with('/');
let n = path.len();
let mut out = LazyBuf::new(path.to_string());
let mut r = 0;
let mut dotdot = 0;
if rooted {
out.append(b'/');
r = 1;
dotdot = 1;
}
while r < n {
match path.as_bytes()[r] {
b'/' => {
// Empty path element
r += 1;
}
b'.' if r + 1 == n || path.as_bytes()[r + 1] == b'/' => {
// . element
r += 1;
}
b'.' if path.as_bytes()[r + 1] == b'.' && (r + 2 == n || path.as_bytes()[r + 2] == b'/') => {
// .. element: remove to last /
r += 2;
if out.w > dotdot {
// Can backtrack
out.w -= 1;
while out.w > dotdot && out.index(out.w) != b'/' {
out.w -= 1;
}
} else if !rooted {
// Cannot backtrack but not rooted, so append .. element.
if out.w > 0 {
out.append(b'/');
}
out.append(b'.');
out.append(b'.');
dotdot = out.w;
}
}
_ => {
// Real path element.
// Add slash if needed
if (rooted && out.w != 1) || (!rooted && out.w != 0) {
out.append(b'/');
}
// Copy element
while r < n && path.as_bytes()[r] != b'/' {
out.append(path.as_bytes()[r]);
r += 1;
}
}
}
}
// Turn empty string into "."
if out.w == 0 {
return ".".to_string();
}
out.string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_clean() {
assert_eq!(clean(""), ".");
assert_eq!(clean("abc"), "abc");
assert_eq!(clean("abc/def"), "abc/def");
assert_eq!(clean("a/b/c"), "a/b/c");
assert_eq!(clean("."), ".");
assert_eq!(clean(".."), "..");
assert_eq!(clean("../.."), "../..");
assert_eq!(clean("../../abc"), "../../abc");
assert_eq!(clean("/abc"), "/abc");
assert_eq!(clean("/"), "/");
assert_eq!(clean("abc/"), "abc");
assert_eq!(clean("abc/def/"), "abc/def");
assert_eq!(clean("a/b/c/"), "a/b/c");
assert_eq!(clean("./"), ".");
assert_eq!(clean("../"), "..");
assert_eq!(clean("../../"), "../..");
assert_eq!(clean("/abc/"), "/abc");
assert_eq!(clean("abc//def//ghi"), "abc/def/ghi");
assert_eq!(clean("//abc"), "/abc");
assert_eq!(clean("///abc"), "/abc");
assert_eq!(clean("//abc//"), "/abc");
assert_eq!(clean("abc//"), "abc");
assert_eq!(clean("abc/./def"), "abc/def");
assert_eq!(clean("/./abc/def"), "/abc/def");
assert_eq!(clean("abc/."), "abc");
assert_eq!(clean("abc/./../def"), "def");
assert_eq!(clean("abc//./../def"), "def");
assert_eq!(clean("abc/../../././../def"), "../../def");
assert_eq!(clean("abc/def/ghi/../jkl"), "abc/def/jkl");
assert_eq!(clean("abc/def/../ghi/../jkl"), "abc/jkl");
assert_eq!(clean("abc/def/.."), "abc");
assert_eq!(clean("abc/def/../.."), ".");
assert_eq!(clean("/abc/def/../.."), "/");
assert_eq!(clean("abc/def/../../.."), "..");
assert_eq!(clean("/abc/def/../../.."), "/");
assert_eq!(clean("abc/def/../../../ghi/jkl/../../../mno"), "../../mno");
}
}
+1 -1
View File
@@ -60,7 +60,7 @@ futures-util.workspace = true
# uuid = { version = "1.8.0", features = ["v4", "fast-rng", "serde"] }
ecstore = { path = "../ecstore" }
s3s = "0.10.0"
clap = { version = "4.5.18", features = ["derive"] }
clap = { version = "4.5.20", features = ["derive"] }
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "time"] }
hyper-util = { version = "0.1.9", features = [
"tokio",
+1 -1
View File
@@ -22,7 +22,7 @@ use hyper_util::{
use protos::proto_gen::node_service::node_service_server::NodeServiceServer;
use s3s::{auth::SimpleAuth, service::S3ServiceBuilder};
use service::hybrid;
use std::{io::IsTerminal, net::SocketAddr, process::exit, str::FromStr};
use std::{io::IsTerminal, net::SocketAddr, str::FromStr};
use tokio::net::TcpListener;
use tonic::{metadata::MetadataValue, Request, Status};
use tracing::{debug, info};
+205 -11
View File
@@ -1,7 +1,11 @@
use bytes::Bytes;
use ecstore::bucket::error::BucketMetadataError;
use ecstore::bucket::get_bucket_metadata_sys;
use ecstore::bucket::metadata::BUCKET_POLICY_CONFIG;
use ecstore::bucket::metadata::BUCKET_TAGGING_CONFIG;
use ecstore::bucket::metadata::BUCKET_VERSIONING_CONFIG;
use ecstore::bucket::policy::bucket_policy::BucketPolicy;
use ecstore::bucket::policy_sys::PolicySys;
use ecstore::bucket::tags::Tags;
use ecstore::bucket::versioning::State as VersioningState;
use ecstore::bucket::versioning::Versioning;
@@ -21,7 +25,6 @@ use ecstore::store_api::PutObjReader;
use ecstore::store_api::StorageAPI;
use futures::pin_mut;
use futures::{Stream, StreamExt};
use http::status;
use http::HeaderMap;
use log::warn;
use s3s::dto::*;
@@ -267,16 +270,6 @@ impl S3 for FS {
Ok(S3Response::new(output))
}
#[tracing::instrument(level = "debug", skip(self))]
async fn get_object_lock_configuration(
&self,
_req: S3Request<GetObjectLockConfigurationInput>,
) -> S3Result<S3Response<GetObjectLockConfigurationOutput>> {
// mc cp step 1
let output = GetObjectLockConfigurationOutput::default();
Ok(S3Response::new(output))
}
#[tracing::instrument(
level = "debug",
skip(self, req),
@@ -933,6 +926,207 @@ impl S3 for FS {
Ok(S3Response::new(PutBucketVersioningOutput {}))
}
async fn get_bucket_policy_status(
&self,
_req: S3Request<GetBucketPolicyStatusInput>,
) -> S3Result<S3Response<GetBucketPolicyStatusOutput>> {
Err(s3_error!(NotImplemented, "GetBucketPolicyStatus is not implemented yet"))
}
async fn get_bucket_policy(&self, req: S3Request<GetBucketPolicyInput>) -> S3Result<S3Response<GetBucketPolicyOutput>> {
let GetBucketPolicyInput { bucket, .. } = req.input;
let layer = new_object_layer_fn();
let lock = layer.read().await;
let store = lock
.as_ref()
.ok_or_else(|| S3Error::with_message(S3ErrorCode::InternalError, "Not init"))?;
if let Err(e) = store.get_bucket_info(&bucket, &BucketOptions::default()).await {
if DiskError::VolumeNotFound.is(&e) {
return Err(s3_error!(NoSuchBucket));
} else {
return Err(S3Error::with_message(S3ErrorCode::InternalError, format!("{}", e)));
}
}
let cfg = match PolicySys::get(&bucket).await {
Ok(res) => res,
Err(err) => {
if BucketMetadataError::BucketPolicyNotFound.is(&err) {
return Err(s3_error!(NoSuchBucketPolicy));
}
return Err(S3Error::with_message(S3ErrorCode::InternalError, format!("{}", err)));
}
};
let policys = try_!(cfg.marshal_msg());
Ok(S3Response::new(GetBucketPolicyOutput { policy: Some(policys) }))
}
async fn put_bucket_policy(&self, req: S3Request<PutBucketPolicyInput>) -> S3Result<S3Response<PutBucketPolicyOutput>> {
let PutBucketPolicyInput { bucket, policy, .. } = req.input;
let layer = new_object_layer_fn();
let lock = layer.read().await;
let store = lock
.as_ref()
.ok_or_else(|| S3Error::with_message(S3ErrorCode::InternalError, "Not init"))?;
if let Err(e) = store.get_bucket_info(&bucket, &BucketOptions::default()).await {
if DiskError::VolumeNotFound.is(&e) {
return Err(s3_error!(NoSuchBucket));
} else {
return Err(S3Error::with_message(S3ErrorCode::InternalError, format!("{}", e)));
}
}
let cfg = try_!(BucketPolicy::unmarshal(policy.as_bytes()));
warn!("put_bucket_policy struct {:?}", &cfg);
if let Err(err) = cfg.validate(&bucket) {
warn!("put_bucket_policy input {:?}", &policy);
warn!("cfg.validate err {:?}", err);
return Err(s3_error!(InvalidPolicyDocument));
}
let data = try_!(cfg.marshal_msg());
let bucket_meta_sys_lock = get_bucket_metadata_sys().await;
let mut bucket_meta_sys = bucket_meta_sys_lock.write().await;
try_!(bucket_meta_sys.update(&bucket, BUCKET_POLICY_CONFIG, data.into()).await);
Ok(S3Response::new(PutBucketPolicyOutput {}))
}
async fn delete_bucket_policy(
&self,
req: S3Request<DeleteBucketPolicyInput>,
) -> S3Result<S3Response<DeleteBucketPolicyOutput>> {
let DeleteBucketPolicyInput { bucket, .. } = req.input;
let layer = new_object_layer_fn();
let lock = layer.read().await;
let store = lock
.as_ref()
.ok_or_else(|| S3Error::with_message(S3ErrorCode::InternalError, "Not init"))?;
if let Err(e) = store.get_bucket_info(&bucket, &BucketOptions::default()).await {
if DiskError::VolumeNotFound.is(&e) {
return Err(s3_error!(NoSuchBucket));
} else {
return Err(S3Error::with_message(S3ErrorCode::InternalError, format!("{}", e)));
}
}
let bucket_meta_sys_lock = get_bucket_metadata_sys().await;
let mut bucket_meta_sys = bucket_meta_sys_lock.write().await;
try_!(bucket_meta_sys.delete(&bucket, BUCKET_POLICY_CONFIG).await);
Ok(S3Response::new(DeleteBucketPolicyOutput {}))
}
#[tracing::instrument(level = "debug", skip(self))]
async fn get_bucket_lifecycle_configuration(
&self,
_req: S3Request<GetBucketLifecycleConfigurationInput>,
) -> S3Result<S3Response<GetBucketLifecycleConfigurationOutput>> {
Err(s3_error!(NotImplemented, "GetBucketLifecycleConfiguration is not implemented yet"))
}
#[tracing::instrument(level = "debug", skip(self))]
async fn put_bucket_lifecycle_configuration(
&self,
_req: S3Request<PutBucketLifecycleConfigurationInput>,
) -> S3Result<S3Response<PutBucketLifecycleConfigurationOutput>> {
Err(s3_error!(NotImplemented, "PutBucketLifecycleConfiguration is not implemented yet"))
}
#[tracing::instrument(level = "debug", skip(self))]
async fn delete_bucket_lifecycle(
&self,
_req: S3Request<DeleteBucketLifecycleInput>,
) -> S3Result<S3Response<DeleteBucketLifecycleOutput>> {
Err(s3_error!(NotImplemented, "DeleteBucketLifecycle is not implemented yet"))
}
async fn get_bucket_encryption(
&self,
_req: S3Request<GetBucketEncryptionInput>,
) -> S3Result<S3Response<GetBucketEncryptionOutput>> {
Err(s3_error!(NotImplemented, "GetBucketEncryption is not implemented yet"))
}
async fn put_bucket_encryption(
&self,
_req: S3Request<PutBucketEncryptionInput>,
) -> S3Result<S3Response<PutBucketEncryptionOutput>> {
Err(s3_error!(NotImplemented, "PutBucketEncryption is not implemented yet"))
}
async fn delete_bucket_encryption(
&self,
_req: S3Request<DeleteBucketEncryptionInput>,
) -> S3Result<S3Response<DeleteBucketEncryptionOutput>> {
Err(s3_error!(NotImplemented, "DeleteBucketEncryption is not implemented yet"))
}
#[tracing::instrument(level = "debug", skip(self))]
async fn get_object_lock_configuration(
&self,
_req: S3Request<GetObjectLockConfigurationInput>,
) -> S3Result<S3Response<GetObjectLockConfigurationOutput>> {
// mc cp step 1
let output = GetObjectLockConfigurationOutput::default();
Ok(S3Response::new(output))
}
#[tracing::instrument(level = "debug", skip(self))]
async fn put_object_lock_configuration(
&self,
_req: S3Request<PutObjectLockConfigurationInput>,
) -> S3Result<S3Response<PutObjectLockConfigurationOutput>> {
Err(s3_error!(NotImplemented, "PutObjectLockConfiguration is not implemented yet"))
}
async fn get_bucket_replication(
&self,
_req: S3Request<GetBucketReplicationInput>,
) -> S3Result<S3Response<GetBucketReplicationOutput>> {
Err(s3_error!(NotImplemented, "GetBucketReplication is not implemented yet"))
}
async fn put_bucket_replication(
&self,
_req: S3Request<PutBucketReplicationInput>,
) -> S3Result<S3Response<PutBucketReplicationOutput>> {
Err(s3_error!(NotImplemented, "PutBucketReplication is not implemented yet"))
}
async fn delete_bucket_replication(
&self,
_req: S3Request<DeleteBucketReplicationInput>,
) -> S3Result<S3Response<DeleteBucketReplicationOutput>> {
Err(s3_error!(NotImplemented, "DeleteBucketReplication is not implemented yet"))
}
async fn get_bucket_notification_configuration(
&self,
_req: S3Request<GetBucketNotificationConfigurationInput>,
) -> S3Result<S3Response<GetBucketNotificationConfigurationOutput>> {
Err(s3_error!(NotImplemented, "GetBucketNotificationConfiguration is not implemented yet"))
}
async fn put_bucket_notification_configuration(
&self,
_req: S3Request<PutBucketNotificationConfigurationInput>,
) -> S3Result<S3Response<PutBucketNotificationConfigurationOutput>> {
Err(s3_error!(NotImplemented, "PutBucketNotificationConfiguration is not implemented yet"))
}
}
#[allow(dead_code)]