1 /*
2  * RESTinio
3  */
4 
5 /*!
6  * @file
7  * @brief Helpers for dealing with bearer authentification.
8  *
9  * @since v.0.6.7.1
10  */
11 
12 #pragma once
13 
14 #include <restinio/helpers/http_field_parsers/authorization.hpp>
15 
16 #include <restinio/http_headers.hpp>
17 #include <restinio/request_handler.hpp>
18 #include <restinio/expected.hpp>
19 
20 #include <iostream>
21 
22 namespace restinio
23 {
24 
25 namespace http_field_parsers
26 {
27 
28 namespace bearer_auth
29 {
30 
31 //
32 // params_t
33 //
34 /*!
35  * @brief Parameters for bearer authentification.
36  *
37  * @since v.0.6.7.1
38  */
39 struct params_t
40 {
41 	//! Access Token.
42 	/*!
43 	 * Can't be empty.
44 	 */
45 	std::string token;
46 };
47 
48 //
49 // extraction_error_t
50 //
51 /*!
52  * @brief Error codes for failures of extraction of bearer authentification
53  * parameters.
54  *
55  * @since v.0.6.7.1
56  */
57 enum class extraction_error_t
58 {
59 	//! There is no HTTP field with authentification parameters.
60 	no_auth_http_field,
61 
62 	//! The HTTP field with authentification parameters can't be parsed.
63 	illegal_http_field_value,
64 
65 	//! Different authentification scheme found.
66 	//! bearer authentification scheme is expected.
67 	not_bearer_auth_scheme,
68 
69 	//! Invalid value of parameter for bearer authentification scheme.
70 	//! The single parameter in the form of b64token is expected.
71 	invalid_bearer_auth_param,
72 };
73 
74 /*!
75  * @brief Helper function to get a string name of extraction_error enum.
76  *
77  * @since v.0.6.9
78  */
79 RESTINIO_NODISCARD
80 inline string_view_t
to_string_view(extraction_error_t what)81 to_string_view( extraction_error_t what ) noexcept
82 {
83 	string_view_t result{ "<unknown>" };
84 
85 	switch( what )
86 	{
87 	case extraction_error_t::no_auth_http_field:
88 		result = string_view_t{ "no_auth_http_field" };
89 	break;
90 
91 	case extraction_error_t::illegal_http_field_value:
92 		result = string_view_t{ "illegal_http_field_value" };
93 	break;
94 
95 	case extraction_error_t::not_bearer_auth_scheme:
96 		result = string_view_t{ "not_bearer_auth_scheme" };
97 	break;
98 
99 	case extraction_error_t::invalid_bearer_auth_param:
100 		result = string_view_t{ "invalid_bearer_auth_param" };
101 	break;
102 	}
103 
104 	return result;
105 }
106 
107 //
108 // try_extract_params
109 //
110 /*!
111  * @brief Helper function for getting parameters of bearer authentification
112  * from an already parsed HTTP-field.
113  *
114  * @attention
115  * This function doesn't check the content of
116  * authorization_value_t::auth_scheme. It's expected that this field was
117  * checked earlier.
118  *
119  * Usage example:
120  * @code
121  * auto on_request(restinio::request_handle_t & req) {
122  * 	using namespace restinio::http_field_parsers;
123  *		const auto opt_field = req.header().opt_value_of(
124  * 			restinio::http_field::authorization);
125  * 	if(opt_field) {
126  * 		const auto parsed_field = authorization_value_t::try_parse(*opt_field);
127  * 		if(parsed_field) {
128  * 			if("basic" == parsed_field->auth_scheme) {
129  * 				... // Dealing with Basic authentification scheme.
130  * 			}
131  * 			else if("bearer" == parsed_field->auth_scheme) {
132  * 				using namespace restinio::http_field_parsers::bearer_auth;
133  * 				const auto bearer_params = try_extract_params(*parsed_field);
134  * 				if(bearer_params) {
135  * 					const std::string & token = auth_params->token;
136  * 					... // Do something with token.
137  * 				}
138  * 			}
139  * 			else {
140  * 				... // Other authentification schemes.
141  * 			}
142  * 		}
143  * 	}
144  * 	...
145  * }
146  * @endcode
147  *
148  * @since v.0.6.8
149  */
150 RESTINIO_NODISCARD
151 inline expected_t< params_t, extraction_error_t >
try_extract_params(const authorization_value_t & http_field)152 try_extract_params(
153 	const authorization_value_t & http_field )
154 {
155 	const auto * b64token = get_if<authorization_value_t::token68_t>(
156 			&http_field.auth_param );
157 	if( !b64token )
158 		return make_unexpected( extraction_error_t::invalid_bearer_auth_param );
159 
160 	return params_t{ b64token->value };
161 }
162 
163 /*!
164  * @brief Helper function for getting parameters of bearer authentification
165  * from an already parsed HTTP-field.
166  *
167  * @attention
168  * This function doesn't check the content of
169  * authorization_value_t::auth_scheme. It's expected that this field was
170  * checked earlier.
171  *
172  * @note
173  * This function can be used if one wants to avoid memory allocation
174  * and can reuse value of auth_params.
175  *
176  * Usage example (please note that `const` is not used in code when
177  * authorization HTTP-field is parsed):
178  * @code
179  * auto on_request(restinio::request_handle_t & req) {
180  * 	using namespace restinio::http_field_parsers;
181  *		const auto opt_field = req.header().opt_value_of(
182  * 			restinio::http_field::authorization);
183  * 	if(opt_field) {
184  * 		// parsed_field is a mutable object.
185  * 		// The content of parsed_field->auth_param can be moved out.
186  * 		auto parsed_field = authorization_value_t::try_parse(*opt_field);
187  * 		if(parsed_field) {
188  * 			if("basic" == parsed_field->auth_scheme) {
189  * 				... // Dealing with Basic authentification scheme.
190  * 			}
191  * 			else if("bearer" == parsed_field->auth_scheme) {
192  * 				using namespace restinio::http_field_parsers::bearer_auth;
193  * 				const auto bearer_params =
194  * 						// Please note the usage of std::move here.
195  * 						try_extract_params(std::move(*parsed_field));
196  * 				if(bearer_params) {
197  * 					const std::string & token = auth_params->token;
198  * 					... // Do something with token.
199  * 				}
200  * 			}
201  * 			else {
202  * 				... // Other authentification schemes.
203  * 			}
204  * 		}
205  * 	}
206  * 	...
207  * }
208  * @endcode
209  *
210  * @since v.0.6.8
211  */
212 RESTINIO_NODISCARD
213 inline expected_t< params_t, extraction_error_t >
try_extract_params(authorization_value_t && http_field)214 try_extract_params(
215 	authorization_value_t && http_field )
216 {
217 	auto * b64token = get_if<authorization_value_t::token68_t>(
218 			&http_field.auth_param );
219 	if( !b64token )
220 		return make_unexpected( extraction_error_t::invalid_bearer_auth_param );
221 
222 	return params_t{ std::move(b64token->value) };
223 }
224 
225 namespace impl
226 {
227 
228 RESTINIO_NODISCARD
229 inline expected_t< params_t, extraction_error_t >
perform_extraction_attempt(const optional_t<string_view_t> opt_field_value)230 perform_extraction_attempt(
231 	const optional_t< string_view_t > opt_field_value )
232 {
233 	if( !opt_field_value )
234 		return make_unexpected( extraction_error_t::no_auth_http_field );
235 
236 	auto field_value_parse_result = authorization_value_t::try_parse(
237 			*opt_field_value );
238 	if( !field_value_parse_result )
239 		return make_unexpected( extraction_error_t::illegal_http_field_value );
240 
241 	auto & parsed_value = *field_value_parse_result;
242 	if( "bearer" != parsed_value.auth_scheme )
243 		return make_unexpected( extraction_error_t::not_bearer_auth_scheme );
244 
245 	return try_extract_params( std::move(parsed_value) );
246 }
247 
248 } /* namespace impl */
249 
250 //
251 // try_extract_params
252 //
253 /*!
254  * @brief Helper function for getting parameters of bearer authentification
255  * from a set of HTTP-fields.
256  *
257  * This helper function is intended to be used for cases when authentification
258  * parameters are stored inside a HTTP-field with a custom name. For example:
259  * @code
260  * auto check_authorization(const restinio::http_header_fields_t & fields) {
261  * 	using namespace restinio::http_field_parsers::bearer_auth;
262  * 	const auto auth_params = try_extract_params(fields, "X-My-Authorization");
263  * 	if(auth_params) {
264  * 		const std::string & token = auth_params->token;
265  * 		... // Do something with token.
266  * 	}
267  * 	...
268  * }
269  * @endcode
270  *
271  * @since v.0.6.9
272  */
273 RESTINIO_NODISCARD
274 inline expected_t< params_t, extraction_error_t >
try_extract_params(const http_header_fields_t & fields,string_view_t auth_field_name)275 try_extract_params(
276 	//! A set of HTTP-fields.
277 	const http_header_fields_t & fields,
278 	//! The name of a HTTP-field with authentification parameters.
279 	string_view_t auth_field_name )
280 {
281 	return impl::perform_extraction_attempt(
282 			fields.opt_value_of( auth_field_name ) );
283 }
284 
285 /*!
286  * @brief Helper function for getting parameters of bearer authentification
287  * from a request.
288  *
289  * This helper function is intended to be used for cases when authentification
290  * parameters are stored inside a HTTP-field with a custom name. For example:
291  * @code
292  * auto on_request(restinio::request_handle_t & req) {
293  * 	using namespace restinio::http_field_parsers::bearer_auth;
294  * 	const auto auth_params = try_extract_params(*req, "X-My-Authorization");
295  * 	if(auth_params) {
296  * 		const std::string & token = auth_params->token;
297  * 		... // Do something with token.
298  * 	}
299  * 	...
300  * }
301  * @endcode
302  *
303  * @since v.0.6.7.1
304  */
305 template< typename Extra_Data >
306 RESTINIO_NODISCARD
307 inline expected_t< params_t, extraction_error_t >
try_extract_params(const generic_request_t<Extra_Data> & req,string_view_t auth_field_name)308 try_extract_params(
309 	//! A request that should hold a HTTP-field with authentification
310 	//! parameters.
311 	const generic_request_t< Extra_Data > & req,
312 	//! The name of a HTTP-field with authentification parameters.
313 	string_view_t auth_field_name )
314 {
315 	return try_extract_params( req.header(), auth_field_name );
316 }
317 
318 /*!
319  * @brief Helper function for getting parameters of bearer authentification
320  * from a set of HTTP-fields.
321  *
322  * Usage example:
323  * @code
324  * auto check_authorization(const restinio::http_header_fields_t & fields) {
325  * 	using namespace restinio::http_field_parsers::bearer_auth;
326  * 	const auto auth_params = try_extract_params(
327  * 			fields, restinio::http_field::authorization);
328  * 	if(auth_params) {
329  * 		const std::string & token = auth_params->token;
330  * 		... // Do something with token.
331  * 	}
332  * 	...
333  * }
334  * @endcode
335  *
336  * @since v.0.6.9
337  */
338 RESTINIO_NODISCARD
339 inline expected_t< params_t, extraction_error_t >
try_extract_params(const http_header_fields_t & fields,http_field_t auth_field_id)340 try_extract_params(
341 	//! A set of HTTP-fields.
342 	const http_header_fields_t & fields,
343 	//! The ID of a HTTP-field with authentification parameters.
344 	http_field_t auth_field_id )
345 {
346 	return impl::perform_extraction_attempt(
347 			fields.opt_value_of( auth_field_id ) );
348 }
349 
350 /*!
351  * @brief Helper function for getting parameters of bearer authentification
352  * from a request.
353  *
354  * Usage example:
355  * @code
356  * auto on_request(restinio::request_handle_t & req) {
357  * 	using namespace restinio::http_field_parsers::bearer_auth;
358  * 	const auto auth_params = try_extract_params(
359  * 			*req, restinio::http_field::authorization);
360  * 	if(auth_params) {
361  * 		const std::string & token = auth_params->token;
362  * 		... // Do something with token.
363  * 	}
364  * 	...
365  * }
366  * @endcode
367  *
368  * @since v.0.6.7.1
369  */
370 template< typename Extra_Data >
371 RESTINIO_NODISCARD
372 inline expected_t< params_t, extraction_error_t >
try_extract_params(const generic_request_t<Extra_Data> & req,http_field_t auth_field_id)373 try_extract_params(
374 	//! A request that should hold a HTTP-field with authentification
375 	//! parameters.
376 	const generic_request_t< Extra_Data > & req,
377 	//! The ID of a HTTP-field with authentification parameters.
378 	http_field_t auth_field_id )
379 {
380 	return try_extract_params( req.header(), auth_field_id );
381 }
382 
383 } /* namespace bearer_auth */
384 
385 } /* namespace http_field_parsers */
386 
387 } /* namespace restinio */
388 
389