Cleanup and improve client error handling

This commit is contained in:
William Brown 2020-08-01 20:31:05 +10:00 committed by Firstyear
parent c4805d2915
commit 217e3455a2
19 changed files with 532 additions and 292 deletions

View file

@ -23,7 +23,7 @@ impl KanidmAsyncClient {
// format doesn't work in async ?!
// let dest = format!("{}{}", self.addr, dest);
let req_string = serde_json::to_string(&request).unwrap();
let req_string = serde_json::to_string(&request).map_err(ClientError::JSONEncode)?;
let response = self
.client
@ -34,21 +34,28 @@ impl KanidmAsyncClient {
.await
.map_err(ClientError::Transport)?;
let opid = response.headers().get(KOPID);
debug!(
"opid -> {:?}",
opid.expect("Missing opid? Refusing to proceed ...")
);
let opid = response
.headers()
.get(KOPID)
.and_then(|hv| hv.to_str().ok().map(|s| s.to_string()))
.unwrap_or_else(|| "missing_kopid".to_string());
debug!("opid -> {:?}", opid);
match response.status() {
reqwest::StatusCode::OK => {}
unexpect => return Err(ClientError::Http(unexpect, response.json().await.ok())),
unexpect => {
return Err(ClientError::Http(
unexpect,
response.json().await.ok(),
opid,
))
}
}
// TODO #253: What about errors
let r: T = response.json().await.unwrap();
Ok(r)
response
.json()
.await
.map_err(|e| ClientError::JSONDecode(e, opid))
}
async fn perform_put_request<R: Serialize, T: DeserializeOwned>(
@ -61,7 +68,7 @@ impl KanidmAsyncClient {
// format doesn't work in async ?!
// let dest = format!("{}{}", self.addr, dest);
let req_string = serde_json::to_string(&request).unwrap();
let req_string = serde_json::to_string(&request).map_err(ClientError::JSONEncode)?;
let response = self
.client
@ -72,21 +79,29 @@ impl KanidmAsyncClient {
.await
.map_err(ClientError::Transport)?;
let opid = response.headers().get(KOPID);
debug!(
"opid -> {:?}",
opid.expect("Missing opid? Refusing to proceed ...")
);
let opid = response
.headers()
.get(KOPID)
.and_then(|hv| hv.to_str().ok().map(|s| s.to_string()))
.unwrap_or_else(|| "missing_kopid".to_string());
debug!("opid -> {:?}", opid);
match response.status() {
reqwest::StatusCode::OK => {}
unexpect => return Err(ClientError::Http(unexpect, response.json().await.ok())),
unexpect => {
return Err(ClientError::Http(
unexpect,
response.json().await.ok(),
opid,
))
}
}
// TODO #253: What about errors
let r: T = response.json().await.unwrap();
Ok(r)
response
.json()
.await
.map_err(|e| ClientError::JSONDecode(e, opid))
}
async fn perform_get_request<T: DeserializeOwned>(&self, dest: &str) -> Result<T, ClientError> {
@ -100,21 +115,29 @@ impl KanidmAsyncClient {
.await
.map_err(ClientError::Transport)?;
let opid = response.headers().get(KOPID);
debug!(
"opid -> {:?}",
opid.expect("Missing opid? Refusing to proceed ...")
);
let opid = response
.headers()
.get(KOPID)
.and_then(|hv| hv.to_str().ok().map(|s| s.to_string()))
.unwrap_or_else(|| "missing_kopid".to_string());
debug!("opid -> {:?}", opid);
match response.status() {
reqwest::StatusCode::OK => {}
unexpect => return Err(ClientError::Http(unexpect, response.json().await.ok())),
unexpect => {
return Err(ClientError::Http(
unexpect,
response.json().await.ok(),
opid,
))
}
}
// TODO #253: What about errors
let r: T = response.json().await.unwrap();
Ok(r)
response
.json()
.await
.map_err(|e| ClientError::JSONDecode(e, opid))
}
async fn perform_delete_request(&self, dest: &str) -> Result<bool, ClientError> {
@ -126,20 +149,28 @@ impl KanidmAsyncClient {
.await
.map_err(ClientError::Transport)?;
let opid = response.headers().get(KOPID);
debug!(
"opid -> {:?}",
opid.expect("Missing opid? Refusing to proceed ...")
);
let opid = response
.headers()
.get(KOPID)
.and_then(|hv| hv.to_str().ok().map(|s| s.to_string()))
.unwrap_or_else(|| "missing_kopid".to_string());
debug!("opid -> {:?}", opid);
match response.status() {
reqwest::StatusCode::OK => {}
unexpect => return Err(ClientError::Http(unexpect, response.json().await.ok())),
unexpect => {
return Err(ClientError::Http(
unexpect,
response.json().await.ok(),
opid,
))
}
}
let r: bool = response.json().await.unwrap();
Ok(r)
response
.json()
.await
.map_err(|e| ClientError::JSONDecode(e, opid))
}
pub async fn auth_step_init(&self, ident: &str) -> Result<AuthState, ClientError> {
@ -205,17 +236,37 @@ impl KanidmAsyncClient {
let whoami_dest = [self.addr.as_str(), "/v1/self"].concat();
// format!("{}/v1/self", self.addr);
debug!("{:?}", whoami_dest);
let response = self.client.get(whoami_dest.as_str()).send().await.unwrap();
let response = self
.client
.get(whoami_dest.as_str())
.send()
.await
.map_err(ClientError::Transport)?;
let opid = response
.headers()
.get(KOPID)
.and_then(|hv| hv.to_str().ok().map(|s| s.to_string()))
.unwrap_or_else(|| "missing_kopid".to_string());
debug!("opid -> {:?}", opid);
match response.status() {
// Continue to process.
reqwest::StatusCode::OK => {}
reqwest::StatusCode::UNAUTHORIZED => return Ok(None),
unexpect => return Err(ClientError::Http(unexpect, response.json().await.ok())),
unexpect => {
return Err(ClientError::Http(
unexpect,
response.json().await.ok(),
opid,
))
}
}
let r: WhoamiResponse =
serde_json::from_str(response.text().await.unwrap().as_str()).unwrap();
let r: WhoamiResponse = response
.json()
.await
.map_err(|e| ClientError::JSONDecode(e, opid))?;
Ok(Some((r.youare, r.uat)))
}

View file

@ -1,5 +1,12 @@
#![deny(warnings)]
#![warn(unused_extern_crates)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![deny(clippy::panic)]
#![deny(clippy::unreachable)]
#![deny(clippy::await_holding_lock)]
#![deny(clippy::needless_pass_by_value)]
#![deny(clippy::trivially_copy_pass_by_ref)]
#[macro_use]
extern crate log;
@ -8,6 +15,7 @@ use reqwest::header::CONTENT_TYPE;
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_derive::Deserialize;
use serde_json::error::Error as SerdeJsonError;
use std::collections::BTreeMap;
use std::fs::{metadata, File, Metadata};
use std::io::Read;
@ -35,12 +43,13 @@ pub const KOPID: &str = "X-KANIDM-OPID";
#[derive(Debug)]
pub enum ClientError {
Unauthorized,
Http(reqwest::StatusCode, Option<OperationError>),
Http(reqwest::StatusCode, Option<OperationError>, String),
Transport(reqwest::Error),
AuthenticationFailed,
JsonParse,
EmptyResponse,
TOTPVerifyFailed(Uuid, TOTPSecret),
JSONDecode(reqwest::Error, String),
JSONEncode(SerdeJsonError),
}
#[derive(Debug, Deserialize)]
@ -341,7 +350,7 @@ impl KanidmClient {
) -> Result<T, ClientError> {
let dest = format!("{}{}", self.addr, dest);
let req_string = serde_json::to_string(&request).unwrap();
let req_string = serde_json::to_string(&request).map_err(ClientError::JSONEncode)?;
let response = self
.client
@ -351,21 +360,21 @@ impl KanidmClient {
.send()
.map_err(ClientError::Transport)?;
let opid = response.headers().get(KOPID);
debug!(
"opid -> {:?}",
opid.expect("Missing opid? Refusing to proceed ...")
);
let opid = response
.headers()
.get(KOPID)
.and_then(|hv| hv.to_str().ok().map(|s| s.to_string()))
.unwrap_or_else(|| "missing_kopid".to_string());
debug!("opid -> {:?}", opid);
match response.status() {
reqwest::StatusCode::OK => {}
unexpect => return Err(ClientError::Http(unexpect, response.json().ok())),
unexpect => return Err(ClientError::Http(unexpect, response.json().ok(), opid)),
}
// TODO #253: What about errors
let r: T = response.json().unwrap();
Ok(r)
response
.json()
.map_err(|e| ClientError::JSONDecode(e, opid))
}
fn perform_put_request<R: Serialize, T: DeserializeOwned>(
@ -375,7 +384,7 @@ impl KanidmClient {
) -> Result<T, ClientError> {
let dest = format!("{}{}", self.addr, dest);
let req_string = serde_json::to_string(&request).unwrap();
let req_string = serde_json::to_string(&request).map_err(ClientError::JSONEncode)?;
let response = self
.client
@ -385,21 +394,21 @@ impl KanidmClient {
.send()
.map_err(ClientError::Transport)?;
let opid = response.headers().get(KOPID);
debug!(
"opid -> {:?}",
opid.expect("Missing opid? Refusing to proceed ...")
);
let opid = response
.headers()
.get(KOPID)
.and_then(|hv| hv.to_str().ok().map(|s| s.to_string()))
.unwrap_or_else(|| "missing_kopid".to_string());
debug!("opid -> {:?}", opid);
match response.status() {
reqwest::StatusCode::OK => {}
unexpect => return Err(ClientError::Http(unexpect, response.json().ok())),
unexpect => return Err(ClientError::Http(unexpect, response.json().ok(), opid)),
}
// TODO #253: What about errors
let r: T = response.json().unwrap();
Ok(r)
response
.json()
.map_err(|e| ClientError::JSONDecode(e, opid))
}
fn perform_get_request<T: DeserializeOwned>(&self, dest: &str) -> Result<T, ClientError> {
@ -410,21 +419,21 @@ impl KanidmClient {
.send()
.map_err(ClientError::Transport)?;
let opid = response.headers().get(KOPID);
debug!(
"opid -> {:?}",
opid.expect("Missing opid? Refusing to proceed ...")
);
let opid = response
.headers()
.get(KOPID)
.and_then(|hv| hv.to_str().ok().map(|s| s.to_string()))
.unwrap_or_else(|| "missing_kopid".to_string());
debug!("opid -> {:?}", opid);
match response.status() {
reqwest::StatusCode::OK => {}
unexpect => return Err(ClientError::Http(unexpect, response.json().ok())),
unexpect => return Err(ClientError::Http(unexpect, response.json().ok(), opid)),
}
// TODO #253: What about errors
let r: T = response.json().unwrap();
Ok(r)
response
.json()
.map_err(|e| ClientError::JSONDecode(e, opid))
}
fn perform_delete_request(&self, dest: &str) -> Result<bool, ClientError> {
@ -435,36 +444,50 @@ impl KanidmClient {
.send()
.map_err(ClientError::Transport)?;
let opid = response.headers().get(KOPID);
debug!(
"opid -> {:?}",
opid.expect("Missing opid? Refusing to proceed ...")
);
let opid = response
.headers()
.get(KOPID)
.and_then(|hv| hv.to_str().ok().map(|s| s.to_string()))
.unwrap_or_else(|| "missing_kopid".to_string());
debug!("opid -> {:?}", opid);
match response.status() {
reqwest::StatusCode::OK => {}
unexpect => return Err(ClientError::Http(unexpect, response.json().ok())),
unexpect => return Err(ClientError::Http(unexpect, response.json().ok(), opid)),
}
let r: bool = response.json().unwrap();
Ok(r)
response
.json()
.map_err(|e| ClientError::JSONDecode(e, opid))
}
// whoami
// Can't use generic get due to possible un-auth case.
pub fn whoami(&self) -> Result<Option<(Entry, UserAuthToken)>, ClientError> {
let whoami_dest = format!("{}/v1/self", self.addr);
let response = self.client.get(whoami_dest.as_str()).send().unwrap();
let response = self
.client
.get(whoami_dest.as_str())
.send()
.map_err(ClientError::Transport)?;
let opid = response
.headers()
.get(KOPID)
.and_then(|hv| hv.to_str().ok().map(|s| s.to_string()))
.unwrap_or_else(|| "missing_kopid".to_string());
debug!("opid -> {:?}", opid);
match response.status() {
// Continue to process.
reqwest::StatusCode::OK => {}
reqwest::StatusCode::UNAUTHORIZED => return Ok(None),
unexpect => return Err(ClientError::Http(unexpect, response.json().ok())),
unexpect => return Err(ClientError::Http(unexpect, response.json().ok(), opid)),
}
let r: WhoamiResponse = serde_json::from_str(response.text().unwrap().as_str()).unwrap();
let r: WhoamiResponse = response
.json()
.map_err(|e| ClientError::JSONDecode(e, opid))?;
Ok(Some((r.youare, r.uat)))
}
@ -608,12 +631,12 @@ impl KanidmClient {
self.perform_get_request(format!("/v1/group/{}/_attr/member", id).as_str())
}
pub fn idm_group_set_members(&self, id: &str, members: Vec<&str>) -> Result<bool, ClientError> {
pub fn idm_group_set_members(&self, id: &str, members: &[&str]) -> Result<bool, ClientError> {
let m: Vec<_> = members.iter().map(|v| (*v).to_string()).collect();
self.perform_put_request(format!("/v1/group/{}/_attr/member", id).as_str(), m)
}
pub fn idm_group_add_members(&self, id: &str, members: Vec<&str>) -> Result<bool, ClientError> {
pub fn idm_group_add_members(&self, id: &str, members: &[&str]) -> Result<bool, ClientError> {
let m: Vec<_> = members.iter().map(|v| (*v).to_string()).collect();
self.perform_post_request(format!("/v1/group/{}/_attr/member", id).as_str(), m)
}
@ -881,7 +904,12 @@ impl KanidmClient {
// 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()))
.and_then(|mut r: Vec<String>|
// Get the first result
r.pop()
.ok_or(
ClientError::EmptyResponse
))
}
// pub fn idm_domain_put_attr

View file

@ -63,9 +63,7 @@ fn create_user(rsclient: &KanidmClient, id: &str, group_name: &str) -> () {
None => rsclient.idm_group_create(&group_name).unwrap(),
};
rsclient
.idm_group_add_members(&group_name, vec![id])
.unwrap();
rsclient.idm_group_add_members(&group_name, &[id]).unwrap();
}
fn is_attr_writable(rsclient: &KanidmClient, id: &str, attr: &str) -> Option<bool> {
@ -115,7 +113,7 @@ fn is_attr_writable(rsclient: &KanidmClient, id: &str, attr: &str) -> Option<boo
fn add_all_attrs(mut rsclient: &mut KanidmClient, id: &str, group_name: &str) {
// Extend with posix attrs to test read attr: gidnumber and loginshell
rsclient
.idm_group_add_members("idm_admins", vec!["admin"])
.idm_group_add_members("idm_admins", &["admin"])
.unwrap();
rsclient
.idm_account_unix_extend(id, None, Some(&"/bin/bash"))
@ -157,10 +155,10 @@ fn create_user_with_all_attrs(
fn login_account(rsclient: &mut KanidmClient, id: &str) -> () {
rsclient
.idm_group_add_members("idm_people_account_password_import_priv", vec!["admin"])
.idm_group_add_members("idm_people_account_password_import_priv", &["admin"])
.unwrap();
rsclient
.idm_group_add_members("idm_people_extend_priv", vec!["admin"])
.idm_group_add_members("idm_people_extend_priv", &["admin"])
.unwrap();
rsclient
@ -215,7 +213,7 @@ fn test_modify_group(rsclient: &KanidmClient, group_names: &[&str], is_modificab
["description", "name"].iter().for_each(|attr| {
assert!(is_attr_writable(&rsclient, group, attr).unwrap() == is_modificable)
});
assert!(rsclient.idm_group_add_members(group, vec!["test"]).is_ok() == is_modificable);
assert!(rsclient.idm_group_add_members(group, &["test"]).is_ok() == is_modificable);
});
}
@ -326,7 +324,7 @@ fn test_default_entries_rbac_group_managers() {
rsclient.idm_group_create("test_group").unwrap();
rsclient
.idm_group_add_members("test_group", vec!["test"])
.idm_group_add_members("test_group", &["test"])
.unwrap();
assert!(is_attr_writable(&rsclient, "test_group", "description").unwrap());
});
@ -588,7 +586,7 @@ fn test_default_entries_rbac_anonymous_entry() {
.unwrap();
create_user_with_all_attrs(&mut rsclient, "test", Some("test_group"));
rsclient
.idm_group_add_members("test_group", vec!["anonymous"])
.idm_group_add_members("test_group", &["anonymous"])
.unwrap();
add_all_attrs(&mut rsclient, "anonymous", "test_group");

View file

@ -251,14 +251,14 @@ fn test_server_rest_group_lifecycle() {
// Add a member.
rsclient
.idm_group_add_members("demo_group", vec!["admin"])
.idm_group_add_members("demo_group", &["admin"])
.unwrap();
let members = rsclient.idm_group_get_members("demo_group").unwrap();
assert!(members == Some(vec!["admin@example.com".to_string()]));
// Set the list of members
rsclient
.idm_group_set_members("demo_group", vec!["admin", "demo_group"])
.idm_group_set_members("demo_group", &["admin", "demo_group"])
.unwrap();
let members = rsclient.idm_group_get_members("demo_group").unwrap();
assert!(
@ -395,7 +395,7 @@ fn test_server_rest_account_lifecycle() {
// To enable the admin to actually make some of these changes, we have
// to make them a people admin. NOT recommended in production!
rsclient
.idm_group_add_members("idm_account_write_priv", vec!["admin"])
.idm_group_add_members("idm_account_write_priv", &["admin"])
.unwrap();
// Create a new account
@ -499,7 +499,7 @@ fn test_server_rest_posix_lifecycle() {
assert!(res.is_ok());
// Not recommended in production!
rsclient
.idm_group_add_members("idm_admins", vec!["admin"])
.idm_group_add_members("idm_admins", &["admin"])
.unwrap();
// Create a new account
@ -517,7 +517,7 @@ fn test_server_rest_posix_lifecycle() {
// Extend the group with posix attrs
rsclient.idm_group_create("posix_group").unwrap();
rsclient
.idm_group_add_members("posix_group", vec!["posix_account"])
.idm_group_add_members("posix_group", &["posix_account"])
.unwrap();
rsclient.idm_group_unix_extend("posix_group", None).unwrap();
@ -576,7 +576,7 @@ fn test_server_rest_posix_auth_lifecycle() {
// Not recommended in production!
rsclient
.idm_group_add_members("idm_admins", vec!["admin"])
.idm_group_add_members("idm_admins", &["admin"])
.unwrap();
// Setup a unix user
@ -634,7 +634,7 @@ fn test_server_rest_recycle_lifecycle() {
// Not recommended in production!
rsclient
.idm_group_add_members("idm_admins", vec!["admin"])
.idm_group_add_members("idm_admins", &["admin"])
.unwrap();
// Setup a unix user
@ -674,10 +674,10 @@ fn test_server_rest_account_import_password() {
// To enable the admin to actually make some of these changes, we have
// to make them a password import admin. NOT recommended in production!
rsclient
.idm_group_add_members("idm_people_account_password_import_priv", vec!["admin"])
.idm_group_add_members("idm_people_account_password_import_priv", &["admin"])
.unwrap();
rsclient
.idm_group_add_members("idm_people_extend_priv", vec!["admin"])
.idm_group_add_members("idm_people_extend_priv", &["admin"])
.unwrap();
// Create a new account
@ -718,7 +718,7 @@ fn test_server_rest_totp_auth_lifecycle() {
// Not recommended in production!
rsclient
.idm_group_add_members("idm_admins", vec!["admin"])
.idm_group_add_members("idm_admins", &["admin"])
.unwrap();
// Create a new account

View file

@ -171,70 +171,82 @@ impl AccountOpt {
}
};
client
.idm_account_primary_credential_set_password(
acsopt.aopts.account_id.as_str(),
password.as_str(),
)
.unwrap();
if let Err(e) = client.idm_account_primary_credential_set_password(
acsopt.aopts.account_id.as_str(),
password.as_str(),
) {
eprintln!("Error -> {:?}", e);
}
}
AccountCredential::GeneratePassword(acsopt) => {
let client = acsopt.copt.to_client();
let npw = client
.idm_account_primary_credential_set_generated(
acsopt.aopts.account_id.as_str(),
)
.unwrap();
println!(
"Generated password for {}: {}",
acsopt.aopts.account_id, npw
);
match client.idm_account_primary_credential_set_generated(
acsopt.aopts.account_id.as_str(),
) {
Ok(npw) => {
println!(
"Generated password for {}: {}",
acsopt.aopts.account_id, npw
);
}
Err(e) => {
eprintln!("Error -> {:?}", e);
}
}
}
}, // end AccountOpt::Credential
AccountOpt::Radius(aropt) => match aropt {
AccountRadius::Show(aopt) => {
let client = aopt.copt.to_client();
let rcred = client
.idm_account_radius_credential_get(aopt.aopts.account_id.as_str())
.unwrap();
let rcred =
client.idm_account_radius_credential_get(aopt.aopts.account_id.as_str());
match rcred {
Some(s) => println!("Radius secret: {}", s),
None => println!("NO Radius secret"),
Ok(Some(s)) => println!("Radius secret: {}", s),
Ok(None) => println!("NO Radius secret"),
Err(e) => {
eprintln!("Error -> {:?}", e);
}
}
}
AccountRadius::Generate(aopt) => {
let client = aopt.copt.to_client();
client
if let Err(e) = client
.idm_account_radius_credential_regenerate(aopt.aopts.account_id.as_str())
.unwrap();
{
eprintln!("Error -> {:?}", e);
}
}
AccountRadius::Delete(aopt) => {
let client = aopt.copt.to_client();
client
.idm_account_radius_credential_delete(aopt.aopts.account_id.as_str())
.unwrap();
if let Err(e) =
client.idm_account_radius_credential_delete(aopt.aopts.account_id.as_str())
{
eprintln!("Error -> {:?}", e);
}
}
}, // end AccountOpt::Radius
AccountOpt::Posix(apopt) => match apopt {
AccountPosix::Show(aopt) => {
let client = aopt.copt.to_client();
let token = client
.idm_account_unix_token_get(aopt.aopts.account_id.as_str())
.unwrap();
println!("{}", token);
match client.idm_account_unix_token_get(aopt.aopts.account_id.as_str()) {
Ok(token) => println!("{}", token),
Err(e) => {
eprintln!("Error -> {:?}", e);
}
}
}
AccountPosix::Set(aopt) => {
let client = aopt.copt.to_client();
client
.idm_account_unix_extend(
aopt.aopts.account_id.as_str(),
aopt.gidnumber,
aopt.shell.as_deref(),
)
.unwrap();
if let Err(e) = client.idm_account_unix_extend(
aopt.aopts.account_id.as_str(),
aopt.gidnumber,
aopt.shell.as_deref(),
) {
eprintln!("Error -> {:?}", e);
}
}
AccountPosix::SetPassword(aopt) => {
let client = aopt.copt.to_client();
@ -246,77 +258,74 @@ impl AccountOpt {
}
};
client
.idm_account_unix_cred_put(
aopt.aopts.account_id.as_str(),
password.as_str(),
)
.unwrap();
if let Err(e) = client.idm_account_unix_cred_put(
aopt.aopts.account_id.as_str(),
password.as_str(),
) {
eprintln!("Error -> {:?}", e);
}
}
}, // end AccountOpt::Posix
AccountOpt::Ssh(asopt) => match asopt {
AccountSsh::List(aopt) => {
let client = aopt.copt.to_client();
let pkeys = client
.idm_account_get_ssh_pubkeys(aopt.aopts.account_id.as_str())
.unwrap();
for pkey in pkeys {
println!("{}", pkey)
match client.idm_account_get_ssh_pubkeys(aopt.aopts.account_id.as_str()) {
Ok(pkeys) => pkeys.iter().for_each(|pkey| println!("{}", pkey)),
Err(e) => {
eprintln!("Error -> {:?}", e);
}
}
}
AccountSsh::Add(aopt) => {
let client = aopt.copt.to_client();
client
.idm_account_post_ssh_pubkey(
aopt.aopts.account_id.as_str(),
aopt.tag.as_str(),
aopt.pubkey.as_str(),
)
.unwrap();
if let Err(e) = client.idm_account_post_ssh_pubkey(
aopt.aopts.account_id.as_str(),
aopt.tag.as_str(),
aopt.pubkey.as_str(),
) {
eprintln!("Error -> {:?}", e);
}
}
AccountSsh::Delete(aopt) => {
let client = aopt.copt.to_client();
client
.idm_account_delete_ssh_pubkey(
aopt.aopts.account_id.as_str(),
aopt.tag.as_str(),
)
.unwrap();
if let Err(e) = client.idm_account_delete_ssh_pubkey(
aopt.aopts.account_id.as_str(),
aopt.tag.as_str(),
) {
eprintln!("Error -> {:?}", e);
}
}
}, // end AccountOpt::Ssh
AccountOpt::List(copt) => {
let client = copt.to_client();
let r = client.idm_account_list().unwrap();
for e in r {
println!("{}", e);
match client.idm_account_list() {
Ok(r) => r.iter().for_each(|ent| println!("{}", ent)),
Err(e) => eprintln!("Error -> {:?}", e),
}
}
AccountOpt::Get(aopt) => {
let client = aopt.copt.to_client();
let e = client
.idm_account_get(aopt.aopts.account_id.as_str())
.unwrap();
match e {
Some(e) => println!("{}", e),
None => println!("No matching entries"),
match client.idm_account_get(aopt.aopts.account_id.as_str()) {
Ok(Some(e)) => println!("{}", e),
Ok(None) => println!("No matching entries"),
Err(e) => eprintln!("Error -> {:?}", e),
}
}
AccountOpt::Delete(aopt) => {
let client = aopt.copt.to_client();
client
.idm_account_delete(aopt.aopts.account_id.as_str())
.unwrap();
if let Err(e) = client.idm_account_delete(aopt.aopts.account_id.as_str()) {
eprintln!("Error -> {:?}", e)
}
}
AccountOpt::Create(acopt) => {
let client = acopt.copt.to_client();
client
.idm_account_create(
acopt.aopts.account_id.as_str(),
acopt.display_name.as_str(),
)
.unwrap();
if let Err(e) = client.idm_account_create(
acopt.aopts.account_id.as_str(),
acopt.display_name.as_str(),
) {
eprintln!("Error -> {:?}", e)
}
}
}
}

View file

@ -27,35 +27,54 @@ impl CommonOpt {
let config_path: String = shellexpand::tilde("~/.config/kanidm").into_owned();
debug!("Attempting to use config {}", "/etc/kanidm/config");
let client_builder = KanidmClientBuilder::new()
let client_builder = match KanidmClientBuilder::new()
.read_options_from_optional_config("/etc/kanidm/config")
.and_then(|cb| {
debug!("Attempting to use config {}", config_path);
cb.read_options_from_optional_config(config_path)
})
.expect("Failed to parse config (if present)");
}) {
Ok(c) => c,
Err(e) => {
error!("Failed to parse config (if present) -- {:?}", e);
std::process::exit(1);
}
};
let client_builder = match &self.addr {
Some(a) => client_builder.address(a.to_string()),
None => client_builder,
};
let ca_path: Option<&str> = self.ca_path.as_ref().map(|p| p.to_str().unwrap());
let ca_path: Option<&str> = self.ca_path.as_ref().map(|p| p.to_str()).flatten();
let client_builder = match ca_path {
Some(p) => client_builder
.add_root_certificate_filepath(p)
.expect("Failed to access CA file"),
Some(p) => match client_builder.add_root_certificate_filepath(p) {
Ok(cb) => cb,
Err(e) => {
error!("Failed to add ca certificate -- {:?}", e);
std::process::exit(1);
}
},
None => client_builder,
};
let client = client_builder
.build()
.expect("Failed to build client instance");
let client = match client_builder.build() {
Ok(c) => c,
Err(e) => {
error!("Failed to build client instance -- {:?}", e);
std::process::exit(1);
}
};
let r = if self.username == "anonymous" {
client.auth_anonymous()
} else {
let password = rpassword::prompt_password_stderr("Enter password: ").unwrap();
let password = match rpassword::prompt_password_stderr("Enter password: ") {
Ok(p) => p,
Err(e) => {
error!("Failed to create password prompt -- {:?}", e);
std::process::exit(1);
}
};
client.auth_simple_password(self.username.as_str(), password.as_str())
};

View file

@ -70,29 +70,38 @@ impl GroupOpt {
match self {
GroupOpt::List(copt) => {
let client = copt.to_client();
let r = client.idm_group_list().unwrap();
for e in r {
println!("{}", e);
match client.idm_group_list() {
Ok(r) => r.iter().for_each(|ent| println!("{}", ent)),
Err(e) => {
eprintln!("Error -> {:?}", e);
}
}
}
GroupOpt::Create(gcopt) => {
let client = gcopt.copt.to_client();
client.idm_group_create(gcopt.name.as_str()).unwrap();
if let Err(e) = client.idm_group_create(gcopt.name.as_str()) {
eprintln!("Error -> {:?}", e);
}
}
GroupOpt::Delete(gcopt) => {
let client = gcopt.copt.to_client();
client.idm_group_delete(gcopt.name.as_str()).unwrap();
if let Err(e) = client.idm_group_delete(gcopt.name.as_str()) {
eprintln!("Error -> {:?}", e);
}
}
GroupOpt::PurgeMembers(gcopt) => {
let client = gcopt.copt.to_client();
client.idm_group_purge_members(gcopt.name.as_str()).unwrap();
if let Err(e) = client.idm_group_purge_members(gcopt.name.as_str()) {
eprintln!("Error -> {:?}", e);
}
}
GroupOpt::ListMembers(gcopt) => {
let client = gcopt.copt.to_client();
let members = client.idm_group_get_members(gcopt.name.as_str()).unwrap();
if let Some(groups) = members {
for m in groups {
println!("{:?}", m);
match client.idm_group_get_members(gcopt.name.as_str()) {
Ok(Some(groups)) => groups.iter().for_each(|m| println!("{:?}", m)),
Ok(None) => {}
Err(e) => {
eprintln!("Error -> {:?}", e);
}
}
}
@ -100,31 +109,33 @@ impl GroupOpt {
let client = gcopt.copt.to_client();
let new_members: Vec<&str> = gcopt.members.iter().map(|s| s.as_str()).collect();
client
.idm_group_add_members(gcopt.name.as_str(), new_members)
.unwrap();
if let Err(e) = client.idm_group_add_members(gcopt.name.as_str(), &new_members) {
eprintln!("Error -> {:?}", e);
}
}
GroupOpt::SetMembers(gcopt) => {
let client = gcopt.copt.to_client();
let new_members: Vec<&str> = gcopt.members.iter().map(|s| s.as_str()).collect();
client
.idm_group_set_members(gcopt.name.as_str(), new_members)
.unwrap();
if let Err(e) = client.idm_group_set_members(gcopt.name.as_str(), &new_members) {
eprintln!("Error -> {:?}", e);
}
}
GroupOpt::Posix(gpopt) => match gpopt {
GroupPosix::Show(gcopt) => {
let client = gcopt.copt.to_client();
let token = client
.idm_group_unix_token_get(gcopt.name.as_str())
.unwrap();
println!("{}", token);
match client.idm_group_unix_token_get(gcopt.name.as_str()) {
Ok(token) => println!("{}", token),
Err(e) => eprintln!("Error -> {:?}", e),
}
}
GroupPosix::Set(gcopt) => {
let client = gcopt.copt.to_client();
client
.idm_group_unix_extend(gcopt.name.as_str(), gcopt.gidnumber)
.unwrap();
if let Err(e) =
client.idm_group_unix_extend(gcopt.name.as_str(), gcopt.gidnumber)
{
eprintln!("Error -> {:?}", e);
}
}
},
} // end match

View file

@ -1,3 +1,13 @@
#![deny(warnings)]
#![warn(unused_extern_crates)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![deny(clippy::panic)]
#![deny(clippy::unreachable)]
#![deny(clippy::await_holding_lock)]
#![deny(clippy::needless_pass_by_value)]
#![deny(clippy::trivially_copy_pass_by_ref)]
#[macro_use]
extern crate log;
use structopt::StructOpt;
@ -52,9 +62,17 @@ impl SelfOpt {
SelfOpt::SetPassword(copt) => {
let client = copt.to_client();
let password = rpassword::prompt_password_stderr("Enter new password: ").unwrap();
let password = match rpassword::prompt_password_stderr("Enter new password: ") {
Ok(p) => p,
Err(e) => {
eprintln!("Error -> {:?}", e);
return;
}
};
client.idm_account_set_password(password).unwrap();
if let Err(e) = client.idm_account_set_password(password) {
eprintln!("Error -> {:?}", e);
}
}
}
}
@ -103,14 +121,23 @@ impl ClientOpt {
}
pub(crate) fn password_prompt(prompt: &str) -> Option<String> {
let password = rpassword::prompt_password_stderr(prompt).unwrap();
for _ in 0..3 {
let password = match rpassword::prompt_password_stderr(prompt) {
Ok(p) => p,
Err(_e) => return None,
};
let password_confirm =
rpassword::prompt_password_stderr("Retype the new password to confirm: ").unwrap();
let password_confirm =
match rpassword::prompt_password_stderr("Retype the new password to confirm: ") {
Ok(p) => p,
Err(_e) => return None,
};
if password == password_confirm {
Some(password)
} else {
None
if password == password_confirm {
return Some(password);
} else {
eprintln!("Passwords do not match");
}
}
None
}

View file

@ -11,14 +11,6 @@ use std::path::PathBuf;
use serde::de::DeserializeOwned;
fn read_file<T: DeserializeOwned, P: AsRef<Path>>(path: P) -> Result<T, Box<dyn Error>> {
let f = File::open(path)?;
let r = BufReader::new(f);
let t: T = serde_json::from_reader(r)?;
Ok(t)
}
#[derive(Debug, StructOpt)]
pub struct FilterOpt {
#[structopt()]
@ -30,7 +22,7 @@ pub struct FilterOpt {
#[derive(Debug, StructOpt)]
pub struct CreateOpt {
#[structopt(parse(from_os_str))]
file: Option<PathBuf>,
file: PathBuf,
#[structopt(flatten)]
commonopts: CommonOpt,
}
@ -42,7 +34,7 @@ pub struct ModifyOpt {
#[structopt()]
filter: String,
#[structopt(parse(from_os_str))]
file: Option<PathBuf>,
file: PathBuf,
}
#[derive(Debug, StructOpt)]
@ -57,6 +49,14 @@ pub enum RawOpt {
Delete(FilterOpt),
}
fn read_file<T: DeserializeOwned, P: AsRef<Path>>(path: P) -> Result<T, Box<dyn Error>> {
let f = File::open(path)?;
let r = BufReader::new(f);
let t: T = serde_json::from_reader(r)?;
Ok(t)
}
impl RawOpt {
pub fn debug(&self) -> bool {
match self {
@ -72,46 +72,75 @@ impl RawOpt {
RawOpt::Search(sopt) => {
let client = sopt.commonopts.to_client();
let filter: Filter = serde_json::from_str(sopt.filter.as_str()).unwrap();
let rset = client.search(filter).unwrap();
let filter: Filter = match serde_json::from_str(sopt.filter.as_str()) {
Ok(f) => f,
Err(e) => {
eprintln!("Error -> {:?}", e);
return;
}
};
rset.iter().for_each(|e| {
println!("{}", e);
});
match client.search(filter) {
Ok(rset) => rset.iter().for_each(|e| println!("{}", e)),
Err(e) => {
eprintln!("Error -> {:?}", e);
}
}
}
RawOpt::Create(copt) => {
let client = copt.commonopts.to_client();
// Read the file?
match &copt.file {
Some(p) => {
let r_entries: Vec<BTreeMap<String, Vec<String>>> = read_file(p).unwrap();
let entries = r_entries.into_iter().map(|b| Entry { attrs: b }).collect();
client.create(entries).unwrap();
}
None => {
println!("Must provide a file");
let r_entries: Vec<BTreeMap<String, Vec<String>>> = match read_file(&copt.file) {
Ok(r) => r,
Err(e) => {
eprintln!("Error -> {:?}", e);
return;
}
};
let entries = r_entries.into_iter().map(|b| Entry { attrs: b }).collect();
if let Err(e) = client.create(entries) {
eprintln!("Error -> {:?}", e);
}
}
RawOpt::Modify(mopt) => {
let client = mopt.commonopts.to_client();
// Read the file?
match &mopt.file {
Some(p) => {
let filter: Filter = serde_json::from_str(mopt.filter.as_str()).unwrap();
let r_list: Vec<Modify> = read_file(p).unwrap();
let modlist = ModifyList::new_list(r_list);
client.modify(filter, modlist).unwrap();
let filter: Filter = match serde_json::from_str(mopt.filter.as_str()) {
Ok(f) => f,
Err(e) => {
eprintln!("Error -> {:?}", e);
return;
}
None => {
println!("Must provide a file");
};
let r_list: Vec<Modify> = match read_file(&mopt.file) {
Ok(r) => r,
Err(e) => {
eprintln!("Error -> {:?}", e);
return;
}
};
let modlist = ModifyList::new_list(r_list);
if let Err(e) = client.modify(filter, modlist) {
eprintln!("Error -> {:?}", e);
}
}
RawOpt::Delete(dopt) => {
let client = dopt.commonopts.to_client();
let filter: Filter = serde_json::from_str(dopt.filter.as_str()).unwrap();
client.delete(filter).unwrap();
let filter: Filter = match serde_json::from_str(dopt.filter.as_str()) {
Ok(f) => f,
Err(e) => {
eprintln!("Error -> {:?}", e);
return;
}
};
if let Err(e) = client.delete(filter) {
eprintln!("Error -> {:?}", e);
}
}
}
}

View file

@ -27,22 +27,28 @@ impl RecycleOpt {
match self {
RecycleOpt::List(copt) => {
let client = copt.to_client();
let r = client.recycle_bin_list().unwrap();
for e in r {
println!("{}", e);
match client.recycle_bin_list() {
Ok(r) => r.iter().for_each(|e| println!("{}", e)),
Err(e) => {
eprintln!("Error -> {:?}", e);
}
}
}
RecycleOpt::Get(nopt) => {
let client = nopt.copt.to_client();
let e = client.recycle_bin_get(nopt.name.as_str()).unwrap();
match e {
Some(e) => println!("{}", e),
None => println!("No matching entries"),
match client.recycle_bin_get(nopt.name.as_str()) {
Ok(Some(e)) => println!("{}", e),
Ok(None) => println!("No matching entries"),
Err(e) => {
eprintln!("Error -> {:?}", e);
}
}
}
RecycleOpt::Revive(nopt) => {
let client = nopt.copt.to_client();
client.recycle_bin_revive(nopt.name.as_str()).unwrap();
if let Err(e) = client.recycle_bin_revive(nopt.name.as_str()) {
eprintln!("Error -> {:?}", e);
}
}
}
}

View file

@ -1,4 +1,13 @@
#![deny(warnings)]
#![warn(unused_extern_crates)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![deny(clippy::panic)]
#![deny(clippy::unreachable)]
#![deny(clippy::await_holding_lock)]
#![deny(clippy::needless_pass_by_value)]
#![deny(clippy::trivially_copy_pass_by_ref)]
#[macro_use]
extern crate libnss;
#[macro_use]

View file

@ -1,5 +1,14 @@
#![deny(warnings)]
extern crate libc;
#![warn(unused_extern_crates)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![deny(clippy::panic)]
#![deny(clippy::unreachable)]
#![deny(clippy::await_holding_lock)]
#![deny(clippy::needless_pass_by_value)]
#![deny(clippy::trivially_copy_pass_by_ref)]
// extern crate libc;
mod pam;
use crate::pam::constants::*;

View file

@ -271,8 +271,12 @@ impl CacheLayer {
ClientError::Http(
StatusCode::UNAUTHORIZED,
Some(OperationError::NotAuthenticated),
opid,
) => {
error!("transport unauthenticated, moving to offline");
error!(
"transport unauthenticated, moving to offline - eventid {}",
opid
);
// Something went wrong, mark offline.
let time = SystemTime::now().add(Duration::from_secs(15));
self.set_cachestate(CacheState::OfflineNextCheck(time))
@ -282,14 +286,16 @@ impl CacheLayer {
ClientError::Http(
StatusCode::BAD_REQUEST,
Some(OperationError::NoMatchingEntries),
opid,
)
| ClientError::Http(
StatusCode::BAD_REQUEST,
Some(OperationError::InvalidAccountState(_)),
opid,
) => {
// We wele able to contact the server but the entry has been removed, or
// is not longer a valid posix account.
debug!("entry has been removed or is no longer a valid posix account, clearing from cache ...");
debug!("entry has been removed or is no longer a valid posix account, clearing from cache ... - eventid {}", opid);
token
.map(|tok| self.delete_cache_usertoken(&tok.uuid))
// Now an option<result<t, _>>
@ -335,8 +341,12 @@ impl CacheLayer {
ClientError::Http(
StatusCode::UNAUTHORIZED,
Some(OperationError::NotAuthenticated),
opid,
) => {
error!("transport unauthenticated, moving to offline");
error!(
"transport unauthenticated, moving to offline - eventid {}",
opid
);
// Something went wrong, mark offline.
let time = SystemTime::now().add(Duration::from_secs(15));
self.set_cachestate(CacheState::OfflineNextCheck(time))
@ -346,12 +356,14 @@ impl CacheLayer {
ClientError::Http(
StatusCode::BAD_REQUEST,
Some(OperationError::NoMatchingEntries),
opid,
)
| ClientError::Http(
StatusCode::BAD_REQUEST,
Some(OperationError::InvalidAccountState(_)),
opid,
) => {
debug!("entry has been removed or is no longer a valid posix group, clearing from cache ...");
debug!("entry has been removed or is no longer a valid posix group, clearing from cache ... - eventid {}", opid);
token
.map(|tok| self.delete_cache_grouptoken(&tok.uuid))
// Now an option<result<t, _>>
@ -620,8 +632,12 @@ impl CacheLayer {
ClientError::Http(
StatusCode::UNAUTHORIZED,
Some(OperationError::NotAuthenticated),
opid,
) => {
error!("transport unauthenticated, moving to offline");
error!(
"transport unauthenticated, moving to offline - eventid {}",
opid
);
// Something went wrong, mark offline.
let time = SystemTime::now().add(Duration::from_secs(15));
self.set_cachestate(CacheState::OfflineNextCheck(time))
@ -634,12 +650,17 @@ impl CacheLayer {
ClientError::Http(
StatusCode::BAD_REQUEST,
Some(OperationError::NoMatchingEntries),
opid,
)
| ClientError::Http(
StatusCode::BAD_REQUEST,
Some(OperationError::InvalidAccountState(_)),
opid,
) => {
error!("unknown account or is not a valid posix account");
error!(
"unknown account or is not a valid posix account - eventid {}",
opid
);
Ok(None)
}
er => {

View file

@ -1,4 +1,13 @@
#![deny(warnings)]
#![warn(unused_extern_crates)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![deny(clippy::panic)]
#![deny(clippy::unreachable)]
#![deny(clippy::await_holding_lock)]
#![deny(clippy::needless_pass_by_value)]
#![deny(clippy::trivially_copy_pass_by_ref)]
#[macro_use]
extern crate log;

View file

@ -7,7 +7,7 @@ use std::convert::TryFrom;
use std::fmt;
use crate::cache::Id;
use std::sync::{Mutex, MutexGuard};
use tokio::sync::{Mutex, MutexGuard};
use kanidm::be::dbvalue::DbPasswordV1;
use kanidm::credential::Password;

View file

@ -1,5 +1,12 @@
#![deny(warnings)]
#![warn(unused_extern_crates)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![deny(clippy::panic)]
#![deny(clippy::unreachable)]
#![deny(clippy::await_holding_lock)]
#![deny(clippy::needless_pass_by_value)]
#![deny(clippy::trivially_copy_pass_by_ref)]
#[macro_use]
extern crate serde_derive;

View file

@ -101,7 +101,7 @@ fn test_fixture(rsclient: &KanidmClient) -> () {
assert!(res.is_ok());
// Not recommended in production!
rsclient
.idm_group_add_members("idm_admins", vec!["admin"])
.idm_group_add_members("idm_admins", &["admin"])
.unwrap();
// Create a new account
@ -126,7 +126,7 @@ fn test_fixture(rsclient: &KanidmClient) -> () {
// Setup a group
rsclient.idm_group_create("testgroup1").unwrap();
rsclient
.idm_group_add_members("testgroup1", vec!["testaccount1"])
.idm_group_add_members("testgroup1", &["testaccount1"])
.unwrap();
rsclient
.idm_group_unix_extend("testgroup1", Some(20001))

View file

@ -63,12 +63,12 @@ macro_rules! try_from_entry {
let uuid = $value.get_uuid().clone();
Ok(Account {
uuid: uuid,
name: name,
displayname: displayname,
groups: groups,
primary: primary,
spn: spn,
uuid,
name,
displayname,
groups,
primary,
spn,
})
}};
}

View file

@ -1,5 +1,12 @@
#![deny(warnings)]
#![warn(unused_extern_crates)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![deny(clippy::panic)]
#![deny(clippy::unreachable)]
#![deny(clippy::await_holding_lock)]
#![deny(clippy::needless_pass_by_value)]
#![deny(clippy::trivially_copy_pass_by_ref)]
#[macro_use]
extern crate log;