1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
// Copyright Epic Games, Inc. All Rights Reserved.
#include "zenhttp/auth/oidc.h"
#include <zenhttp/httpclient.h>
ZEN_THIRD_PARTY_INCLUDES_START
#include <fmt/format.h>
#include <json11.hpp>
ZEN_THIRD_PARTY_INCLUDES_END
namespace zen {
namespace details {
using StringArray = std::vector<std::string>;
StringArray ToStringArray(const json11::Json JsonArray)
{
StringArray Result;
const auto& Items = JsonArray.array_items();
for (const auto& Item : Items)
{
Result.push_back(Item.string_value());
}
return Result;
}
} // namespace details
using namespace std::literals;
static std::string
FormUrlEncode(std::string_view Input)
{
std::string Result;
Result.reserve(Input.size());
for (char C : Input)
{
if ((C >= 'A' && C <= 'Z') || (C >= 'a' && C <= 'z') || (C >= '0' && C <= '9') || C == '-' || C == '_' || C == '.' || C == '~')
{
Result.push_back(C);
}
else
{
Result.append(fmt::format("%{:02X}", static_cast<uint8_t>(C)));
}
}
return Result;
}
OidcClient::OidcClient(const OidcClient::Options& Options)
{
m_BaseUrl = std::string(Options.BaseUrl);
m_ClientId = std::string(Options.ClientId);
}
OidcClient::InitResult
OidcClient::Initialize()
{
HttpClient Http{m_BaseUrl};
HttpClient::Response Response = Http.Get("/.well-known/openid-configuration"sv);
if (!Response)
{
return {.Reason = Response.ErrorMessage("")};
}
if (Response.StatusCode != HttpResponseCode::OK)
{
return {.Reason = std::string{ToString(Response.StatusCode)}};
}
std::string JsonError;
json11::Json Json = json11::Json::parse(std::string{Response.AsText()}, JsonError);
if (JsonError.empty() == false)
{
return {.Reason = std::move(JsonError)};
}
m_Config = {.Issuer = Json["issuer"].string_value(),
.AuthorizationEndpoint = Json["authorization_endpoint"].string_value(),
.TokenEndpoint = Json["token_endpoint"].string_value(),
.UserInfoEndpoint = Json["userinfo_endpoint"].string_value(),
.RegistrationEndpoint = Json["registration_endpoint"].string_value(),
.EndSessionEndpoint = Json["end_session_endpoint"].string_value(),
.DeviceAuthorizationEndpoint = Json["device_authorization_endpoint"].string_value(),
.JwksUri = Json["jwks_uri"].string_value(),
.SupportedResponseTypes = details::ToStringArray(Json["response_types_supported"]),
.SupportedResponseModes = details::ToStringArray(Json["response_modes_supported"]),
.SupportedGrantTypes = details::ToStringArray(Json["grant_types_supported"]),
.SupportedScopes = details::ToStringArray(Json["scopes_supported"]),
.SupportedTokenEndpointAuthMethods = details::ToStringArray(Json["token_endpoint_auth_methods_supported"]),
.SupportedClaims = details::ToStringArray(Json["claims_supported"])};
return {.Ok = true};
}
OidcClient::RefreshTokenResult
OidcClient::RefreshToken(std::string_view RefreshToken)
{
const std::string Body =
fmt::format("grant_type=refresh_token&refresh_token={}&client_id={}", FormUrlEncode(RefreshToken), FormUrlEncode(m_ClientId));
HttpClient Http{m_Config.TokenEndpoint};
HttpClient::KeyValueMap Headers{{"Content-Type", "application/x-www-form-urlencoded"}};
HttpClient::Response Response = Http.Post("", IoBufferBuilder::MakeFromMemory(MemoryView{Body.data(), Body.size()}), Headers);
if (!Response)
{
return {.Reason = std::string{Response.ErrorMessage("")}};
}
if (Response.StatusCode != HttpResponseCode::OK)
{
return {.Reason = fmt::format("{} ({})", ToString(Response.StatusCode), Response.AsText())};
}
std::string JsonError;
json11::Json Json = json11::Json::parse(std::string{Response.AsText()}, JsonError);
if (JsonError.empty() == false)
{
return {.Reason = std::move(JsonError)};
}
return {.TokenType = Json["token_type"].string_value(),
.AccessToken = Json["access_token"].string_value(),
.RefreshToken = Json["refresh_token"].string_value(),
.IdentityToken = Json["id_token"].string_value(),
.Scope = Json["scope"].string_value(),
.ExpiresInSeconds = static_cast<int64_t>(Json["expires_in"].int_value()),
.Ok = true};
}
} // namespace zen
|