This is an automated email from the ASF dual-hosted git repository. erisu pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/cordova-ios.git
The following commit(s) were added to refs/heads/master by this push: new c97845ac feat: add privacy-manifest config support (#1406) c97845ac is described below commit c97845acb7e4fe659c0f71103bef44700da37d5c Author: knaito-asial <81949829+knaito-as...@users.noreply.github.com> AuthorDate: Wed Mar 13 15:03:13 2024 +0900 feat: add privacy-manifest config support (#1406) * feat: privacy manifest settings in config.xml * refac: remove unused code and tidy up * refac: update class name PlatformConfigParser * feat: add elementtree in package.json * dev: change privacy manifest tag name to privacy-manifest from privacy-manifest-ios * test: add test codes * refactor: Modified to match Linter. * refac: improve PlatformConfigParser * refac: remove unnecessary blank line --------- Co-authored-by: エリス <er...@users.noreply.github.com> --- lib/PlatformConfigParser.js | 32 +++++++++++++ lib/prepare.js | 40 ++++++++++++++-- package-lock.json | 1 + package.json | 3 +- .../unit/fixtures/prepare/no-privacy-manifest.xml | 23 +++++++++ .../unit/fixtures/prepare/privacy-manifest.xml | 56 ++++++++++++++++++++++ tests/spec/unit/prepare.spec.js | 38 +++++++++++++++ 7 files changed, 189 insertions(+), 4 deletions(-) diff --git a/lib/PlatformConfigParser.js b/lib/PlatformConfigParser.js new file mode 100644 index 00000000..675287c6 --- /dev/null +++ b/lib/PlatformConfigParser.js @@ -0,0 +1,32 @@ +/** + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +const ConfigParser = require('cordova-common').ConfigParser; + +class PlatformConfigParser extends ConfigParser { + /** + * Returns the privacy manifest node, if available. + * Otherwise `null` is returned. + */ + getPrivacyManifest () { + return this.doc.find('./platform[@name="ios"]/privacy-manifest'); + } +} + +module.exports = PlatformConfigParser; diff --git a/lib/prepare.js b/lib/prepare.js index 38239e4c..cfaab02c 100644 --- a/lib/prepare.js +++ b/lib/prepare.js @@ -17,12 +17,11 @@ under the License. */ -'use strict'; - const fs = require('fs-extra'); const path = require('path'); const unorm = require('unorm'); const plist = require('plist'); +const et = require('elementtree'); const URL = require('url'); const events = require('cordova-common').events; const xmlHelpers = require('cordova-common').xmlHelpers; @@ -35,6 +34,7 @@ const FileUpdater = require('cordova-common').FileUpdater; const projectFile = require('./projectFile'); const Podfile = require('./Podfile').Podfile; const check_reqs = require('./check_reqs'); +const PlatformConfigParser = require('./PlatformConfigParser'); // launch storyboard and related constants const IMAGESET_COMPACT_SIZE_CLASS = 'compact'; @@ -43,9 +43,16 @@ const CDV_ANY_SIZE_CLASS = 'any'; module.exports.prepare = function (cordovaProject, options) { const platformJson = PlatformJson.load(this.locations.root, 'ios'); const munger = new PlatformMunger('ios', this.locations.root, platformJson, new PluginInfoProvider()); - this._config = updateConfigFile(cordovaProject.projectConfig, munger, this.locations); + const parser = new PlatformConfigParser(cordovaProject.projectConfig.path); + try { + const manifest = parser.getPrivacyManifest(); + overwritePrivacyManifest(manifest, this.locations); + } catch (err) { + return Promise.reject(new CordovaError(`Could not parse PrivacyManifest in config.xml: ${err}`)); + } + // Update own www dir with project's www assets and plugins' assets and js-files return updateWww(cordovaProject, this.locations) // update project according to config.xml changes. @@ -87,6 +94,33 @@ module.exports.clean = function (options) { }); }; +/** + * Overwrites the privacy manifest file with the provided manifest or sets the default manifest. + * @param {ElementTree} manifest - The manifest to be written to the privacy manifest file. + * @param {Object} locations - The locations object containing the path to the Xcode Cordova project. + */ +function overwritePrivacyManifest (manifest, locations) { + const privacyManifestDest = path.join(locations.xcodeCordovaProj, 'PrivacyInfo.xcprivacy'); + if (manifest != null) { + const XML_DECLARATION = '<?xml version="1.0" encoding="UTF-8"?>\n'; + const DOCTYPE = '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n'; + const plistElement = et.Element('plist'); + plistElement.set('version', '1.0'); + const dictElement = et.SubElement(plistElement, 'dict'); + manifest.getchildren().forEach((child) => { + dictElement.append(child); + }); + const etree = new et.ElementTree(plistElement); + const xmlString = XML_DECLARATION + DOCTYPE + etree.write({ xml_declaration: false }); + fs.writeFileSync(privacyManifestDest, xmlString, 'utf-8'); + return; + } + // Set default privacy manifest + const defaultPrivacyManifest = path.join(__dirname, '..', 'templates', 'project', '__PROJECT_NAME__', 'PrivacyInfo.xcprivacy'); + const xmlString = fs.readFileSync(defaultPrivacyManifest, 'utf8'); + fs.writeFileSync(privacyManifestDest, xmlString, 'utf-8'); +} + /** * Updates config files in project based on app's config.xml and config munge, * generated by plugins. diff --git a/package-lock.json b/package-lock.json index 744d6aa1..862e2115 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "Apache-2.0", "dependencies": { "cordova-common": "^5.0.0", + "elementtree": "^0.1.7", "execa": "^5.1.1", "fs-extra": "^11.1.1", "ios-sim": "^8.0.2", diff --git a/package.json b/package.json index 6082340a..2a305d22 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,8 @@ "unorm": "^1.6.0", "which": "^3.0.1", "xcode": "^3.0.1", - "xml-escape": "^1.1.0" + "xml-escape": "^1.1.0", + "elementtree": "^0.1.7" }, "nyc": { "include": [ diff --git a/tests/spec/unit/fixtures/prepare/no-privacy-manifest.xml b/tests/spec/unit/fixtures/prepare/no-privacy-manifest.xml new file mode 100644 index 00000000..98afac21 --- /dev/null +++ b/tests/spec/unit/fixtures/prepare/no-privacy-manifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +<widget id="io.cordova.hellocordova" ios-CFBundleIdentifier="io.cordova.hellocordova.ios" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0"> + <name>SampleApp</name> +</widget> diff --git a/tests/spec/unit/fixtures/prepare/privacy-manifest.xml b/tests/spec/unit/fixtures/prepare/privacy-manifest.xml new file mode 100644 index 00000000..510464a5 --- /dev/null +++ b/tests/spec/unit/fixtures/prepare/privacy-manifest.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +<widget id="io.cordova.hellocordova" ios-CFBundleIdentifier="io.cordova.hellocordova.ios" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0"> + <name>SampleApp</name> + <platform name="ios"> + <privacy-manifest> + <key>NSPrivacyTracking</key> + <true/> + <key>NSPrivacyAccessedAPITypes</key> + <array/> + <key>NSPrivacyTrackingDomains</key> + <array/> + <key>NSPrivacyCollectedDataTypes</key> + <array> + <dict> + <!-- The value provided by Apple for 'Device ID' data type --> + <key>NSPrivacyCollectedDataType</key> + <string>NSPrivacyCollectedDataTypeDeviceID</string> + + <!-- Fingerprint Identification SDK does not link the 'Device ID' with user's identity --> + <key>NSPrivacyCollectedDataTypeLinked</key> + <false/> + + <!-- Fingerprint Identification SDK does not use 'Device ID' for tracking --> + <key>NSPrivacyCollectedDataTypeTracking</key> + <false/> + + <!-- Fingerprint Identification SDK uses 'Device ID' for App Functionality + (prevent fraud and implement security measures) --> + <key>NSPrivacyCollectedDataTypePurposes</key> + <array> + <string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string> + </array> + </dict> + </array> + </privacy-manifest> + </platform> +</widget> diff --git a/tests/spec/unit/prepare.spec.js b/tests/spec/unit/prepare.spec.js index b51a9f6d..2ec83531 100644 --- a/tests/spec/unit/prepare.spec.js +++ b/tests/spec/unit/prepare.spec.js @@ -1504,6 +1504,44 @@ describe('prepare', () => { expect(plist.build.calls.mostRecent().args[0].CFBundleDisplayName).toEqual('MyApp'); }); }); + it('Test#021 : <privacy-manifest> - should write out the privacy manifest ', () => { + plist.parse.and.callThrough(); + writeFileSyncSpy.and.callThrough(); + const projectRoot = iosProject; + const platformProjDir = path.join(projectRoot, 'platforms', 'ios', 'SampleApp'); + const PlatformConfigParser = require('../../../lib/PlatformConfigParser'); + const my_config = new PlatformConfigParser(path.join(FIXTURES, 'prepare', 'privacy-manifest.xml')); + const privacyManifest = my_config.getPrivacyManifest(); + const overwritePrivacyManifest = prepare.__get__('overwritePrivacyManifest'); + overwritePrivacyManifest(privacyManifest, p.locations); + const privacyManifestPathDest = path.join(platformProjDir, 'PrivacyInfo.xcprivacy'); + expect(writeFileSyncSpy).toHaveBeenCalledWith(privacyManifestPathDest, jasmine.any(String), 'utf-8'); + const xml = writeFileSyncSpy.calls.all()[0].args[1]; + const json = plist.parse(xml); + expect(json.NSPrivacyTracking).toBeTrue(); + expect(json.NSPrivacyAccessedAPITypes.length).toBe(0); + expect(json.NSPrivacyTrackingDomains.length).toBe(0); + expect(json.NSPrivacyCollectedDataTypes.length).toBe(1); + }); + it('Test#022 : no <privacy-manifest> - should write out the privacy manifest ', () => { + plist.parse.and.callThrough(); + writeFileSyncSpy.and.callThrough(); + const projectRoot = iosProject; + const platformProjDir = path.join(projectRoot, 'platforms', 'ios', 'SampleApp'); + const PlatformConfigParser = require('../../../lib/PlatformConfigParser'); + const my_config = new PlatformConfigParser(path.join(FIXTURES, 'prepare', 'no-privacy-manifest.xml')); + const privacyManifest = my_config.getPrivacyManifest(); + const overwritePrivacyManifest = prepare.__get__('overwritePrivacyManifest'); + overwritePrivacyManifest(privacyManifest, p.locations); + const privacyManifestPathDest = path.join(platformProjDir, 'PrivacyInfo.xcprivacy'); + expect(writeFileSyncSpy).toHaveBeenCalledWith(privacyManifestPathDest, jasmine.any(String), 'utf-8'); + const xml = writeFileSyncSpy.calls.all()[0].args[1]; + const json = plist.parse(xml); + expect(json.NSPrivacyTracking).toBeFalse(); + expect(json.NSPrivacyAccessedAPITypes.length).toBe(0); + expect(json.NSPrivacyTrackingDomains.length).toBe(0); + expect(json.NSPrivacyCollectedDataTypes.length).toBe(0); + }); }); describe('<resource-file> tests', () => { --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@cordova.apache.org For additional commands, e-mail: commits-h...@cordova.apache.org