1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
use std::collections::{BTreeSet, HashMap};
use std::fmt::{Display, Formatter};
use std::str::FromStr;
use serde_json::Value;
#[cfg(feature = "interactive-auth")]
use graph_core::http::JsonHttpResponse;
#[cfg(feature = "interactive-auth")]
use crate::interactive::WindowCloseReason;
#[cfg(feature = "interactive-auth")]
use crate::identity::{DeviceCodeCredential, PublicClientApplication};
/// The Device Authorization Response: the authorization server generates a unique device
/// verification code and an end-user code that are valid for a limited time and includes
/// them in the HTTP response body using the "application/json" format [RFC8259] with a
/// 200 (OK) status code
///
/// The actual [device code response](https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-device-code#device-authorization-response)
/// that is received from Microsoft Graph does not include the verification_uri_complete field
/// even though it's in the [specification](https://datatracker.ietf.org/doc/html/rfc8628#section-3.2).
/// The device code response from Microsoft Graph looks like similar to the following:
///
/// ```json
/// {
/// "device_code": String("FABABAAEAAAD--DLA3VO7QrddgJg7WevrgJ7Czy_TDsDClt2ELoEC8ePWFs"),
/// "expires_in": Number(900),
/// "interval": Number(5),
/// "message": String("To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code FQK5HW3UF to authenticate."),
/// "user_code": String("FQK5HW3UF"),
/// "verification_uri": String("https://microsoft.com/devicelogin"),
/// }
/// ```
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct DeviceAuthorizationResponse {
/// A long string used to verify the session between the client and the authorization server.
/// The client uses this parameter to request the access token from the authorization server.
pub device_code: String,
/// The number of seconds before the device_code and user_code expire.
pub expires_in: u64,
/// OPTIONAL
/// The minimum amount of time in seconds that the client
/// SHOULD wait between polling requests to the token endpoint. If no
/// value is provided, clients MUST use 5 as the default.
#[serde(default = "default_interval")]
pub interval: u64,
/// User friendly text response that can be used for display purpose.
pub message: String,
/// A short string shown to the user that's used to identify the session on a secondary device.
pub user_code: String,
/// Verification URL where the user must navigate to authenticate using the device code
/// and credentials.
pub verification_uri: String,
/// The verification_uri_complete response field is not included or supported
/// by Microsoft at this time. It is included here because it is part of the
/// [standard](https://datatracker.ietf.org/doc/html/rfc8628) and in the case
/// that Microsoft decides to include it.
pub verification_uri_complete: Option<String>,
/// List of the scopes that would be held by token.
pub scopes: Option<BTreeSet<String>>,
#[serde(flatten)]
pub additional_fields: HashMap<String, Value>,
}
fn default_interval() -> u64 {
5
}
impl Display for DeviceAuthorizationResponse {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}, {}, {}, {}, {}, {}, {:#?}, {:#?}",
self.device_code,
self.expires_in,
self.interval,
self.message,
self.user_code,
self.verification_uri,
self.verification_uri_complete,
self.scopes
)
}
}
/// Response types used when polling for a device code
/// https://datatracker.ietf.org/doc/html/rfc8628#section-3.5
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum PollDeviceCodeEvent {
/// The user hasn't finished authenticating, but hasn't canceled the flow.
/// Repeat the request after at least interval seconds.
AuthorizationPending,
/// The end user denied the authorization request.
/// Stop polling and revert to an unauthenticated state.
AuthorizationDeclined,
/// The device_code sent to the /token endpoint wasn't recognized.
/// Verify that the client is sending the correct device_code in the request.
BadVerificationCode,
/// Value of expires_in has been exceeded and authentication is no longer possible
/// with device_code.
/// Stop polling and revert to an unauthenticated state.
ExpiredToken,
/// Not yet supported by Microsoft but listed in the specification.
///
/// The authorization request was denied.
AccessDenied,
/// Not yet supported by Microsoft but listed in the specification.
///
/// A variant of "authorization_pending", the authorization request is
/// still pending and polling should continue, but the interval MUST
/// be increased by 5 seconds for this and all subsequent requests.
SlowDown,
}
impl PollDeviceCodeEvent {
pub fn as_str(&self) -> &'static str {
match self {
PollDeviceCodeEvent::AuthorizationPending => "authorization_pending",
PollDeviceCodeEvent::AuthorizationDeclined => "authorization_declined",
PollDeviceCodeEvent::BadVerificationCode => "bad_verification_code",
PollDeviceCodeEvent::ExpiredToken => "expired_token",
PollDeviceCodeEvent::AccessDenied => "access_denied",
PollDeviceCodeEvent::SlowDown => "slow_down",
}
}
}
impl FromStr for PollDeviceCodeEvent {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"authorization_pending" => Ok(PollDeviceCodeEvent::AuthorizationPending),
"authorization_declined" => Ok(PollDeviceCodeEvent::AuthorizationDeclined),
"bad_verification_code" => Ok(PollDeviceCodeEvent::BadVerificationCode),
"expired_token" => Ok(PollDeviceCodeEvent::ExpiredToken),
"access_denied" => Ok(PollDeviceCodeEvent::AccessDenied),
"slow_down" => Ok(PollDeviceCodeEvent::SlowDown),
_ => Err(()),
}
}
}
impl AsRef<str> for PollDeviceCodeEvent {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Display for PollDeviceCodeEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[cfg(feature = "interactive-auth")]
#[derive(Debug)]
pub enum InteractiveDeviceCodeEvent {
DeviceAuthorizationResponse {
response: JsonHttpResponse,
device_authorization_response: Option<DeviceAuthorizationResponse>,
},
PollDeviceCode {
poll_device_code_event: PollDeviceCodeEvent,
response: JsonHttpResponse,
},
WindowClosed(WindowCloseReason),
SuccessfulAuthEvent {
response: JsonHttpResponse,
public_application: PublicClientApplication<DeviceCodeCredential>,
},
}