Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic auth support for images #356

Merged
merged 4 commits into from
Jul 7, 2024
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion app/src/main/kotlin/com/github/gotify/CoilInstance.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import coil.request.ImageRequest
import com.github.gotify.api.CertUtils
import com.github.gotify.client.model.Application
import java.io.IOException
import okhttp3.Credentials
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import org.tinylog.kotlin.Logger

object CoilInstance {
Expand Down Expand Up @@ -69,7 +72,9 @@ object CoilInstance {
context: Context,
sslSettings: SSLSettings
): Pair<SSLSettings, ImageLoader> {
val builder = OkHttpClient.Builder()
val builder = OkHttpClient
.Builder()
.addInterceptor(BasicAuthInterceptor())
CertUtils.applySslSettings(builder, sslSettings)
val loader = ImageLoader.Builder(context)
.okHttpClient(builder.build())
Expand All @@ -85,3 +90,24 @@ object CoilInstance {
return sslSettings to loader
}
}

private class BasicAuthInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()

// If there's no username, skip the authentication
if (request.url.username.isNotEmpty()) {
val basicAuthString = "${request.url.username}:${request.url.password}@"
val url = request.url.toString().replace(basicAuthString, "")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this replace have a use, for me it works with and without? I've improved the placeholder handling and added logging for failing requests. Could you have a look at it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cyb3rko should I merge this without your review?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, there's a lot going on at the moment.
Hopefully I can find some time today to take a look.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your changes work on my side.
I could have sworn I found edge cases where it wouldn't work without stripping the URL, but I can't reproduce them at the moment. The only thing that could be interesting is what happens if the response is still 401 even after applying basic auth. Does it always start the interceptor again and again and run into request loops?
With stripping the URL we can not have that problem at all because on the next try it does not have a request.url.username anymore.

Nevertheless, I'm fine with merging.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for looking over it. It seems this behavior is required when using okhttp3.Authenticator

Reactive Authentication

Implementations authenticate by returning a follow-up request that includes an authorization
header, or they may decline the challenge by returning null. In this case the unauthenticated
response will be returned to the caller that triggered it.

Implementations should check if the initial request already included an attempt to
authenticate. If so it is likely that further attempts will not be useful and the authenticator
should give up.

but the Interceptor doesn't do new requests, so I'll remove it from here.

request = request
.newBuilder()
.header(
"Authorization",
Credentials.basic(request.url.username, request.url.password)
)
.url(url)
.build()
}
return chain.proceed(request)
}
}
Loading