Unverified Commit 53c08440 authored by Kip's avatar Kip Committed by GitHub

Implemented support for Authorization headers using basic method... (#725)

* {src/common/http_header.cc,include/pistache/http_header.h}: Implemented support for Authorization headers using basic method...
include/pistache/base64.{cc,h}: Added utility classes and helper functions for base 64 encoding and decoding...
tests/headers_test.cc: Added unit test authorization_basic_test...
version.txt: Bumped versioning metadata...

* Reformatted last commit with clang-format(1) per Dennis' request. Looks awful.
parent 7bb01321
/*
Copyright (C) 2019-2020, Kip Warner.
Released under the terms of Apache License 2.0.
*/
// Multiple include protection...
#ifndef _BASE_64_H_
#define _BASE_64_H_
// Includes...
// Build environment configuration...
#include <pistache/config.h>
// Standard C++ / POSIX system headers...
#include <cstddef>
#include <filesystem>
#include <string>
#include <vector>
// A class for performing decoding to raw bytes from base 64 encoding...
class Base64Decoder {
// Public methods...
public:
// Constructor...
explicit Base64Decoder(const std::string &Base64EncodedString)
: m_Base64EncodedString(Base64EncodedString) {}
// Calculate length of decoded raw bytes from that would be generated if
// the base 64 encoded input buffer was decoded. This is not a static
// method because we need to examine the string...
std::vector<std::byte>::size_type CalculateDecodedSize() const;
// Decode base 64 encoding into raw bytes...
const std::vector<std::byte> &Decode();
// Get raw decoded data...
const std::vector<std::byte> &GetRawDecodedData() const noexcept {
return m_DecodedData;
}
// Protected methods...
protected:
// Convert an octet character to corresponding sextet, provided it can
// safely be represented as such. Otherwise return 0xff...
std::byte DecodeCharacter(const unsigned char Character) const;
// Protected attributes...
protected:
// Base 64 encoded string to decode...
const std::string &m_Base64EncodedString;
// Decoded raw data...
std::vector<std::byte> m_DecodedData;
};
// A class for performing base 64 encoding from raw bytes...
class Base64Encoder {
// Public methods...
public:
// Construct encoder to encode from a raw input buffer...
explicit Base64Encoder(const std::vector<std::byte> &InputBuffer)
: m_InputBuffer(InputBuffer) {}
// Calculate length of base 64 string that would need to be generated
// for raw data of a given length...
static std::string::size_type CalculateEncodedSize(
const std::vector<std::byte>::size_type DecodedSize) noexcept;
// Encode raw data input buffer to base 64...
const std::string &Encode() noexcept;
// Encode a string into base 64 format...
static std::string EncodeString(const std::string &StringInput);
// Get the encoded data...
const std::string &GetBase64EncodedString() const noexcept {
return m_Base64EncodedString;
}
// Protected methods...
protected:
// Encode single binary byte to 6-bit base 64 character...
unsigned char EncodeByte(const std::byte Byte) const;
// Protected attributes...
protected:
// Raw bytes to encode to base 64 string...
const std::vector<std::byte> &m_InputBuffer;
// Base64 encoded string...
std::string m_Base64EncodedString;
};
// Multiple include protection...
#endif
......@@ -326,11 +326,27 @@ class Authorization : public Header {
public:
NAME("Authorization");
enum class Method { Basic, Bearer, Unknown };
Authorization() : value_("NONE") {}
explicit Authorization(std::string &&val) : value_(std::move(val)) {}
explicit Authorization(const std::string &val) : value_(val) {}
// What type of authorization method was used?
Method getMethod() const noexcept;
// Check if a particular authorization method was used...
template <Method M> bool hasMethod() const noexcept { return hasMethod<M>(); }
// Get decoded user ID and password if basic method was used...
std::string getBasicUser() const;
std::string getBasicPassword() const;
// Set encoded user ID and password for basic method...
void setBasicUserPassword(const std::string &User,
const std::string &Password);
void parse(const std::string &data) override;
void write(std::ostream &os) const override;
......@@ -340,6 +356,12 @@ private:
std::string value_;
};
template <>
bool Authorization::hasMethod<Authorization::Method::Basic>() const noexcept;
template <>
bool Authorization::hasMethod<Authorization::Method::Bearer>() const noexcept;
class ContentType : public Header {
public:
NAME("Content-Type")
......
This diff is collapsed.
......@@ -4,6 +4,7 @@
Implementation of common HTTP headers described by the RFC
*/
#include <pistache/base64.h>
#include <pistache/common.h>
#include <pistache/config.h>
#include <pistache/http.h>
......@@ -283,6 +284,127 @@ void ContentLength::parse(const std::string &data) {
void ContentLength::write(std::ostream &os) const { os << value_; }
// What type of authorization method was used?
Authorization::Method Authorization::getMethod() const noexcept {
// Basic...
if (hasMethod<Method::Basic>())
return Method::Basic;
// Bearer...
else if (hasMethod<Method::Bearer>())
return Method::Bearer;
// Unknown...
else
return Method::Unknown;
}
// Authorization is basic method...
template <>
bool Authorization::hasMethod<Authorization::Method::Basic>() const noexcept {
// Method should begin with "Basic: "
if (value().rfind("Basic ", 0) == std::string::npos)
return false;
// Verify value is long enough to contain basic method's credentials...
if (value().length() <= std::string("Basic ").length())
return false;
// Looks good...
return true;
}
// Authorization is bearer method...
template <>
bool Authorization::hasMethod<Authorization::Method::Bearer>() const noexcept {
// Method should begin with "Bearer: "
if (value().rfind("Bearer ", 0) == std::string::npos)
return false;
// Verify value is long enough to contain basic method's credentials...
if (value().length() <= std::string("Bearer ").length())
return false;
// Looks good...
return true;
}
// Get decoded user ID if basic method was used...
std::string Authorization::getBasicUser() const {
// Verify basic authorization method was used...
if (!hasMethod<Authorization::Method::Basic>())
throw std::runtime_error("Authorization header does not use Basic method.");
// Extract encoded credentials...
const std::string EncodedCredentials(
value_.begin() + std::string("Basic ").length(), value_.end());
// Decode them...
Base64Decoder Decoder(EncodedCredentials);
const std::vector<std::byte> &BinaryDecodedCredentials = Decoder.Decode();
// Transform to string...
std::string DecodedCredentials;
for (std::byte CurrentByte : BinaryDecodedCredentials)
DecodedCredentials.push_back(static_cast<char>(CurrentByte));
// Find user ID and password delimiter...
const auto Delimiter = DecodedCredentials.find_first_of(':');
// None detected. Assume this is a malformed header...
if (Delimiter == std::string::npos)
return std::string();
// Extract and return just the user ID...
return std::string(DecodedCredentials.begin(),
DecodedCredentials.begin() + Delimiter);
}
// Get decoded password if basic method was used...
std::string Authorization::getBasicPassword() const {
// Verify basic authorization method was used...
if (!hasMethod<Authorization::Method::Basic>())
throw std::runtime_error("Authorization header does not use Basic method.");
// Extract encoded credentials...
const std::string EncodedCredentials(
value_.begin() + std::string("Basic ").length(), value_.end());
// Decode them...
Base64Decoder Decoder(EncodedCredentials);
const std::vector<std::byte> &BinaryDecodedCredentials = Decoder.Decode();
// Transform to string...
std::string DecodedCredentials;
for (std::byte CurrentByte : BinaryDecodedCredentials)
DecodedCredentials.push_back(static_cast<char>(CurrentByte));
// Find user ID and password delimiter...
const auto Delimiter = DecodedCredentials.find_first_of(':');
// None detected. Assume this is a malformed header...
if (Delimiter == std::string::npos)
return std::string();
// Extract and return just the password...
return std::string(DecodedCredentials.begin() + Delimiter + 1,
DecodedCredentials.end());
}
// Set encoded user ID and password for basic method...
void Authorization::setBasicUserPassword(const std::string &User,
const std::string &Password) {
// Verify user ID does not contain a colon...
if (User.find_first_of(':') != std::string::npos)
throw std::runtime_error("User ID cannot contain a colon.");
// Format credentials string...
const std::string Credentials = User + std::string(":") + Password;
// Encode credentials...
value_ = std::string("Basic ") + Base64Encoder::EncodeString(Credentials);
}
void Authorization::parse(const std::string &data) {
try {
value_ = data;
......
......@@ -271,7 +271,43 @@ TEST(headers_test, content_length) {
ASSERT_TRUE(cl.value() == 3495U);
}
TEST(headers_test, authorization_test) {
// Verify authorization header with basic method works correctly...
TEST(headers_test, authorization_basic_test) {
Pistache::Http::Header::Authorization au;
std::ostringstream oss;
// Sample basic method authorization header for credentials
// Aladdin:OpenSesame base 64 encoded...
const std::string BasicEncodedValue = "Basic QWxhZGRpbjpPcGVuU2VzYW1l";
// Try parsing the raw basic authorization value...
au.parse(BasicEncodedValue);
// Verify what went in is what came out...
au.write(oss);
ASSERT_TRUE(BasicEncodedValue == oss.str());
oss = std::ostringstream();
// Verify authorization header recognizes it is basic method and no other...
ASSERT_TRUE(
au.hasMethod<Pistache::Http::Header::Authorization::Method::Basic>());
ASSERT_FALSE(
au.hasMethod<Pistache::Http::Header::Authorization::Method::Bearer>());
// Set credentials from decoded user and password...
au.setBasicUserPassword("Aladdin", "OpenSesame");
// Verify it encoded correctly...
au.write(oss);
ASSERT_TRUE(BasicEncodedValue == oss.str());
oss = std::ostringstream();
// Verify it decoded correctly...
ASSERT_TRUE(au.getBasicUser() == "Aladdin");
ASSERT_TRUE(au.getBasicPassword() == "OpenSesame");
}
TEST(headers_test, authorization_bearer_test) {
Pistache::Http::Header::Authorization au;
std::ostringstream oss;
au.parse("Bearer "
......@@ -281,6 +317,11 @@ TEST(headers_test, authorization_test) {
"d0131JxqX4xSZLlO5xMRrCPBgn_00OxKJ9CQdnpjpuzblNQd2-A");
au.write(oss);
ASSERT_TRUE(
au.hasMethod<Pistache::Http::Header::Authorization::Method::Bearer>());
ASSERT_FALSE(
au.hasMethod<Pistache::Http::Header::Authorization::Method::Basic>());
ASSERT_TRUE(
"Bearer "
"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXUyJ9."
......
VERSION_MAJOR 0
VERSION_MINOR 0
VERSION_PATCH 002
VERSION_GIT_DATE 20200117
VERSION_GIT_DATE 20200301
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment