oauth2_gitlab/
authorization_code_grant.rs1use 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}