Skip to content
This repository was archived by the owner on Feb 12, 2025. It is now read-only.

Commit 7dd32b6

Browse files
authored
Handle expires_on in int format (#698)
* Handle expires_on in int format Unmarshal the value into an interface{} and perform the proper conversion depending on the underlying type. * remove the cruft * convert date-time expires_on to Unix time * fix padding for test * remove comment
1 parent e10c7aa commit 7dd32b6

File tree

2 files changed

+69
-21
lines changed

2 files changed

+69
-21
lines changed

autorest/adal/token.go

+20-11
Original file line numberDiff line numberDiff line change
@@ -1104,8 +1104,8 @@ func (spt *ServicePrincipalToken) refreshInternal(ctx context.Context, resource
11041104

11051105
// AAD returns expires_in as a string, ADFS returns it as an int
11061106
ExpiresIn json.Number `json:"expires_in"`
1107-
// expires_on can be in two formats, a UTC time stamp or the number of seconds.
1108-
ExpiresOn string `json:"expires_on"`
1107+
// expires_on can be in three formats, a UTC time stamp, or the number of seconds as a string *or* int.
1108+
ExpiresOn interface{} `json:"expires_on"`
11091109
NotBefore json.Number `json:"not_before"`
11101110

11111111
Resource string `json:"resource"`
@@ -1118,7 +1118,7 @@ func (spt *ServicePrincipalToken) refreshInternal(ctx context.Context, resource
11181118
}
11191119
expiresOn := json.Number("")
11201120
// ADFS doesn't include the expires_on field
1121-
if token.ExpiresOn != "" {
1121+
if token.ExpiresOn != nil {
11221122
if expiresOn, err = parseExpiresOn(token.ExpiresOn); err != nil {
11231123
return newTokenRefreshError(fmt.Sprintf("adal: failed to parse expires_on: %v value '%s'", err, token.ExpiresOn), resp)
11241124
}
@@ -1135,18 +1135,27 @@ func (spt *ServicePrincipalToken) refreshInternal(ctx context.Context, resource
11351135
}
11361136

11371137
// converts expires_on to the number of seconds
1138-
func parseExpiresOn(s string) (json.Number, error) {
1139-
// convert the expiration date to the number of seconds from now
1138+
func parseExpiresOn(s interface{}) (json.Number, error) {
1139+
// the JSON unmarshaler treats JSON numbers unmarshaled into an interface{} as float64
1140+
asFloat64, ok := s.(float64)
1141+
if ok {
1142+
// this is the number of seconds as int case
1143+
return json.Number(strconv.FormatInt(int64(asFloat64), 10)), nil
1144+
}
1145+
asStr, ok := s.(string)
1146+
if !ok {
1147+
return "", fmt.Errorf("unexpected expires_on type %T", s)
1148+
}
1149+
// convert the expiration date to the number of seconds from the unix epoch
11401150
timeToDuration := func(t time.Time) json.Number {
1141-
dur := t.Sub(time.Now().UTC())
1142-
return json.Number(strconv.FormatInt(int64(dur.Round(time.Second).Seconds()), 10))
1151+
return json.Number(strconv.FormatInt(t.UTC().Unix(), 10))
11431152
}
1144-
if _, err := strconv.ParseInt(s, 10, 64); err == nil {
1153+
if _, err := json.Number(asStr).Int64(); err == nil {
11451154
// this is the number of seconds case, no conversion required
1146-
return json.Number(s), nil
1147-
} else if eo, err := time.Parse(expiresOnDateFormatPM, s); err == nil {
1155+
return json.Number(asStr), nil
1156+
} else if eo, err := time.Parse(expiresOnDateFormatPM, asStr); err == nil {
11481157
return timeToDuration(eo), nil
1149-
} else if eo, err := time.Parse(expiresOnDateFormat, s); err == nil {
1158+
} else if eo, err := time.Parse(expiresOnDateFormat, asStr); err == nil {
11501159
return timeToDuration(eo), nil
11511160
} else {
11521161
// unknown format

autorest/adal/token_test.go

+49-10
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,7 @@ func TestTokenWillExpireIn(t *testing.T) {
8888
}
8989

9090
func TestParseExpiresOn(t *testing.T) {
91-
// get current time, round to nearest second, and add one hour
92-
n := time.Now().UTC().Round(time.Second).Add(time.Hour)
91+
n := time.Now().UTC()
9392
amPM := "AM"
9493
if n.Hour() >= 12 {
9594
amPM = "PM"
@@ -107,12 +106,12 @@ func TestParseExpiresOn(t *testing.T) {
107106
{
108107
Name: "timestamp with AM/PM",
109108
String: fmt.Sprintf("%d/%d/%d %d:%02d:%02d %s +00:00", n.Month(), n.Day(), n.Year(), n.Hour(), n.Minute(), n.Second(), amPM),
110-
Value: 3600,
109+
Value: n.Unix(),
111110
},
112111
{
113112
Name: "timestamp without AM/PM",
114-
String: fmt.Sprintf("%d/%d/%d %d:%02d:%02d +00:00", n.Month(), n.Day(), n.Year(), n.Hour(), n.Minute(), n.Second()),
115-
Value: 3600,
113+
String: fmt.Sprintf("%02d/%02d/%02d %02d:%02d:%02d +00:00", n.Month(), n.Day(), n.Year(), n.Hour(), n.Minute(), n.Second()),
114+
Value: n.Unix(),
116115
},
117116
}
118117
for _, tc := range testcases {
@@ -368,7 +367,8 @@ func TestServicePrincipalTokenFromASE(t *testing.T) {
368367
}
369368
spt.MaxMSIRefreshAttempts = 1
370369
// expires_on is sent in UTC
371-
expiresOn := time.Now().UTC().Add(time.Hour)
370+
nowTime := time.Now()
371+
expiresOn := nowTime.UTC().Add(time.Hour)
372372
// use int format for expires_in
373373
body := mocks.NewBody(newTokenJSON("3600", expiresOn.Format(expiresOnDateFormat), "test"))
374374
resp := mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK")
@@ -407,10 +407,8 @@ func TestServicePrincipalTokenFromASE(t *testing.T) {
407407
if err != nil {
408408
t.Fatalf("adal: failed to get ExpiresOn %v", err)
409409
}
410-
// depending on elapsed time it might be slightly less that one hour
411-
const hourInSeconds = int64(time.Hour / time.Second)
412-
if v > hourInSeconds || v < hourInSeconds-1 {
413-
t.Fatalf("adal: expected %v, got %v", int64(time.Hour/time.Second), v)
410+
if nowAsUnix := nowTime.Add(time.Hour).Unix(); v != nowAsUnix {
411+
t.Fatalf("adal: expected %v, got %v", nowAsUnix, v)
414412
}
415413
if body.IsOpen() {
416414
t.Fatalf("the response was not closed!")
@@ -891,6 +889,34 @@ func TestServicePrincipalTokenEnsureFreshRefreshes(t *testing.T) {
891889
}
892890
}
893891

892+
func TestServicePrincipalTokenEnsureFreshWithIntExpiresOn(t *testing.T) {
893+
spt := newServicePrincipalToken()
894+
expireToken(&spt.inner.Token)
895+
896+
body := mocks.NewBody(newTokenJSONIntExpiresOn(`"3600"`, 12345, "test"))
897+
resp := mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK")
898+
899+
f := false
900+
c := mocks.NewSender()
901+
s := DecorateSender(c,
902+
(func() SendDecorator {
903+
return func(s Sender) Sender {
904+
return SenderFunc(func(r *http.Request) (*http.Response, error) {
905+
f = true
906+
return resp, nil
907+
})
908+
}
909+
})())
910+
spt.SetSender(s)
911+
err := spt.EnsureFresh()
912+
if err != nil {
913+
t.Fatalf("adal: ServicePrincipalToken#EnsureFresh returned an unexpected error (%v)", err)
914+
}
915+
if !f {
916+
t.Fatal("adal: ServicePrincipalToken#EnsureFresh failed to call Refresh for stale token")
917+
}
918+
}
919+
894920
func TestServicePrincipalTokenEnsureFreshFails1(t *testing.T) {
895921
spt := newServicePrincipalToken()
896922
expireToken(&spt.inner.Token)
@@ -1461,6 +1487,19 @@ func newTokenJSON(expiresIn, expiresOn, resource string) string {
14611487
expiresIn, expiresOn, nb, resource)
14621488
}
14631489

1490+
func newTokenJSONIntExpiresOn(expiresIn string, expiresOn int, resource string) string {
1491+
return fmt.Sprintf(`{
1492+
"access_token" : "accessToken",
1493+
"expires_in" : %s,
1494+
"expires_on" : %d,
1495+
"not_before" : "%d",
1496+
"resource" : "%s",
1497+
"token_type" : "Bearer",
1498+
"refresh_token": "ABC123"
1499+
}`,
1500+
expiresIn, expiresOn, expiresOn, resource)
1501+
}
1502+
14641503
func newADFSTokenJSON(expiresIn int) string {
14651504
return fmt.Sprintf(`{
14661505
"access_token" : "accessToken",

0 commit comments

Comments
 (0)