react-native-msal2 1.0.8 → 1.0.10
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/android/build.gradle +12 -36
- package/android/src/main/java/com/reactnativemsal/RNMSALModule.kt +511 -0
- package/android/src/main/java/com/reactnativemsal/RNMSALPackage.kt +28 -0
- package/android/src/main/java/com/reactnativemsal/ReadableMapUtils.kt +82 -0
- package/package.json +1 -1
- package/react-native-msal2.podspec +1 -1
- package/android/src/main/java/com/reactnativemsal/FileUtils.java +0 -80
- package/android/src/main/java/com/reactnativemsal/RNMSALModule.java +0 -482
- package/android/src/main/java/com/reactnativemsal/RNMSALPackage.java +0 -25
- package/android/src/main/java/com/reactnativemsal/ReadableMapUtils.java +0 -105
package/android/build.gradle
CHANGED
|
@@ -1,38 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
google()
|
|
4
|
-
mavenCentral()
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
dependencies {
|
|
8
|
-
classpath 'com.android.tools.build:gradle:4.0.2'
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
apply plugin: 'com.android.library'
|
|
1
|
+
apply plugin: "com.android.library"
|
|
2
|
+
apply plugin: "org.jetbrains.kotlin.android"
|
|
13
3
|
|
|
14
4
|
def safeExtGet(prop, fallback) {
|
|
15
5
|
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
16
6
|
}
|
|
17
7
|
|
|
18
8
|
android {
|
|
19
|
-
namespace
|
|
20
|
-
|
|
9
|
+
namespace "com.reactnativemsal"
|
|
10
|
+
|
|
11
|
+
compileSdkVersion safeExtGet("compileSdkVersion", 34)
|
|
12
|
+
|
|
21
13
|
defaultConfig {
|
|
22
|
-
minSdkVersion safeExtGet(
|
|
23
|
-
targetSdkVersion safeExtGet(
|
|
24
|
-
versionCode 1
|
|
25
|
-
versionName "1.0"
|
|
14
|
+
minSdkVersion safeExtGet("minSdkVersion", 21)
|
|
15
|
+
targetSdkVersion safeExtGet("targetSdkVersion", 34)
|
|
26
16
|
}
|
|
27
17
|
|
|
28
|
-
buildTypes {
|
|
29
|
-
release {
|
|
30
|
-
minifyEnabled false
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
lintOptions {
|
|
34
|
-
disable 'GradleCompatible'
|
|
35
|
-
}
|
|
36
18
|
compileOptions {
|
|
37
19
|
sourceCompatibility JavaVersion.VERSION_1_8
|
|
38
20
|
targetCompatibility JavaVersion.VERSION_1_8
|
|
@@ -40,19 +22,13 @@ android {
|
|
|
40
22
|
}
|
|
41
23
|
|
|
42
24
|
repositories {
|
|
43
|
-
mavenLocal()
|
|
44
|
-
maven {
|
|
45
|
-
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
|
|
46
|
-
url("$rootDir/../node_modules/react-native/android")
|
|
47
|
-
}
|
|
48
25
|
google()
|
|
49
26
|
mavenCentral()
|
|
50
27
|
}
|
|
51
28
|
|
|
52
29
|
dependencies {
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
30
|
+
//noinspection UseTomlInstead
|
|
31
|
+
compileOnly "com.facebook.react:react-android"
|
|
32
|
+
//noinspection UseTomlInstead
|
|
33
|
+
implementation "com.microsoft.identity.client:msal:8.2.1"
|
|
58
34
|
}
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
package com.reactnativemsal
|
|
2
|
+
|
|
3
|
+
import android.content.pm.PackageManager
|
|
4
|
+
import android.net.Uri
|
|
5
|
+
import android.util.Base64
|
|
6
|
+
import android.util.Log
|
|
7
|
+
import com.facebook.react.bridge.Arguments.createArray
|
|
8
|
+
import com.facebook.react.bridge.Arguments.createMap
|
|
9
|
+
import com.facebook.react.bridge.Arguments.fromArray
|
|
10
|
+
import com.facebook.react.bridge.Promise
|
|
11
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
12
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
13
|
+
import com.facebook.react.bridge.ReactMethod
|
|
14
|
+
import com.facebook.react.bridge.ReadableArray
|
|
15
|
+
import com.facebook.react.bridge.ReadableMap
|
|
16
|
+
import com.facebook.react.bridge.WritableArray
|
|
17
|
+
import com.facebook.react.bridge.WritableMap
|
|
18
|
+
import com.microsoft.identity.client.AcquireTokenParameters
|
|
19
|
+
import com.microsoft.identity.client.AcquireTokenSilentParameters
|
|
20
|
+
import com.microsoft.identity.client.AuthenticationCallback
|
|
21
|
+
import com.microsoft.identity.client.IAccount
|
|
22
|
+
import com.microsoft.identity.client.IAuthenticationResult
|
|
23
|
+
import com.microsoft.identity.client.IMultiTenantAccount
|
|
24
|
+
import com.microsoft.identity.client.IMultipleAccountPublicClientApplication
|
|
25
|
+
import com.microsoft.identity.client.IMultipleAccountPublicClientApplication.RemoveAccountCallback
|
|
26
|
+
import com.microsoft.identity.client.Prompt
|
|
27
|
+
import com.microsoft.identity.client.PublicClientApplication
|
|
28
|
+
import com.microsoft.identity.client.SilentAuthenticationCallback
|
|
29
|
+
import com.microsoft.identity.client.exception.MsalException
|
|
30
|
+
import org.json.JSONArray
|
|
31
|
+
import org.json.JSONException
|
|
32
|
+
import org.json.JSONObject
|
|
33
|
+
import java.io.File
|
|
34
|
+
import java.io.FileWriter
|
|
35
|
+
import java.security.MessageDigest
|
|
36
|
+
import java.util.AbstractMap
|
|
37
|
+
import java.util.regex.Matcher
|
|
38
|
+
import java.util.regex.Pattern
|
|
39
|
+
|
|
40
|
+
class RNMSALModule(reactContext: ReactApplicationContext?) :
|
|
41
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
42
|
+
private var publicClientApplication: IMultipleAccountPublicClientApplication? = null
|
|
43
|
+
|
|
44
|
+
override fun getName(): String {
|
|
45
|
+
return NAME
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@ReactMethod
|
|
49
|
+
fun createPublicClientApplication(params: ReadableMap, promise: Promise) {
|
|
50
|
+
val context = reactApplicationContext
|
|
51
|
+
try {
|
|
52
|
+
// We have to make a JSON file containing the MSAL configuration, then use that file to
|
|
53
|
+
// create the PublicClientApplication
|
|
54
|
+
// We first need to create the JSON model using the passed in parameters
|
|
55
|
+
|
|
56
|
+
val config =
|
|
57
|
+
if (params.hasKey("androidConfigOptions")) params.getMap("androidConfigOptions") else null
|
|
58
|
+
|
|
59
|
+
val msalConfigJsonObj =
|
|
60
|
+
if (config != null) ReadableMapUtils.toJsonObject(config) else JSONObject()
|
|
61
|
+
|
|
62
|
+
// Account mode. Required to be MULTIPLE for this library
|
|
63
|
+
msalConfigJsonObj.put("account_mode", "MULTIPLE")
|
|
64
|
+
|
|
65
|
+
// If broker_redirect_uri_registered is not provided in androidConfigOptions,
|
|
66
|
+
// default it to false
|
|
67
|
+
if (!msalConfigJsonObj.has("broker_redirect_uri_registered")) {
|
|
68
|
+
msalConfigJsonObj.put("broker_redirect_uri_registered", false)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
val auth = params.getMap("auth")
|
|
72
|
+
|
|
73
|
+
// Authority
|
|
74
|
+
val authority = ReadableMapUtils.getStringOrDefault(
|
|
75
|
+
auth,
|
|
76
|
+
"authority",
|
|
77
|
+
"https://login.microsoftonline.com/common"
|
|
78
|
+
)
|
|
79
|
+
msalConfigJsonObj.put("authority", authority)
|
|
80
|
+
|
|
81
|
+
// Client id
|
|
82
|
+
msalConfigJsonObj.put("client_id", ReadableMapUtils.getStringOrThrow(auth, "clientId"))
|
|
83
|
+
|
|
84
|
+
// Redirect URI
|
|
85
|
+
msalConfigJsonObj.put(
|
|
86
|
+
"redirect_uri",
|
|
87
|
+
if (auth!!.hasKey("redirectUri")) auth.getString("redirectUri") else makeRedirectUri(
|
|
88
|
+
context
|
|
89
|
+
).toString()
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
// Authorities
|
|
93
|
+
val knownAuthorities = auth.getArray("knownAuthorities")
|
|
94
|
+
// List WILL be instantiated and empty if `knownAuthorities` is null
|
|
95
|
+
val authoritiesList = readableArrayToStringList(knownAuthorities)
|
|
96
|
+
// Make sure the `authority` makes it in the authority list
|
|
97
|
+
if (!authoritiesList.contains(authority)) {
|
|
98
|
+
authoritiesList.add(authority)
|
|
99
|
+
}
|
|
100
|
+
// The authoritiesList is just a list of urls (strings), but the native android MSAL
|
|
101
|
+
// library expects an array of objects, so we have to parse the urls
|
|
102
|
+
val authoritiesJsonArr = makeAuthoritiesJsonArray(authoritiesList, authority)
|
|
103
|
+
msalConfigJsonObj.put("authorities", authoritiesJsonArr)
|
|
104
|
+
|
|
105
|
+
// Serialize the JSON config to a string
|
|
106
|
+
val serializedMsalConfig = msalConfigJsonObj.toString()
|
|
107
|
+
Log.d("RNMSALModule", serializedMsalConfig)
|
|
108
|
+
|
|
109
|
+
// Create a temporary file and write the serialized config to it
|
|
110
|
+
val file = File.createTempFile("RNMSAL_msal_config", ".tmp")
|
|
111
|
+
file.deleteOnExit()
|
|
112
|
+
val writer = FileWriter(file)
|
|
113
|
+
writer.write(serializedMsalConfig)
|
|
114
|
+
writer.close()
|
|
115
|
+
|
|
116
|
+
// Finally, create the PCA with the temporary config file we created
|
|
117
|
+
publicClientApplication =
|
|
118
|
+
PublicClientApplication.createMultipleAccountPublicClientApplication(
|
|
119
|
+
context, file
|
|
120
|
+
)
|
|
121
|
+
promise.resolve(null)
|
|
122
|
+
} catch (e: Exception) {
|
|
123
|
+
promise.reject(e)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@Throws(JSONException::class, IllegalArgumentException::class)
|
|
128
|
+
private fun makeAuthoritiesJsonArray(
|
|
129
|
+
authorityUrls: MutableList<String>,
|
|
130
|
+
authority: String?
|
|
131
|
+
): JSONArray {
|
|
132
|
+
val authoritiesJsonArr = JSONArray()
|
|
133
|
+
var foundDefaultAuthority = false
|
|
134
|
+
|
|
135
|
+
for (authorityUrl in authorityUrls) {
|
|
136
|
+
val authorityJsonObj = JSONObject()
|
|
137
|
+
|
|
138
|
+
// Authority is set as the default if one is not set yet, and it matches `authority`
|
|
139
|
+
if (!foundDefaultAuthority && authorityUrl == authority) {
|
|
140
|
+
authorityJsonObj.put("default", true)
|
|
141
|
+
foundDefaultAuthority = true
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
val aadAuthorityMatcher: Matcher = aadAuthorityPattern.matcher(authorityUrl)
|
|
145
|
+
val b2cAuthorityMatcher: Matcher = b2cAuthorityPattern.matcher(authorityUrl)
|
|
146
|
+
|
|
147
|
+
if (aadAuthorityMatcher.find()) {
|
|
148
|
+
val group = aadAuthorityMatcher.group(1)
|
|
149
|
+
requireNotNull(group) { "Could not match group 1 for regex https://login.microsoftonline.com/([^/]+) in authority \"$authorityUrl\"" }
|
|
150
|
+
|
|
151
|
+
val audience = when (group) {
|
|
152
|
+
"common" -> JSONObject().put("type", "AzureADandPersonalMicrosoftAccount")
|
|
153
|
+
"organizations" -> JSONObject().put("type", "AzureADMultipleOrgs")
|
|
154
|
+
"consumers" -> JSONObject().put("type", "PersonalMicrosoftAccount")
|
|
155
|
+
else -> // assume `group` is a tenant id
|
|
156
|
+
JSONObject().put("type", "AzureADMyOrg").put("tenant_id", group)
|
|
157
|
+
}
|
|
158
|
+
authorityJsonObj.put("type", AUTHORITY_TYPE_AAD)
|
|
159
|
+
authorityJsonObj.put("audience", audience)
|
|
160
|
+
} else if (b2cAuthorityMatcher.find()) {
|
|
161
|
+
authorityJsonObj.put("type", AUTHORITY_TYPE_B2C)
|
|
162
|
+
authorityJsonObj.put("authority_url", authorityUrl)
|
|
163
|
+
} else {
|
|
164
|
+
throw IllegalArgumentException("Authority \"$authorityUrl\" doesn't match AAD regex https://login.microsoftonline.com/([^/]+) or B2C regex https://([^/]+)/tfp/([^/]+)/.+")
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
authoritiesJsonArr.put(authorityJsonObj)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// If a default authority was not found, we set the first authority as the default
|
|
171
|
+
if (!foundDefaultAuthority && authoritiesJsonArr.length() > 0) {
|
|
172
|
+
authoritiesJsonArr.getJSONObject(0).put("default", true)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return authoritiesJsonArr
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@Throws(Exception::class)
|
|
179
|
+
private fun makeRedirectUri(context: ReactApplicationContext): Uri? {
|
|
180
|
+
try {
|
|
181
|
+
val packageName = context.packageName
|
|
182
|
+
val info = context.packageManager
|
|
183
|
+
.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
|
|
184
|
+
if (info.signatures?.size != 1) {
|
|
185
|
+
throw RuntimeException("RNMSAL expected there to be exactly one signature for package $packageName")
|
|
186
|
+
}
|
|
187
|
+
val signature = info.signatures?.first()
|
|
188
|
+
val messageDigest = MessageDigest.getInstance("SHA")
|
|
189
|
+
messageDigest.update(signature!!.toByteArray())
|
|
190
|
+
val signatureHash = Base64.encodeToString(messageDigest.digest(), Base64.NO_WRAP)
|
|
191
|
+
Log.d("RNMSALModule", signatureHash)
|
|
192
|
+
|
|
193
|
+
return Uri.Builder().scheme("msauth")
|
|
194
|
+
.authority(packageName)
|
|
195
|
+
.appendPath(signatureHash)
|
|
196
|
+
.build()
|
|
197
|
+
} catch (ex: Exception) {
|
|
198
|
+
throw Exception(
|
|
199
|
+
"Could not create redirect uri from package name and signature hash",
|
|
200
|
+
ex
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@ReactMethod
|
|
206
|
+
fun acquireToken(params: ReadableMap, promise: Promise) {
|
|
207
|
+
try {
|
|
208
|
+
val acquireTokenParameters =
|
|
209
|
+
AcquireTokenParameters.Builder()
|
|
210
|
+
.startAuthorizationFromActivity(this.reactApplicationContext.currentActivity)
|
|
211
|
+
|
|
212
|
+
// Required parameters
|
|
213
|
+
val scopes = readableArrayToStringList(params.getArray("scopes"))
|
|
214
|
+
acquireTokenParameters.withScopes(scopes)
|
|
215
|
+
|
|
216
|
+
// Optional parameters
|
|
217
|
+
if (params.hasKey("authority")) {
|
|
218
|
+
acquireTokenParameters.fromAuthority(params.getString("authority"))
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (params.hasKey("promptType")) {
|
|
222
|
+
acquireTokenParameters.withPrompt(Prompt.entries[params.getInt("promptType")])
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (params.hasKey("loginHint")) {
|
|
226
|
+
acquireTokenParameters.withLoginHint(params.getString("loginHint"))
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (params.hasKey("extraScopesToConsent")) {
|
|
230
|
+
acquireTokenParameters.withOtherScopesToAuthorize(
|
|
231
|
+
readableArrayToStringList(params.getArray("extraScopesToConsent"))
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (params.hasKey("extraQueryParameters")) {
|
|
236
|
+
val parameters: MutableList<MutableMap.MutableEntry<String?, String?>?> =
|
|
237
|
+
ArrayList()
|
|
238
|
+
params.getMap("extraQueryParameters")?.toHashMap()?.entries?.let {
|
|
239
|
+
for (entry in it) {
|
|
240
|
+
parameters.add(
|
|
241
|
+
AbstractMap.SimpleEntry<String?, String?>(
|
|
242
|
+
entry.key,
|
|
243
|
+
entry.value.toString()
|
|
244
|
+
)
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
acquireTokenParameters.withAuthorizationQueryStringParameters(parameters)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
acquireTokenParameters.withCallback(getAuthInteractiveCallback(promise))
|
|
252
|
+
publicClientApplication!!.acquireToken(acquireTokenParameters.build())
|
|
253
|
+
} catch (e: Exception) {
|
|
254
|
+
promise.reject(e)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private fun getAuthInteractiveCallback(promise: Promise): AuthenticationCallback {
|
|
259
|
+
return object : AuthenticationCallback {
|
|
260
|
+
override fun onCancel() {
|
|
261
|
+
promise.reject("userCancel", "userCancel")
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
override fun onSuccess(authenticationResult: IAuthenticationResult?) {
|
|
265
|
+
if (authenticationResult != null) {
|
|
266
|
+
promise.resolve(msalResultToDictionary(authenticationResult))
|
|
267
|
+
} else {
|
|
268
|
+
promise.resolve(null)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
override fun onError(exception: MsalException) {
|
|
273
|
+
promise.reject(exception)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
@ReactMethod
|
|
279
|
+
fun acquireTokenSilent(params: ReadableMap, promise: Promise) {
|
|
280
|
+
try {
|
|
281
|
+
val acquireTokenSilentParameters =
|
|
282
|
+
AcquireTokenSilentParameters.Builder()
|
|
283
|
+
|
|
284
|
+
// Required parameters
|
|
285
|
+
val scopes = readableArrayToStringList(params.getArray("scopes"))
|
|
286
|
+
acquireTokenSilentParameters.withScopes(scopes)
|
|
287
|
+
|
|
288
|
+
val accountIn = params.getMap("account")
|
|
289
|
+
val accountIdentifier = accountIn?.getString("identifier")
|
|
290
|
+
val account = publicClientApplication!!.getAccount(
|
|
291
|
+
accountIdentifier!!
|
|
292
|
+
)
|
|
293
|
+
acquireTokenSilentParameters.forAccount(account)
|
|
294
|
+
|
|
295
|
+
// Optional parameters
|
|
296
|
+
var authority: String? =
|
|
297
|
+
publicClientApplication!!
|
|
298
|
+
.configuration
|
|
299
|
+
.defaultAuthority
|
|
300
|
+
.authorityURL
|
|
301
|
+
.toString()
|
|
302
|
+
if (params.hasKey("authority")) {
|
|
303
|
+
authority = params.getString("authority")
|
|
304
|
+
}
|
|
305
|
+
acquireTokenSilentParameters.fromAuthority(authority)
|
|
306
|
+
|
|
307
|
+
if (params.hasKey("forceRefresh")) {
|
|
308
|
+
acquireTokenSilentParameters.forceRefresh(params.getBoolean("forceRefresh"))
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
acquireTokenSilentParameters.withCallback(getAuthSilentCallback(promise))
|
|
312
|
+
publicClientApplication!!.acquireTokenSilentAsync(acquireTokenSilentParameters.build())
|
|
313
|
+
} catch (e: Exception) {
|
|
314
|
+
promise.reject(e)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private fun getAuthSilentCallback(promise: Promise): SilentAuthenticationCallback {
|
|
319
|
+
return object : SilentAuthenticationCallback {
|
|
320
|
+
override fun onSuccess(authenticationResult: IAuthenticationResult?) {
|
|
321
|
+
if (authenticationResult != null) {
|
|
322
|
+
promise.resolve(msalResultToDictionary(authenticationResult))
|
|
323
|
+
} else {
|
|
324
|
+
promise.resolve(null)
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
override fun onError(exception: MsalException) {
|
|
329
|
+
promise.reject(exception)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
@ReactMethod
|
|
335
|
+
fun getAccounts(promise: Promise) {
|
|
336
|
+
try {
|
|
337
|
+
val accounts = publicClientApplication!!.accounts
|
|
338
|
+
val array = createArray()
|
|
339
|
+
if (accounts != null) {
|
|
340
|
+
for (account in accounts) {
|
|
341
|
+
array.pushMap(accountToMap(account))
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
promise.resolve(array)
|
|
345
|
+
} catch (e: Exception) {
|
|
346
|
+
promise.reject(e)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
@ReactMethod
|
|
351
|
+
fun getAccount(accountIdentifier: String, promise: Promise) {
|
|
352
|
+
try {
|
|
353
|
+
val account = publicClientApplication!!.getAccount(accountIdentifier)
|
|
354
|
+
if (account != null) {
|
|
355
|
+
promise.resolve(accountToMap(account))
|
|
356
|
+
} else {
|
|
357
|
+
promise.resolve(null)
|
|
358
|
+
}
|
|
359
|
+
} catch (e: Exception) {
|
|
360
|
+
promise.reject(e)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
@ReactMethod
|
|
365
|
+
fun removeAccount(accountIn: ReadableMap, promise: Promise) {
|
|
366
|
+
try {
|
|
367
|
+
// Required parameters
|
|
368
|
+
publicClientApplication?.let { app ->
|
|
369
|
+
accountIn.getString(("identifier"))?.let {
|
|
370
|
+
val account = app.getAccount(it)
|
|
371
|
+
|
|
372
|
+
app.removeAccount(
|
|
373
|
+
account,
|
|
374
|
+
object : RemoveAccountCallback {
|
|
375
|
+
override fun onRemoved() {
|
|
376
|
+
promise.resolve(true)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
override fun onError(exception: MsalException) {
|
|
380
|
+
promise.reject(exception)
|
|
381
|
+
}
|
|
382
|
+
})
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
} catch (e: Exception) {
|
|
386
|
+
promise.reject(e)
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
private fun msalResultToDictionary(result: IAuthenticationResult): WritableMap {
|
|
391
|
+
val map = createMap()
|
|
392
|
+
map.putString("accessToken", result.accessToken)
|
|
393
|
+
map.putString("expiresOn", String.format("%s", result.expiresOn.time / 1000))
|
|
394
|
+
var idToken = result.account.idToken
|
|
395
|
+
if (idToken == null) {
|
|
396
|
+
idToken =
|
|
397
|
+
(result.account as IMultiTenantAccount).tenantProfiles[result.tenantId]?.idToken
|
|
398
|
+
}
|
|
399
|
+
map.putString("idToken", idToken)
|
|
400
|
+
map.putArray("scopes", fromArray(result.scope))
|
|
401
|
+
map.putString("tenantId", result.tenantId)
|
|
402
|
+
map.putMap("account", accountToMap(result.account))
|
|
403
|
+
return map
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
private fun accountToMap(account: IAccount): WritableMap {
|
|
407
|
+
val map = createMap()
|
|
408
|
+
map.putString("identifier", account.id)
|
|
409
|
+
map.putString("username", account.username)
|
|
410
|
+
map.putString("tenantId", account.tenantId)
|
|
411
|
+
val claims = account.claims
|
|
412
|
+
if (claims != null) {
|
|
413
|
+
map.putMap("claims", toWritableMap(claims))
|
|
414
|
+
}
|
|
415
|
+
return map
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
private fun readableArrayToStringList(readableArray: ReadableArray?): MutableList<String> {
|
|
420
|
+
val list: MutableList<String> = ArrayList()
|
|
421
|
+
if (readableArray != null) {
|
|
422
|
+
for (item in readableArray.toArrayList()) {
|
|
423
|
+
list.add(item.toString())
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return list
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
private fun toWritableMap(map: MutableMap<String?, *>): WritableMap {
|
|
430
|
+
val writableMap = createMap()
|
|
431
|
+
for (entry in map.entries) {
|
|
432
|
+
val key: String = entry.key!!
|
|
433
|
+
when (val value: Any? = entry.value) {
|
|
434
|
+
null -> {
|
|
435
|
+
writableMap.putNull(key)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
is Boolean -> {
|
|
439
|
+
writableMap.putBoolean(key, value)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
is Double -> {
|
|
443
|
+
writableMap.putDouble(key, value)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
is Int -> {
|
|
447
|
+
writableMap.putInt(key, value)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
is String -> {
|
|
451
|
+
writableMap.putString(key, value)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
is MutableMap<*, *> -> {
|
|
455
|
+
writableMap.putMap(key, toWritableMap(value as MutableMap<String?, *>))
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
is MutableList<*> -> {
|
|
459
|
+
writableMap.putArray(key, toWritableArray(value))
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return writableMap
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
private fun toWritableArray(list: MutableList<*>): WritableArray {
|
|
467
|
+
val writableArray = createArray()
|
|
468
|
+
for (value in list.toTypedArray()) {
|
|
469
|
+
when (value) {
|
|
470
|
+
null -> {
|
|
471
|
+
writableArray.pushNull()
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
is Boolean -> {
|
|
475
|
+
writableArray.pushBoolean(value)
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
is Double -> {
|
|
479
|
+
writableArray.pushDouble(value)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
is Int -> {
|
|
483
|
+
writableArray.pushInt(value)
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
is String -> {
|
|
487
|
+
writableArray.pushString(value)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
is MutableMap<*, *> -> {
|
|
491
|
+
writableArray.pushMap(toWritableMap(value as MutableMap<String?, *>))
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
is MutableList<*> -> {
|
|
495
|
+
writableArray.pushArray(toWritableArray(value))
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return writableArray
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
companion object {
|
|
503
|
+
const val NAME = "RNMSAL"
|
|
504
|
+
private const val AUTHORITY_TYPE_B2C = "B2C"
|
|
505
|
+
private const val AUTHORITY_TYPE_AAD = "AAD"
|
|
506
|
+
|
|
507
|
+
private val aadAuthorityPattern: Pattern =
|
|
508
|
+
Pattern.compile("https://login\\.microsoftonline\\.com/([^/]+)")
|
|
509
|
+
private val b2cAuthorityPattern: Pattern = Pattern.compile("https://([^/]+)/([^/]+)/.+")
|
|
510
|
+
}
|
|
511
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
package com.reactnativemsal
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
|
|
9
|
+
class RNMSALPackage : BaseReactPackage() {
|
|
10
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
11
|
+
return RNMSALModule(reactContext)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
|
|
15
|
+
return ReactModuleInfoProvider {
|
|
16
|
+
mapOf(
|
|
17
|
+
RNMSALModule.NAME to ReactModuleInfo(
|
|
18
|
+
RNMSALModule.NAME,
|
|
19
|
+
RNMSALModule::class.java.name,
|
|
20
|
+
canOverrideExistingModule = false,
|
|
21
|
+
needsEagerInit = false,
|
|
22
|
+
isCxxModule = false,
|
|
23
|
+
isTurboModule = false
|
|
24
|
+
)
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
package com.reactnativemsal
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReadableArray
|
|
4
|
+
import com.facebook.react.bridge.ReadableMap
|
|
5
|
+
import com.facebook.react.bridge.ReadableType
|
|
6
|
+
import org.json.JSONArray
|
|
7
|
+
import org.json.JSONException
|
|
8
|
+
import org.json.JSONObject
|
|
9
|
+
|
|
10
|
+
object ReadableMapUtils {
|
|
11
|
+
@Throws(JSONException::class)
|
|
12
|
+
fun toJsonObject(readableMap: ReadableMap): JSONObject {
|
|
13
|
+
val jsonObject = JSONObject()
|
|
14
|
+
|
|
15
|
+
val iterator = readableMap.keySetIterator()
|
|
16
|
+
|
|
17
|
+
while (iterator.hasNextKey()) {
|
|
18
|
+
val key = iterator.nextKey()
|
|
19
|
+
val type = readableMap.getType(key)
|
|
20
|
+
|
|
21
|
+
when (type) {
|
|
22
|
+
ReadableType.Null -> jsonObject.put(key, null)
|
|
23
|
+
ReadableType.Boolean -> jsonObject.put(key, readableMap.getBoolean(key))
|
|
24
|
+
ReadableType.Number -> jsonObject.put(key, readableMap.getDouble(key))
|
|
25
|
+
ReadableType.String -> jsonObject.put(key, readableMap.getString(key))
|
|
26
|
+
ReadableType.Map -> jsonObject.put(
|
|
27
|
+
key,
|
|
28
|
+
toJsonObject(
|
|
29
|
+
readableMap.getMap(key)!!
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
ReadableType.Array -> jsonObject.put(
|
|
34
|
+
key, toJsonArray(readableMap.getArray(key)!!)
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return jsonObject
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@Throws(JSONException::class)
|
|
43
|
+
fun toJsonArray(readableArray: ReadableArray): JSONArray {
|
|
44
|
+
val jsonArray = JSONArray()
|
|
45
|
+
|
|
46
|
+
for (i in 0..<readableArray.size()) {
|
|
47
|
+
val type = readableArray.getType(i)
|
|
48
|
+
|
|
49
|
+
when (type) {
|
|
50
|
+
ReadableType.Null -> jsonArray.put(i, null)
|
|
51
|
+
ReadableType.Boolean -> jsonArray.put(i, readableArray.getBoolean(i))
|
|
52
|
+
ReadableType.Number -> jsonArray.put(i, readableArray.getDouble(i))
|
|
53
|
+
ReadableType.String -> jsonArray.put(i, readableArray.getString(i))
|
|
54
|
+
ReadableType.Map -> jsonArray.put(
|
|
55
|
+
i,
|
|
56
|
+
toJsonObject(readableArray.getMap(i)!!)
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
ReadableType.Array -> jsonArray.put(
|
|
60
|
+
i, toJsonArray(readableArray.getArray(i)!!)
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return jsonArray
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fun getStringOrDefault(map: ReadableMap?, key: String, defaultValue: String): String {
|
|
69
|
+
return try {
|
|
70
|
+
getStringOrThrow(map, key)
|
|
71
|
+
} catch (_: Exception) {
|
|
72
|
+
defaultValue
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fun getStringOrThrow(map: ReadableMap?, key: String): String {
|
|
77
|
+
requireNotNull(map) { "Map is null" }
|
|
78
|
+
|
|
79
|
+
return map.getString(key)
|
|
80
|
+
?: throw NoSuchElementException("$key doesn't exist on map or is null")
|
|
81
|
+
}
|
|
82
|
+
}
|