Authorization session always ask for user password in helper - objective-c

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.

Related

Trying to use TwitterLib 25 with GAS

So using the following simple code to just test tweet to Twitter. I have signed up for the account and have all the pertinent info. The only thing I may be doing wrong is putting the wrong key/secret in the wrong area. Here is my Twitter dev setup:
This is my simple code:
function sendTweet(status) {
status = "Is this a cool tweet?";
var twitterKeys = {
TWITTER_CONSUMER_KEY: '**************************',
TWITTER_CONSUMER_SECRET: '**************************',
TWITTER_ACCESS_TOKEN: '**************************-**************************',
TWITTER_ACCESS_SECRET: '**************************',
};
var props = PropertiesService.getScriptProperties();
props.setProperties(twitterKeys);
var service = new Twitterlib.OAuth(props);
if (service.hasAccess()) {
var response = service.sendTweet(status);
if (response) {
Logger.log('Tweet ID ' + response.id_str);
} else {
// Tweet could not be sent
// Go to View -> Logs to see the error message
}
}
}
My consumer key/secret -> API Key and Secret below and my access token/secret -> Access Token and Secret below. I am seeing the following error which doesn't give me a lot to go on as far as errors and been fighting to figure this out for days.
Send tweet failure. Error was:
{"name":"Exception"}
options were:
{"method":"POST","payload":"status=Is%20this%20a%20cool%20tweet%3F","headers":{"Authorization":"OAuth oauth_consumer_key=\"**************************\", oauth_nonce=\"**************************\", oauth_signature=\"**************************%3D\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\"1644456180\", oauth_token=\"**************************-**************************\", oauth_version=\"1.0\""},"escaping":false}
Any help or resources would be a huge help in figuring this out!! Thank you in advance.
phi
This turned out being I needed to turn on essential API access in the Twitter API Developer site. Once I turned that on, and retried, it was a success. Hope that helps anyone else.

Auth0 Get userId in response payload?

When a user logins using the Auth0 lock on my client side, I get an idToken, but also an idTokenPayload which looks like this:
idTokenPayload = {
audience: "AUTH0CLIENTID",
exp: 1494190538,
iat: 1494154538,
iss: "AUTH0DOMAIN"
sub: "USERNAME"
};
Would it be possible to return the userId in Auth0's database instead of the username in the sub field?
The reason I want to do this is that I want to keep Auth0's db for users, and I have on my server-side some Profile, Post, Comment etc entities which have a userId column. Right now before each request on my entities I need to populate the user by doing an extra request: let id = Profile.find("... where username === auth0.sub").getId(); (pseudo-code of course).
With the C# lock sdk, you get back an Auth0User after the call to the LoginAsync method in the Auth0 client. Let's call this variable auth0User. If I look at auth0User.Profile, a JObject (it's a JSON object if you're not using C#), it contains a JSON array named "identities". My identities variable initialization looks like:
var identities = (JArray)auth0User.Profile["identities"];
This array contains all the identity providers associated with the user. If like me you haven't attached any other sign in besides Auth0, there will be just 1 entry here. Each object in this JSON array will contain a "provider" string and a "user_id" string. If the provider says "auth0" then it's from Auth0. Since I don't use FB or other account types I'm not exactly sure what they say. Here's my C# code to get the UserID:
var identities = (JArray)auth0User.Profile["identities"];
if (identities != null)
{
foreach (var identity in identities)
{
var provider = (string)identity["provider"];
if (string.Equals(provider, "auth0"))
{
UserID = (string)identity["user_id"];
break;
}
}
}
I believe that this should all be provided standard without needing to add any rules or webhooks. This article should explain in more detail and also gives examples in javascript: auth0 normalized user profile

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.

viaRemember not work - laravel

Auth :: attempt works perfect, but when you pass the second parameter "true" apparently does not care or does not recover with viaRemember
viaRemember fails to work, check this
controller User
`$`userdata = array(
'email' => trim(Input::get('username')),
'password' => trim(Input::get('password'))
);
if(Auth::attempt(`$`userdata, true)){
return Redirect::to('/dashboard');
}
view 'dashboard', always show 777
#if (Auth::viaRemember())
{{666}}
#else
{{777}}
#endif
I have hit the same obstacle, so looking into the code one can see that viaRemember is not meant to be used as a function to check if the user was logged into the system in one of all the ways a user can be logged in.
'viaRemember' is meant to check if a user was logged into the system specifically via the `viaRemember' cookie.
From what I gather, authentication of user is remembered in two ways:
a via remember cookie.
The cookie value is compared to the via remember field in the users table.
a session cookie.
The cookie value is used in the server to get the session from the
session store. On the session object from the store there is data attached. One of the
data items is the user id connected to the session. The first time
the session was created, the system attached the user id to the data
of the season.
In Illuminate\Auth\Guard class:
public function user()
{
if ($this->loggedOut) return;
// If we have already retrieved the user for the current request we can just
// return it back immediately. We do not want to pull the user data every
// request into the method because that would tremendously slow an app.
if ( ! is_null($this->user))
{
return $this->user;
}
$id = $this->session->get($this->getName());
// First we will try to load the user using the identifier in the session if
// one exists. Otherwise we will check for a "remember me" cookie in this
// request, and if one exists, attempt to retrieve the user using that.
$user = null;
if ( ! is_null($id))
{
$user = $this->provider->retrieveByID($id);
}
// If the user is null, but we decrypt a "recaller" cookie we can attempt to
// pull the user data on that cookie which serves as a remember cookie on
// the application. Once we have a user we can return it to the caller.
$recaller = $this->getRecaller();
if (is_null($user) && ! is_null($recaller))
{
$user = $this->getUserByRecaller($recaller);
}
return $this->user = $user;
}
The getUserByRecaller function is called only if the session cookie authentication did not work.
The viaRemember flag is only set in the getUserByRecaller function. The viaRemember method is only a simple getter method.
public function viaRemember()
{
return $this->viaRemember;
}
So in the end, we can use Auth::check() that does make all the checks including the viaRemember check. It calls the user() function in the Guard class.
It seems also the viaRemember is only an indicator. You need to do a type of Auth::check() the will get the process of authentication started and so the user() function will be called.
It seems that your project is on Laravel 4.0 but viaRemember() is added in Laravel 4.1! So that's expected.
in config\session.php file change the 'expire_on_close' = false to true and once you close restart your browser, it must be ok.

How to list all the activities in a specific google domain?

Any ideas on how can I list all the activities in my domain by using the new google+ domain's API in java?
The Developers' Live video shows at 4:00 minute mark that you can do something like this:
Plus.Activities.List listActivities = plus.activities().list("me", "domain");
The Link for this code is here.
But when I actually run the same line of code it shows me the following error.
{
"code" : 400,
"errors" : [ {
"domain" : "global",
"location" : "collection",
"locationType" : "parameter",
"message" : "Invalid string value: 'domain'. Allowed values: [user]",
"reason" : "invalidParameter"
} ],
"message" : "Invalid string value: 'domain'. Allowed values: [user]"
}
The error makes sense as in the activities.list documentation it says that "user" is the only acceptable value for collection and not "domain."
So what should I do about this issue?
As you say, the only available way is to list posts by the currently logged user. You have to use user delegation (with service accounts) and loop over all users in the domain in order to get all published activities.
You can use the updated field on the response to check if there is anything new in a user's list of activities.
This line of thought applies to the whole Domains API: every operation is done on behalf of a user, there is no "admin" account with superpowers. This can be a limitation when acting on a big number of users, as you are forced to authenticate for each one in turn (if someone has an idea on how to achieve this in a more efficient way, please share!)
As the documentation sais, only "public" is allowed:
https://developers.google.com/+/api/latest/activities/list
However even using the code provided in the example in the API doc, after going through successful authentication I get 0 activities.
/** List the public activities for the authenticated user. */
private static void listActivities() throws IOException {
System.out.println("Listing My Activities");
// Fetch the first page of activities
Plus.Activities.List listActivities = plus.activities().list("me", "public");
listActivities.setMaxResults(100L);
// Pro tip: Use partial responses to improve response time considerably
listActivities.setFields("nextPageToken,items(id,url,object/content)");
ActivityFeed activityFeed = listActivities.execute();
// Unwrap the request and extract the pieces we want
List<Activity> activities = activityFeed.getItems();
System.out.println("Number of activities: " + activities.size());
// Loop through until we arrive at an empty page
while (activities != null) {
for (Activity activity : activities) {
System.out.println("ID " + activity.getId() + " Content: " +
activity.getObject().getContent());
}
// We will know we are on the last page when the next page token is null.
// If this is the case, break.
if (activityFeed.getNextPageToken() == null) {
break;
}
// Prepare to request the next page of activities
listActivities.setPageToken(activityFeed.getNextPageToken());
// Execute and process the next page request
activityFeed = listActivities.execute();
activities = activityFeed.getItems();
}
}
Anybody know how to get this to work?
when you use Google+ API you must use "public" but when you use Google+ Domains API you must use "user" parameter value.