Stroika Library 3.0d23
 
Loading...
Searching...
No Matches
Client.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
7#include "Stroika/Foundation/Containers/Association.h"
15#include "Stroika/Foundation/IO/Network/Transfer/Connection.h"
18
19#include "Client.h"
20
21using namespace Stroika::Foundation;
25using namespace Stroika::Foundation::Execution;
27
28using namespace Stroika::Frameworks;
29using namespace Stroika::Frameworks::Auth::OAuth;
30
31using Memory::BLOB;
32
33// Comment this in to turn on aggressive noisy DbgTrace in this module
34// #define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
35
36/*
37 ********************************************************************************
38 ************************* Auth::OAuth::TokenRequest ****************************
39 ********************************************************************************
40 */
41String TokenRequest::ToString () const
42{
44 sb << "{"sv;
45 sb << "client_id: "sv << client_id;
46 if (code) {
47 sb << ", code: "sv << code;
48 sb << ", grant_type: authorization_code"sv;
49 }
50 if (refresh_token) {
51 sb << ", refresh_token: "sv << refresh_token;
52 sb << ", grant_type: refresh_token"sv;
53 }
54 if (client_secret) {
55 sb << ", client_secret: "sv << client_secret;
56 }
57 if (redirect_uri) {
58 sb << ", redirect_uri: "sv << redirect_uri;
59 }
60 if (code_verifier) {
61 sb << ", code_verifier: "sv << code_verifier;
62 }
63 sb << "}"sv;
64 return sb;
65}
66
67const ObjectVariantMapper TokenRequest::kMapper = [] () {
69 mapper.AddCommonType<String> ();
70 mapper.AddCommonType<optional<String>> ();
71 mapper.AddCommonType<optional<URI>> ();
72 mapper.AddClass<TokenRequest> ({
73 {"client_id"sv, &TokenRequest::client_id},
74 {"code"sv, &TokenRequest::code},
75 {"refresh_token"sv, &TokenRequest::refresh_token},
76 {"client_secret"sv, &TokenRequest::client_secret},
77 {"redirect_uri"sv, &TokenRequest::redirect_uri},
78 {"code_verifier"sv, &TokenRequest::code_verifier},
79 });
80 return mapper;
81}();
82
83TypedBLOB TokenRequest::ToWireFormat () const
84{
85 if (not code and not refresh_token) {
86 static const auto kExcept_ = RuntimeErrorException{"Missing authorization code/refresh_token"sv};
87 Throw (kExcept_);
88 }
89 if (code and refresh_token) {
90 static const auto kExcept_ = RuntimeErrorException{"Cannot combine authorization code/refresh_token"sv};
91 Throw (kExcept_);
92 }
93 if (client_id.empty ()) {
94 static const auto kExcept_ = RuntimeErrorException{"Missing client_id"sv};
95 Throw (kExcept_);
96 }
97 BLOB reqBody = [&] () {
99 params.Add ({"client_id"sv, client_id});
100 if (code) {
101 params.Add ({"code"sv, *code});
102 params.Add ({"grant_type"sv, "authorization_code"sv});
103 }
104 else {
105 params.Add ({"refresh_token"sv, *refresh_token});
106 params.Add ({"grant_type"sv, "refresh_token"sv});
107 }
108 if (client_secret) {
109 params.Add ({"client_secret"sv, *client_secret});
110 }
111 if (redirect_uri) {
112 params.Add ({"redirect_uri"sv, redirect_uri->As<String> ()});
113 }
114 if (code_verifier) {
115 params.Add ({"code_verifier"sv, *code_verifier});
116 }
117 return Variant::FormURLEncoded::Writer{}.WriteAsBLOB (params);
118 }();
119 return TypedBLOB{reqBody, InternetMediaTypes::kWWWFormURLEncoded};
120}
121
122TokenRequest TokenRequest::FromWireFormat (const TypedBLOB& src)
123{
124 if (not src.fType or not InternetMediaTypeRegistry::sThe->IsA (InternetMediaTypes::kWWWFormURLEncoded, *src.fType)) {
125 static const auto kExcept_ = RuntimeErrorException{"Expected {}"_f(InternetMediaTypes::kWWWFormURLEncoded)};
126 Throw (kExcept_);
127 }
129 static const auto kExcept_clientid_ = RuntimeErrorException{"Missing client_id"sv};
130 static const auto kExcept_authCode_ = RuntimeErrorException{"Missing authentication code"sv};
131 static const auto kExcept_grant_type_ = RuntimeErrorException{"Missing grant_type"sv};
132 auto code = params.LookupOne ("code"sv);
133 auto refresh_token = params.LookupOne ("refresh_token"sv);
134 if (not code and not refresh_token) {
135 static const auto kExcept_ = RuntimeErrorException{"Missing authorization code/refresh_token"sv};
136 Throw (kExcept_);
137 }
138 if (code and refresh_token) {
139 static const auto kExcept_ = RuntimeErrorException{"Cannot combine authorization code/refresh_token"sv};
140 Throw (kExcept_);
141 }
142 return TokenRequest{.client_id = params.LookupOneChecked ("client_id"sv, kExcept_clientid_),
143 .code = code,
144 .refresh_token = refresh_token,
145 .client_secret = params.LookupOne ("client_secret"sv),
146 .redirect_uri = params.LookupOne ("redirect_uri"sv)};
147}
148
149/*
150 ********************************************************************************
151 ************************* Auth::OAuth::TokenResponse ***************************
152 ********************************************************************************
153 */
154String TokenResponse::ToString () const
155{
156 StringBuilder sb;
157 sb << "{"sv;
158 sb << "access_token: "sv << access_token;
159 sb << ", expires_at: "sv << expires_at;
160 sb << ", scope: "sv << scope;
161 if (refresh_token) {
162 sb << ", refresh_token: "sv << refresh_token;
163 }
164 if (id_token) {
165 sb << ", id_token: "sv << id_token;
166 }
167 if (token_type) {
168 sb << ", token_type: "sv << token_type;
169 }
170 sb << "}"sv;
171 return sb;
172}
173
174const ObjectVariantMapper TokenResponse::kMapper = [] () {
175 ObjectVariantMapper mapper;
176 using TypeMappingDetails = ObjectVariantMapper::TypeMappingDetails;
177 mapper.AddCommonType<String> ();
178 mapper.AddCommonType<optional<String>> ();
179 mapper.AddCommonType<DateTime> ();
180 mapper.AddCommonType<Set<String>> ();
181 mapper.AddClass<TokenResponse> ({
182 {"access_token"sv, &TokenResponse::access_token},
183 // expires_at in wire-format is expires_in seconds into future
184 {"expires_in"sv, &TokenResponse::expires_at,
186 [] ([[maybe_unused]] const ObjectVariantMapper& mapper, const DateTime* objOfType) -> VariantValue {
187 return VariantValue{(objOfType->AsUTC () - DateTime::NowUTC ()).As<int> ()};
188 }),
190 [] ([[maybe_unused]] const ObjectVariantMapper& mapper, const VariantValue& d, DateTime* into) -> void {
191 *into = DateTime::NowUTC ().AddSeconds (d.As<int> ());
192 })}},
193 // scope in wire-format is space separated
194 {"scope"sv, &TokenResponse::scope,
196 [] ([[maybe_unused]] const ObjectVariantMapper& mapper, const Set<String>* objOfType) -> VariantValue {
197 return objOfType->Join (" "sv);
198 }),
200 [] ([[maybe_unused]] const ObjectVariantMapper& mapper, const VariantValue& d, Set<String>* into) -> void {
201 *into = Set<String>{d.As<String> ().Tokenize ()};
202 })}},
203 {"refresh_token"sv, &TokenResponse::refresh_token},
204 {"id_token"sv, &TokenResponse::id_token},
205 {"token_type"sv, &TokenResponse::token_type},
206 });
207 return mapper;
208}();
209
210TypedBLOB TokenResponse::ToWireFormat () const
211{
212 return TypedBLOB{Variant::JSON::Writer{}.WriteAsBLOB (kMapper.FromObject (*this)), InternetMediaTypes::kJSON};
213}
214
215TokenResponse TokenResponse::FromWireFormat (const TypedBLOB& src)
216{
217 if (not src.fType or not InternetMediaTypeRegistry::sThe->IsA (InternetMediaTypes::kJSON, *src.fType)) {
218 static const auto kExcept_ = RuntimeErrorException{"Expected JSON"sv};
219 Throw (kExcept_);
220 }
221 return kMapper.ToObject<TokenResponse> (Variant::JSON::Reader{}.Read (src.fData));
222}
223
224/*
225 ********************************************************************************
226 ********************* Auth::OAuth::TokenRevocationRequest **********************
227 ********************************************************************************
228 */
229String TokenRevocationRequest::ToString () const
230{
231 StringBuilder sb;
232 sb << "{"sv;
233 sb << "access_token: "sv << access_token;
234 if (refresh_token) {
235 sb << ", refresh_token: "sv << *refresh_token;
236 }
237 if (client_id) {
238 sb << ", client_id: "sv << *client_id;
239 }
240 if (client_secret) {
241 sb << ", client_secret: "sv << *client_secret;
242 }
243 sb << "}"sv;
244 return sb;
245}
246
247const ObjectVariantMapper TokenRevocationRequest::kMapper = [] () {
248 ObjectVariantMapper mapper;
249 mapper.AddCommonType<String> ();
250 mapper.AddCommonType<optional<String>> ();
251 mapper.AddClass<TokenRevocationRequest> ({
252 {"access_token"sv, &TokenRevocationRequest::access_token},
253 {"refresh_token"sv, &TokenRevocationRequest::refresh_token},
254 {"client_id"sv, &TokenRevocationRequest::client_id},
255 {"client_secret"sv, &TokenRevocationRequest::client_secret},
256 });
257 return mapper;
258}();
259
260TypedBLOB TokenRevocationRequest::ToWireFormat () const
261{
262 if (access_token.empty ()) {
263 static const auto kExcept_ = RuntimeErrorException{"Missing access_token"sv};
264 Throw (kExcept_);
265 }
266 BLOB reqBody = [&] () {
268 if (refresh_token) {
269 params.Add ({"token_type_hint"sv, "refresh_token"sv});
270 params.Add ({"token"sv, *refresh_token});
271 }
272 else {
273 params.Add ({"token_type_hint"sv, "access_token"sv});
274 params.Add ({"token"sv, access_token});
275 }
276 if (client_id) {
277 params.Add ({"client_id"sv, *client_id});
278 }
279 if (client_secret) {
280 params.Add ({"client_secret"sv, *client_secret});
281 }
282 return Variant::FormURLEncoded::Writer{}.WriteAsBLOB (params);
283 }();
284 return TypedBLOB{reqBody, InternetMediaTypes::kWWWFormURLEncoded};
285}
286
287/*
288 ********************************************************************************
289 ******************** Auth::OAuth::TokenIntrospectionResponse *******************
290 ********************************************************************************
291 */
292String TokenIntrospectionResponse::ToString () const
293{
294 StringBuilder sb;
295 sb << "{"sv;
296 sb << ", expires_at: "sv << expires_at;
297 sb << "}"sv;
298 return sb;
299}
300
301const ObjectVariantMapper TokenIntrospectionResponse::kMapper = [] () {
302 ObjectVariantMapper mapper;
303 using TypeMappingDetails = ObjectVariantMapper::TypeMappingDetails;
304 mapper.AddCommonType<String> ();
305 mapper.AddCommonType<optional<String>> ();
306 mapper.AddCommonType<DateTime> ();
307 mapper.AddCommonType<Set<String>> ();
308 // @todo Introspection API INCOMPLETE.... - but need to test with provider that supports it to flesh this out usefully
310 // expires_at in wire-format is expires_in seconds into future
313 [] ([[maybe_unused]] const ObjectVariantMapper& mapper, const DateTime* objOfType) -> VariantValue {
314 return VariantValue{(objOfType->AsUTC () - DateTime::NowUTC ()).As<int> ()};
315 }),
317 [] ([[maybe_unused]] const ObjectVariantMapper& mapper, const VariantValue& d, DateTime* into) -> void {
318 *into = DateTime::NowUTC ().AddSeconds (d.As<int> ());
319 })}},
320 });
321 return mapper;
322}();
323
324TypedBLOB TokenIntrospectionResponse::ToWireFormat () const
325{
326 return TypedBLOB{Variant::JSON::Writer{}.WriteAsBLOB (kMapper.FromObject (*this)), InternetMediaTypes::kJSON};
327}
328
329TokenIntrospectionResponse TokenIntrospectionResponse::FromWireFormat (const TypedBLOB& src)
330{
331 if (not src.fType or not InternetMediaTypeRegistry::sThe->IsA (InternetMediaTypes::kJSON, *src.fType)) {
332 static const auto kExcept_ = RuntimeErrorException{"Expected JSON"sv};
333 Throw (kExcept_);
334 }
335 return kMapper.ToObject<TokenIntrospectionResponse> (Variant::JSON::Reader{}.Read (src.fData));
336}
337
338/*
339 ********************************************************************************
340 ****************************** Auth::OAuth::UserInfo ***************************
341 ********************************************************************************
342 */
343String UserInfo::ToString () const
344{
345 StringBuilder sb;
346 sb << "{"sv;
347 if (name) {
348 sb << "name: "sv << name;
349 }
350 if (given_name) {
351 sb << ", given_name: "sv << given_name;
352 }
353 if (family_name) {
354 sb << ", family_name: "sv << family_name;
355 }
356 if (email) {
357 sb << ", email: "sv << email;
358 }
359 if (picture) {
360 sb << ", picture: "sv << picture;
361 }
362 sb << "}"sv;
363 return sb;
364}
365
366const ObjectVariantMapper UserInfo::kMapper = [] () {
367 ObjectVariantMapper mapper;
368 mapper.AddCommonType<String> ();
369 mapper.AddCommonType<optional<String>> ();
370 mapper.AddCommonType<URI> ();
371 mapper.AddCommonType<optional<URI>> ();
372 mapper.AddClass<UserInfo> ({
373 {"name"sv, &UserInfo::name},
374 {"given_name"sv, &UserInfo::given_name},
375 {"family_name"sv, &UserInfo::family_name},
376 {"email"sv, &UserInfo::email},
377 {"picture"sv, &UserInfo::picture},
378 });
379 return mapper;
380}();
381
382UserInfo UserInfo::FromWireFormat (const TypedBLOB& src)
383{
384 if (not src.fType or not InternetMediaTypeRegistry::sThe->IsA (InternetMediaTypes::kJSON, *src.fType)) {
385 static const auto kExcept_ = RuntimeErrorException{"Expected JSON"sv};
386 Throw (kExcept_);
387 }
388 return kMapper.ToObject<UserInfo> (Variant::JSON::Reader{}.Read (src.fData));
389}
390
391/*
392 ********************************************************************************
393 ***************************** Auth::OAuth::Fetcher *****************************
394 ********************************************************************************
395 */
396Fetcher::Fetcher (const ProviderConfiguration& providerConfiguration, const Options& options)
397 : fProviderConfiguration_{providerConfiguration}
398 , fOptions_{options}
399 , fCache_{options.fCaching ? make_unique<Cache_> () : nullptr}
400{
401}
402
403Fetcher::Fetcher (const Fetcher& src)
404 : fProviderConfiguration_{src.fProviderConfiguration_}
405 , fOptions_{src.fOptions_}
406 , fCache_{src.fCache_ ? make_unique<Cache_> () : nullptr}
407{
408}
409
411{
412#if USE_NOISY_TRACE_IN_THIS_MODULE_
413 Debug::TraceContextBumper ctx{"OAuth::Fetcher::GetToken", "tr={}"_f, tr};
414#endif
415 auto nonCachingFetcher = [&] () -> TokenResponse {
416 using namespace IO::Network::Transfer;
417 URI tokenRequestURI = Memory::ValueOfOrThrow (fProviderConfiguration_.token_uri, RuntimeErrorException{"no token_uri"sv});
418 auto connection = Connection::New ();
419 try {
420 //DbgTrace ("Sending={}"_f, Streams::BinaryToText::Convert (tr.ToWireFormat ().fData));
421 Response r = connection.POST (tokenRequestURI, tr.ToWireFormat ());
422 //DbgTrace ("rawResponse={}"_f, Streams::BinaryToText::Convert (r.GetData ()));
423 return TokenResponse::FromWireFormat (r.GetTypedData ());
424 }
425 catch (...) {
426 DbgTrace ("Fetcher::Token: exception={}"_f, current_exception ());
428 }
429 };
430 auto r = nonCachingFetcher ();
431 if (fCache_) {
432 optional<UserInfo> uInfo = nullopt;
433 if (r.id_token) {
434 // @todo
435 // NOTE - ID_token doesnt contain EXACTLY same info as user_info endpoint - may need to update API to reflect this difference
436 // No, a decoded ID token may not contain the exact same information as the userinfo endpoint response
437 // . The information can overlap significantly, but there are key differences:
438
439 // cache ID_Token return from TOKEN API (since that has the expiry and userinfo information)
440 // This maybe best! Avoids whole API call, and I'm not sure we have the right URL todo this with facebook
441 // as identity manager...
442 // @todo if we got access token AND id token - parse out of ID token the user info and cache in
443 // ...
444 }
445 fCache_->fAccessToken2UserInfo.Add (r.access_token, uInfo, r.expires_at.As<Time::TimePointSeconds> ());
446 }
447#if USE_NOISY_TRACE_IN_THIS_MODULE_
448 DbgTrace ("returning: {}"_f, r);
449#endif
450 return r;
451}
452
453void Fetcher::RevokeTokens (const TokenRevocationRequest& tr) const
454{
455#if USE_NOISY_TRACE_IN_THIS_MODULE_
456 Debug::TraceContextBumper ctx{"OAuth::Fetcher::RevokeTokens", "tr={}"_f, tr};
457#endif
458 if (fCache_) {
459 // remove references to the argument access_token (we dont cache refresh tokens currently)
460 fCache_->fAccessToken2UserInfo.Remove (tr.access_token);
461 }
462 if (optional<URI> revokeURI = fProviderConfiguration_.revocation_endpoint) {
463 using namespace IO::Network::Transfer;
464 auto connection = Connection::New ();
465 try {
466 //DbgTrace ("Sending={}"_f, Streams::BinaryToText::Convert (tr.ToWireFormat ().fData));
467 [[maybe_unused]] Response r = connection.POST (*revokeURI, tr.ToWireFormat ());
468 }
469 catch (...) {
470 DbgTrace ("Fetcher::RevokeTokens: exception={}"_f, current_exception ());
472 }
473 }
474 else {
475 DbgTrace ("Fetcher::RevokeTokens: skipping due to missing revocation_endpoint"_f);
476 }
477}
478
479UserInfo Fetcher::GetUserInfo (const String& accessToken) const
480{
481#if USE_NOISY_TRACE_IN_THIS_MODULE_
482 Debug::TraceContextBumper ctx{"OAuth::Fetcher::GetUserInfo", "accessToken={}"_f, accessToken};
483#endif
484 if (fCache_) {
485 // AccessToken can be in cache, but with no user info (if we got back GetToken result, but no ID-Token).
486 if (optional<optional<UserInfo>> oou = fCache_->fAccessToken2UserInfo.Lookup (accessToken); oou and *oou) {
487 return **oou;
488 }
489 }
490 auto nonCachingFetcher = [&] () -> UserInfo {
491#if USE_NOISY_TRACE_IN_THIS_MODULE_
492 Debug::TraceContextBumper ctx2{"upstream oauth provider fetcher"};
493#endif
494 using namespace IO::Network::Transfer;
495 URI userInfoRequestURI = Memory::ValueOfOrThrow (fProviderConfiguration_.userinfo_endpoint, RuntimeErrorException{"no userinfo_endpoint"sv});
496 auto authInfo = Connection::Options::Authentication{"Bearer "sv + accessToken};
497 auto connection = Connection::New (Connection::Options{.fAuthentication = authInfo});
498 try {
499 Response r = connection.GET (userInfoRequestURI);
500 //DbgTrace ("rawResponse={}"_f, Streams::BinaryToText::Convert (r.GetData ()));
501 return UserInfo::FromWireFormat (r.GetTypedData ());
502 }
503 catch (...) {
504 DbgTrace ("Fetcher::UserInfo: exception={}"_f, current_exception ());
506 }
507 };
508 UserInfo userInfo = nonCachingFetcher ();
509 if (fCache_) {
510 optional<Time::TimePointSeconds> accessTokenExpiresAt = fCache_->fAccessToken2UserInfo.GetExpiration (accessToken);
511 if (accessTokenExpiresAt == nullopt) {
512 // if this is first time we've seen the access_code (e.g. load balancing situation where another server generates access_code and we dont see it)
513 // we still need to know how long the user_info is valid for - so ask, and if we cannot tell, make a conservative guess
514 if (optional<TokenIntrospectionResponse> o = FetchTokenIntrospectionQueitly_ (accessToken)) {
515 accessTokenExpiresAt = o->expires_at.As<Time::TimePointSeconds> ();
516 }
517 else {
518 // @todo this should be option/configurable behavior
519 static constexpr auto kWAG_ = 30s;
520 Time::DateTime t = Time::DateTime::NowUTC () + kWAG_;
521 accessTokenExpiresAt = t.As<Time::TimePointSeconds> ();
522 }
523 }
524 Assert (accessTokenExpiresAt);
525 fCache_->fAccessToken2UserInfo.Add (accessToken, userInfo, *accessTokenExpiresAt);
526 }
527#if USE_NOISY_TRACE_IN_THIS_MODULE_
528 DbgTrace ("returning: {}"_f, userInfo);
529#endif
530 return userInfo;
531}
532
533optional<TokenIntrospectionResponse> Fetcher::FetchTokenIntrospectionQueitly_ (const String& accessToken) const
534{
535 if (fProviderConfiguration_.introspection_endpoint) {
536 // NYI, but no biggie cuz google doesn't either
537 // https://datatracker.ietf.org/doc/html/rfc7662
539 }
540 if (fProviderConfiguration_.tokeninfo_endpoint) {
541 // non-standard approach, but the only one that works with google, the only provider I currently support!
542 using namespace IO::Network::Transfer;
543 auto authInfo = Connection::Options::Authentication{"Bearer "sv + accessToken};
544 auto connection = Connection::New (Connection::Options{.fAuthentication = authInfo});
545 try {
546 //
547 // A successful request returns a JSON object containing information about the token, such as:
548 // issued_to: The client ID to whom the token was issued.
549 // audience: The intended audience for the token.
550 // user_id: The obfuscated unique identifier for the user.
551 // scope: The space-separated list of scopes granted to the token.
552 // expires_in: The number of seconds left until the token expires.
553 // email: The user's email address.
554 // verified_email: A boolean indicating if the email address is verified.
555 // hd: The hosted domain of the user if they belong to a Google Workspace account.
556 //
557 // Google TokenInfo Endpoint
558 // You can use this endpoint to "introspect" an access token by sending a GET request:
559 // Endpoint: https://oauth2.googleapis.com/tokeninfo
560 // Parameter: access_token
561 // Example Request
562 // http
563 // GET https://oauth2.googleapis.com
564 // Use code with caution.
565 //
566 // Expected JSON Response
567 // If the token is valid, Google returns metadata including the expiration time:
568 // json
569 // {
570 // "azp": "123456789-example.apps.googleusercontent.com",
571 // "aud": "123456789-example.apps.googleusercontent.com",
572 // "sub": "111222333444555",
573 // "scope": "https://www.googleapis.com/auth/userinfo.email openid",
574 // "exp": "1710275200", // Expiration time in Unix epoch format
575 // "expires_in": "3599", // Seconds remaining until expiration
576 // "email": "user@example.com",
577 // "email_verified": "true"
578 // }
579 //
580 // CLOSE to same as UserInfo - but all I use this for is the expiration info, so good enuf for that...
581 //
582 Response r = connection.GET (*fProviderConfiguration_.tokeninfo_endpoint);
583 // DbgTrace ("rawResponse={}"_f, Streams::BinaryToText::Convert (r.GetData ()));
584 return TokenIntrospectionResponse::FromWireFormat (r.GetTypedData ());
585 }
586 catch (...) {
587 DbgTrace ("Fetcher::FetchTokenIntrospectionQueitly_: exception={} - BEING IGNORED"_f, current_exception ());
588 return nullopt;
589 }
590 }
591 return nullopt;
592}
#define AssertNotImplemented()
Definition Assertions.h:402
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
Definition Realtime.h:82
#define DbgTrace
Definition Trace.h:317
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
An Association pairs key values with (possibly multiple or none) mapped_type values....
nonvirtual optional< mapped_type > LookupOne(ArgByValueType< key_type > key) const
Lookup and return the first (maybe arbitrarily chosen which is first) value with this key,...
nonvirtual void Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt)
nonvirtual mapped_type LookupOneChecked(ArgByValueType< key_type > key, const THROW_IF_MISSING &throwIfMissing) const
Lookup and return the first (maybe arbitrarily chosen which is first) value with this key,...
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
static Execution::Synchronized< InternetMediaTypeRegistry > sThe
ObjectVariantMapper can be used to map C++ types to and from variant-union types, which can be transp...
nonvirtual void AddClass(const Traversal::Iterable< StructFieldInfo > &fieldDescriptions, const ClassMapperOptions< CLASS > &mapperOptions={})
function< VariantValue(const ObjectVariantMapper &mapper, const T *objOfType)> FromObjectMapperType
nonvirtual void AddCommonType(ARGS &&... args)
nonvirtual VariantValue FromObject(const T &from) const
nonvirtual T ToObject(const VariantValue &v) const
function< void(const ObjectVariantMapper &mapper, const VariantValue &d, T *into)> ToObjectMapperType
TypedBLOB is a named tuple<Memory::BLOB, optional<InternetMediaType>> - with friendlier names,...
Definition TypedBLOB.h:48
Read a stream into an Association (or VariantValue) - following https://url.spec.whatwg....
nonvirtual Association< String, String > ReadAssociation(const Streams::InputStream::Ptr< byte > &in) const
Association (or VariantValue) to the output stream - following https://url.spec.whatwg....
nonvirtual Memory::BLOB WriteAsBLOB(const VariantValue &v) const
Simple variant-value (case variant union) object, with (variant) basic types analogous to a value in ...
nonvirtual CONTAINER_OF_T As(CONTAINER_OF_T_CONSTRUCTOR_ARGS... args) const
simple wrapper on IO::Network::Transfer to do fetching (more configurability to do)
Definition Client.h:238
nonvirtual UserInfo GetUserInfo(const String &accessToken) const
Definition Client.cpp:479
nonvirtual TokenResponse GetToken(const TokenRequest &tr) const
Definition Client.cpp:410
nonvirtual void RevokeTokens(const TokenRevocationRequest &tr) const
Definition Client.cpp:453
Track configuration data about stuff that differentiates different OAuth providers - what URLs to use...
optional< URI > tokeninfo_endpoint
logically similar to introspection_endpoint, but googles incompatible way
optional< URI > introspection_endpoint
RFC 7662 compatible API for finding info about a token - https://datatracker.ietf....
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
RFC 7662 compatible API for finding info about a token - https://datatracker.ietf....
Definition Client.h:186
this is the argument to the Fetcher::GetToken () API. It typically consists of a client_id,...
Definition Client.h:50
this is the response to the Fetcher::GetToken () API. It typically provides an 'access token' with a ...
Definition Client.h:132