Skip to content
This repository has been archived by the owner on Feb 1, 2021. It is now read-only.

FIX(iOS): NSDictionary of contexts is not thread safe (crash) #169

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/ios/CordovaAdalPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// Implements Apache Cordova plugin for Microsoft Azure ADAL
@interface CordovaAdalPlugin : CDVPlugin

- (void)pluginInitialize;

// AuthenticationContext methods
- (void)createAsync:(CDVInvokedUrlCommand *)command;
- (void)acquireTokenAsync:(CDVInvokedUrlCommand *)command;
Expand All @@ -22,9 +24,6 @@
- (void)tokenCacheReadItems:(CDVInvokedUrlCommand *)command;
- (void)tokenCacheDeleteItem:(CDVInvokedUrlCommand *)command;

+ (ADAuthenticationContext *)getOrCreateAuthContext:(NSString *)authority
validateAuthority:(BOOL)validate;

- (void)setLogger:(CDVInvokedUrlCommand *)command;
- (void)setLogLevel:(CDVInvokedUrlCommand *) command;
@end
200 changes: 103 additions & 97 deletions src/ios/CordovaAdalPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,32 @@

#import <ADAL/ADAL.h>

@interface CordovaAdalPlugin() {
NSMutableDictionary *existingContexts;
}

@end

@implementation CordovaAdalPlugin

- (void)pluginInitialize
{
existingContexts = [NSMutableDictionary new];
}

- (void)createAsync:(CDVInvokedUrlCommand *)command
{
[self.commandDelegate runInBackground:^{
@try
{
NSString *authority = ObjectOrNil([command.arguments objectAtIndex:0]);
BOOL validateAuthority = [[command.arguments objectAtIndex:1] boolValue];

[CordovaAdalPlugin getOrCreateAuthContext:authority
validateAuthority:validateAuthority];

[self getOrCreateAuthContext:authority
validateAuthority:validateAuthority];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];

[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
@catch (ADAuthenticationError *error)
Expand All @@ -47,14 +58,14 @@ - (void)acquireTokenAsync:(CDVInvokedUrlCommand *)command
NSURL *redirectUri = [NSURL URLWithString:[command.arguments objectAtIndex:4]];
NSString *userId = ObjectOrNil([command.arguments objectAtIndex:5]);
NSString *extraQueryParameters = ObjectOrNil([command.arguments objectAtIndex:6]);

ADAuthenticationContext *authContext = [CordovaAdalPlugin getOrCreateAuthContext:authority
validateAuthority:validateAuthority];
ADAuthenticationContext *authContext = [self getOrCreateAuthContext:authority
validateAuthority:validateAuthority];
// `x-msauth-` redirect url prefix means we should use brokered authentication
// https://github.com/AzureAD/azure-activedirectory-library-for-objc#brokered-authentication
authContext.credentialsType = (redirectUri.scheme && [redirectUri.scheme hasPrefix: @"x-msauth-"]) ?
AD_CREDENTIALS_AUTO : AD_CREDENTIALS_EMBEDDED;

AD_CREDENTIALS_AUTO : AD_CREDENTIALS_EMBEDDED;
// TODO iOS sdk requires user name instead of guid so we should map provided id to a known user name
userId = [CordovaAdalUtils mapUserIdToUserName:authContext
userId:userId];
Expand All @@ -67,7 +78,7 @@ - (void)acquireTokenAsync:(CDVInvokedUrlCommand *)command
userId:userId
extraQueryParameters:extraQueryParameters
completionBlock:^(ADAuthenticationResult *result) {

NSMutableDictionary *msg = [CordovaAdalUtils ADAuthenticationResultToDictionary: result];
CDVCommandStatus status = (AD_SUCCEEDED != result.status) ? CDVCommandStatus_ERROR : CDVCommandStatus_OK;
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:status messageAsDictionary: msg];
Expand All @@ -94,14 +105,14 @@ - (void)acquireTokenSilentAsync:(CDVInvokedUrlCommand *)command
NSString *resourceId = ObjectOrNil([command.arguments objectAtIndex:2]);
NSString *clientId = ObjectOrNil([command.arguments objectAtIndex:3]);
NSString *userId = ObjectOrNil([command.arguments objectAtIndex:4]);

ADAuthenticationContext *authContext = [CordovaAdalPlugin getOrCreateAuthContext:authority
validateAuthority:validateAuthority];

ADAuthenticationContext *authContext = [self getOrCreateAuthContext:authority
validateAuthority:validateAuthority];
// TODO iOS sdk requires user name instead of guid so we should map provided id to a known user name
userId = [CordovaAdalUtils mapUserIdToUserName:authContext
userId:userId];

[authContext acquireTokenSilentWithResource:resourceId
clientId:clientId
redirectUri:nil
Expand All @@ -128,23 +139,23 @@ - (void)tokenCacheClear:(CDVInvokedUrlCommand *)command
@try
{
ADAuthenticationError *error;

ADKeychainTokenCache* cacheStore = [ADKeychainTokenCache new];

NSArray *cacheItems = [cacheStore allItems:&error];

for (int i = 0; i < cacheItems.count; i++)
{
[cacheStore removeItem: cacheItems[i] error: &error];
}

if (error != nil)
{
@throw(error);
}

CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];

[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
@catch (ADAuthenticationError *error)
Expand All @@ -162,27 +173,27 @@ - (void)tokenCacheReadItems:(CDVInvokedUrlCommand *)command
@try
{
ADAuthenticationError *error;

ADKeychainTokenCache* cacheStore = [ADKeychainTokenCache new];

//get all items from cache
NSArray *cacheItems = [cacheStore allItems:&error];

NSMutableArray *items = [NSMutableArray arrayWithCapacity:cacheItems.count];

if (error != nil)
{
@throw(error);
}

for (id obj in cacheItems)
{
[items addObject:[CordovaAdalUtils ADTokenCacheStoreItemToDictionary:obj]];
}

CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
messageAsArray:items];

[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
@catch (ADAuthenticationError *error)
Expand All @@ -199,55 +210,55 @@ - (void)tokenCacheDeleteItem:(CDVInvokedUrlCommand *)command
@try
{
ADAuthenticationError *error;

NSString *authority = ObjectOrNil([command.arguments objectAtIndex:0]);
BOOL validateAuthority = [[command.arguments objectAtIndex:1] boolValue];
NSString *itemAuthority = ObjectOrNil([command.arguments objectAtIndex:2]);
NSString *resourceId = ObjectOrNil([command.arguments objectAtIndex:3]);
NSString *clientId = ObjectOrNil([command.arguments objectAtIndex:4]);
NSString *userId = ObjectOrNil([command.arguments objectAtIndex:5]);

ADAuthenticationContext *authContext = [CordovaAdalPlugin getOrCreateAuthContext:authority
validateAuthority:validateAuthority];

ADAuthenticationContext *authContext = [self getOrCreateAuthContext:authority
validateAuthority:validateAuthority];
// TODO iOS sdk requires user name instead of guid so we should map provided id to a known user name
userId = [CordovaAdalUtils mapUserIdToUserName:authContext
userId:userId];

ADKeychainTokenCache* cacheStore = [ADKeychainTokenCache new];

//get all items from cache
NSArray *cacheItems = [cacheStore allItems:&error];

if (error != nil)
{
@throw(error);
}

for (ADTokenCacheItem* item in cacheItems)
{
NSDictionary *itemAllClaims = [[item userInformation] allClaims];

NSString * userUniqueName = (itemAllClaims && itemAllClaims[@"unique_name"]) ? itemAllClaims[@"unique_name"] : nil;

if ([itemAuthority isEqualToString:[item authority]]
&& ((userUniqueName != nil && [userUniqueName isEqualToString:userId])
|| [userId isEqualToString:[[item userInformation] userId]])
&& [clientId isEqualToString:[item clientId]]
// resource could be nil which is fine
&& ((!resourceId && ![item resource]) || [resourceId isEqualToString:[item resource]])) {

//remove item
[cacheStore removeItem:item error: &error];

if (error != nil)
{
@throw(error);
}
}

}

CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
Expand All @@ -262,70 +273,65 @@ - (void)tokenCacheDeleteItem:(CDVInvokedUrlCommand *)command

- (void) setLogger:(CDVInvokedUrlCommand *)command
{
[self.commandDelegate runInBackground:^{
[ADLogger setLogCallBack:^(ADAL_LOG_LEVEL logLevel, NSString *message, NSString *additionalInfo, NSInteger errorCode, NSDictionary *userInfo) {
NSMutableDictionary *logItem = [[NSMutableDictionary alloc] init];

[logItem setObject:[NSNumber numberWithInt:logLevel] forKey:@"level"];
[logItem setValue:message forKey:@"message"];
[logItem setValue:additionalInfo forKey:@"additionalInfo"];
[logItem setValue:[NSNumber numberWithInteger:errorCode] forKey:@"errorCode"];

CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
messageAsDictionary:logItem];
[pluginResult setKeepCallbackAsBool:YES];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
[self.commandDelegate runInBackground:^{
[ADLogger setLogCallBack:^(ADAL_LOG_LEVEL logLevel, NSString *message, NSString *additionalInfo, NSInteger errorCode, NSDictionary *userInfo) {
NSMutableDictionary *logItem = [[NSMutableDictionary alloc] init];
[logItem setObject:[NSNumber numberWithInt:logLevel] forKey:@"level"];
[logItem setValue:message forKey:@"message"];
[logItem setValue:additionalInfo forKey:@"additionalInfo"];
[logItem setValue:[NSNumber numberWithInteger:errorCode] forKey:@"errorCode"];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
messageAsDictionary:logItem];
[pluginResult setKeepCallbackAsBool:YES];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}

- (void) setLogLevel:(CDVInvokedUrlCommand *)command
{
[self.commandDelegate runInBackground:^{
@try {
NSNumber *logLevel = [command.arguments objectAtIndex:0];
[ADLogger setLevel:[logLevel intValue]];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];

[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

@catch(ADAuthenticationError* error) {
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
messageAsDictionary:[CordovaAdalUtils ADAuthenticationErrorToDictionary:error]];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
}];
[self.commandDelegate runInBackground:^{
@try {
NSNumber *logLevel = [command.arguments objectAtIndex:0];
[ADLogger setLevel:[logLevel intValue]];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
@catch(ADAuthenticationError* error) {
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
messageAsDictionary:[CordovaAdalUtils ADAuthenticationErrorToDictionary:error]];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
}];
}

static NSMutableDictionary *existingContexts = nil;

+ (ADAuthenticationContext *)getOrCreateAuthContext:(NSString *)authority
- (ADAuthenticationContext *)getOrCreateAuthContext:(NSString *)authority
validateAuthority:(BOOL)validate
{
if (!existingContexts)
{
existingContexts = [NSMutableDictionary dictionaryWithCapacity:1];
}

ADAuthenticationContext *authContext = [existingContexts objectForKey:authority];

if (!authContext)
{
ADAuthenticationError *error;

authContext = [ADAuthenticationContext authenticationContextWithAuthority:authority
validateAuthority:validate
error:&error];
if (error != nil)
@synchronized(self->existingContexts) {
ADAuthenticationContext *authContext = [existingContexts objectForKey:authority];

if (!authContext)
{
@throw(error);
ADAuthenticationError *error;

authContext = [ADAuthenticationContext authenticationContextWithAuthority:authority
validateAuthority:validate
error:&error];
if (error != nil)
{
@throw(error);
}

[existingContexts setObject:authContext forKey:authority];
}

[existingContexts setObject:authContext forKey:authority];
return authContext;
}

return authContext;
}

static id ObjectOrNil(id object)
Expand Down