in be/src/transport/THttpServer.cpp [274:488]
void THttpServer::headersDone() {
if (!header_x_request_id_.empty() || !header_x_session_id_.empty() ||
!header_x_query_id_.empty()) {
VLOG_RPC << "HTTP Connection Tracing Headers"
<< (header_x_request_id_.empty() ? "" : " x-request-id=" + header_x_request_id_)
<< (header_x_session_id_.empty() ? "" : " x-session-id=" + header_x_session_id_)
<< (header_x_query_id_.empty() ? "" : " x-query-id=" + header_x_query_id_);
}
// Trim and truncate the value of the 'X-Forwarded-For' header.
string origin = origin_;
// After copying origin, reset the value so that it can be reused for the next message.
origin_ = "";
StripWhiteSpace(&origin);
if (origin.length() > MAX_X_FORWARDED_HEADER_LENGTH) {
origin = origin.substr(0, MAX_X_FORWARDED_HEADER_LENGTH);
}
// Store the truncated value of the 'X-Forwarded-For' header in the Connection Context.
callbacks_.set_http_origin_fn(origin);
if (!has_ldap_ && !has_kerberos_ && !has_saml_ && !has_jwt_ && !has_oauth_) {
// We don't need to authenticate.
resetAuthState();
return;
}
if (readWholeBodyForAuth_) {
DCHECK(has_saml_);
// 2nd SAML message in browser mode, get authNResponse from IP.
// Will be handled in bodyDone. Must return before processing
// cookies, as the cookies from the IdP server can confuse our
// logic.
return;
}
bool authorized = false;
// Try authenticating with cookies first.
if (use_cookies_ && !cookie_value_.empty()) {
StripWhiteSpace(&cookie_value_);
// If a 'Cookie' header was provided with an empty value, we ignore it rather than
// counting it as a failed cookie attempt.
if (!cookie_value_.empty()) {
if (callbacks_.cookie_auth_fn(cookie_value_)) {
authorized = true;
if (metrics_enabled_) http_metrics_->total_cookie_auth_success_->Increment(1);
} else if (metrics_enabled_) {
http_metrics_->total_cookie_auth_failure_->Increment(1);
}
}
}
if (!authorized && (has_jwt_ || has_oauth_) && !auth_value_.empty()
&& auth_value_.find('.') != string::npos) {
// Check Authorization header with the Bearer authentication scheme as:
// Authorization: Bearer <token>
// JWT contains at least one period ('.'). A well-formed JWT consists of three
// concatenated Base64url-encoded strings, separated by dots (.).
StripWhiteSpace(&auth_value_);
string jwt_token;
bool got_bearer_auth = TryStripPrefixString(auth_value_, "Bearer ", &jwt_token);
if (got_bearer_auth) {
if (has_jwt_ && callbacks_.jwt_token_auth_fn(jwt_token)) {
authorized = true;
if (metrics_enabled_)
http_metrics_->total_jwt_token_auth_success_->Increment(1);
}
if (!authorized && has_oauth_ && callbacks_.oauth_token_auth_fn(jwt_token)) {
authorized = true;
if (metrics_enabled_)
http_metrics_->total_oauth_token_auth_success_->Increment(1);
}
if (!authorized) {
if (has_jwt_ && metrics_enabled_)
http_metrics_->total_jwt_token_auth_failure_->Increment(1);
if (has_oauth_ && metrics_enabled_)
http_metrics_->total_oauth_token_auth_failure_->Increment(1);
}
}
}
if (!authorized && has_saml_) {
bool fallback_to_other_auths = true;
if (saml_port_ != -1) {
fallback_to_other_auths = false;
// 1st SAML message in browser mode, redirect SSO.
impala::TWrappedHttpResponse* response = callbacks_.get_saml_redirect_fn();
if (response != nullptr) {
returnWrappedResponse(*response);
resetAuthState();
throw TTransportException("HTTP auth - SAML redirection.");
}
} else if (!auth_value_.empty()) {
StripWhiteSpace(&auth_value_);
string stripped_bearer_auth_token;
bool got_bearer_auth =
TryStripPrefixString(auth_value_, "Bearer ", &stripped_bearer_auth_token);
if (got_bearer_auth) {
fallback_to_other_auths = false;
// Final SAML message in browser mode, check bearer and replace it with a cookie.
DCHECK(wrapped_request_ != nullptr);
wrapped_request_->headers[HEADER_AUTHORIZATION] = auth_value_;
if (callbacks_.validate_saml2_bearer_fn()) {
// During EE tests it makes things easier to return 401-Unauthorized here.
// This hack can be removed once there is a Python client that
// supports SAML (IMPALA-10496).
if (!FLAGS_saml2_ee_test_mode) authorized = true;
if (metrics_enabled_) http_metrics_->total_saml_auth_success_->Increment(1);
}
}
}
if (!authorized && !fallback_to_other_auths) {
// Do not fallback to other auth mechanisms, as the client probably expects
// only SAML related responses.
if (metrics_enabled_) http_metrics_->total_saml_auth_failure_->Increment(1);
resetAuthState();
returnUnauthorized();
throw TTransportException("HTTP auth failed.");
}
}
// Bypass auth for connections from trusted domains. Returns a cookie on the first
// successful auth attempt. This check is performed after checking for cookie to avoid
// subsequent reverse DNS lookups which can be unpredictably costly.
// This is also done after SAML related authentication, because it is assumed that if
// the client started the SAML workflow then it doesn't expect Impala to succeed with
// another mechanism.
if (!authorized && check_trusted_domain_) {
string transport_origin;
if (FLAGS_trusted_domain_use_xff_header) {
impala::Status status = impala::GetXFFOriginClientAddress(origin, transport_origin);
if (!status.ok()) LOG(WARNING) << status.GetDetail();
} else {
transport_origin = transport_->getOrigin();
}
StripWhiteSpace(&transport_origin);
if (transport_origin.empty() && FLAGS_trusted_domain_use_xff_header &&
FLAGS_trusted_domain_empty_xff_header_use_origin) {
transport_origin = transport_->getOrigin();
StripWhiteSpace(&transport_origin);
}
if (!transport_origin.empty()) {
if (callbacks_.trusted_domain_check_fn(transport_origin, auth_value_)) {
authorized = true;
if (metrics_enabled_) {
http_metrics_->total_trusted_domain_check_success_->Increment(1);
}
}
}
}
// Bypass auth for connections if trusted auth header was found in connection string.
if (!authorized && found_trusted_auth_header_ && !auth_value_.empty()) {
if (callbacks_.trusted_auth_header_handle_fn(auth_value_)) {
authorized = true;
if (metrics_enabled_) {
http_metrics_->total_trusted_auth_header_check_success_->Increment(1);
}
}
}
// If cookie auth wasn't successful, try to auth with the 'Authorization' header.
if (!authorized) {
// Determine what type of auth header we got.
StripWhiteSpace(&auth_value_);
string stripped_basic_auth_token;
bool got_basic_auth =
TryStripPrefixString(auth_value_, "Basic ", &stripped_basic_auth_token);
string basic_auth_token = got_basic_auth ? move(stripped_basic_auth_token) : "";
string stripped_negotiate_auth_token;
bool got_negotiate_auth =
TryStripPrefixString(auth_value_, "Negotiate ", &stripped_negotiate_auth_token);
string negotiate_auth_token =
got_negotiate_auth ? move(stripped_negotiate_auth_token) : "";
// We can only have gotten one type of auth header.
DCHECK(!got_basic_auth || !got_negotiate_auth);
// For each auth type we support, we call the auth callback if the didn't get a header
// of the other auth type or if the other auth type isn't supported. This way, if a
// client select a supported auth method, they'll only get return headers for that
// method, but if they didn't specify a valid auth method or they didn't provide a
// 'Authorization' header at all, they'll get back 'WWW-Authenticate' return headers
// for all supported auth types.
if (has_ldap_ && (!got_negotiate_auth || !has_kerberos_)) {
if (callbacks_.basic_auth_fn(basic_auth_token)) {
authorized = true;
if (metrics_enabled_) http_metrics_->total_basic_auth_success_->Increment(1);
} else {
if (got_basic_auth && metrics_enabled_) {
http_metrics_->total_basic_auth_failure_->Increment(1);
}
}
}
if (has_kerberos_ && (!got_basic_auth || !has_ldap_)) {
bool is_complete;
if (callbacks_.negotiate_auth_fn(negotiate_auth_token, &is_complete)) {
// If 'is_complete' is false we want to return a 401.
authorized = is_complete;
if (is_complete && metrics_enabled_) {
http_metrics_->total_negotiate_auth_success_->Increment(1);
}
} else {
if (got_negotiate_auth && metrics_enabled_) {
http_metrics_->total_negotiate_auth_failure_->Increment(1);
}
}
}
}
resetAuthState();
if (!authorized) {
returnUnauthorized();
throw TTransportException("HTTP auth failed.");
}
}