1use std::env;
4use std::io::Read;
5use std::str;
6use std::sync::Arc;
7use std::time::Duration;
8
9use backoff::{Error as BackoffError, ExponentialBackoff, Operation};
10use failure::ResultExt;
11use reqwest::{StatusCode, Url};
12use reqwest::blocking::{Client, Response};
13use url::ParseError;
14
15use crate::parsers::*;
16use crate::{AccessToken, InitializationError, InitializationResult, TokenInfo};
17use crate::{TokenInfoError, TokenInfoErrorKind, TokenInfoResult, TokenInfoService};
18
19#[cfg(feature = "async")]
20use crate::async_client::AsyncTokenInfoServiceClientLight;
21#[cfg(feature = "metrix")]
22use crate::metrics::metrix::MetrixCollector;
23#[cfg(feature = "async")]
24use crate::metrics::{DevNullMetricsCollector, MetricsCollector};
25#[cfg(feature = "metrix")]
26use metrix::processor::{AggregatesProcessors, ProcessorMount};
27
28pub struct TokenInfoServiceClientBuilder<P: TokenInfoParser> {
38 pub parser: Option<P>,
39 pub endpoint: Option<String>,
40 pub query_parameter: Option<String>,
41 pub fallback_endpoint: Option<String>,
42}
43
44impl<P> TokenInfoServiceClientBuilder<P>
45where
46 P: TokenInfoParser + Clone + Sync + Send + 'static,
47{
48 pub fn new(parser: P) -> Self {
51 let mut builder = Self::default();
52 builder.with_parser(parser);
53 builder
54 }
55
56 pub fn with_parser(&mut self, parser: P) -> &mut Self {
58 self.parser = Some(parser);
59 self
60 }
61
62 pub fn with_endpoint<T: Into<String>>(&mut self, endpoint: T) -> &mut Self {
65 self.endpoint = Some(endpoint.into());
66 self
67 }
68
69 pub fn with_fallback_endpoint<T: Into<String>>(&mut self, endpoint: T) -> &mut Self {
72 self.fallback_endpoint = Some(endpoint.into());
73 self
74 }
75
76 pub fn with_query_parameter<T: Into<String>>(&mut self, parameter: T) -> &mut Self {
79 self.query_parameter = Some(parameter.into());
80 self
81 }
82
83 pub fn build(self) -> InitializationResult<TokenInfoServiceClient> {
86 let parser = if let Some(parser) = self.parser {
87 parser
88 } else {
89 return Err(InitializationError("No token info parser.".into()));
90 };
91
92 let endpoint = if let Some(endpoint) = self.endpoint {
93 endpoint
94 } else {
95 return Err(InitializationError("No endpoint.".into()));
96 };
97
98 TokenInfoServiceClient::new::<P>(
99 &endpoint,
100 self.query_parameter.as_ref().map(|s| &**s),
101 self.fallback_endpoint.as_ref().map(|s| &**s),
102 parser,
103 )
104 }
105
106 #[cfg(feature = "async")]
109 pub fn build_async(
110 self,
111 ) -> InitializationResult<AsyncTokenInfoServiceClientLight<P, DevNullMetricsCollector>> {
112 self.build_async_with_metrics(DevNullMetricsCollector)
113 }
114
115 #[cfg(feature = "async")]
118 pub fn build_async_with_metrics<M>(
119 self,
120 metrics_collector: M,
121 ) -> InitializationResult<AsyncTokenInfoServiceClientLight<P, M>>
122 where
123 M: MetricsCollector + Clone + Send + 'static,
124 {
125 let parser = if let Some(parser) = self.parser {
126 parser
127 } else {
128 return Err(InitializationError("No token info parser.".into()));
129 };
130
131 let endpoint = if let Some(endpoint) = self.endpoint {
132 endpoint
133 } else {
134 return Err(InitializationError("No endpoint.".into()));
135 };
136
137 AsyncTokenInfoServiceClientLight::with_metrics(
138 &endpoint,
139 self.query_parameter.as_ref().map(|s| &**s),
140 self.fallback_endpoint.as_ref().map(|s| &**s),
141 parser,
142 metrics_collector,
143 )
144 }
145
146 #[cfg(all(feature = "async", feature = "metrix"))]
153 pub fn build_async_with_metrix<M, T>(
154 self,
155 takes_metrics: &mut M,
156 group_name: Option<T>,
157 ) -> InitializationResult<AsyncTokenInfoServiceClientLight<P, MetrixCollector>>
158 where
159 M: AggregatesProcessors,
160 T: Into<String>,
161 {
162 let metrics_collector = if let Some(group) = group_name {
163 let mut mount = ProcessorMount::new(group);
164 let collector = MetrixCollector::new(&mut mount);
165 takes_metrics.add_processor(mount);
166 collector
167 } else {
168 MetrixCollector::new(takes_metrics)
169 };
170
171 self.build_async_with_metrics(metrics_collector)
172 }
173
174 pub fn from_env() -> InitializationResult<Self> {
189 let endpoint = env::var("TOKKIT_TOKEN_INTROSPECTION_ENDPOINT").map_err(|err| {
190 InitializationError(format!("'TOKKIT_TOKEN_INTROSPECTION_ENDPOINT': {}", err))
191 })?;
192 let query_parameter = match env::var("TOKKIT_TOKEN_INTROSPECTION_QUERY_PARAMETER") {
193 Ok(v) => Some(v),
194 Err(env::VarError::NotPresent) => None,
195 Err(err) => {
196 return Err(InitializationError(format!(
197 "'TOKKIT_TOKEN_INTROSPECTION_QUERY_PARAMETER': {}",
198 err
199 )));
200 }
201 };
202 let fallback_endpoint = match env::var("TOKKIT_TOKEN_INTROSPECTION_FALLBACK_ENDPOINT") {
203 Ok(v) => Some(v),
204 Err(env::VarError::NotPresent) => None,
205 Err(err) => {
206 return Err(InitializationError(format!(
207 "'TOKKIT_TOKEN_INTROSPECTION_FALLBACK_ENDPOINT': {}",
208 err
209 )));
210 }
211 };
212 Ok(TokenInfoServiceClientBuilder {
213 parser: Default::default(),
214 endpoint: Some(endpoint),
215 query_parameter,
216 fallback_endpoint,
217 })
218 }
219}
220
221impl TokenInfoServiceClientBuilder<PlanBTokenInfoParser> {
222 pub fn plan_b(endpoint: String) -> TokenInfoServiceClientBuilder<PlanBTokenInfoParser> {
226 let mut builder = Self::default();
227 builder.with_parser(PlanBTokenInfoParser);
228 builder.with_endpoint(endpoint);
229 builder.with_query_parameter("access_token");
230 builder
231 }
232
233 pub fn plan_b_from_env(
242 ) -> InitializationResult<TokenInfoServiceClientBuilder<PlanBTokenInfoParser>> {
243 let mut builder = Self::from_env()?;
244 builder.with_parser(PlanBTokenInfoParser);
245 builder.with_query_parameter("access_token");
246 Ok(builder)
247 }
248}
249
250impl TokenInfoServiceClientBuilder<GoogleV3TokenInfoParser> {
251 pub fn google_v3() -> TokenInfoServiceClientBuilder<GoogleV3TokenInfoParser> {
256 let mut builder = Self::default();
257 builder.with_parser(GoogleV3TokenInfoParser);
258 builder.with_endpoint("https://www.googleapis.com/oauth2/v3/tokeninfo");
259 builder.with_query_parameter("access_token");
260 builder
261 }
262}
263
264impl TokenInfoServiceClientBuilder<AmazonTokenInfoParser> {
265 pub fn amazon() -> TokenInfoServiceClientBuilder<AmazonTokenInfoParser> {
270 let mut builder = Self::default();
271 builder.with_parser(AmazonTokenInfoParser);
272 builder.with_endpoint("https://api.amazon.com/auth/O2/tokeninfo");
273 builder.with_query_parameter("access_token");
274 builder
275 }
276}
277
278impl<P: TokenInfoParser> Default for TokenInfoServiceClientBuilder<P> {
279 fn default() -> Self {
280 TokenInfoServiceClientBuilder {
281 parser: Default::default(),
282 endpoint: Default::default(),
283 query_parameter: Default::default(),
284 fallback_endpoint: Default::default(),
285 }
286 }
287}
288
289pub struct TokenInfoServiceClient {
296 url_prefix: Arc<String>,
297 fallback_url_prefix: Option<Arc<String>>,
298 http_client: Client,
299 parser: Arc<dyn TokenInfoParser + Sync + Send + 'static>,
300}
301
302impl TokenInfoServiceClient {
303 pub fn new<P>(
306 endpoint: &str,
307 query_parameter: Option<&str>,
308 fallback_endpoint: Option<&str>,
309 parser: P,
310 ) -> InitializationResult<TokenInfoServiceClient>
311 where
312 P: TokenInfoParser + Sync + Send + 'static,
313 {
314 let url_prefix = assemble_url_prefix(endpoint, &query_parameter)
315 .map_err(InitializationError)?;
316
317 let fallback_url_prefix = if let Some(fallback_endpoint_address) = fallback_endpoint {
318 Some(
319 assemble_url_prefix(fallback_endpoint_address, &query_parameter)
320 .map_err(InitializationError)?,
321 )
322 } else {
323 None
324 };
325
326 let client = Client::new();
327 Ok(TokenInfoServiceClient {
328 url_prefix: Arc::new(url_prefix),
329 fallback_url_prefix: fallback_url_prefix.map(Arc::new),
330 http_client: client,
331 parser: Arc::new(parser),
332 })
333 }
334}
335
336pub(crate) fn assemble_url_prefix(
337 endpoint: &str,
338 query_parameter: &Option<&str>,
339) -> ::std::result::Result<String, String> {
340 let mut url_prefix = String::from(endpoint);
341
342 if let Some(query_parameter) = query_parameter {
343 if url_prefix.ends_with('/') {
344 url_prefix.pop();
345 }
346 url_prefix.push_str(&format!("?{}=", query_parameter));
347 } else if !url_prefix.ends_with('/') {
348 url_prefix.push('/');
349 }
350
351 let test_url = format!("{}test_token", url_prefix);
352 let _ = test_url
353 .parse::<Url>()
354 .map_err(|err| format!("Invalid URL: {}", err))?;
355
356 Ok(url_prefix)
357}
358
359impl TokenInfoService for TokenInfoServiceClient {
360 fn introspect(&self, token: &AccessToken) -> TokenInfoResult<TokenInfo> {
361 let url: Url = complete_url(&self.url_prefix, token)?;
362 let fallback_url = match self.fallback_url_prefix {
363 Some(ref fb_url_prefix) => Some(complete_url(fb_url_prefix, token)?),
364 None => None,
365 };
366 get_with_fallback(url, fallback_url, &self.http_client, &*self.parser)
367 }
368}
369
370impl Clone for TokenInfoServiceClient {
371 fn clone(&self) -> Self {
372 TokenInfoServiceClient {
373 url_prefix: self.url_prefix.clone(),
374 fallback_url_prefix: self.fallback_url_prefix.clone(),
375 http_client: self.http_client.clone(),
376 parser: self.parser.clone(),
377 }
378 }
379}
380
381fn complete_url(url_prefix: &str, token: &AccessToken) -> TokenInfoResult<Url> {
382 let mut url_str = url_prefix.to_string();
383 url_str.push_str(token.0.as_ref());
384 let url = url_str.parse()?;
385 Ok(url)
386}
387
388fn get_with_fallback(
389 url: Url,
390 fallback_url: Option<Url>,
391 client: &Client,
392 parser: &dyn TokenInfoParser,
393) -> TokenInfoResult<TokenInfo> {
394 get_from_remote(url, client, parser).or_else(|err| match *err.kind() {
395 TokenInfoErrorKind::Client(_) => Err(err),
396 _ => fallback_url
397 .map(|url| get_from_remote(url, client, parser))
398 .unwrap_or(Err(err)),
399 })
400}
401
402fn get_from_remote<P>(
403 url: Url,
404 http_client: &Client,
405 parser: &P,
406) -> TokenInfoResult<TokenInfo>
407where
408 P: TokenInfoParser + ?Sized,
409{
410 let mut op = || match get_from_remote_no_retry(url.clone(), http_client, parser) {
411 Ok(token_info) => Ok(token_info),
412 Err(err) => match *err.kind() {
413 TokenInfoErrorKind::InvalidResponseContent(_) => Err(BackoffError::Permanent(err)),
414 TokenInfoErrorKind::UrlError(_) => Err(BackoffError::Permanent(err)),
415 TokenInfoErrorKind::NotAuthenticated(_) => Err(BackoffError::Permanent(err)),
416 TokenInfoErrorKind::Client(_) => Err(BackoffError::Permanent(err)),
417 _ => Err(BackoffError::Transient(err)),
418 },
419 };
420
421 let mut backoff = ExponentialBackoff::default();
422 backoff.max_elapsed_time = Some(Duration::from_millis(200));
423 backoff.initial_interval = Duration::from_millis(10);
424 backoff.multiplier = 1.5;
425
426 let notify = |err, _| {
427 warn!("Retry on token info service: {}", err);
428 };
429
430 let retry_result = op.retry_notify(&mut backoff, notify);
431
432 match retry_result {
433 Ok(token_info) => Ok(token_info),
434 Err(BackoffError::Transient(err)) => Err(err),
435 Err(BackoffError::Permanent(err)) => Err(err),
436 }
437}
438
439fn get_from_remote_no_retry<P>(
440 url: Url,
441 http_client: &Client,
442 parser: &P,
443) -> TokenInfoResult<TokenInfo>
444where
445 P: TokenInfoParser + ?Sized,
446{
447 let request_builder = http_client.get(url);
448 match request_builder.send() {
449 Ok(ref mut response) => process_response(response, parser),
450 Err(err) => Err(TokenInfoErrorKind::Connection(err.to_string()).into()),
451 }
452}
453
454fn process_response<P>(
455 response: &mut Response,
456 parser: &P,
457) -> TokenInfoResult<TokenInfo>
458where
459 P: TokenInfoParser + ?Sized,
460{
461 let mut body = Vec::new();
462 response
463 .read_to_end(&mut body)
464 .context(TokenInfoErrorKind::Io(
465 "Could not read response bode".to_string(),
466 ))?;
467 if response.status() == StatusCode::OK {
468 let result: TokenInfo = match parser.parse(&body) {
469 Ok(info) => info,
470 Err(msg) => {
471 return Err(TokenInfoErrorKind::InvalidResponseContent(msg.to_string()).into());
472 }
473 };
474 Ok(result)
475 } else if response.status() == StatusCode::UNAUTHORIZED {
476 let msg = str::from_utf8(&body)?;
477 Err(TokenInfoErrorKind::NotAuthenticated(format!(
478 "The server refused the token: {}",
479 msg
480 ))
481 .into())
482 } else if response.status().is_client_error() {
483 let msg = str::from_utf8(&body)?;
484 Err(TokenInfoErrorKind::Client(msg.to_string()).into())
485 } else if response.status().is_server_error() {
486 let msg = str::from_utf8(&body)?;
487 Err(TokenInfoErrorKind::Server(msg.to_string()).into())
488 } else {
489 let msg = str::from_utf8(&body)?;
490 Err(TokenInfoErrorKind::Other(msg.to_string()).into())
491 }
492}
493
494impl From<ParseError> for TokenInfoError {
495 fn from(what: ParseError) -> Self {
496 TokenInfoErrorKind::UrlError(what.to_string()).into()
497 }
498}
499
500impl From<str::Utf8Error> for TokenInfoError {
501 fn from(what: str::Utf8Error) -> Self {
502 TokenInfoErrorKind::InvalidResponseContent(what.to_string()).into()
503 }
504}