-
Notifications
You must be signed in to change notification settings - Fork 36
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
Add check for unset correlation ID when configuring request headers #2435
Conversation
@@ -310,7 +311,11 @@ class NativeAuthRequestProvider(private val config: NativeAuthOAuth2Configuratio | |||
//region helpers | |||
private fun getRequestHeaders(correlationId: String): Map<String, String?> { | |||
val headers: MutableMap<String, String?> = TreeMap() | |||
headers[AuthenticationConstants.AAD.CLIENT_REQUEST_ID] = correlationId | |||
|
|||
if (correlationId != "UNSET") { |
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.
The string 'UNSET' should be used from a constant file.
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.
Having a look at the rest of the codebase and "UNSET" seems to be used in place instead of being used as a constant value. I'd rather stick with the convention here, given that it's a straightforward name and something that doesn't have a specific meaning or is used outside of the context of the SDK.
@@ -148,6 +151,22 @@ class NativeAuthRequestHandlerTest { | |||
) | |||
} | |||
|
|||
@Test | |||
fun testSignUpStartWithUnsetCorrelationIdShouldHaveNilHeader() { |
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.
testSignUpStartWithUnsetCorrelationIdShouldHaveNilHeader
-> 'testSignUpStartWithUnsetCorrelationIdShouldNotHaveHeader'
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.
The title is accurate as it is - the test passes a "UNSET" value, and in this case the header should be nil.
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.
@rmccahill can you elaborate? Why should we be sending an empty header (i.e. the REQUEST_ID
header is set, but it doesn't contain a value), rather than not set the header at all?
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.
Actually, what you're saying seems to contradict line 316 of NativeAuthRequestProvider.kt
. Or maybe we're mis-communicating :D
The way I see it there's a difference between "no header" vs. "header is set and value is null
". Saurabh and I mean the first (and so I would agree that the test name should be "should not have header").
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.
Ah I see what you mean - in my mind 'nil header' refers to the header not being present. But I can see how that's confusing. I've updated the naming on these tests to reflect the suggested name.
...rc/main/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthRequestProvider.kt
Outdated
Show resolved
Hide resolved
@rmccahill could you remove the conditional setting of the correlation ID in Context:
|
The removal and fix of the conditional setting of the correlation ID in AccountState is here AzureAD/microsoft-authentication-library-for-android#2135 |
# Conflicts: # changelog.txt
# Conflicts: # common4j/src/main/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthRequestProvider.kt
…nse(requestCorrelationId = requestCorrelationId),the SDK use the local correlation id rather than the header from the http response. Thus,the filer in the getHeader won't work for this issue.
if (correlationId == null) { | ||
correlationId = UNSET; | ||
if (correlationId == null || correlationId.equals(UNSET)) { | ||
correlationId = UUID.randomUUID().toString(); |
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 can also use correlationId = "";
and let CommandDispatcher.initializeDiagnosticContext
final String correlationId = StringUtil.isNullOrEmpty(requestCorrelationId) ? UUID.randomUUID().toString() : requestCorrelationId;
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.
@Yuki-YuXin if what you mention is also a solution, then why did you add this code change here? to rephrase: I agree that what CommandDispatcher.initializeDiagnosticContext
does should be sufficient. So why is your code change needed?
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.
Because the current code snippet will set the correlationId to UNSET. If the correlationId passed into CommandDispatcher.initializeDiagnosticContext is UNSET, then after correlationId = StringUtil.isNullOrEmpty(requestCorrelationId) ? UUID.randomUUID().toString() : requestCorrelationId, the correlationId would keep as "UNSET"
@@ -310,8 +311,8 @@ class NativeAuthRequestProvider(private val config: NativeAuthOAuth2Configuratio | |||
private fun getRequestHeaders(correlationId: String): Map<String, String?> { | |||
val headers: MutableMap<String, String?> = TreeMap() | |||
headers[AuthenticationConstants.AAD.CLIENT_REQUEST_ID] = correlationId |
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.
Revert it back because we are using the local parameters.getCorrelationId() in the http/SDK response rather than the response header CLIENT_REQUEST_ID, which means even though we filter out the UNSET in the header, it will still exist in the response.
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.
I'm not following this. This method isn't used in responses; it's used to create requests.
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.
Yes, the reason we don't want UNSET to be sent in the http request header is because we don't want the server to response "UNSET" to us in the http response header. However, the SDK didn't use the http response header to construct the SDK response. It used the local correlation passed in or recorded in DiagnosticContext. Since we don't use the correlation id in the http response header then nothing sent in the request will affect the correlation ID of the SDK response
…r.createSignUpChallengeRequest(correlationId = "UNSET")
if (correlationId == null) { | ||
correlationId = UNSET; | ||
if (correlationId == null || correlationId.equals(UNSET)) { | ||
correlationId = UUID.randomUUID().toString(); |
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.
Am I right in saying:
it's okay to create a UUID here, because:
- we don't need to use/set the thread ID, because that's not what's being used in the request headers (i.e. when setting the request headers, we use map key
DiagnosticContext.CORRELATION_ID
, notDiagnosticContext.THREAD_ID
CommandDispatcher.initializeDiagnosticContext()
does the same; generate a UUID.
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.
Yes, that's correct.
@@ -310,8 +311,8 @@ class NativeAuthRequestProvider(private val config: NativeAuthOAuth2Configuratio | |||
private fun getRequestHeaders(correlationId: String): Map<String, String?> { | |||
val headers: MutableMap<String, String?> = TreeMap() | |||
headers[AuthenticationConstants.AAD.CLIENT_REQUEST_ID] = correlationId | |||
headers[AuthenticationConstants.SdkPlatformFields.PRODUCT] = LibraryInfoHelper.getLibraryName() | |||
headers[AuthenticationConstants.SdkPlatformFields.VERSION] = LibraryInfoHelper.getLibraryVersion() | |||
headers[AuthenticationConstants.SdkPlatformFields.PRODUCT] = DiagnosticContext.INSTANCE.requestContext[AuthenticationConstants.SdkPlatformFields.PRODUCT] |
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.
I think this code change is wrong. And the one on the line below.
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.
I have no idea why the changes here. If that's wrong, I will revert it back.
@@ -103,7 +103,7 @@ class SignInInteractor( | |||
) | |||
|
|||
val rawApiResponse = nativeAuthResponseHandler.getSignInInitiateResultFromHttpResponse( | |||
requestCorrelationId = requestCorrelationId, | |||
requestCorrelationId = requestCorrelationId, // Question: should we use the CLIENT_REQUEST_ID header from response here? |
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.
I think you're misunderstanding what's happening here (as did I when we talked about it yesterday).
- when the request is created (
nativeAuthRequestProvider.createSignInInitiateRequest
)getRequestHeaders()
is used to compose the correlation ID, which in turn takes the correlation ID from the command parameters. - when the response object is created (by converting the raw API response into a local data model), we pass the original correlation ID to the request provider. We do this because the API might not return a correlation ID, in which case we set it manually using
requestCorrelationId
.
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.
Oh, my bad.
After checking inside getSignInInitiateResultFromHttpResponse
// If the API doesn't return a correlation ID header value, use the correlation ID of the original API request
val correlationId: String = response.getHeaderValue(AuthenticationConstants.AAD.CLIENT_REQUEST_ID, 0).let {responseCorrelationId ->
Then we definitely should have the try catch mechanism in the header.
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.
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.
@@ -310,8 +311,8 @@ class NativeAuthRequestProvider(private val config: NativeAuthOAuth2Configuratio | |||
private fun getRequestHeaders(correlationId: String): Map<String, String?> { | |||
val headers: MutableMap<String, String?> = TreeMap() | |||
headers[AuthenticationConstants.AAD.CLIENT_REQUEST_ID] = correlationId |
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.
Going back to the original goal of this ticket: I still think we need a catch-all fallback here that catches any "UNSET" values and ensures we don't send it to the API (because if we do, the API will return "UNSET" back to us, which we don't want). There are 2 ways I can think of:
- catch "UNSET" and replace with UUID
- catch "UNSET" and not set correlation ID at all. This is what MSAL does. The expectation is that the API should then create a correlation ID and send it back to us.
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.
Currently, I keep the changes to apply the second option: "catch "UNSET" and not set correlation ID at all". The expectation could not be achieved for the reason in the PR summary. I don't think we should make changes to the NativeAuthResponseHandler part about the header in this PR. If the pipeline passes, maybe we can have a discussion later on what would be best.
...test/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthRequestHandlerTest.kt
Show resolved
Hide resolved
...test/com/microsoft/identity/common/java/nativeauth/providers/NativeAuthRequestHandlerTest.kt
Show resolved
Hide resolved
@SammyO After talking to IOS, the UNSET correlation id seems to be only an issue on the Android side. "In brief what native auth iOS SDK operates around the correlationID:
|
@Yuki-YuXin I don't think that's exactly the question we want to ask the iOS team. iOS' intended behaviour that you describe is also the intended behaviour on Android. However, we need to account for situations where that intended design has a bug. The question is: if the SDK (iOS or Android) doesn't send a correlation ID to the API, does the API generate one and send it back through headers? Our assumption is yes, but our findings from Friday state the opposite. |
If a correlation ID hasn't been configured for a request, the 'UNSET' value will be sent to the server, and the server will then return that 'UNSET' value from the response. To prevent this, the correlation ID should only be appended if it is a valid value.
Additionally, tests have been added to ensure that an 'UNSET' value isn't added to the request headers, and valid correlation IDs are being added to the request headers on a valid request.
Also, there are a few tweaks made to
NativeAuthRequestHandlerTest
- there are some tests where a request is configured and created, but there are no assertions made on those requests. I've added a few checks to tests that were missing validations, and some other small tweaks.Update:
“If a correlation ID hasn't been configured for a request, the 'UNSET' value will be sent to the server, and the server will then return that 'UNSET' value from the response.” -> After experiment, this assumption is wrong. The header used on the SDK side is client-request-id not ms-client-request-id, these two values are different if they both attached in the response header. The interesting thing is that, if client sends invalid correlation id like "UNSET", the server would not return the client-request-id to the client. On the contrary, if the correlation id is valid and client sent it in the client-request-id header, the server will return it as the same value in the client-request-id header. SDK made the assumption that if no client-request-id header is sent to the server from client, the server will generate one for the client seems untrue. The correlation id is dependant on client-request-id x-ms-request-id is the request specific, it targets the Service Request Id not the CorrelationId, while x-ms-request-id is returned from the server in all times.