blob: 004fddbb358325e340e0b8f2ee70a5e40b956176 [file] [log] [blame]
Ed Tanous9140a672017-04-24 17:01:32 -07001#include "token_authorization_middleware.hpp"
Ed Tanousf9273472017-02-28 16:05:13 -08002#include <crow/app.h>
Ed Tanousb4a7bfa2017-04-04 17:23:00 -07003#include "gmock/gmock.h"
Ed Tanous1ff48782017-04-18 12:45:08 -07004#include "gtest/gtest.h"
Ed Tanousf9273472017-02-28 16:05:13 -08005
Ed Tanous8041f312017-04-03 09:47:01 -07006using namespace crow;
7using namespace std;
8
Ed Tanous1e94fa42017-04-03 13:41:19 -07009// Tests that static urls are correctly passed
10TEST(TokenAuthentication, TestBasicReject) {
Ed Tanous911ac312017-08-15 09:37:42 -070011 App<crow::TokenAuthorization::Middleware> app;
Ed Tanous1e94fa42017-04-03 13:41:19 -070012 decltype(app)::server_t server(&app, "127.0.0.1", 45451);
13 CROW_ROUTE(app, "/")([]() { return 200; });
14 auto _ = async(launch::async, [&] { server.run(); });
15 asio::io_service is;
16 std::string sendmsg;
Ed Tanousf9273472017-02-28 16:05:13 -080017
Ed Tanous1e94fa42017-04-03 13:41:19 -070018 static char buf[2048];
Ed Tanousf9273472017-02-28 16:05:13 -080019
Ed Tanous1e94fa42017-04-03 13:41:19 -070020 // Homepage should be passed with no credentials
21 sendmsg = "GET /\r\n\r\n";
22 {
Ed Tanous8041f312017-04-03 09:47:01 -070023 asio::ip::tcp::socket c(is);
Ed Tanous1e94fa42017-04-03 13:41:19 -070024 c.connect(asio::ip::tcp::endpoint(
25 asio::ip::address::from_string("127.0.0.1"), 45451));
Ed Tanous8041f312017-04-03 09:47:01 -070026 c.send(asio::buffer(sendmsg));
Ed Tanous7d3dba42017-04-05 13:04:39 -070027 c.receive(asio::buffer(buf, 2048));
Ed Tanous8041f312017-04-03 09:47:01 -070028 c.close();
Ed Tanous1e94fa42017-04-03 13:41:19 -070029 EXPECT_EQ("200", std::string(buf + 9, buf + 12));
30 }
31
32 // static should be passed with no credentials
33 sendmsg = "GET /static/index.html\r\n\r\n";
34 {
35 asio::ip::tcp::socket c(is);
36 c.connect(asio::ip::tcp::endpoint(
37 asio::ip::address::from_string("127.0.0.1"), 45451));
38 c.send(asio::buffer(sendmsg));
39 c.receive(asio::buffer(buf, 2048));
40 c.close();
41 EXPECT_EQ("404", std::string(buf + 9, buf + 12));
42 }
43
44 server.stop();
45}
46
47// Tests that Base64 basic strings work
48TEST(TokenAuthentication, TestRejectedResource) {
Ed Tanous911ac312017-08-15 09:37:42 -070049 App<crow::TokenAuthorization::Middleware> app;
Ed Tanous1e94fa42017-04-03 13:41:19 -070050 app.bindaddr("127.0.0.1").port(45451);
51 CROW_ROUTE(app, "/")([]() { return 200; });
52 auto _ = async(launch::async, [&] { app.run(); });
53
54 asio::io_service is;
55 static char buf[2048];
56
57 // Other resources should not be passed
58 std::string sendmsg = "GET /foo\r\n\r\n";
59 asio::ip::tcp::socket c(is);
60 for (int i = 0; i < 200; i++) {
61 try {
62 c.connect(asio::ip::tcp::endpoint(
63 asio::ip::address::from_string("127.0.0.1"), 45451));
64 } catch (std::exception e) {
65 // do nothing
66 }
67 }
68 c.send(asio::buffer(sendmsg));
Ed Tanous7d3dba42017-04-05 13:04:39 -070069 c.receive(asio::buffer(buf, 2048));
Ed Tanous1e94fa42017-04-03 13:41:19 -070070 c.close();
71 EXPECT_EQ("401", std::string(buf + 9, buf + 12));
72
73 app.stop();
74}
75
76// Tests that Base64 basic strings work
77TEST(TokenAuthentication, TestGetLoginUrl) {
Ed Tanous911ac312017-08-15 09:37:42 -070078 App<crow::TokenAuthorization::Middleware> app;
Ed Tanous1e94fa42017-04-03 13:41:19 -070079 app.bindaddr("127.0.0.1").port(45451);
80 CROW_ROUTE(app, "/")([]() { return 200; });
81 auto _ = async(launch::async, [&] { app.run(); });
82
83 asio::io_service is;
84 static char buf[2048];
85
86 // Other resources should not be passed
87 std::string sendmsg = "GET /login\r\n\r\n";
88 asio::ip::tcp::socket c(is);
89 for (int i = 0; i < 200; i++) {
90 try {
91 c.connect(asio::ip::tcp::endpoint(
92 asio::ip::address::from_string("127.0.0.1"), 45451));
93 } catch (std::exception e) {
94 // do nothing
95 }
96 }
97 c.send(asio::buffer(sendmsg));
Ed Tanous7d3dba42017-04-05 13:04:39 -070098 c.receive(asio::buffer(buf, 2048));
Ed Tanous1e94fa42017-04-03 13:41:19 -070099 c.close();
100 EXPECT_EQ("401", std::string(buf + 9, buf + 12));
101
102 app.stop();
103}
104
105// Tests boundary conditions on login
106TEST(TokenAuthentication, TestPostBadLoginUrl) {
Ed Tanous911ac312017-08-15 09:37:42 -0700107 App<crow::TokenAuthorization::Middleware> app;
Ed Tanous1e94fa42017-04-03 13:41:19 -0700108 app.bindaddr("127.0.0.1").port(45451);
109 CROW_ROUTE(app, "/")([]() { return 200; });
110 auto _ = async(launch::async, [&] { app.run(); });
111
112 asio::io_service is;
113 std::array<char, 2048> buf;
114 std::string sendmsg;
115
116 auto send_to_localhost = [&is, &buf](std::string sendmsg) {
117 asio::ip::tcp::socket c(is);
118 c.connect(asio::ip::tcp::endpoint(
119 asio::ip::address::from_string("127.0.0.1"), 45451));
120 c.send(asio::buffer(sendmsg));
Ed Tanous7d3dba42017-04-05 13:04:39 -0700121 c.receive(asio::buffer(buf));
Ed Tanous1e94fa42017-04-03 13:41:19 -0700122 c.close();
123 };
124
125 {
126 // Retry a couple of times waiting for the server to come up
127 asio::ip::tcp::socket c(is);
128 for (int i = 0; i < 200; i++) {
129 try {
130 c.connect(asio::ip::tcp::endpoint(
131 asio::ip::address::from_string("127.0.0.1"), 45451));
132 c.close();
133 break;
134 } catch (std::exception e) {
135 // do nothing. We expect this to fail while the server is starting up
136 }
137 }
138 }
139
140 // Test blank login credentials
141 sendmsg = "POST /login\r\nContent-Length:0\r\n\r\n\r\n";
142 {
143 send_to_localhost(sendmsg);
144 auto return_code = std::string(&buf[9], &buf[12]);
145 EXPECT_EQ("400", return_code);
146 }
147
148 // Test wrong login credentials
149 sendmsg =
150 "POST /login\r\nContent-Length:38\r\n\r\n{\"username\": \"foo\", "
151 "\"password\": \"bar\"}\r\n";
152 {
153 send_to_localhost(sendmsg);
154 auto return_code = std::string(&buf[9], &buf[12]);
155 EXPECT_EQ("401", return_code);
156 // TODO(ed) need to test more here. Response string?
157 }
158
159 // Test only sending a username
160 sendmsg =
161 "POST /login\r\nContent-Length:19\r\n\r\n{\"username\": \"foo\"}\r\n";
162 {
163 send_to_localhost(sendmsg);
164 auto return_code = std::string(&buf[9], &buf[12]);
165 EXPECT_EQ("400", return_code);
166 }
167
168 // Test only sending a password
169 sendmsg =
170 "POST /login\r\nContent-Length:19\r\n\r\n{\"password\": \"foo\"}\r\n";
171 {
172 send_to_localhost(sendmsg);
173 auto return_code = std::string(&buf[9], &buf[12]);
174 EXPECT_EQ("400", return_code);
175 }
176
177 app.stop();
178}
179
Ed Tanous4d92cbf2017-06-22 15:41:02 -0700180// Test class that allows login for a fixed password.
181class KnownLoginAuthenticator {
182 public:
183 inline bool authenticate(const std::string& username,
184 const std::string& password) {
185 return (username == "dude") && (password == "foo");
186 }
187};
188
Ed Tanous1e94fa42017-04-03 13:41:19 -0700189TEST(TokenAuthentication, TestSuccessfulLogin) {
Ed Tanous911ac312017-08-15 09:37:42 -0700190 App<crow::TokenAuthorization::Middleware> app;
Ed Tanous1e94fa42017-04-03 13:41:19 -0700191 app.bindaddr("127.0.0.1").port(45451);
192 CROW_ROUTE(app, "/")([]() { return 200; });
193 auto _ = async(launch::async, [&] { app.run(); });
194
195 asio::io_service is;
196 std::array<char, 2048> buf;
197 std::string sendmsg;
198
199 auto send_to_localhost = [&is, &buf](std::string sendmsg) {
200 asio::ip::tcp::socket c(is);
201 c.connect(asio::ip::tcp::endpoint(
202 asio::ip::address::from_string("127.0.0.1"), 45451));
203 c.send(asio::buffer(sendmsg));
Ed Tanous7d3dba42017-04-05 13:04:39 -0700204 c.receive(asio::buffer(buf));
Ed Tanous1e94fa42017-04-03 13:41:19 -0700205 c.close();
206 };
207
208 {
209 // Retry a couple of times waiting for the server to come up
210 asio::ip::tcp::socket c(is);
211 for (int i = 0; i < 200; i++) {
212 try {
213 c.connect(asio::ip::tcp::endpoint(
214 asio::ip::address::from_string("127.0.0.1"), 45451));
215 c.close();
216 break;
217 } catch (std::exception e) {
218 // do nothing. We expect this to fail while the server is starting up
219 }
220 }
221 }
222
223 // Test correct login credentials
224 sendmsg =
225 "POST /login\r\nContent-Length:40\r\n\r\n{\"username\": \"dude\", "
Ed Tanousf3d847c2017-06-12 16:01:42 -0700226 "\"password\": \"foo\"}\r\n";
Ed Tanous1e94fa42017-04-03 13:41:19 -0700227 {
228 send_to_localhost(sendmsg);
Ed Tanousb4a7bfa2017-04-04 17:23:00 -0700229 std::string response(std::begin(buf), std::end(buf));
230 // This is a routine to split strings until a newline is hit
231 // TODO(ed) this should really use the HTTP parser
232 std::vector<std::string> headers;
233 std::string::size_type pos = 0;
234 std::string::size_type prev = 0;
235 int content_length = 0;
236 std::string content_encoding("");
237 while ((pos = response.find("\r\n", prev)) != std::string::npos) {
238 auto this_string = response.substr(prev, pos - prev);
239 if (this_string == "") {
240 prev = pos + 2;
241 break;
242 }
243
244 headers.push_back(this_string);
245 prev = pos + 2;
246 }
247 EXPECT_EQ(headers[0], "HTTP/1.1 200 OK");
248 EXPECT_THAT(headers, testing::Contains("Content-Type: application/json"));
249 auto http_content = response.substr(prev);
Ed Tanous1e94fa42017-04-03 13:41:19 -0700250 }
251
Ed Tanous1e94fa42017-04-03 13:41:19 -0700252 // Try to use those login credentials to access a resource
253 sendmsg =
254 "GET /\r\nAuthorization: token\r\n\r\n{\"username\": \"dude\", "
255 "\"password\": \"dude\"}\r\n";
256 {
257 send_to_localhost(sendmsg);
258 auto return_code = std::string(&buf[9], &buf[12]);
259 EXPECT_EQ("200", return_code);
260 }
261
262 app.stop();
Ed Tanous8041f312017-04-03 09:47:01 -0700263}