blob: fcb5b65e80c3d7d072faed1f30f6844378e47c89 [file] [log] [blame]
Ed Tanous9140a672017-04-24 17:01:32 -07001#include "token_authorization_middleware.hpp"
Ed Tanous1abe55e2018-09-05 08:30:59 -07002
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +02003#include <condition_variable>
4#include <future>
5#include <mutex>
Ed Tanous1abe55e2018-09-05 08:30:59 -07006
Ed Tanousb4a7bfa2017-04-04 17:23:00 -07007#include "gmock/gmock.h"
Ed Tanous1ff48782017-04-18 12:45:08 -07008#include "gtest/gtest.h"
Ed Tanousf9273472017-02-28 16:05:13 -08009
Ed Tanous8041f312017-04-03 09:47:01 -070010using namespace crow;
Ed Tanous8041f312017-04-03 09:47:01 -070011
Ed Tanous1abe55e2018-09-05 08:30:59 -070012class TokenAuth : public ::testing::Test
13{
14 public:
15 TokenAuth() :
16 lk(std::unique_lock<std::mutex>(m)),
Ed Tanous8f626352018-12-19 14:51:54 -080017 io(std::make_shared<boost::asio::io_context>())
Gunnar Mills1214b7e2020-06-04 10:11:30 -050018 {}
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +020019
Ed Tanous1abe55e2018-09-05 08:30:59 -070020 std::mutex m;
21 std::condition_variable cv;
22 std::unique_lock<std::mutex> lk;
Ed Tanous8f626352018-12-19 14:51:54 -080023 std::shared_ptr<boost::asio::io_context> io;
Ed Tanous1abe55e2018-09-05 08:30:59 -070024 int testPort = 45451;
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +020025};
26
Ed Tanous1abe55e2018-09-05 08:30:59 -070027TEST_F(TokenAuth, SpecialResourcesAreAcceptedWithoutAuth)
28{
Ed Tanous52cc1122020-07-18 13:51:21 -070029 App app(io);
Ed Tanous1abe55e2018-09-05 08:30:59 -070030 crow::token_authorization::requestRoutes(app);
31 BMCWEB_ROUTE(app, "/redfish/v1")
32 ([]() { return boost::beast::http::status::ok; });
33 auto _ = std::async(std::launch::async, [&] {
34 app.port(testPort).run();
35 cv.notify_one();
36 io->run();
37 });
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +020038
Ed Tanous8f626352018-12-19 14:51:54 -080039 asio::io_context is;
Ed Tanous1abe55e2018-09-05 08:30:59 -070040 std::string sendmsg;
Ed Tanousf9273472017-02-28 16:05:13 -080041
Ed Tanous1abe55e2018-09-05 08:30:59 -070042 static char buf[2048];
Ed Tanousf9273472017-02-28 16:05:13 -080043
Ed Tanous1abe55e2018-09-05 08:30:59 -070044 // Homepage should be passed with no credentials
45 sendmsg = "GET /\r\n\r\n";
46 {
47 asio::ip::tcp::socket c(is);
48 c.connect(asio::ip::tcp::endpoint(
49 asio::ip::address::from_string("127.0.0.1"), 45451));
50 c.send(asio::buffer(sendmsg));
51 c.receive(asio::buffer(buf, 2048));
52 c.close();
53 EXPECT_EQ("200", std::string(buf + 9, buf + 12));
54 }
Ed Tanous1e94fa42017-04-03 13:41:19 -070055
Ed Tanous1abe55e2018-09-05 08:30:59 -070056 // static should be passed with no credentials
57 sendmsg = "GET /static/index.html\r\n\r\n";
58 {
59 asio::ip::tcp::socket c(is);
60 c.connect(asio::ip::tcp::endpoint(
61 asio::ip::address::from_string("127.0.0.1"), 45451));
62 c.send(asio::buffer(sendmsg));
63 c.receive(asio::buffer(buf, 2048));
64 c.close();
65 EXPECT_EQ("404", std::string(buf + 9, buf + 12));
66 }
Ed Tanous1e94fa42017-04-03 13:41:19 -070067
Ed Tanous1abe55e2018-09-05 08:30:59 -070068 app.stop();
Ed Tanous1e94fa42017-04-03 13:41:19 -070069}
70
71// Tests that Base64 basic strings work
Ed Tanous1abe55e2018-09-05 08:30:59 -070072TEST(TokenAuthentication, TestRejectedResource)
73{
Ed Tanous52cc1122020-07-18 13:51:21 -070074 App app;
Ed Tanous1abe55e2018-09-05 08:30:59 -070075 app.bindaddr("127.0.0.1").port(45451);
76 BMCWEB_ROUTE(app, "/")([]() { return boost::beast::http::status::ok; });
77 auto _ = async(std::launch::async, [&] { app.run(); });
Ed Tanous1e94fa42017-04-03 13:41:19 -070078
Ed Tanous8f626352018-12-19 14:51:54 -080079 asio::io_context is;
Ed Tanous1abe55e2018-09-05 08:30:59 -070080 static char buf[2048];
Ed Tanous1e94fa42017-04-03 13:41:19 -070081
Ed Tanous1abe55e2018-09-05 08:30:59 -070082 // Other resources should not be passed
83 std::string sendmsg = "GET /foo\r\n\r\n";
84 asio::ip::tcp::socket c(is);
85 for (int i = 0; i < 200; i++)
86 {
87 try
88 {
89 c.connect(asio::ip::tcp::endpoint(
90 asio::ip::address::from_string("127.0.0.1"), 45451));
91 }
92 catch (std::exception e)
93 {
94 // do nothing
95 }
Ed Tanous1e94fa42017-04-03 13:41:19 -070096 }
Ed Tanous1abe55e2018-09-05 08:30:59 -070097 c.send(asio::buffer(sendmsg));
98 c.receive(asio::buffer(buf, 2048));
99 c.close();
100 EXPECT_EQ("401", std::string(buf + 9, buf + 12));
Ed Tanous1e94fa42017-04-03 13:41:19 -0700101
Ed Tanous1abe55e2018-09-05 08:30:59 -0700102 app.stop();
Ed Tanous1e94fa42017-04-03 13:41:19 -0700103}
104
105// Tests that Base64 basic strings work
Ed Tanous1abe55e2018-09-05 08:30:59 -0700106TEST(TokenAuthentication, TestGetLoginUrl)
107{
Ed Tanous52cc1122020-07-18 13:51:21 -0700108 App app;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700109 app.bindaddr("127.0.0.1").port(45451);
110 BMCWEB_ROUTE(app, "/")([]() { return boost::beast::http::status::ok; });
111 auto _ = async(std::launch::async, [&] { app.run(); });
Ed Tanous1e94fa42017-04-03 13:41:19 -0700112
Ed Tanous8f626352018-12-19 14:51:54 -0800113 asio::io_context is;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700114 static char buf[2048];
Ed Tanous1e94fa42017-04-03 13:41:19 -0700115
Ed Tanous1abe55e2018-09-05 08:30:59 -0700116 // Other resources should not be passed
117 std::string sendmsg = "GET /login\r\n\r\n";
118 asio::ip::tcp::socket c(is);
119 for (int i = 0; i < 200; i++)
120 {
121 try
122 {
123 c.connect(asio::ip::tcp::endpoint(
124 asio::ip::address::from_string("127.0.0.1"), 45451));
125 }
126 catch (std::exception e)
127 {
128 // do nothing
129 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700130 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700131 c.send(asio::buffer(sendmsg));
132 c.receive(asio::buffer(buf, 2048));
133 c.close();
134 EXPECT_EQ("401", std::string(buf + 9, buf + 12));
Ed Tanous1e94fa42017-04-03 13:41:19 -0700135
Ed Tanous1abe55e2018-09-05 08:30:59 -0700136 app.stop();
Ed Tanous1e94fa42017-04-03 13:41:19 -0700137}
138
139// Tests boundary conditions on login
Ed Tanous1abe55e2018-09-05 08:30:59 -0700140TEST(TokenAuthentication, TestPostBadLoginUrl)
141{
Ed Tanous52cc1122020-07-18 13:51:21 -0700142 App app;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700143 app.bindaddr("127.0.0.1").port(45451);
144 BMCWEB_ROUTE(app, "/")([]() { return boost::beast::http::status::ok; });
145 auto _ = async(std::launch::async, [&] { app.run(); });
Ed Tanous1e94fa42017-04-03 13:41:19 -0700146
Ed Tanous8f626352018-12-19 14:51:54 -0800147 asio::io_context is;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700148 std::array<char, 2048> buf;
149 std::string sendmsg;
Ed Tanous1e94fa42017-04-03 13:41:19 -0700150
Ed Tanous1abe55e2018-09-05 08:30:59 -0700151 auto send_to_localhost = [&is, &buf](std::string sendmsg) {
152 asio::ip::tcp::socket c(is);
Ed Tanous1e94fa42017-04-03 13:41:19 -0700153 c.connect(asio::ip::tcp::endpoint(
154 asio::ip::address::from_string("127.0.0.1"), 45451));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700155 c.send(asio::buffer(sendmsg));
156 c.receive(asio::buffer(buf));
Ed Tanous1e94fa42017-04-03 13:41:19 -0700157 c.close();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700158 };
159
160 {
161 // Retry a couple of times waiting for the server to come up
162 asio::ip::tcp::socket c(is);
163 for (int i = 0; i < 200; i++)
164 {
165 try
166 {
167 c.connect(asio::ip::tcp::endpoint(
168 asio::ip::address::from_string("127.0.0.1"), 45451));
169 c.close();
170 break;
171 }
172 catch (std::exception e)
173 {
174 // do nothing. We expect this to fail while the server is
175 // starting up
176 }
177 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700178 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700179
Ed Tanous1abe55e2018-09-05 08:30:59 -0700180 // Test blank login credentials
181 sendmsg = "POST /login\r\nContent-Length:0\r\n\r\n\r\n";
182 {
183 send_to_localhost(sendmsg);
184 auto return_code = std::string(&buf[9], &buf[12]);
185 EXPECT_EQ("400", return_code);
186 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700187
Ed Tanous1abe55e2018-09-05 08:30:59 -0700188 // Test wrong login credentials
189 sendmsg = "POST /login\r\nContent-Length:38\r\n\r\n{\"username\": \"foo\", "
190 "\"password\": \"bar\"}\r\n";
191 {
192 send_to_localhost(sendmsg);
193 auto return_code = std::string(&buf[9], &buf[12]);
194 EXPECT_EQ("401", return_code);
195 // TODO(ed) need to test more here. Response string?
196 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700197
Ed Tanous1abe55e2018-09-05 08:30:59 -0700198 // Test only sending a username
199 sendmsg =
200 "POST /login\r\nContent-Length:19\r\n\r\n{\"username\": \"foo\"}\r\n";
201 {
202 send_to_localhost(sendmsg);
203 auto return_code = std::string(&buf[9], &buf[12]);
204 EXPECT_EQ("400", return_code);
205 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700206
Ed Tanous1abe55e2018-09-05 08:30:59 -0700207 // Test only sending a password
208 sendmsg =
209 "POST /login\r\nContent-Length:19\r\n\r\n{\"password\": \"foo\"}\r\n";
210 {
211 send_to_localhost(sendmsg);
212 auto return_code = std::string(&buf[9], &buf[12]);
213 EXPECT_EQ("400", return_code);
214 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700215
Ed Tanous1abe55e2018-09-05 08:30:59 -0700216 app.stop();
Ed Tanous1e94fa42017-04-03 13:41:19 -0700217}
218
Ed Tanous4d92cbf2017-06-22 15:41:02 -0700219// Test class that allows login for a fixed password.
Ed Tanous1abe55e2018-09-05 08:30:59 -0700220class KnownLoginAuthenticator
221{
222 public:
223 inline bool authenticate(const std::string& username,
224 const std::string& password)
225 {
226 return (username == "dude") && (password == "foo");
227 }
Ed Tanous4d92cbf2017-06-22 15:41:02 -0700228};
229
Ed Tanous1abe55e2018-09-05 08:30:59 -0700230TEST(TokenAuthentication, TestSuccessfulLogin)
231{
Ed Tanous52cc1122020-07-18 13:51:21 -0700232 App app;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700233 app.bindaddr("127.0.0.1").port(45451);
234 BMCWEB_ROUTE(app, "/")([]() { return boost::beast::http::status::ok; });
235 auto _ = async(std::launch::async, [&] { app.run(); });
Ed Tanous1e94fa42017-04-03 13:41:19 -0700236
Ed Tanous8f626352018-12-19 14:51:54 -0800237 asio::io_context is;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700238 std::array<char, 2048> buf;
239 std::string sendmsg;
Ed Tanous1e94fa42017-04-03 13:41:19 -0700240
Ed Tanous1abe55e2018-09-05 08:30:59 -0700241 auto send_to_localhost = [&is, &buf](std::string sendmsg) {
242 asio::ip::tcp::socket c(is);
Ed Tanous1e94fa42017-04-03 13:41:19 -0700243 c.connect(asio::ip::tcp::endpoint(
244 asio::ip::address::from_string("127.0.0.1"), 45451));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700245 c.send(asio::buffer(sendmsg));
246 c.receive(asio::buffer(buf));
Ed Tanous1e94fa42017-04-03 13:41:19 -0700247 c.close();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700248 };
249
250 {
251 // Retry a couple of times waiting for the server to come up
252 asio::ip::tcp::socket c(is);
253 for (int i = 0; i < 200; i++)
254 {
255 try
256 {
257 c.connect(asio::ip::tcp::endpoint(
258 asio::ip::address::from_string("127.0.0.1"), 45451));
259 c.close();
260 break;
261 }
262 catch (std::exception e)
263 {
264 // do nothing. We expect this to fail while the server is
265 // starting up
266 }
267 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700268 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700269
Ed Tanous1abe55e2018-09-05 08:30:59 -0700270 // Test correct login credentials
271 sendmsg =
272 "POST /login\r\nContent-Length:40\r\n\r\n{\"username\": \"dude\", "
273 "\"password\": \"foo\"}\r\n";
274 {
275 send_to_localhost(sendmsg);
276 std::string response(std::begin(buf), std::end(buf));
277 // This is a routine to split strings until a newline is hit
278 // TODO(ed) this should really use the HTTP parser
279 std::vector<std::string> headers;
280 std::string::size_type pos = 0;
281 std::string::size_type prev = 0;
282 int content_length = 0;
283 std::string content_encoding("");
284 while ((pos = response.find("\r\n", prev)) != std::string::npos)
285 {
286 auto this_string = response.substr(prev, pos - prev);
287 if (this_string == "")
288 {
289 prev = pos + 2;
290 break;
291 }
Ed Tanousb4a7bfa2017-04-04 17:23:00 -0700292
Ed Tanous1abe55e2018-09-05 08:30:59 -0700293 headers.push_back(this_string);
294 prev = pos + 2;
295 }
296 EXPECT_EQ(headers[0], "HTTP/1.1 200 OK");
297 EXPECT_THAT(headers,
298 testing::Contains("Content-Type: application/json"));
299 auto http_content = response.substr(prev);
Ed Tanousb4a7bfa2017-04-04 17:23:00 -0700300 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700301
Ed Tanous1abe55e2018-09-05 08:30:59 -0700302 // Try to use those login credentials to access a resource
303 sendmsg = "GET /\r\nAuthorization: token\r\n\r\n{\"username\": \"dude\", "
304 "\"password\": \"dude\"}\r\n";
305 {
306 send_to_localhost(sendmsg);
307 auto return_code = std::string(&buf[9], &buf[12]);
308 EXPECT_EQ("200", return_code);
309 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700310
Ed Tanous1abe55e2018-09-05 08:30:59 -0700311 app.stop();
Ed Tanous52cc1122020-07-18 13:51:21 -0700312}