incremental
diff --git a/include/pam_authenticate.hpp b/include/pam_authenticate.hpp
new file mode 100644
index 0000000..153dbc7
--- /dev/null
+++ b/include/pam_authenticate.hpp
@@ -0,0 +1,65 @@
+#include <security/pam_appl.h>
+
+// function used to get user input
+inline int pam_function_conversation(int num_msg,
+ const struct pam_message** msg,
+ struct pam_response** resp,
+ void* appdata_ptr) {
+ char* pass = (char*)malloc(strlen((char*)appdata_ptr) + 1);
+ strcpy(pass, (char*)appdata_ptr);
+
+ int i;
+
+ *resp = (pam_response*)calloc(num_msg, sizeof(struct pam_response));
+
+ for (i = 0; i < num_msg; ++i) {
+ /* Ignore all PAM messages except prompting for hidden input */
+ if (msg[i]->msg_style != PAM_PROMPT_ECHO_OFF) continue;
+
+ /* Assume PAM is only prompting for the password as hidden input */
+ resp[i]->resp = pass;
+ }
+
+ return PAM_SUCCESS;
+}
+
+class PamAuthenticator {
+ public:
+ inline bool authenticate(const std::string& username,
+ const std::string& password) {
+ const struct pam_conv local_conversation = {pam_function_conversation,
+ (char*)password.c_str()};
+ pam_handle_t* local_auth_handle = NULL; // this gets set by pam_start
+
+ int retval;
+ retval = pam_start("su", username.c_str(), &local_conversation,
+ &local_auth_handle);
+
+ if (retval != PAM_SUCCESS) {
+ //printf("pam_start returned: %d\n ", retval);
+ return false;
+ }
+
+ retval = pam_authenticate(local_auth_handle,
+ PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK);
+
+ if (retval != PAM_SUCCESS) {
+ if (retval == PAM_AUTH_ERR) {
+ //printf("Authentication failure.\n");
+ } else {
+ //printf("pam_authenticate returned %d\n", retval);
+ }
+ return false;
+ }
+
+ //printf("Authenticated.\n");
+ retval = pam_end(local_auth_handle, retval);
+
+ if (retval != PAM_SUCCESS) {
+ //printf("pam_end returned\n");
+ return false;
+ }
+
+ return true;
+ }
+};
\ No newline at end of file
diff --git a/include/security_headers_middleware.hpp b/include/security_headers_middleware.hpp
index 7e84543..19644f4 100644
--- a/include/security_headers_middleware.hpp
+++ b/include/security_headers_middleware.hpp
@@ -4,12 +4,45 @@
#include <crow/http_response.h>
namespace crow {
+static const std::string strict_transport_security_key =
+ "Strict-Transport-Security";
+static const std::string strict_transport_security_value =
+ "max-age=31536000; includeSubdomains; preload";
+
+static const std::string ua_compatability_key = "X-UA-Compatible";
+static const std::string ua_compatability_value = "IE=11";
+
+static const std::string xframe_key = "X-Frame-Options";
+static const std::string xframe_value = "DENY";
+
+static const std::string xss_key = "X-XSS-Protection";
+static const std::string xss_value = "1; mode=block";
+
+static const std::string content_security_key = "X-Content-Security-Policy";
+static const std::string content_security_value = "default-src 'self'";
+
struct SecurityHeadersMiddleware {
struct context {};
- void before_handle(crow::request& req, response& res, context& ctx);
+ void before_handle(crow::request& req,
+ response& res,
+ context& ctx) {}
- void after_handle(request& req, response& res, context& ctx);
+ void after_handle(request& /*req*/,
+ response& res,
+ context& ctx) {
+ /*
+ TODO(ed) these should really check content types. for example,
+ X-UA-Compatible header doesn't make sense when retrieving a JSON or
+ javascript file. It doesn't hurt anything, it's just ugly.
+ */
+ res.add_header(strict_transport_security_key,
+ strict_transport_security_value);
+ res.add_header(ua_compatability_key, ua_compatability_value);
+ res.add_header(xframe_key, xframe_value);
+ res.add_header(xss_key, xss_value);
+ res.add_header(content_security_key, content_security_value);
+ }
};
}
\ No newline at end of file
diff --git a/include/token_authorization_middleware.hpp b/include/token_authorization_middleware.hpp
index 214fd93..d333e6c 100644
--- a/include/token_authorization_middleware.hpp
+++ b/include/token_authorization_middleware.hpp
@@ -4,25 +4,145 @@
#include <crow/http_response.h>
#include <boost/container/flat_set.hpp>
+#include <base64.hpp>
+
+#include <pam_authenticate.hpp>
+
namespace crow {
struct User {};
-struct TokenAuthorizationMiddleware {
+using random_bytes_engine =
+ std::independent_bits_engine<std::default_random_engine, CHAR_BIT,
+ unsigned char>;
+
+template <class AuthenticationFunction>
+struct TokenAuthorization {
// TODO(ed) auth_token shouldn't really be passed to the context
// it opens the possibility of exposure by and endpoint.
// instead we should only pass some kind of "user" struct
struct context {
- //std::string auth_token;
+ // std::string auth_token;
};
- TokenAuthorizationMiddleware();
+ TokenAuthorization(){};
- void before_handle(crow::request& req, response& res, context& ctx);
+ void before_handle(crow::request& req, response& res, context& ctx) {
+ auto return_unauthorized = [&req, &res]() {
+ res.code = 401;
+ res.end();
+ };
- void after_handle(request& req, response& res, context& ctx);
+ auto return_bad_request = [&req, &res]() {
+ res.code = 400;
+ res.end();
+ };
+
+ auto return_internal_error = [&req, &res]() {
+ res.code = 500;
+ res.end();
+ };
+
+ if (req.url == "/" || boost::starts_with(req.url, "/static/")) {
+ // TODO this is total hackery to allow the login page to work before the
+ // user is authenticated. Also, it will be quite slow for all pages
+ // instead
+ // of a one time hit for the whitelist entries. Ideally, this should be
+ // done in the url router handler, with tagged routes for the whitelist
+ // entries. Another option would be to whitelist a minimal for based page
+ // that didn't
+ // load the full angular UI until after login
+ return;
+ }
+
+ if (req.url == "/login") {
+ if (req.method != HTTPMethod::POST) {
+ return_unauthorized();
+ return;
+ } else {
+ auto login_credentials = crow::json::load(req.body);
+ if (!login_credentials) {
+ return_bad_request();
+ return;
+ }
+ if (!login_credentials.has("username") ||
+ !login_credentials.has("password")) {
+ return_bad_request();
+ return;
+ }
+ auto username = login_credentials["username"].s();
+ auto password = login_credentials["password"].s();
+ auto p = AuthenticationFunction();
+ if (p.authenticate(username, password)) {
+ crow::json::wvalue x;
+
+ // TODO(ed) the RNG should be initialized at start, not every time we
+ // want a token
+ std::random_device rand;
+ random_bytes_engine rbe;
+ std::string token('a', 20);
+ // TODO(ed) for some reason clang-tidy finds a divide by zero error in
+ // cstdlibc here commented out for now. Needs investigation
+ std::generate(std::begin(token), std::end(token), std::ref(rbe)); // NOLINT
+ std::string encoded_token;
+ base64::base64_encode(token, encoded_token);
+ // ctx.auth_token = encoded_token;
+ this->auth_token2.insert(encoded_token);
+
+ x["token"] = encoded_token;
+
+ res.write(json::dump(x));
+ res.add_header("Content-Type", "application/json");
+ res.end();
+ } else {
+ return_unauthorized();
+ return;
+ }
+ }
+
+ } else { // Normal, non login, non static file request
+ // Check to make sure we're logged in
+ if (this->auth_token2.empty()) {
+ return_unauthorized();
+ return;
+ }
+ // Check for an authorization header, reject if not present
+ if (req.headers.count("Authorization") != 1) {
+ return_unauthorized();
+ return;
+ }
+
+ std::string auth_header = req.get_header_value("Authorization");
+ // If the user is attempting any kind of auth other than token, reject
+ if (!boost::starts_with(auth_header, "Token ")) {
+ return_unauthorized();
+ return;
+ }
+ std::string auth_key = auth_header.substr(6);
+ // TODO(ed), use span here instead of constructing a new string
+ if (this->auth_token2.find(auth_key) == this->auth_token2.end()) {
+ return_unauthorized();
+ return;
+ }
+
+ if (req.url == "/logout") {
+ this->auth_token2.erase(auth_key);
+ res.code = 200;
+ res.end();
+ return;
+ }
+
+ // else let the request continue unharmed
+ }
+ }
+
+ void after_handle(request& req, response& res, context& ctx) {
+ // Do nothing
+ }
private:
boost::container::flat_set<std::string> auth_token2;
};
+
+using TokenAuthorizationMiddleware = TokenAuthorization<PamAuthenticator>;
}
\ No newline at end of file