Google task API authentication issue ruby - ruby-on-rails-3

I am having the problem to authenticate a user for google tasks.
At first it authenticates the user and do things perfect. But in the second trip it throws an error.
Signet::AuthorizationError - Authorization failed. Server message:
{
"error" : "invalid_grant"
}:
following is the code:
def api_client code=""
#client ||= (begin
client = Google::APIClient.new
client.authorization.client_id = settings.credentials["client_id"]
client.authorization.client_secret = settings.credentials["client_secret"]
client.authorization.scope = settings.credentials["scope"]
client.authorization.access_token = "" #settings.credentials["access_token"]
client.authorization.redirect_uri = to('/callbackfunction')
client.authorization.code = code
client
end)
end
get '/callbackfunction' do
code = params[:code]
c = api_client code
c.authorization.fetch_access_token!
result = c.execute("tasks.tasklists.list",{"UserId"=>"me"})
unless result.response.status == 401
p "#{JSON.parse(result.body)}"
else
redirect ("/oauth2authorize")
end
end
get '/oauth2authorize' do
redirect api_client.authorization.authorization_uri.to_s, 303
end
What is the problem in performing the second request?
UPDATE:
This is the link and parameters to user consent.
https://accounts.google.com/o/oauth2/auth?
access_type=offline&
approval_prompt=force&
client_id=somevalue&
redirect_uri=http://localhost:4567/oauth2callback&
response_type=code&
scope=https://www.googleapis.com/auth/tasks

The problem is fixed.
Solution:
In the callbackfunction the tokens which are received through the code provided by the user consent are stored in the database.
Then in other functions just retrieve those tokens from the database and use to process whatever you want against the google task API.
get '/callbackfunction' do
code = params[:code]
c = api_client code
c.authorization.fetch_access_token!
# store the tokens in the database.
end
get '/tasklists' do
# Retrieve the codes from the database and create a client
result = client.execute("tasks.tasklists.list",{"UserId"=>"me"})
unless result.response.status == 401
p "#{JSON.parse(result.body)}"
else
redirect "/oauth2authorize"
end
end

I am using rails, and i store the token only inside DB.
then using a script i am setting up new client before calling execute, following is the code.
client = Google::APIClient.new(:application_name => 'my-app', :application_version => '1.0')
client.authorization.scope = 'https://www.googleapis.com/auth/analytics.readonly'
client.authorization.client_id = Settings.ga.app_key
client.authorization.client_secret = Settings.ga.app_secret
client.authorization.access_token = auth.token
client.authorization.refresh_token = true
client.authorization.update_token!({access_token: auth.token})
client.authorization.fetch_access_token!
if client.authorization.refresh_token && client.authorization.expired?
client.authorization.fetch_access_token!
end
puts "Getting accounts list..."
result = client.execute(:api_method => analytics.management.accounts.list)
puts " ===========> #{result.inspect}"
items = JSON.parse(result.response.body)['items']
But,it gives same error you are facing,
/signet-0.4.5/lib/signet/oauth_2/client.rb:875:in `fetch_access_token': Authorization failed. Server message: (Signet::AuthorizationError)
{
"error" : "invalid_grant"
}
from /signet-0.4.5/lib/signet/oauth_2/client.rb:888:in `fetch_access_token!'
Please suggest why it is not able to use the given token? I have used oauth2, so user is already authorized. Now i want to access the api and fetch the data...
===================UPDATE ===================
Ok, two issues were there,
Permission is to be added to devise.rb,
config.omniauth :google_oauth2, Settings.ga.app_key,Settings.ga.app_secret,{
access_type: "offline",
approval_prompt: "" ,
:scope => "userinfo.email, userinfo.profile, plus.me, analytics.readonly"
}
refresh_token must be passed to the API call, otherwise its not able to authorize.
I hope this helps to somebody, facing similar issue.

Related

Shopify Multipass created_at Field

I am currently trying to implement a login to Shopify over the Storefront API via Multipass.
However, what it isn't clear to me from the Documentation on that Page, how the "created_at" Field is used. Since it states that this field should be filled with the current timestamp.
But what if the same users logs in a second time via Multipass, should it be filled with the timestamp of the second login.
Or should the original Multipass token be stored somewhere, and reused at a second login, instead of generating a new one?
Yes you need to set it always to the current time. I guess it stands for "token created at".
This is the code I use in Python:
class Multipass:
def __init__(self, secret):
key = SHA256.new(secret.encode('utf-8')).digest()
self.encryptionKey = key[0:16]
self.signatureKey = key[16:32]
def generate_token(self, customer_data_hash):
customer_data_hash['created_at'] = datetime.datetime.utcnow().isoformat()
cipher_text = self.encrypt(json.dumps(customer_data_hash))
return urlsafe_b64encode(cipher_text + self.sign(cipher_text))
def generate_url(self, customer_data_hash, url):
token = self.generate_token(customer_data_hash).decode('utf-8')
return '{0}/account/login/multipass/{1}'.format(url, token)
def encrypt(self, plain_text):
plain_text = self.pad(plain_text)
iv = get_random_bytes(AES.block_size)
cipher = AES.new(self.encryptionKey, AES.MODE_CBC, iv)
return iv + cipher.encrypt(plain_text.encode('utf-8'))
def sign(self, secret):
return HMAC.new(self.signatureKey, secret, SHA256).digest()
#staticmethod
def pad(s):
return s + (AES.block_size - len(s) % AES.block_size) * chr(AES.block_size - len(s) % AES.block_size)
And so
...
customer_object = {
**user,# customer data
"verified_email": True
}
multipass = Multipass(multipass_secret)
return multipass.generate_url(customer_object, environment["url"])
How can someone login a second time? If they are already logged in, they would not essentially be able to re-login without logging out. If they logged out, the multi-pass would assign a new timestamp. When would this flow occur of a user logging in a second time and not being issued a brand new login? How would they do this?

how to read the console output in python without executing any command

I have an API which gets the success or error message on console.I am new to python and trying to read the response. Google throws so many examples to use subprocess but I dont want to run,call any command or sub process. I just want to read the output after below API call.
This is the response in console when success
17:50:52 | Logged in!!
This is the github link for the sdk and documentation
https://github.com/5paisa/py5paisa
This is the code
from py5paisa import FivePaisaClient
email = "myemailid#gmail.com"
pw = "mypassword"
dob = "mydateofbirth"
cred={
"APP_NAME":"app-name",
"APP_SOURCE":"app-src",
"USER_ID":"user-id",
"PASSWORD":"pw",
"USER_KEY":"user-key",
"ENCRYPTION_KEY":"enc-key"
}
client = FivePaisaClient(email=email, passwd=pw, dob=dob,cred=cred)
client.login()
In general it is bad practice to get a value from STDOUT. There are some ways but it's pretty tricky (it's not made for it). And the problem doesn't come from you but from the API which is wrongly designed, it should return a value e.g. True or False (at least) to tell you if you logged in, and they don't do it.
So, according to their documentation it is not possible to know if you're logged in, but you may be able to see if you're logged in by checking the attribute client_code in the client object.
If client.client_code is equal to something then it should be logged in and if it is equal to something else then not. You can try comparing it's value when you successfully login or when it fails (wrong credential for instance). Then you can put a condition : if it is None or False or 0 (you will have to see this by yourself) then it is failed.
Can you try doing the following with a successful and failed login:
client.login()
print(client.client_code)
Source of the API:
# Login function :
# (...)
message = res["body"]["Message"]
if message == "":
log_response("Logged in!!")
else:
log_response(message)
self._set_client_code(res["body"]["ClientCode"])
# (...)
# _set_client_code function :
def _set_client_code(self, client_code):
try:
self.client_code = client_code # <<<< That's what we want
except Exception as e:
log_response(e)
Since this questions asks how to capture "stdout" one way you can accomplish this is to intercept the log message before it hits stdout.
The minimum code to capture a log message within a Python script looks this:
#!/usr/bin/env python3
import logging
logger = logging.getLogger(__name__)
class RequestHandler(logging.Handler):
def emit(self, record):
if record.getMessage().startswith("Hello"):
print("hello detected")
handler = RequestHandler()
logger.addHandler(handler)
logger.warning("Hello world")
Putting it all together you may be able to do something like this:
import logging
from py5paisa import FivePaisaClient
email = "myemailid#gmail.com"
pw = "mypassword"
dob = "mydateofbirth"
cred={
"APP_NAME":"app-name",
"APP_SOURCE":"app-src",
"USER_ID":"user-id",
"PASSWORD":"pw",
"USER_KEY":"user-key",
"ENCRYPTION_KEY":"enc-key"
}
client = FivePaisaClient(email=email, passwd=pw, dob=dob,cred=cred)
class PaisaClient(logging.Handler):
def __init__():
self.loggedin = False # this is the variable we can use to see if we are "logged in"
def emit(self, record):
if record.getMessage().startswith("Logged in!!")
self.loggedin = True
def login():
client.login()
logging.getLogger(py5paisa) # get the logger for the py5paisa library
# tutorial here: https://betterstack.com/community/questions/how-to-disable-logging-from-python-request-library/
logging.basicConfig(handlers=[PaisaClient()], level=0, force=True)
c = PaisaClient()
c.login()

Xero Oauth2 Node Examples

I am doing some expermenting with the xero API, however i cant seem to get past the Connect to Xero returning an error
"Sorry, something went wrong
Go back and try again.
If the issue continues, check out our Status Page."
I have setup my App in the xero dev center
I have tried these 2 repos
https://github.com/XeroAPI/xero-node-oauth2-app
https://github.com/XeroAPI/node-oauth2-example
Both yeld the same result just an error page, no information in console/dev tools
Any help would be amazing as im completely stuck with this
So that looks like the error you get when either API keys and/or callback urls are not setup correctly.
Have you swapped in all your api keys & callback urls to the .env (environment) files?
Create a .env file in the root of your project & replace the 3 variables
Create an .env file in the root of your project using touch .env or edit the sample prefix off sample.env and change out with your /myapps credentials of the app you just made.
CLIENT_ID=...
CLIENT_SECRET=...
REDIRECT_URI=...
Here is the library that is used successfully with ouath2.0 tokenization. The token is expired in 30 mints. After that, we need to refresh the token with old token objects.
First set up an app in developer.xero.com.
Add Company Name and Redirect URL while creating the app.
Setup environment configuration in your file.
X_CLIENT_ID=CD43E78278ED4BE68F35F155C3E708F7
X_CLIENT_SECRET=IuP5TrE70JoyYiezMRM2KwvcHFYoLy3qRbD3NFlOkYLN0Asy
X_REDIRECT_URL=https://baseredirecturl.com/xero/default/redirect
Step-1: Here is the code for creating a token and refresh token.
public function actionConnectXero()
{
$session = Yii::$app->session;
$request = Yii::$app->request;
if (empty($request->get('code'))) {
// If we don't have an authorization code then get one
$authUrl = $this->provider->getAuthorizationUrl([
'scope' => 'offline_access openid email profile accounting.settings accounting.transactions accounting.contacts accounting.reports.read projects accounting.journals.read'
]);
//offline_access openid email profile accounting.settings accounting.transactions accounting.contacts accounting.reports.read projects accounting.journals.read
$session->set('oauth2state', $this->provider->getState());
$this->redirect($authUrl);
// Check given state against previously stored one to mitigate CSRF attack
} elseif (empty($request->get('state')) || ($request->get('state') !== $session->get('oauth2state'))) {
$session->remove('oauth2state');
exit('Invalid state');
} else {
// Try to get an access token (using the authorization code grant)
$token = $this->provider->getAccessToken('authorization_code', [
'code' => $request->get('code')
]);
$session->set('access_token', $token);
//If you added the openid/profile scopes you can access the authorizing user's identity.
$identity = $this->provider->getResourceOwner($token);
echo "<pre>";
print_r($identity);
//Get the tenants that this user is authorized to access
$tenants = $this->provider->getTenants($token);
print_r($tenants);
$session->set('tenantId', $tenants[0]->tenantId);
exit;
}
}
Step-2: Redirect to URL.
public function actionRedirectXero()
{
$request = Yii::$app->request;
$codeStr = explode("?", $request->getUrl());
$token = $this->provider->getAccessToken('authorization_code', [
'code' => $request->get('code')
]);
$tenants = $this->provider->getTenants($token);
$exits = XeroConfigs::find()->where(['created_by' => Yii::$app->user->identity->id])->one();
$xeroConf = $exits ? XeroConfigs::findOne($exits->id) : new XeroConfigs();
$xeroConf->access_token = $token;
$xeroConf->refresh_token = $token->getRefreshToken();
$xeroConf->expiry = $token->getExpires();
$xeroConf->tenant_id = isset($tenants[0]) ? $tenants[0]->id : 0;
$xeroConf->token_object = serialize($token);
$xeroConf->created_by = Yii::$app->user->identity->id;
$xeroConf->save();
$this->redirect('/xero/default/get-xero-data?'.$codeStr[1]);
}
Step-3: Get data from xero. I just save and get contacts. for more examples, you can check the package documentation.
public function actionGetXeroData(){
$configs = XeroConfigs::find()->where(['created_by' => Yii::$app->user->identity->id])->one();
if($configs->expiry < time()){
$newAccessToken = $this->provider->getAccessToken('refresh_token', [
'grant_type' => 'refresh_token',
'refresh_token' => $configs->refresh_token
]);
$tenants = $this->provider->getTenants($newAccessToken);
$xeroConf = XeroConfigs::findOne($configs->id);
$xeroConf->access_token = $newAccessToken;
$xeroConf->refresh_token = $newAccessToken->getRefreshToken();
$xeroConf->expiry = $newAccessToken->getExpires();
$xeroConf->tenant_id = isset($tenants[0]) ? $tenants[0]->id : 0;;
$xeroConf->token_object = serialize($newAccessToken);;
$xeroConf->updated_at = Carbon::now()->toDateTimeString();
$xeroConf->created_by = Yii::$app->user->identity->id;
$xeroConf->save();
$configs = XeroConfigs::find()->where(['created_by' => Yii::$app->user->identity->id])->one();
}
$tokenObj = unserialize($configs->token_object);
$tenants = $this->provider->getTenants($tokenObj);
$xero = new \XeroPHP\Application($tokenObj, $tenants[0]->tenantId);
$contact = new Contact($xero);
$contact->setName('Hassan Raza')
->setAccountNumber('0245541574185741')
->setContactID('852986')
->setGUID('52552548-5585-8715-8888-871222554154')
->setBankAccountDetail('0245541574185741')
->setTaxNumber('55545352')
->setContactStatus('ACTIVE')
->setSkypeUserName('hassan_raza2010')
->setTrackingCategoryName('Manager')
->setFirstName('Hassan')
->setLastName('Raza')
->setEmailAddress('hassan#xero.com');
$response = $contact->save();
dd($response->getResponseBody());

Paypal Php Sdk - NotifyUrl is not a fully qualified URL Error

I have this code
$product_info = array();
if(isset($cms['site']['url_data']['product_id'])){
$product_info = $cms['class']['product']->get($cms['site']['url_data']['product_id']);
}
if(!isset($product_info['id'])){
/*
echo 'No product info.';
exit();
*/
header_url(SITE_URL.'?subpage=user_subscription#xl_xr_page_my%20account');
}
$fee = $product_info['yearly_price_end'] / 100 * $product_info['fee'];
$yearly_price_end = $product_info['yearly_price_end'] + $fee;
$fee = ($product_info['setup_price_end'] / 100) * $product_info['fee'];
$setup_price_end = $product_info['setup_price_end'] + $fee;
if(isset($_SESSION['discountcode_amount'])){
$setup_price_end = $setup_price_end - $_SESSION['discountcode_amount'];
unset($_SESSION['discountcode_amount']);
}
$error = false;
$plan_id = '';
$approvalUrl = '';
$ReturnUrl = SITE_URL.'payment/?payment_type=paypal&payment_page=process_agreement';
$CancelUrl = SITE_URL.'payment/?payment_type=paypal&payment_page=cancel_agreement';
$now = $cms['date'];
$now->modify('+5 minutes');
$apiContext = new \PayPal\Rest\ApiContext(
new \PayPal\Auth\OAuthTokenCredential(
$cms['options']['plugin_paypal_clientid'], // ClientID
$cms['options']['plugin_paypal_clientsecret'] // ClientSecret
)
);
use PayPal\Api\ChargeModel;
use PayPal\Api\Currency;
use PayPal\Api\MerchantPreferences;
use PayPal\Api\PaymentDefinition;
use PayPal\Api\Plan;
use PayPal\Api\Patch;
use PayPal\Api\PatchRequest;
use PayPal\Common\PayPalModel;
use PayPal\Api\Agreement;
use PayPal\Api\Payer;
use PayPal\Api\ShippingAddress;
// Create a new instance of Plan object
$plan = new Plan();
// # Basic Information
// Fill up the basic information that is required for the plan
$plan->setName($product_info['name'])
->setDescription($product_info['desc_text'])
->setType('fixed');
// # Payment definitions for this billing plan.
$paymentDefinition = new PaymentDefinition();
// The possible values for such setters are mentioned in the setter method documentation.
// Just open the class file. e.g. lib/PayPal/Api/PaymentDefinition.php and look for setFrequency method.
// You should be able to see the acceptable values in the comments.
$setFrequency = 'Year';
//$setFrequency = 'Day';
$paymentDefinition->setName('Regular Payments')
->setType('REGULAR')
->setFrequency($setFrequency)
->setFrequencyInterval("1")
->setCycles("999")
->setAmount(new Currency(array('value' => $yearly_price_end, 'currency' => $cms['session']['client']['currency']['iso_code'])));
// Charge Models
$chargeModel = new ChargeModel();
$chargeModel->setType('SHIPPING')
->setAmount(new Currency(array('value' => 0, 'currency' => $cms['session']['client']['currency']['iso_code'])));
$paymentDefinition->setChargeModels(array($chargeModel));
$merchantPreferences = new MerchantPreferences();
// ReturnURL and CancelURL are not required and used when creating billing agreement with payment_method as "credit_card".
// However, it is generally a good idea to set these values, in case you plan to create billing agreements which accepts "paypal" as payment_method.
// This will keep your plan compatible with both the possible scenarios on how it is being used in agreement.
$merchantPreferences->setReturnUrl($ReturnUrl)
->setCancelUrl($CancelUrl)
->setAutoBillAmount("yes")
->setInitialFailAmountAction("CONTINUE")
->setMaxFailAttempts("0")
->setSetupFee(new Currency(array('value' => $setup_price_end, 'currency' => $cms['session']['client']['currency']['iso_code'])));
$plan->setPaymentDefinitions(array($paymentDefinition));
$plan->setMerchantPreferences($merchantPreferences);
// ### Create Plan
try {
$output = $plan->create($apiContext);
} catch (Exception $ex){
die($ex);
}
echo $output->getId().'<br />';
echo $output.'<br />';
Been working with paypal php sdk for some days now and my code stop working.
So i went back to basic and i am still getting the same damn error.
I am trying to create a plan for subscription but getting the following error:
"NotifyUrl is not a fully qualified URL"
I have no idea how to fix this as i dont use NotfifyUrl in my code?
Could be really nice if anyone had an idea how to fix this problem :)
Thanks
PayPal did a update to their API last night which has caused problem within their SDK.
They are sending back null values in their responses.
I MUST stress the error is not on sending the request to PayPal, but on processing their response.
BUG Report : https://github.com/paypal/PayPal-PHP-SDK/issues/1151
Pull Request : https://github.com/paypal/PayPal-PHP-SDK/pull/1152
Hope this helps, but their current SDK is throwing exceptions.
Use below simple fix.
Replace below function in vendor\paypal\rest-api-sdk-php\lib\PayPal\Api\MerchantPreferences.php
public function setNotifyUrl($notify_url)
{
if(!empty($notify_url)){
UrlValidator::validate($notify_url, "NotifyUrl");
}
$this->notify_url = $notify_url;
return $this;
}
If you get the same error for return_url/cancel_url, add the if condition as above.
Note: This is not a permanent solution, you can use this until getting the update from PayPal.
From the GitHub repo for the PayPal PHP SDK, I see that the error you mentioned is thrown when MerchantPreferences is not given a valid NotifyUrl. I see you're setting the CancelUrl and ReturnUrl, but not the NotifyUrl. You may simply need to set that as well, i.e.:
$NotifyUrl = (some url goes here)
$obj->setNotifyUrl($NotifyUrl);
Reason behind it!
error comes from.
vendor\paypal\rest-api-sdk-php\lib\PayPal\Validation\UrlValidator.php
line.
if (filter_var($url, FILTER_VALIDATE_URL) === false) {
throw new \InvalidArgumentException("$urlName is not a fully qualified URL");
}
FILTER_VALIDATE_URL: according to this php function.
INVALID URL: "http://cat_n.domain.net.in/"; // IT CONTAIN _ UNDERSCORE.
VALID URL: "http://cat-n.domain.net.in/"; it separated with - dash
here you can dump your url.
vendor\paypal\rest-api-sdk-php\lib\PayPal\Validation\UrlValidator.php
public static function validate($url, $urlName = null)
{
var_dump($url);
}
And then check this here: https://www.w3schools.com/PHP/phptryit.asp?filename=tryphp_func_validate_url
you can check here what character will reason for invalid.

App-Only Token Fetches Users But Can't Create Subscriptions?

I am using an "app-only" token to retrieve my users which works just fine.
Once the app loops through the users, it's supposed to create a subscription as shown below.
However, when attempting to create the subscription, the request simply returns:
Code: InvalidRequest Message: Unable to connect to the remote server
Inner error
My question is, why does the subscription request fail to connect when I am obviously able to successfully connect in the first request which retrieves the users?
How can I see the inner error?
string tenantId = appSettings.TenantId;
var client = sdkHelper.GetAuthenticatedClientAppOnly(tenantId);
// this request works...
IGraphServiceUsersCollectionPage users = await client.Users.Request().Filter("userPrincipalName eq 'MY_USER_PRINCIPAL_NAME'").GetAsync();
if (users?.Count > 0)
{
foreach (User user in users)
{
// this request doesn't work...
Subscription newSubscription = new Subscription();
string clientState = Guid.NewGuid().ToString();
newSubscription = await client.Subscriptions.Request().AddAsync(new Subscription
{
Resource = $"users/{ user.Id }#{ tenantId }/mailFolders('Inbox')/messages",
//Resource = $"users/{ user.UserPrincipalName }/mailFolders('Inbox')/messages", // also tried using email address
ChangeType = "created",
NotificationUrl = "https://localhost:44334/notification/listen",
ClientState = clientState,
//ExpirationDateTime = DateTime.UtcNow + new TimeSpan(0, 0, 4230, 0) // current maximum lifespan for messages
ExpirationDateTime = DateTime.UtcNow + new TimeSpan(0, 0, 15, 0) // shorter duration useful for testing
});
}
}
When I wrap the call in a try/catch, the error message is just this with no inner exception:
Exception of type 'Microsoft.Graph.ServiceException' was thrown.
I have tried all three resources URLs but all result in the same error as shown above:
Resource = $"users/{ user.Id }/mailFolders('Inbox')/messages",
Resource = $"users/{ user.Id }#{ tenantId }/mailFolders('Inbox')/messages",
Resource = $"users/{ user.UserPrincipalName }/mailFolders('Inbox')/messages",
I found this thread on github but the requests don't seem to work for me.
Allow Application-Only requests to create subscriptions - https://github.com/microsoftgraph/microsoft-graph-docs/issues/238
NotificationUrl cannot be "localhost!" The request is being sent from a remote server so it has no idea what localhost would be thus it fails. If I deploy my project to a remote server and pass the URL, it will likely work. I will try that...