Skip to content
This repository has been archived by the owner on May 20, 2022. It is now read-only.

Commit

Permalink
fix: make authority parsing more forgiving (#138)
Browse files Browse the repository at this point in the history
  • Loading branch information
josmithua authored Dec 23, 2021
1 parent c278420 commit d0cc429
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 47 deletions.
1 change: 0 additions & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ def safeExtGet(prop, fallback) {

android {
compileSdkVersion safeExtGet('Msal_compileSdkVersion', 29)
buildToolsVersion safeExtGet('Msal_buildToolsVersion', '29.0.2')
defaultConfig {
minSdkVersion safeExtGet('Msal_minSdkVersion', 16)
targetSdkVersion safeExtGet('Msal_targetSdkVersion', 29)
Expand Down
67 changes: 34 additions & 33 deletions android/src/main/java/com/reactnativemsal/RNMSALModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ public class RNMSALModule extends ReactContextBaseJavaModule {
private static final String AUTHORITY_TYPE_B2C = "B2C";
private static final String AUTHORITY_TYPE_AAD = "AAD";

private static final Pattern aadMyOrgAuthorityPattern = Pattern.compile("https://login.microsoftonline.com/(?<tenant>.+)");
private static final Pattern b2cAuthorityPattern = Pattern.compile("https://.+?/tfp/(?<tenant>.+?)/.+");
private static final Pattern aadAuthorityPattern = Pattern.compile("https://login\\.microsoftonline\\.com/([^/]+)");
private static final Pattern b2cAuthorityPattern = Pattern.compile("https://([^/]+)/tfp/([^/]+)/.+");

private IMultipleAccountPublicClientApplication publicClientApplication;

Expand Down Expand Up @@ -134,7 +134,7 @@ public void createPublicClientApplication(ReadableMap params, Promise promise) {
}
}

private JSONArray makeAuthoritiesJsonArray(List<String> authorityUrls, String authority) throws JSONException {
private JSONArray makeAuthoritiesJsonArray(List<String> authorityUrls, String authority) throws JSONException, IllegalArgumentException {
JSONArray authoritiesJsonArr = new JSONArray();
boolean foundDefaultAuthority = false;

Expand All @@ -147,38 +147,39 @@ private JSONArray makeAuthoritiesJsonArray(List<String> authorityUrls, String au
foundDefaultAuthority = true;
}

// Parse this information from the authority url. Some variables will end up staying null
String type = null, audience_type = null, audience_tenantId = null, b2cAuthorityUrl = null;

Matcher aadAuthorityMatcher = aadAuthorityPattern.matcher(authorityUrl);
Matcher b2cAuthorityMatcher = b2cAuthorityPattern.matcher(authorityUrl);
Matcher aadMyOrgAuthorityMatcher = aadMyOrgAuthorityPattern.matcher(authorityUrl);

if (authorityUrl.equals("https://login.microsoftonline.com/common")) {
type = AUTHORITY_TYPE_AAD;
audience_type = "AzureADandPersonalMicrosoftAccount";
} else if (authorityUrl.equals("https://login.microsoftonline.com/organizations")) {
type = AUTHORITY_TYPE_AAD;
audience_type = "AzureADMultipleOrgs";
} else if (authorityUrl.equals("https://login.microsoftonline.com/consumers")) {
type = AUTHORITY_TYPE_AAD;
audience_type = "PersonalMicrosoftAccount";

if (aadAuthorityMatcher.find()) {
String group = aadAuthorityMatcher.group(1);
if (group == null)
throw new IllegalArgumentException("Could not match group 1 for regex https://login.microsoftonline.com/([^/]+) in authority \"" + authorityUrl + "\"");

JSONObject audience;
switch (group) {
case "common":
audience = new JSONObject().put("type", "AzureADandPersonalMicrosoftAccount");
break;
case "organizations":
audience = new JSONObject().put("type", "AzureADMultipleOrgs");
break;
case "consumers":
audience = new JSONObject().put("type", "PersonalMicrosoftAccount");
break;
default:
// assume `group` is a tenant id
audience = new JSONObject().put("type", "AzureADMyOrg").put("tenant_id", group);
break;
}
authorityJsonObj.put("type", AUTHORITY_TYPE_AAD);
authorityJsonObj.put("audience", audience);
} else if (b2cAuthorityMatcher.find()) {
type = AUTHORITY_TYPE_B2C;
b2cAuthorityUrl = authorityUrl;
} else if (aadMyOrgAuthorityMatcher.find()) {
type = AUTHORITY_TYPE_AAD;
audience_type = "AzureADMyOrg";
audience_tenantId = aadMyOrgAuthorityMatcher.group(1);
authorityJsonObj.put("type", AUTHORITY_TYPE_B2C);
authorityJsonObj.put("authority_url", authorityUrl);
} else {
throw new IllegalArgumentException("Authority \"" + authorityUrl + "\" doesn't match AAD regex https://login.microsoftonline.com/([^/]+) or B2C regex https://([^/]+)/tfp/([^/]+)/.+");
}

authorityJsonObj
.put("type", type)
.put("authority_url", b2cAuthorityUrl)
.put("audience", audience_type == null ? null : new JSONObject()
.put("type", audience_type)
.put("tenant_id", audience_tenantId)
);

authoritiesJsonArr.put(authorityJsonObj);
}

Expand Down Expand Up @@ -396,8 +397,8 @@ private WritableMap msalResultToDictionary(@NonNull IAuthenticationResult result
map.putString("accessToken", result.getAccessToken());
map.putString("expiresOn", String.format("%s", result.getExpiresOn().getTime() / 1000));
String idToken = result.getAccount().getIdToken();
if (idToken==null){
idToken = ((IMultiTenantAccount) result.getAccount()).getTenantProfiles().get(result.getTenantId()).getIdToken();
if (idToken == null) {
idToken = ((IMultiTenantAccount) result.getAccount()).getTenantProfiles().get(result.getTenantId()).getIdToken();
}
map.putString("idToken", idToken);
map.putArray("scopes", Arguments.fromArray(result.getScope()));
Expand Down
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: cde416483dac037923206447da6e1454df403714
DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de
FBLazyVector: e686045572151edef46010a6f819ade377dfeb4b
FBReactNativeSpec: e25cb775f8cbabc7d19f436b53e66a1423c9b832
Flipper: d3da1aa199aad94455ae725e9f3aa43f3ec17021
Expand All @@ -487,7 +487,7 @@ SPEC CHECKSUMS:
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
Flipper-RSocket: 127954abe8b162fcaf68d2134d34dc2bd7076154
FlipperKit: 8a20b5c5fcf9436cac58551dc049867247f64b00
glog: 40a13f7840415b9a77023fbcae0f1e6f43192af3
glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62
hermes-engine: 7d97ba46a1e29bacf3e3c61ecb2804a5ddd02d4f
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
MSAL: 3c4dc8c7337457a1ffc2bc6f72fb4c003687b50c
Expand Down
35 changes: 24 additions & 11 deletions example/src/b2cClient.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Platform } from 'react-native';

import PublicClientApplication from 'react-native-msal';
import type {
MSALAccount,
MSALConfiguration,
MSALInteractiveParams,
MSALResult,
MSALSilentParams,
MSALAccount,
MSALSignoutParams,
MSALSilentParams,
MSALWebviewParams,
MSALConfiguration,
} from 'react-native-msal';

export interface B2CPolicies {
Expand Down Expand Up @@ -70,9 +71,17 @@ export class B2CClient {
try {
// If we don't provide an authority, the PCA will use the one we passed to it when we created it
// (the sign in sign up policy)
return await this.pca.acquireToken(params);
} catch (error) {
if (error.message.includes(B2CClient.B2C_PASSWORD_CHANGE) && this.policyUrls.passwordReset) {
const result = await this.pca.acquireToken(params);
if (!result) {
throw new Error('Could not sign in: Result was undefined.');
}
return result;
} catch (error: unknown) {
if (
error instanceof Error &&
error.message.includes(B2CClient.B2C_PASSWORD_CHANGE) &&
this.policyUrls.passwordReset
) {
return await this.resetPassword(params);
} else {
throw error;
Expand All @@ -87,9 +96,13 @@ export class B2CClient {
// We provide the account that we got when we signed in, with the matching sign in sign up authority
// Which again, we set as the default authority so we don't need to provide it explicitly.
try {
return await this.pca.acquireTokenSilent({ ...params, account });
} catch (error) {
if (error.message.includes(B2CClient.B2C_EXPIRED_GRANT)) {
const result = await this.pca.acquireTokenSilent({ ...params, account });
if (!result) {
throw new Error('Could not acquire token silently: Result was undefined.');
}
return result;
} catch (error: unknown) {
if (error instanceof Error && error.message.includes(B2CClient.B2C_EXPIRED_GRANT)) {
await this.pca.signOut({ ...params, account });
return await this.signIn(params);
} else {
Expand Down Expand Up @@ -143,15 +156,15 @@ export class B2CClient {
private async getAccountForPolicy(policyUrl: string): Promise<MSALAccount | undefined> {
const policy = policyUrl.split('/').pop();
const accounts = await this.pca.getAccounts();
return accounts.find((account) => account.identifier.includes(policy.toLowerCase()));
return accounts.find((account) => account.identifier.includes(policy!.toLowerCase()));
}
}

function makeAuthority(authorityBase: string, policyName: string) {
return `${authorityBase}/${policyName}`;
}

function makePolicyUrls(authorityBase, policyNames: B2CPolicies): B2CPolicies {
function makePolicyUrls(authorityBase: string, policyNames: B2CPolicies): B2CPolicies {
return Object.entries(policyNames).reduce(
(prev, [key, policyName]) => ({ ...prev, [key]: makeAuthority(authorityBase, policyName) }),
{} as B2CPolicies
Expand Down

0 comments on commit d0cc429

Please sign in to comment.