oauth2_gitlab/
authorization_code_grant.rs

1use oauth2_client::{
2    authorization_code_grant::provider_ext::{
3        ProviderExtAuthorizationCodeGrantOidcSupportType,
4        ProviderExtAuthorizationCodeGrantPkceSupportType,
5    },
6    re_exports::{ClientId, ClientSecret, Map, RedirectUri, Url, UrlParseError, Value},
7    Provider, ProviderExtAuthorizationCodeGrant,
8};
9use oauth2_doorkeeper::DoorkeeperProviderWithAuthorizationCodeFlow;
10
11use crate::{authorization_url, token_url, GitlabScope};
12
13#[derive(Debug, Clone)]
14pub struct GitlabProviderForEndUsers {
15    inner: DoorkeeperProviderWithAuthorizationCodeFlow<GitlabScope>,
16    base_url: Url,
17}
18impl GitlabProviderForEndUsers {
19    pub fn new(
20        base_url: impl AsRef<str>,
21        client_id: ClientId,
22        client_secret: ClientSecret,
23        redirect_uri: RedirectUri,
24    ) -> Result<Self, UrlParseError> {
25        Ok(Self {
26            inner: DoorkeeperProviderWithAuthorizationCodeFlow::<GitlabScope>::new(
27                client_id,
28                client_secret,
29                redirect_uri,
30                token_url(base_url.as_ref())?.as_str(),
31                authorization_url(base_url.as_ref())?.as_str(),
32            )?,
33            base_url: base_url.as_ref().parse()?,
34        })
35    }
36}
37impl Provider for GitlabProviderForEndUsers {
38    type Scope = GitlabScope;
39
40    fn client_id(&self) -> Option<&ClientId> {
41        self.inner.client_id()
42    }
43
44    fn client_secret(&self) -> Option<&ClientSecret> {
45        self.inner.client_secret()
46    }
47
48    fn token_endpoint_url(&self) -> &Url {
49        self.inner.token_endpoint_url()
50    }
51
52    fn extra(&self) -> Option<Map<String, Value>> {
53        let mut map = Map::new();
54        map.insert(
55            "base_url".to_owned(),
56            Value::String(self.base_url.to_string()),
57        );
58        Some(map)
59    }
60}
61impl ProviderExtAuthorizationCodeGrant for GitlabProviderForEndUsers {
62    fn redirect_uri(&self) -> Option<&RedirectUri> {
63        self.inner.redirect_uri()
64    }
65
66    fn oidc_support_type(&self) -> Option<ProviderExtAuthorizationCodeGrantOidcSupportType> {
67        Some(ProviderExtAuthorizationCodeGrantOidcSupportType::Yes)
68    }
69
70    fn pkce_support_type(&self) -> Option<ProviderExtAuthorizationCodeGrantPkceSupportType> {
71        Some(ProviderExtAuthorizationCodeGrantPkceSupportType::Yes)
72    }
73
74    fn scopes_default(&self) -> Option<Vec<<Self as Provider>::Scope>> {
75        Some(vec![
76            GitlabScope::Openid,
77            GitlabScope::Profile,
78            GitlabScope::Email,
79        ])
80    }
81
82    fn authorization_endpoint_url(&self) -> &Url {
83        self.inner.authorization_endpoint_url()
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    use oauth2_client::{
92        authorization_code_grant::AccessTokenEndpoint,
93        re_exports::{Endpoint as _, Response},
94    };
95
96    #[test]
97    fn access_token_response() -> Result<(), Box<dyn std::error::Error>> {
98        let provider = GitlabProviderForEndUsers::new(
99            "https://gitlab.com/",
100            "CLIENT_ID".to_owned(),
101            "CLIENT_SECRET".to_owned(),
102            RedirectUri::new("https://client.example.com/cb")?,
103        )?;
104
105        let response_body = include_str!(
106            "../tests/response_body_json_files/access_token_with_authorization_code_grant.json"
107        );
108        let body_ret = AccessTokenEndpoint::new(&provider, "CODE".to_owned())
109            .parse_response(Response::builder().body(response_body.as_bytes().to_vec())?)?;
110
111        match body_ret {
112            Ok(body) => {
113                let map = body.extra().unwrap();
114                assert!(map.get("created_at").is_some());
115            }
116            Err(body) => panic!("{body:?}"),
117        }
118
119        Ok(())
120    }
121}