github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/azure/internal/ad/client.go (about) 1 // This file is based on code from Azure/azure-sdk-for-go 2 // and Azure/go-autorest, which are both 3 // Copyright Microsoft Corporation. See the LICENSE 4 // file in this directory for details. 5 // 6 // NOTE(axw) this file contains a client for a subset of the 7 // Microsoft Graph API, which is not currently supported by 8 // the Azure SDK. When it is, this will be deleted. 9 10 package ad 11 12 import ( 13 "bytes" 14 "encoding/json" 15 "fmt" 16 "io/ioutil" 17 "net/http" 18 19 "github.com/Azure/go-autorest/autorest" 20 "github.com/Azure/go-autorest/autorest/azure" 21 "github.com/juju/juju/provider/azure/internal/useragent" 22 ) 23 24 const ( 25 // APIVersion is the version of the Active Directory API 26 APIVersion = "1.6" 27 ) 28 29 var ( 30 // utf8BOM is the UTF-8 BOM, which may come at the beginning 31 // of a UTF-8 stream. Since it has no effect on the stream, 32 // it can be stripped off and ignored. 33 utf8BOM = []byte("\ufeff") 34 ) 35 36 type ManagementClient struct { 37 autorest.Client 38 BaseURI string 39 APIVersion string 40 } 41 42 func NewManagementClient(baseURI string) ManagementClient { 43 client := autorest.NewClientWithUserAgent("arm-graphrbac/" + APIVersion) 44 useragent.UpdateClient(&client) 45 return ManagementClient{ 46 Client: client, 47 BaseURI: baseURI, 48 APIVersion: APIVersion, 49 } 50 } 51 52 // WithOdataErrorUnlessStatusCode returns a RespondDecorator that emits an 53 // azure.RequestError by reading the response body unless the response HTTP status code 54 // is among the set passed. 55 // 56 // If there is a chance service may return responses other than the Azure error 57 // format and the response cannot be parsed into an error, a decoding error will 58 // be returned containing the response body. In any case, the Responder will 59 // return an error if the status code is not satisfied. 60 // 61 // If this Responder returns an error, the response body will be replaced with 62 // an in-memory reader, which needs no further closing. 63 // 64 // NOTE(axw) this function is based on go-autorest/autorest/azure.WithErrorUnlessStatusCode. 65 // The only difference is that we extract "odata.error", instead of "error", 66 // from the response body; and we do not extract the message, which is in a 67 // different format for odata.error, and irrelevant to us. 68 func WithOdataErrorUnlessStatusCode(codes ...int) autorest.RespondDecorator { 69 return func(r autorest.Responder) autorest.Responder { 70 return autorest.ResponderFunc(func(resp *http.Response) error { 71 err := r.Respond(resp) 72 if err == nil && !autorest.ResponseHasStatusCode(resp, codes...) { 73 var oe odataRequestError 74 defer resp.Body.Close() 75 76 // Copy and replace the Body in case it does not contain an error object. 77 // This will leave the Body available to the caller. 78 data, readErr := ioutil.ReadAll(resp.Body) 79 if readErr != nil { 80 return readErr 81 } 82 resp.Body = ioutil.NopCloser(bytes.NewReader(data)) 83 decodeErr := json.Unmarshal(bytes.TrimPrefix(data, utf8BOM), &oe) 84 85 if decodeErr != nil { 86 return fmt.Errorf("ad: error response cannot be parsed: %q error: %v", data, decodeErr) 87 } else if oe.ServiceError == nil { 88 oe.ServiceError = &odataServiceError{Code: "Unknown"} 89 } 90 91 e := azure.RequestError{ 92 ServiceError: &azure.ServiceError{ 93 Code: oe.ServiceError.Code, 94 Message: oe.ServiceError.Message.Value, 95 }, 96 RequestID: azure.ExtractRequestID(resp), 97 } 98 if e.StatusCode == nil { 99 e.StatusCode = resp.StatusCode 100 } 101 err = &e 102 } 103 return err 104 }) 105 } 106 } 107 108 // See https://msdn.microsoft.com/en-us/library/azure/ad/graph/howto/azure-ad-graph-api-error-codes-and-error-handling 109 type odataRequestError struct { 110 ServiceError *odataServiceError `json:"odata.error"` 111 } 112 113 type odataServiceError struct { 114 Code string `json:"code"` 115 Message odataServiceErrorMessage `json:"message"` 116 } 117 118 type odataServiceErrorMessage struct { 119 Lang string `json:"lang"` 120 Value string `json:"value"` 121 }