29 password badlisting (#158)

Implements #29 password badlist and quality checking. This checks all new passwords are at least length 10, pass zxcvbn and are not container in a badlist. The current badlist is a preprocessed content of rockyou from seclists, but later wwe'll update this to the top 10million badlist which when processed is about 70k entries..
This commit is contained in:
Firstyear 2019-12-13 08:49:32 +10:00 committed by GitHub
parent b579c5395c
commit 2ede944fdb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 78174 additions and 34 deletions

2
.gitignore vendored
View file

@ -7,3 +7,5 @@
test.db test.db
/vendor /vendor
kanidm_rlm_python/test_data/certs/ kanidm_rlm_python/test_data/certs/
vendor.tar.gz

View file

@ -7,6 +7,7 @@
- [Accounts and Groups](./accounts_and_groups.md) - [Accounts and Groups](./accounts_and_groups.md)
- [SSH Key Distribution](./ssh_key_dist.md) - [SSH Key Distribution](./ssh_key_dist.md)
- [RADIUS](./radius.md) - [RADIUS](./radius.md)
- [Password Quality and Badlisting](./password_quality.md)
----------- -----------
[Why TLS?](./why_tls.md) [Why TLS?](./why_tls.md)

View file

@ -0,0 +1,40 @@
# Password Quality and Badlisting
Kanidm embeds a set of tools to help your users use and create strong passwords. This is important
as not all user types will require MFA for their roles, but compromised accounts still pose a risk.
There may also be deployment or other barriers to a site rolling out site wide MFA.
## Quality Checking
Kanidm enforces that all passwords are checked by the library "zxcvbn". This has a large number of
checks for password quality. It also provides constructive feedback to users on how to improve their
passwords if it was rejected.
Some things that zxcvbn looks for is use of the account name or email in the password, common passwords,
low entropy passwords, dates, reverse words and more.
This library can not be disabled - all passwords in Kanidm must pass this check.
## Password Badlisting
This is the process of configuring a list of passwords to exclude from being able to be used. This
is especially useful if a specific business has been notified of a compromised account, allowing
you to maintain a list of customised excluded passwords.
The other value to this feature is being able to badlist common passwords that zxcvbn does not
detect, or from other large scale password compromises.
By default we ship with a preconfigured badlist that is updated overtime as new password breach
lists are made available.
## Updating your own badlist.
You can update your own badlist by using the proided `kanidm_badlist_preprocess` tool which helps
to automate this process.
Given a list of passwords in a text file, it will generate a modification set which can be
applied. The tool also provides the command you need to run to apply this.
kanidm_badlist_preprocess -m -o /tmp/modlist.json <password file> [<password file> <password file> ...]

View file

@ -258,13 +258,19 @@ fn test_server_admin_reset_simple_password() {
let res = rsclient.modify(f.clone(), m.clone()); let res = rsclient.modify(f.clone(), m.clone());
assert!(res.is_ok()); assert!(res.is_ok());
// Now set it's password. // Now set it's password - should be rejected based on low quality
let res = rsclient.idm_account_primary_credential_set_password("testperson", "password"); let res = rsclient.idm_account_primary_credential_set_password("testperson", "password");
assert!(res.is_err());
// Set the password to ensure it's good
let res = rsclient.idm_account_primary_credential_set_password(
"testperson",
"tai4eCohtae9aegheo3Uw0oobahVighaig6heeli",
);
assert!(res.is_ok()); assert!(res.is_ok());
// Check it stuck. // Check it stuck.
let tclient = rsclient.new_session().expect("failed to build new session"); let tclient = rsclient.new_session().expect("failed to build new session");
assert!(tclient assert!(tclient
.auth_simple_password("testperson", "password") .auth_simple_password("testperson", "tai4eCohtae9aegheo3Uw0oobahVighaig6heeli")
.is_ok()); .is_ok());
// Generate a pw instead // Generate a pw instead

View file

@ -9,6 +9,8 @@ serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
uuid = { version = "0.7", features = ["serde", "v4"] } uuid = { version = "0.7", features = ["serde", "v4"] }
actix = { version = "0.7", optional = true } actix = { version = "0.7", optional = true }
zxcvbn = { version = "2.0", features = ["ser"] }
[dev-dependencies] [dev-dependencies]
serde_json = "1.0" serde_json = "1.0"

View file

@ -1,6 +1,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::fmt; use std::fmt;
use uuid::Uuid; use uuid::Uuid;
// use zxcvbn::feedback;
// These proto implementations are here because they have public definitions // These proto implementations are here because they have public definitions
@ -25,6 +26,22 @@ pub enum PluginError {
} }
#[derive(Serialize, Deserialize, Debug, PartialEq)] #[derive(Serialize, Deserialize, Debug, PartialEq)]
pub enum ConsistencyError {
Unknown,
// Class, Attribute
SchemaClassMissingAttribute(String, String),
QueryServerSearchFailure,
EntryUuidCorrupt(u64),
UuidIndexCorrupt(String),
UuidNotUnique(String),
RefintNotUpheld(u64),
MemberOfInvalid(u64),
InvalidAttributeType(String),
DuplicateUniqueAttribute(String),
InvalidSPN(u64),
}
#[derive(Serialize, Deserialize, Debug)]
pub enum OperationError { pub enum OperationError {
EmptyRequest, EmptyRequest,
Backend, Backend,
@ -57,22 +74,19 @@ pub enum OperationError {
InvalidSessionState, InvalidSessionState,
SystemProtectedObject, SystemProtectedObject,
SystemProtectedAttribute, SystemProtectedAttribute,
PasswordTooWeak,
PasswordTooShort(usize),
PasswordEmpty,
PasswordBadListed,
} }
#[derive(Serialize, Deserialize, Debug, PartialEq)] impl PartialEq for OperationError {
pub enum ConsistencyError { fn eq(&self, other: &Self) -> bool {
Unknown, // We do this to avoid InvalidPassword being checked as it's not
// Class, Attribute // derive PartialEq. Generally we only use the PartialEq for TESTING
SchemaClassMissingAttribute(String, String), // anyway.
QueryServerSearchFailure, std::mem::discriminant(self) == std::mem::discriminant(other)
EntryUuidCorrupt(u64), }
UuidIndexCorrupt(String),
UuidNotUnique(String),
RefintNotUpheld(u64),
MemberOfInvalid(u64),
InvalidAttributeType(String),
DuplicateUniqueAttribute(String),
InvalidSPN(u64),
} }
/* ===== higher level types ===== */ /* ===== higher level types ===== */

View file

@ -13,6 +13,10 @@ path = "src/main.rs"
name = "kanidm_ssh_authorizedkeys" name = "kanidm_ssh_authorizedkeys"
path = "src/ssh_authorizedkeys.rs" path = "src/ssh_authorizedkeys.rs"
[[bin]]
name = "kanidm_badlist_preprocess"
path = "src/badlist_preprocess.rs"
[dependencies] [dependencies]
kanidm_client = { path = "../kanidm_client" } kanidm_client = { path = "../kanidm_client" }
kanidm_proto = { path = "../kanidm_proto" } kanidm_proto = { path = "../kanidm_proto" }
@ -23,4 +27,6 @@ env_logger = "0.6"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
shellexpand = "1.0" shellexpand = "1.0"
rayon = "1.2"
zxcvbn = "2.0"

View file

@ -0,0 +1,136 @@
extern crate structopt;
// use shellexpand;
use rayon::prelude::*;
use serde_json;
use std::fs::File;
use std::io::prelude::*;
use std::io::BufWriter;
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};
use structopt::StructOpt;
use zxcvbn;
use kanidm_proto::v1::Modify;
extern crate env_logger;
#[macro_use]
extern crate log;
#[derive(Debug, StructOpt)]
struct ClientOpt {
#[structopt(short = "d", long = "debug")]
debug: bool,
#[structopt(short = "m", long = "modlist")]
modlist: bool,
#[structopt(short = "o", long = "output")]
outfile: PathBuf,
#[structopt(parse(from_os_str))]
password_list: Vec<PathBuf>,
}
fn main() {
let opt = ClientOpt::from_args();
if opt.debug {
::std::env::set_var("RUST_LOG", "kanidm=debug,kanidm_client=debug");
} else {
::std::env::set_var("RUST_LOG", "kanidm=info,kanidm_client=info");
}
env_logger::init();
if opt.modlist {
debug!("Running in modlist generation mode");
} else {
debug!("Running in list filtering mode");
}
info!("Kanidm badlist preprocessor - this may take a long time ...");
// Build a temp struct for all the pws.
// TODO: Shellexpand all of these.
/*
let expanded_paths: Vec<_> = opt.password_list.iter()
.map(|p| {
shellexpand::tilde(p).into_owned()
})
.collect();
debug!("Using paths -> {:?}", expanded_paths);
*/
let mut pwset: Vec<String> = Vec::new();
// Read them all in, remove blank lines.
for f in opt.password_list.iter() {
let mut file = match File::open(f) {
Ok(v) => v,
Err(_) => {
info!("Skipping file -> {:?}", f);
continue;
}
};
let mut contents = String::new();
match file.read_to_string(&mut contents) {
Ok(_) => {}
Err(e) => {
error!("{:?} -> {:?}", f, e);
continue;
}
}
let mut inner_pw: Vec<_> = contents.as_str().lines().map(|s| s.to_string()).collect();
pwset.append(&mut inner_pw);
}
debug!("Deduplicating pre-set ...");
pwset.sort_unstable();
pwset.dedup();
info!("Have {} pws to process", pwset.len());
let count: AtomicUsize = AtomicUsize::new(0);
// Create an empty slice for empty site options, not needed in this context.
let site_opts: Vec<&str> = Vec::new();
// Run zxcbvn over them with filter, use btreeset to remove dups if any
let mut filt_pwset: Vec<_> = pwset
.into_par_iter()
.inspect(|_| {
let tc = count.fetch_add(1, Ordering::AcqRel);
if tc % 1000 == 0 {
info!("{} ...", tc)
}
})
.filter(|v| {
if v.len() == 0 {
return false;
}
if v.len() < 10 {
return false;
}
let r = zxcvbn::zxcvbn(v.as_str(), site_opts.as_slice()).expect("Empty Password?");
// score of 2 or less is too weak and we'd already reject it.
r.score() >= 3
})
.collect();
// Now sort and dedup
debug!("Deduplicating results ...");
filt_pwset.sort_unstable();
filt_pwset.dedup();
debug!("Starting file write ...");
// Now we write these out.
let fileout = File::create(opt.outfile).expect("Failed to create file");
let bwrite = BufWriter::new(fileout);
// All remaining are either
if opt.modlist {
// - written to a file ready for modify, with a modify command printed.
let modlist: Vec<Modify> = filt_pwset
.into_iter()
.map(|p| Modify::Present("badlist_password".to_string(), p))
.collect();
serde_json::to_writer_pretty(bwrite, &modlist).expect("Failed to serialise modlist");
// println!("next step: kanidm raw modify -D admin '{{\"Eq\": [\"uuid\", \"00000000-0000-0000-0000-ffffff000026\"]}}' <your outfile>");
} else {
// - printed in json format
serde_json::to_writer_pretty(bwrite, &filt_pwset).expect("Failed to serialise modlist");
}
}

View file

@ -57,4 +57,5 @@ rpassword = "0.4"
num_cpus = "1.10" num_cpus = "1.10"
idlset = "0.1" idlset = "0.1"
zxcvbn = "2.0"

View file

@ -1,5 +1,9 @@
use uuid::Uuid; use uuid::Uuid;
// Re-export as needed
pub mod system_config;
pub use crate::constants::system_config::JSON_SYSTEM_CONFIG_V1;
// Increment this as we add new schema types and values!!! // Increment this as we add new schema types and values!!!
pub static SYSTEM_INDEX_VERSION: i64 = 3; pub static SYSTEM_INDEX_VERSION: i64 = 3;
// On test builds, define to 60 seconds // On test builds, define to 60 seconds
@ -10,6 +14,7 @@ pub static PURGE_TIMEOUT: u64 = 60;
pub static PURGE_TIMEOUT: u64 = 3600; pub static PURGE_TIMEOUT: u64 = 3600;
// 5 minute auth session window. // 5 minute auth session window.
pub static AUTH_SESSION_TIMEOUT: u64 = 300; pub static AUTH_SESSION_TIMEOUT: u64 = 300;
pub static PW_MIN_LENGTH: usize = 10;
// Built in group and account ranges. // Built in group and account ranges.
pub static STR_UUID_ADMIN: &'static str = "00000000-0000-0000-0000-000000000000"; pub static STR_UUID_ADMIN: &'static str = "00000000-0000-0000-0000-000000000000";
@ -108,11 +113,14 @@ pub static UUID_SCHEMA_ATTR_DOMAIN_SSID: &'static str = "00000000-0000-0000-0000
pub static UUID_SCHEMA_ATTR_GIDNUMBER: &'static str = "00000000-0000-0000-0000-ffff00000056"; pub static UUID_SCHEMA_ATTR_GIDNUMBER: &'static str = "00000000-0000-0000-0000-ffff00000056";
pub static UUID_SCHEMA_CLASS_POSIXACCOUNT: &'static str = "00000000-0000-0000-0000-ffff00000057"; pub static UUID_SCHEMA_CLASS_POSIXACCOUNT: &'static str = "00000000-0000-0000-0000-ffff00000057";
pub static UUID_SCHEMA_CLASS_POSIXGROUP: &'static str = "00000000-0000-0000-0000-ffff00000058"; pub static UUID_SCHEMA_CLASS_POSIXGROUP: &'static str = "00000000-0000-0000-0000-ffff00000058";
pub static UUID_SCHEMA_ATTR_BADLIST_PASSWORD: &'static str = "00000000-0000-0000-0000-ffff00000059";
pub static UUID_SCHEMA_CLASS_SYSTEM_CONFIG: &'static str = "00000000-0000-0000-0000-ffff00000060";
// System and domain infos // System and domain infos
// I'd like to strongly criticise william of the past for fucking up these allocations. // 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_SYSTEM_INFO: &'static str = "00000000-0000-0000-0000-ffffff000001";
pub static UUID_DOMAIN_INFO: &'static str = "00000000-0000-0000-0000-ffffff000025"; pub static UUID_DOMAIN_INFO: &'static str = "00000000-0000-0000-0000-ffffff000025";
// DO NOT allocate here, allocate below.
// Access controls // Access controls
// skip 00 / 01 - see system info // skip 00 / 01 - see system info
@ -154,6 +162,9 @@ pub static _UUID_IDM_ACP_HP_GROUP_MANAGE_PRIV_V1: &'static str =
"00000000-0000-0000-0000-ffffff000024"; "00000000-0000-0000-0000-ffffff000024";
// Skip 25 - see domain info. // Skip 25 - see domain info.
pub static UUID_IDM_ACP_DOMAIN_ADMIN_PRIV_V1: &'static str = "00000000-0000-0000-0000-ffffff000026"; pub static UUID_IDM_ACP_DOMAIN_ADMIN_PRIV_V1: &'static str = "00000000-0000-0000-0000-ffffff000026";
pub static STR_UUID_SYSTEM_CONFIG: &'static str = "00000000-0000-0000-0000-ffffff000027";
pub static UUID_IDM_ACP_SYSTEM_CONFIG_PRIV_V1: &'static str =
"00000000-0000-0000-0000-ffffff000028";
// End of system ranges // End of system ranges
pub static STR_UUID_DOES_NOT_EXIST: &'static str = "00000000-0000-0000-0000-fffffffffffe"; pub static STR_UUID_DOES_NOT_EXIST: &'static str = "00000000-0000-0000-0000-fffffffffffe";
@ -163,6 +174,7 @@ lazy_static! {
pub static ref UUID_ADMIN: Uuid = Uuid::parse_str(STR_UUID_ADMIN).unwrap(); 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(); pub static ref UUID_DOES_NOT_EXIST: Uuid = Uuid::parse_str(STR_UUID_DOES_NOT_EXIST).unwrap();
pub static ref UUID_ANONYMOUS: Uuid = Uuid::parse_str(STR_UUID_ANONYMOUS).unwrap(); pub static ref UUID_ANONYMOUS: Uuid = Uuid::parse_str(STR_UUID_ANONYMOUS).unwrap();
pub static ref UUID_SYSTEM_CONFIG: Uuid = Uuid::parse_str(STR_UUID_SYSTEM_CONFIG).unwrap();
} }
pub static JSON_ADMIN_V1: &'static str = r#"{ pub static JSON_ADMIN_V1: &'static str = r#"{
@ -1204,7 +1216,7 @@ pub static JSON_IDM_ACP_HP_GROUP_MANAGE_PRIV_V1: &'static str = r#"{
} }
}"#; }"#;
// 25 - domain admins acp // 28 - domain admins acp
pub static JSON_IDM_ACP_DOMAIN_ADMIN_PRIV_V1: &'static str = r#"{ pub static JSON_IDM_ACP_DOMAIN_ADMIN_PRIV_V1: &'static str = r#"{
"attrs": { "attrs": {
"class": [ "class": [
@ -1238,6 +1250,36 @@ pub static JSON_IDM_ACP_DOMAIN_ADMIN_PRIV_V1: &'static str = r#"{
} }
}"#; }"#;
// 28 - system config
pub static JSON_IDM_ACP_SYSTEM_CONFIG_PRIV_V1: &'static str = r#"{
"attrs": {
"class": [
"object",
"access_control_profile",
"access_control_search",
"access_control_modify"
],
"name": ["idm_acp_system_config_priv"],
"uuid": ["00000000-0000-0000-0000-ffffff000028"],
"description": ["Builtin IDM Control for granting system configuration rights"],
"acp_receiver": [
"{\"Eq\":[\"memberof\",\"00000000-0000-0000-0000-000000000019\"]}"
],
"acp_targetscope": [
"{\"And\": [{\"Eq\": [\"uuid\",\"00000000-0000-0000-0000-ffffff000027\"]}, {\"AndNot\": {\"Or\": [{\"Eq\": [\"class\", \"tombstone\"]}, {\"Eq\": [\"class\", \"recycled\"]}]}}]}"
],
"acp_search_attr": [
"name",
"uuid",
"description",
"badlist_password"
],
"acp_modify_presentattr": [
"badlist_password"
]
}
}"#;
// Anonymous should be the last opbject in the range here. // Anonymous should be the last opbject in the range here.
pub static JSON_ANONYMOUS_V1: &'static str = r#"{ pub static JSON_ANONYMOUS_V1: &'static str = r#"{
"attrs": { "attrs": {
@ -1538,6 +1580,7 @@ pub static JSON_SCHEMA_ATTR_DOMAIN_SSID: &'static str = r#"{
] ]
} }
}"#; }"#;
pub static JSON_SCHEMA_ATTR_GIDNUMBER: &'static str = r#"{ pub static JSON_SCHEMA_ATTR_GIDNUMBER: &'static str = r#"{
"attrs": { "attrs": {
"class": [ "class": [
@ -1567,6 +1610,35 @@ pub static JSON_SCHEMA_ATTR_GIDNUMBER: &'static str = r#"{
} }
}"#; }"#;
pub static JSON_SCHEMA_ATTR_BADLIST_PASSWORD: &'static str = r#"{
"attrs": {
"class": [
"object",
"system",
"attributetype"
],
"description": [
"A password that is badlisted meaning that it can not be set as a valid password by any user account."
],
"index": [],
"unique": [
"true"
],
"multivalue": [
"true"
],
"attributename": [
"badlist_password"
],
"syntax": [
"UTF8STRING_INSENSITIVE"
],
"uuid": [
"00000000-0000-0000-0000-ffff00000059"
]
}
}"#;
pub static JSON_SCHEMA_CLASS_PERSON: &'static str = r#" pub static JSON_SCHEMA_CLASS_PERSON: &'static str = r#"
{ {
"valid": { "valid": {
@ -1719,6 +1791,7 @@ pub static JSON_SCHEMA_CLASS_POSIXGROUP: &'static str = r#"
} }
} }
"#; "#;
pub static JSON_SCHEMA_CLASS_POSIXACCOUNT: &'static str = r#" pub static JSON_SCHEMA_CLASS_POSIXACCOUNT: &'static str = r#"
{ {
"attrs": { "attrs": {
@ -1743,6 +1816,31 @@ pub static JSON_SCHEMA_CLASS_POSIXACCOUNT: &'static str = r#"
} }
"#; "#;
pub static JSON_SCHEMA_CLASS_SYSTEM_CONFIG: &'static str = r#"
{
"attrs": {
"class": [
"object",
"system",
"classtype"
],
"description": [
"The class representing a system (topologies) configuration options."
],
"classname": [
"system_config"
],
"systemmay": [
"description",
"badlist_password"
],
"uuid": [
"00000000-0000-0000-0000-ffff00000060"
]
}
}
"#;
// need a domain_trust_info as well. // need a domain_trust_info as well.
// TODO // TODO

View file

@ -0,0 +1,638 @@
[
"100preteamare",
"14defebrero",
"1life1love",
"1life2live",
"1love1life",
"1love4life",
"212224236248",
"2813308004",
"2fast2furious",
"2gether4ever",
"2pacshakur",
"30secondstomars",
"3doorsdown",
"6cyclemind",
"<div><embed src=\\\\",
"@hotmail.com",
"@yahoo.com",
"Lets you update your FunNotes and more!",
"TEQUIEROMUCHO",
"TEXT ONLY AD",
"abretesesamo",
"administrador",
"aeropostale",
"akinkalang",
"akucintakamu",
"akusayangkamu",
"alfayomega",
"alhamdulillah",
"allaboutme",
"allahuakbar",
"alleyesonme",
"alquimista",
"alwaysandforever",
"amarteduele",
"amigas4ever",
"amigasporsiempre",
"amigasx100pre",
"amigasxsiempre",
"amoamifamilia",
"amordelbueno",
"amordemivida",
"amoresperros",
"amoreterno",
"amorimposible",
"amorporsiempre",
"amorprohibido",
"amorverdadero",
"amotemuito",
"anaranjado",
"angeldeamor",
"angellocsin",
"angelofdeath",
"anggandako",
"aniversario",
"apaixonada",
"apocalipsa",
"apocalipse",
"apocalipsis",
"apolinario",
"arquitectura",
"arrolladora",
"asieslavida",
"assalamualaikum",
"auxiliadora",
"avengedsevenfold",
"ayamgoreng",
"babasonicos",
"balla4life",
"barriofino",
"bball4life",
"bebitalinda",
"bellissima",
"bendiciones",
"benfiquista",
"bestfriends4ever",
"bestfriendsforever",
"bienvenido",
"billandben",
"blackandwhite",
"blackeyedpeas",
"bobesponja",
"bobthebuilder",
"bomboncito",
"borreguito",
"boysoverflowers",
"bringmetolife",
"bustitbaby",
"cachorrita",
"cachorrito",
"cafetacuba",
"calculadora",
"californication",
"camiloteamo",
"candyland1",
"candyshop1",
"canttouchthis",
"caperucita",
"caprichosa",
"caradeperro",
"caranguejo",
"caricatura",
"caritadeangel",
"carteldesanta",
"castravete",
"catinthehat",
"catsanddogs",
"celticfc1888",
"cenicienta",
"chaparrita",
"chaparrito",
"charolastra",
"chicafresa",
"chikistrikis",
"chilindrina",
"chingatumadre",
"chiquititas",
"chocoholic",
"chris brown",
"chupachups",
"cintasejati",
"classof2004",
"classof2005",
"classof2006",
"classof2007",
"classof2008",
"classof2009",
"classof2010",
"classof2011",
"classof2012",
"computacion",
"comunicacion",
"confidencial",
"contabilidad",
"cookiesncream",
"corazondemelon",
"cositarica",
"cradleoffilth",
"crazysexycool",
"crepusculo",
"crisostomo",
"cristomeama",
"cristoteama",
"cristoteamo",
"cristovive",
"cualquiera",
"cualquiercosa",
"cuchurrumin",
"cymruambyth",
"daddyslilgirl",
"daddyslittlegirl",
"danitykane",
"daveyhavok",
"dcshoecousa",
"deportivocali",
"depredador",
"desiderata",
"dgenerationx",
"dimmuborgir",
"diosesbueno",
"diostebendiga",
"divalicious",
"dolcegabbana",
"dracomalfoy",
"dragosteamea",
"eatmyshorts",
"ecuatoriana",
"elamorapesta",
"elamordemivida",
"elamorduele",
"elamornoexiste",
"emperatriz",
"encantadia",
"enfermagem",
"enfermeria",
"ereselamordemivida",
"ereslomaximo",
"ereslomejor",
"eresmiamor",
"eresmivida",
"escritorio",
"espiritusanto",
"estadosunidos",
"estrelinha",
"estudiante",
"ewankosayo",
"extraterrestre",
"eyeshield21",
"fadetoblack",
"fergalicious",
"figueiredo",
"filadelfia",
"finisterra",
"fishandchips",
"flordeliza",
"flordeloto",
"floricienta",
"florinsalam",
"floripondia",
"foreverandever",
"frangipani",
"free2rhyme",
"fresasconcrema",
"frootloops",
"fuckevery1",
"fuckthepope",
"funinthesun",
"funkymunky",
"fushigiyugi",
"fushigiyuugi",
"gastronomia",
"gatitolindo",
"gearsofwar",
"gettherefast",
"girlygirl1",
"glorytogod",
"godschild1",
"gofuckyourself",
"goody2shoes",
"grandtheftauto",
"grenouille",
"gryffindor",
"gummybear1",
"gunsandroses",
"gunsnroses",
"habbohotel",
"hakunamatata",
"hannah montana",
"happygolucky",
"harry potter",
"hateitorloveit",
"haveaniceday",
"hello kitty",
"hindikoalam",
"hipopotamo",
"hocuspocus",
"holaatodos",
"holacomoestas",
"holaquetal",
"hollaback1",
"homeandaway",
"homesweethome",
"hoobastank",
"hotandsexy",
"hotmail.com",
"hotmail123",
"hugsandkisses",
"hugsnkisses",
"hunnibunni",
"hunterxhunter",
"i love you",
"i.love.you",
"i_love_you",
"iamwhatiam",
"ichliebedich",
"idontloveyou",
"ihatelife1",
"ihatemylife",
"ihave3kids",
"iheartyou!",
"iheartyou1",
"iheartyou2",
"ikawlamang",
"ikhouvanjou",
"illnevertell",
"ilove2dance",
"iloveboys!",
"iloveboys1",
"iloveboys2",
"ilovechrisbrown",
"ilovecody1",
"ilovedogs1",
"ilovejake1",
"ilovejose1",
"ilovejosh!",
"ilovejosh1",
"ilovekyle1",
"ilovemike!",
"ilovemyboo",
"ilovemycat",
"ilovemydad",
"ilovemydaddy",
"ilovemydog",
"ilovemyfriends",
"ilovemymom",
"ilovemymommy",
"ilovemymum",
"ilovemymummy",
"ilovemysister",
"ilovemyson",
"ilovenickjonas",
"ilovenoone",
"ilovepink1",
"iloveryan!",
"iloveryan1",
"ilovesome1",
"ilovethelord",
"ilovethisgame",
"ilovetodance",
"iloveusomuch",
"iloveyousomuch",
"ilovezacefron",
"iluv2dance",
"iluvu4ever",
"imprimanta",
"imthebest1",
"inalcanzable",
"indragostita",
"inframundo",
"inglaterra",
"ingoditrust",
"inmaculada",
"inolvidable",
"insaneclownposse",
"inspiracion",
"inteligencia",
"inteligente",
"invu4uraqt",
"ioriyagami",
"itsallaboutme",
"iubireamea",
"iwillsurvive",
"jabbawockeez",
"jackandjill",
"jamiroquai",
"jensenackles",
"jesusesamor",
"jigglypuff",
"joeyjordison",
"jogabonito",
"jonas brothers",
"joshgroban",
"juggalette",
"kagandahan",
"kaleidostar",
"keepitreal",
"keteimporta",
"kilometros",
"kimsamsoon",
"kingofkings",
"kmzwa8awaa",
"kumbiakings",
"kuvhlubkoj",
"lacramioara",
"lacunacoil",
"laffytaffy",
"lamaravilla",
"lamashermosa",
"laprincesita",
"larcenciel",
"lasdivinas",
"lavidaesbella",
"lavidaloca",
"leedongwook",
"leothelion",
"licenciada",
"lifegoeson",
"lifesabitch",
"linkin park",
"lipgloss12",
"literatura",
"livelaughlove",
"livelovelaugh",
"liveyourlife",
"lordoftherings",
"loserface1",
"losmejores",
"lotsoflove",
"loveandhate",
"loveandpeace",
"loveisintheair",
"lovemeorhateme",
"lovenkrands",
"loveofmylife",
"loveorhate",
"lovetolove",
"loveu4ever",
"loveydovey",
"loveyousomuch",
"luciernaga",
"luvme4ever",
"luzviminda",
"machupichu",
"madalinutza",
"mahal kita",
"mahalkokayo",
"mahalnamahalkita",
"makedonija",
"mamichula1",
"mapagmahal",
"maravillosa",
"maravilloso",
"mardecopas",
"mariadelcarmen",
"matrimonio",
"meamomucho",
"mejoresamigas",
"memyselfandi",
"meneketehe",
"mequieromucho",
"mercadotecnia",
"metamorfosis",
"miamorerestu",
"miamorteamo",
"mikeshinoda",
"milagritos",
"millonarios",
"mimamamemima",
"mimejoramiga",
"mirmodepon",
"mis3amores",
"misdosamores",
"misericordia",
"missthang1",
"miunicoamor",
"mividaerestu",
"mividaloca",
"mommasgirl",
"mommyanddaddy",
"monserrath",
"morethanwords",
"moscraciun",
"moulinrouge",
"msnhotmail",
"muiesteaua",
"mummyanddaddy",
"mummysgirl",
"musicislife",
"musicismylife",
"muthafucka",
"muñequita",
"mychemicalromance",
"mylittlepony",
"myonlylove",
"myslideshow",
"myspace.com",
"nabucodonosor",
"nascimento",
"nasigoreng",
"nebunatica",
"nepomuceno",
"neversaynever",
"nick jonas",
"nickjonas1",
"nistelrooy",
"nomeacuerdo",
"nomeolvides",
"nosequeponer",
"nuncateolvidare",
"nymphetamine",
"odontologia",
"ojosverdes",
"oneandonly",
"oneofakind",
"onetreehill",
"onomatopoeia",
"ositolindo",
"ositopanda",
"padrinosmagicos",
"painislove",
"pandalandia",
"panganiban",
"pangilinan",
"panicatthedisco",
"pantelimon",
"paralelepipedo",
"paralelipiped",
"parasiempre",
"pasawayako",
"pasodeblas",
"peace&love",
"peaceandlove",
"periwinkle",
"petewentz1",
"pimpmyride",
"pinkaholic",
"pinkandblue",
"playa4life",
"policarpio",
"politecnico",
"praisethelord",
"prettyinpink",
"prostituta",
"psicologia",
"psihologie",
"puccaygaru",
"punknotdead",
"pussinboots",
"queteimporta",
"quetzalcoatl",
"qwertyuiopasdfghjklzxcvbnm",
"recuerdame",
"resistencia",
"restinpeace",
"reymisterio619",
"reymysterio619",
"ricardoarjona",
"romeoyjulieta",
"rosesarered",
"rositafresita",
"rupertgrint",
"ryansheckler",
"ryomaechizen",
"sampaguita",
"sangreazul",
"sarangheyo",
"sassygirl1",
"sasukeuchiha",
"schokolade",
"sebasteamo",
"sectumsempra",
"semeolvido",
"seniseviyorum",
"sentimiento",
"sesshomaru",
"sesshoumaru",
"sexandthecity",
"sexonthebeach",
"sexymomma1",
"sexythang1",
"sexything1",
"shaggy2dope",
"shippuuden",
"shopaholic",
"showmethemoney",
"siemprejuntos",
"siempreteamare",
"simanjuntak",
"simplementeyo",
"sinterklaas",
"sk8er4life",
"skateordie",
"soloparami",
"soloparati",
"somostuyyo",
"souljaboy1",
"souljagirl",
"souljagurl",
"soyelmejor",
"soylamejor",
"soylomaximo",
"soylomejor",
"spongebobsquarepants",
"steauabucuresti",
"suankularb",
"subhanallah",
"sugarandspice",
"sugarnspice",
"superchica",
"superinggo",
"superpoderosa",
"supladitah",
"tamagotchi",
"taugammaphi",
"teamareporsiempre",
"teamaresiempre",
"teamarex100pre",
"teamarexsiempre",
"teamobebito",
"teamodemasiado",
"teamogordo",
"teamomiamor",
"teamomibebe",
"teamomivida",
"teamosoloati",
"teamotanto",
"teamox100pre",
"tecnologia",
"teextraño",
"teiubescmult",
"tekelomucho",
"tekelomuxo",
"tekieromucho",
"tekieromuxo",
"telecomanda",
"teletubbies",
"tenecesito",
"tengounamor",
"teolvidare",
"tequieromucho",
"tequieromuxo",
"tesigoamando",
"thaitanium",
"theblackparade",
"theoneandonly",
"theveronicas",
"thisismypassword",
"threedaysgrace",
"timbiriche",
"tinkywinky",
"titoelbambino",
"tivogliobene",
"todalavida",
"todocambio",
"todopoderoso",
"tohoshinki",
"tokio hotel",
"tomandjerry",
"tomwelling",
"trandafiri",
"trincheranorte",
"triskelion",
"tueresmiamor",
"tueresmivida",
"tumamacalata",
"tuttifrutti",
"tuyyox100pre",
"uchihasasuke",
"undermyskin",
"unforgetable",
"unodostres",
"vacaciones",
"valderrama",
"vatoslocos",
"verjaardag",
"vetealamierda",
"veterinaria",
"villacorta",
"vivaelrock",
"vivalaraza",
"vivalavida",
"vivelavida",
"webelongtogether",
"weezyfbaby",
"welcometomylife",
"whereisthelove",
"winniethepooh",
"wipemedown",
"wisinyandel",
"wisinyyandel",
"worldofwarcraft",
"yosoyelmejor",
"yosoylamejor",
"youcantseeme",
"yougotserved",
"yuyuhakusho",
"zonnebloem"
]

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,647 @@
// This is seperated because the password badlist section may become very long
pub static JSON_SYSTEM_CONFIG_V1: &'static str = r####"{
"attrs": {
"class": ["object", "system_config", "system"],
"uuid": ["00000000-0000-0000-0000-ffffff000027"],
"description": ["System (replicated) configuration options."],
"badlist_password": [
"demo_badlist_shohfie3aeci2oobur0aru9uushah6EiPi2woh4hohngoighaiRuepieN3ongoo1",
"100preteamare",
"14defebrero",
"1life1love",
"1life2live",
"1love1life",
"1love4life",
"212224236248",
"2813308004",
"2fast2furious",
"2gether4ever",
"2pacshakur",
"30secondstomars",
"3doorsdown",
"6cyclemind",
"<div><embed src=\\\\",
"@hotmail.com",
"@yahoo.com",
"Lets you update your FunNotes and more!",
"TEQUIEROMUCHO",
"TEXT ONLY AD",
"abretesesamo",
"administrador",
"aeropostale",
"akinkalang",
"akucintakamu",
"akusayangkamu",
"alfayomega",
"alhamdulillah",
"allaboutme",
"allahuakbar",
"alleyesonme",
"alquimista",
"alwaysandforever",
"amarteduele",
"amigas4ever",
"amigasporsiempre",
"amigasx100pre",
"amigasxsiempre",
"amoamifamilia",
"amordelbueno",
"amordemivida",
"amoresperros",
"amoreterno",
"amorimposible",
"amorporsiempre",
"amorprohibido",
"amorverdadero",
"amotemuito",
"anaranjado",
"angeldeamor",
"angellocsin",
"angelofdeath",
"anggandako",
"aniversario",
"apaixonada",
"apocalipsa",
"apocalipse",
"apocalipsis",
"apolinario",
"arquitectura",
"arrolladora",
"asieslavida",
"assalamualaikum",
"auxiliadora",
"avengedsevenfold",
"ayamgoreng",
"babasonicos",
"balla4life",
"barriofino",
"bball4life",
"bebitalinda",
"bellissima",
"bendiciones",
"benfiquista",
"bestfriends4ever",
"bestfriendsforever",
"bienvenido",
"billandben",
"blackandwhite",
"blackeyedpeas",
"bobesponja",
"bobthebuilder",
"bomboncito",
"borreguito",
"boysoverflowers",
"bringmetolife",
"bustitbaby",
"cachorrita",
"cachorrito",
"cafetacuba",
"calculadora",
"californication",
"camiloteamo",
"candyland1",
"candyshop1",
"canttouchthis",
"caperucita",
"caprichosa",
"caradeperro",
"caranguejo",
"caricatura",
"caritadeangel",
"carteldesanta",
"castravete",
"catinthehat",
"catsanddogs",
"celticfc1888",
"cenicienta",
"chaparrita",
"chaparrito",
"charolastra",
"chicafresa",
"chikistrikis",
"chilindrina",
"chingatumadre",
"chiquititas",
"chocoholic",
"chris brown",
"chupachups",
"cintasejati",
"classof2004",
"classof2005",
"classof2006",
"classof2007",
"classof2008",
"classof2009",
"classof2010",
"classof2011",
"classof2012",
"computacion",
"comunicacion",
"confidencial",
"contabilidad",
"cookiesncream",
"corazondemelon",
"cositarica",
"cradleoffilth",
"crazysexycool",
"crepusculo",
"crisostomo",
"cristomeama",
"cristoteama",
"cristoteamo",
"cristovive",
"cualquiera",
"cualquiercosa",
"cuchurrumin",
"cymruambyth",
"daddyslilgirl",
"daddyslittlegirl",
"danitykane",
"daveyhavok",
"dcshoecousa",
"deportivocali",
"depredador",
"desiderata",
"dgenerationx",
"dimmuborgir",
"diosesbueno",
"diostebendiga",
"divalicious",
"dolcegabbana",
"dracomalfoy",
"dragosteamea",
"eatmyshorts",
"ecuatoriana",
"elamorapesta",
"elamordemivida",
"elamorduele",
"elamornoexiste",
"emperatriz",
"encantadia",
"enfermagem",
"enfermeria",
"ereselamordemivida",
"ereslomaximo",
"ereslomejor",
"eresmiamor",
"eresmivida",
"escritorio",
"espiritusanto",
"estadosunidos",
"estrelinha",
"estudiante",
"ewankosayo",
"extraterrestre",
"eyeshield21",
"fadetoblack",
"fergalicious",
"figueiredo",
"filadelfia",
"finisterra",
"fishandchips",
"flordeliza",
"flordeloto",
"floricienta",
"florinsalam",
"floripondia",
"foreverandever",
"frangipani",
"free2rhyme",
"fresasconcrema",
"frootloops",
"fuckevery1",
"fuckthepope",
"funinthesun",
"funkymunky",
"fushigiyugi",
"fushigiyuugi",
"gastronomia",
"gatitolindo",
"gearsofwar",
"gettherefast",
"girlygirl1",
"glorytogod",
"godschild1",
"gofuckyourself",
"goody2shoes",
"grandtheftauto",
"grenouille",
"gryffindor",
"gummybear1",
"gunsandroses",
"gunsnroses",
"habbohotel",
"hakunamatata",
"hannah montana",
"happygolucky",
"harry potter",
"hateitorloveit",
"haveaniceday",
"hello kitty",
"hindikoalam",
"hipopotamo",
"hocuspocus",
"holaatodos",
"holacomoestas",
"holaquetal",
"hollaback1",
"homeandaway",
"homesweethome",
"hoobastank",
"hotandsexy",
"hotmail.com",
"hotmail123",
"hugsandkisses",
"hugsnkisses",
"hunnibunni",
"hunterxhunter",
"i love you",
"i.love.you",
"i_love_you",
"iamwhatiam",
"ichliebedich",
"idontloveyou",
"ihatelife1",
"ihatemylife",
"ihave3kids",
"iheartyou!",
"iheartyou1",
"iheartyou2",
"ikawlamang",
"ikhouvanjou",
"illnevertell",
"ilove2dance",
"iloveboys!",
"iloveboys1",
"iloveboys2",
"ilovechrisbrown",
"ilovecody1",
"ilovedogs1",
"ilovejake1",
"ilovejose1",
"ilovejosh!",
"ilovejosh1",
"ilovekyle1",
"ilovemike!",
"ilovemyboo",
"ilovemycat",
"ilovemydad",
"ilovemydaddy",
"ilovemydog",
"ilovemyfriends",
"ilovemymom",
"ilovemymommy",
"ilovemymum",
"ilovemymummy",
"ilovemysister",
"ilovemyson",
"ilovenickjonas",
"ilovenoone",
"ilovepink1",
"iloveryan!",
"iloveryan1",
"ilovesome1",
"ilovethelord",
"ilovethisgame",
"ilovetodance",
"iloveusomuch",
"iloveyousomuch",
"ilovezacefron",
"iluv2dance",
"iluvu4ever",
"imprimanta",
"imthebest1",
"inalcanzable",
"indragostita",
"inframundo",
"inglaterra",
"ingoditrust",
"inmaculada",
"inolvidable",
"insaneclownposse",
"inspiracion",
"inteligencia",
"inteligente",
"invu4uraqt",
"ioriyagami",
"itsallaboutme",
"iubireamea",
"iwillsurvive",
"jabbawockeez",
"jackandjill",
"jamiroquai",
"jensenackles",
"jesusesamor",
"jigglypuff",
"joeyjordison",
"jogabonito",
"jonas brothers",
"joshgroban",
"juggalette",
"kagandahan",
"kaleidostar",
"keepitreal",
"keteimporta",
"kilometros",
"kimsamsoon",
"kingofkings",
"kmzwa8awaa",
"kumbiakings",
"kuvhlubkoj",
"lacramioara",
"lacunacoil",
"laffytaffy",
"lamaravilla",
"lamashermosa",
"laprincesita",
"larcenciel",
"lasdivinas",
"lavidaesbella",
"lavidaloca",
"leedongwook",
"leothelion",
"licenciada",
"lifegoeson",
"lifesabitch",
"linkin park",
"lipgloss12",
"literatura",
"livelaughlove",
"livelovelaugh",
"liveyourlife",
"lordoftherings",
"loserface1",
"losmejores",
"lotsoflove",
"loveandhate",
"loveandpeace",
"loveisintheair",
"lovemeorhateme",
"lovenkrands",
"loveofmylife",
"loveorhate",
"lovetolove",
"loveu4ever",
"loveydovey",
"loveyousomuch",
"luciernaga",
"luvme4ever",
"luzviminda",
"machupichu",
"madalinutza",
"mahal kita",
"mahalkokayo",
"mahalnamahalkita",
"makedonija",
"mamichula1",
"mapagmahal",
"maravillosa",
"maravilloso",
"mardecopas",
"mariadelcarmen",
"matrimonio",
"meamomucho",
"mejoresamigas",
"memyselfandi",
"meneketehe",
"mequieromucho",
"mercadotecnia",
"metamorfosis",
"miamorerestu",
"miamorteamo",
"mikeshinoda",
"milagritos",
"millonarios",
"mimamamemima",
"mimejoramiga",
"mirmodepon",
"mis3amores",
"misdosamores",
"misericordia",
"missthang1",
"miunicoamor",
"mividaerestu",
"mividaloca",
"mommasgirl",
"mommyanddaddy",
"monserrath",
"morethanwords",
"moscraciun",
"moulinrouge",
"msnhotmail",
"muiesteaua",
"mummyanddaddy",
"mummysgirl",
"musicislife",
"musicismylife",
"muthafucka",
"muñequita",
"mychemicalromance",
"mylittlepony",
"myonlylove",
"myslideshow",
"myspace.com",
"nabucodonosor",
"nascimento",
"nasigoreng",
"nebunatica",
"nepomuceno",
"neversaynever",
"nick jonas",
"nickjonas1",
"nistelrooy",
"nomeacuerdo",
"nomeolvides",
"nosequeponer",
"nuncateolvidare",
"nymphetamine",
"odontologia",
"ojosverdes",
"oneandonly",
"oneofakind",
"onetreehill",
"onomatopoeia",
"ositolindo",
"ositopanda",
"padrinosmagicos",
"painislove",
"pandalandia",
"panganiban",
"pangilinan",
"panicatthedisco",
"pantelimon",
"paralelepipedo",
"paralelipiped",
"parasiempre",
"pasawayako",
"pasodeblas",
"peace&love",
"peaceandlove",
"periwinkle",
"petewentz1",
"pimpmyride",
"pinkaholic",
"pinkandblue",
"playa4life",
"policarpio",
"politecnico",
"praisethelord",
"prettyinpink",
"prostituta",
"psicologia",
"psihologie",
"puccaygaru",
"punknotdead",
"pussinboots",
"queteimporta",
"quetzalcoatl",
"qwertyuiopasdfghjklzxcvbnm",
"recuerdame",
"resistencia",
"restinpeace",
"reymisterio619",
"reymysterio619",
"ricardoarjona",
"romeoyjulieta",
"rosesarered",
"rositafresita",
"rupertgrint",
"ryansheckler",
"ryomaechizen",
"sampaguita",
"sangreazul",
"sarangheyo",
"sassygirl1",
"sasukeuchiha",
"schokolade",
"sebasteamo",
"sectumsempra",
"semeolvido",
"seniseviyorum",
"sentimiento",
"sesshomaru",
"sesshoumaru",
"sexandthecity",
"sexonthebeach",
"sexymomma1",
"sexythang1",
"sexything1",
"shaggy2dope",
"shippuuden",
"shopaholic",
"showmethemoney",
"siemprejuntos",
"siempreteamare",
"simanjuntak",
"simplementeyo",
"sinterklaas",
"sk8er4life",
"skateordie",
"soloparami",
"soloparati",
"somostuyyo",
"souljaboy1",
"souljagirl",
"souljagurl",
"soyelmejor",
"soylamejor",
"soylomaximo",
"soylomejor",
"spongebobsquarepants",
"steauabucuresti",
"suankularb",
"subhanallah",
"sugarandspice",
"sugarnspice",
"superchica",
"superinggo",
"superpoderosa",
"supladitah",
"tamagotchi",
"taugammaphi",
"teamareporsiempre",
"teamaresiempre",
"teamarex100pre",
"teamarexsiempre",
"teamobebito",
"teamodemasiado",
"teamogordo",
"teamomiamor",
"teamomibebe",
"teamomivida",
"teamosoloati",
"teamotanto",
"teamox100pre",
"tecnologia",
"teextraño",
"teiubescmult",
"tekelomucho",
"tekelomuxo",
"tekieromucho",
"tekieromuxo",
"telecomanda",
"teletubbies",
"tenecesito",
"tengounamor",
"teolvidare",
"tequieromucho",
"tequieromuxo",
"tesigoamando",
"thaitanium",
"theblackparade",
"theoneandonly",
"theveronicas",
"thisismypassword",
"threedaysgrace",
"timbiriche",
"tinkywinky",
"titoelbambino",
"tivogliobene",
"todalavida",
"todocambio",
"todopoderoso",
"tohoshinki",
"tokio hotel",
"tomandjerry",
"tomwelling",
"trandafiri",
"trincheranorte",
"triskelion",
"tueresmiamor",
"tueresmivida",
"tumamacalata",
"tuttifrutti",
"tuyyox100pre",
"uchihasasuke",
"undermyskin",
"unforgetable",
"unodostres",
"vacaciones",
"valderrama",
"vatoslocos",
"verjaardag",
"vetealamierda",
"veterinaria",
"villacorta",
"vivaelrock",
"vivalaraza",
"vivalavida",
"vivelavida",
"webelongtogether",
"weezyfbaby",
"welcometomylife",
"whereisthelove",
"winniethepooh",
"wipemedown",
"wisinyandel",
"wisinyyandel",
"worldofwarcraft",
"yosoyelmejor",
"yosoylamejor",
"youcantseeme",
"yougotserved",
"yuyuhakusho",
"zonnebloem"
]
}
}"####;

View file

@ -43,6 +43,16 @@ macro_rules! try_from_entry {
.get_ava_single_credential("primary_credential") .get_ava_single_credential("primary_credential")
.map(|v| v.clone()); .map(|v| v.clone());
let spn = $value
.get_ava_single("spn")
.map(|s| {
debug_assert!(s.is_spn());
s.to_proto_string_clone()
})
.ok_or(OperationError::InvalidAccountState(
"Missing attribute: spn".to_string(),
))?;
// Resolved by the caller // Resolved by the caller
let groups = $groups; let groups = $groups;
@ -54,6 +64,7 @@ macro_rules! try_from_entry {
displayname: displayname, displayname: displayname,
groups: groups, groups: groups,
primary: primary, primary: primary,
spn: spn,
}) })
}}; }};
} }
@ -74,6 +85,10 @@ pub(crate) struct Account {
// primary: Credential // primary: Credential
// app_creds: Vec<Credential> // app_creds: Vec<Credential>
// account expiry? (as opposed to cred expiry) // account expiry? (as opposed to cred expiry)
pub spn: String,
// TODO: When you add mail, you should update the check to zxcvbn
// to include these.
// pub mail: Vec<String>
} }
impl Account { impl Account {
@ -170,17 +185,13 @@ impl Account {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::constants::JSON_ANONYMOUS_V1; use crate::constants::JSON_ANONYMOUS_V1;
use crate::entry::{Entry, EntryNew, EntryValid}; // use crate::entry::{Entry, EntryNew, EntryValid};
use crate::idm::account::Account; // use crate::idm::account::Account;
#[test] #[test]
fn test_idm_account_from_anonymous() { fn test_idm_account_from_anonymous() {
let anon_e: Entry<EntryValid, EntryNew> = let anon_e = entry_str_to_account!(JSON_ANONYMOUS_V1);
unsafe { Entry::unsafe_from_entry_str(JSON_ANONYMOUS_V1).to_valid_new() }; println!("{:?}", anon_e);
let anon_e = unsafe { anon_e.to_valid_committed() };
let anon_account = Account::try_from_entry_no_groups(anon_e).expect("Must not fail");
println!("{:?}", anon_account);
// I think that's it? we may want to check anonymous mech ... // I think that's it? we may want to check anonymous mech ...
} }

View file

@ -1,12 +1,19 @@
#[cfg(test)] #[cfg(test)]
macro_rules! entry_str_to_account { macro_rules! entry_str_to_account {
($entry_str:expr) => {{ ($entry_str:expr) => {{
use crate::entry::{Entry, EntryNew, EntryValid}; use crate::entry::{Entry, EntryInvalid, EntryNew};
use crate::idm::account::Account; use crate::idm::account::Account;
use crate::value::Value;
let e: Entry<EntryValid, EntryNew> = let mut e: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str($entry_str);
unsafe { Entry::unsafe_from_entry_str($entry_str).to_valid_new() }; // Add spn, because normally this is generated but in tests we can't.
let e = unsafe { e.to_valid_committed() }; let spn = e
.get_ava_single_str("name")
.map(|s| Value::new_spn_str(s, "example.com"))
.expect("Failed to munge spn from name!");
e.set_avas("spn", vec![spn]);
let e = unsafe { e.to_valid_new().to_valid_committed() };
Account::try_from_entry_no_groups(e).expect("Account conversion failure") Account::try_from_entry_no_groups(e).expect("Account conversion failure")
}}; }};

View file

@ -1,5 +1,6 @@
use crate::audit::AuditScope; use crate::audit::AuditScope;
use crate::constants::AUTH_SESSION_TIMEOUT; use crate::constants::UUID_SYSTEM_CONFIG;
use crate::constants::{AUTH_SESSION_TIMEOUT, PW_MIN_LENGTH};
use crate::event::{AuthEvent, AuthEventStep, AuthResult}; use crate::event::{AuthEvent, AuthEventStep, AuthResult};
use crate::idm::account::Account; use crate::idm::account::Account;
use crate::idm::authsession::AuthSession; use crate::idm::authsession::AuthSession;
@ -20,6 +21,7 @@ use concread::cowcell::{CowCell, CowCellWriteTxn};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::time::Duration; use std::time::Duration;
use uuid::Uuid; use uuid::Uuid;
use zxcvbn;
pub struct IdmServer { pub struct IdmServer {
// There is a good reason to keep this single thread - it // There is a good reason to keep this single thread - it
@ -252,14 +254,67 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
return Err(OperationError::SystemProtectedObject); return Err(OperationError::SystemProtectedObject);
} }
// TODO: Is it a security issue to reveal pw policy checks BEFORE permission is // Question: Is it a security issue to reveal pw policy checks BEFORE permission is
// determined over the credential modification? // determined over the credential modification?
// //
// I don't think so - because we should only be showing how STRONG the pw is ... // I don't think so - because we should only be showing how STRONG the pw is ...
// is the password long enough? // password strength and badlisting is always global, rather than per-pw-policy.
// pw-policy as check on the account is about requirements for mfa for example.
//
// check a password badlist // is the password at least 10 char?
if pce.cleartext.len() < PW_MIN_LENGTH {
return Err(OperationError::PasswordTooShort(PW_MIN_LENGTH));
}
// does the password pass zxcvbn?
// Get related inputs, such as account name, email, etc.
let related: Vec<&str> = vec![
account.name.as_str(),
account.displayname.as_str(),
account.spn.as_str(),
];
let entropy = try_audit!(
au,
zxcvbn::zxcvbn(pce.cleartext.as_str(), related.as_slice())
.map_err(|_| OperationError::PasswordEmpty)
);
// check account pwpolicy (for 3 or 4)? Do we need pw strength beyond this
// or should we be enforcing mfa instead
if entropy.score() < 3 {
// The password is too week as per:
// https://docs.rs/zxcvbn/2.0.0/zxcvbn/struct.Entropy.html
let feedback: zxcvbn::feedback::Feedback = entropy
.feedback()
.as_ref()
.ok_or(OperationError::InvalidState)
.map(|v| v.clone())
.map_err(|e| {
audit_log!(au, "zxcvbn returned no feedback when score < 3");
e
})?;
audit_log!(au, "pw feedback -> {:?}", feedback);
// return Err(OperationError::PasswordTooWeak(feedback))
return Err(OperationError::PasswordTooWeak);
}
// check a password badlist to eliminate more content
// we check the password as "lower case" to help eliminate possibilities
let lc_password = PartialValue::new_iutf8s(pce.cleartext.as_str());
let badlist_entry = try_audit!(
au,
self.qs_write.internal_search_uuid(au, &UUID_SYSTEM_CONFIG)
);
if badlist_entry.attribute_value_pres("badlist_password", &lc_password) {
audit_log!(au, "Password found in badlist, rejecting");
return Err(OperationError::PasswordBadListed);
}
// it returns a modify // it returns a modify
let modlist = try_audit!( let modlist = try_audit!(
@ -737,4 +792,37 @@ mod tests {
assert!(r1 == tok_r.secret); assert!(r1 == tok_r.secret);
}) })
} }
#[test]
fn test_idm_simple_password_reject_weak() {
run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| {
// len check
let mut idms_prox_write = idms.proxy_write();
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "password", None);
let e = idms_prox_write.set_account_password(au, &pce);
assert!(e.is_err());
// zxcvbn check
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "password1234", None);
let e = idms_prox_write.set_account_password(au, &pce);
assert!(e.is_err());
// Check the "name" checking works too (I think admin may hit a common pw rule first)
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, "admin_nta", None);
let e = idms_prox_write.set_account_password(au, &pce);
assert!(e.is_err());
// Check that the demo badlist password is rejected.
let pce = PasswordChangeEvent::new_internal(
&UUID_ADMIN,
"demo_badlist_shohfie3aeci2oobur0aru9uushah6EiPi2woh4hohngoighaiRuepieN3ongoo1",
None,
);
let e = idms_prox_write.set_account_password(au, &pce);
assert!(e.is_err());
assert!(idms_prox_write.commit(au).is_ok());
})
}
} }

View file

@ -26,16 +26,21 @@ lazy_static! {
m.insert("may"); m.insert("may");
// Allow modification of some domain info types for local configuration. // Allow modification of some domain info types for local configuration.
m.insert("domain_ssid"); m.insert("domain_ssid");
m.insert("badlist_password");
m m
}; };
static ref PVCLASS_SYSTEM: PartialValue = PartialValue::new_class("system"); static ref PVCLASS_SYSTEM: PartialValue = PartialValue::new_class("system");
static ref PVCLASS_TOMBSTONE: PartialValue = PartialValue::new_class("tombstone"); static ref PVCLASS_TOMBSTONE: PartialValue = PartialValue::new_class("tombstone");
static ref PVCLASS_RECYCLED: PartialValue = PartialValue::new_class("recycled"); static ref PVCLASS_RECYCLED: PartialValue = PartialValue::new_class("recycled");
static ref PVCLASS_DOMAIN_INFO: PartialValue = PartialValue::new_class("domain_info"); static ref PVCLASS_DOMAIN_INFO: PartialValue = PartialValue::new_class("domain_info");
static ref PVCLASS_SYSTEM_INFO: PartialValue = PartialValue::new_class("system_info");
static ref PVCLASS_SYSTEM_CONFIG: PartialValue = PartialValue::new_class("system_config");
static ref VCLASS_SYSTEM: Value = Value::new_class("system"); static ref VCLASS_SYSTEM: Value = Value::new_class("system");
static ref VCLASS_TOMBSTONE: Value = Value::new_class("tombstone"); static ref VCLASS_TOMBSTONE: Value = Value::new_class("tombstone");
static ref VCLASS_RECYCLED: Value = Value::new_class("recycled"); static ref VCLASS_RECYCLED: Value = Value::new_class("recycled");
static ref VCLASS_DOMAIN_INFO: Value = Value::new_class("domain_info"); static ref VCLASS_DOMAIN_INFO: Value = Value::new_class("domain_info");
static ref VCLASS_SYSTEM_INFO: Value = Value::new_class("system_info");
static ref VCLASS_SYSTEM_CONFIG: Value = Value::new_class("system_config");
} }
impl Plugin for Protected { impl Plugin for Protected {
@ -63,6 +68,8 @@ impl Plugin for Protected {
Ok(_) => { Ok(_) => {
if cand.attribute_value_pres("class", &PVCLASS_SYSTEM) if cand.attribute_value_pres("class", &PVCLASS_SYSTEM)
|| cand.attribute_value_pres("class", &PVCLASS_DOMAIN_INFO) || cand.attribute_value_pres("class", &PVCLASS_DOMAIN_INFO)
|| cand.attribute_value_pres("class", &PVCLASS_SYSTEM_INFO)
|| cand.attribute_value_pres("class", &PVCLASS_SYSTEM_CONFIG)
|| cand.attribute_value_pres("class", &PVCLASS_TOMBSTONE) || cand.attribute_value_pres("class", &PVCLASS_TOMBSTONE)
|| cand.attribute_value_pres("class", &PVCLASS_RECYCLED) || cand.attribute_value_pres("class", &PVCLASS_RECYCLED)
{ {
@ -99,6 +106,8 @@ impl Plugin for Protected {
if a == "class" if a == "class"
&& (v == &(VCLASS_SYSTEM.clone()) && (v == &(VCLASS_SYSTEM.clone())
|| v == &(VCLASS_DOMAIN_INFO.clone()) || v == &(VCLASS_DOMAIN_INFO.clone())
|| v == &(VCLASS_SYSTEM_INFO.clone())
|| v == &(VCLASS_SYSTEM_CONFIG.clone())
|| v == &(VCLASS_TOMBSTONE.clone()) || v == &(VCLASS_TOMBSTONE.clone())
|| v == &(VCLASS_RECYCLED.clone())) || v == &(VCLASS_RECYCLED.clone()))
{ {
@ -183,6 +192,8 @@ impl Plugin for Protected {
Ok(_) => { Ok(_) => {
if cand.attribute_value_pres("class", &PVCLASS_SYSTEM) if cand.attribute_value_pres("class", &PVCLASS_SYSTEM)
|| cand.attribute_value_pres("class", &PVCLASS_DOMAIN_INFO) || cand.attribute_value_pres("class", &PVCLASS_DOMAIN_INFO)
|| cand.attribute_value_pres("class", &PVCLASS_SYSTEM_INFO)
|| cand.attribute_value_pres("class", &PVCLASS_SYSTEM_CONFIG)
|| cand.attribute_value_pres("class", &PVCLASS_TOMBSTONE) || cand.attribute_value_pres("class", &PVCLASS_TOMBSTONE)
|| cand.attribute_value_pres("class", &PVCLASS_RECYCLED) || cand.attribute_value_pres("class", &PVCLASS_RECYCLED)
{ {

View file

@ -1631,12 +1631,14 @@ impl<'a> QueryServerWriteTransaction<'a> {
JSON_SCHEMA_ATTR_DOMAIN_UUID, JSON_SCHEMA_ATTR_DOMAIN_UUID,
JSON_SCHEMA_ATTR_DOMAIN_SSID, JSON_SCHEMA_ATTR_DOMAIN_SSID,
JSON_SCHEMA_ATTR_GIDNUMBER, JSON_SCHEMA_ATTR_GIDNUMBER,
JSON_SCHEMA_ATTR_BADLIST_PASSWORD,
JSON_SCHEMA_CLASS_PERSON, JSON_SCHEMA_CLASS_PERSON,
JSON_SCHEMA_CLASS_GROUP, JSON_SCHEMA_CLASS_GROUP,
JSON_SCHEMA_CLASS_ACCOUNT, JSON_SCHEMA_CLASS_ACCOUNT,
JSON_SCHEMA_CLASS_DOMAIN_INFO, JSON_SCHEMA_CLASS_DOMAIN_INFO,
JSON_SCHEMA_CLASS_POSIXACCOUNT, JSON_SCHEMA_CLASS_POSIXACCOUNT,
JSON_SCHEMA_CLASS_POSIXGROUP, JSON_SCHEMA_CLASS_POSIXGROUP,
JSON_SCHEMA_CLASS_SYSTEM_CONFIG,
]; ];
let mut audit_si = AuditScope::new("start_initialise_schema_idm"); let mut audit_si = AuditScope::new("start_initialise_schema_idm");
@ -1660,7 +1662,10 @@ impl<'a> QueryServerWriteTransaction<'a> {
let mut audit_an = AuditScope::new("start_system_core_items"); let mut audit_an = AuditScope::new("start_system_core_items");
let res = self let res = self
.internal_assert_or_create_str(&mut audit_an, JSON_SYSTEM_INFO_V1) .internal_assert_or_create_str(&mut audit_an, JSON_SYSTEM_INFO_V1)
.and_then(|_| self.internal_migrate_or_create_str(&mut audit_an, JSON_DOMAIN_INFO_V1)); .and_then(|_| self.internal_migrate_or_create_str(&mut audit_an, JSON_DOMAIN_INFO_V1))
.and_then(|_| {
self.internal_migrate_or_create_str(&mut audit_an, JSON_SYSTEM_CONFIG_V1)
});
audit.append_scope(audit_an); audit.append_scope(audit_an);
audit_log!(audit, "initialise_idm p1 -> result {:?}", res); audit_log!(audit, "initialise_idm p1 -> result {:?}", res);
debug_assert!(res.is_ok()); debug_assert!(res.is_ok());
@ -1743,6 +1748,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
JSON_IDM_ACP_SCHEMA_WRITE_CLASSES_PRIV_V1, JSON_IDM_ACP_SCHEMA_WRITE_CLASSES_PRIV_V1,
JSON_IDM_ACP_ACP_MANAGE_PRIV_V1, JSON_IDM_ACP_ACP_MANAGE_PRIV_V1,
JSON_IDM_ACP_DOMAIN_ADMIN_PRIV_V1, JSON_IDM_ACP_DOMAIN_ADMIN_PRIV_V1,
JSON_IDM_ACP_SYSTEM_CONFIG_PRIV_V1,
]; ];
let res: Result<(), _> = idm_entries let res: Result<(), _> = idm_entries