react-native-intune 0.1.1 → 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/RNIntune.podspec +3 -3
- package/android/build.gradle +46 -25
- package/android/src/main/java/com/reactnativeintune/RNIntunePackage.kt +11 -6
- package/android/src/main/java/com/reactnativeintune/RNIntuneResetJournal.kt +1 -1
- package/ios/RNIntunePlistGuard.m +1 -1
- package/ios/RNIntuneResetJournal.m +1 -1
- package/lib/module/api/tokenProvider.js +3 -0
- package/lib/module/api/tokenProvider.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/internal/decode.js +23 -2
- package/lib/module/internal/decode.js.map +1 -1
- package/lib/module/types.js +37 -6
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/src/api/tokenProvider.d.ts +3 -0
- package/lib/typescript/src/api/tokenProvider.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/internal/decode.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +61 -11
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +7 -8
- package/plugin/transforms.js +155 -2
- package/plugin/withIntune.js +27 -0
- package/scripts/doctor.mjs +7 -3
- package/scripts/lib/checks.mjs +483 -27
- package/scripts/lib/edits.mjs +329 -0
- package/scripts/lib/project.mjs +13 -0
- package/scripts/setup.mjs +59 -9
- package/sdk-lock.json +9 -8
- package/sdk-versions.json +125 -28
- package/src/api/tokenProvider.ts +3 -0
- package/src/index.ts +1 -0
- package/src/internal/decode.ts +27 -8
- package/src/types.ts +66 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-intune",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-rc.1",
|
|
4
4
|
"description": "Microsoft Intune app protection policies (MAM) for React Native",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"app.plugin.js",
|
|
23
23
|
"plugin",
|
|
24
24
|
"scripts",
|
|
25
|
-
"!scripts/verify
|
|
25
|
+
"!scripts/verify",
|
|
26
26
|
"sdk-versions.json",
|
|
27
27
|
"sdk-lock.json",
|
|
28
28
|
"!vendor",
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
],
|
|
45
45
|
"scripts": {
|
|
46
46
|
"fetch-sdks": "node scripts/fetch-sdks.mjs",
|
|
47
|
-
"verify:expo": "node scripts/verify
|
|
47
|
+
"verify:expo": "node scripts/verify/expo.mjs",
|
|
48
|
+
"verify:bare": "node scripts/verify/bare.mjs",
|
|
48
49
|
"postinstall": "node scripts/fetch-sdks.mjs --soft --quiet",
|
|
49
50
|
"example": "yarn workspace react-native-intune-example",
|
|
50
51
|
"clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
|
|
@@ -100,9 +101,9 @@
|
|
|
100
101
|
"typescript": "^6.0.3"
|
|
101
102
|
},
|
|
102
103
|
"peerDependencies": {
|
|
103
|
-
"@expo/config-plugins": ">=
|
|
104
|
+
"@expo/config-plugins": ">=54.0.0",
|
|
104
105
|
"react": "*",
|
|
105
|
-
"react-native": "
|
|
106
|
+
"react-native": ">=0.74.0"
|
|
106
107
|
},
|
|
107
108
|
"workspaces": [
|
|
108
109
|
"example",
|
|
@@ -197,9 +198,7 @@
|
|
|
197
198
|
],
|
|
198
199
|
"version": "0.63.0"
|
|
199
200
|
},
|
|
200
|
-
"bin":
|
|
201
|
-
"react-native-intune": "scripts/cli.mjs"
|
|
202
|
-
},
|
|
201
|
+
"bin": "scripts/cli.mjs",
|
|
203
202
|
"engines": {
|
|
204
203
|
"node": ">=18"
|
|
205
204
|
},
|
package/plugin/transforms.js
CHANGED
|
@@ -27,9 +27,20 @@ intunemam {
|
|
|
27
27
|
}
|
|
28
28
|
`;
|
|
29
29
|
|
|
30
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Applies the MAM plugin to android/app/build.gradle.
|
|
32
|
+
*
|
|
33
|
+
* The guard looks for an actual application at the start of a line, not the plugin id
|
|
34
|
+
* anywhere: commenting the apply out is the first thing anyone does when the bytecode
|
|
35
|
+
* rewrite breaks a library, and a guard that read the comment as applied would leave
|
|
36
|
+
* the app unprotected while `setup` reported success.
|
|
37
|
+
*/
|
|
31
38
|
function withMamPluginApplied(contents) {
|
|
32
|
-
if (
|
|
39
|
+
if (
|
|
40
|
+
/^[ \t]*(?:apply\s+plugin\s*:\s*|id\s*\(?\s*)["']com\.microsoft\.intune\.mam["']/m.test(
|
|
41
|
+
contents
|
|
42
|
+
)
|
|
43
|
+
) {
|
|
33
44
|
return contents;
|
|
34
45
|
}
|
|
35
46
|
return `${contents.trimEnd()}\n${MAM_PLUGIN_BLOCK}`;
|
|
@@ -187,9 +198,151 @@ function addImports(contents, imports) {
|
|
|
187
198
|
return `${contents.slice(0, at)}\n${missing.join('\n')}${contents.slice(at)}`;
|
|
188
199
|
}
|
|
189
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Forwards the sign-in redirect to MSAL from the generated AppDelegate.
|
|
203
|
+
*
|
|
204
|
+
* The URL type alone is not enough. When the broker — Authenticator or Company Portal —
|
|
205
|
+
* finishes, it opens the app with the result, and unless the AppDelegate hands that URL
|
|
206
|
+
* to MSAL it arrives and is dropped: the sign-in never completes and nothing reports
|
|
207
|
+
* why. Both halves were needed on a device, and the bare example has always had this
|
|
208
|
+
* one; the Expo plugin wrote the URL type and not the forward.
|
|
209
|
+
*
|
|
210
|
+
* MSAL goes first in the `||` chain, so a redirect meant for it is consumed before
|
|
211
|
+
* Linking sees it; any other URL falls through unchanged.
|
|
212
|
+
*
|
|
213
|
+
* Returns null when there is no `open url` handler to extend — a warning beats an
|
|
214
|
+
* AppDelegate edited in the wrong place.
|
|
215
|
+
*/
|
|
216
|
+
function withMsalResponseHandler(contents, language) {
|
|
217
|
+
if (/handleMSALResponse/.test(contents)) {
|
|
218
|
+
return contents;
|
|
219
|
+
}
|
|
220
|
+
const isSwift = language === 'swift';
|
|
221
|
+
|
|
222
|
+
const handler = isSwift
|
|
223
|
+
? /(func\s+application\s*\(\s*_\s+\w+\s*:\s*UIApplication\s*,\s*open\s+url\s*:\s*URL\s*,\s*options\s*:[^)]*\)\s*->\s*Bool\s*\{\s*\n(\s*)return\s+)/
|
|
224
|
+
: /(-\s*\(BOOL\)\s*application\s*:\s*\(UIApplication\s*\*\)\s*\w+\s+openURL\s*:\s*\(NSURL\s*\*\)\s*url\s+options\s*:[^{]*\{\s*\n(\s*)return\s+)/;
|
|
225
|
+
if (!handler.test(contents)) {
|
|
226
|
+
return withNewMsalResponseHandler(contents, isSwift);
|
|
227
|
+
}
|
|
228
|
+
const forward = isSwift
|
|
229
|
+
? 'MSALPublicClientApplication.handleMSALResponse(url, sourceApplication: options[.sourceApplication] as? String) || '
|
|
230
|
+
: '[MSALPublicClientApplication handleMSALResponse:url sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]] || ';
|
|
231
|
+
let next = contents.replace(handler, `$1${forward}`);
|
|
232
|
+
|
|
233
|
+
const importLine = isSwift ? 'import MSAL' : '#import <MSAL/MSAL.h>';
|
|
234
|
+
const imports = isSwift
|
|
235
|
+
? /^(?:\w+\s+)?import\s+\w+.*$/gm
|
|
236
|
+
: /^#import\s+.*$/gm;
|
|
237
|
+
const lastImport = [...next.matchAll(imports)].pop();
|
|
238
|
+
if (!lastImport) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
const at = lastImport.index + lastImport[0].length;
|
|
242
|
+
next = `${next.slice(0, at)}\n${importLine}${next.slice(at)}`;
|
|
243
|
+
return next;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* React Native's own templates have no open-URL handler at all — only Expo's does — so
|
|
248
|
+
* for them the forward is a whole method. It goes at the end of the AppDelegate class,
|
|
249
|
+
* found by matching braces from the class declaration; string literals and comments are
|
|
250
|
+
* skipped so a brace in either cannot end the class early.
|
|
251
|
+
*
|
|
252
|
+
* No `override`: in these templates the class is a UIResponder conforming to
|
|
253
|
+
* UIApplicationDelegate, and the method is the protocol's, not a superclass's.
|
|
254
|
+
*/
|
|
255
|
+
function withNewMsalResponseHandler(contents, isSwift) {
|
|
256
|
+
const importLine = isSwift ? 'import MSAL' : '#import <MSAL/MSAL.h>';
|
|
257
|
+
let next = contents;
|
|
258
|
+
|
|
259
|
+
if (isSwift) {
|
|
260
|
+
const declaration = /class\s+AppDelegate\b[^{]*\{/.exec(next);
|
|
261
|
+
if (!declaration) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
const end = matchingBrace(
|
|
265
|
+
next,
|
|
266
|
+
declaration.index + declaration[0].length - 1
|
|
267
|
+
);
|
|
268
|
+
if (end === null) {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
const method = `
|
|
272
|
+
/// Hands the sign-in result back to MSAL. Without it the broker's reply reaches the app
|
|
273
|
+
/// and is dropped, and sign-in never completes. Added by react-native-intune setup.
|
|
274
|
+
func application(
|
|
275
|
+
_ app: UIApplication,
|
|
276
|
+
open url: URL,
|
|
277
|
+
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
|
|
278
|
+
) -> Bool {
|
|
279
|
+
MSALPublicClientApplication.handleMSALResponse(
|
|
280
|
+
url,
|
|
281
|
+
sourceApplication: options[.sourceApplication] as? String
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
`;
|
|
285
|
+
next = `${next.slice(0, end).replace(/\s*$/, '\n')}${method}${next.slice(end)}`;
|
|
286
|
+
} else {
|
|
287
|
+
const implementation = /@implementation\s+AppDelegate\b[^\n]*\n/.exec(next);
|
|
288
|
+
if (!implementation) {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
const at = implementation.index + implementation[0].length;
|
|
292
|
+
const method = `
|
|
293
|
+
// Hands the sign-in result back to MSAL. Without it the broker's reply reaches the app and
|
|
294
|
+
// is dropped, and sign-in never completes. Added by react-native-intune setup.
|
|
295
|
+
- (BOOL)application:(UIApplication *)application
|
|
296
|
+
openURL:(NSURL *)url
|
|
297
|
+
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options
|
|
298
|
+
{
|
|
299
|
+
return [MSALPublicClientApplication handleMSALResponse:url
|
|
300
|
+
sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]];
|
|
301
|
+
}
|
|
302
|
+
`;
|
|
303
|
+
next = `${next.slice(0, at)}${method}${next.slice(at)}`;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const imports = isSwift
|
|
307
|
+
? /^(?:\w+\s+)?import\s+\w+.*$/gm
|
|
308
|
+
: /^#import\s+.*$/gm;
|
|
309
|
+
const lastImport = [...next.matchAll(imports)].pop();
|
|
310
|
+
if (!lastImport) {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
const at = lastImport.index + lastImport[0].length;
|
|
314
|
+
return `${next.slice(0, at)}\n${importLine}${next.slice(at)}`;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** The index of the brace closing the one at `open`, skipping strings and comments. */
|
|
318
|
+
function matchingBrace(src, open) {
|
|
319
|
+
let depth = 0;
|
|
320
|
+
for (let i = open; i < src.length; i += 1) {
|
|
321
|
+
const c = src[i];
|
|
322
|
+
if (c === '/' && src[i + 1] === '/') {
|
|
323
|
+
i = src.indexOf('\n', i);
|
|
324
|
+
if (i === -1) return null;
|
|
325
|
+
} else if (c === '/' && src[i + 1] === '*') {
|
|
326
|
+
i = src.indexOf('*/', i + 2) + 1;
|
|
327
|
+
if (i === 0) return null;
|
|
328
|
+
} else if (c === '"') {
|
|
329
|
+
for (i += 1; i < src.length && src[i] !== '"'; i += 1) {
|
|
330
|
+
if (src[i] === '\\') i += 1;
|
|
331
|
+
}
|
|
332
|
+
} else if (c === '{') {
|
|
333
|
+
depth += 1;
|
|
334
|
+
} else if (c === '}') {
|
|
335
|
+
depth -= 1;
|
|
336
|
+
if (depth === 0) return i;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
|
|
190
342
|
module.exports = {
|
|
191
343
|
MARKER,
|
|
192
344
|
withMamPluginApplied,
|
|
193
345
|
withMamClasspath,
|
|
194
346
|
withMamApplication,
|
|
347
|
+
withMsalResponseHandler,
|
|
195
348
|
};
|
package/plugin/withIntune.js
CHANGED
|
@@ -37,6 +37,7 @@ const {
|
|
|
37
37
|
createRunOncePlugin,
|
|
38
38
|
withAndroidManifest,
|
|
39
39
|
withAppBuildGradle,
|
|
40
|
+
withAppDelegate,
|
|
40
41
|
withEntitlementsPlist,
|
|
41
42
|
withInfoPlist,
|
|
42
43
|
withMainApplication,
|
|
@@ -48,6 +49,7 @@ const {
|
|
|
48
49
|
withMamApplication,
|
|
49
50
|
withMamClasspath,
|
|
50
51
|
withMamPluginApplied,
|
|
52
|
+
withMsalResponseHandler,
|
|
51
53
|
} = require('./transforms');
|
|
52
54
|
|
|
53
55
|
const pkg = require('../package.json');
|
|
@@ -169,6 +171,30 @@ const withMsalUrlScheme = (config, { maxFileProtectionLevel, keychainGroup }) =>
|
|
|
169
171
|
return c;
|
|
170
172
|
});
|
|
171
173
|
|
|
174
|
+
/**
|
|
175
|
+
* The other half of the URL type. Without it the broker's redirect reaches the app and
|
|
176
|
+
* is dropped, and sign-in never completes — see withMsalResponseHandler.
|
|
177
|
+
*/
|
|
178
|
+
const withMsalRedirectForward = (config) =>
|
|
179
|
+
withAppDelegate(config, (c) => {
|
|
180
|
+
const result = withMsalResponseHandler(
|
|
181
|
+
c.modResults.contents,
|
|
182
|
+
c.modResults.language
|
|
183
|
+
);
|
|
184
|
+
if (result === null) {
|
|
185
|
+
WarningAggregator.addWarningIOS(
|
|
186
|
+
MARKER,
|
|
187
|
+
'Could not forward the MSAL redirect in AppDelegate — no open-URL handler ' +
|
|
188
|
+
'was recognised. Sign-in through Authenticator or Company Portal will not ' +
|
|
189
|
+
'return to the app until this is done by hand. Run ' +
|
|
190
|
+
'`npx react-native-intune doctor` for the exact change.'
|
|
191
|
+
);
|
|
192
|
+
return c;
|
|
193
|
+
}
|
|
194
|
+
c.modResults.contents = result;
|
|
195
|
+
return c;
|
|
196
|
+
});
|
|
197
|
+
|
|
172
198
|
// ---------------------------------------------------------------- android
|
|
173
199
|
|
|
174
200
|
const withBrokerQueries = (config) =>
|
|
@@ -305,6 +331,7 @@ const withIntune = (config, props = {}) => {
|
|
|
305
331
|
let next = config;
|
|
306
332
|
next = withKeychainGroups(next, { keychainGroup });
|
|
307
333
|
next = withMsalUrlScheme(next, { maxFileProtectionLevel, keychainGroup });
|
|
334
|
+
next = withMsalRedirectForward(next);
|
|
308
335
|
next = withBrokerQueries(next);
|
|
309
336
|
next = withRedirectActivity(next, { androidSignatureHash });
|
|
310
337
|
next = withGradle(next);
|
package/scripts/doctor.mjs
CHANGED
|
@@ -116,7 +116,8 @@ function main() {
|
|
|
116
116
|
2
|
|
117
117
|
) + '\n'
|
|
118
118
|
);
|
|
119
|
-
process.
|
|
119
|
+
process.exitCode = s.blocking.length > 0 ? 1 : 0;
|
|
120
|
+
return;
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
process.stdout.write(`\nreact-native-intune doctor\n${project.root}\n`);
|
|
@@ -141,7 +142,7 @@ function main() {
|
|
|
141
142
|
`licensed for Intune, that an App Protection Policy targets this app, and that\n` +
|
|
142
143
|
`a broker is installed on the device. Enrollment needs all three.\n\n`
|
|
143
144
|
);
|
|
144
|
-
|
|
145
|
+
return;
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
if (s.silent.length > 0) {
|
|
@@ -182,7 +183,10 @@ function main() {
|
|
|
182
183
|
}
|
|
183
184
|
|
|
184
185
|
process.stdout.write('\n');
|
|
185
|
-
process.exit(
|
|
186
|
+
// An exit code, not process.exit(): on macOS a write to a pipe is asynchronous, and
|
|
187
|
+
// exiting straight after a long report cut it off mid-way whenever doctor's output was
|
|
188
|
+
// piped — into a CI log, a file, or `| less`.
|
|
189
|
+
process.exitCode = s.blocking.length > 0 ? 1 : 0;
|
|
186
190
|
}
|
|
187
191
|
|
|
188
192
|
/**
|