16 #include <macaroons.h>
17 #include <uuid/uuid.h>
23 char *r = (
char *) malloc(l + 1);
27 for (i = 0; i < l; i++) {
35 savec[0] = str[i + 1];
36 savec[1] = str[i + 2];
39 r[j] = strtol(savec, 0, 16);
42 }
else if (str[i] ==
'+') r[j] =
' ';
59 output.reserve(input.size());
60 char prior_chr =
'\0';
61 size_t output_idx = 0;
62 for (
size_t idx = 0; idx < input.size(); idx++) {
63 char chr = input[idx];
64 if (prior_chr ==
'/' && chr ==
'/') {
68 output += input[output_idx];
77 return cv.compare(0, 5,
"name:") == 0 ||
78 cv.compare(0, 5,
"path:") == 0 ||
79 cv.compare(0, 7,
"before:") == 0;
84 return cv.compare(0, 9,
"activity:") == 0;
91 if (input.find(
"PT") != 0)
96 std::string remaining = input;
99 remaining = remaining.substr(pos);
100 if (remaining.size() == 0)
break;
104 cur_duration = stol(remaining, &pos);
109 if (pos >= remaining.size())
113 char unit = remaining[pos];
121 cur_duration *= 3600;
127 duration += cur_duration;
139 Handler::GenerateID(
const std::string &resource,
141 const std::string &activities,
142 const std::vector<std::string> &other_caveats,
143 const std::string &before)
146 uuid_generate_random(uu);
148 uuid_unparse(uu, uuid_buf);
149 std::string result(uuid_buf);
157 std::stringstream ss;
158 ss <<
"ID=" << result <<
", ";
160 if (entity.
prot[0] !=
'\0') {ss <<
"protocol=" << entity.
prot <<
", ";}
161 if (entity.
name) {ss <<
"name=" << entity.
name <<
", ";}
162 if (entity.
host) {ss <<
"host=" << entity.
host <<
", ";}
163 if (entity.
vorg) {ss <<
"vorg=" << entity.
vorg <<
", ";}
164 if (entity.
role) {ss <<
"role=" << entity.
role <<
", ";}
165 if (entity.
grps) {ss <<
"groups=" << entity.
grps <<
", ";}
167 if (activities.size()) {ss <<
"base_activities=" << activities <<
", ";}
169 for (std::vector<std::string>::const_iterator iter = other_caveats.begin();
170 iter != other_caveats.end();
173 ss <<
"user_caveat=" << *iter <<
", ";
176 ss <<
"expires=" << before;
178 m_log->
Emsg(
"MacaroonGen", ss.str().c_str());
184 Handler::GenerateActivities(
const XrdHttpExtReq & req,
const std::string &resource)
const
186 std::string result =
"activity:READ_METADATA";
203 return !strcmp(verb,
"POST") || !strncmp(path,
"/.well-known/", 13) ||
204 !strncmp(path,
"/.oauth2/", 9);
208 if (req.
verb !=
"GET")
210 return req.
SendSimpleResp(405,
nullptr,
nullptr,
"Only GET is valid for oauth config.", 0);
213 if (header == req.
headers.end())
215 return req.
SendSimpleResp(400,
nullptr,
nullptr,
"Host header is required.", 0);
218 json_object *response_obj = json_object_new_object();
221 return req.
SendSimpleResp(500,
nullptr,
nullptr,
"Unable to create new JSON response object.", 0);
223 std::string token_endpoint =
"https://" + header->second +
"/.oauth2/token";
224 json_object *endpoint_obj =
225 json_object_new_string_len(token_endpoint.c_str(), token_endpoint.size());
228 return req.
SendSimpleResp(500,
nullptr,
nullptr,
"Unable to create a new JSON macaroon string.", 0);
230 json_object_object_add(response_obj,
"token_endpoint", endpoint_obj);
232 const char *response_result = json_object_to_json_string_ext(response_obj, JSON_C_TO_STRING_PRETTY);
233 int retval = req.
SendSimpleResp(200,
nullptr,
nullptr, response_result, 0);
234 json_object_put(response_obj);
240 if (req.
verb !=
"POST")
242 "Only POST method is allowed to request a macaroon",
false);
245 if (header == req.
headers.end() || header->second !=
"application/x-www-form-urlencoded")
246 return req.
SendSimpleResp(415,
nullptr,
"accept: application/x-www-form-urlencoded",
247 "Content-Type must be 'application/macaroon-request' to request a macaroon",
false);
250 return req.
SendSimpleResp(413,
nullptr,
nullptr,
"Macaroon request too large (must be less than 4KB)",
false);
253 char *request_data_raw =
nullptr;
256 return req.
SendSimpleResp(400,
nullptr,
nullptr,
"Missing or invalid body of request.", 0);
258 std::string request_data(request_data_raw, req.
length);
259 bool found_grant_type =
false;
260 ssize_t validity = -1;
263 std::istringstream token_stream(request_data);
266 std::string::size_type eq = token.find(
"=");
267 if (eq == std::string::npos)
269 return req.
SendSimpleResp(400,
nullptr,
nullptr,
"Invalid format for form-encoding", 0);
271 std::string key = token.substr(0, eq);
272 std::string value = token.substr(eq + 1);
274 if (key ==
"grant_type")
276 found_grant_type =
true;
277 if (value !=
"client_credentials")
279 return req.
SendSimpleResp(400,
nullptr,
nullptr,
"Invalid grant type specified.", 0);
282 else if (key ==
"expire_in")
284 if ((validity = std::strtoll(value.c_str(),
nullptr, 10)) <= 0)
285 return req.
SendSimpleResp(400,
nullptr,
nullptr,
"Expiration request has invalid value.", 0);
287 else if (key ==
"scope")
289 char *value_raw =
unquote(value.c_str());
290 if (value_raw ==
nullptr)
292 return req.
SendSimpleResp(400,
nullptr,
nullptr,
"Unable to unquote scope.", 0);
298 if (!found_grant_type)
300 return req.
SendSimpleResp(400,
nullptr,
nullptr,
"Grant type not specified.", 0);
304 return req.
SendSimpleResp(400,
nullptr,
nullptr,
"Scope was not specified.", 0);
306 std::istringstream token_stream_scope(scope);
308 std::vector<std::string> other_caveats;
311 std::string::size_type col = token.find(
":");
312 if (col == std::string::npos)
314 return req.
SendSimpleResp(400,
nullptr,
nullptr,
"Invalid format for requested scope", 0);
316 std::string key = token.substr(0, col);
317 std::string value = token.substr(col + 1);
323 else if (value != path)
326 std::stringstream ss;
327 ss <<
"Encountered requested scope request for authorization " << key
328 <<
" with resource path " << value <<
"; however, prior request had path "
330 m_log->
Emsg(
"MacaroonRequest", ss.str().c_str());
332 return req.
SendSimpleResp(500,
nullptr,
nullptr,
"Server only supports all scopes having the same path", 0);
334 other_caveats.push_back(key);
340 std::vector<std::string> other_caveats_final;
341 if (!other_caveats.empty()) {
342 std::stringstream ss;
344 for (std::vector<std::string>::const_iterator iter = other_caveats.begin();
345 iter != other_caveats.end();
350 const std::string &final_str = ss.str();
351 other_caveats_final.push_back(final_str.substr(0, final_str.size() - 1));
353 return GenerateMacaroonResponse(req, path, other_caveats_final, validity,
true);
359 if (req.
resource ==
"/.well-known/oauth-authorization-server") {
360 return ProcessOAuthConfig(req);
361 }
else if (req.
resource ==
"/.oauth2/token") {
362 return ProcessTokenRequest(req);
366 if (header == req.
headers.end() || header->second !=
"application/macaroon-request")
367 return req.
SendSimpleResp(415,
nullptr,
"accept: application/macaroon-request",
368 "Content-Type must be 'application/macaroon-request' to request a macaroon",
false);
371 if (header == req.
headers.end())
372 return req.
SendSimpleResp(411,
nullptr,
nullptr,
"Content-Length missing; not a valid POST",
false);
374 ssize_t blen = std::strtoll(header->second.c_str(),
nullptr, 10);
377 return req.
SendSimpleResp(400,
nullptr,
nullptr,
"Content-Length has invalid value.",
false);
380 return req.
SendSimpleResp(413,
nullptr,
nullptr,
"Macaroon request too large (must be less than 4KB)",
false);
385 if (req.
BuffgetData(blen, &request_data,
true) != blen)
387 return req.
SendSimpleResp(400,
nullptr,
nullptr,
"Missing or invalid body of request.", 0);
389 json_tokener *tokener = json_tokener_new();
392 return req.
SendSimpleResp(500,
nullptr,
nullptr,
"Internal error when allocating token parser.", 0);
394 json_object *macaroon_req = json_tokener_parse_ex(tokener, request_data, blen);
395 enum json_tokener_error err = json_tokener_get_error(tokener);
396 json_tokener_free(tokener);
397 if (err != json_tokener_success)
399 if (macaroon_req) json_object_put(macaroon_req);
400 return req.
SendSimpleResp(400,
nullptr,
nullptr,
"Invalid JSON serialization of macaroon request.", 0);
402 json_object *validity_obj;
403 if (!json_object_object_get_ex(macaroon_req,
"validity", &validity_obj))
405 json_object_put(macaroon_req);
406 return req.
SendSimpleResp(400,
nullptr,
nullptr,
"JSON request does not include a `validity`", 0);
408 const char *validity_cstr = json_object_get_string(validity_obj);
411 json_object_put(macaroon_req);
412 return req.
SendSimpleResp(400,
nullptr,
nullptr,
"validity key cannot be cast to a string", 0);
414 std::string validity_str(validity_cstr);
418 json_object_put(macaroon_req);
419 return req.
SendSimpleResp(400,
nullptr,
nullptr,
"Invalid ISO 8601 duration for validity key", 0);
421 json_object *caveats_obj;
422 std::vector<std::string> other_caveats;
423 if (json_object_object_get_ex(macaroon_req,
"caveats", &caveats_obj))
425 if (json_object_is_type(caveats_obj, json_type_array))
428 int array_length = json_object_array_length(caveats_obj);
429 other_caveats.reserve(array_length);
430 for (
int idx=0; idx<array_length; idx++)
432 json_object *caveat_item = json_object_array_get_idx(caveats_obj, idx);
435 const char *caveat_item_str = json_object_get_string(caveat_item);
437 if (!caveat_item_str) {
438 json_object_put(macaroon_req);
439 return req.
SendSimpleResp(400,
nullptr,
nullptr,
"Malformed or invalid caveat", 0);
443 json_object_put(macaroon_req);
445 "Cannot accept caveat with reserved key (name, path, before)\n", 0);
449 json_object_put(macaroon_req);
451 "Cannot accept caveat of unsupported type (supported types: activity)\n", 0);
454 other_caveats.emplace_back(caveat_item_str);
459 json_object_put(macaroon_req);
461 return GenerateMacaroonResponse(req, req.
resource, other_caveats, validity,
false);
466 Handler::GenerateMacaroonResponse(
XrdHttpExtReq &req,
const std::string &resource,
467 const std::vector<std::string> &other_caveats, ssize_t validity,
bool oauth_response)
471 if (m_max_duration > 0)
473 validity = (validity > m_max_duration) ? m_max_duration : validity;
477 char utc_time_buf[21];
478 if (!strftime(utc_time_buf, 21,
"%FT%TZ", gmtime(&now)))
480 return req.
SendSimpleResp(500,
nullptr,
nullptr,
"Internal error constructing UTC time", 0);
482 std::string utc_time_str(utc_time_buf);
483 std::stringstream ss;
484 ss <<
"before:" << utc_time_str;
485 std::string utc_time_caveat = ss.str();
487 std::string activities = GenerateActivities(req, resource);
491 for (
const auto &caveat : other_caveats) {
492 if (caveat.compare(0, 9,
"activity:") == 0) {
493 std::set<std::string> allowed;
494 { std::stringstream ss(activities.substr(9));
497 std::string result =
"activity:";
499 std::stringstream ss(caveat.substr(9));
501 if (allowed.count(a)) {
502 if (!first) result +=
',';
507 if (result.size() > 9)
512 std::string macaroon_id = GenerateID(resource, req.
GetSecEntity(), activities, other_caveats, utc_time_str);
513 enum macaroon_returncode mac_err;
515 struct macaroon *mac = macaroon_create(
reinterpret_cast<const unsigned char*
>(m_location.c_str()),
517 reinterpret_cast<const unsigned char*
>(m_secret.c_str()),
519 reinterpret_cast<const unsigned char*
>(macaroon_id.c_str()),
520 macaroon_id.size(), &mac_err);
522 return req.
SendSimpleResp(500,
nullptr,
nullptr,
"Internal error constructing the macaroon", 0);
526 struct macaroon *mac_with_name;
529 std::stringstream name_caveat_ss;
530 name_caveat_ss <<
"name:" << sec_name;
531 std::string name_caveat = name_caveat_ss.str();
532 mac_with_name = macaroon_add_first_party_caveat(mac,
533 reinterpret_cast<const unsigned char*
>(name_caveat.c_str()),
536 macaroon_destroy(mac);
542 return req.
SendSimpleResp(500,
nullptr,
nullptr,
"Internal error adding 'name' caveat to macaroon", 0);
545 struct macaroon *mac_with_activities = macaroon_add_first_party_caveat(mac_with_name,
546 reinterpret_cast<const unsigned char*
>(activities.c_str()),
549 macaroon_destroy(mac_with_name);
550 if (!mac_with_activities)
552 return req.
SendSimpleResp(500,
nullptr,
nullptr,
"Internal error adding 'activity' caveat to macaroon", 0);
559 std::string path_caveat =
"path:" + resource;
560 struct macaroon *mac_with_path = macaroon_add_first_party_caveat(mac_with_activities,
561 reinterpret_cast<const unsigned char*
>(path_caveat.c_str()),
564 macaroon_destroy(mac_with_activities);
565 if (!mac_with_path) {
566 return req.
SendSimpleResp(500,
nullptr,
nullptr,
"Internal error adding 'path' caveat to macaroon", 0);
569 struct macaroon *mac_with_date = macaroon_add_first_party_caveat(mac_with_path,
570 reinterpret_cast<const unsigned char*
>(utc_time_caveat.c_str()),
571 utc_time_caveat.size(),
573 macaroon_destroy(mac_with_path);
574 if (!mac_with_date) {
575 return req.
SendSimpleResp(500,
nullptr,
nullptr,
"Internal error adding date to macaroon", 0);
578 size_t size_hint = macaroon_serialize_size_hint(mac_with_date);
580 std::vector<char> macaroon_resp; macaroon_resp.resize(size_hint);
581 if (macaroon_serialize(mac_with_date, &macaroon_resp[0], size_hint, &mac_err))
583 printf(
"Returned macaroon_serialize code: %zu\n", size_hint);
584 return req.
SendSimpleResp(500,
nullptr,
nullptr,
"Internal error serializing macaroon", 0);
586 macaroon_destroy(mac_with_date);
588 json_object *response_obj = json_object_new_object();
591 return req.
SendSimpleResp(500,
nullptr,
nullptr,
"Unable to create new JSON response object.", 0);
593 json_object *macaroon_obj = json_object_new_string_len(&macaroon_resp[0], strlen(&macaroon_resp[0]));
596 return req.
SendSimpleResp(500,
nullptr,
nullptr,
"Unable to create a new JSON macaroon string.", 0);
598 json_object_object_add(response_obj, oauth_response ?
"access_token" :
"macaroon", macaroon_obj);
600 json_object *expire_in_obj = json_object_new_int64(validity);
603 return req.
SendSimpleResp(500,
nullptr,
nullptr,
"Unable to create a new JSON validity object.", 0);
605 json_object_object_add(response_obj,
"expires_in", expire_in_obj);
607 const char *macaroon_result = json_object_to_json_string_ext(response_obj, JSON_C_TO_STRING_PRETTY);
608 int retval = req.
SendSimpleResp(200,
nullptr,
nullptr, macaroon_result, 0);
609 json_object_put(response_obj);
@ AOP_Any
Special for getting privs.
static bool is_supported_caveat(const std::string &cv)
static bool is_reserved_caveat(const std::string &cv)
char * unquote(const char *str)
static ssize_t determine_validity(const std::string &input)
void getline(uchar *buff, int blen)
virtual bool MatchesPath(const char *verb, const char *path) override
Tells if the incoming path is recognized as one of the paths that have to be processed.
virtual int ProcessReq(XrdHttpExtReq &req) override
virtual XrdAccPrivs Access(const XrdSecEntity *Entity, const char *path, const Access_Operation oper, XrdOucEnv *Env=0)=0
std::map< std::string, std::string > & headers
int BuffgetData(int blen, char **data, bool wait)
Get a pointer to data read from the client, valid for up to blen bytes from the buffer....
const XrdSecEntity & GetSecEntity() const
int SendSimpleResp(int code, const char *desc, const char *header_to_add, const char *body, long long bodylen)
Sends a basic response. If the length is < 0 then it is calculated internally.
static std::map< std::string, T >::const_iterator caseInsensitiveFind(const std::map< std::string, T > &m, const std::string &lowerCaseSearchKey)
char * vorg
Entity's virtual organization(s)
char prot[XrdSecPROTOIDSIZE]
Auth protocol used (e.g. krb5)
char * grps
Entity's group name(s)
char * name
Entity's name.
char * role
Entity's role(s)
char * endorsements
Protocol specific endorsements.
char * host
Entity's host name dnr dependent.
int Emsg(const char *esfx, int ecode, const char *text1, const char *text2=0)
std::string NormalizeSlashes(const std::string &)