blob: 2d1edcda1618a0975cbb0745f91a8706477cb836 [file] [log] [blame]
Ed Tanousf9273472017-02-28 16:05:13 -08001#pragma once
2
3#include <crow/http_request.h>
4#include <crow/http_response.h>
Ed Tanous4758d5b2017-06-06 15:28:13 -07005#include <boost/container/flat_set.hpp>
Ed Tanousf9273472017-02-28 16:05:13 -08006
Ed Tanousf3d847c2017-06-12 16:01:42 -07007#include <base64.hpp>
8
9#include <pam_authenticate.hpp>
10
Ed Tanous99923322017-03-03 14:21:24 -080011namespace crow {
Ed Tanousb4d29f42017-03-24 16:39:25 -070012
Ed Tanous1ff48782017-04-18 12:45:08 -070013struct User {};
Ed Tanousb4d29f42017-03-24 16:39:25 -070014
Ed Tanousf3d847c2017-06-12 16:01:42 -070015using random_bytes_engine =
16 std::independent_bits_engine<std::default_random_engine, CHAR_BIT,
17 unsigned char>;
18
19template <class AuthenticationFunction>
20struct TokenAuthorization {
Ed Tanous3dac7492017-08-02 13:46:20 -070021 private:
22 random_bytes_engine rbe;
23
24 public:
Ed Tanous99923322017-03-03 14:21:24 -080025 struct context {
Ed Tanousf3d847c2017-06-12 16:01:42 -070026 // std::string auth_token;
Ed Tanous99923322017-03-03 14:21:24 -080027 };
Ed Tanousf9273472017-02-28 16:05:13 -080028
Ed Tanousf3d847c2017-06-12 16:01:42 -070029 TokenAuthorization(){};
Ed Tanousc4771fb2017-03-13 13:39:49 -070030
Ed Tanousf3d847c2017-06-12 16:01:42 -070031 void before_handle(crow::request& req, response& res, context& ctx) {
32 auto return_unauthorized = [&req, &res]() {
33 res.code = 401;
34 res.end();
35 };
Ed Tanousf9273472017-02-28 16:05:13 -080036
Ed Tanousf3d847c2017-06-12 16:01:42 -070037 auto return_bad_request = [&req, &res]() {
38 res.code = 400;
39 res.end();
40 };
41
42 auto return_internal_error = [&req, &res]() {
43 res.code = 500;
44 res.end();
45 };
46
47 if (req.url == "/" || boost::starts_with(req.url, "/static/")) {
48 // TODO this is total hackery to allow the login page to work before the
49 // user is authenticated. Also, it will be quite slow for all pages
Ed Tanous3dac7492017-08-02 13:46:20 -070050 // instead of a one time hit for the whitelist entries. Ideally, this
51 // should be
Ed Tanousf3d847c2017-06-12 16:01:42 -070052 // done in the url router handler, with tagged routes for the whitelist
53 // entries. Another option would be to whitelist a minimal for based page
Ed Tanous3dac7492017-08-02 13:46:20 -070054 // that didn't load the full angular UI until after login
Ed Tanousf3d847c2017-06-12 16:01:42 -070055 return;
56 }
57
58 if (req.url == "/login") {
59 if (req.method != HTTPMethod::POST) {
60 return_unauthorized();
61 return;
62 } else {
Ed Tanous3dac7492017-08-02 13:46:20 -070063 std::string username;
64 std::string password;
65 try {
66 auto login_credentials = nlohmann::json::parse(req.body);
67 username = login_credentials["username"];
68 password = login_credentials["password"];
69 } catch (...) {
Ed Tanousf3d847c2017-06-12 16:01:42 -070070 return_bad_request();
71 return;
72 }
Ed Tanous3dac7492017-08-02 13:46:20 -070073
Ed Tanousf3d847c2017-06-12 16:01:42 -070074 auto p = AuthenticationFunction();
75 if (p.authenticate(username, password)) {
Ed Tanous3dac7492017-08-02 13:46:20 -070076 nlohmann::json x;
Ed Tanousf3d847c2017-06-12 16:01:42 -070077
Ed Tanousf3d847c2017-06-12 16:01:42 -070078 std::string token('a', 20);
79 // TODO(ed) for some reason clang-tidy finds a divide by zero error in
80 // cstdlibc here commented out for now. Needs investigation
Ed Tanous3dac7492017-08-02 13:46:20 -070081 std::generate(std::begin(token), std::end(token),
82 std::ref(rbe)); // NOLINT
Ed Tanousf3d847c2017-06-12 16:01:42 -070083 std::string encoded_token;
84 base64::base64_encode(token, encoded_token);
85 // ctx.auth_token = encoded_token;
86 this->auth_token2.insert(encoded_token);
87
Ed Tanous3dac7492017-08-02 13:46:20 -070088 nlohmann::json ret{{"token", encoded_token}};
Ed Tanousf3d847c2017-06-12 16:01:42 -070089
Ed Tanous3dac7492017-08-02 13:46:20 -070090 res.write(ret.dump());
Ed Tanousf3d847c2017-06-12 16:01:42 -070091 res.add_header("Content-Type", "application/json");
92 res.end();
93 } else {
94 return_unauthorized();
95 return;
96 }
97 }
98
99 } else { // Normal, non login, non static file request
100 // Check to make sure we're logged in
101 if (this->auth_token2.empty()) {
102 return_unauthorized();
103 return;
104 }
105 // Check for an authorization header, reject if not present
106 if (req.headers.count("Authorization") != 1) {
107 return_unauthorized();
108 return;
109 }
110
111 std::string auth_header = req.get_header_value("Authorization");
112 // If the user is attempting any kind of auth other than token, reject
113 if (!boost::starts_with(auth_header, "Token ")) {
114 return_unauthorized();
115 return;
116 }
117 std::string auth_key = auth_header.substr(6);
118 // TODO(ed), use span here instead of constructing a new string
119 if (this->auth_token2.find(auth_key) == this->auth_token2.end()) {
120 return_unauthorized();
121 return;
122 }
123
124 if (req.url == "/logout") {
125 this->auth_token2.erase(auth_key);
126 res.code = 200;
127 res.end();
128 return;
129 }
130
131 // else let the request continue unharmed
132 }
133 }
134
135 void after_handle(request& req, response& res, context& ctx) {
136 // Do nothing
137 }
Ed Tanous8041f312017-04-03 09:47:01 -0700138
Ed Tanous1ff48782017-04-18 12:45:08 -0700139 private:
Ed Tanous4758d5b2017-06-06 15:28:13 -0700140 boost::container::flat_set<std::string> auth_token2;
Ed Tanous99923322017-03-03 14:21:24 -0800141};
Ed Tanousf3d847c2017-06-12 16:01:42 -0700142
143using TokenAuthorizationMiddleware = TokenAuthorization<PamAuthenticator>;
Ed Tanousf9273472017-02-28 16:05:13 -0800144}