Ed Tanous | 1ccd57c | 2017-03-21 13:15:58 -0700 | [diff] [blame] | 1 | #include <random> |
Ed Tanous | f927347 | 2017-02-28 16:05:13 -0800 | [diff] [blame] | 2 | #include <unordered_map> |
Ed Tanous | f927347 | 2017-02-28 16:05:13 -0800 | [diff] [blame] | 3 | #include <boost/algorithm/string/predicate.hpp> |
| 4 | |
| 5 | #include <token_authorization_middleware.hpp> |
Ed Tanous | 1ccd57c | 2017-03-21 13:15:58 -0700 | [diff] [blame] | 6 | #include <crow/logging.h> |
Ed Tanous | c4771fb | 2017-03-13 13:39:49 -0700 | [diff] [blame] | 7 | #include <base64.hpp> |
Ed Tanous | f927347 | 2017-02-28 16:05:13 -0800 | [diff] [blame] | 8 | |
Ed Tanous | c4771fb | 2017-03-13 13:39:49 -0700 | [diff] [blame] | 9 | namespace crow { |
| 10 | |
| 11 | using random_bytes_engine = std::independent_bits_engine<std::default_random_engine, CHAR_BIT, unsigned char>; |
| 12 | |
Ed Tanous | f927347 | 2017-02-28 16:05:13 -0800 | [diff] [blame] | 13 | |
Ed Tanous | 9992332 | 2017-03-03 14:21:24 -0800 | [diff] [blame] | 14 | void TokenAuthorizationMiddleware::before_handle(crow::request& req, response& res, context& ctx) { |
Ed Tanous | 9b65f1f | 2017-03-07 15:17:13 -0800 | [diff] [blame] | 15 | |
Ed Tanous | 9992332 | 2017-03-03 14:21:24 -0800 | [diff] [blame] | 16 | auto return_unauthorized = [&req, &res]() { |
| 17 | res.code = 401; |
| 18 | res.end(); |
| 19 | }; |
Ed Tanous | 1ccd57c | 2017-03-21 13:15:58 -0700 | [diff] [blame] | 20 | |
| 21 | LOG(DEBUG) << "Got route " << req.url; |
| 22 | |
Ed Tanous | 9b65f1f | 2017-03-07 15:17:13 -0800 | [diff] [blame] | 23 | if (req.url == "/" || boost::starts_with(req.url, "/static/")){ |
| 24 | //TODO this is total hackery to allow the login page to work before the user |
Ed Tanous | c4771fb | 2017-03-13 13:39:49 -0700 | [diff] [blame] | 25 | // is authenticated. Also, it will be quite slow for all pages instead of |
| 26 | // a one time hit for the whitelist entries. |
Ed Tanous | 9b65f1f | 2017-03-07 15:17:13 -0800 | [diff] [blame] | 27 | // Ideally, this should be done in the url router handler, with tagged routes |
| 28 | // for the whitelist entries. |
Ed Tanous | 1ccd57c | 2017-03-21 13:15:58 -0700 | [diff] [blame] | 29 | // Another option would be to whitelist a minimal |
Ed Tanous | 9b65f1f | 2017-03-07 15:17:13 -0800 | [diff] [blame] | 30 | return; |
| 31 | } |
| 32 | |
Ed Tanous | 9992332 | 2017-03-03 14:21:24 -0800 | [diff] [blame] | 33 | if (req.url == "/login") { |
Ed Tanous | c4771fb | 2017-03-13 13:39:49 -0700 | [diff] [blame] | 34 | if (req.method != HTTPMethod::POST){ |
| 35 | return_unauthorized(); |
| 36 | return; |
| 37 | } else { |
| 38 | auto login_credentials = crow::json::load(req.body); |
| 39 | if (!login_credentials){ |
| 40 | return_unauthorized(); |
| 41 | return; |
| 42 | } |
| 43 | auto username = login_credentials["username"].s(); |
| 44 | auto password = login_credentials["password"].s(); |
Ed Tanous | 1ccd57c | 2017-03-21 13:15:58 -0700 | [diff] [blame] | 45 | |
| 46 | // TODO(ed) pull real passwords from PAM |
Ed Tanous | c4771fb | 2017-03-13 13:39:49 -0700 | [diff] [blame] | 47 | if (username == "dude" && password == "dude"){ |
Ed Tanous | 1ccd57c | 2017-03-21 13:15:58 -0700 | [diff] [blame] | 48 | //TODO(ed) the RNG should be initialized at start, not every time we want a token |
Ed Tanous | c4771fb | 2017-03-13 13:39:49 -0700 | [diff] [blame] | 49 | std::random_device rand; |
| 50 | random_bytes_engine rbe; |
| 51 | std::string token('a', 20); |
| 52 | std::generate(begin(token), end(token), std::ref(rbe)); |
| 53 | std::string encoded_token; |
| 54 | base64::base64_encode(token, encoded_token); |
| 55 | ctx.auth_token = encoded_token; |
| 56 | this->auth_token2 = encoded_token; |
Ed Tanous | c4771fb | 2017-03-13 13:39:49 -0700 | [diff] [blame] | 57 | } else { |
| 58 | return_unauthorized(); |
| 59 | return; |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | } else if (req.url == "/logout") { |
| 64 | this->auth_token2 = ""; |
| 65 | } else { // Normal, non login, non static file request |
| 66 | // Check to make sure we're logged in |
| 67 | if (this->auth_token2.empty()){ |
| 68 | return_unauthorized(); |
| 69 | return; |
| 70 | } |
| 71 | // Check for an authorization header, reject if not present |
| 72 | if (req.headers.count("Authorization") != 1) { |
| 73 | return_unauthorized(); |
| 74 | return; |
| 75 | } |
| 76 | |
| 77 | std::string auth_header = req.get_header_value("Authorization"); |
| 78 | // If the user is attempting any kind of auth other than token, reject |
| 79 | if (!boost::starts_with(auth_header, "Token ")) { |
| 80 | return_unauthorized(); |
| 81 | return; |
| 82 | } |
| 83 | |
| 84 | //todo, use span here instead of constructing a new string |
| 85 | if (auth_header.substr(6) != this->auth_token2){ |
| 86 | return_unauthorized(); |
| 87 | return; |
| 88 | } |
Ed Tanous | 1ccd57c | 2017-03-21 13:15:58 -0700 | [diff] [blame] | 89 | // else let the request continue unharmed |
Ed Tanous | 9992332 | 2017-03-03 14:21:24 -0800 | [diff] [blame] | 90 | } |
| 91 | } |
Ed Tanous | f927347 | 2017-02-28 16:05:13 -0800 | [diff] [blame] | 92 | |
Ed Tanous | 9992332 | 2017-03-03 14:21:24 -0800 | [diff] [blame] | 93 | void TokenAuthorizationMiddleware::after_handle(request& /*req*/, response& res, context& ctx) { |
Ed Tanous | c4771fb | 2017-03-13 13:39:49 -0700 | [diff] [blame] | 94 | |
Ed Tanous | 9992332 | 2017-03-03 14:21:24 -0800 | [diff] [blame] | 95 | } |
Ed Tanous | c4771fb | 2017-03-13 13:39:49 -0700 | [diff] [blame] | 96 | } |