drogue_bazaar/actix/auth/authentication/
mod.rs

1mod middleware;
2
3use crate::auth::{openid, pat, AuthError, UserInformation};
4use ::openid::{Claims, CustomClaims};
5use chrono::{DateTime, LocalResult, TimeZone, Utc};
6pub use middleware::AuthenticatedUntil;
7use tracing::instrument;
8
9/// Credentials
10pub enum Credentials {
11    /// openID token
12    OpenIDToken(String),
13    /// username + Personal Access Token
14    AccessToken(UsernameAndToken),
15    /// Anonymous
16    Anonymous,
17}
18
19pub struct UsernameAndToken {
20    pub username: String,
21    pub access_token: Option<String>,
22}
23
24/// An Authentication middleware for actix-web relying on drogue-cloud user-auth-service and an openID service
25///
26/// This middleware will act on each request and try to authenticate the request with :
27/// - The `Authorisation: Bearer` header, which should contain an openID token.
28/// - The `Authorisation: Basic` header, which should contain a username and an access token issued by the drogue-cloud API.
29/// - The `token` query parameter, which should contain am openID token.
30///
31/// If more than one of the above is provided, the request will be responded with `400: Bad request.`
32///
33/// After the authentication is successful, this middleware will inject the `UserInformation` in the request object and forward it.
34///
35/// # Fields
36///
37/// * `open_id` - An instance of [`openid::Authenticator`] It's an openID client. It is used to verify OpenID tokens.
38/// * `token` - An instance of [`pat::Authenticator`]. It's a client for drogue-cloud-user-auth-service. It is used to verify API keys.
39/// * `enable_access_token` - Whether to allow access tokens for authentication.
40///
41#[derive(Clone, Debug)]
42pub enum AuthN {
43    /// Authentication is disabled, all requests will be using [`UserInformation::Anonymous`].
44    Disabled,
45    /// Authentication is enabled, using openid or API tokens.
46    ///
47    /// **NOTE:** If neither is provided, all requests will fail.
48    Enabled {
49        openid: Option<openid::Authenticator>,
50        token: Option<pat::Authenticator>,
51    },
52}
53
54/// Map a combination of openid and PAT authenticator
55impl From<(Option<openid::Authenticator>, Option<pat::Authenticator>)> for AuthN {
56    fn from(auth: (Option<openid::Authenticator>, Option<pat::Authenticator>)) -> Self {
57        let (openid, token) = auth;
58        if openid.is_none() {
59            AuthN::Disabled
60        } else {
61            AuthN::Enabled { openid, token }
62        }
63    }
64}
65
66impl AuthN {
67    #[instrument(skip_all, err)]
68    async fn authenticate(
69        &self,
70        credentials: Credentials,
71    ) -> Result<(UserInformation, Option<DateTime<Utc>>), AuthError> {
72        match self {
73            Self::Disabled => {
74                // authentication disabled
75                Ok((UserInformation::Anonymous, None))
76            }
77            Self::Enabled { openid, token } => match credentials {
78                Credentials::AccessToken(creds) => {
79                    if let Some(token) = token {
80                        if creds.access_token.is_none() {
81                            log::debug!("Cannot authenticate : empty access token.");
82                            return Err(AuthError::InvalidRequest(String::from(
83                                "No access token provided.",
84                            )));
85                        }
86                        let auth_response = token
87                            .authenticate(pat::Request {
88                                user_id: creds.username.clone(),
89                                access_token: creds.access_token.clone().unwrap_or_default(),
90                            })
91                            .await
92                            .map_err(|e| AuthError::Internal(e.to_string()))?;
93                        match auth_response.outcome {
94                            pat::Outcome::Known(details) => {
95                                Ok((UserInformation::Authenticated(details), None))
96                            }
97                            pat::Outcome::Unknown => {
98                                log::debug!("Unknown access token");
99                                Err(AuthError::Forbidden)
100                            }
101                        }
102                    } else {
103                        log::debug!("Access token authentication disabled");
104                        Err(AuthError::InvalidRequest(
105                            "Access token authentication disabled".to_string(),
106                        ))
107                    }
108                }
109                Credentials::OpenIDToken(token) => {
110                    if let Some(openid) = openid {
111                        match openid.validate_token(&token).await {
112                            Ok(token) => Ok((
113                                UserInformation::Authenticated(token.clone().into()),
114                                Some(to_expiration(token.standard_claims().exp())?),
115                            )),
116                            Err(err) => {
117                                log::debug!("Authentication error: {err}");
118                                Err(AuthError::Forbidden)
119                            }
120                        }
121                    } else {
122                        log::debug!("Open ID authentication disabled");
123                        Err(AuthError::InvalidRequest(
124                            "Open ID authentication disabled".to_string(),
125                        ))
126                    }
127                }
128                Credentials::Anonymous => Ok((UserInformation::Anonymous, None)),
129            },
130        }
131    }
132}
133
134/// Convert "exp" timestamp to `DateTime`.
135fn to_expiration(exp: i64) -> Result<DateTime<Utc>, AuthError> {
136    match Utc.timestamp_opt(exp, 0) {
137        LocalResult::None => Err(AuthError::Internal(
138            "Unable to convert timestamp".to_string(),
139        )),
140        LocalResult::Single(exp) => Ok(exp),
141        LocalResult::Ambiguous(min, _) => Ok(min),
142    }
143}