zendesk_apps_support 4.29.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +176 -0
- data/README.md +39 -0
- data/config/locales/en.yml +163 -0
- data/config/locales/translations/zendesk_apps_support.yml +405 -0
- data/lib/zendesk_apps_support.rb +42 -0
- data/lib/zendesk_apps_support/app_file.rb +44 -0
- data/lib/zendesk_apps_support/app_requirement.rb +11 -0
- data/lib/zendesk_apps_support/app_version.rb +78 -0
- data/lib/zendesk_apps_support/assets/default_app_logo.svg +10 -0
- data/lib/zendesk_apps_support/assets/default_styles.scss +3 -0
- data/lib/zendesk_apps_support/assets/default_template.html.erb +10 -0
- data/lib/zendesk_apps_support/assets/installed.js.erb +21 -0
- data/lib/zendesk_apps_support/assets/src.js.erb +37 -0
- data/lib/zendesk_apps_support/build_translation.rb +51 -0
- data/lib/zendesk_apps_support/engine.rb +11 -0
- data/lib/zendesk_apps_support/finders.rb +36 -0
- data/lib/zendesk_apps_support/i18n.rb +31 -0
- data/lib/zendesk_apps_support/installation.rb +22 -0
- data/lib/zendesk_apps_support/installed.rb +27 -0
- data/lib/zendesk_apps_support/location.rb +71 -0
- data/lib/zendesk_apps_support/manifest.rb +197 -0
- data/lib/zendesk_apps_support/manifest/location_options.rb +35 -0
- data/lib/zendesk_apps_support/manifest/no_override_hash.rb +50 -0
- data/lib/zendesk_apps_support/manifest/parameter.rb +23 -0
- data/lib/zendesk_apps_support/package.rb +402 -0
- data/lib/zendesk_apps_support/product.rb +31 -0
- data/lib/zendesk_apps_support/sass_functions.rb +43 -0
- data/lib/zendesk_apps_support/stylesheet_compiler.rb +38 -0
- data/lib/zendesk_apps_support/validations/banner.rb +35 -0
- data/lib/zendesk_apps_support/validations/manifest.rb +412 -0
- data/lib/zendesk_apps_support/validations/marketplace.rb +31 -0
- data/lib/zendesk_apps_support/validations/mime.rb +41 -0
- data/lib/zendesk_apps_support/validations/requests.rb +47 -0
- data/lib/zendesk_apps_support/validations/requirements.rb +154 -0
- data/lib/zendesk_apps_support/validations/secrets.rb +77 -0
- data/lib/zendesk_apps_support/validations/secure_settings.rb +37 -0
- data/lib/zendesk_apps_support/validations/source.rb +25 -0
- data/lib/zendesk_apps_support/validations/stylesheets.rb +28 -0
- data/lib/zendesk_apps_support/validations/svg.rb +81 -0
- data/lib/zendesk_apps_support/validations/templates.rb +20 -0
- data/lib/zendesk_apps_support/validations/translations.rb +160 -0
- data/lib/zendesk_apps_support/validations/validation_error.rb +77 -0
- metadata +327 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ZendeskAppsSupport
|
4
|
+
module Validations
|
5
|
+
module Marketplace
|
6
|
+
WHITELISTED_EXPERIMENTS = %w[hashParams newCssCompiler].freeze
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def call(package)
|
10
|
+
[no_symlinks(package.root), *no_experiments(package.manifest)].compact
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def no_symlinks(path)
|
16
|
+
if Dir["#{path}/**/{*,.*}"].any? { |f| File.symlink?(f) }
|
17
|
+
return ValidationError.new(:symlink_in_zip)
|
18
|
+
end
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def no_experiments(manifest)
|
23
|
+
invalid_experiments = manifest.enabled_experiments - WHITELISTED_EXPERIMENTS
|
24
|
+
invalid_experiments.map do |experiment|
|
25
|
+
ValidationError.new(:invalid_experiment, experiment: experiment)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mimemagic'
|
4
|
+
|
5
|
+
module ZendeskAppsSupport
|
6
|
+
module Validations
|
7
|
+
module Mime
|
8
|
+
UNSUPPORTED_MIME_TYPES = %w[
|
9
|
+
vnd.rar rar zip gzip pdf doc docx avi bin bz bz2 csh sh jar mp3 mpeg odt pptx ppt xls xlsx 7z
|
10
|
+
].freeze
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def call(package)
|
14
|
+
unsupported_files =
|
15
|
+
package.files.find_all { |app_file| block_listed?(app_file) }.map(&:relative_path)
|
16
|
+
|
17
|
+
[mime_type_warning(unsupported_files)] if unsupported_files.any?
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def block_listed?(app_file)
|
23
|
+
mime_type = MimeMagic.by_magic(app_file.read)
|
24
|
+
|
25
|
+
content_subtype = mime_type.subtype if mime_type
|
26
|
+
extension_name = app_file.extension.delete('.')
|
27
|
+
|
28
|
+
([content_subtype, extension_name] & UNSUPPORTED_MIME_TYPES).any?
|
29
|
+
end
|
30
|
+
|
31
|
+
def mime_type_warning(file_names)
|
32
|
+
ValidationError.new(
|
33
|
+
:unsupported_mime_type_detected,
|
34
|
+
file_names: file_names.join(', '),
|
35
|
+
count: file_names.count
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ipaddress_2'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module ZendeskAppsSupport
|
7
|
+
module Validations
|
8
|
+
module Requests
|
9
|
+
class << self
|
10
|
+
def call(package)
|
11
|
+
files = package.js_files + package.html_files
|
12
|
+
|
13
|
+
files.each do |file|
|
14
|
+
file_content = file.read
|
15
|
+
|
16
|
+
http_protocol_urls = find_address_containing_http(file_content)
|
17
|
+
next unless http_protocol_urls.any?
|
18
|
+
package.warnings << insecure_http_requests_warning(
|
19
|
+
http_protocol_urls,
|
20
|
+
file.relative_path
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
package.warnings.flatten!
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def insecure_http_requests_warning(http_protocol_urls, relative_path)
|
30
|
+
http_protocol_urls = http_protocol_urls.join(
|
31
|
+
I18n.t('txt.apps.admin.error.app_build.listing_comma')
|
32
|
+
)
|
33
|
+
|
34
|
+
I18n.t(
|
35
|
+
'txt.apps.admin.warning.app_build.insecure_http_request',
|
36
|
+
uri: http_protocol_urls,
|
37
|
+
file: relative_path
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
def find_address_containing_http(file_content)
|
42
|
+
file_content.scan(URI.regexp(['http'])).map(&:compact).map(&:last)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ZendeskAppsSupport
|
4
|
+
module Validations
|
5
|
+
module Requirements
|
6
|
+
MAX_REQUIREMENTS = 5000
|
7
|
+
MAX_CUSTOM_OBJECTS_REQUIREMENTS = 50
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def call(package)
|
11
|
+
if package.manifest.requirements_only? && !package.has_requirements?
|
12
|
+
return [ValidationError.new(:missing_requirements)]
|
13
|
+
elsif !supports_requirements(package) && package.has_requirements?
|
14
|
+
return [ValidationError.new(:requirements_not_supported)]
|
15
|
+
elsif !package.has_requirements?
|
16
|
+
return []
|
17
|
+
end
|
18
|
+
|
19
|
+
begin
|
20
|
+
requirements = package.requirements_json
|
21
|
+
rescue ZendeskAppsSupport::Manifest::OverrideError => e
|
22
|
+
return [ValidationError.new(:duplicate_requirements, duplicate_keys: e.key, count: 1)]
|
23
|
+
end
|
24
|
+
|
25
|
+
[].tap do |errors|
|
26
|
+
errors << invalid_requirements_types(requirements)
|
27
|
+
errors << excessive_requirements(requirements)
|
28
|
+
errors << excessive_custom_objects_requirements(requirements)
|
29
|
+
errors << invalid_channel_integrations(requirements)
|
30
|
+
errors << invalid_custom_fields(requirements)
|
31
|
+
errors << invalid_custom_objects(requirements)
|
32
|
+
errors << missing_required_fields(requirements)
|
33
|
+
errors.flatten!
|
34
|
+
errors.compact!
|
35
|
+
end
|
36
|
+
rescue JSON::ParserError => e
|
37
|
+
return [ValidationError.new(:requirements_not_json, errors: e)]
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def supports_requirements(package)
|
43
|
+
!package.manifest.marketing_only? && package.manifest.products_ignore_locations != [Product::CHAT]
|
44
|
+
end
|
45
|
+
|
46
|
+
def missing_required_fields(requirements)
|
47
|
+
[].tap do |errors|
|
48
|
+
requirements.each do |requirement_type, requirement|
|
49
|
+
next if %w[channel_integrations custom_objects].include? requirement_type
|
50
|
+
requirement.each do |identifier, fields|
|
51
|
+
next if fields.nil? || fields.include?('title')
|
52
|
+
errors << ValidationError.new(:missing_required_fields,
|
53
|
+
field: 'title',
|
54
|
+
identifier: identifier)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def excessive_requirements(requirements)
|
61
|
+
count = requirements.values.map do |req|
|
62
|
+
req.is_a?(Hash) ? req.values : req
|
63
|
+
end.flatten.size
|
64
|
+
ValidationError.new(:excessive_requirements, max: MAX_REQUIREMENTS, count: count) if count > MAX_REQUIREMENTS
|
65
|
+
end
|
66
|
+
|
67
|
+
def excessive_custom_objects_requirements(requirements)
|
68
|
+
custom_objects = requirements[AppRequirement::CUSTOM_OBJECTS_KEY]
|
69
|
+
return unless custom_objects
|
70
|
+
|
71
|
+
count = custom_objects.values.flatten.size
|
72
|
+
if count > MAX_CUSTOM_OBJECTS_REQUIREMENTS
|
73
|
+
ValidationError.new(:excessive_custom_objects_requirements, max: MAX_CUSTOM_OBJECTS_REQUIREMENTS,
|
74
|
+
count: count)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def invalid_custom_fields(requirements)
|
79
|
+
user_fields = requirements['user_fields']
|
80
|
+
organization_fields = requirements['organization_fields']
|
81
|
+
return if user_fields.nil? && organization_fields.nil?
|
82
|
+
[].tap do |errors|
|
83
|
+
[user_fields, organization_fields].compact.each do |field_group|
|
84
|
+
field_group.each do |identifier, fields|
|
85
|
+
next if fields.include? 'key'
|
86
|
+
errors << ValidationError.new(:missing_required_fields,
|
87
|
+
field: 'key',
|
88
|
+
identifier: identifier)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def invalid_channel_integrations(requirements)
|
95
|
+
channel_integrations = requirements['channel_integrations']
|
96
|
+
return unless channel_integrations
|
97
|
+
[].tap do |errors|
|
98
|
+
if channel_integrations.size > 1
|
99
|
+
errors << ValidationError.new(:multiple_channel_integrations)
|
100
|
+
end
|
101
|
+
channel_integrations.each do |identifier, fields|
|
102
|
+
next if fields.include? 'manifest_url'
|
103
|
+
errors << ValidationError.new(:missing_required_fields,
|
104
|
+
field: 'manifest_url',
|
105
|
+
identifier: identifier)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def invalid_custom_objects(requirements)
|
111
|
+
custom_objects = requirements[AppRequirement::CUSTOM_OBJECTS_KEY]
|
112
|
+
return if custom_objects.nil?
|
113
|
+
|
114
|
+
[].tap do |errors|
|
115
|
+
unless custom_objects.key?(AppRequirement::CUSTOM_OBJECTS_TYPE_KEY)
|
116
|
+
errors << ValidationError.new(:missing_required_fields,
|
117
|
+
field: AppRequirement::CUSTOM_OBJECTS_TYPE_KEY,
|
118
|
+
identifier: AppRequirement::CUSTOM_OBJECTS_KEY)
|
119
|
+
end
|
120
|
+
|
121
|
+
valid_schema = {
|
122
|
+
AppRequirement::CUSTOM_OBJECTS_TYPE_KEY => %w[key schema],
|
123
|
+
AppRequirement::CUSTOM_OBJECTS_RELATIONSHIP_TYPE_KEY => %w[key source target]
|
124
|
+
}
|
125
|
+
|
126
|
+
valid_schema.keys.each do |requirement_type|
|
127
|
+
(custom_objects[requirement_type] || []).each do |requirement|
|
128
|
+
validate_custom_objects_keys(requirement.keys, valid_schema[requirement_type], requirement_type, errors)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def invalid_requirements_types(requirements)
|
135
|
+
invalid_types = requirements.keys - ZendeskAppsSupport::AppRequirement::TYPES
|
136
|
+
unless invalid_types.empty?
|
137
|
+
ValidationError.new(:invalid_requirements_types,
|
138
|
+
invalid_types: invalid_types.join(', '),
|
139
|
+
count: invalid_types.length)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def validate_custom_objects_keys(keys, expected_keys, identifier, errors = [])
|
144
|
+
missing_keys = expected_keys - keys
|
145
|
+
missing_keys.each do |key|
|
146
|
+
errors << ValidationError.new(:missing_required_fields,
|
147
|
+
field: key,
|
148
|
+
identifier: identifier)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ZendeskAppsSupport
|
4
|
+
module Validations
|
5
|
+
module Secrets
|
6
|
+
SECRET_KEYWORDS = %w[
|
7
|
+
password secret secretToken secret_token auth_key
|
8
|
+
authKey auth_pass authPass auth_user AuthUser api_key
|
9
|
+
].freeze
|
10
|
+
|
11
|
+
APPLICATION_SECRETS = {
|
12
|
+
# rubocop:disable Metrics/LineLength
|
13
|
+
'Slack Token' => /(xox[p|b|o|a]-*.[a-z0-9])/,
|
14
|
+
'RSA Private Key' => /-----BEGIN RSA PRIVATE KEY-----/,
|
15
|
+
'SSH Private Key (OpenSSH)' => /-----BEGIN OPENSSH PRIVATE KEY-----/,
|
16
|
+
'SSH Private Key (DSA)' => /-----BEGIN DSA PRIVATE KEY-----/,
|
17
|
+
'SSH Private Key (EC)' => /-----BEGIN EC PRIVATE KEY-----/,
|
18
|
+
'PGP Private Key Block' => /-----BEGIN PGP PRIVATE KEY BLOCK-----/,
|
19
|
+
'Facebook OAuth Token' => /([f|F][a|A][c|C][e|E][b|B][o|O][o|O][k|K]( [|:\"=-]|[:\"=-|]).*.[0-9a-f]{24,36})/,
|
20
|
+
'Twitter OAuth Token' => /([t|T][w|W][i|I][t|T][t|T][e|E][r|R]( [:\"=-]|[:\"=-]).*.[0-9a-zA-Z]{30,45})/,
|
21
|
+
'Github Token' => /([g|G][i|I][t|T][h|H][u|U][b|B]( [:\"=-]|[:\"=-]).*.[0-9a-zA-Z]{30,45})/,
|
22
|
+
'Google OAuth Token' => /([c|C][l|L][i|I][e|E][n|N][t|T][\-_][s|S][e|E][c|C][r|R][e|E][t|T]( [:\"=-]|[:\"=-]).*[a-zA-Z0-9\-_]{16,32})/,
|
23
|
+
'AWS Access Key ID' => /(AKIA[0-9A-Z]{8,24})/,
|
24
|
+
'AWS Secret Access Key' => /([a|A][w|W][s|S][_-][s|S][e|E][c|C][r|R][e|E][t|T][_-][a|A][c|C][c|C][e|E][s|S][s|S][_-][k|K][e|E][y|Y].*.[0-9a-zA-Z]{24,48})/,
|
25
|
+
'Heroku API Key' => /([h|H][e|E][r|R][o|O][k|K][u|U].*[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{6,18})/,
|
26
|
+
'Quickpay Secret' => /(quickpay_secret:.*.[0-9a-zA-Z]{24,72})/,
|
27
|
+
'Doorman Secret' => /([d|D][o|O][o|O][r|R][m|M][a|A][n|N][-_][s|S][e|E][c|C][r|R][e|E][t|T].*.[0-9a-f]{16,132})/,
|
28
|
+
'Shared Session Secret' => /(shared_session_secret.*.[0-9a-f]{4,132})/,
|
29
|
+
'Permanent Cookie Secret' => /(permanent_cookie_secret.*.[0-9a-f]{120,156})/,
|
30
|
+
'Scarlett AWS Secret Key' => /([sS][cC][aA][rR][lL][eE][tT][tT][_-][aA][wW][sS][_-][sS][eE][cC][rR][eE][tT][_-][kK][eE][yY].*.[0-9a-zA-Z+.]{35,45})/,
|
31
|
+
'Braintree Key' => /(braintree_key.*.[0-9a-zA-Z]{16,36})/,
|
32
|
+
'Ticket Validation Key' => /(ticket_validation_key.*.[0-9a-zA-Z]{15,25})/,
|
33
|
+
'App Key' => /([aA][pP][pP][-_][kK][eE][yY]( [:\"=-]|[:\"=-]).*.[0-9a-zA-Z+_.-]{4,156})/,
|
34
|
+
'App Secret' => /([aA][pP][pP][-_][sS][eE][cC][rR][eE][tT]( [:\"=-]|[:\"=-]).*.[0-9a-zA-Z+_.-]{4,156})/,
|
35
|
+
'Consumer Key' => /([cC][oO][nN][sS][uU][mM][eE][rR][-_][kK][eE][yY]( [:\"=-]|[:\"=-]).*.[0-9a-zA-Z+_.-]{4,156})/,
|
36
|
+
'Consumer Secret' => /([cC][oO][nN][sS][uU][mM][eE][rR][-_][sS][eE][cC][rR][eE][tT]( [:\"=-]|[:\"=-]).*.[0-9a-zA-Z+_.-]{4,156})/,
|
37
|
+
'Generic Secret' => /(?m)^([sS][eE][cC][rR][eE][tT]( [:\"=-]|[:\"=-]).*.[0-9a-zA-Z+_.-]{4,156})/,
|
38
|
+
'Master Key' => /([mM][aA][sS][tT][eE][rR][-_][kK][eE][yY]( [:\"=-]|[:\"=-]).*.[0-9a-zA-Z+_.-]{4,156})/,
|
39
|
+
'Master Secret' => /([mM][aA][sS][tT][eE][rR][-_][sS][eE][cC][rR][eE][tT]( [:\"=-]|[:\"=-]).*.[0-9a-zA-Z+_.-]{4,156})/,
|
40
|
+
'Token Key' => /([tT][oO][kK][eE][nN][-_][kK][eE][yY]( [:\"=-]|[:\"=-]).*.[0-9a-zA-Z+_.-]{4,156})/,
|
41
|
+
'Token Secret' => /([tT][oO][kK][eE][nN][-_][sS][eE][cC][rR][eE][tT]( [:\"=-]|[:\"=-]).*.[0-9a-zA-Z+_.-]{4,156})/,
|
42
|
+
'Zendesk Zopim Mobile SSO Key' => /(zendesk_zopim_mobile_sso_key.*.[0-9a-f]{58,68})/,
|
43
|
+
'Help Center Private Key' => /([pP][rR][iI][vV][aA][tT][eE][-_][kK][eE][yY]( [:\"=-]|[:\"=-]).*.[0-9a-zA-Z+_.-]{4,156})/,
|
44
|
+
'X-Outbound-Key' => /([xX][-][oO][uU][tT][bB][oO][uU][nN][dD][-][kK][eE][yY][:\" \t=-].*.[0-9a-z-]{32,36})/,
|
45
|
+
'Attachment Token Key' => /(attachment_token_key.*.[0-9a-f]{24,72})/,
|
46
|
+
'Password' => /([pP][aA][sS][sS][wW][oO][rR][dD].*.[0-9a-zA-Z+_.-]{4,156})/,
|
47
|
+
'Token' => /([tT][oO][kK][eE][nN]( [:\"=-]|[:\"=-]).*.[0-9a-zA-Z+_.-]{4,156})/
|
48
|
+
# rubocop:enable Metrics/LineLength
|
49
|
+
}.freeze
|
50
|
+
|
51
|
+
class << self
|
52
|
+
def call(package)
|
53
|
+
compromised_files = package.text_files.map do |file|
|
54
|
+
contents = file.read
|
55
|
+
|
56
|
+
APPLICATION_SECRETS.each do |secret_type, regex_str|
|
57
|
+
next unless contents =~ Regexp.new(regex_str)
|
58
|
+
package.warnings << I18n.t('txt.apps.admin.warning.app_build.application_secret',
|
59
|
+
file: file.relative_path,
|
60
|
+
secret_type: secret_type)
|
61
|
+
end
|
62
|
+
|
63
|
+
match = Regexp.union(SECRET_KEYWORDS).match(contents)
|
64
|
+
"#{file.relative_path} ('#{match[0]}...')" if match
|
65
|
+
end.compact
|
66
|
+
|
67
|
+
return unless compromised_files.any?
|
68
|
+
package.warnings << I18n.t('txt.apps.admin.warning.app_build.generic_secrets',
|
69
|
+
files: compromised_files.join(
|
70
|
+
I18n.t('txt.apps.admin.error.app_build.listing_comma')
|
71
|
+
),
|
72
|
+
count: compromised_files.count)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ZendeskAppsSupport
|
4
|
+
module Validations
|
5
|
+
module SecureSettings
|
6
|
+
SECURABLE_KEYWORDS = %w[token key pwd password].freeze
|
7
|
+
SECURABLE_KEYWORDS_REGEXP = Regexp.new(SECURABLE_KEYWORDS.join('|'), Regexp::IGNORECASE)
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def call(package)
|
11
|
+
manifest_params = package.manifest.parameters
|
12
|
+
|
13
|
+
insecure_params_found = manifest_params.any? { |param| insecure_param?(param) }
|
14
|
+
|
15
|
+
package.warnings << secure_settings_warning if insecure_params_found
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def insecure_param?(parameter)
|
21
|
+
parameter.name =~ SECURABLE_KEYWORDS_REGEXP && type_password_or_text?(parameter.type) && !parameter.secure
|
22
|
+
end
|
23
|
+
|
24
|
+
def type_password_or_text?(parameter_type)
|
25
|
+
parameter_type == 'text' || parameter_type == 'password'
|
26
|
+
end
|
27
|
+
|
28
|
+
def secure_settings_warning
|
29
|
+
I18n.t(
|
30
|
+
'txt.apps.admin.error.app_build.translation.insecure_token_parameter_in_manifest',
|
31
|
+
link: 'https://developer.zendesk.com/apps/docs/developer-guide/using_sdk#using-secure-settings'
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ZendeskAppsSupport
|
4
|
+
module Validations
|
5
|
+
module Source
|
6
|
+
class << self
|
7
|
+
def call(package)
|
8
|
+
if app_doesnt_require_source?(package.manifest) && contain_source_files?(package)
|
9
|
+
ValidationError.new(:no_source_required_apps)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def contain_source_files?(package)
|
16
|
+
package.js_files.any? || package.template_files.any? || !package.app_css.empty?
|
17
|
+
end
|
18
|
+
|
19
|
+
def app_doesnt_require_source?(manifest)
|
20
|
+
manifest.requirements_only? || manifest.marketing_only?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zendesk_apps_support/stylesheet_compiler'
|
4
|
+
|
5
|
+
module ZendeskAppsSupport
|
6
|
+
module Validations
|
7
|
+
module Stylesheets
|
8
|
+
class << self
|
9
|
+
def call(package)
|
10
|
+
css_error = validate_styles(package.app_css)
|
11
|
+
css_error ? [css_error] : []
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def validate_styles(css)
|
17
|
+
compiler = ZendeskAppsSupport::StylesheetCompiler.new(css, nil, nil)
|
18
|
+
begin
|
19
|
+
compiler.compile
|
20
|
+
rescue SassC::SyntaxError, Sass::SyntaxError => e
|
21
|
+
return ValidationError.new(:stylesheet_error, sass_error: e.message)
|
22
|
+
end
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|