Skip to content

Commit

Permalink
Support for unauthenticated and Basic auth proxies
Browse files Browse the repository at this point in the history
  • Loading branch information
ndanssi committed Feb 13, 2023
1 parent 4db5b0c commit 2f99ad8
Show file tree
Hide file tree
Showing 8 changed files with 283 additions and 104 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Edit `config-oradaz.xml`.
* IdentityRiskyServicePrincipal.Read.All
* IdentityRiskyUser.Read.All
* IdentityUserFlow.Read.All
* OnPremDirectorySynchronization.Read.All
* Organization.Read.All
* Policy.Read.All
* Policy.Read.PermissionGrant
Expand Down
51 changes: 49 additions & 2 deletions schema.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<schema oradaz_version="1.0.08.11" schema_version="1.0.10.12">
<schema oradaz_version="1.1.02.02" schema_version="1.1.02.13">
<service>
<name>graphAPI</name>
<description>Microsoft Graph (through custom app)</description>
Expand Down Expand Up @@ -107,6 +107,11 @@
<uri>policies/authenticationmethodspolicy/authenticationMethodConfigurations/x509Certificate</uri>
<api_version>beta</api_version>
</request>
<request>
<name>graph_authenticationStrengthPolicies</name>
<uri>policies/authenticationStrengthPolicies</uri>
<api_version>beta</api_version>
</request>
<request>
<name>graph_authorizationPolicy</name>
<uri>policies/authorizationPolicy</uri>
Expand Down Expand Up @@ -546,6 +551,11 @@
<uri>onPremisesPublishingProfiles/applicationProxy/connectors</uri>
<api_version>beta</api_version>
</request>
<request>
<name>graph_onPremisesSynchronization</name>
<uri>directory/onPremisesSynchronization</uri>
<api_version>beta</api_version>
</request>
<request>
<name>graph_onSignUpStartListeners</name>
<uri>identity/events/onSignupStart</uri>
Expand Down Expand Up @@ -602,6 +612,30 @@
<relationship name="graph_pim_azureResources_roleSettings" />
</relationships>
</request>
<request>
<name>graph_pim_roleManagementPolicyAssignmentsDirectory</name>
<uri>policies/roleManagementPolicyAssignments</uri>
<param>$filter=scopeId eq &apos;/&apos; and scopeType eq &apos;Directory&apos;&amp;$expand=policy($expand=rules)</param>
<api_version>beta</api_version>
</request>
<request>
<name>graph_pim_roleManagementPolicyAssignmentsDirectoryRole</name>
<uri>policies/roleManagementPolicyAssignments</uri>
<param>$filter=scopeId eq &apos;/&apos; and scopeType eq &apos;DirectoryRole&apos;&amp;$expand=policy($expand=rules)</param>
<api_version>beta</api_version>
</request>
<request>
<name>graph_pim_roleManagementPoliciesDirectory</name>
<uri>policies/roleManagementPolicies</uri>
<param>$filter=scopeId eq &apos;/&apos; and scopeType eq &apos;Directory&apos;&amp;$expand=rules</param>
<api_version>beta</api_version>
</request>
<request>
<name>graph_pim_roleManagementPoliciesDirectoryRole</name>
<uri>policies/roleManagementPolicies</uri>
<param>$filter=scopeId eq &apos;/&apos; and scopeType eq &apos;DirectoryRole&apos;&amp;$expand=rules</param>
<api_version>beta</api_version>
</request>
<request>
<name>graph_provisioning</name>
<uri>auditLogs/provisioning?$top=1000</uri>
Expand All @@ -614,19 +648,27 @@
<relationship name="graph_resourceNamespaces_resourceActions" />
</relationships>
</request>
<request>
<name>graph_riskBasedNotifications</name>
<uri>identityProtection/settings/notifications</uri>
<api_version>beta</api_version>
</request>
<request>
<name>graph_riskDetections</name>
<uri>identityProtection/riskDetections</uri>
</request>
<request>
<name>graph_riskyServicePrincipals</name>
<uri>identityProtection/riskyServicePrincipals</uri>
<api_version>beta</api_version>
</request>
<request>
<name>graph_riskyUsers</name>
<uri>identityProtection/riskyUsers</uri>
</request>
<request>
<name>graph_riskySignIns</name>
<uri>auditLogs/signIns?$filter=(riskState eq &apos;atRisk&apos; or riskState eq &apos;confirmedCompromised&apos;)</uri>
</request>
<request>
<name>graph_roleDefinitions</name>
<uri>roleManagement/directory/roleDefinitions</uri>
Expand Down Expand Up @@ -737,6 +779,11 @@
<relationship name="graph_tokenLifetimePolicies_appliesTo" />
</relationships>
</request>
<request>
<name>graph_topSignIns</name>
<uri>auditLogs/signIns?$top=1000</uri>
<api_version>beta</api_version>
</request>
<request>
<name>graph_trustFrameworkPolicies</name>
<uri>trustFramework/policies</uri>
Expand Down
139 changes: 80 additions & 59 deletions src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub struct TokenResponseV1 {
pub oradaz_error: Option<String>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct TokenResponse {
pub expires_in: Option<u64>,
pub ext_expires_in: Option<u64>,
Expand Down Expand Up @@ -89,7 +89,7 @@ pub struct TokenResponse {
}

impl TokenResponse {
pub fn refresh_token(&mut self) -> Result<(), Error> {
pub fn refresh_token(&mut self, client: &reqwest::blocking::Client) -> Result<(), Error> {
match &self.refresh_token {
Some(r) => {
info!("{:FL$}Refreshing token for api {}", "refresh_token", self.api);
Expand All @@ -100,12 +100,12 @@ impl TokenResponse {
("refresh_token", r)
];

let authority = match Authority::new(&self.authority_url) {
let authority = match Authority::new(&self.authority_url, client) {
Ok(a) => a,
Err(_e) => return Err(Error::InvalidAuthorityUrlError)
};

let response = http_post(&authority.token_endpoint, &params).unwrap();
let response = http_post(&authority.token_endpoint, &params, client).unwrap();

let mut json_response: TokenResponse = response.json().unwrap();

Expand Down Expand Up @@ -153,7 +153,7 @@ impl TokenResponse {
Ok(())
}

pub fn refresh_token_for_resource(&mut self, resource: &str) -> Result<(), Error> {
pub fn refresh_token_for_resource(&mut self, resource: &str, client: &reqwest::blocking::Client) -> Result<(), Error> {
match &self.refresh_token {
Some(r) => {
let params: Vec<(&str, &str)> = vec![
Expand All @@ -164,12 +164,12 @@ impl TokenResponse {
("resource", resource)
];

let authority = match AuthorityV1::new(&self.authority_url) {
let authority = match AuthorityV1::new(&self.authority_url, client) {
Ok(a) => a,
Err(_e) => return Err(Error::InvalidAuthorityUrlError)
};

let response = http_post(&authority.token_endpoint, &params).unwrap();
let response = http_post(&authority.token_endpoint, &params, client).unwrap();

let mut json_response: TokenResponseV1 = response.json().unwrap();

Expand Down Expand Up @@ -236,8 +236,8 @@ pub struct Authority {
}

impl Authority {
pub fn new(authority_url: &str) -> Result<Self, Error> {
let tenant_discovery_response = match Self::tenant_discovery(authority_url){
pub fn new(authority_url: &str, client: &reqwest::blocking::Client) -> Result<Self, Error> {
let tenant_discovery_response = match Self::tenant_discovery(authority_url, client){
Ok(t) => t,
Err(e) => {
error!("{:FL$}Invalid tenant id provided", "tenant_discovery");
Expand All @@ -255,8 +255,8 @@ impl Authority {
})
}

fn tenant_discovery(authority_url: &str) -> Result<TenantDiscoveryResponse, Error> {
let response = http_get(&format!("{}/v2.0/.well-known/openid-configuration", authority_url));
fn tenant_discovery(authority_url: &str, client: &reqwest::blocking::Client) -> Result<TenantDiscoveryResponse, Error> {
let response = http_get(&format!("{}/v2.0/.well-known/openid-configuration", authority_url), client);
let response = response.unwrap();
let response = match response.json::<TenantDiscoveryResponse>() {
Err(_e) => return Err(Error::InvalidAuthorityUrlError),
Expand All @@ -274,8 +274,8 @@ pub struct AuthorityV1 {
}

impl AuthorityV1 {
pub fn new(authority_url: &str) -> Result<Self, Error> {
let tenant_discovery_response = match Self::tenant_discovery(authority_url){
pub fn new(authority_url: &str, client: &reqwest::blocking::Client) -> Result<Self, Error> {
let tenant_discovery_response = match Self::tenant_discovery(authority_url, client){
Ok(t) => t,
Err(e) => {
error!("{:FL$}Invalid tenant id provided", "tenant_discovery");
Expand All @@ -293,8 +293,8 @@ impl AuthorityV1 {
})
}

fn tenant_discovery(authority_url: &str) -> Result<TenantDiscoveryResponse, Error> {
let response = http_get(&format!("{}/.well-known/openid-configuration", authority_url));
fn tenant_discovery(authority_url: &str, client: &reqwest::blocking::Client) -> Result<TenantDiscoveryResponse, Error> {
let response = http_get(&format!("{}/.well-known/openid-configuration", authority_url), client);
let response = response.unwrap();
let response = match response.json::<TenantDiscoveryResponse>() {
Err(_e) => return Err(Error::InvalidAuthorityUrlError),
Expand All @@ -310,8 +310,8 @@ pub struct PublicClientApplication {
}

impl PublicClientApplication {
pub fn new(client_id: &str, authority_url: &str) -> Result<PublicClientApplication, Error> {
let authority = match Authority::new(authority_url) {
pub fn new(client_id: &str, authority_url: &str, client: &reqwest::blocking::Client) -> Result<PublicClientApplication, Error> {
let authority = match Authority::new(authority_url, client) {
Ok(a) => a,
Err(_e) => return Err(Error::InvalidAuthorityUrlError)
};
Expand All @@ -322,11 +322,11 @@ impl PublicClientApplication {
})
}

pub fn initiate_device_flow(&self, scopes: &[&str]) -> Result<DeviceCodeFlow, Error> {
pub fn initiate_device_flow(&self, scopes: &[&str], client: &reqwest::blocking::Client) -> Result<DeviceCodeFlow, Error> {
let scopes: &str = &format!("{} openid offline_access", scopes.join(" "));
let params: Vec<(&str, &str)> = vec![("client_id", &self.client_id), ("scope", scopes)];

let device_code_flow = match http_post(&self.authority.device_code_endpoint, &params) {
let device_code_flow = match http_post(&self.authority.device_code_endpoint, &params, client) {
Err(_e) => {
error!("{:FL$}Could not acquire device flow for scopes {}", "initiate_device_flow", &scopes);
return Err(Error::CannotAcquireDeviceCodeFlowError)
Expand All @@ -347,7 +347,7 @@ impl PublicClientApplication {
Ok(flow)
}

pub fn acquire_token_by_device_flow(&self, api: &str, authority_url: &str, scopes: &[&str], flow: DeviceCodeFlow) -> Result<TokenResponse, Error> {
pub fn acquire_token_by_device_flow(&self, api: &str, authority_url: &str, scopes: &[&str], flow: &DeviceCodeFlow, client: &reqwest::blocking::Client) -> Result<TokenResponse, Error> {
let scopes: &str = &format!("{} openid offline_access", scopes.join(" "));
let params: Vec<(&str, &str)> = vec![
("client_id", &self.client_id),
Expand All @@ -363,7 +363,7 @@ impl PublicClientApplication {
return Err(Error::ExpiredDeciveCodeError);
}

let response = http_post(&self.authority.token_endpoint, &params).unwrap();
let response = http_post(&self.authority.token_endpoint, &params, client).unwrap();

let mut json_response: TokenResponse = response.json().unwrap();

Expand Down Expand Up @@ -422,51 +422,72 @@ impl PublicClientApplication {
}
}

fn http_get(url: &str) -> Result<reqwest::blocking::Response, reqwest::Error> {
reqwest::blocking::get(url)
}

fn http_post(url: &str, params: &[(&str, &str)]) -> Result<reqwest::blocking::Response, reqwest::Error> {
let client = reqwest::blocking::Client::new();
client.post(url).form(&params).send()
fn http_get(url: &str, client: &reqwest::blocking::Client) -> Result<reqwest::blocking::Response, Error> {
match client.get(url).send() {
Ok(r) => Ok(r),
Err(e) => {
error!("{:FL$}Could not check prerequisites.", "http_get");
error!("{:FL$}\t{}", "", e);
return Err(Error::PrerequisitesCheckError);
}
}
}

pub fn get_token(service: &config::Service, tenant: &str, app_id: &str) -> Result<TokenResponse, Error> {
let authority_url: &str = &format!("https://login.microsoftonline.com/{}", tenant);
let mut api_client_id: &str = &service.client_id;
if api_client_id.is_empty() {
api_client_id = app_id;
};
let scope: &str = &format!("{}.default", service.base_url);
let scopes: Vec<&str> = vec![scope];
let pca = match PublicClientApplication::new(api_client_id, authority_url){
Ok(p) => p,
Err(e) => return Err(e)
};
let flow = match pca.initiate_device_flow(&scopes) {
Err(e) => return Err(e),
Ok(flow) => {
let description = format!(" / ! \\ INTERACTION REQUIRED!!! | {:40}", &service.description);
println!("{}", White.on(Red).paint(" / \\ | Authentication for: "));
println!("{}", White.on(Red).paint(description));
println!("{}", White.on(Red).paint("/_____\\ | REST API "));
println!("{}", flow.message);
flow
}
};
let mut token = match pca.acquire_token_by_device_flow(&service.name, authority_url, &scopes, flow) {
fn http_post(url: &str, params: &[(&str, &str)], client: &reqwest::blocking::Client) -> Result<reqwest::blocking::Response, Error> {
match client.post(url).form(&params).send() {
Ok(r) => Ok(r),
Err(e) => {
error!("{:FL$}Could not acquire token for API {}", "get_token", &service.name);
return Err(e)
},
Ok(token) => {
token
error!("{:FL$}Could not check prerequisites.", "http_post");
error!("{:FL$}\t{}", "", e);
return Err(Error::PrerequisitesCheckError);
}
};
}
}

pub fn get_token(service: &config::Service, tenant: &str, app_id: &str, client: &reqwest::blocking::Client) -> Result<TokenResponse, Error> {
let mut do_loop = true;
let mut token:TokenResponse = Default::default();
let description = format!(" / ! \\ INTERACTION REQUIRED!!! | {:40}", &service.description);
println!("{}", White.on(Red).paint(" / \\ | Authentication for: "));
println!("{}", White.on(Red).paint(description));
println!("{}", White.on(Red).paint("/_____\\ | REST API "));
while do_loop {
let authority_url: &str = &format!("https://login.microsoftonline.com/{}", tenant);
let mut api_client_id: &str = &service.client_id;
if api_client_id.is_empty() {
api_client_id = app_id;
};
let scope: &str = &format!("{}.default", service.base_url);
let scopes: Vec<&str> = vec![scope];
let pca = match PublicClientApplication::new(api_client_id, authority_url, client){
Ok(p) => p,
Err(e) => return Err(e)
};
let flow = match pca.initiate_device_flow(&scopes, client) {
Err(e) => return Err(e),
Ok(flow) => {
println!("{}", flow.message);
flow
}
};
match pca.acquire_token_by_device_flow(&service.name, authority_url, &scopes, &flow, client) {
Err(Error::ExpiredDeciveCodeError) => {
info!("{:FL$}Performing a new authentication request for API {}", "get_token", &service.name);
}
Err(e) => {
error!("{:FL$}Could not acquire token for API {}", "get_token", &service.name);
return Err(e)
},
Ok(token_value) => {
token = token_value;
do_loop = false;
}
};
}
if service.resource.is_empty() {
Ok(token)
} else {
match token.refresh_token_for_resource(&service.resource) {
match token.refresh_token_for_resource(&service.resource, client) {
Err(e) => {
error!("{:FL$}Could not refresh token for API {} and resource {}", "get_token", &service.name, &service.resource);
Err(e)
Expand Down
Loading

0 comments on commit 2f99ad8

Please sign in to comment.