blob: e3a18f108cfe0d0c95d570eab454649e641b2314 [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 Tanous4d92cbf2017-06-22 15:41:02 -07009
Ed Tanousf3d847c2017-06-12 16:01:42 -070010
Ed Tanous1e94fa42017-04-03 13:41:19 -070011// Tests that static urls are correctly passed
12TEST(TokenAuthentication, TestBasicReject) {
13 App<crow::TokenAuthorizationMiddleware> app;
14 decltype(app)::server_t server(&app, "127.0.0.1", 45451);
15 CROW_ROUTE(app, "/")([]() { return 200; });
16 auto _ = async(launch::async, [&] { server.run(); });
17 asio::io_service is;
18 std::string sendmsg;
Ed Tanousf9273472017-02-28 16:05:13 -080019
Ed Tanous1e94fa42017-04-03 13:41:19 -070020 static char buf[2048];
Ed Tanousf9273472017-02-28 16:05:13 -080021
Ed Tanous1e94fa42017-04-03 13:41:19 -070022 // Homepage should be passed with no credentials
23 sendmsg = "GET /\r\n\r\n";
24 {
Ed Tanous8041f312017-04-03 09:47:01 -070025 asio::ip::tcp::socket c(is);
Ed Tanous1e94fa42017-04-03 13:41:19 -070026 c.connect(asio::ip::tcp::endpoint(
27 asio::ip::address::from_string("127.0.0.1"), 45451));
Ed Tanous8041f312017-04-03 09:47:01 -070028 c.send(asio::buffer(sendmsg));
Ed Tanous7d3dba42017-04-05 13:04:39 -070029 c.receive(asio::buffer(buf, 2048));
Ed Tanous8041f312017-04-03 09:47:01 -070030 c.close();
Ed Tanous1e94fa42017-04-03 13:41:19 -070031 EXPECT_EQ("200", std::string(buf + 9, buf + 12));
32 }
33
34 // static should be passed with no credentials
35 sendmsg = "GET /static/index.html\r\n\r\n";
36 {
37 asio::ip::tcp::socket c(is);
38 c.connect(asio::ip::tcp::endpoint(
39 asio::ip::address::from_string("127.0.0.1"), 45451));
40 c.send(asio::buffer(sendmsg));
41 c.receive(asio::buffer(buf, 2048));
42 c.close();
43 EXPECT_EQ("404", std::string(buf + 9, buf + 12));
44 }
45
46 server.stop();
47}
48
49// Tests that Base64 basic strings work
50TEST(TokenAuthentication, TestRejectedResource) {
51 App<crow::TokenAuthorizationMiddleware> app;
52 app.bindaddr("127.0.0.1").port(45451);
53 CROW_ROUTE(app, "/")([]() { return 200; });
54 auto _ = async(launch::async, [&] { app.run(); });
55
56 asio::io_service is;
57 static char buf[2048];
58
59 // Other resources should not be passed
60 std::string sendmsg = "GET /foo\r\n\r\n";
61 asio::ip::tcp::socket c(is);
62 for (int i = 0; i < 200; i++) {
63 try {
64 c.connect(asio::ip::tcp::endpoint(
65 asio::ip::address::from_string("127.0.0.1"), 45451));
66 } catch (std::exception e) {
67 // do nothing
68 }
69 }
70 c.send(asio::buffer(sendmsg));
Ed Tanous7d3dba42017-04-05 13:04:39 -070071 c.receive(asio::buffer(buf, 2048));
Ed Tanous1e94fa42017-04-03 13:41:19 -070072 c.close();
73 EXPECT_EQ("401", std::string(buf + 9, buf + 12));
74
75 app.stop();
76}
77
78// Tests that Base64 basic strings work
79TEST(TokenAuthentication, TestGetLoginUrl) {
80 App<crow::TokenAuthorizationMiddleware> app;
81 app.bindaddr("127.0.0.1").port(45451);
82 CROW_ROUTE(app, "/")([]() { return 200; });
83 auto _ = async(launch::async, [&] { app.run(); });
84
85 asio::io_service is;
86 static char buf[2048];
87
88 // Other resources should not be passed
89 std::string sendmsg = "GET /login\r\n\r\n";
90 asio::ip::tcp::socket c(is);
91 for (int i = 0; i < 200; i++) {
92 try {
93 c.connect(asio::ip::tcp::endpoint(
94 asio::ip::address::from_string("127.0.0.1"), 45451));
95 } catch (std::exception e) {
96 // do nothing
97 }
98 }
99 c.send(asio::buffer(sendmsg));
Ed Tanous7d3dba42017-04-05 13:04:39 -0700100 c.receive(asio::buffer(buf, 2048));
Ed Tanous1e94fa42017-04-03 13:41:19 -0700101 c.close();
102 EXPECT_EQ("401", std::string(buf + 9, buf + 12));
103
104 app.stop();
105}
106
107// Tests boundary conditions on login
108TEST(TokenAuthentication, TestPostBadLoginUrl) {
109 App<crow::TokenAuthorizationMiddleware> app;
110 app.bindaddr("127.0.0.1").port(45451);
111 CROW_ROUTE(app, "/")([]() { return 200; });
112 auto _ = async(launch::async, [&] { app.run(); });
113
114 asio::io_service is;
115 std::array<char, 2048> buf;
116 std::string sendmsg;
117
118 auto send_to_localhost = [&is, &buf](std::string sendmsg) {
119 asio::ip::tcp::socket c(is);
120 c.connect(asio::ip::tcp::endpoint(
121 asio::ip::address::from_string("127.0.0.1"), 45451));
122 c.send(asio::buffer(sendmsg));
Ed Tanous7d3dba42017-04-05 13:04:39 -0700123 c.receive(asio::buffer(buf));
Ed Tanous1e94fa42017-04-03 13:41:19 -0700124 c.close();
125 };
126
127 {
128 // Retry a couple of times waiting for the server to come up
129 asio::ip::tcp::socket c(is);
130 for (int i = 0; i < 200; i++) {
131 try {
132 c.connect(asio::ip::tcp::endpoint(
133 asio::ip::address::from_string("127.0.0.1"), 45451));
134 c.close();
135 break;
136 } catch (std::exception e) {
137 // do nothing. We expect this to fail while the server is starting up
138 }
139 }
140 }
141
142 // Test blank login credentials
143 sendmsg = "POST /login\r\nContent-Length:0\r\n\r\n\r\n";
144 {
145 send_to_localhost(sendmsg);
146 auto return_code = std::string(&buf[9], &buf[12]);
147 EXPECT_EQ("400", return_code);
148 }
149
150 // Test wrong login credentials
151 sendmsg =
152 "POST /login\r\nContent-Length:38\r\n\r\n{\"username\": \"foo\", "
153 "\"password\": \"bar\"}\r\n";
154 {
155 send_to_localhost(sendmsg);
156 auto return_code = std::string(&buf[9], &buf[12]);
157 EXPECT_EQ("401", return_code);
158 // TODO(ed) need to test more here. Response string?
159 }
160
161 // Test only sending a username
162 sendmsg =
163 "POST /login\r\nContent-Length:19\r\n\r\n{\"username\": \"foo\"}\r\n";
164 {
165 send_to_localhost(sendmsg);
166 auto return_code = std::string(&buf[9], &buf[12]);
167 EXPECT_EQ("400", return_code);
168 }
169
170 // Test only sending a password
171 sendmsg =
172 "POST /login\r\nContent-Length:19\r\n\r\n{\"password\": \"foo\"}\r\n";
173 {
174 send_to_localhost(sendmsg);
175 auto return_code = std::string(&buf[9], &buf[12]);
176 EXPECT_EQ("400", return_code);
177 }
178
179 app.stop();
180}
181
Ed Tanous4d92cbf2017-06-22 15:41:02 -0700182// Test class that allows login for a fixed password.
183class KnownLoginAuthenticator {
184 public:
185 inline bool authenticate(const std::string& username,
186 const std::string& password) {
187 return (username == "dude") && (password == "foo");
188 }
189};
190
Ed Tanous1e94fa42017-04-03 13:41:19 -0700191TEST(TokenAuthentication, TestSuccessfulLogin) {
Ed Tanousf3d847c2017-06-12 16:01:42 -0700192 App<crow::TokenAuthorization<KnownLoginAuthenticator>> app;
Ed Tanous1e94fa42017-04-03 13:41:19 -0700193 app.bindaddr("127.0.0.1").port(45451);
194 CROW_ROUTE(app, "/")([]() { return 200; });
195 auto _ = async(launch::async, [&] { app.run(); });
196
197 asio::io_service is;
198 std::array<char, 2048> buf;
199 std::string sendmsg;
200
201 auto send_to_localhost = [&is, &buf](std::string sendmsg) {
202 asio::ip::tcp::socket c(is);
203 c.connect(asio::ip::tcp::endpoint(
204 asio::ip::address::from_string("127.0.0.1"), 45451));
205 c.send(asio::buffer(sendmsg));
Ed Tanous7d3dba42017-04-05 13:04:39 -0700206 c.receive(asio::buffer(buf));
Ed Tanous1e94fa42017-04-03 13:41:19 -0700207 c.close();
208 };
209
210 {
211 // Retry a couple of times waiting for the server to come up
212 asio::ip::tcp::socket c(is);
213 for (int i = 0; i < 200; i++) {
214 try {
215 c.connect(asio::ip::tcp::endpoint(
216 asio::ip::address::from_string("127.0.0.1"), 45451));
217 c.close();
218 break;
219 } catch (std::exception e) {
220 // do nothing. We expect this to fail while the server is starting up
221 }
222 }
223 }
224
225 // Test correct login credentials
226 sendmsg =
227 "POST /login\r\nContent-Length:40\r\n\r\n{\"username\": \"dude\", "
Ed Tanousf3d847c2017-06-12 16:01:42 -0700228 "\"password\": \"foo\"}\r\n";
Ed Tanous1e94fa42017-04-03 13:41:19 -0700229 {
230 send_to_localhost(sendmsg);
Ed Tanousb4a7bfa2017-04-04 17:23:00 -0700231 std::string response(std::begin(buf), std::end(buf));
232 // This is a routine to split strings until a newline is hit
233 // TODO(ed) this should really use the HTTP parser
234 std::vector<std::string> headers;
235 std::string::size_type pos = 0;
236 std::string::size_type prev = 0;
237 int content_length = 0;
238 std::string content_encoding("");
239 while ((pos = response.find("\r\n", prev)) != std::string::npos) {
240 auto this_string = response.substr(prev, pos - prev);
241 if (this_string == "") {
242 prev = pos + 2;
243 break;
244 }
245
246 headers.push_back(this_string);
247 prev = pos + 2;
248 }
249 EXPECT_EQ(headers[0], "HTTP/1.1 200 OK");
250 EXPECT_THAT(headers, testing::Contains("Content-Type: application/json"));
251 auto http_content = response.substr(prev);
Ed Tanous1e94fa42017-04-03 13:41:19 -0700252 }
253
Ed Tanous1e94fa42017-04-03 13:41:19 -0700254 // Try to use those login credentials to access a resource
255 sendmsg =
256 "GET /\r\nAuthorization: token\r\n\r\n{\"username\": \"dude\", "
257 "\"password\": \"dude\"}\r\n";
258 {
259 send_to_localhost(sendmsg);
260 auto return_code = std::string(&buf[9], &buf[12]);
261 EXPECT_EQ("200", return_code);
262 }
263
264 app.stop();
Ed Tanous8041f312017-04-03 09:47:01 -0700265}