Skip to content

feat(query): Support create/alter/drop/desc/show password policy #14012

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/common/exception/src/exception_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@ build_exceptions! {
NetworkPolicyAlreadyExists(2208),
IllegalNetworkPolicy(2209),
NetworkPolicyIsUsedByUser(2210),
UnknownPasswordPolicy(2211),
PasswordPolicyAlreadyExists(2212),
IllegalPasswordPolicy(2213),
PasswordPolicyIsUsedByUser(2214),
InvalidPassword(2215),

// Meta api error codes.
DatabaseAlreadyExists(2301),
Expand Down
2 changes: 2 additions & 0 deletions src/meta/app/src/principal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mod connection;
mod file_format;
mod network_policy;
mod ownership_info;
mod password_policy;
mod principal_identity;
mod role_info;
mod user_auth;
Expand All @@ -35,6 +36,7 @@ pub use connection::*;
pub use file_format::*;
pub use network_policy::NetworkPolicy;
pub use ownership_info::OwnershipInfo;
pub use password_policy::PasswordPolicy;
pub use principal_identity::PrincipalIdentity;
pub use role_info::RoleInfo;
pub use role_info::RoleInfoSerdeError;
Expand Down
35 changes: 35 additions & 0 deletions src/meta/app/src/principal/password_policy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2021 Datafuse Labs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use chrono::DateTime;
use chrono::Utc;

#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Eq, PartialEq, Default)]
pub struct PasswordPolicy {
pub name: String,
pub min_length: u64,
pub max_length: u64,
pub min_upper_case_chars: u64,
pub min_lower_case_chars: u64,
pub min_numeric_chars: u64,
pub min_special_chars: u64,
pub min_age_days: u64,
pub max_age_days: u64,
pub max_retries: u64,
pub lockout_time_mins: u64,
pub history: u64,
pub comment: String,
pub create_on: DateTime<Utc>,
pub update_on: Option<DateTime<Utc>>,
}
16 changes: 16 additions & 0 deletions src/meta/app/src/principal/user_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ pub struct UserOption {
default_role: Option<String>,

network_policy: Option<String>,

password_policy: Option<String>,
}

impl UserOption {
Expand All @@ -115,6 +117,7 @@ impl UserOption {
flags,
default_role: None,
network_policy: None,
password_policy: None,
}
}

Expand All @@ -137,6 +140,11 @@ impl UserOption {
self
}

pub fn with_password_policy(mut self, password_policy: Option<String>) -> Self {
self.password_policy = password_policy;
self
}

pub fn with_set_flag(mut self, flag: UserOptionFlag) -> Self {
self.flags.insert(flag);
self
Expand All @@ -154,6 +162,10 @@ impl UserOption {
self.network_policy.as_ref()
}

pub fn password_policy(&self) -> Option<&String> {
self.password_policy.as_ref()
}

pub fn set_default_role(&mut self, default_role: Option<String>) {
self.default_role = default_role;
}
Expand All @@ -162,6 +174,10 @@ impl UserOption {
self.network_policy = network_policy;
}

pub fn set_password_policy(&mut self, password_policy: Option<String>) {
self.password_policy = password_policy;
}

pub fn set_all_flag(&mut self) {
self.flags = BitFlags::all();
}
Expand Down
60 changes: 59 additions & 1 deletion src/meta/proto-conv/src/user_from_to_protobuf_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ impl FromToProto for mt::principal::UserOption {
Ok(mt::principal::UserOption::default()
.with_flags(flags)
.with_default_role(p.default_role)
.with_network_policy(p.network_policy))
.with_network_policy(p.network_policy)
.with_password_policy(p.password_policy))
}

fn to_pb(&self) -> Result<pb::UserOption, Incompatible> {
Expand All @@ -109,6 +110,7 @@ impl FromToProto for mt::principal::UserOption {
flags: self.flags().bits(),
default_role: self.default_role().cloned(),
network_policy: self.network_policy().cloned(),
password_policy: self.password_policy().cloned(),
})
}
}
Expand Down Expand Up @@ -389,3 +391,59 @@ impl FromToProto for mt::principal::NetworkPolicy {
})
}
}

impl FromToProto for mt::principal::PasswordPolicy {
type PB = pb::PasswordPolicy;
fn get_pb_ver(p: &Self::PB) -> u64 {
p.ver
}
fn from_pb(p: pb::PasswordPolicy) -> Result<Self, Incompatible>
where Self: Sized {
reader_check_msg(p.ver, p.min_reader_ver)?;
Ok(mt::principal::PasswordPolicy {
name: p.name.clone(),
min_length: p.min_length,
max_length: p.max_length,
min_upper_case_chars: p.min_upper_case_chars,
min_lower_case_chars: p.min_lower_case_chars,
min_numeric_chars: p.min_numeric_chars,
min_special_chars: p.min_special_chars,
min_age_days: p.min_age_days,
max_age_days: p.max_age_days,
max_retries: p.max_retries,
lockout_time_mins: p.lockout_time_mins,
history: p.history,
comment: p.comment,
create_on: DateTime::<Utc>::from_pb(p.create_on)?,
update_on: match p.update_on {
Some(t) => Some(DateTime::<Utc>::from_pb(t)?),
None => None,
},
})
}

fn to_pb(&self) -> Result<pb::PasswordPolicy, Incompatible> {
Ok(pb::PasswordPolicy {
ver: VER,
min_reader_ver: MIN_READER_VER,
name: self.name.clone(),
min_length: self.min_length,
max_length: self.max_length,
min_upper_case_chars: self.min_upper_case_chars,
min_lower_case_chars: self.min_lower_case_chars,
min_numeric_chars: self.min_numeric_chars,
min_special_chars: self.min_special_chars,
min_age_days: self.min_age_days,
max_age_days: self.max_age_days,
max_retries: self.max_retries,
lockout_time_mins: self.lockout_time_mins,
history: self.history,
comment: self.comment.clone(),
create_on: self.create_on.to_pb()?,
update_on: match &self.update_on {
Some(t) => Some(t.to_pb()?),
None => None,
},
})
}
}
1 change: 1 addition & 0 deletions src/meta/proto-conv/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ const META_CHANGE_LOG: &[(u64, &str)] = &[
(64, "2023-11-16: Add: user.proto/NDJsonFileFormatParams add field `missing_field_as` and `null_field_as`", ),
(65, "2023-11-16: Retype: use Datetime<Utc> instead of u64 to in lvt.time", ),
(66, "2023-12-15: Add: stage.proto/StageInfo::created_on", ),
(67, "2023-12-19: Add: user.proto/PasswordPolicy and UserOption::password_policy", ),
// Dear developer:
// If you're gonna add a new metadata version, you'll have to add a test for it.
// You could just copy an existing test file(e.g., `../tests/it/v024_table_meta.rs`)
Expand Down
1 change: 1 addition & 0 deletions src/meta/proto-conv/tests/it/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,4 @@ mod v063_connection;
mod v064_ndjson_format_params;
mod v065_least_visible_time;
mod v066_stage_create_on;
mod v067_password_policy;
110 changes: 110 additions & 0 deletions src/meta/proto-conv/tests/it/v067_password_policy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright 2021 Datafuse Labs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::collections::HashSet;

use chrono::TimeZone;
use chrono::Utc;
use databend_common_meta_app::principal::UserPrivilegeType;
use enumflags2::make_bitflags;
use minitrace::func_name;

use crate::common;

// These bytes are built when a new version in introduced,
// and are kept for backward compatibility test.
//
// *************************************************************
// * These messages should never be updated, *
// * only be added when a new version is added, *
// * or be removed when an old version is no longer supported. *
// *************************************************************
//
// The message bytes are built from the output of `test_build_pb_buf()`
#[test]
fn test_decode_v67_password_policy() -> anyhow::Result<()> {
// password policy
let bytes: Vec<u8> = vec![
10, 19, 116, 101, 115, 116, 112, 97, 115, 115, 119, 111, 114, 100, 112, 111, 108, 105, 99,
121, 49, 16, 8, 24, 30, 32, 1, 40, 2, 48, 3, 56, 4, 64, 10, 72, 90, 80, 5, 88, 10, 96, 1,
106, 12, 115, 111, 109, 101, 32, 99, 111, 109, 109, 101, 110, 116, 114, 23, 50, 48, 49, 52,
45, 49, 49, 45, 50, 56, 32, 49, 50, 58, 48, 48, 58, 48, 57, 32, 85, 84, 67, 122, 23, 50,
48, 49, 52, 45, 49, 49, 45, 50, 56, 32, 49, 50, 58, 48, 48, 58, 48, 57, 32, 85, 84, 67,
160, 6, 67, 168, 6, 24,
];

let want = || databend_common_meta_app::principal::PasswordPolicy {
name: "testpasswordpolicy1".to_string(),
min_length: 8,
max_length: 30,
min_upper_case_chars: 1,
min_lower_case_chars: 2,
min_numeric_chars: 3,
min_special_chars: 4,
min_age_days: 10,
max_age_days: 90,
max_retries: 5,
lockout_time_mins: 10,
history: 1,
comment: "some comment".to_string(),
create_on: Utc.with_ymd_and_hms(2014, 11, 28, 12, 0, 9).unwrap(),
update_on: Some(Utc.with_ymd_and_hms(2014, 11, 28, 12, 0, 9).unwrap()),
};

common::test_pb_from_to(func_name!(), want())?;
common::test_load_old(func_name!(), bytes.as_slice(), 67, want())?;

// user info with password policy
let bytes: Vec<u8> = vec![
10, 9, 116, 101, 115, 116, 95, 117, 115, 101, 114, 18, 1, 37, 26, 25, 18, 17, 10, 13, 116,
101, 115, 116, 95, 112, 97, 115, 115, 119, 111, 114, 100, 16, 1, 160, 6, 67, 168, 6, 24,
34, 26, 10, 18, 10, 8, 10, 0, 160, 6, 67, 168, 6, 24, 16, 2, 160, 6, 67, 168, 6, 24, 160,
6, 67, 168, 6, 24, 42, 15, 8, 10, 16, 128, 80, 24, 128, 160, 1, 160, 6, 67, 168, 6, 24, 50,
46, 8, 1, 18, 5, 114, 111, 108, 101, 49, 26, 8, 109, 121, 112, 111, 108, 105, 99, 121, 34,
19, 116, 101, 115, 116, 112, 97, 115, 115, 119, 111, 114, 100, 112, 111, 108, 105, 99, 121,
49, 160, 6, 67, 168, 6, 24, 160, 6, 67, 168, 6, 24,
];

let want = || databend_common_meta_app::principal::UserInfo {
name: "test_user".to_string(),
hostname: "%".to_string(),
auth_info: databend_common_meta_app::principal::AuthInfo::Password {
hash_value: [
116, 101, 115, 116, 95, 112, 97, 115, 115, 119, 111, 114, 100,
]
.to_vec(),
hash_method: databend_common_meta_app::principal::PasswordHashMethod::DoubleSha1,
},
grants: databend_common_meta_app::principal::UserGrantSet::new(
vec![databend_common_meta_app::principal::GrantEntry::new(
databend_common_meta_app::principal::GrantObject::Global,
make_bitflags!(UserPrivilegeType::{Create}),
)],
HashSet::new(),
),
quota: databend_common_meta_app::principal::UserQuota {
max_cpu: 10,
max_memory_in_bytes: 10240,
max_storage_in_bytes: 20480,
},
option: databend_common_meta_app::principal::UserOption::default()
.with_set_flag(databend_common_meta_app::principal::UserOptionFlag::TenantSetting)
.with_default_role(Some("role1".into()))
.with_network_policy(Some("mypolicy".to_string()))
.with_password_policy(Some("testpasswordpolicy1".to_string())),
};

common::test_pb_from_to(func_name!(), want())?;
common::test_load_old(func_name!(), bytes.as_slice(), 67, want())
}
Loading