Obtaining a Facebook auth token for a command-line (desktop) application - ruby

I am working for a charity which is promoting sign language, and they want to post a video to their FB page every day. There's a large (and growing) number of videos, so they want to schedule the uploads programmatically. I don't really mind what programming language I end up doing this in, but I've tried the following and not got very far:
Perl using WWW::Facebook::API (old REST API)
my $res = $client->video->upload(
title => $name,
description => $description,
data => scalar(read_file("videos/split/$name.mp4"))
);
Authentication is OK, and this correctly posts a facebook.video.upload method to https://api-video.facebook.com/restserver.php. Unfortunately, this returns "Method unknown". I presume this is to do with the REST API being deprecated.
Facebook::Graph in Perl or fb_graph gem in Ruby. (OAuth API)
I can't even authenticate. Both of these are geared towards web rather than desktop applications of OAuth, but I think I ought to be able to do:
my $fb = Facebook::Graph->new(
app_id => "xxx",
secret => "yyy",
postback => "https://www.facebook.com/connect/login_success.html"
);
print $fb->authorize->extend_permissions(qw(publish_stream read_stream))->uri_as_string;
Go to that URL in my browser, capture the code parameter returned, and then
my $r = $fb->request_access_token($code);
Unfortunately:
Could not fetch access token: Bad Request at /Library/Perl/5.16/Facebook/Graph/AccessToken/Response.pm line 26
Similarly in Ruby, using fb_graph,
fb_auth = FbGraph::Auth.new(APP_ID, APP_SECRET)
client = fb_auth.client
client.redirect_uri = "https://www.facebook.com/connect/login_success.html"
puts client.authorization_uri(
:scope => [:publish_stream, :read_stream]
)
Gives me a URL which returns a code, but running
client.authorization_code = <code>
FbGraph.debug!
access_token = client.access_token!
returns
{
"error": {
"message": "Missing client_id parameter.",
"type": "OAuthException",
"code": 101
}
}
Update: When I change the access_token! call to access_token!("foobar") to force Rack::OAuth2::Client to put the identifier and secret into the request body, I get the following error instead:
{
"error": {
"message": "The request is invalid because the app is configured as a desktop app",
"type": "OAuthException",
"code": 1
}
}
How am I supposed to authenticate a desktop/command line app to Facebook using OAuth?

So, I finally got it working, without setting up a web server and doing a callback. The trick, counter-intuitively, was to turn off the "Desktop application" setting and not to request offline_access.
FaceBook::Graph's support for posting videos doesn't seem to work at the moment, so I ended up doing it in Ruby.
fb_auth = FbGraph::Auth.new(APP_ID, APP_SECRET)
client = fb_auth.client
client.redirect_uri = "https://www.facebook.com/connect/login_success.html"
if ARGV.length == 0
puts "Go to this URL"
puts client.authorization_uri(:scope => [:publish_stream, :read_stream] )
puts "Then run me again with the code"
exit
end
if ARGV.length == 1
client.authorization_code = ARGV[0]
access_token = client.access_token! :client_auth_body
File.open("authtoken.txt", "w") { |io| io.write(access_token) }
exit
end
file, title, description = ARGV
access_token = File.read("authtoken.txt")
fb_auth.exchange_token! access_token
File.open("authtoken.txt", "w") { |io| io.write(fb_auth.access_token) }
me = FbGraph::Page.new(PAGE_ID, :access_token => access_token)
me.video!(
:source => File.new(file),
:title => title,
:description => description
)

Problem is in your case that for OAuth you'll need some endpoint URL which is publicly reachable over the Internet for Facebook servers, which can be a no-go for normal client PCs, or a desktop application which is capable of WebViews (and I assume, command line isn't).
Facebook states at https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#login that you can build a desktop client login flow, but only via so-called WebViews. Therefore, you'd need to call the OAuth endpoint like this:
https://www.facebook.com/dialog/oauth?client_id={YOUR_APP_ID}&redirect_uri=https://www.facebook.com/connect/login_success.html&response_type=token&scope={YOUR_PERMISSION_LIST}
You then have to inspect the resulting redirected WebView URL as quoted:
When using a desktop app and logging in, Facebook redirects people to
the redirect_uri mentioned above and places an access token along with
some other metadata (such as token expiry time) in the URI fragment:
https://www.facebook.com/connect/login_success.html#access_token=ACCESS_TOKEN...
Your app needs to detect this redirect and then read the access token out of the URI using the mechanisms provided by the OS and development framework you are using.
If you want to do this in "hacking mode", I'd recommend to do the following.:
As you want to post to a Page, get a Page Access Token and store it locally. This can be done by using the Graph Explorer at the
https://developers.facebook.com/tools/explorer?method=GET&path=me%2Faccounts
endpoint. Remember to give "manage_pages" and "publish_actions" permissions.
Use cURL (http://curl.haxx.se/docs/manpage.html) to POST the videos to the Graph API with the Access Token and the appropriate Page ID you acquired in step 1 like the following:
curl -v -0 --form title={YOUR_TITLE} --form
description={YOUR_DESCRIPTION} --form source=#{YOUR_FULL_FILE_PATH}
https://graph-video.facebook.com/{YOUR_PAGE_ID}/videos?access_token={YOUR_ACCESS_TOKEN}
References:
https://developers.facebook.com/docs/graph-api/reference/page/videos/#publish
https://developers.facebook.com/docs/reference/api/video/

From the facebook video API reference:
An individual Video in the Graph API.
To read a Video, issue an HTTP GET request to /VIDEO_ID with the
user_videos permission. This will return videos that the user has
uploaded or has been tagged in.
Video POST requests should use graph-video.facebook.com.
So you should be posting to graph-video.facebook.com if you are to upload video.
You also need extended permissions from the user or profile you'll be uploading to, in this case you need video_upload this is going to be requested once only, when the user currently logged in is asked for such permission for the app.
And your endpoint should be:
https://graph-video.facebook.com/me/videos
If you always want to post to a specific user than you'll have to change the endpoint part from /me to the User ID or page ID.
Here's a sample (in PHP):
$app_id = "YOUR_APP_ID";
$app_secret = "YOUR_APP_SECRET";
$my_url = "YOUR_POST_LOGIN_URL";
$video_title = "YOUR_VIDEO_TITLE";
$video_desc = "YOUR_VIDEO_DESCRIPTION";
$code = $_REQUEST["code"];
if(empty($code)) {
$dialog_url = "http://www.facebook.com/dialog/oauth?client_id="
. $app_id . "&redirect_uri=" . urlencode($my_url)
. "&scope=publish_stream";
echo("<script>top.location.href='" . $dialog_url . "'</script>");
}
$token_url = "https://graph.facebook.com/oauth/access_token?client_id="
. $app_id . "&redirect_uri=" . urlencode($my_url)
. "&client_secret=" . $app_secret
. "&code=" . $code;
$access_token = file_get_contents($token_url);
$post_url = "https://graph-video.facebook.com/me/videos?"
. "title=" . $video_title. "&description=" . $video_desc
. "&". $access_token;
echo '<form enctype="multipart/form-data" action=" '.$post_url.' "
method="POST">';
echo 'Please choose a file:';
echo '<input name="file" type="file">';
echo '<input type="submit" value="Upload" />';
echo '</form>';
Although I'm concerned about the upload speed if the videos are too big, but I'm guessing your customer has already sorted that out (compress/optimize/short videos etc.)
I've made you a demo here. Go to my website (I own that domain) and try to upload a video. I tried with this one which is a relatively small 4Mb file. Be sure that this script will only try to upload a video, nothing more (to the FB profile you are currently logged in, that is) but, if you are still concerned, copy my snippet, upload it to your own server (with PHP support of course) and create a test app where the site url is that domain and be sure to specify in the $my_url variable your endpoint which is basically the full path to your script receiving responses from facebook:
http://yourdomain.com/testfb.php
If you still want to do it on a desktop app then you have to go to developer.facebook.com on your app settings:
Settings > Advanced
And look for the first option:
And enable that switch so that facebook allows you to POST from a desktop or native app instead of a web server.
Note: I'm not an expert on Ruby, but the above working PHP code should be pretty obvious and easy to port to it.

as far as I recall, what you want isn't really possible without some kind of endpoint that can receive a callback from facebook.
If you can finagle an oauth token, from say the Graph API Explorer, then it becomes pretty trivial to use a gem like koala to upload your video.
here's the salient bit:
#graph = Koala::Facebook::API.new(access_token)
#graph.put_video(path_to_my_video)
I've made you a sample project here: fb-upload-example

Related

how to handle ruby file with jwt for applestore connect api

I've been struggling to sign the jwt and I'm not familiar with the ruby file provided by apple on WWDC.. the code reads
require "base64"
require "jwt"
ISSUER_ID = "your-ID"
KEY_ID = "your-KeyID"
private_key = OpenSSL::PKey.read(File.read())
token = JWT.encode(
{
iss: ISSUER_ID,
exp: Time.now.to_i + 20 * 60,
aud: "appstoreconnect-v1"
},
private_key,
"ES256",
header_fields={
kid: KEY_ID }
)
puts token
the code keeps giving me this error when I run it on terminal.
enter image description here
my goal is simple, I just want to return some data from the GET api but am struggling with the 401 error on postman.
You have a syntax error in your code according to the screenshot you posted.
There's also what appears to be an error or misconfiguration in your code sample.
The gem most frequently used to encode/decode JWTs in ruby is here; this is the gem you're using in your example (via require "jwt") There are a number of examples on that page you can reference, but if you look thru the README you'll note they mention you can only use a kid with RSA and you're not using RSA in your example.
Try this:
token = JWT.encode(
{
iss: ISSUER_ID,
exp: Time.now.to_i + 20 * 60,
aud: "appstoreconnect-v1"
},
private_key,
"ES256"
)
Here's a link to using a JWK with the kid and the RSA algorithm.
Search for ecdsa_key on that same page for examples with ES256 (you'll find the one I posted above).
Seeing the original example from the WWDC docs might help provide additional context for the correct configuration but I'm not sure where to find that document. If it's public and you can link to it I can follow up.

FedEx API Tracking a package what is the XML url?

I am having trouble using the FedEx API, I have read through the documentation pertaining to tracking packages. I have gotten all of the numbers I need to fill into the URL (Test tracking number, Account Number, Account Key, Meter Number, User Name and password).
I was given the URL to use from FedEx, "The production server URL is “https://ws.fedex.com:443/web-services”, however, I am also told to replace the credentials with my actual information (Test tracking number, Account Number, Account Key, Meter Number, User Name and password).
I found this code on another StackOverflow post, and I saw that there was a user who had commented that this way of accessing the FedEx xml was outdated. Anyway I included the code posted by Harshal_m_joshi and edited by houbysoft here (original post Tracking API for Fedex and UPS).
xml_req =
"<TrackRequest xmlns='http://fedex.com/ws/track/v3'><WebAuthenticationDetail><UserCredential><Key>YOUR_ACC_KEY</Key>
<Password>YOUR_ACC_PASSWORD</Password></UserCredential></WebAuthenticationDetail><ClientDetail>
<AccountNumber>YOUR_ACC_NUMBER</AccountNumber><MeterNumber>YOUR_ACC_METER_NUMBER</MeterNumber></ClientDetail>
<TransactionDetail><CustomerTransactionId>ActiveShipping</CustomerTransactionId></TransactionDetail>
<Version><ServiceId>trck</ServiceId><Major>3</Major><Intermediate>0</Intermediate><Minor>0</Minor></Version>
<PackageIdentifier><Value>#{track_no}</Value><Type>TRACKING_NUMBER_OR_DOORTAG</Type></PackageIdentifier>
<IncludeDetailedScans>1</IncludeDetailedScans></TrackRequest>"
path = "https://gatewaybeta.fedex.com:443/xml"
#this url connects to the test server of fedex
# for live server url is:"https://gateway.fedex.com:443/xml"
url = URI.parse(path)
http = Net::HTTP.new(url.host,url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
response = http.post(url.path, xml_req)
response_body = response.body
res = response_body.gsub(/<(\/)?.*?\:(.*?)>/, '<\1\2>')
hash = Hash.from_xml(res.to_s)
and that's it you will get response in hash variable, I converted xml response in to Hash because we can easily use Hash object at our view to display response data.
I was wondering if anyone knew of the new way to do this, or could at least point me in the right direction. I am fairly new to API's only having worked with USPS and WUnderground, which were both easy as pi ;) Any help would be greatly appreciated.
You are going to POST the data to https://ws.fedex.com:443/web-services
Your POST data should look like this...
<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns=\"http://fedex.com/ws/track/v5\">
<soapenv:Header/>
<soapenv:Body>
<TrackRequest xmlns:ns=\"http://fedex.com/ws/track/v5\"
xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">
<WebAuthenticationDetail>
<UserCredential>
<Key>" .$CFG["fedex_key"] ."</Key>
<Password>" .$CFG["fedex_password"] ."</Password>
</UserCredential>
</WebAuthenticationDetail>
<ClientDetail>
<AccountNumber>" . $CFG["fedex_acct"] ."</AccountNumber>
<MeterNumber>" . $CFG["fedex_meter"] ."</MeterNumber>
" .$tracknum ."
trck
5
0
0
" .$tracknum ."
TRACKING_NUMBER_OR_DOORTAG
true

Can not get signed in email using Office 365 REST API

I followed this post http://dev.office.com/code-samples-detail/2142 and Ruby to get user's email address. Here is the code:
# Parses an ID token and returns the user's email
def get_email_from_id_token(id_token)
# JWT is in three parts, separated by a '.'
token_parts = id_token.split('.')
# Token content is in the second part
encoded_token = token_parts[1]
# It's base64, but may not be padded
# Fix padding so Base64 module can decode
leftovers = token_parts[1].length.modulo(4)
if leftovers == 2
encoded_token += '=='
elsif leftovers == 3
encoded_token += '='
end
# Base64 decode (urlsafe version)
decoded_token = Base64.urlsafe_decode64(encoded_token)
# Load into a JSON object
jwt = JSON.parse(decoded_token)
# Email is in the 'preferred_username' field
email = jwt['preferred_username']
end
This function worked very well, I can get user's email address. But today, this function still works without error but the JSON I got not contain user's email address anymore.
Could someone help me? I want to get user's email address. Thank you !
Azure deployed a breaking change to the v2 app model, and you don't get user info by default anymore.
You can read all about it here: https://azure.microsoft.com/en-us/documentation/articles/active-directory-v2-preview-oidc-changes/, but to summarize:
The openid scope used to give you basic profile info for the user.
That wasn't in line with the OpenID standard
Azure changed to require that you request the profile scope to get access to that information
For that sample, find this bit:
# Scopes required by the app
SCOPES = [ 'openid',
'https://outlook.office.com/mail.read' ]
And change it to:
# Scopes required by the app
SCOPES = [ 'openid',
'profile',
'https://outlook.office.com/mail.read' ]
Please add profile and email in your scope :
SCOPES = [ 'openid',
'profile',
'email',
'https://outlook.office.com/mail.read' ]

Google oauth2 error "Required parameter is missing: response_type" (Ruby)

My first Ruby adventure is to try and get oauth2 token for web service app from google. There are many questions and answers on this topic, but none that resolved my issue.
I registered a dummy web service with google and am trying to get authorization code (even before trying to get a token). Doesn't matter what mandatory parameter I omit (e.g. client_id) when accessing https://accounts.google.com/o/oauth2/auth I always get 400 error with message 'Required parameter is missing: response_type'.
Here is the code snippet, following Google's guidelines in https://developers.google.com/accounts/docs/OAuth2WebServer#formingtheurl:
get '/' do
requestGoogleAccessToken(102030, "http://localhost:4567/oauth2callback/")
end
def requestGoogleAccessToken(auth_code, redirect_uri)
googleAuthHost = 'https://accounts.google.com/o/oauth2/auth'
googleClientID = "XXXXXXXXXXX.apps.googleusercontent.com"
scope = 'https://www.googleapis.com/auth/calendar'
url="#{googleAuthHost}?scope=profile&redirect_uri=#{CGI.escape(redirect_uri)}&client_id=#{googleClientID}&response_type=code"
uri=URI.parse(url)
https=Net::HTTP.new(uri.host,uri.port)
https.use_ssl=true
req=Net::HTTP::Post.new(uri.path)
res=https.request(req)
puts "\n res: ", res
puts "\n response body: ", res.body
end
Thanks in advance for your help.

get a new short-lived user access_token

I need to renew a long-lived access token. I read
Renew long lived access token server side topic and wrote a code as follows:
<?php
$code = $_REQUEST["code"];
if(empty($code)) {
$dialog_url = "https://www.facebook.com/dialog/oauth?"
. "client_id=$app_id"
. "&redirect_uri=$my_url"
. "&scope=..."
;
echo("<script> top.location.href='" . $dialog_url . "'</script>");
}
else
{
$response = file_get_contents("https://graph.facebook.com/oauth/access_token?"
. "client_id=$app_id"
. "&redirect_uri=$my_url"
. "&client_secret=$app_secret"
. "&code=$code"
);
$params = null;
parse_str($response, $params);
$access_token=$params['access_token'];
$response = file_get_contents("https://graph.facebook.com/oauth/access_token?"
. "client_id=$app_id"
. "&client_secret=$app_secret"
. "&redirect_uri=$my_url"
. "&grant_type=fb_exchange_token"
. "&fb_exchange_token=$access_token"
);
}
?>
On the first invocation it acquires 60-days access token all right. I expect that on the next invocations it would acquire another (may be with the same name) 60-days tokens, and I would see in the Debugger https://developers.facebook.com/tools/debug that issue time and expiration time changes, but the times do not change. What's wrong with my scenario?
Have you compared the tokens to each other? Facebook will send you back the existing access token when the same call is made in less than 24 hours. Also, tokens are set to not expire if you have also requested page tokens for the user. See my answer here: Facebook Page Access Tokens - Do these expire? for more info on this subject.
One way you can be sure to get a new token each time is if you revoke access by making an http DELETE call to /PROFILE_ID/permissions and then requesting a new token. The only bad thing about this is it will require you to put the user through the oAuth dialog again.

Resources