Skip to content

Commit 4c7f570

Browse files
authored
Merge pull request #404 from zendesk/aishkan/secure-settings-scope
[APPS-7700] Add validation for scopes attribute for secure param
2 parents 1ce2320 + cef6f7a commit 4c7f570

File tree

7 files changed

+207
-35
lines changed

7 files changed

+207
-35
lines changed

config/locales/en.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,11 @@ en:
281281
invalid_url: '%{field} must be a valid URL, got "%{value}".'
282282
password_parameter_type_deprecated: 'Password parameter type can no longer
283283
be used. Use Secure settings instead. Learn more: %{link}.'
284+
field_requires_secure_parameter: "%{field} cannot be defined in a non-secure
285+
parameter."
286+
field_cannot_be_empty: "%{field} cannot be empty."
287+
field_contains_invalid_values: "%{field} contains invalid values: %{values}."
288+
field_contains_invalid_keys: "%{field} contains invalid keys: %{keys}."
284289
warning:
285290
app_build:
286291
deprecated_version: You are targeting a deprecated version of the framework.

config/locales/translations/zendesk_apps_support.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,3 +633,19 @@ parts:
633633
key: "txt.apps.admin.error.app_build.password_parameter_type_deprecated"
634634
title: "App builder job: Password parameter type is deprecated"
635635
value: "Password parameter type can no longer be used. Use Secure settings instead. Learn more: %{link}."
636+
- translation:
637+
key: "txt.apps.admin.error.app_build.field_requires_secure_parameter"
638+
title: "App builder job: Error when parameter field requires secure setting set to true. Placeholder %{field} shows parameter field along with parameter name like \"parameter[name='param'].scopes\" found in the supplied manifest file."
639+
value: "%{field} cannot be defined in a non-secure parameter."
640+
- translation:
641+
key: "txt.apps.admin.error.app_build.field_cannot_be_empty"
642+
title: "App builder job: field cannot be empty. %{field} will be replaced with empty fields such as \"name\", \"author\" \"parameter[name='param'].scopes\" etc in the supplied manifest file."
643+
value: "%{field} cannot be empty."
644+
- translation:
645+
key: "txt.apps.admin.error.app_build.field_contains_invalid_values"
646+
title: "App builder job: Error for invalid field values. Placeholder %{field} shows parameter fields like \"parameter[name='param'].scopes\" in the supplied manifest file, %{values} shows the invalid values in the user input corresponding to those fields."
647+
value: "%{field} contains invalid values: %{values}."
648+
- translation:
649+
key: "txt.apps.admin.error.app_build.field_contains_invalid_keys"
650+
title: "App builder job: Error for invalid field keys. Placeholder %{field} shows parameter fields like \"parameter[name='param'].scopes\" provided in the supplied manifest file, %{keys} shows the invalid keys found within the field."
651+
value: "%{field} contains invalid keys: %{keys}."

lib/zendesk_apps_support/manifest/parameter.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module ZendeskAppsSupport
44
class Manifest
55
class Parameter
66
TYPES = %w[text password checkbox url number multiline hidden oauth].freeze
7-
ATTRIBUTES = %i[name type required secure default].freeze
7+
ATTRIBUTES = %i[name type required secure default scopes].freeze
88
attr_reader(*ATTRIBUTES)
99
def default?
1010
@has_default
@@ -17,6 +17,7 @@ def initialize(p)
1717
@secure = p['secure'] || false
1818
@has_default = p.key? 'default'
1919
@default = p['default'] if @has_default
20+
@scopes = p['scopes']
2021
end
2122
end
2223
end

lib/zendesk_apps_support/package.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@ def validate(options = {})
3636
skip_marketplace_translations = options.fetch(:skip_marketplace_translations, false)
3737
error_on_password_parameter = options.fetch(:error_on_password_parameter, false)
3838
validate_custom_objects_v2 = options.fetch(:validate_custom_objects_v2, false)
39+
validate_scopes_for_secure_parameter = options.fetch(:validate_scopes_for_secure_parameter, false)
3940

4041
errors = []
41-
errors << Validations::Manifest.call(self, error_on_password_parameter: error_on_password_parameter)
42+
errors << Validations::Manifest.call(self, error_on_password_parameter: error_on_password_parameter, validate_scopes_for_secure_parameter: validate_scopes_for_secure_parameter)
4243

4344
if has_valid_manifest?(errors)
4445
errors << Validations::Marketplace.call(self) if marketplace

lib/zendesk_apps_support/validations/manifest.rb

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ module Manifest
1111
OAUTH_REQUIRED_FIELDS = %w[client_id client_secret authorize_uri access_token_uri].freeze
1212
PARAMETER_TYPES = ZendeskAppsSupport::Manifest::Parameter::TYPES
1313
OAUTH_MANIFEST_LINK = 'https://developer.zendesk.com/apps/docs/developer-guide/manifest#oauth'
14+
SECURE_PARAM_SCOPES = %w[header body url_host url_path jwt_secret_key jwt_claim basic_auth_username basic_auth_password].freeze
1415

1516
class << self
16-
def call(package, error_on_password_parameter: false)
17+
def call(package, error_on_password_parameter: false, validate_scopes_for_secure_parameter: false)
1718
unless package.has_file?('manifest.json')
1819
nested_manifest = package.files.find { |file| file =~ %r{\A[^/]+?/manifest\.json\Z} }
1920
if nested_manifest
@@ -24,7 +25,7 @@ def call(package, error_on_password_parameter: false)
2425

2526
package.warnings << password_parameter_warning if !error_on_password_parameter && password_param_present?(package.manifest)
2627

27-
collate_manifest_errors(package, error_on_password_parameter)
28+
collate_manifest_errors(package, error_on_password_parameter, validate_scopes_for_secure_parameter)
2829
rescue JSON::ParserError => e
2930
return [ValidationError.new(:manifest_not_json, errors: e)]
3031
rescue ZendeskAppsSupport::Manifest::OverrideError => e
@@ -37,7 +38,7 @@ def password_param_present?(manifest)
3738
manifest.parameters.any? { |p| p.type == 'password' }
3839
end
3940

40-
def collate_manifest_errors(package, error_on_password_parameter)
41+
def collate_manifest_errors(package, error_on_password_parameter, validate_scopes_for_secure_parameter)
4142
manifest = package.manifest
4243

4344
errors = [
@@ -46,7 +47,7 @@ def collate_manifest_errors(package, error_on_password_parameter)
4647
oauth_error(manifest),
4748
default_locale_error(manifest, package),
4849
validate_urls(manifest),
49-
validate_parameters(manifest),
50+
validate_parameters(manifest, validate_scopes_for_secure_parameter),
5051
if manifest.requirements_only? || manifest.marketing_only?
5152
[ ban_location(manifest),
5253
ban_framework_version(manifest) ]
@@ -87,7 +88,7 @@ def validate_urls(manifest)
8788
errors
8889
end
8990

90-
def validate_parameters(manifest)
91+
def validate_parameters(manifest, validate_scopes_for_secure_parameter)
9192
if manifest.marketing_only?
9293
marketing_only_errors(manifest)
9394
else
@@ -97,7 +98,8 @@ def validate_parameters(manifest)
9798
invalid_type_error(manifest),
9899
too_many_oauth_parameters(manifest),
99100
oauth_cannot_be_secure(manifest),
100-
name_as_parameter_name_error(manifest)
101+
name_as_parameter_name_error(manifest),
102+
*(validate_scopes_for_secure_parameter ? invalid_secure_param_scopes_errors(manifest) : invalid_scopes_key_error(manifest)),
101103
]
102104
end
103105
end
@@ -110,6 +112,52 @@ def oauth_cannot_be_secure(manifest)
110112
end
111113
end
112114

115+
def invalid_scopes_key_error(manifest)
116+
errors = []
117+
manifest.parameters.each do |parameter|
118+
next if parameter.scopes.nil?
119+
120+
errors << ValidationError.new(:field_contains_invalid_keys,
121+
field: "parameters[name=\"#{parameter.name}\"]",
122+
keys: 'scopes')
123+
end
124+
errors
125+
end
126+
127+
def invalid_secure_param_scopes_errors(manifest)
128+
errors = []
129+
130+
manifest.parameters.each do |parameter|
131+
next if parameter.scopes.nil?
132+
133+
unless parameter.secure
134+
errors << ValidationError.new(:field_requires_secure_parameter, field: "parameters[name=\"#{parameter.name}\"].scopes")
135+
end
136+
137+
unless parameter.scopes.is_a?(Array)
138+
errors << ValidationError.new(:unacceptable_array,
139+
field: "parameters[name=\"#{parameter.name}\"].scopes",
140+
value: parameter.scopes.class.to_s)
141+
next
142+
end
143+
144+
if parameter.scopes.empty?
145+
errors << ValidationError.new(:field_cannot_be_empty, field: "parameters[name=\"#{parameter.name}\"].scopes")
146+
end
147+
148+
invalid_scopes = parameter.scopes - SECURE_PARAM_SCOPES
149+
if invalid_scopes.any?
150+
errors << ValidationError.new(
151+
:field_contains_invalid_values,
152+
field: "parameters[name=\"#{parameter.name}\"].scopes",
153+
values: invalid_scopes.join(I18n.t('txt.apps.admin.error.app_build.listing_comma'))
154+
)
155+
end
156+
end
157+
158+
errors
159+
end
160+
113161
def marketing_only_errors(manifest)
114162
[
115163
ban_parameters(manifest),
@@ -261,12 +309,12 @@ def parameters_error(manifest)
261309
end
262310

263311
invalid_required = manifest.parameters.map do |parameter|
264-
validate_boolean(parameter.required, "parameters.#{parameter.name}.required")
312+
validate_boolean(parameter.required, "parameters[name=\"#{parameter.name}\"].required")
265313
end.compact
266314
return invalid_required if invalid_required.any?
267315

268316
invalid_secure = manifest.parameters.map do |parameter|
269-
validate_boolean(parameter.secure, "parameters.#{parameter.name}.secure")
317+
validate_boolean(parameter.secure, "parameters[name=\"#{parameter.name}\"].secure")
270318
end.compact
271319
return invalid_secure if invalid_secure.any?
272320
end

spec/package_spec.rb

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -455,18 +455,6 @@ def build_app_source_with_files(files)
455455
end
456456
end
457457

458-
context 'when error_on_password_parameter is true' do
459-
let(:package) { ZendeskAppsSupport::Package.new('spec/fixtures/iframe_only_app') }
460-
461-
before do
462-
allow(ZendeskAppsSupport::Validations::Manifest).to receive(:call)
463-
package.validate!(marketplace: true, error_on_password_parameter: true)
464-
end
465-
it 'validate manifest and passes in the error_on_password_parameter correctly' do
466-
expect(ZendeskAppsSupport::Validations::Manifest).to have_received(:call).with(package, {:error_on_password_parameter => true})
467-
end
468-
end
469-
470458
context 'when validate_custom_objects_v2 is true' do
471459
let(:package) { ZendeskAppsSupport::Package.new('spec/fixtures/iframe_only_app') }
472460

@@ -481,18 +469,25 @@ def build_app_source_with_files(files)
481469
end
482470

483471
context 'when handling validation options' do
472+
let(:package) { ZendeskAppsSupport::Package.new('spec/fixtures/iframe_only_app') }
473+
484474
before do
485475
allow(ZendeskAppsSupport::Validations::Manifest).to receive(:call).and_return([])
486476
allow(ZendeskAppsSupport::Validations::Translations).to receive(:call)
487477
allow(ZendeskAppsSupport::Validations::Requirements).to receive(:call)
488478
end
489479

480+
it 'passes manifest validation options correctly' do
481+
package.validate!(marketplace: true, error_on_password_parameter: true, validate_scopes_for_secure_parameter: true)
482+
expect(ZendeskAppsSupport::Validations::Manifest).to have_received(:call).with(package, {:error_on_password_parameter => true, :validate_scopes_for_secure_parameter => true})
483+
end
484+
490485
it 'uses default values when called with empty options' do
491486
package.validate!
492487

493488
expect(ZendeskAppsSupport::Validations::Manifest).to have_received(:call).with(
494489
package,
495-
{ error_on_password_parameter: false }
490+
{ error_on_password_parameter: false, validate_scopes_for_secure_parameter: false }
496491
)
497492
expect(ZendeskAppsSupport::Validations::Marketplace).to have_received(:call).with(package)
498493
expect(ZendeskAppsSupport::Validations::Translations).to have_received(:call).with(

0 commit comments

Comments
 (0)