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

KTOR-7644 Make re-auth status codes configurable #4420

Merged
merged 7 commits into from
Nov 19, 2024

Conversation

wkornewald
Copy link
Contributor

@wkornewald wkornewald commented Oct 22, 2024

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

@wkornewald wkornewald changed the title Make re-auth status codes configurable KTOR-7644 Make re-auth status codes configurable Oct 28, 2024
@osipxd osipxd changed the base branch from main to 3.1.0-eap October 29, 2024 09:11
@osipxd osipxd force-pushed the 3.1.0-eap branch 2 times, most recently from a65ff10 to 9ae3d49 Compare October 31, 2024 10:15
@wkornewald wkornewald force-pushed the reauth-status-codes branch 2 times, most recently from 2034352 to 5f0ca66 Compare October 31, 2024 15:20
Copy link
Member

@osipxd osipxd left a 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) {
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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

Copy link
Member

@osipxd osipxd left a 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?

@marychatte
Copy link
Member

Thanks for the PR! Can we also please add tests for it?

@wkornewald
Copy link
Contributor Author

Thanks for the PR! Can we also please add tests for it?

Sure, added one and switched to a suspend lambda.

@osipxd osipxd self-requested a review November 7, 2024 09:05
/**
* 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 }
Copy link
Member

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.

Suggested change
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?

Copy link
Contributor Author

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) || ... }

Copy link
Member

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.

Copy link
Contributor Author

@wkornewald wkornewald Nov 7, 2024

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?

Copy link
Member

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 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Pushed the renamed version.

Copy link
Member

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:

  1. Use a function with a lambda parameter instead of a setter
    The reason is that syntax doSomething { ... } 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, so checkResponseUnauthorized or checkResponseIsUnautorized fit better in this case.
  2. 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 }
}

Copy link
Contributor Author

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.

Some services use 403 instead of 401. Changing them might be impossible. With this change Ktor can flexibly work with any broken service.
Copy link
Member

@osipxd osipxd left a 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.

@wkornewald
Copy link
Contributor Author

Oh right I forgot the InternalAPI markers. You should have access now.

Comment on lines +43 to +45
@InternalAPI
public var isUnauthorizedResponse: suspend (HttpResponse) -> Boolean = { it.status == HttpStatusCode.Unauthorized }
private set
Copy link
Contributor Author

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.

Copy link
Member

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.

@osipxd osipxd merged commit 6e0eb10 into ktorio:3.1.0-eap Nov 19, 2024
13 checks passed
e5l pushed a commit that referenced this pull request Nov 19, 2024
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>
@wkornewald wkornewald deleted the reauth-status-codes branch November 20, 2024 06:43
osipxd added a commit that referenced this pull request Nov 22, 2024
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>
e5l pushed a commit that referenced this pull request Nov 25, 2024
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>
e5l pushed a commit that referenced this pull request Nov 26, 2024
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>
e5l pushed a commit that referenced this pull request Nov 27, 2024
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>
e5l pushed a commit that referenced this pull request Nov 28, 2024
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>
e5l pushed a commit that referenced this pull request Nov 29, 2024
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>
e5l pushed a commit that referenced this pull request Dec 3, 2024
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>
e5l pushed a commit that referenced this pull request Dec 3, 2024
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>
osipxd added a commit that referenced this pull request Dec 6, 2024
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>
osipxd added a commit that referenced this pull request Dec 11, 2024
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>
osipxd added a commit that referenced this pull request Dec 11, 2024
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>
osipxd added a commit that referenced this pull request Dec 12, 2024
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>
osipxd added a commit that referenced this pull request Dec 12, 2024
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>
osipxd added a commit that referenced this pull request Dec 19, 2024
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>
Stexxe pushed a commit that referenced this pull request Dec 24, 2024
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants