diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index 161db9e0d..016af13f7 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -33,10 +33,14 @@ Then you can setup the initial admin account and initialise the database into yo docker run --rm -i -t -v kanidmd:/data firstyear/kanidmd:latest /home/kanidm/target/release/kanidmd recover_account -D /data/kanidm.db -n admin -You can now run the server - note that we provide all the options on the cli, but this pattern -may change in the future. +You then want to set your domain name: - docker run -p 8443:8443 -v /Users/william/development/rsidm/insecure:/data firstyear/kanidmd:latest /home/kanidm/target/release/kanidmd server -D /data/kanidm.db -C /data/ca.pem -c /data/cert.pem -k /data/key.pem --bindaddr 0.0.0.0:8443 --domain localhost + docker run --rm -i -t -v kanidmd:/data firstyear/kanidmd:latest /home/kanidm/target/release/kanidmd domain_name_change -D /data/kanidm.db -n idm.example.com + +Now we can run the server. Previously we had to specify all options, but now domain_name is part of +the database. + + docker run -p 8443:8443 -v /Users/william/development/rsidm/insecure:/data firstyear/kanidmd:latest # Using the cli diff --git a/README.md b/README.md index 678de36b1..dfad6e0c8 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ You can now build and run the server with: cd kanidmd cargo run -- recover_account -D /tmp/kanidm.db -n admin - cargo run -- server -D /tmp/kanidm.db -C ../insecure/ca.pem -c ../insecure/cert.pem -k ../insecure/key.pem --domain localhost --bindaddr 127.0.0.1:8080 + cargo run -- server -D /tmp/kanidm.db -C ../insecure/ca.pem -c ../insecure/cert.pem -k ../insecure/key.pem --bindaddr 127.0.0.1:8080 In a new terminal, you can now build and run the client tools with: diff --git a/kanidm_client/src/lib.rs b/kanidm_client/src/lib.rs index c464db433..da9c33c99 100644 --- a/kanidm_client/src/lib.rs +++ b/kanidm_client/src/lib.rs @@ -598,6 +598,29 @@ impl KanidmClient { self.perform_delete_request(format!("/v1/account/{}/_ssh_pubkeys/{}", id, tag).as_str()) } + // ==== domain_info (aka domain) + pub fn idm_domain_list(&self) -> Result, ClientError> { + self.perform_get_request("/v1/domain") + } + + pub fn idm_domain_get(&self, id: &str) -> Result { + self.perform_get_request(format!("/v1/domain/{}", id).as_str()) + } + + // pub fn idm_domain_get_attr + pub fn idm_domain_get_ssid(&self, id: &str) -> Result { + self.perform_get_request(format!("/v1/domain/{}/_attr/domain_ssid", id).as_str()) + .and_then(|mut r: Vec| Ok(r.pop().unwrap())) + } + + // pub fn idm_domain_put_attr + pub fn idm_domain_set_ssid(&self, id: &str, ssid: &str) -> Result<(), ClientError> { + self.perform_put_request( + format!("/v1/domain/{}/_attr/domain_ssid", id).as_str(), + vec![ssid.to_string()], + ) + } + // ==== schema pub fn idm_schema_list(&self) -> Result, ClientError> { self.perform_get_request("/v1/schema") diff --git a/kanidm_client/tests/proto_v1_test.rs b/kanidm_client/tests/proto_v1_test.rs index 755a3df31..ba47e3801 100644 --- a/kanidm_client/tests/proto_v1_test.rs +++ b/kanidm_client/tests/proto_v1_test.rs @@ -528,6 +528,29 @@ fn test_server_rest_sshkey_lifecycle() { }); } +#[test] +fn test_server_rest_domain_lifecycle() { + run_test(|rsclient: KanidmClient| { + let res = rsclient.auth_simple_password("admin", ADMIN_TEST_PASSWORD); + assert!(res.is_ok()); + + let mut dlist = rsclient.idm_domain_list().unwrap(); + assert!(dlist.len() == 1); + + let dlocal = rsclient.idm_domain_get("domain_local").unwrap(); + // There should be one, and it's the domain_local + assert!(dlist.pop().unwrap().attrs == dlocal.attrs); + + // Change the ssid + rsclient + .idm_domain_set_ssid("domain_local", "new_ssid") + .unwrap(); + // check get and get the ssid and domain info + let nssid = rsclient.idm_domain_get_ssid("domain_local").unwrap(); + assert!(nssid == "new_ssid"); + }); +} + // Test the self version of the radius path. // Test hitting all auth-required endpoints and assert they give unauthorized. diff --git a/kanidm_proto/src/v1.rs b/kanidm_proto/src/v1.rs index 445dc5432..aa4839c48 100644 --- a/kanidm_proto/src/v1.rs +++ b/kanidm_proto/src/v1.rs @@ -72,6 +72,7 @@ pub enum ConsistencyError { MemberOfInvalid(u64), InvalidAttributeType(String), DuplicateUniqueAttribute(String), + InvalidSPN(u64), } /* ===== higher level types ===== */ diff --git a/kanidmd/src/lib/be/dbvalue.rs b/kanidmd/src/lib/be/dbvalue.rs index 586e0868c..2db3e81c9 100644 --- a/kanidmd/src/lib/be/dbvalue.rs +++ b/kanidmd/src/lib/be/dbvalue.rs @@ -37,4 +37,5 @@ pub enum DbValueV1 { CR(DbValueCredV1), RU(String), SK(DbValueTaggedStringV1), + SP(String, String), } diff --git a/kanidmd/src/lib/config.rs b/kanidmd/src/lib/config.rs index 90fa2712f..23dfcab01 100644 --- a/kanidmd/src/lib/config.rs +++ b/kanidmd/src/lib/config.rs @@ -18,7 +18,6 @@ pub struct TlsConfiguration { #[derive(Serialize, Deserialize, Debug)] pub struct Configuration { pub address: String, - pub domain: String, pub threads: usize, // db type later pub db_path: String, @@ -32,7 +31,6 @@ pub struct Configuration { impl fmt::Display for Configuration { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "address: {}, ", self.address) - .and_then(|_| write!(f, "domain: {}, ", self.domain)) .and_then(|_| write!(f, "thread count: {}, ", self.threads)) .and_then(|_| write!(f, "dbpath: {}, ", self.db_path)) .and_then(|_| write!(f, "max request size: {}b, ", self.maximum_request)) @@ -52,7 +50,6 @@ impl Configuration { pub fn new() -> Self { let mut c = Configuration { address: String::from("127.0.0.1:8080"), - domain: String::from("localhost"), threads: num_cpus::get(), db_path: String::from(""), maximum_request: 262144, // 256k diff --git a/kanidmd/src/lib/constants.rs b/kanidmd/src/lib/constants.rs index c7ba3b589..d4c3f0104 100644 --- a/kanidmd/src/lib/constants.rs +++ b/kanidmd/src/lib/constants.rs @@ -1,5 +1,7 @@ use uuid::Uuid; +// Increment this as we add new schema types and values!!! +pub static SYSTEM_INDEX_VERSION: i64 = 3; // On test builds, define to 60 seconds #[cfg(test)] pub static PURGE_TIMEOUT: u64 = 60; @@ -9,9 +11,150 @@ pub static PURGE_TIMEOUT: u64 = 3600; // 5 minute auth session window. pub static AUTH_SESSION_TIMEOUT: u64 = 300; +// Built in group and account ranges. pub static STR_UUID_ADMIN: &'static str = "00000000-0000-0000-0000-000000000000"; -pub static STR_UUID_ANONYMOUS: &'static str = "00000000-0000-0000-0000-ffffffffffff"; +pub static _UUID_IDM_ADMINS: &'static str = "00000000-0000-0000-0000-000000000001"; +pub static _UUID_IDM_PEOPLE_READ_PRIV: &'static str = "00000000-0000-0000-0000-000000000002"; +pub static _UUID_IDM_PEOPLE_WRITE_PRIV: &'static str = "00000000-0000-0000-0000-000000000003"; +pub static _UUID_IDM_GROUP_WRITE_PRIV: &'static str = "00000000-0000-0000-0000-000000000004"; +pub static _UUID_IDM_ACCOUNT_READ_PRIV: &'static str = "00000000-0000-0000-0000-000000000005"; +pub static _UUID_IDM_ACCOUNT_WRITE_PRIV: &'static str = "00000000-0000-0000-0000-000000000006"; +pub static _UUID_IDM_RADIUS_SERVERS: &'static str = "00000000-0000-0000-0000-000000000007"; +pub static _UUID_IDM_HP_ACCOUNT_READ_PRIV: &'static str = "00000000-0000-0000-0000-000000000008"; +pub static _UUID_IDM_HP_ACCOUNT_WRITE_PRIV: &'static str = "00000000-0000-0000-0000-000000000009"; +pub static _UUID_IDM_SCHEMA_MANAGE_PRIV: &'static str = "00000000-0000-0000-0000-000000000010"; +pub static _UUID_IDM_ACP_MANAGE_PRIV: &'static str = "00000000-0000-0000-0000-000000000011"; +pub static _UUID_IDM_HP_GROUP_WRITE_PRIV: &'static str = "00000000-0000-0000-0000-000000000012"; +pub static _UUID_IDM_PEOPLE_MANAGE_PRIV: &'static str = "00000000-0000-0000-0000-000000000013"; +pub static _UUID_IDM_ACCOUNT_MANAGE_PRIV: &'static str = "00000000-0000-0000-0000-000000000014"; +pub static _UUID_IDM_GROUP_MANAGE_PRIV: &'static str = "00000000-0000-0000-0000-000000000015"; +pub static _UUID_IDM_HP_ACCOUNT_MANAGE_PRIV: &'static str = "00000000-0000-0000-0000-000000000016"; +pub static _UUID_IDM_HP_GROUP_MANAGE_PRIV: &'static str = "00000000-0000-0000-0000-000000000017"; +pub static _UUID_IDM_ADMIN_V1: &'static str = "00000000-0000-0000-0000-000000000018"; +pub static _UUID_SYSTEM_ADMINS: &'static str = "00000000-0000-0000-0000-000000000019"; +// TODO +pub static UUID_DOMAIN_ADMINS: &'static str = "00000000-0000-0000-0000-000000000020"; +// +pub static _UUID_IDM_HIGH_PRIVILEGE: &'static str = "00000000-0000-0000-0000-000000001000"; + +// Builtin schema +pub static UUID_SCHEMA_ATTR_CLASS: &'static str = "00000000-0000-0000-0000-ffff00000000"; +pub static UUID_SCHEMA_ATTR_UUID: &'static str = "00000000-0000-0000-0000-ffff00000001"; +pub static UUID_SCHEMA_ATTR_NAME: &'static str = "00000000-0000-0000-0000-ffff00000002"; +pub static UUID_SCHEMA_ATTR_SPN: &'static str = "00000000-0000-0000-0000-ffff00000003"; +pub static UUID_SCHEMA_ATTR_DESCRIPTION: &'static str = "00000000-0000-0000-0000-ffff00000004"; +pub static UUID_SCHEMA_ATTR_MULTIVALUE: &'static str = "00000000-0000-0000-0000-ffff00000005"; +pub static UUID_SCHEMA_ATTR_UNIQUE: &'static str = "00000000-0000-0000-0000-ffff00000047"; +pub static UUID_SCHEMA_ATTR_INDEX: &'static str = "00000000-0000-0000-0000-ffff00000006"; +pub static UUID_SCHEMA_ATTR_SYNTAX: &'static str = "00000000-0000-0000-0000-ffff00000007"; +pub static UUID_SCHEMA_ATTR_SYSTEMMAY: &'static str = "00000000-0000-0000-0000-ffff00000008"; +pub static UUID_SCHEMA_ATTR_MAY: &'static str = "00000000-0000-0000-0000-ffff00000009"; +pub static UUID_SCHEMA_ATTR_SYSTEMMUST: &'static str = "00000000-0000-0000-0000-ffff00000010"; +pub static UUID_SCHEMA_ATTR_MUST: &'static str = "00000000-0000-0000-0000-ffff00000011"; +pub static UUID_SCHEMA_ATTR_MEMBEROF: &'static str = "00000000-0000-0000-0000-ffff00000012"; +pub static UUID_SCHEMA_ATTR_MEMBER: &'static str = "00000000-0000-0000-0000-ffff00000013"; +pub static UUID_SCHEMA_ATTR_DIRECTMEMBEROF: &'static str = "00000000-0000-0000-0000-ffff00000014"; +pub static UUID_SCHEMA_ATTR_VERSION: &'static str = "00000000-0000-0000-0000-ffff00000015"; +pub static UUID_SCHEMA_ATTR_DOMAIN: &'static str = "00000000-0000-0000-0000-ffff00000016"; +pub static UUID_SCHEMA_ATTR_ACP_ENABLE: &'static str = "00000000-0000-0000-0000-ffff00000017"; +pub static UUID_SCHEMA_ATTR_ACP_RECEIVER: &'static str = "00000000-0000-0000-0000-ffff00000018"; +pub static UUID_SCHEMA_ATTR_ACP_TARGETSCOPE: &'static str = "00000000-0000-0000-0000-ffff00000019"; +pub static UUID_SCHEMA_ATTR_ACP_SEARCH_ATTR: &'static str = "00000000-0000-0000-0000-ffff00000020"; +pub static UUID_SCHEMA_ATTR_ACP_CREATE_CLASS: &'static str = "00000000-0000-0000-0000-ffff00000021"; +pub static UUID_SCHEMA_ATTR_ACP_CREATE_ATTR: &'static str = "00000000-0000-0000-0000-ffff00000022"; +pub static UUID_SCHEMA_ATTR_ACP_MODIFY_REMOVEDATTR: &'static str = + "00000000-0000-0000-0000-ffff00000023"; +pub static UUID_SCHEMA_ATTR_ACP_MODIFY_PRESENTATTR: &'static str = + "00000000-0000-0000-0000-ffff00000024"; +pub static UUID_SCHEMA_ATTR_ACP_MODIFY_CLASS: &'static str = "00000000-0000-0000-0000-ffff00000025"; +pub static UUID_SCHEMA_CLASS_ATTRIBUTETYPE: &'static str = "00000000-0000-0000-0000-ffff00000026"; +pub static UUID_SCHEMA_CLASS_CLASSTYPE: &'static str = "00000000-0000-0000-0000-ffff00000027"; +pub static UUID_SCHEMA_CLASS_OBJECT: &'static str = "00000000-0000-0000-0000-ffff00000028"; +pub static UUID_SCHEMA_CLASS_EXTENSIBLEOBJECT: &'static str = + "00000000-0000-0000-0000-ffff00000029"; +pub static UUID_SCHEMA_CLASS_MEMBEROF: &'static str = "00000000-0000-0000-0000-ffff00000030"; +pub static UUID_SCHEMA_CLASS_RECYCLED: &'static str = "00000000-0000-0000-0000-ffff00000031"; +pub static UUID_SCHEMA_CLASS_TOMBSTONE: &'static str = "00000000-0000-0000-0000-ffff00000032"; +pub static UUID_SCHEMA_CLASS_SYSTEM_INFO: &'static str = "00000000-0000-0000-0000-ffff00000033"; +pub static UUID_SCHEMA_CLASS_ACCESS_CONTROL_PROFILE: &'static str = + "00000000-0000-0000-0000-ffff00000034"; +pub static UUID_SCHEMA_CLASS_ACCESS_CONTROL_SEARCH: &'static str = + "00000000-0000-0000-0000-ffff00000035"; +pub static UUID_SCHEMA_CLASS_ACCESS_CONTROL_DELETE: &'static str = + "00000000-0000-0000-0000-ffff00000036"; +pub static UUID_SCHEMA_CLASS_ACCESS_CONTROL_MODIFY: &'static str = + "00000000-0000-0000-0000-ffff00000037"; +pub static UUID_SCHEMA_CLASS_ACCESS_CONTROL_CREATE: &'static str = + "00000000-0000-0000-0000-ffff00000038"; +pub static UUID_SCHEMA_CLASS_SYSTEM: &'static str = "00000000-0000-0000-0000-ffff00000039"; +pub static UUID_SCHEMA_ATTR_DISPLAYNAME: &'static str = "00000000-0000-0000-0000-ffff00000040"; +pub static UUID_SCHEMA_ATTR_MAIL: &'static str = "00000000-0000-0000-0000-ffff00000041"; +pub static UUID_SCHEMA_ATTR_SSH_PUBLICKEY: &'static str = "00000000-0000-0000-0000-ffff00000042"; +pub static UUID_SCHEMA_ATTR_PRIMARY_CREDENTIAL: &'static str = + "00000000-0000-0000-0000-ffff00000043"; +pub static UUID_SCHEMA_CLASS_PERSON: &'static str = "00000000-0000-0000-0000-ffff00000044"; +pub static UUID_SCHEMA_CLASS_GROUP: &'static str = "00000000-0000-0000-0000-ffff00000045"; +pub static UUID_SCHEMA_CLASS_ACCOUNT: &'static str = "00000000-0000-0000-0000-ffff00000046"; +// GAP - 47 +pub static UUID_SCHEMA_ATTR_ATTRIBUTENAME: &'static str = "00000000-0000-0000-0000-ffff00000048"; +pub static UUID_SCHEMA_ATTR_CLASSNAME: &'static str = "00000000-0000-0000-0000-ffff00000049"; +pub static UUID_SCHEMA_ATTR_LEGALNAME: &'static str = "00000000-0000-0000-0000-ffff00000050"; +pub static UUID_SCHEMA_ATTR_RADIUS_SECRET: &'static str = "00000000-0000-0000-0000-ffff00000051"; +pub static UUID_SCHEMA_CLASS_DOMAIN_INFO: &'static str = "00000000-0000-0000-0000-ffff00000052"; +pub static UUID_SCHEMA_ATTR_DOMAIN_NAME: &'static str = "00000000-0000-0000-0000-ffff00000053"; +pub static UUID_SCHEMA_ATTR_DOMAIN_UUID: &'static str = "00000000-0000-0000-0000-ffff00000054"; +pub static UUID_SCHEMA_ATTR_DOMAIN_SSID: &'static str = "00000000-0000-0000-0000-ffff00000055"; + +// System and domain infos +// I'd like to strongly criticise william of the past for fucking up these allocations. +pub static _UUID_SYSTEM_INFO: &'static str = "00000000-0000-0000-0000-ffffff000001"; +pub static UUID_DOMAIN_INFO: &'static str = "00000000-0000-0000-0000-ffffff000025"; + +// Access controls +// skip 00 / 01 - see system info +pub static _UUID_IDM_ADMINS_ACP_RECYCLE_SEARCH_V1: &'static str = + "00000000-0000-0000-0000-ffffff000002"; +pub static _UUID_IDM_ADMINS_ACP_REVIVE_V1: &'static str = "00000000-0000-0000-0000-ffffff000003"; +pub static _UUID_IDM_SELF_ACP_READ_V1: &'static str = "00000000-0000-0000-0000-ffffff000004"; +pub static _UUID_IDM_ALL_ACP_READ_V1: &'static str = "00000000-0000-0000-0000-ffffff000006"; +pub static _UUID_IDM_ACP_PEOPLE_READ_PRIV_V1: &'static str = "00000000-0000-0000-0000-ffffff000007"; +pub static _UUID_IDM_ACP_PEOPLE_WRITE_PRIV_V1: &'static str = + "00000000-0000-0000-0000-ffffff000008"; +pub static _UUID_IDM_ACP_GROUP_WRITE_PRIV_V1: &'static str = "00000000-0000-0000-0000-ffffff000009"; +pub static _UUID_IDM_ACP_ACCOUNT_READ_PRIV_V1: &'static str = + "00000000-0000-0000-0000-ffffff000010"; +pub static _UUID_IDM_ACP_ACCOUNT_WRITE_PRIV_V1: &'static str = + "00000000-0000-0000-0000-ffffff000011"; +pub static _UUID_IDM_ACP_ACCOUNT_MANAGE_PRIV_V1: &'static str = + "00000000-0000-0000-0000-ffffff000012"; +pub static _UUID_IDM_ACP_PEOPLE_MANAGE_PRIV_V1: &'static str = + "00000000-0000-0000-0000-ffffff000013"; +pub static _UUID_IDM_ACP_RADIUS_SERVERS_V1: &'static str = "00000000-0000-0000-0000-ffffff000014"; +pub static _UUID_IDM_ACP_HP_ACCOUNT_READ_PRIV_V1: &'static str = + "00000000-0000-0000-0000-ffffff000015"; +pub static _UUID_IDM_ACP_HP_ACCOUNT_WRITE_PRIV_V1: &'static str = + "00000000-0000-0000-0000-ffffff000016"; +pub static _UUID_IDM_ACP_HP_GROUP_WRITE_PRIV_V1: &'static str = + "00000000-0000-0000-0000-ffffff000017"; +pub static _UUID_IDM_ACP_SCHEMA_WRITE_ATTRS_PRIV_V1: &'static str = + "00000000-0000-0000-0000-ffffff000018"; +pub static _UUID_IDM_ACP_ACP_MANAGE_PRIV_V1: &'static str = "00000000-0000-0000-0000-ffffff000019"; +pub static _UUID_IDM_ACP_SCHEMA_WRITE_CLASSES_PRIV_V1: &'static str = + "00000000-0000-0000-0000-ffffff000020"; +pub static _UUID_IDM_SELF_ACP_WRITE_V1: &'static str = "00000000-0000-0000-0000-ffffff000021"; +pub static _UUID_IDM_ACP_GROUP_MANAGE_PRIV_V1: &'static str = + "00000000-0000-0000-0000-ffffff000022"; +pub static _UUID_IDM_ACP_HP_ACCOUNT_MANAGE_PRIV_V1: &'static str = + "00000000-0000-0000-0000-ffffff000023"; +pub static _UUID_IDM_ACP_HP_GROUP_MANAGE_PRIV_V1: &'static str = + "00000000-0000-0000-0000-ffffff000024"; +// Skip 25 - see domain info. +pub static UUID_IDM_ACP_DOMAIN_ADMIN_PRIV_V1: &'static str = "00000000-0000-0000-0000-ffffff000026"; + +// End of system ranges pub static STR_UUID_DOES_NOT_EXIST: &'static str = "00000000-0000-0000-0000-fffffffffffe"; +pub static STR_UUID_ANONYMOUS: &'static str = "00000000-0000-0000-0000-ffffffffffff"; + lazy_static! { pub static ref UUID_ADMIN: Uuid = Uuid::parse_str(STR_UUID_ADMIN).unwrap(); pub static ref UUID_DOES_NOT_EXIST: Uuid = Uuid::parse_str(STR_UUID_DOES_NOT_EXIST).unwrap(); @@ -46,7 +189,6 @@ pub static JSON_IDM_ADMIN_V1: &'static str = r#"{ } }"#; -pub static _UUID_IDM_ADMINS: &'static str = "00000000-0000-0000-0000-000000000001"; pub static JSON_IDM_ADMINS_V1: &'static str = r#"{ "valid": { "uuid": "00000000-0000-0000-0000-000000000001" @@ -61,7 +203,6 @@ pub static JSON_IDM_ADMINS_V1: &'static str = r#"{ } }"#; -pub static _UUID_SYSTEM_ADMINS: &'static str = "00000000-0000-0000-0000-000000000019"; pub static JSON_SYSTEM_ADMINS_V1: &'static str = r#"{ "valid": { "uuid": "00000000-0000-0000-0000-000000000019" @@ -78,7 +219,6 @@ pub static JSON_SYSTEM_ADMINS_V1: &'static str = r#"{ // groups // * People read managers -pub static _UUID_IDM_PEOPLE_READ_PRIV: &'static str = "00000000-0000-0000-0000-000000000002"; pub static JSON_IDM_PEOPLE_READ_PRIV_V1: &'static str = r#"{ "attrs": { "class": ["group", "object"], @@ -89,7 +229,6 @@ pub static JSON_IDM_PEOPLE_READ_PRIV_V1: &'static str = r#"{ } }"#; // * People write managers -pub static _UUID_IDM_PEOPLE_MANAGE_PRIV: &'static str = "00000000-0000-0000-0000-000000000013"; pub static JSON_IDM_PEOPLE_MANAGE_PRIV_V1: &'static str = r#"{ "attrs": { "class": ["group", "object"], @@ -101,7 +240,6 @@ pub static JSON_IDM_PEOPLE_MANAGE_PRIV_V1: &'static str = r#"{ ] } }"#; -pub static _UUID_IDM_PEOPLE_WRITE_PRIV: &'static str = "00000000-0000-0000-0000-000000000003"; pub static JSON_IDM_PEOPLE_WRITE_PRIV_V1: &'static str = r#"{ "attrs": { "class": ["group", "object"], @@ -113,7 +251,6 @@ pub static JSON_IDM_PEOPLE_WRITE_PRIV_V1: &'static str = r#"{ }"#; // * group write manager (no read, everyone has read via the anon, etc) // IDM_GROUP_CREATE_PRIV -pub static _UUID_IDM_GROUP_MANAGE_PRIV: &'static str = "00000000-0000-0000-0000-000000000015"; pub static JSON_IDM_GROUP_MANAGE_PRIV_V1: &'static str = r#"{ "attrs": { "class": ["group", "object"], @@ -126,7 +263,6 @@ pub static JSON_IDM_GROUP_MANAGE_PRIV_V1: &'static str = r#"{ ] } }"#; -pub static _UUID_IDM_GROUP_WRITE_PRIV: &'static str = "00000000-0000-0000-0000-000000000004"; pub static JSON_IDM_GROUP_WRITE_PRIV_V1: &'static str = r#"{ "attrs": { "class": ["group", "object"], @@ -139,7 +275,6 @@ pub static JSON_IDM_GROUP_WRITE_PRIV_V1: &'static str = r#"{ } }"#; // * account read manager -pub static _UUID_IDM_ACCOUNT_READ_PRIV: &'static str = "00000000-0000-0000-0000-000000000005"; pub static JSON_IDM_ACCOUNT_READ_PRIV_V1: &'static str = r#"{ "attrs": { "class": ["group", "object"], @@ -152,7 +287,6 @@ pub static JSON_IDM_ACCOUNT_READ_PRIV_V1: &'static str = r#"{ } }"#; // * account write manager -pub static _UUID_IDM_ACCOUNT_MANAGE_PRIV: &'static str = "00000000-0000-0000-0000-000000000014"; pub static JSON_IDM_ACCOUNT_MANAGE_PRIV_V1: &'static str = r#"{ "attrs": { "class": ["group", "object"], @@ -164,7 +298,6 @@ pub static JSON_IDM_ACCOUNT_MANAGE_PRIV_V1: &'static str = r#"{ ] } }"#; -pub static _UUID_IDM_ACCOUNT_WRITE_PRIV: &'static str = "00000000-0000-0000-0000-000000000006"; pub static JSON_IDM_ACCOUNT_WRITE_PRIV_V1: &'static str = r#"{ "attrs": { "class": ["group", "object"], @@ -175,7 +308,6 @@ pub static JSON_IDM_ACCOUNT_WRITE_PRIV_V1: &'static str = r#"{ } }"#; // * RADIUS servers -pub static _UUID_IDM_RADIUS_SERVERS: &'static str = "00000000-0000-0000-0000-000000000007"; pub static JSON_IDM_RADIUS_SERVERS_V1: &'static str = r#"{ "attrs": { "class": ["group", "object"], @@ -185,7 +317,6 @@ pub static JSON_IDM_RADIUS_SERVERS_V1: &'static str = r#"{ } }"#; // * high priv account read manager -pub static _UUID_IDM_HP_ACCOUNT_READ_PRIV: &'static str = "00000000-0000-0000-0000-000000000008"; pub static JSON_IDM_HP_ACCOUNT_READ_PRIV_V1: &'static str = r#"{ "attrs": { "class": ["group", "object"], @@ -198,7 +329,6 @@ pub static JSON_IDM_HP_ACCOUNT_READ_PRIV_V1: &'static str = r#"{ } }"#; // * high priv account write manager -pub static _UUID_IDM_HP_ACCOUNT_MANAGE_PRIV: &'static str = "00000000-0000-0000-0000-000000000016"; pub static JSON_IDM_HP_ACCOUNT_MANAGE_PRIV_V1: &'static str = r#"{ "attrs": { "class": ["group", "object"], @@ -210,7 +340,6 @@ pub static JSON_IDM_HP_ACCOUNT_MANAGE_PRIV_V1: &'static str = r#"{ ] } }"#; -pub static _UUID_IDM_HP_ACCOUNT_WRITE_PRIV: &'static str = "00000000-0000-0000-0000-000000000009"; pub static JSON_IDM_HP_ACCOUNT_WRITE_PRIV_V1: &'static str = r#"{ "attrs": { "class": ["group", "object"], @@ -223,7 +352,6 @@ pub static JSON_IDM_HP_ACCOUNT_WRITE_PRIV_V1: &'static str = r#"{ } }"#; // * Schema write manager -pub static _UUID_IDM_SCHEMA_MANAGE_PRIV: &'static str = "00000000-0000-0000-0000-000000000010"; pub static JSON_IDM_SCHEMA_MANAGE_PRIV_V1: &'static str = r#"{ "attrs": { "class": ["group", "object"], @@ -236,7 +364,6 @@ pub static JSON_IDM_SCHEMA_MANAGE_PRIV_V1: &'static str = r#"{ } }"#; // * ACP read/write manager -pub static _UUID_IDM_ACP_MANAGE_PRIV: &'static str = "00000000-0000-0000-0000-000000000011"; pub static JSON_IDM_ACP_MANAGE_PRIV_V1: &'static str = r#"{ "attrs": { "class": ["group", "object"], @@ -247,7 +374,6 @@ pub static JSON_IDM_ACP_MANAGE_PRIV_V1: &'static str = r#"{ } }"#; -pub static _UUID_IDM_HP_GROUP_MANAGE_PRIV: &'static str = "00000000-0000-0000-0000-000000000017"; pub static JSON_IDM_HP_GROUP_MANAGE_PRIV_V1: &'static str = r#"{ "attrs": { "class": ["group", "object"], @@ -257,7 +383,6 @@ pub static JSON_IDM_HP_GROUP_MANAGE_PRIV_V1: &'static str = r#"{ "member": ["00000000-0000-0000-0000-000000000019"] } }"#; -pub static _UUID_IDM_HP_GROUP_WRITE_PRIV: &'static str = "00000000-0000-0000-0000-000000000009"; pub static JSON_IDM_HP_GROUP_WRITE_PRIV_V1: &'static str = r#"{ "attrs": { "class": ["group", "object"], @@ -269,9 +394,19 @@ pub static JSON_IDM_HP_GROUP_WRITE_PRIV_V1: &'static str = r#"{ ] } }"#; +pub static JSON_DOMAIN_ADMINS: &'static str = r#"{ + "attrs": { + "class": ["group", "object"], + "name": ["domain_admins"], + "uuid": ["00000000-0000-0000-0000-000000000020"], + "description": ["Builtin IDM Group for granting local domain administration rights and trust administration rights."], + "member": [ + "00000000-0000-0000-0000-000000000000" + ] + } +}"#; // This must be the last group to init to include the UUID of the other high priv groups. -pub static _UUID_IDM_HIGH_PRIVILEGE: &'static str = "00000000-0000-0000-0000-000000001000"; pub static JSON_IDM_HIGH_PRIVILEGE_V1: &'static str = r#"{ "attrs": { "class": ["group", "object"], @@ -297,19 +432,28 @@ pub static JSON_IDM_HIGH_PRIVILEGE_V1: &'static str = r#"{ "00000000-0000-0000-0000-000000000016", "00000000-0000-0000-0000-000000000017", "00000000-0000-0000-0000-000000000019", + "00000000-0000-0000-0000-000000000020", "00000000-0000-0000-0000-000000001000" ] } }"#; -pub static _UUID_SYSTEM_INFO: &'static str = "00000000-0000-0000-0000-ffffff000001"; pub static JSON_SYSTEM_INFO_V1: &'static str = r#"{ "attrs": { - "class": ["object", "system_info"], + "class": ["object", "system_info", "system"], "uuid": ["00000000-0000-0000-0000-ffffff000001"], "description": ["System info and metadata object."], - "version": ["1"], - "domain": ["example.com"] + "version": ["2"] + } +}"#; + +pub static JSON_DOMAIN_INFO_V1: &'static str = r#"{ + "attrs": { + "class": ["object", "domain_info", "system"], + "name": ["domain_local"], + "uuid": ["00000000-0000-0000-0000-ffffff000025"], + "description": ["This local domain's info and metadata object."], + "domain_name": ["example.com"] } }"#; @@ -358,8 +502,6 @@ pub static JSON_IDM_ACP_XX_V1: &'static str = r#"{ }"#; */ -pub static _UUID_IDM_ADMINS_ACP_RECYCLE_SEARCH_V1: &'static str = - "00000000-0000-0000-0000-ffffff000002"; pub static JSON_IDM_ADMINS_ACP_RECYCLE_SEARCH_V1: &'static str = r#"{ "attrs": { "class": ["object", "access_control_profile", "access_control_search"], @@ -377,7 +519,6 @@ pub static JSON_IDM_ADMINS_ACP_RECYCLE_SEARCH_V1: &'static str = r#"{ } }"#; -pub static _UUID_IDM_ADMINS_ACP_REVIVE_V1: &'static str = "00000000-0000-0000-0000-ffffff000003"; pub static JSON_IDM_ADMINS_ACP_REVIVE_V1: &'static str = r#"{ "attrs": { "class": ["object", "access_control_profile", "access_control_modify"], @@ -396,7 +537,6 @@ pub static JSON_IDM_ADMINS_ACP_REVIVE_V1: &'static str = r#"{ } }"#; -pub static _UUID_IDM_SELF_ACP_READ_V1: &'static str = "00000000-0000-0000-0000-ffffff000004"; pub static JSON_IDM_SELF_ACP_READ_V1: &'static str = r#"{ "attrs": { "class": ["object", "access_control_profile", "access_control_search"], @@ -412,6 +552,7 @@ pub static JSON_IDM_SELF_ACP_READ_V1: &'static str = r#"{ ], "acp_search_attr": [ "name", + "spn", "displayname", "legalname", "class", @@ -423,7 +564,6 @@ pub static JSON_IDM_SELF_ACP_READ_V1: &'static str = r#"{ } }"#; -pub static _UUID_IDM_SELF_ACP_WRITE_V1: &'static str = "00000000-0000-0000-0000-ffffff000021"; pub static JSON_IDM_SELF_ACP_WRITE_V1: &'static str = r#"{ "attrs": { "class": ["object", "access_control_profile", "access_control_modify"], @@ -446,39 +586,6 @@ pub static JSON_IDM_SELF_ACP_WRITE_V1: &'static str = r#"{ } }"#; -/* -pub static _UUID_IDM_ADMINS_ACP_MANAGE_V1: &'static str = "00000000-0000-0000-0000-ffffff000005"; -pub static JSON_IDM_ADMINS_ACP_MANAGE_V1: &'static str = r#"{ - "attrs": { - "class": [ - "object", - "access_control_profile", - "access_control_modify", - "access_control_create", - "access_control_delete", - "access_control_search" - ], - "name": ["idm_admins_acp_manage"], - "uuid": ["00000000-0000-0000-0000-ffffff000005"], - "description": ["Builtin IDM Administrators Access Controls to manage the install."], - "acp_enable": ["true"], - "acp_receiver": [ - "{\"Eq\":[\"memberof\",\"00000000-0000-0000-0000-000000000001\"]}" - ], - "acp_targetscope": [ - "{\"And\": [{\"Pres\": \"class\"}, {\"AndNot\": {\"Or\": [{\"Eq\": [\"class\", \"tombstone\"]}, {\"Eq\": [\"class\", \"recycled\"]}]}}]}" - ], - "acp_search_attr": ["name", "class", "uuid", "classname", "attributename", "memberof"], - "acp_modify_class": ["person"], - "acp_modify_removedattr": ["class", "displayname", "name", "description"], - "acp_modify_presentattr": ["class", "displayname", "name", "description"], - "acp_create_class": ["object", "person", "account"], - "acp_create_attr": ["name", "class", "description", "displayname"] - } -}"#; -*/ - -pub static _UUID_IDM_ALL_ACP_READ_V1: &'static str = "00000000-0000-0000-0000-ffffff000006"; pub static JSON_IDM_ALL_ACP_READ_V1: &'static str = r#"{ "state": null, "attrs": { @@ -495,6 +602,7 @@ pub static JSON_IDM_ALL_ACP_READ_V1: &'static str = r#"{ ], "acp_search_attr": [ "name", + "spn", "displayname", "class", "memberof", @@ -506,7 +614,6 @@ pub static JSON_IDM_ALL_ACP_READ_V1: &'static str = r#"{ }"#; // 7 people read acp JSON_IDM_PEOPLE_READ_PRIV_V1 -pub static _UUID_IDM_ACP_PEOPLE_READ_PRIV_V1: &'static str = "00000000-0000-0000-0000-ffffff000007"; pub static JSON_IDM_ACP_PEOPLE_READ_PRIV_V1: &'static str = r#"{ "attrs": { "class": [ @@ -530,8 +637,6 @@ pub static JSON_IDM_ACP_PEOPLE_READ_PRIV_V1: &'static str = r#"{ } }"#; // 8 people write acp JSON_IDM_PEOPLE_WRITE_PRIV_V1 -pub static _UUID_IDM_ACP_PEOPLE_WRITE_PRIV_V1: &'static str = - "00000000-0000-0000-0000-ffffff000008"; pub static JSON_IDM_ACP_PEOPLE_WRITE_PRIV_V1: &'static str = r#"{ "attrs": { "class": [ @@ -558,7 +663,6 @@ pub static JSON_IDM_ACP_PEOPLE_WRITE_PRIV_V1: &'static str = r#"{ } }"#; // 9 group write acp JSON_IDM_GROUP_WRITE_PRIV_V1 -pub static _UUID_IDM_ACP_GROUP_WRITE_PRIV_V1: &'static str = "00000000-0000-0000-0000-ffffff000009"; pub static JSON_IDM_ACP_GROUP_WRITE_PRIV_V1: &'static str = r#"{ "attrs": { "class": [ @@ -578,7 +682,7 @@ pub static JSON_IDM_ACP_GROUP_WRITE_PRIV_V1: &'static str = r#"{ "{\"And\": [{\"Eq\": [\"class\",\"group\"]}, {\"AndNot\": {\"Or\": [{\"Eq\": [\"memberof\",\"00000000-0000-0000-0000-000000001000\"]}, {\"Eq\": [\"class\", \"tombstone\"]}, {\"Eq\": [\"class\", \"recycled\"]}]}}]}" ], "acp_search_attr": [ - "class", "name", "uuid", "description", "member" + "class", "name", "spn", "uuid", "description", "member" ], "acp_modify_removedattr": [ "name", "description", "member" @@ -589,8 +693,6 @@ pub static JSON_IDM_ACP_GROUP_WRITE_PRIV_V1: &'static str = r#"{ } }"#; // 10 account read acp JSON_IDM_ACCOUNT_READ_PRIV_V1 -pub static _UUID_IDM_ACP_ACCOUNT_READ_PRIV_V1: &'static str = - "00000000-0000-0000-0000-ffffff000010"; pub static JSON_IDM_ACP_ACCOUNT_READ_PRIV_V1: &'static str = r#"{ "attrs": { "class": [ @@ -609,13 +711,11 @@ pub static JSON_IDM_ACP_ACCOUNT_READ_PRIV_V1: &'static str = r#"{ "{\"And\": [{\"Eq\": [\"class\",\"account\"]}, {\"AndNot\": {\"Or\": [{\"Eq\": [\"memberof\",\"00000000-0000-0000-0000-000000001000\"]}, {\"Eq\": [\"class\", \"tombstone\"]}, {\"Eq\": [\"class\", \"recycled\"]}]}}]}" ], "acp_search_attr": [ - "class", "name", "uuid", "displayname", "ssh_publickey", "primary_credential", "memberof", "mail" + "class", "name", "spn", "uuid", "displayname", "ssh_publickey", "primary_credential", "memberof", "mail" ] } }"#; // 11 account write acp JSON_IDM_ACCOUNT_WRITE_PRIV_V1 -pub static _UUID_IDM_ACP_ACCOUNT_WRITE_PRIV_V1: &'static str = - "00000000-0000-0000-0000-ffffff000011"; pub static JSON_IDM_ACP_ACCOUNT_WRITE_PRIV_V1: &'static str = r#"{ "attrs": { "class": [ @@ -642,8 +742,6 @@ pub static JSON_IDM_ACP_ACCOUNT_WRITE_PRIV_V1: &'static str = r#"{ } }"#; // 12 service account create acp (only admins?) JSON_IDM_SERVICE_ACCOUNT_CREATE_PRIV_V1 -pub static _UUID_IDM_ACP_ACCOUNT_MANAGE_PRIV_V1: &'static str = - "00000000-0000-0000-0000-ffffff000012"; pub static JSON_IDM_ACP_ACCOUNT_MANAGE_PRIV_V1: &'static str = r#"{ "attrs": { "class": [ @@ -676,8 +774,6 @@ pub static JSON_IDM_ACP_ACCOUNT_MANAGE_PRIV_V1: &'static str = r#"{ } }"#; // 13 user (person) account create acp JSON_IDM_PERSON_ACCOUNT_CREATE_PRIV_V1 -pub static _UUID_IDM_ACP_PEOPLE_MANAGE_PRIV_V1: &'static str = - "00000000-0000-0000-0000-ffffff000013"; pub static JSON_IDM_ACP_PEOPLE_MANAGE_PRIV_V1: &'static str = r#"{ "attrs": { "class": [ @@ -712,7 +808,6 @@ pub static JSON_IDM_ACP_PEOPLE_MANAGE_PRIV_V1: &'static str = r#"{ }"#; // 14 radius read acp JSON_IDM_RADIUS_SERVERS_V1 -pub static _UUID_IDM_ACP_RADIUS_SERVERS_V1: &'static str = "00000000-0000-0000-0000-ffffff000014"; // The targetscope of this could change later to a "radius access" group or similar so we can add/remove // users from having radius access easier. pub static JSON_IDM_ACP_RADIUS_SERVERS_V1: &'static str = r#"{ @@ -733,13 +828,11 @@ pub static JSON_IDM_ACP_RADIUS_SERVERS_V1: &'static str = r#"{ "{\"And\": [{\"Pres\": \"class\"}, {\"AndNot\": {\"Or\": [{\"Eq\": [\"class\", \"tombstone\"]}, {\"Eq\": [\"class\", \"recycled\"]}]}}]}" ], "acp_search_attr": [ - "name", "uuid", "radius_secret" + "name", "spn", "uuid", "radius_secret" ] } }"#; // 15 high priv account read JSON_IDM_HP_ACCOUNT_READ_PRIV_V1 -pub static _UUID_IDM_ACP_HP_ACCOUNT_READ_PRIV_V1: &'static str = - "00000000-0000-0000-0000-ffffff000015"; pub static JSON_IDM_ACP_HP_ACCOUNT_READ_PRIV_V1: &'static str = r#"{ "attrs": { "class": [ @@ -758,13 +851,11 @@ pub static JSON_IDM_ACP_HP_ACCOUNT_READ_PRIV_V1: &'static str = r#"{ "{\"And\": [{\"Eq\": [\"class\",\"account\"]}, {\"Eq\": [\"memberof\",\"00000000-0000-0000-0000-000000001000\"]}, {\"AndNot\": {\"Or\": [{\"Eq\": [\"class\", \"tombstone\"]}, {\"Eq\": [\"class\", \"recycled\"]}]}}]}" ], "acp_search_attr": [ - "class", "name", "uuid", "displayname", "ssh_publickey", "primary_credential", "memberof" + "class", "name", "spn", "uuid", "displayname", "ssh_publickey", "primary_credential", "memberof" ] } }"#; // 16 high priv account write JSON_IDM_HP_ACCOUNT_WRITE_PRIV_V1 -pub static _UUID_IDM_ACP_HP_ACCOUNT_WRITE_PRIV_V1: &'static str = - "00000000-0000-0000-0000-ffffff000016"; pub static JSON_IDM_ACP_HP_ACCOUNT_WRITE_PRIV_V1: &'static str = r#"{ "attrs": { "class": [ @@ -792,8 +883,6 @@ pub static JSON_IDM_ACP_HP_ACCOUNT_WRITE_PRIV_V1: &'static str = r#"{ }"#; // 17 high priv group write --> JSON_IDM_HP_GROUP_WRITE_PRIV_V1 (12) -pub static _UUID_IDM_ACP_HP_GROUP_WRITE_PRIV_V1: &'static str = - "00000000-0000-0000-0000-ffffff000017"; pub static JSON_IDM_ACP_HP_GROUP_WRITE_PRIV_V1: &'static str = r#"{ "attrs": { "class": [ @@ -825,8 +914,6 @@ pub static JSON_IDM_ACP_HP_GROUP_WRITE_PRIV_V1: &'static str = r#"{ }"#; // 18 schema write JSON_IDM_SCHEMA_WRITE_PRIV_V1 -pub static _UUID_IDM_ACP_SCHEMA_WRITE_ATTRS_PRIV_V1: &'static str = - "00000000-0000-0000-0000-ffffff000018"; pub static JSON_IDM_ACP_SCHEMA_WRITE_ATTRS_PRIV_V1: &'static str = r#"{ "attrs": { "class": [ @@ -888,7 +975,6 @@ pub static JSON_IDM_ACP_SCHEMA_WRITE_ATTRS_PRIV_V1: &'static str = r#"{ }"#; // 19 acp read/write -pub static _UUID_IDM_ACP_ACP_MANAGE_PRIV_V1: &'static str = "00000000-0000-0000-0000-ffffff000019"; pub static JSON_IDM_ACP_ACP_MANAGE_PRIV_V1: &'static str = r#"{ "attrs": { "class": [ @@ -982,8 +1068,6 @@ pub static JSON_IDM_ACP_ACP_MANAGE_PRIV_V1: &'static str = r#"{ } }"#; -pub static _UUID_IDM_ACP_SCHEMA_WRITE_CLASSES_PRIV_V1: &'static str = - "00000000-0000-0000-0000-ffffff000020"; pub static JSON_IDM_ACP_SCHEMA_WRITE_CLASSES_PRIV_V1: &'static str = r#"{ "attrs": { "class": [ @@ -1043,8 +1127,6 @@ pub static JSON_IDM_ACP_SCHEMA_WRITE_CLASSES_PRIV_V1: &'static str = r#"{ // 21 - anonymous / everyone schema read. // 22 - group create right -pub static _UUID_IDM_ACP_GROUP_MANAGE_PRIV_V1: &'static str = - "00000000-0000-0000-0000-ffffff000022"; pub static JSON_IDM_ACP_GROUP_MANAGE_PRIV_V1: &'static str = r#"{ "attrs": { "class": [ @@ -1076,8 +1158,6 @@ pub static JSON_IDM_ACP_GROUP_MANAGE_PRIV_V1: &'static str = r#"{ }"#; // 23 - HP account manage -pub static _UUID_IDM_ACP_HP_ACCOUNT_MANAGE_PRIV_V1: &'static str = - "00000000-0000-0000-0000-ffffff000023"; pub static JSON_IDM_ACP_HP_ACCOUNT_MANAGE_PRIV_V1: &'static str = r#"{ "attrs": { "class": [ @@ -1111,8 +1191,6 @@ pub static JSON_IDM_ACP_HP_ACCOUNT_MANAGE_PRIV_V1: &'static str = r#"{ }"#; // 24 - hp group manage -pub static _UUID_IDM_ACP_HP_GROUP_MANAGE_PRIV_V1: &'static str = - "00000000-0000-0000-0000-ffffff000024"; pub static JSON_IDM_ACP_HP_GROUP_MANAGE_PRIV_V1: &'static str = r#"{ "attrs": { "class": [ @@ -1143,6 +1221,41 @@ pub static JSON_IDM_ACP_HP_GROUP_MANAGE_PRIV_V1: &'static str = r#"{ } }"#; +// 25 - domain admins acp +pub static JSON_IDM_ACP_DOMAIN_ADMIN_PRIV_V1: &'static str = r#"{ + "attrs": { + "class": [ + "object", + "access_control_profile", + "access_control_search", + "access_control_modify" + ], + "name": ["idm_acp_domain_admin_priv"], + "uuid": ["00000000-0000-0000-0000-ffffff000026"], + "description": ["Builtin IDM Control for granting domain info administration locally"], + "acp_enable": ["true"], + "acp_receiver": [ + "{\"Eq\":[\"memberof\",\"00000000-0000-0000-0000-000000000020\"]}" + ], + "acp_targetscope": [ + "{\"And\": [{\"Eq\": [\"uuid\",\"00000000-0000-0000-0000-ffffff000025\"]}, {\"AndNot\": {\"Or\": [{\"Eq\": [\"class\", \"tombstone\"]}, {\"Eq\": [\"class\", \"recycled\"]}]}}]}" + ], + "acp_search_attr": [ + "name", + "uuid", + "domain_name", + "domain_ssid", + "domain_uuid" + ], + "acp_modify_removedattr": [ + "domain_ssid" + ], + "acp_modify_presentattr": [ + "domain_ssid" + ] + } +}"#; + // Anonymous should be the last opbject in the range here. pub static JSON_ANONYMOUS_V1: &'static str = r#"{ "attrs": { @@ -1156,62 +1269,8 @@ pub static JSON_ANONYMOUS_V1: &'static str = r#"{ // Core // Schema uuids start at 00000000-0000-0000-0000-ffff00000000 -pub static UUID_SCHEMA_ATTR_CLASS: &'static str = "00000000-0000-0000-0000-ffff00000000"; -pub static UUID_SCHEMA_ATTR_UUID: &'static str = "00000000-0000-0000-0000-ffff00000001"; -pub static UUID_SCHEMA_ATTR_NAME: &'static str = "00000000-0000-0000-0000-ffff00000002"; -pub static UUID_SCHEMA_ATTR_ATTRIBUTENAME: &'static str = "00000000-0000-0000-0000-ffff00000048"; -pub static UUID_SCHEMA_ATTR_CLASSNAME: &'static str = "00000000-0000-0000-0000-ffff00000049"; -pub static UUID_SCHEMA_ATTR_PRINCIPAL_NAME: &'static str = "00000000-0000-0000-0000-ffff00000003"; -pub static UUID_SCHEMA_ATTR_DESCRIPTION: &'static str = "00000000-0000-0000-0000-ffff00000004"; -pub static UUID_SCHEMA_ATTR_MULTIVALUE: &'static str = "00000000-0000-0000-0000-ffff00000005"; -pub static UUID_SCHEMA_ATTR_UNIQUE: &'static str = "00000000-0000-0000-0000-ffff00000047"; -pub static UUID_SCHEMA_ATTR_INDEX: &'static str = "00000000-0000-0000-0000-ffff00000006"; -pub static UUID_SCHEMA_ATTR_SYNTAX: &'static str = "00000000-0000-0000-0000-ffff00000007"; -pub static UUID_SCHEMA_ATTR_SYSTEMMAY: &'static str = "00000000-0000-0000-0000-ffff00000008"; -pub static UUID_SCHEMA_ATTR_MAY: &'static str = "00000000-0000-0000-0000-ffff00000009"; -pub static UUID_SCHEMA_ATTR_SYSTEMMUST: &'static str = "00000000-0000-0000-0000-ffff00000010"; -pub static UUID_SCHEMA_ATTR_MUST: &'static str = "00000000-0000-0000-0000-ffff00000011"; -pub static UUID_SCHEMA_ATTR_MEMBEROF: &'static str = "00000000-0000-0000-0000-ffff00000012"; -pub static UUID_SCHEMA_ATTR_MEMBER: &'static str = "00000000-0000-0000-0000-ffff00000013"; -pub static UUID_SCHEMA_ATTR_DIRECTMEMBEROF: &'static str = "00000000-0000-0000-0000-ffff00000014"; -pub static UUID_SCHEMA_ATTR_VERSION: &'static str = "00000000-0000-0000-0000-ffff00000015"; -pub static UUID_SCHEMA_ATTR_DOMAIN: &'static str = "00000000-0000-0000-0000-ffff00000016"; -pub static UUID_SCHEMA_ATTR_ACP_ENABLE: &'static str = "00000000-0000-0000-0000-ffff00000017"; -pub static UUID_SCHEMA_ATTR_ACP_RECEIVER: &'static str = "00000000-0000-0000-0000-ffff00000018"; -pub static UUID_SCHEMA_ATTR_ACP_TARGETSCOPE: &'static str = "00000000-0000-0000-0000-ffff00000019"; -pub static UUID_SCHEMA_ATTR_ACP_SEARCH_ATTR: &'static str = "00000000-0000-0000-0000-ffff00000020"; -pub static UUID_SCHEMA_ATTR_ACP_CREATE_CLASS: &'static str = "00000000-0000-0000-0000-ffff00000021"; -pub static UUID_SCHEMA_ATTR_ACP_CREATE_ATTR: &'static str = "00000000-0000-0000-0000-ffff00000022"; -pub static UUID_SCHEMA_ATTR_ACP_MODIFY_REMOVEDATTR: &'static str = - "00000000-0000-0000-0000-ffff00000023"; -pub static UUID_SCHEMA_ATTR_ACP_MODIFY_PRESENTATTR: &'static str = - "00000000-0000-0000-0000-ffff00000024"; -pub static UUID_SCHEMA_ATTR_ACP_MODIFY_CLASS: &'static str = "00000000-0000-0000-0000-ffff00000025"; - -pub static UUID_SCHEMA_CLASS_ATTRIBUTETYPE: &'static str = "00000000-0000-0000-0000-ffff00000026"; -pub static UUID_SCHEMA_CLASS_CLASSTYPE: &'static str = "00000000-0000-0000-0000-ffff00000027"; -pub static UUID_SCHEMA_CLASS_OBJECT: &'static str = "00000000-0000-0000-0000-ffff00000028"; -pub static UUID_SCHEMA_CLASS_EXTENSIBLEOBJECT: &'static str = - "00000000-0000-0000-0000-ffff00000029"; -pub static UUID_SCHEMA_CLASS_MEMBEROF: &'static str = "00000000-0000-0000-0000-ffff00000030"; - -pub static UUID_SCHEMA_CLASS_RECYCLED: &'static str = "00000000-0000-0000-0000-ffff00000031"; -pub static UUID_SCHEMA_CLASS_TOMBSTONE: &'static str = "00000000-0000-0000-0000-ffff00000032"; -pub static UUID_SCHEMA_CLASS_SYSTEM_INFO: &'static str = "00000000-0000-0000-0000-ffff00000033"; -pub static UUID_SCHEMA_CLASS_ACCESS_CONTROL_PROFILE: &'static str = - "00000000-0000-0000-0000-ffff00000034"; -pub static UUID_SCHEMA_CLASS_ACCESS_CONTROL_SEARCH: &'static str = - "00000000-0000-0000-0000-ffff00000035"; -pub static UUID_SCHEMA_CLASS_ACCESS_CONTROL_DELETE: &'static str = - "00000000-0000-0000-0000-ffff00000036"; -pub static UUID_SCHEMA_CLASS_ACCESS_CONTROL_MODIFY: &'static str = - "00000000-0000-0000-0000-ffff00000037"; -pub static UUID_SCHEMA_CLASS_ACCESS_CONTROL_CREATE: &'static str = - "00000000-0000-0000-0000-ffff00000038"; -pub static UUID_SCHEMA_CLASS_SYSTEM: &'static str = "00000000-0000-0000-0000-ffff00000039"; // system supplementary -pub static UUID_SCHEMA_ATTR_DISPLAYNAME: &'static str = "00000000-0000-0000-0000-ffff00000040"; pub static JSON_SCHEMA_ATTR_DISPLAYNAME: &'static str = r#"{ "valid": { "uuid": "00000000-0000-0000-0000-ffff00000040" @@ -1246,7 +1305,6 @@ pub static JSON_SCHEMA_ATTR_DISPLAYNAME: &'static str = r#"{ ] } }"#; -pub static UUID_SCHEMA_ATTR_MAIL: &'static str = "00000000-0000-0000-0000-ffff00000041"; pub static JSON_SCHEMA_ATTR_MAIL: &'static str = r#" { "valid": { @@ -1283,7 +1341,6 @@ pub static JSON_SCHEMA_ATTR_MAIL: &'static str = r#" } } "#; -pub static UUID_SCHEMA_ATTR_SSH_PUBLICKEY: &'static str = "00000000-0000-0000-0000-ffff00000042"; pub static JSON_SCHEMA_ATTR_SSH_PUBLICKEY: &'static str = r#" { "valid": { @@ -1318,8 +1375,6 @@ pub static JSON_SCHEMA_ATTR_SSH_PUBLICKEY: &'static str = r#" } } "#; -pub static UUID_SCHEMA_ATTR_PRIMARY_CREDENTIAL: &'static str = - "00000000-0000-0000-0000-ffff00000043"; pub static JSON_SCHEMA_ATTR_PRIMARY_CREDENTIAL: &'static str = r#" { "valid": { @@ -1354,7 +1409,6 @@ pub static JSON_SCHEMA_ATTR_PRIMARY_CREDENTIAL: &'static str = r#" } } "#; -pub static UUID_SCHEMA_ATTR_LEGALNAME: &'static str = "00000000-0000-0000-0000-ffff00000050"; pub static JSON_SCHEMA_ATTR_LEGALNAME: &'static str = r#"{ "attrs": { "class": [ @@ -1385,7 +1439,6 @@ pub static JSON_SCHEMA_ATTR_LEGALNAME: &'static str = r#"{ ] } }"#; -pub static UUID_SCHEMA_ATTR_RADIUS_SECRET: &'static str = "00000000-0000-0000-0000-ffff00000051"; pub static JSON_SCHEMA_ATTR_RADIUS_SECRET: &'static str = r#"{ "attrs": { "class": [ @@ -1415,7 +1468,95 @@ pub static JSON_SCHEMA_ATTR_RADIUS_SECRET: &'static str = r#"{ } }"#; -pub static UUID_SCHEMA_CLASS_PERSON: &'static str = "00000000-0000-0000-0000-ffff00000044"; +pub static JSON_SCHEMA_ATTR_DOMAIN_NAME: &'static str = r#"{ + "attrs": { + "class": [ + "object", + "system", + "attributetype" + ], + "description": [ + "The domain's DNS name for webauthn and SPN generation purposes." + ], + "index": [ + "EQUALITY" + ], + "unique": [ + "true" + ], + "multivalue": [ + "false" + ], + "attributename": [ + "domain_name" + ], + "syntax": [ + "UTF8STRING_INSENSITIVE" + ], + "uuid": [ + "00000000-0000-0000-0000-ffff00000053" + ] + } +}"#; +pub static JSON_SCHEMA_ATTR_DOMAIN_UUID: &'static str = r#"{ + "attrs": { + "class": [ + "object", + "system", + "attributetype" + ], + "description": [ + "The domain's uuid, used in CSN and trust relationships." + ], + "index": [ + "EQUALITY" + ], + "unique": [ + "true" + ], + "multivalue": [ + "false" + ], + "attributename": [ + "domain_uuid" + ], + "syntax": [ + "UUID" + ], + "uuid": [ + "00000000-0000-0000-0000-ffff00000054" + ] + } +}"#; +pub static JSON_SCHEMA_ATTR_DOMAIN_SSID: &'static str = r#"{ + "attrs": { + "class": [ + "object", + "system", + "attributetype" + ], + "description": [ + "The domains site-wide SSID for device autoconfiguration of wireless" + ], + "index": [], + "unique": [ + "true" + ], + "multivalue": [ + "false" + ], + "attributename": [ + "domain_ssid" + ], + "syntax": [ + "UTF8STRING" + ], + "uuid": [ + "00000000-0000-0000-0000-ffff00000055" + ] + } +}"#; + pub static JSON_SCHEMA_CLASS_PERSON: &'static str = r#" { "valid": { @@ -1449,7 +1590,6 @@ pub static JSON_SCHEMA_CLASS_PERSON: &'static str = r#" } "#; -pub static UUID_SCHEMA_CLASS_GROUP: &'static str = "00000000-0000-0000-0000-ffff00000045"; pub static JSON_SCHEMA_CLASS_GROUP: &'static str = r#" { "valid": { @@ -1472,7 +1612,8 @@ pub static JSON_SCHEMA_CLASS_GROUP: &'static str = r#" "member" ], "systemmust": [ - "name" + "name", + "spn" ], "uuid": [ "00000000-0000-0000-0000-ffff00000045" @@ -1480,7 +1621,6 @@ pub static JSON_SCHEMA_CLASS_GROUP: &'static str = r#" } } "#; -pub static UUID_SCHEMA_CLASS_ACCOUNT: &'static str = "00000000-0000-0000-0000-ffff00000046"; pub static JSON_SCHEMA_CLASS_ACCOUNT: &'static str = r#" { "attrs": { @@ -1502,7 +1642,8 @@ pub static JSON_SCHEMA_CLASS_ACCOUNT: &'static str = r#" ], "systemmust": [ "displayname", - "name" + "name", + "spn" ], "uuid": [ "00000000-0000-0000-0000-ffff00000046" @@ -1511,6 +1652,43 @@ pub static JSON_SCHEMA_CLASS_ACCOUNT: &'static str = r#" } "#; +// domain_info type +// domain_uuid +// domain_name <- should be the dns name? +// domain_ssid <- for radius +// +pub static JSON_SCHEMA_CLASS_DOMAIN_INFO: &'static str = r#" + { + "attrs": { + "class": [ + "object", + "system", + "classtype" + ], + "description": [ + "Local domain information and partial configuration." + ], + "classname": [ + "domain_info" + ], + "systemmay": [ + "domain_ssid" + ], + "systemmust": [ + "name", + "domain_uuid", + "domain_name" + ], + "uuid": [ + "00000000-0000-0000-0000-ffff00000052" + ] + } + } +"#; + +// need a domain_trust_info as well. +// TODO + // ============ TEST DATA ============ #[cfg(test)] pub static JSON_TESTPERSON1: &'static str = r#"{ diff --git a/kanidmd/src/lib/core.rs b/kanidmd/src/lib/core.rs index bf18b9581..40569fd5c 100644 --- a/kanidmd/src/lib/core.rs +++ b/kanidmd/src/lib/core.rs @@ -975,6 +975,42 @@ fn group_id_delete( json_rest_event_delete_id(path, req, state, filter) } +fn domain_get( + (req, state): (HttpRequest, State), +) -> impl Future { + let filter = filter_all!(f_eq("class", PartialValue::new_class("domain_info"))); + json_rest_event_get(req, state, filter, None) +} + +fn domain_id_get( + (path, req, state): (Path, HttpRequest, State), +) -> impl Future { + let filter = filter_all!(f_eq("class", PartialValue::new_class("domain_info"))); + json_rest_event_get_id(path, req, state, filter, None) +} + +fn domain_id_get_attr( + (path, req, state): ( + Path<(String, String)>, + HttpRequest, + State, + ), +) -> impl Future { + let filter = filter_all!(f_eq("class", PartialValue::new_class("domain_info"))); + json_rest_event_get_id_attr(path, req, state, filter) +} + +fn domain_id_put_attr( + (path, req, state): ( + Path<(String, String)>, + HttpRequest, + State, + ), +) -> impl Future { + let filter = filter_all!(f_eq("class", PartialValue::new_class("domain_info"))); + json_rest_event_put_id_attr(path, req, state, filter) +} + fn do_nothing((_req, _state): (HttpRequest, State)) -> String { "did nothing".to_string() } @@ -1289,6 +1325,42 @@ pub fn reindex_server_core(config: Configuration) { }; } +pub fn domain_rename_core(config: Configuration, new_domain_name: String) { + let mut audit = AuditScope::new("domain_rename"); + + // Start the backend. + let be = match setup_backend(&config) { + Ok(be) => be, + Err(e) => { + error!("Failed to setup BE: {:?}", e); + return; + } + }; + let server_id = be.get_db_sid(); + // setup the qs - *with* init of the migrations and schema. + let (qs, _idms) = match setup_qs_idms(&mut audit, be, server_id) { + Ok(t) => t, + Err(e) => { + debug!("{}", audit); + error!("Unable to setup query server or idm server -> {:?}", e); + return; + } + }; + + let mut qs_write = qs.write(); + let r = qs_write + .domain_rename(&mut audit, new_domain_name.as_str()) + .and_then(|_| qs_write.commit(&mut audit)); + + match r { + Ok(_) => info!("Domain Rename Success!"), + Err(e) => { + error!("Domain Rename Failed - Rollback has occured: {:?}", e); + std::process::exit(1); + } + }; +} + pub fn reset_sid_core(config: Configuration) { let mut audit = AuditScope::new("reset_sid_core"); // Setup the be @@ -1484,7 +1556,7 @@ pub fn create_server_core(config: Configuration) { // Copy the max size let max_size = config.maximum_request; let secure_cookies = config.secure_cookies; - // let domain = config.domain.clone(); + // domain will come from the qs now! let cookie_key: [u8; 32] = config.cookie_key.clone(); // start the web server @@ -1700,6 +1772,17 @@ pub fn create_server_core(config: Configuration) { }) // Claims // TBD + // Domain + .resource("/v1/domain", |r| { + r.method(http::Method::GET).with_async(domain_get); + }) + .resource("/v1/domain/{id}", |r| { + r.method(http::Method::GET).with_async(domain_id_get); + }) + .resource("/v1/domain/{id}/_attr/{attr}", |r| { + r.method(http::Method::GET).with_async(domain_id_get_attr); + r.method(http::Method::PUT).with_async(domain_id_put_attr); + }) // Recycle Bin .resource("/v1/recycle_bin", |r| { r.method(http::Method::GET).with(do_nothing) diff --git a/kanidmd/src/lib/entry.rs b/kanidmd/src/lib/entry.rs index 841a0be6c..bd63ba757 100644 --- a/kanidmd/src/lib/entry.rs +++ b/kanidmd/src/lib/entry.rs @@ -259,7 +259,7 @@ impl Entry { .map(|(k, vs)| { let attr = k.to_lowercase(); let vv: BTreeSet = match attr.as_str() { - "name" | "attributename" | "classname" | "version" | "domain" => { + "name" | "attributename" | "classname" | "version" | "domain" | "domain_name" => { vs.into_iter().map(|v| Value::new_iutf8(v)).collect() } "userid" | "uidnumber" => { @@ -274,7 +274,7 @@ impl Entry { => { vs.into_iter().map(|v| Value::new_attr(v.as_str())).collect() } - "uuid" => { + "uuid" | "domain_uuid" => { vs.into_iter().map(|v| Value::new_uuids(v.as_str()) .unwrap_or_else(|| { warn!("WARNING: Allowing syntax incorrect attribute to be presented UTF8 string"); @@ -320,6 +320,15 @@ impl Entry { "displayname" | "description" => { vs.into_iter().map(|v| Value::new_utf8(v)).collect() } + "spn" => { + vs.into_iter().map(|v| { + Value::new_spn_parse(v.as_str()) + .unwrap_or_else(|| { + warn!("WARNING: Allowing syntax incorrect SPN attribute to be presented UTF8 string"); + Value::new_utf8(v) + }) + }).collect() + } ia => { warn!("WARNING: Allowing invalid attribute {} to be interpretted as UTF8 string. YOU MAY ENCOUNTER ODD BEHAVIOUR!!!", ia); vs.into_iter().map(|v| Value::new_utf8(v)).collect() @@ -1036,90 +1045,6 @@ impl Entry { pub fn get_uuid(&self) -> &Uuid { &self.valid.uuid } - - pub fn filter_from_attrs(&self, attrs: &Vec) -> Option> { - // Because we are a valid entry, a filter we create still may not - // be valid because the internal server entry templates are still - // created by humans! Plus double checking something already valid - // is not bad ... - // - // Generate a filter from the attributes requested and defined. - // Basically, this is a series of nested and's (which will be - // optimised down later: but if someone wants to solve flatten() ...) - - // Take name: (a, b), name: (c, d) -> (name, a), (name, b), (name, c), (name, d) - - let mut pairs: Vec<(&str, &Value)> = Vec::new(); - - for attr in attrs { - match self.attrs.get(attr) { - Some(values) => { - for v in values { - pairs.push((attr, v)) - } - } - None => return None, - } - } - - Some(filter_all!(f_and( - pairs - .into_iter() - .map(|(attr, value)| { - // We use FC directly here instead of f_eq to avoid an excess clone. - FC::Eq(attr, value.to_partialvalue()) - }) - .collect() - ))) - } - - pub fn gen_modlist_assert( - &self, - schema: &dyn SchemaTransaction, - ) -> Result, SchemaError> { - // Create a modlist from this entry. We make this assuming we want the entry - // to have this one as a subset of values. This means if we have single - // values, we'll replace, if they are multivalue, we present them. - let mut mods = ModifyList::new(); - - for (k, vs) in self.attrs.iter() { - // WHY?! We skip uuid here because it is INVALID for a UUID - // to be in a modlist, and the base.rs plugin will fail if it - // is there. This actually doesn't matter, because to apply the - // modlist in these situations we already know the entry MUST - // exist with that UUID, we only need to conform it's other - // attributes into the same state. - // - // In the future, if we make uuid a real entry type, then this - // check can "go away" because uuid will never exist as an ava. - // - // NOTE: Remove this check when uuid becomes a real attribute. - // UUID is now a real attribute, but it also has an ava for db_entry - // conversion - so what do? If we remove it here, we could have CSN issue with - // repl on uuid conflict, but it probably shouldn't be an ava either ... - // as a result, I think we need to keep this continue line to not cause issues. - if k == "uuid" { - continue; - } - // Get the schema attribute type out. - match schema.is_multivalue(k) { - Ok(r) => { - if !r { - // As this is single value, purge then present to maintain this - // invariant - mods.push_mod(Modify::Purged(k.clone())); - } - } - // A schema error happened, fail the whole operation. - Err(e) => return Err(e), - } - for v in vs { - mods.push_mod(Modify::Present(k.clone(), v.clone())); - } - } - - Ok(mods) - } } impl Entry { @@ -1352,6 +1277,11 @@ impl Entry { .and_then(|f: &ProtoFilter| Some((*f).clone())) } + pub(crate) fn generate_spn(&self, domain_name: &str) -> Option { + self.get_ava_single_str("name") + .and_then(|name| Some(Value::new_spn_str(name, domain_name))) + } + pub fn attribute_pres(&self, attr: &str) -> bool { // Note, we don't normalise attr name, but I think that's not // something we should over-optimise on. @@ -1442,6 +1372,90 @@ impl Entry { FilterResolved::AndNot(f) => !self.entry_match_no_index_inner(f), } } + + pub fn filter_from_attrs(&self, attrs: &Vec) -> Option> { + // Because we are a valid entry, a filter we create still may not + // be valid because the internal server entry templates are still + // created by humans! Plus double checking something already valid + // is not bad ... + // + // Generate a filter from the attributes requested and defined. + // Basically, this is a series of nested and's (which will be + // optimised down later: but if someone wants to solve flatten() ...) + + // Take name: (a, b), name: (c, d) -> (name, a), (name, b), (name, c), (name, d) + + let mut pairs: Vec<(&str, &Value)> = Vec::new(); + + for attr in attrs { + match self.attrs.get(attr) { + Some(values) => { + for v in values { + pairs.push((attr, v)) + } + } + None => return None, + } + } + + Some(filter_all!(f_and( + pairs + .into_iter() + .map(|(attr, value)| { + // We use FC directly here instead of f_eq to avoid an excess clone. + FC::Eq(attr, value.to_partialvalue()) + }) + .collect() + ))) + } + + pub fn gen_modlist_assert( + &self, + schema: &dyn SchemaTransaction, + ) -> Result, SchemaError> { + // Create a modlist from this entry. We make this assuming we want the entry + // to have this one as a subset of values. This means if we have single + // values, we'll replace, if they are multivalue, we present them. + let mut mods = ModifyList::new(); + + for (k, vs) in self.attrs.iter() { + // WHY?! We skip uuid here because it is INVALID for a UUID + // to be in a modlist, and the base.rs plugin will fail if it + // is there. This actually doesn't matter, because to apply the + // modlist in these situations we already know the entry MUST + // exist with that UUID, we only need to conform it's other + // attributes into the same state. + // + // In the future, if we make uuid a real entry type, then this + // check can "go away" because uuid will never exist as an ava. + // + // NOTE: Remove this check when uuid becomes a real attribute. + // UUID is now a real attribute, but it also has an ava for db_entry + // conversion - so what do? If we remove it here, we could have CSN issue with + // repl on uuid conflict, but it probably shouldn't be an ava either ... + // as a result, I think we need to keep this continue line to not cause issues. + if k == "uuid" { + continue; + } + // Get the schema attribute type out. + match schema.is_multivalue(k) { + Ok(r) => { + if !r { + // As this is single value, purge then present to maintain this + // invariant + mods.push_mod(Modify::Purged(k.clone())); + } + } + // A schema error happened, fail the whole operation. + Err(e) => return Err(e), + } + for v in vs { + mods.push_mod(Modify::Present(k.clone(), v.clone())); + } + } + + Ok(mods) + } } impl Entry diff --git a/kanidmd/src/lib/plugins/domain.rs b/kanidmd/src/lib/plugins/domain.rs new file mode 100644 index 000000000..04649f197 --- /dev/null +++ b/kanidmd/src/lib/plugins/domain.rs @@ -0,0 +1,91 @@ +// Manage and generate domain uuid's and/or trust related domain +// management. + +// The primary point of this is to generate a unique domain UUID on startup +// which is importart for management of the replication topo and trust +// relationships. +use crate::plugins::Plugin; + +use crate::audit::AuditScope; +use crate::entry::{Entry, EntryInvalid, EntryNew}; +use crate::event::CreateEvent; +use crate::server::QueryServerWriteTransaction; +use crate::value::{PartialValue, Value}; +use kanidm_proto::v1::OperationError; + +use uuid::Uuid; + +lazy_static! { + static ref PVCLASS_DOMAIN_INFO: PartialValue = PartialValue::new_class("domain_info"); +} + +pub struct Domain {} + +impl Plugin for Domain { + fn id() -> &'static str { + "plugin_domain" + } + + fn pre_create_transform( + au: &mut AuditScope, + _qs: &mut QueryServerWriteTransaction, + cand: &mut Vec>, + _ce: &CreateEvent, + ) -> Result<(), OperationError> { + audit_log!(au, "Entering base pre_create_transform"); + cand.iter_mut().for_each(|e| { + if e.attribute_value_pres("class", &PVCLASS_DOMAIN_INFO) + && !e.attribute_pres("domain_uuid") + { + let u = Value::new_uuid(Uuid::new_v4()); + e.set_avas("domain_uuid", vec![u]); + audit_log!(au, "plugin_domain: Applying uuid transform"); + audit_log!(au, "{:?}", e); + } + }); + audit_log!(au, "Ending base pre_create_transform"); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::entry::{Entry, EntryInvalid, EntryNew}; + use crate::server::{QueryServerTransaction, QueryServerWriteTransaction}; + use uuid::Uuid; + // test we can create and generate the id + #[test] + fn test_domain_generate_uuid() { + let preload = vec![]; + let e: Entry = Entry::unsafe_from_entry_str( + r#"{ + "attrs": { + "class": ["domain_info", "system"], + "name": ["domain_example.net.au"], + "uuid": ["96fd1112-28bc-48ae-9dda-5acb4719aaba"], + "description": ["Demonstration of a remote domain's info being created for uuid generaiton"], + "domain_name": ["example.net.au"], + "domain_ssid": ["Example_Wifi"] + } + }"#, + ); + let create = vec![e]; + + run_create_test!( + Ok(()), + preload, + create, + None, + |au, qs_write: &QueryServerWriteTransaction| { + // Check that a uuid was added? + let e = qs_write + .internal_search_uuid( + au, + &Uuid::parse_str("96fd1112-28bc-48ae-9dda-5acb4719aaba").unwrap(), + ) + .unwrap(); + assert!(e.get_ava("domain_uuid").is_some()); + } + ); + } +} diff --git a/kanidmd/src/lib/plugins/mod.rs b/kanidmd/src/lib/plugins/mod.rs index 092e9f334..30fbfd4ad 100644 --- a/kanidmd/src/lib/plugins/mod.rs +++ b/kanidmd/src/lib/plugins/mod.rs @@ -9,11 +9,13 @@ mod macros; mod attrunique; mod base; +mod domain; mod failure; mod memberof; mod protected; mod recycle; mod refint; +mod spn; trait Plugin { fn id() -> &'static str; @@ -278,11 +280,12 @@ impl Plugins { ce: &CreateEvent, ) -> Result<(), OperationError> { audit_segment!(au, || { - let res = - run_pre_create_transform_plugin!(au, qs, cand, ce, base::Base).and_then(|_| { + let res = run_pre_create_transform_plugin!(au, qs, cand, ce, base::Base) + .and_then(|_| run_pre_create_transform_plugin!(au, qs, cand, ce, domain::Domain)) + .and_then(|_| run_pre_create_transform_plugin!(au, qs, cand, ce, spn::Spn)) + .and_then(|_| { run_pre_create_transform_plugin!(au, qs, cand, ce, attrunique::AttrUnique) }); - res }) } @@ -323,6 +326,7 @@ impl Plugins { audit_segment!(au, || { let res = run_pre_modify_plugin!(au, qs, cand, me, protected::Protected) .and_then(|_| run_pre_modify_plugin!(au, qs, cand, me, base::Base)) + .and_then(|_| run_pre_modify_plugin!(au, qs, cand, me, spn::Spn)) .and_then(|_| run_pre_modify_plugin!(au, qs, cand, me, attrunique::AttrUnique)); res @@ -341,8 +345,8 @@ impl Plugins { run_post_modify_plugin!(au, qs, pre_cand, cand, me, refint::ReferentialIntegrity) .and_then(|_| { run_post_modify_plugin!(au, qs, pre_cand, cand, me, memberof::MemberOf) - }); - + }) + .and_then(|_| run_post_modify_plugin!(au, qs, pre_cand, cand, me, spn::Spn)); res }) } @@ -382,6 +386,7 @@ impl Plugins { run_verify_plugin!(au, qs, &mut results, attrunique::AttrUnique); run_verify_plugin!(au, qs, &mut results, refint::ReferentialIntegrity); run_verify_plugin!(au, qs, &mut results, memberof::MemberOf); + run_verify_plugin!(au, qs, &mut results, spn::Spn); results } } diff --git a/kanidmd/src/lib/plugins/protected.rs b/kanidmd/src/lib/plugins/protected.rs index 629a1e3fc..11973aadc 100644 --- a/kanidmd/src/lib/plugins/protected.rs +++ b/kanidmd/src/lib/plugins/protected.rs @@ -20,16 +20,22 @@ pub struct Protected {} lazy_static! { static ref ALLOWED_ATTRS: HashSet<&'static str> = { let mut m = HashSet::new(); + // Allow modification of some schema class types to allow local extension + // of schema types. m.insert("must"); m.insert("may"); + // Allow modification of some domain info types for local configuration. + m.insert("domain_ssid"); m }; static ref PVCLASS_SYSTEM: PartialValue = PartialValue::new_class("system"); static ref PVCLASS_TOMBSTONE: PartialValue = PartialValue::new_class("tombstone"); static ref PVCLASS_RECYCLED: PartialValue = PartialValue::new_class("recycled"); + static ref PVCLASS_DOMAIN_INFO: PartialValue = PartialValue::new_class("domain_info"); static ref VCLASS_SYSTEM: Value = Value::new_class("system"); static ref VCLASS_TOMBSTONE: Value = Value::new_class("tombstone"); static ref VCLASS_RECYCLED: Value = Value::new_class("recycled"); + static ref VCLASS_DOMAIN_INFO: Value = Value::new_class("domain_info"); } impl Plugin for Protected { @@ -56,6 +62,7 @@ impl Plugin for Protected { Err(_) => acc, Ok(_) => { if cand.attribute_value_pres("class", &PVCLASS_SYSTEM) + || cand.attribute_value_pres("class", &PVCLASS_DOMAIN_INFO) || cand.attribute_value_pres("class", &PVCLASS_TOMBSTONE) || cand.attribute_value_pres("class", &PVCLASS_RECYCLED) { @@ -81,7 +88,7 @@ impl Plugin for Protected { ); return Ok(()); } - // Prevent adding class: system, tombstone, or recycled. + // Prevent adding class: system, domain_info, tombstone, or recycled. me.modlist.iter().fold(Ok(()), |acc, m| { if acc.is_err() { acc @@ -91,6 +98,7 @@ impl Plugin for Protected { // TODO: Can we avoid this clone? if a == "class" && (v == &(VCLASS_SYSTEM.clone()) + || v == &(VCLASS_DOMAIN_INFO.clone()) || v == &(VCLASS_TOMBSTONE.clone()) || v == &(VCLASS_RECYCLED.clone())) { @@ -104,7 +112,8 @@ impl Plugin for Protected { } })?; - // HARD block mods on tombstone or recycle. + // HARD block mods on tombstone or recycle. We soft block on the rest as they may + // have some allowed attrs. cand.iter().fold(Ok(()), |acc, cand| match acc { Err(_) => acc, Ok(_) => { @@ -123,6 +132,8 @@ impl Plugin for Protected { if acc { acc } else { + // We don't need to check for domain info here because domain_info has a class + // system also. We just need to block it from being created. c.attribute_value_pres("class", &PVCLASS_SYSTEM) } }); @@ -171,6 +182,7 @@ impl Plugin for Protected { Err(_) => acc, Ok(_) => { if cand.attribute_value_pres("class", &PVCLASS_SYSTEM) + || cand.attribute_value_pres("class", &PVCLASS_DOMAIN_INFO) || cand.attribute_value_pres("class", &PVCLASS_TOMBSTONE) || cand.attribute_value_pres("class", &PVCLASS_RECYCLED) { @@ -204,7 +216,7 @@ mod tests { ], "name": ["idm_admins_acp_allow_all_test"], "uuid": ["bb18f746-a409-497d-928c-5455d4aef4f7"], - "description": ["Builtin IDM Administrators Access Controls."], + "description": ["Builtin IDM Administrators Access Controls for TESTING."], "acp_enable": ["true"], "acp_receiver": [ "{\"Eq\":[\"uuid\",\"00000000-0000-0000-0000-000000000000\"]}" @@ -213,11 +225,11 @@ mod tests { "{\"Pres\":\"class\"}" ], "acp_search_attr": ["name", "class", "uuid", "classname", "attributename"], - "acp_modify_class": ["system"], - "acp_modify_removedattr": ["class", "displayname", "may", "must"], - "acp_modify_presentattr": ["class", "displayname", "may", "must"], - "acp_create_class": ["object", "person", "system"], - "acp_create_attr": ["name", "class", "description", "displayname"] + "acp_modify_class": ["system", "domain_info"], + "acp_modify_removedattr": ["class", "displayname", "may", "must", "domain_name", "domain_ssid"], + "acp_modify_presentattr": ["class", "displayname", "may", "must", "domain_name", "domain_ssid"], + "acp_create_class": ["object", "person", "system", "domain_info"], + "acp_create_attr": ["name", "class", "description", "displayname", "domain_name", "domain_ssid", "uuid"] } }"#; @@ -372,4 +384,99 @@ mod tests { |_, _| {} ); } + + #[test] + fn test_modify_domain() { + // Can edit *my* domain_ssid and domain_name + let acp: Entry = Entry::unsafe_from_entry_str(JSON_ADMIN_ALLOW_ALL); + // Show that adding a system class is denied + let e: Entry = Entry::unsafe_from_entry_str( + r#"{ + "attrs": { + "class": ["domain_info"], + "name": ["domain_example.net.au"], + "uuid": ["96fd1112-28bc-48ae-9dda-5acb4719aaba"], + "description": ["Demonstration of a remote domain's info being created for uuid generaiton"], + "domain_name": ["example.net.au"], + "domain_ssid": ["Example_Wifi"] + } + }"#, + ); + + let preload = vec![acp, e.clone()]; + + run_modify_test!( + Ok(()), + preload, + filter!(f_eq( + "name", + PartialValue::new_iutf8s("domain_example.net.au") + )), + modlist!([ + m_purge("domain_ssid"), + m_pres("domain_ssid", &Value::new_utf8s("NewExampleWifi")), + ]), + Some(JSON_ADMIN_V1), + |_, _| {} + ); + } + + #[test] + fn test_ext_create_domain() { + // can not add a domain_info type - note the lack of class: system + let acp: Entry = Entry::unsafe_from_entry_str(JSON_ADMIN_ALLOW_ALL); + let preload = vec![acp]; + let e: Entry = Entry::unsafe_from_entry_str( + r#"{ + "attrs": { + "class": ["domain_info"], + "name": ["domain_example.net.au"], + "uuid": ["96fd1112-28bc-48ae-9dda-5acb4719aaba"], + "description": ["Demonstration of a remote domain's info being created for uuid generaiton"], + "domain_name": ["example.net.au"], + "domain_ssid": ["Example_Wifi"] + } + }"#, + ); + let create = vec![e]; + + run_create_test!( + Err(OperationError::SystemProtectedObject), + preload, + create, + Some(JSON_ADMIN_V1), + |_, _| {} + ); + } + + #[test] + fn test_delete_domain() { + let acp: Entry = Entry::unsafe_from_entry_str(JSON_ADMIN_ALLOW_ALL); + // On the real thing we have a class: system, but to prove the point ... + let e: Entry = Entry::unsafe_from_entry_str( + r#"{ + "attrs": { + "class": ["domain_info"], + "name": ["domain_example.net.au"], + "uuid": ["96fd1112-28bc-48ae-9dda-5acb4719aaba"], + "description": ["Demonstration of a remote domain's info being created for uuid generaiton"], + "domain_name": ["example.net.au"], + "domain_ssid": ["Example_Wifi"] + } + }"#, + ); + + let preload = vec![acp, e.clone()]; + + run_delete_test!( + Err(OperationError::SystemProtectedObject), + preload, + filter!(f_eq( + "name", + PartialValue::new_iutf8s("domain_example.net.au") + )), + Some(JSON_ADMIN_V1), + |_, _| {} + ); + } } diff --git a/kanidmd/src/lib/plugins/spn.rs b/kanidmd/src/lib/plugins/spn.rs new file mode 100644 index 000000000..97673e110 --- /dev/null +++ b/kanidmd/src/lib/plugins/spn.rs @@ -0,0 +1,436 @@ +// Generate and manage spn's for all entries in the domain. Also deals with +// the infrequent - but possible - case where a domain is renamed. +use crate::plugins::Plugin; + +use crate::audit::AuditScope; +use crate::constants::UUID_DOMAIN_INFO; +use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryValid}; +use crate::event::{CreateEvent, ModifyEvent}; +use crate::server::{ + QueryServerReadTransaction, QueryServerTransaction, QueryServerWriteTransaction, +}; +use crate::value::PartialValue; +// use crate::value::{PartialValue, Value}; +use kanidm_proto::v1::{ConsistencyError, OperationError}; +use uuid::Uuid; + +pub struct Spn {} + +lazy_static! { + static ref UUID_DOMAIN_INFO_T: Uuid = + Uuid::parse_str(UUID_DOMAIN_INFO).expect("Unable to parse constant UUID_DOMAIN_INFO"); + static ref CLASS_GROUP: PartialValue = PartialValue::new_iutf8s("group"); + static ref CLASS_ACCOUNT: PartialValue = PartialValue::new_iutf8s("account"); + static ref PV_UUID_DOMAIN_INFO: PartialValue = PartialValue::new_uuids(UUID_DOMAIN_INFO) + .expect("Unable to parse constant UUID_DOMAIN_INFO"); +} + +impl Spn { + fn get_domain_name( + au: &mut AuditScope, + qs: &mut QueryServerWriteTransaction, + ) -> Result { + qs.internal_search_uuid(au, &UUID_DOMAIN_INFO_T) + .and_then(|e| { + e.get_ava_single_string("domain_name") + .ok_or(OperationError::InvalidEntryState) + }) + .map_err(|e| { + audit_log!(au, "Error getting domain name -> {:?}", e); + e + }) + } + + fn get_domain_name_ro( + au: &mut AuditScope, + qs: &QueryServerReadTransaction, + ) -> Result { + qs.internal_search_uuid(au, &UUID_DOMAIN_INFO_T) + .and_then(|e| { + e.get_ava_single_string("domain_name") + .ok_or(OperationError::InvalidEntryState) + }) + .map_err(|e| { + audit_log!(au, "Error getting domain name -> {:?}", e); + e + }) + } +} + +impl Plugin for Spn { + fn id() -> &'static str { + "plugin_spn" + } + + // hook on pre-create and modify to generate / validate. + fn pre_create_transform( + au: &mut AuditScope, + qs: &mut QueryServerWriteTransaction, + cand: &mut Vec>, + _ce: &CreateEvent, + ) -> Result<(), OperationError> { + // Always generate the spn and set it. Why? Because the effort + // needed to validate is the same as generation, so we may as well + // just generate and set blindly when required. + + // TODO: Should we work out what classes dynamically from schema into a filter? + let mut domain_name: Option = None; + + for e in cand.iter_mut() { + if e.attribute_value_pres("class", &CLASS_GROUP) + || e.attribute_value_pres("class", &CLASS_ACCOUNT) + { + if domain_name.is_none() { + domain_name = Some(Self::get_domain_name(au, qs)?); + } + + // It should be impossible to hit this expect as the is_none case should cause it to be replaced above. + let some_domain_name = domain_name + .as_ref() + .expect("Domain name option memory corruption has occured."); + + let spn = e + .generate_spn(some_domain_name.as_str()) + .ok_or(OperationError::InvalidEntryState) + .map_err(|e| { + audit_log!( + au, + "Account or group missing name, unable to generate spn!? {:?}", + e + ); + e + })?; + audit_log!(au, "plugin_spn: set spn to {:?}", spn); + e.set_avas("spn", vec![spn]); + } + } + Ok(()) + } + + fn pre_modify( + au: &mut AuditScope, + qs: &mut QueryServerWriteTransaction, + cand: &mut Vec>, + _me: &ModifyEvent, + ) -> Result<(), OperationError> { + // Always generate and set *if* spn was an attribute on any of the mod + // list events. + let mut domain_name: Option = None; + + for e in cand.iter_mut() { + if e.attribute_value_pres("class", &CLASS_GROUP) + || e.attribute_value_pres("class", &CLASS_ACCOUNT) + { + if domain_name.is_none() { + domain_name = Some(Self::get_domain_name(au, qs)?); + } + + // It should be impossible to hit this expect as the is_none case should cause it to be replaced above. + let some_domain_name = domain_name + .as_ref() + .expect("Domain name option memory corruption has occured."); + + let spn = e + .generate_spn(some_domain_name.as_str()) + .ok_or(OperationError::InvalidEntryState) + .map_err(|e| { + audit_log!( + au, + "Account or group missing name, unable to generate spn!? {:?}", + e + ); + e + })?; + audit_log!(au, "plugin_spn: set spn to {:?}", spn); + e.set_avas("spn", vec![spn]); + } + } + Ok(()) + } + + fn post_modify( + au: &mut AuditScope, + qs: &mut QueryServerWriteTransaction, + // List of what we modified that was valid? + pre_cand: &Vec>, + cand: &Vec>, + _ce: &ModifyEvent, + ) -> Result<(), OperationError> { + // On modify, if changing domain_name on UUID_DOMAIN_INFO + // trigger the spn regen ... which is expensive. Future + // todo will be improvements to modify on large txns. + + let domain_name_changed = + cand.iter() + .zip(pre_cand.iter()) + .fold(None, |acc, (post, pre)| { + if acc.is_some() { + acc + } else { + if post.attribute_value_pres("uuid", &PV_UUID_DOMAIN_INFO) + && post.get_ava_single("domain_name") + != pre.get_ava_single("domain_name") + { + post.get_ava_single("domain_name") + } else { + acc + } + } + }); + + let domain_name = match domain_name_changed { + Some(s) => s, + None => return Ok(()), + }; + + audit_log!( + au, + "IMPORTANT!!! Changing domain name to \"{:?}\". THIS MAY TAKE A LONG TIME ...", + domain_name + ); + + // All we do is purge spn, and allow the plugin to recreate. Neat! It's also all still + // within the transaction, just incase! + qs.internal_modify( + au, + filter!(f_or!([ + f_eq("class", PartialValue::new_class("group")), + f_eq("class", PartialValue::new_class("account")) + ])), + modlist!([m_purge("spn")]), + ) + } + + fn verify( + au: &mut AuditScope, + qs: &QueryServerReadTransaction, + ) -> Vec> { + // Verify that all items with spn's have valid spns. + // We need to consider the case that an item has a different origin domain too, + // so we should be able to verify that *those* spns validate to the trusted domain info + // we have been sent also. It's not up to use to generate those though ... + + let domain_name = match Self::get_domain_name_ro(au, qs) + .map_err(|_| Err(ConsistencyError::QueryServerSearchFailure)) + { + Ok(dn) => dn, + Err(e) => return vec![e], + }; + + let filt_in = filter!(f_or!([ + f_eq("class", PartialValue::new_class("group")), + f_eq("class", PartialValue::new_class("account")) + ])); + + let all_cand = match qs + .internal_search(au, filt_in) + .map_err(|_| Err(ConsistencyError::QueryServerSearchFailure)) + { + Ok(all_cand) => all_cand, + Err(e) => return vec![e], + }; + + let mut r = Vec::new(); + + for e in all_cand { + let g_spn = match e.generate_spn(domain_name.as_str()) { + Some(s) => s, + None => { + audit_log!( + au, + "Entry {:?} SPN could not be generated (missing name!?)", + e.get_uuid() + ); + debug_assert!(false); + r.push(Err(ConsistencyError::InvalidSPN(e.get_id()))); + continue; + } + }; + match e.get_ava_single("spn") { + Some(r_spn) => { + audit_log!(au, "verify spn: s {:?} == ex {:?} ?", r_spn, g_spn); + if *r_spn != g_spn { + audit_log!( + au, + "Entry {:?} SPN does not match expected s {:?} != ex {:?}", + e.get_uuid(), + r_spn, + g_spn, + ); + debug_assert!(false); + r.push(Err(ConsistencyError::InvalidSPN(e.get_id()))) + } else { + audit_log!(au, "spn is ok! 👍"); + } + } + None => { + audit_log!(au, "Entry {:?} does not contain an SPN", e.get_uuid(),); + r.push(Err(ConsistencyError::InvalidSPN(e.get_id()))) + } + } + } + r + } +} + +#[cfg(test)] +mod tests { + use crate::constants::UUID_ADMIN; + use crate::entry::{Entry, EntryInvalid, EntryNew}; + use crate::server::{QueryServerTransaction, QueryServerWriteTransaction}; + use crate::value::{PartialValue, Value}; + + #[test] + fn test_spn_generate_create() { + // on create don't provide + let e: Entry = Entry::unsafe_from_entry_str( + r#"{ + "valid": null, + "state": null, + "attrs": { + "class": ["account"], + "name": ["testperson"], + "description": ["testperson"], + "displayname": ["testperson"] + } + }"#, + ); + + let create = vec![e.clone()]; + let preload = Vec::new(); + + run_create_test!( + Ok(()), + preload, + create, + None, + |_au, _qs_write: &QueryServerWriteTransaction| {} + ); + // We don't need a validator due to the fn verify above. + } + + #[test] + fn test_spn_generate_modify() { + // on a purge of the spen, generate it. + let e: Entry = Entry::unsafe_from_entry_str( + r#"{ + "valid": null, + "state": null, + "attrs": { + "class": ["account"], + "name": ["testperson"], + "description": ["testperson"], + "displayname": ["testperson"] + } + }"#, + ); + + let preload = vec![e]; + + run_modify_test!( + Ok(()), + preload, + filter!(f_eq("name", PartialValue::new_iutf8s("testperson"))), + modlist!([m_purge("spn")]), + None, + |_, _| {} + ); + } + + #[test] + fn test_spn_validate_create() { + // on create providing invalid spn, we over-write it. + let e: Entry = Entry::unsafe_from_entry_str( + r#"{ + "valid": null, + "state": null, + "attrs": { + "class": ["account"], + "spn": ["testperson@invalid_domain.com"], + "name": ["testperson"], + "description": ["testperson"], + "displayname": ["testperson"] + } + }"#, + ); + + let create = vec![e.clone()]; + let preload = Vec::new(); + + run_create_test!( + Ok(()), + preload, + create, + None, + |_au, _qs_write: &QueryServerWriteTransaction| {} + ); + } + + #[test] + fn test_spn_validate_modify() { + // On modify (removed/present) of the spn, just regenerate it. + let e: Entry = Entry::unsafe_from_entry_str( + r#"{ + "valid": null, + "state": null, + "attrs": { + "class": ["account"], + "name": ["testperson"], + "description": ["testperson"], + "displayname": ["testperson"] + } + }"#, + ); + + let preload = vec![e]; + + run_modify_test!( + Ok(()), + preload, + filter!(f_eq("name", PartialValue::new_iutf8s("testperson"))), + modlist!([ + m_purge("spn"), + m_pres("spn", &Value::new_spn_str("invalid", "spn")) + ]), + None, + |_, _| {} + ); + } + + #[test] + fn test_spn_regen_domain_rename() { + run_test!(|server: &QueryServer, au: &mut AuditScope| { + let mut server_txn = server.write(); + + let ex1 = Value::new_spn_str("admin", "example.com"); + let ex2 = Value::new_spn_str("admin", "new.example.com"); + // get the current domain name + // check the spn on admin is admin@ + let e_pre = server_txn + .internal_search_uuid(au, &UUID_ADMIN) + .expect("must not fail"); + + let e_pre_spn = e_pre.get_ava_single("spn").expect("must not fail"); + assert!(*e_pre_spn == ex1); + + // trigger the domain_name change (this will be a cli option to the server + // in the final version), but it will still call the same qs function to perform the + // change. + server_txn + .domain_rename(au, "new.example.com") + .expect("should not fail!"); + + // check the spn on admin is admin@ + let e_post = server_txn + .internal_search_uuid(au, &UUID_ADMIN) + .expect("must not fail"); + + let e_post_spn = e_post.get_ava_single("spn").expect("must not fail"); + debug!("{:?}", e_post_spn); + debug!("{:?}", ex2); + assert!(*e_post_spn == ex2); + + server_txn.commit(au).expect("Must not fail"); + }); + } +} diff --git a/kanidmd/src/lib/schema.rs b/kanidmd/src/lib/schema.rs index 68eefed0a..410772675 100644 --- a/kanidmd/src/lib/schema.rs +++ b/kanidmd/src/lib/schema.rs @@ -230,6 +230,14 @@ impl SchemaAttribute { } } + fn validate_spn(&self, v: &Value) -> Result<(), SchemaError> { + if v.is_spn() { + Ok(()) + } else { + Err(SchemaError::InvalidAttributeSyntax) + } + } + // TODO: There may be a difference between a value and a filter value on complex // types - IE a complex type may have multiple parts that are secret, but a filter // on that may only use a single tagged attribute for example. @@ -246,6 +254,7 @@ impl SchemaAttribute { SyntaxType::CREDENTIAL => v.is_credential(), SyntaxType::RADIUS_UTF8STRING => v.is_radius_string(), SyntaxType::SSHKEY => v.is_sshkey(), + SyntaxType::SERVICE_PRINCIPLE_NAME => v.is_spn(), }; if r { Ok(()) @@ -360,6 +369,13 @@ impl SchemaAttribute { acc } }), + SyntaxType::SERVICE_PRINCIPLE_NAME => ava.iter().fold(Ok(()), |acc, v| { + if acc.is_ok() { + self.validate_spn(v) + } else { + acc + } + }), } } } @@ -571,6 +587,21 @@ impl SchemaInner { syntax: SyntaxType::UTF8STRING_INSENSITIVE, }, ); + s.attributes.insert( + String::from("spn"), + SchemaAttribute { + name: String::from("spn"), + uuid: Uuid::parse_str(UUID_SCHEMA_ATTR_SPN) + .expect("unable to parse static uuid"), + description: String::from( + "The service principle name of an object, unique across all domain trusts", + ), + multivalue: false, + unique: true, + index: vec![IndexType::EQUALITY], + syntax: SyntaxType::SERVICE_PRINCIPLE_NAME, + }, + ); s.attributes.insert( String::from("attributename"), SchemaAttribute { @@ -1047,7 +1078,7 @@ impl SchemaInner { systemmust: vec![ String::from("version"), // Needed when we implement principalnames? - String::from("domain"), + // String::from("domain"), // String::from("hostname"), ], must: vec![], diff --git a/kanidmd/src/lib/server.rs b/kanidmd/src/lib/server.rs index 1d7f385e4..95047542c 100644 --- a/kanidmd/src/lib/server.rs +++ b/kanidmd/src/lib/server.rs @@ -457,6 +457,7 @@ pub trait QueryServerTransaction { SyntaxType::CREDENTIAL => Err(OperationError::InvalidAttribute("Credentials can not be supplied through modification - please use the IDM api".to_string())), SyntaxType::RADIUS_UTF8STRING => Err(OperationError::InvalidAttribute("Radius secrets can not be supplied through modification - please use the IDM api".to_string())), SyntaxType::SSHKEY => Err(OperationError::InvalidAttribute("SSH public keys can not be supplied through modification - please use the IDM api".to_string())), + SyntaxType::SERVICE_PRINCIPLE_NAME => Err(OperationError::InvalidAttribute("SPNs are generated and not able to be set".to_string())), } } None => { @@ -531,6 +532,10 @@ pub trait QueryServerTransaction { SyntaxType::CREDENTIAL => Ok(PartialValue::new_credential_tag(value.as_str())), SyntaxType::RADIUS_UTF8STRING => Ok(PartialValue::new_radius_string()), SyntaxType::SSHKEY => Ok(PartialValue::new_sshkey_tag_s(value.as_str())), + SyntaxType::SERVICE_PRINCIPLE_NAME => PartialValue::new_spn_s(value.as_str()) + .ok_or(OperationError::InvalidAttribute( + "Invalid SPN syntax".to_string(), + )), } } None => { @@ -738,7 +743,7 @@ impl QueryServer { let reindex_write_1 = self.write(); reindex_write_1 - .upgrade_reindex(audit, 1) + .upgrade_reindex(audit, SYSTEM_INDEX_VERSION) .and_then(|_| reindex_write_1.commit(audit))?; // Because we init the schema here, and commit, this reloads meaning @@ -760,10 +765,11 @@ impl QueryServer { .initialise_schema_idm(audit) .and_then(|_| ts_write_2.commit(audit))?; - // reindex and set to version 2 + // reindex and set to version + 1, this way when we bump the version + // we are essetially pushing this version id back up to step write_1 let reindex_write_2 = self.write(); reindex_write_2 - .upgrade_reindex(audit, 2) + .upgrade_reindex(audit, SYSTEM_INDEX_VERSION + 1) .and_then(|_| reindex_write_2.commit(audit))?; let mut ts_write_3 = self.write(); @@ -1454,13 +1460,15 @@ impl<'a> QueryServerWriteTransaction<'a> { e_str: &str, ) -> Result<(), OperationError> { let res = audit_segment!(audit, || Entry::from_proto_entry_str(audit, e_str, self) + /* .and_then(|e: Entry| { let schema = self.get_schema(); e.validate(schema) .map_err(|e| OperationError::SchemaViolation(e)) }) + */ .and_then( - |e: Entry| self.internal_migrate_or_create(audit, e) + |e: Entry| self.internal_migrate_or_create(audit, e) )); audit_log!(audit, "internal_migrate_or_create_str -> result {:?}", res); assert!(res.is_ok()); @@ -1470,7 +1478,7 @@ impl<'a> QueryServerWriteTransaction<'a> { pub fn internal_migrate_or_create( &mut self, audit: &mut AuditScope, - e: Entry, + e: Entry, ) -> Result<(), OperationError> { // if the thing exists, ensure the set of attributes on // Entry A match and are present (but don't delete multivalue, or extended @@ -1490,7 +1498,7 @@ impl<'a> QueryServerWriteTransaction<'a> { Ok(results) => { if results.len() == 0 { // It does not exist. Create it. - self.internal_create(audit, vec![e.invalidate()]) + self.internal_create(audit, vec![e]) } else if results.len() == 1 { // If the thing is subset, pass match e.gen_modlist_assert(&self.schema) { @@ -1584,7 +1592,7 @@ impl<'a> QueryServerWriteTransaction<'a> { .into_iter() .map(|e| { audit_log!(audit, "init schema -> {}", e); - self.internal_migrate_or_create(audit, e) + self.internal_migrate_or_create(audit, e.invalidate()) }) .collect(); assert!(r.is_ok()); @@ -1600,9 +1608,13 @@ impl<'a> QueryServerWriteTransaction<'a> { JSON_SCHEMA_ATTR_SSH_PUBLICKEY, JSON_SCHEMA_ATTR_PRIMARY_CREDENTIAL, JSON_SCHEMA_ATTR_RADIUS_SECRET, + JSON_SCHEMA_ATTR_DOMAIN_NAME, + JSON_SCHEMA_ATTR_DOMAIN_UUID, + JSON_SCHEMA_ATTR_DOMAIN_SSID, JSON_SCHEMA_CLASS_PERSON, JSON_SCHEMA_CLASS_GROUP, JSON_SCHEMA_CLASS_ACCOUNT, + JSON_SCHEMA_CLASS_DOMAIN_INFO, ]; let mut audit_si = AuditScope::new("start_initialise_schema_idm"); @@ -1625,16 +1637,20 @@ impl<'a> QueryServerWriteTransaction<'a> { let mut audit_an = AuditScope::new("start_system_core_items"); let res = self .internal_assert_or_create_str(&mut audit_an, JSON_SYSTEM_INFO_V1) - .and_then(|_| self.internal_migrate_or_create_str(&mut audit_an, JSON_ANONYMOUS_V1)); + .and_then(|_| self.internal_migrate_or_create_str(&mut audit_an, JSON_DOMAIN_INFO_V1)); audit.append_scope(audit_an); assert!(res.is_ok()); if res.is_err() { return res; } + // The domain info now exists, we should be able to do these migrations as they will + // cause SPN regenerations to occur + // Check the admin object exists (migrations). // Create the default idm_admin group. let admin_entries = [ + JSON_ANONYMOUS_V1, JSON_ADMIN_V1, JSON_IDM_ADMIN_V1, JSON_IDM_ADMINS_V1, @@ -1675,6 +1691,7 @@ impl<'a> QueryServerWriteTransaction<'a> { JSON_IDM_HP_GROUP_MANAGE_PRIV_V1, JSON_IDM_HP_GROUP_WRITE_PRIV_V1, JSON_IDM_ACP_MANAGE_PRIV_V1, + JSON_DOMAIN_ADMINS, JSON_IDM_HIGH_PRIVILEGE_V1, // Built in access controls. JSON_IDM_ADMINS_ACP_RECYCLE_SEARCH_V1, @@ -1700,6 +1717,7 @@ impl<'a> QueryServerWriteTransaction<'a> { JSON_IDM_ACP_SCHEMA_WRITE_ATTRS_PRIV_V1, JSON_IDM_ACP_SCHEMA_WRITE_CLASSES_PRIV_V1, JSON_IDM_ACP_ACP_MANAGE_PRIV_V1, + JSON_IDM_ACP_DOMAIN_ADMIN_PRIV_V1, ]; let res: Result<(), _> = idm_entries @@ -1832,6 +1850,19 @@ impl<'a> QueryServerWriteTransaction<'a> { Ok(()) } + /// Initiate a domain rename process. This is generally an internal function but it's + /// exposed to the cli for admins to be able to initiate the process. + pub fn domain_rename( + &mut self, + audit: &mut AuditScope, + new_domain_name: &str, + ) -> Result<(), OperationError> { + let modl = ModifyList::new_purge_and_set("domain_name", Value::new_iutf8s(new_domain_name)); + let udi = PartialValue::new_uuids(UUID_DOMAIN_INFO).ok_or(OperationError::InvalidUuid)?; + let filt = filter_all!(f_eq("uuid", udi)); + self.internal_modify(audit, filt, modl) + } + pub fn reindex(&self, audit: &mut AuditScope) -> Result<(), OperationError> { // initiate a be reindex here. This could have been from first run checking // the versions, or it could just be from the cli where an admin needs to do an @@ -2162,7 +2193,7 @@ mod tests { filter!(f_eq("name", PartialValue::new_iutf8s("testperson1"))), ModifyList::new_list(vec![ Modify::Present("class".to_string(), Value::new_class("system_info")), - Modify::Present("domain".to_string(), Value::new_iutf8s("domain.name")), + // Modify::Present("domain".to_string(), Value::new_iutf8s("domain.name")), Modify::Present("version".to_string(), Value::new_iutf8s("1")), ]), ) diff --git a/kanidmd/src/lib/value.rs b/kanidmd/src/lib/value.rs index eade86bf3..645fae356 100644 --- a/kanidmd/src/lib/value.rs +++ b/kanidmd/src/lib/value.rs @@ -10,6 +10,13 @@ use uuid::Uuid; use sshkeys::PublicKey as SshPublicKey; use std::cmp::Ordering; +use regex::Regex; + +lazy_static! { + static ref SPN_RE: Regex = + Regex::new("(?P[^@]+)@(?P[^@]+)").expect("Invalid SPN regex found"); +} + #[allow(non_camel_case_types)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] pub enum IndexType { @@ -87,6 +94,7 @@ pub enum SyntaxType { CREDENTIAL, RADIUS_UTF8STRING, SSHKEY, + SERVICE_PRINCIPLE_NAME, } impl TryFrom<&str> for SyntaxType { @@ -106,6 +114,7 @@ impl TryFrom<&str> for SyntaxType { "CREDENTIAL" => Ok(SyntaxType::CREDENTIAL), "RADIUS_UTF8STRING" => Ok(SyntaxType::RADIUS_UTF8STRING), "SSHKEY" => Ok(SyntaxType::SSHKEY), + "SERVICE_PRINCIPLE_NAME" => Ok(SyntaxType::SERVICE_PRINCIPLE_NAME), _ => Err(()), } } @@ -127,6 +136,7 @@ impl TryFrom for SyntaxType { 8 => Ok(SyntaxType::CREDENTIAL), 9 => Ok(SyntaxType::RADIUS_UTF8STRING), 10 => Ok(SyntaxType::SSHKEY), + 11 => Ok(SyntaxType::SERVICE_PRINCIPLE_NAME), _ => Err(()), } } @@ -146,6 +156,7 @@ impl SyntaxType { SyntaxType::CREDENTIAL => "CREDENTIAL", SyntaxType::RADIUS_UTF8STRING => "RADIUS_UTF8STRING", SyntaxType::SSHKEY => "SSHKEY", + SyntaxType::SERVICE_PRINCIPLE_NAME => "SERVICE_PRINCIPLE_NAME", }) } @@ -162,6 +173,7 @@ impl SyntaxType { SyntaxType::CREDENTIAL => 8, SyntaxType::RADIUS_UTF8STRING => 9, SyntaxType::SSHKEY => 10, + SyntaxType::SERVICE_PRINCIPLE_NAME => 11, } } } @@ -189,6 +201,7 @@ pub enum PartialValue { Cred(String), SshKey(String), RadiusCred, + Spn(String, String), } impl PartialValue { @@ -374,6 +387,33 @@ impl PartialValue { } } + pub fn new_spn_s(s: &str) -> Option { + SPN_RE.captures(s).map(|caps| { + let name = caps + .name("name") + .expect("Failed to access name group") + .as_str() + .to_string(); + let realm = caps + .name("realm") + .expect("Failed to access realm group") + .as_str() + .to_string(); + PartialValue::Spn(name, realm) + }) + } + + pub fn new_spn_nrs(n: &str, r: &str) -> Self { + PartialValue::Spn(n.to_string(), r.to_string()) + } + + pub fn is_spn(&self) -> bool { + match self { + PartialValue::Spn(_, _) => true, + _ => false, + } + } + pub fn to_str(&self) -> Option<&str> { match self { PartialValue::Utf8(s) => Some(s.as_str()), @@ -408,6 +448,7 @@ impl PartialValue { // This will never match as we never index radius creds! See generate_idx_eq_keys PartialValue::RadiusCred => "_".to_string(), PartialValue::SshKey(tag) => tag.to_string(), + PartialValue::Spn(name, realm) => format!("{}@{}", name, realm), } } @@ -801,6 +842,27 @@ impl Value { } } + pub fn new_spn_parse(v: &str) -> Option { + PartialValue::new_spn_s(v).map(|spn| Value { + pv: spn, + data: None, + }) + } + + pub fn new_spn_str(n: &str, r: &str) -> Self { + Value { + pv: PartialValue::new_spn_nrs(n, r), + data: None, + } + } + + pub fn is_spn(&self) -> bool { + match &self.pv { + PartialValue::Spn(_, _) => true, + _ => false, + } + } + pub fn contains(&self, s: &PartialValue) -> bool { self.pv.contains(s) } @@ -866,6 +928,10 @@ impl Value { pv: PartialValue::SshKey(ts.t), data: Some(DataValue::SshKey(ts.d)), }), + DbValueV1::SP(n, r) => Ok(Value { + pv: PartialValue::Spn(n, r), + data: None, + }), } } @@ -922,6 +988,7 @@ impl Value { d: sk, }) } + PartialValue::Spn(n, r) => DbValueV1::SP(n.clone(), r.clone()), } } @@ -1028,6 +1095,7 @@ impl Value { // We don't disclose the radius credential unless by special // interfaces. PartialValue::RadiusCred => "radius".to_string(), + PartialValue::Spn(n, r) => format!("{}@{}", n, r), } } @@ -1085,6 +1153,7 @@ impl Value { vec![tag.to_string()] } PartialValue::RadiusCred => vec![], + PartialValue::Spn(n, r) => vec![format!("{}@{}", n, r)], } } } @@ -1184,6 +1253,23 @@ mod tests { assert!(!sk5.validate()); } + #[test] + fn test_value_spn() { + // Create an spn vale + let spnv = Value::new_spn_str("claire", "example.net.au"); + // create an spn pv + let spnp = PartialValue::new_spn_nrs("claire", "example.net.au"); + // check it's indexing output + let vidx_key = spnv.generate_idx_eq_keys().pop().unwrap(); + let idx_key = spnp.get_idx_eq_key(); + assert!(idx_key == vidx_key); + // check it can parse from name@realm + let spn_parse = PartialValue::new_spn_s("claire@example.net.au").unwrap(); + assert!(spn_parse == spnp); + // check it can produce name@realm as str from the pv. + assert!("claire@example.net.au" == spnv.to_proto_string_clone()); + } + /* #[test] fn test_schema_syntax_json_filter() { diff --git a/kanidmd/src/server/main.rs b/kanidmd/src/server/main.rs index 4df92079d..ad376a6b8 100644 --- a/kanidmd/src/server/main.rs +++ b/kanidmd/src/server/main.rs @@ -11,8 +11,8 @@ extern crate log; use kanidm::config::Configuration; use kanidm::core::{ - backup_server_core, create_server_core, recover_account_core, reindex_server_core, - reset_sid_core, restore_server_core, verify_server_core, + backup_server_core, create_server_core, domain_rename_core, recover_account_core, + reindex_server_core, reset_sid_core, restore_server_core, verify_server_core, }; use std::path::PathBuf; @@ -34,8 +34,6 @@ struct ServerOpt { cert_path: Option, #[structopt(parse(from_os_str), short = "k", long = "key")] key_path: Option, - #[structopt(short = "r", long = "domain")] - domain: String, #[structopt(short = "b", long = "bindaddr")] bind: Option, #[structopt(flatten)] @@ -66,6 +64,14 @@ struct RecoverAccountOpt { commonopts: CommonOpt, } +#[derive(Debug, StructOpt)] +struct DomainOpt { + #[structopt(short)] + new_domain_name: String, + #[structopt(flatten)] + commonopts: CommonOpt, +} + #[derive(Debug, StructOpt)] enum Opt { #[structopt(name = "server")] @@ -82,6 +88,8 @@ enum Opt { ResetServerId(CommonOpt), #[structopt(name = "reindex")] Reindex(CommonOpt), + #[structopt(name = "domain_name_change")] + DomainChange(DomainOpt), } impl Opt { @@ -92,6 +100,7 @@ impl Opt { Opt::Backup(bopt) => bopt.commonopts.debug, Opt::Restore(ropt) => ropt.commonopts.debug, Opt::RecoverAccount(ropt) => ropt.commonopts.debug, + Opt::DomainChange(dopt) => dopt.commonopts.debug, } } } @@ -120,7 +129,6 @@ fn main() { config.update_db_path(&sopt.commonopts.db_path); config.update_tls(&sopt.ca_path, &sopt.cert_path, &sopt.key_path); config.update_bind(&sopt.bind); - config.domain = sopt.domain.clone(); let sys = actix::System::new("kanidm-server"); create_server_core(config); @@ -180,5 +188,11 @@ fn main() { config.update_db_path(&copt.db_path); reindex_server_core(config); } + Opt::DomainChange(dopt) => { + info!("Running in domain name change mode ... this may take a long time ..."); + + config.update_db_path(&dopt.commonopts.db_path); + domain_rename_core(config, dopt.new_domain_name); + } } }