parsec_service/authenticators/direct_authenticator/
mod.rs

1// Copyright 2019 Contributors to the Parsec project.
2// SPDX-License-Identifier: Apache-2.0
3//! Direct authenticator
4//!
5//! The `DirectAuthenticator` implements the [direct authentication](https://parallaxsecond.github.io/parsec-book/parsec_service/system_architecture.html#authentication-tokens)
6//! functionality set out in the system architecture. As such, it attempts to parse the request
7//! authentication field into an UTF-8 string and returns the result as an application name.
8//! This authenticator does not offer any security value and should only be used in environments
9//! where all the clients and the service are mutually trustworthy.
10
11use super::{AdminList, Application, ApplicationIdentity, Authenticate};
12use crate::front::listener::ConnectionMetadata;
13use crate::utils::config::Admin;
14use log::error;
15use parsec_interface::operations::list_authenticators;
16use parsec_interface::requests::request::RequestAuth;
17use parsec_interface::requests::AuthType;
18use parsec_interface::requests::{ResponseStatus, Result};
19use parsec_interface::secrecy::ExposeSecret;
20use std::str;
21
22/// Direct authentication authenticator implementation
23#[derive(Clone, Debug)]
24pub struct DirectAuthenticator {
25    admins: AdminList,
26}
27
28impl DirectAuthenticator {
29    /// Create new direct authenticator
30    pub fn new(admins: Vec<Admin>) -> Self {
31        DirectAuthenticator {
32            admins: admins.into(),
33        }
34    }
35}
36
37impl Authenticate for DirectAuthenticator {
38    fn describe(&self) -> Result<list_authenticators::AuthenticatorInfo> {
39        Ok(list_authenticators::AuthenticatorInfo {
40            description: String::from(
41                "Directly parses the authentication field as a UTF-8 string and uses that as the \
42                application identity. Should be used for testing only.",
43            ),
44            version_maj: 0,
45            version_min: 1,
46            version_rev: 0,
47            id: AuthType::Direct,
48        })
49    }
50
51    fn authenticate(
52        &self,
53        auth: &RequestAuth,
54        _: Option<ConnectionMetadata>,
55    ) -> Result<Application> {
56        if auth.buffer.expose_secret().is_empty() {
57            error!("The direct authenticator does not expect empty authentication values.");
58            Err(ResponseStatus::AuthenticationError)
59        } else {
60            match str::from_utf8(auth.buffer.expose_secret()) {
61                Ok(str) => {
62                    let app_name = String::from(str);
63                    let is_admin = self.admins.is_admin(&app_name);
64                    Ok(Application {
65                        identity: ApplicationIdentity {
66                            name: app_name,
67                            auth: AuthType::Direct.into(),
68                        },
69                        is_admin,
70                    })
71                }
72                Err(_) => {
73                    error!("Error parsing the authentication value as a UTF-8 string.");
74                    Err(ResponseStatus::AuthenticationError)
75                }
76            }
77        }
78    }
79}
80
81#[cfg(test)]
82mod test {
83    use super::super::Authenticate;
84    use super::DirectAuthenticator;
85    use parsec_interface::requests::request::RequestAuth;
86    use parsec_interface::requests::ResponseStatus;
87
88    #[test]
89    fn successful_authentication() {
90        let authenticator = DirectAuthenticator {
91            admins: Default::default(),
92        };
93
94        let app_name = "app_name".to_string();
95        let req_auth = RequestAuth::new(app_name.clone().into_bytes());
96        let conn_metadata = None;
97
98        let application = authenticator
99            .authenticate(&req_auth, conn_metadata)
100            .expect("Failed to authenticate");
101
102        assert_eq!(application.identity.name, app_name);
103        assert!(!application.is_admin);
104    }
105
106    #[test]
107    fn failed_authentication() {
108        let authenticator = DirectAuthenticator {
109            admins: Default::default(),
110        };
111        let conn_metadata = None;
112        let status = authenticator
113            .authenticate(&RequestAuth::new(vec![0xff; 5]), conn_metadata)
114            .expect_err("Authentication should have failed");
115
116        assert_eq!(status, ResponseStatus::AuthenticationError);
117    }
118
119    #[test]
120    fn empty_auth() {
121        let authenticator = DirectAuthenticator {
122            admins: Default::default(),
123        };
124        let conn_metadata = None;
125        let status = authenticator
126            .authenticate(&RequestAuth::new(Vec::new()), conn_metadata)
127            .expect_err("Empty auth should have failed");
128
129        assert_eq!(status, ResponseStatus::AuthenticationError);
130    }
131
132    #[test]
133    fn admin_check() {
134        let admin_name = String::from("admin_name");
135        let admin = toml::from_str(&format!("name = '{}'", admin_name)).unwrap();
136        let authenticator = DirectAuthenticator {
137            admins: vec![admin].into(),
138        };
139
140        let app_name = "app_name".to_string();
141        let req_auth = RequestAuth::new(app_name.clone().into_bytes());
142        let conn_metadata = None;
143
144        let application = authenticator
145            .authenticate(&req_auth, conn_metadata)
146            .expect("Failed to authenticate");
147
148        assert_eq!(application.identity.name, app_name);
149        assert!(!application.is_admin);
150
151        let req_auth = RequestAuth::new(admin_name.clone().into_bytes());
152        let application = authenticator
153            .authenticate(&req_auth, conn_metadata)
154            .expect("Failed to authenticate");
155
156        assert_eq!(application.identity.name, admin_name);
157        assert!(application.is_admin);
158    }
159}