fastlane-plugin-firebase_app_distribution 0.3.2 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fc1248f0ff34d8d0f74cafde4a839963d6d38bd73921bd7c5e8b02bd8b3fafeb
4
- data.tar.gz: 7a890cb66dbff3157e3e7a490e5cfd7b994a3b7d6dfbee860afa8adc61038da0
3
+ metadata.gz: 95f39c2a13ac05b24c98fe9a6d1fc86b0b943c2863ef1828389456e6d6755760
4
+ data.tar.gz: b2bcc6f542c283d7bb2b8627dd09d4c1bcf2efb5831b5383a94a062cdc508df6
5
5
  SHA512:
6
- metadata.gz: 2e97c2cc4f08ef9d3930305116c828e7b8fffb6a627958e502b1b9423e1cbe1b9505c06ec49d49d771cca353b371ee2aa4aa0f219f29c0cb90e41a028415f48f
7
- data.tar.gz: dc8b9b8cfdce10575df9bb157c5a464967e7082a18f71c30ddd2e7bbffcfda2f2265c091f1c65e4faf0683888c93f87078fb8e720cd2339d970c06c85c4302b5
6
+ metadata.gz: 4c6cdc3695ceb9c9eb3cc5615e7f6a3cd5ce7a51abfb7f08ef782f82148b506b77993f58ce2b66543d43b0b529959f2bea2a52d3c57db165b4fa4a8bcc7c2db7
7
+ data.tar.gz: 00d23c9c049c7a1058d07e5f2df2eb05eb7a41ee8c7cf15aca07e3e524a99752f679d238eb99054367ebbb86ba95cc3391be007bb23f95817119345511f6e451
@@ -18,6 +18,10 @@ module Fastlane
18
18
  def self.run(params)
19
19
  params.values # to validate all inputs before looking for the ipa/apk/aab
20
20
 
21
+ if params[:debug]
22
+ UI.important("Warning: Debug logging enabled. Output may include sensitive information.")
23
+ end
24
+
21
25
  app_id = app_id_from_params(params)
22
26
  app_name = app_name_from_app_id(app_id)
23
27
  platform = lane_platform || platform_from_app_id(app_id)
@@ -197,12 +201,12 @@ module Fastlane
197
201
  type: String),
198
202
  FastlaneCore::ConfigItem.new(key: :groups,
199
203
  env_name: "FIREBASEAPPDISTRO_GROUPS",
200
- description: "The groups used for distribution, separated by commas",
204
+ description: "The group aliases used for distribution, separated by commas",
201
205
  optional: true,
202
206
  type: String),
203
207
  FastlaneCore::ConfigItem.new(key: :groups_file,
204
208
  env_name: "FIREBASEAPPDISTRO_GROUPS_FILE",
205
- description: "The groups used for distribution, separated by commas",
209
+ description: "The group aliases used for distribution, separated by commas",
206
210
  optional: true,
207
211
  type: String),
208
212
  FastlaneCore::ConfigItem.new(key: :testers,
@@ -1,11 +1,9 @@
1
1
  require 'googleauth'
2
- require 'googleauth/stores/file_token_store'
3
2
  require "fileutils"
4
3
 
5
4
  module Fastlane
6
5
  module Actions
7
6
  class FirebaseAppDistributionLoginAction < Action
8
- OOB_URI = "urn:ietf:wg:oauth:2.0:oob"
9
7
  SCOPE = "https://www.googleapis.com/auth/cloud-platform"
10
8
 
11
9
  # In this type of application, the client secret is not treated as a secret.
@@ -14,24 +12,65 @@ module Fastlane
14
12
  CLIENT_SECRET = "j9iVZfS8kkCEFUPaAeJV0sAi"
15
13
 
16
14
  def self.run(params)
15
+ callback_uri = "http://localhost:#{params[:port]}"
17
16
  client_id = Google::Auth::ClientId.new(CLIENT_ID, CLIENT_SECRET)
18
- authorizer = Google::Auth::UserAuthorizer.new(client_id, SCOPE, nil)
19
- url = authorizer.get_authorization_url(base_url: OOB_URI)
17
+ authorizer = Google::Auth::UserAuthorizer.new(client_id, SCOPE, nil, callback_uri)
18
+
19
+ # Create an anti-forgery state token as described here:
20
+ # https://developers.google.com/identity/protocols/OpenIDConnect#createxsrftoken
21
+ state = SecureRandom.hex(16)
22
+ url = authorizer.get_authorization_url(state: state)
20
23
 
21
24
  UI.message("Open the following address in your browser and sign in with your Google account:")
22
25
  UI.message(url)
23
- UI.message("")
24
- code = UI.input("Enter the resulting code here: ")
25
- credentials = authorizer.get_credentials_from_code(code: code, base_url: OOB_URI)
26
- UI.message("")
27
26
 
27
+ response_params = get_authorization_code(params[:port])
28
+
29
+ # Confirm that the state in the response matches the state token used to
30
+ # generate the authorization URL.
31
+ unless state == response_params['state'][0]
32
+ UI.crash!('An error has occurred. The state parameter in the authorization response does not match the expected state, which could mean that a malicious attacker is trying to make a login request.')
33
+ end
34
+
35
+ user_credentials = authorizer.get_credentials_from_code(
36
+ code: response_params['code'][0]
37
+ )
28
38
  UI.success("Set the refresh token as the FIREBASE_TOKEN environment variable")
29
- UI.success("Refresh Token: #{credentials.refresh_token}")
30
- rescue Signet::AuthorizationError
31
- UI.error("The code you entered is invalid. Copy and paste the code and try again.")
39
+ UI.success("Refresh Token: #{user_credentials.refresh_token}")
32
40
  rescue => error
33
41
  UI.error(error.to_s)
34
- UI.crash!("An error has occured, please login again.")
42
+ UI.crash!("An error has occurred, please login again.")
43
+ end
44
+
45
+ def self.get_authorization_code(port)
46
+ begin
47
+ server = TCPServer.open(port)
48
+ rescue Errno::EADDRINUSE => error
49
+ UI.error(error.to_s)
50
+ UI.crash!("Port #{port} is in use. Please specify a different one using the port parameter.")
51
+ end
52
+ client = server.accept
53
+ callback_request = client.readline
54
+ # Use a regular expression to extract the request line from the first line of
55
+ # the callback request, e.g.:
56
+ # GET /?code=AUTH_CODE&state=XYZ&scope=... HTTP/1.1
57
+ matcher = /GET +([^ ]+)/.match(callback_request)
58
+ response_params = CGI.parse(URI.parse(matcher[1]).query) unless matcher.nil?
59
+
60
+ client.puts("HTTP/1.1 200 OK")
61
+ client.puts("Content-Type: text/html")
62
+ client.puts("")
63
+ client.puts("<b>")
64
+ if response_params['code'].nil?
65
+ client.puts("Failed to retrieve authorization code.")
66
+ else
67
+ client.puts("Authorization code was successfully retrieved.")
68
+ end
69
+ client.puts("</b>")
70
+ client.puts("<p>Please check the console output.</p>")
71
+ client.close
72
+
73
+ return response_params
35
74
  end
36
75
 
37
76
  #####################################################
@@ -55,6 +94,18 @@ module Fastlane
55
94
  def self.is_supported?(platform)
56
95
  [:ios, :android].include?(platform)
57
96
  end
97
+
98
+ def self.available_options
99
+ [
100
+ FastlaneCore::ConfigItem.new(key: :port,
101
+ env_name: "FIREBASEAPPDISTRO_LOGIN_PORT",
102
+ description: "Port for the local web server which receives the response from Google's authorization server",
103
+ default_value: "8081",
104
+ optional: true,
105
+ type: String)
106
+
107
+ ]
108
+ end
58
109
  end
59
110
  end
60
111
  end
@@ -46,7 +46,7 @@ module Fastlane
46
46
  request.headers[CONTENT_TYPE] = APPLICATION_JSON
47
47
  end
48
48
  rescue Faraday::ClientError
49
- UI.user_error!("#{ErrorMessage::INVALID_TESTERS} \nEmails: #{emails} \nGroups: #{group_aliases}")
49
+ UI.user_error!("#{ErrorMessage::INVALID_TESTERS} \nEmails: #{emails} \nGroup Aliases: #{group_aliases}")
50
50
  end
51
51
  UI.success("✅ Added testers/groups.")
52
52
  end
@@ -4,6 +4,8 @@ module Fastlane
4
4
  module Auth
5
5
  module FirebaseAppDistributionAuthClient
6
6
  TOKEN_CREDENTIAL_URI = "https://oauth2.googleapis.com/token"
7
+ REDACTION_EXPOSED_LENGTH = 5
8
+ REDACTION_CHARACTER = "X"
7
9
 
8
10
  # Returns the auth token for any of the auth methods (Firebase CLI token,
9
11
  # Google service account, firebase-tools). To ensure that a specific
@@ -73,8 +75,14 @@ module Fastlane
73
75
  client.fetch_access_token!
74
76
  client.access_token
75
77
  rescue Signet::AuthorizationError => error
76
- log_authorization_error_details(error) if debug
77
- UI.user_error!(ErrorMessage::REFRESH_TOKEN_ERROR)
78
+ error_message = ErrorMessage::REFRESH_TOKEN_ERROR
79
+ if debug
80
+ error_message += "\nRefresh token used: #{format_token(refresh_token)}\n"
81
+ error_message += error_details(error)
82
+ else
83
+ error_message += " #{debug_instructions}"
84
+ end
85
+ UI.user_error!(error_message)
78
86
  end
79
87
 
80
88
  def service_account(google_service_path, debug)
@@ -86,14 +94,32 @@ module Fastlane
86
94
  rescue Errno::ENOENT
87
95
  UI.user_error!("#{ErrorMessage::SERVICE_CREDENTIALS_NOT_FOUND}: #{google_service_path}")
88
96
  rescue Signet::AuthorizationError => error
89
- log_authorization_error_details(error) if debug
90
- UI.user_error!("#{ErrorMessage::SERVICE_CREDENTIALS_ERROR}: #{google_service_path}")
97
+ error_message = "#{ErrorMessage::SERVICE_CREDENTIALS_ERROR}: \"#{google_service_path}\""
98
+ if debug
99
+ error_message += "\n#{error_details(error)}"
100
+ else
101
+ error_message += ". #{debug_instructions}"
102
+ end
103
+ UI.user_error!(error_message)
104
+ end
105
+
106
+ def error_details(error)
107
+ "#{error.message}\nResponse status: #{error.response.status}"
108
+ end
109
+
110
+ def debug_instructions
111
+ "For more information, try again with firebase_app_distribution's \"debug\" parameter set to \"true\"."
91
112
  end
92
113
 
93
- def log_authorization_error_details(error)
94
- UI.error("Error fetching access token:")
95
- UI.error(error.message)
96
- UI.error("Response status: #{error.response.status}")
114
+ # Formats and redacts a token for printing out during debug logging. Examples:
115
+ # 'abcd' -> '"abcd"''
116
+ # 'abcdef1234' -> '"XXXXXf1234" (redacted)'
117
+ def format_token(str)
118
+ redaction_notice = str.length > REDACTION_EXPOSED_LENGTH ? " (redacted)" : ""
119
+ exposed_start_char = [str.length - REDACTION_EXPOSED_LENGTH, 0].max
120
+ exposed_characters = str[exposed_start_char, REDACTION_EXPOSED_LENGTH]
121
+ redacted_characters = REDACTION_CHARACTER * [str.length - REDACTION_EXPOSED_LENGTH, 0].max
122
+ "\"#{redacted_characters}#{exposed_characters}\"#{redaction_notice}"
97
123
  end
98
124
  end
99
125
  end
@@ -11,9 +11,9 @@ module ErrorMessage
11
11
  INVALID_APP_ID = "App Distribution could not find your app. Make sure to onboard your app by pressing the \"Get started\" button on the App Distribution page in the Firebase console: https://console.firebase.google.com/project/_/appdistribution. App ID"
12
12
  INVALID_PROJECT = "App Distribution could not find your Firebase project. Make sure to onboard an app in your project by pressing the \"Get started\" button on the App Distribution page in the Firebase console: https://console.firebase.google.com/project/_/appdistribution."
13
13
  INVALID_PATH = "Could not read content from"
14
- INVALID_TESTERS = "Could not enable access for testers. Check that the groups exist and the tester emails are formatted correctly"
14
+ INVALID_TESTERS = "Could not enable access for testers. Check that the tester emails are formatted correctly, the groups exist and you are using group aliases (not group names) for specifying groups."
15
15
  INVALID_RELEASE_NOTES = "Failed to add release notes"
16
- SERVICE_CREDENTIALS_ERROR = "App Distribution could not generate credentials from the service credentials file specified. Service Account Path"
16
+ SERVICE_CREDENTIALS_ERROR = "App Distribution could not generate credentials from the service credentials file specified"
17
17
  PLAY_ACCOUNT_NOT_LINKED = "This project is not linked to a Google Play account."
18
18
  APP_NOT_PUBLISHED = "This app is not published in the Google Play console."
19
19
  NO_APP_WITH_GIVEN_BUNDLE_ID_IN_PLAY_ACCOUNT = "App with matching package name does not exist in Google Play."
@@ -24,13 +24,12 @@ module Fastlane
24
24
  value
25
25
  end
26
26
 
27
- # Returns the array representation of a string with comma seperated values.
28
- #
29
- # Does not work with strings whose individual values have spaces. EX "Hello World" the space will be removed to "HelloWorld"
27
+ # Returns the array representation of a string with trimmed comma
28
+ # seperated values.
30
29
  def string_to_array(string)
31
30
  return nil if string.nil? || string.empty?
32
- string_array = string.gsub(/\s+/, '').split(",")
33
- return string_array
31
+ # Strip string and then strip individual values
32
+ string.strip.split(",").map(&:strip)
34
33
  end
35
34
 
36
35
  def parse_plist(path)
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module FirebaseAppDistribution
3
- VERSION = "0.3.2"
3
+ VERSION = "0.3.4"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane-plugin-firebase_app_distribution
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Natchev
8
8
  - Manny Jimenez
9
9
  - Alonso Salas Infante
10
- autorequire:
10
+ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2021-12-15 00:00:00.000000000 Z
13
+ date: 2022-04-14 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: pry
@@ -138,7 +138,7 @@ dependencies:
138
138
  - - ">="
139
139
  - !ruby/object:Gem::Version
140
140
  version: 2.127.1
141
- description:
141
+ description:
142
142
  email:
143
143
  - snatchev@google.com
144
144
  - mannyjimenez@google.com
@@ -168,7 +168,7 @@ homepage: https://github.com/fastlane/fastlane-plugin-firebase_app_distribution
168
168
  licenses:
169
169
  - MIT
170
170
  metadata: {}
171
- post_install_message:
171
+ post_install_message:
172
172
  rdoc_options: []
173
173
  require_paths:
174
174
  - lib
@@ -183,8 +183,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
183
183
  - !ruby/object:Gem::Version
184
184
  version: '0'
185
185
  requirements: []
186
- rubygems_version: 3.2.32
187
- signing_key:
186
+ rubygems_version: 3.1.4
187
+ signing_key:
188
188
  specification_version: 4
189
189
  summary: Release your beta builds to Firebase App Distribution. https://firebase.google.com/docs/app-distribution
190
190
  test_files: []