Add client-token header to spclient requests

- Also fix an overflow panic when a token cannot be parsed.

- Getting tokens always requires the keymaster client ID;
  passing the actual client ID yields a HashCash challenge.
This commit is contained in:
Roderick van Domburg 2022-08-02 23:06:02 +02:00
parent ebfe8ca36c
commit cdf84925ad
No known key found for this signature in database
GPG key ID: 87F5FDE8A56219F4
3 changed files with 12 additions and 6 deletions

View file

@ -2,7 +2,7 @@ use std::{fmt, path::PathBuf, str::FromStr};
use url::Url; use url::Url;
const KEYMASTER_CLIENT_ID: &str = "65b708073fc0480ea92a077233ca87bd"; pub const KEYMASTER_CLIENT_ID: &str = "65b708073fc0480ea92a077233ca87bd";
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SessionConfig { pub struct SessionConfig {

View file

@ -9,7 +9,7 @@ use futures_util::future::IntoStream;
use http::header::HeaderValue; use http::header::HeaderValue;
use hyper::{ use hyper::{
client::ResponseFuture, client::ResponseFuture,
header::{ACCEPT, AUTHORIZATION, CONTENT_ENCODING, CONTENT_TYPE, RANGE}, header::{HeaderName, ACCEPT, AUTHORIZATION, CONTENT_ENCODING, CONTENT_TYPE, RANGE},
Body, HeaderMap, Method, Request, Body, HeaderMap, Method, Request,
}; };
use protobuf::Message; use protobuf::Message;
@ -19,6 +19,7 @@ use thiserror::Error;
use crate::{ use crate::{
apresolve::SocketAddress, apresolve::SocketAddress,
cdn_url::CdnUrl, cdn_url::CdnUrl,
config::KEYMASTER_CLIENT_ID,
error::ErrorKind, error::ErrorKind,
protocol::{ protocol::{
canvaz::EntityCanvazRequest, canvaz::EntityCanvazRequest,
@ -40,6 +41,9 @@ component! {
pub type SpClientResult = Result<Bytes, Error>; pub type SpClientResult = Result<Bytes, Error>;
#[allow(clippy::declare_interior_mutable_const)]
const CLIENT_TOKEN: HeaderName = HeaderName::from_static("client-token");
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum SpClientError { pub enum SpClientError {
#[error("missing attribute {0}")] #[error("missing attribute {0}")]
@ -116,7 +120,7 @@ impl SpClient {
message.set_request_type(ClientTokenRequestType::REQUEST_CLIENT_DATA_REQUEST); message.set_request_type(ClientTokenRequestType::REQUEST_CLIENT_DATA_REQUEST);
let client_data = message.mut_client_data(); let client_data = message.mut_client_data();
client_data.set_client_id(self.session().client_id()); client_data.set_client_id(KEYMASTER_CLIENT_ID.to_string());
client_data.set_client_version(version::SEMVER.to_string()); client_data.set_client_version(version::SEMVER.to_string());
let connectivity_data = client_data.mut_connectivity_sdk_data(); let connectivity_data = client_data.mut_connectivity_sdk_data();
@ -287,6 +291,7 @@ impl SpClient {
.token_provider() .token_provider()
.get_token("playlist-read") .get_token("playlist-read")
.await?; .await?;
let client_token = self.client_token().await?;
let headers_mut = request.headers_mut(); let headers_mut = request.headers_mut();
if let Some(ref hdrs) = headers { if let Some(ref hdrs) = headers {
@ -296,6 +301,7 @@ impl SpClient {
AUTHORIZATION, AUTHORIZATION,
HeaderValue::from_str(&format!("{} {}", token.token_type, token.access_token,))?, HeaderValue::from_str(&format!("{} {}", token.token_type, token.access_token,))?,
); );
headers_mut.insert(CLIENT_TOKEN, HeaderValue::from_str(&client_token)?);
last_response = self.session().http_client().request_body(request).await; last_response = self.session().http_client().request_body(request).await;

View file

@ -13,7 +13,7 @@ use std::time::{Duration, Instant};
use serde::Deserialize; use serde::Deserialize;
use thiserror::Error; use thiserror::Error;
use crate::Error; use crate::{config::KEYMASTER_CLIENT_ID, Error};
component! { component! {
TokenProvider : TokenProviderInner { TokenProvider : TokenProviderInner {
@ -65,7 +65,7 @@ impl TokenProvider {
// scopes must be comma-separated // scopes must be comma-separated
pub async fn get_token(&self, scopes: &str) -> Result<Token, Error> { pub async fn get_token(&self, scopes: &str) -> Result<Token, Error> {
let client_id = self.session().client_id(); let client_id = KEYMASTER_CLIENT_ID;
if client_id.is_empty() { if client_id.is_empty() {
return Err(Error::invalid_argument("Client ID cannot be empty")); return Err(Error::invalid_argument("Client ID cannot be empty"));
} }
@ -115,7 +115,7 @@ impl Token {
} }
pub fn is_expired(&self) -> bool { pub fn is_expired(&self) -> bool {
self.timestamp + (self.expires_in - Self::EXPIRY_THRESHOLD) < Instant::now() self.timestamp + (self.expires_in.saturating_sub(Self::EXPIRY_THRESHOLD)) < Instant::now()
} }
pub fn in_scope(&self, scope: &str) -> bool { pub fn in_scope(&self, scope: &str) -> bool {