React-Native Zendesk Auth integration - objective-c

I am trying to integrate zendesk with react-native. The problem is that I don't know objective-c so I am having problem with the authentication.
I want to open ZenDeskChat, for that I can do it in 2 different way (anonymous or auth). To use the auth procedure I have to create a JWT from my server and send this token to zendesk before I open the chat. I copy some function from zendesk documentation but because my lake of knowladge with objective-c I can't figure out how to pass the token to zendesk.
I don't know how to use this code.
#implementation ZDKAuthJWT
​
- (void)getToken:(void (^)(NSString * _Nullable, NSError * _Nullable))completion {
// Call completion block once you get the JWT token
completion(<"token">, <"error">);
}
I am supposed to call it somewhere here :
RCT_EXPORT_METHOD(setUserIdentity: (NSDictionary *)user) {
if (user[#"token"]) {
id<ZDKObjCIdentity> userIdentity = [[ZDKObjCJwt alloc] initWithToken:user[#"token"]];
[[ZDKZendesk instance] setIdentity:userIdentity];
RCTAuthJWT *authenticator = [RCTAuthJWT new];
[ZDKChat.instance setIdentityWithAuthenticator:authenticator];
} else {
id<ZDKObjCIdentity> userIdentity = [[ZDKObjCAnonymous alloc] initWithName:user[#"name"] // name is nullable
email:user[#"email"]]; // email is nullable
[[ZDKZendesk instance] setIdentity:userIdentity];
}
}

Related

Parse Server data not shared between app and today extension

I'm using a parse server for delivering news and calendar information to iOS and Android apps. (Therefore I don't need any user accounts.)
I followed the instructions on the parse site (https://docs.parseplatform.org/ios/guide/#enable-data-sharing) to enable data sharing but the data doesn't get shared. I have to download the information in the app and the today extension.
Here is the code I use to initialize the parse SDK:
+ (void)establishConnectionForExtension:(BOOL)extension {
// enable app groups
if (extension) [Parse enableDataSharingWithApplicationGroupIdentifier:#"group.de.company.app" containingApplication:#"de.company.app"];
else [Parse enableDataSharingWithApplicationGroupIdentifier:#"group.de.company.app"];
// initialize parse
[Parse initializeWithConfiguration:[ParseClientConfiguration configurationWithBlock:^(id<ParseMutableClientConfiguration> _Nonnull configuration) {
configuration.applicationId = PARSE_ID;
configuration.clientKey = PARSE_KEY;
configuration.server = #"https://";
configuration.localDatastoreEnabled = YES;
}]];
}
I found the solution:
+ (void)establishConnectionForExtension:(BOOL)extension {
[Parse initializeWithConfiguration:[ParseClientConfiguration configurationWithBlock:^(id<ParseMutableClientConfiguration> _Nonnull configuration) {
configuration.applicationGroupIdentifier = #"group.de.company.app";
configuration.applicationId = PARSE_ID;
configuration.clientKey = PARSE_KEY;
configuration.localDatastoreEnabled = YES;
configuration.server = #"https://";
if (extension) configuration.containingApplicationBundleIdentifier = #"de.company.app";
}]];
}
It would be great to find this in the official Parse documentation... (I needed three days to to get this working.)

How do I force AWS Cognito to retrieve an IdentityId from the server?

In the iOS SDK (v2.4.8) I can't logout a user and then login as a different user correctly.
The (correct) cognityIdentityId returned by AWS for the first user (since an app start) is also returned for the second user (unless the app is restarted). This gives access to the AWSCognitoDataset of one user by another.
I think this is because the iOS SDK has cached the id and the documented call to clear that cache, doesn't fully work.
When logging in:
// one-off initialisation
self.credentialsProvider = AWSCognitoCredentialsProvider(regionType:AWSRegionType.USEast1, identityPoolId:Constants.CognitoIdentityPoolId)
let configuration = AWSServiceConfiguration(region:AWSRegionType.USEast1, credentialsProvider:self.credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration
…
// I get idToken from my external provider serice (Auth0)
func doAmazonLogin(idToken: String, success : () -> (), _ failure : (NSError) -> ()) {
var task: AWSTask?
//Initialize clients for new idToken
if self.credentialsProvider?.identityProvider.identityProviderManager == nil || idToken != Application.sharedInstance.retrieveIdToken() {
let logins = [Constants.CognitoIDPUrl: idToken]
task = self.initializeClients(logins)
} else {
//Use existing clients
self.credentialsProvider?.invalidateCachedTemporaryCredentials()
task = self.credentialsProvider?.getIdentityId()
}
//Make login
task!.continueWithBlock { (task: AWSTask!) -> AnyObject! in
if (task.error != nil) {
failure(task.error!)
} else {
// the task result will contain the identity id
let cognitoId:String? = task.result as? String
self.customIdentityProviderManager!.addToken(Constants.CognitoIDPUrl, value:idToken)
//Store Cognito token in keychain
Application.sharedInstance.storeCognitoToken(cognitoId)
success()
}
return nil
}
}
func initializeClients(logins: [NSObject:AnyObject]?) -> AWSTask? {
//Create identity provider managet with logins
let manager = CustomIdentityProviderManager(tokens: logins!)
self.credentialsProvider?.setIdentityProviderManagerOnce(manager)
return self.credentialsProvider?.getIdentityId()
}
When logging out:
// Clear ALL saved values for this provider (identityId, credentials, logins). [docs][1]
let keychain = A0SimpleKeychain(service:"…")
keychain.clearAll()
I've also tried adding:
credentialsProvider!.clearCredentials()
credentialsProvider!.clearKeychain()
Is anyone using the AWS iOS SDK and has coded logout successfully such that a new user can login cleanly?
There is the oddly named method credentialsProvider.setIdentityProviderManagerOnce() - I can't find this documented but its name suggests that it should only be called once per session. But if keychain.clearAll() removes logins then one would need to call setIdentityProviderManagerOnce each time a new user logins in in order to setup logins each time.
Can you describe your login/logout flow a bit more? Cognito doesn't support multiple logins from the same provider per identity, so it sounds like, unless you're using multiple, it isn't actually changing the identity.
In any case, have you tried the clearKeyChain method on the credentials provider? It's largely for use cases like this - clearing everything.

Authorization session always ask for user password in helper

I have an Objective-C application (https://github.com/NBICreator/NBICreator) with a privileged helper tool.
I have a few different privileged tasks the helper will need to perform during one build, and I want to have the user authenticate only once to perform those tasks.
The authorization works, but I can't seem to reuse the session in the helper. The user always have to authenticate for every step, even if I supply the exact same right to the Security Server and use the same AuthenticationRef.
I have read the docs and tested the pre-authentication methods in the main app first, and tried printing out (and retaining the auth session in the helper as well). But nothing I've tried have yet to work successfully.
I need som help figuring out why the Security Server feel the need to reauthenticate.
The code on GitHub in the Master Branch is current, and what I'm trying and testing by changing things back and forth. With that code, the user have to authenticate each time I call a helper function, even if I use the same authentication right.
This is what the right looks like in the authorization database:
class = rule;
created = "470329367.933364";
"default-prompt" = {
"" = "NBICreator is trying to start an Imagr workflow.";
};
identifier = "com.github.NBICreator";
modified = "470329367.933364";
requirement = "identifier \"com.github.NBICreator\" and anchor apple generic and certificate leaf[subject.CN] = \"Mac Developer: Erik Berglund (BXUF2UUW7E)\" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */";
rule = (
"authenticate-admin"
);
version = 0;
This is where I define the right: https://github.com/NBICreator/NBICreator/blob/master/NBICreator/Helper/NBCHelperAuthorization.m#L36-L43
NSStringFromSelector(#selector(authorizeWorkflowImagr:withReply:)) : #{
kCommandKeyAuthRightName : #"com.github.NBICreator.workflowImagr",
kCommandKeyAuthRightDefault : #kAuthorizationRuleAuthenticateAsAdmin,
kCommandKeyAuthRightDesc : NSLocalizedString(
#"NBICreator is trying to start an Imagr workflow.",
#"prompt shown when user is required to authorize to add a user"
)
},
And this is where I check if the user is authenticated:
https://github.com/NBICreator/NBICreator/blob/master/NBICreator/Helper/NBCHelperAuthorization.m#L222-L253
+ (NSError *)checkAuthorization:(NSData *)authData command:(SEL)command authRef:(AuthorizationRef)authRef {
#pragma unused(authData)
NSError * error;
OSStatus err = 0;
AuthorizationItem oneRight = { NULL, 0, NULL, 0 };
AuthorizationRights rights = { 1, &oneRight };
oneRight.name = [#"com.github.NBICreator.workflowImagr" UTF8String];
err = AuthorizationCopyRights(
authRef,
&rights,
NULL,
kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed,
NULL
);
if ( err != errAuthorizationSuccess ) {
NSString *message = CFBridgingRelease(SecCopyErrorMessageString(err, NULL));
error = [NSError errorWithDomain:[[NSProcessInfo processInfo] processName] code:err userInfo:#{ NSLocalizedDescriptionKey : message }];
}
return error;
}
As you can see there, I'm testing by setting a hardcoded right name to try and resue that right to the Security Server.
I'm stumped right now, and can't seem to find a way forward. Hoping someone here might know where to look.
I found the answer in how I set up the right in the rights database.
I was using the default code from the Apple example EvenBetterAuthorizationExample for creating and using the rights. But that only points to this documentation for the different rights to use.
None of those were helping me with authenticatin once and the having the helper be authenticated the next time I request authentication for the same right.
After some digging and looking into the actual rules in the authorization database I found a way to copy the rule that AuthenticateWithPrivileges uses, that works by authenticating for 5 minutes until requiring re-authentication.
So, I changed my code for creating my custom right from this:
NSStringFromSelector(#selector(addUsersToVolumeAtPath:userShortName:userPassword:authorization:withReply:)) : #{
kCommandKeyAuthRightName : NBCAuthorizationRightAddUsers,
kCommandKeyAuthRightDefault : #kAuthorizationRuleAuthenticateAsAdmin,
kCommandKeyAuthRightDesc : NSLocalizedString(
#"NBICreator is trying to add a user.",
#"prompt shown when user is required to authorize to add a user"
)
},
To this:
NSStringFromSelector(#selector(addUsersToVolumeAtPath:userShortName:userPassword:authorization:withReply:)) : #{
kCommandKeyAuthRightName : NBCAuthorizationRightAddUsers,
kCommandKeyAuthRightDefault : #{
#"class": #"user",
#"group": #"admin",
#"timeout": #(300),
#"version": #(1),
},
kCommandKeyAuthRightDesc : NSLocalizedString(
#"NBICreator is trying to add a user.",
#"prompt shown when user is required to authorize to add a user"
)
},
So, instead of using #kAuthorizationRuleAuthenticateAsAdmin as the RightDefault, i passed in this dict:
#{
#"class": #"user",
#"group": #"admin",
#"timeout": #(300),
#"version": #(1),
},
After that, my helper can request authorization from the user, and then I can reuse that session for the same right during 5 minutes without asking the user again.

How to query what the worklight server host information is in hybrid app

Is there a way to query the worklight server URL information from inside of a hybrid application? I have a need when I build an app for a remote server to establish credentials with the server that WL is installed on before I can call adapter procedures from that server. Currently I'm doing this by performing an dojo.xhrGet on the console. The URL is hardcoded in the app at this time. For many reasons I would like be able to query this information at run time. In iOS this information is stored in worklight.plist and in android it is in assets/wlclient.properties.
I'm running WL 6.1 and I've tried calling get properties as below
WL.Client.getAppProperty(WL.AppProp.WORKLIGHT_ROOT_URL);
WL.Client.getAppProperty(WL.AppProp.APP_SERVICES_URL);
but all they return is
/MyApp/apps/services/api/simpleApp/common/
/MyApp/apps/services/
I need to get to the host information like the following found in the android wlclient.propeties
wlServerProtocol = https
wlServerHost = zzzz.aaa.bb.com
wlServerPort = 15024
wlServerContext = /
or in iOS worklight.plist
<key>protocol</key>
<string>https</string>
<key>host</key>
<string>zzzz.aaa.bb.com</string>
<key>port</key>
<string>15024</string>
<key>wlServerContext</key>
<string>/</string>
Any help will be greatly welcomed.
In Worklight, these properties are not exposed to the client-side.
You can submit a feature request.
Some alternatives:
Re-route/Divert some WL.Client Adapter Invocation traffic to WL Server through different URL (for PCI payment and security requirements)?
Detect Worklight Server Hostname/IP Address from Worklight Client code
Is it possible to access to the HTTPRequest in the worklight adapter implementation?
I did a little more investigation and was able to write a cordova plugin for android and ios to read in the value from the prospective property files that WL generates. Below are the different sources. See WorkLight Getting Started Section 6 for more information on writing a cordova plugin. It was actually fairly easy. Once you get past how... different Objective-C is, it took just a few minutes.
Android code
WorklightPropertiesPlugin.java
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.json.JSONArray;
import org.json.JSONException;
import android.content.res.AssetManager;
import android.content.res.Resources;
public class WorklightPropertiesPlugin extends CordovaPlugin {
#Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
boolean handled = false;
String responseText = "";
if(action.equalsIgnoreCase("serverURL")) {
try {
AssetManager am = this.cordova.getActivity().getApplicationContext().getAssets();
InputStream is = am.open("wlclient.properties");
Properties prop = new Properties();
prop.load(is);
String wlServerProtocol = prop.getProperty("wlServerProtocol");
String wlServerHost = prop.getProperty("wlServerHost");
String wlServerPort = prop.getProperty("wlServerPort");
String wlServerContext = prop.getProperty("wlServerContext");
responseText = wlServerProtocol + "://" + wlServerHost + ":" + wlServerPort + wlServerContext;
handled = true;
} catch(IOException e) {
callbackContext.error("Error loading properties " + e.getLocalizedMessage());
}
}
if(handled) {
callbackContext.success(responseText);
}
return handled;
}
}
iOS code
WorklightPropertiesPlugin.h
#import <Foundation/Foundation.h>
#import <Cordova/CDV.h>
#interface WorklightPropertiesPlugin : CDVPlugin
- (void) serverURL:(CDVInvokedUrlCommand*) command;
#end
WorklightPropertiesPlugin.m
#import "WorklightPropertiesPlugin.h"
#implementation WorklightPropertiesPlugin
- (void) serverURL:(CDVInvokedUrlCommand*)command{
NSString * response = nil;
CDVPluginResult *pluginResult = nil;
NSString* plistLoc =[[NSBundle mainBundle]pathForResource:#"worklight" ofType:#"plist"];
if(plistLoc == nil){
[NSException raise:#"[Remote Load Initialization Error]" format:#"Unable to locate worklight.plist"];
}
NSDictionary* wlProps = [NSDictionary dictionaryWithContentsOfFile:plistLoc];
NSString* proto = [wlProps valueForKey:#"protocol"];
NSString* host = [wlProps valueForKey:#"host"];
NSString* port = [wlProps valueForKey:#"port"];
NSString* wlServerContext = [wlProps valueForKey:#"wlServerContext"];
if(proto == nil || host == nil || port == nil){
[NSException raise:#"[Remote Load Initialization Error]" format:#"host, port and protocol are all required keys in worklight.plist"];
}
response = [NSString stringWithFormat:#"%#://%#:%#%#", proto, host, port, wlServerContext];
pluginResult = [CDVPluginResult resultWithStatus: CDVCommandStatus_OK messageAsString:response];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
#end

Phonegap Plugins

I need some Help regarding Phonegap plugins:
First , i have a JS File which invoke the native code using the phonegap.exec() containing the result handler function, error handler function , a reference to the native class's name and native function name as well as an array of parameters . My question is : if it is possible to invoke the function (native method) with given specified parameters?
That means : in my phonegap plugin file (.h & .m)
1- can i specify the arguments and their Types (NSInteger, NSString) like java
void myMethod(int a , string b){}
-(void) myMethod:(NSMutableArray )arguments withDict:(NSMutableDictionary)options;
Or is it as specified by Phonegap or Objective C ?
2- And what does it withDict means in this case ??
3- can i addicate this?
4- Why should my Code looks like this ?
-(void)myMethod: (NSMutableArray*)arguments withDict:(NSMutableDictionary*)options {
NSString *callbackID =[arguments pop];
NSString *myParam = #"";
NSArray *arrayArguments = [[arguments objectAtIndex:0] componentsSeparatedByString:#"|"];
NSString *stringArgument = ([arArguments objectAtIndex:0]);
I want to invoke my method like this :
why shall i put my arguments (as a String array element) then take it out , split it to get the right element from the String )?
Many Thanks for helping
Ok here's how you do it… The example I've added in below is a implementation of the Email plugin in Phonegap, with multiple strings. You can always substitute my string code to identify NSNumbers, or any other kind of arguments.
In JS ::
First I create the arguments with their values. . .
var attachmentData = {};
attachmentData.data = userData;
attachmentData.fileName = fileName;
var mailData = {};
mailData.toRecipients = "";
mailData.subject = "Exporting Backup for user data";
mailData.body = "User data for application. Please find the attachment containing the data from the last week.";
nativeMail(attachmentData, mailData);
Now we call a function that packages all this data for a Phonegap Plugin and sends it to the plugin class
function nativeMail(attachmentData, mailData){
e_args = {};
if(mailData.toRecipients)
e_args.toRecipients = mailData.toRecipients;
if(mailData.subject)
e_args.subject = mailData.subject; //"Hello World";
if(mailData.body)
e_args.body = mailData.body;
//call to phonegap plugin for native mail iOS
e_args.attachmentFileName = attachmentData.fileName;
e_args.datatoattach = attachmentData.data;
cordova.exec(null, fail, "EmailComposer", "showEmailComposer", [e_args]);
}
Now the EmailComposer.h file
- (void) showEmailComposer:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
And finally, how to take these arguments from the Mutablle Dictionary/Array and retrieve our string values.
- (void) showEmailComposer:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
{
NSString* toRecipientsString = [options valueForKey:#"toRecipients"];
NSString* ccRecipientsString = [options valueForKey:#"ccRecipients"];
NSString* bccRecipientsString = [options valueForKey:#"bccRecipients"];
NSString* subject = [options valueForKey:#"subject"];
NSString* body = [options valueForKey:#"body"];
NSString* isHTML = [options valueForKey:#"bIsHTML"];
. . . . . .
}
This is the only way to go about it. Its to do with the way that Phonegap itself handles data to be passed from your javascript web app to the native class. It cannot be changed. The MutableDictionary or the MutableArray will handle any kind of data you need it to. There are no limitations. However, the passing of this data can only be done using the format above. Once you have the options and arguments in the .m class, you are free to retrieve them or parse them into the data type you need.