1 // Copyright 2021 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 ///////////////////////////////////////////////////////////////////////////////
16
17 #include "tink/jwt/raw_jwt.h"
18
19 #include <string>
20 #include <utility>
21 #include <vector>
22
23 #include "absl/status/status.h"
24 #include "absl/strings/numbers.h"
25 #include "absl/strings/str_format.h"
26 #include "absl/strings/substitute.h"
27 #include "absl/time/time.h"
28 #include "tink/jwt/internal/json_util.h"
29
30 namespace crypto {
31 namespace tink {
32
33 namespace {
34
35 using ::google::protobuf::Struct;
36 using ::google::protobuf::Value;
37
38 // Registered claim names, as defined in
39 // https://tools.ietf.org/html/rfc7519#section-4.1.
40 constexpr absl::string_view kJwtClaimIssuer = "iss";
41 constexpr absl::string_view kJwtClaimSubject = "sub";
42 constexpr absl::string_view kJwtClaimAudience = "aud";
43 constexpr absl::string_view kJwtClaimExpiration = "exp";
44 constexpr absl::string_view kJwtClaimNotBefore = "nbf";
45 constexpr absl::string_view kJwtClaimIssuedAt = "iat";
46 constexpr absl::string_view kJwtClaimJwtId = "jti";
47
48 constexpr int64_t kJwtTimestampMax = 253402300799; // 31 Dec 9999, 23:59:59 GMT
49
IsRegisteredClaimName(absl::string_view name)50 bool IsRegisteredClaimName(absl::string_view name) {
51 return name == kJwtClaimIssuer || name == kJwtClaimSubject ||
52 name == kJwtClaimAudience || name == kJwtClaimExpiration ||
53 name == kJwtClaimNotBefore || name == kJwtClaimIssuedAt ||
54 name == kJwtClaimJwtId;
55 }
56
ValidatePayloadName(absl::string_view name)57 util::Status ValidatePayloadName(absl::string_view name) {
58 if (IsRegisteredClaimName(name)) {
59 return absl::InvalidArgumentError(absl::Substitute(
60 "claim '$0' is invalid because it's a registered name; "
61 "use the corresponding getter or setter method.",
62 name));
63 }
64 return util::OkStatus();
65 }
66
HasClaimOfKind(const google::protobuf::Struct & json_proto,absl::string_view name,Value::KindCase kind)67 bool HasClaimOfKind(const google::protobuf::Struct& json_proto,
68 absl::string_view name, Value::KindCase kind) {
69 if (IsRegisteredClaimName(name)) {
70 return false;
71 }
72 const auto& fields = json_proto.fields();
73 auto it = fields.find(std::string(name));
74 if (it == fields.end()) {
75 return false;
76 }
77 const Value& value = it->second;
78 return value.kind_case() == kind;
79 }
80
81 // Returns true if the claim is present but not a string.
ClaimIsNotAString(const google::protobuf::Struct & json_proto,absl::string_view name)82 bool ClaimIsNotAString(const google::protobuf::Struct& json_proto,
83 absl::string_view name) {
84 const auto& fields = json_proto.fields();
85 auto it = fields.find(std::string(name));
86 if (it == fields.end()) {
87 return false;
88 }
89 const Value& value = it->second;
90 return value.kind_case() != Value::kStringValue;
91 }
92
93 // Returns true if the claim is present but not a list.
ClaimIsNotAList(google::protobuf::Struct & json_proto,absl::string_view name)94 bool ClaimIsNotAList(google::protobuf::Struct& json_proto,
95 absl::string_view name) {
96 const auto& fields = json_proto.fields();
97 auto it = fields.find(std::string(name));
98 if (it == fields.end()) {
99 return false;
100 }
101 const Value& value = it->second;
102 return value.kind_case() != Value::kListValue;
103 }
104
105 // Returns true if the claim is present but not a timestamp.
ClaimIsNotATimestamp(const google::protobuf::Struct & json_proto,absl::string_view name)106 bool ClaimIsNotATimestamp(const google::protobuf::Struct& json_proto,
107 absl::string_view name) {
108 const auto& fields = json_proto.fields();
109 auto it = fields.find(std::string(name));
110 if (it == fields.end()) {
111 return false;
112 }
113 const Value& value = it->second;
114 if (value.kind_case() != Value::kNumberValue) {
115 return true;
116 }
117 double timestamp = value.number_value();
118 return (timestamp > kJwtTimestampMax) || (timestamp < 0);
119 }
120
TimeToTimestamp(absl::Time time)121 int64_t TimeToTimestamp(absl::Time time) {
122 // We round the timestamp to a whole number. We always round down.
123 return absl::ToUnixSeconds(time);
124 }
125
TimestampToTime(double timestamp)126 absl::Time TimestampToTime(double timestamp) {
127 if (timestamp > kJwtTimestampMax) {
128 return absl::FromUnixSeconds(kJwtTimestampMax);
129 }
130 return absl::FromUnixSeconds(timestamp);
131 }
132
ValidateAudienceClaim(const google::protobuf::Struct & json_proto)133 util::Status ValidateAudienceClaim(const google::protobuf::Struct& json_proto) {
134 const auto& fields = json_proto.fields();
135 auto it = fields.find(std::string(kJwtClaimAudience));
136 if (it == fields.end()) {
137 return util::OkStatus();
138 }
139 const Value& value = it->second;
140 if (value.kind_case() == Value::kStringValue) {
141 return util::OkStatus();
142 }
143 if (value.kind_case() != Value::kListValue) {
144 return util::Status(absl::StatusCode::kInvalidArgument,
145 "aud claim is not a list");
146 }
147 if (value.list_value().values_size() < 1) {
148 return util::Status(absl::StatusCode::kInvalidArgument,
149 "aud claim is present but empty");
150 }
151 for (const Value& v : value.list_value().values()) {
152 if (v.kind_case() != Value::kStringValue) {
153 return util::Status(absl::StatusCode::kInvalidArgument,
154 "aud claim is not a list of strings");
155 }
156 }
157 return util::OkStatus();
158 }
159
160 } // namespace
161
FromJson(absl::optional<std::string> type_header,absl::string_view json_payload)162 util::StatusOr<RawJwt> RawJwt::FromJson(absl::optional<std::string> type_header,
163 absl::string_view json_payload) {
164 util::StatusOr<google::protobuf::Struct> proto =
165 jwt_internal::JsonStringToProtoStruct(json_payload);
166 if (!proto.ok()) {
167 return proto.status();
168 }
169 if (ClaimIsNotAString(*proto, kJwtClaimIssuer) ||
170 ClaimIsNotAString(*proto, kJwtClaimSubject) ||
171 ClaimIsNotATimestamp(*proto, kJwtClaimExpiration) ||
172 ClaimIsNotATimestamp(*proto, kJwtClaimNotBefore) ||
173 ClaimIsNotATimestamp(*proto, kJwtClaimIssuedAt)) {
174 return util::Status(absl::StatusCode::kInvalidArgument,
175 "contains an invalid registered claim");
176 }
177 util::Status aud_status = ValidateAudienceClaim(*proto);
178 if (!aud_status.ok()) {
179 return aud_status;
180 }
181 RawJwt token(type_header, *std::move(proto));
182 return token;
183 }
184
GetJsonPayload() const185 util::StatusOr<std::string> RawJwt::GetJsonPayload() const {
186 return jwt_internal::ProtoStructToJsonString(json_proto_);
187 }
188
189 RawJwt::RawJwt() = default;
190
RawJwt(absl::optional<std::string> type_header,google::protobuf::Struct json_proto)191 RawJwt::RawJwt(absl::optional<std::string> type_header,
192 google::protobuf::Struct json_proto) {
193 type_header_ = type_header;
194 json_proto_ = json_proto;
195 }
196
HasTypeHeader() const197 bool RawJwt::HasTypeHeader() const { return type_header_.has_value(); }
198
GetTypeHeader() const199 util::StatusOr<std::string> RawJwt::GetTypeHeader() const {
200 if (!type_header_.has_value()) {
201 return util::Status(absl::StatusCode::kInvalidArgument,
202 "No type header found");
203 }
204 return *type_header_;
205 }
206
HasIssuer() const207 bool RawJwt::HasIssuer() const {
208 return json_proto_.fields().contains(std::string(kJwtClaimIssuer));
209 }
210
GetIssuer() const211 util::StatusOr<std::string> RawJwt::GetIssuer() const {
212 const auto& fields = json_proto_.fields();
213 auto it = fields.find(std::string(kJwtClaimIssuer));
214 if (it == fields.end()) {
215 return util::Status(absl::StatusCode::kInvalidArgument, "No Issuer found");
216 }
217 const Value& value = it->second;
218 if (value.kind_case() != Value::kStringValue) {
219 return util::Status(absl::StatusCode::kInvalidArgument,
220 "Issuer is not a string");
221 }
222 return value.string_value();
223 }
224
HasSubject() const225 bool RawJwt::HasSubject() const {
226 return json_proto_.fields().contains(std::string(kJwtClaimSubject));
227 }
228
GetSubject() const229 util::StatusOr<std::string> RawJwt::GetSubject() const {
230 const auto& fields = json_proto_.fields();
231 auto it = fields.find(std::string(kJwtClaimSubject));
232 if (it == fields.end()) {
233 return util::Status(absl::StatusCode::kInvalidArgument, "No Subject found");
234 }
235 const Value& value = it->second;
236 if (value.kind_case() != Value::kStringValue) {
237 return util::Status(absl::StatusCode::kInvalidArgument,
238 "Subject is not a string");
239 }
240 return value.string_value();
241 }
242
HasAudiences() const243 bool RawJwt::HasAudiences() const {
244 return json_proto_.fields().contains(std::string(kJwtClaimAudience));
245 }
246
GetAudiences() const247 util::StatusOr<std::vector<std::string>> RawJwt::GetAudiences() const {
248 const auto& fields = json_proto_.fields();
249 auto it = fields.find(std::string(kJwtClaimAudience));
250 if (it == fields.end()) {
251 return util::Status(absl::StatusCode::kNotFound, "No Audiences found");
252 }
253 Value list = it->second;
254 if (list.kind_case() != Value::kListValue) {
255 std::vector<std::string> audiences;
256 audiences.push_back(list.string_value());
257 return audiences;
258 }
259 if (list.kind_case() != Value::kListValue) {
260 return util::Status(absl::StatusCode::kInvalidArgument,
261 "Audiences is not a list");
262 }
263 std::vector<std::string> audiences;
264 for (const auto& value : list.list_value().values()) {
265 if (value.kind_case() != Value::kStringValue) {
266 return util::Status(absl::StatusCode::kInvalidArgument,
267 "Audiences is not a list of strings");
268 }
269 audiences.push_back(value.string_value());
270 }
271 return audiences;
272 }
273
HasJwtId() const274 bool RawJwt::HasJwtId() const {
275 return json_proto_.fields().contains(std::string(kJwtClaimJwtId));
276 }
277
GetJwtId() const278 util::StatusOr<std::string> RawJwt::GetJwtId() const {
279 const auto& fields = json_proto_.fields();
280 auto it = fields.find(std::string(kJwtClaimJwtId));
281 if (it == fields.end()) {
282 return util::Status(absl::StatusCode::kNotFound, "No JwtId found");
283 }
284 const Value& value = it->second;
285 if (value.kind_case() != Value::kStringValue) {
286 return util::Status(absl::StatusCode::kInvalidArgument,
287 "JwtId is not a string");
288 }
289 return value.string_value();
290 }
291
HasExpiration() const292 bool RawJwt::HasExpiration() const {
293 return json_proto_.fields().contains(std::string(kJwtClaimExpiration));
294 }
295
GetExpiration() const296 util::StatusOr<absl::Time> RawJwt::GetExpiration() const {
297 const auto& fields = json_proto_.fields();
298 auto it = fields.find(std::string(kJwtClaimExpiration));
299 if (it == fields.end()) {
300 return util::Status(absl::StatusCode::kNotFound, "No Expiration found");
301 }
302 const Value& value = it->second;
303 if (value.kind_case() != Value::kNumberValue) {
304 return util::Status(absl::StatusCode::kInvalidArgument,
305 "Expiration is not a number");
306 }
307 return TimestampToTime(value.number_value());
308 }
309
HasNotBefore() const310 bool RawJwt::HasNotBefore() const {
311 return json_proto_.fields().contains(std::string(kJwtClaimNotBefore));
312 }
313
GetNotBefore() const314 util::StatusOr<absl::Time> RawJwt::GetNotBefore() const {
315 const auto& fields = json_proto_.fields();
316 auto it = fields.find(std::string(kJwtClaimNotBefore));
317 if (it == fields.end()) {
318 return util::Status(absl::StatusCode::kNotFound, "No NotBefore found");
319 }
320 const Value& value = it->second;
321 if (value.kind_case() != Value::kNumberValue) {
322 return util::Status(absl::StatusCode::kInvalidArgument,
323 "NotBefore is not a number");
324 }
325 return TimestampToTime(value.number_value());
326 }
327
HasIssuedAt() const328 bool RawJwt::HasIssuedAt() const {
329 return json_proto_.fields().contains(std::string(kJwtClaimIssuedAt));
330 }
331
GetIssuedAt() const332 util::StatusOr<absl::Time> RawJwt::GetIssuedAt() const {
333 const auto& fields = json_proto_.fields();
334 auto it = fields.find(std::string(kJwtClaimIssuedAt));
335 if (it == fields.end()) {
336 return util::Status(absl::StatusCode::kNotFound, "No IssuedAt found");
337 }
338 const Value& value = it->second;
339 if (value.kind_case() != Value::kNumberValue) {
340 return util::Status(absl::StatusCode::kInvalidArgument,
341 "IssuedAt is not a number");
342 }
343 return TimestampToTime(value.number_value());
344 }
345
IsNullClaim(absl::string_view name) const346 bool RawJwt::IsNullClaim(absl::string_view name) const {
347 return HasClaimOfKind(json_proto_, name, Value::kNullValue);
348 }
349
HasBooleanClaim(absl::string_view name) const350 bool RawJwt::HasBooleanClaim(absl::string_view name) const {
351 return HasClaimOfKind(json_proto_, name, Value::kBoolValue);
352 }
353
GetBooleanClaim(absl::string_view name) const354 util::StatusOr<bool> RawJwt::GetBooleanClaim(
355 absl::string_view name) const {
356 util::Status status = ValidatePayloadName(name);
357 if (!status.ok()) {
358 return status;
359 }
360 const auto& fields = json_proto_.fields();
361 auto it = fields.find(std::string(name));
362 if (it == fields.end()) {
363 return util::Status(absl::StatusCode::kNotFound,
364 absl::Substitute("claim '$0' not found", name));
365 }
366 const Value& value = it->second;
367 if (value.kind_case() != Value::kBoolValue) {
368 return util::Status(absl::StatusCode::kInvalidArgument,
369 absl::Substitute("claim '$0' is not a bool", name));
370 }
371 return value.bool_value();
372 }
373
HasStringClaim(absl::string_view name) const374 bool RawJwt::HasStringClaim(absl::string_view name) const {
375 return HasClaimOfKind(json_proto_, name, Value::kStringValue);
376 }
377
GetStringClaim(absl::string_view name) const378 util::StatusOr<std::string> RawJwt::GetStringClaim(
379 absl::string_view name) const {
380 util::Status status = ValidatePayloadName(name);
381 if (!status.ok()) {
382 return status;
383 }
384 const auto& fields = json_proto_.fields();
385 auto it = fields.find(std::string(name));
386 if (it == fields.end()) {
387 return util::Status(absl::StatusCode::kNotFound,
388 absl::Substitute("claim '$0' not found", name));
389 }
390 const Value& value = it->second;
391 if (value.kind_case() != Value::kStringValue) {
392 return util::Status(absl::StatusCode::kInvalidArgument,
393 absl::Substitute("claim '$0' is not a string", name));
394 }
395 return value.string_value();
396 }
397
HasNumberClaim(absl::string_view name) const398 bool RawJwt::HasNumberClaim(absl::string_view name) const {
399 return HasClaimOfKind(json_proto_, name, Value::kNumberValue);
400 }
401
GetNumberClaim(absl::string_view name) const402 util::StatusOr<double> RawJwt::GetNumberClaim(absl::string_view name) const {
403 util::Status status = ValidatePayloadName(name);
404 if (!status.ok()) {
405 return status;
406 }
407 const auto& fields = json_proto_.fields();
408 auto it = fields.find(std::string(name));
409 if (it == fields.end()) {
410 return util::Status(absl::StatusCode::kNotFound,
411 absl::Substitute("claim '$0' not found", name));
412 }
413 const Value& value = it->second;
414 if (value.kind_case() != Value::kNumberValue) {
415 return util::Status(absl::StatusCode::kInvalidArgument,
416 absl::Substitute("claim '$0' is not a number", name));
417 }
418 return value.number_value();
419 }
420
HasJsonObjectClaim(absl::string_view name) const421 bool RawJwt::HasJsonObjectClaim(absl::string_view name) const {
422 return HasClaimOfKind(json_proto_, name, Value::kStructValue);
423 }
424
GetJsonObjectClaim(absl::string_view name) const425 util::StatusOr<std::string> RawJwt::GetJsonObjectClaim(
426 absl::string_view name) const {
427 util::Status status = ValidatePayloadName(name);
428 if (!status.ok()) {
429 return status;
430 }
431 const auto& fields = json_proto_.fields();
432 auto it = fields.find(std::string(name));
433 if (it == fields.end()) {
434 return util::Status(absl::StatusCode::kNotFound,
435 absl::Substitute("claim '$0' not found", name));
436 }
437 const Value& value = it->second;
438 if (value.kind_case() != Value::kStructValue) {
439 return util::Status(
440 absl::StatusCode::kInvalidArgument,
441 absl::Substitute("claim '$0' is not a JSON object", name));
442 }
443 return jwt_internal::ProtoStructToJsonString(value.struct_value());
444 }
445
HasJsonArrayClaim(absl::string_view name) const446 bool RawJwt::HasJsonArrayClaim(absl::string_view name) const {
447 return HasClaimOfKind(json_proto_, name, Value::kListValue);
448 }
449
GetJsonArrayClaim(absl::string_view name) const450 util::StatusOr<std::string> RawJwt::GetJsonArrayClaim(
451 absl::string_view name) const {
452 util::Status status = ValidatePayloadName(name);
453 if (!status.ok()) {
454 return status;
455 }
456 const auto& fields = json_proto_.fields();
457 auto it = fields.find(std::string(name));
458 if (it == fields.end()) {
459 return util::Status(absl::StatusCode::kNotFound,
460 absl::Substitute("claim '$0' not found", name));
461 }
462 const Value& value = it->second;
463 if (value.kind_case() != Value::kListValue) {
464 return util::Status(
465 absl::StatusCode::kInvalidArgument,
466 absl::Substitute("claim '$0' is not a JSON array", name));
467 }
468 return jwt_internal::ProtoListToJsonString(value.list_value());
469 }
470
CustomClaimNames() const471 std::vector<std::string> RawJwt::CustomClaimNames() const {
472 const auto& fields = json_proto_.fields();
473 std::vector<std::string> values;
474 for (auto it = fields.begin(); it != fields.end(); it++) {
475 if (!IsRegisteredClaimName(it->first)) {
476 values.push_back(it->first);
477 }
478 }
479 return values;
480 }
481
RawJwtBuilder()482 RawJwtBuilder::RawJwtBuilder() { without_expiration_ = false; }
483
SetTypeHeader(absl::string_view type_header)484 RawJwtBuilder& RawJwtBuilder::SetTypeHeader(absl::string_view type_header) {
485 type_header_ = std::string(type_header);
486 return *this;
487 }
488
SetIssuer(absl::string_view issuer)489 RawJwtBuilder& RawJwtBuilder::SetIssuer(absl::string_view issuer) {
490 auto fields = json_proto_.mutable_fields();
491 Value value;
492 value.set_string_value(std::string(issuer));
493 (*fields)[std::string(kJwtClaimIssuer)] = value;
494 return *this;
495 }
496
SetSubject(absl::string_view subject)497 RawJwtBuilder& RawJwtBuilder::SetSubject(absl::string_view subject) {
498 auto fields = json_proto_.mutable_fields();
499 Value value;
500 value.set_string_value(std::string(subject));
501 (*fields)[std::string(kJwtClaimSubject)] = value;
502 return *this;
503 }
504
SetAudience(absl::string_view audience)505 RawJwtBuilder& RawJwtBuilder::SetAudience(absl::string_view audience) {
506 // Make sure that "aud" is not already a list by a call to SetAudiences or
507 // AddAudience.
508 if (ClaimIsNotAString(json_proto_, kJwtClaimAudience)) {
509 error_ = util::Status(absl::StatusCode::kInvalidArgument,
510 "SetAudience() must not be called together with "
511 "SetAudiences() or AddAudience");
512 return *this;
513 }
514 auto fields = json_proto_.mutable_fields();
515 Value value;
516 value.set_string_value(std::string(audience));
517 (*fields)[std::string(kJwtClaimAudience)] = value;
518 return *this;
519 }
520
SetAudiences(std::vector<std::string> audiences)521 RawJwtBuilder& RawJwtBuilder::SetAudiences(std::vector<std::string> audiences) {
522 // Make sure that "aud" is not already a string by a call to SetAudience.
523 if (ClaimIsNotAList(json_proto_, kJwtClaimAudience)) {
524 error_ = util::Status(
525 absl::StatusCode::kInvalidArgument,
526 "SetAudiences() and SetAudience() must not be called together");
527 return *this;
528 }
529 auto fields = json_proto_.mutable_fields();
530 Value value;
531 for (const auto& audience : audiences) {
532 value.mutable_list_value()->add_values()->set_string_value(audience);
533 }
534 (*fields)[std::string(kJwtClaimAudience)] = value;
535 return *this;
536 }
537
AddAudience(absl::string_view audience)538 RawJwtBuilder& RawJwtBuilder::AddAudience(absl::string_view audience) {
539 // Make sure that "aud" is not already a string by a call to SetAudience.
540 if (ClaimIsNotAList(json_proto_, kJwtClaimAudience)) {
541 error_ = util::Status(
542 absl::StatusCode::kInvalidArgument,
543 "AddAudience() and SetAudience() must not be called together");
544 return *this;
545 }
546 auto fields = json_proto_.mutable_fields();
547 auto insertion_result =
548 fields->insert({std::string(kJwtClaimAudience), Value()});
549 google::protobuf::ListValue* list_value =
550 insertion_result.first->second.mutable_list_value();
551 list_value->add_values()->set_string_value(std::string(audience));
552 return *this;
553 }
554
SetJwtId(absl::string_view jwid)555 RawJwtBuilder& RawJwtBuilder::SetJwtId(absl::string_view jwid) {
556 auto fields = json_proto_.mutable_fields();
557 Value value;
558 value.set_string_value(std::string(jwid));
559 (*fields)[std::string(kJwtClaimJwtId)] = value;
560 return *this;
561 }
562
WithoutExpiration()563 RawJwtBuilder& RawJwtBuilder::WithoutExpiration() {
564 without_expiration_ = true;
565 return *this;
566 }
567
SetExpiration(absl::Time expiration)568 RawJwtBuilder& RawJwtBuilder::SetExpiration(absl::Time expiration) {
569 int64_t exp_timestamp = TimeToTimestamp(expiration);
570 if ((exp_timestamp > kJwtTimestampMax) || (exp_timestamp < 0)) {
571 if (!error_.has_value()) {
572 error_ = util::Status(absl::StatusCode::kInvalidArgument,
573 "invalid expiration timestamp");
574 }
575 return *this;
576 }
577 auto fields = json_proto_.mutable_fields();
578 Value value;
579 value.set_number_value(exp_timestamp);
580 (*fields)[std::string(kJwtClaimExpiration)] = value;
581 return *this;
582 }
583
SetNotBefore(absl::Time not_before)584 RawJwtBuilder& RawJwtBuilder::SetNotBefore(absl::Time not_before) {
585 int64_t nbf_timestamp = TimeToTimestamp(not_before);
586 if ((nbf_timestamp > kJwtTimestampMax) || (nbf_timestamp < 0)) {
587 if (!error_.has_value()) {
588 error_ = util::Status(absl::StatusCode::kInvalidArgument,
589 "invalid not_before timestamp");
590 }
591 return *this;
592 }
593 auto fields = json_proto_.mutable_fields();
594 Value value;
595 value.set_number_value(nbf_timestamp);
596 (*fields)[std::string(kJwtClaimNotBefore)] = value;
597 return *this;
598 }
599
SetIssuedAt(absl::Time issued_at)600 RawJwtBuilder& RawJwtBuilder::SetIssuedAt(absl::Time issued_at) {
601 int64_t iat_timestamp = TimeToTimestamp(issued_at);
602 if ((iat_timestamp > kJwtTimestampMax) || (iat_timestamp < 0)) {
603 if (!error_.has_value()) {
604 error_ = util::Status(absl::StatusCode::kInvalidArgument,
605 "invalid issued_at timestamp");
606 }
607 return *this;
608 }
609 auto fields = json_proto_.mutable_fields();
610 Value value;
611 value.set_number_value(iat_timestamp);
612 (*fields)[std::string(kJwtClaimIssuedAt)] = value;
613 return *this;
614 }
615
AddNullClaim(absl::string_view name)616 RawJwtBuilder& RawJwtBuilder::AddNullClaim(absl::string_view name) {
617 util::Status status = ValidatePayloadName(name);
618 if (!status.ok()) {
619 if (!error_.has_value()) {
620 error_ = status;
621 }
622 return *this;
623 }
624 auto fields = json_proto_.mutable_fields();
625 Value value;
626 value.set_null_value(google::protobuf::NULL_VALUE);
627 (*fields)[std::string(name)] = value;
628 return *this;
629 }
630
AddBooleanClaim(absl::string_view name,bool bool_value)631 RawJwtBuilder& RawJwtBuilder::AddBooleanClaim(absl::string_view name,
632 bool bool_value) {
633 util::Status status = ValidatePayloadName(name);
634 if (!status.ok()) {
635 if (!error_.has_value()) {
636 error_ = status;
637 }
638 return *this;
639 }
640 auto fields = json_proto_.mutable_fields();
641 Value value;
642 value.set_bool_value(bool_value);
643 (*fields)[std::string(name)] = value;
644 return *this;
645 }
646
AddStringClaim(absl::string_view name,absl::string_view string_value)647 RawJwtBuilder& RawJwtBuilder::AddStringClaim(absl::string_view name,
648 absl::string_view string_value) {
649 util::Status status = ValidatePayloadName(name);
650 if (!status.ok()) {
651 if (!error_.has_value()) {
652 error_ = status;
653 }
654 return *this;
655 }
656 auto fields = json_proto_.mutable_fields();
657 Value value;
658 value.set_string_value(std::string(string_value));
659 (*fields)[std::string(name)] = value;
660 return *this;
661 }
662
AddNumberClaim(absl::string_view name,double double_value)663 RawJwtBuilder& RawJwtBuilder::AddNumberClaim(absl::string_view name,
664 double double_value) {
665 util::Status status = ValidatePayloadName(name);
666 if (!status.ok()) {
667 if (!error_.has_value()) {
668 error_ = status;
669 }
670 return *this;
671 }
672 auto fields = json_proto_.mutable_fields();
673 Value value;
674 value.set_number_value(double_value);
675 (*fields)[std::string(name)] = value;
676 return *this;
677 }
678
AddJsonObjectClaim(absl::string_view name,absl::string_view object_value)679 RawJwtBuilder& RawJwtBuilder::AddJsonObjectClaim(
680 absl::string_view name, absl::string_view object_value) {
681 util::Status status = ValidatePayloadName(name);
682 if (!status.ok()) {
683 if (!error_.has_value()) {
684 error_ = status;
685 }
686 return *this;
687 }
688 util::StatusOr<google::protobuf::Struct> proto =
689 jwt_internal::JsonStringToProtoStruct(object_value);
690 if (!proto.ok()) {
691 if (!error_.has_value()) {
692 error_ = proto.status();
693 }
694 return *this;
695 }
696 auto fields = json_proto_.mutable_fields();
697 Value value;
698 *value.mutable_struct_value() = *std::move(proto);
699 (*fields)[std::string(name)] = value;
700 return *this;
701 }
702
AddJsonArrayClaim(absl::string_view name,absl::string_view array_value)703 RawJwtBuilder& RawJwtBuilder::AddJsonArrayClaim(absl::string_view name,
704 absl::string_view array_value) {
705 util::Status status = ValidatePayloadName(name);
706 if (!status.ok()) {
707 if (!error_.has_value()) {
708 error_ = status;
709 }
710 return *this;
711 }
712 util::StatusOr<google::protobuf::ListValue> list =
713 jwt_internal::JsonStringToProtoList(array_value);
714 if (!list.ok()) {
715 if (!error_.has_value()) {
716 error_ = list.status();
717 }
718 return *this;
719 }
720 auto fields = json_proto_.mutable_fields();
721 Value value;
722 *value.mutable_list_value() = *list;
723 (*fields)[std::string(name)] = value;
724 return *this;
725 }
726
Build()727 util::StatusOr<RawJwt> RawJwtBuilder::Build() {
728 if (error_.has_value()) {
729 return *error_;
730 }
731 if (!json_proto_.fields().contains(std::string(kJwtClaimExpiration)) &&
732 !without_expiration_) {
733 return util::Status(
734 absl::StatusCode::kInvalidArgument,
735 "neither SetExpiration() nor WithoutExpiration() was called");
736 }
737 if (json_proto_.fields().contains(std::string(kJwtClaimExpiration)) &&
738 without_expiration_) {
739 return util::Status(
740 absl::StatusCode::kInvalidArgument,
741 "SetExpiration() and WithoutExpiration() must not be called together");
742 }
743 RawJwt token(type_header_, json_proto_);
744 return token;
745 }
746
747 } // namespace tink
748 } // namespace crypto
749