Tornado facebook_request() to get email - facebook

I'm using tornado and trying to get a facebook user's email address from the Graph API. I have the following code (most of which is from the Tornado website)
class FacebookAuth2Handler(BaseHandler,tornado.auth.FacebookGraphMixin):
#tornado.gen.coroutine
def get(self):
if self.get_argument("code", False):
user = yield self.get_authenticated_user(redirect_uri=self.settings["facebook_redirect_uri"],
client_id=self.settings["facebook_app_id"],
client_secret=self.settings["facebook_secret"],
code=self.get_argument("code"))
ob = yield self.facebook_request("/me/email",access_token=user["access_token"])
print(ob)
else:
yield self.authorize_redirect(redirect_uri=self.settings["facebook_redirect_uri"],
client_id=self.settings["facebook_app_id"],
extra_params={"scope": ["email","public_profile"]})
The problem seems to be fetching the /me/email with the facebook_request() this crashes with the following:
tornado.auth.AuthError: Error response HTTP 400: Bad Request fetching https://graph.facebook.com/me/email?access_token=xxxxxxx

Setting the path to "/me/email" is not valid, and setting it to "/me?fields=email" causes it to send your url as "/me?fields=email?access_token=xxxxxxx", which is no good either.
use the fields parameter:
ob = yield self.facebook_request(
path="/me",
access_token=user["access_token"],
fields="email,gender"
)
or you can really simplify things by adding the extra_fields parameter to get_authenticated_user. Note it is a python list, not a comma-separated string like above:
user = yield self.get_authenticated_user(redirect_uri=self.settings["facebook_redirect_uri"],
client_id=self.settings["facebook_app_id"],
client_secret=self.settings["facebook_secret"],
code=self.get_argument("code"),
extra_fields=['email','gender']
)
Any missing or unpermitted fields will show as None in the returned user mapping object.

Related

Verify X-Hub-Signature from Facebook

I'm something of a beginner with the Play Framework (2.5 and Scala in this case) - and I'm trying to learn by building a bot for Facebook messenger. However I've gotten stuck trying to verify the signature of the messages.
I've followed the Facebook documentation and created a webhook. Which handles POST requests usinggetRawMessages (see code below). This then tries to verify that the request is signed by Facebook using the verifyPayload function. However I can't seem to get the computed and the actual hashes to match up.
I've taken a lead from looking at this question: How to verify Instagram real-time API x-hub-signature in Java? which seems to be doing pretty much what I want, but for the Instagram equivalent. But I still can't seem to get it right.
val secret = "<facebooks secret token>"
def getRawMessages = Action (parse.raw) {
request =>
val xHubSignatureOption = request.headers.get("X-Hub-Signature")
try {
for {
signature <- xHubSignatureOption
rawBodyAsBytes <- request.body.asBytes()
} yield {
val rawBody = rawBodyAsBytes.toArray[Byte]
val incomingHash = signature.split("=").last
val verified = verifyPayload(rawBody, secret, incomingHash)
Logger.info(s"Was verified? $verified")
}
Ok("Test")
}
catch {
case _ => Ok("Test")
}
}
val HMAC_SHA1_ALGORITHM = "HmacSHA1"
def verifyPayload(payloadBytes: Array[Byte], secret: String, expected: String): Boolean = {
val secretKeySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), HMAC_SHA1_ALGORITHM)
val mac = Mac.getInstance(HMAC_SHA1_ALGORITHM)
mac.init(secretKeySpec)
val result = mac.doFinal(payloadBytes)
val computedHash = Hex.encodeHex(result).mkString
Logger.info(s"Computed hash: $computedHash")
computedHash == expected
}
The Facebook webhook docs state:
The HTTP request will contain an X-Hub-Signature header which contains
the SHA1 signature of the request payload, using the app secret as the
key, and prefixed with sha1=. Your callback endpoint can verify this
signature to validate the integrity and origin of the payload
Please note that the calculation is made on the escaped unicode
version of the payload, with lower case hex digits. If you just
calculate against the decoded bytes, you will end up with a different
signature. For example, the string äöå should be escaped to
\u00e4\u00f6\u00e5.
I'm guessing that what I'm missing is getting the payload properly escaped to unicode, but I can't seem to find a way to do it. And the answer in the referenced question also appeared to just get the byte array without doing anything more with it (jsonRawBytes = jsonRaw.asBytes();).
Any help with how to proceed would be much appreciated.
Turns out I was using the wrong secret all along. Should anyone else make the same mistake, please note that it "App Secret" that is available on the application dashboard that you want. See the screenshot below.

How to do authentication using Akka HTTP

Looking for a good explanation on how to do authentication using akka HTTP. Given a route that looks like
val route =
path("account") {
authenticateBasic(realm = "some realm", myAuthenticator) { user =>
get {
encodeResponseWith(Deflate) {
complete {
//do something here
}
}
}
}
}
The documentation outlines a way, but then the pertinent part performing the actual authentication is omitted...
// backend entry points
def myAuthenticator: Authenticator[User] = ???
Where can I find an example implementation of such an authenticator? I have the logic already for authenticating a user given a user name and password, but what i can't figure out is how to get a username/password (or token containing both) from the HTTP request (or RequestContext).
Authenticator is just a function UserCredentials => Option[T], where UserCredentials in case of being (check with pattern matching) Provided have verifySecret(secret) method which you need to safely call and return Some (Some user for example) in case of success, like:
def myAuthenticator: Authenticator[User] = {
case p#Provided(username) =>
if(p.verifySecret(myGetSecret(username))) Some(username) else None
case Missing => None //you can throw an exeption here to get customized response otherwise it will be regular `CredentialsMissing` message
}
myGetSecret is your custom function which gets username and returns your secret (e.g. password), getting it possibly from database. verifySecret will securely compare (to avoid timing attack) provided password with your password from myGetSecret. Generally, "secret" is any hidden information (like hash of credentials or token) but in case of basic authentication it is just a plain password extracted from http headers.
If you need more customized approach - use authenticateOrRejectWithChallenge that gets HttpCredentials as an input, so you can extract provided password from there.
More info about authorization is in scaladocs.

Play Framework 2.4 how to use return statement in Action controller

is there a way to return a value inside Action controller.
I have a method in my User model which returns the number of friends of a given user.
def nrOfFriends(current_user: Long): Int = {
DB.withConnection{ implicit connection =>
var nr: Int = SQL("select count(*) from friend where user_id_1=" + current_user + " or user_id_2=" + current_user).as(scalar[Int].single)
nr
}
}
In my controller, I just want to return the value from the model
def freunde() = IsAuthenticated { username => _ =>
User.findByUsername(username).map { user =>
var nr: Int = Friend.nrOfFriends(user.id.get)
Ok(""+nr)
}.getOrElse(Forbidden)
}
But in the way that is written, it will print "empty string " concatenated with the number
If I replace Ok(""+nr) with Ok(nr) I receive the following error:
"Cannot write an instance of Int to HTTP response. Try to define a Writeable[Int]"
I need for my action to return a value so that I can pass the value from the action to header.views.html inside the navbar something like that
#Freund.freunde Friends
if you want your response to just be the value of nr you can simply call nr.toString:
def freunde() = IsAuthenticated { username => _ =>
User.findByUsername(username).map { user =>
var nr: Int = Friend.nrOfFriends(user.id.get)
Ok(nr.toString)
}.getOrElse(Forbidden)
}
The error you're getting makes reference to the fact that Int doesn't have an implicit Writeable[Int] in scope. So play doesn't know how display an Int in an http response.
You can add make Int writeable by putting this in scope:
implicit val intWriteable = play.api.http.Writeable[Int](_.toString.getBytes, None)
Then you would be able to just say:
Ok(nr)
without error.
However, it sounds like you just want the result of nrOfFriends inside an unrelated template. If that's the case, you should be using an Action at all. Instead just call your model function inside the template where you need the data.
#User.nrOfFriends(user.id) Friends
Of course you would need to pass in the user to the template as well.
You didn't post a full sample of all the code involved in what you are trying to accomplish so I think this is the best I can do for now. Perhaps try posting the base template that your <a> is in.
An important point is that Actions are for production an HTTP response, and not just plain data internally to the application.
An action of a controller handles a request and generates a result to be sent to the client. In other words, an action returns a play.api.mvc.Result value, representing the HTTP response to send to the web client. In your example Ok constructs a 200 OK response. The body of the response must be one of the predefined types, including text/plain, json, and html. The number of a friends is an integer and is NOT an acceptable type of the body. Therefore, a simple way to address this problem is to convert it into a text/plain using .toString().
On the other hand, you can define a writer for Int that lets Play know how to convert an integer into a json format.
For more details, please take a look at this https://www.playframework.com/documentation/2.2.x/ScalaActions.

Ruby: How does Sinatra interpret the return values of route blocks?

Sinatra allows the return values of route blocks to take many different forms.
How does Sinatra know how to extract status, headers, and body from each type of return value?
Particularly, I'm looking for the source code (logic) that does this, because I'd like to do something similar.
I've searched the Sinatra & Rack sources to no avail.
The code you’re looking for is in the invoke method:
def invoke
res = catch(:halt) { yield }
res = [res] if Fixnum === res or String === res
if Array === res and Fixnum === res.first
res = res.dup
status(res.shift)
body(res.pop)
headers(*res)
elsif res.respond_to? :each
body res
end
nil # avoid double setting the same response tuple twice
end
There are a couple of quirks to look out for. For example if you return a hash Sinatra will see that it responds to each and treat it as the body, but since it doesn’t yield strings this can cause an error or unexpected results when the webserver tries to send the content. Also the rack spec says this about the status: “When parsed as integer (to_i), it must be greater than or equal to 100” and so you should be able to pass a string like "200" that will be converted with to_i, but Sinatra only looks for Fixnums, which goes against its claim of “You can return any object that would either be a valid Rack response”.

how to use facebook api on a group

i created an application to test facebook api using Python, then, in the application, i created its Group, but, the problem that this Group dont know who i'm; it shows me that am the Admin, but when i try to publish something using Tornado, i get the error
GraphAPIError: (#210) User not visible
and this is because it seems that it deletes the cookie, because when am using the group's profile, then i cant see the GraphAPI since it dont know who is authentificated!
here is the code:
class MainHandler(BaseHandler, tornado.auth.FacebookGraphMixin):
#tornado.web.authenticated
#tornado.web.asynchronous
def get(self):
self.facebook_request("/me/home", self.print_callback,access_token=self.current_user["access_token"])
a = self.current_user["access_token"]
self.graph = GraphAPI(a)
def print_callback(self, data):
self.graph.post_wall(self, "heloooooooo")
an sorry, because i dont get well the logic behind users Token, and Groups tokens? here i got a user Token? and because am the admin, i cant post!
EDIT: here are some snapshots i took from the application:
picture 1
picture 2
Update: i tried this:
def print_callback(self, me):
self.graph.post_wall(self, "helooooo", profile_id="267914489995838")
and got the error:
self.graph.post_wall(self, "helooooo", profile_id="267914489995838")
TypeError: post_wall() got multiple values for keyword argument 'profile_id'
and i used what is in the Tornado-Facebook-API
def post_wall(self, message, profile_id='me', body=None, **kwargs):
#XXX move to separate User class?
body = body or {}
body['message'] = message
self._make_request("{0}/feed".format(profile_id), method='POST',
body=body, **kwargs)
update2: here is the full code
class BaseHandler(tornado.web.RequestHandler):
def get_current_user(self):
user_json = self.get_secure_cookie("user")
if not user_json: return None
return tornado.escape.json_decode(user_json)
class MainHandler(BaseHandler, tornado.auth.FacebookGraphMixin):
#tornado.web.authenticated
#tornado.web.asynchronous
def get(self):
self.facebook_request("/me/accounts", self._on_accounts,
access_token=self.current_user["access_token"])
self.a = self.current_user["access_token"]
self.graph = GraphAPI(self.a)
def _on_accounts(self, account):
if account is None:
# Session may have expired
print "on accounts failed"
return
for acc in account["data"]:
if acc["id"] == "267914489995838":
print acc["access_token"]
self.facebook_request("/PAGE_ID/feed",
post_args={"message": "Test"},
access_token=acc["access_token"],
callback=self.async_callback(self._on_page_post))
def _on_page_post(self, post):
if not post:
# Post failed
return
class AuthLoginHandler(BaseHandler, tornado.auth.FacebookGraphMixin):
#tornado.web.asynchronous
def get(self):
my_url = (self.request.protocol + "://" + self.request.host +
"/auth/login?next=" +
tornado.escape.url_escape(self.get_argument("next", "/")))
if self.get_argument("code", False):
self.get_authenticated_user(
redirect_uri=my_url,
client_id=self.settings["facebook_api_key"],
client_secret=self.settings["facebook_secret"],
code=self.get_argument("code"),
callback=self._on_auth)
return
self.authorize_redirect(redirect_uri=my_url,
client_id=self.settings["facebook_api_key"],
extra_params={"scope": "read_stream, publish_stream"})
def _on_auth(self, user):
if not user:
raise tornado.web.HTTPError(500, "Facebook auth failed")
self.set_secure_cookie("user", tornado.escape.json_encode(user))
self.redirect(self.get_argument("next", "/"))
class AuthLogoutHandler(BaseHandler, tornado.auth.FacebookGraphMixin):
def get(self):
self.clear_cookie("user")
self.redirect(self.get_argument("next", "/"))
class PostModule(tornado.web.UIModule):
def render(self, post):
return self.render_string("modules/post.html", post=post)
Some clarifications:
A #200 error is a permission error. In this case, you don't have permissions to post somewhere.
There are two main types of tokens: user tokens and page tokens
You can add a group to an application's roles http://developers.facebook.com/blog/post/531/
As far as I know, there is no way to add an application to a group
So I think based on the Facebook id given you either want to do two things
Post to an application's timeline
Post to a group via an application
Posting to an application's timeline
This requires the application page access token, which you get from /me/accounts using the manage_pages and publish_stream permissions
self.facebook_request("/me/accounts", self._on_accounts,
access_token=self.current_user["access_token"])
def _on_accounts(self, account):
if account is None:
# Session may have expired
print "on accounts failed"
return
for acc in account["data"]:
if acc["id"] == "PAGE_ID":
print acc["access_token"]
self.facebook_request("/PAGE_ID/feed",
post_args={"message": "Test"},
access_token=acc["access_token"],
callback=self.async_callback(self._on_page_post))
def _on_page_post(self, post):
if not post:
# Post failed
return
See http://developers.facebook.com/docs/reference/api/application/ for more info
A full example can be seen at https://gist.github.com/3867203 (Which doesn't handle duplicate posting)