Recently, I got PHP to talk to Google Hangouts. It's not ground-breaking - as it mostly translates idea from python, but I haven't seen it done before in PHP. I'm going to use this functionality in my Raspberry Pi / Arduino powered security connector - the TANC.
So Why not Python?
I've been doing a lot of work on my TANC project - the Arduino / Raspberry Pi powered Internet security connector. I've been trying to get all the software choices and foundations for a release finalized. One feature - getting alerts by instant message - I had demoed and knew it was possible, but now I needed to decide on the core technologies to accomplish this task.
As I said before, the logic was mostly copied from the Hangups project. So, why didn't I just use the existing python projects, you ask? There are two reasons… maybe three:
- Connecting to beanstalkd. I'm using beanstalkd as a central message queue and all components must talk to that. There seemed to be only one beanstalkd client in Python, and - in the PHP world - that is usually a bad sign.
- I need to package the project into .deb.
When I was thinking about how to package a project that relied on hangups, I tried to choose between bundling everything, building on the client, and using another packager like pip or setup tools. How does one package up a python program that might rely on compiled C extensions? Should I force an ARM emulator into my release toolchain? Should I just do stuff like "pip install"
in the postinst script in the .deb file?
I found a project that actually answers these questions: https://github.com/benjaminirving/python-debian-packaging-example. But then I realized that I would have to learn a lot of new things, investigate new projects, submit new issues, all just to package one component of my project.
Lastly, I encountered an error using the hangups project with my own Google account. There wasn't any traction on my bug for a week. The fix is adding a simple try/except block. I would have had to fork and maintain my own repo to continue the project in Python. That just wasn't very appealing to me.
https://github.com/tdryer/hangups/issues/303
Protobuf and PHP
So, the decision to use PHP had to result in less overhead and friction than learning to package complex virtualenv installs in python. I could already communicate with beanstalkd in PHP, so the roadblocks were:
- Signing in to Google
- Using Protobufs in PHP
From reading the source to hangups, I knew signing in to Google wasn't as simple as an OAuth2 call. I knew there were competing implementations of protobuf compilers, so I decided to start there, and then tackle the sign in problem.
The official protobuf compiler form Google only works with version 3 protobuf files. The hangups project only uses v2. Later, I figured out it's quite easy to upgrade, but this initially lead me to try other projects. In the end, the code generated from the official library acts as a single project. It stores an armored version of the source protobuf file - which is quite large in this case - and unarmors and analyzes it every request. This behavior seems like a huge waste, and the field numbers where not visible anywhere in the generated code. It almost seems like the generated is not generated enough, it relies too much on run-time analysis of the proto file to be considered "generated".
Mostly, all the code generators were very bad. None of them used a fluid syntax style (returning this). None of the constructors would take parameters. This leads to a lot of inverted code, where I have to make the last used item in a chain of requirements first.
Instead of compact, nested code like this:
$response = $this->send(
new SendChatMessageRequest([
'event_header' => new EventHeader( [
'client_id'=> '12345'
]),
'conversation_id' => new ConversationId( [
'id'=>'12345'
])
]);
You end up with unreadable code like this:
$conversationId = new ConversationId();
$conversationId->setId('12345');
$eventHeader = new EventHeader();
$eventHeader->setClientId('12345');
$chatMessageRequest = new SendChatMessageRequest();
$chatMessageRequest->setEventHeader($eventHeader);
$chatMessageRequest->setConversationId($conversationId);
$response = $this->send($chatMessageRequest);
The above example is abridged. It gets very ugly and hard to follow. You can't see what relates to what. You must name every short lived variable of no consequence.
I settled (emphasis on "settled") for protoc-gen-php because it made individual files for every class and wasn't as cryptic as the Google output (i.e. it was the least bad).
Selecting an HTTP Library
After studying Tom Dryer's Hangups project for so long, I grew accustomed to how that HTTP library worked. It's called "requests": HTTP for Humans. It has a neat way to execute multiple HTTP requests in a row which all share the same cookie information. This can be accomplished with the "Cookie Jar" pattern that you see in most other HTTP libraries, but here it's transparent.
So, I went looking for a PHP library that was similarly transparent in the handling of "Set-Cookie" HTTP headers. I tried Guzzle first. But after 20 minutes of searching their github account, I wasn't sure if it even had a Response class at all. How could I call $response->cookies() if there's no response class?
I looked at 2 more libraries. Unirest and Requests. Unirest didn't seem to offer any examples of handling response cookies (everyone just shows you how to set cookies from a file). And Ryan Mccue's Requests project was inspired by the Python Requests library. Perfect.
$session = new Requests_Session();
$session->post(...); // <-- sever sends Set-Cookie headers
$session->get(...); // <-- second request includes cookies from first post
//If your HTTP library does this and I'm just a moron for not noticing write better Quickstart/Docs.
//When I have to evaluate 3-4 libraries, I can't spend
//more than 10 min on each to make first round cuts.
OAuth and Uberauth
Now, here's where it gets interesting confusing.
Hangouts uses an undocumented, unpublished API. There's no official way to create a client. By profiling the network calls made by the iOS version, some people have made some great reverse engineering strides.
Couple that research with some research into Google's 2-factor auth security holes, and you get a login system for undocumented APIs.
See:
- http://stackoverflow.com/questions/18153538/android-google-authentication-ubertoken
- http://nelenkov.blogspot.com/2012/11/sso-using-account-manager.html
- https://duo.com/blog/beyond-the-vulnerabilities-of-the-application-specific-password-exploiting-google-chrome-s-oauth2-tokens
Essentially, what you're doing is signing in to regular OAuth2 with an OAuthLogin scope and email scope. Then you request an uberauth token, you merge your session which has the uberauth token to another Google property, now your session is authenticated, and you can access undocumented Google APIS, like hangouts.
I'm not sure anyone outside of Google knows exactly how the MergeSession endpoint and uberauth token work. But, the long and short of it is that you can now use PHP to send Hangout messages.
See also: