blob: 8f4c5c825233414eab116b03d319823cfb8c430f [file] [log] [blame]
Ed Tanous9140a672017-04-24 17:01:32 -07001#include "token_authorization_middleware.hpp"
Ed Tanous1abe55e2018-09-05 08:30:59 -07002#include "webserver_common.hpp"
3
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +02004#include <condition_variable>
5#include <future>
6#include <mutex>
Ed Tanous1abe55e2018-09-05 08:30:59 -07007
Ed Tanousb4a7bfa2017-04-04 17:23:00 -07008#include "gmock/gmock.h"
Ed Tanous1ff48782017-04-18 12:45:08 -07009#include "gtest/gtest.h"
Ed Tanousf9273472017-02-28 16:05:13 -080010
Ed Tanous8041f312017-04-03 09:47:01 -070011using namespace crow;
Ed Tanous8041f312017-04-03 09:47:01 -070012
Ed Tanous1abe55e2018-09-05 08:30:59 -070013class TokenAuth : public ::testing::Test
14{
15 public:
16 TokenAuth() :
17 lk(std::unique_lock<std::mutex>(m)),
Ed Tanous8f626352018-12-19 14:51:54 -080018 io(std::make_shared<boost::asio::io_context>())
Ed Tanous1abe55e2018-09-05 08:30:59 -070019 {
20 }
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +020021
Ed Tanous1abe55e2018-09-05 08:30:59 -070022 std::mutex m;
23 std::condition_variable cv;
24 std::unique_lock<std::mutex> lk;
Ed Tanous8f626352018-12-19 14:51:54 -080025 std::shared_ptr<boost::asio::io_context> io;
Ed Tanous1abe55e2018-09-05 08:30:59 -070026 int testPort = 45451;
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +020027};
28
Ed Tanous1abe55e2018-09-05 08:30:59 -070029TEST_F(TokenAuth, SpecialResourcesAreAcceptedWithoutAuth)
30{
31 CrowApp app(io);
32 crow::token_authorization::requestRoutes(app);
33 BMCWEB_ROUTE(app, "/redfish/v1")
34 ([]() { return boost::beast::http::status::ok; });
35 auto _ = std::async(std::launch::async, [&] {
36 app.port(testPort).run();
37 cv.notify_one();
38 io->run();
39 });
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +020040
Ed Tanous8f626352018-12-19 14:51:54 -080041 asio::io_context is;
Ed Tanous1abe55e2018-09-05 08:30:59 -070042 std::string sendmsg;
Ed Tanousf9273472017-02-28 16:05:13 -080043
Ed Tanous1abe55e2018-09-05 08:30:59 -070044 static char buf[2048];
Ed Tanousf9273472017-02-28 16:05:13 -080045
Ed Tanous1abe55e2018-09-05 08:30:59 -070046 // Homepage should be passed with no credentials
47 sendmsg = "GET /\r\n\r\n";
48 {
49 asio::ip::tcp::socket c(is);
50 c.connect(asio::ip::tcp::endpoint(
51 asio::ip::address::from_string("127.0.0.1"), 45451));
52 c.send(asio::buffer(sendmsg));
53 c.receive(asio::buffer(buf, 2048));
54 c.close();
55 EXPECT_EQ("200", std::string(buf + 9, buf + 12));
56 }
Ed Tanous1e94fa42017-04-03 13:41:19 -070057
Ed Tanous1abe55e2018-09-05 08:30:59 -070058 // static should be passed with no credentials
59 sendmsg = "GET /static/index.html\r\n\r\n";
60 {
61 asio::ip::tcp::socket c(is);
62 c.connect(asio::ip::tcp::endpoint(
63 asio::ip::address::from_string("127.0.0.1"), 45451));
64 c.send(asio::buffer(sendmsg));
65 c.receive(asio::buffer(buf, 2048));
66 c.close();
67 EXPECT_EQ("404", std::string(buf + 9, buf + 12));
68 }
Ed Tanous1e94fa42017-04-03 13:41:19 -070069
Ed Tanous1abe55e2018-09-05 08:30:59 -070070 app.stop();
Ed Tanous1e94fa42017-04-03 13:41:19 -070071}
72
73// Tests that Base64 basic strings work
Ed Tanous1abe55e2018-09-05 08:30:59 -070074TEST(TokenAuthentication, TestRejectedResource)
75{
76 App<crow::persistent_data::Middleware,
77 crow::token_authorization::Middleware>
78 app;
79 app.bindaddr("127.0.0.1").port(45451);
80 BMCWEB_ROUTE(app, "/")([]() { return boost::beast::http::status::ok; });
81 auto _ = async(std::launch::async, [&] { app.run(); });
Ed Tanous1e94fa42017-04-03 13:41:19 -070082
Ed Tanous8f626352018-12-19 14:51:54 -080083 asio::io_context is;
Ed Tanous1abe55e2018-09-05 08:30:59 -070084 static char buf[2048];
Ed Tanous1e94fa42017-04-03 13:41:19 -070085
Ed Tanous1abe55e2018-09-05 08:30:59 -070086 // Other resources should not be passed
87 std::string sendmsg = "GET /foo\r\n\r\n";
88 asio::ip::tcp::socket c(is);
89 for (int i = 0; i < 200; i++)
90 {
91 try
92 {
93 c.connect(asio::ip::tcp::endpoint(
94 asio::ip::address::from_string("127.0.0.1"), 45451));
95 }
96 catch (std::exception e)
97 {
98 // do nothing
99 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700100 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700101 c.send(asio::buffer(sendmsg));
102 c.receive(asio::buffer(buf, 2048));
103 c.close();
104 EXPECT_EQ("401", std::string(buf + 9, buf + 12));
Ed Tanous1e94fa42017-04-03 13:41:19 -0700105
Ed Tanous1abe55e2018-09-05 08:30:59 -0700106 app.stop();
Ed Tanous1e94fa42017-04-03 13:41:19 -0700107}
108
109// Tests that Base64 basic strings work
Ed Tanous1abe55e2018-09-05 08:30:59 -0700110TEST(TokenAuthentication, TestGetLoginUrl)
111{
112 App<crow::persistent_data::Middleware,
113 crow::token_authorization::Middleware>
114 app;
115 app.bindaddr("127.0.0.1").port(45451);
116 BMCWEB_ROUTE(app, "/")([]() { return boost::beast::http::status::ok; });
117 auto _ = async(std::launch::async, [&] { app.run(); });
Ed Tanous1e94fa42017-04-03 13:41:19 -0700118
Ed Tanous8f626352018-12-19 14:51:54 -0800119 asio::io_context is;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700120 static char buf[2048];
Ed Tanous1e94fa42017-04-03 13:41:19 -0700121
Ed Tanous1abe55e2018-09-05 08:30:59 -0700122 // Other resources should not be passed
123 std::string sendmsg = "GET /login\r\n\r\n";
124 asio::ip::tcp::socket c(is);
125 for (int i = 0; i < 200; i++)
126 {
127 try
128 {
129 c.connect(asio::ip::tcp::endpoint(
130 asio::ip::address::from_string("127.0.0.1"), 45451));
131 }
132 catch (std::exception e)
133 {
134 // do nothing
135 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700136 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700137 c.send(asio::buffer(sendmsg));
138 c.receive(asio::buffer(buf, 2048));
139 c.close();
140 EXPECT_EQ("401", std::string(buf + 9, buf + 12));
Ed Tanous1e94fa42017-04-03 13:41:19 -0700141
Ed Tanous1abe55e2018-09-05 08:30:59 -0700142 app.stop();
Ed Tanous1e94fa42017-04-03 13:41:19 -0700143}
144
145// Tests boundary conditions on login
Ed Tanous1abe55e2018-09-05 08:30:59 -0700146TEST(TokenAuthentication, TestPostBadLoginUrl)
147{
148 App<crow::persistent_data::Middleware,
149 crow::token_authorization::Middleware>
150 app;
151 app.bindaddr("127.0.0.1").port(45451);
152 BMCWEB_ROUTE(app, "/")([]() { return boost::beast::http::status::ok; });
153 auto _ = async(std::launch::async, [&] { app.run(); });
Ed Tanous1e94fa42017-04-03 13:41:19 -0700154
Ed Tanous8f626352018-12-19 14:51:54 -0800155 asio::io_context is;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700156 std::array<char, 2048> buf;
157 std::string sendmsg;
Ed Tanous1e94fa42017-04-03 13:41:19 -0700158
Ed Tanous1abe55e2018-09-05 08:30:59 -0700159 auto send_to_localhost = [&is, &buf](std::string sendmsg) {
160 asio::ip::tcp::socket c(is);
Ed Tanous1e94fa42017-04-03 13:41:19 -0700161 c.connect(asio::ip::tcp::endpoint(
162 asio::ip::address::from_string("127.0.0.1"), 45451));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700163 c.send(asio::buffer(sendmsg));
164 c.receive(asio::buffer(buf));
Ed Tanous1e94fa42017-04-03 13:41:19 -0700165 c.close();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700166 };
167
168 {
169 // Retry a couple of times waiting for the server to come up
170 asio::ip::tcp::socket c(is);
171 for (int i = 0; i < 200; i++)
172 {
173 try
174 {
175 c.connect(asio::ip::tcp::endpoint(
176 asio::ip::address::from_string("127.0.0.1"), 45451));
177 c.close();
178 break;
179 }
180 catch (std::exception e)
181 {
182 // do nothing. We expect this to fail while the server is
183 // starting up
184 }
185 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700186 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700187
Ed Tanous1abe55e2018-09-05 08:30:59 -0700188 // Test blank login credentials
189 sendmsg = "POST /login\r\nContent-Length:0\r\n\r\n\r\n";
190 {
191 send_to_localhost(sendmsg);
192 auto return_code = std::string(&buf[9], &buf[12]);
193 EXPECT_EQ("400", return_code);
194 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700195
Ed Tanous1abe55e2018-09-05 08:30:59 -0700196 // Test wrong login credentials
197 sendmsg = "POST /login\r\nContent-Length:38\r\n\r\n{\"username\": \"foo\", "
198 "\"password\": \"bar\"}\r\n";
199 {
200 send_to_localhost(sendmsg);
201 auto return_code = std::string(&buf[9], &buf[12]);
202 EXPECT_EQ("401", return_code);
203 // TODO(ed) need to test more here. Response string?
204 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700205
Ed Tanous1abe55e2018-09-05 08:30:59 -0700206 // Test only sending a username
207 sendmsg =
208 "POST /login\r\nContent-Length:19\r\n\r\n{\"username\": \"foo\"}\r\n";
209 {
210 send_to_localhost(sendmsg);
211 auto return_code = std::string(&buf[9], &buf[12]);
212 EXPECT_EQ("400", return_code);
213 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700214
Ed Tanous1abe55e2018-09-05 08:30:59 -0700215 // Test only sending a password
216 sendmsg =
217 "POST /login\r\nContent-Length:19\r\n\r\n{\"password\": \"foo\"}\r\n";
218 {
219 send_to_localhost(sendmsg);
220 auto return_code = std::string(&buf[9], &buf[12]);
221 EXPECT_EQ("400", return_code);
222 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700223
Ed Tanous1abe55e2018-09-05 08:30:59 -0700224 app.stop();
Ed Tanous1e94fa42017-04-03 13:41:19 -0700225}
226
Ed Tanous4d92cbf2017-06-22 15:41:02 -0700227// Test class that allows login for a fixed password.
Ed Tanous1abe55e2018-09-05 08:30:59 -0700228class KnownLoginAuthenticator
229{
230 public:
231 inline bool authenticate(const std::string& username,
232 const std::string& password)
233 {
234 return (username == "dude") && (password == "foo");
235 }
Ed Tanous4d92cbf2017-06-22 15:41:02 -0700236};
237
Ed Tanous1abe55e2018-09-05 08:30:59 -0700238TEST(TokenAuthentication, TestSuccessfulLogin)
239{
240 App<crow::persistent_data::Middleware,
241 crow::token_authorization::Middleware>
242 app;
243 app.bindaddr("127.0.0.1").port(45451);
244 BMCWEB_ROUTE(app, "/")([]() { return boost::beast::http::status::ok; });
245 auto _ = async(std::launch::async, [&] { app.run(); });
Ed Tanous1e94fa42017-04-03 13:41:19 -0700246
Ed Tanous8f626352018-12-19 14:51:54 -0800247 asio::io_context is;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700248 std::array<char, 2048> buf;
249 std::string sendmsg;
Ed Tanous1e94fa42017-04-03 13:41:19 -0700250
Ed Tanous1abe55e2018-09-05 08:30:59 -0700251 auto send_to_localhost = [&is, &buf](std::string sendmsg) {
252 asio::ip::tcp::socket c(is);
Ed Tanous1e94fa42017-04-03 13:41:19 -0700253 c.connect(asio::ip::tcp::endpoint(
254 asio::ip::address::from_string("127.0.0.1"), 45451));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700255 c.send(asio::buffer(sendmsg));
256 c.receive(asio::buffer(buf));
Ed Tanous1e94fa42017-04-03 13:41:19 -0700257 c.close();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700258 };
259
260 {
261 // Retry a couple of times waiting for the server to come up
262 asio::ip::tcp::socket c(is);
263 for (int i = 0; i < 200; i++)
264 {
265 try
266 {
267 c.connect(asio::ip::tcp::endpoint(
268 asio::ip::address::from_string("127.0.0.1"), 45451));
269 c.close();
270 break;
271 }
272 catch (std::exception e)
273 {
274 // do nothing. We expect this to fail while the server is
275 // starting up
276 }
277 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700278 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700279
Ed Tanous1abe55e2018-09-05 08:30:59 -0700280 // Test correct login credentials
281 sendmsg =
282 "POST /login\r\nContent-Length:40\r\n\r\n{\"username\": \"dude\", "
283 "\"password\": \"foo\"}\r\n";
284 {
285 send_to_localhost(sendmsg);
286 std::string response(std::begin(buf), std::end(buf));
287 // This is a routine to split strings until a newline is hit
288 // TODO(ed) this should really use the HTTP parser
289 std::vector<std::string> headers;
290 std::string::size_type pos = 0;
291 std::string::size_type prev = 0;
292 int content_length = 0;
293 std::string content_encoding("");
294 while ((pos = response.find("\r\n", prev)) != std::string::npos)
295 {
296 auto this_string = response.substr(prev, pos - prev);
297 if (this_string == "")
298 {
299 prev = pos + 2;
300 break;
301 }
Ed Tanousb4a7bfa2017-04-04 17:23:00 -0700302
Ed Tanous1abe55e2018-09-05 08:30:59 -0700303 headers.push_back(this_string);
304 prev = pos + 2;
305 }
306 EXPECT_EQ(headers[0], "HTTP/1.1 200 OK");
307 EXPECT_THAT(headers,
308 testing::Contains("Content-Type: application/json"));
309 auto http_content = response.substr(prev);
Ed Tanousb4a7bfa2017-04-04 17:23:00 -0700310 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700311
Ed Tanous1abe55e2018-09-05 08:30:59 -0700312 // Try to use those login credentials to access a resource
313 sendmsg = "GET /\r\nAuthorization: token\r\n\r\n{\"username\": \"dude\", "
314 "\"password\": \"dude\"}\r\n";
315 {
316 send_to_localhost(sendmsg);
317 auto return_code = std::string(&buf[9], &buf[12]);
318 EXPECT_EQ("200", return_code);
319 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700320
Ed Tanous1abe55e2018-09-05 08:30:59 -0700321 app.stop();
Ed Tanous8041f312017-04-03 09:47:01 -0700322}