On Sun, Jul 12, 2009 at 10:30:31PM -0700, jon wrote:
> Have you done any more research into this problem?
Yes, I tried a few of the suggestions on the list. Thanks to everyone
for their helpful responses. I haven't had time to progress the
application very far, but the basics are working.
Here are a few of my notes:
Javascript
==========
I disabled the addition of Javascript by adding to
bootstrap/liftweb/Boot.scala:
+ // Do not insert Javascript-based GC
+ LiftRules.enableLiftGC = false;
+
+ // Do not include Ajax include
+ LiftRules.autoIncludeAjax = _ => false;
Logging
=======
Liftweb sends stack traces to the browser, when running in development
mode. This isn't good for Facebook applications, because the content
of 500 error responses is not shown. So I changed to logging to a file
by adding to bootstrap/liftweb/Boot.scala:
+ // Send exceptions to log, not the browser
+ LiftRules.exceptionHandler.prepend {
+ case (_, r, e) =>
+ Log.error("Exception being returned to browser when processing "+r, e)
+ XhtmlResponse((<html>Something unexpected happened while serving the page at {r.uri}
+ </html>),ResponseInfo.docType(r), List("Content-Type" -> "text/html"), Nil, 500, S.ieMode)
+ }
Input validation
================
It appears that the current version of the Lift Facebook API does not
verify the signature on the Facebook POST variables. This would allow
someone to spoof a different Facebook user to the application. I care
about this in my application, so I wrote the following method to
validate parameters:
+ /**
+ * Confirm that the parameters really came from Facebook
+ */
+ def validateParameters: Boolean = {
+ // Check that the right parameters are present
+ if (S.request.isEmpty)
+ return false
+
+ val actualSignature = S.param("fb_sig")
+ if (actualSignature.isEmpty)
+ return false
+
+ // Format parameters for hashing, and append the secret
+ val params = S.request.open_!.params
+ val signedParams = for ((k,v) <- params if k.startsWith("fb_sig_")) yield k.substring(7) + "=" + v.mkString("")
+ val signedParamStr = signedParams.toList.sort(_ < _).mkString("") + FacebookRestApi.secret
+
+ // Hash the string and convert to hex
+ val md = _root_.java.security.MessageDigest.getInstance("MD5")
+ def byteToHex(b: Byte): String = Integer.toHexString((b & 0xf0) >>> 4) + Integer.toHexString(b & 0x0f)
+ val expectedSignature = md.digest((signedParamStr).getBytes).map(byteToHex(_)).mkString("")
+
+ // Verify the signature
+ return expectedSignature == actualSignature.open_!
+ }
Maybe this should go into the Facebook API. I haven't tried this.
Templates
=========
To keep the Lift XML parser happy, I added a namespace declaration for
the Facebook namespace. I also had to add a top level element. I found
that Facebook removes an <html> element before rendering FBML, so that
is a good choice.
This creates a template like the following:
+ <html xmlns:fb="http://apps.facebook.com/ns/1.0">
+ <fb:explanation> <fb:message>Explanation message</fb:message> This is the explanation message text. </fb:explanation>
+ </html>
> I am also exploring this area-- by creating your application as an
> iframe rather than fbml, which seems to be the recommended approach
> (http://stackoverflow.com/questions/219804/new-facebook-app-fbml-or-
> iframe), you should be able to use all the lift goodies out of the
> box.
Yes, I looked into this too, but it seems that opinions are split as
to what is better. For another set of ideas, see the Facebook wiki:
http://wiki.developers.facebook.com/index.php/Choosing_between_an_FBML_or_IFrame_Application
I went for FBML because it seemed simpler, and I've done one of these
before using Python and Pylons. I switched to Scala because I really
miss the lack of static type checking there.
Regarding cookies, it turns out that they are supported by the
Facebook API, although are a beta feature:
http://wiki.developers.facebook.com/index.php/Cookies
This will hopefully mean that session-based things in Lift will still
work.
I hope this helps. I would be very interested in hearing your
experience too.
Steven.