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>
Create(Options options,std::vector<std::string> scopes,grpc_error ** error)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
UrlExternalAccountCredentials(Options options,std::vector<std::string> scopes,grpc_error ** error)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
RetrieveSubjectToken(HTTPRequestContext * ctx,const Options & options,std::function<void (std::string,grpc_error *)> cb)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
OnRetrieveSubjectToken(void * arg,grpc_error * error)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
OnRetrieveSubjectTokenInternal(grpc_error * error)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
FinishRetrieveSubjectToken(std::string subject_token,grpc_error * error)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
214