zendesk_apps_support 4.29.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|