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