Skip to content

Commit 089e9e1

Browse files
committed
Add validation for scopes attribute for secure param
1 parent cd649b1 commit 089e9e1

File tree

5 files changed

+220
-20
lines changed

5 files changed

+220
-20
lines changed

config/locales/en.yml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -238,10 +238,12 @@ en:
238238
invalid_url: '%{field} must be a valid URL, got "%{value}".'
239239
password_parameter_type_deprecated: 'Password parameter type can no longer
240240
be used. Use Secure settings instead. Learn more: %{link}.'
241-
scope_requires_secure_parameter: Scopes can only be defined on secure
242-
parameters.
243-
scopes_cannot_be_empty: Scopes cannot be empty when defined.
244-
invalid_secure_parameter_scopes: 'Invalid secure parameter scopes: %{invalid_scope}.'
241+
scopes_require_secure_parameter: Scopes can only be defined on secure
242+
parameters for %{field}.
243+
field_cannot_be_empty: "%{field} cannot be empty."
244+
field_contains_invalid_values: "%{field} contains invalid values: %{values}."
245+
field_contains_invalid_attributes: "%{field} contains invalid attributes:
246+
%{attributes}."
245247
warning:
246248
app_build:
247249
deprecated_version: You are targeting a deprecated version of the framework.

config/locales/translations/zendesk_apps_support.yml

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -628,14 +628,18 @@ parts:
628628
title: "App builder job: Password parameter type is deprecated"
629629
value: "Password parameter type can no longer be used. Use Secure settings instead. Learn more: %{link}."
630630
- translation:
631-
key: "txt.apps.admin.error.app_build.scope_requires_secure_parameter"
631+
key: "txt.apps.admin.error.app_build.scopes_require_secure_parameter"
632632
title: "App builder job: scopes can be defined only on secure parameters"
633-
value: "Scopes can only be defined on secure parameters."
633+
value: "Scopes can only be defined on secure parameters for %{field}."
634634
- translation:
635-
key: "txt.apps.admin.error.app_build.scopes_cannot_be_empty"
636-
title: "App builder job: scopes cannot be empty when defined"
637-
value: "Scopes cannot be empty when defined."
635+
key: "txt.apps.admin.error.app_build.field_cannot_be_empty"
636+
title: "App builder job: field cannot be empty"
637+
value: "%{field} cannot be empty."
638638
- translation:
639-
key: "txt.apps.admin.error.app_build.invalid_secure_parameter_scopes"
640-
title: "App builder job: invalid secure parameter scopes"
641-
value: "Invalid secure parameter scopes: %{invalid_scope}."
639+
key: "txt.apps.admin.error.app_build.field_contains_invalid_values"
640+
title: "App builder job: invalid field values"
641+
value: "%{field} contains invalid values: %{values}."
642+
- translation:
643+
key: "txt.apps.admin.error.app_build.field_contains_invalid_attributes"
644+
title: "App builder job: invalid field attributes"
645+
value: "%{field} contains invalid attributes: %{attributes}."

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/validations/manifest.rb

Lines changed: 51 additions & 6 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_scope_attribute_error(manifest)),
101103
]
102104
end
103105
end
@@ -110,6 +112,49 @@ def oauth_cannot_be_secure(manifest)
110112
end
111113
end
112114

115+
def invalid_scope_attribute_error(manifest)
116+
errors = []
117+
manifest.parameters.each do |parameter|
118+
next if parameter.scopes.nil?
119+
120+
errors << ValidationError.new(:field_contains_invalid_attributes,
121+
field: "parameters.[name=#{parameter.name}]",
122+
attributes: ['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(:scopes_require_secure_parameter, field: "parameters.[name=#{parameter.name}]")
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(:field_contains_invalid_values,
151+
field: "parameters.[name=#{parameter.name}]", values: invalid_scopes)
152+
end
153+
end
154+
155+
errors
156+
end
157+
113158
def marketing_only_errors(manifest)
114159
[
115160
ban_parameters(manifest),

spec/validations/manifest_spec.rb

Lines changed: 149 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def create_package(parameter_hash)
2424

2525
RSpec::Matchers.define :have_error do |error|
2626
match do |package|
27-
errors = ZendeskAppsSupport::Validations::Manifest.call(package)
27+
errors = ZendeskAppsSupport::Validations::Manifest.call(package, validate_scopes_for_secure_parameter: true)
2828
errors.map!(&:to_s) unless error.is_a? Symbol
2929
@actual = errors.compact
3030

@@ -864,4 +864,152 @@ def create_package(parameter_hash)
864864
end
865865
end
866866
end
867+
868+
context 'scope parameter validations' do
869+
context 'when validate_scopes_for_secure_parameter is false' do
870+
it 'should not validate scopes even if parameter has invalid scopes' do
871+
@manifest_hash = {
872+
'parameters' => [
873+
{
874+
'name' => 'api_token',
875+
'type' => 'text',
876+
'secure' => false,
877+
'scopes' => ['header']
878+
}
879+
]
880+
}
881+
package = create_package(@manifest_hash)
882+
errors = ZendeskAppsSupport::Validations::Manifest.call(package, validate_scopes_for_secure_parameter: false)
883+
expect(errors.find { |e| e.key == :scopes_require_secure_parameter }).to be_nil
884+
end
885+
886+
it 'should have an invalid_attributes error when scope attribute is present' do
887+
@manifest_hash = {
888+
'parameters' => [
889+
{
890+
'name' => 'api_token',
891+
'type' => 'text',
892+
'secure' => true,
893+
'scopes' => ['header']
894+
}
895+
]
896+
}
897+
package = create_package(@manifest_hash)
898+
errors = ZendeskAppsSupport::Validations::Manifest.call(package, validate_scopes_for_secure_parameter: false)
899+
expect(errors.map(&:to_s)).to include(/parameters\.\[name=api_token\] contains invalid attributes: \["scopes"\]/)
900+
end
901+
end
902+
903+
context 'when validate_scopes_for_secure_parameter is true' do
904+
context 'when a parameter has scopes but is not secure' do
905+
it 'should have an error requiring secure parameter for scopes' do
906+
@manifest_hash = {
907+
'parameters' => [
908+
{
909+
'name' => 'api_token',
910+
'type' => 'text',
911+
'secure' => false,
912+
'scopes' => ['header']
913+
}
914+
]
915+
}
916+
package = create_package(@manifest_hash)
917+
errors = ZendeskAppsSupport::Validations::Manifest.call(package, validate_scopes_for_secure_parameter: true)
918+
expect(errors.find { |e| e.key == :scopes_require_secure_parameter }).not_to be_nil
919+
end
920+
end
921+
end
922+
923+
context 'when a parameter has scopes but is not secure' do
924+
it 'should have an error requiring secure parameter for scopes' do
925+
@manifest_hash = {
926+
'parameters' => [
927+
{
928+
'name' => 'api_token',
929+
'type' => 'text',
930+
'secure' => false,
931+
'scopes' => ['header']
932+
}
933+
]
934+
}
935+
expect(create_package(@manifest_hash)).to have_error(:scopes_require_secure_parameter)
936+
end
937+
end
938+
939+
context 'when a parameter has scopes and is secure' do
940+
it 'should not have an error for valid scope values' do
941+
@manifest_hash = {
942+
'parameters' => [
943+
{
944+
'name' => 'api_token',
945+
'type' => 'text',
946+
'secure' => true,
947+
'scopes' => ['header', 'body']
948+
}
949+
]
950+
}
951+
expect(create_package(@manifest_hash)).not_to have_error(:scopes_require_secure_parameter)
952+
expect(create_package(@manifest_hash)).not_to have_error(:invalid_secure_parameter_scopes)
953+
end
954+
955+
it 'should have an error for invalid scope values with all invalid scopes in the error' do
956+
@manifest_hash = {
957+
'parameters' => [
958+
{
959+
'name' => 'api_token',
960+
'type' => 'text',
961+
'secure' => true,
962+
'scopes' => ['invalid_scope1', 'header', 'invalid_scope2', 'body', 'invalid_scope3']
963+
}
964+
]
965+
}
966+
expect(create_package(@manifest_hash)).to have_error(/parameters\.\[name=api_token\] contains invalid values: \["invalid_scope1", "invalid_scope2", "invalid_scope3"\]/)
967+
end
968+
969+
it 'should have an error when scopes are empty' do
970+
@manifest_hash = {
971+
'parameters' => [
972+
{
973+
'name' => 'api_token',
974+
'type' => 'text',
975+
'secure' => true,
976+
'scopes' => []
977+
}
978+
]
979+
}
980+
expect(create_package(@manifest_hash)).to have_error(/parameters\.\[name=api_token\]\.scopes cannot be empty/)
981+
end
982+
983+
it 'should have an error when scopes is not an array' do
984+
@manifest_hash = {
985+
'parameters' => [
986+
{
987+
'name' => 'api_token',
988+
'type' => 'text',
989+
'secure' => true,
990+
'scopes' => 'header'
991+
}
992+
]
993+
}
994+
expect(create_package(@manifest_hash)).to have_error(:unacceptable_array)
995+
end
996+
997+
end
998+
999+
context 'when a parameter has no scopes' do
1000+
it 'should not have any scope-related errors' do
1001+
@manifest_hash = {
1002+
'parameters' => [
1003+
{
1004+
'name' => 'api_token',
1005+
'type' => 'text',
1006+
'secure' => true
1007+
}
1008+
]
1009+
}
1010+
expect(create_package(@manifest_hash)).not_to have_error(:scopes_require_secure_parameter)
1011+
expect(create_package(@manifest_hash)).not_to have_error(:invalid_secure_parameter_scopes)
1012+
end
1013+
end
1014+
end
8671015
end

0 commit comments

Comments
 (0)