Don't publish the ScopeSet struct

Just pass `&[&str]` into custom storage providers. The scopeset struct has a range of unnecessary internal features.

It's now also part of the interface for custom storage providers that the given scopes will be both unique and sorted.

The only slightly awkward thing is that there's no conventient way to expose a `scopes_covered_by` helper method (which almost all custom storage engines will need), but it's still included in the example code.
This commit is contained in:
Daniel Rodgers-Pryor
2021-03-25 21:08:12 +11:00
parent 384963e091
commit 98ee92f8b2
2 changed files with 39 additions and 41 deletions

View File

@@ -2,7 +2,7 @@
use anyhow::anyhow;
use async_trait::async_trait;
use std::sync::RwLock;
use yup_oauth2::storage::{ScopeSet, TokenInfo, TokenStorage};
use yup_oauth2::storage::{TokenInfo, TokenStorage};
struct ExampleTokenStore {
store: RwLock<Vec<StoredToken>>,
@@ -13,11 +13,19 @@ struct StoredToken {
serialized_token: String,
}
/// Is this set of scopes covered by the other? Returns true if the other
/// set is a superset of this one. Use this when implementing TokenStorage.get()
fn scopes_covered_by(scopes: &[&str], possible_match_or_superset: &[&str]) -> bool {
scopes
.iter()
.all(|s| possible_match_or_superset.iter().any(|t| t == s))
}
/// Here we implement our own token storage. You could write the serialized token and scope data
/// to disk, an OS keychain, a database or whatever suits your use-case
#[async_trait]
impl TokenStorage for ExampleTokenStore {
async fn set(&self, scopes: ScopeSet<'_, &str>, token: TokenInfo) -> anyhow::Result<()> {
async fn set(&self, scopes: &[&str], token: TokenInfo) -> anyhow::Result<()> {
let data = serde_json::to_string(&token).unwrap();
println!("Storing token for scopes {:?}", scopes);
@@ -28,18 +36,25 @@ impl TokenStorage for ExampleTokenStore {
.map_err(|_| anyhow!("Unable to lock store for writing"))?;
store.push(StoredToken {
scopes: scopes.scopes(),
scopes: scopes.iter().map(|str| str.to_string()).collect(),
serialized_token: data,
});
Ok(())
}
async fn get(&self, target_scopes: ScopeSet<'_, &str>) -> Option<TokenInfo> {
async fn get(&self, target_scopes: &[&str]) -> Option<TokenInfo> {
// Retrieve the token data
self.store.read().ok().and_then(|store| {
for stored_token in store.iter() {
if target_scopes.is_covered_by(&stored_token.scopes) {
if scopes_covered_by(
target_scopes,
&stored_token
.scopes
.iter()
.map(|s| &s[..])
.collect::<Vec<_>>()[..],
) {
return serde_json::from_str(&stored_token.serialized_token).ok();
}
}

View File

@@ -54,7 +54,7 @@ impl ScopeFilter {
/// A set of scopes
#[derive(Debug)]
pub struct ScopeSet<'a, T> {
pub(crate) struct ScopeSet<'a, T> {
hash: ScopeHash,
filter: ScopeFilter,
scopes: &'a [T],
@@ -109,25 +109,6 @@ where
scopes,
}
}
/// Get the scopes for storage when implementing TokenStorage.set().
/// Returned scope strings are unique and sorted.
pub fn scopes(&self) -> Vec<String> {
self.scopes
.iter()
.map(|scope| scope.as_ref().to_string())
.sorted()
.unique()
.collect()
}
/// Is this set of scopes covered by the other? Returns true if the other
/// set is a superset of this one. Use this when implementing TokenStorage.get()
pub fn is_covered_by(&self, other_scopes: &[String]) -> bool {
self.scopes
.iter()
.all(|s| other_scopes.iter().any(|t| t.as_str() == s.as_ref()))
}
}
/// Implement your own token storage solution by implementing this trait. You need a way to
@@ -137,10 +118,10 @@ pub trait TokenStorage: Send + Sync {
/// Store a token for the given set of scopes so that it can be retrieved later by get()
/// ScopeSet implements Hash so that you can easily serialize and store it.
/// TokenInfo can be serialized with serde.
async fn set(&self, scopes: ScopeSet<'_, &str>, token: TokenInfo) -> anyhow::Result<()>;
async fn set(&self, scopes: &[&str], token: TokenInfo) -> anyhow::Result<()>;
/// Retrieve a token stored by set for the given set of scopes
async fn get(&self, scopes: ScopeSet<'_, &str>) -> Option<TokenInfo>;
async fn get(&self, scopes: &[&str]) -> Option<TokenInfo>;
}
pub(crate) enum Storage {
@@ -162,15 +143,17 @@ impl Storage {
Storage::Memory { tokens } => Ok(tokens.lock().await.set(scopes, token)?),
Storage::Disk(disk_storage) => Ok(disk_storage.set(scopes, token).await?),
Storage::Custom(custom_storage) => {
let str_scopes: Vec<_> = scopes.scopes.iter().map(|scope| scope.as_ref()).collect();
let str_scopes: Vec<_> = scopes
.scopes
.iter()
.map(|scope| scope.as_ref())
.sorted()
.unique()
.collect();
(*custom_storage)
.set(
ScopeSet {
hash: scopes.hash,
filter: scopes.filter,
scopes: &str_scopes[..],
},
&str_scopes[..], // TODO: sorted, unique
token,
)
.await
@@ -186,15 +169,15 @@ impl Storage {
Storage::Memory { tokens } => tokens.lock().await.get(scopes),
Storage::Disk(disk_storage) => disk_storage.get(scopes).await,
Storage::Custom(custom_storage) => {
let str_scopes: Vec<_> = scopes.scopes.iter().map(|scope| scope.as_ref()).collect();
let str_scopes: Vec<_> = scopes
.scopes
.iter()
.map(|scope| scope.as_ref())
.sorted()
.unique()
.collect();
(*custom_storage)
.get(ScopeSet {
hash: scopes.hash,
filter: scopes.filter,
scopes: &str_scopes[..],
})
.await
(*custom_storage).get(&str_scopes[..]).await
}
}
}