Git Product home page Git Product logo

diqwest's Introduction

diqwest

This crate extends reqwest to be able to send requests with digest auth flow.

When you send a request with digest auth flow this first request will be executed. In case the response is a 401 the www-authenticate header is parsed and the answer is calculated. The initial request is executed again with additional Authorization header. The response will be returned from send_with_digest_auth().

In case the first response is not a 401 this first response is returned from send_with_digest_auth() without any manipulation. In case the first response is a 401 but the www-authenticate header is missing the first reponse is returned as well.

diqwest is a lean crate and has nearly no dependencies:

  • reqwest, for sure, as diqwest is an extension to it. Without any enabled features and no default features.
  • digest_auth is used to calculate the answer. Without any enabled feature and no default features.
  • url is used to validate urls on type level. Without any enabled feature and no default features.

That's it. No other dependencies are used. Not even thiserror is used to not force it on you.

Examples

Async (default)

use diqwest::WithDigestAuth;
use reqwest::{Client, Response};

// Call `.send_with_digest_auth()` on `RequestBuilder` like `send()`
let response: Response = Client::new()
  .get("url")
  .send_with_digest_auth("username", "password")
  .await?;

Blocking (feature flag blocking has to be enabled in Cargo.toml)

use diqwest::blocking::WithDigestAuth;
use reqwest::blocking::{Client, Response};

// Call `.send_with_digest_auth()` on `RequestBuilder` like `send()`
let response: Response = Client::new()
  .get("url")
  .send_with_digest_auth("username", "password")?;

diqwest's People

Contributors

maoertel avatar rcastill avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

diqwest's Issues

no method named 'send_with_digest_auth` ?

I have a minimum viable call I am trying to execute.. but getting method not found

use reqwest::Error;
use diqwest::WithDigestAuth;
use reqwest::{Client, Response};

#[tokio::main]
async fn main() -> Result<(), Error> {

    let public_key = "PUBLICKEY";
    let private_key = "PRIVATEKEY";

    let client = reqwest::Client::new();

    let response: Response = Client::new()
        .get("https://cloud.mongodb.com/api/atlas/v1.0/groups")
        .send_with_digest_auth(public_key, private_key)
        .await?;

    // Check if the request was successful and only then read the text
    if response.status().is_success() {
        let body = response.text().await?;
        println!("{}", body);
    } else {
        // If the response was an error, print the status code and message
        eprintln!("Error: {:?}", response.status());
    }


    Ok(())
}

only spits back

error[E0599]: no method named `send_with_digest_auth` found for struct `RequestBuilder` in the current scope
  --> src/main.rs:16:10
   |
14 |       let response: Response = Client::new()
   |  ______________________________-
15 | |         .get("https://cloud.mongodb.com/api/atlas/v1.0/groups")
16 | |         .send_with_digest_auth(public_key, private_key)
   | |         -^^^^^^^^^^^^^^^^^^^^^ method not found in `RequestBuilder`
   | |_________|
   | 

Fails if username/password is defined in url

In reqwest, when username/password is provided in url, it seems to use basic authentication. Something like this:

http://user:pass@host:port/path

My idea was to use .send_with_digest_authentication() using such an url, that way the authentication method would be "automatic":

  • If endpoint was basic, then first response would be OK
  • If endpoint was digest, then second request should do it

Problem is, reqwest seems to fallback to basic auth because of the user/pass in the url. I tried to patch diqwest in a fork to make sure username/pass are removed for the second request, but it seems that there is no way to access the url from RequestBuilder.

The only way that I can think of this working, is that response from RequestBuilder::build is used. But that needs access to Client::execute.

I don't know if solving this issue is in the scope of this crate, but an automatic authentication process would be a nice to have. Maybe there is another approach?

Issue with Mikrotik SwOS

I'm working on a program to log into the web interface of a Mikrotik Switch.

The authentication is failing with diqwest. The authentication works as expected in a browser / with curl.

I created a VERY simple program(golang) to act as an http server and ALWAYS send the same WWW-Authenticate Header:

package main

import "net/http"

func main() {
	http.HandleFunc("/hello", getHelo)
	err := http.ListenAndServe(":8099", nil)
	if err != nil {
		println(err.Error())
	}
}

func getHelo(w http.ResponseWriter, r *http.Request) {
	println(r.Header.Get("Authorization"))
	w.Header().Set("WWW-Authenticate", "Digest realm=\"CSR305-1G-4s+\", qop=\"auth\", nonce=\"7f267bd4\", stale=FALSE")
	w.WriteHeader(401)
	w.Write([]byte("Hello"))
}

The Curl Test command: curl -vvv --digest -u "admin:admin" http://127.0.0.1:8099/hello

and the Rust diqwest code:

    let client = Client::new();
    let url = format!("http://{}/hello","127.0.0.1:8099");
    let mut response = client
        .get(&url)
        .send_with_digest_auth("admin", "admin");

Below are the headers received by the server:

cURL :: Digest username="admin", realm="CSR305-1G-4s+", nonce="7f267bd4", uri="/hello", cnonce="ODRkZjFjYWQ4NDQwYmM0NmFjM2JjMjg0NDU1YmJlYmQ=", nc=00000001, qop=auth, response="9b919621bc520ba65b54b9ae73039d17"

Diqwest :: Digest username="admin", realm="CSR305-1G-4s+", nonce="7f267bd4", uri="/hello", cnonce="9e6a40050bb3da09b2c172af433d7c90", nc=00000001, qop=auth, response="2c1604f23e48d28111d47e33b81a3d13", algorithm=MD5

qop is auth and the algorithm is unspecified.

HA1 should be: 903fda724aba1586c086fdbed1e7eeb0
HA2 should be: 87969d867ee02588e3220f2c3b0ad34b

cURL's response should be: 9b919621bc520ba65b54b9ae73039d17
diqwest's response should be: 2c1604f23e48d28111d47e33b81a3d13


is it possible that the additional "algorithm=MD5" header is messing up the auth algorithm in SwitchOS?

Remove previous Authorization header

I have the following use-case:

User gives authenticated url. Something like this: http://user:pass@host:port/path

If request is submitted with such an Url with reqwest, it will automatically form the "Authorization" header, assuming it's basic authentication.

The idea is to be able to "automatically detect" if it is basic auth or digest auth.

In order to implement this, send_with_digest_auth() could remove any previous "Authorization" headers set. But I'm not sure if this is possible. Maybe reqwest adds the header after send_with_digest_auth() acts.

Is this even something that diqwest should handle?

Maybe this issue can be brought to reqwest itself?

Bad requests due to full request URI in Authorization header

Hi,

I was not able to get diqwest to successfully work against an embedded device with an HTTP server that requires digest authentication. I was able to get it to work with curl --digest, so I compared the requests generated by curl vs. those generated by reqwest+diqwest.

It turns out curl generates an Authorization header that looks like:

Authorization: Digest username="...", realm="...", nonce="...", uri="/cgi-bin/egauge-show?c&T=1639976400,1642654800", cnonce="...", nc=00000001, qop=auth, response="..."

while reqwest+diqwest generates an Authorization header that looks like:

authorization: Digest username="...", realm="...", nonce="...", uri="http://100.100.0.208/cgi-bin/egauge-show?c&T=1639976400,1642654800", qop=auth, nc=00000001, cnonce="...", response="...", algorithm=MD5

I was able to get diqwest to generate an Authorization header closer to what curl generates with something like this:

diff --git a/src/core.rs b/src/core.rs
index 14d714a..2c2e9f5 100644
--- a/src/core.rs
+++ b/src/core.rs
@@ -25,11 +25,24 @@ impl WithDigestAuth for RequestBuilder {
     match first_response.status() {
       StatusCode::UNAUTHORIZED => {
         let request = clone_request_builder(self)?.build()?;
-        let url = request.url();
+        let full_url = request.url();
         let method = HttpMethod::from(request.method().as_str());
         let body = request.body().and_then(|b| b.as_bytes());
-        let answer =
-          DigestAuthHelper::parse_digest_auth_header(first_response, url.as_str(), method, body, username, password)?;
+
+        let mut base_url = full_url.clone();
+        match base_url.path_segments_mut() {
+          Ok(mut path) => {
+            path.clear();
+          }
+          Err(_) => {}
+        }
+
+        let url = format!(
+          "/{}",
+          base_url.make_relative(&full_url).ok_or(RequestBuilderNotCloneableError)?
+        );
+
+        let answer = DigestAuthHelper::parse_digest_auth_header(first_response, &url, method, body, username, password)?;
 
         Ok(clone_request_builder(self)?.header("Authorization", answer).send().await?)
       }

Is this a bug in diqwest?

Thanks!

Error: Diqwest(RequestBuilderNotCloneable) Request body must not be a stream

Hi,

First, thanks for this crate. It's useful.

I'm encountering sort of an edge case where I'm consuming an API endpoint that requires digest auth and a multipart/form-data body.

Upon sending the request with send_with_digest_auth(), I get the error as written in the title.

I've been having some difficulty finding the same use case and solutions following some google search. Would you have a suggestion?

I'm passing a Vec<u8> to reqwest::multipart::Form, and that buffer is acquired from std::fs::read().

Query parameters being dropped from URI in Authorization header

Hi @maoertel,

Just following up on #1 and the fix in #4.

I tried upgrading to diqwest 1.1.0 and I see the uri field in the Authorization header is now being reduced to just the URI path, which seems to trim a little too much. I have some query parameters set and the query string is also getting stripped. For the embedded device I'm sending requests to this makes it return a 400.

This patch worked for me:

diff --git a/src/blocking.rs b/src/blocking.rs
index 061be24..d35b553 100644
--- a/src/blocking.rs
+++ b/src/blocking.rs
@@ -23,7 +23,7 @@ impl WithDigestAuth for RequestBuilder {
     match first_response.status() {
       StatusCode::UNAUTHORIZED => {
         let request = clone_request_builder(self)?.build()?;
-        let path = request.url().path();
+        let path = request.url()[url::Position::AfterPort..];
         let method = HttpMethod::from(request.method().as_str());
         let body = request.body().and_then(|b| b.as_bytes());
         let answer = parse_digest_auth_header(first_response.headers(), path, method, body, username, password);
diff --git a/src/lib.rs b/src/lib.rs
index a431830..f8ad652 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -71,7 +71,7 @@ impl WithDigestAuth for RequestBuilder {
     match first_response.status() {
       StatusCode::UNAUTHORIZED => {
         let request = clone_request_builder(self)?.build()?;
-        let path = request.url().path();
+        let path = &request.url()[url::Position::AfterPort..];
         let method = HttpMethod::from(request.method().as_str());
         let body = request.body().and_then(|b| b.as_bytes());
         let answer = parse_digest_auth_header(first_response.headers(), path, method, body, username, password);

Upgrade reqwest to v0.12

Hello, thanks for this amazing project.

Reqwest v0.12 upgrades hyper, http and http-body to v1. It seems like a minor version update, but its impact is huge.

Reqwest v0.11 released on January 6, 2021. It's been a long time since reqwest v0.11 to v0.12.

Can you update the reqwest dependency to v0.12 and release a new version?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.