-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
KTOR-7644 Make re-auth status codes configurable #4420
Conversation
90a256c
to
d6e7166
Compare
a65ff10
to
9ae3d49
Compare
2034352
to
5f0ca66
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the pull request! Seems useful to me
Let's discuss an alternative approach before we merge this.
if (origin.request.attributes.contains(AuthCircuitBreaker)) return@on origin | ||
|
||
var call = origin | ||
|
||
val candidateProviders = HashSet(providers) | ||
|
||
while (call.response.status == HttpStatusCode.Unauthorized) { | ||
while (call.response.status in pluginConfig.reAuthStatusCodes) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Speaking of broken services, I've seen some APIs returning 200 OK
with actual status code in a body. We could provide a lambda instead of a list of statuses:
var isUnauthorized: (HttpResponse) -> Boolean = { it.status == HttpStatusCode.Unauthorized }
@e5l, @bjhham, @marychatte, what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds much better than the list. I've pushed that change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@osipxd
I like the idea with lambda!
returning 200 OK with actual status code in a body
Do you mean it could be used like this?
isUnauthorized = { it.bodyAsText() == "401" }
If yes, then isUnauthorized
function should be suspend
ktor-client/ktor-client-plugins/ktor-client-auth/common/src/io/ktor/client/plugins/auth/Auth.kt
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thank you!
@bjhham, @marychatte, could you also take a look?
ktor-client/ktor-client-plugins/ktor-client-auth/common/src/io/ktor/client/plugins/auth/Auth.kt
Outdated
Show resolved
Hide resolved
Thanks for the PR! Can we also please add tests for it? |
Sure, added one and switched to a suspend lambda. |
/** | ||
* A lambda function to control whether a response is unauthorized and should trigger a refresh / re-auth. | ||
*/ | ||
public var isUnauthorized: suspend (HttpResponse) -> Boolean = { it.status == HttpStatusCode.Unauthorized } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's refine the API a bit to make it more consistent to the existing ones.
public var isUnauthorized: suspend (HttpResponse) -> Boolean = { it.status == HttpStatusCode.Unauthorized } | |
internal var isUnauthorized: suspend (HttpResponse) -> Boolean = { it.status == HttpStatusCode.Unauthorized } | |
/** ... */ | |
public fun isUnauthorized(block: suspend (HttpResponse) -> Boolean) { | |
isUnauthorized = block | |
} |
Though, I'm not sure about naming. Probably, isUnauthorizedResponse
would be better. @marychatte, what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But then you couldn't build on top of existing values:
val original = isUnauthorized
isUnauthorized = { original(it) || ... }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could make only set()
private then, giving the possibility to get the configured isAuthorized
:
public var isUnauthorized: suspend (HttpResponse) -> Boolean = { it.status == HttpStatusCode.Unauthorized }
private set()
The point is to specify this field without an assignment operator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is a getter + a differently named setter function better than a single var unifying those two?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@osipxd isUnauthorizedResponse
sounds valid to me, it adds more clarity 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pushed the renamed version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is a getter + a differently named setter function better than a single var unifying those two?
The main concern for me is the inconsistency with the existing APIs. Let's break my proposal into two points:
- Use a function with a lambda parameter instead of a setter
The reason is that syntaxdoSomething { ... }
is widespread across all plugins’ configurations, while fields are usually used for primitive types or enums. Though such functions usually contain a verb in the name, socheckResponseUnauthorized
orcheckResponseIsUnautorized
fit better in this case. - Hide the field from public API
Making the field internal makes it simpler for us to change implementation in the future without breaking changes. To be honest, I can't come up with any use case when I want to build check on top of an existing value. The default value is trivial, so I'd say it is clearer to copy it instead of reuse not to be affected by possible further changes. So it feels safe to make this field internal.
The combination of internal field + function lets us introduce shortcuts for common use cases. For example, we could introduce such an API in the future:
fun reAuthorizeOnStatuses(vararg statusCodes: HttpStatusCode) {
isUnauthorizedResponse = { it.status in statusCodes }
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please take a look at the latest commit. I've changed it. Though, this solution seems less direct and understandable, especially when there's a public getter (which probably is missing in many of the other APIs?). The reAuthorizeOnStatuses
extra function can also still be provided with a var, too, so I don't see that argument.
01843ea
to
ca5c4be
Compare
ce83e36
to
ccb920f
Compare
06aef8c
to
6680b91
Compare
Some services use 403 instead of 401. Changing them might be impossible. With this change Ktor can flexibly work with any broken service.
6680b91
to
cc7a176
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for keeping this branch up-to-date!
Could you allow editing by maintainers? I want to mark isUnauthorizedResponse
as @InternalAPI
.
Oh right I forgot the InternalAPI markers. You should have access now. |
@InternalAPI | ||
public var isUnauthorizedResponse: suspend (HttpResponse) -> Boolean = { it.status == HttpStatusCode.Unauthorized } | ||
private set |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this an internal API? My intention was to allow access to the current value, so you can extend an existing rule.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We want to preserve the right to introduce breaking changes to this field. Keeping it public for those who’re ready to further breaking changes.
Some services use 403 instead of 401. Changing them might be impossible. With this change Ktor can flexibly work with any broken service. --------- Co-authored-by: Osip Fatkullin <osip.fatkullin@jetbrains.com>
Some services use 403 instead of 401. Changing them might be impossible. With this change Ktor can flexibly work with any broken service. --------- Co-authored-by: Osip Fatkullin <osip.fatkullin@jetbrains.com>
Some services use 403 instead of 401. Changing them might be impossible. With this change Ktor can flexibly work with any broken service. --------- Co-authored-by: Osip Fatkullin <osip.fatkullin@jetbrains.com>
Some services use 403 instead of 401. Changing them might be impossible. With this change Ktor can flexibly work with any broken service. --------- Co-authored-by: Osip Fatkullin <osip.fatkullin@jetbrains.com>
Some services use 403 instead of 401. Changing them might be impossible. With this change Ktor can flexibly work with any broken service. --------- Co-authored-by: Osip Fatkullin <osip.fatkullin@jetbrains.com>
Some services use 403 instead of 401. Changing them might be impossible. With this change Ktor can flexibly work with any broken service. --------- Co-authored-by: Osip Fatkullin <osip.fatkullin@jetbrains.com>
Some services use 403 instead of 401. Changing them might be impossible. With this change Ktor can flexibly work with any broken service. --------- Co-authored-by: Osip Fatkullin <osip.fatkullin@jetbrains.com>
Some services use 403 instead of 401. Changing them might be impossible. With this change Ktor can flexibly work with any broken service. --------- Co-authored-by: Osip Fatkullin <osip.fatkullin@jetbrains.com>
Some services use 403 instead of 401. Changing them might be impossible. With this change Ktor can flexibly work with any broken service. --------- Co-authored-by: Osip Fatkullin <osip.fatkullin@jetbrains.com>
Some services use 403 instead of 401. Changing them might be impossible. With this change Ktor can flexibly work with any broken service. --------- Co-authored-by: Osip Fatkullin <osip.fatkullin@jetbrains.com>
Some services use 403 instead of 401. Changing them might be impossible. With this change Ktor can flexibly work with any broken service. --------- Co-authored-by: Osip Fatkullin <osip.fatkullin@jetbrains.com>
Some services use 403 instead of 401. Changing them might be impossible. With this change Ktor can flexibly work with any broken service. --------- Co-authored-by: Osip Fatkullin <osip.fatkullin@jetbrains.com>
Some services use 403 instead of 401. Changing them might be impossible. With this change Ktor can flexibly work with any broken service. --------- Co-authored-by: Osip Fatkullin <osip.fatkullin@jetbrains.com>
Some services use 403 instead of 401. Changing them might be impossible. With this change Ktor can flexibly work with any broken service. --------- Co-authored-by: Osip Fatkullin <osip.fatkullin@jetbrains.com>
Some services use 403 instead of 401. Changing them might be impossible. With this change Ktor can flexibly work with any broken service. --------- Co-authored-by: Osip Fatkullin <osip.fatkullin@jetbrains.com>
Some services use 403 instead of 401. Changing them might be impossible. With this change Ktor can flexibly work with any broken service. --------- Co-authored-by: Osip Fatkullin <osip.fatkullin@jetbrains.com>
Some services use 403 instead of 401. Changing them might be impossible. With this change Ktor can flexibly work with any broken service.
KTOR-7644 Auth: Make re-auth/refresh status codes configurable