Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add privacy-manifest config support #1406

Merged
merged 10 commits into from
Mar 13, 2024
34 changes: 34 additions & 0 deletions lib/PlatformConfigParser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
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.
*/

'use strict';
erisu marked this conversation as resolved.
Show resolved Hide resolved

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');
erisu marked this conversation as resolved.
Show resolved Hide resolved
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>
39 changes: 39 additions & 0 deletions tests/spec/unit/prepare.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1504,6 +1504,45 @@
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);
});

Check failure on line 1544 in tests/spec/unit/prepare.spec.js

View workflow job for this annotation

GitHub Actions / NodeJS 16.x on macos-latest

Block must not be padded by blank lines

Check failure on line 1544 in tests/spec/unit/prepare.spec.js

View workflow job for this annotation

GitHub Actions / NodeJS 16.x on ubuntu-latest

Block must not be padded by blank lines

Check failure on line 1544 in tests/spec/unit/prepare.spec.js

View workflow job for this annotation

GitHub Actions / NodeJS 18.x on macos-latest

Block must not be padded by blank lines

Check failure on line 1544 in tests/spec/unit/prepare.spec.js

View workflow job for this annotation

GitHub Actions / NodeJS 20.x on macos-latest

Block must not be padded by blank lines

Check failure on line 1544 in tests/spec/unit/prepare.spec.js

View workflow job for this annotation

GitHub Actions / NodeJS 18.x on ubuntu-latest

Block must not be padded by blank lines

Check failure on line 1544 in tests/spec/unit/prepare.spec.js

View workflow job for this annotation

GitHub Actions / NodeJS 20.x on ubuntu-latest

Block must not be padded by blank lines

erisu marked this conversation as resolved.
Show resolved Hide resolved
});

describe('<resource-file> tests', () => {
Expand Down
Loading