Google URL Shortening API using PHP - google-api

Now I know this is a common topic that has been over the Internet especially StackOverflow. But what I do not have (or haven't yet seen) is a straight solution for the token being maintained over a session.
I'm using the Google APIs Client Library for PHP .
My Query:
I have one index.php where I fetch the user data (such as name) using the Google_Oauth2Service from the PHP client lib. The user is authenticated successfully and all goes well. Now I'd like to use the URL Shortening service and therefore I have a shorten.php where I have the code to try fetch the short URL. Some how this doesn't work.
index.php
//include google api files
require_once 'src/Google_Client.php';
require_once 'src/contrib/Google_Oauth2Service.php';
require_once 'src/contrib/Google_UrlshortenerService.php';
//start session
session_start();
$gClient = new Google_Client();
$gClient->setApplicationName('Test App');
$gClient->setClientId($google_client_id);
$gClient->setClientSecret($google_client_secret);
$gClient->setRedirectUri($google_redirect_url);
$gClient->setDeveloperKey($google_developer_key);
$google_oauthV2 = new Google_Oauth2Service($gClient);
....
....
Here I have started the session and made an object of the Google_Client. I have declared the client id, secret and all other details.
Then I get the access token on successful authentication and store it in the Session variable so that when I try to fetch the short url (using jQuery ajax) from shorten.php, I can use the existing token.
$_SESSION['token'] = $gClient->getAccessToken();
....
Now in shorten.php
session_start();
require_once 'src/Google_Client.php';
require_once 'src/contrib/Google_Oauth2Service.php';
require_once 'src/contrib/Google_UrlshortenerService.php';
$gClient = new Google_Client();
$gClient->setApplicationName('Test App');
$gClient->setClientId($google_client_id);
$gClient->setClientSecret($google_client_secret);
$gClient->setRedirectUri($google_redirect_url);
$gClient->setDeveloperKey($google_developer_key);
....
if (isset($_SESSION['token']) && $_SESSION['token']) {
// Set the access token from the session
$gClient->setAccessToken($_SESSION['token']);
$url_service = new Google_UrlshortenerService($gClient);
// Check if a URL has been passed
if (isset($_GET['url'])) {
$url = new Google_Url();
$url->longUrl = $_GET['url'];
$shortURL = $url_service->url->insert($url);
....
This is the exact line where the code breaks $shortURL = $url_service->url->insert($url); I was successful in getting the token using the Session variable and created a successful URL Service object. But just when I call the insert method, that's when it fails.
From the apache error logs :
husain#innovate:~/myprojects/web$ tail -1 /var/log/apache2/error.log | sed -e 's/\\n/\n/g'
[Thu Mar 28 00:42:35 2013] [error] [client 127.0.0.1] PHP Fatal error: Uncaught exception 'Google_ServiceException' with message 'Error calling POST https://www.googleapis.com/urlshortener/v1/url?key=AIzaSyCxfXP-xS-QYJw-7mM4SNG3EW9ryj_Oiv4: (401) Invalid Credentials' in /home/husain/myprojects/web/apps/src/io/Google_REST.php:66
Stack trace:
#0 /home/husain/myprojects/web/apps/src/io/Google_REST.php(36): Google_REST::decodeHttpResponse(Object(Google_HttpRequest))
#1 /home/husain/myprojects/web/apps/src/service/Google_ServiceResource.php(177): Google_REST::execute(Object(Google_HttpRequest))
#2 /home/husain/myprojects/web/apps/src/contrib/Google_UrlshortenerService.php(38): Google_ServiceResource->__call('insert', Array)
#3 /home/husain/myprojects/web/apps/shorten.php(44): Google_UrlServiceResource->insert(Object(Google_Url))
#4 {main}
thrown in /home/husain/myprojects/web/apps/src/io/Google_REST.php on line 66
When I dump the Google_Client variables on the index.php and shorten.php files, this is what I get.
index.php
Google_Client Object
(
[scopes:protected] => Array
(
)
[useObjects:protected] =>
[services:protected] => Array
(
[oauth2] => Array
(
[scope] => Array
(
[0] => https://www.googleapis.com/auth/userinfo.profile
[1] => https://www.googleapis.com/auth/userinfo.email
)
)
)
[authenticated:Google_Client:private] =>
)
shorten.php
object(Google_Client)#1 (4) {
["scopes":protected]=>
array(0) {
}
["useObjects":protected]=>
bool(false)
["services":protected]=>
array(1) {
["urlshortener"]=>
array(1) {
["scope"]=>
string(44) "https://www.googleapis.com/auth/urlshortener"
}
}
["authenticated":"Google_Client":private]=>
bool(false)
}
and both aint the same so I'm assuming that there is something not right here. Help or direction please.

You're not requesting the UrlShortener scope when authenticating, that's why it's failing.
To get the scope added, you can create an UrlshortenerService before authenticating in index.php, passing the same instance of the client, as in:
//start session
session_start();
$gClient = new Google_Client();
$gClient->setApplicationName('Test App');
$gClient->setClientId($google_client_id);
$gClient->setClientSecret($google_client_secret);
$gClient->setRedirectUri($google_redirect_url);
$gClient->setDeveloperKey($google_developer_key);
$google_oauthV2 = new Google_Oauth2Service($gClient);
$url_service = new Google_UrlshortenerService($gClient);
Alternatively, you can use setScopes to override the automatically generated scope

Related

Unable to get Keycloak client initiated client account linking to work

The request to start the client iniated account linking fails.
The console is showing a WARN of type: CLIENT_INITIATED_ACCOUNT_LINKING_ERROR with error: invalid_token.
The url was generated as described here: https://www.keycloak.org/docs/latest/server_development/#client-initiated-account-linking, by php backend system.
Also making sure to use UTF8 encoding when generating the hash
All prerequisites as describe it the section have been fulfilled.
Im' using Keycloak 15.0.2 and Laravel with Socialite to authenticate users.
This is how the hash is generated.
$keycloack_user = Socialite::driver('keycloak')->user();
$bearerToken = $keycloack_user->token;
$tokenParts = explode(".", $bearerToken);
$tokenHeader = base64_decode($tokenParts[0]);
$tokenPayload = base64_decode($tokenParts[1]);
$jwtHeader = json_decode($tokenHeader);
$jwtPayload = json_decode($tokenPayload);
$client_id = $jwtPayload->azp;
$host = $jwtPayload->iss;
$session_state = $jwtPayload->session_state;
$nonce = Str::random(20);
$provider = "google";
$input = $nonce . $session_state . $client_id . $provider;
$utf8encoded = utf8_encode($input);
$hashed = hash('sha256', $utf8encoded);
$encoded = rtrim(strtr(base64_encode($hashed), '+/', '-_'), '=');
Then the linking url is constructed as shown below:
$redirect_uri = urlencode(...);
$full_url = $host . "/broker/". $provider ."/link?client_id=". $client_id ."&redirect_uri=". $redirect_uri ."&nonce=". $nonce ."&hash=" . $encoded;
I'm currently testing a my local machine, without using https for any of the applications. Loging in works fine and when inspecting the JWT token, the needed role mappings are present:
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
But when accessing the url it says "Invalid request" and the Keycloak console indicates the token is invalid.
Update: Solution was to return the result of the hash method as raw binary data
$hashed = hash('sha256', $utf8encoded, true);
I had to work on the same task lately but with the client implemented in JavaScript. I was also stuck for quite a while till I realized how uncommonly keycloak is expecting the encoded hash value. You need to consider following two points:
Encode the hash string into hexadecimal before base64 conversion
Replace + by - and / by _. Besides that remove trailing = symbols
Below you find a working snippet written in JS:
import sjcl from "sjcl";
hexToBase64(hexstring) {
return btoa(hexstring.match(/\w{2}/g).map(function(a) {
return String.fromCharCode(parseInt(a, 16));
}).join(""));
},
// Assume nonce, session_state, clientId, provider to be given
var data = nonce + session_state + clientId + provider;
var myBitArray = sjcl.hash.sha256.hash(data)
var hashedData = sjcl.codec.hex.fromBits(myBitArray)
var base64HashedData = this.hexToBase64(HashedData)
base64HashedData = base64HashedData.replaceAll('+','-').replaceAll('/','_').replaceAll('=','')
base64HashedData is then what you need to pass as hash query parameter to the link endpoint of keycloak.

Issue with PEM file while building a query using Laravel and Goutte

I'm building a website using Laravel 8 and Goutte 4. I'm trying to send a request to a website that requires authentication with a .pem file. In order to do so, I include the 'local_cert' param when creating the instance of http-client like this:
use Goutte\Client;
use Symfony\Component\HttpClient\HttpClient;
class Query
{
protected $client;
public function __construct() {
$this->client = (new Client(
HttpClient::create([
'timeout' => 30,
'local_cert' => //absolute path to the .pem file,
])
));
}
public function query() {
$this->client->request('GET', "https://palena.sii.cl/cvc_cgi/dte/of_solicita_folios");
}
}
This works perfectly well on my local server, but when I try it on the production server, I get an Exception: "Problem with the local SSL certificate". The .pem file being used is the same in development and production, and the php can read the file in the production server.
The full error in the laravel Log is:
[2020-12-02 21:32:30] production.ERROR: Problem with the local SSL certificate for "https://palena.sii.cl/cvc_cgi/dte/of_solicita_folios". {"userId":1,"exception":"[object] (Symfony\Component\HttpClient\Exception\TransportException(code: 0): Problem with the local SSL certificate for "https://palena.sii.cl/cvc_cgi/dte/of_solicita_folios". at /my/path/vendor/symfony/http-client/Chunk/ErrorChunk.php:65)
did you fixed? i only make this work with GuzzleHttp
$requestAuthentication = $clientAuth->request('GET', $urlAuthentication, [
'cert' => [$rutaPem, 'clave'],
'cookies' => $cookiesAuth,
]);

symfony 4 app on heroku with remote database

I really need some help here hehe
Ok i got a symfony 4 application that is working perfectly in local environement.
I installed the app on heroku, but i want to access my Mysql database on my web hosting.
To be able to do that i had to install the Fixie app on heroku to have two ip adress and be able to whitelist those address for the remote access of my database.
The app is running good, but if i go to any links that have a call to do at the database i got a timeout.
I think the problem is in my index.php file, when installing Fixie you have to add code to the trust proxy
Heres what i have right now in my index.php file
<?php
use App\Kernel;
use Symfony\Component\Debug\Debug;
use Symfony\Component\HttpFoundation\Request;
require dirname(__DIR__).'/config/bootstrap.php';
if ($_SERVER['APP_DEBUG']) {
umask(0000);
Debug::enable();
}
$trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? $_ENV['TRUSTED_PROXIES'] ?? false;
$trustedProxies = $trustedProxies ? explode(',', $trustedProxies) : [];
if($_SERVER['APP_ENV'] == 'prod') $trustedProxies[] = $_SERVER['REMOTE_ADDR'];
if($trustedProxies) {
Request::setTrustedProxies($trustedProxies, Request::HEADER_X_FORWARDED_AWS_ELB);
}
if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? $_ENV['TRUSTED_HOSTS'] ?? false) {
Request::setTrustedHosts([$trustedHosts]);
}
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
Thats the log message i get when i try to access a page that have a call to the database
2019-11-28T22:13:12.218311+00:00 app[web.1]: [2019-11-28 22:12:42] request.INFO: Matched route "publicResultat". {"route":"publicResultat","route_parameters":{"_route":"publicResultat","_controller":"App\\Controller\\PublicController::index"},"request_uri":"https://jugement.herokuapp.com/public","method":"GET"} []
2019-11-28T22:13:12.218435+00:00 app[web.1]: [2019-11-28 22:12:42] security.INFO: Populated the TokenStorage with an anonymous Token. [] []
2019-11-28T22:13:12.220033+00:00 app[web.1]: [2019-11-28 22:13:12] request.CRITICAL: Uncaught PHP Exception Doctrine\DBAL\Exception\ConnectionException: "An exception occurred in driver: SQLSTATE[HY000] [2002] Connection timed out" at /app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractMySQLDriver.php line 93 {"exception":"[object] (Doctrine\\DBAL\\Exception\\ConnectionException(code: 0): An exception occurred in driver: SQLSTATE[HY000] [2002] Connection timed out at /app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractMySQLDriver.php:93, Doctrine\\DBAL\\Driver\\PDOException(code: 2002): SQLSTATE[HY000] [2002] Connection timed out at /app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:31, PDOException(code: 2002): SQLSTATE[HY000] [2002] Connection timed out at /app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:27)"} []
When you install fixie in heroku you have to add something this is the example they give for php but i don't understand how to do that in my symfony app
PHP cURL is the easiest way to get your PHP application working correctly with Fixie. Here’s how:
<?php
function proxyRequest() {
$fixieUrl = getenv("FIXIE_URL");
$parsedFixieUrl = parse_url($fixieUrl);
$proxy = $parsedFixieUrl['host'].":".$parsedFixieUrl['port'];
$proxyAuth = $parsedFixieUrl['user'].":".$parsedFixieUrl['pass'];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_PROXY, $proxy);
curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxyAuth);
curl_close($ch);
}
$response = proxyRequest();
print_r($response);
?>
Hope that i am clear on my problem
any help would be very useful
I also found strange that i have no header present when the call is done
Request URL: https://jugement.herokuapp.com/public
Referrer Policy: no-referrer-when-downgrade
Thats all i have in the header

Laravel issue Credentials are required to create a Client

I need to test send SMS to mobile I get Credentials are required to create a Client error for My Code Here
.env
TWILIO_ACCOUNT_SID=AC15...................
TWILIO_AUTH_TOKEN=c3...................
TWILIO_NUMBER=+1111...
Config\App
'twilio' => [
'TWILIO_AUTH_TOKEN' => env('TWILIO_AUTH_TOKEN'),
'TWILIO_ACCOUNT_SID' => env('TWILIO_ACCOUNT_SID'),
'TWILIO_NUMBER' => env('TWILIO_NUMBER')
],
Controller
$accountSid = env('TWILIO_ACCOUNT_SID');
$authToken = env('TWILIO_AUTH_TOKEN');
$twilioNumber = env('TWILIO_NUMBER');
$client = new Client($accountSid, $authToken);
try {
$client->messages->create(
'0020109.....',
[
"body" => 'test',
"from" => $twilioNumber
// On US phone numbers, you could send an image as well!
// 'mediaUrl' => $imageUrl
]
);
Log::info('Message sent to ' . $twilioNumber);
} catch (TwilioException $e) {
Log::error(
'Could not send SMS notification.' .
' Twilio replied with: ' . $e
);
}
Twilio developer evangelist here.
A quick read over the environment config for Laravel suggests to me that you can use the env method within your config files, as you are doing, but it's not necessarily available in application code. Since you are committing your environment variables to the config object, I think you need to use the config method instead.
$accountSid = config('TWILIO_ACCOUNT_SID');
$authToken = config('TWILIO_AUTH_TOKEN');
$twilioNumber = config('TWILIO_NUMBER');
Let me know if that helps at all.

Guzzle Http Not Logging In Laravel With Monolog

I have this example:
use Monolog\Logger;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\MessageFormatter;
public function getApiTest()
{
$stack = HandlerStack::create();
$stack->push(
Middleware::log(
new Logger('Logger'),
new MessageFormatter('{req_body} - {res_body}')
)
);
$client = new \GuzzleHttp\Client(
[
'base_uri' => 'http://apitesting.test/api/',
'handler' => $stack,
]
);
echo (string) $client->get('apitest')->getBody();
}
Which should be logging the request and response, from what I understand.
I have a custom logging channel built for logging to the database instead.. But I have now disabled it and went back to Laravel's file logging - but this is still not logging the Guzzle request/response.
It seems that the first parameter of the Middleware::log() is suppose to be the log channel that you are trying to use. For example:
$stack = HandlerStack::create();
$logChannel = app()->get('log')->channel('my-custom-channel');
$stack->push(
Middleware::log(
$logChannel,
new MessageFormatter('{req_body} - {res_body}')
)
);
That will tell the middleware which log channel that you are trying to use.

Resources