From f39d22a59816a18e9b2777067330193ce336b7a0 Mon Sep 17 00:00:00 2001 From: Mokhtar Date: Mon, 29 Apr 2024 17:28:56 +0200 Subject: [PATCH] feat(#615): add support for `assetlinks.json` when compiling app settings (#616) --- src/fn/compile-app-settings.js | 9 ++ src/lib/validate-app-settings.js | 17 +++ .../app_settings.expected.json | 51 +++++++ .../android-app-links/invalid-file/.eslintrc | 0 .../invalid-file/app_settings/assetlinks.json | 11 ++ .../app_settings/base_settings.json | 19 +++ .../invalid-file/contact-summary.js | 0 .../invalid-file/rules.nools.js | 0 .../invalid-file/targets.json | 1 + .../android-app-links/project/.eslintrc | 0 .../project/app_settings/assetlinks.json | 27 ++++ .../project/app_settings/base_settings.json | 19 +++ .../project/contact-summary.js | 0 .../android-app-links/project/rules.nools.js | 0 .../android-app-links/project/targets.json | 1 + test/fn/compile-app-settings.spec.js | 13 +- test/lib/validate-app-settings.spec.js | 144 +++++++++++++++++- 17 files changed, 309 insertions(+), 3 deletions(-) create mode 100644 test/data/compile-app-settings/android-app-links/app_settings.expected.json create mode 100644 test/data/compile-app-settings/android-app-links/invalid-file/.eslintrc create mode 100644 test/data/compile-app-settings/android-app-links/invalid-file/app_settings/assetlinks.json create mode 100644 test/data/compile-app-settings/android-app-links/invalid-file/app_settings/base_settings.json create mode 100644 test/data/compile-app-settings/android-app-links/invalid-file/contact-summary.js create mode 100644 test/data/compile-app-settings/android-app-links/invalid-file/rules.nools.js create mode 100644 test/data/compile-app-settings/android-app-links/invalid-file/targets.json create mode 100644 test/data/compile-app-settings/android-app-links/project/.eslintrc create mode 100644 test/data/compile-app-settings/android-app-links/project/app_settings/assetlinks.json create mode 100644 test/data/compile-app-settings/android-app-links/project/app_settings/base_settings.json create mode 100644 test/data/compile-app-settings/android-app-links/project/contact-summary.js create mode 100644 test/data/compile-app-settings/android-app-links/project/rules.nools.js create mode 100644 test/data/compile-app-settings/android-app-links/project/targets.json diff --git a/src/fn/compile-app-settings.js b/src/fn/compile-app-settings.js index 0529c65e7..41ce7ee40 100644 --- a/src/fn/compile-app-settings.js +++ b/src/fn/compile-app-settings.js @@ -95,6 +95,15 @@ const compileAppSettingsForProject = async (projectDir, options) => { } appSettings.schedules = scheduleSettings; } + + const assetlinks = readOptionalJson(path.join(projectDir, 'app_settings/assetlinks.json')); + if (assetlinks) { + const validate = validateAppSettings.validateAssetlinks(assetlinks); + if (!validate.valid) { + throw new Error(`Invalid assetlinks: ${validate.error}`); + } + appSettings.assetlinks = assetlinks; + } } else { warn(`app_settings.json file should not be edited directly. Please create a base_settings.json file in app_settings folder and move any manually defined configurations there.`); diff --git a/src/lib/validate-app-settings.js b/src/lib/validate-app-settings.js index 52fc98920..ff4a0db38 100644 --- a/src/lib/validate-app-settings.js +++ b/src/lib/validate-app-settings.js @@ -64,6 +64,19 @@ const ScheduleSchema = joi.array().items( }) ); +const AssetlinksSchema = joi.array().items( + joi.object({ + relation: joi.array().items( + joi.string().valid('delegate_permission/common.handle_all_urls').required() + ).length(1).required(), + target: joi.object({ + namespace: joi.string().valid('android_app').required(), + package_name: joi.string().required(), + sha256_cert_fingerprints: joi.array().items(joi.string().required()).min(1).required(), + }).required(), + }), +).min(1); + module.exports = { validateFormsSchema: (formsObject) => { const { error } = FormsSchema.validate(formsObject); @@ -72,5 +85,9 @@ module.exports = { validateScheduleSchema: (scheduleObject) => { const { error } = ScheduleSchema.validate(scheduleObject); return error ? { valid: false, error } : { valid: true }; + }, + validateAssetlinks: (assetlinksObject) => { + const { error } = AssetlinksSchema.validate(assetlinksObject); + return error ? { valid: false, error } : { valid: true }; } }; diff --git a/test/data/compile-app-settings/android-app-links/app_settings.expected.json b/test/data/compile-app-settings/android-app-links/app_settings.expected.json new file mode 100644 index 000000000..8780a1d70 --- /dev/null +++ b/test/data/compile-app-settings/android-app-links/app_settings.expected.json @@ -0,0 +1,51 @@ +{ + "locale": "en", + "locales": [ + { + "code": "en", + "name": "English" + }, + { + "code": "es", + "name": "Español (Spanish)" + } + ], + "kujua-reporting": [ + { + "code": "SS", + "reporting_freq": "monthly" + } + ], + "assetlinks": [ + { + "relation": [ + "delegate_permission/common.handle_all_urls" + ], + "target": { + "namespace": "android_app", + "package_name": "org.medicmobile.webapp.mobile", + "sha256_cert_fingerprints": [ + "62:BF:C1:78:24:D8:4D:5C:B4:E1:8B:66:98:EA:14:16:57:6F:A4:E5:96:CD:93:81:B2:65:19:71:A7:80:EA:4D" + ] + } + }, + { + "relation": [ + "delegate_permission/common.handle_all_urls" + ], + "target": { + "namespace": "android_app", + "package_name": "org.medicmobile.something.else", + "sha256_cert_fingerprints": [ + "asdf", + "zxcvbn" + ] + } + } + ], + "contact_summary": "", + "tasks": { + "rules": "", + "targets": {} + } +} diff --git a/test/data/compile-app-settings/android-app-links/invalid-file/.eslintrc b/test/data/compile-app-settings/android-app-links/invalid-file/.eslintrc new file mode 100644 index 000000000..e69de29bb diff --git a/test/data/compile-app-settings/android-app-links/invalid-file/app_settings/assetlinks.json b/test/data/compile-app-settings/android-app-links/invalid-file/app_settings/assetlinks.json new file mode 100644 index 000000000..f38bfbf08 --- /dev/null +++ b/test/data/compile-app-settings/android-app-links/invalid-file/app_settings/assetlinks.json @@ -0,0 +1,11 @@ +[ + { + "relation": [ + "delegate_permission/common.handle_all_urls" + ], + "target": { + "namespace": "android_app", + "package_name": "org.medicmobile.webapp.mobile" + } + } +] diff --git a/test/data/compile-app-settings/android-app-links/invalid-file/app_settings/base_settings.json b/test/data/compile-app-settings/android-app-links/invalid-file/app_settings/base_settings.json new file mode 100644 index 000000000..9a0482d6b --- /dev/null +++ b/test/data/compile-app-settings/android-app-links/invalid-file/app_settings/base_settings.json @@ -0,0 +1,19 @@ +{ + "locale": "en", + "locales": [ + { + "code": "en", + "name": "English" + }, + { + "code": "es", + "name": "Español (Spanish)" + } + ], + "kujua-reporting": [ + { + "code": "SS", + "reporting_freq": "monthly" + } + ] +} diff --git a/test/data/compile-app-settings/android-app-links/invalid-file/contact-summary.js b/test/data/compile-app-settings/android-app-links/invalid-file/contact-summary.js new file mode 100644 index 000000000..e69de29bb diff --git a/test/data/compile-app-settings/android-app-links/invalid-file/rules.nools.js b/test/data/compile-app-settings/android-app-links/invalid-file/rules.nools.js new file mode 100644 index 000000000..e69de29bb diff --git a/test/data/compile-app-settings/android-app-links/invalid-file/targets.json b/test/data/compile-app-settings/android-app-links/invalid-file/targets.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/data/compile-app-settings/android-app-links/invalid-file/targets.json @@ -0,0 +1 @@ +{} diff --git a/test/data/compile-app-settings/android-app-links/project/.eslintrc b/test/data/compile-app-settings/android-app-links/project/.eslintrc new file mode 100644 index 000000000..e69de29bb diff --git a/test/data/compile-app-settings/android-app-links/project/app_settings/assetlinks.json b/test/data/compile-app-settings/android-app-links/project/app_settings/assetlinks.json new file mode 100644 index 000000000..a4ae0f985 --- /dev/null +++ b/test/data/compile-app-settings/android-app-links/project/app_settings/assetlinks.json @@ -0,0 +1,27 @@ +[ + { + "relation": [ + "delegate_permission/common.handle_all_urls" + ], + "target": { + "namespace": "android_app", + "package_name": "org.medicmobile.webapp.mobile", + "sha256_cert_fingerprints": [ + "62:BF:C1:78:24:D8:4D:5C:B4:E1:8B:66:98:EA:14:16:57:6F:A4:E5:96:CD:93:81:B2:65:19:71:A7:80:EA:4D" + ] + } + }, + { + "relation": [ + "delegate_permission/common.handle_all_urls" + ], + "target": { + "namespace": "android_app", + "package_name": "org.medicmobile.something.else", + "sha256_cert_fingerprints": [ + "asdf", + "zxcvbn" + ] + } + } +] diff --git a/test/data/compile-app-settings/android-app-links/project/app_settings/base_settings.json b/test/data/compile-app-settings/android-app-links/project/app_settings/base_settings.json new file mode 100644 index 000000000..9a0482d6b --- /dev/null +++ b/test/data/compile-app-settings/android-app-links/project/app_settings/base_settings.json @@ -0,0 +1,19 @@ +{ + "locale": "en", + "locales": [ + { + "code": "en", + "name": "English" + }, + { + "code": "es", + "name": "Español (Spanish)" + } + ], + "kujua-reporting": [ + { + "code": "SS", + "reporting_freq": "monthly" + } + ] +} diff --git a/test/data/compile-app-settings/android-app-links/project/contact-summary.js b/test/data/compile-app-settings/android-app-links/project/contact-summary.js new file mode 100644 index 000000000..e69de29bb diff --git a/test/data/compile-app-settings/android-app-links/project/rules.nools.js b/test/data/compile-app-settings/android-app-links/project/rules.nools.js new file mode 100644 index 000000000..e69de29bb diff --git a/test/data/compile-app-settings/android-app-links/project/targets.json b/test/data/compile-app-settings/android-app-links/project/targets.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/data/compile-app-settings/android-app-links/project/targets.json @@ -0,0 +1 @@ +{} diff --git a/test/fn/compile-app-settings.spec.js b/test/fn/compile-app-settings.spec.js index 5ccf49496..8265899d8 100644 --- a/test/fn/compile-app-settings.spec.js +++ b/test/fn/compile-app-settings.spec.js @@ -47,7 +47,7 @@ const scenarios = [ folder: 'purge/no-export-purge/project', }, { - description: 'should handle a project with eslint error when --debug flag is present', + description: 'should handle a project with eslint error when --debug flag is present', folder: 'eslint-error/project', extraArgs: ['--debug'], }, @@ -58,11 +58,15 @@ const scenarios = [ { description: 'should handle a configuration using the base_settings file', folder: 'base-settings/project', - }, + }, { description: 'should handle a configuration using the forms.json and schedules.json files', folder: 'sms-modules/project', }, + { + description: 'should handle a configuration using the assetlinks.json file', + folder: 'android-app-links/project', + }, // REJECTION SCENARIOS { @@ -115,6 +119,11 @@ const scenarios = [ folder: 'missing-eslintrc/project', error: 'No eslint configuration', }, + { + description: 'should reject a configuration using an invalid assetlinks.json file', + folder: 'android-app-links/invalid-file', + error: 'Invalid assetlinks: ValidationError: "[0].target.sha256_cert_fingerprints" is required', + }, ]; describe('compile-app-settings', () => { diff --git a/test/lib/validate-app-settings.spec.js b/test/lib/validate-app-settings.spec.js index 016da9c43..acaedb2ef 100644 --- a/test/lib/validate-app-settings.spec.js +++ b/test/lib/validate-app-settings.spec.js @@ -3,7 +3,6 @@ const { expect } = require('chai'); const validateAppSettings = require('../../src/lib/validate-app-settings'); describe('validate-app-settings', () => { - describe('validateFormsSchema', () => { const isValid = (formsObject) => { @@ -158,4 +157,147 @@ describe('validate-app-settings', () => { }); + describe('validateAssetlinks', () => { + const isValid = (assetlinks) => { + const result = validateAppSettings.validateAssetlinks(assetlinks); + expect(result.valid).to.be.true; + }; + + const isNotValid = (assetlinks, errorMessage) => { + const result = validateAppSettings.validateAssetlinks(assetlinks); + expect(result.valid).to.be.false; + expect(result.error.details.length).to.equal(1); + expect(result.error.details[0].message).to.equal(errorMessage); + }; + + it('returns true for assetlinks with one entry', () => { + isValid([{ + relation: ['delegate_permission/common.handle_all_urls'], + target: { + namespace: 'android_app', + package_name: 'org.medicmobile.webapp.mobile', + sha256_cert_fingerprints: ['long sha256 fingerprint 62:BF:C1:78...'] + } + }]); + }); + + it('returns true for assetlinks with multiple entries', () => { + // for example when associating 1 domain to multiple apks + isValid([ + { + relation: ['delegate_permission/common.handle_all_urls'], + target: { + namespace: 'android_app', + package_name: 'org.medicmobile.webapp.mobile', + sha256_cert_fingerprints: ['long sha256 fingerprint 62:BF:C1:78...'] + } + }, + { + relation: [ + 'delegate_permission/common.handle_all_urls' + ], + target: { + namespace: 'android_app', + package_name: 'org.medicmobile.other.app', + sha256_cert_fingerprints: ['long other sha256 fingerprint 26:FB:1C:87...'] + } + }, + ]); + }); + + it('returns true for assetlinks with multiple apk fingerprints', () => { + isValid([{ + relation: ['delegate_permission/common.handle_all_urls'], + target: { + namespace: 'android_app', + package_name: 'org.medicmobile.webapp.mobile', + sha256_cert_fingerprints: [ + 'long sha256 fingerprint 62:BF:C1:78...', + 'long other sha256 fingerprint 26:FB:1C:87...', + ], + } + }]); + }); + + it('returns false for an empty assetlinks', () => { + isNotValid([], '"value" must contain at least 1 items'); + }); + + it('returns false for assetlinks with constant properties that were changed', () => { + isNotValid( + [{ + relation: ['something wrong'], + target: { + namespace: 'android_app', + package_name: 'org.medicmobile.webapp.mobile', + sha256_cert_fingerprints: ['long sha256 fingerprint 62:BF:C1:78...'] + } + }], + '"[0].relation[0]" must be [delegate_permission/common.handle_all_urls]', + ); + + isNotValid( + [{ + relation: ['delegate_permission/common.handle_all_urls'], + target: { + namespace: 'something else', + package_name: 'org.medicmobile.webapp.mobile', + sha256_cert_fingerprints: ['long sha256 fingerprint 62:BF:C1:78...'] + } + }], + '"[0].target.namespace" must be [android_app]', + ); + }); + + it('returns false for assetlinks with missing properties', () => { + isNotValid( + [{ + target: { + namespace: 'android_app', + package_name: 'org.medicmobile.webapp.mobile', + sha256_cert_fingerprints: ['long sha256 fingerprint 62:BF:C1:78...'] + } + }], + '"[0].relation" is required', + ); + + isNotValid( + [{ relation: ['delegate_permission/common.handle_all_urls'] }], + '"[0].target" is required', + ); + + isNotValid( + [{ + relation: ['delegate_permission/common.handle_all_urls'], + target: { + package_name: 'org.medicmobile.webapp.mobile', + sha256_cert_fingerprints: ['long sha256 fingerprint 62:BF:C1:78...'] + } + }], + '"[0].target.namespace" is required', + ); + + isNotValid( + [{ + relation: ['delegate_permission/common.handle_all_urls'], + target: { + namespace: 'android_app', + sha256_cert_fingerprints: ['long sha256 fingerprint 62:BF:C1:78...'] + } + }], + '"[0].target.package_name" is required', + ); + + isNotValid( + [{ + relation: ['delegate_permission/common.handle_all_urls'], + target: { + namespace: 'android_app', + package_name: 'org.medicmobile.webapp.mobile', + } + }], + '"[0].target.sha256_cert_fingerprints" is required', + ); + }); + }); });