So I'm trying to get a grasp of OpenID, and I feel I understand the theory, etc... now it's come to implementing it. I've got a very basic setup that sends a curl request to the google provider address..
https://www.google.com/accounts/o8/id
parses the returned XRDS xml file
<?xml version="1.0" encoding="UTF-8"?>
<xrds:XRDS xmlns:xrds="xri://$xrds" xmlns="xri://$xrd*($v*2.0)">
<XRD>
<Service priority="0">
<Type>http://specs.openid.net/auth/2.0/server</Type>
<Type>http://openid.net/srv/ax/1.0</Type>
<Type>http://specs.openid.net/extensions/ui/1.0/mode/popup</Type>
<Type>http://specs.openid.net/extensions/ui/1.0/icon</Type>
<Type>http://specs.openid.net/extensions/pape/1.0</Type>
<URI>https://www.google.com/accounts/o8/ud</URI>
</Service>
</XRD>
</xrds:XRDS>
After retrieving the actual provider for google from their XRDS document I redirect using this function...
public function RedirectToEndpoint() {
$params = array();
$params['openid.mode'] = 'checkid_setup';
$params['openid.ns'] = 'http://specs.openid.net/auth/2.0';
$params['openid.claimed_id'] = 'http://specs.openid.net/auth/2.0/identifier_select';
$params['openid.identity'] = 'http://specs.openid.net/auth/2.0/identifier_select';
$params['openid.return_to'] = $this->URLs['return_to'];
$params['openid.realm'] = $this->URLs['realm'];
$join = stripos($this->URLs['openid_server'], '?') ? '&' : '?';
$redirect_to = $this->URLs['openid_server'] . $join . $this->array2url($params);
if (headers_sent()){ // Use JavaScript to redirect if content has been previously sent (not recommended, but safe)
echo '<script language="JavaScript" type="text/javascript">window.location=\'';
echo $redirect_to;
echo '\';</script>';
}else{ // Default Header Redirect
header('Location: ' . $redirect_to);
}
}
The array2url is a simple function which converts the assoc array $params to append to the query string.
The generated url is such...
https://www.google.com/accounts/o8/ud?openid.mode=checkid_setup&openid.ns=http://specs.openid.net/auth/2.0&openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.return_to=http://learn.local/openid/return.php&openid.realm=http://learn.local/openid/index.html&
However, you end up at a page requested is invalid. And a nice 400 Bad Request.. any ideas?
All in all, I couldn't feel more stupid! It came down to setting my realm correctly. I had a realm which lead to my return address being out of the available "scope" so to speak. For future people who run into a 400 error, it might be..
$openid->SetReturnAddress(('http://learn.local/openid/return.php')); //.return_to
$openid->SetDomain(('http://learn.local/openid/index.html')); //Realm
A poorly configured realm.. facepalm
It is now...
$openid->SetDomain(('http://learn.local')); //Realm
Related
I'm having an hard time working with google storage.
So I'm trying to make a signed url, already have the client id (which is an email) and private key (as described here) so:
STEP 1: construct the string
function googleBuildConfigurationString($method, $expiration, $file, array $options = []) {
$allowedMethods = ['GET', 'HEAD', 'PUT', 'DELETE'];
// initialize
$method = strtoupper($method);
$contentType = $options['Content_Type'];
$contentMd5 = $options['Content_MD5'] ? base64_encode($options['Content_MD5']) : '';
$headers = $options['Canonicalized_Extension_Headers'] ? $options['Canonicalized_Extension_Headers'] . PHP_EOL : '';
$file = $file ? $file : $options['Canonicalized_Resource'];
// validate
if(array_search($method, $allowedMethods) === false) {
throw new RuntimeException("Method '{$method}' is not allowed");
}
if(!$expiration) {
throw new RuntimeException("An expiration date should be provided.");
}
return <<<TXT
{$method}
{$contentMd5}
{$contentType}
{$expiration}
{$headers}{$file}
TXT;
}
So far so good (I think), echoing the output it looks similar to the examples, so now to sign the string
STEP 2: signing the string
Initialy I was using openssl_public_encrypt, after searching around found that google-api-php-client has the Google_Signer_P12 (which actually uses openssl_sign), so the method looks like:
function googleSignString($certificatePath, $stringToSign) {
return (new Google_Signer_P12(
file_get_contents($certificatePath),
'notasecret'
))->sign($stringToSign);
}
And here I'm not sure if this is signing it correctly, finally building final url
STEP 3: building the URL
function googleBuildSignedUrl($serviceEmail, $file, $expiration, $signature) {
return "http://storage.googleapis.com{$file}"
. "?GoogleAccessId={$serviceEmail}"
. "&Expires={$expiration}"
. "&Signature=" . urlencode($signature);
}
But the opening the URL in the browser will retrieve:
<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>
The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.
</Message>
<StringToSign>GET 1437470250 /example/video.mp4</StringToSign>
</Error>
I've added a gist with the final script to be easier to read
So any idea what am I doing wrong?
I've found the solution, there was a bug on the expiration date where I was doing:
$expiration = (new DateTime())->modify('+3h')->getTimestamp();
So I've changed h to hours so that it works now, like:
$expiration = (new DateTime())->modify('+3hours')->getTimestamp();
But that didn't quite solved it, the actual missing part is that the Google_Signer_P12::sign() requires it to be encoded on base64, which is specified on the google docs:
Google Cloud Storage expects the Base64 encoded signature in its APIs.
However I (wrongly) though that Google_Signer_P12::sign() already would do that, so after I understood that it was required I've changed the sign method to:
function googleSignString($certificatePath, $stringToSign)
{
return base64_encode((new Google_Signer_P12(
file_get_contents($certificatePath),
'notasecret'
))->sign($stringToSign));
}
And it is working now!!!
I've also updated the gist for anyone that wants to use it :)
I'm testing and learning the php-openid library at here: https://github.com/openid/php-openid
I downloaded the whole package and uploaded examples/consumer to http://www.example.com/openid and also uploaded Auth to http://www.example.com/Auth because my website only needs to be a relying party.
It's working correctly when I tested it with one of my openids and it displays "You have successfully verified xxxxx as your identity. "
However, it DOES NOT return any SREG attributes such as email or name which my openid profile does have these information.
I didn't make any change to any of the consumer example files (index.php, try_auth.php, finish_auth.php, nor common.php), so the code looks like this:
In try_auth.php:
$sreg_request = Auth_OpenID_SRegRequest::build(
// Required
array('nickname'),
// Optional
array('fullname', 'email'));
if ($sreg_request) {
$auth_request->addExtension($sreg_request);
}
In finish_auth.php:
$sreg = $sreg_resp->contents();
if (#$sreg['email']) {
$success .= " You also returned '".escape($sreg['email']).
"' as your email.";
}
if (#$sreg['nickname']) {
$success .= " Your nickname is '".escape($sreg['nickname']).
"'.";
}
if (#$sreg['fullname']) {
$success .= " Your fullname is '".escape($sreg['fullname']).
"'.";
}
I tried:
$sreg = $sreg_resp->contents();
print_r($sreg);
But it turned out to be an empty array:
Array
(
)
I tried:
https://www.google.com/accounts/o8/id
yahoo.com
ichsie.myopenid.com
And it all ends up with an empty array of $sreg.
I tried lightopenid which uses AX rather than SREG:
contact/email
namePerson
And they are correctly returning the values.
So how can I make the php-openid library return the attributes I need?
Does the provider actually returned the SREG attributes? What is the HTTP response you are sending from try_auth.php? What is the HTTP request that you received on finish_auth.php?
I'm planning to obscure link urls with the following way. It stores urls in an array and then if it detects the parameter in the $_GET array, it redirects to the saved url.
<?
/* Plugin Name: Sample Link Cloak */
add_action('admin_menu', 'sample_link_cloak');
function sample_link_cloak() {
add_options_page(
'Sample Link Cloak',
'Sample Link Cloak',
'manage_options',
'sample_link_cloak',
'sample_link_cloak_admin');
}
function sample_link_cloak_admin() {
?>
<div class="wrap">
<?php
$links = '<p>google.com</p>' . PHP_EOL
. '<p>wordpress.org</p>' . PHP_EOL
. '<p>stackoverflow.com</p>' . PHP_EOL;
$doc = new DOMDocument();
#$doc->loadHTML($links);
$array_urls = array();
foreach ($doc->getElementsByTagName('a') as $node) {
$url = $node->getAttribute('href');
$hash = md5($url);
$array_urls[$hash] = $url;
$url = site_url('?urlcloak=' . $hash);
$node->setAttribute('href', $url);
}
echo $doc->saveXML();
update_option('sample_urlcloak', $array_urls);
?>
</div>
<?php
}
add_action('init', 'cloakurls');
function cloakurls() {
if (isset($_GET['urlcloak'])) {
$array_urls = get_option('sample_urlcloak');
wp_redirect($array_urls[$_GET['urlcloak']]);
exit;
}
}
There is a problem I can predict with this method. The number of links increases day by day so the stored data have to be deleted when they reach to some extent. But if the visitor saves the web page on the local drive and read the contents later on and click the link, at this point if the server does not store the url, it won't redirect.
I think it is pretty much the same issue described here. Looking for more efficient way to serve numerous link redirects? but for a distributed plugin, is it realistic/practical to edit the .htaccess file? I guess not all users have the right access to modify .htaccess. I'm not sure.
I'm wondering if somebody can direct me to the right direction.
It depends on your application. Usually you can do what the other SO post describes, but if it really is random; or you don't have control over the redirected site - use a database. It's function is to quickly find what you are looking for on a hard drive.
Now to wonder a bit further than your current approach - what about two-way obfuscation? If it's just obfuscation (not security) use such an algorithm that you could both encrypt and decrypt the url. That way you wouldn't need to store any data.
I have this class to send a SOAP-request (the class also defines the header)
class Personinfo
{
function __construct() {
$this->soap = new SoapClient('mysource.wsdl',array('trace' => 1));
}
private function build_auth_header() {
$auth->BrukerID = 'userid';
$auth->Passord = 'pass';
$auth->SluttBruker = 'name';
$auth->Versjon = 'v1-1-0';
$authvalues = new SoapVar($auth, SOAP_ENC_OBJECT);
$header = new SoapHeader('http://www.example.com', "BrukerAutorisasjon", // Rename this to the tag you need
$authvalues, false);
$this->soap->__setSoapHeaders(array($header));
}
public function hentPersoninfo($params){
$this->build_auth_header();
$res = $this->soap->hentPersoninfo($params);
return $res;
}
}
The problem is that there's something wrong with my function and the response is an error. I'd like to find out what content I am sending with my request, but I can't figure out how.
I've tried a try/catch-block in the hentPersoninfo-function that calls $this->soap->__getLastRequest but it is always empty.
What am I doing wrong?
Before I ever start accessing a service programmatically, I use SoapUI to ensure that I know what needs sent to the service, and what I should expect back.
This way, you can ensure the issue isn't in the web service and/or in your understanding of how you should access the web service.
After you understand this, you can narrow your focus onto making the relevant SOAP framework do what you need it to do.
Been having major issues trying to solve this issue, I'll be happy to give a +500 bounty to someone who can help me get this work.
Basically, I'm trying to call this web service using Nusoap:
https://www.eway.com.au/gateway/ManagedPaymentService/managedCreditCardPayment.asmx?op=QueryCustomer
This is what I've got so far:
class Eway
{
var $username = 'test#eway.com.au';
var $pw = 'test123';
var $customerId = '87654321';
private function setHeaders($client)
{
$headers = <<<EOT
<eWAYHeader xmlns="http://www.eway.com.au/gateway/managedPayment">
<eWAYCustomerID>$this->customerId</eWAYCustomerID>
<Username>$this->username</Username>
<Password>$this->pw</Password>
</eWAYHeader>
EOT;
$client->setHeaders($headers);
return $client;
}
function getCustomer($ewayId = 9876543211000)
{
$url = 'https://www.eway.com.au/gateway/ManagedPaymentService/managedCreditCardPayment.asmx?WSDL';
$client = new Nusoap_client($url, true);
$this->setHeaders($client);
$args['QueryCustomer'] = array('managedCustomerID'=>$ewayId);
$result = $client->call('QueryCustomer', $args);
print_r($result);
}
}
When I run this code and do $eway->getCustomer() I get the following error:
Array
(
[faultcode] => soap:Client
[faultstring] => eWayCustomerID, Username and Password needs to be specified in the soap header.
)
What am I doing wrong?
If you could fix my class and give me working code which is able to do the QueryCustomer method using the test customer id and return its info, I'll be glad to give you +500 rep and my eternal gratitude. Obviously it'll be 48 hours before I can start the bounty, but I promise that I will do it.
I could be missing the point, but you never actually assign the returned object to $client:
function getCustomer($ewayId = 9876543211000)
{
$url = 'https://www.eway.com.au/gateway/ManagedPaymentService/managedCreditCardPayment.asmx?WSDL';
$client = new Nusoap_client($url, true);
$client = $this->setHeaders($client);
$args['QueryCustomer'] = array('managedCustomerID'=>$ewayId);
$result = $client->call('QueryCustomer', $args);
print_r($result);
}
You could also set $client as a class variable if desired or by sending the parameter as a reference.
Looking at the data, I do not know if this matters, but you are using var for your class variable declarations and then using private for the function. If you are using php5 I would stay away from the var:
private $username = 'test#eway.com.au';
private $pw = 'test123';
private $customerId = '87654321';
Use the private or public or protected (whichever your class requires) instead to keep consistency. I doubt this will solve your problem, just something to be conscious about.
Possible Solution
Ok, doing some digging of my own, figured this out, you need to encase the actual header you add in a SOAP:Header deal. I tested the below and it was working for me, so give it a try:
private function setHeaders($client)
{
$headers = <<<EOT
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/" >
<SOAP:Header>
<eWAYHeader xmlns="http://www.eway.com.au/gateway/managedPayment">
<eWAYCustomerID>$this->customerId</eWAYCustomerID>
<Username>$this->username</Username>
<Password>$this->pw</Password>
</eWAYHeader>
</SOAP:Header>
EOT;
$client->setHeaders($headers);
return $client;
}
It did not return any errors. So yea, it seems that is the likely culprit. (Note I also implemented the $client = $this->setHeaders($client); I mentioned above as well.
And my Final Answer is:
Alright did a bit of digging and found something that works. Not saying it is right, but yea it works.
private function setHeaders($client)
{
$headers = <<<EOT
<eWAYHeader xmlns="https://www.eway.com.au/gateway/managedpayment">
<eWAYCustomerID>$this->customerId</eWAYCustomerID>
<Username>$this->username</Username>
<Password>$this->pw</Password>
</eWAYHeader>
EOT;
$client->setHeaders($headers);
return $client;
}
function getCustomer($ewayId = 123456789012)
{
$url = 'https://www.eway.com.au/gateway/ManagedPaymentService/managedCreditCardPayment.asmx?WSDL';
$client = new nusoap_client($url);
$client = $this->setHeaders($client);
$args['QueryCustomer'] = array('managedCustomerID'=>$ewayId);
$result = $client->call('QueryCustomer', $args, $namespace='https://www.eway.com.au/gateway/managedpayment', $soapAction='https://www.eway.com.au/gateway/managedpayment/QueryCustomer');
print_r($result);
//echo "\n{$client->request}\n"; // This echos out the response you are sending for debugging.
}
It seems the namespace and soapAction were the key ingredients. I found these using the link you originally posted: https://www.eway.com.au/gateway/ManagedPaymentService/managedCreditCardPayment.asmx?op=QueryCustomer
Basically, I just looked at that response, and then did some searching to figure out the soapAction, and then just messed with it until the request being sent matched the page you posted. It returns a failed login, but yea. That generally means something is working, and is probably due to the test data. But that gives you a baseline to go off of.
And the $client->request is a handy debugging tool for the future.
Update 5:
nusoap actually wraps the request with SOAP-ENV, like:
<SOAP-ENV:Header><eWAYHeader xmlns="https://www.eway.com.au/gateway/managedpayment">
<eWayCustomerID>87654321</eWayCustomerID>
<Username>test#eway.com.au</Username>
<Password>test123</Password>
</eWAYHeader></SOAP-ENV:Header>
While in the docs for EWay soap:Header must be used. I couldn't find a mention of the latter in nusoap headers.
Update 4:
This link has a good tip:
Got it. It was a case issue but not
there, and their PDF is incorrect.
For anyone that gets this in the
future, the PDF says:
<eWAYHeader
xmlns="http://www.eway.com.au/gateway/managedPayment">
It should be:
<eWAYHeader
xmlns="https://www.eway.com.au/gateway/managedpayment">
So this right here:
$client->setHeaders($headers);
The SoapClient class doesn't have that method. Instead, you can create a new SoapHeader.
private function setHeaders($client)
{
$headers = new stdClass;
$headers->eWAYCustomerID = $this->customerId;
$headers->Username = $this->username;
$headers->Password = $this->pw;
$ewayHeader = new SoapHeader(
"http://www.eway.com.au/gateway/managedPayment",
"eWAYHeader",
$headers
);
$client->__setSoapHeaders(array($ewayHeader));
return $client;
}
Edit: Alright, digging deeper:
private function prepHeaders()
{
return array(
'eWAYHeader' => array(
'eWAYCustomerID' => $this->customerId,
'Username' => $this->username,
'Password' => $this->pw
)
);
}
function getCustomer($ewayId = 9876543211000)
{
$url = 'https://www.eway.com.au/gateway/ManagedPaymentService/managedCreditCardPayment.asmx?WSDL';
$client = new nusoap_client($url);
$args['QueryCustomer'] = array('managedCustomerID'=>$ewayId);
$result = $client->call('QueryCustomer', $args, null, null, $this->prepHeaders());
print_r($result);
}
What happens if you do that?
I know this is not a full solution to the issue, but although this question is quite old, my findings may help lead to a concrete resolution.
I've been experiencing a similar error in relation to your mention of the HTTP "SOAPAction" header. (I am, however, dealing with a different eWay API than you. I'm dealing with the "Rapid API", which last week was renamed to from "Merchant Hosted Payments", which was part of the reason why my script wasn't working).
To return to the point, I found that if you don't specify the HTTP "SOAPAction" header, eWay returns a SoapFault with the following error message.
"System.Web.Services.Protocols.SoapException: Unable to handle request without a valid action parameter. Please supply a valid soap action."
If you add the HTTP "SOAPAction" header, you get an error no matter what you set it to.
"System.Web.Services.Protocols.SoapException: Server did not recognize the value of HTTP Header SOAPAction: XXX"
I'm also told by a member of eWay's support staff that they have an issues with an internal redirect, which they are now looking into resolving.
<ME> (2012-05-25 02:50:18)
I had an idea of what it could be. What is the "SOAPAction" HTTP header supposed to be set to?
<ME> (2012-05-25 02:52:05)
I couldn't find it in the documentation.
<EWAY_SUPPORT_STAFF> (2012-05-25 02:53:38)
The only thing that is required typically is the endpoint which is https://au.ewaypayments.com/hotpotato/soap.asmx and the <CreateAccessCode xmlns="https://au.ewaypayments.com/hotpotato/">
<EWAY_SUPPORT_STAFF> (2012-05-25 02:54:10)
In my tests it is working but what is happening is that requests are being redirected to the old URL which does not accept the CreateAccessCode method
<ME> (2012-05-25 02:56:58)
You did say that.
<ME> (2012-05-25 02:57:13)
So is this bug happening in the production environment?
<EWAY_SUPPORT_STAFF> (2012-05-25 02:57:57)
Yes it appears so. I have escalated this to Development and attached our last chat transcript and my own test results. They are looking at it now.