1
0
mirror of https://github.com/oauth2-proxy/oauth2-proxy.git synced 2025-02-03 13:21:51 +02:00
oauth2-proxy/providers/azure_test.go

400 lines
14 KiB
Go
Raw Normal View History

2015-11-09 09:28:34 +01:00
package providers
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
2015-11-09 09:28:34 +01:00
"net/http"
"net/http/httptest"
"net/url"
"strings"
2015-11-09 09:28:34 +01:00
"testing"
"time"
"github.com/coreos/go-oidc/v3/oidc"
improved audience handling to support client credentials access tokens without aud claims (#1204) * implementation draft * add cfg options skip-au-when-missing && client-id-verification-claim; enhance the provider data verification logic for sake of the added options * refactor configs, added logging and add additional claim verification * simplify logic by just having one configuration similar to oidc-email-claim * added internal oidc token verifier, so that aud check behavior can be managed with oauth2-proxy and is compatible with extra-jwt-issuers * refactored verification to reduce complexity * refactored verification to reduce complexity * added docs * adjust tests to support new OIDCAudienceClaim and OIDCExtraAudiences options * extend unit tests and ensure that audience is set with the value of aud claim configuration * revert filemodes and update docs * update docs * remove unneccesary logging, refactor audience existence check and added additional unit tests * fix linting issues after rebase on origin/main * cleanup: use new imports for migrated libraries after rebase on origin/main * adapt mock in keycloak_oidc_test.go * allow specifying multiple audience claims, fixed bug where jwt issuers client id was not the being considered and fixed bug where aud claims with multiple audiences has broken the whole validation * fixed formatting issue * do not pass the whole options struct to minimize complexity and dependency to the configuration structure * added changelog entry * update docs Co-authored-by: Sofia Weiler <sofia.weiler@aoe.com> Co-authored-by: Christian Zenker <christian.zenker@aoe.com>
2022-02-15 17:12:22 +01:00
"github.com/golang-jwt/jwt"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
internaloidc "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/providers/oidc"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/assert"
2015-11-09 09:28:34 +01:00
)
type fakeAzureKeySetStub struct{}
func (fakeAzureKeySetStub) VerifySignature(_ context.Context, jwt string) (payload []byte, err error) {
decodeString, err := base64.RawURLEncoding.DecodeString(strings.Split(jwt, ".")[1])
if err != nil {
return nil, err
}
return decodeString, nil
}
type azureOAuthPayload struct {
AccessToken string `json:"access_token,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
ExpiresOn int64 `json:"expires_on,omitempty,string"`
IDToken string `json:"id_token,omitempty"`
}
func testAzureProvider(hostname string, opts options.AzureOptions) *AzureProvider {
verificationOptions := internaloidc.IDTokenVerificationOptions{
improved audience handling to support client credentials access tokens without aud claims (#1204) * implementation draft * add cfg options skip-au-when-missing && client-id-verification-claim; enhance the provider data verification logic for sake of the added options * refactor configs, added logging and add additional claim verification * simplify logic by just having one configuration similar to oidc-email-claim * added internal oidc token verifier, so that aud check behavior can be managed with oauth2-proxy and is compatible with extra-jwt-issuers * refactored verification to reduce complexity * refactored verification to reduce complexity * added docs * adjust tests to support new OIDCAudienceClaim and OIDCExtraAudiences options * extend unit tests and ensure that audience is set with the value of aud claim configuration * revert filemodes and update docs * update docs * remove unneccesary logging, refactor audience existence check and added additional unit tests * fix linting issues after rebase on origin/main * cleanup: use new imports for migrated libraries after rebase on origin/main * adapt mock in keycloak_oidc_test.go * allow specifying multiple audience claims, fixed bug where jwt issuers client id was not the being considered and fixed bug where aud claims with multiple audiences has broken the whole validation * fixed formatting issue * do not pass the whole options struct to minimize complexity and dependency to the configuration structure * added changelog entry * update docs Co-authored-by: Sofia Weiler <sofia.weiler@aoe.com> Co-authored-by: Christian Zenker <christian.zenker@aoe.com>
2022-02-15 17:12:22 +01:00
AudienceClaims: []string{"aud"},
ClientID: "cd6d4fae-f6a6-4a34-8454-2c6b598e9532",
}
2015-11-09 09:28:34 +01:00
p := NewAzureProvider(
&ProviderData{
ProviderName: "",
LoginURL: &url.URL{},
RedeemURL: &url.URL{},
ProfileURL: &url.URL{},
ValidateURL: &url.URL{},
ProtectedResource: &url.URL{},
Scope: "",
EmailClaim: "email",
improved audience handling to support client credentials access tokens without aud claims (#1204) * implementation draft * add cfg options skip-au-when-missing && client-id-verification-claim; enhance the provider data verification logic for sake of the added options * refactor configs, added logging and add additional claim verification * simplify logic by just having one configuration similar to oidc-email-claim * added internal oidc token verifier, so that aud check behavior can be managed with oauth2-proxy and is compatible with extra-jwt-issuers * refactored verification to reduce complexity * refactored verification to reduce complexity * added docs * adjust tests to support new OIDCAudienceClaim and OIDCExtraAudiences options * extend unit tests and ensure that audience is set with the value of aud claim configuration * revert filemodes and update docs * update docs * remove unneccesary logging, refactor audience existence check and added additional unit tests * fix linting issues after rebase on origin/main * cleanup: use new imports for migrated libraries after rebase on origin/main * adapt mock in keycloak_oidc_test.go * allow specifying multiple audience claims, fixed bug where jwt issuers client id was not the being considered and fixed bug where aud claims with multiple audiences has broken the whole validation * fixed formatting issue * do not pass the whole options struct to minimize complexity and dependency to the configuration structure * added changelog entry * update docs Co-authored-by: Sofia Weiler <sofia.weiler@aoe.com> Co-authored-by: Christian Zenker <christian.zenker@aoe.com>
2022-02-15 17:12:22 +01:00
Verifier: internaloidc.NewVerifier(oidc.NewVerifier(
"https://issuer.example.com",
fakeAzureKeySetStub{},
&oidc.Config{
ClientID: "cd6d4fae-f6a6-4a34-8454-2c6b598e9532",
SkipClientIDCheck: true,
SkipIssuerCheck: true,
SkipExpiryCheck: true,
},
improved audience handling to support client credentials access tokens without aud claims (#1204) * implementation draft * add cfg options skip-au-when-missing && client-id-verification-claim; enhance the provider data verification logic for sake of the added options * refactor configs, added logging and add additional claim verification * simplify logic by just having one configuration similar to oidc-email-claim * added internal oidc token verifier, so that aud check behavior can be managed with oauth2-proxy and is compatible with extra-jwt-issuers * refactored verification to reduce complexity * refactored verification to reduce complexity * added docs * adjust tests to support new OIDCAudienceClaim and OIDCExtraAudiences options * extend unit tests and ensure that audience is set with the value of aud claim configuration * revert filemodes and update docs * update docs * remove unneccesary logging, refactor audience existence check and added additional unit tests * fix linting issues after rebase on origin/main * cleanup: use new imports for migrated libraries after rebase on origin/main * adapt mock in keycloak_oidc_test.go * allow specifying multiple audience claims, fixed bug where jwt issuers client id was not the being considered and fixed bug where aud claims with multiple audiences has broken the whole validation * fixed formatting issue * do not pass the whole options struct to minimize complexity and dependency to the configuration structure * added changelog entry * update docs Co-authored-by: Sofia Weiler <sofia.weiler@aoe.com> Co-authored-by: Christian Zenker <christian.zenker@aoe.com>
2022-02-15 17:12:22 +01:00
), verificationOptions),
}, opts)
2015-11-09 09:28:34 +01:00
if hostname != "" {
updateURL(p.Data().LoginURL, hostname)
updateURL(p.Data().RedeemURL, hostname)
updateURL(p.Data().ProfileURL, hostname)
updateURL(p.Data().ValidateURL, hostname)
updateURL(p.Data().ProtectedResource, hostname)
}
return p
}
func TestNewAzureProvider(t *testing.T) {
g := NewWithT(t)
// Test that defaults are set when calling for a new provider with nothing set
providerData := NewAzureProvider(&ProviderData{}, options.AzureOptions{}).Data()
g.Expect(providerData.ProviderName).To(Equal("Azure"))
g.Expect(providerData.LoginURL.String()).To(Equal("https://login.microsoftonline.com/common/oauth2/authorize"))
g.Expect(providerData.RedeemURL.String()).To(Equal("https://login.microsoftonline.com/common/oauth2/token"))
g.Expect(providerData.ProfileURL.String()).To(Equal("https://graph.microsoft.com/v1.0/me"))
g.Expect(providerData.ValidateURL.String()).To(Equal("https://graph.microsoft.com/v1.0/me"))
g.Expect(providerData.Scope).To(Equal("openid"))
2015-11-09 09:28:34 +01:00
}
func TestAzureProviderOverrides(t *testing.T) {
p := NewAzureProvider(
&ProviderData{
LoginURL: &url.URL{
Scheme: "https",
Host: "example.com",
Path: "/oauth/auth"},
RedeemURL: &url.URL{
Scheme: "https",
Host: "example.com",
Path: "/oauth/token"},
ProfileURL: &url.URL{
Scheme: "https",
Host: "example.com",
Path: "/oauth/profile"},
ValidateURL: &url.URL{
Scheme: "https",
Host: "example.com",
Path: "/oauth/tokeninfo"},
ProtectedResource: &url.URL{
Scheme: "https",
Host: "example.com"},
Scope: "profile"},
options.AzureOptions{})
2015-11-09 09:28:34 +01:00
assert.NotEqual(t, nil, p)
assert.Equal(t, "Azure", p.Data().ProviderName)
assert.Equal(t, "https://example.com/oauth/auth",
p.Data().LoginURL.String())
assert.Equal(t, "https://example.com/oauth/token",
p.Data().RedeemURL.String())
assert.Equal(t, "https://example.com/oauth/profile",
p.Data().ProfileURL.String())
assert.Equal(t, "https://example.com/oauth/tokeninfo",
p.Data().ValidateURL.String())
assert.Equal(t, "https://example.com",
p.Data().ProtectedResource.String())
assert.Equal(t, "profile", p.Data().Scope)
}
func TestAzureSetTenant(t *testing.T) {
p := testAzureProvider("", options.AzureOptions{Tenant: "example"})
2015-11-09 09:28:34 +01:00
assert.Equal(t, "Azure", p.Data().ProviderName)
assert.Equal(t, "example", p.Tenant)
assert.Equal(t, "https://login.microsoftonline.com/example/oauth2/authorize",
p.Data().LoginURL.String())
assert.Equal(t, "https://login.microsoftonline.com/example/oauth2/token",
p.Data().RedeemURL.String())
assert.Equal(t, "https://graph.microsoft.com/v1.0/me",
2015-11-09 09:28:34 +01:00
p.Data().ProfileURL.String())
assert.Equal(t, "https://graph.microsoft.com",
2015-11-09 09:28:34 +01:00
p.Data().ProtectedResource.String())
assert.Equal(t, "https://graph.microsoft.com/v1.0/me", p.Data().ValidateURL.String())
2015-11-09 09:28:34 +01:00
assert.Equal(t, "openid", p.Data().Scope)
}
func testAzureBackend(payload string, accessToken, refreshToken string) *httptest.Server {
return testAzureBackendWithError(payload, accessToken, refreshToken, false)
}
func testAzureBackendWithError(payload string, accessToken, refreshToken string, injectError bool) *httptest.Server {
path := "/v1.0/me"
2015-11-09 09:28:34 +01:00
return httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
if (r.URL.Path != path) && r.Method != http.MethodPost {
2015-11-09 09:28:34 +01:00
w.WriteHeader(404)
} else if r.Method == http.MethodPost && r.Body != nil {
if injectError {
w.WriteHeader(400)
} else {
w.WriteHeader(200)
}
w.Write([]byte(payload))
} else if !IsAuthorizedInHeaderWithToken(r.Header, accessToken) &&
!isAuthorizedRefreshInURLWithToken(r.URL, refreshToken) {
2015-11-09 09:28:34 +01:00
w.WriteHeader(403)
} else {
w.WriteHeader(200)
w.Write([]byte(payload))
}
}))
}
func TestAzureProviderEnrichSession(t *testing.T) {
testCases := []struct {
Description string
Email string
PayloadFromAzureBackend string
ExpectedEmail string
ExpectedError error
}{
{
Description: "should return email using mail property from Azure backend",
PayloadFromAzureBackend: `{ "mail": "user@windows.net" }`,
ExpectedEmail: "user@windows.net",
},
{
Description: "should return email using otherMails property returned from Azure backend",
PayloadFromAzureBackend: `{ "mail": null, "otherMails": ["user@windows.net", "altuser@windows.net"] }`,
ExpectedEmail: "user@windows.net",
},
{
Description: "should return email using userPrincipalName from Azure backend",
PayloadFromAzureBackend: `{ "mail": null, "otherMails": [], "userPrincipalName": "user@windows.net" }`,
ExpectedEmail: "user@windows.net",
},
{
Description: "should return error when Azure backend doesn't return email information",
PayloadFromAzureBackend: `{ "mail": null, "otherMails": [], "userPrincipalName": null }`,
ExpectedError: fmt.Errorf("unable to get email address: %v", errors.New("type assertion to string failed")),
},
{
Description: "should return specific error when unable to get email",
PayloadFromAzureBackend: `{ "mail": null, "otherMails": [], "userPrincipalName": "" }`,
ExpectedError: errors.New("unable to get email address"),
},
{
Description: "should return error when otherMails from Azure backend is not a valid type",
PayloadFromAzureBackend: `{ "mail": null, "otherMails": "", "userPrincipalName": null }`,
ExpectedError: fmt.Errorf("unable to get email address: %v", errors.New("type assertion to string failed")),
},
{
Description: "should not query profile api when email is already set in session",
Email: "user@windows.net",
ExpectedEmail: "user@windows.net",
},
}
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
var (
b *httptest.Server
host string
)
if testCase.PayloadFromAzureBackend != "" {
b = testAzureBackend(testCase.PayloadFromAzureBackend, authorizedAccessToken, "")
defer b.Close()
bURL, _ := url.Parse(b.URL)
host = bURL.Host
}
p := testAzureProvider(host, options.AzureOptions{})
session := CreateAuthorizedSession()
session.Email = testCase.Email
err := p.EnrichSession(context.Background(), session)
assert.Equal(t, testCase.ExpectedError, err)
assert.Equal(t, testCase.ExpectedEmail, session.Email)
})
}
}
func TestAzureProviderRedeem(t *testing.T) {
testCases := []struct {
Name string
RefreshToken string
ExpiresOn time.Time
EmailFromIDToken string
EmailFromAccessToken string
IsIDTokenMalformed bool
InjectRedeemURLError bool
}{
{
Name: "with id_token returned",
EmailFromIDToken: "foo1@example.com",
RefreshToken: "some_refresh_token",
ExpiresOn: time.Now().Add(time.Hour),
},
{
Name: "without id_token returned, fallback to access token",
EmailFromAccessToken: "foo2@example.com",
RefreshToken: "some_refresh_token",
ExpiresOn: time.Now().Add(time.Hour),
},
{
Name: "id_token malformed, fallback to access token",
EmailFromAccessToken: "foo3@example.com",
RefreshToken: "some_refresh_token",
ExpiresOn: time.Now().Add(time.Hour),
IsIDTokenMalformed: true,
},
{
Name: "both id_token and access tokens are valid, return email from id_token",
EmailFromIDToken: "foo1@example.com",
EmailFromAccessToken: "foo3@example.com",
RefreshToken: "some_refresh_token",
ExpiresOn: time.Now().Add(time.Hour),
},
{
Name: "redeem URL failed, should return error",
EmailFromIDToken: "foo1@example.com",
EmailFromAccessToken: "foo3@example.com",
RefreshToken: "some_refresh_token",
ExpiresOn: time.Now().Add(time.Hour),
InjectRedeemURLError: true,
},
}
for _, testCase := range testCases {
t.Run(testCase.Name, func(t *testing.T) {
idTokenString := ""
accessTokenString := ""
if testCase.EmailFromIDToken != "" {
var err error
improved audience handling to support client credentials access tokens without aud claims (#1204) * implementation draft * add cfg options skip-au-when-missing && client-id-verification-claim; enhance the provider data verification logic for sake of the added options * refactor configs, added logging and add additional claim verification * simplify logic by just having one configuration similar to oidc-email-claim * added internal oidc token verifier, so that aud check behavior can be managed with oauth2-proxy and is compatible with extra-jwt-issuers * refactored verification to reduce complexity * refactored verification to reduce complexity * added docs * adjust tests to support new OIDCAudienceClaim and OIDCExtraAudiences options * extend unit tests and ensure that audience is set with the value of aud claim configuration * revert filemodes and update docs * update docs * remove unneccesary logging, refactor audience existence check and added additional unit tests * fix linting issues after rebase on origin/main * cleanup: use new imports for migrated libraries after rebase on origin/main * adapt mock in keycloak_oidc_test.go * allow specifying multiple audience claims, fixed bug where jwt issuers client id was not the being considered and fixed bug where aud claims with multiple audiences has broken the whole validation * fixed formatting issue * do not pass the whole options struct to minimize complexity and dependency to the configuration structure * added changelog entry * update docs Co-authored-by: Sofia Weiler <sofia.weiler@aoe.com> Co-authored-by: Christian Zenker <christian.zenker@aoe.com>
2022-02-15 17:12:22 +01:00
token := idTokenClaims{
StandardClaims: jwt.StandardClaims{Audience: "cd6d4fae-f6a6-4a34-8454-2c6b598e9532"},
Email: testCase.EmailFromIDToken}
idTokenString, err = newSignedTestIDToken(token)
assert.NoError(t, err)
}
if testCase.EmailFromAccessToken != "" {
var err error
improved audience handling to support client credentials access tokens without aud claims (#1204) * implementation draft * add cfg options skip-au-when-missing && client-id-verification-claim; enhance the provider data verification logic for sake of the added options * refactor configs, added logging and add additional claim verification * simplify logic by just having one configuration similar to oidc-email-claim * added internal oidc token verifier, so that aud check behavior can be managed with oauth2-proxy and is compatible with extra-jwt-issuers * refactored verification to reduce complexity * refactored verification to reduce complexity * added docs * adjust tests to support new OIDCAudienceClaim and OIDCExtraAudiences options * extend unit tests and ensure that audience is set with the value of aud claim configuration * revert filemodes and update docs * update docs * remove unneccesary logging, refactor audience existence check and added additional unit tests * fix linting issues after rebase on origin/main * cleanup: use new imports for migrated libraries after rebase on origin/main * adapt mock in keycloak_oidc_test.go * allow specifying multiple audience claims, fixed bug where jwt issuers client id was not the being considered and fixed bug where aud claims with multiple audiences has broken the whole validation * fixed formatting issue * do not pass the whole options struct to minimize complexity and dependency to the configuration structure * added changelog entry * update docs Co-authored-by: Sofia Weiler <sofia.weiler@aoe.com> Co-authored-by: Christian Zenker <christian.zenker@aoe.com>
2022-02-15 17:12:22 +01:00
token := idTokenClaims{
StandardClaims: jwt.StandardClaims{Audience: "cd6d4fae-f6a6-4a34-8454-2c6b598e9532"},
Email: testCase.EmailFromAccessToken}
accessTokenString, err = newSignedTestIDToken(token)
assert.NoError(t, err)
}
if testCase.IsIDTokenMalformed {
idTokenString = "this is a malformed id_token"
}
payload := azureOAuthPayload{
IDToken: idTokenString,
RefreshToken: testCase.RefreshToken,
AccessToken: accessTokenString,
ExpiresOn: testCase.ExpiresOn.Unix(),
}
payloadBytes, err := json.Marshal(payload)
assert.NoError(t, err)
b := testAzureBackendWithError(string(payloadBytes), accessTokenString, testCase.RefreshToken, testCase.InjectRedeemURLError)
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testAzureProvider(bURL.Host, options.AzureOptions{})
p.Data().RedeemURL.Path = "/common/oauth2/token"
s, err := p.Redeem(context.Background(), "https://localhost", "1234")
if testCase.InjectRedeemURLError {
assert.NotNil(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, idTokenString, s.IDToken)
assert.Equal(t, accessTokenString, s.AccessToken)
assert.Equal(t, testCase.ExpiresOn.Unix(), s.ExpiresOn.Unix())
assert.Equal(t, testCase.RefreshToken, s.RefreshToken)
if testCase.EmailFromIDToken != "" {
assert.Equal(t, testCase.EmailFromIDToken, s.Email)
} else {
assert.Equal(t, testCase.EmailFromAccessToken, s.Email)
}
}
})
}
}
func TestAzureProviderProtectedResourceConfigured(t *testing.T) {
p := testAzureProvider("", options.AzureOptions{})
p.ProtectedResource, _ = url.Parse("http://my.resource.test")
result := p.GetLoginURL("https://my.test.app/oauth", "", "")
assert.Contains(t, result, "resource="+url.QueryEscape("http://my.resource.test"))
}
func TestAzureProviderRefresh(t *testing.T) {
email := "foo@example.com"
subject := "foo"
improved audience handling to support client credentials access tokens without aud claims (#1204) * implementation draft * add cfg options skip-au-when-missing && client-id-verification-claim; enhance the provider data verification logic for sake of the added options * refactor configs, added logging and add additional claim verification * simplify logic by just having one configuration similar to oidc-email-claim * added internal oidc token verifier, so that aud check behavior can be managed with oauth2-proxy and is compatible with extra-jwt-issuers * refactored verification to reduce complexity * refactored verification to reduce complexity * added docs * adjust tests to support new OIDCAudienceClaim and OIDCExtraAudiences options * extend unit tests and ensure that audience is set with the value of aud claim configuration * revert filemodes and update docs * update docs * remove unneccesary logging, refactor audience existence check and added additional unit tests * fix linting issues after rebase on origin/main * cleanup: use new imports for migrated libraries after rebase on origin/main * adapt mock in keycloak_oidc_test.go * allow specifying multiple audience claims, fixed bug where jwt issuers client id was not the being considered and fixed bug where aud claims with multiple audiences has broken the whole validation * fixed formatting issue * do not pass the whole options struct to minimize complexity and dependency to the configuration structure * added changelog entry * update docs Co-authored-by: Sofia Weiler <sofia.weiler@aoe.com> Co-authored-by: Christian Zenker <christian.zenker@aoe.com>
2022-02-15 17:12:22 +01:00
idToken := idTokenClaims{
Email: email,
StandardClaims: jwt.StandardClaims{
Audience: "cd6d4fae-f6a6-4a34-8454-2c6b598e9532",
Subject: subject,
},
}
idTokenString, err := newSignedTestIDToken(idToken)
assert.NoError(t, err)
timestamp, err := time.Parse(time.RFC3339, "3006-01-02T22:04:05Z")
assert.NoError(t, err)
newAccessToken := "new_some_access_token"
payload := azureOAuthPayload{
IDToken: idTokenString,
RefreshToken: "new_some_refresh_token",
AccessToken: newAccessToken,
ExpiresOn: timestamp.Unix(),
}
payloadBytes, err := json.Marshal(payload)
assert.NoError(t, err)
refreshToken := "some_refresh_token"
b := testAzureBackend(string(payloadBytes), newAccessToken, refreshToken)
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testAzureProvider(bURL.Host, options.AzureOptions{})
expires := time.Now().Add(time.Duration(-1) * time.Hour)
session := &sessions.SessionState{AccessToken: "some_access_token", RefreshToken: refreshToken, IDToken: "some_id_token", ExpiresOn: &expires}
refreshed, err := p.RefreshSession(context.Background(), session)
assert.Equal(t, nil, err)
assert.True(t, refreshed)
assert.NotEqual(t, session, nil)
assert.Equal(t, newAccessToken, session.AccessToken)
assert.Equal(t, "new_some_refresh_token", session.RefreshToken)
assert.Equal(t, idTokenString, session.IDToken)
assert.Equal(t, email, session.Email)
assert.Equal(t, timestamp, session.ExpiresOn.UTC())
}