Skip to content

Commit

Permalink
feat: add privacy-manifest config support (#1406)
Browse files Browse the repository at this point in the history
* 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: エリス <[email protected]>
  • Loading branch information
knaito-asial and erisu authored Mar 13, 2024
1 parent d316558 commit c97845a
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 4 deletions.
32 changes: 32 additions & 0 deletions lib/PlatformConfigParser.js
Original file line number Diff line number Diff line change
@@ -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;
40 changes: 37 additions & 3 deletions lib/prepare.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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';
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
23 changes: 23 additions & 0 deletions tests/spec/unit/fixtures/prepare/no-privacy-manifest.xml
Original file line number Diff line number Diff line change
@@ -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>
56 changes: 56 additions & 0 deletions tests/spec/unit/fixtures/prepare/privacy-manifest.xml
Original file line number Diff line number Diff line change
@@ -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>
38 changes: 38 additions & 0 deletions tests/spec/unit/prepare.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down

0 comments on commit c97845a

Please sign in to comment.