use super::super::super::{
comment::shouldbespace,
common::commas,
ending,
error::{expect_tag_no_case, expected},
literal::{ident, strand, strand::strand_raw},
part::base,
IResult, ParseError,
};
use crate::{iam::Role, statements::DefineUserStatement, Ident, Strand};
use argon2::{password_hash::SaltString, Argon2, PasswordHasher};
use nom::{
branch::alt,
bytes::complete::tag_no_case,
combinator::cut,
multi::{many0, separated_list1},
Err,
};
use rand::{distributions::Alphanumeric, rngs::OsRng, Rng};
pub fn user(i: &str) -> IResult<&str, DefineUserStatement> {
let (i, _) = tag_no_case("USER")(i)?;
let (i, _) = shouldbespace(i)?;
let (i, (name, base, opts)) = cut(|i| {
let (i, name) = ident(i)?;
let (i, _) = shouldbespace(i)?;
let (i, _) = expect_tag_no_case("ON")(i)?;
let (i, _) = shouldbespace(i)?;
let (i, base) = base(i)?;
let (i, opts) = user_opts(i)?;
let (i, _) = expected("PASSWORD, PASSHASH, ROLES, or COMMENT", ending::query)(i)?;
Ok((i, (name, base, opts)))
})(i)?;
let mut res = DefineUserStatement {
name,
base,
roles: vec!["Viewer".into()], code: rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(128)
.map(char::from)
.collect::<String>(),
..Default::default()
};
for opt in opts {
match opt {
DefineUserOption::Password(v) => {
res.hash = Argon2::default()
.hash_password(v.as_ref(), &SaltString::generate(&mut OsRng))
.unwrap()
.to_string()
}
DefineUserOption::Passhash(v) => {
res.hash = v;
}
DefineUserOption::Roles(v) => {
res.roles = v;
}
DefineUserOption::Comment(v) => {
res.comment = Some(v);
}
}
}
Ok((i, res))
}
enum DefineUserOption {
Password(String),
Passhash(String),
Roles(Vec<Ident>),
Comment(Strand),
}
fn user_opts(i: &str) -> IResult<&str, Vec<DefineUserOption>> {
many0(alt((user_pass, user_hash, user_roles, user_comment)))(i)
}
fn user_pass(i: &str) -> IResult<&str, DefineUserOption> {
let (i, _) = shouldbespace(i)?;
let (i, _) = tag_no_case("PASSWORD")(i)?;
let (i, _) = shouldbespace(i)?;
let (i, v) = cut(strand_raw)(i)?;
Ok((i, DefineUserOption::Password(v)))
}
fn user_hash(i: &str) -> IResult<&str, DefineUserOption> {
let (i, _) = shouldbespace(i)?;
let (i, _) = tag_no_case("PASSHASH")(i)?;
let (i, _) = shouldbespace(i)?;
let (i, v) = cut(strand_raw)(i)?;
Ok((i, DefineUserOption::Passhash(v)))
}
fn user_comment(i: &str) -> IResult<&str, DefineUserOption> {
let (i, _) = shouldbespace(i)?;
let (i, _) = tag_no_case("COMMENT")(i)?;
let (i, _) = shouldbespace(i)?;
let (i, v) = cut(strand)(i)?;
Ok((i, DefineUserOption::Comment(v)))
}
fn user_roles(i: &str) -> IResult<&str, DefineUserOption> {
let (i, _) = shouldbespace(i)?;
let (i, _) = tag_no_case("ROLES")(i)?;
let (i, _) = shouldbespace(i)?;
let (i, roles) = separated_list1(commas, |i| {
let (i, v) = cut(ident)(i)?;
v.as_str().parse::<Role>().map_err(|_| Err::Failure(ParseError::Role(i, v.to_string())))?;
Ok((i, v))
})(i)?;
Ok((i, DefineUserOption::Roles(roles)))
}