Skip to main content

rustfs_policy/
arn.rs

1// Copyright 2024 RustFS Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::error::{Error, Result};
16use regex::Regex;
17
18const ARN_PREFIX_ARN: &str = "arn";
19const ARN_PARTITION_RUSTFS: &str = "rustfs";
20const ARN_SERVICE_IAM: &str = "iam";
21const ARN_RESOURCE_TYPE_ROLE: &str = "role";
22
23#[derive(Debug, PartialEq, Eq, Hash)]
24pub struct ARN {
25    pub partition: String,
26    pub service: String,
27    pub region: String,
28    pub resource_type: String,
29    pub resource_id: String,
30}
31
32impl ARN {
33    pub fn new_iam_role_arn(resource_id: &str, server_region: &str) -> Result<Self> {
34        let valid_resource_id_regex = Regex::new(r"^[A-Za-z0-9_/\.-]+$")?;
35        if !valid_resource_id_regex.is_match(resource_id) {
36            return Err(Error::other("ARN resource ID invalid"));
37        }
38        Ok(ARN {
39            partition: ARN_PARTITION_RUSTFS.to_string(),
40            service: ARN_SERVICE_IAM.to_string(),
41            region: server_region.to_string(),
42            resource_type: ARN_RESOURCE_TYPE_ROLE.to_string(),
43            resource_id: resource_id.to_string(),
44        })
45    }
46
47    pub fn parse(arn_str: &str) -> Result<Self> {
48        let ps: Vec<&str> = arn_str.split(':').collect();
49        if ps.len() != 6 || ps[0] != ARN_PREFIX_ARN {
50            return Err(Error::other("ARN format invalid"));
51        }
52
53        if ps[1] != ARN_PARTITION_RUSTFS {
54            return Err(Error::other("ARN partition invalid"));
55        }
56
57        if ps[2] != ARN_SERVICE_IAM {
58            return Err(Error::other("ARN service invalid"));
59        }
60
61        if !ps[4].is_empty() {
62            return Err(Error::other("ARN account-id invalid"));
63        }
64
65        let res: Vec<&str> = ps[5].splitn(2, '/').collect();
66        if res.len() != 2 {
67            return Err(Error::other("ARN resource invalid"));
68        }
69
70        if res[0] != ARN_RESOURCE_TYPE_ROLE {
71            return Err(Error::other("ARN resource type invalid"));
72        }
73
74        let valid_resource_id_regex = Regex::new(r"^[A-Za-z0-9_/\.-]+$")?;
75        if !valid_resource_id_regex.is_match(res[1]) {
76            return Err(Error::other("ARN resource ID invalid"));
77        }
78
79        Ok(ARN {
80            partition: ARN_PARTITION_RUSTFS.to_string(),
81            service: ARN_SERVICE_IAM.to_string(),
82            region: ps[3].to_string(),
83            resource_type: ARN_RESOURCE_TYPE_ROLE.to_string(),
84            resource_id: res[1].to_string(),
85        })
86    }
87}
88
89impl std::fmt::Display for ARN {
90    #[allow(clippy::write_literal)]
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92        write!(
93            f,
94            "{}:{}:{}:{}:{}:{}/{}",
95            ARN_PREFIX_ARN,
96            self.partition,
97            self.service,
98            self.region,
99            "", // account-id is always empty in this implementation
100            self.resource_type,
101            self.resource_id
102        )
103    }
104}