1 /*
2   Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved.
3 
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License, version 2.0,
6   as published by the Free Software Foundation.
7 
8   This program is also distributed with certain software (including
9   but not limited to OpenSSL) that is licensed under separate terms,
10   as designated in a particular file or component or in included license
11   documentation.  The authors of MySQL hereby grant you an additional
12   permission to link the program and your derivative works with the
13   separately licensed software that they have included with MySQL.
14 
15   This program is distributed in the hope that it will be useful,
16   but WITHOUT ANY WARRANTY; without even the implied warranty of
17   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18   GNU General Public License for more details.
19 
20   You should have received a copy of the GNU General Public License
21   along with this program; if not, write to the Free Software
22   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
23 */
24 
25 #include "http_auth.h"
26 
27 #include <algorithm>
28 #include <string>
29 
30 #include "http_auth_backend.h"
31 #include "http_auth_error.h"
32 #include "http_auth_method_basic.h"
33 #include "matcher.h"
34 #include "mysqlrouter/http_server_component.h"
35 
quote(const std::string & str)36 std::string HttpQuotedString::quote(const std::string &str) {
37   std::string out;
38 
39   out.append("\"");
40   for (const auto &c : str) {
41     if (c == '"') {
42       out += '\\';
43       out += '"';
44     } else if (c == '\\') {
45       out += '\\';
46       out += '\\';
47     } else {
48       out += c;
49     }
50   }
51   out.append("\"");
52 
53   return out;
54 }
55 
str() const56 std::string HttpAuthChallenge::str() const {
57   std::string out;
58 
59   out.append(scheme_);
60 
61   bool is_first = true;
62   if (!token_.empty()) {
63     out.append(" ");
64     out.append(token_);
65     is_first = false;
66   }
67 
68   for (auto &kv : params_) {
69     if (!is_first) {
70       out.append(",");
71     } else {
72       out.append(" ");
73     }
74     out.append(kv.first);
75     out.append("=");
76     out.append(HttpQuotedString::quote(kv.second));
77 
78     is_first = false;
79   }
80 
81   return out;
82 }
83 
84 /**
85  * match a TCHAR.
86  *
87  * @param c character to check
88  * @returns success
89  * @retval true if c is a TCHAR
90  */
is_tchar(char c)91 static bool is_tchar(char c) {
92   return Matcher::Sor<Matcher::One<'!', '#', '$', '%', '&', '\'', '*', '+', '-',
93                                    '.', '^', '_', '`', '|', '~'>,
94                       Matcher::Alnum>::match(c);
95 }
96 
97 /**
98  * match a TOKEN68.
99  *
100  * @param c character to check
101  * @returns success
102  * @retval true if c is a TOKEN68
103  */
is_token68(char c)104 static bool is_token68(char c) {
105   return Matcher::Sor<Matcher::One<'+', '-', '.', '/', '=', '_', '~'>,
106                       Matcher::Alnum>::match(c);
107 }
108 
from_header(const std::string & hdr,std::error_code & errc)109 HttpAuthCredentials HttpAuthCredentials::from_header(const std::string &hdr,
110                                                      std::error_code &errc) {
111   if (hdr.empty()) {
112     errc = make_error_code(std::errc::invalid_argument);
113     return {{}, {}, {}};
114   }
115   // Basic dGVzdDoxMjPCow==
116   auto begin_scheme = hdr.begin();
117   auto end_scheme = std::find_if_not(hdr.begin(), hdr.end(), is_tchar);
118   // stopped too late
119   if (begin_scheme == end_scheme) {
120     errc = make_error_code(std::errc::invalid_argument);
121     return {{}, {}, {}};
122   }
123 
124   std::string scheme(begin_scheme, end_scheme);
125   std::string token;
126 
127   if (end_scheme != hdr.end()) {
128     auto begin_sp = end_scheme;
129     auto end_sp =
130         std::find_if_not(end_scheme, hdr.end(), Matcher::One<' '>::match);
131 
132     if (begin_sp != end_sp) {
133       // if there is a SP, we may also see a token
134       auto begin_token = end_sp;
135       auto end_token = std::find_if_not(begin_token, hdr.end(), is_token68);
136 
137       token = std::string(begin_token, end_token);
138     }
139   }
140 
141   // the RFC allows params after or instead of the token.
142   // currently they are ignored. They should be added as soon as auth-method
143   // is supported that needs them.
144 
145   return {scheme, token, {}};
146 }
147 
str() const148 std::string HttpAuthCredentials::str() const {
149   std::string out;
150 
151   out.append(scheme_);
152   out.append(" ");
153   bool is_first = true;
154   if (!token_.empty()) {
155     out.append(token_);
156     is_first = false;
157   }
158 
159   for (auto &kv : params_) {
160     if (!is_first) {
161       out.append(",");
162     }
163     out.append(kv.first);
164     out.append("=");
165     out.append(HttpQuotedString::quote(kv.second));
166 
167     is_first = false;
168   }
169 
170   return out;
171 }
172 
require_auth(HttpRequest & req,std::shared_ptr<HttpAuthRealm> realm)173 bool HttpAuth::require_auth(HttpRequest &req,
174                             std::shared_ptr<HttpAuthRealm> realm) {
175   constexpr char kAuthorization[]{"Authorization"};
176   constexpr char kWwwAuthenticate[]{"WWW-Authenticate"};
177   constexpr char kMethodBasic[]{"Basic"};
178   // enforce authentication
179   auto authorization = req.get_input_headers().get(kAuthorization);
180 
181   auto out_hdrs = req.get_output_headers();
182 
183   // no Authorization, tell the client to authenticate
184   if (authorization == nullptr) {
185     out_hdrs.add(kWwwAuthenticate, HttpAuthChallenge(realm->method(), "",
186                                                      {{"realm", realm->name()}})
187                                        .str()
188                                        .c_str());
189     req.send_reply(HttpStatusCode::Unauthorized);
190     return true;
191   }
192 
193   // split Basic <...>
194   std::error_code ec;
195   auto creds = HttpAuthCredentials::from_header(authorization, ec);
196   if (ec) {
197     // parsing header failed
198     req.send_reply(HttpStatusCode::BadRequest);
199     return true;
200   }
201 
202   if (creds.scheme() == kMethodBasic) {
203     std::error_code ec;
204     auto auth_data =
205         HttpAuthMethodBasic::decode_authorization(creds.token(), ec);
206     if (ec) {
207       req.send_reply(HttpStatusCode::BadRequest);
208       return true;
209     }
210 
211     ec = realm->authenticate(auth_data.username, auth_data.password);
212     if (ec) {
213       out_hdrs.add(
214           kWwwAuthenticate,
215           HttpAuthChallenge(realm->method(), "", {{"realm", realm->name()}})
216               .str()
217               .c_str());
218       if (ec == make_error_code(HttpAuthErrc::kAuthorizationNotSupported))
219         req.send_reply(HttpStatusCode::Forbidden);
220       else
221         req.send_reply(HttpStatusCode::Unauthorized);
222       return true;
223     }
224   } else {
225     // we never announced something else
226     req.send_reply(HttpStatusCode::BadRequest);
227     return true;
228   }
229 
230   return false;
231 }
232