blob: 3ac7947b948275b16a44119b77455cbb1c1116ec [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>())
Gunnar Mills1214b7e2020-06-04 10:11:30 -050019 {}
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +020020
Ed Tanous1abe55e2018-09-05 08:30:59 -070021 std::mutex m;
22 std::condition_variable cv;
23 std::unique_lock<std::mutex> lk;
Ed Tanous8f626352018-12-19 14:51:54 -080024 std::shared_ptr<boost::asio::io_context> io;
Ed Tanous1abe55e2018-09-05 08:30:59 -070025 int testPort = 45451;
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +020026};
27
Ed Tanous1abe55e2018-09-05 08:30:59 -070028TEST_F(TokenAuth, SpecialResourcesAreAcceptedWithoutAuth)
29{
30 CrowApp app(io);
31 crow::token_authorization::requestRoutes(app);
32 BMCWEB_ROUTE(app, "/redfish/v1")
33 ([]() { return boost::beast::http::status::ok; });
34 auto _ = std::async(std::launch::async, [&] {
35 app.port(testPort).run();
36 cv.notify_one();
37 io->run();
38 });
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +020039
Ed Tanous8f626352018-12-19 14:51:54 -080040 asio::io_context is;
Ed Tanous1abe55e2018-09-05 08:30:59 -070041 std::string sendmsg;
Ed Tanousf9273472017-02-28 16:05:13 -080042
Ed Tanous1abe55e2018-09-05 08:30:59 -070043 static char buf[2048];
Ed Tanousf9273472017-02-28 16:05:13 -080044
Ed Tanous1abe55e2018-09-05 08:30:59 -070045 // Homepage should be passed with no credentials
46 sendmsg = "GET /\r\n\r\n";
47 {
48 asio::ip::tcp::socket c(is);
49 c.connect(asio::ip::tcp::endpoint(
50 asio::ip::address::from_string("127.0.0.1"), 45451));
51 c.send(asio::buffer(sendmsg));
52 c.receive(asio::buffer(buf, 2048));
53 c.close();
54 EXPECT_EQ("200", std::string(buf + 9, buf + 12));
55 }
Ed Tanous1e94fa42017-04-03 13:41:19 -070056
Ed Tanous1abe55e2018-09-05 08:30:59 -070057 // static should be passed with no credentials
58 sendmsg = "GET /static/index.html\r\n\r\n";
59 {
60 asio::ip::tcp::socket c(is);
61 c.connect(asio::ip::tcp::endpoint(
62 asio::ip::address::from_string("127.0.0.1"), 45451));
63 c.send(asio::buffer(sendmsg));
64 c.receive(asio::buffer(buf, 2048));
65 c.close();
66 EXPECT_EQ("404", std::string(buf + 9, buf + 12));
67 }
Ed Tanous1e94fa42017-04-03 13:41:19 -070068
Ed Tanous1abe55e2018-09-05 08:30:59 -070069 app.stop();
Ed Tanous1e94fa42017-04-03 13:41:19 -070070}
71
72// Tests that Base64 basic strings work
Ed Tanous1abe55e2018-09-05 08:30:59 -070073TEST(TokenAuthentication, TestRejectedResource)
74{
75 App<crow::persistent_data::Middleware,
76 crow::token_authorization::Middleware>
77 app;
78 app.bindaddr("127.0.0.1").port(45451);
79 BMCWEB_ROUTE(app, "/")([]() { return boost::beast::http::status::ok; });
80 auto _ = async(std::launch::async, [&] { app.run(); });
Ed Tanous1e94fa42017-04-03 13:41:19 -070081
Ed Tanous8f626352018-12-19 14:51:54 -080082 asio::io_context is;
Ed Tanous1abe55e2018-09-05 08:30:59 -070083 static char buf[2048];
Ed Tanous1e94fa42017-04-03 13:41:19 -070084
Ed Tanous1abe55e2018-09-05 08:30:59 -070085 // Other resources should not be passed
86 std::string sendmsg = "GET /foo\r\n\r\n";
87 asio::ip::tcp::socket c(is);
88 for (int i = 0; i < 200; i++)
89 {
90 try
91 {
92 c.connect(asio::ip::tcp::endpoint(
93 asio::ip::address::from_string("127.0.0.1"), 45451));
94 }
95 catch (std::exception e)
96 {
97 // do nothing
98 }
Ed Tanous1e94fa42017-04-03 13:41:19 -070099 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700100 c.send(asio::buffer(sendmsg));
101 c.receive(asio::buffer(buf, 2048));
102 c.close();
103 EXPECT_EQ("401", std::string(buf + 9, buf + 12));
Ed Tanous1e94fa42017-04-03 13:41:19 -0700104
Ed Tanous1abe55e2018-09-05 08:30:59 -0700105 app.stop();
Ed Tanous1e94fa42017-04-03 13:41:19 -0700106}
107
108// Tests that Base64 basic strings work
Ed Tanous1abe55e2018-09-05 08:30:59 -0700109TEST(TokenAuthentication, TestGetLoginUrl)
110{
111 App<crow::persistent_data::Middleware,
112 crow::token_authorization::Middleware>
113 app;
114 app.bindaddr("127.0.0.1").port(45451);
115 BMCWEB_ROUTE(app, "/")([]() { return boost::beast::http::status::ok; });
116 auto _ = async(std::launch::async, [&] { app.run(); });
Ed Tanous1e94fa42017-04-03 13:41:19 -0700117
Ed Tanous8f626352018-12-19 14:51:54 -0800118 asio::io_context is;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700119 static char buf[2048];
Ed Tanous1e94fa42017-04-03 13:41:19 -0700120
Ed Tanous1abe55e2018-09-05 08:30:59 -0700121 // Other resources should not be passed
122 std::string sendmsg = "GET /login\r\n\r\n";
123 asio::ip::tcp::socket c(is);
124 for (int i = 0; i < 200; i++)
125 {
126 try
127 {
128 c.connect(asio::ip::tcp::endpoint(
129 asio::ip::address::from_string("127.0.0.1"), 45451));
130 }
131 catch (std::exception e)
132 {
133 // do nothing
134 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700135 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700136 c.send(asio::buffer(sendmsg));
137 c.receive(asio::buffer(buf, 2048));
138 c.close();
139 EXPECT_EQ("401", std::string(buf + 9, buf + 12));
Ed Tanous1e94fa42017-04-03 13:41:19 -0700140
Ed Tanous1abe55e2018-09-05 08:30:59 -0700141 app.stop();
Ed Tanous1e94fa42017-04-03 13:41:19 -0700142}
143
144// Tests boundary conditions on login
Ed Tanous1abe55e2018-09-05 08:30:59 -0700145TEST(TokenAuthentication, TestPostBadLoginUrl)
146{
147 App<crow::persistent_data::Middleware,
148 crow::token_authorization::Middleware>
149 app;
150 app.bindaddr("127.0.0.1").port(45451);
151 BMCWEB_ROUTE(app, "/")([]() { return boost::beast::http::status::ok; });
152 auto _ = async(std::launch::async, [&] { app.run(); });
Ed Tanous1e94fa42017-04-03 13:41:19 -0700153
Ed Tanous8f626352018-12-19 14:51:54 -0800154 asio::io_context is;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700155 std::array<char, 2048> buf;
156 std::string sendmsg;
Ed Tanous1e94fa42017-04-03 13:41:19 -0700157
Ed Tanous1abe55e2018-09-05 08:30:59 -0700158 auto send_to_localhost = [&is, &buf](std::string sendmsg) {
159 asio::ip::tcp::socket c(is);
Ed Tanous1e94fa42017-04-03 13:41:19 -0700160 c.connect(asio::ip::tcp::endpoint(
161 asio::ip::address::from_string("127.0.0.1"), 45451));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700162 c.send(asio::buffer(sendmsg));
163 c.receive(asio::buffer(buf));
Ed Tanous1e94fa42017-04-03 13:41:19 -0700164 c.close();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700165 };
166
167 {
168 // Retry a couple of times waiting for the server to come up
169 asio::ip::tcp::socket c(is);
170 for (int i = 0; i < 200; i++)
171 {
172 try
173 {
174 c.connect(asio::ip::tcp::endpoint(
175 asio::ip::address::from_string("127.0.0.1"), 45451));
176 c.close();
177 break;
178 }
179 catch (std::exception e)
180 {
181 // do nothing. We expect this to fail while the server is
182 // starting up
183 }
184 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700185 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700186
Ed Tanous1abe55e2018-09-05 08:30:59 -0700187 // Test blank login credentials
188 sendmsg = "POST /login\r\nContent-Length:0\r\n\r\n\r\n";
189 {
190 send_to_localhost(sendmsg);
191 auto return_code = std::string(&buf[9], &buf[12]);
192 EXPECT_EQ("400", return_code);
193 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700194
Ed Tanous1abe55e2018-09-05 08:30:59 -0700195 // Test wrong login credentials
196 sendmsg = "POST /login\r\nContent-Length:38\r\n\r\n{\"username\": \"foo\", "
197 "\"password\": \"bar\"}\r\n";
198 {
199 send_to_localhost(sendmsg);
200 auto return_code = std::string(&buf[9], &buf[12]);
201 EXPECT_EQ("401", return_code);
202 // TODO(ed) need to test more here. Response string?
203 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700204
Ed Tanous1abe55e2018-09-05 08:30:59 -0700205 // Test only sending a username
206 sendmsg =
207 "POST /login\r\nContent-Length:19\r\n\r\n{\"username\": \"foo\"}\r\n";
208 {
209 send_to_localhost(sendmsg);
210 auto return_code = std::string(&buf[9], &buf[12]);
211 EXPECT_EQ("400", return_code);
212 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700213
Ed Tanous1abe55e2018-09-05 08:30:59 -0700214 // Test only sending a password
215 sendmsg =
216 "POST /login\r\nContent-Length:19\r\n\r\n{\"password\": \"foo\"}\r\n";
217 {
218 send_to_localhost(sendmsg);
219 auto return_code = std::string(&buf[9], &buf[12]);
220 EXPECT_EQ("400", return_code);
221 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700222
Ed Tanous1abe55e2018-09-05 08:30:59 -0700223 app.stop();
Ed Tanous1e94fa42017-04-03 13:41:19 -0700224}
225
Ed Tanous4d92cbf2017-06-22 15:41:02 -0700226// Test class that allows login for a fixed password.
Ed Tanous1abe55e2018-09-05 08:30:59 -0700227class KnownLoginAuthenticator
228{
229 public:
230 inline bool authenticate(const std::string& username,
231 const std::string& password)
232 {
233 return (username == "dude") && (password == "foo");
234 }
Ed Tanous4d92cbf2017-06-22 15:41:02 -0700235};
236
Ed Tanous1abe55e2018-09-05 08:30:59 -0700237TEST(TokenAuthentication, TestSuccessfulLogin)
238{
239 App<crow::persistent_data::Middleware,
240 crow::token_authorization::Middleware>
241 app;
242 app.bindaddr("127.0.0.1").port(45451);
243 BMCWEB_ROUTE(app, "/")([]() { return boost::beast::http::status::ok; });
244 auto _ = async(std::launch::async, [&] { app.run(); });
Ed Tanous1e94fa42017-04-03 13:41:19 -0700245
Ed Tanous8f626352018-12-19 14:51:54 -0800246 asio::io_context is;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700247 std::array<char, 2048> buf;
248 std::string sendmsg;
Ed Tanous1e94fa42017-04-03 13:41:19 -0700249
Ed Tanous1abe55e2018-09-05 08:30:59 -0700250 auto send_to_localhost = [&is, &buf](std::string sendmsg) {
251 asio::ip::tcp::socket c(is);
Ed Tanous1e94fa42017-04-03 13:41:19 -0700252 c.connect(asio::ip::tcp::endpoint(
253 asio::ip::address::from_string("127.0.0.1"), 45451));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700254 c.send(asio::buffer(sendmsg));
255 c.receive(asio::buffer(buf));
Ed Tanous1e94fa42017-04-03 13:41:19 -0700256 c.close();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700257 };
258
259 {
260 // Retry a couple of times waiting for the server to come up
261 asio::ip::tcp::socket c(is);
262 for (int i = 0; i < 200; i++)
263 {
264 try
265 {
266 c.connect(asio::ip::tcp::endpoint(
267 asio::ip::address::from_string("127.0.0.1"), 45451));
268 c.close();
269 break;
270 }
271 catch (std::exception e)
272 {
273 // do nothing. We expect this to fail while the server is
274 // starting up
275 }
276 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700277 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700278
Ed Tanous1abe55e2018-09-05 08:30:59 -0700279 // Test correct login credentials
280 sendmsg =
281 "POST /login\r\nContent-Length:40\r\n\r\n{\"username\": \"dude\", "
282 "\"password\": \"foo\"}\r\n";
283 {
284 send_to_localhost(sendmsg);
285 std::string response(std::begin(buf), std::end(buf));
286 // This is a routine to split strings until a newline is hit
287 // TODO(ed) this should really use the HTTP parser
288 std::vector<std::string> headers;
289 std::string::size_type pos = 0;
290 std::string::size_type prev = 0;
291 int content_length = 0;
292 std::string content_encoding("");
293 while ((pos = response.find("\r\n", prev)) != std::string::npos)
294 {
295 auto this_string = response.substr(prev, pos - prev);
296 if (this_string == "")
297 {
298 prev = pos + 2;
299 break;
300 }
Ed Tanousb4a7bfa2017-04-04 17:23:00 -0700301
Ed Tanous1abe55e2018-09-05 08:30:59 -0700302 headers.push_back(this_string);
303 prev = pos + 2;
304 }
305 EXPECT_EQ(headers[0], "HTTP/1.1 200 OK");
306 EXPECT_THAT(headers,
307 testing::Contains("Content-Type: application/json"));
308 auto http_content = response.substr(prev);
Ed Tanousb4a7bfa2017-04-04 17:23:00 -0700309 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700310
Ed Tanous1abe55e2018-09-05 08:30:59 -0700311 // Try to use those login credentials to access a resource
312 sendmsg = "GET /\r\nAuthorization: token\r\n\r\n{\"username\": \"dude\", "
313 "\"password\": \"dude\"}\r\n";
314 {
315 send_to_localhost(sendmsg);
316 auto return_code = std::string(&buf[9], &buf[12]);
317 EXPECT_EQ("200", return_code);
318 }
Ed Tanous1e94fa42017-04-03 13:41:19 -0700319
Ed Tanous1abe55e2018-09-05 08:30:59 -0700320 app.stop();
Ed Tanous8041f312017-04-03 09:47:01 -0700321}