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

feat: adding support for authentication type on UserAuthorizer #1421

Closed
wants to merge 19 commits into from

Conversation

BigTailWolf
Copy link
Contributor

@BigTailWolf BigTailWolf commented Jun 7, 2024

Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly:

  • Make sure to open an issue as a bug/issue before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea
  • Ensure the tests and linter pass
  • Code coverage does not decrease (if any source code was changed)
  • Appropriate docs were updated (if necessary)

Fixes #<issue_number_goes_here> ☕️

The change basically following the logic that NodeJS change: googleapis/google-auth-library-nodejs#1814

The key point is telling the client how are the UserAuthorizer going to provide auth with token URI.
Our current way is to have client_secret sending as part of the post url parameter. The STS endpoint won't allow that and they are not accepting client_secret field. Instead, the STS is using basic auth which takes a base64 encoding of client_id:client_secret.

Here the change is to provide a parameter to UserAuthorizer which auth from #RFC we are using and set the POST (Current way) as default.
Then in the implementation, when sending the token request, we apply a basic auth header if the authentication type is set to BASIC.

If you write sample code, please follow the samples format.

@BigTailWolf BigTailWolf requested a review from a team as a code owner June 7, 2024 21:34
@product-auto-label product-auto-label bot added the size: m Pull request size is medium. label Jun 7, 2024
@@ -206,7 +210,8 @@ public LowLevelHttpResponse execute() throws IOException {
}
String foundSecret = query.get("client_secret");
String expectedSecret = clients.get(foundId);
if (foundSecret == null || !foundSecret.equals(expectedSecret)) {
if ((foundSecret == null || !foundSecret.equals(expectedSecret))
&& clients.get("Authorization") == null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we are validating the auth header anywhere.

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 is the limitation of MockTokenServerTransport as it doesn't capture request header during it's execute method. So we have to use an addHeader method to simulate what has been added to the headers. And then in the test code call we cal this addHeader prior to make the http request.

Copy link
Contributor

Choose a reason for hiding this comment

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

You can get the request header -- we do it in other tests I'm pretty sure

BigTailWolf and others added 3 commits June 10, 2024 22:50
Co-authored-by: Leo <39062083+lsirac@users.noreply.github.com>
Co-authored-by: Leo <39062083+lsirac@users.noreply.github.com>
@BigTailWolf BigTailWolf requested a review from lsirac June 11, 2024 07:11
@lsirac
Copy link
Contributor

lsirac commented Jun 11, 2024

The library methods here return UserCredentials. Is this compatible with BYOID? We have ExternalAccountAuthorizedUserCredentials for BYOID.

@BigTailWolf
Copy link
Contributor Author

The library methods here return UserCredentials. Is this compatible with BYOID? We have ExternalAccountAuthorizedUserCredentials for BYOID.

ExternalAccountAuthorizedUserCredentials won't need any changes to have the capability of BYOID. Cloud Code can choose this one if they want to switch.
Per the sync with Cloud Code, they currently using UserAuthorizer for existing flow, what we do is just adding the capability to UserAuthorizer calling token endpoint with basic auth header.

@product-auto-label product-auto-label bot added size: l Pull request size is large. and removed size: m Pull request size is medium. labels Jun 12, 2024
Copy link

sonarcloud bot commented Jun 12, 2024

@lsirac
Copy link
Contributor

lsirac commented Jul 19, 2024

The library methods here return UserCredentials. Is this compatible with BYOID? We have ExternalAccountAuthorizedUserCredentials for BYOID.

ExternalAccountAuthorizedUserCredentials won't need any changes to have the capability of BYOID. Cloud Code can choose this one if they want to switch. Per the sync with Cloud Code, they currently using UserAuthorizer for existing flow, what we do is just adding the capability to UserAuthorizer calling token endpoint with basic auth header.

The method you've updated here returns UserCredentials. This is not compatible with the BYOID flow.

@BigTailWolf
Copy link
Contributor Author

The library methods here return UserCredentials. Is this compatible with BYOID? We have ExternalAccountAuthorizedUserCredentials for BYOID.

ExternalAccountAuthorizedUserCredentials won't need any changes to have the capability of BYOID. Cloud Code can choose this one if they want to switch. Per the sync with Cloud Code, they currently using UserAuthorizer for existing flow, what we do is just adding the capability to UserAuthorizer calling token endpoint with basic auth header.

The method you've updated here returns UserCredentials. This is not compatible with the BYOID flow.

UserCredentials is holding clientId, tokenServerUri, refreshToken, access_token etc. That's all the user wants.
And during the authentication flow. Now with modification. We have the generate auth URL done inside the getCredentialsFromCode with the addtionalParameters properly set. That will give the auth url we need, then we make the http request to sts.googleapis.com/v1/oauthtoken and exchange the access token. The access token is going to be set in the property of UserCredentials. And this will be return to the user agent (Cloud Code in our case).

Maybe this is not the same flow with our existing BYOID flow. I believe the flow is still work as help the UserAuthorizer class able to get the access token with minimum changes.

Comment on lines +110 to +111
PKCEProvider pkce,
ClientAuthenticationType clientAuthentication) {
Copy link
Contributor

@lqiu96 lqiu96 Jul 22, 2024

Choose a reason for hiding this comment

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

Since this is a private constructor, can we update this to take in the Builder class instead of additional parameters?

private UserAuthorizer(Builder builder) {
  // Assign the fields.
}

If we change this constructor to take a Builder, then the javadocs suggestion above doesn't matter.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think so. The Builder's build() method is using this parameter to initialize the value.

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe you can change the builder's build() method so that it pass the builder as the sole parameter to the constructor:

    public UserAuthorizer build() {
      return new UserAuthorizer(this);
    }

I think this would be cleaner because future implementations can add additional params without having to explicitly new fields to the constructor:

  private UserAuthorizer(UserAuthorizer.Builder builder) {
    this.clientId = Preconditions.checkNotNull(builder.clientId);
    this.scopes = ImmutableList.copyOf(Preconditions.checkNotNull(builder.scopes));
    this.callbackUri = (builder.callbackUri == null) ? DEFAULT_CALLBACK_URI : builder.callbackUri;
    this.transportFactory =
        (builder.transportFactory == null) ? OAuth2Utils.HTTP_TRANSPORT_FACTORY : builder.transportFactory;
    this.tokenServerUri = (builder.tokenServerUri == null) ? OAuth2Utils.TOKEN_SERVER_URI : builder.tokenServerUri;
    this.userAuthUri = (builder.userAuthUri == null) ? OAuth2Utils.USER_AUTH_URI : builder.userAuthUri;
    this.tokenStore = (builder.tokenStore == null) ? new MemoryTokensStorage() : builder.tokenStore;
    this.pkce = builder.pkce;
    this.clientAuthenticationType =
        (builder.clientAuthenticationType == null)
            ? ClientAuthenticationType.CLIENT_SECRET_POST
            : builder.clientAuthenticationType;
  }

I think this can be refactored even more, so that the builder sets default values and it doesn't need to be validated in the constructor:

  public static class Builder {

    private ClientId clientId;
    private TokenStore tokenStore = new MemoryTokensStorage();
    private URI callbackUri = DEFAULT_CALLBACK_URI;
    private URI tokenServerUri = OAuth2Utils.TOKEN_SERVER_URI;
    private URI userAuthUri = OAuth2Utils.USER_AUTH_URI;
    private Collection<String> scopes;
    private HttpTransportFactory transportFactory = OAuth2Utils.HTTP_TRANSPORT_FACTORY;
    private PKCEProvider pkce;
    private ClientAuthenticationType clientAuthenticationType = ClientAuthenticationType.CLIENT_SECRET_POST;
...

and the constructor could be something like:

  private UserAuthorizer(UserAuthorizer.Builder builder) {
    this.clientId = Preconditions.checkNotNull(builder.clientId);
    this.scopes = ImmutableList.copyOf(Preconditions.checkNotNull(builder.scopes));
    this.callbackUri = builder.callbackUri;
    this.transportFactory = builder.transportFactory;
    this.tokenServerUri = builder.tokenServerUri;
    this.userAuthUri = builder.userAuthUri;
    this.tokenStore = builder.tokenStore;
    this.pkce = builder.pkce;
    this.clientAuthenticationType = builder.clientAuthenticationType;
  }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The refactor make sense to me. But that's sort of a bigger refactor than this change and irrelevant to the purpose of this change.
Can I do a separate one for that? Maybe create an issue and resolve it through the refactor change.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure, it's a private constructor and we can change implementation later. If you could create an issue in this repo and follow up on it sometime in the coming weeks, that would be great! Thanks!

Copy link

sonarcloud bot commented Jul 22, 2024

@lsirac lsirac closed this Aug 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
size: l Pull request size is large.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants