Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Close #19695 : Add Basic and Bearer Authorization to the CPP Pistache generator #19978

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ public CppPistacheServerCodegen() {
private void setupSupportingFiles() {
supportingFiles.clear();
supportingFiles.add(new SupportingFile("api-base-header.mustache", "api", "ApiBase.h"));
supportingFiles.add(new SupportingFile("api-base-source.mustache", "api", "ApiBase.cpp"));
supportingFiles.add(new SupportingFile("helpers-header.mustache", "model", modelNamePrefix + "Helpers.h"));
supportingFiles.add(new SupportingFile("helpers-source.mustache", "model", modelNamePrefix + "Helpers.cpp"));
supportingFiles.add(new SupportingFile("main-api-server.mustache", "", modelNamePrefix + "main-api-server.cpp"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,47 @@
namespace {{apiNamespace}}
{


{{#authMethods}}{{#isBasicBasic}}
typedef struct
{
std::string user;
std::string password;
std::unique_ptr<void, std::function<void(void*)>> userdata;
} HttpBasicCredentials;

typedef std::function<bool(HttpBasicCredentials &)> BasicCredentialsAuthenticator;
{{/isBasicBasic}}

{{#isBasicBearer}}
typedef struct
{
std::string token;
std::unique_ptr<void, std::function<void(void*)>> userdata;
} HttpBearerToken;

typedef std::function<bool(HttpBearerToken &)> BearerTokenAuthenticator;
{{/isBasicBearer}}
{{/authMethods}}



class ApiBase {
public:
explicit ApiBase(const std::shared_ptr<Pistache::Rest::Router>& rtr) : router(rtr) {};
explicit ApiBase(const std::shared_ptr<Pistache::Rest::Router>& rtr);
virtual ~ApiBase() = default;
virtual void init() = 0;

{{#authMethods}}{{#isBasicBasic}}void setBasicCredentialsAuthenticator( const BasicCredentialsAuthenticator &newBasicCredentialsAuthenticator);{{/isBasicBasic}}{{/authMethods}}
{{#authMethods}}{{#isBasicBearer}}void setBearerTokenAuthenticator( const BearerTokenAuthenticator &newbearerTokenAuthenticator);{{/isBasicBearer}}{{/authMethods}}


protected:
const std::shared_ptr<Pistache::Rest::Router> router;
{{#authMethods}}{{#isBasicBasic}}std::optional<BasicCredentialsAuthenticator> basicCredentialsAuthenticator;{{/isBasicBasic}}{{/authMethods}}
{{#authMethods}}{{#isBasicBearer}}std::optional<BearerTokenAuthenticator> bearerTokenAuthenticator;{{/isBasicBearer}}{{/authMethods}}


};

} // namespace {{apiNamespace}}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{{>licenseInfo}}
#include "ApiBase.h"

namespace {{apiNamespace}}
{

ApiBase::ApiBase(const std::shared_ptr<Pistache::Rest::Router>& rtr) : router(rtr)
{
}

{{#authMethods}}{{#isBasicBasic}}
void ApiBase::setBasicCredentialsAuthenticator( const BasicCredentialsAuthenticator &newBasicCredentialsAuthenticator)
{
basicCredentialsAuthenticator = newBasicCredentialsAuthenticator;
}
{{/isBasicBasic}}
{{#isBasicBearer}}
void ApiBase::setBearerTokenAuthenticator( const BearerTokenAuthenticator &newbearerTokenAuthenticator)
{
bearerTokenAuthenticator = newbearerTokenAuthenticator;
}
{{/isBasicBearer}}{{/authMethods}}

} // Namespace {{apiNamespace}}
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ private:
{{#allParams}}
/// <param name="{{paramName}}">{{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}</param>
{{/allParams}}
virtual void {{operationIdSnakeCase}}({{#allParams}}const {{#isModel}}{{^isOptional}}{{modelNamespace}}::{{/isOptional}}{{/isModel}}{{{dataType}}} &{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}Pistache::Http::ResponseWriter &response) = 0;
virtual void {{operationIdSnakeCase}}({{#authMethods}}{{#isBasicBasic}}const HttpBasicCredentials &credentials, {{/isBasicBasic}}{{#isBasicBearer}}const HttpBearerToken &accessToken, {{/isBasicBearer}}{{/authMethods}} {{#allParams}}const {{#isModel}}{{^isOptional}}{{modelNamespace}}::{{/isOptional}}{{/isModel}}{{{dataType}}} &{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}Pistache::Http::ResponseWriter &response) = 0;
{{/vendorExtensions.x-codegen-pistache-is-parsing-supported}}
{{^vendorExtensions.x-codegen-pistache-is-parsing-supported}}
virtual void {{operationIdSnakeCase}}(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter &response) = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public:

{{#operation}}
{{#vendorExtensions.x-codegen-pistache-is-parsing-supported}}
void {{operationIdSnakeCase}}({{#allParams}}const {{{dataType}}} &{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}Pistache::Http::ResponseWriter &response);
void {{operationIdSnakeCase}}({{#authMethods}}{{#isBasicBasic}}const HttpBasicCredentials &credentials,{{/isBasicBasic}}{{#isBasicBearer}}const HttpBearerToken &bearerToken, {{/isBasicBearer}}{{/authMethods}}{{#allParams}}const {{{dataType}}} &{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}Pistache::Http::ResponseWriter &response);
{{/vendorExtensions.x-codegen-pistache-is-parsing-supported}}
{{^vendorExtensions.x-codegen-pistache-is-parsing-supported}}
void {{operationIdSnakeCase}}(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter &response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,129 @@ using namespace {{modelNamespace}};{{/hasModelImport}}
{{classname}}Impl::{{classname}}Impl(const std::shared_ptr<Pistache::Rest::Router>& rtr)
: {{classname}}(rtr)
{
{{#authMethods}}{{#isBasicBasic}}/*

Http Basic Auth
===============

Do this in the individual classes in the constructor

this->setBasicCredentialsAuthenticator(
[](HttpBasicCredentials &credentials)->bool
{
if(credentials.user == "foo" && credentials.password == "bar")
{

const int userIdOfFoo = 66;
credentials.userdata = std::unique_ptr<void, std::function<void(void*)>> (
reinterpret_cast<void*>(new int(userIdOfFoo)),
[&](void* ptr)
{
int * value = reinterpret_cast<int*>(ptr);
delete value;
}
);
return true;
}
return false;
}
);

or in main:

for (auto api : apiImpls) {
api->init();

api->setBasicCredentialsAuthenticator(
[]( HttpBasicCredentials &credentials)->bool
{
if(credentials.user == "foo" && credentials.password == "bar")
{

const int userIdOfFoo = 66;
credentials.userdata = std::unique_ptr<void, std::function<void(void*)>> (
reinterpret_cast<void*>(new int(userIdOfFoo)),
[&](void* ptr)
{
int * value = reinterpret_cast<int*>(ptr);
delete value;
}
);
return true;
}
return false;
}
);
}

or a mix.

Until you do either, protected resources will result in a 401.
*/{{/isBasicBasic}}
{{#isBasicBearer}}/*

Http Basic Bearer
===============

Do this in the individual classes in the constructor

this->setBearerTokenAuthenticator(
[](HttpBearerToken &token)->bool
{
if(token.token == "Zm9vYmFyCg==")
{
const int userIdOfFoo = 99;
token.userdata = std::unique_ptr<void,std::function<void(void*)>>(
reinterpret_cast<void*>(new int(userIdOfFoo)),
[&](void* ptr)
{
int * value = reinterpret_cast<int*>(ptr);
delete value;
}
);
return true;
}
return false;
}
);

or in main:

for (auto api : apiImpls) {
api->init();

api->setBearerTokenAuthenticator(
[](HttpBearerToken &token)->bool
{
if(token.token == "Zm9vYmFyCg==")
{
const int userIdOfFoo = 99;
token.userdata = std::unique_ptr<void,std::function<void(void*)>>(
reinterpret_cast<void*>(new int(userIdOfFoo)),
[&](void* ptr)
{
int * value = reinterpret_cast<int*>(ptr);
delete value;
}
);
return true;
}
return false;
}
);
}

or a mix.

Until you do either, protected resources will result in a 401.
*/{{/isBasicBearer}}
{{/authMethods}}

}

{{#operation}}
{{#vendorExtensions.x-codegen-pistache-is-parsing-supported}}
void {{classname}}Impl::{{operationIdSnakeCase}}({{#allParams}}const {{{dataType}}} &{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}Pistache::Http::ResponseWriter &response) {
void {{classname}}Impl::{{operationIdSnakeCase}}({{#authMethods}}{{#isBasicBasic}}const HttpBasicCredentials &credentials, {{/isBasicBasic}}{{#isBasicBearer}}const HttpBearerToken &bearerToken, {{/isBasicBearer}}{{/authMethods}}{{#allParams}}const {{{dataType}}} &{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}Pistache::Http::ResponseWriter &response) {
response.send(Pistache::Http::Code::Ok, "Do some magic\n");
}
{{/vendorExtensions.x-codegen-pistache-is-parsing-supported}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ const std::string {{classname}}::base = "{{basePathWithoutHost}}";

{{classname}}::{{classname}}(const std::shared_ptr<Pistache::Rest::Router>& rtr)
: ApiBase(rtr)
{
}
{}

void {{classname}}::init() {
setupRoutes();
Expand Down Expand Up @@ -119,9 +118,78 @@ void {{classname}}::{{operationIdSnakeCase}}_handler(const Pistache::Rest::Reque
}

try {

{{/bodyParam}}
{{/hasBodyParam}}
this->{{operationIdSnakeCase}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}response);

{{#authMethods}}
#ifndef HTTP_BASIC_AUTH_DEFINED
#define HTTP_BASIC_AUTH_DEFINED 0
#endif
#ifndef HTTP_BEARER_AUTH_DEFINED
#define HTTP_BEARER_AUTH_DEFINED 0
#endif
{{/authMethods}}



{{#authMethods}}{{#isBasicBasic}}
#undef HTTP_BASIC_AUTH_DEFINED
#define HTTP_BASIC_AUTH_DEFINED 1

auto basicAuthHeader = request.headers().tryGet<Pistache::Http::Header::Authorization>();

if( (!basicAuthHeader) || (basicAuthHeader->getMethod() != Pistache::Http::Header::Authorization::Method::Basic))
{
response.send(Pistache::Http::Code::Unauthorized, "");
return;
}
HttpBasicCredentials credentials{basicAuthHeader->getBasicUser(), basicAuthHeader->getBasicPassword()};
if( ! this->basicCredentialsAuthenticator.has_value())
{
response.send(Pistache::Http::Code::Unauthorized, "");
return;
}

if( ! this->basicCredentialsAuthenticator.value()(credentials))
{
response.send(Pistache::Http::Code::Unauthorized, "");
return;
}

{{/isBasicBasic}}{{/authMethods}}

{{#authMethods}}{{#isBasicBearer}}
#undef HTTP_BEARER_AUTH_DEFINED
#define HTTP_BEARER_AUTH_DEFINED 1
auto bearerAuthHeader = request.headers().tryGet<Pistache::Http::Header::Authorization>();

if( (!bearerAuthHeader) || (bearerAuthHeader->getMethod() != Pistache::Http::Header::Authorization::Method::Bearer))
{
response.send(Pistache::Http::Code::Unauthorized, "");
return;
}
std::string completeHeaderValue = bearerAuthHeader->value();
const std::string tokenAsString(completeHeaderValue.begin() + std::string("Bearer ").length(), completeHeaderValue.end());

HttpBearerToken bearerToken{tokenAsString};
if( ! this->bearerTokenAuthenticator.has_value())
{
response.send(Pistache::Http::Code::Unauthorized, "");
return;
}

if( ! this->bearerTokenAuthenticator.value()(bearerToken))
{
response.send(Pistache::Http::Code::Unauthorized, "");
return;
}



{{/isBasicBearer}}{{/authMethods}}

this->{{operationIdSnakeCase}}({{#authMethods}}{{#isBasicBasic}}credentials,{{/isBasicBasic}}{{#isBasicBearer}}bearerToken,{{/isBasicBearer}}{{/authMethods}}{{#allParams}}{{paramName}}, {{/allParams}}response);
{{/vendorExtensions.x-codegen-pistache-is-parsing-supported}}
{{^vendorExtensions.x-codegen-pistache-is-parsing-supported}}
try {
Expand All @@ -139,6 +207,22 @@ void {{classname}}::{{operationIdSnakeCase}}_handler(const Pistache::Rest::Reque
response.send(Pistache::Http::Code::Internal_Server_Error, e.what());
}

#define REST_PATH "{{{vendorExtensions.x-codegen-pistache-path}}}" {{! this is nessecary, because the path does not exist in the authMethods scope.}}
{{#authMethods}}
{{! This static assert may be rendered more that once, so the compilation will fail even harder!}}
static_assert(HTTP_BASIC_AUTH_DEFINED + HTTP_BEARER_AUTH_DEFINED < 2, "Path '" REST_PATH "' has more than one security scheme specified, and the Pistache server generator does not support that." );
{{/authMethods}}
#undef REST_PATH

{{#authMethods}}
#ifdef HTTP_BEARER_AUTH_DEFINED
#undef HTTP_BEARER_AUTH_DEFINED
#endif
#ifdef HTTP_BASIC_AUTH_DEFINED
#undef HTTP_BASIC_AUTH_DEFINED
#endif
{{/authMethods}}

}
{{/operation}}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
CMakeLists.txt
README.md
api/ApiBase.cpp
api/ApiBase.h
api/PetApi.cpp
api/PetApi.h
Expand Down
23 changes: 23 additions & 0 deletions samples/server/petstore/cpp-pistache-everything/api/ApiBase.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* OpenAPI Petstore
* This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
#include "ApiBase.h"

namespace org::openapitools::server::api
{

ApiBase::ApiBase(const std::shared_ptr<Pistache::Rest::Router>& rtr) : router(rtr)
{
}



} // Namespace org::openapitools::server::api
Loading
Loading