I want to reward users on signing up to my app by giving them gift card balance in their amazon account and to do that i have used Load Amazon Balance api which is similar to Amazon Incentive api.
Amazon requires a digitally signed authorization payload in order to access the api. I've compared my code to amazon's process of digitally signing using aws secret key and aws access key. But its still giving me
"The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details."
I'm attaching the code of my class in the post. Can anyone help me out by looking at the code and telling if there is any mistake in the process i'm performing for sending information to the api. Thanks.
<?php
/**
* Created by PhpStorm.
*
* Date: 07-09-2018
* Time: 01:18 PM
*/
namespace App;
use App\AppConstant\AppConstant;
use App\Entities\Models\User;
use Illuminate\Support\Facades\Auth;
class AmazonReward
{
/**
* This is the bulk of the logic for making AGCOD calls.
*/
//Constants
public static $apikey;
public static $clientId = env("client_id");
public static $clientSecret = env("client_secret");
public static $awsSecretKey = env("client_secret");
public static $partnerId = env("partner_id");
public static $SERVICE_NAME = "AGCODService";
public static $ACCEPT_HEADER = "accept";
public static $CONTENT_HEADER = "content-type";
public static $HOST_HEADER = "host";
public static $XAMZDATE_HEADER = "x-amz-date";
public static $XAMZTARGET_HEADER = "x-amz-target";
public static $AUTHORIZATION_HEADER = "Authorization";
public static $AWS_SHA256_ALGORITHM = "AWS4-HMAC-SHA256";
public static $KEY_QUALIFIER = "AWS4";
public static $TERMINATION_STRING = "aws4_request";
const SERVICE_NAME = "AGCODService";
const ACCEPT_HEADER = "accept";
const CONTENT_HEADER = "content-type";
const HOST_HEADER = "host";
const XAMZDATE_HEADER = "x-amz-date";
const XAMZTARGET_HEADER = "x-amz-target";
const AUTHORIZATION_HEADER = "Authorization";
const AWS_SHA256_ALGORITHM = "AWS4-HMAC-SHA256";
const KEY_QUALIFIER = "AWS4";
const TERMINATION_STRING = "aws4_request";
const ENDPOINT ='agcod-v2.amazon.com';
// const ENDPOINT ='agcod-v2-gamma.amazon.com';
public static function get_paypload_giftcard($partnerId, $gcRequestId, $currencyCode, $gcAmount, $customer_id)
{
$amount = trim($gcAmount);
$payload = array(
"loadBalanceRequestId" => $gcRequestId,
"partnerId" => $partnerId,
"amount" =>
array(
"currencyCode" => $currencyCode,
"value" => floatval($amount)
),
"account" =>
array(
"id" => $customer_id,
"type" => "2"
),
"timestamp" => time(),
// "transactionSource" => [
// "sourceId" => ""
// ],
"externalReference" => "",
"notificationDetails" => [
"notificationMessage" => ""
]
);
return json_encode($payload);
}
/**
* Builds the "formal" request that can be hashed to verify the contents of the request.
* The request does not get sent to the server in this format, but the hash of this is.
*
* #return The formal request
*/
public static function buildCanonicalRequest($serviceOperation, $payloadHash, $customer_id) {
$ACCEPT_HEADER = self::ACCEPT_HEADER;
$HOST_HEADER = self::HOST_HEADER;
$XAMZDATE_HEADER = self::XAMZDATE_HEADER;
$XAMZTARGET_HEADER = self::XAMZTARGET_HEADER;
$ACCEPT_HEADER = self::ACCEPT_HEADER;
$dateTimeString = Self::getTimeStamp();
$header1 = Self::header1($serviceOperation);
$canonicalRequest = "POST\n/$serviceOperation HTTP/1.1\n\n$header1\n\n$ACCEPT_HEADER;$HOST_HEADER;$XAMZDATE_HEADER;$XAMZTARGET_HEADER\n$payloadHash";
return $canonicalRequest;
}
/**
* Returns part of the header used in the canonical request.
*
* #return the portion of the header.
*/
public static function header1($serviceOperation) {
$ACCEPT_HEADER = self::ACCEPT_HEADER;
$XAMZDATE_HEADER = self::XAMZDATE_HEADER;
$XAMZTARGET_HEADER = self::XAMZTARGET_HEADER;
$HOST_HEADER = Self::HOST_HEADER;
$dateTimeString = Self::getTimeStamp();
$endpoint = self::ENDPOINT;
$contentType = Self::getContentType();
return
"$ACCEPT_HEADER:$contentType\n$HOST_HEADER:$endpoint\n$XAMZDATE_HEADER:$dateTimeString\n$XAMZTARGET_HEADER:com.amazonaws.agcod.AGCODService.$serviceOperation";
}
public static function disburseFunds($customer_id){
$apiString = "https://" . Self::ENDPOINT . "/LoadAmazonBalance";
$ch = curl_init();
$xAmzDateTime = gmdate('Ymd\THis\Z');
$xAmzDate = substr($xAmzDateTime, 0, 8);
$xAmzTarget = "com.amazonaws.agcod.AGCODService.LoadAmazonBalance";
$region = "us-east-1";
$serviceOperation = "LoadAmazonBalance";
// $canonicalRequestHash = Self::myHash($canonicalRequest);
$payload = Self::get_paypload_giftcard( Self::$partnerId, Self::$partnerId . time(), "USD", 5.00, $customer_id);
$payloadHash = Self::myHash($payload);
$canonicalRequest = Self::buildCanonicalRequest($serviceOperation, $payloadHash , $customer_id);
$dateTimeString = Self::getTimeStamp();
$curl_response = Self::invokeRequest($payload, $xAmzDateTime,$canonicalRequest, $serviceOperation);
echo "<pre>";
print_r($curl_response);
exit;
$json = json_decode($curl_response);
if ( isset($json->message) || ! isset($json->cardInfo) )
{
echo "<pre>";
print_r($json);
exit;
$ret = array();
$ret['success']=false;
$ret['errorCode'] = time();
return $ret;
}
$ret = array();
$ret['success']=true;
$ret['gcValue']= $json->cardInfo->value->amount;
$ret["gcCode"]= $json->gcClaimCode;
$ret["gcResponseId"]= $json->gcId; ////done
$ret["gcRequestId"]=$json->creationRequestId;
echo "<pre>";
print_r($ret);
exit;
return $ret;
// echo "<pre>";
// print_r($canonicalRequestHash);
// exit;
// // $canonicalRequestHash = myHash($canonicalRequest);
// // $stringToSign = buildStringToSign($canonicalRequestHash);
// echo "<pre>";
// print_r($this->buildAuthSignature($stringToSign));
// exit;
// $authotization = "AWS4-HMAC-SHA256 Credential=AKIAIGHKAVYIDBOH3O3A/" . $xAmzTarget . "/" . $region . "/AGCODService/aws4_request,SignedHeaders=accept;host;x-amz-date;x-amz-target, Signature=ec86661c1d39f74b5891666505bb7656b172b0d060d911bee3b6a1c29ae17657";
// /*$post = http_build_query([
// ["Authorization" => "DEMO1"],
// ["x-amz-date" => $xAmzDate],
// ["x-amz-target" => $xAmzTarget],
// ["Accept" => "application/json"],
// ["Host" => "agcod-v2-gamma.amazon.com"]
// ]);*/
// // $post = [];
// $post = [];
// $post["loadBalanceRequestId"] = "Amazon123456";
// $post["partnerId"] = "";
// $post["amount"] = [];
// $post["amount"]["currencyCode"] = "";
// $post["amount"]["value"] = "";
// $post["account"] = [];
// $post["account"]["id"] = "F2044";
// $post["account"]["type"] = "0";
// $post["transactionSource"] = [];
// $post["transactionSource"]["sourceId"] = "";
// $post["externalReference"] = "";
// $post["notificationDetails"] = [];
// $post["notificationDetails"]["notificationMessage"] = "";
// $customHeaders = [
// "Authorization" => "Amazon",
// "x-amz-date" => $xAmzDateTime,
// "x-amz-target" => $xAmzTarget,
// "Accept" => "application/json",
// "Host" => "agcod-v2-gamma.amazon.com"
// ];
// curl_setopt($ch, CURLOPT_URL, $apiString);
// curl_setopt($ch, CURLOPT_POST, 1);
// curl_setopt($ch, CURLOPT_POSTFIELDS,$post);
// curl_setopt($ch, CURLOPT_HTTPHEADER, $customHeaders);
// curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// $server_output = curl_exec($ch);
// curl_close ($ch);
// // Further processing ...
// if ($server_output == "OK") {
// }else{
// }
}
/**
* Builds the authenication string used to prove that the request is allowed and made by the right party.
*
* #param $stringToSign The string to sign.
* #return The authenication signature.
*/
public static function buildAuthSignature($dateString, $stringToSign) {
$AWS_SHA256_ALGORITHM = Self::AWS_SHA256_ALGORITHM;
$SERVICE_NAME = Self::SERVICE_NAME;
$TERMINATION_STRING = Self::TERMINATION_STRING;
$ACCEPT_HEADER = Self::ACCEPT_HEADER;
$HOST_HEADER = Self::HOST_HEADER;
$XAMZDATE_HEADER = Self::XAMZDATE_HEADER;
$XAMZTARGET_HEADER = Self::XAMZTARGET_HEADER;
$awsKeyId = Self::$clientId;
$regionName= Self::getRegion();
$derivedKey = Self::buildDerivedKey(Self::getDateString($dateString));
$derivedKey_lower = Self::buildDerivedKey(Self::getDateString($dateString), false);
$dateString = Self::getDateString($dateString);
// Calculate signature per http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
$finalSignature = Self::hmac($stringToSign, $derivedKey, false);
// Assemble Authorization Header with signing information
// per http://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
$authorizationValue =
$AWS_SHA256_ALGORITHM
. " Credential=" . $awsKeyId
. "/" . $dateString . "/" . $regionName . "/" . $SERVICE_NAME . "/" . $TERMINATION_STRING . ","
. " SignedHeaders="
. $ACCEPT_HEADER . ";" . $HOST_HEADER . ";" . $XAMZDATE_HEADER . ";" . $XAMZTARGET_HEADER . ","
. " Signature="
. $finalSignature;
return $authorizationValue;
}
/**
* Hashes the string using sha256, the standard AWS hash.
*
* #param $data a string to sign
* #return a string hash of $data
*/
public static function myHash($data) {
return hash("sha256",$data);
}
/**
* Builds the string that gets hashed and used in the authenication.
*
* #param $canonicalRequestHash The hash of the canonicalRequest
* #return The string to sign.
*/
public static function buildStringToSign($canonicalRequestHash){
$AWS_SHA256_ALGORITHM = Self::AWS_SHA256_ALGORITHM;
$TERMINATION_STRING = Self::TERMINATION_STRING;
$SERVICE_NAME = Self::SERVICE_NAME;
$awsSecretKey = Self::$awsSecretKey;
$regionName = Self::getRegion();
$dateTimeString = Self::getTimeStamp();
$dateString = Self::getDateString($dateTimeString);
$stringToSign = "$AWS_SHA256_ALGORITHM\n$dateTimeString\n$dateString/$regionName/$SERVICE_NAME/$TERMINATION_STRING\n$canonicalRequestHash";
return $stringToSign;
}
/**
* Gets the time stamp used to make the request. If not set by the client it is set to the current time on the first call to this function.
*
* #return The time stamp
*/
public static function getTimeStamp() {
global $timeStamp;
if(!isset($timeStamp)) {
//GMT time. Format is YYYYMMDDTHHmmssZ where T and Z are literals, YYYY is 4 digit year, MM is 2 digit month, DD is 2 digit day, HH is 2 digit hour (24 hour clock) mm is 2 digit minute, ss is 2 digit second.
$timeStamp = gmdate('Ymd\THis\Z');
}
return $timeStamp;
}
/**
* Get the format that we will make the request in. This tells the server how to parse the request.
* This value is retrieved from the client and can either be json or xml.
*
* #return The request format as to be passed to the AGCOD server.
*/
public static function getContentType() {
return "application/json"; //Request in JSON format
}
/**
* Makes the service call to the AGCOD server.
*
* #return The repsonse from the server (in XML or JSON format) with HTML character escaped.
*/
public static function invokeRequest($payload, $dateTimeString,$canonicalRequest, $serviceOperation) {
$KEY_QUALIFIER = self::KEY_QUALIFIER;
$ACCEPT_HEADER = self::ACCEPT_HEADER;
$CONTENT_HEADER = self::CONTENT_HEADER;
$HOST_HEADER = self::HOST_HEADER;
$XAMZDATE_HEADER = self::XAMZDATE_HEADER;
$XAMZTARGET_HEADER = self::XAMZTARGET_HEADER;
$AUTHORIZATION_HEADER = self::AUTHORIZATION_HEADER;
$canonicalRequestHash = Self::myHash($canonicalRequest);
$stringToSign = Self::buildStringToSign($canonicalRequestHash);
$authorizationValue = Self::buildAuthSignature($dateTimeString, $stringToSign);
$endpoint = Self::ENDPOINT;
$regionName = Self::getRegion();
$SERVICE_NAME = "AGCODService";
$serviceTarget = "com.amazonaws.agcod." . $SERVICE_NAME . "." . $serviceOperation;
$contentType = Self::getContentType();
$url = "https://" . Self::ENDPOINT . "/" . $serviceOperation;
// print_r($url);
// exit;
//Prepare to send the data to the server
$handle = curl_init($url);
//Yes, do POST not GET
curl_setopt($handle, CURLOPT_POST, true);
//This is header, not post fields
curl_setopt($handle, CURLOPT_HTTPHEADER , array(
"Content-Type:$contentType",
'Content-Length: ' . strlen($payload),
$AUTHORIZATION_HEADER. ":" . $authorizationValue,
$XAMZDATE_HEADER . ":" . $dateTimeString,
$XAMZTARGET_HEADER . ":" . $serviceTarget,
"host:" . Self::ENDPOINT,
$ACCEPT_HEADER . ":" . $contentType
));
curl_setopt($handle, CURLOPT_POSTFIELDS, $payload);
//Yes, don't print the result to the web page, just give it to us in a string.
curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
//Do the request
$result = curl_exec($handle);
if (empty($result)) {
// some kind of an error happened
die(curl_error($handle));
curl_close($handle); // close cURL handler
}
//Free the resource
curl_close($handle);
$signaturePos = strpos($authorizationValue, "Signature=");
if($signaturePos == FALSE || $signaturePos + 10 >= strlen($authorizationValue)) {
$signatureStr = "Malformed";
}
else {
$signatureStr = substr($authorizationValue, $signaturePos + 10);
}
print_r($result);
exit;
return $result;
}
/**
* Gets the region based on the server we connect too.
*
* #return The region.
*/
public static function getRegion() {
$endpoint = Self::ENDPOINT;
$regionName = "us-east-1";
if ($endpoint == "agcod-v2-eu.amazon.com" || $endpoint == "agcod-v2-eu-gamma.amazon.com") {
$regionName = "eu-west-1";
}
else if ($endpoint == "agcod-v2-fe.amazon.com" || $endpoint == "agcod-v2-fe-gamma.amazon.com") {
$regionName = "us-west-2";
}
return $regionName;
}
/**
* Performs a hmac hash using sha256, which is what AWS uses.
*
* #param $data The data to sign.
* #param $key The key to sign the data with.
* #param $raw true to provide a raw ascii string, false to use a hex string.
* #return the hash of $data
*/
public static function hmac($data, $key, $raw = true) {
return hash_hmac("sha256", $data, $key, $raw);
}
/**
* Gets the date for the request, which is either what the client passed in, or today if none was given.
*
* #return The date in YYYYMMDD format.
*/
public static function getDateString($dateTimeString) {
// $dateTimeString = Self::getTimeStamp();
return substr($dateTimeString, 0, 8);
}
/**
* Builds the derived key, which is used for authorizating the request.
*
* #param $rawOutput true to return an ascii string using raw byes, false to return a hex string
*/
public static function buildDerivedKey($dateString, $rawOutput = true) {
$KEY_QUALIFIER = Self::KEY_QUALIFIER;
$TERMINATION_STRING = Self::TERMINATION_STRING;
$SERVICE_NAME= Self::SERVICE_NAME;
// Get pasted AWS Secret Key from user input
$awsSecretKey = Self::$awsSecretKey;
// Append Key Qaulifier, "AWS4", to secret key per http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html
$signatureAWSKey = $KEY_QUALIFIER . $awsSecretKey;
$regionName = Self::getRegion();
// $dateString = Self::getDateString();
$kDate = Self::hmac($dateString, $signatureAWSKey);
$kRegion = Self::hmac($regionName, $kDate);
$kService = Self::hmac($SERVICE_NAME, $kRegion);
// Derived the Signing key (derivedKey aka kSigning)
$derivedKey = Self::hmac($TERMINATION_STRING, $kService, $rawOutput);
return $derivedKey;
}
}
Related
I am using opencart 3 my issue is that when customer close browser and open again customer is logout, i want to change this behavior, i need when customer once login he/she should not logout until he/she click logout button, even if he/she close the browser and open again he/she should remain login.
You have to modify 3 files in OpenCart to accomplish this.
/catalog/controller/account/login.php
During login process, you have to store customer ID and email in cookie. It worth to store them encrypted. Email is not enough, because you have to check that stored customer ID belongs to stored email.
public function index() {
[...]
if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validate()) {
// Unset guest
unset($this->session->data['guest']);
// store customer ID and email encrypted
$my_customer_id = $this->customer->getId();
$my_customer_id_crypted = $this->encrypt($my_customer_id, "your_key_for_customer_id_encryption");
$my_email = $this->request->post['email'];
$my_email_crypted = $this->encrypt($this->request->post['email'], "your_key_for_email_encryption");
setcookie("MyCustomerID", $my_customer_id_crypted , time() + (365 * 24 * 60 * 60) , "/");
setcookie("MyEmail", $my_email_crypted , time() + (365 * 24 * 60 * 60) , "/");
[...]
}
[...]
}
[...]
// https://www.phpcluster.com/simple-two-way-encryption-in-php/
// you can use other encryption if you want, just an example
protected function encrypt($plainText, $key) {
$secretKey = md5($key);
$iv = substr( hash( 'sha256', "aaaabbbbcccccddddeweee" ), 0, 16 );
$encryptedText = openssl_encrypt($plainText, 'AES-128-CBC', $secretKey, OPENSSL_RAW_DATA, $iv);
return base64_encode($encryptedText);
}
/catalog/controller/account/logout.php
During logout process, you have to delete customer ID and email cookies
public function index() {
if ($this->customer->isLogged()) {
$this->customer->logout();
// delete cookies
unset($_COOKIE['MyCustomerID']);
unset($_COOKIE['MyEmail']);
setcookie("MyCustomerID", "", 0, "/");
setcookie("MyEmail", "", 0, "/");
[...]
}
[...]
}
/catalog/controller/common/footer.php
In this file you can auto login customer if everything is OK and extend cookie lifetime, footer is used on every page load so it is a good way I mean
public function index() {
[...]
$data['scripts'] = $this->document->getScripts('footer');
$data['styles'] = $this->document->getStyles('footer');
if (isset($_COOKIE["MyCustomerID"]) && isset($_COOKIE["MyEmail"]) && $_COOKIE["MyCustomerID"] != '' && $_COOKIE["MyEmail"] != '') {
$my_customer_id_crypted = $_COOKIE["MyCustomerID"];
$my_customer_id = $this->decrypt($my_customer_id_crypted, "your_key_for_customer_id_encryption");
$my_email_crypted = $_COOKIE["MyEmail"];
$my_email = $this->decrypt($my_email_crypted, "your_key_for_email_encryption");
$config = new Config();
$config->load('default');
if ( $my_customer_id != "" && $my_email != "" && $my_customer_id == (int)$my_customer_id ) {
if ( !$this->customer->isLogged() ) {
if ( $my_customer_id == $this->getCustomerIdByEmailAddress( $my_email ) ) { // auto login, when customer ID belongs to this email address
$this->customer->login($my_email, "", true); // we use OpenCart override log in method
//$this->log->write('customer logged in automatically');
$this->load->model('account/address');
if ($this->config->get('config_tax_customer') == 'payment') {
$this->session->data['payment_address'] = $this->model_account_address->getAddress($this->customer->getAddressId());
}
if ($this->config->get('config_tax_customer') == 'shipping') {
$this->session->data['shipping_address'] = $this->model_account_address->getAddress($this->customer->getAddressId());
}
// extend cookies lifetime
setcookie("MyCustomerID", $my_customer_id_crypted , time() + (365 * 24 * 60 * 60) , "/");
setcookie("MyEmail", $my_email_crypted , time() + (365 * 24 * 60 * 60) , "/");
$this->response->redirect($_SERVER['REQUEST_URI']);
}
}
}
}
[...]
}
// https://www.phpcluster.com/simple-two-way-encryption-in-php/
// decrypt function for previous used encryption
protected function decrypt($encryptedText, $key) {
$key = md5($key);
$iv = substr( hash( 'sha256', "aaaabbbbcccccddddeweee" ), 0, 16 );
$decryptedText = openssl_decrypt(base64_decode($encryptedText), 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv);
return $decryptedText;
}
protected function getCustomerIdByEmailAddress($email) {
$sql_txt = "";
$sql_txt .= "SELECT customer_id";
$sql_txt .= " FROM ".DB_PREFIX."customer";
$sql_txt .= " WHERE LOWER(email) = '".$this->db->escape(utf8_strtolower($email))."'";
$customer_query = $this->db->query($sql_txt);
if ($customer_query->num_rows)
{
return $customer_query->row['customer_id'];
}
else
{
return -1;
}
}
You can refine this code if you want, currently I use this method to auto login customer
I created a Middleware wich should only redirect the user to an other Website (given in Request Url by the Parameter redirect)
class Middleware
{
public function __invoke($request, $response, $next)
{
// Call next middleware or app
$response = $next($request, $response);
$redirectUrl = //get redirect url
return $response->withStatus(200)->withHeader('Location', $redirectUrl);
}
}
I already testet this and the Redirect works fine. So I came to that Point to write Unit-Tests. I failed ... This was my attempt:
class MiddlewareTest extends \PHPUnit_Framework_TestCase
{
public $request = array(...); //inserted needed properties
public function testInvoke(String $url) {
$next = function () : bool
{
return true;
}; //empty function
$request['request']['scriptUri'] = "/parameterStuff&redirect=" . $url; //overwrite the Uri with provided Url
$redirect = new Middleware($request, array(), $next);
//just to test if result of response still empty
$iCount = count((array)$redirect);
$this->assertEquals(0, $iCount);
}
public function invokeProvider() : array
{
return array(
array('http://example.com')
);
}
}
This test is successful but ofc it shouldn't... The return of this function should be a valid response. I tested this in my Browser and echo the return. It has a value there and it's the correct response with the expected Header. The return Value I receive in my Unit-Test is an empty object.
I red the Slim Documentation about the response Object and it sais:
This method returns a copy of the Response object that has the new header value.
So I should definitely receive something from it. I also tried to return a copy of the response:
$copyresponse = response->withStatus(200)->withHeader('Location', $redirectUrl);
return $copyresponse;
This don't works as well. Any Idea what could cause my Problem and how to solve it?
(I want to test if the redirect url is set correctly in the response to ensure that the redirect would work)
You have to mock the request and check if the Location header is correctly set, its length is 1 and the status code is 200. I wrote some different middleware and I used this method.
class LocationTest extends \PHPUnit_Framework_TestCase
{
/**
* PSR7 request object.
*
* #var Psr\Http\Message\RequestInterface
*/
protected $request;
/**
* PSR7 response object.
*
* #var Psr\Http\Message\ResponseInterface
*/
protected $response;
protected $headers;
protected $serverParams;
protected $body;
/**
* Run before each test.
*/
public function setUp()
{
$uri = Uri::createFromString('https://example.com:443/foo/bar');
$this->headers = new Headers();
$this->headers->set('REMOTE_ADDR', '127.0.0.1');
$this->cookies = [];
$env = Environment::mock();
$this->serverParams = $env->all();
$this->body = new Body(fopen('php://temp', 'r+'));
$this->response = new Response();
$this->request = new Request('GET', $uri, $this->headers, $this->cookies, $this->serverParams, $this->body);
}
/**
* #dataProvider locationProvider
*/
public function testLocation($url)
{
$options = array(
'ip' => '192.*',
);
$mw = new RestrictRoute($options);
$next = function ($req, $res) {
return $res;
};
$uri = Uri::createFromString('https://example.com:443/foo/bar?redirect=' . $url);
$this->request = new Request('GET', $uri, $this->headers, $this->cookies, $this->serverParams, $this->body);
$redirect = $mw($this->request, $this->response, $next);
$location = $redirect->getHeader('Location');
$this->assertEquals($redirect->getStatusCode(), 200);
$this->assertEquals(count($location), 1);
$this->assertEquals($location[0], $url);
}
public function locationProvider(){
return [
['http://www.google.it'],
['http://stackoverflow.com/'],
];
}
}
I need to upload files into a forum in moodle through Web Services, I have been searching and I found that exist a WS called core_files_upload(). I looked at the code of this WS and the context level of this WS is only (block, course, coursecat, system, user or module), the context forum not exist and this WS is deprecated.
I also found that in Moodle 3.0 there are 2 new WS related to forums, they are mod_forum_add_discussion and mod_forum_add_discussion_post but I also looked at the code and they don't allow upload.
Then I decided that the best solution would be to create a new one, but I don't know much about uploading files in Moodle and can't find much information. I'm a little bit familiar with the structure of the WS in Moodle because I have made 2 very simple WS but I don't know how to do this.
If anyone knows how to do it or have any examples or documentation that may be useful, it would be helpful.
Thanks in advance.
You can look up for the file
/webservice/upload.php
this file is for uploading files
$params = array('token'=>$token, 'filepath' => $filepath
, 'filearea'=>'draft') + $_FILES ;
$serverurl = <domainname>. '/webservice/upload.php'. '?token=' . $token;
$ch = curl_init();
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_VERBOSE, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible;)");
curl_setopt($ch, CURLOPT_URL, $serverurl);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
$response = curl_exec($ch);
or this can code can help you, that will create a file in moodle data and record in file table from temp file.
$fs = get_file_storage();
$target_path = <target-path>;
$context = get_context_instance(CONTEXT_USER, $userID, MUST_EXIST);
$cm = get_coursemodule_from_instance('forum', $forum->id);
$modcontext = context_module::instance($cm->id, MUST_EXIST);
//saving in draft
$item = rand();
$file_record = array('contextid'=>$context->id, 'component'=>'user', 'filearea'=>'draft', 'itemid'=>$item,
'filepath'=>"/", 'filename'=><filename>, 'userid'=>$userID);
$fs->create_file_from_pathname($file_record, $CFG->dirroot."/".$target_path.<filename>);
// saving for post
$file_record = array('contextid'=>$modcontext->id, 'component'=>'mod_forum', 'filearea'=>'post', 'itemid'=>$post->id,
'filepath'=>"/", 'filename'=><filename>, 'userid'=>$userID);
$fs->create_file_from_pathname($file_record, $CFG->dirroot."/".$target_path.$imagefileEnc);
At the request of #insightful i I'm going to publish my code, I hope this helps somebody
First the externallib.php of the WS
externallib.php
require_once($CFG->libdir . "/externallib.php");
require_once($CFG->libdir . "/filelib.php");
class local_upload_tfg_external extends external_api {
/**
* Returns description of method parameters
* #return external_function_parameters
*/
public static function upload_tfg_parameters() {
return new external_function_parameters(
array('community_key' => new external_value(PARAM_TEXT, 'Comunidad a la que se subira el TFG o TFM.', VALUE_REQUIRED, '', NULL_NOT_ALLOWED),
'username' => new external_value(PARAM_TEXT, 'Usuario que sube el archivo.', VALUE_REQUIRED, '', NULL_NOT_ALLOWED),
'folder_name' => new external_value(PARAM_TEXT, 'Directorio donde se subira el archivo.', VALUE_REQUIRED, '', NULL_NOT_ALLOWED))
);
}
public static function upload_tfg($community_key, $username, $folder_name) {
global $CFG, $DB, $USER;
require_once($CFG->dirroot . "/mod/forum/lib.php");
$params = self::validate_parameters(self::upload_tfg_parameters(),
array(
'community_key' => $community_key,
'username' => $username,
'folder_name' => $folder_name
));
//Context validation
//OPTIONAL but in most web service it should present
$contextoUser = get_context_instance(CONTEXT_USER, $USER->id);
self::validate_context($contextoUser);
$fs = get_file_storage();
$warnings = array();
//We get the course to which the file must be uploaded
$sql = "SELECT id FROM mdl_course WHERE idnumber = ?";
$result = $DB->get_records_sql($sql, array($community_key));
foreach ( $result as $n ) {
$courseid = $n->id;
}
if( is_null($courseid)){
throw new moodle_exception('no_course','No existe el curso');
}
//e get the forum to upload the file
$sql = "SELECT f.id FROM mdl_forum f , mdl_course c WHERE c.idnumber = ? AND c.id = f.course AND f.name = ?";
$result = $DB->get_records_sql($sql, array($community_key, $folder_name));
foreach ( $result as $n ) {
$forumid = $n->id;
}
if( is_null($forumid)){
throw new moodle_exception('no_forum','No existe el foro');
}
//We get the user who wants to upload the file
$sql = "SELECT id FROM mdl_user WHERE username = ?";
$result = $DB->get_records_sql($sql, array($username));
foreach ( $result as $n ) {
$userid = $n->id;
}
if( is_null($userid)){
throw new moodle_exception('no_user','No existe el usuario');
}
//We check if the user belongs to the course
$contextCourse = context_course::instance($courseid);
$enrolled = is_enrolled($contextCourse, $userid, '', true);
if( !$enrolled){
throw new moodle_exception('user_no_enrolled','El usuario no está en el curso');
}
//Files received
$numFiles = count($_FILES);
if($numFiles > 0){
// Request and permission validation.
$forum = $DB->get_record('forum', array('id' => $forumid), '*', MUST_EXIST);
list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
$context = context_module::instance($cm->id);
self::validate_context($context);
// Normalize group.
if (!groups_get_activity_groupmode($cm)) {
// Groups not supported, force to -1.
$groupid = -1;
} else {
// Check if we receive the default or and empty value for groupid,
// in this case, get the group for the user in the activity.
if ($groupid === -1 ) {
$groupid = groups_get_activity_group($cm);
} else{
$groupid = -1;
}
}
if (!forum_user_can_post_discussion($forum, $groupid, -1, $cm, $context)) {
throw new moodle_exception('cannotcreatediscussion', 'forum');
}
$thresholdwarning = forum_check_throttling($forum, $cm);
forum_check_blocking_threshold($thresholdwarning);
foreach ($_FILES as $fieldname=>$uploaded_file) {
// check upload errors
if (!empty($_FILES[$fieldname]['error'])) {
switch ($_FILES[$fieldname]['error']) {
case UPLOAD_ERR_INI_SIZE:
throw new moodle_exception('upload_error_ini_size', 'repository_upload');
break;
case UPLOAD_ERR_FORM_SIZE:
throw new moodle_exception('upload_error_form_size', 'repository_upload');
break;
case UPLOAD_ERR_PARTIAL:
throw new moodle_exception('upload_error_partial', 'repository_upload');
break;
case UPLOAD_ERR_NO_FILE:
throw new moodle_exception('upload_error_no_file', 'repository_upload');
break;
case UPLOAD_ERR_NO_TMP_DIR:
throw new moodle_exception('upload_error_no_tmp_dir', 'repository_upload');
break;
case UPLOAD_ERR_CANT_WRITE:
throw new moodle_exception('upload_error_cant_write', 'repository_upload');
break;
case UPLOAD_ERR_EXTENSION:
throw new moodle_exception('upload_error_extension', 'repository_upload');
break;
default:
throw new moodle_exception('nofile');
}
}
/*$file = new stdClass();
$file->filename = clean_param($_FILES[$fieldname]['name'], PARAM_FILE);
// check system maxbytes setting
if (($_FILES[$fieldname]['size'] > get_max_upload_file_size($CFG->maxbytes))) {
// oversize file will be ignored, error added to array to notify web service client
$file->errortype = 'fileoversized';
$file->error = get_string('maxbytes', 'error');
} else {
$file->filepath = $_FILES[$fieldname]['tmp_name'];
// calculate total size of upload
//$totalsize += $_FILES[$fieldname]['size'];
}
$files[] = $file;*/
$filename = $_FILES[$fieldname]['name'];
$filepath = $_FILES[$fieldname]['tmp_name'];
// Create the discussion.
$discussion = new stdClass();
$discussion->course = $course->id;
$discussion->forum = $forum->id;
$discussion->message = "<p>".$filename."</p>";
$discussion->messageformat = FORMAT_HTML; // Force formatting for now.
$discussion->messagetrust = trusttext_trusted($context);
$discussion->itemid = 0;
$discussion->groupid = $groupid;
$discussion->mailnow = 0;
$discussion->subject = $filename;
$discussion->name = $discussion->subject;
$discussion->timestart = 0;
$discussion->timeend = 0;
if ($discussionid = forum_add_discussion($discussion)) {
$discussion->id = $discussionid;
//We update the user of the discursion to the one of the user received
$sql = "UPDATE mdl_forum_discussions SET userid= ? WHERE id = ?";
$DB->execute($sql, array($userid, $discussionid));
/*
// Trigger events and completion.
$params = array(
'context' => $context,
'objectid' => $discussion->id,
'other' => array(
'forumid' => $forum->id,
)
);
$event = \mod_forum\event\discussion_created::create($params);
$event->add_record_snapshot('forum_discussions', $discussion);
$event->trigger();
$completion = new completion_info($course);
if ($completion->is_enabled($cm) && ($forum->completiondiscussions || $forum->completionposts)) {
$completion->update_state($cm, COMPLETION_COMPLETE);
}
$settings = new stdClass();
$settings->discussionsubscribe = false; //discussionsubscribe (bool); subscribe to the discussion?, default to true
forum_post_subscription($settings, $forum, $discussion
//se guardara el fichero
// Get any existing file size limits.
//$maxareabytes = FILE_AREA_MAX_BYTES_UNLIMITED;
//$maxupload = get_user_max_upload_file_size($context, $CFG->maxbytes);
// Check the size of this upload.
//if ($maxupload !== USER_CAN_IGNORE_FILE_SIZE_LIMITS && $totalsize > $maxupload) {
// throw new file_exception('userquotalimit');
//}
*/
//We get the course to which the file must be uploaded
$sql = "SELECT id FROM mdl_forum_posts WHERE discussion = ? ORDER BY id ASC LIMIT 1";
$result = $DB->get_records_sql($sql, array($discussionid));
foreach ( $result as $n ) {
$postid = $n->id;
}
if( is_null($postid)){
throw new moodle_exception('no_post','No existe el post');
}
//We update the post to put the user that they send us and indicate that they have a file
$sql = "UPDATE mdl_forum_posts SET userid= ?, attachment= 1 WHERE id = ?";
$DB->execute($sql, array($userid, $postid));
$user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
$file_record = new stdClass;
$file_record->component = 'mod_forum';
$file_record->contextid = $context->id;
$file_record->userid = $userid;
$file_record->filearea = 'attachment';
$file_record->filename = $filename;
$file_record->filepath = '/';
$file_record->itemid = $postid;
$file_record->license = $CFG->sitedefaultlicense;
$file_record->author = fullname($user);
$file_record->source = $filename;
//Check if the file already exist
$existingfile = $fs->file_exists($file_record->contextid, $file_record->component, $file_record->filearea,
$file_record->itemid, $file_record->filepath, $file_record->filename);
if ($existingfile) {
throw new file_exception('filenameexist');
} else {
$stored_file = $fs->create_file_from_pathname($file_record, $filepath);
}
} else {
throw new moodle_exception('couldnotadd', 'forum');
}
}
}else{
throw new moodle_exception('nofile');
}
$result = array();
$result['discussionid'] = $discussionid;
$result['warnings'] = $warnings;
return $result;
}
/**
* Returns description of method result value
* #return external_multiple_structure
*/
public static function upload_tfg_returns() {
return new external_single_structure(
array(
'discussionid' => new external_value(PARAM_INT, 'New Discussion ID'),
'warnings' => new external_warnings()
)
);
}
}
And an example to call this WS maybe this:
$target_url = "http://localhost/pruebaMoodle/webservice/rest/server.php";
//$target_url = "http://localhost/upload.php";
$username = "raula"; //User uploading the file (must belong to the course)
$community_key = "c15c033a43976gPT"; //custom identifier of course
$folder_name = "Course 2016"; //Name of the forum to which the file will be uploaded
$fname = 'Prueba.txt'; //Name of the file or path where it is located but is in the same directory as this file
$token = "28b054f102a75a0eab2a0053fd930d62"; //token created for ws
$file_name_with_full_path = realpath($fname);
$cfile = new CURLFile($file_name_with_full_path);
$post = array (
'username' => $username,
'community_key' => $community_key,
'folder_name' => $folder_name,
'wstoken' => $token,
'wsfunction' => 'upload_tfg_tfm',
'moodlewsrestformat' => 'json',
'upload_file' => $cfile
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible;)");
curl_setopt($ch, CURLOPT_HTTPHEADER,array('User-Agent: Opera/9.80 (Windows NT 6.2; Win64; x64) Presto/2.12.388 Version/12.15','Referer: http://localhost','Content-Type: multipart/form-data'));
curl_setopt($ch, CURLOPT_FRESH_CONNECT, 1);
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
$result = curl_exec ($ch);
if ($result === FALSE) {
echo "Error sending " . $fname . " " . curl_error($ch) . "\n";
curl_close ($ch);
}else{
curl_close ($ch);
echo "<br><br>Result: " . $result. "<br>";
}
I hope this can serve you
I am trying to build a contact form in Joomla 2.5. After filling all the fields and by submitting the form I am getting an error like this:
The following from address failed: myname#gmail.com.
The e-mail I use is valid.
My SMTP settings are as follows:
Mail Settings
Mailer: SMTP Server
Mail from: myname#gmail.com
From Name: joomla
Sendmail Path: /usr/sbin/sendmail
SMTP Authentication: Yes
SMTP Security: SSL
SMTP Port: 465
SMTP Username: myname#gmail.com
SMTP Password: ******
SMTP Host: smtp.gmail.com
This the code for sending email
<?php
/**
* #package Joomla.Site
* #subpackage com_mailto
* #copyright Copyright (C) 2005 - 2012 Open Source Matters, Inc. All rights reserved.
* #license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
/**
* #package Joomla.Site
* #subpackage com_mailto
*/
class MailtoController extends JControllerLegacy
{
/**
* Show the form so that the user can send the link to someone
*
* #access public
* #since 1.5*/
function mailto()
{
$session = JFactory::getSession();
$session->set('com_mailto.formtime', time());
JRequest::setVar('view', 'mailto');
$this->display();
}
/**
* Send the message and display a notice
*
* #access public
* #since 1.5
*/
function send()
{
// Check for request forgeries
JSession::checkToken() or jexit(JText::_('JINVALID_TOKEN'));
$app = JFactory::getApplication();
$session = JFactory::getSession();
$db = JFactory::getDbo();
$timeout = $session->get('com_mailto.formtime', 0);
if ($timeout == 0 || time() - $timeout < 20) {
JError::raiseNotice(500, JText:: _ ('COM_MAILTO_EMAIL_NOT_SENT'));
return $this->mailto();
}
$SiteName = $app->getCfg('sitename');
$MailFrom = $app->getCfg('mailfrom');
$FromName = $app->getCfg('fromname');
$link = MailtoHelper::validateHash(JRequest::getCMD('link', '', 'post'));
// Verify that this is a local link
if (!$link || !JURI::isInternal($link)) {
//Non-local url...
JError::raiseNotice(500, JText:: _ ('COM_MAILTO_EMAIL_NOT_SENT'));
return $this->mailto();
}
// An array of email headers we do not want to allow as input
$headers = array ( 'Content-Type:',
'MIME-Version:',
'Content-Transfer-Encoding:',
'bcc:',
'cc:');
// An array of the input fields to scan for injected headers
$fields = array(
'mailto',
'sender',
'from',
'subject',
);
/*
* Here is the meat and potatoes of the header injection test. We
* iterate over the array of form input and check for header strings.
* If we find one, send an unauthorized header and die.
*/
foreach ($fields as $field)
{
foreach ($headers as $header)
{
if (strpos($_POST[$field], $header) !== false)
{
JError::raiseError(403, '');
}
}
}
/*
* Free up memory
*/
unset ($headers, $fields);
$email = JRequest::getString('mailto', '', 'post');
$sender = JRequest::getString('sender', '', 'post');
$from = JRequest::getString('from', '', 'post');
$subject_default = JText::sprintf('COM_MAILTO_SENT_BY', $sender);
$subject = JRequest::getString('subject', $subject_default, 'post');
// Check for a valid to address
$error = false;
if (! $email || ! JMailHelper::isEmailAddress($email))
{
$error = JText::sprintf('COM_MAILTO_EMAIL_INVALID', $email);
JError::raiseWarning(0, $error);
}
// Check for a valid from address
if (! $from || ! JMailHelper::isEmailAddress($from))
{
$error = JText::sprintf('COM_MAILTO_EMAIL_INVALID', $from);
JError::raiseWarning(0, $error);
}
if ($error)
{
return $this->mailto();
}
// Build the message to send
$msg = JText :: _('COM_MAILTO_EMAIL_MSG');
$body = sprintf($msg, $SiteName, $sender, $from, $link);
// Clean the email data
$subject = JMailHelper::cleanSubject($subject);
$body = JMailHelper::cleanBody($body);
$sender = JMailHelper::cleanAddress($sender);
// Send the email
if (JFactory::getMailer()->sendMail($from, $sender, $email, $subject, $body) !== true)
{
JError::raiseNotice(500, JText:: _ ('COM_MAILTO_EMAIL_NOT_SENT'));
return $this->mailto();
}
JRequest::setVar('view', 'sent');
$this->display();
}
}
Just check by changing mailer in global configuration at administration page
On Linux (SELINUX) apply:
setsebool -P httpd_can_network_connect=1
setsebool -P httpd_can_sendmail=1
reboot
On Windows:
In the php.ini
Un-comment
;extension=php_openssl.dll
Facebook requires a public url to POST to in order to deauthorize your app for a user.
I am behind a firewall and do not wish to test this on my production environment, therefore has anyone managed to mimic this POST in a c# (or other) integration test?
It would involve building up a form with a field signed_request and encrypted to base64 and the posting it to my local web app to handle...
Here is how I do it in PHP:
<?php
function generateSignedRequest($data, $secret) {
$payload = base64UrlEncode(json_encode($data));
$signature = base64UrlEncode(hash_hmac('sha256', $payload, $secret, $raw = true));
return "$signature.$payload";
}
function base64UrlEncode($input) {
$input = rtrim(base64_encode($input), '=');
return strtr($input, '+/', '-_');
}
$app_secret = "YOUR_APP_SECRET_HERE";
$data = array(
'algorithm' => 'HMAC-SHA256',
'issued_at' => 1332162396,
'user' => array(
'country' => 'bg',
'locale' => 'en_US'
),
'user_id' => '100003387159594'
);
$signed_request = generateSignedRequest($data, $app_secret);
?>
<form action="deauthorize_callback.php" method="post">
<input type="text" name="signed_request" value="<?php echo $signed_request?>" size="255" />
<input type="submit" value="Send Request" />
</form>
If you have PHP in your local environment you can try submiting the form to your backend and see if it works. You just have to replace your app secret and user id in the data array.
Hope this helps, it worked for me so far.
I managed to find something similar for building the payload in Ruby which I've converted, here it is with an integration test:
This class builds the string that you will POST as request.form["signed_request"]
public class SignedRequest
{
public static string SignedRequestFor(string applicationSecret, string json)
{
// encode payload
var payload = ToUrlBase64String(json);
// create sig
var Hmac = SignWithHmac(UTF8Encoding.UTF8.GetBytes(payload), UTF8Encoding.UTF8.GetBytes(applicationSecret));
var HmacBase64 = ToUrlBase64String(Hmac);
// concat and return
return HmacBase64.Concatenate(".", payload);
}
private static byte[] SignWithHmac(byte[] dataToSign, byte[] keyBody)
{
using (var hmacAlgorithm = new HMACSHA256(keyBody))
{
hmacAlgorithm.ComputeHash(dataToSign);
return hmacAlgorithm.Hash;
}
}
private static string ToUrlBase64String(string input)
{
byte[] encodedBytes = Encoding.UTF8.GetBytes(input);
return ToUrlBase64String(encodedBytes);
}
private static string ToUrlBase64String(byte[] input)
{
return Convert.ToBase64String(input).Replace("=", String.Empty)
.Replace('+', '-')
.Replace('/', '_');
}
}
Small value object used by the test:
public class RequestEnvelope
{
public long user_id { get; set; }
public string algorithm = "HMAC-SHA256";
}
The test:
[Test]
public void CanDeauthorizeUser()
{
var appSecret = ConfigurationManager.AppSettings["NewsletterSubscriptionFacebookAppSecret"];
var envelope = new RequestEnvelope { user_id = 123456 };
var signedRequest = SignedRequest.SignedRequestFor(appSecret, JsonConvert.SerializeObject(envelope));
var postData = "signed_request=" + signedRequest;
byte[] postBytes = Encoding.UTF8.GetBytes(postData);
var request = WebRequest.Create(ConfigurationManager.AppSettings["AppWebUrl"] + "/SubscriptionHandler.ashx?a=deactivate");
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = postBytes.Length;
using (var newStream = request.GetRequestStream())
{
// Send the data.
newStream.Write(postBytes, 0, postBytes.Length);
newStream.Close();
}
// Get the response.
using (var response = request.GetResponse())
{
var status = ((HttpWebResponse) response).StatusDescription;
// Get the stream containing all content returned by the requested server.
using (var dataStream = response.GetResponseStream())
{
// Open the stream using a StreamReader for easy access.
using (var reader = new StreamReader(dataStream))
{
// Read the content fully up to the end.
string responseFromServer = reader.ReadToEnd();
Console.WriteLine(responseFromServer);
Console.WriteLine("status: " + status);
}
}
}
}
Snippet from the Handler that I post to:
string signedRequestValue = HttpContext.Current.Request.Form["signed_request"];
var items = SignedRequestParser.Parse(ConfigurationManager.AppSettings["NewsletterSubscriptionFacebookAppSecret"], signedRequestValue);
And the code for parsing that (taken/modified from the codeplex facebook API):
public class SignedRequestParser
{
/// <summary>
/// Parses the signed request string.
/// </summary>
/// <param name="appSecret">The unique app hash known the provider and the developer</param>
/// <param name="signedRequestValue">The encoded signed request value.</param>
/// <returns>The valid signed request.</returns>
public static Dictionary<string, string> Parse(string appSecret, string signedRequestValue)
{
Check.Require(!String.IsNullOrEmpty(signedRequestValue));
Check.Require(signedRequestValue.Contains("."), "Invalid Signed Request");
string[] parts = signedRequestValue.Split('.');
var encodedValue = parts[0];
if (String.IsNullOrEmpty(encodedValue))
{
throw new InvalidOperationException("Invalid Signed Request");
}
var sig = Base64UrlDecode(encodedValue);
var payload = parts[1];
using (var cryto = new System.Security.Cryptography.HMACSHA256(Encoding.UTF8.GetBytes(appSecret)))
{
var hash = Convert.ToBase64String(cryto.ComputeHash(Encoding.UTF8.GetBytes(payload)));
var hashDecoded = Base64UrlDecode(hash);
if (hashDecoded != sig)
{
throw new InvalidOperationException("Invalid Signed Request.");
}
}
var payloadJson = Encoding.UTF8.GetString(Convert.FromBase64String(Base64UrlDecode(payload)));
var data = JsonConvert.DeserializeObject<Dictionary<string, string>>(payloadJson);
return data;
}
/// <summary>
/// Converts the base 64 url encoded string to standard base 64 encoding.
/// </summary>
/// <param name="encodedValue">The encoded value.</param>
/// <returns>The base 64 string.</returns>
private static string Base64UrlDecode(string encodedValue)
{
Check.Require(!String.IsNullOrEmpty(encodedValue));
encodedValue = encodedValue.Replace('+', '-').Replace('/', '_').Trim();
int pad = encodedValue.Length % 4;
if (pad > 0)
{
pad = 4 - pad;
}
encodedValue = encodedValue.PadRight(encodedValue.Length + pad, '=');
return encodedValue;
}
}