diff options
author | Roland Reichwein <mail@reichwein.it> | 2020-04-15 20:01:25 +0200 |
---|---|---|
committer | Roland Reichwein <mail@reichwein.it> | 2020-04-15 20:01:25 +0200 |
commit | f6e703a938a95c555b388f79966cf955c5d07dc6 (patch) | |
tree | f47eae93adf9f72b1472ced2a1a051e90845dafe | |
parent | 1ce0bb7ad50129fbab6c0a75f18eee6149ca1be3 (diff) |
HTTP Auth (Basic)
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | base64.cpp | 23 | ||||
-rw-r--r-- | base64.h | 7 | ||||
-rw-r--r-- | config.cpp | 15 | ||||
-rw-r--r-- | config.h | 1 | ||||
-rw-r--r-- | response.cpp | 28 | ||||
-rw-r--r-- | webserver.conf | 1 |
7 files changed, 74 insertions, 2 deletions
@@ -58,6 +58,7 @@ LIBS+= \ endif PROGSRC=\ + base64.cpp \ config.cpp \ file.cpp \ http.cpp \ diff --git a/base64.cpp b/base64.cpp new file mode 100644 index 0000000..3847f0a --- /dev/null +++ b/base64.cpp @@ -0,0 +1,23 @@ +#include "base64.h" + +#include <boost/archive/iterators/binary_from_base64.hpp> +#include <boost/archive/iterators/base64_from_binary.hpp> +#include <boost/archive/iterators/transform_width.hpp> +#include <boost/algorithm/string.hpp> + +std::string decode64(const std::string &val) +{ + using namespace boost::archive::iterators; + using It = transform_width<binary_from_base64<std::string::const_iterator>, 8, 6>; + return boost::algorithm::trim_right_copy_if(std::string(It(std::begin(val)), It(std::end(val))), [](char c) { + return c == '\0'; + }); +} + +std::string encode64(const std::string &val) +{ + using namespace boost::archive::iterators; + using It = base64_from_binary<transform_width<std::string::const_iterator, 6, 8>>; + auto tmp = std::string(It(std::begin(val)), It(std::end(val))); + return tmp.append((3 - val.size() % 3) % 3, '='); +} diff --git a/base64.h b/base64.h new file mode 100644 index 0000000..6a3f2c3 --- /dev/null +++ b/base64.h @@ -0,0 +1,7 @@ +#pragma once + +#include <string> + +std::string decode64(const std::string &val); +std::string encode64(const std::string &val); + @@ -63,8 +63,19 @@ void Config::readConfigfile(std::string filename) auto attrs = x.second.get_child("<xmlattr>"); path.requested = attrs.get<std::string>("requested"); for (const auto& param: x.second) { // get all sub-elements of <path> - if (param.first.size() > 0 && param.first[0] != '<') // exclude meta-elements like <xmlattr> - path.params[param.first.data()] = param.second.data(); + if (param.first.size() > 0 && param.first[0] != '<') { // exclude meta-elements like <xmlattr> + if (param.first == "auth") { + try { + std::string login{param.second.get<std::string>("<xmlattr>.login")}; + std::string password{param.second.get<std::string>("<xmlattr>.password")}; + path.auth[login] = password; + } catch (const std::exception& ex) { + std::cerr << "Warning: Can't read auth data from config: " << ex.what() << std::endl; + } + } else { + path.params[param.first] = param.second.data(); + } + } } site_struct.paths.push_back(path); } else if (x.first == "certpath"s) { @@ -13,6 +13,7 @@ struct Path std::string requested; // the requested path // mandatory entries: "plugin", "target", others are optional std::unordered_map<std::string, std::string> params; // what to serve, e.g. which filesystem path (target), and which plugin + std::unordered_map<std::string, std::string> auth; // optional }; struct Site diff --git a/response.cpp b/response.cpp index 8f66c54..0c619a2 100644 --- a/response.cpp +++ b/response.cpp @@ -1,4 +1,6 @@ #include "response.h" + +#include "base64.h" #include "file.h" #include <boost/algorithm/string/predicate.hpp> @@ -183,6 +185,8 @@ response_type HttpStatus(std::string status, std::string message, response_type& { res.result(unsigned(stoul(status))); res.set(http::field::content_type, "text/html"); + if (res.result_int() == 401) + res.set(http::field::www_authenticate, "Basic realm=\"Webbox Login\""); res.body() = "<html><body><h1>"s + VersionString + " Error</h1><p>"s + status + " "s + message + "</p></body></html>"s; res.prepare_payload(); @@ -201,6 +205,30 @@ response_type generate_response(request_type& req, Server& server) try { RequestContext req_ctx{req, server}; // can throw std::out_of_range + auto& auth{req_ctx.GetPath().auth}; + if (auth.size() != 0) { + std::string authorization{req[http::field::authorization]}; + if (authorization.substr(0, 6) != "Basic "s) + return HttpStatus("401", "Bad Authorization Type", res); + + authorization = authorization.substr(6); + authorization = decode64(authorization); + + size_t pos {authorization.find(':')}; + if (pos == authorization.npos) + return HttpStatus("401", "Bad Authorization Encoding", res); + + std::string login{authorization.substr(0, pos)}; + std::string password{authorization.substr(pos + 1)}; + + auto it {auth.find(login)}; + if (it == auth.end()) + return HttpStatus("401", "Bad Authorization", res); + + if (it->second != password) + return HttpStatus("401", "Bad Authorization", res); // should be same message as previous one to prevent login guessing + } + plugin_type plugin{req_ctx.GetPlugin()}; auto GetServerParamFunction {std::function<std::string(const std::string& key)>(std::bind(GetServerParam, _1, std::ref(server)))}; diff --git a/webserver.conf b/webserver.conf index 35ff2f6..fb5def6 100644 --- a/webserver.conf +++ b/webserver.conf @@ -30,6 +30,7 @@ <target>/home/ernie/testbox</target> <WEBBOX_NAME>Testbox1</WEBBOX_NAME> <WEBBOX_READONLY>0</WEBBOX_READONLY> + <auth login="abc" password="def"/> </path> <certpath>/home/ernie/code/webserver/fullchain.pem</certpath> <keypath>/home/ernie/code/webserver/privkey.pem</keypath> |