Skip to content

Commit c97845a

Browse files
knaito-asialerisu
andauthored
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: エリス <erisu@users.noreply.github.com>
1 parent d316558 commit c97845a

7 files changed

+189
-4
lines changed

lib/PlatformConfigParser.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
Licensed to the Apache Software Foundation (ASF) under one
3+
or more contributor license agreements. See the NOTICE file
4+
distributed with this work for additional information
5+
regarding copyright ownership. The ASF licenses this file
6+
to you under the Apache License, Version 2.0 (the
7+
"License"); you may not use this file except in compliance
8+
with the License. You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing,
13+
software distributed under the License is distributed on an
14+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
KIND, either express or implied. See the License for the
16+
specific language governing permissions and limitations
17+
under the License.
18+
*/
19+
20+
const ConfigParser = require('cordova-common').ConfigParser;
21+
22+
class PlatformConfigParser extends ConfigParser {
23+
/**
24+
* Returns the privacy manifest node, if available.
25+
* Otherwise `null` is returned.
26+
*/
27+
getPrivacyManifest () {
28+
return this.doc.find('./platform[@name="ios"]/privacy-manifest');
29+
}
30+
}
31+
32+
module.exports = PlatformConfigParser;

lib/prepare.js

+37-3
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,11 @@
1717
under the License.
1818
*/
1919

20-
'use strict';
21-
2220
const fs = require('fs-extra');
2321
const path = require('path');
2422
const unorm = require('unorm');
2523
const plist = require('plist');
24+
const et = require('elementtree');
2625
const URL = require('url');
2726
const events = require('cordova-common').events;
2827
const xmlHelpers = require('cordova-common').xmlHelpers;
@@ -35,6 +34,7 @@ const FileUpdater = require('cordova-common').FileUpdater;
3534
const projectFile = require('./projectFile');
3635
const Podfile = require('./Podfile').Podfile;
3736
const check_reqs = require('./check_reqs');
37+
const PlatformConfigParser = require('./PlatformConfigParser');
3838

3939
// launch storyboard and related constants
4040
const IMAGESET_COMPACT_SIZE_CLASS = 'compact';
@@ -43,9 +43,16 @@ const CDV_ANY_SIZE_CLASS = 'any';
4343
module.exports.prepare = function (cordovaProject, options) {
4444
const platformJson = PlatformJson.load(this.locations.root, 'ios');
4545
const munger = new PlatformMunger('ios', this.locations.root, platformJson, new PluginInfoProvider());
46-
4746
this._config = updateConfigFile(cordovaProject.projectConfig, munger, this.locations);
4847

48+
const parser = new PlatformConfigParser(cordovaProject.projectConfig.path);
49+
try {
50+
const manifest = parser.getPrivacyManifest();
51+
overwritePrivacyManifest(manifest, this.locations);
52+
} catch (err) {
53+
return Promise.reject(new CordovaError(`Could not parse PrivacyManifest in config.xml: ${err}`));
54+
}
55+
4956
// Update own www dir with project's www assets and plugins' assets and js-files
5057
return updateWww(cordovaProject, this.locations)
5158
// update project according to config.xml changes.
@@ -87,6 +94,33 @@ module.exports.clean = function (options) {
8794
});
8895
};
8996

97+
/**
98+
* Overwrites the privacy manifest file with the provided manifest or sets the default manifest.
99+
* @param {ElementTree} manifest - The manifest to be written to the privacy manifest file.
100+
* @param {Object} locations - The locations object containing the path to the Xcode Cordova project.
101+
*/
102+
function overwritePrivacyManifest (manifest, locations) {
103+
const privacyManifestDest = path.join(locations.xcodeCordovaProj, 'PrivacyInfo.xcprivacy');
104+
if (manifest != null) {
105+
const XML_DECLARATION = '<?xml version="1.0" encoding="UTF-8"?>\n';
106+
const DOCTYPE = '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n';
107+
const plistElement = et.Element('plist');
108+
plistElement.set('version', '1.0');
109+
const dictElement = et.SubElement(plistElement, 'dict');
110+
manifest.getchildren().forEach((child) => {
111+
dictElement.append(child);
112+
});
113+
const etree = new et.ElementTree(plistElement);
114+
const xmlString = XML_DECLARATION + DOCTYPE + etree.write({ xml_declaration: false });
115+
fs.writeFileSync(privacyManifestDest, xmlString, 'utf-8');
116+
return;
117+
}
118+
// Set default privacy manifest
119+
const defaultPrivacyManifest = path.join(__dirname, '..', 'templates', 'project', '__PROJECT_NAME__', 'PrivacyInfo.xcprivacy');
120+
const xmlString = fs.readFileSync(defaultPrivacyManifest, 'utf8');
121+
fs.writeFileSync(privacyManifestDest, xmlString, 'utf-8');
122+
}
123+
90124
/**
91125
* Updates config files in project based on app's config.xml and config munge,
92126
* generated by plugins.

package-lock.json

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@
5151
"unorm": "^1.6.0",
5252
"which": "^3.0.1",
5353
"xcode": "^3.0.1",
54-
"xml-escape": "^1.1.0"
54+
"xml-escape": "^1.1.0",
55+
"elementtree": "^0.1.7"
5556
},
5657
"nyc": {
5758
"include": [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Licensed to the Apache Software Foundation (ASF) under one
4+
or more contributor license agreements. See the NOTICE file
5+
distributed with this work for additional information
6+
regarding copyright ownership. The ASF licenses this file
7+
to you under the Apache License, Version 2.0 (the
8+
"License"); you may not use this file except in compliance
9+
with the License. You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing,
14+
software distributed under the License is distributed on an
15+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
KIND, either express or implied. See the License for the
17+
specific language governing permissions and limitations
18+
under the License.
19+
-->
20+
21+
<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">
22+
<name>SampleApp</name>
23+
</widget>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Licensed to the Apache Software Foundation (ASF) under one
4+
or more contributor license agreements. See the NOTICE file
5+
distributed with this work for additional information
6+
regarding copyright ownership. The ASF licenses this file
7+
to you under the Apache License, Version 2.0 (the
8+
"License"); you may not use this file except in compliance
9+
with the License. You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing,
14+
software distributed under the License is distributed on an
15+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
KIND, either express or implied. See the License for the
17+
specific language governing permissions and limitations
18+
under the License.
19+
-->
20+
21+
<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">
22+
<name>SampleApp</name>
23+
<platform name="ios">
24+
<privacy-manifest>
25+
<key>NSPrivacyTracking</key>
26+
<true/>
27+
<key>NSPrivacyAccessedAPITypes</key>
28+
<array/>
29+
<key>NSPrivacyTrackingDomains</key>
30+
<array/>
31+
<key>NSPrivacyCollectedDataTypes</key>
32+
<array>
33+
<dict>
34+
<!-- The value provided by Apple for 'Device ID' data type -->
35+
<key>NSPrivacyCollectedDataType</key>
36+
<string>NSPrivacyCollectedDataTypeDeviceID</string>
37+
38+
<!-- Fingerprint Identification SDK does not link the 'Device ID' with user's identity -->
39+
<key>NSPrivacyCollectedDataTypeLinked</key>
40+
<false/>
41+
42+
<!-- Fingerprint Identification SDK does not use 'Device ID' for tracking -->
43+
<key>NSPrivacyCollectedDataTypeTracking</key>
44+
<false/>
45+
46+
<!-- Fingerprint Identification SDK uses 'Device ID' for App Functionality
47+
(prevent fraud and implement security measures) -->
48+
<key>NSPrivacyCollectedDataTypePurposes</key>
49+
<array>
50+
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
51+
</array>
52+
</dict>
53+
</array>
54+
</privacy-manifest>
55+
</platform>
56+
</widget>

tests/spec/unit/prepare.spec.js

+38
Original file line numberDiff line numberDiff line change
@@ -1504,6 +1504,44 @@ describe('prepare', () => {
15041504
expect(plist.build.calls.mostRecent().args[0].CFBundleDisplayName).toEqual('MyApp');
15051505
});
15061506
});
1507+
it('Test#021 : <privacy-manifest> - should write out the privacy manifest ', () => {
1508+
plist.parse.and.callThrough();
1509+
writeFileSyncSpy.and.callThrough();
1510+
const projectRoot = iosProject;
1511+
const platformProjDir = path.join(projectRoot, 'platforms', 'ios', 'SampleApp');
1512+
const PlatformConfigParser = require('../../../lib/PlatformConfigParser');
1513+
const my_config = new PlatformConfigParser(path.join(FIXTURES, 'prepare', 'privacy-manifest.xml'));
1514+
const privacyManifest = my_config.getPrivacyManifest();
1515+
const overwritePrivacyManifest = prepare.__get__('overwritePrivacyManifest');
1516+
overwritePrivacyManifest(privacyManifest, p.locations);
1517+
const privacyManifestPathDest = path.join(platformProjDir, 'PrivacyInfo.xcprivacy');
1518+
expect(writeFileSyncSpy).toHaveBeenCalledWith(privacyManifestPathDest, jasmine.any(String), 'utf-8');
1519+
const xml = writeFileSyncSpy.calls.all()[0].args[1];
1520+
const json = plist.parse(xml);
1521+
expect(json.NSPrivacyTracking).toBeTrue();
1522+
expect(json.NSPrivacyAccessedAPITypes.length).toBe(0);
1523+
expect(json.NSPrivacyTrackingDomains.length).toBe(0);
1524+
expect(json.NSPrivacyCollectedDataTypes.length).toBe(1);
1525+
});
1526+
it('Test#022 : no <privacy-manifest> - should write out the privacy manifest ', () => {
1527+
plist.parse.and.callThrough();
1528+
writeFileSyncSpy.and.callThrough();
1529+
const projectRoot = iosProject;
1530+
const platformProjDir = path.join(projectRoot, 'platforms', 'ios', 'SampleApp');
1531+
const PlatformConfigParser = require('../../../lib/PlatformConfigParser');
1532+
const my_config = new PlatformConfigParser(path.join(FIXTURES, 'prepare', 'no-privacy-manifest.xml'));
1533+
const privacyManifest = my_config.getPrivacyManifest();
1534+
const overwritePrivacyManifest = prepare.__get__('overwritePrivacyManifest');
1535+
overwritePrivacyManifest(privacyManifest, p.locations);
1536+
const privacyManifestPathDest = path.join(platformProjDir, 'PrivacyInfo.xcprivacy');
1537+
expect(writeFileSyncSpy).toHaveBeenCalledWith(privacyManifestPathDest, jasmine.any(String), 'utf-8');
1538+
const xml = writeFileSyncSpy.calls.all()[0].args[1];
1539+
const json = plist.parse(xml);
1540+
expect(json.NSPrivacyTracking).toBeFalse();
1541+
expect(json.NSPrivacyAccessedAPITypes.length).toBe(0);
1542+
expect(json.NSPrivacyTrackingDomains.length).toBe(0);
1543+
expect(json.NSPrivacyCollectedDataTypes.length).toBe(0);
1544+
});
15071545
});
15081546

15091547
describe('<resource-file> tests', () => {

0 commit comments

Comments
 (0)