Giter Site home page Giter Site logo

okhttp-digest's Introduction

okhttp-digest

A digest authenticator for okhttp. Most of the code is ported from Apache Http Client.

Important

This artifact has moved from jcenter to maven central! The coordinates have changed from

com.burgstaller:okhttp-digest:<version> to io.github.rburgst:okhttp-digest:<version>

For more details, see #71.

Usage

final DigestAuthenticator authenticator = new DigestAuthenticator(new Credentials("username", "pass"));

final Map<String, CachingAuthenticator> authCache = new ConcurrentHashMap<>();
final OkHttpClient client = new OkHttpClient.Builder()
        .authenticator(new CachingAuthenticatorDecorator(authenticator, authCache))
        .addInterceptor(new AuthenticationCacheInterceptor(authCache))
        .build();

String url = "http://www.google.com";
Request request = new Request.Builder()
        .url(url)
        .get()
        .build();
Response response = client.newCall(request).execute();

If you want to support multiple authentication schemes (including auth caching) then this should work:

final OkHttpClient.Builder builder = new OkHttpClient.Builder();
final Map<String, CachingAuthenticator> authCache = new ConcurrentHashMap<>();

final Credentials credentials = new Credentials("username", "pass");
final BasicAuthenticator basicAuthenticator = new BasicAuthenticator(credentials);
final DigestAuthenticator digestAuthenticator = new DigestAuthenticator(credentials);

// note that all auth schemes should be registered as lowercase!
DispatchingAuthenticator authenticator = new DispatchingAuthenticator.Builder()
        .with("digest", digestAuthenticator)
        .with("basic", basicAuthenticator)
        .build();

final OkHttpClient client = builder
        .authenticator(new CachingAuthenticatorDecorator(authenticator, authCache))
        .addInterceptor(new AuthenticationCacheInterceptor(authCache, new DefaultRequestCacheKeyProvider()))
        .addNetworkInterceptor(logger)
        .build();

If you want to cache Proxy credentials, you need to add a NetworkInterceptor :

final OkHttpClient client = builder
        .authenticator(new CachingAuthenticatorDecorator(authenticator, authCache))
        .addNetworkInterceptor(new AuthenticationCacheInterceptor(authCache, new DefaultProxyCacheKeyProvider()))
        .addNetworkInterceptor(logger)
        .build();

You can also combine Proxy AND Web site Authentication :

final OkHttpClient client = builder
        .authenticator(new CachingAuthenticatorDecorator(authenticator, authCache))
        .addNetworkInterceptor(new AuthenticationCacheInterceptor(authCache,new DefaultProxyCacheKeyProvider()))
        .addInterceptor(new AuthenticationCacheInterceptor(authCache,new DefaultRequestCacheKeyProvider()))        
        .addNetworkInterceptor(logger)
        .build();

Maven Central Build Status

Use via gradle

implementation 'io.github.rburgst:okhttp-digest:3.1.0'

okhttp-digest's People

Contributors

ahardewig avatar alexeyvasilyev avatar chewie69006 avatar clescot avatar drlue avatar gmousset avatar johnjohndoe avatar jruesga avatar kudo avatar mengxn avatar neher avatar rburgst avatar rburgstaller avatar sammefford avatar swankjesse avatar vladrc avatar

Stargazers

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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

okhttp-digest's Issues

Problem with "previous basic authentication failed, returning null"

Thanks four your hard work to making this repo possible!

I've a question using the okhttp-digest for authentication:
Connecting to a specific server (https://www.pcloud.com/) I'm getting often (not always) 401 responses while presenting the correct credentials, around every randomly 5 request using the same setup.

Often during the communiction with this server I get an an W/OkHttp: previous basic authentication failed, returning null in the log but everything seems to be fine. Only if after that, a Cached authentication expired. Sending a new request. and followed again by a previous basic authentication failed, returning null appears in the log, the server responds with a 401.

03-10 10:24:10.586 5144-5180 W/OkHttp: previous basic authentication failed, returning null
03-10 10:24:10.586 5144-5180 D/OkHttp: Cached authentication expired. Sending a new request.
03-10 10:24:10.916 5144-5180 W/OkHttp: previous basic authentication failed, returning null

Can you imagine what I'm doing wrong or is just the server bad configured? Communicating with other servers I'm facing no errors and never saw this logs before.

The complete HTTP-response when this error happen:

D	20180308141040.447	OkHttp	<-- 401 Unauthorized https://webdav.pcloud.com/[_path_] (411ms)
D	20180308141040.448	OkHttp	Date: Thu, 08 Mar 2018 20:10:41 GMT
D	20180308141040.449	OkHttp	Server: Apache/2.4.10 (Debian)
D	20180308141040.450	OkHttp	Keep-Alive: timeout=5, max=98
D	20180308141040.455	OkHttp	Connection: Keep-Alive
D	20180308141040.463	OkHttp	<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>401 Unauthorized</title>
</head><body>
<h1>Unauthorized</h1>
<p>This server could not verify that you
are authorized to access the document
requested.  Either you supplied the wrong
credentials (e.g., bad password), or your
browser doesn't understand how to supply
the credentials required.</p>
<hr>
<address>Apache/2.4.10 (Debian) Server at webdav.pcloud.com Port 443</address>
</body></html>

THANKS!

https

final DigestAuthenticator authenticator = new DigestAuthenticator(new Credentials(unicomUserName, unicomPassword));

    final Map<String, CachingAuthenticator> authCache = new ConcurrentHashMap<>();
    final OkHttpClient client = new OkHttpClient.Builder()
            .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(unicomProxyHost, 8143))) //https:8143
            .authenticator(new CachingAuthenticatorDecorator(authenticator, authCache))
            .addInterceptor(new AuthenticationCacheInterceptor(authCache))
            .build();

// String url = "http://www.google.com";
Request request = new Request.Builder()
.url(unicomUrl)
.build();
try (Response response = client.newCall(request).execute()) {

        return response.body().string();
    }

use proxy and digest lead to error:
Process: com.example.admin.mydemo, PID: 13867
java.lang.IllegalStateException: closed
at okio.RealBufferedSource.rangeEquals(RealBufferedSource.java:398)
at okio.RealBufferedSource.rangeEquals(RealBufferedSource.java:392)
at okhttp3.internal.Util.bomAwareCharset(Util.java:431)
at okhttp3.ResponseBody.string(ResponseBody.java:174)
at com.example.admin.mydemo.MainActivity.unicomHttpRequest(MainActivity.java:258)
at com.example.admin.mydemo.MainActivity.access$000(MainActivity.java:32)
at com.example.admin.mydemo.MainActivity$1.run(MainActivity.java:85)
at java.lang.Thread.run(Thread.java:761)

Can't use OkHttpClient.Builder().addInterceptor() or OkHttpClient.Builder().authenticator()

Hello!,
The examples for this library seem outdated, but I have been trying to use it on the last okhttp release (3.0.0-RC1) as such:

DigestAuthenticator authenticator = new DigestAuthenticator(new Credentials("user", "password"));
final Map<String, CachingAuthenticator> authCache = new ConcurrentHashMap<>();

client = new OkHttpClient.Builder()
.addInterceptor(new AuthenticationCacheInterceptor(authCache))
.authenticator(new CachingAuthenticatorDecorator(authenticator, authCache))
.build();

The problem is that neither .addInterceptor() or .authenticator() accepts AuthenticationCacheInterceptor or CachingAuthenticatorDecorator as values.

How is it supposed to be used now?

Thanks and best regards!

Does this build without Android?

When I downloaded the source ran with "./gradlew install" I got this:

* Where:
Build file 'C:\temp\okhttp-digest-1.12\okhttp-digest-1.12\build.gradle' line: 116

* What went wrong:
A problem occurred evaluating root project 'okhttp-digest-1.12'.
> SDK location not found. Define location with sdk.dir in the local.properties file or with an ANDROID_HOME environment variable.

I can't point it to ANDROID_HOME since I don't have android. Can I not build or use this library without android?

Proxy Digest Authentication

Hi,
I try to use okhhtp-digest for proxy authentication so I set proxy attribute to true.
But in findDigestHeader() method the "WWW-Authenticate" header is used instead of testing the proxy attribute and using WWW_AUTH or PROXY_AUTH constants.

Even after this change, the authentication fails :( The proxy-Authorization header is generated but refused by the proxy. And the mechanism to prevent infinite loops does not work also because of wrong header name used.

Here is the Proxy Authenticator I use :

public class ProxyDigestAuthenticator extends DigestAuthenticator {

    public ProxyDigestAuthenticator(Credentials credentials) {
        super(credentials);
        setProxy(true);
    }

    @Override
    public Request authenticate(Route route, Response response) throws IOException {

        if(response.request().header(PROXY_AUTH_RESP) != null)
            return null;


        String header = findProxyDigestHeader(response);
        parseChallenge(header, 7, header.length() - 7);
        // first copy all request headers to our params array
        copyHeaderMap(response.headers(), parameters);

        return authenticateWithState(response.request());
    }

    private String findProxyDigestHeader(Response response) {
        final List<String> headers = response.headers(PROXY_AUTH);
        for (String header : headers) {
            if (header.startsWith("Digest")) {
                return header;
            }
        }
        throw new IllegalArgumentException("unsupported auth scheme: " + headers);
    }


    private void copyHeaderMap(Headers headers, Map<String, String> dest) {
        for (int i = 0; i < headers.size(); i++) {
            dest.put(headers.name(i), headers.value(i));
        }
    }

}

And how it is used :

        final ProxyDigestAuthenticator authenticator = new ProxyDigestAuthenticator(new Credentials("proxy_user", "proxy_pass"));
        builder.proxyAuthenticator(authenticator);

        final Map<String, CachingAuthenticator> authCache = new ConcurrentHashMap<>();
        builder.addInterceptor(new AuthenticationCacheInterceptor(authCache));
        builder.authenticator(new CachingAuthenticatorDecorator(authenticator, authCache));        
        OkHttpClient client = builder.build();


        Request request = new Request.Builder()
                .url("https://mySite.com")
                .build();

    Response response = client.newCall(request).execute();

Any idea why it fails with these changes ?

Thanks

java.lang.IllegalArgumentException: unsupported auth scheme: Basic realm="DVRNVRDVS"

Hello. Some servers sends 2 WWW-Authenticate headers.

WWW-Authenticate:Digest realm="DVRNVRDVS", domain="httpsHost", qop="auth", nonce="7676767677:676767676767", opaque="", algorithm="MD5", stale="FALSE"
WWW-Authenticate:Basic realm="DVRNVRDVS"

You must use response.headers("WWW-Authenticate") instead of response.header("WWW-Authenticate")

Method havePreviousDigestAuthorizationAndShouldAbort ignores Proxy

Method havePreviousDigestAuthorizationAndShouldAbort() looks only at "Authorization" header, and ignores "Proxy-Authorization" header completely. This means we ignore the fact that we could be interacting with a proxy rather than with a WWW server. If the user enters wrong credentials, then we'll constantly repeat our requests to the proxy. This loop never breaks. It's happening with my proxy now.
So, I suggest the following. Instead of

final String previousAuthorizationHeader = request.header("Authorization");

it should be something smarter, like this:

final String headerKey;
if (isProxy())
    headerKey = PROXY_AUTH_RESP;
else
    headerKey = WWW_AUTH_RESP;
final String previousAuthorizationHeader = request.header(headerKey);

Does this support Java 1.7?

My code works fine against Java 1.8. But when I test against Java 1.7 every request gets a 401 response and has to resend. Then, if the payload isn't retryable, the resend goes through blank. No error is thrown, it just silently fails to send the payload with the second request. I'll put together steps to reproduce, but I wanted to first ask if this library has been tested with Java 1.7.

Not working with latest Retrofit and OKHttp?

Hey I am trying your implementation. But the OkHttpClient does not have a function called client.setAuthenticator(new CachingAuthenticatorDecorator(authenticator, authCache));?
Am I missing something? Or does it not support the newest version of OKHttp?

I am using compile 'com.squareup.retrofit2:retrofit:2.2.0'
and try to set the Client like:

`OkHttpClient httpClient = new OkHttpClient();
final DigestAuthenticator authenticator = new DigestAuthenticator(new Credentials("username", "pass"));

    final Map<String, CachingAuthenticator> authCache = new ConcurrentHashMap<>();
    httpClient.interceptors().add(new AuthenticationCacheInterceptor(authCache));
    httpClient.authenticator().add(new CachingAuthenticatorDecorator(authenticator, authCache)); //this line is not working

    Retrofit.Builder retrofitBuilder = NetworkHelper.create().retrofit();
    retrofitBuilder.client(httpClient);
    Retrofit retrofit = retrofitBuilder.build();`

Add package names to README

As I'm getting started, it would help if you included the import statements. That way I don't have to go digging to see the package names in order to import the classes.

OkHttp3:authenticator.setCredentials wrong

client = new OkHttpClient();
final DigestAuthenticator authenticator = new DigestAuthenticator();
authenticator.setCredentials(new Credentials("username", "pass"));

this is your code, but authenticator don't have method "setCredentials",can you help me to solve it?

okHttpClient.setAuthenticator(...); no such method

Attempting to use the example code in the readme for this project, but not able to find the above method in this project or the basic okhttp3 library. What is the correct usage?
I see that in 2.5.0 okhttp the method did exist, however if using 3.9.0 how would the code run?

No v0.7 tag so not on Bintray

v0.7 is listed in the release notes but it does not have a git tag associated with it so it has not been pushed out to bintray for consumption.

Authentication Cache Concurrent Modification Exception

I received this crash report and I couldn't figure where is the problem. Using version 1.18 with Okhttp 3.12.6.

Fatal Exception: java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode + 1441(HashMap.java:1441)
at java.util.HashMap$EntryIterator.next + 1475(HashMap.java:1475)
at java.util.HashMap$EntryIterator.next + 1473(HashMap.java:1473)
at java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$1.next + 1703(Collections.java:1703)
at java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$1.next + 1696(Collections.java:1696)
at java.util.HashMap.putMapEntries + 511(HashMap.java:511)
at java.util.HashMap. + 489(HashMap.java:489)
at zn.a + 26(DigestAuthenticator.java:26)
at wn.a + 2(DispatchingAuthenticator.java:2)
at sn.intercept + 6(AuthenticationCacheInterceptor.java:6)
at okhttp3.internal.http.RealInterceptorChain.proceed + 10(RealInterceptorChain.java:10)
at okhttp3.internal.http.RealInterceptorChain.proceed + 1(RealInterceptorChain.java:1)
at rd1$b$a.intercept + 9(HTTPClient.kt:9)
at okhttp3.internal.http.RealInterceptorChain.proceed + 10(RealInterceptorChain.java:10)
at okhttp3.internal.http.RealInterceptorChain.proceed + 1(RealInterceptorChain.java:1)
at okhttp3.RealCall.getResponseWithInterceptorChain + 13(RealCall.java:13)
at okhttp3.RealCall$AsyncCall.execute + 2(RealCall.java:2)
at okhttp3.internal.NamedRunnable.run + 3(NamedRunnable.java:3)
at java.util.concurrent.ThreadPoolExecutor.runWorker + 1167(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run + 641(ThreadPoolExecutor.java:641)
at java.lang.Thread.run + 764(Thread.java:764)

Remove "missing realm in challenge" log entry

Remove "missing realm in challenge" log entry from DigestAuthenticator. It overloads logs with this log entry if basic HTTP GET requests are coming all the time and caching used.

For example

Map<String, CachingAuthenticator> gAuthCache = new ConcurrentHashMap<>();
Credentials credentials = new Credentials(username, password);
final BasicAuthenticator  basicAuthenticator  = new BasicAuthenticator(credentials);
final DigestAuthenticator digestAuthenticator = new DigestAuthenticator(credentials);

DispatchingAuthenticator authenticator = new DispatchingAuthenticator.Builder()
  .with("digest", digestAuthenticator)
  .with("basic",  basicAuthenticator)
  .build();

OkHttpClient.Builder builder = new OkHttpClient.Builder();
OkHttpClient client = builder
  .authenticator(new CachingAuthenticatorDecorator(authenticator, gAuthCache))
  .addInterceptor(new AuthenticationCacheInterceptor(gAuthCache))
  ....

This happens because DispatchingAuthenticator will first call DigestAuthenticator.authenticateWithState() and only then BasicAuthenticator.authenticateWithState().

Authenticator should be selected according to their priority

ISSUE
If basic and digest authenticators specified (see code below), basic one (not secured one) is used always if web server supports both basic and digest authentications.

DispatchingAuthenticator authenticator = new DispatchingAuthenticator.Builder()
.with("digest", digestAuthenticator)
.with("basic", basicAuthenticator)
.build();

EXAMPLE
GET /Streaming/Channels/1/picture HTTP/1.1
Host: 192.168.1.4:1140
Connection: Keep-Alive

HTTP/1.1 401 Unauthorized
Date: Mon, 08 Aug 2016 17:28:03 GMT
Server: App-webs/
Content-Length: 275
Content-Type: text/html
Connection: close
WWW-Authenticate: Digest qop="auth", realm="DS-2CD2432F-IW", nonce="4d6a6c4551305a43515449364e6d497a4d6a63314e546b3d", stale="FALSE"
WWW-Authenticate: Basic realm="DS-2CD2432F-IW"

[data skipped]

GET /Streaming/Channels/1/picture HTTP/1.1
Authorization: Basic YWRtaW46dGVzdA==
Host: 192.168.1.4:1140
Connection: Keep-Alive

HTTP/1.1 200 OK
Content-Type: image/jpeg; charset="UTF-8"
Content-Length:131764

[data skipped]

The issue is that authenticate(...) method in DispatchingAuthenticator selects authenticator according to it's key value in hashmap authenticatorRegistry.

FIX
authenticatorRegistry in DispatchingAuthenticator class should be of type LinkedHashMap instead of HashMap.

Maven can't find

I tried the following in my maven pom.xml:

	<dependency>
		<groupId>com.burgstaller</groupId>
		<artifactId>okhttp-digest</artifactId>
		<version>1.12</version>
	</dependency>

But I get "Could not find artifact com.burgstaller:okhttp-digest:jar:1.12 in central (https://repo.maven.apache.org/maven2)". When I search in mvnrepository.com I can't find anything under com.burgstaller. Is this deployed in maven central? If not, does gradle have another way to get it?

Digest Auth Without Using httpclient

I rewrote the DigestAuthenticator (in Kotlin, but you can convert it to Java).

This does quite a few things:

  • Handles MD5-sess
  • Handles qop=auth (needs a little more work for auth-int)
  • Has a significantly smaller foot print (one class now, it could be split up a little bit)
  • Handles caching correctly (remembers nonce and nonceCount)
  • Will fail on incorrect user/pass.

Noted issues:

  • Does not retry on STALE=true (a valid auth which is stale will cause 401)

Have a look through, if you are happy I can move to Java and do a PR.

/**
 * Created by chris on 08/06/2016.
 * For project Owlr Android
 */
@Suppress("NOTHING_TO_INLINE")
class DigestAuthenticator() : CachingAuthenticator {

    /**
     * Previous cached auth headers <host:port>,<field,value>>
     */
    private val cachedAuthFields = mutableMapOf<String, MutableMap<String, String>>()
    private val charset = Charset.forName("ISO-8859-1")

    override fun authenticate(route: Route?, response: Response): Request? {
        val request = response.request()

        // We see if this request has previously been authed by this nonce
        request.url().let { "${it.host()}:${it.port()}" }.let { it to cachedAuthFields[it] }.apply {
            val (key, fields) = this
            val nonce: String = fields?.get("nonce") ?: return@apply
            if (havePreviouslyAuthedWithSameNonce(request, nonce)) {
                Timber.w("Previous Digest authentication failed, returning null")
                // Clear the cachedAuthFields just in case the nonce is invalid
                cachedAuthFields.remove(key)
                return null
            }
        }

        // If no Digest Request we don't auth.
        val auth = findAuthenticationDigestHeader(response.headers()) ?: return null
        val authFields = splitAuthFields(auth.substring(7))

        return authFromRequest(authFields, response.request())
                ?.apply {
                    // We only cache the authFields if we manage to actually create a auth response
                    request.url().apply { cachedAuthFields.put("${host()}:${port()}", authFields) }
                }
    }

    override fun authenticateWithState(request: Request): Request? {
        // If we haven't authed this host previously this will be null, so we can't preemptively auth it
        val cachedAuthFields = request.url().let { "${it.host()}:${it.port()}" }.let { cachedAuthFields[it] } ?: return null
        cachedAuthFields["realm"] ?: return null
        cachedAuthFields["nonce"] ?: return null
        return authFromRequest(cachedAuthFields, request)
    }

    fun authFromRequest(authFields: MutableMap<String, String>, request: Request): Request? {
        // Pull out username and password from request
        val (username, password) = Credentials(request)

        // We generate a cnonce and nc for the following situations
        var cnonce = ""
        var nc = ""
        when {
            authFields["algorithm"] == "MD5-sess"
                    || authFields["qop"] == "auth"
                    || authFields["qop"] == "auth-int" -> {
                cnonce = getClientNonce()
                nc = getNonceCount(authFields["nc"])
                authFields["nc"] = nc
            }
        }
        val md5: MessageDigest = try {
            MessageDigest.getInstance("MD5")
        } catch(e: NoSuchAlgorithmException) {
            return null
        }

        val HA1: String? = try {
            md5.reset()
            val ha1str = arrayOf(username, authFields["realm"], password).joinToString(":")
            md5.update(ha1str.toByteArray(charset))
            md5.digest().toHexString().let {
                if (authFields["algorithm"] != "MD5-sess") return@let it
                val ha1Ha1Str = arrayOf(it, authFields["nonce"], cnonce).joinToString(":")
                md5.apply {
                    reset(); update(ha1Ha1Str.toByteArray(charset))
                }.digest().toHexString()
            }
        } catch(e: UnsupportedEncodingException) {
            return null
        }

        val HA2 = try {
            md5.reset()
            val ha2str = arrayOf(request.method(), request.url().getUri()).joinToString(":")
            md5.update(ha2str.toByteArray(charset))
            md5.digest().toHexString()
        } catch(e: UnsupportedEncodingException) {
            return null
        }

        val HA3 = try {
            md5.reset()
            val ha3str = when (authFields["qop"]) {
                "auth", "auth-int" -> arrayOf(HA1, authFields["nonce"], nc, cnonce, authFields["qop"], HA2).joinToString(":")
                else -> arrayOf(HA1, authFields["nonce"], HA2).joinToString(":")
            }
            md5.update(ha3str.toByteArray(charset))
            md5.digest().toHexString()
        } catch(e: UnsupportedEncodingException) {
            return null
        }

        val authHeader = buildString {
            append("Digest ")
            append("username").append("=\"").append(username).append("\", ")
            append("realm").append("=\"").append(authFields["realm"]).append("\", ")
            append("nonce").append("=\"").append(authFields["nonce"]).append("\", ")
            append("uri").append("=\"").append(request.url().getUri()).append("\", ")
            append("qop").append('=').append(authFields["qop"] ?: "").append(", ")
            if (cnonce.isNotBlank()) {
                append("nc").append('=').append(nc).append(", ")
                append("cnonce").append("=\"").append(cnonce).append("\", ")
            }
            append("response").append("=\"").append(HA3).append("\", ")
            append("opaque").append("=\"").append(authFields["opaque"] ?: "").append("\"")
        }

        return request.newBuilder()
                .header("Authorization", authHeader)
                .build()
    }

    companion object {

        private val secureRandom by lazy { SecureRandom() }

        private inline fun ByteArray.toHexString(): String = ByteString.of(*this).hex()

        /**
         * Generates a unique client nonce
         */
        private inline fun getClientNonce(): String {
            val cnonceByteArrray = ByteArray(16)
            secureRandom.nextBytes(cnonceByteArrray)
            return ByteString.of(*cnonceByteArrray).hex()
        }

        /**
         * Will see if there was a previous nonceCount and increment the previous count if so.
         */
        private inline fun getNonceCount(previousNC: String?): String {
            return ((previousNC?.toInt() ?: 0) + 1).toString().padStart(8, '0')
        }

        private inline fun HttpUrl.getUri(): String {
            return "${this.encodedPath()}${this.encodedQuery() ?: ""}"
        }

        fun splitAuthFields(authString: String): MutableMap<String, String> {
            val fields = mutableMapOf<String, String>()
            authString.splitToSequence(',').filter { it.isNotBlank() }
                    .forEach { keyPair ->
                        keyPair.splitToSequence('=')
                                .map { it.trim('"', '\t', ' ') }
                                .take(2)
                                .toList()
                                .apply {
                                    fields.put(get(0), get(1))
                                }
                    }
            return fields;
        }

        private fun havePreviouslyAuthedWithSameNonce(request: Request, nonce: String): Boolean {
            // prevent infinite loops when the password is wrong
            request.header("Authorization")?.apply {
                if (startsWith("Digest")) {
                    // Safety
                    val requestNonce = splitAuthFields(this.substring(7)).let {
                        val nonce = it["nonce"]
                        Timber.d("Last Request nonce: $nonce")
                        nonce
                    } ?: return false
                    if (requestNonce == nonce) {
                        return true
                    }
                }
            }
            return false
        }

        private fun findAuthenticationDigestHeader(headers: Headers): String? {
            return headers.values("WWW-Authenticate").firstOrNull { it.startsWith("Digest ") }
        }
    }
}

DispatchingAuthenticator gets mixed up under high concurrency

When used in an application with many concurrent requests, I get the warning "previous digest authentication with same nonce failed, returning null" frequently right before I get a failed request. I'm wondering if somehow the shared "parameters" object in DigestAuthenticator is getting overwritten by multiple concurrent requests and thus accidentally getting marked as stale. I've been using DispatchingAuthenticator as directed in the README.md, but only using BasicAuthenticator or DigestAuthenticator because of #32.

Multiple retries with invalid password

Hi there,

I think that the fix for issue #8 may have introduced a bug when connecting with an incorrect password. I am talking to a third-party server, and every time I attempt to talk to it, it gives me back a unique nonce.

If I attempt to connect to the server using an invalid password, I get a 401 response, which contains a new WWW-Authenticate header (with a new nonce). This means that the new function havePreviousDigestAuthorizationWithSameNonce() is returning true, and so we keep retrying the same request again and again (I suspect eventually we will hit the 20 retries limit, but I dont have the patience).

Anyway, I am a HTTP Digest newbie, but my understanding is that the actual fix for #8 may have worked better if it looked for the 'stale="TRUE"' flag (and then verified that it has in fact received a new nonce to replace the stale one).

I have played around with this and seem to get the correct results but I am unable to test with a server that returns stale values, so I don't have a lot of confidence in my fix for the general case. Are you interested in looking at a patch or pull request to see what I mean?

Cheers,

Glen.

java.lang.NoSuchMethodError with okhttp 4.3.0

Error NoSuchMethodError happens when calling
Platform.get().log(Platform.WARN, "previous digest authentication with same nonce failed, returning null", null);

STACKS:

implementation "com.squareup.okhttp3:okhttp:4.3.0"
implementation "com.burgstaller:okhttp-digest:2.0"

NVIDIA SHIELD Android TV (foster), Android 9

java.lang.NoSuchMethodError: 
  at com.burgstaller.okhttp.basic.BasicAuthenticator.authFromRequest (BasicAuthenticator.java:39)
  at com.burgstaller.okhttp.basic.BasicAuthenticator.authenticate (BasicAuthenticator.java:30)

LGE Qua tab PX (b3), Android 7.0

java.lang.NoSuchMethodError: 
  at com.burgstaller.okhttp.digest.DigestAuthenticator.authenticateWithState (DigestAuthenticator.java:227)
  at com.burgstaller.okhttp.digest.DigestAuthenticator.authenticate (DigestAuthenticator.java:177)

FIX:
log() function in Platform.kt has changed in this commit
square/okhttp@16173e2#diff-f5b1ec0bc5e1662d90457f72acde2607

-  open fun log(level: Int, message: String, t: Throwable?) {
+  open fun log(message: String, level: Int = INFO, t: Throwable? = null) {

Swapping params should fix the issue.

Should throw exception and not retry when the request is not repeatable

When the request itself is not repeatable (let's say if the body's request is a stream), we should throw an exception when we get a 401 and not retry the request. For example see this request and response. The body is not repeatable.

PUT /v1/ext/test/evaltest.xqy HTTP/1.1
ML-Agent-ID: java
Content-Type: text/plain
Transfer-Encoding: chunked
Host: 192.168.0.39:8012
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.6.0

8d3
declare namespace test='http://marklogic.com/test';
declare variable $test:myString as xs:string external;
declare variable $myArray as json:array external;
declare variable $myObject as json:object external;
declare variable $myAnyUri as xs:anyURI external;
declare variable $myBinary as binary() external;
declare variable $myBase64Binary as xs:base64Binary external;
declare variable $myHexBinary as xs:hexBinary external;
declare variable $myDuration as xs:duration external;
declare variable $myQName as xs:QName external;
declare variable $myDocument as document-node() external;
declare variable $myComment as document-node() external;
(: declare variable $myComment as comment() external; :)
declare variable $myElement as element() external;
declare variable $myProcessingInstruction as document-node() external;
declare variable $myText as text() external;
declare variable $myBool as xs:boolean external;
declare variable $myInteger as xs:integer external;
declare variable $myBigInteger as xs:string external;
declare variable $myDecimal as xs:decimal external;
declare variable $myDouble as xs:double external;
declare variable $myFloat as xs:float external;
declare variable $myGDay as xs:gDay external;
declare variable $myGMonth as xs:gMonth external;
declare variable $myGMonthDay as xs:gMonthDay external;
declare variable $myGYear as xs:gYear external;
declare variable $myGYearMonth as xs:gYearMonth external;
declare variable $myDate as xs:date external;
declare variable $myDateTime as xs:dateTime external;
declare variable $myTime as xs:time external;
let $myAttribute             := $myElement/@* 
let $myComment               := $myComment/comment() 
let $myProcessingInstruction := $myProcessingInstruction/processing-instruction() 
let $myCtsQuery := cts:word-query('a') 
let $myFunction := xdmp:functions()[2]
return (
    $test:myString, $myArray, $myObject, $myAnyUri, 
    $myBinary, $myBase64Binary, $myHexBinary, $myDuration, $myQName,
    $myDocument, $myAttribute, $myComment, $myElement, $myProcessingInstruction, $myText, 
    $myBool, $myInteger, $myBigInteger, $myDecimal, $myDouble, $myFloat,
    $myGDay, $myGMonth, $myGMonthDay, $myGYear, $myGYearMonth, $myDate, $myDateTime, $myTime,
    $myCtsQuery, $myFunction
)

0

HTTP/1.1 401 Unauthorized
Server: MarkLogic
WWW-Authenticate: Digest realm="public", qop="auth", nonce="eaa1e15dd0e0d4e3760c8a82c799e2a2", opaque="7b5ca17f3c782ac1"
Content-Type: application/json; charset=utf-8
Content-Length: 104
Connection: Keep-Alive
Keep-Alive: timeout=5

{"errorResponse":   {"statusCode":401,
   "status":"Unauthorized",
   "message":"401 Unauthorized"
  }
}

PUT /v1/ext/test/evaltest.xqy HTTP/1.1
ML-Agent-ID: java
Authorization: Digest username="rest-admin", realm="public", nonce="eaa1e15dd0e0d4e3760c8a82c799e2a2", uri="/v1/ext/test/evaltest.xqy", response="18bad7d22af59c0e3ffe537014a71def", qop=auth, nc=00000001, cnonce="15af601b99993a21", algorithm=MD5, opaque="7b5ca17f3c782ac1"
Content-Type: text/plain
Transfer-Encoding: chunked
Host: 192.168.0.39:8012
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.6.0

0

HTTP/1.1 201 Created
Location: 
Server: MarkLogic
Content-Length: 0
Connection: Keep-Alive
Keep-Alive: timeout=5

Please note that, in order to make this work, we have a ping as a first call to get the nonce and then make this request. However if something goes awry and we encounter a situation like this, we should get a 401 and not retry which would result in an empty body.

Maven Central?

Now that we're using okhttp-digest, our users need to add the jcentral repository to their maven / gradle projects. Before they could get everything from maven central. Unfortunately, some users of our project are struggling with the requirement to add jcentral, just because it's one more step and something they haven't done before.

Is there any way okhttp-digest could also deploy to maven central? Official instructions are here. An automated option is here.

Method havePreviousDigestAuthorizationAndShouldAbort do not check nonce

I think there is a problem with your method havePreviousDigestAuthorizationAndShouldAbort.
It receives a "String nonce" but it never checks if the nonce is available in the previous request header.
That way you will log "previous digest authentication with same nonce failed, returning null" even when the nonce was never present in the request.

IncompatibleClassChangeError using digest access authentication and latest OkHttp

Thanks for this awesome software.

Using the latest version of OkHttp 4.0.1 or 4.0.0 I get the following exception when trying to connect to a WebDAV source using digest access authentication:

 java.lang.IncompatibleClassChangeError: The method 'java.lang.String okhttp3.internal.http.RequestLine.requestPath(okhttp3.HttpUrl)' was expected to be of type static but instead was found to be of type virtual (declaration of 'com.burgstaller.okhttp.digest.DigestAuthenticator' appears in /data/app/org.cryptomator-Ln-apUZxqmV0fbXRF9zInw==/base.apk!classes14.dex)
        at com.burgstaller.okhttp.digest.DigestAuthenticator.authenticateWithState(DigestAuthenticator.java:233)
        at com.burgstaller.okhttp.digest.DigestAuthenticator.authenticate(DigestAuthenticator.java:176)
        at com.burgstaller.okhttp.DispatchingAuthenticator.authenticate(DispatchingAuthenticator.java:45)
        at com.burgstaller.okhttp.CachingAuthenticatorDecorator.authenticate(CachingAuthenticatorDecorator.java:30)
        at okhttp3.internal.http.RetryAndFollowUpInterceptor.followUpRequest(RetryAndFollowUpInterceptor.kt:213)
        at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:102)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87)
        at com.burgstaller.okhttp.AuthenticationCacheInterceptor.intercept(AuthenticationCacheInterceptor.java:45)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87)
        at org.cryptomator.data.cloud.webdav.network.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:49)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:112)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:87)
        at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.kt:184)
        at okhttp3.RealCall.execute(RealCall.kt:66)

If I downgrade the OkHttp client to eg 3.14.1, everything works as expected.

Manifest shouldn't define unneeded attributes

Since this library doesn't required backup and doesn't made use of the use, allowBackup and supportsRtl aren't needed and cause apps importing this library to use manifest merger to replace this properties.
For allowBackup isn't currently possible becasue there is a bug that prevents to replace the library property (https://code.google.com/p/android/issues/detail?id=70073).

I fixed it here jruesga@78fb09f (and fixed other lint warnings as well). Since i modify sdk versions and the lint warnings a didn't pushed a pull request, but i could do it easy if you one.

java.net.ProtocolException: Too many follow-up requests: 21

Hi, I'm trying out your lib w/ latest Android SDK M and trying a http digest authentication web service returns me: java.net.ProtocolException: Too many follow-up requests: 21

I used your sample code:
OkHttpClient client = new OkHttpClient();
final DigestAuthenticator authenticator = new DigestAuthenticator(new Credentials(username, password));

        final Map<String, CachingAuthenticator> authCache = new ConcurrentHashMap<>();
        client.interceptors().add(new AuthenticationCacheInterceptor(authCache));
        client.setAuthenticator(new CachingAuthenticatorDecorator(authenticator, authCache));

        Request request = new Request.Builder()
                .url(url).get().build();
        Response response = client.newCall(request).execute();

Have you run into such an issue?

Assumes Android Logger

The logger used throughout is "android.util.Log". This causes problems for programs that want to use okhttp-digest and are not on an Android device.

Dynamically Change Credentials

Hello everyone.
I need to change the Credentials on runtime without having to build a new instance of my OkHttpClient.
Is it possible? What would be a good alternative?

I am using Dagger2 to keep singleton instances of my Retrofit and OkHttpClient. It works flawlessly with the credentials of the 'default' user but I need to change them later on to access other methods.

    @Provides
    @Singleton
    @DigestAuth
    Credentials provideDigestAuthCredentials(){
        return new Credentials("default", "default");
    }

    @Provides
    @Singleton
    @DigestAuth
    OkHttpClient provideDigestAuthOkHttpClient(@DigestAuth Credentials credentials,
                                               @DigestAuth UrlHostSelectionInterceptor urlHostSelectionInterceptor){
        final DigestAuthenticator digestAuthenticator = new DigestAuthenticator(credentials);
        final Map<String, CachingAuthenticator> authCache = new ConcurrentHashMap<>();
        return new OkHttpClient.Builder()
                .addInterceptor(urlHostSelectionInterceptor)
                .authenticator(new CachingAuthenticatorDecorator(digestAuthenticator,authCache))
                .addInterceptor(new AuthenticationCacheInterceptor(authCache))
                .build();
    }

    @Provides
    @Singleton
    @DigestAuth
    Retrofit provideDigestAuthRetrofit(@DigestAuth OkHttpClient okHttpClient, Gson gson){
        return new Retrofit.Builder()
                .baseUrl(GlobalConstants.LAN_DEFAULT_URL)
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create(gson))
                .client(okHttpClient)
                .build();
    }

How to use UTF-8 in basic and digest access authentication

okhttp does support providing different char-sets for authentication. Using the default of okhttp which is ISO_8859_1 I've problems with some servers and chars e.g. "äöü" and want to switch to UTF-8.

How can I archive this using basic and digest access authentication and this library?

String authValue = okhttp3.Credentials.basic(credentials.getUserName(), credentials.getPassword());

If I provide the authentication directly to okhttp using UTF-8 my Nextcloud accepts chars like "äöü" in the password.

.authenticator((route, response) -> {
    String credential = okhttp3.Credentials.basic("test", "äöüäöüäöü", UTF_8);
    return response.request().newBuilder().header("Authorization", credential).build();
})

okhttp-digest 1.14 error: java.lang.NullPointerException: Attempt to invoke interface method 'int java.util.Map.size()' on a null object reference

Error in okhttp-digest 1.14. It works at the same time in okhttp-digest 1.13.

 W/System.err: java.lang.NullPointerException: Attempt to invoke interface method 'int java.util.Map.size()' on a null object reference
 W/System.err:     at java.util.HashMap.putMapEntries(HashMap.java:500)
 W/System.err:     at java.util.HashMap.<init>(HashMap.java:489)
 W/System.err:     at com.burgstaller.okhttp.digest.DigestAuthenticator.authenticateWithState(DigestAuthenticator.java:204)
 W/System.err:     at com.burgstaller.okhttp.DispatchingAuthenticator.authenticateWithState(DispatchingAuthenticator.java:55)
 W/System.err:     at com.burgstaller.okhttp.AuthenticationCacheInterceptor.intercept(AuthenticationCacheInterceptor.java:40)
 W/System.err:     at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
 W/System.err:     at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
 W/System.err:     at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:185)
 W/System.err:     at okhttp3.RealCall.execute(RealCall.java:69)

URI contains hostname and protocol

I tried your digest authenticator, however the authentication always fails. From my understanding the bug is in DigestAuthenticator line 332:

if (qop == QOP_AUTH) {
            // Method ":" digest-uri-value
            a2 = method + ':' + uri; // <-- here

The value uri is at runtime a full URL like http://example.org/path - however the request Uri used by OkHttp in the HTTP request (first line) is /path (no host and no protocol). I have read that fully qualified uris including protocol and host are allowed as request line, however from my knowledge the common style is to send only the path and use the Host field instead in the request.

In rfc2617 I found the following statement:

The authenticating server must assure that the resource designated by the "uri" directive is the same as the resource specified in the Request-Line; if they are not, the server SHOULD return a 400 Bad Request error.

DigestAuthenticator returning 401 when the nonce isn't valid anymore

Hi,
I run into problems with the AuthenticationCacheInterceptor: The CachingAuthenticator seems to cache a successful digest authentication (which is of course correct so far) and tries to reuse it on subsequent request (also correct).
The problem is that the request might be several seconds later than the original request thus invalidating the nonce.
In previous versions, this was fine as the authenticator just tried again until OkHttp's request limit (20) was reached. As the authentication itself was correct, the next request worked fine.
But the following code in DigestAuthenticator prevents any request after an unsuccessful authentication to be executed:

// prevent infinite loops when the password is wrong
final String authorizationHeader = request.header("Authorization");
if (authorizationHeader != null && authorizationHeader.startsWith("Digest")) {
Log.w(TAG, "previous digest authentication failed, returning null");
return null;
}

It does prevent infinite loops (well 'infinite' as in a maximum of 20 requests) on invalid passwords but it doesn't take into consideration that something else (e. g. the previously cached nonce) was wrong.
Is there a simple way to fix this? I've removed the code and my authentication works fine again.
Thanks!

Missing nonce in challenge

Hi,

I am receiving several reports of this problem. See stack trace below. Is it my fault?

java.lang.IllegalArgumentException: missing nonce in challenge
at com.burgstaller.okhttp.digest.DigestAuthenticator.authenticateWithState(DigestAuthenticator.java:170)
at com.burgstaller.okhttp.digest.DigestAuthenticator.authenticate(DigestAuthenticator.java:148)
at com.burgstaller.okhttp.DispatchingAuthenticator.authenticate(DispatchingAuthenticator.java:45)
at com.burgstaller.okhttp.CachingAuthenticatorDecorator.authenticate(CachingAuthenticatorDecorator.java:30)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.followUpRequest(RetryAndFollowUpInterceptor.java:283)
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:153)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at com.burgstaller.okhttp.AuthenticationCacheInterceptor.intercept(AuthenticationCacheInterceptor.java:37)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at com.xxxxxxx.xxxxxx.singleton.HTTPClient$1.intercept(HTTPClient.java:64)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:170)
at okhttp3.RealCall.access$100(RealCall.java:33)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:120)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)

DigestAuthenticator QOP_AUTH_INT

Sooo while scanning through the code I came across this gem in the DigestAuthenticator class.

            // Method ":" digest-uri-value ":" H(entity-body)
            RequestBody entity = null;
            if (request.body() != null) {
                entity = request.body();
            }
            if (entity != null) {
                // If the entity is not repeatable, try falling back onto QOP_AUTH
                if (qopset.contains("auth")) {
                    qop = QOP_AUTH;
                    a2 = method + ':' + uri;
                } else {
                    throw new AuthenticationException("Qop auth-int cannot be used with " +
                            "a non-repeatable entity");
                }
            } else {
                final HttpEntityDigester entityDigester = new HttpEntityDigester(digester);
                try {
                    if (entity != null) {
                        entity.writeTo(entityDigester);
                    }
                    entityDigester.close();
                } catch (final IOException ex) {
                    throw new AuthenticationException("I/O error reading entity content", ex);
                }
                a2 = method + ':' + uri + ':' + encode(entityDigester.getDigest());
            }

You will never ever get the entity to writeTo the entityDigester.
Also checking body() is not null before applying it to something that can be null is a bit weird...

Need NOTICE file and prominent statement of modification

I'm thinking you'll want to add a NOTICE file and prominent statement of modification in each of the classes from HttpClient for which you modified the package name. HttpClient is licensed with the Apache 2.0 license, correct? In the Apache 2.0 license it states you may redistribute the code if you do the following:

  1. Redistribution. You may reproduce and distribute copies of the
    Work or Derivative Works thereof in any medium, with or without
    modifications, and in Source or Object form, provided that You
    meet the following conditions:

    (a) You must give any other recipients of the Work or
    Derivative Works a copy of this License; and

    (b) You must cause any modified files to carry prominent notices
    stating that You changed the files; and

    (c) You must retain, in the Source form of any Derivative Works
    that You distribute, all copyright, patent, trademark, and
    attribution notices from the Source form of the Work,
    excluding those notices that do not pertain to any part of
    the Derivative Works; and

    (d) If the Work includes a "NOTICE" text file as part of its
    distribution, then any Derivative Works that You distribute must
    include a readable copy of the attribution notices contained
    within such NOTICE file, excluding those notices that do not
    pertain to any part of the Derivative Works, in at least one
    of the following places: within a NOTICE text file distributed
    as part of the Derivative Works; within the Source form or
    documentation, if provided along with the Derivative Works; or,
    within a display generated by the Derivative Works, if and
    wherever such third-party notices normally appear. The contents
    of the NOTICE file are for informational purposes only and
    do not modify the License. You may add Your own attribution
    notices within Derivative Works that You distribute, alongside
    or as an addendum to the NOTICE text from the Work, provided
    that such additional attribution notices cannot be construed
    as modifying the License.

Still getting 401 after Authentication Challenge

I'm trying to do Digest Authentication using okhttp-digest 2.2 and okhttp 4.4.0 to a server on the local network. After making the initial request without the Authentication header, the server replies with a Challenge. The initial request is resend with the generated Digest Authentication header, but I still get a 401 with an Authentication Challenge.

I'm not exactly sure what's the issue, but I compared the requests made on Android with okhttp-digest/okhttp with iOS (using Alamofire). It is working on iOS.

The generated Authorization header on Android:

key value
username 85szSUFQF2jJzRAY_eQpsA==
realm XTV
nonce MTU4Mjk5MjkzMjUwMzphZDFhMWExZmE3Nzg5NmZkZjlhZTM4ODA2ZjkzNjUyOQ==
uri 192.168.1.6:1926
response 42a83bebf69d0c1c16a8b643e44ecef6
qop auth
nc 00000001
cnonce 4f6a4ad6ee010e00
algorithm MD5

And on iOS:

key value
username G5hXS1YdTIDMKc81Nr3YEQ==
realm XTV
nonce MTU4Mjk5Mjk3NTUzMjpiNzdiZTJiMTRmNWRjOTJmMDQ2OWQzNmE5NTliOWFkNg==
uri /6/channeldb/tv/channelLists/all
response 25d1976209a214d64b5033638bf9532f
algorithm MD5
cnonce 7fc19177939316b80ff5b49a56b90dfc
nc 00000001
qop auth

A striking difference is the uri.

platform uri
Android 192.168.1.6:1926
iOS /6/channeldb/tv/channelLists/all

While on Android the host with port is used, on iOS it's the path component. Could this be the issue?

Digest Authentication failing with Dispatching Authenticator

Here's that new issue. My hybrid web app is a complicated use case. I tried a singleton of the DispatchingAuthenticator to support both Basic and Digest auth schemes. Currently, it's set up with conditional logic to chose either BasicAuthenticator or digest. An instance for each one is cached in the sAuthenticator (see client setup) instance from the client setup below.

sClient = new OkHttpClient.Builder()
.followRedirects(false)
.authenticator(sAuthenticator)
.addNetworkInterceptor(logging)
.addInterceptor(new AuthenticationCacheInterceptor(sAuthenticator.authCache))
.addInterceptor(new OKGoInterceptor())
.cache(sCache)
.cookieJar(sCookieJar)
.connectTimeout(45, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();

It's the dynamic setting of credentials from issue #41 that I want to ask about. From that question, what exactly needs to be cleared? The entire cache or the Authenticator that is being reset?

The custom authenticator logic for my use case is using one, static Credentials object for both authenticators and sets the username and password from user input before calling authenticate. This is working (both basic and digest) when I test for success. When I give the wrong password and get new input from the user and try again the right credentials, a call to the authenticator's authenticateWithState gets back null with this warning:
"W/OkHttp: previous basic authentication failed, returning null"

I could use some help. Thanks in advance for any suggestions.

Http proxy with digest auth, error when server sends HTTP-301 redirect

Tested with okhttp-digest 2.1 and okhttp 4.4.0
Using a squid as proxy on ubuntu-server (the docker image)
configured as in this howto: https://blog.mafr.de/2013/06/16/setting-up-a-web-proxy-with-squid/

Digest Authentication at the web proxy is working well, but after the remote server was sending an http-301 redirect, the client wants to call the new URL.
The session is keep-alive, so another authentication is not needed.
But the DigestAuthenticator still sees the 407 response code, and then looks for the digest header, and crashes because the value is "OkHttp-Preemptive" then.

The following is a helpful workaround:
`
DigestAuthenticator authenticator = new DigestAuthenticator(new Credentials(username, password));

Authenticator correctedAuthenticator = new Authenticator() {
@OverRide
public Request authenticate(Route route, Response response) throws IOException {
// workaround problem on HTTP-301 redirect
if ("OkHttp-Preemptive".equals(response.header("Proxy-Authenticate"))) {
return null;
}
return authenticator.authenticate(route, response);
}
};
builder = builder.proxyAuthenticator(correctedAuthenticator);`

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.