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

[supply] added support for refresh tokens as an authentication method #16414

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

moly
Copy link

@moly moly commented May 1, 2020

Checklist

  • I've run bundle exec rspec from the root directory to see all new and existing tests pass
  • I've followed the fastlane code style and run bundle exec rubocop -a to ensure the code style is valid
  • I've read the Contribution Guidelines
  • I've updated the documentation if necessary.

Motivation and Context

Resolves issue #16352. In some corporate setups the play store page is not owned/managed internally, which can make it difficult to get access to the service account. Adding support for alternative authentication methods would make fastlane more useful in these situations.

Description

Adds two new parameters to the supply action, json_token and json_token_data. The former allows the user to provide a path to a json file containing the refresh token data, while the latter allows specifying the raw refresh token string directly. If either of these parameters are present, the supply client will use Google::Auth::UserRefreshCredentials.make_creds() to authenticate with the play store api.

Testing Steps

Generate a refresh token using the steps here: https://developers.google.com/android-publisher/authorization#generating_a_refresh_token

Create a json file with the following format:

{
  "grant_type" : "refresh_token",
  "client_id" : <the client ID token created in the APIs Console>,
  "client_secret" : <the client secret corresponding to the client ID>,
  "refresh_token" : <the refresh token from the previous step>
}

Run: bundle exec fastlane supply --json-token /path/to/json

You can also specify the parameter using the SUPPLY_JSON_TOKEN environment variable or by setting json_token_file in your AppFile.

@googlebot
Copy link

Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

📝 Please visit https://cla.developers.google.com/ to sign.

Once you've signed (or fixed any issues), please reply here with @googlebot I signed it! and we'll verify it.


What to do if you already signed the CLA

Individual signers
Corporate signers

ℹ️ Googlers: Go here for more info.

@moly
Copy link
Author

moly commented May 1, 2020

@googlebot I signed it!

@googlebot
Copy link

CLAs look good, thanks!

ℹ️ Googlers: Go here for more info.

@googlebot googlebot added cla: yes and removed cla: no labels May 1, 2020
@janpio janpio added the tool: supply upload_to_playstore label May 2, 2020
@janpio janpio changed the title [supply] added support for refresh tokens as an authentication method (#16352) [supply] added support for refresh tokens as an authentication method May 2, 2020
@janpio
Copy link
Member

janpio commented May 2, 2020

Nice.

Is json_token the right name for this?

@moly
Copy link
Author

moly commented May 2, 2020

I named it json_token to keep in line with the existing json_key parameter. I'm happy to change it if you have any suggestions?

Copy link
Member

@joshdholtz joshdholtz left a comment

Choose a reason for hiding this comment

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

This is super cool! I wasn’t able to get this to work tonight. I got an error of [!] Google Api Error: forbidden: The caller does not have permission - The caller does not have permission. So that is probably on me somehow but I’m not sure exactly what yet. But I did leave some questions and requested some changes 😊

Thanks for all the work on this!

@@ -116,6 +117,35 @@ def self.available_options
UI.user_error!("Could not parse service account json JSON::ParseError")
end
end),
FastlaneCore::ConfigItem.new(key: :json_token,
Copy link
Member

Choose a reason for hiding this comment

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

I think it make sense to call this refresh_token. json_token is a bit to similar to json_key and I think it would cause some confusion 😊

UI.user_error!("Could not find refresh token json file at path '#{File.expand_path(value)}'") unless File.exist?(File.expand_path(value))
UI.user_error!("'#{value}' doesn't seem to be a JSON file") unless FastlaneCore::Helper.json_file?(File.expand_path(value))
end),
FastlaneCore::ConfigItem.new(key: :json_token_data,
Copy link
Member

Choose a reason for hiding this comment

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

And this be refresh_token_data

optional: true,
description: "The path to a file containing refresh token JSON, used to authenticate with Google",
code_gen_sensitive: true,
default_value: CredentialsManager::AppfileConfig.try_fetch_value(:json_token_file),
Copy link
Member

Choose a reason for hiding this comment

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

This should look for same name as the option so refresh_token

optional: true,
description: "The raw refresh token JSON data used to authenticate with Google",
code_gen_sensitive: true,
default_value: CredentialsManager::AppfileConfig.try_fetch_value(:json_token_data_raw),
Copy link
Member

Choose a reason for hiding this comment

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

And this refresh_token_data

@@ -132,6 +132,14 @@ def json_key_data_raw(*args, &block)
setter(:json_key_data_raw, *args, &block)
end

def json_token_file(*args, &block)
Copy link
Member

Choose a reason for hiding this comment

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

refresh_token

setter(:json_token_file, *args, &block)
end

def json_token_data_raw(*args, &block)
Copy link
Member

Choose a reason for hiding this comment

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

refresh_token_data

if params[:json_token] || params[:json_token_data]
auth_client = Google::Auth::UserRefreshCredentials.make_creds(json_key_io: service_account_json, scope: self.class::SCOPE)
else
auth_client = Google::Auth::ServiceAccountCredentials.make_creds(json_key_io: service_account_json, scope: self.class::SCOPE)
Copy link
Member

Choose a reason for hiding this comment

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

This method is now failing for with NoMethodError: [!] undefined method gsub' for nil:NilClass `. Did this break for you?

Copy link
Author

Choose a reason for hiding this comment

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

Good catch, I'd accidentally caused service_account_authentication to return nil when using a json key. Should be fixed now.

@moly
Copy link
Author

moly commented May 12, 2020

Renamed all instances of json_token and json_token_data to refresh_token and refresh_token_data` as requested.

@joshdholtz
Copy link
Member

@moly Thanks for making those changes! Do you have any idea what could be causing my permission issue I mentioned above? 😇 I’m stumped lol

@moly
Copy link
Author

moly commented May 12, 2020

@joshdholtz does your Google account have the right permissions for the android project you're using? It might need to have the "release manager" role or higher. I'll check my setup tomorrow and provide better step by step instructions.

@moly
Copy link
Author

moly commented May 13, 2020

@joshdholtz I set up a new app today and couldn't reproduce your permission issue. Based on the error message, the only things I could think of is that either the google account you're using doesn't have the right permissions for the play store app you're targeting, or you didn't allow access to the api project on the oauth consent screen correctly. Here's the steps I used, my google account is the owner of the play store app.

  1. Go to https://play.google.com/apps/publish/#ApiAccessPlace. Click "Create OAuthClient"
  2. If you haven't before you may need to create an oauth consent screen. You can give it a name but leave everything else as it is and click save.
  3. https://play.google.com/apps/publish/#ApiAccessPlace should now show "Google Play Android Developer" under linked projects and a new client under "OAuth Clients". Under "OAuth Clients" click "view in Google Developers Console", then click "Google Play Android Developer" under "OAuth 2.0 Client IDs". You should be able to see the client ID and client secret.
  4. Go to the following url, replacing YOUR_CLIENT_ID with the client ID from the last step: https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/androidpublisher&response_type=code&access_type=offline&redirect_uri=urn:ietf:wg:oauth:2.0:oob&client_id=YOUR_CLIENT_ID
  5. Click to allow the Google Play Android Developer project permission to manage your android apps.
  6. You should get redirected to a page with an authorisation code. Run the following command:
    curl -s -d "grant_type=authorization_code&code=YOUR_AUTHORISATION_CODE&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&redirect_uri=urn:ietf:wg:oauth:2.0:oob" -X POST https://accounts.google.com/o/oauth2/token
  7. The response should contain your refresh token. Create a json file with the following format:
    { "grant_type" : "refresh_token", "client_id" : "YOUR_CLIENT_ID", "client_secret" : "YOUR_CLIENT_SECRET", "refresh_token" : "YOUR_REFRESH_TOKEN" }
  8. Save the json somewhere in the project directory, then run fastlane supply init --refresh-token path/to/refresh_token.json.

@moly
Copy link
Author

moly commented May 13, 2020

Refactored a little to fix some failing tests

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants