Skip to content

Commit e00b3c0

Browse files
chlowelljhendrixMSFT
authored andcommitted
Restore ARM CAE support for azcore beta (Azure#20657)
This reverts commit 9020972.
1 parent 204a3c4 commit e00b3c0

File tree

3 files changed

+54
-6
lines changed

3 files changed

+54
-6
lines changed

sdk/azcore/arm/runtime/policy_bearer_token.go

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package runtime
55

66
import (
77
"context"
8+
"encoding/base64"
89
"fmt"
910
"net/http"
1011
"strings"
@@ -63,11 +64,28 @@ func NewBearerTokenPolicy(cred azcore.TokenCredential, opts *armpolicy.BearerTok
6364
p.scopes = make([]string, len(opts.Scopes))
6465
copy(p.scopes, opts.Scopes)
6566
p.btp = azruntime.NewBearerTokenPolicy(cred, opts.Scopes, &azpolicy.BearerTokenOptions{
66-
AuthorizationHandler: azpolicy.AuthorizationHandler{OnRequest: p.onRequest},
67+
AuthorizationHandler: azpolicy.AuthorizationHandler{
68+
OnChallenge: p.onChallenge,
69+
OnRequest: p.onRequest,
70+
},
6771
})
6872
return p
6973
}
7074

75+
func (b *BearerTokenPolicy) onChallenge(req *azpolicy.Request, res *http.Response, authNZ func(azpolicy.TokenRequestOptions) error) error {
76+
challenge := res.Header.Get(shared.HeaderWWWAuthenticate)
77+
claims, err := parseChallenge(challenge)
78+
if err != nil {
79+
// the challenge contains claims we can't parse
80+
return err
81+
} else if claims != "" {
82+
// request a new token having the specified claims, send the request again
83+
return authNZ(azpolicy.TokenRequestOptions{Claims: claims, Scopes: b.scopes})
84+
}
85+
// auth challenge didn't include claims, so this is a simple authorization failure
86+
return azruntime.NewResponseError(res)
87+
}
88+
7189
// onRequest authorizes requests with one or more bearer tokens
7290
func (b *BearerTokenPolicy) onRequest(req *azpolicy.Request, authNZ func(azpolicy.TokenRequestOptions) error) error {
7391
// authorize the request with a token for the primary tenant
@@ -97,3 +115,31 @@ func (b *BearerTokenPolicy) onRequest(req *azpolicy.Request, authNZ func(azpolic
97115
func (b *BearerTokenPolicy) Do(req *azpolicy.Request) (*http.Response, error) {
98116
return b.btp.Do(req)
99117
}
118+
119+
// parseChallenge parses claims from an authentication challenge issued by ARM so a client can request a token
120+
// that will satisfy conditional access policies. It returns a non-nil error when the given value contains
121+
// claims it can't parse. If the value contains no claims, it returns an empty string and a nil error.
122+
func parseChallenge(wwwAuthenticate string) (string, error) {
123+
claims := ""
124+
var err error
125+
for _, param := range strings.Split(wwwAuthenticate, ",") {
126+
if _, after, found := strings.Cut(param, "claims="); found {
127+
if claims != "" {
128+
// The header contains multiple challenges, at least two of which specify claims. The specs allow this
129+
// but it's unclear what a client should do in this case and there's as yet no concrete example of it.
130+
err = fmt.Errorf("found multiple claims challenges in %q", wwwAuthenticate)
131+
break
132+
}
133+
// trim stuff that would get an error from RawURLEncoding; claims may or may not be padded
134+
claims = strings.Trim(after, `\"=`)
135+
// we don't return this error because it's something unhelpful like "illegal base64 data at input byte 42"
136+
if b, decErr := base64.RawURLEncoding.DecodeString(claims); decErr == nil {
137+
claims = string(b)
138+
} else {
139+
err = fmt.Errorf("failed to parse claims from %q", wwwAuthenticate)
140+
break
141+
}
142+
}
143+
}
144+
return claims, err
145+
}

sdk/azcore/arm/runtime/policy_bearer_token_test.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,6 @@ func TestAuxiliaryTenants(t *testing.T) {
203203
}
204204

205205
func TestBearerTokenPolicyChallengeParsing(t *testing.T) {
206-
t.Skip("unskip this test after adding back CAE support")
207206
for _, test := range []struct {
208207
challenge, desc, expectedClaims string
209208
err error
@@ -262,10 +261,9 @@ func TestBearerTokenPolicyChallengeParsing(t *testing.T) {
262261
cred := mockCredential{
263262
getTokenImpl: func(ctx context.Context, actual azpolicy.TokenRequestOptions) (azcore.AccessToken, error) {
264263
calls += 1
265-
// TODO: uncomment after restoring TokenRequestOptions.Claims
266-
// if calls == 2 && test.expectedClaims != "" {
267-
// require.Equal(t, test.expectedClaims, actual.Claims)
268-
// }
264+
if calls == 2 && test.expectedClaims != "" {
265+
require.Equal(t, test.expectedClaims, actual.Claims)
266+
}
269267
return azcore.AccessToken{Token: "...", ExpiresOn: time.Now().Add(time.Hour).UTC()}, nil
270268
},
271269
}

sdk/azcore/internal/exported/exported.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ type AccessToken struct {
5151
// TokenRequestOptions contain specific parameter that may be used by credentials types when attempting to get a token.
5252
// Exported as policy.TokenRequestOptions.
5353
type TokenRequestOptions struct {
54+
// Claims are any additional claims required for the token to satisfy a conditional access policy, such as a
55+
// service may return in a claims challenge following an authorization failure. If a service returned the
56+
// claims value base64 encoded, it must be decoded before setting this field.
57+
Claims string
5458
// Scopes contains the list of permission scopes required for the token.
5559
Scopes []string
5660

0 commit comments

Comments
 (0)