Newer
Older
/* Copyright 2021 Dominik George <dominik.george@teckids.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

Nik | Klampfradler
committed
get_config,
get_optional

Nik | Klampfradler
committed
use serde::de::Deserialize;
use crate::logging::setup_log;
use oauth2::{
AuthUrl,
ClientId,
ClientSecret,
RequestTokenError,
ResourceOwnerPassword,
ResourceOwnerUsername,
Scope,
TokenUrl
};
use oauth2::basic::{
BasicClient,
BasicTokenResponse
};
use oauth2::reqwest;
use oauth2::reqwest::http_client;
use pamsm::{PamServiceModule, Pam, PamFlag, PamError, PamLibExt};

Nik | Klampfradler
committed
fn get_or_pam_error<'de, T: Deserialize<'de>>(config: &Config, key: &str) -> Result<T, PamError> {
match get_optional(config, key) {
Some(v) => {
debug!("Configuration key found: {}", key);

Nik | Klampfradler
committed
None => {
error!("Configuration key not found: {}", key);
return Err(PamError::SERVICE_ERR);
},
}
}
fn do_legacy_auth(username: String, password: String, config: Config) -> Result<BasicTokenResponse, PamError> {
let client_id = ClientId::new(get_or_pam_error(&config, "pam.client_id")?);
let client_secret = match get_optional(&config, "pam.client_secret") {
Some(v) => Some(ClientSecret::new(v)),
None => None,
};
let auth_url = match AuthUrl::new(get_or_pam_error(&config, "pam.auth_url")?) {
Ok(u) => u,
_ => {
error!("Could not parse authorization URL");
return Err(PamError::SERVICE_ERR);
},
let token_url = match get_optional(&config, "pam.token_url") {
Some(v) => match TokenUrl::new(v) {
Ok(u) => Some(u),
Err(_) => {
error!("Could not parse token URL");
return Err(PamError::SERVICE_ERR);
}

Nik | Klampfradler
committed
let scopes: Vec<&str> = get_or_pam_error(&config, "pam.scopes")?;
let res_username = ResourceOwnerUsername::new(username);
let res_password = ResourceOwnerPassword::new(password);
let client = BasicClient::new(client_id, client_secret, auth_url, token_url);

Nik | Klampfradler
committed
let mut request = client.exchange_password(&res_username, &res_password);
for scope in scopes {
request = request.add_scope(Scope::new(scope.to_string()));
}
let result = request.request(http_client);
Err(e) => match e {
RequestTokenError::Request(re) => match re {
reqwest::Error::Reqwest(ree) => {
error!("Request error requesting token: {}", ree);
return Err(PamError::AUTHINFO_UNAVAIL);
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
},
reqwest::Error::Http(he) => {
error!("HTTP error requesting token: {}", he);
return Err(PamError::AUTHINFO_UNAVAIL);
},
reqwest::Error::Io(ioe) => {
error!("IO error requesting token: {}", ioe);
return Err(PamError::SYSTEM_ERR);
},
_ => {
error!("Unknown error: {}", re);
return Err(PamError::SERVICE_ERR);
},
},
RequestTokenError::ServerResponse(t) => {
error!("Authorization server returned error: {}", t);
return Err(PamError::AUTH_ERR);
},
RequestTokenError::Parse(pe, _) => {
error!("Error parsing response: {}", pe);
return Err(PamError::SERVICE_ERR);
},
_ => {
error!("Unknown error: {}", e);
return Err(PamError::SERVICE_ERR);
},
fn pam_sm_prepare(argv: &Vec<String>) -> Config {
let conf_args = argv_to_config(argv);
let conf = get_config(Some(conf_args));
let mut log_level = log::LevelFilter::Error;
if get_optional(&conf, "pam.debug").unwrap_or_default() {
log_level = log::LevelFilter::Debug;
}
setup_log(log_level);
return conf;
}
struct PamOidc;
impl PamServiceModule for PamOidc {
fn authenticate(pamh: Pam, _: PamFlag, argv: Vec<String>) -> PamError {
let conf = pam_sm_prepare(&argv);
if conf.get_str("pam.flow").unwrap() == "password" {
debug!("Starting Resource Owner Password Credentials OAuth flow");
let username = match pamh.get_user(None) {
Ok(Some(u)) => match u.to_str() {
Ok(u) => u,
_ => {
error!("Could not convert user name to string, aborting");
return PamError::CRED_INSUFFICIENT;
},
},
Ok(None) => {
error!("No username supplied");
return PamError::CRED_INSUFFICIENT;
},
Err(e) => {
error!("Error getting user name: {}", e);
return e;
debug!("Successfully got user name");
let password = match pamh.get_authtok(None) {
Ok(Some(p)) => match p.to_str() {
Ok(p) => p,
_ => {
error!("Could not convert password to string");
return PamError::CRED_INSUFFICIENT;
},
},
Ok(None) => {
error!("No password supplied");
return PamError::CRED_INSUFFICIENT;
},
Err(e) => {
error!("Error getting password: {}", e);
return e;
debug!("Successfully got password");
match do_legacy_auth(username.to_string(), password.to_string(), conf) {
Ok(_) => {
info!("Authenticated {} using Resource Owner Password Grant", username);
return PamError::SUCCESS;
},
Err(e) => {
error!("Error in legacy authentication: {}", e);
return e;
},
error!("Unknown flow for authentication");