127 domain info type (#150)

Implements #127 and #125. This adds domain_info support, and spn types and generation. It also correctly handles domain renaming, and has tooling to support this. It "should" work on an upgrade, due to the correct bump of index version, but I plan to test this from a backup of my production instance soon.
This commit is contained in:
Firstyear 2019-11-29 10:48:22 +10:00 committed by GitHub
parent 6157c65d3a
commit 0609196048
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 1408 additions and 283 deletions

View file

@ -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

View file

@ -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:

View file

@ -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<Vec<Entry>, ClientError> {
self.perform_get_request("/v1/domain")
}
pub fn idm_domain_get(&self, id: &str) -> Result<Entry, ClientError> {
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<String, ClientError> {
self.perform_get_request(format!("/v1/domain/{}/_attr/domain_ssid", id).as_str())
.and_then(|mut r: Vec<String>| 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<Vec<Entry>, ClientError> {
self.perform_get_request("/v1/schema")

View file

@ -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.

View file

@ -72,6 +72,7 @@ pub enum ConsistencyError {
MemberOfInvalid(u64),
InvalidAttributeType(String),
DuplicateUniqueAttribute(String),
InvalidSPN(u64),
}
/* ===== higher level types ===== */

View file

@ -37,4 +37,5 @@ pub enum DbValueV1 {
CR(DbValueCredV1),
RU(String),
SK(DbValueTaggedStringV1),
SP(String, String),
}

View file

@ -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

View file

@ -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#"{

View file

@ -975,6 +975,42 @@ fn group_id_delete(
json_rest_event_delete_id(path, req, state, filter)
}
fn domain_get(
(req, state): (HttpRequest<AppState>, State<AppState>),
) -> impl Future<Item = HttpResponse, Error = Error> {
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<String>, HttpRequest<AppState>, State<AppState>),
) -> impl Future<Item = HttpResponse, Error = Error> {
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<AppState>,
State<AppState>,
),
) -> impl Future<Item = HttpResponse, Error = Error> {
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<AppState>,
State<AppState>,
),
) -> impl Future<Item = HttpResponse, Error = Error> {
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<AppState>, State<AppState>)) -> 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)

View file

@ -259,7 +259,7 @@ impl Entry<EntryInvalid, EntryNew> {
.map(|(k, vs)| {
let attr = k.to_lowercase();
let vv: BTreeSet<Value> = 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<EntryInvalid, EntryNew> {
=> {
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<EntryInvalid, EntryNew> {
"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<STATE> Entry<EntryValid, STATE> {
pub fn get_uuid(&self) -> &Uuid {
&self.valid.uuid
}
pub fn filter_from_attrs(&self, attrs: &Vec<String>) -> Option<Filter<FilterInvalid>> {
// 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<ModifyList<ModifyInvalid>, 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<EntryReduced, EntryCommitted> {
@ -1352,6 +1277,11 @@ impl<VALID, STATE> Entry<VALID, STATE> {
.and_then(|f: &ProtoFilter| Some((*f).clone()))
}
pub(crate) fn generate_spn(&self, domain_name: &str) -> Option<Value> {
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<VALID, STATE> Entry<VALID, STATE> {
FilterResolved::AndNot(f) => !self.entry_match_no_index_inner(f),
}
}
pub fn filter_from_attrs(&self, attrs: &Vec<String>) -> Option<Filter<FilterInvalid>> {
// 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<ModifyList<ModifyInvalid>, 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<STATE> Entry<EntryInvalid, STATE>

View file

@ -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<Entry<EntryInvalid, EntryNew>>,
_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<EntryInvalid, EntryNew> = 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());
}
);
}
}

View file

@ -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
}
}

View file

@ -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<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(JSON_ADMIN_ALLOW_ALL);
// Show that adding a system class is denied
let e: Entry<EntryInvalid, EntryNew> = 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<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(JSON_ADMIN_ALLOW_ALL);
let preload = vec![acp];
let e: Entry<EntryInvalid, EntryNew> = 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<EntryInvalid, EntryNew> = 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<EntryInvalid, EntryNew> = 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),
|_, _| {}
);
}
}

View file

@ -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<String, OperationError> {
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<String, OperationError> {
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<Entry<EntryInvalid, EntryNew>>,
_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<String> = 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<Entry<EntryInvalid, EntryCommitted>>,
_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<String> = 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<Entry<EntryValid, EntryCommitted>>,
cand: &Vec<Entry<EntryValid, EntryCommitted>>,
_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<Result<(), ConsistencyError>> {
// 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<EntryInvalid, EntryNew> = 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<EntryInvalid, EntryNew> = 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<EntryInvalid, EntryNew> = 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<EntryInvalid, EntryNew> = 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@<initial domain>
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@<new domain>
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");
});
}
}

View file

@ -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![],

View file

@ -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<EntryInvalid, EntryNew>| {
let schema = self.get_schema();
e.validate(schema)
.map_err(|e| OperationError::SchemaViolation(e))
})
*/
.and_then(
|e: Entry<EntryValid, EntryNew>| self.internal_migrate_or_create(audit, e)
|e: Entry<EntryInvalid, EntryNew>| 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<EntryValid, EntryNew>,
e: Entry<EntryInvalid, EntryNew>,
) -> 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")),
]),
)

View file

@ -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<name>[^@]+)@(?P<realm>[^@]+)").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<usize> 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<Self> {
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<Self> {
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() {

View file

@ -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<PathBuf>,
#[structopt(parse(from_os_str), short = "k", long = "key")]
key_path: Option<PathBuf>,
#[structopt(short = "r", long = "domain")]
domain: String,
#[structopt(short = "b", long = "bindaddr")]
bind: Option<String>,
#[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);
}
}
}