Skip to content

Commit 6f986c6

Browse files
phpbgsamuelchemla
andauthored
Add digest auth support (#18)
Add digest auth support --------- Co-authored-by: Samuel CHEMLA <[email protected]>
1 parent a00fb96 commit 6f986c6

File tree

4 files changed

+46
-81
lines changed

4 files changed

+46
-81
lines changed

app/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,5 @@ dependencies {
8787
debugImplementation 'androidx.compose.ui:ui-test-manifest'
8888
implementation 'com.google.guava:guava:32.1.2-android'
8989
implementation 'ru.gildor.coroutines:kotlin-coroutines-okhttp:1.0'
90+
implementation 'io.github.rburgst:okhttp-digest:3.1.0'
9091
}

app/src/main/java/com/phpbg/easysync/dav/OptionalBasicAuthInterceptor.kt

Lines changed: 0 additions & 52 deletions
This file was deleted.

app/src/main/java/com/phpbg/easysync/dav/WebDavService.kt

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,27 @@
2525
package com.phpbg.easysync.dav
2626

2727
import android.util.Log
28+
import com.burgstaller.okhttp.AuthenticationCacheInterceptor
29+
import com.burgstaller.okhttp.CachingAuthenticatorDecorator
30+
import com.burgstaller.okhttp.DefaultRequestCacheKeyProvider
31+
import com.burgstaller.okhttp.DispatchingAuthenticator
32+
import com.burgstaller.okhttp.basic.BasicAuthenticator
33+
import com.burgstaller.okhttp.digest.CachingAuthenticator
34+
import com.burgstaller.okhttp.digest.Credentials
35+
import com.burgstaller.okhttp.digest.DigestAuthenticator
36+
import com.google.common.cache.Cache
37+
import com.google.common.cache.CacheBuilder
38+
import com.google.common.net.UrlEscapers
2839
import com.phpbg.easysync.BuildConfig
2940
import com.phpbg.easysync.settings.Settings
3041
import com.phpbg.easysync.util.ParametrizedMutex
3142
import com.phpbg.easysync.util.TTLHashSet
32-
import com.google.common.cache.Cache
33-
import com.google.common.cache.CacheBuilder
34-
import com.google.common.net.UrlEscapers
43+
import kotlinx.coroutines.CoroutineScope
3544
import kotlinx.coroutines.Dispatchers
36-
import kotlinx.coroutines.coroutineScope
3745
import kotlinx.coroutines.flow.Flow
3846
import kotlinx.coroutines.flow.first
47+
import kotlinx.coroutines.flow.launchIn
3948
import kotlinx.coroutines.flow.onEach
40-
import kotlinx.coroutines.launch
4149
import kotlinx.coroutines.sync.Mutex
4250
import kotlinx.coroutines.sync.withLock
4351
import kotlinx.coroutines.withContext
@@ -60,14 +68,15 @@ import java.nio.file.attribute.FileTime
6068
import java.time.ZonedDateTime
6169
import java.time.format.DateTimeFormatter.ISO_DATE_TIME
6270
import java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME
71+
import java.util.concurrent.ConcurrentHashMap
6372
import java.util.concurrent.TimeUnit
6473

74+
6575
private const val TAG = "WebDavService"
6676

6777
class WebDavService(
6878
private var rootUrl: RootPath,
69-
private val httpClient: OkHttpClient,
70-
private val optionalBasicAuthInterceptor: OptionalBasicAuthInterceptor
79+
private var httpClient: OkHttpClient,
7180
) {
7281
// Mutex to avoid concurrent creation of same directories
7382
private val mkcolPMutex = ParametrizedMutex<String>()
@@ -83,7 +92,7 @@ class WebDavService(
8392

8493
fun updateFromSettings(settings: Settings) {
8594
rootUrl = RootPath(settings.url).concat(CollectionPath(settings.davPath))
86-
optionalBasicAuthInterceptor.updateCredentials(settings.username, settings.password)
95+
httpClient = createHttpClient(settings)
8796
}
8897

8998
//https://learn.microsoft.com/en-us/previous-versions/office/developer/exchange-server-2003/aa142923(v=exchg.65)
@@ -367,39 +376,38 @@ class WebDavService(
367376
mutex.withLock {
368377
if (instance == null) {
369378
instance = create(settings)
370-
coroutineScope {
371-
launch {
372-
settingsFlow.onEach { newSettings ->
373-
instance!!.updateFromSettings(newSettings)
374-
}
375-
}
376-
}
379+
settingsFlow
380+
.onEach { instance!!.updateFromSettings(it) }
381+
.launchIn(CoroutineScope(Dispatchers.Default))
377382
}
378383
}
379384
}
380385
return instance!!
381386
}
382387

383-
fun create(settings: Settings): WebDavService {
384-
Log.d(TAG, "Creating DAVService with URL: " + settings.url)
385-
if (settings.url.isEmpty()) {
386-
throw MisconfigurationException()
387-
}
388-
val basicAuthInterceptor = OptionalBasicAuthInterceptor(
389-
settings.username,
390-
settings.password
391-
)
388+
private fun createHttpClient(settings: Settings): OkHttpClient {
389+
val authCache: Map<String, CachingAuthenticator> = ConcurrentHashMap()
390+
val credentials = Credentials(settings.username, settings.password)
391+
val basicAuthenticator = BasicAuthenticator(credentials)
392+
val digestAuthenticator = DigestAuthenticator(credentials)
393+
394+
// note that all auth schemes should be registered as lowercase!
395+
val authenticator = DispatchingAuthenticator.Builder()
396+
.with("digest", digestAuthenticator)
397+
.with("basic", basicAuthenticator)
398+
.build()
392399

393400
val dispatcher = Dispatcher()
394401
dispatcher.maxRequestsPerHost = 6
395402
val okHttpClientBuilder = OkHttpClient.Builder()
403+
.authenticator(CachingAuthenticatorDecorator(authenticator, authCache))
404+
.addInterceptor(AuthenticationCacheInterceptor(authCache, DefaultRequestCacheKeyProvider()))
396405
.connectTimeout(10, TimeUnit.SECONDS)
397406
.readTimeout(60, TimeUnit.SECONDS)
398407
.writeTimeout(60, TimeUnit.SECONDS)
399408
.callTimeout(3600, TimeUnit.SECONDS)
400409
.dispatcher(dispatcher)
401410
.addInterceptor(TrafficStatsInterceptor())
402-
.addInterceptor(basicAuthInterceptor)
403411
.retryOnConnectionFailure(false)
404412

405413
if (BuildConfig.DEBUG) {
@@ -409,12 +417,20 @@ class WebDavService(
409417
okHttpClientBuilder.addInterceptor(loggingInterceptor)
410418
}
411419

412-
val okHttpClient = okHttpClientBuilder.build()
420+
return okHttpClientBuilder.build()
421+
}
422+
423+
fun create(settings: Settings): WebDavService {
424+
Log.d(TAG, "Creating DAVService with URL: " + settings.url)
425+
if (settings.url.isEmpty()) {
426+
throw MisconfigurationException()
427+
}
428+
429+
val okHttpClient = createHttpClient(settings)
413430

414431
return WebDavService(
415432
RootPath(settings.url).concat(CollectionPath(settings.davPath)),
416-
okHttpClient,
417-
basicAuthInterceptor
433+
okHttpClient
418434
)
419435
}
420436
}

readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Synchronize your phone with your WebDAV server, the easy way.
77
* Synchronize in both directions :
88
* Files touched on your phone will be uploaded / deleted on your server
99
* Files touched on your server will be uploaded / deleted on your phone
10-
* Basic auth (login with password)
10+
* Basic auth (login with password) or Digest auth
1111
* Secure storage of your credentials
1212
* Preserve timestamps if you use Nextcloud as DAV server
1313

0 commit comments

Comments
 (0)