@@ -5,6 +5,7 @@ package runtime
5
5
6
6
import (
7
7
"context"
8
+ "encoding/base64"
8
9
"fmt"
9
10
"net/http"
10
11
"strings"
@@ -63,11 +64,28 @@ func NewBearerTokenPolicy(cred azcore.TokenCredential, opts *armpolicy.BearerTok
63
64
p .scopes = make ([]string , len (opts .Scopes ))
64
65
copy (p .scopes , opts .Scopes )
65
66
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
+ },
67
71
})
68
72
return p
69
73
}
70
74
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
+
71
89
// onRequest authorizes requests with one or more bearer tokens
72
90
func (b * BearerTokenPolicy ) onRequest (req * azpolicy.Request , authNZ func (azpolicy.TokenRequestOptions ) error ) error {
73
91
// authorize the request with a token for the primary tenant
@@ -97,3 +115,31 @@ func (b *BearerTokenPolicy) onRequest(req *azpolicy.Request, authNZ func(azpolic
97
115
func (b * BearerTokenPolicy ) Do (req * azpolicy.Request ) (* http.Response , error ) {
98
116
return b .btp .Do (req )
99
117
}
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
+ }
0 commit comments