XRootD
XrdMacaroonsHandler.cc
Go to the documentation of this file.
1 #include "XrdMacaroonsHandler.hh"
2 
4 #include "XrdAcc/XrdAccPrivs.hh"
5 #include "XrdOuc/XrdOucTUtils.hh"
6 #include "XrdSec/XrdSecEntity.hh"
7 #include "XrdSys/XrdSysError.hh"
8 
9 #include <cstring>
10 #include <iostream>
11 #include <set>
12 #include <sstream>
13 #include <string>
14 
15 #include <json.h>
16 #include <macaroons.h>
17 #include <uuid/uuid.h>
18 
19 using namespace Macaroons;
20 
21 char *unquote(const char *str) {
22  int l = strlen(str);
23  char *r = (char *) malloc(l + 1);
24  r[0] = '\0';
25  int i, j = 0;
26 
27  for (i = 0; i < l; i++) {
28 
29  if (str[i] == '%') {
30  char savec[3];
31  if (l <= i + 3) {
32  free(r);
33  return nullptr;
34  }
35  savec[0] = str[i + 1];
36  savec[1] = str[i + 2];
37  savec[2] = '\0';
38 
39  r[j] = strtol(savec, 0, 16);
40 
41  i += 2;
42  } else if (str[i] == '+') r[j] = ' ';
43  else r[j] = str[i];
44 
45  j++;
46  }
47 
48  r[j] = '\0';
49 
50  return r;
51 
52 }
53 
54 std::string Macaroons::NormalizeSlashes(const std::string &input)
55 {
56  std::string output;
57  // In most cases, the output should be "about as large"
58  // as the input
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 == '/') {
65  output_idx++;
66  continue;
67  }
68  output += input[output_idx];
69  prior_chr = chr;
70  output_idx++;
71  }
72  return output;
73 }
74 
75 static bool is_reserved_caveat(const std::string &cv)
76 {
77  return cv.compare(0, 5, "name:") == 0 ||
78  cv.compare(0, 5, "path:") == 0 ||
79  cv.compare(0, 7, "before:") == 0;
80 }
81 
82 static bool is_supported_caveat(const std::string &cv)
83 {
84  return cv.compare(0, 9, "activity:") == 0;
85 }
86 
87 static
88 ssize_t determine_validity(const std::string& input)
89 {
90  ssize_t duration = 0;
91  if (input.find("PT") != 0)
92  {
93  return -1;
94  }
95  size_t pos = 2;
96  std::string remaining = input;
97  do
98  {
99  remaining = remaining.substr(pos);
100  if (remaining.size() == 0) break;
101  long cur_duration;
102  try
103  {
104  cur_duration = stol(remaining, &pos);
105  } catch (...)
106  {
107  return -1;
108  }
109  if (pos >= remaining.size())
110  {
111  return -1;
112  }
113  char unit = remaining[pos];
114  switch (unit) {
115  case 'S':
116  break;
117  case 'M':
118  cur_duration *= 60;
119  break;
120  case 'H':
121  cur_duration *= 3600;
122  break;
123  default:
124  return -1;
125  };
126  pos ++;
127  duration += cur_duration;
128  } while (1);
129  return duration;
130 }
131 
133 {
134  delete m_chain;
135 }
136 
137 
138 std::string
139 Handler::GenerateID(const std::string &resource,
140  const XrdSecEntity &entity,
141  const std::string &activities,
142  const std::vector<std::string> &other_caveats,
143  const std::string &before)
144 {
145  uuid_t uu;
146  uuid_generate_random(uu);
147  char uuid_buf[37];
148  uuid_unparse(uu, uuid_buf);
149  std::string result(uuid_buf);
150 
151 // The following code shoul have been strictly for debugging purposes. This
152 // added code skips it unless debug logging has been enabled. Due to the code
153 // structure, indentation is a bit of a struggle as this is a minimal fix.
154 //
155 if (m_log->getMsgMask() & LogMask::Debug)
156  {
157  std::stringstream ss;
158  ss << "ID=" << result << ", ";
159  ss << "resource=" << NormalizeSlashes(resource) << ", ";
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 << ", ";}
166  if (entity.endorsements) {ss << "endorsements=" << entity.endorsements << ", ";}
167  if (activities.size()) {ss << "base_activities=" << activities << ", ";}
168 
169  for (std::vector<std::string>::const_iterator iter = other_caveats.begin();
170  iter != other_caveats.end();
171  iter++)
172  {
173  ss << "user_caveat=" << *iter << ", ";
174  }
175 
176  ss << "expires=" << before;
177 
178  m_log->Emsg("MacaroonGen", ss.str().c_str()); // Mask::Debug
179  }
180  return result;
181 }
182 
183 std::string
184 Handler::GenerateActivities(const XrdHttpExtReq & req, const std::string &resource) const
185 {
186  std::string result = "activity:READ_METADATA";
187  // TODO - generate environment object that includes the Authorization header.
188  XrdAccPrivs privs = m_chain ? m_chain->Access(&req.GetSecEntity(), resource.c_str(), AOP_Any, nullptr) : XrdAccPriv_None;
189  if ((privs & XrdAccPriv_Create) == XrdAccPriv_Create) {result += ",UPLOAD";}
190  if (privs & XrdAccPriv_Read) {result += ",DOWNLOAD";}
191  if (privs & XrdAccPriv_Delete) {result += ",DELETE";}
192  if ((privs & XrdAccPriv_Chown) == XrdAccPriv_Chown) {result += ",MANAGE,UPDATE_METADATA";}
193  if (privs & XrdAccPriv_Readdir) {result += ",LIST";}
194  return result;
195 }
196 
197 // See if the macaroon handler is interested in this request.
198 // We intercept all POST requests as we will be looking for a particular
199 // header.
200 bool
201 Handler::MatchesPath(const char *verb, const char *path)
202 {
203  return !strcmp(verb, "POST") || !strncmp(path, "/.well-known/", 13) ||
204  !strncmp(path, "/.oauth2/", 9);
205 }
206 
207 int Handler::ProcessOAuthConfig(XrdHttpExtReq &req) {
208  if (req.verb != "GET")
209  {
210  return req.SendSimpleResp(405, nullptr, nullptr, "Only GET is valid for oauth config.", 0);
211  }
212  auto header = XrdOucTUtils::caseInsensitiveFind(req.headers,"host");
213  if (header == req.headers.end())
214  {
215  return req.SendSimpleResp(400, nullptr, nullptr, "Host header is required.", 0);
216  }
217 
218  json_object *response_obj = json_object_new_object();
219  if (!response_obj)
220  {
221  return req.SendSimpleResp(500, nullptr, nullptr, "Unable to create new JSON response object.", 0);
222  }
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());
226  if (!endpoint_obj)
227  {
228  return req.SendSimpleResp(500, nullptr, nullptr, "Unable to create a new JSON macaroon string.", 0);
229  }
230  json_object_object_add(response_obj, "token_endpoint", endpoint_obj);
231 
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);
235  return retval;
236 }
237 
238 int Handler::ProcessTokenRequest(XrdHttpExtReq &req)
239 {
240  if (req.verb != "POST")
241  return req.SendSimpleResp(405, nullptr, "allow: POST",
242  "Only POST method is allowed to request a macaroon", false);
243 
244  auto header = XrdOucTUtils::caseInsensitiveFind(req.headers, "content-type");
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);
248 
249  if (req.length > 4096)
250  return req.SendSimpleResp(413, nullptr, nullptr, "Macaroon request too large (must be less than 4KB)", false);
251 
252  // Note: this does not null-terminate the buffer contents.
253  char *request_data_raw = nullptr;
254 
255  if (req.length <= 0 || req.BuffgetData(req.length, &request_data_raw, true) != req.length)
256  return req.SendSimpleResp(400, nullptr, nullptr, "Missing or invalid body of request.", 0);
257 
258  std::string request_data(request_data_raw, req.length);
259  bool found_grant_type = false;
260  ssize_t validity = -1;
261  std::string scope;
262  std::string token;
263  std::istringstream token_stream(request_data);
264  while (std::getline(token_stream, token, '&'))
265  {
266  std::string::size_type eq = token.find("=");
267  if (eq == std::string::npos)
268  {
269  return req.SendSimpleResp(400, nullptr, nullptr, "Invalid format for form-encoding", 0);
270  }
271  std::string key = token.substr(0, eq);
272  std::string value = token.substr(eq + 1);
273  //std::cout << "Found key " << key << ", value " << value << std::endl;
274  if (key == "grant_type")
275  {
276  found_grant_type = true;
277  if (value != "client_credentials")
278  {
279  return req.SendSimpleResp(400, nullptr, nullptr, "Invalid grant type specified.", 0);
280  }
281  }
282  else if (key == "expire_in")
283  {
284  if ((validity = std::strtoll(value.c_str(), nullptr, 10)) <= 0)
285  return req.SendSimpleResp(400, nullptr, nullptr, "Expiration request has invalid value.", 0);
286  }
287  else if (key == "scope")
288  {
289  char *value_raw = unquote(value.c_str());
290  if (value_raw == nullptr)
291  {
292  return req.SendSimpleResp(400, nullptr, nullptr, "Unable to unquote scope.", 0);
293  }
294  scope = value_raw;
295  free(value_raw);
296  }
297  }
298  if (!found_grant_type)
299  {
300  return req.SendSimpleResp(400, nullptr, nullptr, "Grant type not specified.", 0);
301  }
302  if (scope.empty())
303  {
304  return req.SendSimpleResp(400, nullptr, nullptr, "Scope was not specified.", 0);
305  }
306  std::istringstream token_stream_scope(scope);
307  std::string path;
308  std::vector<std::string> other_caveats;
309  while (std::getline(token_stream_scope, token, ' '))
310  {
311  std::string::size_type col = token.find(":");
312  if (col == std::string::npos)
313  {
314  return req.SendSimpleResp(400, nullptr, nullptr, "Invalid format for requested scope", 0);
315  }
316  std::string key = token.substr(0, col);
317  std::string value = token.substr(col + 1);
318  //std::cout << "Found activity " << key << ", path " << value << std::endl;
319  if (path.empty())
320  {
321  path = value;
322  }
323  else if (value != path)
324  {
325  if (m_log->getMsgMask() & LogMask::Error) {
326  std::stringstream ss;
327  ss << "Encountered requested scope request for authorization " << key
328  << " with resource path " << value << "; however, prior request had path "
329  << path;
330  m_log->Emsg("MacaroonRequest", ss.str().c_str()); // Mask::Error
331  }
332  return req.SendSimpleResp(500, nullptr, nullptr, "Server only supports all scopes having the same path", 0);
333  }
334  other_caveats.push_back(key);
335  }
336  if (path.empty())
337  {
338  path = "/";
339  }
340  std::vector<std::string> other_caveats_final;
341  if (!other_caveats.empty()) {
342  std::stringstream ss;
343  ss << "activity:";
344  for (std::vector<std::string>::const_iterator iter = other_caveats.begin();
345  iter != other_caveats.end();
346  iter++)
347  {
348  ss << *iter << ",";
349  }
350  const std::string &final_str = ss.str();
351  other_caveats_final.push_back(final_str.substr(0, final_str.size() - 1));
352  }
353  return GenerateMacaroonResponse(req, path, other_caveats_final, validity, true);
354 }
355 
356 // Process a macaroon request.
358 {
359  if (req.resource == "/.well-known/oauth-authorization-server") {
360  return ProcessOAuthConfig(req);
361  } else if (req.resource == "/.oauth2/token") {
362  return ProcessTokenRequest(req);
363  }
364 
365  auto header = XrdOucTUtils::caseInsensitiveFind(req.headers,"content-type");
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);
369 
370  header = XrdOucTUtils::caseInsensitiveFind(req.headers,"content-length");
371  if (header == req.headers.end())
372  return req.SendSimpleResp(411, nullptr, nullptr, "Content-Length missing; not a valid POST", false);
373 
374  ssize_t blen = std::strtoll(header->second.c_str(), nullptr, 10);
375 
376  if (blen <= 0)
377  return req.SendSimpleResp(400, nullptr, nullptr, "Content-Length has invalid value.", false);
378 
379  if (blen > 4096)
380  return req.SendSimpleResp(413, nullptr, nullptr, "Macaroon request too large (must be less than 4KB)", false);
381 
382  // request_data is not necessarily null-terminated; hence, we use the more advanced _ex variant
383  // of the tokener to avoid making a copy of the character buffer.
384  char *request_data;
385  if (req.BuffgetData(blen, &request_data, true) != blen)
386  {
387  return req.SendSimpleResp(400, nullptr, nullptr, "Missing or invalid body of request.", 0);
388  }
389  json_tokener *tokener = json_tokener_new();
390  if (!tokener)
391  {
392  return req.SendSimpleResp(500, nullptr, nullptr, "Internal error when allocating token parser.", 0);
393  }
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)
398  {
399  if (macaroon_req) json_object_put(macaroon_req);
400  return req.SendSimpleResp(400, nullptr, nullptr, "Invalid JSON serialization of macaroon request.", 0);
401  }
402  json_object *validity_obj;
403  if (!json_object_object_get_ex(macaroon_req, "validity", &validity_obj))
404  {
405  json_object_put(macaroon_req);
406  return req.SendSimpleResp(400, nullptr, nullptr, "JSON request does not include a `validity`", 0);
407  }
408  const char *validity_cstr = json_object_get_string(validity_obj);
409  if (!validity_cstr)
410  {
411  json_object_put(macaroon_req);
412  return req.SendSimpleResp(400, nullptr, nullptr, "validity key cannot be cast to a string", 0);
413  }
414  std::string validity_str(validity_cstr);
415  ssize_t validity = determine_validity(validity_str);
416  if (validity <= 0)
417  {
418  json_object_put(macaroon_req);
419  return req.SendSimpleResp(400, nullptr, nullptr, "Invalid ISO 8601 duration for validity key", 0);
420  }
421  json_object *caveats_obj;
422  std::vector<std::string> other_caveats;
423  if (json_object_object_get_ex(macaroon_req, "caveats", &caveats_obj))
424  {
425  if (json_object_is_type(caveats_obj, json_type_array))
426  { // Caveats were provided. Let's record them.
427  // TODO - could just add these in-situ. No need for the other_caveats vector.
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++)
431  {
432  json_object *caveat_item = json_object_array_get_idx(caveats_obj, idx);
433  if (caveat_item)
434  {
435  const char *caveat_item_str = json_object_get_string(caveat_item);
436 
437  if (!caveat_item_str) {
438  json_object_put(macaroon_req);
439  return req.SendSimpleResp(400, nullptr, nullptr, "Malformed or invalid caveat", 0);
440  }
441 
442  if (is_reserved_caveat(caveat_item_str)) {
443  json_object_put(macaroon_req);
444  return req.SendSimpleResp(400, nullptr, nullptr,
445  "Cannot accept caveat with reserved key (name, path, before)\n", 0);
446  }
447 
448  if (!is_supported_caveat(caveat_item_str)) {
449  json_object_put(macaroon_req);
450  return req.SendSimpleResp(400, nullptr, nullptr,
451  "Cannot accept caveat of unsupported type (supported types: activity)\n", 0);
452  }
453 
454  other_caveats.emplace_back(caveat_item_str);
455  }
456  }
457  }
458  }
459  json_object_put(macaroon_req);
460 
461  return GenerateMacaroonResponse(req, req.resource, other_caveats, validity, false);
462 }
463 
464 
465 int
466 Handler::GenerateMacaroonResponse(XrdHttpExtReq &req, const std::string &resource,
467  const std::vector<std::string> &other_caveats, ssize_t validity, bool oauth_response)
468 {
469  time_t now;
470  time(&now);
471  if (m_max_duration > 0)
472  {
473  validity = (validity > m_max_duration) ? m_max_duration : validity;
474  }
475  now += validity;
476 
477  char utc_time_buf[21];
478  if (!strftime(utc_time_buf, 21, "%FT%TZ", gmtime(&now)))
479  {
480  return req.SendSimpleResp(500, nullptr, nullptr, "Internal error constructing UTC time", 0);
481  }
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();
486 
487  std::string activities = GenerateActivities(req, resource);
488 
489  // Intersect user-requested activities with those the authz chain permits.
490  // A caveat can only attenuate privileges, never grant new ones.
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));
495  for (std::string a; std::getline(ss, a, ','); )
496  allowed.insert(a); }
497  std::string result = "activity:";
498  bool first = true;
499  std::stringstream ss(caveat.substr(9));
500  for (std::string a; std::getline(ss, a, ','); ) {
501  if (allowed.count(a)) {
502  if (!first) result += ',';
503  result += a;
504  first = false;
505  }
506  }
507  if (result.size() > 9)
508  activities = result;
509  }
510  }
511 
512  std::string macaroon_id = GenerateID(resource, req.GetSecEntity(), activities, other_caveats, utc_time_str);
513  enum macaroon_returncode mac_err;
514 
515  struct macaroon *mac = macaroon_create(reinterpret_cast<const unsigned char*>(m_location.c_str()),
516  m_location.size(),
517  reinterpret_cast<const unsigned char*>(m_secret.c_str()),
518  m_secret.size(),
519  reinterpret_cast<const unsigned char*>(macaroon_id.c_str()),
520  macaroon_id.size(), &mac_err);
521  if (!mac) {
522  return req.SendSimpleResp(500, nullptr, nullptr, "Internal error constructing the macaroon", 0);
523  }
524 
525  // Embed the SecEntity name, if present.
526  struct macaroon *mac_with_name;
527  const char * sec_name = req.GetSecEntity().name;
528  if (sec_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()),
534  name_caveat.size(),
535  &mac_err);
536  macaroon_destroy(mac);
537  } else {
538  mac_with_name = mac;
539  }
540  if (!mac_with_name)
541  {
542  return req.SendSimpleResp(500, nullptr, nullptr, "Internal error adding 'name' caveat to macaroon", 0);
543  }
544 
545  struct macaroon *mac_with_activities = macaroon_add_first_party_caveat(mac_with_name,
546  reinterpret_cast<const unsigned char*>(activities.c_str()),
547  activities.size(),
548  &mac_err);
549  macaroon_destroy(mac_with_name);
550  if (!mac_with_activities)
551  {
552  return req.SendSimpleResp(500, nullptr, nullptr, "Internal error adding 'activity' caveat to macaroon", 0);
553  }
554 
555  // Note we don't call `NormalizeSlashes` here; for backward compatibility reasons, we ensure the
556  // token issued is identical to what was working with prior versions of XRootD. This allows for a
557  // mix of old/new versions in a single cluster to interoperate. In a few years, it might be reasonable
558  // to invoke it here as well.
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()),
562  path_caveat.size(),
563  &mac_err);
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);
567  }
568 
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(),
572  &mac_err);
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);
576  }
577 
578  size_t size_hint = macaroon_serialize_size_hint(mac_with_date);
579 
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))
582  {
583  printf("Returned macaroon_serialize code: %zu\n", size_hint);
584  return req.SendSimpleResp(500, nullptr, nullptr, "Internal error serializing macaroon", 0);
585  }
586  macaroon_destroy(mac_with_date);
587 
588  json_object *response_obj = json_object_new_object();
589  if (!response_obj)
590  {
591  return req.SendSimpleResp(500, nullptr, nullptr, "Unable to create new JSON response object.", 0);
592  }
593  json_object *macaroon_obj = json_object_new_string_len(&macaroon_resp[0], strlen(&macaroon_resp[0]));
594  if (!macaroon_obj)
595  {
596  return req.SendSimpleResp(500, nullptr, nullptr, "Unable to create a new JSON macaroon string.", 0);
597  }
598  json_object_object_add(response_obj, oauth_response ? "access_token" : "macaroon", macaroon_obj);
599 
600  json_object *expire_in_obj = json_object_new_int64(validity);
601  if (!expire_in_obj)
602  {
603  return req.SendSimpleResp(500, nullptr, nullptr, "Unable to create a new JSON validity object.", 0);
604  }
605  json_object_object_add(response_obj, "expires_in", expire_in_obj);
606 
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);
610  return retval;
611 }
@ AOP_Any
Special for getting privs.
XrdAccPrivs
Definition: XrdAccPrivs.hh:39
@ XrdAccPriv_Chown
Definition: XrdAccPrivs.hh:41
@ XrdAccPriv_Read
Definition: XrdAccPrivs.hh:49
@ XrdAccPriv_None
Definition: XrdAccPrivs.hh:53
@ XrdAccPriv_Delete
Definition: XrdAccPrivs.hh:43
@ XrdAccPriv_Create
Definition: XrdAccPrivs.hh:42
@ XrdAccPriv_Readdir
Definition: XrdAccPrivs.hh:50
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)
bool Debug
void getline(uchar *buff, int blen)
@ Error
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
std::string resource
std::string verb
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)
Definition: XrdOucTUtils.hh:79
char * vorg
Entity's virtual organization(s)
Definition: XrdSecEntity.hh:71
char prot[XrdSecPROTOIDSIZE]
Auth protocol used (e.g. krb5)
Definition: XrdSecEntity.hh:67
char * grps
Entity's group name(s)
Definition: XrdSecEntity.hh:73
char * name
Entity's name.
Definition: XrdSecEntity.hh:69
char * role
Entity's role(s)
Definition: XrdSecEntity.hh:72
char * endorsements
Protocol specific endorsements.
Definition: XrdSecEntity.hh:75
char * host
Entity's host name dnr dependent.
Definition: XrdSecEntity.hh:70
int Emsg(const char *esfx, int ecode, const char *text1, const char *text2=0)
Definition: XrdSysError.cc:95
int getMsgMask()
Definition: XrdSysError.hh:156
std::string NormalizeSlashes(const std::string &)