Postman: Basic авторизация через скрипт

в 9:15, , рубрики: api, api testing, javascript, Postman, postman api, postman тесты
Первое изображение для статьи=) привлечение аудитории

Первое изображение для статьи =) привлечение аудитории

Всем привет, меня зовут Алексей Нихаенко и я дата инженер. Это мой первый пост на Хабре и я хочу поведать вам свое более близкое знакомство с инструментом Postman.

О чем пойдет речь?

  • Что такое базовая авторизация и способы использования внутри Postman

  • Задача, которую я преследовал и зачем понадобилась автоматизация (Pre-Request Script)

  • Простые примеры скрипта с Basic Authorization в Pre-Request Script

  • Строим дерево вариантов поведения скрипта

  • Итоговый скрипт, заключение

1. Что такое базовая авторизация и способы использования внутри Postman

Базовая авторизация (Basic Auth) - авторизация, использованная с помощью имени пользователя (username ) и пароля (password ). Отправляется на сервер заголовком (header ) шаблонно вот так:

'Authorization': f'Basic {base64(username:password)}'

Где:

  • Authorization - наименование заголовка (ключ)

  • f"Basic {base64(username:password)}" - закодированное в base64 пара имя_пользователя:пароль

Например, если имя пользователя у нас UserNameRandom а пароль AnyPassword, то вот так по итогу будет выглядеть заголовок:

'Authorization': 'Basic VXNlck5hbWVSYW5kb206QW55UGFzc3dvcmQ='

Закодировать (Encode) в Base64 можете сами онлайн ССЫЛКА

Декодировать (Decode) из Base64 в строку можете сами онлайн ССЫЛКА

Авторизоваться базово внутри Postman можно двумя способами:

  • Через внутренний функционал по авторизации (тогда заголовок он сам подставляет закодированный)

Базовая авторизация через встроенный механизм Postman

Базовая авторизация через встроенный механизм Postman
  • Либо подставляя закодированный в Base64 значение заголовка Authorization

    Base64 авторизационные данные в заголовке

    Base64 авторизационные данные в заголовке

Задача, зачем нужна автоматизация

Есть Jira. Некоторые запросы должны получать задачи (GET-запросы), некоторые - переводить статус задачи в другой статус (в моем случае из "Новый" в "Отменен"). Я со своей автоматизацией хотел покрыть несколько задач:

  • Хочу попробовать отправить запрос с правильными логинами-паролями

  • Отправить запрос с неправильными логином-паролем

  • Отправить запрос без заголовка Authorization

  • Максимально защитить логины-пароли, чтобы, делясь коллекцией, я "не палил" их

  • Сохранить результаты запросов в Example максимально секурно (см. пункт выше) и чтобы было понятно - использовался ли заголовок авторизации и какой

В связи с вышеизложенным мне хотелось как можно меньше ручных манипуляций. В голову пришла фича Postman, с помощью которой можно до отправки запроса устанавливать заголовки (и вообще много чего еще) в автоматическом режиме с помощью скриптинга на JavaScript - Pre-Request Script (простите за тавтологию, постараюсь больше не использовать)

Простые примеры скрипта с Basic Authorization

Прежде чем перейти к скриптингу, нужно подумать - а где мы будем хранить логины-пароли? На ум приходят три вещи:

  • Переменные внутри коллекции (не безопасно)

Переменные внутри коллекции

Переменные внутри коллекции
  • Переменные внутри скрипта (еще хуже, сложно для поддержки еще к тому же)

Переменные внутри скрипта

Переменные внутри скрипта
  • Переменные внутри окружения Postman (то, что нужно)

Переменные внутри окружения

Переменные внутри окружения

Переменные внутри окружения безопасны за счет не только того, что они скрыты визуально (если вдруг попадут в скрин), но и при "поделиться коллекцией" вы не передаете Credentials (учетные данные, логин-пароль)

Следующий вопрос - а куда помещаем скрипт? Я отвечу сразу - на уровне коллекции, чтобы при создании новых запросов заголовок автоматически применялся

Еще важный момент - внутрь коллекции я создал переменную base_url со значением "https://jira.com". В запросах могут фигурировать {{base_url}} - это Postman берет переменную из коллекции и применяет в качестве URL запроса

Итак, приступаем к кодингу. Начнем с самого простого, пойдем потом на усложнение.

Представим, что у нас переменные внутри скрипта, никаких заголовков не устанавливалось на уровне запросов, тогда нам нужно добавить заголовок. Обратите, пожалуйста, внимание на различие add и upsert (в будущем, на усложнении понадобится)

// Зашитые внутрь скрипта креденшелы
const login = "UserNameRandom";
const password = "AnyPassword";

// Кодирование в base64
const encodedCredentials = Buffer.from(`${login}:${password}`).toString('base64');
// Установка заголовка Authorization
// add используется для добавления заголовка
// upsert используется для обновления (хотя должен для вставки и обновления)
pm.request.headers.add({
    key: "Authorization",
    value: `Basic ${encodedCredentials}`
});
// Выводим опционально значение в консоль Postman
console.log("Заголовок Authorization добавлен:", `Basic ${encodedCredentials}`);

Результат мы можем увидеть на следующем экране

Результат выполнения скрипта по добавлению заголовка Authorization

Результат выполнения скрипта по добавлению заголовка Authorization

Ниже - скрипты по взятию переменных из коллекции и из окружения

var login = "UserNameRandom";
var password = "AnyPassword";
console.log(`Зашитые внутри скрипта логин: ${login} и пароль ${password}`);
login = pm.collectionVariables.get("login");
password = pm.collectionVariables.get("password");
console.log(`Переменные коллекции, логин: ${login} и пароль ${password}`);
login = pm.environment.get("login")
password = pm.environment.get("password");
console.log(`Переменные окружения, логин: ${login} и пароль ${password}`);

Строим дерево вариантов поведения скрипта

Теперь, когда какой-никакой, а скрипт есть, нужно его улучшить, учитывая несколько вопросов нюансов:

Вопрос

Варианты поведения

Переменные login или password отсутствуют

Запрос отправляется без изменений в заголовках

Прерываем работу и скрипта и запроса

Заголовок Authorization отсутствует

Добавляем

Не добавляем, запрос отправляется

Не добавляем, запрос НЕ отправляется

Заголовок Authorization присутствует

Изменяем его в любом случае

Изменяем его по условию

Не изменяем никогда, запрос отправляется

Не изменяем никогда, запрос НЕ отправляется

Заголовок Authorization присутствует, но он отключен

Нужно учитывать только включенные

Заголовков Authorization несколько и все они активные

Выдавать ошибку, что заголовков несколько

Брать какой-то 1 заголовок по условию и см. выше - изменять ли его?

Заголовка Authorization нет, однако меня в систему пускает. Почему?

Авторизация и не нужна

Сохранились куки, их нужно чистить перед запросом

Тут, наверное, стоит пояснить - а что значит включенные (активные) отключенные заголовки? Объясняю - бывает так, что в одном запросе ты накидаешь несколько заголовков с одинаковым ключом, но разным значением, чтоб в какой-то момент подключать нужный. Вот как это выглядит визуально:

Признак активности заголовка в Postman

Признак активности заголовка в Postman

Прежде чем перейти к тому, как ответил я, давайте разбираться со всем в коде:

  • Хм, как получить значение login-password из переменных окружения?

// Получение переменных из окружения
const login = pm.environment.get("login");
const password = pm.environment.get("password");
  • Как определить - есть ли и логин и пароль? А как в скрипте написать сообщение об ошибке не прерывая работу? А прерывая?

if (!login || !password) {
  // Определяем переменную для вывода в консоль
  var reason_error = "Не найдены переменные login или password"
  // Выводим лог в консоль. Не прерывает работу ни скрипта, ни запроса
  console.error(reason_error);
  // Прерываем работу, выбрасывая ошибку
  throw new Error(reason_error)
  }
  • Хм, а как посмотреть отключенные заголовки Authorization?

// Наименование (ключ) заголовка
var header_name = "Authorization"
// Смотрим на отключенные заголовки header_name
const disabledHeaders = pm.request.headers.filter(header => header.disabled && header.key.toLowerCase() === header_name.toLowerCase());
// Считаем количество отключённых заголовков header_name
const disabledCount = disabledHeaders.length;
if (disabledCount >= 1) {
  console.log(`Количество отключенных заголовков ${header_name} = ${disabledCount}`)
}
else {
  console.log("Нет отключенных заголовков")
}
  • Хм, а как посмотреть включенные заголовки Authorization? Да то же самое, только вместо header.disabled пишем !header.disabled

  • А как посмотреть вообще все заголовки?

const authHeader = pm.request.headers.get("Authorization");
  • А как чистить куки? А вот это интересный вопрос, потому что:

    • Код выглядит просто:

      // Очищаем cookies перед выполнением всех шагов
      const var_cookies = pm.cookies.jar();
      var_cookies.clear(pm.request.url, function (error) {
        // error - <Error>
      });

    • Но перед этим нужно добавить сайт в Domains Allowlist (без http/https)

Шаг 1. Проваливаемся в Cookies

Шаг 1. Проваливаемся в Cookies
Шаг 2. Проваливаемся в Domains Allowlist

Шаг 2. Проваливаемся в Domains Allowlist
Шаг 3. Добавляем сайт в Domains Allowlist

Шаг 3. Добавляем сайт в Domains Allowlist

Отлично, основные моменты кода вспомнили, теперь давайте определяться с последовательностью действий с учетом вопросов нюансов.

  • Перед запросом очищаем все куки

  • Берем активные заголовки Authorization без учета регистра

  • Если их больше 1 - выдаем ошибку, прерываем работу скрипта и запроса

  • Если их 0 - значит так и задумано пользователем, просто отправляем запрос дальше

  • Если их 1 - обновляю только в случае, если в значении встречаются <*calc*>, где * - любой символ и не чувствителен к регистру

    • <Calculated By Script> - заменится значение, т.к. встречаются все символы

    • <CALC> - тоже заменится. Напомню, что ищем не чувствительное к регистру

    • <Any> - Не заменится

    • Symbol<calc> - не заменится, т.к. символ "<" не идет первым

    • <calc>symbol - не заменится, т.к. символ ">" не идет последним

  • Идем получать переменные окружения. Если чего-то нет - ошибка, прерываем все.

Таким образом мы:

  • прерываем возможность несколько активных Authorization засунуть в запрос

  • даем возможность без Authorization отправить запрос

  • даем возможность со своим Authorization отправить запрос (он же бывает не только Basic, правильно?)

  • когда нам действительно нужны логин и пароль - проверяем их наличие и выдаем ошибку, если чего-то нет

Итоговый скрипт, заключение

Что-то мне кажется, что за меня все скажет итоговый скрипт, который я использую

// ====================================================
// ====================================================
// ТУТ НИЧЕГО НЕ ТРОГАТЬ!!!
// ====================================================
// ====================================================

// Функция для установки заголовка Authorization
function setAuthorizationHeader() {
    // Получение переменных из коллекции
    const login = pm.environment.get("login");
    const password = pm.environment.get("password");

    if (!login || !password) {
        var reason_error = "Не найдены переменные login или password"
        // Логируем с прерыванием
        throw new Error(reason_error)
        // Логируем с продолжением работы скрипта
        // console.error(reason_error);
        return;
    }

    // Кодирование в base64 (используем Buffer для совместимости с Node.js)
    const encodedCredentials = Buffer.from(`${login}:${password}`).toString('base64');
    
    // Установка заголовка Authorization
    pm.request.headers.upsert({
        key: "Authorization",
        value: `Basic ${encodedCredentials}`
    });
    // console.log("Заголовок Authorization добавлен:", `Basic ${encodedCredentials}`);
}

// Очищаем cookies перед выполнением всех шагов
const var_cookies = pm.cookies.jar();
var_cookies.clear(pm.request.url, function (error) {
  // error - <Error>
});

// Наименование (ключ) заголовка
var header_name = "Authorization"

// Фильтрация отключенных заголовков header_name
const disabledHeaders = pm.request.headers.filter(header => header.disabled && header.key.toLowerCase() === header_name.toLowerCase());
// Считаем количество отключенных header_name
const disabledCount = disabledHeaders.length;
// Фильтрация включенных заголовков header_name
const enabledHeaders = pm.request.headers.filter(header => !header.disabled && header.key.toLowerCase() === header_name.toLowerCase());
// Подсчёт количества включенных header_name
const enabledCount = enabledHeaders.length;
console.log("Количество отключённых заголовков Authorization:", disabledCount);
console.log(`Количество включенных заголовков Authorization: ${enabledCount}`);



// ====================================================
// ====================================================
// РЕДАКТИРОВАТЬ ТОЛЬКО БЛОК НИЖЕ
// ====================================================
// ====================================================

// Если количество отключенных заголовком Authorization НЕ РАВНО включенным 
//if (disabledCount !== enabledCount) {
if (enabledCount > 1) {
    throw new Error(`Заголовков ${header_name} больше 1. Заголовок должен быть таким 1 в запросе`)
} else if (enabledCount === 0) {
    console.log("Запрос будет отправлен без заголовка Authorization")
} else {
    // Создаем регулярное выражение для поиска "calc" без учёта регистра
    const regex = /<.*calc.*>/i;
    // Проверяем, есть ли вхождение в переменной первого элемента (и единственного) enabledHeaders
    if (regex.test(enabledHeaders[0].value)) {
        // console.log('Вхождение "<*calc*>" найдено в значении заголовка.');
        console.log(`Заголовок ${header_name} будет изменен согласно скрипту`);
        // Установка заголовка
        setAuthorizationHeader();
    } else {
        console.log("Активный заголовок Authorization:",enabledHeaders[0].value);
    }
}

И вот несколько скриншотов работы Postman:

Захардкоженный заголовок не изменяется

Захардкоженный заголовок не изменяется
Запрос не отправился, т.к. 2 активных заголовка Authorization

Запрос не отправился, т.к. 2 активных заголовка Authorization
Отправка запроса без Authorization

Отправка запроса без Authorization
Результат работы скрипта. Заголовок нужный найден и заменен

Результат работы скрипта. Заголовок нужный найден и заменен

И, конечно, я добился того, ради чего все это затевалось, а именно я просто сохранил результат отправки запроса без боязни случайно "спалить" свой логин и пароль

Результат порадовал=)

Результат порадовал =)

Прошу вас, критикуйте, предлагайте, очень буду рад слышать мнение всех вас, золотых моих, умных, чтоб стать и самому лучше и сообществу помочь :-)

Автор: kneaded

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js