react-native-intune 0.1.0-alpha.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/LICENSE +27 -0
- package/NOTICE +49 -0
- package/README.md +164 -0
- package/RNIntune.podspec +69 -0
- package/android/build.gradle +152 -0
- package/android/consumer-rules.pro +28 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/reactnativeintune/RNIntuneAuth.kt +330 -0
- package/android/src/main/java/com/reactnativeintune/RNIntuneAuthCallback.kt +195 -0
- package/android/src/main/java/com/reactnativeintune/RNIntuneConfig.kt +63 -0
- package/android/src/main/java/com/reactnativeintune/RNIntuneEventQueue.kt +50 -0
- package/android/src/main/java/com/reactnativeintune/RNIntuneModule.kt +752 -0
- package/android/src/main/java/com/reactnativeintune/RNIntuneMsalConfig.kt +78 -0
- package/android/src/main/java/com/reactnativeintune/RNIntuneNotifications.kt +187 -0
- package/android/src/main/java/com/reactnativeintune/RNIntunePackage.kt +33 -0
- package/android/src/main/java/com/reactnativeintune/RNIntunePolicy.kt +108 -0
- package/android/src/main/java/com/reactnativeintune/RNIntuneReset.kt +85 -0
- package/android/src/main/java/com/reactnativeintune/RNIntuneResetJournal.kt +140 -0
- package/app.plugin.js +10 -0
- package/ios/RNIntune.h +20 -0
- package/ios/RNIntune.mm +467 -0
- package/ios/RNIntuneAuth.h +121 -0
- package/ios/RNIntuneAuth.m +390 -0
- package/ios/RNIntuneCore.h +150 -0
- package/ios/RNIntuneCore.m +535 -0
- package/ios/RNIntuneDelegates.h +70 -0
- package/ios/RNIntuneDelegates.m +268 -0
- package/ios/RNIntunePendingRequests.h +57 -0
- package/ios/RNIntunePendingRequests.m +230 -0
- package/ios/RNIntunePlistGuard.h +37 -0
- package/ios/RNIntunePlistGuard.m +147 -0
- package/ios/RNIntunePolicy.h +25 -0
- package/ios/RNIntunePolicy.m +112 -0
- package/ios/RNIntuneReset.h +34 -0
- package/ios/RNIntuneReset.m +93 -0
- package/ios/RNIntuneResetJournal.h +69 -0
- package/ios/RNIntuneResetJournal.m +106 -0
- package/lib/module/NativeIntune.js +20 -0
- package/lib/module/NativeIntune.js.map +1 -0
- package/lib/module/api/auth.js +66 -0
- package/lib/module/api/auth.js.map +1 -0
- package/lib/module/api/enrollment.js +104 -0
- package/lib/module/api/enrollment.js.map +1 -0
- package/lib/module/api/events.js +50 -0
- package/lib/module/api/events.js.map +1 -0
- package/lib/module/api/lifecycle.js +73 -0
- package/lib/module/api/lifecycle.js.map +1 -0
- package/lib/module/api/policy.js +23 -0
- package/lib/module/api/policy.js.map +1 -0
- package/lib/module/api/tokenProvider.js +80 -0
- package/lib/module/api/tokenProvider.js.map +1 -0
- package/lib/module/index.js +82 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/internal/decode.js +153 -0
- package/lib/module/internal/decode.js.map +1 -0
- package/lib/module/internal/emitter.js +15 -0
- package/lib/module/internal/emitter.js.map +1 -0
- package/lib/module/internal/errors.js +32 -0
- package/lib/module/internal/errors.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +143 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeIntune.d.ts +120 -0
- package/lib/typescript/src/NativeIntune.d.ts.map +1 -0
- package/lib/typescript/src/api/auth.d.ts +37 -0
- package/lib/typescript/src/api/auth.d.ts.map +1 -0
- package/lib/typescript/src/api/enrollment.d.ts +54 -0
- package/lib/typescript/src/api/enrollment.d.ts.map +1 -0
- package/lib/typescript/src/api/events.d.ts +24 -0
- package/lib/typescript/src/api/events.d.ts.map +1 -0
- package/lib/typescript/src/api/lifecycle.d.ts +33 -0
- package/lib/typescript/src/api/lifecycle.d.ts.map +1 -0
- package/lib/typescript/src/api/policy.d.ts +14 -0
- package/lib/typescript/src/api/policy.d.ts.map +1 -0
- package/lib/typescript/src/api/tokenProvider.d.ts +18 -0
- package/lib/typescript/src/api/tokenProvider.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +67 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/internal/decode.d.ts +48 -0
- package/lib/typescript/src/internal/decode.d.ts.map +1 -0
- package/lib/typescript/src/internal/emitter.d.ts +11 -0
- package/lib/typescript/src/internal/emitter.d.ts.map +1 -0
- package/lib/typescript/src/internal/errors.d.ts +22 -0
- package/lib/typescript/src/internal/errors.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +317 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +204 -0
- package/plugin/transforms.js +153 -0
- package/plugin/withIntune.js +320 -0
- package/scripts/cli.mjs +72 -0
- package/scripts/doctor.mjs +232 -0
- package/scripts/fetch-sdks.mjs +456 -0
- package/scripts/lib/checks.mjs +720 -0
- package/scripts/lib/project.mjs +196 -0
- package/scripts/setup.mjs +233 -0
- package/sdk-lock.json +27 -0
- package/sdk-versions.json +70 -0
- package/src/NativeIntune.ts +130 -0
- package/src/api/auth.ts +73 -0
- package/src/api/enrollment.ts +112 -0
- package/src/api/events.ts +87 -0
- package/src/api/lifecycle.ts +75 -0
- package/src/api/policy.ts +22 -0
- package/src/api/tokenProvider.ts +80 -0
- package/src/index.ts +127 -0
- package/src/internal/decode.ts +191 -0
- package/src/internal/emitter.ts +14 -0
- package/src/internal/errors.ts +43 -0
- package/src/types.ts +381 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright (c) 2026 react-native-intune contributors. MIT.
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
#import "RNIntuneAuth.h"
|
|
6
|
+
#import "RNIntuneCore.h"
|
|
7
|
+
|
|
8
|
+
#import <MSAL/MSAL.h>
|
|
9
|
+
|
|
10
|
+
/// The scope a sign-in falls back to. See the note on `defaultScopes` in the header for
|
|
11
|
+
/// why this cannot be `openid profile offline_access`.
|
|
12
|
+
static NSString *const RNIntuneDefaultScope = @"https://graph.microsoft.com/User.Read";
|
|
13
|
+
|
|
14
|
+
@implementation RNIntuneAuth {
|
|
15
|
+
MSALPublicClientApplication *_client;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
+ (NSArray<NSString *> *)defaultScopes
|
|
19
|
+
{
|
|
20
|
+
return @[ RNIntuneDefaultScope ];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#pragma mark - Configuration
|
|
24
|
+
|
|
25
|
+
- (BOOL)configureWithClientId:(NSString *)clientId
|
|
26
|
+
authority:(NSString *)authority
|
|
27
|
+
redirectUri:(NSString *)redirectUri
|
|
28
|
+
keychainGroup:(NSString *)keychainGroup
|
|
29
|
+
error:(NSError **)error
|
|
30
|
+
{
|
|
31
|
+
NSURL *authorityUrl = [NSURL URLWithString:authority];
|
|
32
|
+
if (authorityUrl == nil) {
|
|
33
|
+
if (error != NULL) {
|
|
34
|
+
*error = [RNIntuneCore
|
|
35
|
+
errorWithCode:RNIntuneErrorNative
|
|
36
|
+
message:@"The authority is not a valid URL. Expected something like "
|
|
37
|
+
@"https://login.microsoftonline.com/<tenantId>."];
|
|
38
|
+
}
|
|
39
|
+
return NO;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
NSError *authorityError = nil;
|
|
43
|
+
MSALAADAuthority *aadAuthority = [[MSALAADAuthority alloc] initWithURL:authorityUrl
|
|
44
|
+
error:&authorityError];
|
|
45
|
+
if (aadAuthority == nil) {
|
|
46
|
+
if (error != NULL) {
|
|
47
|
+
*error = [RNIntuneCore
|
|
48
|
+
errorWithCode:RNIntuneErrorNative
|
|
49
|
+
message:[NSString stringWithFormat:@"MSAL rejected the authority: %@",
|
|
50
|
+
authorityError
|
|
51
|
+
.localizedDescription
|
|
52
|
+
?: @"unknown reason"]];
|
|
53
|
+
}
|
|
54
|
+
return NO;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
MSALPublicClientApplicationConfig *config =
|
|
58
|
+
[[MSALPublicClientApplicationConfig alloc] initWithClientId:clientId
|
|
59
|
+
redirectUri:redirectUri
|
|
60
|
+
authority:aadAuthority];
|
|
61
|
+
|
|
62
|
+
// Only when the host plist asked for one. MSAL's own default is already
|
|
63
|
+
// `com.microsoft.adalcache`, which is what the Intune SDK looks in — writing that
|
|
64
|
+
// constant here by hand would just be a second place for it to drift (§5.1.4).
|
|
65
|
+
if (keychainGroup.length > 0) {
|
|
66
|
+
config.cacheConfig.keychainSharingGroup = keychainGroup;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
NSError *clientError = nil;
|
|
70
|
+
MSALPublicClientApplication *client =
|
|
71
|
+
[[MSALPublicClientApplication alloc] initWithConfiguration:config error:&clientError];
|
|
72
|
+
if (client == nil) {
|
|
73
|
+
if (error != NULL) {
|
|
74
|
+
// The usual cause is a redirect URI that does not match the required
|
|
75
|
+
// `msauth.<bundle id>` format, or a missing keychain-sharing entitlement — both
|
|
76
|
+
// were real failures on device before the example app was fixed (§5.1.2).
|
|
77
|
+
*error = [RNIntuneCore
|
|
78
|
+
errorWithCode:RNIntuneErrorNative
|
|
79
|
+
message:[NSString
|
|
80
|
+
stringWithFormat:@"MSAL could not be initialised: %@",
|
|
81
|
+
clientError.localizedDescription
|
|
82
|
+
?: @"unknown reason"]];
|
|
83
|
+
}
|
|
84
|
+
return NO;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
_client = client;
|
|
88
|
+
return YES;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
- (void)invalidate
|
|
92
|
+
{
|
|
93
|
+
_client = nil;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
- (BOOL)isConfigured
|
|
97
|
+
{
|
|
98
|
+
return _client != nil;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#pragma mark - Sign-in
|
|
102
|
+
|
|
103
|
+
- (void)signInWithScopes:(NSArray<NSString *> *)scopes
|
|
104
|
+
loginHint:(NSString *)loginHint
|
|
105
|
+
prompt:(NSString *)prompt
|
|
106
|
+
presentingController:(UIViewController *)controller
|
|
107
|
+
completion:(RNIntuneAuthCompletion)completion
|
|
108
|
+
{
|
|
109
|
+
MSALPublicClientApplication *client = _client;
|
|
110
|
+
if (client == nil) {
|
|
111
|
+
completion(nil, [self notConfiguredError]);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
MSALWebviewParameters *webParams =
|
|
116
|
+
[[MSALWebviewParameters alloc] initWithAuthPresentationViewController:controller];
|
|
117
|
+
|
|
118
|
+
MSALInteractiveTokenParameters *params =
|
|
119
|
+
[[MSALInteractiveTokenParameters alloc] initWithScopes:[self resolveScopes:scopes]
|
|
120
|
+
webviewParameters:webParams];
|
|
121
|
+
params.promptType = [self promptTypeFromString:prompt];
|
|
122
|
+
if (loginHint.length > 0) {
|
|
123
|
+
params.loginHint = loginHint;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// MSAL requires the interactive call on the main thread, and it presents UI.
|
|
127
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
128
|
+
[client acquireTokenWithParameters:params
|
|
129
|
+
completionBlock:^(MSALResult *result, NSError *error) {
|
|
130
|
+
[self complete:completion result:result error:error];
|
|
131
|
+
}];
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
- (void)signInSilentWithScopes:(NSArray<NSString *> *)scopes
|
|
136
|
+
accountId:(NSString *)accountId
|
|
137
|
+
completion:(RNIntuneAuthCompletion)completion
|
|
138
|
+
{
|
|
139
|
+
[self acquireTokenWithScopes:scopes
|
|
140
|
+
accountId:accountId
|
|
141
|
+
forceRefresh:NO
|
|
142
|
+
completion:completion];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
- (void)acquireTokenWithScopes:(NSArray<NSString *> *)scopes
|
|
146
|
+
accountId:(NSString *)accountId
|
|
147
|
+
forceRefresh:(BOOL)forceRefresh
|
|
148
|
+
completion:(RNIntuneAuthCompletion)completion
|
|
149
|
+
{
|
|
150
|
+
MSALPublicClientApplication *client = _client;
|
|
151
|
+
if (client == nil) {
|
|
152
|
+
completion(nil, [self notConfiguredError]);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
NSError *lookupError = nil;
|
|
157
|
+
MSALAccount *account = [self accountForId:accountId error:&lookupError];
|
|
158
|
+
if (account == nil) {
|
|
159
|
+
// Not a fault. With no cached account there is nothing to refresh silently, and the
|
|
160
|
+
// caller's next move is the same as for an expired token: sign in interactively.
|
|
161
|
+
completion(nil,
|
|
162
|
+
lookupError ?: [RNIntuneCore
|
|
163
|
+
errorWithCode:RNIntuneErrorInteractionRequired
|
|
164
|
+
message:@"No signed-in account is cached. Call "
|
|
165
|
+
@"signIn to acquire one."]);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
MSALSilentTokenParameters *params =
|
|
170
|
+
[[MSALSilentTokenParameters alloc] initWithScopes:[self resolveScopes:scopes]
|
|
171
|
+
account:account];
|
|
172
|
+
params.forceRefresh = forceRefresh;
|
|
173
|
+
|
|
174
|
+
[client acquireTokenSilentWithParameters:params
|
|
175
|
+
completionBlock:^(MSALResult *result, NSError *error) {
|
|
176
|
+
[self complete:completion result:result error:error];
|
|
177
|
+
}];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#pragma mark - Accounts
|
|
181
|
+
|
|
182
|
+
- (NSArray<NSDictionary<NSString *, id> *> *)accountsWithError:(NSError **)error
|
|
183
|
+
{
|
|
184
|
+
MSALPublicClientApplication *client = _client;
|
|
185
|
+
if (client == nil) {
|
|
186
|
+
if (error != NULL) {
|
|
187
|
+
*error = [self notConfiguredError];
|
|
188
|
+
}
|
|
189
|
+
return nil;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
NSError *msalError = nil;
|
|
193
|
+
NSArray<MSALAccount *> *accounts = [client allAccounts:&msalError];
|
|
194
|
+
if (accounts == nil) {
|
|
195
|
+
if (error != NULL) {
|
|
196
|
+
*error = [RNIntuneCore errorWithCode:RNIntuneErrorNative
|
|
197
|
+
message:msalError.localizedDescription
|
|
198
|
+
?: @"MSAL could not list accounts."];
|
|
199
|
+
}
|
|
200
|
+
return nil;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
NSMutableArray<NSDictionary<NSString *, id> *> *out =
|
|
204
|
+
[NSMutableArray arrayWithCapacity:accounts.count];
|
|
205
|
+
for (MSALAccount *account in accounts) {
|
|
206
|
+
[out addObject:@{
|
|
207
|
+
@"accountId" : [self accountIdFor:account] ?: @"",
|
|
208
|
+
@"tenantId" : account.homeAccountId.tenantId ?: @"",
|
|
209
|
+
@"username" : account.username ?: @"",
|
|
210
|
+
}];
|
|
211
|
+
}
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
- (BOOL)removeAccountId:(NSString *)accountId error:(NSError **)error
|
|
216
|
+
{
|
|
217
|
+
MSALPublicClientApplication *client = _client;
|
|
218
|
+
if (client == nil) {
|
|
219
|
+
if (error != NULL) {
|
|
220
|
+
*error = [self notConfiguredError];
|
|
221
|
+
}
|
|
222
|
+
return NO;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
NSError *lookupError = nil;
|
|
226
|
+
MSALAccount *account = [self accountForId:accountId error:&lookupError];
|
|
227
|
+
if (account == nil) {
|
|
228
|
+
// Already gone is success. `reset` runs this after the SDK has unregistered, and it
|
|
229
|
+
// must be safe to retry from the journal on the next launch (SPEC §7).
|
|
230
|
+
if (lookupError != nil && error != NULL) {
|
|
231
|
+
*error = lookupError;
|
|
232
|
+
return NO;
|
|
233
|
+
}
|
|
234
|
+
return YES;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
NSError *removeError = nil;
|
|
238
|
+
if (![client removeAccount:account error:&removeError]) {
|
|
239
|
+
if (error != NULL) {
|
|
240
|
+
*error = [RNIntuneCore errorWithCode:RNIntuneErrorNative
|
|
241
|
+
message:removeError.localizedDescription
|
|
242
|
+
?: @"MSAL could not remove the account."];
|
|
243
|
+
}
|
|
244
|
+
return NO;
|
|
245
|
+
}
|
|
246
|
+
return YES;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
- (void)removeAllAccounts
|
|
250
|
+
{
|
|
251
|
+
MSALPublicClientApplication *client = _client;
|
|
252
|
+
if (client == nil) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
NSArray<MSALAccount *> *accounts = [client allAccounts:NULL];
|
|
257
|
+
for (MSALAccount *account in accounts) {
|
|
258
|
+
// Errors are deliberately not propagated: this runs inside the reset sequence, where
|
|
259
|
+
// the journal's verification decides whether the reset took, not this loop.
|
|
260
|
+
[client removeAccount:account error:NULL];
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
#pragma mark - Helpers
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Finds the account by the id this module hands out.
|
|
268
|
+
*
|
|
269
|
+
* That id is the Entra object ID, not MSAL's own `identifier` (which is
|
|
270
|
+
* `<objectId>.<tenantId>`), because the Intune SDK's `registerAndEnrollAccountId:` wants
|
|
271
|
+
* the object ID — one value has to serve both, so the narrower one wins. Matching is done
|
|
272
|
+
* over `allAccounts` rather than `accountForIdentifier:` for the same reason: that lookup
|
|
273
|
+
* takes MSAL's composite identifier, which is not what we store.
|
|
274
|
+
*/
|
|
275
|
+
- (MSALAccount *)accountForId:(NSString *)accountId error:(NSError **)error
|
|
276
|
+
{
|
|
277
|
+
MSALPublicClientApplication *client = _client;
|
|
278
|
+
NSError *msalError = nil;
|
|
279
|
+
NSArray<MSALAccount *> *accounts = [client allAccounts:&msalError];
|
|
280
|
+
if (accounts == nil) {
|
|
281
|
+
if (error != NULL) {
|
|
282
|
+
*error = [RNIntuneCore errorWithCode:RNIntuneErrorNative
|
|
283
|
+
message:msalError.localizedDescription
|
|
284
|
+
?: @"MSAL could not list accounts."];
|
|
285
|
+
}
|
|
286
|
+
return nil;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (accountId.length == 0) {
|
|
290
|
+
// Single identity is decided (SPEC §9), so "the account" is unambiguous when there is
|
|
291
|
+
// one. Callers that pass nothing get it rather than an argument error.
|
|
292
|
+
return accounts.firstObject;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
for (MSALAccount *account in accounts) {
|
|
296
|
+
if ([[self accountIdFor:account] isEqualToString:accountId]) {
|
|
297
|
+
return account;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return nil;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
- (NSString *)accountIdFor:(MSALAccount *)account
|
|
304
|
+
{
|
|
305
|
+
return account.homeAccountId.objectId;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
- (NSArray<NSString *> *)resolveScopes:(NSArray<NSString *> *)scopes
|
|
309
|
+
{
|
|
310
|
+
return scopes.count > 0 ? scopes : RNIntuneAuth.defaultScopes;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
- (MSALPromptType)promptTypeFromString:(NSString *)prompt
|
|
314
|
+
{
|
|
315
|
+
if ([prompt isEqualToString:@"selectAccount"]) {
|
|
316
|
+
return MSALPromptTypeSelectAccount;
|
|
317
|
+
}
|
|
318
|
+
if ([prompt isEqualToString:@"login"]) {
|
|
319
|
+
return MSALPromptTypeLogin;
|
|
320
|
+
}
|
|
321
|
+
if ([prompt isEqualToString:@"consent"]) {
|
|
322
|
+
return MSALPromptTypeConsent;
|
|
323
|
+
}
|
|
324
|
+
if ([prompt isEqualToString:@"whenRequired"]) {
|
|
325
|
+
return MSALPromptTypePromptIfNecessary;
|
|
326
|
+
}
|
|
327
|
+
// Including nil. An unknown value must not fail a sign-in: a string added to the JS
|
|
328
|
+
// enum later would otherwise break every already-shipped native binary.
|
|
329
|
+
return MSALPromptTypeDefault;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
- (NSError *)notConfiguredError
|
|
333
|
+
{
|
|
334
|
+
return [RNIntuneCore errorWithCode:RNIntuneErrorNotConfigured
|
|
335
|
+
message:@"MSAL is not configured. Call configure first."];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/// Shapes an `AuthResult`, or maps MSAL's error onto a stable code. The access token here
|
|
339
|
+
/// is for the app's own scopes; the MAM token never passes through (CLAUDE.md rule 9).
|
|
340
|
+
- (void)complete:(RNIntuneAuthCompletion)completion
|
|
341
|
+
result:(MSALResult *)result
|
|
342
|
+
error:(NSError *)error
|
|
343
|
+
{
|
|
344
|
+
if (result != nil) {
|
|
345
|
+
completion(@{
|
|
346
|
+
@"accountId" : [self accountIdFor:result.account] ?: @"",
|
|
347
|
+
@"tenantId" : result.account.homeAccountId.tenantId ?: @"",
|
|
348
|
+
@"username" : result.account.username ?: @"",
|
|
349
|
+
@"accessToken" : result.accessToken,
|
|
350
|
+
@"idToken" : result.idToken ?: NSNull.null,
|
|
351
|
+
// Unix seconds, matching AuthResult in SPEC §13.1.1.
|
|
352
|
+
@"expiresOn" : @(result.expiresOn.timeIntervalSince1970),
|
|
353
|
+
@"scopes" : result.scopes,
|
|
354
|
+
},
|
|
355
|
+
nil);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
completion(nil, [self mapError:error]);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
- (NSError *)mapError:(NSError *)error
|
|
363
|
+
{
|
|
364
|
+
if (error == nil) {
|
|
365
|
+
return [RNIntuneCore errorWithCode:RNIntuneErrorNative
|
|
366
|
+
message:@"MSAL returned neither a result nor an error."];
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if ([error.domain isEqualToString:MSALErrorDomain]) {
|
|
370
|
+
switch ((MSALError)error.code) {
|
|
371
|
+
case MSALErrorInteractionRequired:
|
|
372
|
+
return [RNIntuneCore
|
|
373
|
+
errorWithCode:RNIntuneErrorInteractionRequired
|
|
374
|
+
message:@"A prompt is required. Call signIn instead of signInSilent."];
|
|
375
|
+
case MSALErrorUserCanceled:
|
|
376
|
+
return [RNIntuneCore errorWithCode:RNIntuneErrorUserCancelled
|
|
377
|
+
message:@"The user dismissed the sign-in UI."];
|
|
378
|
+
default:
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Everything else keeps MSAL's own description, which names the cause far better than
|
|
384
|
+
// a re-worded guess would. No token or claim is ever part of it.
|
|
385
|
+
return [RNIntuneCore errorWithCode:RNIntuneErrorNative
|
|
386
|
+
message:error.localizedDescription
|
|
387
|
+
?: @"MSAL failed with no description."];
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
@end
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Pure Objective-C. Everything that talks to the Intune MAM SDK lives on this side of
|
|
3
|
+
// the boundary; RNIntune.mm is ObjC++ only because Codegen conformance and
|
|
4
|
+
// getTurboModule: are C++ (SPEC §5.1.1, CLAUDE.md rule 7).
|
|
5
|
+
//
|
|
6
|
+
// Keep it that way. An enrollment failure should be debuggable across one language
|
|
7
|
+
// boundary, not two, in a domain with no official React Native support.
|
|
8
|
+
//
|
|
9
|
+
|
|
10
|
+
#import <Foundation/Foundation.h>
|
|
11
|
+
|
|
12
|
+
#import "RNIntuneAuth.h"
|
|
13
|
+
|
|
14
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
15
|
+
|
|
16
|
+
/// Stable rejection codes (SPEC §13.6). Never surface a raw platform error to JS.
|
|
17
|
+
extern NSString *const RNIntuneErrorNotConfigured;
|
|
18
|
+
extern NSString *const RNIntuneErrorResetRequired;
|
|
19
|
+
extern NSString *const RNIntuneErrorSDKUnavailable;
|
|
20
|
+
extern NSString *const RNIntuneErrorInvalidAccountId;
|
|
21
|
+
extern NSString *const RNIntuneErrorNotNeeded;
|
|
22
|
+
extern NSString *const RNIntuneErrorExternalAuthMode;
|
|
23
|
+
extern NSString *const RNIntuneErrorNative;
|
|
24
|
+
extern NSString *const RNIntuneErrorTokenProviderFailed;
|
|
25
|
+
extern NSString *const RNIntuneErrorTokenProviderMissing;
|
|
26
|
+
extern NSString *const RNIntuneErrorResetInProgress;
|
|
27
|
+
/// MSAL outcomes that are ordinary control flow, not faults (SPEC §13.6).
|
|
28
|
+
extern NSString *const RNIntuneErrorInteractionRequired;
|
|
29
|
+
extern NSString *const RNIntuneErrorUserCancelled;
|
|
30
|
+
/// Raised by the configure-time assertions below. Not in SPEC §13.6 yet — see §12.7 S1.
|
|
31
|
+
extern NSString *const RNIntuneErrorPlistConflict;
|
|
32
|
+
|
|
33
|
+
/// Event names. Must match the `IntuneEvents` keys in src/types.ts.
|
|
34
|
+
extern NSString *const RNIntuneEventEnrollmentResult;
|
|
35
|
+
extern NSString *const RNIntuneEventPolicyChanged;
|
|
36
|
+
extern NSString *const RNIntuneEventUnenrollmentResult;
|
|
37
|
+
extern NSString *const RNIntuneEventWipeRequested;
|
|
38
|
+
extern NSString *const RNIntuneEventRestartRequired;
|
|
39
|
+
extern NSString *const RNIntuneEventTokenRequest;
|
|
40
|
+
extern NSString *const RNIntuneEventBrokerStatusChanged;
|
|
41
|
+
|
|
42
|
+
/// Resolved configuration. `index.ts` applies every default, so nothing here is optional.
|
|
43
|
+
@interface RNIntuneConfig : NSObject
|
|
44
|
+
@property (nonatomic, copy) NSString *clientId;
|
|
45
|
+
@property (nonatomic, copy) NSString *tenantId;
|
|
46
|
+
@property (nonatomic, copy) NSString *authority;
|
|
47
|
+
@property (nonatomic, copy) NSString *redirectUri;
|
|
48
|
+
@property (nonatomic, copy) NSString *authMode;
|
|
49
|
+
@property (nonatomic, copy) NSString *maxFileProtectionLevel;
|
|
50
|
+
/// Empty string means "use the platform default" (`com.microsoft.adalcache`).
|
|
51
|
+
@property (nonatomic, copy) NSString *keychainGroupOverride;
|
|
52
|
+
@property (nonatomic) BOOL verboseLogging;
|
|
53
|
+
@property (nonatomic) BOOL restartHandledByApp;
|
|
54
|
+
@property (nonatomic) BOOL telemetryEnabled;
|
|
55
|
+
/// Hex strings for the SDK's own screens; empty means "leave Microsoft's default".
|
|
56
|
+
@property (nonatomic, copy) NSString *brandingBackground;
|
|
57
|
+
@property (nonatomic, copy) NSString *brandingForeground;
|
|
58
|
+
@property (nonatomic, copy) NSString *brandingAccent;
|
|
59
|
+
@property (nonatomic, copy) NSString *brandingSecondaryBackground;
|
|
60
|
+
@property (nonatomic, copy) NSString *brandingSecondaryForeground;
|
|
61
|
+
@end
|
|
62
|
+
|
|
63
|
+
@interface RNIntuneCore : NSObject
|
|
64
|
+
|
|
65
|
+
+ (instancetype)shared;
|
|
66
|
+
|
|
67
|
+
/// Set once, before configure. Publishes SDK events to JS.
|
|
68
|
+
- (void)setEventSink:(void (^)(NSString *event, NSDictionary *body))sink;
|
|
69
|
+
|
|
70
|
+
/// YES when the MAM SDK is actually linked into the binary. Checked by class lookup so a
|
|
71
|
+
/// misbuilt binary degrades instead of crashing.
|
|
72
|
+
@property (nonatomic, readonly) BOOL sdkAvailable;
|
|
73
|
+
|
|
74
|
+
@property (nonatomic, readonly, getter=isConfigured) BOOL configured;
|
|
75
|
+
@property (nonatomic, readonly, nullable) NSString *configuredTenantId;
|
|
76
|
+
|
|
77
|
+
/// NO in `authMode: 'external'`, where the host app owns MSAL and the module performs no
|
|
78
|
+
/// sign-in at all (SPEC §3.2).
|
|
79
|
+
@property (nonatomic, readonly) BOOL builtinAuth;
|
|
80
|
+
|
|
81
|
+
/// MSAL. Configured only in `builtin` mode; `isConfigured` is NO otherwise.
|
|
82
|
+
@property (nonatomic, readonly) RNIntuneAuth *auth;
|
|
83
|
+
|
|
84
|
+
/// Applies the runtime overrides, installs the delegates, and runs the configure-time
|
|
85
|
+
/// assertions. Returns NO and fills `error` on `E_RESET_REQUIRED` (different tenant),
|
|
86
|
+
/// `E_SDK_UNAVAILABLE`, or `E_PLIST_CONFLICT`.
|
|
87
|
+
- (BOOL)applyConfig:(RNIntuneConfig *)config error:(NSError *_Nullable *_Nullable)error;
|
|
88
|
+
|
|
89
|
+
/// BrokerStatus (SPEC §13.1). Answerable before `configure`, on purpose: the app needs
|
|
90
|
+
/// it to decide whether to prompt for a broker install.
|
|
91
|
+
- (NSDictionary<NSString *, id> *)brokerStatus;
|
|
92
|
+
|
|
93
|
+
/// IntuneState (SPEC §4.2). Never rejects — reporting `configured: false` is the whole
|
|
94
|
+
/// point of the reconciliation primitive.
|
|
95
|
+
- (NSDictionary<NSString *, id> *)state;
|
|
96
|
+
|
|
97
|
+
/// Diagnostics (SPEC §13.3). No tokens, no UPNs — safe to attach to a support ticket.
|
|
98
|
+
- (NSDictionary<NSString *, NSString *> *)diagnostics;
|
|
99
|
+
|
|
100
|
+
#pragma mark - Enrollment
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Registers and enrolls the account, completing with an `EnrollmentResult`.
|
|
104
|
+
*
|
|
105
|
+
* `registerAndEnrollAccountId:` returns immediately and says nothing; the outcome arrives
|
|
106
|
+
* later on `enrollmentRequestWithStatus:`. So the completion is held against the account
|
|
107
|
+
* ID and fired from the delegate (SPEC §12.5) — never from the call returning.
|
|
108
|
+
*
|
|
109
|
+
* Completes for every outcome, including failures: a non-success status is data, not an
|
|
110
|
+
* error. If the SDK says nothing at all within the timeout it completes with `pending`,
|
|
111
|
+
* which is truthful — the SDK keeps retrying on its own schedule and the result will
|
|
112
|
+
* still arrive as an event.
|
|
113
|
+
*/
|
|
114
|
+
- (void)enrollAccountId:(NSString *)accountId
|
|
115
|
+
completion:(void (^)(NSDictionary<NSString *, id> *result))completion;
|
|
116
|
+
|
|
117
|
+
/// PolicySnapshot (SPEC §4.3). Nil when the SDK is not linked.
|
|
118
|
+
- (nullable NSDictionary<NSString *, id> *)policySnapshot;
|
|
119
|
+
|
|
120
|
+
#pragma mark - Reset (SPEC §7)
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Writes the journal, then unregisters and unenrolls.
|
|
124
|
+
*
|
|
125
|
+
* **Blocks** while the SDK acquires the Intune AAD token, so it must not run on the main
|
|
126
|
+
* thread, and it must happen before the host app purges the account's Entra tokens
|
|
127
|
+
* (SPEC §5.3). The process may not survive it; everything after is driven by the journal.
|
|
128
|
+
*/
|
|
129
|
+
- (void)resetWithWipe:(BOOL)wipe reason:(NSString *)reason;
|
|
130
|
+
|
|
131
|
+
/// Verifies the account is really gone and closes the journal. Returns NO and leaves the
|
|
132
|
+
/// journal open when it is not, so the next launch retries rather than declaring success.
|
|
133
|
+
- (BOOL)completeReset;
|
|
134
|
+
|
|
135
|
+
/// iOS-only interactive enrollment: the SDK runs the sign-in and shows its own UI.
|
|
136
|
+
- (void)enrollInteractiveWithUpn:(nullable NSString *)upn
|
|
137
|
+
completion:(void (^)(NSDictionary<NSString *, id> *result))completion;
|
|
138
|
+
|
|
139
|
+
#pragma mark - Token bridge (SPEC §13.4)
|
|
140
|
+
|
|
141
|
+
/// Answers a pending `tokenRequest`. The token goes to the SDK and never back to JS.
|
|
142
|
+
- (void)resolveTokenRequest:(NSString *)requestId token:(NSString *)token;
|
|
143
|
+
- (void)rejectTokenRequest:(NSString *)requestId reason:(NSString *)reason;
|
|
144
|
+
|
|
145
|
+
/// Builds an NSError carrying one of the stable codes above.
|
|
146
|
+
+ (NSError *)errorWithCode:(NSString *)code message:(NSString *)message;
|
|
147
|
+
|
|
148
|
+
@end
|
|
149
|
+
|
|
150
|
+
NS_ASSUME_NONNULL_END
|