3

I'm having a hard time fetching orders from Magento REST API when I use its GET filters like http://localhost/magento/api/rest/orders/?filter[1][attribute]=entity_id&filter[1][gt]=70&page=1&limit=100

It is giving a "error":[{"code":401,"message":"oauth_problem=signature_invalid"}]

When I try hitting the same API endpoint using a REST Client like Postman, I'm getting back the desired results JSON.

I suspect the square brackets in the filter query might be causing a problem in generating a Oauth signature. All the endpoints without GET filters are working fine. I'm using the Request node module to make the GET request with the oauth headers.

Is there any fix to avoid the signaturre invalid error?

4 Answers 4

2

The problem was within the Request node module I was using to generate a OAuth signature.It didn't factor for square brackets in the URL. I modified the code in the module for including square brackets. Changing the OAuth signature generation method fixed it for me

Sign up to request clarification or add additional context in comments.

2 Comments

Out of interest, what change did you make to encode the square brackets? Ie, how were the square brackets encoded in the url used to generate the signature? I'm having a similar problem with square brackets with another site that uses OAuth.
Do you have your coding solution we can use?
1

For anyone who is wanting the answer to this here is what worked for me:

const axios = require('axios');
const crypto = require('crypto');

exports.handler = async (event) => {
  let base_uri = 'https://YOUR_URL.com'; (ANYTHING UP TO THE /PATH)
  const consumer_key = '';
  const token_key = '';
  const consumer_secret = '';
  const token_secret = '';
==> CHANGE THE QUERY PARAMS TO WHATEVER YOU HAVE <==
  const queryParameters = {
    'searchCriteria[filterGroups][0][filters][0][field]': 'sku',
    'searchCriteria[filterGroups][0][filters][0][condition_type]': 'eq',
    'searchCriteria[filterGroups][0][filters][0][value]': 'aaaaaaaaa',
  };
  const timestamp = Math.floor(Date.now() / 1000);
  const nonce = await generateNonce();

  ==> ADD IN YOUR PATH, IT WILL BE APPENDED TO THE BASE URL <==
  const path = 'products';

  base_uri = `${base_uri}/${path}`;

  const reqsign256 = hmacsign256('GET', base_uri, { ...queryParameters, oauth_consumer_key: consumer_key, oauth_nonce: nonce, oauth_signature_method: 'HMAC-SHA256', oauth_timestamp: timestamp, oauth_token: token_key, oauth_version: '1.0' }, consumer_secret, token_secret);

  const config = {
    method: 'get',
    url: base_uri,
    headers: {
      Authorization: `OAuth oauth_consumer_key="${consumer_key}",oauth_token="${token_key}",oauth_signature_method="HMAC-SHA256",oauth_timestamp="${timestamp}",oauth_nonce="${nonce}",oauth_version="1.0",oauth_signature="${reqsign256}"`
    },
    params: queryParameters,
  };

  let response = {};

  return await axios(config)
    .then(async (response) => {
      response = {
        statusCode: 200,
        body: JSON.stringify(response.data),
      };
      return response;
    })
    .catch((error) => {
      response = {
        statusCode: 200,
        body: JSON.stringify(error.message),
      };
      return response;
    });
};

/**
 *
 * @returns A random string of 11 characters
 */
function generateNonce() {
  let nonce = '';
  const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

  for (let i = 0; i < 11; i++) {
    nonce += possible.charAt(Math.floor(Math.random() * possible.length));
  }

  return nonce;
}

/**
 *
 * @param key
 * @param body
 * @param algorithm
 * @description This is generating the signature using the imported crypto package.
 * @returns The generated signature;
 */
function sha(key, body, algorithm) {
  return crypto.createHmac(algorithm, key).update(body).digest('base64');
}

/**
 *
 * @param str
 * @description The rfc3986 function takes a string as input and returns its encoded representation as per the rules specified in RFC 3986
 * for URI (Uniform Resource Identifier) component encoding. It does this by first using encodeURIComponent to encode the string,
 * which replaces certain characters with their percent-encoded representations. It then replaces characters such as !, *, (, ), and '
 * with their respective percent-encoded representations, which are specified in RFC 3986 as being reserved characters that must be percent-encoded.
 * @returns returns the encoded str value
 */
function rfc3986(str) {
  return encodeURIComponent(str).replace(/!/g, '%21').replace(/\*/g, '%2A').replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/'/g, '%27');
}

/**
 *
 * @param obj
 * @description The map function takes an object as input and returns an array of its key-value pairs.
 * It does this by iterating through the properties of the object using a for...in loop, and for each property, it checks its value.
 * If the value is an array, it pushes an array with the property key and each value of the array into the result array.
 * If the value is an object, it pushes an array with a string representation of the property key concatenated with the property name in square brackets
 * and the property value into the result array.
 * If the value is neither an array nor an object, it pushes an array with the property key and value into the result array.
 * Finally, it returns the result array.
 * @returns arr
 */
function map(obj) {
  let key,
    val,
    arr = [];
  for (key in obj) {
    val = obj[key];
    if (Array.isArray(val)) for (let i = 0; i < val.length; i++) arr.push([key, val[i]]);
    else if (typeof val === 'object') for (let prop in val) arr.push([key + '[' + prop + ']', val[prop]]);
    else arr.push([key, val]);
  }
  return arr;
}

/**
 *
 * @param a
 * @param b
 * @description Used to sort the oauth paramters into ascending order -- this is required for oauth1 to work.
 * @returns the comparison result
 */
function compare(a, b) {
  return a > b ? 1 : a < b ? -1 : 0;
}

/**
 *
 * @param httpMethod
 * @param base_uri
 * @param params
 * @description
 * 1.  First, the name and value of each parameter are encoded
 * 2.  The parameters are sorted by name, using ascending byte value ordering. If two or more parameters share the same name, they are sorted by their value.
 * 3.  The name of each parameter is concatenated to its corresponding value using an "=" character (ASCII code 61) as a separator, even if the value is empty.
 * for example in this case it is assigning oauth_nonce = 'foo' or oauth_signature_method = 'HMAC-SHA256' etc
 * 4.  The sorted name/value pairs are concatenated together into single string by using an "&" character (ASCII code 38) as separator.
 * The final output will be something like this:
 * GET&https%3A%2F%2Fstaging2.ospreylondon.com%2Frest%2FV1%2Fproducts&oauth_consumer_key%3Dxxx%26oauth_nonce%3xxxoauth_signature_method%3DHMAC-SHA256%26oauth_timestamp%3Dxxx%26oauth_token%3xxx%26oauth_version%3D1.0%26 ==> There will also be any url paramaters added at the end.
 * @returns base url
 */
function generateBase(httpMethod, base_uri, params) {
  let normalized = map(params)
    // step 1
    .map(function (p) {
      return [rfc3986(p[0]), rfc3986(p[1] || '')];
    })
    // step 2
    .sort(function (a, b) {
      return compare(a[0], b[0]) || compare(a[1], b[1]);
    })
    //step 3
    .map(function (p) {
      return p.join('=');
    })
    //step 4
    .join('&');

  let base = [rfc3986(httpMethod ? httpMethod.toUpperCase() : 'GET'), rfc3986(base_uri), rfc3986(normalized)].join('&');

  return base;
}

/**
 *
 * @param httpMethod
 * @param base_uri
 * @param params
 * @param consumer_secret
 * @param token_secret
 * @description This takes the paramaters passed in, creates a base uri, creates a key (consumer secret and token secret with & between them)
 * @returns this then returns the result of the sha method ==> this is the signature used in the request.
 */
function hmacsign256(httpMethod, base_uri, params, consumer_secret, token_secret) {
  let base = generateBase(httpMethod, base_uri, params);
  let key = [consumer_secret || '', token_secret || ''].map(rfc3986).join('&');

  return sha(key, base, 'sha256');
}

Comments

0

Since it took me a little while to figure out how to do this I figured I would pass on what I learned. My goal was to make a single request to the Magento’s REST API to return orders with specific order statuses. Looking through the GET filters documentation page wasn’t really helpful until I saw the very last line. Here is the request that I came up with that worked:

http://magentohost.com/api/rest/orders?filter[1][attribute]=status&filter[1][in][1]=pending&filter[1][in][2]=processing The above request will get you a list of all orders with a status of “pending” or “processing”.

Ref: http://magentoforce.com/2014/08/magento-rest-api-multiple-in-get-filters/

Comments

-1

Here is the detailed answer... http://magentoforce.com/2014/08/magento-rest-api-multiple-in-get-filters/

1 Comment

It is always best to include the answer and give the reference link along with it. Now that the answer at the link above is no longer available, we are left to wonder what was it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.