Skip to content
This repository has been archived by the owner on Oct 6, 2020. It is now read-only.

Commit

Permalink
Merge pull request #303 from MindFlavor/issue/290/aad_refresh_token
Browse files Browse the repository at this point in the history
Azure AAD: Exchange refresh token support
  • Loading branch information
Francesco Cogno authored Jun 26, 2020
2 parents a08a649 + 7b73034 commit 1995aac
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 17 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

[![Build Status](https://travis-ci.org/MindFlavor/AzureSDKForRust.svg?branch=master)](https://travis-ci.org/MindFlavor/AzureSDKForRust) [![Coverage Status](https://coveralls.io/repos/MindFlavor/AzureSDKForRust/badge.svg?branch=master&service=github)](https://coveralls.io/github/MindFlavor/AzureSDKForRust?branch=master) ![stability-unstable](https://img.shields.io/badge/stability-unstable-yellow.svg)

[![tag](https://img.shields.io/github/tag/mindflavor/AzureSDKForRust.svg)](https://github.com/MindFlavor/AzureSDKForRust/tree/aad_0.45.1) [![release](https://img.shields.io/github/release/mindflavor/AzureSDKForRust.svg)](https://github.com/MindFlavor/AzureSDKForRust/releases/tag/aad_0.45.1) [![commitssince](https://img.shields.io/github/commits-since/mindflavor/AzureSDKForRust/aad_0.45.1)](https://github.com/MindFlavor/AzureSDKForRust/commits/master)
[![tag](https://img.shields.io/github/tag/mindflavor/AzureSDKForRust.svg)](https://github.com/MindFlavor/AzureSDKForRust/tree/aad_0.46.0) [![release](https://img.shields.io/github/release/mindflavor/AzureSDKForRust.svg)](https://github.com/MindFlavor/AzureSDKForRust/releases/tag/aad_0.46.0) [![commitssince](https://img.shields.io/github/commits-since/mindflavor/AzureSDKForRust/aad_0.46.0)](https://github.com/MindFlavor/AzureSDKForRust/commits/master)

[![GitHub contributors](https://img.shields.io/github/contributors/MindFlavor/AzureSDKForRust.svg)](https://github.com/MindFlavor/AzureSDKForRust/graphs/contributors)

Expand Down
5 changes: 2 additions & 3 deletions azure_sdk_auth_aad/examples/device_code_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use futures::stream::StreamExt;
use oauth2::ClientId;
use std::env;
use std::error::Error;
use std::sync::Arc;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
Expand All @@ -31,7 +30,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
&client_id,
&[
&format!(
"https://{}.blob.core.windows.net/.default",
"https://{}.blob.core.windows.net/user_impersonation",
storage_account_name
),
"offline_access",
Expand All @@ -49,7 +48,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
// return, besides errors, a success meaning either
// Success or Pending. The loop will continue until we
// get either a Success or an error.
let mut stream = Box::pin(device_code_flow.stream(&client));
let mut stream = Box::pin(device_code_flow.stream());
let mut authorization = None;
while let Some(resp) = stream.next().await {
println!("{:?}", resp);
Expand Down
23 changes: 16 additions & 7 deletions azure_sdk_auth_aad/src/device_code_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use azure_sdk_core::errors::AzureError;
use futures::stream::unfold;
use log::debug;
pub use oauth2::{ClientId, ClientSecret};
use std::borrow::Cow;
use std::convert::TryInto;
use std::sync::Arc;
use std::time::Duration;
use url::form_urlencoded;

Expand All @@ -21,7 +21,9 @@ pub struct DeviceCodePhaseOneResponse<'a> {
// from the Azure answer. They will be added
// manually after deserialization
#[serde(skip)]
tenant_id: &'a str,
client: Option<&'a reqwest::Client>,
#[serde(skip)]
tenant_id: Cow<'a, str>,
// we store the ClientId as string instead of
// the original type because it does not
// implement Default and it's in another
Expand All @@ -30,17 +32,22 @@ pub struct DeviceCodePhaseOneResponse<'a> {
client_id: String,
}

pub async fn begin_authorize_device_code_flow<'a, 'b>(
pub async fn begin_authorize_device_code_flow<'a, 'b, T>(
client: &'a reqwest::Client,
tenant_id: &'a str,
tenant_id: T,
client_id: &'a ClientId,
scopes: &'b [&'b str],
) -> Result<DeviceCodePhaseOneResponse<'a>, AzureError> {
) -> Result<DeviceCodePhaseOneResponse<'a>, AzureError>
where
T: Into<Cow<'a, str>>,
{
let mut encoded = form_urlencoded::Serializer::new(String::new());
let encoded = encoded.append_pair("client_id", client_id.as_str());
let encoded = encoded.append_pair("scope", &scopes.join(" "));
let encoded = encoded.finish();

let tenant_id = tenant_id.into();

debug!("encoded ==> {}", encoded);

let url = url::Url::parse(&format!(
Expand Down Expand Up @@ -69,6 +76,7 @@ pub async fn begin_authorize_device_code_flow<'a, 'b>(
expires_in: device_code_reponse.expires_in,
interval: device_code_reponse.interval,
message: device_code_reponse.message,
client: Some(client),
tenant_id,
client_id: client_id.as_str().to_string(),
})
Expand All @@ -92,7 +100,6 @@ impl<'a> DeviceCodePhaseOneResponse<'a> {

pub fn stream<'b>(
&'b self,
client: &'b reqwest::Client,
) -> impl futures::Stream<Item = Result<DeviceCodeResponse, DeviceCodeError>> + 'b + '_ {
#[derive(Debug, Clone, PartialEq)]
enum NextState {
Expand Down Expand Up @@ -123,7 +130,9 @@ impl<'a> DeviceCodePhaseOneResponse<'a> {
let encoded = encoded.append_pair("device_code", &self.device_code);
let encoded = encoded.finish();

let result = match client
let result = match self
.client
.unwrap()
.post(&uri)
.header("ContentType", "application/x-www-form-urlencoded")
.body(encoded)
Expand Down
3 changes: 3 additions & 0 deletions azure_sdk_auth_aad/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ pub mod errors;
mod refresh_token;
pub use refresh_token::*;
mod naive_server;
mod traits;
pub use crate::device_code_flow::*;
pub use crate::device_code_responses::*;
use futures::TryFutureExt;
mod responses;
pub use naive_server::naive_server;
mod prelude;

#[derive(Debug)]
pub struct AuthObj {
Expand Down
1 change: 1 addition & 0 deletions azure_sdk_auth_aad/src/prelude.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub use crate::traits::*;
12 changes: 6 additions & 6 deletions azure_sdk_auth_aad/src/refresh_token.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::responses::RefreshTokenResponse;
use azure_sdk_core::errors::AzureError;
use log::debug;
use oauth2::{AccessToken, ClientId, ClientSecret};
use std::sync::Arc;
use std::convert::TryInto;
use url::form_urlencoded;

pub async fn exchange_refresh_token(
Expand All @@ -10,7 +11,7 @@ pub async fn exchange_refresh_token(
client_id: &ClientId,
client_secret: Option<&ClientSecret>,
refresh_token: &AccessToken,
) -> Result<(), AzureError> {
) -> Result<RefreshTokenResponse, AzureError> {
let mut encoded = form_urlencoded::Serializer::new(String::new());
let encoded = encoded.append_pair("grant_type", "refresh_token");
let encoded = encoded.append_pair("client_id", client_id.as_str());
Expand All @@ -23,7 +24,7 @@ pub async fn exchange_refresh_token(
let encoded = encoded.append_pair("refresh_token", refresh_token.secret());
let encoded = encoded.finish();

println!("encoded ==> {}", encoded);
debug!("encoded ==> {}", encoded);

let url = url::Url::parse(&format!(
"https://login.microsoftonline.com/{}/oauth2/v2.0/token",
Expand All @@ -40,8 +41,7 @@ pub async fn exchange_refresh_token(
.text()
.await
.map_err(|e| AzureError::GenericErrorWithText(e.to_string()))?;
debug!("{}", ret);

println!("{}", ret);

Ok(())
Ok(ret.try_into()?)
}
2 changes: 2 additions & 0 deletions azure_sdk_auth_aad/src/responses/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod refresh_token_response;
pub use refresh_token_response::RefreshTokenResponse;
67 changes: 67 additions & 0 deletions azure_sdk_auth_aad/src/responses/refresh_token_response.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use crate::prelude::*;
use oauth2::AccessToken;
use std::convert::TryInto;

#[derive(Debug, Clone)]
pub struct RefreshTokenResponse {
token_type: String,
scopes: Vec<String>,
expires_in: u64,
ext_expires_in: u64,
access_token: AccessToken,
refresh_token: AccessToken,
}

impl TryInto<RefreshTokenResponse> for String {
type Error = serde_json::Error;

fn try_into(self) -> Result<RefreshTokenResponse, Self::Error> {
// we use a temp struct to deserialize the scope into
// the scopes vec at later time
#[derive(Debug, Clone, Deserialize)]
pub struct _RefreshTokenResponse<'a> {
token_type: String,
scope: &'a str,
expires_in: u64,
ext_expires_in: u64,
access_token: AccessToken,
refresh_token: AccessToken,
}

serde_json::from_str::<_RefreshTokenResponse>(&self).map(|rtr| RefreshTokenResponse {
token_type: rtr.token_type,
scopes: rtr.scope.split(' ').map(|s| s.to_owned()).collect(),
expires_in: rtr.expires_in,
ext_expires_in: rtr.ext_expires_in,
access_token: rtr.access_token,
refresh_token: rtr.refresh_token,
})
}
}

impl BearerToken for RefreshTokenResponse {
fn token_type(&self) -> &str {
&self.token_type
}
fn scopes(&self) -> &[String] {
&self.scopes
}
fn expires_in(&self) -> u64 {
self.expires_in
}
fn access_token(&self) -> &AccessToken {
&self.access_token
}
}

impl RefreshToken for RefreshTokenResponse {
fn refresh_token(&self) -> &AccessToken {
&self.refresh_token
}
}

impl ExtExpiresIn for RefreshTokenResponse {
fn ext_expires_in(&self) -> u64 {
self.ext_expires_in
}
}
16 changes: 16 additions & 0 deletions azure_sdk_auth_aad/src/traits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use oauth2::AccessToken;

pub trait BearerToken {
fn token_type(&self) -> &str;
fn scopes(&self) -> &[String];
fn expires_in(&self) -> u64;
fn access_token(&self) -> &AccessToken;
}

pub trait RefreshToken {
fn refresh_token(&self) -> &AccessToken;
}

pub trait ExtExpiresIn {
fn ext_expires_in(&self) -> u64;
}

0 comments on commit 1995aac

Please sign in to comment.