From adf770d5123db301bc2a27280a11fe7b520468bc Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 18 May 2022 10:43:56 -0400 Subject: [PATCH 1/2] - adds support for CAE --- CHANGELOG.md | 6 ++++ nethttp_request_adapter.go | 53 ++++++++++++++++++++++++++++----- nethttp_request_adapter_test.go | 44 +++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 nethttp_request_adapter_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index d8afe61..f2a9a39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +## [0.4.0] - 2022-05-18 + +### Added + +- Adds support for continuous access evaluation. + ## [0.3.0] - 2022-04-19 ### Changed diff --git a/nethttp_request_adapter.go b/nethttp_request_adapter.go index 228ab30..4e0c141 100644 --- a/nethttp_request_adapter.go +++ b/nethttp_request_adapter.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "io/ioutil" + "regexp" "strconv" "strings" @@ -88,9 +89,13 @@ func (a *NetHttpRequestAdapter) SetBaseUrl(baseUrl string) { func (a *NetHttpRequestAdapter) GetBaseUrl() string { return a.baseUrl } -func (a *NetHttpRequestAdapter) getHttpResponseMessage(requestInfo *abs.RequestInformation) (*nethttp.Response, error) { +func (a *NetHttpRequestAdapter) getHttpResponseMessage(requestInfo *abs.RequestInformation, claims string) (*nethttp.Response, error) { a.setBaseUrlForRequestInformation(requestInfo) - err := a.authenticationProvider.AuthenticateRequest(requestInfo) + additionalContext := make(map[string]interface{}) + if claims != "" { + additionalContext[claimsKey] = claims + } + err := a.authenticationProvider.AuthenticateRequest(requestInfo, additionalContext) if err != nil { return nil, err } @@ -98,8 +103,40 @@ func (a *NetHttpRequestAdapter) getHttpResponseMessage(requestInfo *abs.RequestI if err != nil { return nil, err } - return (*a.httpClient).Do(request) + response, err := (*a.httpClient).Do(request) + if err != nil { + return nil, err + } + return a.retryCAEResponseIfRequired(response, requestInfo, claims) } + +const claimsKey = "claims" + +var reBearer = regexp.MustCompile(`(?i)^Bearer\s`) +var reClaims = regexp.MustCompile(`\"([^\"]*)\"`) + +func (a *NetHttpRequestAdapter) retryCAEResponseIfRequired(response *nethttp.Response, requestInfo *abs.RequestInformation, claims string) (*nethttp.Response, error) { + if response.StatusCode == 401 && + claims == "" { //avoid infinite loop, we only retry once + authenticateHeaderVal := response.Header.Get("WWW-Authenticate") + if authenticateHeaderVal != "" && reBearer.Match([]byte(authenticateHeaderVal)) { + responseClaims := "" + parametersRaw := string(reBearer.ReplaceAll([]byte(authenticateHeaderVal), []byte(""))) + parameters := strings.Split(parametersRaw, ",") + for _, parameter := range parameters { + if strings.HasPrefix(strings.Trim(parameter, " "), claimsKey) { + responseClaims = reClaims.FindStringSubmatch(parameter)[1] + break + } + } + if responseClaims != "" { + return a.getHttpResponseMessage(requestInfo, responseClaims) + } + } + } + return response, nil +} + func (a *NetHttpRequestAdapter) getResponsePrimaryContentType(response *nethttp.Response) string { if response.Header == nil { return "" @@ -143,7 +180,7 @@ func (a *NetHttpRequestAdapter) SendAsync(requestInfo *abs.RequestInformation, c if requestInfo == nil { return nil, errors.New("requestInfo cannot be nil") } - response, err := a.getHttpResponseMessage(requestInfo) + response, err := a.getHttpResponseMessage(requestInfo, "") if err != nil { return nil, err } @@ -178,7 +215,7 @@ func (a *NetHttpRequestAdapter) SendCollectionAsync(requestInfo *abs.RequestInfo if requestInfo == nil { return nil, errors.New("requestInfo cannot be nil") } - response, err := a.getHttpResponseMessage(requestInfo) + response, err := a.getHttpResponseMessage(requestInfo, "") if err != nil { return nil, err } @@ -213,7 +250,7 @@ func (a *NetHttpRequestAdapter) SendPrimitiveAsync(requestInfo *abs.RequestInfor if requestInfo == nil { return nil, errors.New("requestInfo cannot be nil") } - response, err := a.getHttpResponseMessage(requestInfo) + response, err := a.getHttpResponseMessage(requestInfo, "") if err != nil { return nil, err } @@ -269,7 +306,7 @@ func (a *NetHttpRequestAdapter) SendPrimitiveCollectionAsync(requestInfo *abs.Re if requestInfo == nil { return nil, errors.New("requestInfo cannot be nil") } - response, err := a.getHttpResponseMessage(requestInfo) + response, err := a.getHttpResponseMessage(requestInfo, "") if err != nil { return nil, err } @@ -303,7 +340,7 @@ func (a *NetHttpRequestAdapter) SendNoContentAsync(requestInfo *abs.RequestInfor if requestInfo == nil { return errors.New("requestInfo cannot be nil") } - response, err := a.getHttpResponseMessage(requestInfo) + response, err := a.getHttpResponseMessage(requestInfo, "") if err != nil { return err } diff --git a/nethttp_request_adapter_test.go b/nethttp_request_adapter_test.go new file mode 100644 index 0000000..b5a944e --- /dev/null +++ b/nethttp_request_adapter_test.go @@ -0,0 +1,44 @@ +package nethttplibrary + +import ( + nethttp "net/http" + httptest "net/http/httptest" + "net/url" + "testing" + + abs "github.com/microsoft/kiota-abstractions-go" + absauth "github.com/microsoft/kiota-abstractions-go/authentication" + + "github.com/stretchr/testify/assert" +) + +func TestItRetriesOnCAEResponse(t *testing.T) { + methodCallCount := 0 + + testServer := httptest.NewServer(nethttp.HandlerFunc(func(res nethttp.ResponseWriter, req *nethttp.Request) { + if methodCallCount > 0 { + res.WriteHeader(200) + } else { + res.Header().Set("WWW-Authenticate", "Bearer realm=\"\", authorization_uri=\"https://login.microsoftonline.com/common/oauth2/authorize\", client_id=\"00000003-0000-0000-c000-000000000000\", error=\"insufficient_claims\", claims=\"eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwgInZhbHVlIjoiMTY1MjgxMzUwOCJ9fX0=\"") + res.WriteHeader(401) + } + methodCallCount++ + res.Write([]byte("body")) + })) + defer func() { testServer.Close() }() + authProvider := &absauth.AnonymousAuthenticationProvider{} + adapter, err := NewNetHttpRequestAdapter(authProvider) + assert.Nil(t, err) + assert.NotNil(t, adapter) + + uri, err := url.Parse(testServer.URL) + assert.Nil(t, err) + assert.NotNil(t, uri) + request := abs.NewRequestInformation() + request.SetUri(*uri) + request.Method = abs.GET + + err2 := adapter.SendNoContentAsync(request, nil, nil) + assert.Nil(t, err2) + assert.Equal(t, 2, methodCallCount) +} From d4f06e697c9c4097ac90da5e7772d9602c3a34e5 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 19 May 2022 07:26:52 -0400 Subject: [PATCH 2/2] - upgrades abstractions version --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 1c6f2df..d829662 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/microsoft/kiota-http-go go 1.18 require ( - github.com/microsoft/kiota-abstractions-go v0.6.0 + github.com/microsoft/kiota-abstractions-go v0.7.0 github.com/stretchr/testify v1.7.1 ) diff --git a/go.sum b/go.sum index 98ddb01..36142bc 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/microsoft/kiota-abstractions-go v0.6.0 h1:e/mJM8+xidaTxOvuVLdY0uB/3GFHxyZB43vhjmxAuME= github.com/microsoft/kiota-abstractions-go v0.6.0/go.mod h1:fL1Ni2uXdlRPGicO4Ut0aOLmkpunYuAAJwRBLgDhzw4= +github.com/microsoft/kiota-abstractions-go v0.7.0 h1:/yPcUaiTOUvq4stz8qrPi9UWljnCGBcksRYS2BZpUBk= +github.com/microsoft/kiota-abstractions-go v0.7.0/go.mod h1:fL1Ni2uXdlRPGicO4Ut0aOLmkpunYuAAJwRBLgDhzw4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=