Commit d04a2bb0 authored by François Grisez's avatar François Grisez

ExternalAuthentication: add a substitution variable for accessing any header value of the request

This commit also changes the remote URI syntax. Substitution variables are now
enclosed by brace brackets instead of being started by '$' character.
parent deb2e0d4
Pipeline #8045 passed with stages
in 38 minutes and 41 seconds
......@@ -45,6 +45,7 @@ public:
mPriv->as_plugin = reinterpret_cast<auth_splugin_t *>(this);
mPriv->as_callback = responseCb;
}
AuthStatus(const AuthStatus &other) = delete;
virtual ~AuthStatus() {su_home_deinit(&mHome);}
bool allow() const {return mPriv->as_allow;}
......@@ -129,4 +130,4 @@ private:
ResponseCb mResponseCb;
};
}
\ No newline at end of file
}
......@@ -18,17 +18,18 @@
#pragma once
#include <sofia-sip/tport.h>
#include <sofia-sip/msg.h>
#include <sofia-sip/sip.h>
#include <sofia-sip/nta.h>
#include <memory>
#include <functional>
#include <list>
#include <string>
#include <map>
#include <memory>
#include <ostream>
#include <functional>
#include <regex.h>
#include <string>
#include <sofia-sip/msg.h>
#include <sofia-sip/nta.h>
#include <sofia-sip/sip.h>
#include <sofia-sip/tport.h>
namespace flexisip {
......@@ -53,20 +54,14 @@ class MsgSip {
MsgSip(const MsgSip &msgSip);
~MsgSip();
inline msg_t *getMsg() const {
return mMsg;
}
msg_t *getMsg() const {return mMsg;}
sip_t *getSip() const {return (sip_t *)msg_object(mMsg);}
su_home_t *getHome() const {return msg_home(mMsg);}
inline sip_t *getSip() const {
return (sip_t *)msg_object(mMsg);
}
msg_header_t *findHeader(const std::string &name);
const msg_header_t *findHeader(const std::string &name) const {return const_cast<MsgSip *>(this)->findHeader(name);}
inline su_home_t *getHome() const {
return msg_home(mMsg);
}
void serialize() const {
msg_serialize(mMsg, (msg_pub_t *)getSip());
}
void serialize() const {msg_serialize(mMsg, (msg_pub_t *)getSip());}
const char *print();
private:
......@@ -255,4 +250,4 @@ private:
su_home_t mHome;
};
}
\ No newline at end of file
}
......@@ -26,7 +26,8 @@
#include <sofia-sip/msg_addr.h>
using namespace std;
using namespace flexisip;
namespace flexisip {
void MsgSip::assignMsg(msg_t *msg) {
mMsg = msg_ref_create(msg);
......@@ -45,6 +46,19 @@ MsgSip::MsgSip(const MsgSip &msgSip) {
LOGD("New MsgSip %p copied from MsgSip %p", this, &msgSip);
}
msg_header_t *MsgSip::findHeader(const std::string &name) {
const sip_t *sip = getSip();
auto begin = reinterpret_cast<msg_header_t * const *>(&sip->sip_via);
auto end = reinterpret_cast<msg_header_t * const *>(&sip->sip_unknown);
for (auto it = begin; it < end; it++) {
msg_header_t *header = *it;
if (header && strcasecmp(header->sh_common->h_class->hc_name, name.c_str()) == 0) {
return header;
}
}
return nullptr;
}
const char *MsgSip::print() {
// make sure the message is serialized before showing it; it can be very confusing.
size_t msg_size;
......@@ -350,8 +364,10 @@ void ResponseSipEvent::setOutgoingAgent(const shared_ptr<OutgoingAgent> &agent)
ResponseSipEvent::~ResponseSipEvent() {
}
std::ostream &flexisip::operator<<(std::ostream &strm, const url_t &obj){
std::ostream &operator<<(std::ostream &strm, const url_t &obj){
SofiaAutoHome home;
strm<<url_as_string(home.home(), &obj);
return strm;
}
} // namespace flexisip
......@@ -178,9 +178,7 @@ void ModuleAuthenticationBase::configureAuthStatus(FlexisipAuthStatus &as, const
userUriStr, mRealmRegexStr.c_str()
);
if (!regex_search(userUriStr, m, mRealmRegex)) {
SLOGE << "no realm found";
ev->reply(500, "Internal error", TAG_END());
return;
throw runtime_error("no realm found");
}
int index = m.size() == 1 ? 0 : 1;
realm = su_strndup(ev->getHome(), userUriStr + m.position(index), m.length(index));
......
......@@ -18,16 +18,21 @@
#include <algorithm>
#include <cstring>
#include <functional>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <sofia-sip/sip_header.h>
#include <flexisip/logmanager.hh>
#include "utils/string-utils.hh"
#include "external-auth-module.hh"
using namespace std;
using namespace flexisip;
namespace flexisip {
ExternalAuthModule::ExternalAuthModule(su_root_t *root, const std::string &domain, const std::string &algo) : FlexisipAuthModuleBase(root, domain, algo) {
mEngine = nth_engine_create(root, TAG_END());
......@@ -44,16 +49,9 @@ ExternalAuthModule::~ExternalAuthModule() {
void ExternalAuthModule::checkAuthHeader(FlexisipAuthStatus &as, msg_auth_t *credentials, auth_challenger_t const *ach) {
try {
auto &externalAs = dynamic_cast<ExternalAuthModule::Status &>(as);
map<string, string> params = extractParameters(externalAs, *credentials);
string uri;
try {
uri = mUriFormater.format(params);
} catch (const invalid_argument &e) {
ostringstream os;
os << "cannot format HTTP URI: " << e.what();
throw runtime_error(os.str());
}
HttpUriFormater::TranslationFunc func = [&externalAs, credentials](const string &key){return extractParameter(externalAs, *credentials, key);};
string uri = mUriFormater.format(func);
auto *ctx = new HttpRequestCtx({*this, as, *ach});
......@@ -82,26 +80,6 @@ void ExternalAuthModule::checkAuthHeader(FlexisipAuthStatus &as, msg_auth_t *cre
void ExternalAuthModule::loadPassword(const FlexisipAuthStatus &as) {
}
std::map<std::string, std::string> ExternalAuthModule::extractParameters(const Status &as, const msg_auth_t &credentials) const {
map<string, string> params;
for (int i = 0; credentials.au_params[i] != nullptr; i++) {
const char *param = credentials.au_params[i];
const char *equal = strchr(const_cast<char *>(param), '=');
string key(param, equal-param);
string value = StringUtils::strip(equal+1, '"');
params[move(key)] = move(value);
}
params["scheme"] = StringUtils::strip(credentials.au_scheme, '"');
params["method"] = StringUtils::strip(as.method(), '"');
params["from"] = StringUtils::strip(as.fromHeader(), '"');
params["sip-instance"] = StringUtils::strip(as.sipInstance(), '"');
params["uuid"] = as.uuid();
params["domain"] = StringUtils::strip(as.domain(), '"');
return params;
}
void ExternalAuthModule::onHttpResponse(HttpRequestCtx &ctx, nth_client_t *request, const http_t *http) {
shared_ptr<RequestSipEvent> ev;
try {
......@@ -190,6 +168,40 @@ std::map<std::string, std::string> ExternalAuthModule::parseHttpBody(const std::
return result;
}
std::string ExternalAuthModule::extractParameter(const Status &as, const msg_auth_t &credentials, const std::string &paramName) {
if (paramName.compare(0, 7, "header:") == 0) {
string headerName(paramName, 7);
if (!headerName.empty()) {
char encodedHeader[255];
msg_header_t *header = as.event()->getMsgSip()->findHeader(headerName);
if (header) {
cmatch m;
sip_header_e(encodedHeader, sizeof(encodedHeader), reinterpret_cast<sip_header_t *>(header), 0);
if (regex_match(encodedHeader, m, regex(".*:\\s*(.*)\r\n"))) {
return m.str(1);
}
}
}
}
for (int i = 0; credentials.au_params[i] != nullptr; i++) {
const char *param = credentials.au_params[i];
const char *equal = strchr(const_cast<char *>(param), '=');
if (paramName.compare(0, paramName.size(), param, equal-param) == 0) {
return StringUtils::strip(equal+1, '"');
}
}
if (paramName == "scheme") return StringUtils::strip(credentials.au_scheme, '"');
if (paramName == "method") return StringUtils::strip(as.method(), '"');
if (paramName == "from") return StringUtils::strip(as.fromHeader(), '"');
if (paramName == "sip-instance") return StringUtils::strip(as.sipInstance(), '"');
if (paramName == "uuid") return as.uuid();
if (paramName == "domain") return StringUtils::strip(as.domain(), '"');
return "null";
}
int ExternalAuthModule::onHttpResponseCb(nth_client_magic_t *magic, nth_client_t *request, const http_t *http) noexcept {
auto *ctx = reinterpret_cast<HttpRequestCtx *>(magic);
ctx->am.onHttpResponse(*ctx, request, http);
......@@ -210,3 +222,5 @@ bool ExternalAuthModule::validSipCode(int sipCode) {
}
std::array<int, 4> ExternalAuthModule::sValidSipCodes{{200, 401, 407, 403}};
} // namespace flexisip
......@@ -87,10 +87,10 @@ private:
void checkAuthHeader(FlexisipAuthStatus &as, msg_auth_t *credentials, auth_challenger_t const *ach) override;
void loadPassword(const FlexisipAuthStatus &as) override;
std::map<std::string, std::string> extractParameters(const Status &as, const msg_auth_t &credentials) const;
void onHttpResponse(HttpRequestCtx &ctx, nth_client_t *request, const http_t *http);
std::map<std::string, std::string> parseHttpBody(const std::string &body) const;
static std::string extractParameter(const Status &as, const msg_auth_t &credentials, const std::string &paramName);
static int onHttpResponseCb(nth_client_magic_t *magic, nth_client_t *request, const http_t *http) noexcept;
static std::string toString(const http_payload_t *httpPayload);
static bool validSipCode(int sipCode);
......
......@@ -27,6 +27,7 @@
#include <flexisip/plugin.hh>
#include "logmanager.hh"
#include "utils/string-utils.hh"
#include "utils/uri-utils.hh"
......@@ -48,18 +49,20 @@ void ModuleExternalAuthentication::onDeclare(GenericStruct *mc) {
ConfigItemDescriptor items[] = {{
String,
"remote-auth-uri",
"URI to use to connect on the external HTTP server on each request. Each token preceded by "
"'$' character will be replaced before sending the HTTP request. The available tokens are:\n"
"\t* $method: the method of the SIP request that is being challenged. Ex: REGISTER, INVITE, ...\n"
"\t* $sip-instance: the value of +sip.instance parameter.\n"
"\t* $from: the value of the request's 'From:' header\n"
"\t* $domain: the domain name extracted from the From header's URI\n"
"\t* all the parameters available in the Authorization header. Ex: $realm, $nonce, $username, ...\n"
"\t* $uuid: the UUID of the user agent whose request is being challenged. The UUID is gotten from "
"URI to use to connect on the external HTTP server on each request. Each token preceded enclosed "
"by '{' and '}' bracket will be replaced before sending the HTTP request. The available tokens are:\n"
"\t* {method}: the method of the SIP request that is being challenged. Ex: REGISTER, INVITE, ...\n"
"\t* {sip-instance}: the value of +sip.instance parameter.\n"
"\t* {from}: the value of the request's 'From:' header\n"
"\t* {domain}: the domain name extracted from the From header's URI\n"
"\t* all the parameters available in the Authorization header. Ex: {realm}, {nonce}, {username}, ...\n"
"\t* {uuid}: the UUID of the user agent whose request is being challenged. The UUID is gotten from "
"the 'gr' parameter of the contact URI or, if not present, from the '+sip.instance' parameter. "
"If neither 'gr' nor '+sip.instance' parameters are present, then $uuid is be replaced by an empty string."
"\t* {header:<name>}: the value of <name> header of the request to authenticate. Replaced by 'null' if the "
"header is missing. Note: name matching isn't case-sensitive."
"\n"
"Ex: https://$realm.example.com/auth?from=$from&cnonce=$cnonce&username=$username",
"Ex: https://{realm}.example.com/auth?from={from}&cnonce={cnonce}&username={username}&cseq={header:cseq}",
""
},
config_item_end
......@@ -73,15 +76,23 @@ void ModuleExternalAuthentication::onLoad(const GenericStruct *mc) {
}
FlexisipAuthModuleBase *ModuleExternalAuthentication::createAuthModule(const std::string &domain, const std::string &algorithm) {
auto *am = new ExternalAuthModule(getAgent()->getRoot(), domain, algorithm);
am->getFormater().setTemplate(mRemoteUri);
return am;
try {
auto *am = new ExternalAuthModule(getAgent()->getRoot(), domain, algorithm);
am->getFormater().setTemplate(mRemoteUri);
return am;
} catch (const invalid_argument &e) {
LOGF("error while parsing 'module::ExternalAuthentication/remote-auth-uri': %s", e.what());
}
}
FlexisipAuthModuleBase *ModuleExternalAuthentication::createAuthModule(const std::string &domain, const std::string &algorithm, int nonceExpire) {
auto *am = new ExternalAuthModule(getAgent()->getRoot(), domain, algorithm, nonceExpire);
am->getFormater().setTemplate(mRemoteUri);
return am;
try {
auto *am = new ExternalAuthModule(getAgent()->getRoot(), domain, algorithm, nonceExpire);
am->getFormater().setTemplate(mRemoteUri);
return am;
} catch (const invalid_argument &e) {
LOGF("error while parsing 'module::ExternalAuthentication/remote-auth-uri': %s", e.what());
}
}
FlexisipAuthStatus *ModuleExternalAuthentication::createAuthStatus(const std::shared_ptr<RequestSipEvent> &ev) {
......
......@@ -29,32 +29,59 @@ using namespace std;
//=====================================================================================================================
// StringFormater class
//=====================================================================================================================
void StringFormater::setTemplate(const std::string &_template) {
pair<bool, string> syntaxCheckResult = checkTemplateSyntax(_template);
if (!syntaxCheckResult.first) {
throw invalid_argument("invalid syntax: " + syntaxCheckResult.second);
}
mTemplate = _template;
}
std::string StringFormater::format(const std::map<std::string, std::string> &values) const {
TranslationFunc func = [&values](const std::string &key) {
try {
return values.at(key);
} catch (const out_of_range &){
throw invalid_argument("invalid substitution variable {" + key + "}");
}
};
return format(func);
}
std::string StringFormater::format(TranslationFunc &func) const {
string result;
auto it1 = mTemplate.cbegin();
do {
auto it2 = find(it1, mTemplate.cend(), '$');
auto it2 = find(it1, mTemplate.cend(), '{');
result.insert(result.end(), it1, it2);
it1 = it2;
if (it1 != mTemplate.cend()) {
it2 = find_if_not(++it1, mTemplate.cend(), isKeywordChar);
it2 = find(++it1, mTemplate.cend(), '}');
string key(it1, it2);
try {
result += values.at(key);
} catch (const out_of_range &){
ostringstream os;
os << "invalid substitution variable '$" << key << "'";
throw invalid_argument(os.str());
}
it1 = it2;
result += func(key);
it1 = ++it2;
}
} while (it1 != mTemplate.cend());
return result;
}
bool StringFormater::isKeywordChar(char c) {
return ((c >= 'A' && c <= 'z') || c == '-');
std::pair<bool, std::string> StringFormater::checkTemplateSyntax(const std::string &_template) {
pair<bool, string> result(true, "");
for (auto it = _template.cbegin(); it != _template.cend(); ) {
it = find(it, _template.cend(), '{');
if (it != _template.cend()) {
it = find(it, _template.cend(), '}');
if (it == _template.cend()) {
result.first = false;
result.second = "missing closing bracket";
break;
}
}
}
return result;
}
//=====================================================================================================================
......@@ -65,6 +92,11 @@ std::string HttpUriFormater::format(const std::map<std::string, std::string> &va
return StringFormater::format(escape(values));
}
std::string HttpUriFormater::format(TranslationFunc &func) const {
TranslationFunc func2 = [&func](const string &paramName){return UriUtils::escape(func(paramName), UriUtils::httpReserved);};
return StringFormater::format(func2);
}
std::map<std::string, std::string> HttpUriFormater::escape(const std::map<std::string, std::string> &values) {
std::map<std::string, std::string> out;
for(const auto &item : values) {
......
......@@ -18,41 +18,53 @@
#pragma once
#include <functional>
#include <map>
#include <string>
/**
* @brief Factor new strings basing on a template and a map of key-value.
* @brief Factor new strings basing on a template.
*
* The template is a string that contains substitution variables, which
* will be replaced on string creation. Each substitution variable is composed
* by a '$' character followed by the name of the variable. Only alphanumeric
* character and '-' are valid in variable names.
* by a name surrounded by '{' and '}' brackets e.g. '{example}'. The name of the variable can
* be composed by any printable ASCII characters, excepted left brace, which mark
* the end of the variable.
*
* @warning All the characters after a '$' will be taken as variable name until a character
* not suitable for name is encountered.
* While forging the string, each variable is replaced by a string value basing on
* a given key-value map or a translation function.
*/
class StringFormater {
public:
StringFormater(const std::string &_template = "") : mTemplate(_template) {}
/**
* @brief Prototype for translation fuctions.
*/
using TranslationFunc = std::function<std::string(const std::string &)>;
StringFormater(const std::string &_template = "") {setTemplate(_template);}
void setTemplate(const std::string &_template) {mTemplate = _template;}
void setTemplate(const std::string &_template);
const std::string &getTemplate() const {return mTemplate;}
/**
* @brief Create a new string.
* @brief Forge a new string from a map.
*
* @param values A map associating a variable name with the value by which
* the variable will be replaced.
* @return The new string.
*
* An std::invalid_argument exception it thrown if a value couldn't be found in
* the map for a variable.
* @throw std::invalid_argument some variable value couldn't be found
* in values map.
*/
std::string format(const std::map<std::string, std::string> &values) const;
/**
* @brief Forge a new string by using a function.
*/
std::string format(TranslationFunc &func) const;
private:
static bool isKeywordChar(char c);
static std::pair<bool, std::string> checkTemplateSyntax(const std::string &_template);
std::string mTemplate;
};
......@@ -65,6 +77,7 @@ private:
class HttpUriFormater: public StringFormater {
public:
std::string format(const std::map<std::string, std::string> &values) const;
std::string format(TranslationFunc &func) const;
private:
static std::map<std::string, std::string> escape(const std::map<std::string, std::string> &values);
......
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