Imported Upstream version 1.36.0
[platform/upstream/grpc.git] / src / core / lib / security / credentials / external / url_external_account_credentials.cc
1 //
2 // Copyright 2020 gRPC authors.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //     http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 #include <grpc/support/port_platform.h>
17
18 #include "src/core/lib/security/credentials/external/url_external_account_credentials.h"
19
20 #include "absl/strings/str_cat.h"
21 #include "absl/strings/str_format.h"
22 #include "absl/strings/str_split.h"
23
24 namespace grpc_core {
25
26 RefCountedPtr<UrlExternalAccountCredentials>
27 UrlExternalAccountCredentials::Create(Options options,
28                                       std::vector<std::string> scopes,
29                                       grpc_error** error) {
30   auto creds = MakeRefCounted<UrlExternalAccountCredentials>(
31       std::move(options), std::move(scopes), error);
32   if (*error == GRPC_ERROR_NONE) {
33     return creds;
34   } else {
35     return nullptr;
36   }
37 }
38
39 UrlExternalAccountCredentials::UrlExternalAccountCredentials(
40     Options options, std::vector<std::string> scopes, grpc_error** error)
41     : ExternalAccountCredentials(options, std::move(scopes)) {
42   auto it = options.credential_source.object_value().find("url");
43   if (it == options.credential_source.object_value().end()) {
44     *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("url field not present.");
45     return;
46   }
47   if (it->second.type() != Json::Type::STRING) {
48     *error =
49         GRPC_ERROR_CREATE_FROM_STATIC_STRING("url field must be a string.");
50     return;
51   }
52   absl::StatusOr<URI> tmp_url = URI::Parse(it->second.string_value());
53   if (!tmp_url.ok()) {
54     *error = GRPC_ERROR_CREATE_FROM_COPIED_STRING(
55         absl::StrFormat("Invalid credential source url. Error: %s",
56                         tmp_url.status().ToString())
57             .c_str());
58     return;
59   }
60   url_ = *tmp_url;
61   // The url must follow the format of <scheme>://<authority>/<path>
62   std::vector<absl::string_view> v =
63       absl::StrSplit(it->second.string_value(), absl::MaxSplits('/', 3));
64   url_full_path_ = absl::StrCat("/", v[3]);
65   it = options.credential_source.object_value().find("headers");
66   if (it != options.credential_source.object_value().end()) {
67     if (it->second.type() != Json::Type::OBJECT) {
68       *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
69           "The JSON value of credential source headers is not an object.");
70       return;
71     }
72     for (auto const& header : it->second.object_value()) {
73       headers_[header.first] = header.second.string_value();
74     }
75   }
76   it = options.credential_source.object_value().find("format");
77   if (it != options.credential_source.object_value().end()) {
78     const Json& format_json = it->second;
79     if (format_json.type() != Json::Type::OBJECT) {
80       *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
81           "The JSON value of credential source format is not an object.");
82       return;
83     }
84     auto format_it = format_json.object_value().find("type");
85     if (format_it == format_json.object_value().end()) {
86       *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
87           "format.type field not present.");
88       return;
89     }
90     if (format_it->second.type() != Json::Type::STRING) {
91       *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
92           "format.type field must be a string.");
93       return;
94     }
95     format_type_ = format_it->second.string_value();
96     if (format_type_ == "json") {
97       format_it = format_json.object_value().find("subject_token_field_name");
98       if (format_it == format_json.object_value().end()) {
99         *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
100             "format.subject_token_field_name field must be present if the "
101             "format is in Json.");
102         return;
103       }
104       if (format_it->second.type() != Json::Type::STRING) {
105         *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
106             "format.subject_token_field_name field must be a string.");
107         return;
108       }
109       format_subject_token_field_name_ = format_it->second.string_value();
110     }
111   }
112 }
113
114 void UrlExternalAccountCredentials::RetrieveSubjectToken(
115     HTTPRequestContext* ctx, const Options& /*options*/,
116     std::function<void(std::string, grpc_error*)> cb) {
117   if (ctx == nullptr) {
118     FinishRetrieveSubjectToken(
119         "",
120         GRPC_ERROR_CREATE_FROM_STATIC_STRING(
121             "Missing HTTPRequestContext to start subject token retrieval."));
122     return;
123   }
124   ctx_ = ctx;
125   cb_ = cb;
126   grpc_httpcli_request request;
127   memset(&request, 0, sizeof(grpc_httpcli_request));
128   request.host = const_cast<char*>(url_.authority().c_str());
129   request.http.path = gpr_strdup(url_full_path_.c_str());
130   grpc_http_header* headers = nullptr;
131   request.http.hdr_count = headers_.size();
132   headers = static_cast<grpc_http_header*>(
133       gpr_malloc(sizeof(grpc_http_header) * request.http.hdr_count));
134   int i = 0;
135   for (auto const& header : headers_) {
136     headers[i].key = gpr_strdup(header.first.c_str());
137     headers[i].value = gpr_strdup(header.second.c_str());
138     ++i;
139   }
140   request.http.hdrs = headers;
141   request.handshaker =
142       url_.scheme() == "https" ? &grpc_httpcli_ssl : &grpc_httpcli_plaintext;
143   grpc_resource_quota* resource_quota =
144       grpc_resource_quota_create("external_account_credentials");
145   grpc_http_response_destroy(&ctx_->response);
146   ctx_->response = {};
147   GRPC_CLOSURE_INIT(&ctx_->closure, OnRetrieveSubjectToken, this, nullptr);
148   grpc_httpcli_get(ctx_->httpcli_context, ctx_->pollent, resource_quota,
149                    &request, ctx_->deadline, &ctx_->closure, &ctx_->response);
150   grpc_resource_quota_unref_internal(resource_quota);
151   grpc_http_request_destroy(&request.http);
152 }
153
154 void UrlExternalAccountCredentials::OnRetrieveSubjectToken(void* arg,
155                                                            grpc_error* error) {
156   UrlExternalAccountCredentials* self =
157       static_cast<UrlExternalAccountCredentials*>(arg);
158   self->OnRetrieveSubjectTokenInternal(GRPC_ERROR_REF(error));
159 }
160
161 void UrlExternalAccountCredentials::OnRetrieveSubjectTokenInternal(
162     grpc_error* error) {
163   if (error != GRPC_ERROR_NONE) {
164     FinishRetrieveSubjectToken("", error);
165     return;
166   }
167   absl::string_view response_body(ctx_->response.body,
168                                   ctx_->response.body_length);
169   if (format_type_ == "json") {
170     grpc_error* error = GRPC_ERROR_NONE;
171     Json response_json = Json::Parse(response_body, &error);
172     if (error != GRPC_ERROR_NONE ||
173         response_json.type() != Json::Type::OBJECT) {
174       FinishRetrieveSubjectToken(
175           "", GRPC_ERROR_CREATE_FROM_STATIC_STRING(
176                   "The format of response is not a valid json object."));
177       return;
178     }
179     auto response_it =
180         response_json.object_value().find(format_subject_token_field_name_);
181     if (response_it == response_json.object_value().end()) {
182       FinishRetrieveSubjectToken("", GRPC_ERROR_CREATE_FROM_STATIC_STRING(
183                                          "Subject token field not present."));
184       return;
185     }
186     if (response_it->second.type() != Json::Type::STRING) {
187       FinishRetrieveSubjectToken("",
188                                  GRPC_ERROR_CREATE_FROM_STATIC_STRING(
189                                      "Subject token field must be a string."));
190       return;
191     }
192     FinishRetrieveSubjectToken(response_it->second.string_value(), error);
193     return;
194   }
195   FinishRetrieveSubjectToken(std::string(response_body), GRPC_ERROR_NONE);
196 }
197
198 void UrlExternalAccountCredentials::FinishRetrieveSubjectToken(
199     std::string subject_token, grpc_error* error) {
200   // Reset context
201   ctx_ = nullptr;
202   // Move object state into local variables.
203   auto cb = cb_;
204   cb_ = nullptr;
205   // Invoke the callback.
206   if (error != GRPC_ERROR_NONE) {
207     cb("", error);
208   } else {
209     cb(subject_token, GRPC_ERROR_NONE);
210   }
211 }
212
213 }  // namespace grpc_core