-
Notifications
You must be signed in to change notification settings - Fork 121
[REST API] Alamofire network retry and adapt network requests #8483
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
[REST API] Alamofire network retry and adapt network requests #8483
Conversation
…I using application password.
…RLRequest` instead of `URLRequestConvertible`.
…equests if applicable.
…able for REST API.
This reverts commit 6e8efb9.
You can test the changes from this Pull Request by:
|
jaclync
left a comment
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.
Hi @selanthiraiyan 👋 Sorry for some preliminary questions before diving into deeper code review, since I don't know much about this project. Trying to clarify a few things:
- What does the user provide when signing in with the application password for the REST API feature?
- From the issue description:
WPOrg REST API requests should be authenticated using application password. If there is an authentication failure, we should generate application password and retry the REST API request.
What can trigger the original application password to fail/change, is it a common scenario? Just trying to understand why we need to handle this API failure case only for this new authentication method.
- What does the adapter do? I'm not very familiar with the technical requirements, just wanted to get a full understanding of the changes
If there's a p2 or reference that explains the answers to my questions above, feel free to just drop the link here 🙂
Generated by 🚫 dangerJS |
|
Thanks for the review, @jaclync! I have addressed your comments. The PR is ready for the second pass of review. 🙏 |
From the wp-admin UI here, it looks like it shows the name of the app with a CTA to revoke. If the user chooses to revoke the application password here, it feels like they intentionally want to remove access from the app? If that's the case, then it feels a bit intrusive how the app just creates another password. I'm sure this has been thought out before, just sharing my observations from the review. |
jaclync
left a comment
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 I have more understanding of the PR now 😄 thanks for the updates and explanations, I took another pass with some questions/suggestions.
Networking/Networking/ApplicationPassword/RequestAuthenticator.swift
Outdated
Show resolved
Hide resolved
| let credentials: Credentials? | ||
|
|
||
| func convert(_ request: URLRequestConvertible) -> URLRequestConvertible { | ||
| guard let jetpackRequest = request as? JetpackRequest, |
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.
just wondering, do we only do the conversation from JetpackRequest? How about DotcomRequest and WordPressOrgRequest?
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.
DotcomRequestare sent to WordPress.com servers, and it needs WPCOM authentication token. We cannot talk to WPCOM servers using the application password.WordPressOrgRequestwill be sent throughWordPressOrgNetworkinstead ofAlamofireNetwork.WordPressOrgNetworksolely handlesWordPressOrgRequest, and it doesn't useRequestConverter.
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.
TIL, thanks for the explanation. Now I have another question 😅 what are the differences between WordPressOrgRequest and RESTRequest? from the asURLRequest implementation in both structs, the way it sets the URL feels pretty similar 🤔
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.
WordPressOrgRequesttalks to WordPress in general. https://developer.wordpress.org/rest-api/RESTRequesttalks to WooCommerce plugin. https://woocommerce.com/document/woocommerce-rest-api/
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.
WordPressOrgRequest talks to WordPress in general. https://developer.wordpress.org/rest-api/
RESTRequest talks to WooCommerce plugin. https://woocommerce.com/document/woocommerce-rest-api/
Just chiming in to clarify: I believe RESTRequest can be used to handle requests to all WordPress org and Woo requests. The difference between these two types is the way authentication is handled:
WordPressOrgRequestuses cookie authentication.RESTRequestuses application password.
We should probably come up with better naming for these and update the documentation for clearer explanations.
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.
Thanks for the details, Huong!
RESTRequest has this extra parameter/value let wooApiVersion: WooAPIVersion when compared to WordPressOrgRequest.
I thought the wooApiVersion parameter makes it clear that we should use RESTRequest for the WooCommerce plugin's REST API-related requests. I looked again now and noticed that WooAPIVersion has a case none which is an empty string.
We should probably come up with better naming for these and update the documentation for clearer explanations.
Maybe we could have a single struct instead of two and use the WooAPIVersion.none case. What do you think?
I will create an issue after we finish discussing it 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.
Maybe we could have a single struct instead of two and use the WooAPIVersion.none case. What do you think?
This makes sense to me! A few things I notice:
- We're using
WordPressApiValidatorinWordPressOrgRequestbut this is not necessary. SinceWordPressOrgRequestis required to conform toRequest, we can usePlaceholderDataValidatorinstead. WordPressOrgRequestis not using JSON encoding forPOSTandPUTrequests likeRESTRequestdoes, so unifying them withRESTRequestimplementation will fix this too.
Let's use RESTRequest for both then.
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.
Created an issue here #8512
| } | ||
| case .failure(let error): | ||
| completion(nil, error) | ||
| let request = requestConverter.convert(request) |
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 see, now I understand why it has to be converted here - URLRequestConvertible is only available here and not after sessionManager.request(request) where the retry takes place, and the conversion requires the site URL from the credentials so it's not possible at the JetpackRequest call sites. I can't think of other solutions given these constraints, the only thought I had is to move the authentication step to the URLRequestConvertible level but I'm not sure if that works with the retry and what's the best practice.
…and application password authentication methods.
I started an internal discussion about this, and we will update this if we decide to follow a different flow. p1672283352691059-slack-C046HDZL87J |
|
Thanks, @jaclync. I addressed your comments. Kindly take another look when you can. 🙏 |
jaclync
left a comment
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.
Thanks for all the updates and clarifications, LGTM now 👍 sorry it took a while, since the changes are now 1000+ 😆
One non-blocking suggestion is to unit test RequestProcessor on the retry and adaption behavior. given the current PR size, feel free to consider it for the future.
| return try? DefaultApplicationPasswordUseCase(username: username, | ||
| password: password, | ||
| siteAddress: siteAddress) |
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.
does it make sense to validate these two URLs during the login step so that we don't have to handle this error scenario here? If the URLs are invalid from the beginning, the app doesn't seem usable since it can never generate an application password.
docs/architecture-overview.md
Outdated
| 3. **RESTRequest** | ||
|
|
||
| Represents a REST API request which will be used to contact to the site directly. (Skipping Jetpack tunnel) | ||
| These requests are authenticated using application passwords. |
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.
nit: maybe can mention AuthenticatedRESTRequest here?
| These requests are authenticated using application passwords. | |
| These requests are then authenticated using an application password using `AuthenticatedRESTRequest`. |
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.
Done in 9464bd1
docs/architecture-overview.md
Outdated
| Injects application password and a custom user-agent header into anything that conforms to the URLConvertible protocol. Usually wraps up | ||
| a RESTRequest. |
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.
nit: "anything that conforms to the URLConvertible protocol" might not hold anymore?
| Injects application password and a custom user-agent header into anything that conforms to the URLConvertible protocol. Usually wraps up | |
| a RESTRequest. | |
| Injects application password and a custom user-agent header into anything that conforms to the URLConvertible protocol and can be authenticated with an application password. For example, a RESTRequest. |
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.
Done in 9464bd1
| func test_jetpack_request_is_returned_when_credentials_not_available() { | ||
| // Given | ||
| let converter = RequestConverter(credentials: nil) | ||
| let jetpackRequest = JetpackRequest(wooApiVersion: .mark1, method: .get, siteID: 123, path: "test", availableAsRESTRequest: false) |
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.
nit: does it make sense to set availableAsRESTRequest to true to enable this conversion in any case?
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.
Done in d0b7993
| let request = try converter.convert(jetpackRequest).asURLRequest() | ||
| let updatedRequest = try authenticator.authenticate(request) |
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.
nit: is the conversion necessary since availableAsRESTRequest is false already and RequestConverter has been tested separately?
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.
Done in 7ccf5a4
| let updatedRequest = try authenticator.authenticate(request) | ||
|
|
||
| // Then | ||
| XCTAssertTrue(updatedRequest is UnauthenticatedRequest) |
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.
nit: does it make sense to recover this assertion? or has the type of request changed?
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 will be working with URLRequest here. So I had to remove the type checking the assertion and check the header fields directly instead.
| authenticator.authenticateRequest(request) { result in | ||
| updatedRequest = try? result.get() | ||
| } | ||
| let request = try converter.convert(jetpackRequest).asURLRequest() |
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.
nit: similar to before, the request can be passed directly (JetpackRequest or RESTRequest) without the converter so that this is testing just one thing (RequestAuthenticator)
Same for other test cases in this 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.
Great point. Done in 7ccf5a4
| guard case let .wpcom(_, authToken, _) = credentials else { | ||
| XCTFail("Missing credentials.") | ||
| return | ||
| } | ||
|
|
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.
nit: do we still need Credentials? it looks like we can just pass an auth token string to the next line directly.
Same for other test cases in this 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.
Done in c66416d
…nly authentication related logic.
|
Thank you for your patience and the detailed review, Jaclyn! ❤️ 🙇
I created an issue for this. #8507 |

Closes: #8482
Description
As part of the REST API project we need the capability to retry a request in case of authentication failure.
The flow for a REST API request is described below,
The request adapt and retry implementation is followed from https://github.com/wordpress-mobile/WordPressKit-iOS/blob/trunk/WordPressKit/Authenticator.swift
There are no changes to how a Jetpack or WPCOM request is handled. They will be authenticated using the new
RequestAuthenticator. But there will be no retrying.UI tests
As the network started validating the requests using Alamofire's
validatemethod like this, the UI tests started failing for mocks withoutheaderssection.The UI tests were failing due to incorrect
Content-Typein the response. Theheaderssection takes care of adding theContent-Typeto the mock response.To fix this, I added the headers part for all missing mock JSON files.
Testing instructions
Screenshots
NA
RELEASE-NOTES.txtif necessary.