blob: d333e6ce46524c8d385cd3bdfab8cdb9e9316875 [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 Tanousb4d29f42017-03-24 16:39:25 -070021 // TODO(ed) auth_token shouldn't really be passed to the context
22 // it opens the possibility of exposure by and endpoint.
23 // instead we should only pass some kind of "user" struct
Ed Tanous99923322017-03-03 14:21:24 -080024 struct context {
Ed Tanousf3d847c2017-06-12 16:01:42 -070025 // std::string auth_token;
Ed Tanous99923322017-03-03 14:21:24 -080026 };
Ed Tanousf9273472017-02-28 16:05:13 -080027
Ed Tanousf3d847c2017-06-12 16:01:42 -070028 TokenAuthorization(){};
Ed Tanousc4771fb2017-03-13 13:39:49 -070029
Ed Tanousf3d847c2017-06-12 16:01:42 -070030 void before_handle(crow::request& req, response& res, context& ctx) {
31 auto return_unauthorized = [&req, &res]() {
32 res.code = 401;
33 res.end();
34 };
Ed Tanousf9273472017-02-28 16:05:13 -080035
Ed Tanousf3d847c2017-06-12 16:01:42 -070036 auto return_bad_request = [&req, &res]() {
37 res.code = 400;
38 res.end();
39 };
40
41 auto return_internal_error = [&req, &res]() {
42 res.code = 500;
43 res.end();
44 };
45
46 if (req.url == "/" || boost::starts_with(req.url, "/static/")) {
47 // TODO this is total hackery to allow the login page to work before the
48 // user is authenticated. Also, it will be quite slow for all pages
49 // instead
50 // of a one time hit for the whitelist entries. Ideally, this should be
51 // done in the url router handler, with tagged routes for the whitelist
52 // entries. Another option would be to whitelist a minimal for based page
53 // that didn't
54 // load the full angular UI until after login
55 return;
56 }
57
58 if (req.url == "/login") {
59 if (req.method != HTTPMethod::POST) {
60 return_unauthorized();
61 return;
62 } else {
63 auto login_credentials = crow::json::load(req.body);
64 if (!login_credentials) {
65 return_bad_request();
66 return;
67 }
68 if (!login_credentials.has("username") ||
69 !login_credentials.has("password")) {
70 return_bad_request();
71 return;
72 }
73 auto username = login_credentials["username"].s();
74 auto password = login_credentials["password"].s();
75 auto p = AuthenticationFunction();
76 if (p.authenticate(username, password)) {
77 crow::json::wvalue x;
78
79 // TODO(ed) the RNG should be initialized at start, not every time we
80 // want a token
81 std::random_device rand;
82 random_bytes_engine rbe;
83 std::string token('a', 20);
84 // TODO(ed) for some reason clang-tidy finds a divide by zero error in
85 // cstdlibc here commented out for now. Needs investigation
86 std::generate(std::begin(token), std::end(token), std::ref(rbe)); // NOLINT
87 std::string encoded_token;
88 base64::base64_encode(token, encoded_token);
89 // ctx.auth_token = encoded_token;
90 this->auth_token2.insert(encoded_token);
91
92 x["token"] = encoded_token;
93
94 res.write(json::dump(x));
95 res.add_header("Content-Type", "application/json");
96 res.end();
97 } else {
98 return_unauthorized();
99 return;
100 }
101 }
102
103 } else { // Normal, non login, non static file request
104 // Check to make sure we're logged in
105 if (this->auth_token2.empty()) {
106 return_unauthorized();
107 return;
108 }
109 // Check for an authorization header, reject if not present
110 if (req.headers.count("Authorization") != 1) {
111 return_unauthorized();
112 return;
113 }
114
115 std::string auth_header = req.get_header_value("Authorization");
116 // If the user is attempting any kind of auth other than token, reject
117 if (!boost::starts_with(auth_header, "Token ")) {
118 return_unauthorized();
119 return;
120 }
121 std::string auth_key = auth_header.substr(6);
122 // TODO(ed), use span here instead of constructing a new string
123 if (this->auth_token2.find(auth_key) == this->auth_token2.end()) {
124 return_unauthorized();
125 return;
126 }
127
128 if (req.url == "/logout") {
129 this->auth_token2.erase(auth_key);
130 res.code = 200;
131 res.end();
132 return;
133 }
134
135 // else let the request continue unharmed
136 }
137 }
138
139 void after_handle(request& req, response& res, context& ctx) {
140 // Do nothing
141 }
Ed Tanous8041f312017-04-03 09:47:01 -0700142
Ed Tanous1ff48782017-04-18 12:45:08 -0700143 private:
Ed Tanous4758d5b2017-06-06 15:28:13 -0700144 boost::container::flat_set<std::string> auth_token2;
Ed Tanous99923322017-03-03 14:21:24 -0800145};
Ed Tanousf3d847c2017-06-12 16:01:42 -0700146
147using TokenAuthorizationMiddleware = TokenAuthorization<PamAuthenticator>;
Ed Tanousf9273472017-02-28 16:05:13 -0800148}