Curl Replacement on GAE for BaseFacebook class - php

I am wanting to implement federated logins to my GAE app in PHP.
I have looked at a couple of third parties like janrain or the OAuth plugin, but I just can't integrate them very easily and I don't want to pay or have limits. I just want something simple that will hook into the GAE authentication model.
At the moment I have google and yahoo as they are the only 2 that use an endpoint that doesn't require a username, and I can use the UserService::createLoginUrl()
I want to add Facebook, Twitter and Microsoft, plus others I guess, but these are my aim for now.
I started with facebook I am trying to use the facebook connector. Initially I started with the Javascript API and it worked well until you sit behind a firewall and facebook is blocked (then $.getScript() fails unmanageably), so I then started looking at the PHP library (at least then the blocking is a) visible to the user, b) not my responsibility for trapping and handling and c) fits nicely into the URL endpoint model I have)
The problem is that the BaseFacebook class uses cURL. GAE does not.
Does anyone know of a way to build the authentication into GAE so I can use login: required or if not, can anyone with better streams/curl knowlege than me replace the cURL stuff in the BaseFacebook class. Here is the function that's causing my grief:
/**
* Makes an HTTP request. This method can be overridden by subclasses if
* developers want to do fancier things or use something other than curl to
* make the request.
*
* #param string $url The URL to make the request to
* #param array $params The parameters to use for the POST body
* #param CurlHandler $ch Initialized curl handle
*
* #return string The response text
*/
protected function makeRequest($url, $params, $ch=null) {
if (!$ch) {
$ch = curl_init();
}
$opts = self::$CURL_OPTS;
if ($this->getFileUploadSupport()) {
$opts[CURLOPT_POSTFIELDS] = $params;
} else {
$opts[CURLOPT_POSTFIELDS] = http_build_query($params, null, '&');
}
$opts[CURLOPT_URL] = $url;
// disable the 'Expect: 100-continue' behaviour. This causes CURL to wait
// for 2 seconds if the server does not support this header.
if (isset($opts[CURLOPT_HTTPHEADER])) {
$existing_headers = $opts[CURLOPT_HTTPHEADER];
$existing_headers[] = 'Expect:';
$opts[CURLOPT_HTTPHEADER] = $existing_headers;
} else {
$opts[CURLOPT_HTTPHEADER] = array('Expect:');
}
curl_setopt_array($ch, $opts);
$result = curl_exec($ch);
$errno = curl_errno($ch);
// CURLE_SSL_CACERT || CURLE_SSL_CACERT_BADFILE
if ($errno == 60 || $errno == 77) {
self::errorLog('Invalid or no certificate authority found, '.
'using bundled information');
curl_setopt($ch, CURLOPT_CAINFO,
dirname(__FILE__) . DIRECTORY_SEPARATOR . 'fb_ca_chain_bundle.crt');
$result = curl_exec($ch);
}
// With dual stacked DNS responses, it's possible for a server to
// have IPv6 enabled but not have IPv6 connectivity. If this is
// the case, curl will try IPv4 first and if that fails, then it will
// fall back to IPv6 and the error EHOSTUNREACH is returned by the
// operating system.
if ($result === false && empty($opts[CURLOPT_IPRESOLVE])) {
$matches = array();
$regex = '/Failed to connect to ([^:].*): Network is unreachable/';
if (preg_match($regex, curl_error($ch), $matches)) {
if (strlen(#inet_pton($matches[1])) === 16) {
self::errorLog('Invalid IPv6 configuration on server, '.
'Please disable or get native IPv6 on your server.');
self::$CURL_OPTS[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
$result = curl_exec($ch);
}
}
}
if ($result === false) {
$e = new FacebookApiException(array(
'error_code' => curl_errno($ch),
'error' => array(
'message' => curl_error($ch),
'type' => 'CurlException',
),
));
curl_close($ch);
throw $e;
}
curl_close($ch);
return $result;
}

login: required in Google App Engine is not extensible by your application. If you wish to offer login from multiple providers you will need to turn that off in your app.yaml and instead handle the authentication within your application - which it sounds like you're working on.
You might want to look at using the Google Identity Toolkit - it offers login for some of the providers you list (more than just Google) and has PHP examples:
https://developers.google.com/identity-toolkit/

Related

PHP get_headers() fails with Pinterest

I'm currently working on a tool to integrates link of different social networks:
Facebook: https://www.facebook.com/jonathan.parentlevesque
Google plus: https://plus.google.com/+JonathanParentL%C3%A9vesque
Instagram: https://instagram.com/mariloubiz/
Pinterest: https://www.pinterest.com/jonathan_parl/
RSS: https://regex101.com
Twitter: https://twitter.com/arcadefire
Vimeo: https://vimeo.com/ondemand/crashtest/135301838
Youtube: https://www.youtube.com/user/Darkjo666
I'm using very basic regex like this one:
/^https?:\/\/(?:[a-z]{2}|[w]{3})?\.pinterest.com\/[\S]{5,}$/i
on client and server side for minimal domain validation on each links.
Then, I'm using this function to validate that the page really exists (it's useless to integrate social network links that don't work after all):
public static function isUrlExists($url){
$exists = false;
if(!StringManager::stringStartWith($url, "http") and !StringManager::stringStartWith($url, "ftp")){
$url = "https://" . $url;
}
if (preg_match(RegularExpression::URL, $url)){
$headers = get_headers($url);
if ($headers !== false and !empty($headers)){
if (strpos($headers[0], '404') === false){
$exists = true;
}
}
}
return $exists;
}
Note: In this function I'm using Diego Perini's regex for validating the URL before sending the request:
const URL = "%^(?:(?:https?|ftp)://)(?:\S+(?::\S*)?#|\d{1,3}(?:\.\d{1,3}){3}|(?:(?:[a-z\d\x{00a1}-\x{ffff}]+-?)*[a-z\d\x{00a1}-\x{ffff}]+)(?:\.(?:[a-z\d\x{00a1}-\x{ffff}]+-?)*[a-z\d\x{00a1}-\x{ffff}]+)*(?:\.[a-z\x{00a1}-\x{ffff}]{2,6}))(?::\d+)?(?:[^\s]*)?$%iu"; //#copyright Diego Perini
All the tested links so far didn't generate any error, but testing Pinterest produce me this quite scary series of error messages:
get_headers(): SSL operation failed with code 1. OpenSSL Error messages: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed
Array
(
[url] => https://www.pinterest.com/jonathan_parl/
[exists] =>
)
get_headers(): Failed to enable crypto
Array
(
[url] => https://www.pinterest.com/jonathan_parl/
[exists] =>
)
get_headers(https://www.pinterest.com/jonathan_parl/): failed to open stream: operation failed
Array
(
[url] => https://www.pinterest.com/jonathan_parl/
[exists] =>
)
Is anyone has an idea what I'm doing wrong here?
I mean, ain't Pinterest a popular social network with a valid certificate (I don't use it personally, I just created an account for testing)?
Thank you for your help,
Jonathan Parent-Lévesque from Montreal
I tried to create a self-signed certificate for my development environment (Xampp) as suggested by N.B. in his comment. That solution didn't worked for me.
His other solution was to use cUrl or guzzle instead get_headers(). Not only it worked, but, according to this developper's tests:
http://php.net/manual/fr/function.get-headers.php#104723
it is also way faster than get_headers().
For those interested, here's the code of my new function for those interested:
/**
* Send an HTTP request to a the $url and check the header posted back.
*
* #param $url String url to which we must send the request.
* #param $failCodeList Int array list of codes for which the page is considered invalid.
*
* #return Boolean
*/
public static function isUrlExists($url, array $failCodeList = array(404)){
$exists = false;
if(!StringManager::stringStartWith($url, "http") and !StringManager::stringStartWith($url, "ftp")){
$url = "https://" . $url;
}
if (preg_match(RegularExpression::URL, $url)){
$handle = curl_init($url);
curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($handle, CURLOPT_HEADER, true);
curl_setopt($handle, CURLOPT_NOBODY, true);
curl_setopt($handle, CURLOPT_USERAGENT, true);
$headers = curl_exec($handle);
curl_close($handle);
if (empty($failCodeList) or !is_array($failCodeList)){
$failCodeList = array(404);
}
if (!empty($headers)){
$exists = true;
$headers = explode(PHP_EOL, $headers);
foreach($failCodeList as $code){
if (is_numeric($code) and strpos($headers[0], strval($code)) !== false){
$exists = false;
break;
}
}
}
}
return $exists;
}
Let me explains the curl options:
CURLOPT_RETURNTRANSFER: return a string instead of displaying the calling page on the screen.
CURLOPT_SSL_VERIFYPEER: cUrl won't checkout the certificate
CURLOPT_HEADER: include the header in the string
CURLOPT_NOBODY: don't include the body in the string
CURLOPT_USERAGENT: some site needs that to function properly (by example : https://plus.google.com)
Additional note: I explode the header string and user headers[0] to be sure to only validate only the return code and message (example: 200, 404, 405, etc.)
Additional note 2: Sometime validating only the code 404 is not enough (see the unit test), so there's an optional $failCodeList parameter to supply all the code list to reject.
And, of course, here's the unit test to legitimates my coding:
public function testIsUrlExists(){
//invalid
$this->assertFalse(ToolManager::isUrlExists("woot"));
$this->assertFalse(ToolManager::isUrlExists("https://www.facebook.com/jonathan.parentlevesque4545646456"));
$this->assertFalse(ToolManager::isUrlExists("https://plus.google.com/+JonathanParentL%C3%A9vesque890800"));
$this->assertFalse(ToolManager::isUrlExists("https://instagram.com/mariloubiz1232132/", array(404, 405)));
$this->assertFalse(ToolManager::isUrlExists("https://www.pinterest.com/jonathan_parl1231/"));
$this->assertFalse(ToolManager::isUrlExists("https://regex101.com/546465465456"));
$this->assertFalse(ToolManager::isUrlExists("https://twitter.com/arcadefire4566546"));
$this->assertFalse(ToolManager::isUrlExists("https://vimeo.com/**($%?%$", array(400, 405)));
$this->assertFalse(ToolManager::isUrlExists("https://www.youtube.com/user/Darkjo666456456456"));
//valid
$this->assertTrue(ToolManager::isUrlExists("www.google.ca"));
$this->assertTrue(ToolManager::isUrlExists("https://www.facebook.com/jonathan.parentlevesque"));
$this->assertTrue(ToolManager::isUrlExists("https://plus.google.com/+JonathanParentL%C3%A9vesque"));
$this->assertTrue(ToolManager::isUrlExists("https://instagram.com/mariloubiz/"));
$this->assertTrue(ToolManager::isUrlExists("https://www.facebook.com/jonathan.parentlevesque"));
$this->assertTrue(ToolManager::isUrlExists("https://www.pinterest.com/"));
$this->assertTrue(ToolManager::isUrlExists("https://regex101.com"));
$this->assertTrue(ToolManager::isUrlExists("https://twitter.com/arcadefire"));
$this->assertTrue(ToolManager::isUrlExists("https://vimeo.com/"));
$this->assertTrue(ToolManager::isUrlExists("https://www.youtube.com/user/Darkjo666"));
}
I hope this solution will help someone,
Jonathan Parent-Lévesque from Montreal

Can't get Customer Account Data API working in PHP

I'm trying to get Intuit CAD API to work from within a PHP application. I've read the docs using the recommended "SDK" (well, more like a demo app), search the web and tried multiple advice scattered here and there. Here's what I came up with:
// I also use log file for curl - which doesn't add any info
$curl_logfile = fopen('/tmp/curl_debug', 'w+');
$ch = curl_init();
$options = array();
$options[CURLOPT_VERBOSE] = true;
$options[CURLOPT_RETURNTRANSFER] = true;
$options[CURLOPT_TIMEOUT] = 360;
$options[CURLOPT_CERTINFO] = false;
$options[CURLOPT_SSL_VERIFYPEER] = false;
$options[CURLINFO_HEADER_OUT] = true;
$options[CURLOPT_STDERR] = $curl_logfile;
$options[CURLOPT_URL] = $signed_request['signed_url'];
curl_setopt_array($ch, $options);
$raw = curl_exec($ch);
if ($error_num = curl_errno($ch)) {
$error_desc = curl_error($ch);
}
// debug - get all info about the request just issues
$all_data = curl_getinfo($ch);
The code above return 400 as the return code (viewable using curl_getinfo()) and empty string in $raw.
How can I make my PHP app talk to Intuit CAD API?
Also, on the same note - some API method require transferring parameters, so it seem, as part of the path. For example: getInstitutionDetails (v1/institutions/INST-ID). Can I put those variables as parameters? Otherwise, I need to craft each request non-generically, according to the specific API method used :-(
Thanks!

How to use REST services hosted on Apache 2.2 via cURL to send SSL requests?

What is the procedure for sending secure data (login id, password) over https to an Apache 2.2 server with self-signed certificates?
<?php
$uid=$_POST['user'];
$password=$_POST['pass'];
$url = "https://example.com/login";
$cert_file = './certificate.com.pem';
$cert_password = 'xxxxxx';
$ch = curl_init();
$options = array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_POSTFIELDS => 'uid:'.$uid.'&password:'.$password,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_VERBOSE => true,
CURLOPT_URL => $url ,
CURLOPT_SSLCERT => $cert_file ,
CURLOPT_SSLCERTPASSWD => $cert_password ,
CURLOPT_POST => true
);
curl_setopt_array($ch , $options);
$output = curl_exec($ch);
if(!$output)
{
echo "Curl Error : " . curl_error($ch);
}
else
{
echo htmlentities($output);
}
?>
the ERROR we are getting is :
curl error:unable to use client certificate (no key found or wrong passphrase ?)
You'd need to think about it this way:
Your local server asks the remote server to validate the login info. — You would need to make an exception for the self-signed certificate and remember it. (It would be a really a bad habit to simply ignore certificate errors.)
Your local server then checks if the data the remote one sent back isn't an error message and is indeed valid JSON data.
Here's some info on how to make curl remember the self-signed certificate and trust it permanently: http://turboflash.wordpress.com/2009/06/23/curl-adding-installing-trusting-new-self-signed-certificate/ — It should work for the command-line utility just as well as the PHP module.
So, let's make a little function for it. — I'm not going to test its functionality, so I can't promise to have it perfectly error free. I'm also using some practices I wouldn't use in production code, don't account for an API key, use GET parameters and I also make the remote server responsible for any serious sort of error checking and sanitation.
<?php
function remote_login($username, $password) {
/*
Initialize the curl object
*/
$login = curl_init();
/*
Some sanitation. This is probably not enough though.
*/
$username = urlencode($username);
$password = urlencode($password);
/*
Set the url we're going to use.
REST services use clean urls, but here we simply use GET parapeters.
*/
$login_url = 'https://example.com/?username='+$username+'&password='+$password;
curl_setopt($login, CURLOPT_URL, $login_url);
/*
Tell curl we would like to use the data returned from the remote server
*/
curl_setopt($login, CURLOPT_RETURNTRANSFER, true);
/*
Set the returned data as a variable
*/
$login_data = curl_exec($login);
$login_json = json_decode($login_data);
/*
We're not going to do anything else if we encounter any sort of error.
*/
if (($login_data == false) || ($login_json == false)) {
return false;
}
/*
Return the login result as a JSON object
*/
return json_decode($login_data);
}
?>
Hope this helps.

enabling curl extension in php for 000webhost.com

So I'm trying to integrate my app with facebook and I'm using the default example.php's code as my index.php. I've changed the appID and appSecret to match my app's id and secret, whenever I test the code on facebook, it will throw an exception namely UnhandledCurlException. Wraping the code with try and catch will only return 0 whenever I call the getUser() method.
I'm aware that I could enable the curl extension on php.ini, but I can only find that on my localhost but not on the server(000webhost.com) which I used to deploy my app.
I've also heard that it's possible to use .htaccess file to explicitly modify the php configurations and hence enable the curl extension, anyone knows how to do that? or are there any other alternatives which I missed?
This is the function that throws error:
protected function makeRequest($url, $params, $ch=null) {
if (!$ch) {
$ch = curl_init();
}
$opts = self::$CURL_OPTS;
if ($this->getFileUploadSupport()) {
$opts[CURLOPT_POSTFIELDS] = $params;
} else {
$opts[CURLOPT_POSTFIELDS] = http_build_query($params, null, '&');
}
$opts[CURLOPT_URL] = $url;
// disable the 'Expect: 100-continue' behaviour. This causes CURL to wait
// for 2 seconds if the server does not support this header.
if (isset($opts[CURLOPT_HTTPHEADER])) {
$existing_headers = $opts[CURLOPT_HTTPHEADER];
$existing_headers[] = 'Expect:';
$opts[CURLOPT_HTTPHEADER] = $existing_headers;
} else {
$opts[CURLOPT_HTTPHEADER] = array('Expect:');
}
curl_setopt_array($ch, $opts);
$result = curl_exec($ch);
$errno = curl_errno($ch);
// CURLE_SSL_CACERT || CURLE_SSL_CACERT_BADFILE
if ($errno == 60 || $errno == 77) {
self::errorLog('Invalid or no certificate authority found, '.
'using bundled information');
curl_setopt($ch, CURLOPT_CAINFO,
dirname(__FILE__) . DIRECTORY_SEPARATOR . 'fb_ca_chain_bundle.crt');
$result = curl_exec($ch); //the line 1003
}
// With dual stacked DNS responses, it's possible for a server to
// have IPv6 enabled but not have IPv6 connectivity. If this is
// the case, curl will try IPv4 first and if that fails, then it will
// fall back to IPv6 and the error EHOSTUNREACH is returned by the
// operating system.
if ($result === false && empty($opts[CURLOPT_IPRESOLVE])) {
$matches = array();
$regex = '/Failed to connect to ([^:].*): Network is unreachable/';
if (preg_match($regex, curl_error($ch), $matches)) {
if (strlen(#inet_pton($matches[1])) === 16) {
self::errorLog('Invalid IPv6 configuration on server, '.
'Please disable or get native IPv6 on your server.');
self::$CURL_OPTS[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
$result = curl_exec($ch);
}
}
}
if ($result === false) {
$e = new FacebookApiException(array(
'error_code' => curl_errno($ch),
'error' => array(
'message' => curl_error($ch),
'type' => 'CurlException',
),
));
curl_close($ch);
throw $e;
}
curl_close($ch);
return $result;
}
First of all make sure if cURL extension is really configured or not by using this code.
<?php
echo function_exists('curl_version') ? 1:0; // 1 = enabled , 0 = disabled.
If it is disabled, Kindly ask your webhosting provider to enable the extension. [I don't think that's a hard thing to do , instead of playing around .htaccess and other means to enable cURL without their knowledge. ]

Send post data and cookiefile in Google App Engine PHP runtime

I have a code written in PHP and currently running on my shared hosting. Now I'm going to move it on Google App Engine.
sendRequest() method sends post data and cookies to another website and returns a response.
private function sendRequest($url, array $data = array()) {
$ch = curl_init(self::URL_BASE);
$curlConfig = array(
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => $data,
CURLOPT_COOKIE => "user_name=" . $this->username . "; user_password=" . md5($this->password));
if ($url == self::URL_LOGIN) {
$this->cookieFile = tempnam("/tmp", "CURLCOOKIE");
$curlConfig[CURLOPT_COOKIEJAR] = $this->cookieFile;
} else {
$curlConfig[CURLOPT_COOKIEFILE] = $this->cookieFile;
}
curl_setopt_array($ch, $curlConfig);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
Problems:
CURL module is not supported in App Engine
tempnam() function is disabled
I've searched a lot, but couldn't find any alternatives. fsockopen() is also disabled.
Use the stream context to set the cookies on the request, per the example here.
Not sure from your code why you want to persist the cookies and for how long - can you use memcache for this purpose instead?

Categories