Authenticating to MS Graph without user interaction (Rust) - rust

I have a Java application using the Microsoft Graph SDK to read from Azure AD. I am supposed to translate that application to Rust. In Java, i use code similar to this:
import com.azure.identity.ClientSecretCredentialBuilder;
import com.azure.identity.TokenCachePersistenceOptions;
import com.microsoft.graph.authentication.TokenCredentialAuthProvider;
import com.microsoft.graph.requests.GraphServiceClient;
import java.util.List;
public class GraphSdkClient {
public static void testApplicationClient() {
TokenCachePersistenceOptions options = new TokenCachePersistenceOptions()
.setName("ILM-Demo")
.setUnencryptedStorageAllowed(false);
ClientSecretCredentialBuilder builder = new ClientSecretCredentialBuilder()
.clientId("<MyClientId>")
.tenantId("<MyTenantId>")
.clientSecret("<MyClientSecret>")
.tokenCachePersistenceOptions(options);
TokenCredentialAuthProvider provider = new TokenCredentialAuthProvider(
List.of("Calendars.Read", "Calendars.ReadBasic.All"),
builder.build()
);
GraphServiceClient<?> client = GraphServiceClient
.builder()
.authenticationProvider(provider)
.buildClient();
client.me().calendar().calendarView().buildRequest().get();
}
}
It authenticates as an application, using only the client secret. The permission was given half a year ago and as long as the three values from the ClientSecretCredentialBuilder are correct, it works perfectly fine. Now i tried using a similar conecpt in Rust, taken from the graph-rs-sdk crate:
#[cfg(test)]
mod test {
use graph_rs_sdk::oauth::OAuth;
use warp::Filter;
use crate::{CLIENT_ID, CLIENT_SECRET, TENANT_ID};
use serde::{Deserialize, Serialize};
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct ClientCredentialsResponse {
admin_consent: bool,
tenant: String,
}
#[tokio::test]
async fn ut_client_credentials() {
let query = warp::query::<ClientCredentialsResponse>()
.map(Some)
.or_else(|_| async {
Ok::<(Option<ClientCredentialsResponse>,), std::convert::Infallible>((None,))
});
let routes = warp::get()
.and(warp::path("redirect"))
.and(query)
.and_then(handle_redirect);
// Get the oauth client and request a browser sign in
let mut oauth = get_oauth_client();
let mut request = oauth.build_async().client_credentials();
request.browser_authorization().open().unwrap();
warp::serve(routes).run(([127, 0, 0, 1], 8300)).await;
}
async fn handle_redirect(client_credential_option: Option<ClientCredentialsResponse>)
-> Result<Box<dyn warp::Reply>, warp::Rejection> {
match client_credential_option {
Some(client_credential_response) => {
// Print out for debugging purposes.
println!("{:#?}", client_credential_response);
// Request an access token.
request_access_token().await;
// Generic login page response.
Ok(Box::new(
"Successfully Logged In! You can close your browser.",
))
}
None => Err(warp::reject()),
}
}
async fn request_access_token() {
let mut oauth = get_oauth_client();
let mut request = oauth.build_async().client_credentials();
let access_token = request.access_token().send().await.unwrap();
println!("{:#?}", access_token);
oauth.access_token(access_token);
}
fn get_oauth_client() -> OAuth {
let mut oauth = OAuth::new();
oauth
.client_id(CLIENT_ID)
.client_secret(CLIENT_SECRET)
.tenant_id(TENANT_ID)
.add_scope("https://graph.microsoft.com/User.Invite.All")
.redirect_uri("http://localhost:8300/redirect")
// .authorize_url("https://login.microsoftonline.com/common/adminconsent")
.access_token_url("https://login.microsoftonline.com/common/oauth2/v2.0/token");
oauth
}
}
Note that i commented out the authorize url. If this url exits, a browser window opens, requesting an admin to log in. This must not happen. When it is commented out, it sends the request directly to <tenantId>/oauth2/v2.0/authorize instead of <tenantId>/oauth2/v2.0/adminconsent, which is what i want, it instead complains: AADSTS900144: The request body must contain the following parameter: 'scope'.. The scope is given though.
I already tried fiddling with urls for hours and also tried every other authentication concept in the crate, but it doesn't seem to work without interaction, which is not possible in my use case. Does someone know how to achieve that? (All permissions are granted as application permissions, not deligated)
Edit request: Please create and add the tag "graph-rs-sdk". I cannot create it myself but knowing the crate being used would be useful.

Related

Tauri Redirect URI Schema

I'm building a Tauri app and would like to set up OAuth integration with Google. To do so, I will need a URI for the oauth callback, but Tauri is unclear how to configure the schema possibly using this method or with the WindowUrl?
How can I add a URI to my Tauri app so I could like to it like the following example:
myapp://callback
I think it could look something like the following:
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.register_uri_scheme_protocol("myapp", move |app, request| {
# protocol logic here
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Tauri currently doesn't directly support deep linking. A good alternative that I found was this rust project. After installation you can do something like the following:
#[tauri::command]
async fn start_oauth_server(window: Window) -> Result<u16, String> {
println!("Starting server");
start(None, move |url| {
// Because of the unprotected localhost port, you must verify the URL here.
// Preferebly send back only the token, or nothing at all if you can handle everything else in Rust.
// convert the string to a url
let url = url::Url::parse(&url).unwrap();
// get the code query parameter
let code = url
.query_pairs()
.find(|(k, _)| k == "code")
.unwrap_or_default()
.1;
// get the state query parameter
let state = url
.query_pairs()
.find(|(k, _)| k == "state")
.unwrap_or_default()
.1;
// create map of query parameters
let mut query_params = HashMap::new();
query_params.insert("code".to_string(), code.to_string());
query_params.insert("state".to_string(), state.to_string());
query_params.insert(String::from("redirect_uri"), url.to_string());
if window.emit("redirect_uri", query_params).is_ok() {
println!("Sent redirect_uri event");
} else {
println!("Failed to send redirect_uri event");
}
})
.map_err(|err| err.to_string())
}

How can I use oauth2 response to make requests to the api guarded by it?

I am trying to use Authorization Code scheme with oauth2.
How can I use the response from app backend to make new requests to the actual api (yahoo-fantasy) guarded by it? I have no idea how I can access the actual response on the code side, please note that this is a question about implementation, not the concept.
I have the following structure:
$ tree src
src
├── components
│ ├── app.rs
│ └── login.rs
├── components.rs
└── main.rs
with the following files:
// ./src/components/app.rs
use yew::{function_component, html};
use crate::components::login::Login;
#[function_component(App)]
pub fn app() -> Html {
html! {
<>
<Login/>
</>
}
}
// ./src/components/login.rs
use yew::prelude::*;
use yew_oauth2::oauth2::*;
use yew_oauth2::prelude::*; // use `openid::*` when using OpenID connect
pub struct Login;
impl Component for Login {
type Message = ();
type Properties = ();
fn create(ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, ctx: &Context<Self>) -> Html {
let login = ctx.link().callback_once(|_: MouseEvent| {
OAuth2Dispatcher::<Client>::new().start_login();
});
let logout = ctx.link().callback_once(|_: MouseEvent| {
OAuth2Dispatcher::<Client>::new().logout();
});
let config = Config {
client_id: "<my-yahoo-client-id>".to_string(),
auth_url: "https://api.login.yahoo.com/oauth2/request_auth".into(),
token_url: "<my-backend-end-point-for-oauth2>".to_string(),
};
html!(
<OAuth2 config={config}>
<Failure><FailureMessage/></Failure>
<Authenticated>
<button onclick={logout}>{ "Logout" }</button>
</Authenticated>
<NotAuthenticated>
<button onclick={login.clone()}>{ "Login" }</button>
</NotAuthenticated>
</OAuth2>
)
}
}
// ./src/components.rs
pub mod app;
pub mod login;
// ./src/main.rs
mod components;
fn main() {
yew::start_app::<components::app::App>();
}
which I'm developing using:
trunk serve
When the login button is pushed, app backend responds with the following body:
{
"access_token": "<access_token-value>",
"expires_in": 3600,
"refresh_token": "<refresh_token-value>",
"token_type": "bearer"
}
Further see the project dependencies:
# ./Cargo.toml
[package]
edition = "2021"
name = "my_project"
version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dotenv_codegen = "0.15.0"
gloo-net = "0.2.5"
log = "0.4.17"
reqwest = "0.11.13"
serde = "1.0.149"
wasm-bindgen-futures = "0.4.33"
wasm-logger = "0.2.0"
yew = "0.19.3"
yew-oauth2 = "0.4.0"
Further note, I've read the all the answers on the question What is the OAuth 2.0 Bearer Token exactly?, and I could not make a progress and/or have any insight that it is related to my question directly.

How to return contents as a file download in Axum?

I have a Rust application that is acting as a proxy. From the user perspective there is a web UI front end. This contains a button that when invoked will trigger a GET request to the Rust application. This in turn calls an external endpoint that returns the CSV file.
What I want is have the file download to the browser when the user clicks the button. Right now, the contents of the CSV file are returned to the browser rather than the file itself.
use std::net::SocketAddr;
use axum::{Router, Server};
use axum::extract::Json;
use axum::routing::get;
pub async fn downloadfile() -> Result<Json<String>, ApiError> {
let filename = ...;
let endpoint = "http://127.0.0.1:6101/api/CSV/DownloadFile";
let path = format!("{}?filename={}", endpoint, filename);
let response = reqwest::get(path).await?;
let content = response.text().await?;
Ok(Json(content))
}
pub async fn serve(listen_addr: SocketAddr) {
let app = Router::new()
.route("/downloadfile", get(downloadfile));
Server::bind(&listen_addr)
.serve(app.into_make_service())
.await
.unwrap();
}
I understand the reason I'm getting the contents is because I'm returning the content string as JSON. This makes sense. However, what would I need to change to return the file itself so the browser downloads it directly for the user?
I've managed to resolve it and now returns the CSV as a file. Here's the function that works:
use axum::response::Headers;
use http::header::{self, HeaderName};
pub async fn downloadfile() -> Result<(Headers<[(HeaderName, &'static str); 2]>, String), ApiError> {
let filename = ...;
let endpoint = "http://127.0.0.1:6101/api/CSV/DownloadFile";
let path = format!("{}?filename={}", endpoint, filename);
let response = reqwest::get(path).await?;
let content = response.text().await?;
let headers = Headers([
(header::CONTENT_TYPE, "text/csv; charset=utf-8"),
(header::CONTENT_DISPOSITION, "attachment; filename=\"data.csv\""),
]);
Ok((headers, content))
}
I'm still struggling with being able to set a dynamic file name, but that for another question.

How do I authenticate (bind) to ldaps using username and password with the ldap3 crate?

I am searching for a way to supply the username and password while connecting to ldaps.
The examples on the documentation page for crate ldap3 seem not illustrate supplying username and password while binding to ldap.
//taken from https://docs.rs/crate/ldap3/0.9.3
use ldap3::{LdapConn, Scope, SearchEntry};
use ldap3::result::Result;
fn main() -> Result<()> {
let mut ldap = LdapConn::new("ldap://localhost:2389")?;
let (rs, _res) = ldap.search(
"ou=Places,dc=example,dc=org",
Scope::Subtree,
"(&(objectClass=locality)(l=ma*))",
vec!["l"]
)?.success()?;
for entry in rs {
println!("{:?}", SearchEntry::construct(entry));
}
Ok(ldap.unbind()?)
}
This is the solution that I got working on my own project, I hope it helps:
use ldap3::{LdapConnAsync, LdapConnSettings};
// Pass in username and password to authenticate against LDAP
async fn authenticate(username: &str, password: &str) -> bool {
// Connection to the LDAP Server
let (conn, mut ldap) =
LdapConnAsync::with_settings(LdapConnSettings::new()
.set_starttls(true)
.set_no_tls_verify(true),
"ldap://localhost:2389").await.unwrap();
ldap3::drive!(conn);
// Takes the username provided and converts it into an email for validation
// This is required because LDAP uses either the Distinguished name or Email in order to bind. Username alone will not work :/
let email = format!("{}#domain.com", username);
// Attempts a simple bind using the passed in values of username and Password
let result = ldap.simple_bind(email.as_str(), password).await.unwrap().success();
ldap.unbind().await.unwrap();
// If the authentication is successful return true, else return false.
if !result.is_err() {
true
} else { false }
}

How to sign token transaction in Serum Anchor

I have a simple contract using Serum Anchor (on Solana) that transfers tokens from one party to another. It is currently failing with:
Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: Cross-program invocation with unauthorized signer or writable account
Full code from snippets below at: https://gist.github.com/h4rkl/700400f515ab0736fd6d9318d44b2dca
I'm setting up 4 accounts for the transaction:
let mint = null; // key for token mint
let god = null; // main program account to pay from and sign
let creatorAcc = anchor.web3.Keypair.generate(); // account to pay to
let creatorTokenAcc = null; // token account for payment
I'm setting them up as follows:
const [_mint, _god] = await serumCmn.createMintAndVault(
program.provider,
new anchor.BN(MINT_TOKENS),
undefined,
MINT_DECIMALS
);
mint = _mint;
god = _god;
creatorTokenAcc =await serumCmn.createTokenAccount(
program.provider,
mint,
creatorAcc.publicKey
);
And then I make the payment with the following method:
const INTERACTION_FEE = 200000000000000;
await program.rpc.interaction(new anchor.BN(INTERACTION_FEE), {
accounts: {
from: god,
to: creatorTokenAcc,
owner: program.provider.wallet.publicKey,
tokenProgram: TOKEN_PROGRAM_ID,
},
});
In the contract that the method is triggering my interaction process is as follows:
pub fn interaction(ctx: Context<Interaction>, interaction_fee: u64) -> ProgramResult {
let cpi_accounts = Transfer {
from: ctx.accounts.from.to_account_info().clone(),
to: ctx.accounts.to.to_account_info().clone(),
authority: ctx.accounts.owner.clone(),
};
let cpi_program = ctx.accounts.token_program.clone();
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
token::transfer(cpi_ctx, interaction_fee)?;
Ok(())
}
I have setup the Interaction pub struct with the following params:
#[derive(Accounts)]
pub struct Interaction<'info> {
#[account(mut, has_one = owner)]
from: CpiAccount<'info, TokenAccount>,
#[account("from.mint == to.mint")]
to: CpiAccount<'info, TokenAccount>,
#[account(signer)]
owner: AccountInfo<'info>,
token_program: AccountInfo<'info>,
}
As far as I can tell the params are correct, and the god account owns the wallet as the payer and is the signer. Why is this failing, and what am I missing? I'm completely out of ideas.
Answered here by the legendary Armani Ferrante:
Your to account isn't marked mutable.

Resources