github_scopes_rs/
oauth.rs1use super::transform::{
5 GithubScopeAdminLevel, GithubScopeEnterprise, GithubScopeLevel, GithubScopeRepo,
6 GithubScopeUser, GithubTokenScope,
7};
8use anyhow::anyhow;
9use anyhow::Result as AnyResult;
10use log::debug;
11use reqwest::blocking::Client;
12use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION};
13use std::time::Duration;
14
15const MAX_REQUEST_TIME: Duration = Duration::from_secs(1);
17
18const HEADER_SCOPE_KEY: &str = "x-oauth-scopes";
20
21const GITHUB_API_DOMAIN: &str = "https://api.github.com";
23
24pub struct OAuthContext {
26 client: reqwest::blocking::Client,
27 token: String,
28 domain: String,
29 pub scope: Vec<String>,
30}
31
32impl OAuthContext {
33 pub fn new(token: &str) -> AnyResult<Self> {
35 OAuthContext::create(token, GITHUB_API_DOMAIN)
36 }
37
38 pub fn with_domain(token: &str, domain: &str) -> AnyResult<Self> {
40 OAuthContext::create(token, domain)
41 }
42
43 fn create(token: &str, domain: &str) -> AnyResult<Self> {
44 let client = Client::builder()
45 .user_agent(env!("CARGO_PKG_NAME"))
46 .timeout(MAX_REQUEST_TIME)
47 .build()
48 .unwrap();
49
50 let mut scope = OAuthContext {
51 client,
52 token: token.to_string(),
53 domain: domain.to_string(),
54 scope: Vec::new(),
55 };
56 scope.refresh(None)?;
57 Ok(scope)
58 }
59
60 pub fn refresh(&mut self, token: Option<String>) -> AnyResult<()> {
62 let mut headers = HeaderMap::new();
63
64 if let Some(t) = token {
65 self.token = t;
66 debug!("refresh token request from given token");
67 }
68
69 headers.insert(
70 AUTHORIZATION,
71 self.add_authorization_token(self.token.as_ref()),
72 );
73
74 let response = self
75 .client
76 .get(format!("{}/rate_limit", self.domain))
77 .headers(headers)
78 .send()?;
79
80 debug!(
81 "github rate limit response. statusCode:{}, headers:, {:?}",
82 response.status(),
83 response.headers(),
84 );
85
86 if !response.status().is_success() {
87 debug!("could not get token. StatusCode: {}", response.status());
88 return Err(anyhow!("request failed"));
89 }
90
91 let headers_response = response.headers();
92 self.scope = match headers_response.get(HEADER_SCOPE_KEY) {
93 Some(v) => v
94 .to_str()
95 .unwrap()
96 .split(',')
97 .map(|s| s.trim().to_string())
98 .collect::<Vec<String>>(),
99 None => {
100 return Err(anyhow!(format!(
101 "could not get {} from headers response",
102 HEADER_SCOPE_KEY
103 )))
104 }
105 };
106 Ok(())
107 }
108
109 pub fn get_scope_permissions(&self) -> GithubTokenScope {
111 GithubTokenScope {
112 repo: GithubScopeRepo {
113 all: self.scope.contains(&"repo".to_owned()),
114 status: self.scope.contains(&"repo".to_owned())
115 || self.scope.contains(&"repo:status".to_owned()),
116 deployment: self.scope.contains(&"repo".to_owned())
117 || self.scope.contains(&"repo_deployment".to_owned()),
118 public_repo: self.scope.contains(&"repo".to_owned())
119 || self.scope.contains(&"public_repo".to_owned()),
120 invite: self.scope.contains(&"repo".to_owned())
121 || self.scope.contains(&"repo:invite".to_owned()),
122 security_events: self.scope.contains(&"repo".to_owned())
123 || self.scope.contains(&"security_events".to_owned()),
124 },
125 workflow: self.scope.contains(&"workflow".to_owned()),
126 packages: GithubScopeLevel {
127 write: self.scope.contains(&"write:packages".to_owned()),
128 read: self.scope.contains(&"write:packages".to_owned())
129 || self.scope.contains(&"read:packages".to_owned()),
130 },
131 delete_packages: self.scope.contains(&"delete:packages".to_owned()),
132 org: GithubScopeAdminLevel {
133 admin: self.scope.contains(&"admin:org".to_owned()),
134 write: self.scope.contains(&"admin:org".to_owned())
135 || self.scope.contains(&"write:org".to_owned()),
136 read: self.scope.contains(&"admin:org".to_owned())
137 || self.scope.contains(&"write:org".to_owned())
138 || self.scope.contains(&"read:org".to_owned()),
139 },
140 public_key: GithubScopeAdminLevel {
141 admin: self.scope.contains(&"admin:public_key".to_owned()),
142 write: self.scope.contains(&"admin:public_key".to_owned())
143 || self.scope.contains(&"write:public_key".to_owned()),
144 read: self.scope.contains(&"admin:public_key".to_owned())
145 || self.scope.contains(&"write:public_key".to_owned())
146 || self.scope.contains(&"read:public_key".to_owned()),
147 },
148 repo_hook: GithubScopeAdminLevel {
149 admin: self.scope.contains(&"admin:repo_hook".to_owned()),
150 write: self.scope.contains(&"admin:repo_hook".to_owned())
151 || self.scope.contains(&"write:repo_hook".to_owned()),
152 read: self.scope.contains(&"admin:repo_hook".to_owned())
153 || self.scope.contains(&"write:repo_hook".to_owned())
154 || self.scope.contains(&"read:repo_hook".to_owned()),
155 },
156 org_hook: self.scope.contains(&"admin:org_hook".to_owned()),
157 gist: self.scope.contains(&"gist".to_owned()),
158 notifications: self.scope.contains(&"notifications".to_owned()),
159 user: GithubScopeUser {
160 all: self.scope.contains(&"user".to_owned()),
161 read: self.scope.contains(&"user".to_owned())
162 || self.scope.contains(&"read:user".to_owned()),
163 email: self.scope.contains(&"user".to_owned())
164 || self.scope.contains(&"user:email".to_owned()),
165 follow: self.scope.contains(&"user".to_owned())
166 || self.scope.contains(&"user:follow".to_owned()),
167 },
168 delete_repo: self.scope.contains(&"delete_repo".to_owned()),
169 discussion: GithubScopeLevel {
170 write: self.scope.contains(&"write:discussion".to_owned()),
171 read: self.scope.contains(&"write:discussion".to_owned())
172 || self.scope.contains(&"read:discussion".to_owned()),
173 },
174 enterprise: GithubScopeEnterprise {
175 all: self.scope.contains(&"admin:enterprise".to_owned()),
176 manage_runners: self.scope.contains(&"admin:enterprise".to_owned())
177 || self.scope.contains(&"manage_runners:enterprise".to_owned()),
178 manage_billing: self.scope.contains(&"admin:enterprise".to_owned())
179 || self.scope.contains(&"manage_billing:enterprise".to_owned()),
180 read: self.scope.contains(&"admin:enterprise".to_owned())
181 || self.scope.contains(&"manage_billing:enterprise".to_owned())
182 || self.scope.contains(&"read:enterprise".to_owned()),
183 },
184 gpg_key: GithubScopeAdminLevel {
185 admin: self.scope.contains(&"admin:gpg_key".to_owned())
186 || self.scope.contains(&"manage_runners:enterprise".to_owned()),
187 write: self.scope.contains(&"admin:gpg_key".to_owned())
188 || self.scope.contains(&"write:gpg_key".to_owned()),
189 read: self.scope.contains(&"admin:gpg_key".to_owned())
190 || self.scope.contains(&"write:gpg_key".to_owned())
191 || self.scope.contains(&"read:gpg_key".to_owned()),
192 },
193 ssh_signing_key: GithubScopeAdminLevel {
194 admin: self.scope.contains(&"admin:ssh_signing_key".to_owned()),
195 write: self.scope.contains(&"admin:ssh_signing_key".to_owned())
196 || self.scope.contains(&"write:ssh_signing_key".to_owned()),
197 read: self.scope.contains(&"admin:ssh_signing_key".to_owned())
198 || self.scope.contains(&"write:ssh_signing_key".to_owned())
199 || self.scope.contains(&"read:ssh_signing_key".to_owned()),
200 },
201 }
202 }
203
204 fn add_authorization_token(&self, token: &str) -> HeaderValue {
205 HeaderValue::from_str(format!("token {}", token).as_ref()).unwrap()
206 }
207}