blob: 47acc857fd05881ede5e79b23238d8e668a536b1 [file] [log] [blame]
Ed Tanous9140a672017-04-24 17:01:32 -07001#include "token_authorization_middleware.hpp"
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +02002#include <condition_variable>
3#include <future>
4#include <mutex>
5#include "webserver_common.hpp"
Ed Tanousb4a7bfa2017-04-04 17:23:00 -07006#include "gmock/gmock.h"
Ed Tanous1ff48782017-04-18 12:45:08 -07007#include "gtest/gtest.h"
Ed Tanousf9273472017-02-28 16:05:13 -08008
Ed Tanous8041f312017-04-03 09:47:01 -07009using namespace crow;
Ed Tanous8041f312017-04-03 09:47:01 -070010
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +020011class TokenAuth : public ::testing::Test {
12 public:
13 TokenAuth()
14 : lk(std::unique_lock<std::mutex>(m)),
15 io(std::make_shared<boost::asio::io_service>()) {}
16
17 std::mutex m;
18 std::condition_variable cv;
19 std::unique_lock<std::mutex> lk;
20 std::shared_ptr<boost::asio::io_service> io;
21 int testPort = 45451;
22};
23
24TEST_F(TokenAuth, SpecialResourcesAreAcceptedWithoutAuth) {
25 CrowApp app(io);
26 crow::TokenAuthorization::request_routes(app);
27 CROW_ROUTE(app, "/redfish/v1")
28 ([]() { return boost::beast::http::status::ok; });
29 auto _ = std::async(std::launch::async, [&] {
30 app.port(testPort).run();
31 cv.notify_one();
32 io->run();
33 });
34
Ed Tanous1e94fa42017-04-03 13:41:19 -070035 asio::io_service is;
36 std::string sendmsg;
Ed Tanousf9273472017-02-28 16:05:13 -080037
Ed Tanous1e94fa42017-04-03 13:41:19 -070038 static char buf[2048];
Ed Tanousf9273472017-02-28 16:05:13 -080039
Ed Tanous1e94fa42017-04-03 13:41:19 -070040 // Homepage should be passed with no credentials
41 sendmsg = "GET /\r\n\r\n";
42 {
Ed Tanous8041f312017-04-03 09:47:01 -070043 asio::ip::tcp::socket c(is);
Ed Tanous1e94fa42017-04-03 13:41:19 -070044 c.connect(asio::ip::tcp::endpoint(
45 asio::ip::address::from_string("127.0.0.1"), 45451));
Ed Tanous8041f312017-04-03 09:47:01 -070046 c.send(asio::buffer(sendmsg));
Ed Tanous7d3dba42017-04-05 13:04:39 -070047 c.receive(asio::buffer(buf, 2048));
Ed Tanous8041f312017-04-03 09:47:01 -070048 c.close();
Ed Tanous1e94fa42017-04-03 13:41:19 -070049 EXPECT_EQ("200", std::string(buf + 9, buf + 12));
50 }
51
52 // static should be passed with no credentials
53 sendmsg = "GET /static/index.html\r\n\r\n";
54 {
55 asio::ip::tcp::socket c(is);
56 c.connect(asio::ip::tcp::endpoint(
57 asio::ip::address::from_string("127.0.0.1"), 45451));
58 c.send(asio::buffer(sendmsg));
59 c.receive(asio::buffer(buf, 2048));
60 c.close();
61 EXPECT_EQ("404", std::string(buf + 9, buf + 12));
62 }
63
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +020064 app.stop();
Ed Tanous1e94fa42017-04-03 13:41:19 -070065}
66
67// Tests that Base64 basic strings work
68TEST(TokenAuthentication, TestRejectedResource) {
Ed Tanousba9f9a62017-10-11 16:40:35 -070069 App<crow::PersistentData::Middleware, crow::TokenAuthorization::Middleware>
70 app;
Ed Tanous1e94fa42017-04-03 13:41:19 -070071 app.bindaddr("127.0.0.1").port(45451);
Ed Tanouse0d918b2018-03-27 17:41:04 -070072 CROW_ROUTE(app, "/")([]() { return boost::beast::http::status::ok; });
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +020073 auto _ = async(std::launch::async, [&] { app.run(); });
Ed Tanous1e94fa42017-04-03 13:41:19 -070074
75 asio::io_service is;
76 static char buf[2048];
77
78 // Other resources should not be passed
79 std::string sendmsg = "GET /foo\r\n\r\n";
80 asio::ip::tcp::socket c(is);
81 for (int i = 0; i < 200; i++) {
82 try {
83 c.connect(asio::ip::tcp::endpoint(
84 asio::ip::address::from_string("127.0.0.1"), 45451));
85 } catch (std::exception e) {
86 // do nothing
87 }
88 }
89 c.send(asio::buffer(sendmsg));
Ed Tanous7d3dba42017-04-05 13:04:39 -070090 c.receive(asio::buffer(buf, 2048));
Ed Tanous1e94fa42017-04-03 13:41:19 -070091 c.close();
92 EXPECT_EQ("401", std::string(buf + 9, buf + 12));
93
94 app.stop();
95}
96
97// Tests that Base64 basic strings work
98TEST(TokenAuthentication, TestGetLoginUrl) {
Ed Tanousba9f9a62017-10-11 16:40:35 -070099 App<crow::PersistentData::Middleware, crow::TokenAuthorization::Middleware>
100 app;
Ed Tanous1e94fa42017-04-03 13:41:19 -0700101 app.bindaddr("127.0.0.1").port(45451);
Ed Tanouse0d918b2018-03-27 17:41:04 -0700102 CROW_ROUTE(app, "/")([]() { return boost::beast::http::status::ok; });
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200103 auto _ = async(std::launch::async, [&] { app.run(); });
Ed Tanous1e94fa42017-04-03 13:41:19 -0700104
105 asio::io_service is;
106 static char buf[2048];
107
108 // Other resources should not be passed
109 std::string sendmsg = "GET /login\r\n\r\n";
110 asio::ip::tcp::socket c(is);
111 for (int i = 0; i < 200; i++) {
112 try {
113 c.connect(asio::ip::tcp::endpoint(
114 asio::ip::address::from_string("127.0.0.1"), 45451));
115 } catch (std::exception e) {
116 // do nothing
117 }
118 }
119 c.send(asio::buffer(sendmsg));
Ed Tanous7d3dba42017-04-05 13:04:39 -0700120 c.receive(asio::buffer(buf, 2048));
Ed Tanous1e94fa42017-04-03 13:41:19 -0700121 c.close();
122 EXPECT_EQ("401", std::string(buf + 9, buf + 12));
123
124 app.stop();
125}
126
127// Tests boundary conditions on login
128TEST(TokenAuthentication, TestPostBadLoginUrl) {
Ed Tanousba9f9a62017-10-11 16:40:35 -0700129 App<crow::PersistentData::Middleware, crow::TokenAuthorization::Middleware>
130 app;
Ed Tanous1e94fa42017-04-03 13:41:19 -0700131 app.bindaddr("127.0.0.1").port(45451);
Ed Tanouse0d918b2018-03-27 17:41:04 -0700132 CROW_ROUTE(app, "/")([]() { return boost::beast::http::status::ok; });
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200133 auto _ = async(std::launch::async, [&] { app.run(); });
Ed Tanous1e94fa42017-04-03 13:41:19 -0700134
135 asio::io_service is;
136 std::array<char, 2048> buf;
137 std::string sendmsg;
138
139 auto send_to_localhost = [&is, &buf](std::string sendmsg) {
140 asio::ip::tcp::socket c(is);
141 c.connect(asio::ip::tcp::endpoint(
142 asio::ip::address::from_string("127.0.0.1"), 45451));
143 c.send(asio::buffer(sendmsg));
Ed Tanous7d3dba42017-04-05 13:04:39 -0700144 c.receive(asio::buffer(buf));
Ed Tanous1e94fa42017-04-03 13:41:19 -0700145 c.close();
146 };
147
148 {
149 // Retry a couple of times waiting for the server to come up
150 asio::ip::tcp::socket c(is);
151 for (int i = 0; i < 200; i++) {
152 try {
153 c.connect(asio::ip::tcp::endpoint(
154 asio::ip::address::from_string("127.0.0.1"), 45451));
155 c.close();
156 break;
157 } catch (std::exception e) {
158 // do nothing. We expect this to fail while the server is starting up
159 }
160 }
161 }
162
163 // Test blank login credentials
164 sendmsg = "POST /login\r\nContent-Length:0\r\n\r\n\r\n";
165 {
166 send_to_localhost(sendmsg);
167 auto return_code = std::string(&buf[9], &buf[12]);
168 EXPECT_EQ("400", return_code);
169 }
170
171 // Test wrong login credentials
172 sendmsg =
173 "POST /login\r\nContent-Length:38\r\n\r\n{\"username\": \"foo\", "
174 "\"password\": \"bar\"}\r\n";
175 {
176 send_to_localhost(sendmsg);
177 auto return_code = std::string(&buf[9], &buf[12]);
178 EXPECT_EQ("401", return_code);
179 // TODO(ed) need to test more here. Response string?
180 }
181
182 // Test only sending a username
183 sendmsg =
184 "POST /login\r\nContent-Length:19\r\n\r\n{\"username\": \"foo\"}\r\n";
185 {
186 send_to_localhost(sendmsg);
187 auto return_code = std::string(&buf[9], &buf[12]);
188 EXPECT_EQ("400", return_code);
189 }
190
191 // Test only sending a password
192 sendmsg =
193 "POST /login\r\nContent-Length:19\r\n\r\n{\"password\": \"foo\"}\r\n";
194 {
195 send_to_localhost(sendmsg);
196 auto return_code = std::string(&buf[9], &buf[12]);
197 EXPECT_EQ("400", return_code);
198 }
199
200 app.stop();
201}
202
Ed Tanous4d92cbf2017-06-22 15:41:02 -0700203// Test class that allows login for a fixed password.
204class KnownLoginAuthenticator {
205 public:
206 inline bool authenticate(const std::string& username,
207 const std::string& password) {
208 return (username == "dude") && (password == "foo");
209 }
210};
211
Ed Tanous1e94fa42017-04-03 13:41:19 -0700212TEST(TokenAuthentication, TestSuccessfulLogin) {
Ed Tanousba9f9a62017-10-11 16:40:35 -0700213 App<crow::PersistentData::Middleware, crow::TokenAuthorization::Middleware>
214 app;
Ed Tanous1e94fa42017-04-03 13:41:19 -0700215 app.bindaddr("127.0.0.1").port(45451);
Ed Tanouse0d918b2018-03-27 17:41:04 -0700216 CROW_ROUTE(app, "/")([]() { return boost::beast::http::status::ok; });
Borawski.Lukasz4b1b8682018-04-04 12:50:16 +0200217 auto _ = async(std::launch::async, [&] { app.run(); });
Ed Tanous1e94fa42017-04-03 13:41:19 -0700218
219 asio::io_service is;
220 std::array<char, 2048> buf;
221 std::string sendmsg;
222
223 auto send_to_localhost = [&is, &buf](std::string sendmsg) {
224 asio::ip::tcp::socket c(is);
225 c.connect(asio::ip::tcp::endpoint(
226 asio::ip::address::from_string("127.0.0.1"), 45451));
227 c.send(asio::buffer(sendmsg));
Ed Tanous7d3dba42017-04-05 13:04:39 -0700228 c.receive(asio::buffer(buf));
Ed Tanous1e94fa42017-04-03 13:41:19 -0700229 c.close();
230 };
231
232 {
233 // Retry a couple of times waiting for the server to come up
234 asio::ip::tcp::socket c(is);
235 for (int i = 0; i < 200; i++) {
236 try {
237 c.connect(asio::ip::tcp::endpoint(
238 asio::ip::address::from_string("127.0.0.1"), 45451));
239 c.close();
240 break;
241 } catch (std::exception e) {
242 // do nothing. We expect this to fail while the server is starting up
243 }
244 }
245 }
246
247 // Test correct login credentials
248 sendmsg =
249 "POST /login\r\nContent-Length:40\r\n\r\n{\"username\": \"dude\", "
Ed Tanousf3d847c2017-06-12 16:01:42 -0700250 "\"password\": \"foo\"}\r\n";
Ed Tanous1e94fa42017-04-03 13:41:19 -0700251 {
252 send_to_localhost(sendmsg);
Ed Tanousb4a7bfa2017-04-04 17:23:00 -0700253 std::string response(std::begin(buf), std::end(buf));
254 // This is a routine to split strings until a newline is hit
255 // TODO(ed) this should really use the HTTP parser
256 std::vector<std::string> headers;
257 std::string::size_type pos = 0;
258 std::string::size_type prev = 0;
259 int content_length = 0;
260 std::string content_encoding("");
261 while ((pos = response.find("\r\n", prev)) != std::string::npos) {
262 auto this_string = response.substr(prev, pos - prev);
263 if (this_string == "") {
264 prev = pos + 2;
265 break;
266 }
267
268 headers.push_back(this_string);
269 prev = pos + 2;
270 }
271 EXPECT_EQ(headers[0], "HTTP/1.1 200 OK");
272 EXPECT_THAT(headers, testing::Contains("Content-Type: application/json"));
273 auto http_content = response.substr(prev);
Ed Tanous1e94fa42017-04-03 13:41:19 -0700274 }
275
Ed Tanous1e94fa42017-04-03 13:41:19 -0700276 // Try to use those login credentials to access a resource
277 sendmsg =
278 "GET /\r\nAuthorization: token\r\n\r\n{\"username\": \"dude\", "
279 "\"password\": \"dude\"}\r\n";
280 {
281 send_to_localhost(sendmsg);
282 auto return_code = std::string(&buf[9], &buf[12]);
283 EXPECT_EQ("200", return_code);
284 }
285
286 app.stop();
Ed Tanous8041f312017-04-03 09:47:01 -0700287}