pax_global_header00006660000000000000000000000064136737235240014526gustar00rootroot0000000000000052 comment=bb41c77fd235e54e915d318fa41833cb1fce7bf6 golang-github-azure-go-autorest-14.1.1/000077500000000000000000000000001367372352400177345ustar00rootroot00000000000000golang-github-azure-go-autorest-14.1.1/.github/000077500000000000000000000000001367372352400212745ustar00rootroot00000000000000golang-github-azure-go-autorest-14.1.1/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000011201367372352400250670ustar00rootroot00000000000000Thank you for your contribution to Go-AutoRest! We will triage and review it as soon as we can. As part of submitting, please make sure you can make the following assertions: - [ ] I've tested my changes, adding unit tests if applicable. - [ ] I've added Apache 2.0 Headers to the top of any new source files. - [ ] I'm submitting this PR to the `dev` branch, except in the case of urgent bug fixes warranting their own release. - [ ] If I'm targeting `master`, I've updated [CHANGELOG.md](https://github.com/Azure/go-autorest/blob/master/CHANGELOG.md) to address the changes I'm making.golang-github-azure-go-autorest-14.1.1/.gitignore000066400000000000000000000007161367372352400217300ustar00rootroot00000000000000# The standard Go .gitignore file follows. (Sourced from: github.com/github/gitignore/master/Go.gitignore) # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test .DS_Store .idea/ .vscode/ # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof # go-autorest specific vendor/ autorest/azure/example/example golang-github-azure-go-autorest-14.1.1/CHANGELOG.md000066400000000000000000000722221367372352400215520ustar00rootroot00000000000000# CHANGELOG ## v14.1.1 ### Bug Fixes - Change `x-ms-authorization-auxiliary` header value separator to comma. ## v14.1.0 ### New Features - Added `azure.SetEnvironment()` that will update the global environments map with the specified values. ## v14.0.1 ### Bug Fixes - Fix race condition when refreshing token. - Fixed some tests to work with Go 1.14. ## v14.0.0 ## Breaking Changes - By default, the `DoRetryForStatusCodes` functions will no longer infinitely retry a request when the response returns an HTTP status code of 429 (StatusTooManyRequests). To opt in to the old behavior set `autorest.Count429AsRetry` to `false`. ## New Features - Variable `autorest.Max429Delay` can be used to control the maximum delay between retries when a 429 is received with no `Retry-After` header. The default is zero which means there is no cap. ## v13.4.0 ## New Features - Added field `SendDecorators` to the `Client` type. This can be used to specify a custom chain of SendDecorators per client. - Added method `Client.Send()` which includes logic for selecting the preferred chain of SendDecorators. ## v13.3.3 ### Bug Fixes - Fixed connection leak when retrying requests. - Enabled exponential back-off with a 2-minute cap when retrying on 429. - Fixed some cases where errors were inadvertently dropped. ## v13.3.2 ### Bug Fixes - Updated `autorest.AsStringSlice()` to convert slice elements to their string representation. ## v13.3.1 - Updated external dependencies. ### Bug Fixes ## v13.3.0 ### New Features - Added support for shared key and shared access signature token authorization. - `autorest.NewSharedKeyAuthorizer()` and dependent types. - `autorest.NewSASTokenAuthorizer()` and dependent types. - Added `ServicePrincipalToken.SetCustomRefresh()` so a custom refresh function can be invoked when a token has expired. ### Bug Fixes - Fixed `cli.AccessTokensPath()` to respect `AZURE_CONFIG_DIR` when set. - Support parsing error messages in XML responses. ## v13.2.0 ### New Features - Added the following functions to replace their versions that don't take a context. - `adal.InitiateDeviceAuthWithContext()` - `adal.CheckForUserCompletionWithContext()` - `adal.WaitForUserCompletionWithContext()` ## v13.1.0 ### New Features - Added support for MSI authentication on Azure App Service and Azure Functions. ## v13.0.2 ### Bug Fixes - Always retry a request even if the sender returns a non-nil error. ## v13.0.1 ## Bug Fixes - Fixed `autorest.WithQueryParameters()` so that it properly encodes multi-value query parameters. ## v13.0.0 ## Breaking Changes The `tracing` package has been rewritten to provide a common interface for consumers to wire in the tracing package of their choice. What this means is that by default no tracing provider will be compiled into your program and setting the `AZURE_SDK_TRACING_ENABLED` environment variable will have no effect. To enable this previous behavior you must now add the following import to your source file. ```go import _ "github.com/Azure/go-autorest/tracing/opencensus" ``` The APIs required by autorest-generated code have remained but some APIs have been removed and new ones added. The following APIs and variables have been removed (the majority of them were moved to the `opencensus` package). - tracing.Transport - tracing.Enable() - tracing.EnableWithAIForwarding() - tracing.Disable() The following APIs and types have been added - tracing.Tracer - tracing.Register() To hook up a tracer simply call `tracing.Register()` passing in a type that satisfies the `tracing.Tracer` interface. ## v12.4.3 ### Bug Fixes - `autorest.MultiTenantServicePrincipalTokenAuthorizer` will now properly add its auxiliary bearer tokens. ## v12.4.2 ### Bug Fixes - Improvements to the fixes made in v12.4.1. - Remove `override` stanza from Gopkg.toml and `replace` directive from go.mod as they don't apply when being consumed as a dependency. - Switched to latest version of `ocagent` that still depends on protobuf v1.2. - Add indirect dependencies to the `required` clause with matching `constraint` stanzas so that `dep` dependencies match go.sum. ## v12.4.1 ### Bug Fixes - Updated OpenCensus and OCAgent versions to versions that don't depend on v1.3+ of protobuf as it was breaking kubernetes. - Pinned opencensus-proto to a version that's compatible with our versions of OpenCensus and OCAgent. ## v12.4.0 ### New Features - Added `autorest.WithPrepareDecorators` and `autorest.GetPrepareDecorators` for adding and retrieving a custom chain of PrepareDecorators to the provided context. ## v12.3.0 ### New Features - Support for multi-tenant via x-ms-authorization-auxiliary header has been added for client credentials with secret scenario; this basically bundles multiple OAuthConfig and ServicePrincipalToken types into corresponding MultiTenant* types along with a new authorizer that adds the primary and auxiliary token headers to the reqest. The authenticaion helpers have been updated to support this scenario; if environment var AZURE_AUXILIARY_TENANT_IDS is set with a semicolon delimited list of tenants the multi-tenant codepath will kick in to create the appropriate authorizer. See `adal.NewMultiTenantOAuthConfig`, `adal.NewMultiTenantServicePrincipalToken` and `autorest.NewMultiTenantServicePrincipalTokenAuthorizer` along with their supporting types and methods. - Added `autorest.WithSendDecorators` and `autorest.GetSendDecorators` for adding and retrieving a custom chain of SendDecorators to the provided context. - Added `autorest.DoRetryForStatusCodesWithCap` and `autorest.DelayForBackoffWithCap` to enforce an upper bound on the duration between retries. ## v12.2.0 ### New Features - Added `autorest.WithXML`, `autorest.AsMerge`, `autorest.WithBytes` preparer decorators. - Added `autorest.ByUnmarshallingBytes` response decorator. - Added `Response.IsHTTPStatus` and `Response.HasHTTPStatus` helper methods for inspecting HTTP status code in `autorest.Response` types. ### Bug Fixes - `autorest.DelayWithRetryAfter` now supports HTTP-Dates in the `Retry-After` header and is not limited to just 429 status codes. ## v12.1.0 ### New Features - Added `to.ByteSlicePtr()`. - Added blob/queue storage resource ID to `azure.ResourceIdentifier`. ## v12.0.0 ### Breaking Changes In preparation for modules the following deprecated content has been removed. - async.NewFuture() - async.Future.Done() - async.Future.WaitForCompletion() - async.DoPollForAsynchronous() - The `utils` package - validation.NewErrorWithValidationError() - The `version` package ## v11.9.0 ### New Features - Add `ResourceIdentifiers` field to `azure.Environment` containing resource IDs for public and sovereign clouds. ## v11.8.0 ### New Features - Added `autorest.NewClientWithOptions()` to support endpoints that require free renegotiation. ## v11.7.1 ### Bug Fixes - Fix missing support for http(s) proxy when using the default sender. ## v11.7.0 ### New Features - Added methods to obtain a ServicePrincipalToken on the various credential configuration types in the `auth` package. ## v11.6.1 ### Bug Fixes - Fix ACR DNS endpoint for government clouds. - Add Cosmos DB DNS endpoints. - Update dependencies to resolve build breaks in OpenCensus. ## v11.6.0 ### New Features - Added type `autorest.BasicAuthorizer` to support Basic authentication. ## v11.5.2 ### Bug Fixes - Fixed `GetTokenFromCLI` did not work with zsh. ## v11.5.1 ### Bug Fixes - In `Client.sender()` set the minimum TLS version on HTTP clients to 1.2. ## v11.5.0 ### New Features - The `auth` package has been refactored so that the environment and file settings are now available. - The methods used in `auth.NewAuthorizerFromEnvironment()` are now exported so that custom authorization chains can be created. - Added support for certificate authorization for file-based config. ## v11.4.0 ### New Features - Added `adal.AddToUserAgent()` so callers can append custom data to the user-agent header used for ADAL requests. - Exported `adal.UserAgent()` for parity with `autorest.Client`. ## v11.3.2 ### Bug Fixes - In `Future.WaitForCompletionRef()` if the provided context has a deadline don't add the default deadline. ## v11.3.1 ### Bug Fixes - For an LRO PUT operation the final GET URL was incorrectly set to the Location polling header in some cases. ## v11.3.0 ### New Features - Added method `ServicePrincipalToken()` to `DeviceFlowConfig` type. ## v11.2.8 ### Bug Fixes - Deprecate content in the `version` package. The functionality has been superseded by content in the `autorest` package. ## v11.2.7 ### Bug Fixes - Fix environment variable name for enabling tracing from `AZURE_SDK_TRACING_ENABELD` to `AZURE_SDK_TRACING_ENABLED`. Note that for backward compatibility reasons, both will work until the next major version release of the package. ## v11.2.6 ### Bug Fixes - If zero bytes are read from a polling response body don't attempt to unmarshal them. ## v11.2.5 ### Bug Fixes - Removed race condition in `autorest.DoRetryForStatusCodes`. ## v11.2.4 ### Bug Fixes - Function `cli.ProfilePath` now respects environment `AZURE_CONFIG_DIR` if available. ## v11.2.1 NOTE: Versions of Go prior to 1.10 have been removed from CI as they no longer work with golint. ### Bug Fixes - Method `MSIConfig.Authorizer` now supports user-assigned identities. - The adal package now reports its own user-agent string. ## v11.2.0 ### New Features - Added `tracing` package that enables instrumentation of HTTP and API calls. Setting the env variable `AZURE_SDK_TRACING_ENABLED` or calling `tracing.Enable` will start instrumenting the code for metrics and traces. Additionally, setting the env variable `OCAGENT_TRACE_EXPORTER_ENDPOINT` or calling `tracing.EnableWithAIForwarding` will start the instrumentation and connect to an App Insights Local Forwarder that is needs to be running. Note that if the AI Local Forwarder is not running tracking will still be enabled. By default, instrumentation is disabled. Once enabled, instrumentation can also be programatically disabled by calling `Disable`. - Added `DoneWithContext` call for checking LRO status. `Done` has been deprecated. ### Bug Fixes - Don't use the initial request's context for LRO polling. - Don't override the `refreshLock` and the `http.Client` when unmarshalling `ServicePrincipalToken` if it is already set. ## v11.1.1 ### Bug Fixes - When creating a future always include the polling tracker even if there's a failure; this allows the underlying response to be obtained by the caller. ## v11.1.0 ### New Features - Added `auth.NewAuthorizerFromCLI` to create an authorizer configured from the Azure 2.0 CLI. - Added `adal.NewOAuthConfigWithAPIVersion` to create an OAuthConfig with the specified API version. ## v11.0.1 ### New Features - Added `x5c` header to client assertion for certificate Issuer+Subject Name authentication. ## v11.0.0 ### Breaking Changes - To handle differences between ADFS and AAD the following fields have had their types changed from `string` to `json.Number` - ExpiresIn - ExpiresOn - NotBefore ### New Features - Added `auth.NewAuthorizerFromFileWithResource` to create an authorizer from the config file with the specified resource. - Setting a client's `PollingDuration` to zero will use the provided context to control a LRO's polling duration. ## v10.15.5 ### Bug Fixes - In `DoRetryForStatusCodes`, if a request's context is cancelled return the last response. ## v10.15.4 ### Bug Fixes - If a polling operation returns a failure status code return the associated error. ## v10.15.3 ### Bug Fixes - Initialize the polling URL and method for an LRO tracker on each iteration, favoring the Azure-AsyncOperation header. ## v10.15.2 ### Bug Fixes - Use fmt.Fprint when printing request/response so that any escape sequences aren't treated as format specifiers. ## v10.15.1 ### Bug Fixes - If an LRO API returns a `Failed` provisioning state in the initial response return an error at that point so the caller doesn't have to poll. - For failed LROs without an OData v4 error include the response body in the error's `AdditionalInfo` field to aid in diagnosing the failure. ## v10.15.0 ### New Features - Add initial support for request/response logging via setting environment variables. Setting `AZURE_GO_SDK_LOG_LEVEL` to `LogInfo` will log request/response without their bodies. To include the bodies set the log level to `LogDebug`. By default the logger writes to strerr, however it can also write to stdout or a file if specified in `AZURE_GO_SDK_LOG_FILE`. Note that if the specified file already exists it will be truncated. IMPORTANT: by default the logger will redact the Authorization and Ocp-Apim-Subscription-Key headers. Any other secrets will _not_ be redacted. ## v10.14.0 ### New Features - Added package version that contains version constants and user-agent data. ### Bug Fixes - Add the user-agent to token requests. ## v10.13.0 - Added support for additionalInfo in ServiceError type. ## v10.12.0 ### New Features - Added field ServicePrincipalToken.MaxMSIRefreshAttempts to configure the maximun number of attempts to refresh an MSI token. ## v10.11.4 ### Bug Fixes - If an LRO returns http.StatusOK on the initial response with no async headers return the response body from Future.GetResult(). - If there is no "final GET URL" return an error from Future.GetResult(). ## v10.11.3 ### Bug Fixes - In IMDS retry logic, if we don't receive a response don't retry. - Renamed the retry function so it's clear it's meant for IMDS only. - For error response bodies that aren't OData-v4 compliant stick the raw JSON in the ServiceError.Details field so the information isn't lost. - Also add the raw HTTP response to the DetailedResponse. - Removed superfluous wrapping of response error in azure.DoRetryWithRegistration(). ## v10.11.2 ### Bug Fixes - Validation for integers handles int and int64 types. ## v10.11.1 ### Bug Fixes - Adding User information to authorization config as parsed from CLI cache. ## v10.11.0 ### New Features - Added NewServicePrincipalTokenFromManualTokenSecret for creating a new SPT using a manual token and secret - Added method ServicePrincipalToken.MarshalTokenJSON() to marshall the inner Token ## v10.10.0 ### New Features - Most ServicePrincipalTokens can now be marshalled/unmarshall to/from JSON (ServicePrincipalCertificateSecret and ServicePrincipalMSISecret are not supported). - Added method ServicePrincipalToken.SetRefreshCallbacks(). ## v10.9.2 ### Bug Fixes - Refreshing a refresh token obtained from a web app authorization code now works. ## v10.9.1 ### Bug Fixes - The retry logic for MSI token requests now uses exponential backoff per the guidelines. - IsTemporaryNetworkError() will return true for errors that don't implement the net.Error interface. ## v10.9.0 ### Deprecated Methods | Old Method | New Method | | -------------------------: | :---------------------------: | | azure.NewFuture() | azure.NewFutureFromResponse() | | Future.WaitForCompletion() | Future.WaitForCompletionRef() | ### New Features - Added azure.NewFutureFromResponse() for creating a Future from the initial response from an async operation. - Added Future.GetResult() for making the final GET call to retrieve the result from an async operation. ### Bug Fixes - Some futures failed to return their results, this should now be fixed. ## v10.8.2 ### Bug Fixes - Add nil-gaurd to token retry logic. ## v10.8.1 ### Bug Fixes - Return a TokenRefreshError if the sender fails on the initial request. - Don't retry on non-temporary network errors. ## v10.8.0 - Added NewAuthorizerFromEnvironmentWithResource() helper function. ## v10.7.0 ### New Features - Added \*WithContext() methods to ADAL token refresh operations. ## v10.6.2 - Fixed a bug on device authentication. ## v10.6.1 - Added retries to MSI token get request. ## v10.6.0 - Changed MSI token implementation. Now, the token endpoint is the IMDS endpoint. ## v10.5.1 ### Bug Fixes - `DeviceFlowConfig.Authorizer()` now prints the device code message when running `go test`. `-v` flag is required. ## v10.5.0 ### New Features - Added NewPollingRequestWithContext() for use with polling asynchronous operations. ### Bug Fixes - Make retry logic use the request's context instead of the deprecated Cancel object. ## v10.4.0 ### New Features - Added helper for parsing Azure Resource ID's. - Added deprecation message to utils.GetEnvVarOrExit() ## v10.3.0 ### New Features - Added EnvironmentFromURL method to load an Environment from a given URL. This function is particularly useful in the private and hybrid Cloud model, where one may define their own endpoints - Added TokenAudience endpoint to Environment structure. This is useful in private and hybrid cloud models where TokenAudience endpoint can be different from ResourceManagerEndpoint ## v10.2.0 ### New Features - Added endpoints for batch management. ## v10.1.3 ### Bug Fixes - In Client.Do() invoke WithInspection() last so that it will inspect WithAuthorization(). - Fixed authorization methods to invoke p.Prepare() first, aligning them with the other preparers. ## v10.1.2 - Corrected comment for auth.NewAuthorizerFromFile() function. ## v10.1.1 - Updated version number to match current release. ## v10.1.0 ### New Features - Expose the polling URL for futures. ### Bug Fixes - Add validation.NewErrorWithValidationError back to prevent breaking changes (it is deprecated). ## v10.0.0 ### New Features - Added target and innererror fields to ServiceError to comply with OData v4 spec. - The Done() method on futures will now return a ServiceError object when available (it used to return a partial value of such errors). - Added helper methods for obtaining authorizers. - Expose the polling URL for futures. ### Bug Fixes - Switched from glide to dep for dependency management. - Fixed unmarshaling of ServiceError for JSON bodies that don't conform to the OData spec. - Fixed a race condition in token refresh. ### Breaking Changes - The ServiceError.Details field type has been changed to match the OData v4 spec. - Go v1.7 has been dropped from CI. - API parameter validation failures will now return a unique error type validation.Error. - The adal.Token type has been decomposed from adal.ServicePrincipalToken (this was necessary in order to fix the token refresh race). ## v9.10.0 - Fix the Service Bus suffix in Azure public env - Add Service Bus Endpoint (AAD ResourceURI) for use in [Azure Service Bus RBAC Preview](https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-role-based-access-control) ## v9.9.0 ### New Features - Added EventGridKeyAuthorizer for key authorization with event grid topics. ### Bug Fixes - Fixed race condition when auto-refreshing service principal tokens. ## v9.8.1 ### Bug Fixes - Added http.StatusNoContent (204) to the list of expected status codes for long-running operations. - Updated runtime version info so it's current. ## v9.8.0 ### New Features - Added type azure.AsyncOpIncompleteError to be returned from a future's Result() method when the operation has not completed. ## v9.7.1 ### Bug Fixes - Use correct AAD and Graph endpoints for US Gov environment. ## v9.7.0 ### New Features - Added support for application/octet-stream MIME types. ## v9.6.1 ### Bug Fixes - Ensure Authorization header is added to request when polling for registration status. ## v9.6.0 ### New Features - Added support for acquiring tokens via MSI with a user assigned identity. ## v9.5.3 ### Bug Fixes - Don't remove encoding of existing URL Query parameters when calling autorest.WithQueryParameters. - Set correct Content Type when using autorest.WithFormData. ## v9.5.2 ### Bug Fixes - Check for nil \*http.Response before dereferencing it. ## v9.5.1 ### Bug Fixes - Don't count http.StatusTooManyRequests (429) against the retry cap. - Use retry logic when SkipResourceProviderRegistration is set to true. ## v9.5.0 ### New Features - Added support for username + password, API key, authoriazation code and cognitive services authentication. - Added field SkipResourceProviderRegistration to clients to provide a way to skip auto-registration of RPs. - Added utility function AsStringSlice() to convert its parameters to a string slice. ### Bug Fixes - When checking for authentication failures look at the error type not the status code as it could vary. ## v9.4.2 ### Bug Fixes - Validate parameters when creating credentials. - Don't retry requests if the returned status is a 401 (http.StatusUnauthorized) as it will never succeed. ## v9.4.1 ### Bug Fixes - Update the AccessTokensPath() to read access tokens path through AZURE_ACCESS_TOKEN_FILE. If this environment variable is not set, it will fall back to use default path set by Azure CLI. - Use case-insensitive string comparison for polling states. ## v9.4.0 ### New Features - Added WaitForCompletion() to Future as a default polling implementation. ### Bug Fixes - Method Future.Done() shouldn't update polling status for unexpected HTTP status codes. ## v9.3.1 ### Bug Fixes - DoRetryForStatusCodes will retry if sender.Do returns a non-nil error. ## v9.3.0 ### New Features - Added PollingMethod() to Future so callers know what kind of polling mechanism is used. - Added azure.ChangeToGet() which transforms an http.Request into a GET (to be used with LROs). ## v9.2.0 ### New Features - Added support for custom Azure Stack endpoints. - Added type azure.Future used to track the status of long-running operations. ### Bug Fixes - Preserve the original error in DoRetryWithRegistration when registration fails. ## v9.1.1 - Fixes a bug regarding the cookie jar on `autorest.Client.Sender`. ## v9.1.0 ### New Features - In cases where there is a non-empty error from the service, attempt to unmarshal it instead of uniformly calling it an "Unknown" error. - Support for loading Azure CLI Authentication files. - Automatically register your subscription with the Azure Resource Provider if it hadn't been previously. ### Bug Fixes - RetriableRequest can now tolerate a ReadSeekable body being read but not reset. - Adding missing Apache Headers ## v9.0.0 > **IMPORTANT:** This release was intially labeled incorrectly as `v8.4.0`. From the time it was released, it should have been marked `v9.0.0` because it contains breaking changes to the MSI packages. We appologize for any inconvenience this causes. Adding MSI Endpoint Support and CLI token rehydration. ## v8.3.1 Pick up bug fix in adal for MSI support. ## v8.3.0 Updates to Error string formats for clarity. Also, adding a copy of the http.Response to errors for an improved debugging experience. ## v8.2.0 ### New Features - Add support for bearer authentication callbacks - Support 429 response codes that include "Retry-After" header - Support validation constraint "Pattern" for map keys ### Bug Fixes - Make RetriableRequest work with multiple versions of Go ## v8.1.1 Updates the RetriableRequest to take advantage of GetBody() added in Go 1.8. ## v8.1.0 Adds RetriableRequest type for more efficient handling of retrying HTTP requests. ## v8.0.0 ADAL refactored into its own package. Support for UNIX time. ## v7.3.1 - Version Testing now removed from production bits that are shipped with the library. ## v7.3.0 - Exposing new `RespondDecorator`, `ByDiscardingBody`. This allows operations to acknowledge that they do not need either the entire or a trailing portion of accepts response body. In doing so, Go's http library can reuse HTTP connections more readily. - Adding `PrepareDecorator` to target custom BaseURLs. - Adding ACR suffix to public cloud environment. - Updating Glide dependencies. ## v7.2.5 - Fixed the Active Directory endpoint for the China cloud. - Removes UTF-8 BOM if present in response payload. - Added telemetry. ## v7.2.3 - Fixing bug in calls to `DelayForBackoff` that caused doubling of delay duration. ## v7.2.2 - autorest/azure: added ASM and ARM VM DNS suffixes. ## v7.2.1 - fixed parsing of UTC times that are not RFC3339 conformant. ## v7.2.0 - autorest/validation: Reformat validation error for better error message. ## v7.1.0 - preparer: Added support for multipart formdata - WithMultiPartFormdata() - preparer: Added support for sending file in request body - WithFile - client: Added RetryDuration parameter. - autorest/validation: new package for validation code for Azure Go SDK. ## v7.0.7 - Add trailing / to endpoint - azure: add EnvironmentFromName ## v7.0.6 - Add retry logic for 408, 500, 502, 503 and 504 status codes. - Change url path and query encoding logic. - Fix DelayForBackoff for proper exponential delay. - Add CookieJar in Client. ## v7.0.5 - Add check to start polling only when status is in [200,201,202]. - Refactoring for unchecked errors. - azure/persist changes. - Fix 'file in use' issue in renewing token in deviceflow. - Store header RetryAfter for subsequent requests in polling. - Add attribute details in service error. ## v7.0.4 - Better error messages for long running operation failures ## v7.0.3 - Corrected DoPollForAsynchronous to properly handle the initial response ## v7.0.2 - Corrected DoPollForAsynchronous to continue using the polling method first discovered ## v7.0.1 - Fixed empty JSON input error in ByUnmarshallingJSON - Fixed polling support for GET calls - Changed format name from TimeRfc1123 to TimeRFC1123 ## v7.0.0 - Added ByCopying responder with supporting TeeReadCloser - Rewrote Azure asynchronous handling - Reverted to only unmarshalling JSON - Corrected handling of RFC3339 time strings and added support for Rfc1123 time format The `json.Decoder` does not catch bad data as thoroughly as `json.Unmarshal`. Since `encoding/json` successfully deserializes all core types, and extended types normally provide their custom JSON serialization handlers, the code has been reverted back to using `json.Unmarshal`. The original change to use `json.Decode` was made to reduce duplicate code; there is no loss of function, and there is a gain in accuracy, by reverting. Additionally, Azure services indicate requests to be polled by multiple means. The existing code only checked for one of those (that is, the presence of the `Azure-AsyncOperation` header). The new code correctly covers all cases and aligns with the other Azure SDKs. ## v6.1.0 - Introduced `date.ByUnmarshallingJSONDate` and `date.ByUnmarshallingJSONTime` to enable JSON encoded values. ## v6.0.0 - Completely reworked the handling of polled and asynchronous requests - Removed unnecessary routines - Reworked `mocks.Sender` to replay a series of `http.Response` objects - Added `PrepareDecorators` for primitive types (e.g., bool, int32) Handling polled and asynchronous requests is no longer part of `Client#Send`. Instead new `SendDecorators` implement different styles of polled behavior. See`autorest.DoPollForStatusCodes` and `azure.DoPollForAsynchronous` for examples. ## v5.0.0 - Added new RespondDecorators unmarshalling primitive types - Corrected application of inspection and authorization PrependDecorators ## v4.0.0 - Added support for Azure long-running operations. - Added cancelation support to all decorators and functions that may delay. - Breaking: `DelayForBackoff` now accepts a channel, which may be nil. ## v3.1.0 - Add support for OAuth Device Flow authorization. - Add support for ServicePrincipalTokens that are backed by an existing token, rather than other secret material. - Add helpers for persisting and restoring Tokens. - Increased code coverage in the github.com/Azure/autorest/azure package ## v3.0.0 - Breaking: `NewErrorWithError` no longer takes `statusCode int`. - Breaking: `NewErrorWithStatusCode` is replaced with `NewErrorWithResponse`. - Breaking: `Client#Send()` no longer takes `codes ...int` argument. - Add: XML unmarshaling support with `ByUnmarshallingXML()` - Stopped vending dependencies locally and switched to [Glide](https://github.com/Masterminds/glide). Applications using this library should either use Glide or vendor dependencies locally some other way. - Add: `azure.WithErrorUnlessStatusCode()` decorator to handle Azure errors. - Fix: use `net/http.DefaultClient` as base client. - Fix: Missing inspection for polling responses added. - Add: CopyAndDecode helpers. - Improved `./autorest/to` with `[]string` helpers. - Removed golint suppressions in .travis.yml. ## v2.1.0 - Added `StatusCode` to `Error` for more easily obtaining the HTTP Reponse StatusCode (if any) ## v2.0.0 - Changed `to.StringMapPtr` method signature to return a pointer - Changed `ServicePrincipalCertificateSecret` and `NewServicePrincipalTokenFromCertificate` to support generic certificate and private keys ## v1.0.0 - Added Logging inspectors to trace http.Request / Response - Added support for User-Agent header - Changed WithHeader PrepareDecorator to use set vs. add - Added JSON to error when unmarshalling fails - Added Client#Send method - Corrected case of "Azure" in package paths - Added "to" helpers, Azure helpers, and improved ease-of-use - Corrected golint issues ## v1.0.1 - Added CHANGELOG.md ## v1.1.0 - Added mechanism to retrieve a ServicePrincipalToken using a certificate-signed JWT - Added an example of creating a certificate-based ServicePrincipal and retrieving an OAuth token using the certificate ## v1.1.1 - Introduce godeps and vendor dependencies introduced in v1.1.1 golang-github-azure-go-autorest-14.1.1/GNUmakefile000066400000000000000000000006261367372352400220120ustar00rootroot00000000000000DIR?=./autorest/ default: build build: fmt go install $(DIR) test: go test $(DIR) || exit 1 vet: @echo "go vet ." @go vet $(DIR)... ; if [ $$? -eq 1 ]; then \ echo ""; \ echo "Vet found suspicious constructs. Please check the reported constructs"; \ echo "and fix them if necessary before submitting the code for review."; \ exit 1; \ fi fmt: gofmt -w $(DIR) .PHONY: build test vet fmt golang-github-azure-go-autorest-14.1.1/Gopkg.lock000066400000000000000000000206371367372352400216650ustar00rootroot00000000000000# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. [[projects]] digest = "1:892e39e5c083d0943f1e80ab8351690f183c6a5ab24e1d280adcad424c26255e" name = "contrib.go.opencensus.io/exporter/ocagent" packages = ["."] pruneopts = "UT" revision = "a8a6f458bbc1d5042322ad1f9b65eeb0b69be9ea" version = "v0.6.0" [[projects]] digest = "1:8f5acd4d4462b5136af644d25101f0968a7a94ee90fcb2059cec5b7cc42e0b20" name = "github.com/census-instrumentation/opencensus-proto" packages = [ "gen-go/agent/common/v1", "gen-go/agent/metrics/v1", "gen-go/agent/trace/v1", "gen-go/metrics/v1", "gen-go/resource/v1", "gen-go/trace/v1", ] pruneopts = "UT" revision = "d89fa54de508111353cb0b06403c00569be780d8" version = "v0.2.1" [[projects]] digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" name = "github.com/davecgh/go-spew" packages = ["spew"] pruneopts = "UT" revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" version = "v1.1.1" [[projects]] digest = "1:76dc72490af7174349349838f2fe118996381b31ea83243812a97e5a0fd5ed55" name = "github.com/dgrijalva/jwt-go" packages = ["."] pruneopts = "UT" revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e" version = "v3.2.0" [[projects]] digest = "1:cf0d2e435fd4ce45b789e93ef24b5f08e86be0e9807a16beb3694e2d8c9af965" name = "github.com/dimchansky/utfbom" packages = ["."] pruneopts = "UT" revision = "d2133a1ce379ef6fa992b0514a77146c60db9d1c" version = "v1.1.0" [[projects]] branch = "master" digest = "1:b7cb6054d3dff43b38ad2e92492f220f57ae6087ee797dca298139776749ace8" name = "github.com/golang/groupcache" packages = ["lru"] pruneopts = "UT" revision = "611e8accdfc92c4187d399e95ce826046d4c8d73" [[projects]] digest = "1:e3839df32927e8d3403cd5aa7253d966e8ff80fc8f10e2e35d146461cd83fcfa" name = "github.com/golang/protobuf" packages = [ "descriptor", "jsonpb", "proto", "protoc-gen-go/descriptor", "ptypes", "ptypes/any", "ptypes/duration", "ptypes/struct", "ptypes/timestamp", "ptypes/wrappers", ] pruneopts = "UT" revision = "6c65a5562fc06764971b7c5d05c76c75e84bdbf7" version = "v1.3.2" [[projects]] digest = "1:c560cd79300fac84f124b96225181a637a70b60155919a3c36db50b7cca6b806" name = "github.com/grpc-ecosystem/grpc-gateway" packages = [ "internal", "runtime", "utilities", ] pruneopts = "UT" revision = "f7120437bb4f6c71f7f5076ad65a45310de2c009" version = "v1.12.1" [[projects]] digest = "1:5d231480e1c64a726869bc4142d270184c419749d34f167646baa21008eb0a79" name = "github.com/mitchellh/go-homedir" packages = ["."] pruneopts = "UT" revision = "af06845cf3004701891bf4fdb884bfe4920b3727" version = "v1.1.0" [[projects]] digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" name = "github.com/pmezard/go-difflib" packages = ["difflib"] pruneopts = "UT" revision = "792786c7400a136282c1664665ae0a8db921c6c2" version = "v1.0.0" [[projects]] digest = "1:99d32780e5238c2621fff621123997c3e3cca96db8be13179013aea77dfab551" name = "github.com/stretchr/testify" packages = [ "assert", "require", ] pruneopts = "UT" revision = "221dbe5ed46703ee255b1da0dec05086f5035f62" version = "v1.4.0" [[projects]] digest = "1:7c5e00383399fe13de0b4b65c9fdde16275407ce8ac02d867eafeaa916edcc71" name = "go.opencensus.io" packages = [ ".", "internal", "internal/tagencoding", "metric/metricdata", "metric/metricproducer", "plugin/ocgrpc", "plugin/ochttp", "plugin/ochttp/propagation/b3", "plugin/ochttp/propagation/tracecontext", "resource", "stats", "stats/internal", "stats/view", "tag", "trace", "trace/internal", "trace/propagation", "trace/tracestate", ] pruneopts = "UT" revision = "aad2c527c5defcf89b5afab7f37274304195a6b2" version = "v0.22.2" [[projects]] branch = "master" digest = "1:f604f5e2ee721b6757d962dfe7bab4f28aae50c456e39cfb2f3819762a44a6ae" name = "golang.org/x/crypto" packages = [ "pkcs12", "pkcs12/internal/rc2", ] pruneopts = "UT" revision = "e9b2fee46413994441b28dfca259d911d963dfed" [[projects]] branch = "master" digest = "1:334b27eac455cb6567ea28cd424230b07b1a64334a2f861a8075ac26ce10af43" name = "golang.org/x/lint" packages = [ ".", "golint", ] pruneopts = "UT" revision = "fdd1cda4f05fd1fd86124f0ef9ce31a0b72c8448" [[projects]] branch = "master" digest = "1:257a75d024975428ab9192bfc334c3490882f8cb21322ea5784ca8eca000a910" name = "golang.org/x/net" packages = [ "http/httpguts", "http2", "http2/hpack", "idna", "internal/timeseries", "trace", ] pruneopts = "UT" revision = "1ddd1de85cb0337b623b740a609d35817d516a8d" [[projects]] branch = "master" digest = "1:382bb5a7fb4034db3b6a2d19e5a4a6bcf52f4750530603c01ca18a172fa3089b" name = "golang.org/x/sync" packages = ["semaphore"] pruneopts = "UT" revision = "cd5d95a43a6e21273425c7ae415d3df9ea832eeb" [[projects]] branch = "master" digest = "1:4da420ceda5f68e8d748aa2169d0ed44ffadb1bbd6537cf778a49563104189b8" name = "golang.org/x/sys" packages = ["unix"] pruneopts = "UT" revision = "ce4227a45e2eb77e5c847278dcc6a626742e2945" [[projects]] digest = "1:8d8faad6b12a3a4c819a3f9618cb6ee1fa1cfc33253abeeea8b55336721e3405" name = "golang.org/x/text" packages = [ "collate", "collate/build", "internal/colltab", "internal/gen", "internal/language", "internal/language/compact", "internal/tag", "internal/triegen", "internal/ucd", "language", "secure/bidirule", "transform", "unicode/bidi", "unicode/cldr", "unicode/norm", "unicode/rangetable", ] pruneopts = "UT" revision = "342b2e1fbaa52c93f31447ad2c6abc048c63e475" version = "v0.3.2" [[projects]] branch = "master" digest = "1:4eb5ea8395fb60212dd58b92c9db80bab59d5e99c7435f9a6a0a528c373b60e7" name = "golang.org/x/tools" packages = [ "go/ast/astutil", "go/gcexportdata", "go/internal/gcimporter", "go/types/typeutil", ] pruneopts = "UT" revision = "259af5ff87bdcd4abf2ecda8edc3f13f04f26a42" [[projects]] digest = "1:964bb30febc27fabfbec4759fa530c6ec35e77a7c85fed90b9317ea39a054877" name = "google.golang.org/api" packages = ["support/bundler"] pruneopts = "UT" revision = "8a410c21381766a810817fd6200fce8838ecb277" version = "v0.14.0" [[projects]] branch = "master" digest = "1:a8d5c2c6e746b3485e36908ab2a9e3d77b86b81f8156d88403c7d2b462431dfd" name = "google.golang.org/genproto" packages = [ "googleapis/api/httpbody", "googleapis/rpc/status", "protobuf/field_mask", ] pruneopts = "UT" revision = "51378566eb590fa106d1025ea12835a4416dda84" [[projects]] digest = "1:b59ce3ddb11daeeccccc9cb3183b58ebf8e9a779f1c853308cd91612e817a301" name = "google.golang.org/grpc" packages = [ ".", "backoff", "balancer", "balancer/base", "balancer/roundrobin", "binarylog/grpc_binarylog_v1", "codes", "connectivity", "credentials", "credentials/internal", "encoding", "encoding/proto", "grpclog", "internal", "internal/backoff", "internal/balancerload", "internal/binarylog", "internal/buffer", "internal/channelz", "internal/envconfig", "internal/grpcrand", "internal/grpcsync", "internal/resolver/dns", "internal/resolver/passthrough", "internal/syscall", "internal/transport", "keepalive", "metadata", "naming", "peer", "resolver", "serviceconfig", "stats", "status", "tap", ] pruneopts = "UT" revision = "1a3960e4bd028ac0cec0a2afd27d7d8e67c11514" version = "v1.25.1" [[projects]] digest = "1:b75b3deb2bce8bc079e16bb2aecfe01eb80098f5650f9e93e5643ca8b7b73737" name = "gopkg.in/yaml.v2" packages = ["."] pruneopts = "UT" revision = "1f64d6156d11335c3f22d9330b0ad14fc1e789ce" version = "v2.2.7" [solve-meta] analyzer-name = "dep" analyzer-version = 1 input-imports = [ "contrib.go.opencensus.io/exporter/ocagent", "github.com/dgrijalva/jwt-go", "github.com/dimchansky/utfbom", "github.com/mitchellh/go-homedir", "github.com/stretchr/testify/require", "go.opencensus.io/plugin/ochttp", "go.opencensus.io/plugin/ochttp/propagation/tracecontext", "go.opencensus.io/stats/view", "go.opencensus.io/trace", "golang.org/x/crypto/pkcs12", "golang.org/x/lint/golint", ] solver-name = "gps-cdcl" solver-version = 1 golang-github-azure-go-autorest-14.1.1/Gopkg.toml000066400000000000000000000023051367372352400217000ustar00rootroot00000000000000# Gopkg.toml example # # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html # for detailed Gopkg.toml documentation. # # required = ["github.com/user/thing/cmd/thing"] # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] # # [[constraint]] # name = "github.com/user/project" # version = "1.0.0" # # [[constraint]] # name = "github.com/user/project2" # branch = "dev" # source = "github.com/myfork/project2" # # [[override]] # name = "github.com/x/y" # version = "2.4.0" # # [prune] # non-go = false # go-tests = true # unused-packages = true required = ["golang.org/x/lint/golint"] [prune] go-tests = true unused-packages = true [[constraint]] name = "contrib.go.opencensus.io/exporter/ocagent" version = "0.6.0" [[constraint]] name = "github.com/dgrijalva/jwt-go" version = "3.2.0" [[constraint]] name = "github.com/dimchansky/utfbom" version = "1.1.0" [[constraint]] name = "github.com/mitchellh/go-homedir" version = "1.1.0" [[constraint]] name = "github.com/stretchr/testify" version = "1.3.0" [[constraint]] name = "go.opencensus.io" version = "0.22.0" [[constraint]] branch = "master" name = "golang.org/x/crypto" golang-github-azure-go-autorest-14.1.1/LICENSE000066400000000000000000000250171367372352400207460ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS Copyright 2015 Microsoft Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. golang-github-azure-go-autorest-14.1.1/README.md000066400000000000000000000145221367372352400212170ustar00rootroot00000000000000# go-autorest [![GoDoc](https://godoc.org/github.com/Azure/go-autorest/autorest?status.png)](https://godoc.org/github.com/Azure/go-autorest/autorest) [![Build Status](https://dev.azure.com/azure-sdk/public/_apis/build/status/go/Azure.go-autorest?branchName=master)](https://dev.azure.com/azure-sdk/public/_build/latest?definitionId=625&branchName=master) [![Go Report Card](https://goreportcard.com/badge/Azure/go-autorest)](https://goreportcard.com/report/Azure/go-autorest) Package go-autorest provides an HTTP request client for use with [Autorest](https://github.com/Azure/autorest.go)-generated API client packages. An authentication client tested with Azure Active Directory (AAD) is also provided in this repo in the package `github.com/Azure/go-autorest/autorest/adal`. Despite its name, this package is maintained only as part of the Azure Go SDK and is not related to other "ADAL" libraries in [github.com/AzureAD](https://github.com/AzureAD). ## Overview Package go-autorest implements an HTTP request pipeline suitable for use across multiple goroutines and provides the shared routines used by packages generated by [Autorest](https://github.com/Azure/autorest.go). The package breaks sending and responding to HTTP requests into three phases: Preparing, Sending, and Responding. A typical pattern is: ```go req, err := Prepare(&http.Request{}, token.WithAuthorization()) resp, err := Send(req, WithLogging(logger), DoErrorIfStatusCode(http.StatusInternalServerError), DoCloseIfError(), DoRetryForAttempts(5, time.Second)) err = Respond(resp, ByDiscardingBody(), ByClosing()) ``` Each phase relies on decorators to modify and / or manage processing. Decorators may first modify and then pass the data along, pass the data first and then modify the result, or wrap themselves around passing the data (such as a logger might do). Decorators run in the order provided. For example, the following: ```go req, err := Prepare(&http.Request{}, WithBaseURL("https://microsoft.com/"), WithPath("a"), WithPath("b"), WithPath("c")) ``` will set the URL to: ``` https://microsoft.com/a/b/c ``` Preparers and Responders may be shared and re-used (assuming the underlying decorators support sharing and re-use). Performant use is obtained by creating one or more Preparers and Responders shared among multiple go-routines, and a single Sender shared among multiple sending go-routines, all bound together by means of input / output channels. Decorators hold their passed state within a closure (such as the path components in the example above). Be careful to share Preparers and Responders only in a context where such held state applies. For example, it may not make sense to share a Preparer that applies a query string from a fixed set of values. Similarly, sharing a Responder that reads the response body into a passed struct (e.g., `ByUnmarshallingJson`) is likely incorrect. Errors raised by autorest objects and methods will conform to the `autorest.Error` interface. See the included examples for more detail. For details on the suggested use of this package by generated clients, see the Client described below. ## Helpers ### Handling Swagger Dates The Swagger specification (https://swagger.io) that drives AutoRest (https://github.com/Azure/autorest/) precisely defines two date forms: date and date-time. The github.com/Azure/go-autorest/autorest/date package provides time.Time derivations to ensure correct parsing and formatting. ### Handling Empty Values In JSON, missing values have different semantics than empty values. This is especially true for services using the HTTP PATCH verb. The JSON submitted with a PATCH request generally contains only those values to modify. Missing values are to be left unchanged. Developers, then, require a means to both specify an empty value and to leave the value out of the submitted JSON. The Go JSON package (`encoding/json`) supports the `omitempty` tag. When specified, it omits empty values from the rendered JSON. Since Go defines default values for all base types (such as "" for string and 0 for int) and provides no means to mark a value as actually empty, the JSON package treats default values as meaning empty, omitting them from the rendered JSON. This means that, using the Go base types encoded through the default JSON package, it is not possible to create JSON to clear a value at the server. The workaround within the Go community is to use pointers to base types in lieu of base types within structures that map to JSON. For example, instead of a value of type `string`, the workaround uses `*string`. While this enables distinguishing empty values from those to be unchanged, creating pointers to a base type (notably constant, in-line values) requires additional variables. This, for example, ```go s := struct { S *string }{ S: &"foo" } ``` fails, while, this ```go v := "foo" s := struct { S *string }{ S: &v } ``` succeeds. To ease using pointers, the subpackage `to` contains helpers that convert to and from pointers for Go base types which have Swagger analogs. It also provides a helper that converts between `map[string]string` and `map[string]*string`, enabling the JSON to specify that the value associated with a key should be cleared. With the helpers, the previous example becomes ```go s := struct { S *string }{ S: to.StringPtr("foo") } ``` ## Install ```bash go get github.com/Azure/go-autorest/autorest go get github.com/Azure/go-autorest/autorest/azure go get github.com/Azure/go-autorest/autorest/date go get github.com/Azure/go-autorest/autorest/to ``` ### Using with Go Modules In [v12.0.1](https://github.com/Azure/go-autorest/pull/386), this repository introduced the following modules. - autorest/adal - autorest/azure/auth - autorest/azure/cli - autorest/date - autorest/mocks - autorest/to - autorest/validation - autorest - logger - tracing Tagging cumulative SDK releases as a whole (e.g. `v12.3.0`) is still enabled to support consumers of this repo that have not yet migrated to modules. ## License See LICENSE file. ----- This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. golang-github-azure-go-autorest-14.1.1/autorest/000077500000000000000000000000001367372352400216025ustar00rootroot00000000000000golang-github-azure-go-autorest-14.1.1/autorest/adal/000077500000000000000000000000001367372352400225035ustar00rootroot00000000000000golang-github-azure-go-autorest-14.1.1/autorest/adal/README.md000066400000000000000000000166431367372352400237740ustar00rootroot00000000000000# Azure Active Directory authentication for Go This is a standalone package for authenticating with Azure Active Directory from other Go libraries and applications, in particular the [Azure SDK for Go](https://github.com/Azure/azure-sdk-for-go). Note: Despite the package's name it is not related to other "ADAL" libraries maintained in the [github.com/AzureAD](https://github.com/AzureAD) org. Issues should be opened in [this repo's](https://github.com/Azure/go-autorest/issues) or [the SDK's](https://github.com/Azure/azure-sdk-for-go/issues) issue trackers. ## Install ```bash go get -u github.com/Azure/go-autorest/autorest/adal ``` ## Usage An Active Directory application is required in order to use this library. An application can be registered in the [Azure Portal](https://portal.azure.com/) by following these [guidelines](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-integrating-applications) or using the [Azure CLI](https://github.com/Azure/azure-cli). ### Register an Azure AD Application with secret 1. Register a new application with a `secret` credential ``` az ad app create \ --display-name example-app \ --homepage https://example-app/home \ --identifier-uris https://example-app/app \ --password secret ``` 2. Create a service principal using the `Application ID` from previous step ``` az ad sp create --id "Application ID" ``` * Replace `Application ID` with `appId` from step 1. ### Register an Azure AD Application with certificate 1. Create a private key ``` openssl genrsa -out "example-app.key" 2048 ``` 2. Create the certificate ``` openssl req -new -key "example-app.key" -subj "/CN=example-app" -out "example-app.csr" openssl x509 -req -in "example-app.csr" -signkey "example-app.key" -out "example-app.crt" -days 10000 ``` 3. Create the PKCS12 version of the certificate containing also the private key ``` openssl pkcs12 -export -out "example-app.pfx" -inkey "example-app.key" -in "example-app.crt" -passout pass: ``` 4. Register a new application with the certificate content form `example-app.crt` ``` certificateContents="$(tail -n+2 "example-app.crt" | head -n-1)" az ad app create \ --display-name example-app \ --homepage https://example-app/home \ --identifier-uris https://example-app/app \ --key-usage Verify --end-date 2018-01-01 \ --key-value "${certificateContents}" ``` 5. Create a service principal using the `Application ID` from previous step ``` az ad sp create --id "APPLICATION_ID" ``` * Replace `APPLICATION_ID` with `appId` from step 4. ### Grant the necessary permissions Azure relies on a Role-Based Access Control (RBAC) model to manage the access to resources at a fine-grained level. There is a set of [pre-defined roles](https://docs.microsoft.com/en-us/azure/active-directory/role-based-access-built-in-roles) which can be assigned to a service principal of an Azure AD application depending of your needs. ``` az role assignment create --assigner "SERVICE_PRINCIPAL_ID" --role "ROLE_NAME" ``` * Replace the `SERVICE_PRINCIPAL_ID` with the `appId` from previous step. * Replace the `ROLE_NAME` with a role name of your choice. It is also possible to define custom role definitions. ``` az role definition create --role-definition role-definition.json ``` * Check [custom roles](https://docs.microsoft.com/en-us/azure/active-directory/role-based-access-control-custom-roles) for more details regarding the content of `role-definition.json` file. ### Acquire Access Token The common configuration used by all flows: ```Go const activeDirectoryEndpoint = "https://login.microsoftonline.com/" tenantID := "TENANT_ID" oauthConfig, err := adal.NewOAuthConfig(activeDirectoryEndpoint, tenantID) applicationID := "APPLICATION_ID" callback := func(token adal.Token) error { // This is called after the token is acquired } // The resource for which the token is acquired resource := "https://management.core.windows.net/" ``` * Replace the `TENANT_ID` with your tenant ID. * Replace the `APPLICATION_ID` with the value from previous section. #### Client Credentials ```Go applicationSecret := "APPLICATION_SECRET" spt, err := adal.NewServicePrincipalToken( *oauthConfig, appliationID, applicationSecret, resource, callbacks...) if err != nil { return nil, err } // Acquire a new access token err = spt.Refresh() if (err == nil) { token := spt.Token } ``` * Replace the `APPLICATION_SECRET` with the `password` value from previous section. #### Client Certificate ```Go certificatePath := "./example-app.pfx" certData, err := ioutil.ReadFile(certificatePath) if err != nil { return nil, fmt.Errorf("failed to read the certificate file (%s): %v", certificatePath, err) } // Get the certificate and private key from pfx file certificate, rsaPrivateKey, err := decodePkcs12(certData, "") if err != nil { return nil, fmt.Errorf("failed to decode pkcs12 certificate while creating spt: %v", err) } spt, err := adal.NewServicePrincipalTokenFromCertificate( *oauthConfig, applicationID, certificate, rsaPrivateKey, resource, callbacks...) // Acquire a new access token err = spt.Refresh() if (err == nil) { token := spt.Token } ``` * Update the certificate path to point to the example-app.pfx file which was created in previous section. #### Device Code ```Go oauthClient := &http.Client{} // Acquire the device code deviceCode, err := adal.InitiateDeviceAuth( oauthClient, *oauthConfig, applicationID, resource) if err != nil { return nil, fmt.Errorf("Failed to start device auth flow: %s", err) } // Display the authentication message fmt.Println(*deviceCode.Message) // Wait here until the user is authenticated token, err := adal.WaitForUserCompletion(oauthClient, deviceCode) if err != nil { return nil, fmt.Errorf("Failed to finish device auth flow: %s", err) } spt, err := adal.NewServicePrincipalTokenFromManualToken( *oauthConfig, applicationID, resource, *token, callbacks...) if (err == nil) { token := spt.Token } ``` #### Username password authenticate ```Go spt, err := adal.NewServicePrincipalTokenFromUsernamePassword( *oauthConfig, applicationID, username, password, resource, callbacks...) if (err == nil) { token := spt.Token } ``` #### Authorization code authenticate ``` Go spt, err := adal.NewServicePrincipalTokenFromAuthorizationCode( *oauthConfig, applicationID, clientSecret, authorizationCode, redirectURI, resource, callbacks...) err = spt.Refresh() if (err == nil) { token := spt.Token } ``` ### Command Line Tool A command line tool is available in `cmd/adal.go` that can acquire a token for a given resource. It supports all flows mentioned above. ``` adal -h Usage of ./adal: -applicationId string application id -certificatePath string path to pk12/PFC application certificate -mode string authentication mode (device, secret, cert, refresh) (default "device") -resource string resource for which the token is requested -secret string application secret -tenantId string tenant id -tokenCachePath string location of oath token cache (default "/home/cgc/.adal/accessToken.json") ``` Example acquire a token for `https://management.core.windows.net/` using device code flow: ``` adal -mode device \ -applicationId "APPLICATION_ID" \ -tenantId "TENANT_ID" \ -resource https://management.core.windows.net/ ``` golang-github-azure-go-autorest-14.1.1/autorest/adal/cmd/000077500000000000000000000000001367372352400232465ustar00rootroot00000000000000golang-github-azure-go-autorest-14.1.1/autorest/adal/cmd/adal.go000066400000000000000000000166001367372352400245010ustar00rootroot00000000000000package main // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "flag" "fmt" "log" "strings" "crypto/rsa" "crypto/x509" "io/ioutil" "net/http" "os/user" "github.com/Azure/go-autorest/autorest/adal" "golang.org/x/crypto/pkcs12" ) const ( deviceMode = "device" clientSecretMode = "secret" clientCertMode = "cert" refreshMode = "refresh" activeDirectoryEndpoint = "https://login.microsoftonline.com/" ) type option struct { name string value string } var ( mode string resource string tenantID string applicationID string applicationSecret string certificatePath string tokenCachePath string ) func checkMandatoryOptions(mode string, options ...option) { for _, option := range options { if strings.TrimSpace(option.value) == "" { log.Fatalf("Authentication mode '%s' requires mandatory option '%s'.", mode, option.name) } } } func defaultTokenCachePath() string { usr, err := user.Current() if err != nil { log.Fatal(err) } defaultTokenPath := usr.HomeDir + "/.adal/accessToken.json" return defaultTokenPath } func init() { flag.StringVar(&mode, "mode", "device", "authentication mode (device, secret, cert, refresh)") flag.StringVar(&resource, "resource", "", "resource for which the token is requested") flag.StringVar(&tenantID, "tenantId", "", "tenant id") flag.StringVar(&applicationID, "applicationId", "", "application id") flag.StringVar(&applicationSecret, "secret", "", "application secret") flag.StringVar(&certificatePath, "certificatePath", "", "path to pk12/PFC application certificate") flag.StringVar(&tokenCachePath, "tokenCachePath", defaultTokenCachePath(), "location of oath token cache") flag.Parse() switch mode = strings.TrimSpace(mode); mode { case clientSecretMode: checkMandatoryOptions(clientSecretMode, option{name: "resource", value: resource}, option{name: "tenantId", value: tenantID}, option{name: "applicationId", value: applicationID}, option{name: "secret", value: applicationSecret}, ) case clientCertMode: checkMandatoryOptions(clientCertMode, option{name: "resource", value: resource}, option{name: "tenantId", value: tenantID}, option{name: "applicationId", value: applicationID}, option{name: "certificatePath", value: certificatePath}, ) case deviceMode: checkMandatoryOptions(deviceMode, option{name: "resource", value: resource}, option{name: "tenantId", value: tenantID}, option{name: "applicationId", value: applicationID}, ) case refreshMode: checkMandatoryOptions(refreshMode, option{name: "resource", value: resource}, option{name: "tenantId", value: tenantID}, option{name: "applicationId", value: applicationID}, ) default: log.Fatalln("Authentication modes 'secret, 'cert', 'device' or 'refresh' are supported.") } } func acquireTokenClientSecretFlow(oauthConfig adal.OAuthConfig, appliationID string, applicationSecret string, resource string, callbacks ...adal.TokenRefreshCallback) (*adal.ServicePrincipalToken, error) { spt, err := adal.NewServicePrincipalToken( oauthConfig, appliationID, applicationSecret, resource, callbacks...) if err != nil { return nil, err } return spt, spt.Refresh() } func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.PrivateKey, error) { privateKey, certificate, err := pkcs12.Decode(pkcs, password) if err != nil { return nil, nil, err } rsaPrivateKey, isRsaKey := privateKey.(*rsa.PrivateKey) if !isRsaKey { return nil, nil, fmt.Errorf("PKCS#12 certificate must contain an RSA private key") } return certificate, rsaPrivateKey, nil } func acquireTokenClientCertFlow(oauthConfig adal.OAuthConfig, applicationID string, applicationCertPath string, resource string, callbacks ...adal.TokenRefreshCallback) (*adal.ServicePrincipalToken, error) { certData, err := ioutil.ReadFile(certificatePath) if err != nil { return nil, fmt.Errorf("failed to read the certificate file (%s): %v", certificatePath, err) } certificate, rsaPrivateKey, err := decodePkcs12(certData, "") if err != nil { return nil, fmt.Errorf("failed to decode pkcs12 certificate while creating spt: %v", err) } spt, err := adal.NewServicePrincipalTokenFromCertificate( oauthConfig, applicationID, certificate, rsaPrivateKey, resource, callbacks...) if err != nil { return nil, err } return spt, spt.Refresh() } func acquireTokenDeviceCodeFlow(oauthConfig adal.OAuthConfig, applicationID string, resource string, callbacks ...adal.TokenRefreshCallback) (*adal.ServicePrincipalToken, error) { oauthClient := &http.Client{} deviceCode, err := adal.InitiateDeviceAuth( oauthClient, oauthConfig, applicationID, resource) if err != nil { return nil, fmt.Errorf("Failed to start device auth flow: %s", err) } fmt.Println(*deviceCode.Message) token, err := adal.WaitForUserCompletion(oauthClient, deviceCode) if err != nil { return nil, fmt.Errorf("Failed to finish device auth flow: %s", err) } spt, err := adal.NewServicePrincipalTokenFromManualToken( oauthConfig, applicationID, resource, *token, callbacks...) return spt, err } func refreshToken(oauthConfig adal.OAuthConfig, applicationID string, resource string, tokenCachePath string, callbacks ...adal.TokenRefreshCallback) (*adal.ServicePrincipalToken, error) { token, err := adal.LoadToken(tokenCachePath) if err != nil { return nil, fmt.Errorf("failed to load token from cache: %v", err) } spt, err := adal.NewServicePrincipalTokenFromManualToken( oauthConfig, applicationID, resource, *token, callbacks...) if err != nil { return nil, err } return spt, spt.Refresh() } func saveToken(spt adal.Token) error { if tokenCachePath != "" { err := adal.SaveToken(tokenCachePath, 0600, spt) if err != nil { return err } log.Printf("Acquired token was saved in '%s' file\n", tokenCachePath) return nil } return fmt.Errorf("empty path for token cache") } func main() { oauthConfig, err := adal.NewOAuthConfig(activeDirectoryEndpoint, tenantID) if err != nil { panic(err) } callback := func(token adal.Token) error { return saveToken(token) } log.Printf("Authenticating with mode '%s'\n", mode) switch mode { case clientSecretMode: _, err = acquireTokenClientSecretFlow( *oauthConfig, applicationID, applicationSecret, resource, callback) case clientCertMode: _, err = acquireTokenClientCertFlow( *oauthConfig, applicationID, certificatePath, resource, callback) case deviceMode: var spt *adal.ServicePrincipalToken spt, err = acquireTokenDeviceCodeFlow( *oauthConfig, applicationID, resource, callback) if err == nil { err = saveToken(spt.Token()) } case refreshMode: _, err = refreshToken( *oauthConfig, applicationID, resource, tokenCachePath, callback) } if err != nil { log.Fatalf("Failed to acquire a token for resource %s. Error: %v", resource, err) } } golang-github-azure-go-autorest-14.1.1/autorest/adal/config.go000066400000000000000000000115061367372352400243020ustar00rootroot00000000000000package adal // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "errors" "fmt" "net/url" ) const ( activeDirectoryEndpointTemplate = "%s/oauth2/%s%s" ) // OAuthConfig represents the endpoints needed // in OAuth operations type OAuthConfig struct { AuthorityEndpoint url.URL `json:"authorityEndpoint"` AuthorizeEndpoint url.URL `json:"authorizeEndpoint"` TokenEndpoint url.URL `json:"tokenEndpoint"` DeviceCodeEndpoint url.URL `json:"deviceCodeEndpoint"` } // IsZero returns true if the OAuthConfig object is zero-initialized. func (oac OAuthConfig) IsZero() bool { return oac == OAuthConfig{} } func validateStringParam(param, name string) error { if len(param) == 0 { return fmt.Errorf("parameter '" + name + "' cannot be empty") } return nil } // NewOAuthConfig returns an OAuthConfig with tenant specific urls func NewOAuthConfig(activeDirectoryEndpoint, tenantID string) (*OAuthConfig, error) { apiVer := "1.0" return NewOAuthConfigWithAPIVersion(activeDirectoryEndpoint, tenantID, &apiVer) } // NewOAuthConfigWithAPIVersion returns an OAuthConfig with tenant specific urls. // If apiVersion is not nil the "api-version" query parameter will be appended to the endpoint URLs with the specified value. func NewOAuthConfigWithAPIVersion(activeDirectoryEndpoint, tenantID string, apiVersion *string) (*OAuthConfig, error) { if err := validateStringParam(activeDirectoryEndpoint, "activeDirectoryEndpoint"); err != nil { return nil, err } api := "" // it's legal for tenantID to be empty so don't validate it if apiVersion != nil { if err := validateStringParam(*apiVersion, "apiVersion"); err != nil { return nil, err } api = fmt.Sprintf("?api-version=%s", *apiVersion) } u, err := url.Parse(activeDirectoryEndpoint) if err != nil { return nil, err } authorityURL, err := u.Parse(tenantID) if err != nil { return nil, err } authorizeURL, err := u.Parse(fmt.Sprintf(activeDirectoryEndpointTemplate, tenantID, "authorize", api)) if err != nil { return nil, err } tokenURL, err := u.Parse(fmt.Sprintf(activeDirectoryEndpointTemplate, tenantID, "token", api)) if err != nil { return nil, err } deviceCodeURL, err := u.Parse(fmt.Sprintf(activeDirectoryEndpointTemplate, tenantID, "devicecode", api)) if err != nil { return nil, err } return &OAuthConfig{ AuthorityEndpoint: *authorityURL, AuthorizeEndpoint: *authorizeURL, TokenEndpoint: *tokenURL, DeviceCodeEndpoint: *deviceCodeURL, }, nil } // MultiTenantOAuthConfig provides endpoints for primary and aulixiary tenant IDs. type MultiTenantOAuthConfig interface { PrimaryTenant() *OAuthConfig AuxiliaryTenants() []*OAuthConfig } // OAuthOptions contains optional OAuthConfig creation arguments. type OAuthOptions struct { APIVersion string } func (c OAuthOptions) apiVersion() string { if c.APIVersion != "" { return fmt.Sprintf("?api-version=%s", c.APIVersion) } return "1.0" } // NewMultiTenantOAuthConfig creates an object that support multitenant OAuth configuration. // See https://docs.microsoft.com/en-us/azure/azure-resource-manager/authenticate-multi-tenant for more information. func NewMultiTenantOAuthConfig(activeDirectoryEndpoint, primaryTenantID string, auxiliaryTenantIDs []string, options OAuthOptions) (MultiTenantOAuthConfig, error) { if len(auxiliaryTenantIDs) == 0 || len(auxiliaryTenantIDs) > 3 { return nil, errors.New("must specify one to three auxiliary tenants") } mtCfg := multiTenantOAuthConfig{ cfgs: make([]*OAuthConfig, len(auxiliaryTenantIDs)+1), } apiVer := options.apiVersion() pri, err := NewOAuthConfigWithAPIVersion(activeDirectoryEndpoint, primaryTenantID, &apiVer) if err != nil { return nil, fmt.Errorf("failed to create OAuthConfig for primary tenant: %v", err) } mtCfg.cfgs[0] = pri for i := range auxiliaryTenantIDs { aux, err := NewOAuthConfig(activeDirectoryEndpoint, auxiliaryTenantIDs[i]) if err != nil { return nil, fmt.Errorf("failed to create OAuthConfig for tenant '%s': %v", auxiliaryTenantIDs[i], err) } mtCfg.cfgs[i+1] = aux } return mtCfg, nil } type multiTenantOAuthConfig struct { // first config in the slice is the primary tenant cfgs []*OAuthConfig } func (m multiTenantOAuthConfig) PrimaryTenant() *OAuthConfig { return m.cfgs[0] } func (m multiTenantOAuthConfig) AuxiliaryTenants() []*OAuthConfig { return m.cfgs[1:] } golang-github-azure-go-autorest-14.1.1/autorest/adal/config_test.go000066400000000000000000000131421367372352400253370ustar00rootroot00000000000000package adal // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "fmt" "testing" ) const TestAuxTenantPrefix = "aux-tenant-test-" var ( TestAuxTenantIDs = []string{TestAuxTenantPrefix + "0", TestAuxTenantPrefix + "1", TestAuxTenantPrefix + "2"} ) func TestNewOAuthConfig(t *testing.T) { config, err := NewOAuthConfig(TestActiveDirectoryEndpoint, TestTenantID) if err != nil { t.Fatalf("autorest/adal: Unexpected error while creating oauth configuration for tenant: %v.", err) } expected := fmt.Sprintf("https://login.test.com/%s/oauth2/authorize?api-version=1.0", TestTenantID) if config.AuthorizeEndpoint.String() != expected { t.Fatalf("autorest/adal: Incorrect authorize url for Tenant from Environment. expected(%s). actual(%v).", expected, config.AuthorizeEndpoint) } expected = fmt.Sprintf("https://login.test.com/%s/oauth2/token?api-version=1.0", TestTenantID) if config.TokenEndpoint.String() != expected { t.Fatalf("autorest/adal: Incorrect authorize url for Tenant from Environment. expected(%s). actual(%v).", expected, config.TokenEndpoint) } expected = fmt.Sprintf("https://login.test.com/%s/oauth2/devicecode?api-version=1.0", TestTenantID) if config.DeviceCodeEndpoint.String() != expected { t.Fatalf("autorest/adal Incorrect devicecode url for Tenant from Environment. expected(%s). actual(%v).", expected, config.DeviceCodeEndpoint) } } func TestNewOAuthConfigWithAPIVersionNil(t *testing.T) { config, err := NewOAuthConfigWithAPIVersion(TestActiveDirectoryEndpoint, TestTenantID, nil) if err != nil { t.Fatalf("autorest/adal: Unexpected error while creating oauth configuration for tenant: %v.", err) } expected := fmt.Sprintf("https://login.test.com/%s/oauth2/authorize", TestTenantID) if config.AuthorizeEndpoint.String() != expected { t.Fatalf("autorest/adal: Incorrect authorize url for Tenant from Environment. expected(%s). actual(%v).", expected, config.AuthorizeEndpoint) } expected = fmt.Sprintf("https://login.test.com/%s/oauth2/token", TestTenantID) if config.TokenEndpoint.String() != expected { t.Fatalf("autorest/adal: Incorrect authorize url for Tenant from Environment. expected(%s). actual(%v).", expected, config.TokenEndpoint) } expected = fmt.Sprintf("https://login.test.com/%s/oauth2/devicecode", TestTenantID) if config.DeviceCodeEndpoint.String() != expected { t.Fatalf("autorest/adal Incorrect devicecode url for Tenant from Environment. expected(%s). actual(%v).", expected, config.DeviceCodeEndpoint) } } func TestNewOAuthConfigWithAPIVersionNotNil(t *testing.T) { apiVersion := "2.0" config, err := NewOAuthConfigWithAPIVersion(TestActiveDirectoryEndpoint, TestTenantID, &apiVersion) if err != nil { t.Fatalf("autorest/adal: Unexpected error while creating oauth configuration for tenant: %v.", err) } expected := fmt.Sprintf("https://login.test.com/%s/oauth2/authorize?api-version=2.0", TestTenantID) if config.AuthorizeEndpoint.String() != expected { t.Fatalf("autorest/adal: Incorrect authorize url for Tenant from Environment. expected(%s). actual(%v).", expected, config.AuthorizeEndpoint) } expected = fmt.Sprintf("https://login.test.com/%s/oauth2/token?api-version=2.0", TestTenantID) if config.TokenEndpoint.String() != expected { t.Fatalf("autorest/adal: Incorrect authorize url for Tenant from Environment. expected(%s). actual(%v).", expected, config.TokenEndpoint) } expected = fmt.Sprintf("https://login.test.com/%s/oauth2/devicecode?api-version=2.0", TestTenantID) if config.DeviceCodeEndpoint.String() != expected { t.Fatalf("autorest/adal Incorrect devicecode url for Tenant from Environment. expected(%s). actual(%v).", expected, config.DeviceCodeEndpoint) } } func TestNewMultiTenantOAuthConfig(t *testing.T) { cfg, err := NewMultiTenantOAuthConfig(TestActiveDirectoryEndpoint, TestTenantID, TestAuxTenantIDs, OAuthOptions{}) if err != nil { t.Fatalf("autorest/adal: unexpected error while creating multitenant config: %v", err) } expected := fmt.Sprintf("https://login.test.com/%s/oauth2/authorize?api-version=1.0", TestTenantID) if ep := cfg.PrimaryTenant().AuthorizeEndpoint.String(); ep != expected { t.Fatalf("autorest/adal: Incorrect authorize url for Tenant from Environment. expected(%s). actual(%v).", expected, ep) } aux := cfg.AuxiliaryTenants() if len(aux) == 0 { t.Fatal("autorest/adal: unexpected zero-length auxiliary tenants") } for i := range aux { expected := fmt.Sprintf("https://login.test.com/aux-tenant-test-%d/oauth2/authorize?api-version=1.0", i) if ep := aux[i].AuthorizeEndpoint.String(); ep != expected { t.Fatalf("autorest/adal: Incorrect authorize url for Tenant from Environment. expected(%s). actual(%v).", expected, ep) } } } func TestNewMultiTenantOAuthConfigFail(t *testing.T) { _, err := NewMultiTenantOAuthConfig(TestActiveDirectoryEndpoint, TestTenantID, nil, OAuthOptions{}) if err == nil { t.Fatal("autorest/adal: expected non-nil error") } _, err = NewMultiTenantOAuthConfig(TestActiveDirectoryEndpoint, TestTenantID, []string{"one", "two", "three", "four"}, OAuthOptions{}) if err == nil { t.Fatal("autorest/adal: expected non-nil error") } } golang-github-azure-go-autorest-14.1.1/autorest/adal/devicetoken.go000066400000000000000000000236631367372352400253440ustar00rootroot00000000000000package adal // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* This file is largely based on rjw57/oauth2device's code, with the follow differences: * scope -> resource, and only allow a single one * receive "Message" in the DeviceCode struct and show it to users as the prompt * azure-xplat-cli has the following behavior that this emulates: - does not send client_secret during the token exchange - sends resource again in the token exchange request */ import ( "context" "encoding/json" "fmt" "io/ioutil" "net/http" "net/url" "strings" "time" ) const ( logPrefix = "autorest/adal/devicetoken:" ) var ( // ErrDeviceGeneric represents an unknown error from the token endpoint when using device flow ErrDeviceGeneric = fmt.Errorf("%s Error while retrieving OAuth token: Unknown Error", logPrefix) // ErrDeviceAccessDenied represents an access denied error from the token endpoint when using device flow ErrDeviceAccessDenied = fmt.Errorf("%s Error while retrieving OAuth token: Access Denied", logPrefix) // ErrDeviceAuthorizationPending represents the server waiting on the user to complete the device flow ErrDeviceAuthorizationPending = fmt.Errorf("%s Error while retrieving OAuth token: Authorization Pending", logPrefix) // ErrDeviceCodeExpired represents the server timing out and expiring the code during device flow ErrDeviceCodeExpired = fmt.Errorf("%s Error while retrieving OAuth token: Code Expired", logPrefix) // ErrDeviceSlowDown represents the service telling us we're polling too often during device flow ErrDeviceSlowDown = fmt.Errorf("%s Error while retrieving OAuth token: Slow Down", logPrefix) // ErrDeviceCodeEmpty represents an empty device code from the device endpoint while using device flow ErrDeviceCodeEmpty = fmt.Errorf("%s Error while retrieving device code: Device Code Empty", logPrefix) // ErrOAuthTokenEmpty represents an empty OAuth token from the token endpoint when using device flow ErrOAuthTokenEmpty = fmt.Errorf("%s Error while retrieving OAuth token: Token Empty", logPrefix) errCodeSendingFails = "Error occurred while sending request for Device Authorization Code" errCodeHandlingFails = "Error occurred while handling response from the Device Endpoint" errTokenSendingFails = "Error occurred while sending request with device code for a token" errTokenHandlingFails = "Error occurred while handling response from the Token Endpoint (during device flow)" errStatusNotOK = "Error HTTP status != 200" ) // DeviceCode is the object returned by the device auth endpoint // It contains information to instruct the user to complete the auth flow type DeviceCode struct { DeviceCode *string `json:"device_code,omitempty"` UserCode *string `json:"user_code,omitempty"` VerificationURL *string `json:"verification_url,omitempty"` ExpiresIn *int64 `json:"expires_in,string,omitempty"` Interval *int64 `json:"interval,string,omitempty"` Message *string `json:"message"` // Azure specific Resource string // store the following, stored when initiating, used when exchanging OAuthConfig OAuthConfig ClientID string } // TokenError is the object returned by the token exchange endpoint // when something is amiss type TokenError struct { Error *string `json:"error,omitempty"` ErrorCodes []int `json:"error_codes,omitempty"` ErrorDescription *string `json:"error_description,omitempty"` Timestamp *string `json:"timestamp,omitempty"` TraceID *string `json:"trace_id,omitempty"` } // DeviceToken is the object return by the token exchange endpoint // It can either look like a Token or an ErrorToken, so put both here // and check for presence of "Error" to know if we are in error state type deviceToken struct { Token TokenError } // InitiateDeviceAuth initiates a device auth flow. It returns a DeviceCode // that can be used with CheckForUserCompletion or WaitForUserCompletion. // Deprecated: use InitiateDeviceAuthWithContext() instead. func InitiateDeviceAuth(sender Sender, oauthConfig OAuthConfig, clientID, resource string) (*DeviceCode, error) { return InitiateDeviceAuthWithContext(context.Background(), sender, oauthConfig, clientID, resource) } // InitiateDeviceAuthWithContext initiates a device auth flow. It returns a DeviceCode // that can be used with CheckForUserCompletion or WaitForUserCompletion. func InitiateDeviceAuthWithContext(ctx context.Context, sender Sender, oauthConfig OAuthConfig, clientID, resource string) (*DeviceCode, error) { v := url.Values{ "client_id": []string{clientID}, "resource": []string{resource}, } s := v.Encode() body := ioutil.NopCloser(strings.NewReader(s)) req, err := http.NewRequest(http.MethodPost, oauthConfig.DeviceCodeEndpoint.String(), body) if err != nil { return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeSendingFails, err.Error()) } req.ContentLength = int64(len(s)) req.Header.Set(contentType, mimeTypeFormPost) resp, err := sender.Do(req.WithContext(ctx)) if err != nil { return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeSendingFails, err.Error()) } defer resp.Body.Close() rb, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeHandlingFails, err.Error()) } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeHandlingFails, errStatusNotOK) } if len(strings.Trim(string(rb), " ")) == 0 { return nil, ErrDeviceCodeEmpty } var code DeviceCode err = json.Unmarshal(rb, &code) if err != nil { return nil, fmt.Errorf("%s %s: %s", logPrefix, errCodeHandlingFails, err.Error()) } code.ClientID = clientID code.Resource = resource code.OAuthConfig = oauthConfig return &code, nil } // CheckForUserCompletion takes a DeviceCode and checks with the Azure AD OAuth endpoint // to see if the device flow has: been completed, timed out, or otherwise failed // Deprecated: use CheckForUserCompletionWithContext() instead. func CheckForUserCompletion(sender Sender, code *DeviceCode) (*Token, error) { return CheckForUserCompletionWithContext(context.Background(), sender, code) } // CheckForUserCompletionWithContext takes a DeviceCode and checks with the Azure AD OAuth endpoint // to see if the device flow has: been completed, timed out, or otherwise failed func CheckForUserCompletionWithContext(ctx context.Context, sender Sender, code *DeviceCode) (*Token, error) { v := url.Values{ "client_id": []string{code.ClientID}, "code": []string{*code.DeviceCode}, "grant_type": []string{OAuthGrantTypeDeviceCode}, "resource": []string{code.Resource}, } s := v.Encode() body := ioutil.NopCloser(strings.NewReader(s)) req, err := http.NewRequest(http.MethodPost, code.OAuthConfig.TokenEndpoint.String(), body) if err != nil { return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenSendingFails, err.Error()) } req.ContentLength = int64(len(s)) req.Header.Set(contentType, mimeTypeFormPost) resp, err := sender.Do(req.WithContext(ctx)) if err != nil { return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenSendingFails, err.Error()) } defer resp.Body.Close() rb, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenHandlingFails, err.Error()) } if resp.StatusCode != http.StatusOK && len(strings.Trim(string(rb), " ")) == 0 { return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenHandlingFails, errStatusNotOK) } if len(strings.Trim(string(rb), " ")) == 0 { return nil, ErrOAuthTokenEmpty } var token deviceToken err = json.Unmarshal(rb, &token) if err != nil { return nil, fmt.Errorf("%s %s: %s", logPrefix, errTokenHandlingFails, err.Error()) } if token.Error == nil { return &token.Token, nil } switch *token.Error { case "authorization_pending": return nil, ErrDeviceAuthorizationPending case "slow_down": return nil, ErrDeviceSlowDown case "access_denied": return nil, ErrDeviceAccessDenied case "code_expired": return nil, ErrDeviceCodeExpired default: return nil, ErrDeviceGeneric } } // WaitForUserCompletion calls CheckForUserCompletion repeatedly until a token is granted or an error state occurs. // This prevents the user from looping and checking against 'ErrDeviceAuthorizationPending'. // Deprecated: use WaitForUserCompletionWithContext() instead. func WaitForUserCompletion(sender Sender, code *DeviceCode) (*Token, error) { return WaitForUserCompletionWithContext(context.Background(), sender, code) } // WaitForUserCompletionWithContext calls CheckForUserCompletion repeatedly until a token is granted or an error // state occurs. This prevents the user from looping and checking against 'ErrDeviceAuthorizationPending'. func WaitForUserCompletionWithContext(ctx context.Context, sender Sender, code *DeviceCode) (*Token, error) { intervalDuration := time.Duration(*code.Interval) * time.Second waitDuration := intervalDuration for { token, err := CheckForUserCompletionWithContext(ctx, sender, code) if err == nil { return token, nil } switch err { case ErrDeviceSlowDown: waitDuration += waitDuration case ErrDeviceAuthorizationPending: // noop default: // everything else is "fatal" to us return nil, err } if waitDuration > (intervalDuration * 3) { return nil, fmt.Errorf("%s Error waiting for user to complete device flow. Server told us to slow_down too much", logPrefix) } select { case <-time.After(waitDuration): // noop case <-ctx.Done(): return nil, ctx.Err() } } } golang-github-azure-go-autorest-14.1.1/autorest/adal/devicetoken_test.go000066400000000000000000000255711367372352400264030ustar00rootroot00000000000000package adal // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "context" "encoding/json" "fmt" "net/http" "strings" "testing" "time" "github.com/Azure/go-autorest/autorest/mocks" ) const ( TestResource = "SomeResource" TestClientID = "SomeClientID" TestTenantID = "SomeTenantID" TestActiveDirectoryEndpoint = "https://login.test.com/" ) var ( testOAuthConfig, _ = NewOAuthConfig(TestActiveDirectoryEndpoint, TestTenantID) TestOAuthConfig = *testOAuthConfig ) const MockDeviceCodeResponse = ` { "device_code": "10000-40-1234567890", "user_code": "ABCDEF", "verification_url": "http://aka.ms/deviceauth", "expires_in": "900", "interval": "0" } ` const MockDeviceTokenResponse = `{ "access_token": "accessToken", "refresh_token": "refreshToken", "expires_in": "1000", "expires_on": "2000", "not_before": "3000", "resource": "resource", "token_type": "type" } ` func TestDeviceCodeIncludesResource(t *testing.T) { sender := mocks.NewSender() sender.AppendResponse(mocks.NewResponseWithContent(MockDeviceCodeResponse)) code, err := InitiateDeviceAuth(sender, TestOAuthConfig, TestClientID, TestResource) if err != nil { t.Fatalf("adal: unexpected error initiating device auth") } if code.Resource != TestResource { t.Fatalf("adal: InitiateDeviceAuth failed to stash the resource in the DeviceCode struct") } } func TestDeviceCodeReturnsErrorIfSendingFails(t *testing.T) { sender := mocks.NewSender() sender.SetError(fmt.Errorf("this is an error")) _, err := InitiateDeviceAuth(sender, TestOAuthConfig, TestClientID, TestResource) if err == nil || !strings.Contains(err.Error(), errCodeSendingFails) { t.Fatalf("adal: failed to get correct error expected(%s) actual(%s)", errCodeSendingFails, err.Error()) } } func TestDeviceCodeReturnsErrorIfBadRequest(t *testing.T) { sender := mocks.NewSender() body := mocks.NewBody("doesn't matter") sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusBadRequest, "Bad Request")) _, err := InitiateDeviceAuth(sender, TestOAuthConfig, TestClientID, TestResource) if err == nil || !strings.Contains(err.Error(), errCodeHandlingFails) { t.Fatalf("adal: failed to get correct error expected(%s) actual(%s)", errCodeHandlingFails, err.Error()) } if body.IsOpen() { t.Fatalf("response body was left open!") } } func TestDeviceCodeReturnsErrorIfCannotDeserializeDeviceCode(t *testing.T) { gibberishJSON := strings.Replace(MockDeviceCodeResponse, "expires_in", "\":, :gibberish", -1) sender := mocks.NewSender() body := mocks.NewBody(gibberishJSON) sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK")) _, err := InitiateDeviceAuth(sender, TestOAuthConfig, TestClientID, TestResource) if err == nil || !strings.Contains(err.Error(), errCodeHandlingFails) { t.Fatalf("adal: failed to get correct error expected(%s) actual(%s)", errCodeHandlingFails, err.Error()) } if body.IsOpen() { t.Fatalf("response body was left open!") } } func TestDeviceCodeReturnsErrorIfEmptyDeviceCode(t *testing.T) { sender := mocks.NewSender() body := mocks.NewBody("") sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK")) _, err := InitiateDeviceAuth(sender, TestOAuthConfig, TestClientID, TestResource) if err != ErrDeviceCodeEmpty { t.Fatalf("adal: failed to get correct error expected(%s) actual(%s)", ErrDeviceCodeEmpty, err.Error()) } if body.IsOpen() { t.Fatalf("response body was left open!") } } func deviceCode() *DeviceCode { var deviceCode DeviceCode _ = json.Unmarshal([]byte(MockDeviceCodeResponse), &deviceCode) deviceCode.Resource = TestResource deviceCode.ClientID = TestClientID return &deviceCode } func TestDeviceTokenReturns(t *testing.T) { sender := mocks.NewSender() body := mocks.NewBody(MockDeviceTokenResponse) sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK")) _, err := WaitForUserCompletion(sender, deviceCode()) if err != nil { t.Fatalf("adal: got error unexpectedly") } if body.IsOpen() { t.Fatalf("response body was left open!") } } func TestDeviceTokenReturnsErrorIfSendingFails(t *testing.T) { sender := mocks.NewSender() sender.SetError(fmt.Errorf("this is an error")) _, err := WaitForUserCompletion(sender, deviceCode()) if err == nil || !strings.Contains(err.Error(), errTokenSendingFails) { t.Fatalf("adal: failed to get correct error expected(%s) actual(%s)", errTokenSendingFails, err.Error()) } } func TestDeviceTokenReturnsErrorIfServerError(t *testing.T) { sender := mocks.NewSender() body := mocks.NewBody("") sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusInternalServerError, "Internal Server Error")) _, err := WaitForUserCompletion(sender, deviceCode()) if err == nil || !strings.Contains(err.Error(), errTokenHandlingFails) { t.Fatalf("adal: failed to get correct error expected(%s) actual(%s)", errTokenHandlingFails, err.Error()) } if body.IsOpen() { t.Fatalf("response body was left open!") } } func TestDeviceTokenReturnsErrorIfCannotDeserializeDeviceToken(t *testing.T) { gibberishJSON := strings.Replace(MockDeviceTokenResponse, "expires_in", ";:\"gibberish", -1) sender := mocks.NewSender() body := mocks.NewBody(gibberishJSON) sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK")) _, err := WaitForUserCompletion(sender, deviceCode()) if err == nil || !strings.Contains(err.Error(), errTokenHandlingFails) { t.Fatalf("adal: failed to get correct error expected(%s) actual(%s)", errTokenHandlingFails, err.Error()) } if body.IsOpen() { t.Fatalf("response body was left open!") } } func errorDeviceTokenResponse(message string) string { return `{ "error": "` + message + `" }` } func TestDeviceTokenReturnsErrorIfAuthorizationPending(t *testing.T) { sender := mocks.NewSender() body := mocks.NewBody(errorDeviceTokenResponse("authorization_pending")) sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusBadRequest, "Bad Request")) _, err := CheckForUserCompletion(sender, deviceCode()) if err != ErrDeviceAuthorizationPending { t.Fatalf("!!!") } if body.IsOpen() { t.Fatalf("response body was left open!") } } func TestDeviceTokenReturnsErrorIfSlowDown(t *testing.T) { sender := mocks.NewSender() body := mocks.NewBody(errorDeviceTokenResponse("slow_down")) sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusBadRequest, "Bad Request")) _, err := CheckForUserCompletion(sender, deviceCode()) if err != ErrDeviceSlowDown { t.Fatalf("!!!") } if body.IsOpen() { t.Fatalf("response body was left open!") } } type deviceTokenSender struct { errorString string attempts int } func newDeviceTokenSender(deviceErrorString string) *deviceTokenSender { return &deviceTokenSender{errorString: deviceErrorString, attempts: 0} } func (s *deviceTokenSender) Do(req *http.Request) (*http.Response, error) { var resp *http.Response if s.attempts < 1 { s.attempts++ resp = mocks.NewResponseWithContent(errorDeviceTokenResponse(s.errorString)) } else { resp = mocks.NewResponseWithContent(MockDeviceTokenResponse) } return resp, nil } // since the above only exercise CheckForUserCompletion, we repeat the test here, // but with the intent of showing that WaitForUserCompletion loops properly. func TestDeviceTokenSucceedsWithIntermediateAuthPending(t *testing.T) { sender := newDeviceTokenSender("authorization_pending") _, err := WaitForUserCompletion(sender, deviceCode()) if err != nil { t.Fatalf("unexpected error occurred") } } // same as above but with SlowDown now func TestDeviceTokenSucceedsWithIntermediateSlowDown(t *testing.T) { sender := newDeviceTokenSender("slow_down") _, err := WaitForUserCompletion(sender, deviceCode()) if err != nil { t.Fatalf("unexpected error occurred") } } func TestDeviceTokenReturnsErrorIfAccessDenied(t *testing.T) { sender := mocks.NewSender() body := mocks.NewBody(errorDeviceTokenResponse("access_denied")) sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusBadRequest, "Bad Request")) _, err := WaitForUserCompletion(sender, deviceCode()) if err != ErrDeviceAccessDenied { t.Fatalf("adal: got wrong error expected(%s) actual(%s)", ErrDeviceAccessDenied.Error(), err.Error()) } if body.IsOpen() { t.Fatalf("response body was left open!") } } func TestDeviceTokenReturnsErrorIfCodeExpired(t *testing.T) { sender := mocks.NewSender() body := mocks.NewBody(errorDeviceTokenResponse("code_expired")) sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusBadRequest, "Bad Request")) _, err := WaitForUserCompletion(sender, deviceCode()) if err != ErrDeviceCodeExpired { t.Fatalf("adal: got wrong error expected(%s) actual(%s)", ErrDeviceCodeExpired.Error(), err.Error()) } if body.IsOpen() { t.Fatalf("response body was left open!") } } func TestDeviceTokenReturnsErrorForUnknownError(t *testing.T) { sender := mocks.NewSender() body := mocks.NewBody(errorDeviceTokenResponse("unknown_error")) sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusBadRequest, "Bad Request")) _, err := WaitForUserCompletion(sender, deviceCode()) if err == nil { t.Fatalf("failed to get error") } if err != ErrDeviceGeneric { t.Fatalf("adal: got wrong error expected(%s) actual(%s)", ErrDeviceGeneric.Error(), err.Error()) } if body.IsOpen() { t.Fatalf("response body was left open!") } } func TestDeviceTokenReturnsErrorIfTokenEmptyAndStatusOK(t *testing.T) { sender := mocks.NewSender() body := mocks.NewBody("") sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK")) _, err := WaitForUserCompletion(sender, deviceCode()) if err != ErrOAuthTokenEmpty { t.Fatalf("adal: got wrong error expected(%s) actual(%s)", ErrOAuthTokenEmpty.Error(), err.Error()) } if body.IsOpen() { t.Fatalf("response body was left open!") } } func TestWaitForUserCompletionWithContext(t *testing.T) { sender := SenderFunc(func(*http.Request) (*http.Response, error) { return mocks.NewResponseWithContent(`{"error":"authorization_pending"}`), nil }) ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() _, err := WaitForUserCompletionWithContext(ctx, sender, deviceCode()) if err != context.DeadlineExceeded { t.Fatalf("adal: got wrong error expected(%s) actual(%s)", context.DeadlineExceeded.Error(), err.Error()) } } golang-github-azure-go-autorest-14.1.1/autorest/adal/go.mod000066400000000000000000000005631367372352400236150ustar00rootroot00000000000000module github.com/Azure/go-autorest/autorest/adal go 1.12 require ( github.com/Azure/go-autorest/autorest v0.9.0 github.com/Azure/go-autorest/autorest/date v0.2.0 github.com/Azure/go-autorest/autorest/mocks v0.3.0 github.com/Azure/go-autorest/tracing v0.5.0 github.com/dgrijalva/jwt-go v3.2.0+incompatible golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 ) golang-github-azure-go-autorest-14.1.1/autorest/adal/go.sum000066400000000000000000000054111367372352400236370ustar00rootroot00000000000000github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSWlm5Ew6bxipnr/tbM= github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= github.com/Azure/go-autorest/autorest/mocks v0.1.0 h1:Kx+AUU2Te+A3JIyYn6Dfs+cFgx5XorQKuIXrZGoq/SI= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0 h1:Ww5g4zThfD/6cLb4z6xxgeyDa7QDkizMkJKe0ysZXp0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.3.0 h1:qJumjCaCudz+OcqE9/XtEPfvtOjOmKaui4EOpFI6zZc= github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang-github-azure-go-autorest-14.1.1/autorest/adal/go_mod_tidy_hack.go000066400000000000000000000017161367372352400263220ustar00rootroot00000000000000// +build modhack package adal // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // This file, and the github.com/Azure/go-autorest/autorest import, won't actually become part of // the resultant binary. // Necessary for safely adding multi-module repo. // See: https://github.com/golang/go/wiki/Modules#is-it-possible-to-add-a-module-to-a-multi-module-repository import _ "github.com/Azure/go-autorest/autorest" golang-github-azure-go-autorest-14.1.1/autorest/adal/persist.go000066400000000000000000000046331367372352400245310ustar00rootroot00000000000000package adal // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "encoding/json" "fmt" "io/ioutil" "os" "path/filepath" ) // LoadToken restores a Token object from a file located at 'path'. func LoadToken(path string) (*Token, error) { file, err := os.Open(path) if err != nil { return nil, fmt.Errorf("failed to open file (%s) while loading token: %v", path, err) } defer file.Close() var token Token dec := json.NewDecoder(file) if err = dec.Decode(&token); err != nil { return nil, fmt.Errorf("failed to decode contents of file (%s) into Token representation: %v", path, err) } return &token, nil } // SaveToken persists an oauth token at the given location on disk. // It moves the new file into place so it can safely be used to replace an existing file // that maybe accessed by multiple processes. func SaveToken(path string, mode os.FileMode, token Token) error { dir := filepath.Dir(path) err := os.MkdirAll(dir, os.ModePerm) if err != nil { return fmt.Errorf("failed to create directory (%s) to store token in: %v", dir, err) } newFile, err := ioutil.TempFile(dir, "token") if err != nil { return fmt.Errorf("failed to create the temp file to write the token: %v", err) } tempPath := newFile.Name() if err := json.NewEncoder(newFile).Encode(token); err != nil { return fmt.Errorf("failed to encode token to file (%s) while saving token: %v", tempPath, err) } if err := newFile.Close(); err != nil { return fmt.Errorf("failed to close temp file %s: %v", tempPath, err) } // Atomic replace to avoid multi-writer file corruptions if err := os.Rename(tempPath, path); err != nil { return fmt.Errorf("failed to move temporary token to desired output location. src=%s dst=%s: %v", tempPath, path, err) } if err := os.Chmod(path, mode); err != nil { return fmt.Errorf("failed to chmod the token file %s: %v", path, err) } return nil } golang-github-azure-go-autorest-14.1.1/autorest/adal/persist_test.go000066400000000000000000000120461367372352400255650ustar00rootroot00000000000000package adal // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "encoding/json" "io/ioutil" "os" "path" "reflect" "runtime" "strings" "testing" ) const MockTokenJSON string = `{ "access_token": "accessToken", "refresh_token": "refreshToken", "expires_in": "1000", "expires_on": "2000", "not_before": "3000", "resource": "resource", "token_type": "type" }` var TestToken = Token{ AccessToken: "accessToken", RefreshToken: "refreshToken", ExpiresIn: "1000", ExpiresOn: "2000", NotBefore: "3000", Resource: "resource", Type: "type", } func writeTestTokenFile(t *testing.T, suffix string, contents string) *os.File { f, err := ioutil.TempFile(os.TempDir(), suffix) if err != nil { t.Fatalf("azure: unexpected error when creating temp file: %v", err) } defer f.Close() _, err = f.Write([]byte(contents)) if err != nil { t.Fatalf("azure: unexpected error when writing temp test file: %v", err) } return f } func TestLoadToken(t *testing.T) { f := writeTestTokenFile(t, "testloadtoken", MockTokenJSON) defer os.Remove(f.Name()) expectedToken := TestToken actualToken, err := LoadToken(f.Name()) if err != nil { t.Fatalf("azure: unexpected error loading token from file: %v", err) } if *actualToken != expectedToken { t.Fatalf("azure: failed to decode properly expected(%v) actual(%v)", expectedToken, *actualToken) } // test that LoadToken closes the file properly err = SaveToken(f.Name(), 0600, *actualToken) if err != nil { t.Fatalf("azure: could not save token after LoadToken: %v", err) } } func TestLoadTokenFailsBadPath(t *testing.T) { _, err := LoadToken("/tmp/this_file_should_never_exist_really") expectedSubstring := "failed to open file" if err == nil || !strings.Contains(err.Error(), expectedSubstring) { t.Fatalf("azure: failed to get correct error expected(%s) actual(%s)", expectedSubstring, err.Error()) } } func TestLoadTokenFailsBadJson(t *testing.T) { gibberishJSON := strings.Replace(MockTokenJSON, "expires_on", ";:\"gibberish", -1) f := writeTestTokenFile(t, "testloadtokenfailsbadjson", gibberishJSON) defer os.Remove(f.Name()) _, err := LoadToken(f.Name()) expectedSubstring := "failed to decode contents of file" if err == nil || !strings.Contains(err.Error(), expectedSubstring) { t.Fatalf("azure: failed to get correct error expected(%s) actual(%s)", expectedSubstring, err.Error()) } } func token() *Token { var token Token json.Unmarshal([]byte(MockTokenJSON), &token) return &token } func TestSaveToken(t *testing.T) { f, err := ioutil.TempFile("", "testloadtoken") if err != nil { t.Fatalf("azure: unexpected error when creating temp file: %v", err) } defer os.Remove(f.Name()) f.Close() mode := os.ModePerm & 0642 err = SaveToken(f.Name(), mode, *token()) if err != nil { t.Fatalf("azure: unexpected error saving token to file: %v", err) } fi, err := os.Stat(f.Name()) // open a new stat as held ones are not fresh if err != nil { t.Fatalf("azure: stat failed: %v", err) } if runtime.GOOS != "windows" { // permissions don't work on Windows if perm := fi.Mode().Perm(); perm != mode { t.Fatalf("azure: wrong file perm. got:%s; expected:%s file :%s", perm, mode, f.Name()) } } var actualToken Token var expectedToken Token json.Unmarshal([]byte(MockTokenJSON), &expectedToken) contents, err := ioutil.ReadFile(f.Name()) if err != nil { t.Fatal("!!") } json.Unmarshal(contents, &actualToken) if !reflect.DeepEqual(actualToken, expectedToken) { t.Fatal("azure: token was not serialized correctly") } } func TestSaveTokenFailsNoPermission(t *testing.T) { pathWhereWeShouldntHavePermission := "/usr/thiswontwork/atall" if runtime.GOOS == "windows" { pathWhereWeShouldntHavePermission = path.Join(os.Getenv("windir"), "system32\\mytokendir\\mytoken") } err := SaveToken(pathWhereWeShouldntHavePermission, 0644, *token()) expectedSubstring := "failed to create directory" if err == nil || !strings.Contains(err.Error(), expectedSubstring) { t.Fatalf("azure: failed to get correct error expected(%s) actual(%v)", expectedSubstring, err) } } func TestSaveTokenFailsCantCreate(t *testing.T) { tokenPath := "/usr/thiswontwork" if runtime.GOOS == "windows" { tokenPath = path.Join(os.Getenv("windir"), "system32") } err := SaveToken(tokenPath, 0644, *token()) expectedSubstring := "failed to create the temp file to write the token" if err == nil || !strings.Contains(err.Error(), expectedSubstring) { t.Fatalf("azure: failed to get correct error expected(%s) actual(%v)", expectedSubstring, err) } } golang-github-azure-go-autorest-14.1.1/autorest/adal/sender.go000066400000000000000000000064711367372352400243220ustar00rootroot00000000000000package adal // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "crypto/tls" "net/http" "net/http/cookiejar" "sync" "github.com/Azure/go-autorest/tracing" ) const ( contentType = "Content-Type" mimeTypeFormPost = "application/x-www-form-urlencoded" ) var defaultSender Sender var defaultSenderInit = &sync.Once{} // Sender is the interface that wraps the Do method to send HTTP requests. // // The standard http.Client conforms to this interface. type Sender interface { Do(*http.Request) (*http.Response, error) } // SenderFunc is a method that implements the Sender interface. type SenderFunc func(*http.Request) (*http.Response, error) // Do implements the Sender interface on SenderFunc. func (sf SenderFunc) Do(r *http.Request) (*http.Response, error) { return sf(r) } // SendDecorator takes and possibly decorates, by wrapping, a Sender. Decorators may affect the // http.Request and pass it along or, first, pass the http.Request along then react to the // http.Response result. type SendDecorator func(Sender) Sender // CreateSender creates, decorates, and returns, as a Sender, the default http.Client. func CreateSender(decorators ...SendDecorator) Sender { return DecorateSender(sender(), decorators...) } // DecorateSender accepts a Sender and a, possibly empty, set of SendDecorators, which is applies to // the Sender. Decorators are applied in the order received, but their affect upon the request // depends on whether they are a pre-decorator (change the http.Request and then pass it along) or a // post-decorator (pass the http.Request along and react to the results in http.Response). func DecorateSender(s Sender, decorators ...SendDecorator) Sender { for _, decorate := range decorators { s = decorate(s) } return s } func sender() Sender { // note that we can't init defaultSender in init() since it will // execute before calling code has had a chance to enable tracing defaultSenderInit.Do(func() { // Use behaviour compatible with DefaultTransport, but require TLS minimum version. defaultTransport := http.DefaultTransport.(*http.Transport) transport := &http.Transport{ Proxy: defaultTransport.Proxy, DialContext: defaultTransport.DialContext, MaxIdleConns: defaultTransport.MaxIdleConns, IdleConnTimeout: defaultTransport.IdleConnTimeout, TLSHandshakeTimeout: defaultTransport.TLSHandshakeTimeout, ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout, TLSClientConfig: &tls.Config{ MinVersion: tls.VersionTLS12, }, } var roundTripper http.RoundTripper = transport if tracing.IsEnabled() { roundTripper = tracing.NewTransport(transport) } j, _ := cookiejar.New(nil) defaultSender = &http.Client{Jar: j, Transport: roundTripper} }) return defaultSender } golang-github-azure-go-autorest-14.1.1/autorest/adal/token.go000066400000000000000000001135521367372352400241610ustar00rootroot00000000000000package adal // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "context" "crypto/rand" "crypto/rsa" "crypto/sha1" "crypto/x509" "encoding/base64" "encoding/json" "errors" "fmt" "io" "io/ioutil" "math" "net/http" "net/url" "os" "strings" "sync" "time" "github.com/Azure/go-autorest/autorest/date" "github.com/dgrijalva/jwt-go" ) const ( defaultRefresh = 5 * time.Minute // OAuthGrantTypeDeviceCode is the "grant_type" identifier used in device flow OAuthGrantTypeDeviceCode = "device_code" // OAuthGrantTypeClientCredentials is the "grant_type" identifier used in credential flows OAuthGrantTypeClientCredentials = "client_credentials" // OAuthGrantTypeUserPass is the "grant_type" identifier used in username and password auth flows OAuthGrantTypeUserPass = "password" // OAuthGrantTypeRefreshToken is the "grant_type" identifier used in refresh token flows OAuthGrantTypeRefreshToken = "refresh_token" // OAuthGrantTypeAuthorizationCode is the "grant_type" identifier used in authorization code flows OAuthGrantTypeAuthorizationCode = "authorization_code" // metadataHeader is the header required by MSI extension metadataHeader = "Metadata" // msiEndpoint is the well known endpoint for getting MSI authentications tokens msiEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token" // the default number of attempts to refresh an MSI authentication token defaultMaxMSIRefreshAttempts = 5 // asMSIEndpointEnv is the environment variable used to store the endpoint on App Service and Functions asMSIEndpointEnv = "MSI_ENDPOINT" // asMSISecretEnv is the environment variable used to store the request secret on App Service and Functions asMSISecretEnv = "MSI_SECRET" ) // OAuthTokenProvider is an interface which should be implemented by an access token retriever type OAuthTokenProvider interface { OAuthToken() string } // MultitenantOAuthTokenProvider provides tokens used for multi-tenant authorization. type MultitenantOAuthTokenProvider interface { PrimaryOAuthToken() string AuxiliaryOAuthTokens() []string } // TokenRefreshError is an interface used by errors returned during token refresh. type TokenRefreshError interface { error Response() *http.Response } // Refresher is an interface for token refresh functionality type Refresher interface { Refresh() error RefreshExchange(resource string) error EnsureFresh() error } // RefresherWithContext is an interface for token refresh functionality type RefresherWithContext interface { RefreshWithContext(ctx context.Context) error RefreshExchangeWithContext(ctx context.Context, resource string) error EnsureFreshWithContext(ctx context.Context) error } // TokenRefreshCallback is the type representing callbacks that will be called after // a successful token refresh type TokenRefreshCallback func(Token) error // TokenRefresh is a type representing a custom callback to refresh a token type TokenRefresh func(ctx context.Context, resource string) (*Token, error) // Token encapsulates the access token used to authorize Azure requests. // https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-oauth2-client-creds-grant-flow#service-to-service-access-token-response type Token struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` ExpiresIn json.Number `json:"expires_in"` ExpiresOn json.Number `json:"expires_on"` NotBefore json.Number `json:"not_before"` Resource string `json:"resource"` Type string `json:"token_type"` } func newToken() Token { return Token{ ExpiresIn: "0", ExpiresOn: "0", NotBefore: "0", } } // IsZero returns true if the token object is zero-initialized. func (t Token) IsZero() bool { return t == Token{} } // Expires returns the time.Time when the Token expires. func (t Token) Expires() time.Time { s, err := t.ExpiresOn.Float64() if err != nil { s = -3600 } expiration := date.NewUnixTimeFromSeconds(s) return time.Time(expiration).UTC() } // IsExpired returns true if the Token is expired, false otherwise. func (t Token) IsExpired() bool { return t.WillExpireIn(0) } // WillExpireIn returns true if the Token will expire after the passed time.Duration interval // from now, false otherwise. func (t Token) WillExpireIn(d time.Duration) bool { return !t.Expires().After(time.Now().Add(d)) } //OAuthToken return the current access token func (t *Token) OAuthToken() string { return t.AccessToken } // ServicePrincipalSecret is an interface that allows various secret mechanism to fill the form // that is submitted when acquiring an oAuth token. type ServicePrincipalSecret interface { SetAuthenticationValues(spt *ServicePrincipalToken, values *url.Values) error } // ServicePrincipalNoSecret represents a secret type that contains no secret // meaning it is not valid for fetching a fresh token. This is used by Manual type ServicePrincipalNoSecret struct { } // SetAuthenticationValues is a method of the interface ServicePrincipalSecret // It only returns an error for the ServicePrincipalNoSecret type func (noSecret *ServicePrincipalNoSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error { return fmt.Errorf("Manually created ServicePrincipalToken does not contain secret material to retrieve a new access token") } // MarshalJSON implements the json.Marshaler interface. func (noSecret ServicePrincipalNoSecret) MarshalJSON() ([]byte, error) { type tokenType struct { Type string `json:"type"` } return json.Marshal(tokenType{ Type: "ServicePrincipalNoSecret", }) } // ServicePrincipalTokenSecret implements ServicePrincipalSecret for client_secret type authorization. type ServicePrincipalTokenSecret struct { ClientSecret string `json:"value"` } // SetAuthenticationValues is a method of the interface ServicePrincipalSecret. // It will populate the form submitted during oAuth Token Acquisition using the client_secret. func (tokenSecret *ServicePrincipalTokenSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error { v.Set("client_secret", tokenSecret.ClientSecret) return nil } // MarshalJSON implements the json.Marshaler interface. func (tokenSecret ServicePrincipalTokenSecret) MarshalJSON() ([]byte, error) { type tokenType struct { Type string `json:"type"` Value string `json:"value"` } return json.Marshal(tokenType{ Type: "ServicePrincipalTokenSecret", Value: tokenSecret.ClientSecret, }) } // ServicePrincipalCertificateSecret implements ServicePrincipalSecret for generic RSA cert auth with signed JWTs. type ServicePrincipalCertificateSecret struct { Certificate *x509.Certificate PrivateKey *rsa.PrivateKey } // SignJwt returns the JWT signed with the certificate's private key. func (secret *ServicePrincipalCertificateSecret) SignJwt(spt *ServicePrincipalToken) (string, error) { hasher := sha1.New() _, err := hasher.Write(secret.Certificate.Raw) if err != nil { return "", err } thumbprint := base64.URLEncoding.EncodeToString(hasher.Sum(nil)) // The jti (JWT ID) claim provides a unique identifier for the JWT. jti := make([]byte, 20) _, err = rand.Read(jti) if err != nil { return "", err } token := jwt.New(jwt.SigningMethodRS256) token.Header["x5t"] = thumbprint x5c := []string{base64.StdEncoding.EncodeToString(secret.Certificate.Raw)} token.Header["x5c"] = x5c token.Claims = jwt.MapClaims{ "aud": spt.inner.OauthConfig.TokenEndpoint.String(), "iss": spt.inner.ClientID, "sub": spt.inner.ClientID, "jti": base64.URLEncoding.EncodeToString(jti), "nbf": time.Now().Unix(), "exp": time.Now().Add(24 * time.Hour).Unix(), } signedString, err := token.SignedString(secret.PrivateKey) return signedString, err } // SetAuthenticationValues is a method of the interface ServicePrincipalSecret. // It will populate the form submitted during oAuth Token Acquisition using a JWT signed with a certificate. func (secret *ServicePrincipalCertificateSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error { jwt, err := secret.SignJwt(spt) if err != nil { return err } v.Set("client_assertion", jwt) v.Set("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer") return nil } // MarshalJSON implements the json.Marshaler interface. func (secret ServicePrincipalCertificateSecret) MarshalJSON() ([]byte, error) { return nil, errors.New("marshalling ServicePrincipalCertificateSecret is not supported") } // ServicePrincipalMSISecret implements ServicePrincipalSecret for machines running the MSI Extension. type ServicePrincipalMSISecret struct { } // SetAuthenticationValues is a method of the interface ServicePrincipalSecret. func (msiSecret *ServicePrincipalMSISecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error { return nil } // MarshalJSON implements the json.Marshaler interface. func (msiSecret ServicePrincipalMSISecret) MarshalJSON() ([]byte, error) { return nil, errors.New("marshalling ServicePrincipalMSISecret is not supported") } // ServicePrincipalUsernamePasswordSecret implements ServicePrincipalSecret for username and password auth. type ServicePrincipalUsernamePasswordSecret struct { Username string `json:"username"` Password string `json:"password"` } // SetAuthenticationValues is a method of the interface ServicePrincipalSecret. func (secret *ServicePrincipalUsernamePasswordSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error { v.Set("username", secret.Username) v.Set("password", secret.Password) return nil } // MarshalJSON implements the json.Marshaler interface. func (secret ServicePrincipalUsernamePasswordSecret) MarshalJSON() ([]byte, error) { type tokenType struct { Type string `json:"type"` Username string `json:"username"` Password string `json:"password"` } return json.Marshal(tokenType{ Type: "ServicePrincipalUsernamePasswordSecret", Username: secret.Username, Password: secret.Password, }) } // ServicePrincipalAuthorizationCodeSecret implements ServicePrincipalSecret for authorization code auth. type ServicePrincipalAuthorizationCodeSecret struct { ClientSecret string `json:"value"` AuthorizationCode string `json:"authCode"` RedirectURI string `json:"redirect"` } // SetAuthenticationValues is a method of the interface ServicePrincipalSecret. func (secret *ServicePrincipalAuthorizationCodeSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error { v.Set("code", secret.AuthorizationCode) v.Set("client_secret", secret.ClientSecret) v.Set("redirect_uri", secret.RedirectURI) return nil } // MarshalJSON implements the json.Marshaler interface. func (secret ServicePrincipalAuthorizationCodeSecret) MarshalJSON() ([]byte, error) { type tokenType struct { Type string `json:"type"` Value string `json:"value"` AuthCode string `json:"authCode"` Redirect string `json:"redirect"` } return json.Marshal(tokenType{ Type: "ServicePrincipalAuthorizationCodeSecret", Value: secret.ClientSecret, AuthCode: secret.AuthorizationCode, Redirect: secret.RedirectURI, }) } // ServicePrincipalToken encapsulates a Token created for a Service Principal. type ServicePrincipalToken struct { inner servicePrincipalToken refreshLock *sync.RWMutex sender Sender customRefreshFunc TokenRefresh refreshCallbacks []TokenRefreshCallback // MaxMSIRefreshAttempts is the maximum number of attempts to refresh an MSI token. MaxMSIRefreshAttempts int } // MarshalTokenJSON returns the marshalled inner token. func (spt ServicePrincipalToken) MarshalTokenJSON() ([]byte, error) { return json.Marshal(spt.inner.Token) } // SetRefreshCallbacks replaces any existing refresh callbacks with the specified callbacks. func (spt *ServicePrincipalToken) SetRefreshCallbacks(callbacks []TokenRefreshCallback) { spt.refreshCallbacks = callbacks } // SetCustomRefreshFunc sets a custom refresh function used to refresh the token. func (spt *ServicePrincipalToken) SetCustomRefreshFunc(customRefreshFunc TokenRefresh) { spt.customRefreshFunc = customRefreshFunc } // MarshalJSON implements the json.Marshaler interface. func (spt ServicePrincipalToken) MarshalJSON() ([]byte, error) { return json.Marshal(spt.inner) } // UnmarshalJSON implements the json.Unmarshaler interface. func (spt *ServicePrincipalToken) UnmarshalJSON(data []byte) error { // need to determine the token type raw := map[string]interface{}{} err := json.Unmarshal(data, &raw) if err != nil { return err } secret := raw["secret"].(map[string]interface{}) switch secret["type"] { case "ServicePrincipalNoSecret": spt.inner.Secret = &ServicePrincipalNoSecret{} case "ServicePrincipalTokenSecret": spt.inner.Secret = &ServicePrincipalTokenSecret{} case "ServicePrincipalCertificateSecret": return errors.New("unmarshalling ServicePrincipalCertificateSecret is not supported") case "ServicePrincipalMSISecret": return errors.New("unmarshalling ServicePrincipalMSISecret is not supported") case "ServicePrincipalUsernamePasswordSecret": spt.inner.Secret = &ServicePrincipalUsernamePasswordSecret{} case "ServicePrincipalAuthorizationCodeSecret": spt.inner.Secret = &ServicePrincipalAuthorizationCodeSecret{} default: return fmt.Errorf("unrecognized token type '%s'", secret["type"]) } err = json.Unmarshal(data, &spt.inner) if err != nil { return err } // Don't override the refreshLock or the sender if those have been already set. if spt.refreshLock == nil { spt.refreshLock = &sync.RWMutex{} } if spt.sender == nil { spt.sender = sender() } return nil } // internal type used for marshalling/unmarshalling type servicePrincipalToken struct { Token Token `json:"token"` Secret ServicePrincipalSecret `json:"secret"` OauthConfig OAuthConfig `json:"oauth"` ClientID string `json:"clientID"` Resource string `json:"resource"` AutoRefresh bool `json:"autoRefresh"` RefreshWithin time.Duration `json:"refreshWithin"` } func validateOAuthConfig(oac OAuthConfig) error { if oac.IsZero() { return fmt.Errorf("parameter 'oauthConfig' cannot be zero-initialized") } return nil } // NewServicePrincipalTokenWithSecret create a ServicePrincipalToken using the supplied ServicePrincipalSecret implementation. func NewServicePrincipalTokenWithSecret(oauthConfig OAuthConfig, id string, resource string, secret ServicePrincipalSecret, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { if err := validateOAuthConfig(oauthConfig); err != nil { return nil, err } if err := validateStringParam(id, "id"); err != nil { return nil, err } if err := validateStringParam(resource, "resource"); err != nil { return nil, err } if secret == nil { return nil, fmt.Errorf("parameter 'secret' cannot be nil") } spt := &ServicePrincipalToken{ inner: servicePrincipalToken{ Token: newToken(), OauthConfig: oauthConfig, Secret: secret, ClientID: id, Resource: resource, AutoRefresh: true, RefreshWithin: defaultRefresh, }, refreshLock: &sync.RWMutex{}, sender: sender(), refreshCallbacks: callbacks, } return spt, nil } // NewServicePrincipalTokenFromManualToken creates a ServicePrincipalToken using the supplied token func NewServicePrincipalTokenFromManualToken(oauthConfig OAuthConfig, clientID string, resource string, token Token, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { if err := validateOAuthConfig(oauthConfig); err != nil { return nil, err } if err := validateStringParam(clientID, "clientID"); err != nil { return nil, err } if err := validateStringParam(resource, "resource"); err != nil { return nil, err } if token.IsZero() { return nil, fmt.Errorf("parameter 'token' cannot be zero-initialized") } spt, err := NewServicePrincipalTokenWithSecret( oauthConfig, clientID, resource, &ServicePrincipalNoSecret{}, callbacks...) if err != nil { return nil, err } spt.inner.Token = token return spt, nil } // NewServicePrincipalTokenFromManualTokenSecret creates a ServicePrincipalToken using the supplied token and secret func NewServicePrincipalTokenFromManualTokenSecret(oauthConfig OAuthConfig, clientID string, resource string, token Token, secret ServicePrincipalSecret, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { if err := validateOAuthConfig(oauthConfig); err != nil { return nil, err } if err := validateStringParam(clientID, "clientID"); err != nil { return nil, err } if err := validateStringParam(resource, "resource"); err != nil { return nil, err } if secret == nil { return nil, fmt.Errorf("parameter 'secret' cannot be nil") } if token.IsZero() { return nil, fmt.Errorf("parameter 'token' cannot be zero-initialized") } spt, err := NewServicePrincipalTokenWithSecret( oauthConfig, clientID, resource, secret, callbacks...) if err != nil { return nil, err } spt.inner.Token = token return spt, nil } // NewServicePrincipalToken creates a ServicePrincipalToken from the supplied Service Principal // credentials scoped to the named resource. func NewServicePrincipalToken(oauthConfig OAuthConfig, clientID string, secret string, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { if err := validateOAuthConfig(oauthConfig); err != nil { return nil, err } if err := validateStringParam(clientID, "clientID"); err != nil { return nil, err } if err := validateStringParam(secret, "secret"); err != nil { return nil, err } if err := validateStringParam(resource, "resource"); err != nil { return nil, err } return NewServicePrincipalTokenWithSecret( oauthConfig, clientID, resource, &ServicePrincipalTokenSecret{ ClientSecret: secret, }, callbacks..., ) } // NewServicePrincipalTokenFromCertificate creates a ServicePrincipalToken from the supplied pkcs12 bytes. func NewServicePrincipalTokenFromCertificate(oauthConfig OAuthConfig, clientID string, certificate *x509.Certificate, privateKey *rsa.PrivateKey, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { if err := validateOAuthConfig(oauthConfig); err != nil { return nil, err } if err := validateStringParam(clientID, "clientID"); err != nil { return nil, err } if err := validateStringParam(resource, "resource"); err != nil { return nil, err } if certificate == nil { return nil, fmt.Errorf("parameter 'certificate' cannot be nil") } if privateKey == nil { return nil, fmt.Errorf("parameter 'privateKey' cannot be nil") } return NewServicePrincipalTokenWithSecret( oauthConfig, clientID, resource, &ServicePrincipalCertificateSecret{ PrivateKey: privateKey, Certificate: certificate, }, callbacks..., ) } // NewServicePrincipalTokenFromUsernamePassword creates a ServicePrincipalToken from the username and password. func NewServicePrincipalTokenFromUsernamePassword(oauthConfig OAuthConfig, clientID string, username string, password string, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { if err := validateOAuthConfig(oauthConfig); err != nil { return nil, err } if err := validateStringParam(clientID, "clientID"); err != nil { return nil, err } if err := validateStringParam(username, "username"); err != nil { return nil, err } if err := validateStringParam(password, "password"); err != nil { return nil, err } if err := validateStringParam(resource, "resource"); err != nil { return nil, err } return NewServicePrincipalTokenWithSecret( oauthConfig, clientID, resource, &ServicePrincipalUsernamePasswordSecret{ Username: username, Password: password, }, callbacks..., ) } // NewServicePrincipalTokenFromAuthorizationCode creates a ServicePrincipalToken from the func NewServicePrincipalTokenFromAuthorizationCode(oauthConfig OAuthConfig, clientID string, clientSecret string, authorizationCode string, redirectURI string, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { if err := validateOAuthConfig(oauthConfig); err != nil { return nil, err } if err := validateStringParam(clientID, "clientID"); err != nil { return nil, err } if err := validateStringParam(clientSecret, "clientSecret"); err != nil { return nil, err } if err := validateStringParam(authorizationCode, "authorizationCode"); err != nil { return nil, err } if err := validateStringParam(redirectURI, "redirectURI"); err != nil { return nil, err } if err := validateStringParam(resource, "resource"); err != nil { return nil, err } return NewServicePrincipalTokenWithSecret( oauthConfig, clientID, resource, &ServicePrincipalAuthorizationCodeSecret{ ClientSecret: clientSecret, AuthorizationCode: authorizationCode, RedirectURI: redirectURI, }, callbacks..., ) } // GetMSIVMEndpoint gets the MSI endpoint on Virtual Machines. func GetMSIVMEndpoint() (string, error) { return msiEndpoint, nil } func isAppService() bool { _, asMSIEndpointEnvExists := os.LookupEnv(asMSIEndpointEnv) _, asMSISecretEnvExists := os.LookupEnv(asMSISecretEnv) return asMSIEndpointEnvExists && asMSISecretEnvExists } // GetMSIAppServiceEndpoint get the MSI endpoint for App Service and Functions func GetMSIAppServiceEndpoint() (string, error) { asMSIEndpoint, asMSIEndpointEnvExists := os.LookupEnv(asMSIEndpointEnv) if asMSIEndpointEnvExists { return asMSIEndpoint, nil } return "", errors.New("MSI endpoint not found") } // GetMSIEndpoint get the appropriate MSI endpoint depending on the runtime environment func GetMSIEndpoint() (string, error) { if isAppService() { return GetMSIAppServiceEndpoint() } return GetMSIVMEndpoint() } // NewServicePrincipalTokenFromMSI creates a ServicePrincipalToken via the MSI VM Extension. // It will use the system assigned identity when creating the token. func NewServicePrincipalTokenFromMSI(msiEndpoint, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { return newServicePrincipalTokenFromMSI(msiEndpoint, resource, nil, callbacks...) } // NewServicePrincipalTokenFromMSIWithUserAssignedID creates a ServicePrincipalToken via the MSI VM Extension. // It will use the specified user assigned identity when creating the token. func NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint, resource string, userAssignedID string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { return newServicePrincipalTokenFromMSI(msiEndpoint, resource, &userAssignedID, callbacks...) } func newServicePrincipalTokenFromMSI(msiEndpoint, resource string, userAssignedID *string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { if err := validateStringParam(msiEndpoint, "msiEndpoint"); err != nil { return nil, err } if err := validateStringParam(resource, "resource"); err != nil { return nil, err } if userAssignedID != nil { if err := validateStringParam(*userAssignedID, "userAssignedID"); err != nil { return nil, err } } // We set the oauth config token endpoint to be MSI's endpoint msiEndpointURL, err := url.Parse(msiEndpoint) if err != nil { return nil, err } v := url.Values{} v.Set("resource", resource) // App Service MSI currently only supports token API version 2017-09-01 if isAppService() { v.Set("api-version", "2017-09-01") } else { v.Set("api-version", "2018-02-01") } if userAssignedID != nil { v.Set("client_id", *userAssignedID) } msiEndpointURL.RawQuery = v.Encode() spt := &ServicePrincipalToken{ inner: servicePrincipalToken{ Token: newToken(), OauthConfig: OAuthConfig{ TokenEndpoint: *msiEndpointURL, }, Secret: &ServicePrincipalMSISecret{}, Resource: resource, AutoRefresh: true, RefreshWithin: defaultRefresh, }, refreshLock: &sync.RWMutex{}, sender: sender(), refreshCallbacks: callbacks, MaxMSIRefreshAttempts: defaultMaxMSIRefreshAttempts, } if userAssignedID != nil { spt.inner.ClientID = *userAssignedID } return spt, nil } // internal type that implements TokenRefreshError type tokenRefreshError struct { message string resp *http.Response } // Error implements the error interface which is part of the TokenRefreshError interface. func (tre tokenRefreshError) Error() string { return tre.message } // Response implements the TokenRefreshError interface, it returns the raw HTTP response from the refresh operation. func (tre tokenRefreshError) Response() *http.Response { return tre.resp } func newTokenRefreshError(message string, resp *http.Response) TokenRefreshError { return tokenRefreshError{message: message, resp: resp} } // EnsureFresh will refresh the token if it will expire within the refresh window (as set by // RefreshWithin) and autoRefresh flag is on. This method is safe for concurrent use. func (spt *ServicePrincipalToken) EnsureFresh() error { return spt.EnsureFreshWithContext(context.Background()) } // EnsureFreshWithContext will refresh the token if it will expire within the refresh window (as set by // RefreshWithin) and autoRefresh flag is on. This method is safe for concurrent use. func (spt *ServicePrincipalToken) EnsureFreshWithContext(ctx context.Context) error { // must take the read lock when initially checking the token's expiration if spt.inner.AutoRefresh && spt.Token().WillExpireIn(spt.inner.RefreshWithin) { // take the write lock then check again to see if the token was already refreshed spt.refreshLock.Lock() defer spt.refreshLock.Unlock() if spt.inner.Token.WillExpireIn(spt.inner.RefreshWithin) { return spt.refreshInternal(ctx, spt.inner.Resource) } } return nil } // InvokeRefreshCallbacks calls any TokenRefreshCallbacks that were added to the SPT during initialization func (spt *ServicePrincipalToken) InvokeRefreshCallbacks(token Token) error { if spt.refreshCallbacks != nil { for _, callback := range spt.refreshCallbacks { err := callback(spt.inner.Token) if err != nil { return fmt.Errorf("adal: TokenRefreshCallback handler failed. Error = '%v'", err) } } } return nil } // Refresh obtains a fresh token for the Service Principal. // This method is safe for concurrent use. func (spt *ServicePrincipalToken) Refresh() error { return spt.RefreshWithContext(context.Background()) } // RefreshWithContext obtains a fresh token for the Service Principal. // This method is safe for concurrent use. func (spt *ServicePrincipalToken) RefreshWithContext(ctx context.Context) error { spt.refreshLock.Lock() defer spt.refreshLock.Unlock() return spt.refreshInternal(ctx, spt.inner.Resource) } // RefreshExchange refreshes the token, but for a different resource. // This method is safe for concurrent use. func (spt *ServicePrincipalToken) RefreshExchange(resource string) error { return spt.RefreshExchangeWithContext(context.Background(), resource) } // RefreshExchangeWithContext refreshes the token, but for a different resource. // This method is safe for concurrent use. func (spt *ServicePrincipalToken) RefreshExchangeWithContext(ctx context.Context, resource string) error { spt.refreshLock.Lock() defer spt.refreshLock.Unlock() return spt.refreshInternal(ctx, resource) } func (spt *ServicePrincipalToken) getGrantType() string { switch spt.inner.Secret.(type) { case *ServicePrincipalUsernamePasswordSecret: return OAuthGrantTypeUserPass case *ServicePrincipalAuthorizationCodeSecret: return OAuthGrantTypeAuthorizationCode default: return OAuthGrantTypeClientCredentials } } func isIMDS(u url.URL) bool { imds, err := url.Parse(msiEndpoint) if err != nil { return false } return (u.Host == imds.Host && u.Path == imds.Path) || isAppService() } func (spt *ServicePrincipalToken) refreshInternal(ctx context.Context, resource string) error { if spt.customRefreshFunc != nil { token, err := spt.customRefreshFunc(ctx, resource) if err != nil { return err } spt.inner.Token = *token return spt.InvokeRefreshCallbacks(spt.inner.Token) } req, err := http.NewRequest(http.MethodPost, spt.inner.OauthConfig.TokenEndpoint.String(), nil) if err != nil { return fmt.Errorf("adal: Failed to build the refresh request. Error = '%v'", err) } req.Header.Add("User-Agent", UserAgent()) // Add header when runtime is on App Service or Functions if isAppService() { asMSISecret, _ := os.LookupEnv(asMSISecretEnv) req.Header.Add("Secret", asMSISecret) } req = req.WithContext(ctx) if !isIMDS(spt.inner.OauthConfig.TokenEndpoint) { v := url.Values{} v.Set("client_id", spt.inner.ClientID) v.Set("resource", resource) if spt.inner.Token.RefreshToken != "" { v.Set("grant_type", OAuthGrantTypeRefreshToken) v.Set("refresh_token", spt.inner.Token.RefreshToken) // web apps must specify client_secret when refreshing tokens // see https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-code#refreshing-the-access-tokens if spt.getGrantType() == OAuthGrantTypeAuthorizationCode { err := spt.inner.Secret.SetAuthenticationValues(spt, &v) if err != nil { return err } } } else { v.Set("grant_type", spt.getGrantType()) err := spt.inner.Secret.SetAuthenticationValues(spt, &v) if err != nil { return err } } s := v.Encode() body := ioutil.NopCloser(strings.NewReader(s)) req.ContentLength = int64(len(s)) req.Header.Set(contentType, mimeTypeFormPost) req.Body = body } if _, ok := spt.inner.Secret.(*ServicePrincipalMSISecret); ok { req.Method = http.MethodGet req.Header.Set(metadataHeader, "true") } var resp *http.Response if isIMDS(spt.inner.OauthConfig.TokenEndpoint) { resp, err = retryForIMDS(spt.sender, req, spt.MaxMSIRefreshAttempts) } else { resp, err = spt.sender.Do(req) } if err != nil { // don't return a TokenRefreshError here; this will allow retry logic to apply return fmt.Errorf("adal: Failed to execute the refresh request. Error = '%v'", err) } defer resp.Body.Close() rb, err := ioutil.ReadAll(resp.Body) if resp.StatusCode != http.StatusOK { if err != nil { return newTokenRefreshError(fmt.Sprintf("adal: Refresh request failed. Status Code = '%d'. Failed reading response body: %v", resp.StatusCode, err), resp) } return newTokenRefreshError(fmt.Sprintf("adal: Refresh request failed. Status Code = '%d'. Response body: %s", resp.StatusCode, string(rb)), resp) } // for the following error cases don't return a TokenRefreshError. the operation succeeded // but some transient failure happened during deserialization. by returning a generic error // the retry logic will kick in (we don't retry on TokenRefreshError). if err != nil { return fmt.Errorf("adal: Failed to read a new service principal token during refresh. Error = '%v'", err) } if len(strings.Trim(string(rb), " ")) == 0 { return fmt.Errorf("adal: Empty service principal token received during refresh") } var token Token err = json.Unmarshal(rb, &token) if err != nil { return fmt.Errorf("adal: Failed to unmarshal the service principal token during refresh. Error = '%v' JSON = '%s'", err, string(rb)) } spt.inner.Token = token return spt.InvokeRefreshCallbacks(token) } // retry logic specific to retrieving a token from the IMDS endpoint func retryForIMDS(sender Sender, req *http.Request, maxAttempts int) (resp *http.Response, err error) { // copied from client.go due to circular dependency retries := []int{ http.StatusRequestTimeout, // 408 http.StatusTooManyRequests, // 429 http.StatusInternalServerError, // 500 http.StatusBadGateway, // 502 http.StatusServiceUnavailable, // 503 http.StatusGatewayTimeout, // 504 } // extra retry status codes specific to IMDS retries = append(retries, http.StatusNotFound, http.StatusGone, // all remaining 5xx http.StatusNotImplemented, http.StatusHTTPVersionNotSupported, http.StatusVariantAlsoNegotiates, http.StatusInsufficientStorage, http.StatusLoopDetected, http.StatusNotExtended, http.StatusNetworkAuthenticationRequired) // see https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/how-to-use-vm-token#retry-guidance const maxDelay time.Duration = 60 * time.Second attempt := 0 delay := time.Duration(0) for attempt < maxAttempts { if resp != nil && resp.Body != nil { io.Copy(ioutil.Discard, resp.Body) resp.Body.Close() } resp, err = sender.Do(req) // we want to retry if err is not nil or the status code is in the list of retry codes if err == nil && !responseHasStatusCode(resp, retries...) { return } // perform exponential backoff with a cap. // must increment attempt before calculating delay. attempt++ // the base value of 2 is the "delta backoff" as specified in the guidance doc delay += (time.Duration(math.Pow(2, float64(attempt))) * time.Second) if delay > maxDelay { delay = maxDelay } select { case <-time.After(delay): // intentionally left blank case <-req.Context().Done(): err = req.Context().Err() return } } return } func responseHasStatusCode(resp *http.Response, codes ...int) bool { if resp != nil { for _, i := range codes { if i == resp.StatusCode { return true } } } return false } // SetAutoRefresh enables or disables automatic refreshing of stale tokens. func (spt *ServicePrincipalToken) SetAutoRefresh(autoRefresh bool) { spt.inner.AutoRefresh = autoRefresh } // SetRefreshWithin sets the interval within which if the token will expire, EnsureFresh will // refresh the token. func (spt *ServicePrincipalToken) SetRefreshWithin(d time.Duration) { spt.inner.RefreshWithin = d return } // SetSender sets the http.Client used when obtaining the Service Principal token. An // undecorated http.Client is used by default. func (spt *ServicePrincipalToken) SetSender(s Sender) { spt.sender = s } // OAuthToken implements the OAuthTokenProvider interface. It returns the current access token. func (spt *ServicePrincipalToken) OAuthToken() string { spt.refreshLock.RLock() defer spt.refreshLock.RUnlock() return spt.inner.Token.OAuthToken() } // Token returns a copy of the current token. func (spt *ServicePrincipalToken) Token() Token { spt.refreshLock.RLock() defer spt.refreshLock.RUnlock() return spt.inner.Token } // MultiTenantServicePrincipalToken contains tokens for multi-tenant authorization. type MultiTenantServicePrincipalToken struct { PrimaryToken *ServicePrincipalToken AuxiliaryTokens []*ServicePrincipalToken } // PrimaryOAuthToken returns the primary authorization token. func (mt *MultiTenantServicePrincipalToken) PrimaryOAuthToken() string { return mt.PrimaryToken.OAuthToken() } // AuxiliaryOAuthTokens returns one to three auxiliary authorization tokens. func (mt *MultiTenantServicePrincipalToken) AuxiliaryOAuthTokens() []string { tokens := make([]string, len(mt.AuxiliaryTokens)) for i := range mt.AuxiliaryTokens { tokens[i] = mt.AuxiliaryTokens[i].OAuthToken() } return tokens } // EnsureFreshWithContext will refresh the token if it will expire within the refresh window (as set by // RefreshWithin) and autoRefresh flag is on. This method is safe for concurrent use. func (mt *MultiTenantServicePrincipalToken) EnsureFreshWithContext(ctx context.Context) error { if err := mt.PrimaryToken.EnsureFreshWithContext(ctx); err != nil { return fmt.Errorf("failed to refresh primary token: %v", err) } for _, aux := range mt.AuxiliaryTokens { if err := aux.EnsureFreshWithContext(ctx); err != nil { return fmt.Errorf("failed to refresh auxiliary token: %v", err) } } return nil } // RefreshWithContext obtains a fresh token for the Service Principal. func (mt *MultiTenantServicePrincipalToken) RefreshWithContext(ctx context.Context) error { if err := mt.PrimaryToken.RefreshWithContext(ctx); err != nil { return fmt.Errorf("failed to refresh primary token: %v", err) } for _, aux := range mt.AuxiliaryTokens { if err := aux.RefreshWithContext(ctx); err != nil { return fmt.Errorf("failed to refresh auxiliary token: %v", err) } } return nil } // RefreshExchangeWithContext refreshes the token, but for a different resource. func (mt *MultiTenantServicePrincipalToken) RefreshExchangeWithContext(ctx context.Context, resource string) error { if err := mt.PrimaryToken.RefreshExchangeWithContext(ctx, resource); err != nil { return fmt.Errorf("failed to refresh primary token: %v", err) } for _, aux := range mt.AuxiliaryTokens { if err := aux.RefreshExchangeWithContext(ctx, resource); err != nil { return fmt.Errorf("failed to refresh auxiliary token: %v", err) } } return nil } // NewMultiTenantServicePrincipalToken creates a new MultiTenantServicePrincipalToken with the specified credentials and resource. func NewMultiTenantServicePrincipalToken(multiTenantCfg MultiTenantOAuthConfig, clientID string, secret string, resource string) (*MultiTenantServicePrincipalToken, error) { if err := validateStringParam(clientID, "clientID"); err != nil { return nil, err } if err := validateStringParam(secret, "secret"); err != nil { return nil, err } if err := validateStringParam(resource, "resource"); err != nil { return nil, err } auxTenants := multiTenantCfg.AuxiliaryTenants() m := MultiTenantServicePrincipalToken{ AuxiliaryTokens: make([]*ServicePrincipalToken, len(auxTenants)), } primary, err := NewServicePrincipalToken(*multiTenantCfg.PrimaryTenant(), clientID, secret, resource) if err != nil { return nil, fmt.Errorf("failed to create SPT for primary tenant: %v", err) } m.PrimaryToken = primary for i := range auxTenants { aux, err := NewServicePrincipalToken(*auxTenants[i], clientID, secret, resource) if err != nil { return nil, fmt.Errorf("failed to create SPT for auxiliary tenant: %v", err) } m.AuxiliaryTokens[i] = aux } return &m, nil } golang-github-azure-go-autorest-14.1.1/autorest/adal/token_test.go000066400000000000000000000764461367372352400252320ustar00rootroot00000000000000package adal // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "context" "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/json" "fmt" "io/ioutil" "math/big" "net/http" "net/url" "os" "reflect" "strconv" "strings" "sync" "testing" "time" "github.com/Azure/go-autorest/autorest/date" "github.com/Azure/go-autorest/autorest/mocks" jwt "github.com/dgrijalva/jwt-go" ) const ( defaultFormData = "client_id=id&client_secret=secret&grant_type=client_credentials&resource=resource" defaultManualFormData = "client_id=id&grant_type=refresh_token&refresh_token=refreshtoken&resource=resource" ) func TestTokenExpires(t *testing.T) { tt := time.Now().Add(5 * time.Second) tk := newTokenExpiresAt(tt) if tk.Expires().Equal(tt) { t.Fatalf("adal: Token#Expires miscalculated expiration time -- received %v, expected %v", tk.Expires(), tt) } } func TestTokenIsExpired(t *testing.T) { tk := newTokenExpiresAt(time.Now().Add(-5 * time.Second)) if !tk.IsExpired() { t.Fatalf("adal: Token#IsExpired failed to mark a stale token as expired -- now %v, token expires at %v", time.Now().UTC(), tk.Expires()) } } func TestTokenIsExpiredUninitialized(t *testing.T) { tk := &Token{} if !tk.IsExpired() { t.Fatalf("adal: An uninitialized Token failed to mark itself as expired (expiration time %v)", tk.Expires()) } } func TestTokenIsNoExpired(t *testing.T) { tk := newTokenExpiresAt(time.Now().Add(1000 * time.Second)) if tk.IsExpired() { t.Fatalf("adal: Token marked a fresh token as expired -- now %v, token expires at %v", time.Now().UTC(), tk.Expires()) } } func TestTokenWillExpireIn(t *testing.T) { d := 5 * time.Second tk := newTokenExpiresIn(d) if !tk.WillExpireIn(d) { t.Fatal("adal: Token#WillExpireIn mismeasured expiration time") } } func TestServicePrincipalTokenSetAutoRefresh(t *testing.T) { spt := newServicePrincipalToken() if !spt.inner.AutoRefresh { t.Fatal("adal: ServicePrincipalToken did not default to automatic token refreshing") } spt.SetAutoRefresh(false) if spt.inner.AutoRefresh { t.Fatal("adal: ServicePrincipalToken#SetAutoRefresh did not disable automatic token refreshing") } } func TestServicePrincipalTokenSetCustomRefreshFunc(t *testing.T) { spt := newServicePrincipalToken() var refreshFunc TokenRefresh = func(context context.Context, resource string) (*Token, error) { return nil, nil } if spt.customRefreshFunc != nil { t.Fatalf("adal: ServicePrincipalToken#SetCustomRefreshFunc had a default custom refresh func when it shouldn't") } spt.SetCustomRefreshFunc(refreshFunc) if spt.customRefreshFunc == nil { t.Fatalf("adal: ServicePrincipalToken#SetCustomRefreshFunc didn't have a refresh func") } } func TestServicePrincipalTokenSetRefreshWithin(t *testing.T) { spt := newServicePrincipalToken() if spt.inner.RefreshWithin != defaultRefresh { t.Fatal("adal: ServicePrincipalToken did not correctly set the default refresh interval") } spt.SetRefreshWithin(2 * defaultRefresh) if spt.inner.RefreshWithin != 2*defaultRefresh { t.Fatal("adal: ServicePrincipalToken#SetRefreshWithin did not set the refresh interval") } } func TestServicePrincipalTokenSetSender(t *testing.T) { spt := newServicePrincipalToken() c := &http.Client{} spt.SetSender(c) if !reflect.DeepEqual(c, spt.sender) { t.Fatal("adal: ServicePrincipalToken#SetSender did not set the sender") } } func TestServicePrincipalTokenRefreshUsesCustomRefreshFunc(t *testing.T) { spt := newServicePrincipalToken() called := false var refreshFunc TokenRefresh = func(context context.Context, resource string) (*Token, error) { called = true return &Token{}, nil } spt.SetCustomRefreshFunc(refreshFunc) if called { t.Fatalf("adal: ServicePrincipalToken#refreshInternal called the refresh function prior to refreshing") } spt.refreshInternal(context.Background(), "https://example.com") if !called { t.Fatalf("adal: ServicePrincipalToken#refreshInternal didn't call the refresh function") } } func TestServicePrincipalTokenRefreshUsesPOST(t *testing.T) { spt := newServicePrincipalToken() body := mocks.NewBody(newTokenJSON("12345", "test")) resp := mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK") c := mocks.NewSender() s := DecorateSender(c, (func() SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { if r.Method != "POST" { t.Fatalf("adal: ServicePrincipalToken#Refresh did not correctly set HTTP method -- expected %v, received %v", "POST", r.Method) } return resp, nil }) } })()) spt.SetSender(s) err := spt.Refresh() if err != nil { t.Fatalf("adal: ServicePrincipalToken#Refresh returned an unexpected error (%v)", err) } if body.IsOpen() { t.Fatalf("the response was not closed!") } } func TestServicePrincipalTokenFromMSIRefreshUsesGET(t *testing.T) { resource := "https://resource" cb := func(token Token) error { return nil } spt, err := NewServicePrincipalTokenFromMSI("http://msiendpoint/", resource, cb) if err != nil { t.Fatalf("Failed to get MSI SPT: %v", err) } body := mocks.NewBody(newTokenJSON("12345", "test")) resp := mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK") c := mocks.NewSender() s := DecorateSender(c, (func() SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { if r.Method != "GET" { t.Fatalf("adal: ServicePrincipalToken#Refresh did not correctly set HTTP method -- expected %v, received %v", "GET", r.Method) } if h := r.Header.Get("Metadata"); h != "true" { t.Fatalf("adal: ServicePrincipalToken#Refresh did not correctly set Metadata header for MSI") } return resp, nil }) } })()) spt.SetSender(s) err = spt.Refresh() if err != nil { t.Fatalf("adal: ServicePrincipalToken#Refresh returned an unexpected error (%v)", err) } if body.IsOpen() { t.Fatalf("the response was not closed!") } } func TestServicePrincipalTokenFromMSIRefreshCancel(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) endpoint, _ := GetMSIVMEndpoint() spt, err := NewServicePrincipalTokenFromMSI(endpoint, "https://resource") if err != nil { t.Fatalf("Failed to get MSI SPT: %v", err) } c := mocks.NewSender() c.AppendAndRepeatResponse(mocks.NewResponseWithStatus("Internal server error", http.StatusInternalServerError), 5) var wg sync.WaitGroup wg.Add(1) start := time.Now() end := time.Now() go func() { spt.SetSender(c) err = spt.RefreshWithContext(ctx) end = time.Now() wg.Done() }() cancel() wg.Wait() time.Sleep(5 * time.Millisecond) if end.Sub(start) >= time.Second { t.Fatalf("TestServicePrincipalTokenFromMSIRefreshCancel failed to cancel") } } func TestServicePrincipalTokenRefreshSetsMimeType(t *testing.T) { spt := newServicePrincipalToken() body := mocks.NewBody(newTokenJSON("12345", "test")) resp := mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK") c := mocks.NewSender() s := DecorateSender(c, (func() SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { if r.Header.Get(http.CanonicalHeaderKey("Content-Type")) != "application/x-www-form-urlencoded" { t.Fatalf("adal: ServicePrincipalToken#Refresh did not correctly set Content-Type -- expected %v, received %v", "application/x-form-urlencoded", r.Header.Get(http.CanonicalHeaderKey("Content-Type"))) } return resp, nil }) } })()) spt.SetSender(s) err := spt.Refresh() if err != nil { t.Fatalf("adal: ServicePrincipalToken#Refresh returned an unexpected error (%v)", err) } } func TestServicePrincipalTokenRefreshSetsURL(t *testing.T) { spt := newServicePrincipalToken() body := mocks.NewBody(newTokenJSON("12345", "test")) resp := mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK") c := mocks.NewSender() s := DecorateSender(c, (func() SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { if r.URL.String() != TestOAuthConfig.TokenEndpoint.String() { t.Fatalf("adal: ServicePrincipalToken#Refresh did not correctly set the URL -- expected %v, received %v", TestOAuthConfig.TokenEndpoint, r.URL) } return resp, nil }) } })()) spt.SetSender(s) err := spt.Refresh() if err != nil { t.Fatalf("adal: ServicePrincipalToken#Refresh returned an unexpected error (%v)", err) } } func testServicePrincipalTokenRefreshSetsBody(t *testing.T, spt *ServicePrincipalToken, f func(*testing.T, []byte)) { body := mocks.NewBody(newTokenJSON("12345", "test")) resp := mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK") c := mocks.NewSender() s := DecorateSender(c, (func() SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { b, err := ioutil.ReadAll(r.Body) if err != nil { t.Fatalf("adal: Failed to read body of Service Principal token request (%v)", err) } f(t, b) return resp, nil }) } })()) spt.SetSender(s) err := spt.Refresh() if err != nil { t.Fatalf("adal: ServicePrincipalToken#Refresh returned an unexpected error (%v)", err) } } func TestServicePrincipalTokenManualRefreshSetsBody(t *testing.T) { sptManual := newServicePrincipalTokenManual() testServicePrincipalTokenRefreshSetsBody(t, sptManual, func(t *testing.T, b []byte) { if string(b) != defaultManualFormData { t.Fatalf("adal: ServicePrincipalToken#Refresh did not correctly set the HTTP Request Body -- expected %v, received %v", defaultManualFormData, string(b)) } }) } func TestServicePrincipalTokenCertficateRefreshSetsBody(t *testing.T) { sptCert := newServicePrincipalTokenCertificate(t) testServicePrincipalTokenRefreshSetsBody(t, sptCert, func(t *testing.T, b []byte) { body := string(b) values, _ := url.ParseQuery(body) if values["client_assertion_type"][0] != "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" || values["client_id"][0] != "id" || values["grant_type"][0] != "client_credentials" || values["resource"][0] != "resource" { t.Fatalf("adal: ServicePrincipalTokenCertificate#Refresh did not correctly set the HTTP Request Body.") } tok, _ := jwt.Parse(values["client_assertion"][0], nil) if tok == nil { t.Fatalf("adal: ServicePrincipalTokenCertificate#Expected client_assertion to be a JWT") } if _, ok := tok.Header["x5t"]; !ok { t.Fatalf("adal: ServicePrincipalTokenCertificate#Expected client_assertion to have an x5t header") } if _, ok := tok.Header["x5c"]; !ok { t.Fatalf("adal: ServicePrincipalTokenCertificate#Expected client_assertion to have an x5c header") } }) } func TestServicePrincipalTokenUsernamePasswordRefreshSetsBody(t *testing.T) { spt := newServicePrincipalTokenUsernamePassword(t) testServicePrincipalTokenRefreshSetsBody(t, spt, func(t *testing.T, b []byte) { body := string(b) values, _ := url.ParseQuery(body) if values["client_id"][0] != "id" || values["grant_type"][0] != "password" || values["username"][0] != "username" || values["password"][0] != "password" || values["resource"][0] != "resource" { t.Fatalf("adal: ServicePrincipalTokenUsernamePassword#Refresh did not correctly set the HTTP Request Body.") } }) } func TestServicePrincipalTokenAuthorizationCodeRefreshSetsBody(t *testing.T) { spt := newServicePrincipalTokenAuthorizationCode(t) testServicePrincipalTokenRefreshSetsBody(t, spt, func(t *testing.T, b []byte) { body := string(b) values, _ := url.ParseQuery(body) if values["client_id"][0] != "id" || values["grant_type"][0] != OAuthGrantTypeAuthorizationCode || values["code"][0] != "code" || values["client_secret"][0] != "clientSecret" || values["redirect_uri"][0] != "http://redirectUri/getToken" || values["resource"][0] != "resource" { t.Fatalf("adal: ServicePrincipalTokenAuthorizationCode#Refresh did not correctly set the HTTP Request Body.") } }) testServicePrincipalTokenRefreshSetsBody(t, spt, func(t *testing.T, b []byte) { body := string(b) values, _ := url.ParseQuery(body) if values["client_id"][0] != "id" || values["grant_type"][0] != OAuthGrantTypeRefreshToken || values["code"][0] != "code" || values["client_secret"][0] != "clientSecret" || values["redirect_uri"][0] != "http://redirectUri/getToken" || values["resource"][0] != "resource" { t.Fatalf("adal: ServicePrincipalTokenAuthorizationCode#Refresh did not correctly set the HTTP Request Body.") } }) } func TestServicePrincipalTokenSecretRefreshSetsBody(t *testing.T) { spt := newServicePrincipalToken() testServicePrincipalTokenRefreshSetsBody(t, spt, func(t *testing.T, b []byte) { if string(b) != defaultFormData { t.Fatalf("adal: ServicePrincipalToken#Refresh did not correctly set the HTTP Request Body -- expected %v, received %v", defaultFormData, string(b)) } }) } func TestServicePrincipalTokenRefreshClosesRequestBody(t *testing.T) { spt := newServicePrincipalToken() body := mocks.NewBody(newTokenJSON("12345", "test")) resp := mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK") c := mocks.NewSender() s := DecorateSender(c, (func() SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { return resp, nil }) } })()) spt.SetSender(s) err := spt.Refresh() if err != nil { t.Fatalf("adal: ServicePrincipalToken#Refresh returned an unexpected error (%v)", err) } if resp.Body.(*mocks.Body).IsOpen() { t.Fatal("adal: ServicePrincipalToken#Refresh failed to close the HTTP Response Body") } } func TestServicePrincipalTokenRefreshRejectsResponsesWithStatusNotOK(t *testing.T) { spt := newServicePrincipalToken() body := mocks.NewBody(newTokenJSON("12345", "test")) resp := mocks.NewResponseWithBodyAndStatus(body, http.StatusUnauthorized, "Unauthorized") c := mocks.NewSender() s := DecorateSender(c, (func() SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { return resp, nil }) } })()) spt.SetSender(s) err := spt.Refresh() if err == nil { t.Fatalf("adal: ServicePrincipalToken#Refresh should reject a response with status != %d", http.StatusOK) } } func TestServicePrincipalTokenRefreshRejectsEmptyBody(t *testing.T) { spt := newServicePrincipalToken() c := mocks.NewSender() s := DecorateSender(c, (func() SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { return mocks.NewResponse(), nil }) } })()) spt.SetSender(s) err := spt.Refresh() if err == nil { t.Fatal("adal: ServicePrincipalToken#Refresh should reject an empty token") } } func TestServicePrincipalTokenRefreshPropagatesErrors(t *testing.T) { spt := newServicePrincipalToken() c := mocks.NewSender() c.SetError(fmt.Errorf("Faux Error")) spt.SetSender(c) err := spt.Refresh() if err == nil { t.Fatal("adal: Failed to propagate the request error") } } func TestServicePrincipalTokenRefreshReturnsErrorIfNotOk(t *testing.T) { spt := newServicePrincipalToken() c := mocks.NewSender() c.AppendResponse(mocks.NewResponseWithStatus("401 NotAuthorized", http.StatusUnauthorized)) spt.SetSender(c) err := spt.Refresh() if err == nil { t.Fatalf("adal: Failed to return an when receiving a status code other than HTTP %d", http.StatusOK) } } func TestServicePrincipalTokenRefreshUnmarshals(t *testing.T) { spt := newServicePrincipalToken() expiresOn := strconv.Itoa(int(time.Now().Add(3600 * time.Second).Sub(date.UnixEpoch()).Seconds())) j := newTokenJSON(expiresOn, "resource") resp := mocks.NewResponseWithContent(j) c := mocks.NewSender() s := DecorateSender(c, (func() SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { return resp, nil }) } })()) spt.SetSender(s) err := spt.Refresh() if err != nil { t.Fatalf("adal: ServicePrincipalToken#Refresh returned an unexpected error (%v)", err) } else if spt.inner.Token.AccessToken != "accessToken" || spt.inner.Token.ExpiresIn != "3600" || spt.inner.Token.ExpiresOn != json.Number(expiresOn) || spt.inner.Token.NotBefore != json.Number(expiresOn) || spt.inner.Token.Resource != "resource" || spt.inner.Token.Type != "Bearer" { t.Fatalf("adal: ServicePrincipalToken#Refresh failed correctly unmarshal the JSON -- expected %v, received %v", j, *spt) } } func TestServicePrincipalTokenEnsureFreshRefreshes(t *testing.T) { spt := newServicePrincipalToken() expireToken(&spt.inner.Token) body := mocks.NewBody(newTokenJSON("12345", "test")) resp := mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK") f := false c := mocks.NewSender() s := DecorateSender(c, (func() SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { f = true return resp, nil }) } })()) spt.SetSender(s) err := spt.EnsureFresh() if err != nil { t.Fatalf("adal: ServicePrincipalToken#EnsureFresh returned an unexpected error (%v)", err) } if !f { t.Fatal("adal: ServicePrincipalToken#EnsureFresh failed to call Refresh for stale token") } } func TestServicePrincipalTokenEnsureFreshFails1(t *testing.T) { spt := newServicePrincipalToken() expireToken(&spt.inner.Token) c := mocks.NewSender() c.SetError(fmt.Errorf("some failure")) spt.SetSender(c) err := spt.EnsureFresh() if err == nil { t.Fatal("adal: ServicePrincipalToken#EnsureFresh didn't return an error") } if _, ok := err.(TokenRefreshError); ok { t.Fatal("adal: ServicePrincipalToken#EnsureFresh unexpected TokenRefreshError") } } func TestServicePrincipalTokenEnsureFreshFails2(t *testing.T) { spt := newServicePrincipalToken() expireToken(&spt.inner.Token) c := mocks.NewSender() c.AppendResponse(mocks.NewResponseWithStatus("bad request", http.StatusBadRequest)) spt.SetSender(c) err := spt.EnsureFresh() if err == nil { t.Fatal("adal: ServicePrincipalToken#EnsureFresh didn't return an error") } if _, ok := err.(TokenRefreshError); !ok { t.Fatal("adal: ServicePrincipalToken#EnsureFresh didn't return a TokenRefreshError") } } func TestServicePrincipalTokenEnsureFreshSkipsIfFresh(t *testing.T) { spt := newServicePrincipalToken() setTokenToExpireIn(&spt.inner.Token, 1000*time.Second) f := false c := mocks.NewSender() s := DecorateSender(c, (func() SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { f = true return mocks.NewResponse(), nil }) } })()) spt.SetSender(s) err := spt.EnsureFresh() if err != nil { t.Fatalf("adal: ServicePrincipalToken#EnsureFresh returned an unexpected error (%v)", err) } if f { t.Fatal("adal: ServicePrincipalToken#EnsureFresh invoked Refresh for fresh token") } } func TestRefreshCallback(t *testing.T) { callbackTriggered := false spt := newServicePrincipalToken(func(Token) error { callbackTriggered = true return nil }) expiresOn := strconv.Itoa(int(time.Now().Add(3600 * time.Second).Sub(date.UnixEpoch()).Seconds())) sender := mocks.NewSender() j := newTokenJSON(expiresOn, "resource") sender.AppendResponse(mocks.NewResponseWithContent(j)) spt.SetSender(sender) err := spt.Refresh() if err != nil { t.Fatalf("adal: ServicePrincipalToken#Refresh returned an unexpected error (%v)", err) } if !callbackTriggered { t.Fatalf("adal: RefreshCallback failed to trigger call callback") } } func TestRefreshCallbackErrorPropagates(t *testing.T) { errorText := "this is an error text" spt := newServicePrincipalToken(func(Token) error { return fmt.Errorf(errorText) }) expiresOn := strconv.Itoa(int(time.Now().Add(3600 * time.Second).Sub(date.UnixEpoch()).Seconds())) sender := mocks.NewSender() j := newTokenJSON(expiresOn, "resource") sender.AppendResponse(mocks.NewResponseWithContent(j)) spt.SetSender(sender) err := spt.Refresh() if err == nil || !strings.Contains(err.Error(), errorText) { t.Fatalf("adal: RefreshCallback failed to propagate error") } } // This demonstrates the danger of manual token without a refresh token func TestServicePrincipalTokenManualRefreshFailsWithoutRefresh(t *testing.T) { spt := newServicePrincipalTokenManual() spt.inner.Token.RefreshToken = "" err := spt.Refresh() if err == nil { t.Fatalf("adal: ServicePrincipalToken#Refresh should have failed with a ManualTokenSecret without a refresh token") } } func TestNewServicePrincipalTokenFromMSI(t *testing.T) { resource := "https://resource" cb := func(token Token) error { return nil } spt, err := NewServicePrincipalTokenFromMSI("http://msiendpoint/", resource, cb) if err != nil { t.Fatalf("Failed to get MSI SPT: %v", err) } // check some of the SPT fields if _, ok := spt.inner.Secret.(*ServicePrincipalMSISecret); !ok { t.Fatal("SPT secret was not of MSI type") } if spt.inner.Resource != resource { t.Fatal("SPT came back with incorrect resource") } if len(spt.refreshCallbacks) != 1 { t.Fatal("SPT had incorrect refresh callbacks.") } } func TestNewServicePrincipalTokenFromMSIWithUserAssignedID(t *testing.T) { resource := "https://resource" userID := "abc123" cb := func(token Token) error { return nil } spt, err := NewServicePrincipalTokenFromMSIWithUserAssignedID("http://msiendpoint/", resource, userID, cb) if err != nil { t.Fatalf("Failed to get MSI SPT: %v", err) } // check some of the SPT fields if _, ok := spt.inner.Secret.(*ServicePrincipalMSISecret); !ok { t.Fatal("SPT secret was not of MSI type") } if spt.inner.Resource != resource { t.Fatal("SPT came back with incorrect resource") } if len(spt.refreshCallbacks) != 1 { t.Fatal("SPT had incorrect refresh callbacks.") } if spt.inner.ClientID != userID { t.Fatal("SPT had incorrect client ID") } } func TestNewServicePrincipalTokenFromManualTokenSecret(t *testing.T) { token := newToken() secret := &ServicePrincipalAuthorizationCodeSecret{ ClientSecret: "clientSecret", AuthorizationCode: "code123", RedirectURI: "redirect", } spt, err := NewServicePrincipalTokenFromManualTokenSecret(TestOAuthConfig, "id", "resource", token, secret, nil) if err != nil { t.Fatalf("Failed creating new SPT: %s", err) } if !reflect.DeepEqual(token, spt.inner.Token) { t.Fatalf("Tokens do not match: %s, %s", token, spt.inner.Token) } if !reflect.DeepEqual(secret, spt.inner.Secret) { t.Fatalf("Secrets do not match: %s, %s", secret, spt.inner.Secret) } } func TestGetVMEndpoint(t *testing.T) { endpoint, err := GetMSIVMEndpoint() if err != nil { t.Fatal("Coudn't get VM endpoint") } if endpoint != msiEndpoint { t.Fatal("Didn't get correct endpoint") } } func TestGetAppServiceEndpoint(t *testing.T) { const testEndpoint = "http://172.16.1.2:8081/msi/token" if err := os.Setenv(asMSIEndpointEnv, testEndpoint); err != nil { t.Fatalf("os.Setenv: %v", err) } endpoint, err := GetMSIAppServiceEndpoint() if err != nil { t.Fatal("Coudn't get App Service endpoint") } if endpoint != testEndpoint { t.Fatal("Didn't get correct endpoint") } if err := os.Unsetenv(asMSIEndpointEnv); err != nil { t.Fatalf("os.Unsetenv: %v", err) } } func TestGetMSIEndpoint(t *testing.T) { const ( testEndpoint = "http://172.16.1.2:8081/msi/token" testSecret = "DEADBEEF-BBBB-AAAA-DDDD-DDD000000DDD" ) // Test VM well-known endpoint is returned if err := os.Unsetenv(asMSIEndpointEnv); err != nil { t.Fatalf("os.Unsetenv: %v", err) } if err := os.Unsetenv(asMSISecretEnv); err != nil { t.Fatalf("os.Unsetenv: %v", err) } vmEndpoint, err := GetMSIEndpoint() if err != nil { t.Fatal("Coudn't get VM endpoint") } if vmEndpoint != msiEndpoint { t.Fatal("Didn't get correct endpoint") } // Test App Service endpoint is returned if err := os.Setenv(asMSIEndpointEnv, testEndpoint); err != nil { t.Fatalf("os.Setenv: %v", err) } if err := os.Setenv(asMSISecretEnv, testSecret); err != nil { t.Fatalf("os.Setenv: %v", err) } asEndpoint, err := GetMSIEndpoint() if err != nil { t.Fatal("Coudn't get App Service endpoint") } if asEndpoint != testEndpoint { t.Fatal("Didn't get correct endpoint") } if err := os.Unsetenv(asMSIEndpointEnv); err != nil { t.Fatalf("os.Unsetenv: %v", err) } if err := os.Unsetenv(asMSISecretEnv); err != nil { t.Fatalf("os.Unsetenv: %v", err) } } func TestMarshalServicePrincipalNoSecret(t *testing.T) { spt := newServicePrincipalTokenManual() b, err := json.Marshal(spt) if err != nil { t.Fatalf("failed to marshal token: %+v", err) } var spt2 *ServicePrincipalToken err = json.Unmarshal(b, &spt2) if err != nil { t.Fatalf("failed to unmarshal token: %+v", err) } if !reflect.DeepEqual(spt, spt2) { t.Fatal("tokens don't match") } } func TestMarshalServicePrincipalTokenSecret(t *testing.T) { spt := newServicePrincipalToken() b, err := json.Marshal(spt) if err != nil { t.Fatalf("failed to marshal token: %+v", err) } var spt2 *ServicePrincipalToken err = json.Unmarshal(b, &spt2) if err != nil { t.Fatalf("failed to unmarshal token: %+v", err) } if !reflect.DeepEqual(spt, spt2) { t.Fatal("tokens don't match") } } func TestMarshalServicePrincipalCertificateSecret(t *testing.T) { spt := newServicePrincipalTokenCertificate(t) b, err := json.Marshal(spt) if err == nil { t.Fatal("expected error when marshalling certificate token") } var spt2 *ServicePrincipalToken err = json.Unmarshal(b, &spt2) if err == nil { t.Fatal("expected error when unmarshalling certificate token") } } func TestMarshalServicePrincipalMSISecret(t *testing.T) { spt, err := newServicePrincipalTokenFromMSI("http://msiendpoint/", "https://resource", nil) if err != nil { t.Fatalf("failed to get MSI SPT: %+v", err) } b, err := json.Marshal(spt) if err == nil { t.Fatal("expected error when marshalling MSI token") } var spt2 *ServicePrincipalToken err = json.Unmarshal(b, &spt2) if err == nil { t.Fatal("expected error when unmarshalling MSI token") } } func TestMarshalServicePrincipalUsernamePasswordSecret(t *testing.T) { spt := newServicePrincipalTokenUsernamePassword(t) b, err := json.Marshal(spt) if err != nil { t.Fatalf("failed to marshal token: %+v", err) } var spt2 *ServicePrincipalToken err = json.Unmarshal(b, &spt2) if err != nil { t.Fatalf("failed to unmarshal token: %+v", err) } if !reflect.DeepEqual(spt, spt2) { t.Fatal("tokens don't match") } } func TestMarshalServicePrincipalAuthorizationCodeSecret(t *testing.T) { spt := newServicePrincipalTokenAuthorizationCode(t) b, err := json.Marshal(spt) if err != nil { t.Fatalf("failed to marshal token: %+v", err) } var spt2 *ServicePrincipalToken err = json.Unmarshal(b, &spt2) if err != nil { t.Fatalf("failed to unmarshal token: %+v", err) } if !reflect.DeepEqual(spt, spt2) { t.Fatal("tokens don't match") } } func TestMarshalInnerToken(t *testing.T) { spt := newServicePrincipalTokenManual() tokenJSON, err := spt.MarshalTokenJSON() if err != nil { t.Fatalf("failed to marshal token: %+v", err) } testToken := newToken() testToken.RefreshToken = "refreshtoken" testTokenJSON, err := json.Marshal(testToken) if err != nil { t.Fatalf("failed to marshal test token: %+v", err) } if !reflect.DeepEqual(tokenJSON, testTokenJSON) { t.Fatalf("tokens don't match: %s, %s", tokenJSON, testTokenJSON) } var t1 Token err = json.Unmarshal(tokenJSON, &t1) if err != nil { t.Fatalf("failed to unmarshal token: %+v", err) } if !reflect.DeepEqual(t1, testToken) { t.Fatalf("tokens don't match: %s, %s", t1, testToken) } } func TestNewMultiTenantServicePrincipalToken(t *testing.T) { cfg, err := NewMultiTenantOAuthConfig(TestActiveDirectoryEndpoint, TestTenantID, TestAuxTenantIDs, OAuthOptions{}) if err != nil { t.Fatalf("autorest/adal: unexpected error while creating multitenant config: %v", err) } mt, err := NewMultiTenantServicePrincipalToken(cfg, "clientID", "superSecret", "resource") if err != nil { t.Fatalf("autorest/adal: unexpected error while creating multitenant service principal token: %v", err) } if !strings.Contains(mt.PrimaryToken.inner.OauthConfig.AuthorizeEndpoint.String(), TestTenantID) { t.Fatal("didn't find primary tenant ID in primary SPT") } for i := range mt.AuxiliaryTokens { if ep := mt.AuxiliaryTokens[i].inner.OauthConfig.AuthorizeEndpoint.String(); !strings.Contains(ep, fmt.Sprintf("%s%d", TestAuxTenantPrefix, i)) { t.Fatalf("didn't find auxiliary tenant ID in token %s", ep) } } } func newTokenJSON(expiresOn string, resource string) string { return fmt.Sprintf(`{ "access_token" : "accessToken", "expires_in" : "3600", "expires_on" : "%s", "not_before" : "%s", "resource" : "%s", "token_type" : "Bearer", "refresh_token": "ABC123" }`, expiresOn, expiresOn, resource) } func newTokenExpiresIn(expireIn time.Duration) *Token { t := newToken() return setTokenToExpireIn(&t, expireIn) } func newTokenExpiresAt(expireAt time.Time) *Token { t := newToken() return setTokenToExpireAt(&t, expireAt) } func expireToken(t *Token) *Token { return setTokenToExpireIn(t, 0) } func setTokenToExpireAt(t *Token, expireAt time.Time) *Token { t.ExpiresIn = "3600" t.ExpiresOn = json.Number(strconv.FormatInt(int64(expireAt.Sub(date.UnixEpoch())/time.Second), 10)) t.NotBefore = t.ExpiresOn return t } func setTokenToExpireIn(t *Token, expireIn time.Duration) *Token { return setTokenToExpireAt(t, time.Now().Add(expireIn)) } func newServicePrincipalToken(callbacks ...TokenRefreshCallback) *ServicePrincipalToken { spt, _ := NewServicePrincipalToken(TestOAuthConfig, "id", "secret", "resource", callbacks...) return spt } func newServicePrincipalTokenManual() *ServicePrincipalToken { token := newToken() token.RefreshToken = "refreshtoken" spt, _ := NewServicePrincipalTokenFromManualToken(TestOAuthConfig, "id", "resource", token) return spt } func newServicePrincipalTokenCertificate(t *testing.T) *ServicePrincipalToken { template := x509.Certificate{ SerialNumber: big.NewInt(0), Subject: pkix.Name{CommonName: "test"}, BasicConstraintsValid: true, } privateKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { t.Fatal(err) } certificateBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) if err != nil { t.Fatal(err) } certificate, err := x509.ParseCertificate(certificateBytes) if err != nil { t.Fatal(err) } spt, _ := NewServicePrincipalTokenFromCertificate(TestOAuthConfig, "id", certificate, privateKey, "resource") return spt } func newServicePrincipalTokenUsernamePassword(t *testing.T) *ServicePrincipalToken { spt, _ := NewServicePrincipalTokenFromUsernamePassword(TestOAuthConfig, "id", "username", "password", "resource") return spt } func newServicePrincipalTokenAuthorizationCode(t *testing.T) *ServicePrincipalToken { spt, _ := NewServicePrincipalTokenFromAuthorizationCode(TestOAuthConfig, "id", "clientSecret", "code", "http://redirectUri/getToken", "resource") return spt } golang-github-azure-go-autorest-14.1.1/autorest/adal/version.go000066400000000000000000000023061367372352400245200ustar00rootroot00000000000000package adal import ( "fmt" "runtime" ) // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. const number = "v1.0.0" var ( ua = fmt.Sprintf("Go/%s (%s-%s) go-autorest/adal/%s", runtime.Version(), runtime.GOARCH, runtime.GOOS, number, ) ) // UserAgent returns a string containing the Go version, system architecture and OS, and the adal version. func UserAgent() string { return ua } // AddToUserAgent adds an extension to the current user agent func AddToUserAgent(extension string) error { if extension != "" { ua = fmt.Sprintf("%s %s", ua, extension) return nil } return fmt.Errorf("Extension was empty, User Agent remained as '%s'", ua) } golang-github-azure-go-autorest-14.1.1/autorest/authorization.go000066400000000000000000000272561367372352400250450ustar00rootroot00000000000000package autorest // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "crypto/tls" "encoding/base64" "fmt" "net/http" "net/url" "strings" "github.com/Azure/go-autorest/autorest/adal" ) const ( bearerChallengeHeader = "Www-Authenticate" bearer = "Bearer" tenantID = "tenantID" apiKeyAuthorizerHeader = "Ocp-Apim-Subscription-Key" bingAPISdkHeader = "X-BingApis-SDK-Client" golangBingAPISdkHeaderValue = "Go-SDK" authorization = "Authorization" basic = "Basic" ) // Authorizer is the interface that provides a PrepareDecorator used to supply request // authorization. Most often, the Authorizer decorator runs last so it has access to the full // state of the formed HTTP request. type Authorizer interface { WithAuthorization() PrepareDecorator } // NullAuthorizer implements a default, "do nothing" Authorizer. type NullAuthorizer struct{} // WithAuthorization returns a PrepareDecorator that does nothing. func (na NullAuthorizer) WithAuthorization() PrepareDecorator { return WithNothing() } // APIKeyAuthorizer implements API Key authorization. type APIKeyAuthorizer struct { headers map[string]interface{} queryParameters map[string]interface{} } // NewAPIKeyAuthorizerWithHeaders creates an ApiKeyAuthorizer with headers. func NewAPIKeyAuthorizerWithHeaders(headers map[string]interface{}) *APIKeyAuthorizer { return NewAPIKeyAuthorizer(headers, nil) } // NewAPIKeyAuthorizerWithQueryParameters creates an ApiKeyAuthorizer with query parameters. func NewAPIKeyAuthorizerWithQueryParameters(queryParameters map[string]interface{}) *APIKeyAuthorizer { return NewAPIKeyAuthorizer(nil, queryParameters) } // NewAPIKeyAuthorizer creates an ApiKeyAuthorizer with headers. func NewAPIKeyAuthorizer(headers map[string]interface{}, queryParameters map[string]interface{}) *APIKeyAuthorizer { return &APIKeyAuthorizer{headers: headers, queryParameters: queryParameters} } // WithAuthorization returns a PrepareDecorator that adds an HTTP headers and Query Parameters. func (aka *APIKeyAuthorizer) WithAuthorization() PrepareDecorator { return func(p Preparer) Preparer { return DecoratePreparer(p, WithHeaders(aka.headers), WithQueryParameters(aka.queryParameters)) } } // CognitiveServicesAuthorizer implements authorization for Cognitive Services. type CognitiveServicesAuthorizer struct { subscriptionKey string } // NewCognitiveServicesAuthorizer is func NewCognitiveServicesAuthorizer(subscriptionKey string) *CognitiveServicesAuthorizer { return &CognitiveServicesAuthorizer{subscriptionKey: subscriptionKey} } // WithAuthorization is func (csa *CognitiveServicesAuthorizer) WithAuthorization() PrepareDecorator { headers := make(map[string]interface{}) headers[apiKeyAuthorizerHeader] = csa.subscriptionKey headers[bingAPISdkHeader] = golangBingAPISdkHeaderValue return NewAPIKeyAuthorizerWithHeaders(headers).WithAuthorization() } // BearerAuthorizer implements the bearer authorization type BearerAuthorizer struct { tokenProvider adal.OAuthTokenProvider } // NewBearerAuthorizer crates a BearerAuthorizer using the given token provider func NewBearerAuthorizer(tp adal.OAuthTokenProvider) *BearerAuthorizer { return &BearerAuthorizer{tokenProvider: tp} } // WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose // value is "Bearer " followed by the token. // // By default, the token will be automatically refreshed through the Refresher interface. func (ba *BearerAuthorizer) WithAuthorization() PrepareDecorator { return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { r, err := p.Prepare(r) if err == nil { // the ordering is important here, prefer RefresherWithContext if available if refresher, ok := ba.tokenProvider.(adal.RefresherWithContext); ok { err = refresher.EnsureFreshWithContext(r.Context()) } else if refresher, ok := ba.tokenProvider.(adal.Refresher); ok { err = refresher.EnsureFresh() } if err != nil { var resp *http.Response if tokError, ok := err.(adal.TokenRefreshError); ok { resp = tokError.Response() } return r, NewErrorWithError(err, "azure.BearerAuthorizer", "WithAuthorization", resp, "Failed to refresh the Token for request to %s", r.URL) } return Prepare(r, WithHeader(headerAuthorization, fmt.Sprintf("Bearer %s", ba.tokenProvider.OAuthToken()))) } return r, err }) } } // BearerAuthorizerCallbackFunc is the authentication callback signature. type BearerAuthorizerCallbackFunc func(tenantID, resource string) (*BearerAuthorizer, error) // BearerAuthorizerCallback implements bearer authorization via a callback. type BearerAuthorizerCallback struct { sender Sender callback BearerAuthorizerCallbackFunc } // NewBearerAuthorizerCallback creates a bearer authorization callback. The callback // is invoked when the HTTP request is submitted. func NewBearerAuthorizerCallback(s Sender, callback BearerAuthorizerCallbackFunc) *BearerAuthorizerCallback { if s == nil { s = sender(tls.RenegotiateNever) } return &BearerAuthorizerCallback{sender: s, callback: callback} } // WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose value // is "Bearer " followed by the token. The BearerAuthorizer is obtained via a user-supplied callback. // // By default, the token will be automatically refreshed through the Refresher interface. func (bacb *BearerAuthorizerCallback) WithAuthorization() PrepareDecorator { return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { r, err := p.Prepare(r) if err == nil { // make a copy of the request and remove the body as it's not // required and avoids us having to create a copy of it. rCopy := *r removeRequestBody(&rCopy) resp, err := bacb.sender.Do(&rCopy) if err != nil { return r, err } DrainResponseBody(resp) if resp.StatusCode == 401 && hasBearerChallenge(resp.Header) { bc, err := newBearerChallenge(resp.Header) if err != nil { return r, err } if bacb.callback != nil { ba, err := bacb.callback(bc.values[tenantID], bc.values["resource"]) if err != nil { return r, err } return Prepare(r, ba.WithAuthorization()) } } } return r, err }) } } // returns true if the HTTP response contains a bearer challenge func hasBearerChallenge(header http.Header) bool { authHeader := header.Get(bearerChallengeHeader) if len(authHeader) == 0 || strings.Index(authHeader, bearer) < 0 { return false } return true } type bearerChallenge struct { values map[string]string } func newBearerChallenge(header http.Header) (bc bearerChallenge, err error) { challenge := strings.TrimSpace(header.Get(bearerChallengeHeader)) trimmedChallenge := challenge[len(bearer)+1:] // challenge is a set of key=value pairs that are comma delimited pairs := strings.Split(trimmedChallenge, ",") if len(pairs) < 1 { err = fmt.Errorf("challenge '%s' contains no pairs", challenge) return bc, err } bc.values = make(map[string]string) for i := range pairs { trimmedPair := strings.TrimSpace(pairs[i]) pair := strings.Split(trimmedPair, "=") if len(pair) == 2 { // remove the enclosing quotes key := strings.Trim(pair[0], "\"") value := strings.Trim(pair[1], "\"") switch key { case "authorization", "authorization_uri": // strip the tenant ID from the authorization URL asURL, err := url.Parse(value) if err != nil { return bc, err } bc.values[tenantID] = asURL.Path[1:] default: bc.values[key] = value } } } return bc, err } // EventGridKeyAuthorizer implements authorization for event grid using key authentication. type EventGridKeyAuthorizer struct { topicKey string } // NewEventGridKeyAuthorizer creates a new EventGridKeyAuthorizer // with the specified topic key. func NewEventGridKeyAuthorizer(topicKey string) EventGridKeyAuthorizer { return EventGridKeyAuthorizer{topicKey: topicKey} } // WithAuthorization returns a PrepareDecorator that adds the aeg-sas-key authentication header. func (egta EventGridKeyAuthorizer) WithAuthorization() PrepareDecorator { headers := map[string]interface{}{ "aeg-sas-key": egta.topicKey, } return NewAPIKeyAuthorizerWithHeaders(headers).WithAuthorization() } // BasicAuthorizer implements basic HTTP authorization by adding the Authorization HTTP header // with the value "Basic " where is a base64-encoded username:password tuple. type BasicAuthorizer struct { userName string password string } // NewBasicAuthorizer creates a new BasicAuthorizer with the specified username and password. func NewBasicAuthorizer(userName, password string) *BasicAuthorizer { return &BasicAuthorizer{ userName: userName, password: password, } } // WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose // value is "Basic " followed by the base64-encoded username:password tuple. func (ba *BasicAuthorizer) WithAuthorization() PrepareDecorator { headers := make(map[string]interface{}) headers[authorization] = basic + " " + base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", ba.userName, ba.password))) return NewAPIKeyAuthorizerWithHeaders(headers).WithAuthorization() } // MultiTenantServicePrincipalTokenAuthorizer provides authentication across tenants. type MultiTenantServicePrincipalTokenAuthorizer interface { WithAuthorization() PrepareDecorator } // NewMultiTenantServicePrincipalTokenAuthorizer crates a BearerAuthorizer using the given token provider func NewMultiTenantServicePrincipalTokenAuthorizer(tp adal.MultitenantOAuthTokenProvider) MultiTenantServicePrincipalTokenAuthorizer { return &multiTenantSPTAuthorizer{tp: tp} } type multiTenantSPTAuthorizer struct { tp adal.MultitenantOAuthTokenProvider } // WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header using the // primary token along with the auxiliary authorization header using the auxiliary tokens. // // By default, the token will be automatically refreshed through the Refresher interface. func (mt multiTenantSPTAuthorizer) WithAuthorization() PrepareDecorator { return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { r, err := p.Prepare(r) if err != nil { return r, err } if refresher, ok := mt.tp.(adal.RefresherWithContext); ok { err = refresher.EnsureFreshWithContext(r.Context()) if err != nil { var resp *http.Response if tokError, ok := err.(adal.TokenRefreshError); ok { resp = tokError.Response() } return r, NewErrorWithError(err, "azure.multiTenantSPTAuthorizer", "WithAuthorization", resp, "Failed to refresh one or more Tokens for request to %s", r.URL) } } r, err = Prepare(r, WithHeader(headerAuthorization, fmt.Sprintf("Bearer %s", mt.tp.PrimaryOAuthToken()))) if err != nil { return r, err } auxTokens := mt.tp.AuxiliaryOAuthTokens() for i := range auxTokens { auxTokens[i] = fmt.Sprintf("Bearer %s", auxTokens[i]) } return Prepare(r, WithHeader(headerAuxAuthorization, strings.Join(auxTokens, ", "))) }) } } golang-github-azure-go-autorest-14.1.1/autorest/authorization_sas.go000066400000000000000000000037671367372352400257140ustar00rootroot00000000000000package autorest // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "fmt" "net/http" "strings" ) // SASTokenAuthorizer implements an authorization for SAS Token Authentication // this can be used for interaction with Blob Storage Endpoints type SASTokenAuthorizer struct { sasToken string } // NewSASTokenAuthorizer creates a SASTokenAuthorizer using the given credentials func NewSASTokenAuthorizer(sasToken string) (*SASTokenAuthorizer, error) { if strings.TrimSpace(sasToken) == "" { return nil, fmt.Errorf("sasToken cannot be empty") } token := sasToken if strings.HasPrefix(sasToken, "?") { token = strings.TrimPrefix(sasToken, "?") } return &SASTokenAuthorizer{ sasToken: token, }, nil } // WithAuthorization returns a PrepareDecorator that adds a shared access signature token to the // URI's query parameters. This can be used for the Blob, Queue, and File Services. // // See https://docs.microsoft.com/en-us/rest/api/storageservices/delegate-access-with-shared-access-signature func (sas *SASTokenAuthorizer) WithAuthorization() PrepareDecorator { return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { r, err := p.Prepare(r) if err != nil { return r, err } if r.URL.RawQuery != "" { r.URL.RawQuery = fmt.Sprintf("%s&%s", r.URL.RawQuery, sas.sasToken) } else { r.URL.RawQuery = sas.sasToken } r.RequestURI = r.URL.String() return Prepare(r) }) } } golang-github-azure-go-autorest-14.1.1/autorest/authorization_sas_test.go000066400000000000000000000065141367372352400267440ustar00rootroot00000000000000package autorest // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "net/http" "net/url" "testing" ) func TestSasNewSasAuthorizerEmptyToken(t *testing.T) { auth, err := NewSASTokenAuthorizer("") if err == nil { t.Fatalf("azure: SASTokenAuthorizer#NewSASTokenAuthorizer didn't return an error") } if auth != nil { t.Fatalf("azure: SASTokenAuthorizer#NewSASTokenAuthorizer returned an authorizer") } } func TestSasNewSasAuthorizerEmptyTokenWithWhitespace(t *testing.T) { auth, err := NewSASTokenAuthorizer(" ") if err == nil { t.Fatalf("azure: SASTokenAuthorizer#NewSASTokenAuthorizer didn't return an error") } if auth != nil { t.Fatalf("azure: SASTokenAuthorizer#NewSASTokenAuthorizer returned an authorizer") } } func TestSasNewSasAuthorizerValidToken(t *testing.T) { auth, err := NewSASTokenAuthorizer("abc123") if err != nil { t.Fatalf("azure: SASTokenAuthorizer#NewSASTokenAuthorizer returned an error") } if auth == nil { t.Fatalf("azure: SASTokenAuthorizer#NewSASTokenAuthorizer didn't return an authorizer") } } func TestSasAuthorizerRequest(t *testing.T) { testData := []struct { name string token string input string expected string }{ { name: "empty querystring without a prefix", token: "abc123", input: "https://example.com/foo/bar", expected: "https://example.com/foo/bar?abc123", }, { name: "empty querystring with a prefix", token: "?abc123", input: "https://example.com/foo/bar", expected: "https://example.com/foo/bar?abc123", }, { name: "existing querystring without a prefix", token: "abc123", input: "https://example.com/foo/bar?hello=world", expected: "https://example.com/foo/bar?hello=world&abc123", }, { name: "existing querystring with a prefix", token: "?abc123", input: "https://example.com/foo/bar?hello=world", expected: "https://example.com/foo/bar?hello=world&abc123", }, } for _, v := range testData { t.Logf("[DEBUG] Testing Case %q..", v.name) auth, err := NewSASTokenAuthorizer(v.token) if err != nil { t.Fatalf("azure: SASTokenAuthorizer#WithAuthorization expected %q but got an error", v.expected) } url, _ := url.ParseRequestURI(v.input) httpReq := &http.Request{ URL: url, } req, err := Prepare(httpReq, auth.WithAuthorization()) if err != nil { t.Fatalf("azure: SASTokenAuthorizer#WithAuthorization returned an error (%v)", err) } if req.RequestURI != v.expected { t.Fatalf("azure: SASTokenAuthorizer#WithAuthorization failed to set QueryString header - got %q but expected %q", req.RequestURI, v.expected) } if req.Header.Get(http.CanonicalHeaderKey("Authorization")) != "" { t.Fatal("azure: SASTokenAuthorizer#WithAuthorization set an Authorization header when it shouldn't!") } } } golang-github-azure-go-autorest-14.1.1/autorest/authorization_storage.go000066400000000000000000000220351367372352400265570ustar00rootroot00000000000000package autorest // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "bytes" "crypto/hmac" "crypto/sha256" "encoding/base64" "fmt" "net/http" "net/url" "sort" "strings" "time" ) // SharedKeyType defines the enumeration for the various shared key types. // See https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key for details on the shared key types. type SharedKeyType string const ( // SharedKey is used to authorize against blobs, files and queues services. SharedKey SharedKeyType = "sharedKey" // SharedKeyForTable is used to authorize against the table service. SharedKeyForTable SharedKeyType = "sharedKeyTable" // SharedKeyLite is used to authorize against blobs, files and queues services. It's provided for // backwards compatibility with API versions before 2009-09-19. Prefer SharedKey instead. SharedKeyLite SharedKeyType = "sharedKeyLite" // SharedKeyLiteForTable is used to authorize against the table service. It's provided for // backwards compatibility with older table API versions. Prefer SharedKeyForTable instead. SharedKeyLiteForTable SharedKeyType = "sharedKeyLiteTable" ) const ( headerAccept = "Accept" headerAcceptCharset = "Accept-Charset" headerContentEncoding = "Content-Encoding" headerContentLength = "Content-Length" headerContentMD5 = "Content-MD5" headerContentLanguage = "Content-Language" headerIfModifiedSince = "If-Modified-Since" headerIfMatch = "If-Match" headerIfNoneMatch = "If-None-Match" headerIfUnmodifiedSince = "If-Unmodified-Since" headerDate = "Date" headerXMSDate = "X-Ms-Date" headerXMSVersion = "x-ms-version" headerRange = "Range" ) const storageEmulatorAccountName = "devstoreaccount1" // SharedKeyAuthorizer implements an authorization for Shared Key // this can be used for interaction with Blob, File and Queue Storage Endpoints type SharedKeyAuthorizer struct { accountName string accountKey []byte keyType SharedKeyType } // NewSharedKeyAuthorizer creates a SharedKeyAuthorizer using the provided credentials and shared key type. func NewSharedKeyAuthorizer(accountName, accountKey string, keyType SharedKeyType) (*SharedKeyAuthorizer, error) { key, err := base64.StdEncoding.DecodeString(accountKey) if err != nil { return nil, fmt.Errorf("malformed storage account key: %v", err) } return &SharedKeyAuthorizer{ accountName: accountName, accountKey: key, keyType: keyType, }, nil } // WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose // value is " " followed by the computed key. // This can be used for the Blob, Queue, and File Services // // from: https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key // You may use Shared Key authorization to authorize a request made against the // 2009-09-19 version and later of the Blob and Queue services, // and version 2014-02-14 and later of the File services. func (sk *SharedKeyAuthorizer) WithAuthorization() PrepareDecorator { return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { r, err := p.Prepare(r) if err != nil { return r, err } sk, err := buildSharedKey(sk.accountName, sk.accountKey, r, sk.keyType) if err != nil { return r, err } return Prepare(r, WithHeader(headerAuthorization, sk)) }) } } func buildSharedKey(accName string, accKey []byte, req *http.Request, keyType SharedKeyType) (string, error) { canRes, err := buildCanonicalizedResource(accName, req.URL.String(), keyType) if err != nil { return "", err } if req.Header == nil { req.Header = http.Header{} } // ensure date is set if req.Header.Get(headerDate) == "" && req.Header.Get(headerXMSDate) == "" { date := time.Now().UTC().Format(http.TimeFormat) req.Header.Set(headerXMSDate, date) } canString, err := buildCanonicalizedString(req.Method, req.Header, canRes, keyType) if err != nil { return "", err } return createAuthorizationHeader(accName, accKey, canString, keyType), nil } func buildCanonicalizedResource(accountName, uri string, keyType SharedKeyType) (string, error) { errMsg := "buildCanonicalizedResource error: %s" u, err := url.Parse(uri) if err != nil { return "", fmt.Errorf(errMsg, err.Error()) } cr := bytes.NewBufferString("") if accountName != storageEmulatorAccountName { cr.WriteString("/") cr.WriteString(getCanonicalizedAccountName(accountName)) } if len(u.Path) > 0 { // Any portion of the CanonicalizedResource string that is derived from // the resource's URI should be encoded exactly as it is in the URI. // -- https://msdn.microsoft.com/en-gb/library/azure/dd179428.aspx cr.WriteString(u.EscapedPath()) } params, err := url.ParseQuery(u.RawQuery) if err != nil { return "", fmt.Errorf(errMsg, err.Error()) } // See https://github.com/Azure/azure-storage-net/blob/master/Lib/Common/Core/Util/AuthenticationUtility.cs#L277 if keyType == SharedKey { if len(params) > 0 { cr.WriteString("\n") keys := []string{} for key := range params { keys = append(keys, key) } sort.Strings(keys) completeParams := []string{} for _, key := range keys { if len(params[key]) > 1 { sort.Strings(params[key]) } completeParams = append(completeParams, fmt.Sprintf("%s:%s", key, strings.Join(params[key], ","))) } cr.WriteString(strings.Join(completeParams, "\n")) } } else { // search for "comp" parameter, if exists then add it to canonicalizedresource if v, ok := params["comp"]; ok { cr.WriteString("?comp=" + v[0]) } } return string(cr.Bytes()), nil } func getCanonicalizedAccountName(accountName string) string { // since we may be trying to access a secondary storage account, we need to // remove the -secondary part of the storage name return strings.TrimSuffix(accountName, "-secondary") } func buildCanonicalizedString(verb string, headers http.Header, canonicalizedResource string, keyType SharedKeyType) (string, error) { contentLength := headers.Get(headerContentLength) if contentLength == "0" { contentLength = "" } date := headers.Get(headerDate) if v := headers.Get(headerXMSDate); v != "" { if keyType == SharedKey || keyType == SharedKeyLite { date = "" } else { date = v } } var canString string switch keyType { case SharedKey: canString = strings.Join([]string{ verb, headers.Get(headerContentEncoding), headers.Get(headerContentLanguage), contentLength, headers.Get(headerContentMD5), headers.Get(headerContentType), date, headers.Get(headerIfModifiedSince), headers.Get(headerIfMatch), headers.Get(headerIfNoneMatch), headers.Get(headerIfUnmodifiedSince), headers.Get(headerRange), buildCanonicalizedHeader(headers), canonicalizedResource, }, "\n") case SharedKeyForTable: canString = strings.Join([]string{ verb, headers.Get(headerContentMD5), headers.Get(headerContentType), date, canonicalizedResource, }, "\n") case SharedKeyLite: canString = strings.Join([]string{ verb, headers.Get(headerContentMD5), headers.Get(headerContentType), date, buildCanonicalizedHeader(headers), canonicalizedResource, }, "\n") case SharedKeyLiteForTable: canString = strings.Join([]string{ date, canonicalizedResource, }, "\n") default: return "", fmt.Errorf("key type '%s' is not supported", keyType) } return canString, nil } func buildCanonicalizedHeader(headers http.Header) string { cm := make(map[string]string) for k := range headers { headerName := strings.TrimSpace(strings.ToLower(k)) if strings.HasPrefix(headerName, "x-ms-") { cm[headerName] = headers.Get(k) } } if len(cm) == 0 { return "" } keys := []string{} for key := range cm { keys = append(keys, key) } sort.Strings(keys) ch := bytes.NewBufferString("") for _, key := range keys { ch.WriteString(key) ch.WriteRune(':') ch.WriteString(cm[key]) ch.WriteRune('\n') } return strings.TrimSuffix(string(ch.Bytes()), "\n") } func createAuthorizationHeader(accountName string, accountKey []byte, canonicalizedString string, keyType SharedKeyType) string { h := hmac.New(sha256.New, accountKey) h.Write([]byte(canonicalizedString)) signature := base64.StdEncoding.EncodeToString(h.Sum(nil)) var key string switch keyType { case SharedKey, SharedKeyForTable: key = "SharedKey" case SharedKeyLite, SharedKeyLiteForTable: key = "SharedKeyLite" } return fmt.Sprintf("%s %s:%s", key, getCanonicalizedAccountName(accountName), signature) } golang-github-azure-go-autorest-14.1.1/autorest/authorization_storage_test.go000066400000000000000000000113461367372352400276210ustar00rootroot00000000000000package autorest // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "net/http" "testing" ) func TestNewSharedKeyAuthorizer(t *testing.T) { auth, err := NewSharedKeyAuthorizer("golangrocksonazure", "YmFy", SharedKey) if err != nil { t.Fatalf("create shared key authorizer: %v", err) } req, err := http.NewRequest(http.MethodGet, "https://golangrocksonazure.blob.core.windows.net/some/blob.dat", nil) if err != nil { t.Fatalf("create HTTP request: %v", err) } req.Header.Add(headerAcceptCharset, "UTF-8") req.Header.Add(headerContentType, "application/json") req.Header.Add(headerXMSDate, "Wed, 23 Sep 2015 16:40:05 GMT") req.Header.Add(headerContentLength, "0") req.Header.Add(headerXMSVersion, "2015-02-21") req.Header.Add(headerAccept, "application/json;odata=nometadata") req, err = Prepare(req, auth.WithAuthorization()) if err != nil { t.Fatalf("prepare HTTP request: %v", err) } const expected = "SharedKey golangrocksonazure:nYRqgbumDOTPs+Vv1FLH+hm0KPjwwt+Fmj/i16W+lO0=" if auth := req.Header.Get(headerAuthorization); auth != expected { t.Fatalf("expected: %s, go %s", expected, auth) } } func TestNewSharedKeyForTableAuthorizer(t *testing.T) { auth, err := NewSharedKeyAuthorizer("golangrocksonazure", "YmFy", SharedKeyForTable) if err != nil { t.Fatalf("create shared key authorizer: %v", err) } req, err := http.NewRequest(http.MethodGet, "https://golangrocksonazure.table.core.windows.net/tquery()", nil) if err != nil { t.Fatalf("create HTTP request: %v", err) } req.Header.Add(headerAcceptCharset, "UTF-8") req.Header.Add(headerContentType, "application/json") req.Header.Add(headerXMSDate, "Wed, 23 Sep 2015 16:40:05 GMT") req.Header.Add(headerContentLength, "0") req.Header.Add(headerXMSVersion, "2015-02-21") req.Header.Add(headerAccept, "application/json;odata=nometadata") req, err = Prepare(req, auth.WithAuthorization()) if err != nil { t.Fatalf("prepare HTTP request: %v", err) } const expected = "SharedKey golangrocksonazure:73oeIBA2dulLhOBdAlM3U0+DKIWS0UW6InBWCHpOY50=" if auth := req.Header.Get(headerAuthorization); auth != expected { t.Fatalf("expected: %s, go %s", expected, auth) } } func TestNewSharedKeyLiteAuthorizer(t *testing.T) { auth, err := NewSharedKeyAuthorizer("golangrocksonazure", "YmFy", SharedKeyLite) if err != nil { t.Fatalf("create shared key authorizer: %v", err) } req, err := http.NewRequest(http.MethodGet, "https://golangrocksonazure.file.core.windows.net/some/file.dat", nil) if err != nil { t.Fatalf("create HTTP request: %v", err) } req.Header.Add(headerAcceptCharset, "UTF-8") req.Header.Add(headerContentType, "application/json") req.Header.Add(headerXMSDate, "Wed, 23 Sep 2015 16:40:05 GMT") req.Header.Add(headerContentLength, "0") req.Header.Add(headerXMSVersion, "2015-02-21") req.Header.Add(headerAccept, "application/json;odata=nometadata") req, err = Prepare(req, auth.WithAuthorization()) if err != nil { t.Fatalf("prepare HTTP request: %v", err) } const expected = "SharedKeyLite golangrocksonazure:0VODf/mHRDa7lMShzTKbow7lxptaIZ0qIAcVD0lG9PE=" if auth := req.Header.Get(headerAuthorization); auth != expected { t.Fatalf("expected: %s, go %s", expected, auth) } } func TestNewSharedKeyLiteForTableAuthorizer(t *testing.T) { auth, err := NewSharedKeyAuthorizer("golangrocksonazure", "YmFy", SharedKeyLiteForTable) if err != nil { t.Fatalf("create shared key authorizer: %v", err) } req, err := http.NewRequest(http.MethodGet, "https://golangrocksonazure.table.core.windows.net/tquery()", nil) if err != nil { t.Fatalf("create HTTP request: %v", err) } req.Header.Add(headerAcceptCharset, "UTF-8") req.Header.Add(headerContentType, "application/json") req.Header.Add(headerXMSDate, "Wed, 23 Sep 2015 16:40:05 GMT") req.Header.Add(headerContentLength, "0") req.Header.Add(headerXMSVersion, "2015-02-21") req.Header.Add(headerAccept, "application/json;odata=nometadata") req, err = Prepare(req, auth.WithAuthorization()) if err != nil { t.Fatalf("prepare HTTP request: %v", err) } const expected = "SharedKeyLite golangrocksonazure:NusXSFXAvHqr6EQNXnZZ50CvU1sX0iP/FFDHehnixLc=" if auth := req.Header.Get(headerAuthorization); auth != expected { t.Fatalf("expected: %s, go %s", expected, auth) } } golang-github-azure-go-autorest-14.1.1/autorest/authorization_test.go000066400000000000000000000400041367372352400260660ustar00rootroot00000000000000package autorest // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "fmt" "net/http" "reflect" "strings" "testing" "github.com/Azure/go-autorest/autorest/adal" "github.com/Azure/go-autorest/autorest/mocks" ) const ( TestTenantID = "TestTenantID" TestAuxTenent1 = "aux1" TestAuxTenent2 = "aux2" TestAuxTenent3 = "aux3" TestActiveDirectoryEndpoint = "https://login/test.com/" ) func TestWithAuthorizer(t *testing.T) { r1 := mocks.NewRequest() na := &NullAuthorizer{} r2, err := Prepare(r1, na.WithAuthorization()) if err != nil { t.Fatalf("autorest: NullAuthorizer#WithAuthorization returned an unexpected error (%v)", err) } else if !reflect.DeepEqual(r1, r2) { t.Fatalf("autorest: NullAuthorizer#WithAuthorization modified the request -- received %v, expected %v", r2, r1) } } func TestTokenWithAuthorization(t *testing.T) { token := &adal.Token{ AccessToken: "TestToken", Resource: "https://azure.microsoft.com/", Type: "Bearer", } ba := NewBearerAuthorizer(token) req, err := Prepare(&http.Request{}, ba.WithAuthorization()) if err != nil { t.Fatalf("azure: BearerAuthorizer#WithAuthorization returned an error (%v)", err) } else if req.Header.Get(http.CanonicalHeaderKey("Authorization")) != fmt.Sprintf("Bearer %s", token.AccessToken) { t.Fatal("azure: BearerAuthorizer#WithAuthorization failed to set Authorization header") } } func TestServicePrincipalTokenWithAuthorizationNoRefresh(t *testing.T) { oauthConfig, err := adal.NewOAuthConfig(TestActiveDirectoryEndpoint, TestTenantID) if err != nil { t.Fatalf("azure: BearerAuthorizer#WithAuthorization returned an error (%v)", err) } spt, err := adal.NewServicePrincipalToken(*oauthConfig, "id", "secret", "resource", nil) if err != nil { t.Fatalf("azure: BearerAuthorizer#WithAuthorization returned an error (%v)", err) } spt.SetAutoRefresh(false) s := mocks.NewSender() spt.SetSender(s) ba := NewBearerAuthorizer(spt) req, err := Prepare(mocks.NewRequest(), ba.WithAuthorization()) if err != nil { t.Fatalf("azure: BearerAuthorizer#WithAuthorization returned an error (%v)", err) } else if req.Header.Get(http.CanonicalHeaderKey("Authorization")) != fmt.Sprintf("Bearer %s", spt.OAuthToken()) { t.Fatal("azure: BearerAuthorizer#WithAuthorization failed to set Authorization header") } } func TestServicePrincipalTokenWithAuthorizationRefresh(t *testing.T) { oauthConfig, err := adal.NewOAuthConfig(TestActiveDirectoryEndpoint, TestTenantID) if err != nil { t.Fatalf("azure: BearerAuthorizer#WithAuthorization returned an error (%v)", err) } refreshed := false spt, err := adal.NewServicePrincipalToken(*oauthConfig, "id", "secret", "resource", func(t adal.Token) error { refreshed = true return nil }) if err != nil { t.Fatalf("azure: BearerAuthorizer#WithAuthorization returned an error (%v)", err) } jwt := `{ "access_token" : "accessToken", "expires_in" : "3600", "expires_on" : "12345", "not_before" : "67890", "resource" : "test", "token_type" : "Bearer" }` body := mocks.NewBody(jwt) resp := mocks.NewResponseWithBodyAndStatus(body, http.StatusOK, "OK") c := mocks.NewSender() s := DecorateSender(c, (func() SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { return resp, nil }) } })()) spt.SetSender(s) ba := NewBearerAuthorizer(spt) req, err := Prepare(mocks.NewRequest(), ba.WithAuthorization()) if err != nil { t.Fatalf("azure: BearerAuthorizer#WithAuthorization returned an error (%v)", err) } else if req.Header.Get(http.CanonicalHeaderKey("Authorization")) != fmt.Sprintf("Bearer %s", spt.OAuthToken()) { t.Fatal("azure: BearerAuthorizer#WithAuthorization failed to set Authorization header") } if !refreshed { t.Fatal("azure: BearerAuthorizer#WithAuthorization must refresh the token") } } func TestServicePrincipalTokenWithAuthorizationReturnsErrorIfConnotRefresh(t *testing.T) { oauthConfig, err := adal.NewOAuthConfig(TestActiveDirectoryEndpoint, TestTenantID) if err != nil { t.Fatalf("azure: BearerAuthorizer#WithAuthorization returned an error (%v)", err) } spt, err := adal.NewServicePrincipalToken(*oauthConfig, "id", "secret", "resource", nil) if err != nil { t.Fatalf("azure: BearerAuthorizer#WithAuthorization returned an error (%v)", err) } s := mocks.NewSender() s.AppendResponse(mocks.NewResponseWithStatus("400 Bad Request", http.StatusBadRequest)) spt.SetSender(s) ba := NewBearerAuthorizer(spt) _, err = Prepare(mocks.NewRequest(), ba.WithAuthorization()) if err == nil { t.Fatal("azure: BearerAuthorizer#WithAuthorization failed to return an error when refresh fails") } } func TestBearerAuthorizerCallback(t *testing.T) { tenantString := "123-tenantID-456" resourceString := "https://fake.resource.net" s := mocks.NewSender() resp := mocks.NewResponseWithStatus("401 Unauthorized", http.StatusUnauthorized) mocks.SetResponseHeader(resp, bearerChallengeHeader, bearer+" \"authorization\"=\"https://fake.net/"+tenantString+"\",\"resource\"=\""+resourceString+"\"") s.AppendResponse(resp) auth := NewBearerAuthorizerCallback(s, func(tenantID, resource string) (*BearerAuthorizer, error) { if tenantID != tenantString { t.Fatal("BearerAuthorizerCallback: bad tenant ID") } if resource != resourceString { t.Fatal("BearerAuthorizerCallback: bad resource") } oauthConfig, err := adal.NewOAuthConfig(TestActiveDirectoryEndpoint, tenantID) if err != nil { t.Fatalf("azure: NewOAuthConfig returned an error (%v)", err) } spt, err := adal.NewServicePrincipalToken(*oauthConfig, "id", "secret", resource) if err != nil { t.Fatalf("azure: NewServicePrincipalToken returned an error (%v)", err) } spt.SetSender(s) return NewBearerAuthorizer(spt), nil }) _, err := Prepare(mocks.NewRequest(), auth.WithAuthorization()) if err == nil { t.Fatal("azure: BearerAuthorizerCallback#WithAuthorization failed to return an error when refresh fails") } } func TestApiKeyAuthorization(t *testing.T) { headers := make(map[string]interface{}) queryParameters := make(map[string]interface{}) dummyAuthHeader := "dummyAuthHeader" dummyAuthHeaderValue := "dummyAuthHeaderValue" dummyAuthQueryParameter := "dummyAuthQueryParameter" dummyAuthQueryParameterValue := "dummyAuthQueryParameterValue" headers[dummyAuthHeader] = dummyAuthHeaderValue queryParameters[dummyAuthQueryParameter] = dummyAuthQueryParameterValue aka := NewAPIKeyAuthorizer(headers, queryParameters) req, err := Prepare(mocks.NewRequest(), aka.WithAuthorization()) if err != nil { t.Fatalf("azure: APIKeyAuthorizer#WithAuthorization returned an error (%v)", err) } else if req.Header.Get(http.CanonicalHeaderKey(dummyAuthHeader)) != dummyAuthHeaderValue { t.Fatalf("azure: APIKeyAuthorizer#WithAuthorization failed to set %s header", dummyAuthHeader) } else if req.URL.Query().Get(dummyAuthQueryParameter) != dummyAuthQueryParameterValue { t.Fatalf("azure: APIKeyAuthorizer#WithAuthorization failed to set %s query parameter", dummyAuthQueryParameterValue) } } func TestCognitivesServicesAuthorization(t *testing.T) { subscriptionKey := "dummyKey" csa := NewCognitiveServicesAuthorizer(subscriptionKey) req, err := Prepare(mocks.NewRequest(), csa.WithAuthorization()) if err != nil { t.Fatalf("azure: CognitiveServicesAuthorizer#WithAuthorization returned an error (%v)", err) } else if req.Header.Get(http.CanonicalHeaderKey(bingAPISdkHeader)) != golangBingAPISdkHeaderValue { t.Fatalf("azure: CognitiveServicesAuthorizer#WithAuthorization failed to set %s header", bingAPISdkHeader) } else if req.Header.Get(http.CanonicalHeaderKey(apiKeyAuthorizerHeader)) != subscriptionKey { t.Fatalf("azure: CognitiveServicesAuthorizer#WithAuthorization failed to set %s header", apiKeyAuthorizerHeader) } } func TestBasicAuthorization(t *testing.T) { ba := NewBasicAuthorizer("Aladdin", "open sesame") req, err := Prepare(mocks.NewRequest(), ba.WithAuthorization()) if err != nil { t.Fatalf("BasicAuthorizer#WithAuthorization returned an error (%v)", err) } else if req.Header.Get(http.CanonicalHeaderKey(authorization)) != basic+" QWxhZGRpbjpvcGVuIHNlc2FtZQ==" { t.Fatalf("BasicAuthorizer#WithAuthorization failed to set %s header", authorization) } } func TestBasicAuthorizationPasswordOnly(t *testing.T) { ba := NewBasicAuthorizer("", "dummyKey") req, err := Prepare(mocks.NewRequest(), ba.WithAuthorization()) if err != nil { t.Fatalf("BasicAuthorizer#WithAuthorization returned an error (%v)", err) } else if req.Header.Get(http.CanonicalHeaderKey(authorization)) != basic+" OmR1bW15S2V5" { t.Fatalf("BasicAuthorizer#WithAuthorization failed to set %s header", authorization) } } type mockMTSPTProvider struct { p string a []string } func (m mockMTSPTProvider) PrimaryOAuthToken() string { return m.p } func (m mockMTSPTProvider) AuxiliaryOAuthTokens() []string { return m.a } func TestMultitenantAuthorizationOne(t *testing.T) { mtSPTProvider := mockMTSPTProvider{ p: "primary", a: []string{TestAuxTenent1}, } mt := NewMultiTenantServicePrincipalTokenAuthorizer(mtSPTProvider) req, err := Prepare(mocks.NewRequest(), mt.WithAuthorization()) if err != nil { t.Fatalf("unexpected error: %v", err) } if primary := req.Header.Get(headerAuthorization); primary != "Bearer primary" { t.Fatalf("bad primary authorization header %s", primary) } if aux := req.Header.Get(headerAuxAuthorization); aux != "Bearer aux1" { t.Fatalf("bad auxiliary authorization header %s", aux) } } func TestMultitenantAuthorizationThree(t *testing.T) { mtSPTProvider := mockMTSPTProvider{ p: "primary", a: []string{TestAuxTenent1, TestAuxTenent2, TestAuxTenent3}, } mt := NewMultiTenantServicePrincipalTokenAuthorizer(mtSPTProvider) req, err := Prepare(mocks.NewRequest(), mt.WithAuthorization()) if err != nil { t.Fatalf("unexpected error: %v", err) } if primary := req.Header.Get(headerAuthorization); primary != "Bearer primary" { t.Fatalf("bad primary authorization header %s", primary) } if aux := req.Header.Get(headerAuxAuthorization); aux != "Bearer aux1, Bearer aux2, Bearer aux3" { t.Fatalf("bad auxiliary authorization header %s", aux) } } func TestMultiTenantServicePrincipalTokenWithAuthorizationRefresh(t *testing.T) { multiTenantCfg, err := adal.NewMultiTenantOAuthConfig(TestActiveDirectoryEndpoint, TestTenantID, []string{TestAuxTenent1, TestAuxTenent2, TestAuxTenent3}, adal.OAuthOptions{}) if err != nil { t.Fatalf("azure: adal#NewMultiTenantOAuthConfig returned an error (%v)", err) } mtSpt, err := adal.NewMultiTenantServicePrincipalToken(multiTenantCfg, "id", "secret", "resource") if err != nil { t.Fatalf("azure: adal#NewMultiTenantServicePrincipalToken returned an error (%v)", err) } primaryToken := `{ "access_token" : "primary token refreshed", "expires_in" : "3600", "expires_on" : "12345", "not_before" : "67890", "resource" : "test", "token_type" : "Bearer" }` auxToken1 := `{ "access_token" : "aux token 1 refreshed", "expires_in" : "3600", "expires_on" : "12345", "not_before" : "67890", "resource" : "test", "token_type" : "Bearer" }` auxToken2 := `{ "access_token" : "aux token 2 refreshed", "expires_in" : "3600", "expires_on" : "12345", "not_before" : "67890", "resource" : "test", "token_type" : "Bearer" }` auxToken3 := `{ "access_token" : "aux token 3 refreshed", "expires_in" : "3600", "expires_on" : "12345", "not_before" : "67890", "resource" : "test", "token_type" : "Bearer" }` s := mocks.NewSender() s.AppendResponse(mocks.NewResponseWithBodyAndStatus(mocks.NewBody(primaryToken), http.StatusOK, "OK")) s.AppendResponse(mocks.NewResponseWithBodyAndStatus(mocks.NewBody(auxToken1), http.StatusOK, "OK")) s.AppendResponse(mocks.NewResponseWithBodyAndStatus(mocks.NewBody(auxToken2), http.StatusOK, "OK")) s.AppendResponse(mocks.NewResponseWithBodyAndStatus(mocks.NewBody(auxToken3), http.StatusOK, "OK")) mtSpt.PrimaryToken.SetSender(s) for _, aux := range mtSpt.AuxiliaryTokens { aux.SetSender(s) } mta := NewMultiTenantServicePrincipalTokenAuthorizer(mtSpt) req, err := Prepare(mocks.NewRequest(), mta.WithAuthorization()) if err != nil { t.Fatalf("azure: multiTenantSPTAuthorizer#WithAuthorization returned an error (%v)", err) } if ah := req.Header.Get(http.CanonicalHeaderKey("Authorization")); ah != fmt.Sprintf("Bearer %s", mtSpt.PrimaryOAuthToken()) { t.Fatal("azure: multiTenantSPTAuthorizer#WithAuthorization failed to set Authorization header for primary token") } else if ah != "Bearer primary token refreshed" { t.Fatal("azure: multiTenantSPTAuthorizer#WithAuthorization primary token value doesn't match") } auxTokens := mtSpt.AuxiliaryOAuthTokens() for i := range auxTokens { auxTokens[i] = fmt.Sprintf("Bearer %s", auxTokens[i]) } auxHeader := req.Header.Get(http.CanonicalHeaderKey(headerAuxAuthorization)) if auxHeader != strings.Join(auxTokens, ", ") { t.Fatal("azure: multiTenantSPTAuthorizer#WithAuthorization failed to set Authorization header for auxiliary tokens") } for i := range auxTokens { if auxTokens[i] != fmt.Sprintf("Bearer aux token %d refreshed", i+1) { t.Fatal("azure: multiTenantSPTAuthorizer#WithAuthorization auxiliary token value doesn't match") } } } func TestMultiTenantServicePrincipalTokenWithAuthorizationRefreshFail1(t *testing.T) { multiTenantCfg, err := adal.NewMultiTenantOAuthConfig(TestActiveDirectoryEndpoint, TestTenantID, []string{TestAuxTenent1, TestAuxTenent2, TestAuxTenent3}, adal.OAuthOptions{}) if err != nil { t.Fatalf("azure: adal#NewMultiTenantOAuthConfig returned an error (%v)", err) } mtSpt, err := adal.NewMultiTenantServicePrincipalToken(multiTenantCfg, "id", "secret", "resource") if err != nil { t.Fatalf("azure: adal#NewMultiTenantServicePrincipalToken returned an error (%v)", err) } s := mocks.NewSender() s.AppendResponse(mocks.NewResponseWithStatus("access denied", http.StatusForbidden)) mtSpt.PrimaryToken.SetSender(s) for _, aux := range mtSpt.AuxiliaryTokens { aux.SetSender(s) } mta := NewMultiTenantServicePrincipalTokenAuthorizer(mtSpt) _, err = Prepare(mocks.NewRequest(), mta.WithAuthorization()) if err == nil { t.Fatalf("azure: multiTenantSPTAuthorizer#WithAuthorization unexpected nil error") } } func TestMultiTenantServicePrincipalTokenWithAuthorizationRefreshFail2(t *testing.T) { multiTenantCfg, err := adal.NewMultiTenantOAuthConfig(TestActiveDirectoryEndpoint, TestTenantID, []string{TestAuxTenent1, TestAuxTenent2, TestAuxTenent3}, adal.OAuthOptions{}) if err != nil { t.Fatalf("azure: adal#NewMultiTenantOAuthConfig returned an error (%v)", err) } mtSpt, err := adal.NewMultiTenantServicePrincipalToken(multiTenantCfg, "id", "secret", "resource") if err != nil { t.Fatalf("azure: adal#NewMultiTenantServicePrincipalToken returned an error (%v)", err) } primaryToken := `{ "access_token" : "primary token refreshed", "expires_in" : "3600", "expires_on" : "test", "not_before" : "test", "resource" : "test", "token_type" : "Bearer" }` s := mocks.NewSender() s.AppendResponse(mocks.NewResponseWithBodyAndStatus(mocks.NewBody(primaryToken), http.StatusOK, "OK")) s.AppendResponse(mocks.NewResponseWithStatus("access denied", http.StatusForbidden)) mtSpt.PrimaryToken.SetSender(s) for _, aux := range mtSpt.AuxiliaryTokens { aux.SetSender(s) } mta := NewMultiTenantServicePrincipalTokenAuthorizer(mtSpt) _, err = Prepare(mocks.NewRequest(), mta.WithAuthorization()) if err == nil { t.Fatalf("azure: multiTenantSPTAuthorizer#WithAuthorization unexpected nil error") } } golang-github-azure-go-autorest-14.1.1/autorest/autorest.go000066400000000000000000000127441367372352400240070ustar00rootroot00000000000000/* Package autorest implements an HTTP request pipeline suitable for use across multiple go-routines and provides the shared routines relied on by AutoRest (see https://github.com/Azure/autorest/) generated Go code. The package breaks sending and responding to HTTP requests into three phases: Preparing, Sending, and Responding. A typical pattern is: req, err := Prepare(&http.Request{}, token.WithAuthorization()) resp, err := Send(req, WithLogging(logger), DoErrorIfStatusCode(http.StatusInternalServerError), DoCloseIfError(), DoRetryForAttempts(5, time.Second)) err = Respond(resp, ByDiscardingBody(), ByClosing()) Each phase relies on decorators to modify and / or manage processing. Decorators may first modify and then pass the data along, pass the data first and then modify the result, or wrap themselves around passing the data (such as a logger might do). Decorators run in the order provided. For example, the following: req, err := Prepare(&http.Request{}, WithBaseURL("https://microsoft.com/"), WithPath("a"), WithPath("b"), WithPath("c")) will set the URL to: https://microsoft.com/a/b/c Preparers and Responders may be shared and re-used (assuming the underlying decorators support sharing and re-use). Performant use is obtained by creating one or more Preparers and Responders shared among multiple go-routines, and a single Sender shared among multiple sending go-routines, all bound together by means of input / output channels. Decorators hold their passed state within a closure (such as the path components in the example above). Be careful to share Preparers and Responders only in a context where such held state applies. For example, it may not make sense to share a Preparer that applies a query string from a fixed set of values. Similarly, sharing a Responder that reads the response body into a passed struct (e.g., ByUnmarshallingJson) is likely incorrect. Lastly, the Swagger specification (https://swagger.io) that drives AutoRest (https://github.com/Azure/autorest/) precisely defines two date forms: date and date-time. The github.com/Azure/go-autorest/autorest/date package provides time.Time derivations to ensure correct parsing and formatting. Errors raised by autorest objects and methods will conform to the autorest.Error interface. See the included examples for more detail. For details on the suggested use of this package by generated clients, see the Client described below. */ package autorest // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "context" "net/http" "time" ) const ( // HeaderLocation specifies the HTTP Location header. HeaderLocation = "Location" // HeaderRetryAfter specifies the HTTP Retry-After header. HeaderRetryAfter = "Retry-After" ) // ResponseHasStatusCode returns true if the status code in the HTTP Response is in the passed set // and false otherwise. func ResponseHasStatusCode(resp *http.Response, codes ...int) bool { if resp == nil { return false } return containsInt(codes, resp.StatusCode) } // GetLocation retrieves the URL from the Location header of the passed response. func GetLocation(resp *http.Response) string { return resp.Header.Get(HeaderLocation) } // GetRetryAfter extracts the retry delay from the Retry-After header of the passed response. If // the header is absent or is malformed, it will return the supplied default delay time.Duration. func GetRetryAfter(resp *http.Response, defaultDelay time.Duration) time.Duration { retry := resp.Header.Get(HeaderRetryAfter) if retry == "" { return defaultDelay } d, err := time.ParseDuration(retry + "s") if err != nil { return defaultDelay } return d } // NewPollingRequest allocates and returns a new http.Request to poll for the passed response. func NewPollingRequest(resp *http.Response, cancel <-chan struct{}) (*http.Request, error) { location := GetLocation(resp) if location == "" { return nil, NewErrorWithResponse("autorest", "NewPollingRequest", resp, "Location header missing from response that requires polling") } req, err := Prepare(&http.Request{Cancel: cancel}, AsGet(), WithBaseURL(location)) if err != nil { return nil, NewErrorWithError(err, "autorest", "NewPollingRequest", nil, "Failure creating poll request to %s", location) } return req, nil } // NewPollingRequestWithContext allocates and returns a new http.Request with the specified context to poll for the passed response. func NewPollingRequestWithContext(ctx context.Context, resp *http.Response) (*http.Request, error) { location := GetLocation(resp) if location == "" { return nil, NewErrorWithResponse("autorest", "NewPollingRequestWithContext", resp, "Location header missing from response that requires polling") } req, err := Prepare((&http.Request{}).WithContext(ctx), AsGet(), WithBaseURL(location)) if err != nil { return nil, NewErrorWithError(err, "autorest", "NewPollingRequestWithContext", nil, "Failure creating poll request to %s", location) } return req, nil } golang-github-azure-go-autorest-14.1.1/autorest/autorest_test.go000066400000000000000000000120031367372352400250320ustar00rootroot00000000000000package autorest // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "net/http" "testing" "github.com/Azure/go-autorest/autorest/mocks" ) func TestResponseHasStatusCode(t *testing.T) { codes := []int{http.StatusOK, http.StatusAccepted} resp := &http.Response{StatusCode: http.StatusAccepted} if !ResponseHasStatusCode(resp, codes...) { t.Fatalf("autorest: ResponseHasStatusCode failed to find %v in %v", resp.StatusCode, codes) } } func TestResponseHasStatusCodeNotPresent(t *testing.T) { codes := []int{http.StatusOK, http.StatusAccepted} resp := &http.Response{StatusCode: http.StatusInternalServerError} if ResponseHasStatusCode(resp, codes...) { t.Fatalf("autorest: ResponseHasStatusCode unexpectedly found %v in %v", resp.StatusCode, codes) } } func TestNewPollingRequestDoesNotReturnARequestWhenLocationHeaderIsMissing(t *testing.T) { resp := mocks.NewResponseWithStatus("500 InternalServerError", http.StatusInternalServerError) req, _ := NewPollingRequest(resp, nil) if req != nil { t.Fatal("autorest: NewPollingRequest returned an http.Request when the Location header was missing") } } func TestNewPollingRequestReturnsAnErrorWhenPrepareFails(t *testing.T) { resp := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted) mocks.SetAcceptedHeaders(resp) resp.Header.Set(http.CanonicalHeaderKey(HeaderLocation), mocks.TestBadURL) _, err := NewPollingRequest(resp, nil) if err == nil { t.Fatal("autorest: NewPollingRequest failed to return an error when Prepare fails") } } func TestNewPollingRequestDoesNotReturnARequestWhenPrepareFails(t *testing.T) { resp := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted) mocks.SetAcceptedHeaders(resp) resp.Header.Set(http.CanonicalHeaderKey(HeaderLocation), mocks.TestBadURL) req, _ := NewPollingRequest(resp, nil) if req != nil { t.Fatal("autorest: NewPollingRequest returned an http.Request when Prepare failed") } } func TestNewPollingRequestReturnsAGetRequest(t *testing.T) { resp := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted) mocks.SetAcceptedHeaders(resp) req, _ := NewPollingRequest(resp, nil) if req.Method != "GET" { t.Fatalf("autorest: NewPollingRequest did not create an HTTP GET request -- actual method %v", req.Method) } } func TestNewPollingRequestProvidesTheURL(t *testing.T) { resp := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted) mocks.SetAcceptedHeaders(resp) req, _ := NewPollingRequest(resp, nil) if req.URL.String() != mocks.TestURL { t.Fatalf("autorest: NewPollingRequest did not create an HTTP with the expected URL -- received %v, expected %v", req.URL, mocks.TestURL) } } func TestGetLocation(t *testing.T) { resp := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted) mocks.SetAcceptedHeaders(resp) l := GetLocation(resp) if len(l) == 0 { t.Fatalf("autorest: GetLocation failed to return Location header -- expected %v, received %v", mocks.TestURL, l) } } func TestGetLocationReturnsEmptyStringForMissingLocation(t *testing.T) { resp := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted) l := GetLocation(resp) if len(l) != 0 { t.Fatalf("autorest: GetLocation return a value without a Location header -- received %v", l) } } func TestGetRetryAfter(t *testing.T) { resp := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted) mocks.SetAcceptedHeaders(resp) d := GetRetryAfter(resp, DefaultPollingDelay) if d != mocks.TestDelay { t.Fatalf("autorest: GetRetryAfter failed to returned the expected delay -- expected %v, received %v", mocks.TestDelay, d) } } func TestGetRetryAfterReturnsDefaultDelayIfRetryHeaderIsMissing(t *testing.T) { resp := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted) d := GetRetryAfter(resp, DefaultPollingDelay) if d != DefaultPollingDelay { t.Fatalf("autorest: GetRetryAfter failed to returned the default delay for a missing Retry-After header -- expected %v, received %v", DefaultPollingDelay, d) } } func TestGetRetryAfterReturnsDefaultDelayIfRetryHeaderIsMalformed(t *testing.T) { resp := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted) mocks.SetAcceptedHeaders(resp) resp.Header.Set(http.CanonicalHeaderKey(HeaderRetryAfter), "a very bad non-integer value") d := GetRetryAfter(resp, DefaultPollingDelay) if d != DefaultPollingDelay { t.Fatalf("autorest: GetRetryAfter failed to returned the default delay for a malformed Retry-After header -- expected %v, received %v", DefaultPollingDelay, d) } } golang-github-azure-go-autorest-14.1.1/autorest/azure/000077500000000000000000000000001367372352400227305ustar00rootroot00000000000000golang-github-azure-go-autorest-14.1.1/autorest/azure/async.go000066400000000000000000000705231367372352400244030ustar00rootroot00000000000000package azure // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "bytes" "context" "encoding/json" "fmt" "io/ioutil" "net/http" "net/url" "strings" "time" "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/tracing" ) const ( headerAsyncOperation = "Azure-AsyncOperation" ) const ( operationInProgress string = "InProgress" operationCanceled string = "Canceled" operationFailed string = "Failed" operationSucceeded string = "Succeeded" ) var pollingCodes = [...]int{http.StatusNoContent, http.StatusAccepted, http.StatusCreated, http.StatusOK} // Future provides a mechanism to access the status and results of an asynchronous request. // Since futures are stateful they should be passed by value to avoid race conditions. type Future struct { pt pollingTracker } // NewFutureFromResponse returns a new Future object initialized // with the initial response from an asynchronous operation. func NewFutureFromResponse(resp *http.Response) (Future, error) { pt, err := createPollingTracker(resp) return Future{pt: pt}, err } // Response returns the last HTTP response. func (f Future) Response() *http.Response { if f.pt == nil { return nil } return f.pt.latestResponse() } // Status returns the last status message of the operation. func (f Future) Status() string { if f.pt == nil { return "" } return f.pt.pollingStatus() } // PollingMethod returns the method used to monitor the status of the asynchronous operation. func (f Future) PollingMethod() PollingMethodType { if f.pt == nil { return PollingUnknown } return f.pt.pollingMethod() } // DoneWithContext queries the service to see if the operation has completed. func (f *Future) DoneWithContext(ctx context.Context, sender autorest.Sender) (done bool, err error) { ctx = tracing.StartSpan(ctx, "github.com/Azure/go-autorest/autorest/azure/async.DoneWithContext") defer func() { sc := -1 resp := f.Response() if resp != nil { sc = resp.StatusCode } tracing.EndSpan(ctx, sc, err) }() if f.pt == nil { return false, autorest.NewError("Future", "Done", "future is not initialized") } if f.pt.hasTerminated() { return true, f.pt.pollingError() } if err := f.pt.pollForStatus(ctx, sender); err != nil { return false, err } if err := f.pt.checkForErrors(); err != nil { return f.pt.hasTerminated(), err } if err := f.pt.updatePollingState(f.pt.provisioningStateApplicable()); err != nil { return false, err } if err := f.pt.initPollingMethod(); err != nil { return false, err } if err := f.pt.updatePollingMethod(); err != nil { return false, err } return f.pt.hasTerminated(), f.pt.pollingError() } // GetPollingDelay returns a duration the application should wait before checking // the status of the asynchronous request and true; this value is returned from // the service via the Retry-After response header. If the header wasn't returned // then the function returns the zero-value time.Duration and false. func (f Future) GetPollingDelay() (time.Duration, bool) { if f.pt == nil { return 0, false } resp := f.pt.latestResponse() if resp == nil { return 0, false } retry := resp.Header.Get(autorest.HeaderRetryAfter) if retry == "" { return 0, false } d, err := time.ParseDuration(retry + "s") if err != nil { panic(err) } return d, true } // WaitForCompletionRef will return when one of the following conditions is met: the long // running operation has completed, the provided context is cancelled, or the client's // polling duration has been exceeded. It will retry failed polling attempts based on // the retry value defined in the client up to the maximum retry attempts. // If no deadline is specified in the context then the client.PollingDuration will be // used to determine if a default deadline should be used. // If PollingDuration is greater than zero the value will be used as the context's timeout. // If PollingDuration is zero then no default deadline will be used. func (f *Future) WaitForCompletionRef(ctx context.Context, client autorest.Client) (err error) { ctx = tracing.StartSpan(ctx, "github.com/Azure/go-autorest/autorest/azure/async.WaitForCompletionRef") defer func() { sc := -1 resp := f.Response() if resp != nil { sc = resp.StatusCode } tracing.EndSpan(ctx, sc, err) }() cancelCtx := ctx // if the provided context already has a deadline don't override it _, hasDeadline := ctx.Deadline() if d := client.PollingDuration; !hasDeadline && d != 0 { var cancel context.CancelFunc cancelCtx, cancel = context.WithTimeout(ctx, d) defer cancel() } done, err := f.DoneWithContext(ctx, client) for attempts := 0; !done; done, err = f.DoneWithContext(ctx, client) { if attempts >= client.RetryAttempts { return autorest.NewErrorWithError(err, "Future", "WaitForCompletion", f.pt.latestResponse(), "the number of retries has been exceeded") } // we want delayAttempt to be zero in the non-error case so // that DelayForBackoff doesn't perform exponential back-off var delayAttempt int var delay time.Duration if err == nil { // check for Retry-After delay, if not present use the client's polling delay var ok bool delay, ok = f.GetPollingDelay() if !ok { delay = client.PollingDelay } } else { // there was an error polling for status so perform exponential // back-off based on the number of attempts using the client's retry // duration. update attempts after delayAttempt to avoid off-by-one. delayAttempt = attempts delay = client.RetryDuration attempts++ } // wait until the delay elapses or the context is cancelled delayElapsed := autorest.DelayForBackoff(delay, delayAttempt, cancelCtx.Done()) if !delayElapsed { return autorest.NewErrorWithError(cancelCtx.Err(), "Future", "WaitForCompletion", f.pt.latestResponse(), "context has been cancelled") } } return } // MarshalJSON implements the json.Marshaler interface. func (f Future) MarshalJSON() ([]byte, error) { return json.Marshal(f.pt) } // UnmarshalJSON implements the json.Unmarshaler interface. func (f *Future) UnmarshalJSON(data []byte) error { // unmarshal into JSON object to determine the tracker type obj := map[string]interface{}{} err := json.Unmarshal(data, &obj) if err != nil { return err } if obj["method"] == nil { return autorest.NewError("Future", "UnmarshalJSON", "missing 'method' property") } method := obj["method"].(string) switch strings.ToUpper(method) { case http.MethodDelete: f.pt = &pollingTrackerDelete{} case http.MethodPatch: f.pt = &pollingTrackerPatch{} case http.MethodPost: f.pt = &pollingTrackerPost{} case http.MethodPut: f.pt = &pollingTrackerPut{} default: return autorest.NewError("Future", "UnmarshalJSON", "unsupoorted method '%s'", method) } // now unmarshal into the tracker return json.Unmarshal(data, &f.pt) } // PollingURL returns the URL used for retrieving the status of the long-running operation. func (f Future) PollingURL() string { if f.pt == nil { return "" } return f.pt.pollingURL() } // GetResult should be called once polling has completed successfully. // It makes the final GET call to retrieve the resultant payload. func (f Future) GetResult(sender autorest.Sender) (*http.Response, error) { if f.pt.finalGetURL() == "" { // we can end up in this situation if the async operation returns a 200 // with no polling URLs. in that case return the response which should // contain the JSON payload (only do this for successful terminal cases). if lr := f.pt.latestResponse(); lr != nil && f.pt.hasSucceeded() { return lr, nil } return nil, autorest.NewError("Future", "GetResult", "missing URL for retrieving result") } req, err := http.NewRequest(http.MethodGet, f.pt.finalGetURL(), nil) if err != nil { return nil, err } resp, err := sender.Do(req) if err == nil && resp.Body != nil { // copy the body and close it so callers don't have to defer resp.Body.Close() b, err := ioutil.ReadAll(resp.Body) if err != nil { return resp, err } resp.Body = ioutil.NopCloser(bytes.NewReader(b)) } return resp, err } type pollingTracker interface { // these methods can differ per tracker // checks the response headers and status code to determine the polling mechanism updatePollingMethod() error // checks the response for tracker-specific error conditions checkForErrors() error // returns true if provisioning state should be checked provisioningStateApplicable() bool // methods common to all trackers // initializes a tracker's polling URL and method, called for each iteration. // these values can be overridden by each polling tracker as required. initPollingMethod() error // initializes the tracker's internal state, call this when the tracker is created initializeState() error // makes an HTTP request to check the status of the LRO pollForStatus(ctx context.Context, sender autorest.Sender) error // updates internal tracker state, call this after each call to pollForStatus updatePollingState(provStateApl bool) error // returns the error response from the service, can be nil pollingError() error // returns the polling method being used pollingMethod() PollingMethodType // returns the state of the LRO as returned from the service pollingStatus() string // returns the URL used for polling status pollingURL() string // returns the URL used for the final GET to retrieve the resource finalGetURL() string // returns true if the LRO is in a terminal state hasTerminated() bool // returns true if the LRO is in a failed terminal state hasFailed() bool // returns true if the LRO is in a successful terminal state hasSucceeded() bool // returns the cached HTTP response after a call to pollForStatus(), can be nil latestResponse() *http.Response } type pollingTrackerBase struct { // resp is the last response, either from the submission of the LRO or from polling resp *http.Response // method is the HTTP verb, this is needed for deserialization Method string `json:"method"` // rawBody is the raw JSON response body rawBody map[string]interface{} // denotes if polling is using async-operation or location header Pm PollingMethodType `json:"pollingMethod"` // the URL to poll for status URI string `json:"pollingURI"` // the state of the LRO as returned from the service State string `json:"lroState"` // the URL to GET for the final result FinalGetURI string `json:"resultURI"` // used to hold an error object returned from the service Err *ServiceError `json:"error,omitempty"` } func (pt *pollingTrackerBase) initializeState() error { // determine the initial polling state based on response body and/or HTTP status // code. this is applicable to the initial LRO response, not polling responses! pt.Method = pt.resp.Request.Method if err := pt.updateRawBody(); err != nil { return err } switch pt.resp.StatusCode { case http.StatusOK: if ps := pt.getProvisioningState(); ps != nil { pt.State = *ps if pt.hasFailed() { pt.updateErrorFromResponse() return pt.pollingError() } } else { pt.State = operationSucceeded } case http.StatusCreated: if ps := pt.getProvisioningState(); ps != nil { pt.State = *ps } else { pt.State = operationInProgress } case http.StatusAccepted: pt.State = operationInProgress case http.StatusNoContent: pt.State = operationSucceeded default: pt.State = operationFailed pt.updateErrorFromResponse() return pt.pollingError() } return pt.initPollingMethod() } func (pt pollingTrackerBase) getProvisioningState() *string { if pt.rawBody != nil && pt.rawBody["properties"] != nil { p := pt.rawBody["properties"].(map[string]interface{}) if ps := p["provisioningState"]; ps != nil { s := ps.(string) return &s } } return nil } func (pt *pollingTrackerBase) updateRawBody() error { pt.rawBody = map[string]interface{}{} if pt.resp.ContentLength != 0 { defer pt.resp.Body.Close() b, err := ioutil.ReadAll(pt.resp.Body) if err != nil { return autorest.NewErrorWithError(err, "pollingTrackerBase", "updateRawBody", nil, "failed to read response body") } // observed in 204 responses over HTTP/2.0; the content length is -1 but body is empty if len(b) == 0 { return nil } // put the body back so it's available to other callers pt.resp.Body = ioutil.NopCloser(bytes.NewReader(b)) if err = json.Unmarshal(b, &pt.rawBody); err != nil { return autorest.NewErrorWithError(err, "pollingTrackerBase", "updateRawBody", nil, "failed to unmarshal response body") } } return nil } func (pt *pollingTrackerBase) pollForStatus(ctx context.Context, sender autorest.Sender) error { req, err := http.NewRequest(http.MethodGet, pt.URI, nil) if err != nil { return autorest.NewErrorWithError(err, "pollingTrackerBase", "pollForStatus", nil, "failed to create HTTP request") } req = req.WithContext(ctx) preparer := autorest.CreatePreparer(autorest.GetPrepareDecorators(ctx)...) req, err = preparer.Prepare(req) if err != nil { return autorest.NewErrorWithError(err, "pollingTrackerBase", "pollForStatus", nil, "failed preparing HTTP request") } pt.resp, err = sender.Do(req) if err != nil { return autorest.NewErrorWithError(err, "pollingTrackerBase", "pollForStatus", nil, "failed to send HTTP request") } if autorest.ResponseHasStatusCode(pt.resp, pollingCodes[:]...) { // reset the service error on success case pt.Err = nil err = pt.updateRawBody() } else { // check response body for error content pt.updateErrorFromResponse() err = pt.pollingError() } return err } // attempts to unmarshal a ServiceError type from the response body. // if that fails then make a best attempt at creating something meaningful. // NOTE: this assumes that the async operation has failed. func (pt *pollingTrackerBase) updateErrorFromResponse() { var err error if pt.resp.ContentLength != 0 { type respErr struct { ServiceError *ServiceError `json:"error"` } re := respErr{} defer pt.resp.Body.Close() var b []byte if b, err = ioutil.ReadAll(pt.resp.Body); err != nil || len(b) == 0 { goto Default } if err = json.Unmarshal(b, &re); err != nil { goto Default } // unmarshalling the error didn't yield anything, try unwrapped error if re.ServiceError == nil { err = json.Unmarshal(b, &re.ServiceError) if err != nil { goto Default } } // the unmarshaller will ensure re.ServiceError is non-nil // even if there was no content unmarshalled so check the code. if re.ServiceError.Code != "" { pt.Err = re.ServiceError return } } Default: se := &ServiceError{ Code: pt.pollingStatus(), Message: "The async operation failed.", } if err != nil { se.InnerError = make(map[string]interface{}) se.InnerError["unmarshalError"] = err.Error() } // stick the response body into the error object in hopes // it contains something useful to help diagnose the failure. if len(pt.rawBody) > 0 { se.AdditionalInfo = []map[string]interface{}{ pt.rawBody, } } pt.Err = se } func (pt *pollingTrackerBase) updatePollingState(provStateApl bool) error { if pt.Pm == PollingAsyncOperation && pt.rawBody["status"] != nil { pt.State = pt.rawBody["status"].(string) } else { if pt.resp.StatusCode == http.StatusAccepted { pt.State = operationInProgress } else if provStateApl { if ps := pt.getProvisioningState(); ps != nil { pt.State = *ps } else { pt.State = operationSucceeded } } else { return autorest.NewError("pollingTrackerBase", "updatePollingState", "the response from the async operation has an invalid status code") } } // if the operation has failed update the error state if pt.hasFailed() { pt.updateErrorFromResponse() } return nil } func (pt pollingTrackerBase) pollingError() error { if pt.Err == nil { return nil } return pt.Err } func (pt pollingTrackerBase) pollingMethod() PollingMethodType { return pt.Pm } func (pt pollingTrackerBase) pollingStatus() string { return pt.State } func (pt pollingTrackerBase) pollingURL() string { return pt.URI } func (pt pollingTrackerBase) finalGetURL() string { return pt.FinalGetURI } func (pt pollingTrackerBase) hasTerminated() bool { return strings.EqualFold(pt.State, operationCanceled) || strings.EqualFold(pt.State, operationFailed) || strings.EqualFold(pt.State, operationSucceeded) } func (pt pollingTrackerBase) hasFailed() bool { return strings.EqualFold(pt.State, operationCanceled) || strings.EqualFold(pt.State, operationFailed) } func (pt pollingTrackerBase) hasSucceeded() bool { return strings.EqualFold(pt.State, operationSucceeded) } func (pt pollingTrackerBase) latestResponse() *http.Response { return pt.resp } // error checking common to all trackers func (pt pollingTrackerBase) baseCheckForErrors() error { // for Azure-AsyncOperations the response body cannot be nil or empty if pt.Pm == PollingAsyncOperation { if pt.resp.Body == nil || pt.resp.ContentLength == 0 { return autorest.NewError("pollingTrackerBase", "baseCheckForErrors", "for Azure-AsyncOperation response body cannot be nil") } if pt.rawBody["status"] == nil { return autorest.NewError("pollingTrackerBase", "baseCheckForErrors", "missing status property in Azure-AsyncOperation response body") } } return nil } // default initialization of polling URL/method. each verb tracker will update this as required. func (pt *pollingTrackerBase) initPollingMethod() error { if ao, err := getURLFromAsyncOpHeader(pt.resp); err != nil { return err } else if ao != "" { pt.URI = ao pt.Pm = PollingAsyncOperation return nil } if lh, err := getURLFromLocationHeader(pt.resp); err != nil { return err } else if lh != "" { pt.URI = lh pt.Pm = PollingLocation return nil } // it's ok if we didn't find a polling header, this will be handled elsewhere return nil } // DELETE type pollingTrackerDelete struct { pollingTrackerBase } func (pt *pollingTrackerDelete) updatePollingMethod() error { // for 201 the Location header is required if pt.resp.StatusCode == http.StatusCreated { if lh, err := getURLFromLocationHeader(pt.resp); err != nil { return err } else if lh == "" { return autorest.NewError("pollingTrackerDelete", "updateHeaders", "missing Location header in 201 response") } else { pt.URI = lh } pt.Pm = PollingLocation pt.FinalGetURI = pt.URI } // for 202 prefer the Azure-AsyncOperation header but fall back to Location if necessary if pt.resp.StatusCode == http.StatusAccepted { ao, err := getURLFromAsyncOpHeader(pt.resp) if err != nil { return err } else if ao != "" { pt.URI = ao pt.Pm = PollingAsyncOperation } // if the Location header is invalid and we already have a polling URL // then we don't care if the Location header URL is malformed. if lh, err := getURLFromLocationHeader(pt.resp); err != nil && pt.URI == "" { return err } else if lh != "" { if ao == "" { pt.URI = lh pt.Pm = PollingLocation } // when both headers are returned we use the value in the Location header for the final GET pt.FinalGetURI = lh } // make sure a polling URL was found if pt.URI == "" { return autorest.NewError("pollingTrackerPost", "updateHeaders", "didn't get any suitable polling URLs in 202 response") } } return nil } func (pt pollingTrackerDelete) checkForErrors() error { return pt.baseCheckForErrors() } func (pt pollingTrackerDelete) provisioningStateApplicable() bool { return pt.resp.StatusCode == http.StatusOK || pt.resp.StatusCode == http.StatusNoContent } // PATCH type pollingTrackerPatch struct { pollingTrackerBase } func (pt *pollingTrackerPatch) updatePollingMethod() error { // by default we can use the original URL for polling and final GET if pt.URI == "" { pt.URI = pt.resp.Request.URL.String() } if pt.FinalGetURI == "" { pt.FinalGetURI = pt.resp.Request.URL.String() } if pt.Pm == PollingUnknown { pt.Pm = PollingRequestURI } // for 201 it's permissible for no headers to be returned if pt.resp.StatusCode == http.StatusCreated { if ao, err := getURLFromAsyncOpHeader(pt.resp); err != nil { return err } else if ao != "" { pt.URI = ao pt.Pm = PollingAsyncOperation } } // for 202 prefer the Azure-AsyncOperation header but fall back to Location if necessary // note the absence of the "final GET" mechanism for PATCH if pt.resp.StatusCode == http.StatusAccepted { ao, err := getURLFromAsyncOpHeader(pt.resp) if err != nil { return err } else if ao != "" { pt.URI = ao pt.Pm = PollingAsyncOperation } if ao == "" { if lh, err := getURLFromLocationHeader(pt.resp); err != nil { return err } else if lh == "" { return autorest.NewError("pollingTrackerPatch", "updateHeaders", "didn't get any suitable polling URLs in 202 response") } else { pt.URI = lh pt.Pm = PollingLocation } } } return nil } func (pt pollingTrackerPatch) checkForErrors() error { return pt.baseCheckForErrors() } func (pt pollingTrackerPatch) provisioningStateApplicable() bool { return pt.resp.StatusCode == http.StatusOK || pt.resp.StatusCode == http.StatusCreated } // POST type pollingTrackerPost struct { pollingTrackerBase } func (pt *pollingTrackerPost) updatePollingMethod() error { // 201 requires Location header if pt.resp.StatusCode == http.StatusCreated { if lh, err := getURLFromLocationHeader(pt.resp); err != nil { return err } else if lh == "" { return autorest.NewError("pollingTrackerPost", "updateHeaders", "missing Location header in 201 response") } else { pt.URI = lh pt.FinalGetURI = lh pt.Pm = PollingLocation } } // for 202 prefer the Azure-AsyncOperation header but fall back to Location if necessary if pt.resp.StatusCode == http.StatusAccepted { ao, err := getURLFromAsyncOpHeader(pt.resp) if err != nil { return err } else if ao != "" { pt.URI = ao pt.Pm = PollingAsyncOperation } // if the Location header is invalid and we already have a polling URL // then we don't care if the Location header URL is malformed. if lh, err := getURLFromLocationHeader(pt.resp); err != nil && pt.URI == "" { return err } else if lh != "" { if ao == "" { pt.URI = lh pt.Pm = PollingLocation } // when both headers are returned we use the value in the Location header for the final GET pt.FinalGetURI = lh } // make sure a polling URL was found if pt.URI == "" { return autorest.NewError("pollingTrackerPost", "updateHeaders", "didn't get any suitable polling URLs in 202 response") } } return nil } func (pt pollingTrackerPost) checkForErrors() error { return pt.baseCheckForErrors() } func (pt pollingTrackerPost) provisioningStateApplicable() bool { return pt.resp.StatusCode == http.StatusOK || pt.resp.StatusCode == http.StatusNoContent } // PUT type pollingTrackerPut struct { pollingTrackerBase } func (pt *pollingTrackerPut) updatePollingMethod() error { // by default we can use the original URL for polling and final GET if pt.URI == "" { pt.URI = pt.resp.Request.URL.String() } if pt.FinalGetURI == "" { pt.FinalGetURI = pt.resp.Request.URL.String() } if pt.Pm == PollingUnknown { pt.Pm = PollingRequestURI } // for 201 it's permissible for no headers to be returned if pt.resp.StatusCode == http.StatusCreated { if ao, err := getURLFromAsyncOpHeader(pt.resp); err != nil { return err } else if ao != "" { pt.URI = ao pt.Pm = PollingAsyncOperation } } // for 202 prefer the Azure-AsyncOperation header but fall back to Location if necessary if pt.resp.StatusCode == http.StatusAccepted { ao, err := getURLFromAsyncOpHeader(pt.resp) if err != nil { return err } else if ao != "" { pt.URI = ao pt.Pm = PollingAsyncOperation } // if the Location header is invalid and we already have a polling URL // then we don't care if the Location header URL is malformed. if lh, err := getURLFromLocationHeader(pt.resp); err != nil && pt.URI == "" { return err } else if lh != "" { if ao == "" { pt.URI = lh pt.Pm = PollingLocation } } // make sure a polling URL was found if pt.URI == "" { return autorest.NewError("pollingTrackerPut", "updateHeaders", "didn't get any suitable polling URLs in 202 response") } } return nil } func (pt pollingTrackerPut) checkForErrors() error { err := pt.baseCheckForErrors() if err != nil { return err } // if there are no LRO headers then the body cannot be empty ao, err := getURLFromAsyncOpHeader(pt.resp) if err != nil { return err } lh, err := getURLFromLocationHeader(pt.resp) if err != nil { return err } if ao == "" && lh == "" && len(pt.rawBody) == 0 { return autorest.NewError("pollingTrackerPut", "checkForErrors", "the response did not contain a body") } return nil } func (pt pollingTrackerPut) provisioningStateApplicable() bool { return pt.resp.StatusCode == http.StatusOK || pt.resp.StatusCode == http.StatusCreated } // creates a polling tracker based on the verb of the original request func createPollingTracker(resp *http.Response) (pollingTracker, error) { var pt pollingTracker switch strings.ToUpper(resp.Request.Method) { case http.MethodDelete: pt = &pollingTrackerDelete{pollingTrackerBase: pollingTrackerBase{resp: resp}} case http.MethodPatch: pt = &pollingTrackerPatch{pollingTrackerBase: pollingTrackerBase{resp: resp}} case http.MethodPost: pt = &pollingTrackerPost{pollingTrackerBase: pollingTrackerBase{resp: resp}} case http.MethodPut: pt = &pollingTrackerPut{pollingTrackerBase: pollingTrackerBase{resp: resp}} default: return nil, autorest.NewError("azure", "createPollingTracker", "unsupported HTTP method %s", resp.Request.Method) } if err := pt.initializeState(); err != nil { return pt, err } // this initializes the polling header values, we do this during creation in case the // initial response send us invalid values; this way the API call will return a non-nil // error (not doing this means the error shows up in Future.Done) return pt, pt.updatePollingMethod() } // gets the polling URL from the Azure-AsyncOperation header. // ensures the URL is well-formed and absolute. func getURLFromAsyncOpHeader(resp *http.Response) (string, error) { s := resp.Header.Get(http.CanonicalHeaderKey(headerAsyncOperation)) if s == "" { return "", nil } if !isValidURL(s) { return "", autorest.NewError("azure", "getURLFromAsyncOpHeader", "invalid polling URL '%s'", s) } return s, nil } // gets the polling URL from the Location header. // ensures the URL is well-formed and absolute. func getURLFromLocationHeader(resp *http.Response) (string, error) { s := resp.Header.Get(http.CanonicalHeaderKey(autorest.HeaderLocation)) if s == "" { return "", nil } if !isValidURL(s) { return "", autorest.NewError("azure", "getURLFromLocationHeader", "invalid polling URL '%s'", s) } return s, nil } // verify that the URL is valid and absolute func isValidURL(s string) bool { u, err := url.Parse(s) return err == nil && u.IsAbs() } // PollingMethodType defines a type used for enumerating polling mechanisms. type PollingMethodType string const ( // PollingAsyncOperation indicates the polling method uses the Azure-AsyncOperation header. PollingAsyncOperation PollingMethodType = "AsyncOperation" // PollingLocation indicates the polling method uses the Location header. PollingLocation PollingMethodType = "Location" // PollingRequestURI indicates the polling method uses the original request URI. PollingRequestURI PollingMethodType = "RequestURI" // PollingUnknown indicates an unknown polling method and is the default value. PollingUnknown PollingMethodType = "" ) // AsyncOpIncompleteError is the type that's returned from a future that has not completed. type AsyncOpIncompleteError struct { // FutureType is the name of the type composed of a azure.Future. FutureType string } // Error returns an error message including the originating type name of the error. func (e AsyncOpIncompleteError) Error() string { return fmt.Sprintf("%s: asynchronous operation has not completed", e.FutureType) } // NewAsyncOpIncompleteError creates a new AsyncOpIncompleteError with the specified parameters. func NewAsyncOpIncompleteError(futureType string) AsyncOpIncompleteError { return AsyncOpIncompleteError{ FutureType: futureType, } } golang-github-azure-go-autorest-14.1.1/autorest/azure/async_test.go000066400000000000000000000772271367372352400254520ustar00rootroot00000000000000package azure // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "context" "encoding/json" "errors" "fmt" "net/http" "reflect" "testing" "time" "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/mocks" ) func TestCreateFromInvalidRequestVerb(t *testing.T) { resp := mocks.NewResponseWithBodyAndStatus(nil, http.StatusOK, "some status") resp.Request = mocks.NewRequestWithParams(http.MethodGet, mocks.TestURL, nil) _, err := createPollingTracker(resp) if err == nil { t.Fatal("unexpected nil error") } } // DELETE func TestCreateDeleteTracker201Success(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodDelete, nil), http.StatusCreated, nil) mocks.SetLocationHeader(resp, mocks.TestLocationURL) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } if pt.pollingMethod() != PollingLocation { t.Fatalf("wrong polling method: %s", pt.pollingMethod()) } if pt.finalGetURL() != mocks.TestLocationURL { t.Fatalf("wrong final GET URL: %s", pt.finalGetURL()) } } func TestCreateDeleteTracker201FailNoLocation(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodDelete, nil), http.StatusCreated, nil) _, err := createPollingTracker(resp) if err == nil { t.Fatal("unexpected nil error") } } func TestCreateDeleteTracker201FailBadLocation(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodDelete, nil), http.StatusCreated, nil) mocks.SetLocationHeader(resp, mocks.TestBadURL) _, err := createPollingTracker(resp) if err == nil { t.Fatal("unexpected nil error") } } func TestCreateDeleteTracker202SuccessAsyncOp(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodDelete, nil), http.StatusAccepted, nil) setAsyncOpHeader(resp, mocks.TestAzureAsyncURL) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } if pt.pollingMethod() != PollingAsyncOperation { t.Fatalf("wrong polling method: %s", pt.pollingMethod()) } if pt.finalGetURL() != "" { t.Fatal("expected empty GET URL") } } func TestCreateDeleteTracker202SuccessLocation(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodDelete, nil), http.StatusAccepted, nil) mocks.SetLocationHeader(resp, mocks.TestLocationURL) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } if pt.pollingMethod() != PollingLocation { t.Fatalf("wrong polling method: %s", pt.pollingMethod()) } if pt.finalGetURL() != mocks.TestLocationURL { t.Fatalf("wrong final GET URL: %s", pt.finalGetURL()) } } func TestCreateDeleteTracker202SuccessBoth(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodDelete, nil), http.StatusAccepted, nil) setAsyncOpHeader(resp, mocks.TestAzureAsyncURL) mocks.SetLocationHeader(resp, mocks.TestLocationURL) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } if pt.pollingMethod() != PollingAsyncOperation { t.Fatalf("wrong polling method: %s", pt.pollingMethod()) } if pt.finalGetURL() != mocks.TestLocationURL { t.Fatalf("wrong final GET URL: %s", pt.finalGetURL()) } } func TestCreateDeleteTracker202SuccessBadLocation(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodDelete, nil), http.StatusAccepted, nil) setAsyncOpHeader(resp, mocks.TestAzureAsyncURL) mocks.SetLocationHeader(resp, mocks.TestBadURL) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } if pt.pollingMethod() != PollingAsyncOperation { t.Fatalf("wrong polling method: %s", pt.pollingMethod()) } if pt.finalGetURL() != "" { t.Fatal("expected empty GET URL") } } func TestCreateDeleteTracker202FailBadAsyncOp(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodDelete, nil), http.StatusAccepted, nil) setAsyncOpHeader(resp, mocks.TestBadURL) _, err := createPollingTracker(resp) if err == nil { t.Fatal("unexpected nil error") } } func TestCreateDeleteTracker202FailBadLocation(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodDelete, nil), http.StatusAccepted, nil) mocks.SetLocationHeader(resp, mocks.TestBadURL) _, err := createPollingTracker(resp) if err == nil { t.Fatal("unexpected nil error") } } // PATCH func TestCreatePatchTracker201Success(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPatch, nil), http.StatusCreated, nil) setAsyncOpHeader(resp, mocks.TestAzureAsyncURL) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } if pt.pollingMethod() != PollingAsyncOperation { t.Fatalf("wrong polling method: %s", pt.pollingMethod()) } if pt.finalGetURL() != mocks.TestURL { t.Fatalf("wrong final GET URL: %s", pt.finalGetURL()) } } func TestCreatePatchTracker201SuccessNoHeaders(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPatch, nil), http.StatusCreated, nil) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } if pt.pollingMethod() != PollingRequestURI { t.Fatalf("wrong polling method: %s", pt.pollingMethod()) } if pt.finalGetURL() != mocks.TestURL { t.Fatalf("wrong final GET URL: %s", pt.finalGetURL()) } } func TestCreatePatchTracker201FailBadAsyncOp(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPatch, nil), http.StatusCreated, nil) setAsyncOpHeader(resp, mocks.TestBadURL) _, err := createPollingTracker(resp) if err == nil { t.Fatal("unexpected nil error") } } func TestCreatePatchTracker202SuccessAsyncOp(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPatch, nil), http.StatusAccepted, nil) setAsyncOpHeader(resp, mocks.TestAzureAsyncURL) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } if pt.pollingMethod() != PollingAsyncOperation { t.Fatalf("wrong polling method: %s", pt.pollingMethod()) } if pt.finalGetURL() != mocks.TestURL { t.Fatalf("wrong final GET URL: %s", pt.finalGetURL()) } } func TestCreatePatchTracker202SuccessLocation(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPatch, nil), http.StatusAccepted, nil) mocks.SetLocationHeader(resp, mocks.TestLocationURL) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } if pt.pollingMethod() != PollingLocation { t.Fatalf("wrong polling method: %s", pt.pollingMethod()) } if pt.finalGetURL() != mocks.TestURL { t.Fatalf("wrong final GET URL: %s", pt.finalGetURL()) } } func TestCreatePatchTracker202SuccessBoth(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPatch, nil), http.StatusAccepted, nil) setAsyncOpHeader(resp, mocks.TestAzureAsyncURL) mocks.SetLocationHeader(resp, mocks.TestLocationURL) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } if pt.pollingMethod() != PollingAsyncOperation { t.Fatalf("wrong polling method: %s", pt.pollingMethod()) } if pt.finalGetURL() != mocks.TestURL { t.Fatalf("wrong final GET URL: %s", pt.finalGetURL()) } } func TestCreatePatchTracker202FailBadAsyncOp(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPatch, nil), http.StatusAccepted, nil) setAsyncOpHeader(resp, mocks.TestBadURL) _, err := createPollingTracker(resp) if err == nil { t.Fatal("unexpected nil error") } } func TestCreatePatchTracker202FailBadLocation(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPatch, nil), http.StatusAccepted, nil) mocks.SetLocationHeader(resp, mocks.TestBadURL) _, err := createPollingTracker(resp) if err == nil { t.Fatal("unexpected nil error") } } // POST func TestCreatePostTracker201Success(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPost, nil), http.StatusCreated, nil) mocks.SetLocationHeader(resp, mocks.TestLocationURL) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } if pt.pollingMethod() != PollingLocation { t.Fatalf("wrong polling method: %s", pt.pollingMethod()) } if pt.finalGetURL() != mocks.TestLocationURL { t.Fatalf("wrong final GET URL: %s", pt.finalGetURL()) } } func TestCreatePostTracker201FailNoHeader(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPost, nil), http.StatusCreated, nil) _, err := createPollingTracker(resp) if err == nil { t.Fatal("unexpected nil err") } } func TestCreatePostTracker201FailBadHeader(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPost, nil), http.StatusCreated, nil) mocks.SetLocationHeader(resp, mocks.TestBadURL) _, err := createPollingTracker(resp) if err == nil { t.Fatal("unexpected nil err") } } func TestCreatePostTracker202SuccessAsyncOp(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPost, nil), http.StatusAccepted, nil) setAsyncOpHeader(resp, mocks.TestAzureAsyncURL) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } if pt.pollingMethod() != PollingAsyncOperation { t.Fatalf("wrong polling method: %s", pt.pollingMethod()) } if pt.finalGetURL() != "" { t.Fatal("expected empty final GET URL") } } func TestCreatePostTracker202SuccessLocation(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPost, nil), http.StatusAccepted, nil) mocks.SetLocationHeader(resp, mocks.TestLocationURL) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } if pt.pollingMethod() != PollingLocation { t.Fatalf("wrong polling method: %s", pt.pollingMethod()) } if pt.finalGetURL() != mocks.TestLocationURL { t.Fatalf("wrong final GET URI: %s", pt.finalGetURL()) } } func TestCreatePostTracker202SuccessBoth(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPost, nil), http.StatusAccepted, nil) setAsyncOpHeader(resp, mocks.TestAzureAsyncURL) mocks.SetLocationHeader(resp, mocks.TestLocationURL) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } if pt.pollingMethod() != PollingAsyncOperation { t.Fatalf("wrong polling method: %s", pt.pollingMethod()) } if pt.finalGetURL() != mocks.TestLocationURL { t.Fatalf("wrong final GET URL: %s", pt.finalGetURL()) } } func TestCreatePostTracker202SuccessBadLocation(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPost, nil), http.StatusAccepted, nil) setAsyncOpHeader(resp, mocks.TestAzureAsyncURL) mocks.SetLocationHeader(resp, mocks.TestBadURL) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } if pt.pollingMethod() != PollingAsyncOperation { t.Fatalf("wrong polling method: %s", pt.pollingMethod()) } if pt.finalGetURL() != "" { t.Fatal("expected empty final GET URL") } } func TestCreatePostTracker202FailBadAsyncOp(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPost, nil), http.StatusAccepted, nil) setAsyncOpHeader(resp, mocks.TestBadURL) _, err := createPollingTracker(resp) if err == nil { t.Fatal("unexpected nil error") } } func TestCreatePostTracker202FailBadLocation(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPost, nil), http.StatusAccepted, nil) _, err := createPollingTracker(resp) mocks.SetLocationHeader(resp, mocks.TestBadURL) if err == nil { t.Fatal("unexpected nil error") } } // PUT func TestCreatePutTracker201SuccessAsyncOp(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPut, nil), http.StatusCreated, nil) setAsyncOpHeader(resp, mocks.TestAzureAsyncURL) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } if pt.pollingMethod() != PollingAsyncOperation { t.Fatalf("wrong polling method: %s", pt.pollingMethod()) } if pt.finalGetURL() != mocks.TestURL { t.Fatalf("wrong final GET URL: %s", pt.finalGetURL()) } } func TestCreatePutTracker201SuccessNoHeaders(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPut, nil), http.StatusCreated, nil) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } if pt.pollingMethod() != PollingRequestURI { t.Fatalf("wrong polling method: %s", pt.pollingMethod()) } if pt.finalGetURL() != mocks.TestURL { t.Fatalf("wrong final GET URL: %s", pt.finalGetURL()) } } func TestCreatePutTracker201FailBadAsyncOp(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPut, nil), http.StatusCreated, nil) setAsyncOpHeader(resp, mocks.TestBadURL) _, err := createPollingTracker(resp) if err == nil { t.Fatal("unexpected nil error") } } func TestCreatePutTracker202SuccessAsyncOp(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPut, nil), http.StatusAccepted, nil) setAsyncOpHeader(resp, mocks.TestAzureAsyncURL) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } if pt.pollingMethod() != PollingAsyncOperation { t.Fatalf("wrong polling method: %s", pt.pollingMethod()) } if pt.finalGetURL() != mocks.TestURL { t.Fatalf("wrong final GET URL: %s", pt.finalGetURL()) } } func TestCreatePutTracker202SuccessLocation(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPut, nil), http.StatusAccepted, nil) mocks.SetLocationHeader(resp, mocks.TestLocationURL) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } if pt.pollingMethod() != PollingLocation { t.Fatalf("wrong polling method: %s", pt.pollingMethod()) } if pt.finalGetURL() != resp.Request.URL.String() { t.Fatalf("wrong final GET URL: %s", pt.finalGetURL()) } } func TestCreatePutTracker202SuccessBoth(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPut, nil), http.StatusAccepted, nil) setAsyncOpHeader(resp, mocks.TestAzureAsyncURL) mocks.SetLocationHeader(resp, mocks.TestLocationURL) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } if pt.pollingMethod() != PollingAsyncOperation { t.Fatalf("wrong polling method: %s", pt.pollingMethod()) } if pt.finalGetURL() != resp.Request.URL.String() { t.Fatalf("wrong final GET URL: %s", pt.finalGetURL()) } } func TestCreatePutTracker202SuccessBadLocation(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPut, nil), http.StatusAccepted, nil) setAsyncOpHeader(resp, mocks.TestAzureAsyncURL) mocks.SetLocationHeader(resp, mocks.TestBadURL) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } if pt.pollingMethod() != PollingAsyncOperation { t.Fatalf("wrong polling method: %s", pt.pollingMethod()) } if pt.finalGetURL() != mocks.TestURL { t.Fatalf("wrong final GET URL: %s", pt.finalGetURL()) } } func TestCreatePutTracker202FailBadAsyncOp(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPut, nil), http.StatusAccepted, nil) setAsyncOpHeader(resp, mocks.TestBadURL) _, err := createPollingTracker(resp) if err == nil { t.Fatal("unexpected nil error") } } func TestPollPutTrackerSuccessNoHeaders(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPut, nil), http.StatusAccepted, nil) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } sender := mocks.NewSender() sender.AppendResponse(newProvisioningStatusResponse("InProgress")) err = pt.pollForStatus(context.Background(), sender) if err != nil { t.Fatalf("failed to poll for status: %v", err) } err = pt.checkForErrors() if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestPollPutTrackerFailNoHeadersEmptyBody(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPut, nil), http.StatusAccepted, nil) pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } sender := mocks.NewSender() sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(&mocks.Body{}, http.StatusOK, "status ok")) err = pt.pollForStatus(context.Background(), sender) if err != nil { t.Fatalf("failed to poll for status: %v", err) } err = pt.checkForErrors() if err == nil { t.Fatalf("unexpected nil error") } } // errors func TestAsyncPollingReturnsWrappedError(t *testing.T) { resp := newSimpleAsyncResp() pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } sender := mocks.NewSender() sender.AppendResponse(newOperationResourceErrorResponse("Failed")) err = pt.pollForStatus(context.Background(), sender) if err == nil { t.Fatal("unexpected nil polling error") } err = pt.pollingError() if err == nil { t.Fatal("unexpected nil polling error") } if se, ok := err.(*ServiceError); !ok { t.Fatal("incorrect error type") } else if se.Code == "" { t.Fatal("empty service error code") } else if se.Message == "" { t.Fatal("empty service error message") } } func TestLocationPollingReturnsWrappedError(t *testing.T) { resp := newSimpleLocationResp() pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } sender := mocks.NewSender() sender.AppendResponse(newProvisioningStatusErrorResponse("Failed")) err = pt.pollForStatus(context.Background(), sender) if err == nil { t.Fatal("unexpected nil polling error") } err = pt.pollingError() if err == nil { t.Fatal("unexpected nil polling error") } if se, ok := err.(*ServiceError); !ok { t.Fatal("incorrect error type") } else if se.Code == "" { t.Fatal("empty service error code") } else if se.Message == "" { t.Fatal("empty service error message") } } func TestLocationPollingReturnsUnwrappedError(t *testing.T) { resp := newSimpleLocationResp() pt, err := createPollingTracker(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } sender := mocks.NewSender() sender.AppendResponse(newProvisioningStatusUnwrappedErrorResponse("Failed")) err = pt.pollForStatus(context.Background(), sender) if err == nil { t.Fatal("unexpected nil polling error") } err = pt.pollingError() if err == nil { t.Fatal("unexpected nil polling error") } if se, ok := err.(*ServiceError); !ok { t.Fatal("incorrect error type") } else if se.Code == "" { t.Fatal("empty service error code") } else if se.Message == "" { t.Fatal("empty service error message") } } func TestFuture_PollsUntilProvisioningStatusSucceeds(t *testing.T) { r2 := newOperationResourceResponse("busy") r3 := newOperationResourceResponse(operationSucceeded) sender := mocks.NewSender() ctx := context.Background() sender.AppendAndRepeatResponse(r2, 2) sender.AppendResponse(r3) future, err := NewFutureFromResponse(newSimpleAsyncResp()) if err != nil { t.Fatalf("failed to create future: %v", err) } for done, err := future.DoneWithContext(ctx, sender); !done; done, err = future.DoneWithContext(ctx, sender) { if future.PollingMethod() != PollingAsyncOperation { t.Fatalf("wrong future polling method: %s", future.PollingMethod()) } if err != nil { t.Fatalf("polling Done failed: %v", err) } delay, ok := future.GetPollingDelay() if !ok { t.Fatalf("expected Retry-After value") } time.Sleep(delay) } if sender.Attempts() < sender.NumResponses() { t.Fatalf("stopped polling before receiving a terminated OperationResource") } autorest.Respond(future.Response(), autorest.ByClosing()) } func TestFuture_MarshallingSuccess(t *testing.T) { future, err := NewFutureFromResponse(newSimpleAsyncResp()) if err != nil { t.Fatalf("failed to create future: %v", err) } data, err := json.Marshal(future) if err != nil { t.Fatalf("failed to marshal: %v", err) } var future2 Future err = json.Unmarshal(data, &future2) if err != nil { t.Fatalf("failed to unmarshal: %v", err) } if reflect.DeepEqual(future.pt, future2.pt) { t.Fatalf("marshalling unexpected match") } // these fields don't get marshalled so nil them before deep comparison future.pt.(*pollingTrackerPut).resp = nil future.pt.(*pollingTrackerPut).rawBody = nil if !reflect.DeepEqual(future.pt, future2.pt) { t.Fatalf("marshalling futures don't match") } } func TestFuture_MarshallingWithError(t *testing.T) { r2 := newOperationResourceResponse("busy") r3 := newOperationResourceErrorResponse(operationFailed) sender := mocks.NewSender() sender.AppendAndRepeatResponse(r2, 2) sender.AppendResponse(r3) client := autorest.Client{ PollingDelay: 1 * time.Second, PollingDuration: autorest.DefaultPollingDuration, RetryAttempts: autorest.DefaultRetryAttempts, RetryDuration: 1 * time.Second, Sender: sender, } future, err := NewFutureFromResponse(newSimpleAsyncResp()) if err != nil { t.Fatalf("failed to create future: %v", err) } err = future.WaitForCompletionRef(context.Background(), client) if err == nil { t.Fatal("expected non-nil error") } data, err := json.Marshal(future) if err != nil { t.Fatalf("failed to marshal: %v", err) } var future2 Future err = json.Unmarshal(data, &future2) if err != nil { t.Fatalf("failed to unmarshal: %v", err) } if reflect.DeepEqual(future.pt, future2.pt) { t.Fatalf("marshalling unexpected match") } // these fields don't get marshalled so nil them before deep comparison future.pt.(*pollingTrackerPut).resp = nil future.pt.(*pollingTrackerPut).rawBody = nil if !reflect.DeepEqual(future.pt, future2.pt) { t.Fatalf("marshalling futures don't match") } } func TestFuture_CreateFromFailedOperation(t *testing.T) { _, err := NewFutureFromResponse(newAsyncResponseWithError(http.MethodPut)) if err == nil { t.Fatal("expected non-nil error") } } func TestFuture_WaitForCompletionRef(t *testing.T) { r2 := newOperationResourceResponse("busy") r3 := newOperationResourceResponse(operationSucceeded) sender := mocks.NewSender() sender.AppendAndRepeatResponse(r2, 2) sender.AppendResponse(r3) client := autorest.Client{ PollingDelay: 1 * time.Second, PollingDuration: autorest.DefaultPollingDuration, RetryAttempts: autorest.DefaultRetryAttempts, RetryDuration: 1 * time.Second, Sender: sender, } future, err := NewFutureFromResponse(newSimpleAsyncResp()) if err != nil { t.Fatalf("failed to create future: %v", err) } err = future.WaitForCompletionRef(context.Background(), client) if err != nil { t.Fatalf("WaitForCompletion returned non-nil error") } if sender.Attempts() < sender.NumResponses() { t.Fatalf("stopped polling before receiving a terminated OperationResource") } autorest.Respond(future.Response(), autorest.ByClosing()) } func TestFuture_WaitForCompletionTimedOut(t *testing.T) { r2 := newProvisioningStatusResponse("busy") sender := mocks.NewSender() sender.AppendAndRepeatResponseWithDelay(r2, 1*time.Second, 5) future, err := NewFutureFromResponse(newSimpleAsyncResp()) if err != nil { t.Fatalf("failed to create future: %v", err) } client := autorest.Client{ PollingDelay: autorest.DefaultPollingDelay, PollingDuration: 2 * time.Second, RetryAttempts: autorest.DefaultRetryAttempts, RetryDuration: 1 * time.Second, Sender: sender, } err = future.WaitForCompletionRef(context.Background(), client) if err == nil { t.Fatalf("WaitForCompletion returned nil error, should have timed out") } } func TestFuture_WaitForCompletionRetriesExceeded(t *testing.T) { r1 := newProvisioningStatusResponse("InProgress") sender := mocks.NewSender() sender.AppendResponse(r1) sender.AppendAndRepeatError(errors.New("transient network failure"), autorest.DefaultRetryAttempts+1) future, err := NewFutureFromResponse(newSimpleAsyncResp()) if err != nil { t.Fatalf("failed to create future: %v", err) } client := autorest.Client{ PollingDelay: autorest.DefaultPollingDelay, PollingDuration: autorest.DefaultPollingDuration, RetryAttempts: autorest.DefaultRetryAttempts, RetryDuration: 100 * time.Millisecond, Sender: sender, } err = future.WaitForCompletionRef(context.Background(), client) if err == nil { t.Fatalf("WaitForCompletion returned nil error, should have errored out") } } func TestFuture_WaitForCompletionCancelled(t *testing.T) { r1 := newProvisioningStatusResponse("InProgress") sender := mocks.NewSender() sender.AppendAndRepeatResponseWithDelay(r1, 1*time.Second, 5) future, err := NewFutureFromResponse(newSimpleAsyncResp()) if err != nil { t.Fatalf("failed to create future: %v", err) } client := autorest.Client{ PollingDelay: autorest.DefaultPollingDelay, PollingDuration: autorest.DefaultPollingDuration, RetryAttempts: autorest.DefaultRetryAttempts, RetryDuration: autorest.DefaultRetryDuration, Sender: sender, } ctx, cancel := context.WithCancel(context.Background()) go func() { time.Sleep(2 * time.Second) cancel() }() err = future.WaitForCompletionRef(ctx, client) if err == nil { t.Fatalf("WaitForCompletion returned nil error, should have been cancelled") } } func TestFuture_GetResultFromNonAsyncOperation(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodPost, nil), http.StatusOK, mocks.NewBody(someResource)) future, err := NewFutureFromResponse(resp) if err != nil { t.Fatalf("failed to create tracker: %v", err) } if pm := future.PollingMethod(); pm != PollingUnknown { t.Fatalf("wrong polling method: %s", pm) } done, err := future.DoneWithContext(context.Background(), nil) if err != nil { t.Fatalf("failed to check status: %v", err) } if !done { t.Fatal("operation should be done") } res, err := future.GetResult(nil) if err != nil { t.Fatalf("failed to get result: %v", err) } if res != resp { t.Fatal("result and response don't match") } } func TestFuture_GetResultNonTerminal(t *testing.T) { resp := newAsyncResp(newAsyncReq(http.MethodDelete, nil), http.StatusAccepted, mocks.NewBody(fmt.Sprintf(operationResourceFormat, operationInProgress))) mocks.SetResponseHeader(resp, headerAsyncOperation, mocks.TestAzureAsyncURL) future, err := NewFutureFromResponse(resp) if err != nil { t.Fatalf("failed to create future: %v", err) } res, err := future.GetResult(nil) if err == nil { t.Fatal("expected non-nil error") } if res != nil { t.Fatal("expected nil result") } } const ( operationResourceIllegal = ` This is not JSON and should fail...badly. ` // returned from LROs that use Location header pollingStateFormat = ` { "unused" : { "somefield" : 42 }, "properties" : { "provisioningState": "%s" } } ` // returned from LROs that use Location header errorResponse = ` { "error" : { "code" : "InvalidParameter", "message" : "tom-service-DISCOVERY-server-base-v1.core.local' is not a valid captured VHD blob name prefix." } } ` // returned from LROs that use Location header unwrappedErrorResponse = ` { "code" : "InvalidParameter", "message" : "tom-service-DISCOVERY-server-base-v1.core.local' is not a valid captured VHD blob name prefix." } ` // returned from LROs that use Location header pollingStateEmpty = ` { "unused" : { "somefield" : 42 }, "properties" : { } } ` // returned from LROs that use Azure-AsyncOperation header operationResourceFormat = ` { "id": "/subscriptions/id/locations/westus/operationsStatus/sameguid", "name": "sameguid", "status" : "%s", "startTime" : "2006-01-02T15:04:05Z", "endTime" : "2006-01-02T16:04:05Z", "percentComplete" : 50.00, "properties" : { "foo": "bar" } } ` // returned from LROs that use Azure-AsyncOperation header operationResourceErrorFormat = ` { "id": "/subscriptions/id/locations/westus/operationsStatus/sameguid", "name": "sameguid", "status" : "%s", "startTime" : "2006-01-02T15:04:05Z", "endTime" : "2006-01-02T16:04:05Z", "percentComplete" : 50.00, "properties" : {}, "error" : { "code" : "BadArgument", "message" : "The provided database 'foo' has an invalid username." } } ` // returned from an operation marked as LRO but really isn't someResource = ` { "id": "/subscriptions/guid/resourceGroups/rg/providers/something/else/thing", "name": "thing", "type": "Imaginary.type", "location": "Central US", "properties": {} } ` ) // creates an async request with the specified body. func newAsyncReq(reqMethod string, body *mocks.Body) *http.Request { return mocks.NewRequestWithParams(reqMethod, mocks.TestURL, body) } // creates an async response with the specified body. // the req param is the originating LRO request. func newAsyncResp(req *http.Request, statusCode int, body *mocks.Body) *http.Response { status := "Unknown" switch statusCode { case http.StatusOK, http.StatusNoContent: status = "Completed" case http.StatusCreated: status = "Creating" case http.StatusAccepted: status = "In progress" case http.StatusBadRequest: status = "Bad request" } r := mocks.NewResponseWithBodyAndStatus(body, statusCode, status) r.Request = req return r } // creates a simple LRO response, PUT/201 with Azure-AsyncOperation header func newSimpleAsyncResp() *http.Response { r := newAsyncResp(newAsyncReq(http.MethodPut, nil), http.StatusCreated, mocks.NewBody(fmt.Sprintf(operationResourceFormat, operationInProgress))) mocks.SetResponseHeader(r, headerAsyncOperation, mocks.TestAzureAsyncURL) return r } // creates a simple LRO response, POST/201 with Location header func newSimpleLocationResp() *http.Response { r := newAsyncResp(newAsyncReq(http.MethodPost, nil), http.StatusCreated, mocks.NewBody(fmt.Sprintf(pollingStateFormat, operationInProgress))) mocks.SetResponseHeader(r, autorest.HeaderLocation, mocks.TestLocationURL) return r } // creates an async response that contains an error (HTTP 400 + error response body) func newAsyncResponseWithError(reqMethod string) *http.Response { return newAsyncResp(newAsyncReq(reqMethod, nil), http.StatusBadRequest, mocks.NewBody(errorResponse)) } // creates a LRO polling response using the operation resource format (Azure-AsyncOperation LROs) func newOperationResourceResponse(status string) *http.Response { r := mocks.NewResponseWithBodyAndStatus(mocks.NewBody(fmt.Sprintf(operationResourceFormat, status)), http.StatusOK, status) mocks.SetRetryHeader(r, retryDelay) return r } // creates a LRO polling error response using the operation resource format (Azure-AsyncOperation LROs) func newOperationResourceErrorResponse(status string) *http.Response { return mocks.NewResponseWithBodyAndStatus(mocks.NewBody(fmt.Sprintf(operationResourceErrorFormat, status)), http.StatusBadRequest, status) } // creates a LRO polling response using the provisioning state format (Location LROs) func newProvisioningStatusResponse(status string) *http.Response { r := mocks.NewResponseWithBodyAndStatus(mocks.NewBody(fmt.Sprintf(pollingStateFormat, status)), http.StatusOK, status) mocks.SetRetryHeader(r, retryDelay) return r } // creates a LRO polling error response using the provisioning state format (Location LROs) func newProvisioningStatusErrorResponse(status string) *http.Response { return mocks.NewResponseWithBodyAndStatus(mocks.NewBody(errorResponse), http.StatusBadRequest, status) } // creates a LRO polling unwrapped error response using the provisioning state format (Location LROs) func newProvisioningStatusUnwrappedErrorResponse(status string) *http.Response { return mocks.NewResponseWithBodyAndStatus(mocks.NewBody(unwrappedErrorResponse), http.StatusBadRequest, status) } // adds the Azure-AsyncOperation header with the specified location to the response func setAsyncOpHeader(resp *http.Response, location string) { mocks.SetResponseHeader(resp, http.CanonicalHeaderKey(headerAsyncOperation), location) } golang-github-azure-go-autorest-14.1.1/autorest/azure/auth/000077500000000000000000000000001367372352400236715ustar00rootroot00000000000000golang-github-azure-go-autorest-14.1.1/autorest/azure/auth/auth.go000066400000000000000000000650301367372352400251650ustar00rootroot00000000000000package auth // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "bytes" "crypto/rsa" "crypto/x509" "encoding/binary" "encoding/json" "errors" "fmt" "io/ioutil" "log" "os" "strings" "unicode/utf16" "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/adal" "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/azure/cli" "github.com/dimchansky/utfbom" "golang.org/x/crypto/pkcs12" ) // The possible keys in the Values map. const ( SubscriptionID = "AZURE_SUBSCRIPTION_ID" TenantID = "AZURE_TENANT_ID" AuxiliaryTenantIDs = "AZURE_AUXILIARY_TENANT_IDS" ClientID = "AZURE_CLIENT_ID" ClientSecret = "AZURE_CLIENT_SECRET" CertificatePath = "AZURE_CERTIFICATE_PATH" CertificatePassword = "AZURE_CERTIFICATE_PASSWORD" Username = "AZURE_USERNAME" Password = "AZURE_PASSWORD" EnvironmentName = "AZURE_ENVIRONMENT" Resource = "AZURE_AD_RESOURCE" ActiveDirectoryEndpoint = "ActiveDirectoryEndpoint" ResourceManagerEndpoint = "ResourceManagerEndpoint" GraphResourceID = "GraphResourceID" SQLManagementEndpoint = "SQLManagementEndpoint" GalleryEndpoint = "GalleryEndpoint" ManagementEndpoint = "ManagementEndpoint" ) // NewAuthorizerFromEnvironment creates an Authorizer configured from environment variables in the order: // 1. Client credentials // 2. Client certificate // 3. Username password // 4. MSI func NewAuthorizerFromEnvironment() (autorest.Authorizer, error) { settings, err := GetSettingsFromEnvironment() if err != nil { return nil, err } return settings.GetAuthorizer() } // NewAuthorizerFromEnvironmentWithResource creates an Authorizer configured from environment variables in the order: // 1. Client credentials // 2. Client certificate // 3. Username password // 4. MSI func NewAuthorizerFromEnvironmentWithResource(resource string) (autorest.Authorizer, error) { settings, err := GetSettingsFromEnvironment() if err != nil { return nil, err } settings.Values[Resource] = resource return settings.GetAuthorizer() } // EnvironmentSettings contains the available authentication settings. type EnvironmentSettings struct { Values map[string]string Environment azure.Environment } // GetSettingsFromEnvironment returns the available authentication settings from the environment. func GetSettingsFromEnvironment() (s EnvironmentSettings, err error) { s = EnvironmentSettings{ Values: map[string]string{}, } s.setValue(SubscriptionID) s.setValue(TenantID) s.setValue(AuxiliaryTenantIDs) s.setValue(ClientID) s.setValue(ClientSecret) s.setValue(CertificatePath) s.setValue(CertificatePassword) s.setValue(Username) s.setValue(Password) s.setValue(EnvironmentName) s.setValue(Resource) if v := s.Values[EnvironmentName]; v == "" { s.Environment = azure.PublicCloud } else { s.Environment, err = azure.EnvironmentFromName(v) } if s.Values[Resource] == "" { s.Values[Resource] = s.Environment.ResourceManagerEndpoint } return } // GetSubscriptionID returns the available subscription ID or an empty string. func (settings EnvironmentSettings) GetSubscriptionID() string { return settings.Values[SubscriptionID] } // adds the specified environment variable value to the Values map if it exists func (settings EnvironmentSettings) setValue(key string) { if v := os.Getenv(key); v != "" { settings.Values[key] = v } } // helper to return client and tenant IDs func (settings EnvironmentSettings) getClientAndTenant() (string, string) { clientID := settings.Values[ClientID] tenantID := settings.Values[TenantID] return clientID, tenantID } // GetClientCredentials creates a config object from the available client credentials. // An error is returned if no client credentials are available. func (settings EnvironmentSettings) GetClientCredentials() (ClientCredentialsConfig, error) { secret := settings.Values[ClientSecret] if secret == "" { return ClientCredentialsConfig{}, errors.New("missing client secret") } clientID, tenantID := settings.getClientAndTenant() config := NewClientCredentialsConfig(clientID, secret, tenantID) config.AADEndpoint = settings.Environment.ActiveDirectoryEndpoint config.Resource = settings.Values[Resource] if auxTenants, ok := settings.Values[AuxiliaryTenantIDs]; ok { config.AuxTenants = strings.Split(auxTenants, ";") for i := range config.AuxTenants { config.AuxTenants[i] = strings.TrimSpace(config.AuxTenants[i]) } } return config, nil } // GetClientCertificate creates a config object from the available certificate credentials. // An error is returned if no certificate credentials are available. func (settings EnvironmentSettings) GetClientCertificate() (ClientCertificateConfig, error) { certPath := settings.Values[CertificatePath] if certPath == "" { return ClientCertificateConfig{}, errors.New("missing certificate path") } certPwd := settings.Values[CertificatePassword] clientID, tenantID := settings.getClientAndTenant() config := NewClientCertificateConfig(certPath, certPwd, clientID, tenantID) config.AADEndpoint = settings.Environment.ActiveDirectoryEndpoint config.Resource = settings.Values[Resource] return config, nil } // GetUsernamePassword creates a config object from the available username/password credentials. // An error is returned if no username/password credentials are available. func (settings EnvironmentSettings) GetUsernamePassword() (UsernamePasswordConfig, error) { username := settings.Values[Username] password := settings.Values[Password] if username == "" || password == "" { return UsernamePasswordConfig{}, errors.New("missing username/password") } clientID, tenantID := settings.getClientAndTenant() config := NewUsernamePasswordConfig(username, password, clientID, tenantID) config.AADEndpoint = settings.Environment.ActiveDirectoryEndpoint config.Resource = settings.Values[Resource] return config, nil } // GetMSI creates a MSI config object from the available client ID. func (settings EnvironmentSettings) GetMSI() MSIConfig { config := NewMSIConfig() config.Resource = settings.Values[Resource] config.ClientID = settings.Values[ClientID] return config } // GetDeviceFlow creates a device-flow config object from the available client and tenant IDs. func (settings EnvironmentSettings) GetDeviceFlow() DeviceFlowConfig { clientID, tenantID := settings.getClientAndTenant() config := NewDeviceFlowConfig(clientID, tenantID) config.AADEndpoint = settings.Environment.ActiveDirectoryEndpoint config.Resource = settings.Values[Resource] return config } // GetAuthorizer creates an Authorizer configured from environment variables in the order: // 1. Client credentials // 2. Client certificate // 3. Username password // 4. MSI func (settings EnvironmentSettings) GetAuthorizer() (autorest.Authorizer, error) { //1.Client Credentials if c, e := settings.GetClientCredentials(); e == nil { return c.Authorizer() } //2. Client Certificate if c, e := settings.GetClientCertificate(); e == nil { return c.Authorizer() } //3. Username Password if c, e := settings.GetUsernamePassword(); e == nil { return c.Authorizer() } // 4. MSI return settings.GetMSI().Authorizer() } // NewAuthorizerFromFile creates an Authorizer configured from a configuration file in the following order. // 1. Client credentials // 2. Client certificate func NewAuthorizerFromFile(baseURI string) (autorest.Authorizer, error) { settings, err := GetSettingsFromFile() if err != nil { return nil, err } if a, err := settings.ClientCredentialsAuthorizer(baseURI); err == nil { return a, err } if a, err := settings.ClientCertificateAuthorizer(baseURI); err == nil { return a, err } return nil, errors.New("auth file missing client and certificate credentials") } // NewAuthorizerFromFileWithResource creates an Authorizer configured from a configuration file in the following order. // 1. Client credentials // 2. Client certificate func NewAuthorizerFromFileWithResource(resource string) (autorest.Authorizer, error) { s, err := GetSettingsFromFile() if err != nil { return nil, err } if a, err := s.ClientCredentialsAuthorizerWithResource(resource); err == nil { return a, err } if a, err := s.ClientCertificateAuthorizerWithResource(resource); err == nil { return a, err } return nil, errors.New("auth file missing client and certificate credentials") } // NewAuthorizerFromCLI creates an Authorizer configured from Azure CLI 2.0 for local development scenarios. func NewAuthorizerFromCLI() (autorest.Authorizer, error) { settings, err := GetSettingsFromEnvironment() if err != nil { return nil, err } if settings.Values[Resource] == "" { settings.Values[Resource] = settings.Environment.ResourceManagerEndpoint } return NewAuthorizerFromCLIWithResource(settings.Values[Resource]) } // NewAuthorizerFromCLIWithResource creates an Authorizer configured from Azure CLI 2.0 for local development scenarios. func NewAuthorizerFromCLIWithResource(resource string) (autorest.Authorizer, error) { token, err := cli.GetTokenFromCLI(resource) if err != nil { return nil, err } adalToken, err := token.ToADALToken() if err != nil { return nil, err } return autorest.NewBearerAuthorizer(&adalToken), nil } // GetSettingsFromFile returns the available authentication settings from an Azure CLI authentication file. func GetSettingsFromFile() (FileSettings, error) { s := FileSettings{} fileLocation := os.Getenv("AZURE_AUTH_LOCATION") if fileLocation == "" { return s, errors.New("environment variable AZURE_AUTH_LOCATION is not set") } contents, err := ioutil.ReadFile(fileLocation) if err != nil { return s, err } // Auth file might be encoded decoded, err := decode(contents) if err != nil { return s, err } authFile := map[string]interface{}{} err = json.Unmarshal(decoded, &authFile) if err != nil { return s, err } s.Values = map[string]string{} s.setKeyValue(ClientID, authFile["clientId"]) s.setKeyValue(ClientSecret, authFile["clientSecret"]) s.setKeyValue(CertificatePath, authFile["clientCertificate"]) s.setKeyValue(CertificatePassword, authFile["clientCertificatePassword"]) s.setKeyValue(SubscriptionID, authFile["subscriptionId"]) s.setKeyValue(TenantID, authFile["tenantId"]) s.setKeyValue(ActiveDirectoryEndpoint, authFile["activeDirectoryEndpointUrl"]) s.setKeyValue(ResourceManagerEndpoint, authFile["resourceManagerEndpointUrl"]) s.setKeyValue(GraphResourceID, authFile["activeDirectoryGraphResourceId"]) s.setKeyValue(SQLManagementEndpoint, authFile["sqlManagementEndpointUrl"]) s.setKeyValue(GalleryEndpoint, authFile["galleryEndpointUrl"]) s.setKeyValue(ManagementEndpoint, authFile["managementEndpointUrl"]) return s, nil } // FileSettings contains the available authentication settings. type FileSettings struct { Values map[string]string } // GetSubscriptionID returns the available subscription ID or an empty string. func (settings FileSettings) GetSubscriptionID() string { return settings.Values[SubscriptionID] } // adds the specified value to the Values map if it isn't nil func (settings FileSettings) setKeyValue(key string, val interface{}) { if val != nil { settings.Values[key] = val.(string) } } // returns the specified AAD endpoint or the public cloud endpoint if unspecified func (settings FileSettings) getAADEndpoint() string { if v, ok := settings.Values[ActiveDirectoryEndpoint]; ok { return v } return azure.PublicCloud.ActiveDirectoryEndpoint } // ServicePrincipalTokenFromClientCredentials creates a ServicePrincipalToken from the available client credentials. func (settings FileSettings) ServicePrincipalTokenFromClientCredentials(baseURI string) (*adal.ServicePrincipalToken, error) { resource, err := settings.getResourceForToken(baseURI) if err != nil { return nil, err } return settings.ServicePrincipalTokenFromClientCredentialsWithResource(resource) } // ClientCredentialsAuthorizer creates an authorizer from the available client credentials. func (settings FileSettings) ClientCredentialsAuthorizer(baseURI string) (autorest.Authorizer, error) { resource, err := settings.getResourceForToken(baseURI) if err != nil { return nil, err } return settings.ClientCredentialsAuthorizerWithResource(resource) } // ServicePrincipalTokenFromClientCredentialsWithResource creates a ServicePrincipalToken // from the available client credentials and the specified resource. func (settings FileSettings) ServicePrincipalTokenFromClientCredentialsWithResource(resource string) (*adal.ServicePrincipalToken, error) { if _, ok := settings.Values[ClientSecret]; !ok { return nil, errors.New("missing client secret") } config, err := adal.NewOAuthConfig(settings.getAADEndpoint(), settings.Values[TenantID]) if err != nil { return nil, err } return adal.NewServicePrincipalToken(*config, settings.Values[ClientID], settings.Values[ClientSecret], resource) } func (settings FileSettings) clientCertificateConfigWithResource(resource string) (ClientCertificateConfig, error) { if _, ok := settings.Values[CertificatePath]; !ok { return ClientCertificateConfig{}, errors.New("missing certificate path") } cfg := NewClientCertificateConfig(settings.Values[CertificatePath], settings.Values[CertificatePassword], settings.Values[ClientID], settings.Values[TenantID]) cfg.AADEndpoint = settings.getAADEndpoint() cfg.Resource = resource return cfg, nil } // ClientCredentialsAuthorizerWithResource creates an authorizer from the available client credentials and the specified resource. func (settings FileSettings) ClientCredentialsAuthorizerWithResource(resource string) (autorest.Authorizer, error) { spToken, err := settings.ServicePrincipalTokenFromClientCredentialsWithResource(resource) if err != nil { return nil, err } return autorest.NewBearerAuthorizer(spToken), nil } // ServicePrincipalTokenFromClientCertificate creates a ServicePrincipalToken from the available certificate credentials. func (settings FileSettings) ServicePrincipalTokenFromClientCertificate(baseURI string) (*adal.ServicePrincipalToken, error) { resource, err := settings.getResourceForToken(baseURI) if err != nil { return nil, err } return settings.ServicePrincipalTokenFromClientCertificateWithResource(resource) } // ClientCertificateAuthorizer creates an authorizer from the available certificate credentials. func (settings FileSettings) ClientCertificateAuthorizer(baseURI string) (autorest.Authorizer, error) { resource, err := settings.getResourceForToken(baseURI) if err != nil { return nil, err } return settings.ClientCertificateAuthorizerWithResource(resource) } // ServicePrincipalTokenFromClientCertificateWithResource creates a ServicePrincipalToken from the available certificate credentials. func (settings FileSettings) ServicePrincipalTokenFromClientCertificateWithResource(resource string) (*adal.ServicePrincipalToken, error) { cfg, err := settings.clientCertificateConfigWithResource(resource) if err != nil { return nil, err } return cfg.ServicePrincipalToken() } // ClientCertificateAuthorizerWithResource creates an authorizer from the available certificate credentials and the specified resource. func (settings FileSettings) ClientCertificateAuthorizerWithResource(resource string) (autorest.Authorizer, error) { cfg, err := settings.clientCertificateConfigWithResource(resource) if err != nil { return nil, err } return cfg.Authorizer() } func decode(b []byte) ([]byte, error) { reader, enc := utfbom.Skip(bytes.NewReader(b)) switch enc { case utfbom.UTF16LittleEndian: u16 := make([]uint16, (len(b)/2)-1) err := binary.Read(reader, binary.LittleEndian, &u16) if err != nil { return nil, err } return []byte(string(utf16.Decode(u16))), nil case utfbom.UTF16BigEndian: u16 := make([]uint16, (len(b)/2)-1) err := binary.Read(reader, binary.BigEndian, &u16) if err != nil { return nil, err } return []byte(string(utf16.Decode(u16))), nil } return ioutil.ReadAll(reader) } func (settings FileSettings) getResourceForToken(baseURI string) (string, error) { // Compare default base URI from the SDK to the endpoints from the public cloud // Base URI and token resource are the same string. This func finds the authentication // file field that matches the SDK base URI. The SDK defines the public cloud // endpoint as its default base URI if !strings.HasSuffix(baseURI, "/") { baseURI += "/" } switch baseURI { case azure.PublicCloud.ServiceManagementEndpoint: return settings.Values[ManagementEndpoint], nil case azure.PublicCloud.ResourceManagerEndpoint: return settings.Values[ResourceManagerEndpoint], nil case azure.PublicCloud.ActiveDirectoryEndpoint: return settings.Values[ActiveDirectoryEndpoint], nil case azure.PublicCloud.GalleryEndpoint: return settings.Values[GalleryEndpoint], nil case azure.PublicCloud.GraphEndpoint: return settings.Values[GraphResourceID], nil } return "", fmt.Errorf("auth: base URI not found in endpoints") } // NewClientCredentialsConfig creates an AuthorizerConfig object configured to obtain an Authorizer through Client Credentials. // Defaults to Public Cloud and Resource Manager Endpoint. func NewClientCredentialsConfig(clientID string, clientSecret string, tenantID string) ClientCredentialsConfig { return ClientCredentialsConfig{ ClientID: clientID, ClientSecret: clientSecret, TenantID: tenantID, Resource: azure.PublicCloud.ResourceManagerEndpoint, AADEndpoint: azure.PublicCloud.ActiveDirectoryEndpoint, } } // NewClientCertificateConfig creates a ClientCertificateConfig object configured to obtain an Authorizer through client certificate. // Defaults to Public Cloud and Resource Manager Endpoint. func NewClientCertificateConfig(certificatePath string, certificatePassword string, clientID string, tenantID string) ClientCertificateConfig { return ClientCertificateConfig{ CertificatePath: certificatePath, CertificatePassword: certificatePassword, ClientID: clientID, TenantID: tenantID, Resource: azure.PublicCloud.ResourceManagerEndpoint, AADEndpoint: azure.PublicCloud.ActiveDirectoryEndpoint, } } // NewUsernamePasswordConfig creates an UsernamePasswordConfig object configured to obtain an Authorizer through username and password. // Defaults to Public Cloud and Resource Manager Endpoint. func NewUsernamePasswordConfig(username string, password string, clientID string, tenantID string) UsernamePasswordConfig { return UsernamePasswordConfig{ Username: username, Password: password, ClientID: clientID, TenantID: tenantID, Resource: azure.PublicCloud.ResourceManagerEndpoint, AADEndpoint: azure.PublicCloud.ActiveDirectoryEndpoint, } } // NewMSIConfig creates an MSIConfig object configured to obtain an Authorizer through MSI. func NewMSIConfig() MSIConfig { return MSIConfig{ Resource: azure.PublicCloud.ResourceManagerEndpoint, } } // NewDeviceFlowConfig creates a DeviceFlowConfig object configured to obtain an Authorizer through device flow. // Defaults to Public Cloud and Resource Manager Endpoint. func NewDeviceFlowConfig(clientID string, tenantID string) DeviceFlowConfig { return DeviceFlowConfig{ ClientID: clientID, TenantID: tenantID, Resource: azure.PublicCloud.ResourceManagerEndpoint, AADEndpoint: azure.PublicCloud.ActiveDirectoryEndpoint, } } //AuthorizerConfig provides an authorizer from the configuration provided. type AuthorizerConfig interface { Authorizer() (autorest.Authorizer, error) } // ClientCredentialsConfig provides the options to get a bearer authorizer from client credentials. type ClientCredentialsConfig struct { ClientID string ClientSecret string TenantID string AuxTenants []string AADEndpoint string Resource string } // ServicePrincipalToken creates a ServicePrincipalToken from client credentials. func (ccc ClientCredentialsConfig) ServicePrincipalToken() (*adal.ServicePrincipalToken, error) { oauthConfig, err := adal.NewOAuthConfig(ccc.AADEndpoint, ccc.TenantID) if err != nil { return nil, err } return adal.NewServicePrincipalToken(*oauthConfig, ccc.ClientID, ccc.ClientSecret, ccc.Resource) } // MultiTenantServicePrincipalToken creates a MultiTenantServicePrincipalToken from client credentials. func (ccc ClientCredentialsConfig) MultiTenantServicePrincipalToken() (*adal.MultiTenantServicePrincipalToken, error) { oauthConfig, err := adal.NewMultiTenantOAuthConfig(ccc.AADEndpoint, ccc.TenantID, ccc.AuxTenants, adal.OAuthOptions{}) if err != nil { return nil, err } return adal.NewMultiTenantServicePrincipalToken(oauthConfig, ccc.ClientID, ccc.ClientSecret, ccc.Resource) } // Authorizer gets the authorizer from client credentials. func (ccc ClientCredentialsConfig) Authorizer() (autorest.Authorizer, error) { if len(ccc.AuxTenants) == 0 { spToken, err := ccc.ServicePrincipalToken() if err != nil { return nil, fmt.Errorf("failed to get SPT from client credentials: %v", err) } return autorest.NewBearerAuthorizer(spToken), nil } mtSPT, err := ccc.MultiTenantServicePrincipalToken() if err != nil { return nil, fmt.Errorf("failed to get multitenant SPT from client credentials: %v", err) } return autorest.NewMultiTenantServicePrincipalTokenAuthorizer(mtSPT), nil } // ClientCertificateConfig provides the options to get a bearer authorizer from a client certificate. type ClientCertificateConfig struct { ClientID string CertificatePath string CertificatePassword string TenantID string AADEndpoint string Resource string } // ServicePrincipalToken creates a ServicePrincipalToken from client certificate. func (ccc ClientCertificateConfig) ServicePrincipalToken() (*adal.ServicePrincipalToken, error) { oauthConfig, err := adal.NewOAuthConfig(ccc.AADEndpoint, ccc.TenantID) if err != nil { return nil, err } certData, err := ioutil.ReadFile(ccc.CertificatePath) if err != nil { return nil, fmt.Errorf("failed to read the certificate file (%s): %v", ccc.CertificatePath, err) } certificate, rsaPrivateKey, err := decodePkcs12(certData, ccc.CertificatePassword) if err != nil { return nil, fmt.Errorf("failed to decode pkcs12 certificate while creating spt: %v", err) } return adal.NewServicePrincipalTokenFromCertificate(*oauthConfig, ccc.ClientID, certificate, rsaPrivateKey, ccc.Resource) } // Authorizer gets an authorizer object from client certificate. func (ccc ClientCertificateConfig) Authorizer() (autorest.Authorizer, error) { spToken, err := ccc.ServicePrincipalToken() if err != nil { return nil, fmt.Errorf("failed to get oauth token from certificate auth: %v", err) } return autorest.NewBearerAuthorizer(spToken), nil } // DeviceFlowConfig provides the options to get a bearer authorizer using device flow authentication. type DeviceFlowConfig struct { ClientID string TenantID string AADEndpoint string Resource string } // Authorizer gets the authorizer from device flow. func (dfc DeviceFlowConfig) Authorizer() (autorest.Authorizer, error) { spToken, err := dfc.ServicePrincipalToken() if err != nil { return nil, fmt.Errorf("failed to get oauth token from device flow: %v", err) } return autorest.NewBearerAuthorizer(spToken), nil } // ServicePrincipalToken gets the service principal token from device flow. func (dfc DeviceFlowConfig) ServicePrincipalToken() (*adal.ServicePrincipalToken, error) { oauthConfig, err := adal.NewOAuthConfig(dfc.AADEndpoint, dfc.TenantID) if err != nil { return nil, err } oauthClient := &autorest.Client{} deviceCode, err := adal.InitiateDeviceAuth(oauthClient, *oauthConfig, dfc.ClientID, dfc.Resource) if err != nil { return nil, fmt.Errorf("failed to start device auth flow: %s", err) } log.Println(*deviceCode.Message) token, err := adal.WaitForUserCompletion(oauthClient, deviceCode) if err != nil { return nil, fmt.Errorf("failed to finish device auth flow: %s", err) } return adal.NewServicePrincipalTokenFromManualToken(*oauthConfig, dfc.ClientID, dfc.Resource, *token) } func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.PrivateKey, error) { privateKey, certificate, err := pkcs12.Decode(pkcs, password) if err != nil { return nil, nil, err } rsaPrivateKey, isRsaKey := privateKey.(*rsa.PrivateKey) if !isRsaKey { return nil, nil, fmt.Errorf("PKCS#12 certificate must contain an RSA private key") } return certificate, rsaPrivateKey, nil } // UsernamePasswordConfig provides the options to get a bearer authorizer from a username and a password. type UsernamePasswordConfig struct { ClientID string Username string Password string TenantID string AADEndpoint string Resource string } // ServicePrincipalToken creates a ServicePrincipalToken from username and password. func (ups UsernamePasswordConfig) ServicePrincipalToken() (*adal.ServicePrincipalToken, error) { oauthConfig, err := adal.NewOAuthConfig(ups.AADEndpoint, ups.TenantID) if err != nil { return nil, err } return adal.NewServicePrincipalTokenFromUsernamePassword(*oauthConfig, ups.ClientID, ups.Username, ups.Password, ups.Resource) } // Authorizer gets the authorizer from a username and a password. func (ups UsernamePasswordConfig) Authorizer() (autorest.Authorizer, error) { spToken, err := ups.ServicePrincipalToken() if err != nil { return nil, fmt.Errorf("failed to get oauth token from username and password auth: %v", err) } return autorest.NewBearerAuthorizer(spToken), nil } // MSIConfig provides the options to get a bearer authorizer through MSI. type MSIConfig struct { Resource string ClientID string } // Authorizer gets the authorizer from MSI. func (mc MSIConfig) Authorizer() (autorest.Authorizer, error) { msiEndpoint, err := adal.GetMSIEndpoint() if err != nil { return nil, err } var spToken *adal.ServicePrincipalToken if mc.ClientID == "" { spToken, err = adal.NewServicePrincipalTokenFromMSI(msiEndpoint, mc.Resource) if err != nil { return nil, fmt.Errorf("failed to get oauth token from MSI: %v", err) } } else { spToken, err = adal.NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint, mc.Resource, mc.ClientID) if err != nil { return nil, fmt.Errorf("failed to get oauth token from MSI for user assigned identity: %v", err) } } return autorest.NewBearerAuthorizer(spToken), nil } golang-github-azure-go-autorest-14.1.1/autorest/azure/auth/auth_test.go000066400000000000000000000216471367372352400262320ustar00rootroot00000000000000// Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package auth import ( "os" "path/filepath" "reflect" "testing" "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/azure" ) var ( expectedEnvironment = EnvironmentSettings{ Values: map[string]string{ SubscriptionID: "sub-abc-123", TenantID: "tenant-abc-123", ClientID: "client-abc-123", ClientSecret: "client-secret-123", CertificatePath: "~/some/path/cert.pfx", CertificatePassword: "certificate-password", Username: "user-name-abc", Password: "user-password-123", Resource: "my-resource", }, Environment: azure.PublicCloud, } expectedFile = FileSettings{ Values: map[string]string{ ClientID: "client-id-123", ClientSecret: "client-secret-456", SubscriptionID: "sub-id-789", TenantID: "tenant-id-123", ActiveDirectoryEndpoint: "https://login.microsoftonline.com", ResourceManagerEndpoint: "https://management.azure.com/", GraphResourceID: "https://graph.windows.net/", SQLManagementEndpoint: "https://management.core.windows.net:8443/", GalleryEndpoint: "https://gallery.azure.com/", ManagementEndpoint: "https://management.core.windows.net/", }, } ) func setDefaultEnv() { os.Setenv(SubscriptionID, expectedEnvironment.Values[SubscriptionID]) os.Setenv(TenantID, expectedEnvironment.Values[TenantID]) os.Setenv(ClientID, expectedEnvironment.Values[ClientID]) os.Setenv(ClientSecret, expectedEnvironment.Values[ClientSecret]) os.Setenv(CertificatePath, expectedEnvironment.Values[CertificatePath]) os.Setenv(CertificatePassword, expectedEnvironment.Values[CertificatePassword]) os.Setenv(Username, expectedEnvironment.Values[Username]) os.Setenv(Password, expectedEnvironment.Values[Password]) os.Setenv(Resource, expectedEnvironment.Values[Resource]) } func TestGetSettingsFromEnvironment(t *testing.T) { setDefaultEnv() settings, err := GetSettingsFromEnvironment() if err != nil { t.Logf("failed to get settings: %v", err) t.Fail() } if !reflect.DeepEqual(expectedEnvironment, settings) { t.Logf("expected %v, got %v", expectedEnvironment, settings) t.Fail() } if settings.GetSubscriptionID() != expectedEnvironment.Values[SubscriptionID] { t.Log("settings.GetSubscriptionID() return value didn't match") t.Fail() } } func TestGetSettingsFromEnvironmentBadEnvironmentName(t *testing.T) { os.Setenv(EnvironmentName, "badenvironment") defer func() { // must undo this value else other tests will fail os.Setenv(EnvironmentName, "") }() _, err := GetSettingsFromEnvironment() if err == nil { t.Log("unexpected nil error") t.Fail() } } func TestEnvGetClientCertificate(t *testing.T) { setDefaultEnv() settings, err := GetSettingsFromEnvironment() if err != nil { t.Logf("failed to get settings: %v", err) t.Fail() } cfg, err := settings.GetClientCertificate() if err != nil { t.Logf("failed to get config for client cert: %v", err) t.Fail() } if cfg.CertificatePath != expectedEnvironment.Values[CertificatePath] { t.Log("bad certificate path") t.Fail() } if cfg.CertificatePassword != expectedEnvironment.Values[CertificatePassword] { t.Log("bad certificate password") t.Fail() } // should fail as the certificate doesn't exist _, err = cfg.Authorizer() if err == nil { t.Log("unexpected nil error") t.Fail() } } func TestEnvGetUsernamePassword(t *testing.T) { setDefaultEnv() settings, err := GetSettingsFromEnvironment() if err != nil { t.Logf("failed to get settings: %v", err) t.Fail() } cfg, err := settings.GetUsernamePassword() if err != nil { t.Logf("failed to get config for username/password: %v", err) t.Fail() } _, err = cfg.Authorizer() if err != nil { t.Logf("failed to get authorizer for username/password: %v", err) t.Fail() } } func TestEnvGetMSI(t *testing.T) { setDefaultEnv() settings, err := GetSettingsFromEnvironment() if err != nil { t.Logf("failed to get settings: %v", err) t.Fail() } cfg := settings.GetMSI() _, err = cfg.Authorizer() if err != nil { t.Logf("failed to get authorizer for MSI: %v", err) t.Fail() } } func TestEnvGetDeviceFlow(t *testing.T) { setDefaultEnv() settings, err := GetSettingsFromEnvironment() if err != nil { t.Logf("failed to get settings: %v", err) t.Fail() } cfg := settings.GetDeviceFlow() // TODO mock device flow? if cfg.ClientID != expectedEnvironment.Values[ClientID] { t.Log("bad client ID") t.Fail() } if cfg.TenantID != expectedEnvironment.Values[TenantID] { t.Log("bad tenant ID") t.Fail() } } func TestGetSettingsFromFile(t *testing.T) { os.Setenv("AZURE_AUTH_LOCATION", "./testdata/credsutf16le.json") settings, err := GetSettingsFromFile() if err != nil { t.Logf("failed to load config file: %v", err) t.Fail() } if !reflect.DeepEqual(expectedFile, settings) { t.Logf("expected %v, got %v", expectedFile, settings) t.Fail() } if settings.GetSubscriptionID() != expectedFile.Values[SubscriptionID] { t.Log("settings.GetSubscriptionID() return value didn't match") t.Fail() } } func TestNewAuthorizerFromFile(t *testing.T) { os.Setenv("AZURE_AUTH_LOCATION", "./testdata/credsutf16le.json") authorizer, err := NewAuthorizerFromFile("https://management.azure.com") if err != nil || authorizer == nil { t.Logf("NewAuthorizerFromFile failed, got error %v", err) t.Fail() } } func TestNewAuthorizerFromFileWithResource(t *testing.T) { os.Setenv("AZURE_AUTH_LOCATION", "./testdata/credsutf16le.json") authorizer, err := NewAuthorizerFromFileWithResource("https://my.vault.azure.net") if err != nil || authorizer == nil { t.Logf("NewAuthorizerFromFileWithResource failed, got error %v", err) t.Fail() } } func TestNewAuthorizerFromEnvironment(t *testing.T) { setDefaultEnv() authorizer, err := NewAuthorizerFromEnvironment() if err != nil || authorizer == nil { t.Logf("NewAuthorizerFromEnvironment failed, got error %v", err) t.Fail() } } func TestNewAuthorizerFromEnvironmentWithResource(t *testing.T) { setDefaultEnv() authorizer, err := NewAuthorizerFromEnvironmentWithResource("https://my.vault.azure.net") if err != nil || authorizer == nil { t.Logf("NewAuthorizerFromEnvironmentWithResource failed, got error %v", err) t.Fail() } } func TestDecodeAndUnmarshal(t *testing.T) { tests := []string{ "credsutf8.json", "credsutf16le.json", "credsutf16be.json", } for _, test := range tests { os.Setenv("AZURE_AUTH_LOCATION", filepath.Join("./testdata/", test)) settings, err := GetSettingsFromFile() if err != nil { t.Logf("error reading file '%s': %s", test, err) t.Fail() } if !reflect.DeepEqual(expectedFile, settings) { t.Logf("unmarshaled map expected %v, got %v", expectedFile, settings) t.Fail() } } } func TestFileClientCertificateAuthorizer(t *testing.T) { os.Setenv("AZURE_AUTH_LOCATION", "./testdata/credsutf8.json") settings, err := GetSettingsFromFile() if err != nil { t.Logf("failed to load file settings: %v", err) t.Fail() } // add certificate settings settings.Values[CertificatePath] = "~/fake/path/cert.pfx" settings.Values[CertificatePassword] = "fake-password" _, err = settings.ClientCertificateAuthorizer("https://management.azure.com") if err == nil { t.Log("unexpected nil error") t.Fail() } } func TestMultitenantClientCredentials(t *testing.T) { setDefaultEnv() os.Setenv(AuxiliaryTenantIDs, "aux-tenant-1;aux-tenant-2;aux-tenant3") defer func() { os.Setenv(AuxiliaryTenantIDs, "") }() settings, err := GetSettingsFromEnvironment() if err != nil { t.Fatalf("failed to get settings from environment: %v", err) } if settings.Values[AuxiliaryTenantIDs] == "" { t.Fatal("auxiliary tenant IDs are missing in settings") } ccc, err := settings.GetClientCredentials() if err != nil { t.Fatalf("failed to get client credentials config: %v", err) } if len(ccc.AuxTenants) == 0 { t.Fatal("auxiliary tenant IDs are missing in config") } expected := []string{"aux-tenant-1", "aux-tenant-2", "aux-tenant3"} if !reflect.DeepEqual(ccc.AuxTenants, expected) { t.Fatalf("expected auxiliary tenants '%s', got '%s'", expected, ccc.AuxTenants) } a, err := ccc.Authorizer() if err != nil { t.Fatalf("failed to create authorizer: %v", err) } if _, ok := a.(autorest.MultiTenantServicePrincipalTokenAuthorizer); !ok { t.Fatal("authorizer doesn't implement MultiTenantServicePrincipalTokenAuthorizer") } } golang-github-azure-go-autorest-14.1.1/autorest/azure/auth/go.mod000066400000000000000000000005041367372352400247760ustar00rootroot00000000000000module github.com/Azure/go-autorest/autorest/azure/auth go 1.12 require ( github.com/Azure/go-autorest/autorest v0.9.3 github.com/Azure/go-autorest/autorest/adal v0.8.1 github.com/Azure/go-autorest/autorest/azure/cli v0.3.1 github.com/dimchansky/utfbom v1.1.0 golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 ) golang-github-azure-go-autorest-14.1.1/autorest/azure/auth/go.sum000066400000000000000000000074601367372352400250330ustar00rootroot00000000000000github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest v0.9.3 h1:OZEIaBbMdUE/Js+BQKlpO81XlISgipr6yDJ+PSwsgi4= github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0= github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.8.0 h1:CxTzQrySOxDnKpLjFJeZAS5Qrv/qFPkgLjx5bOAi//I= github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= github.com/Azure/go-autorest/autorest/adal v0.8.1 h1:pZdL8o72rK+avFWl+p9nE8RWi1JInZrWJYlnpfXJwHk= github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= github.com/Azure/go-autorest/autorest/azure/cli v0.3.1 h1:LXl088ZQlP0SBppGFsRZonW6hSvwgL5gRByMbvUbx8U= github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw= github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSWlm5Ew6bxipnr/tbM= github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= github.com/Azure/go-autorest/autorest/mocks v0.1.0 h1:Kx+AUU2Te+A3JIyYn6Dfs+cFgx5XorQKuIXrZGoq/SI= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0 h1:Ww5g4zThfD/6cLb4z6xxgeyDa7QDkizMkJKe0ysZXp0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.3.0 h1:qJumjCaCudz+OcqE9/XtEPfvtOjOmKaui4EOpFI6zZc= github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang-github-azure-go-autorest-14.1.1/autorest/azure/auth/go_mod_tidy_hack.go000066400000000000000000000017161367372352400275100ustar00rootroot00000000000000// +build modhack package auth // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // This file, and the github.com/Azure/go-autorest/autorest import, won't actually become part of // the resultant binary. // Necessary for safely adding multi-module repo. // See: https://github.com/golang/go/wiki/Modules#is-it-possible-to-add-a-module-to-a-multi-module-repository import _ "github.com/Azure/go-autorest/autorest" golang-github-azure-go-autorest-14.1.1/autorest/azure/auth/testdata/000077500000000000000000000000001367372352400255025ustar00rootroot00000000000000golang-github-azure-go-autorest-14.1.1/autorest/azure/auth/testdata/credsutf16be.json000066400000000000000000000021061367372352400306710ustar00rootroot00000000000000þÿ{ "clientId": "client-id-123", "clientSecret": "client-secret-456", "subscriptionId": "sub-id-789", "tenantId": "tenant-id-123", "activeDirectoryEndpointUrl": "https://login.microsoftonline.com", "resourceManagerEndpointUrl": "https://management.azure.com/", "activeDirectoryGraphResourceId": "https://graph.windows.net/", "sqlManagementEndpointUrl": "https://management.core.windows.net:8443/", "galleryEndpointUrl": "https://gallery.azure.com/", "managementEndpointUrl": "https://management.core.windows.net/" } golang-github-azure-go-autorest-14.1.1/autorest/azure/auth/testdata/credsutf16le.json000066400000000000000000000021061367372352400307030ustar00rootroot00000000000000ÿþ{ "clientId": "client-id-123", "clientSecret": "client-secret-456", "subscriptionId": "sub-id-789", "tenantId": "tenant-id-123", "activeDirectoryEndpointUrl": "https://login.microsoftonline.com", "resourceManagerEndpointUrl": "https://management.azure.com/", "activeDirectoryGraphResourceId": "https://graph.windows.net/", "sqlManagementEndpointUrl": "https://management.core.windows.net:8443/", "galleryEndpointUrl": "https://gallery.azure.com/", "managementEndpointUrl": "https://management.core.windows.net/" } golang-github-azure-go-autorest-14.1.1/autorest/azure/auth/testdata/credsutf8.json000066400000000000000000000010311367372352400302770ustar00rootroot00000000000000{ "clientId": "client-id-123", "clientSecret": "client-secret-456", "subscriptionId": "sub-id-789", "tenantId": "tenant-id-123", "activeDirectoryEndpointUrl": "https://login.microsoftonline.com", "resourceManagerEndpointUrl": "https://management.azure.com/", "activeDirectoryGraphResourceId": "https://graph.windows.net/", "sqlManagementEndpointUrl": "https://management.core.windows.net:8443/", "galleryEndpointUrl": "https://gallery.azure.com/", "managementEndpointUrl": "https://management.core.windows.net/" } golang-github-azure-go-autorest-14.1.1/autorest/azure/azure.go000066400000000000000000000267701367372352400244210ustar00rootroot00000000000000// Package azure provides Azure-specific implementations used with AutoRest. // See the included examples for more detail. package azure // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "regexp" "strconv" "strings" "github.com/Azure/go-autorest/autorest" ) const ( // HeaderClientID is the Azure extension header to set a user-specified request ID. HeaderClientID = "x-ms-client-request-id" // HeaderReturnClientID is the Azure extension header to set if the user-specified request ID // should be included in the response. HeaderReturnClientID = "x-ms-return-client-request-id" // HeaderRequestID is the Azure extension header of the service generated request ID returned // in the response. HeaderRequestID = "x-ms-request-id" ) // ServiceError encapsulates the error response from an Azure service. // It adhears to the OData v4 specification for error responses. type ServiceError struct { Code string `json:"code"` Message string `json:"message"` Target *string `json:"target"` Details []map[string]interface{} `json:"details"` InnerError map[string]interface{} `json:"innererror"` AdditionalInfo []map[string]interface{} `json:"additionalInfo"` } func (se ServiceError) Error() string { result := fmt.Sprintf("Code=%q Message=%q", se.Code, se.Message) if se.Target != nil { result += fmt.Sprintf(" Target=%q", *se.Target) } if se.Details != nil { d, err := json.Marshal(se.Details) if err != nil { result += fmt.Sprintf(" Details=%v", se.Details) } result += fmt.Sprintf(" Details=%v", string(d)) } if se.InnerError != nil { d, err := json.Marshal(se.InnerError) if err != nil { result += fmt.Sprintf(" InnerError=%v", se.InnerError) } result += fmt.Sprintf(" InnerError=%v", string(d)) } if se.AdditionalInfo != nil { d, err := json.Marshal(se.AdditionalInfo) if err != nil { result += fmt.Sprintf(" AdditionalInfo=%v", se.AdditionalInfo) } result += fmt.Sprintf(" AdditionalInfo=%v", string(d)) } return result } // UnmarshalJSON implements the json.Unmarshaler interface for the ServiceError type. func (se *ServiceError) UnmarshalJSON(b []byte) error { // per the OData v4 spec the details field must be an array of JSON objects. // unfortunately not all services adhear to the spec and just return a single // object instead of an array with one object. so we have to perform some // shenanigans to accommodate both cases. // http://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Toc372793091 type serviceError1 struct { Code string `json:"code"` Message string `json:"message"` Target *string `json:"target"` Details []map[string]interface{} `json:"details"` InnerError map[string]interface{} `json:"innererror"` AdditionalInfo []map[string]interface{} `json:"additionalInfo"` } type serviceError2 struct { Code string `json:"code"` Message string `json:"message"` Target *string `json:"target"` Details map[string]interface{} `json:"details"` InnerError map[string]interface{} `json:"innererror"` AdditionalInfo []map[string]interface{} `json:"additionalInfo"` } se1 := serviceError1{} err := json.Unmarshal(b, &se1) if err == nil { se.populate(se1.Code, se1.Message, se1.Target, se1.Details, se1.InnerError, se1.AdditionalInfo) return nil } se2 := serviceError2{} err = json.Unmarshal(b, &se2) if err == nil { se.populate(se2.Code, se2.Message, se2.Target, nil, se2.InnerError, se2.AdditionalInfo) se.Details = append(se.Details, se2.Details) return nil } return err } func (se *ServiceError) populate(code, message string, target *string, details []map[string]interface{}, inner map[string]interface{}, additional []map[string]interface{}) { se.Code = code se.Message = message se.Target = target se.Details = details se.InnerError = inner se.AdditionalInfo = additional } // RequestError describes an error response returned by Azure service. type RequestError struct { autorest.DetailedError // The error returned by the Azure service. ServiceError *ServiceError `json:"error" xml:"Error"` // The request id (from the x-ms-request-id-header) of the request. RequestID string } // Error returns a human-friendly error message from service error. func (e RequestError) Error() string { return fmt.Sprintf("autorest/azure: Service returned an error. Status=%v %v", e.StatusCode, e.ServiceError) } // IsAzureError returns true if the passed error is an Azure Service error; false otherwise. func IsAzureError(e error) bool { _, ok := e.(*RequestError) return ok } // Resource contains details about an Azure resource. type Resource struct { SubscriptionID string ResourceGroup string Provider string ResourceType string ResourceName string } // ParseResourceID parses a resource ID into a ResourceDetails struct. // See https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-template-functions-resource#return-value-4. func ParseResourceID(resourceID string) (Resource, error) { const resourceIDPatternText = `(?i)subscriptions/(.+)/resourceGroups/(.+)/providers/(.+?)/(.+?)/(.+)` resourceIDPattern := regexp.MustCompile(resourceIDPatternText) match := resourceIDPattern.FindStringSubmatch(resourceID) if len(match) == 0 { return Resource{}, fmt.Errorf("parsing failed for %s. Invalid resource Id format", resourceID) } v := strings.Split(match[5], "/") resourceName := v[len(v)-1] result := Resource{ SubscriptionID: match[1], ResourceGroup: match[2], Provider: match[3], ResourceType: match[4], ResourceName: resourceName, } return result, nil } // NewErrorWithError creates a new Error conforming object from the // passed packageType, method, statusCode of the given resp (UndefinedStatusCode // if resp is nil), message, and original error. message is treated as a format // string to which the optional args apply. func NewErrorWithError(original error, packageType string, method string, resp *http.Response, message string, args ...interface{}) RequestError { if v, ok := original.(*RequestError); ok { return *v } statusCode := autorest.UndefinedStatusCode if resp != nil { statusCode = resp.StatusCode } return RequestError{ DetailedError: autorest.DetailedError{ Original: original, PackageType: packageType, Method: method, StatusCode: statusCode, Message: fmt.Sprintf(message, args...), }, } } // WithReturningClientID returns a PrepareDecorator that adds an HTTP extension header of // x-ms-client-request-id whose value is the passed, undecorated UUID (e.g., // "0F39878C-5F76-4DB8-A25D-61D2C193C3CA"). It also sets the x-ms-return-client-request-id // header to true such that UUID accompanies the http.Response. func WithReturningClientID(uuid string) autorest.PrepareDecorator { preparer := autorest.CreatePreparer( WithClientID(uuid), WithReturnClientID(true)) return func(p autorest.Preparer) autorest.Preparer { return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) { r, err := p.Prepare(r) if err != nil { return r, err } return preparer.Prepare(r) }) } } // WithClientID returns a PrepareDecorator that adds an HTTP extension header of // x-ms-client-request-id whose value is passed, undecorated UUID (e.g., // "0F39878C-5F76-4DB8-A25D-61D2C193C3CA"). func WithClientID(uuid string) autorest.PrepareDecorator { return autorest.WithHeader(HeaderClientID, uuid) } // WithReturnClientID returns a PrepareDecorator that adds an HTTP extension header of // x-ms-return-client-request-id whose boolean value indicates if the value of the // x-ms-client-request-id header should be included in the http.Response. func WithReturnClientID(b bool) autorest.PrepareDecorator { return autorest.WithHeader(HeaderReturnClientID, strconv.FormatBool(b)) } // ExtractClientID extracts the client identifier from the x-ms-client-request-id header set on the // http.Request sent to the service (and returned in the http.Response) func ExtractClientID(resp *http.Response) string { return autorest.ExtractHeaderValue(HeaderClientID, resp) } // ExtractRequestID extracts the Azure server generated request identifier from the // x-ms-request-id header. func ExtractRequestID(resp *http.Response) string { return autorest.ExtractHeaderValue(HeaderRequestID, resp) } // WithErrorUnlessStatusCode returns a RespondDecorator that emits an // azure.RequestError by reading the response body unless the response HTTP status code // is among the set passed. // // If there is a chance service may return responses other than the Azure error // format and the response cannot be parsed into an error, a decoding error will // be returned containing the response body. In any case, the Responder will // return an error if the status code is not satisfied. // // If this Responder returns an error, the response body will be replaced with // an in-memory reader, which needs no further closing. func WithErrorUnlessStatusCode(codes ...int) autorest.RespondDecorator { return func(r autorest.Responder) autorest.Responder { return autorest.ResponderFunc(func(resp *http.Response) error { err := r.Respond(resp) if err == nil && !autorest.ResponseHasStatusCode(resp, codes...) { var e RequestError defer resp.Body.Close() encodedAs := autorest.EncodedAsJSON if strings.Contains(resp.Header.Get("Content-Type"), "xml") { encodedAs = autorest.EncodedAsXML } // Copy and replace the Body in case it does not contain an error object. // This will leave the Body available to the caller. b, decodeErr := autorest.CopyAndDecode(encodedAs, resp.Body, &e) resp.Body = ioutil.NopCloser(&b) if decodeErr != nil { return fmt.Errorf("autorest/azure: error response cannot be parsed: %q error: %v", b.String(), decodeErr) } if e.ServiceError == nil { // Check if error is unwrapped ServiceError decoder := autorest.NewDecoder(encodedAs, bytes.NewReader(b.Bytes())) if err := decoder.Decode(&e.ServiceError); err != nil { return err } } if e.ServiceError.Message == "" { // if we're here it means the returned error wasn't OData v4 compliant. // try to unmarshal the body in hopes of getting something. rawBody := map[string]interface{}{} decoder := autorest.NewDecoder(encodedAs, bytes.NewReader(b.Bytes())) if err := decoder.Decode(&rawBody); err != nil { return err } e.ServiceError = &ServiceError{ Code: "Unknown", Message: "Unknown service error", } if len(rawBody) > 0 { e.ServiceError.Details = []map[string]interface{}{rawBody} } } e.Response = resp e.RequestID = ExtractRequestID(resp) if e.StatusCode == nil { e.StatusCode = resp.StatusCode } err = &e } return err }) } } golang-github-azure-go-autorest-14.1.1/autorest/azure/azure_test.go000066400000000000000000000543311367372352400254520ustar00rootroot00000000000000package azure // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "encoding/json" "fmt" "io/ioutil" "net/http" "reflect" "strconv" "testing" "time" "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/mocks" ) const ( headerAuthorization = "Authorization" longDelay = 5 * time.Second retryDelay = 10 * time.Millisecond testLogPrefix = "azure:" ) // Use a Client Inspector to set the request identifier. func ExampleWithClientID() { uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6" req, _ := autorest.Prepare(&http.Request{}, autorest.AsGet(), autorest.WithBaseURL("https://microsoft.com/a/b/c/")) c := autorest.Client{Sender: mocks.NewSender()} c.RequestInspector = WithReturningClientID(uuid) autorest.SendWithSender(c, req) fmt.Printf("Inspector added the %s header with the value %s\n", HeaderClientID, req.Header.Get(HeaderClientID)) fmt.Printf("Inspector added the %s header with the value %s\n", HeaderReturnClientID, req.Header.Get(HeaderReturnClientID)) // Output: // Inspector added the x-ms-client-request-id header with the value 71FDB9F4-5E49-4C12-B266-DE7B4FD999A6 // Inspector added the x-ms-return-client-request-id header with the value true } func TestWithReturningClientIDReturnsError(t *testing.T) { var errIn error uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6" _, errOut := autorest.Prepare(&http.Request{}, withErrorPrepareDecorator(&errIn), WithReturningClientID(uuid)) if errOut == nil || errIn != errOut { t.Fatalf("azure: WithReturningClientID failed to exit early when receiving an error -- expected (%v), received (%v)", errIn, errOut) } } func TestWithClientID(t *testing.T) { uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6" req, _ := autorest.Prepare(&http.Request{}, WithClientID(uuid)) if req.Header.Get(HeaderClientID) != uuid { t.Fatalf("azure: WithClientID failed to set %s -- expected %s, received %s", HeaderClientID, uuid, req.Header.Get(HeaderClientID)) } } func TestWithReturnClientID(t *testing.T) { b := false req, _ := autorest.Prepare(&http.Request{}, WithReturnClientID(b)) if req.Header.Get(HeaderReturnClientID) != strconv.FormatBool(b) { t.Fatalf("azure: WithReturnClientID failed to set %s -- expected %s, received %s", HeaderClientID, strconv.FormatBool(b), req.Header.Get(HeaderClientID)) } } func TestExtractClientID(t *testing.T) { uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6" resp := mocks.NewResponse() mocks.SetResponseHeader(resp, HeaderClientID, uuid) if ExtractClientID(resp) != uuid { t.Fatalf("azure: ExtractClientID failed to extract the %s -- expected %s, received %s", HeaderClientID, uuid, ExtractClientID(resp)) } } func TestExtractRequestID(t *testing.T) { uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6" resp := mocks.NewResponse() mocks.SetResponseHeader(resp, HeaderRequestID, uuid) if ExtractRequestID(resp) != uuid { t.Fatalf("azure: ExtractRequestID failed to extract the %s -- expected %s, received %s", HeaderRequestID, uuid, ExtractRequestID(resp)) } } func TestIsAzureError_ReturnsTrueForAzureError(t *testing.T) { if !IsAzureError(&RequestError{}) { t.Fatalf("azure: IsAzureError failed to return true for an Azure Service error") } } func TestIsAzureError_ReturnsFalseForNonAzureError(t *testing.T) { if IsAzureError(fmt.Errorf("An Error")) { t.Fatalf("azure: IsAzureError return true for an non-Azure Service error") } } func TestNewErrorWithError_UsesReponseStatusCode(t *testing.T) { e := NewErrorWithError(fmt.Errorf("Error"), "packageType", "method", mocks.NewResponseWithStatus("Forbidden", http.StatusForbidden), "message") if e.StatusCode != http.StatusForbidden { t.Fatalf("azure: NewErrorWithError failed to use the Status Code of the passed Response -- expected %v, received %v", http.StatusForbidden, e.StatusCode) } } func TestNewErrorWithError_ReturnsUnwrappedError(t *testing.T) { e1 := RequestError{} e1.ServiceError = &ServiceError{Code: "42", Message: "A Message"} e1.StatusCode = 200 e1.RequestID = "A RequestID" e2 := NewErrorWithError(&e1, "packageType", "method", nil, "message") if !reflect.DeepEqual(e1, e2) { t.Fatalf("azure: NewErrorWithError wrapped an RequestError -- expected %T, received %T", e1, e2) } } func TestNewErrorWithError_WrapsAnError(t *testing.T) { e1 := fmt.Errorf("Inner Error") var e2 interface{} = NewErrorWithError(e1, "packageType", "method", nil, "message") if _, ok := e2.(RequestError); !ok { t.Fatalf("azure: NewErrorWithError failed to wrap a standard error -- received %T", e2) } } func TestWithErrorUnlessStatusCode_NotAnAzureError(t *testing.T) { body := ` IIS Error page Some non-JSON error page ` r := mocks.NewResponseWithContent(body) r.Request = mocks.NewRequest() r.StatusCode = http.StatusBadRequest r.Status = http.StatusText(r.StatusCode) err := autorest.Respond(r, WithErrorUnlessStatusCode(http.StatusOK), autorest.ByClosing()) ok, _ := err.(*RequestError) if ok != nil { t.Fatalf("azure: azure.RequestError returned from malformed response: %v", err) } // the error body should still be there defer r.Body.Close() b, err := ioutil.ReadAll(r.Body) if err != nil { t.Fatal(err) } if string(b) != body { t.Fatalf("response body is wrong. got=%q exptected=%q", string(b), body) } } func TestWithErrorUnlessStatusCode_FoundAzureErrorWithoutDetails(t *testing.T) { j := `{ "error": { "code": "InternalError", "message": "Azure is having trouble right now." } }` uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6" r := mocks.NewResponseWithContent(j) mocks.SetResponseHeader(r, HeaderRequestID, uuid) r.Request = mocks.NewRequest() r.StatusCode = http.StatusInternalServerError r.Status = http.StatusText(r.StatusCode) err := autorest.Respond(r, WithErrorUnlessStatusCode(http.StatusOK), autorest.ByClosing()) if err == nil { t.Fatalf("azure: returned nil error for proper error response") } azErr, ok := err.(*RequestError) if !ok { t.Fatalf("azure: returned error is not azure.RequestError: %T", err) } expected := "autorest/azure: Service returned an error. Status=500 Code=\"InternalError\" Message=\"Azure is having trouble right now.\"" if !reflect.DeepEqual(expected, azErr.Error()) { t.Fatalf("azure: service error is not unmarshaled properly.\nexpected=%v\ngot=%v", expected, azErr.Error()) } if expected := http.StatusInternalServerError; azErr.StatusCode != expected { t.Fatalf("azure: got wrong StatusCode=%d Expected=%d", azErr.StatusCode, expected) } if expected := uuid; azErr.RequestID != expected { t.Fatalf("azure: wrong request ID in error. expected=%q; got=%q", expected, azErr.RequestID) } _ = azErr.Error() // the error body should still be there defer r.Body.Close() b, err := ioutil.ReadAll(r.Body) if err != nil { t.Fatal(err) } if string(b) != j { t.Fatalf("response body is wrong. got=%q expected=%q", string(b), j) } } func TestWithErrorUnlessStatusCode_FoundAzureFullError(t *testing.T) { j := `{ "error": { "code": "InternalError", "message": "Azure is having trouble right now.", "target": "target1", "details": [{"code": "conflict1", "message":"error message1"}, {"code": "conflict2", "message":"error message2"}], "innererror": { "customKey": "customValue" }, "additionalInfo": [{"type": "someErrorType", "info": {"someProperty": "someValue"}}] } }` uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6" r := mocks.NewResponseWithContent(j) mocks.SetResponseHeader(r, HeaderRequestID, uuid) r.Request = mocks.NewRequest() r.StatusCode = http.StatusInternalServerError r.Status = http.StatusText(r.StatusCode) err := autorest.Respond(r, WithErrorUnlessStatusCode(http.StatusOK), autorest.ByClosing()) if err == nil { t.Fatalf("azure: returned nil error for proper error response") } azErr, ok := err.(*RequestError) if !ok { t.Fatalf("azure: returned error is not azure.RequestError: %T", err) } if expected := "InternalError"; azErr.ServiceError.Code != expected { t.Fatalf("azure: wrong error code. expected=%q; got=%q", expected, azErr.ServiceError.Code) } if azErr.ServiceError.Message == "" { t.Fatalf("azure: error message is not unmarshaled properly") } if *azErr.ServiceError.Target == "" { t.Fatalf("azure: error target is not unmarshaled properly") } d, _ := json.Marshal(azErr.ServiceError.Details) if string(d) != `[{"code":"conflict1","message":"error message1"},{"code":"conflict2","message":"error message2"}]` { t.Fatalf("azure: error details is not unmarshaled properly") } i, _ := json.Marshal(azErr.ServiceError.InnerError) if string(i) != `{"customKey":"customValue"}` { t.Fatalf("azure: inner error is not unmarshaled properly") } a, _ := json.Marshal(azErr.ServiceError.AdditionalInfo) if string(a) != `[{"info":{"someProperty":"someValue"},"type":"someErrorType"}]` { t.Fatalf("azure: error additional info is not unmarshaled properly") } if expected := http.StatusInternalServerError; azErr.StatusCode != expected { t.Fatalf("azure: got wrong StatusCode=%v Expected=%d", azErr.StatusCode, expected) } if expected := uuid; azErr.RequestID != expected { t.Fatalf("azure: wrong request ID in error. expected=%q; got=%q", expected, azErr.RequestID) } _ = azErr.Error() // the error body should still be there defer r.Body.Close() b, err := ioutil.ReadAll(r.Body) if err != nil { t.Fatal(err) } if string(b) != j { t.Fatalf("response body is wrong. got=%q expected=%q", string(b), j) } } func TestWithErrorUnlessStatusCode_NoAzureError(t *testing.T) { j := `{ "Status":"NotFound" }` uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6" r := mocks.NewResponseWithContent(j) mocks.SetResponseHeader(r, HeaderRequestID, uuid) r.Request = mocks.NewRequest() r.StatusCode = http.StatusInternalServerError r.Status = http.StatusText(r.StatusCode) err := autorest.Respond(r, WithErrorUnlessStatusCode(http.StatusOK), autorest.ByClosing()) if err == nil { t.Fatalf("azure: returned nil error for proper error response") } azErr, ok := err.(*RequestError) if !ok { t.Fatalf("azure: returned error is not azure.RequestError: %T", err) } expected := &ServiceError{ Code: "Unknown", Message: "Unknown service error", Details: []map[string]interface{}{ {"Status": "NotFound"}, }, } if !reflect.DeepEqual(expected, azErr.ServiceError) { t.Fatalf("azure: service error is not unmarshaled properly. expected=%q\ngot=%q", expected, azErr.ServiceError) } if expected := http.StatusInternalServerError; azErr.StatusCode != expected { t.Fatalf("azure: got wrong StatusCode=%v Expected=%d", azErr.StatusCode, expected) } if expected := uuid; azErr.RequestID != expected { t.Fatalf("azure: wrong request ID in error. expected=%q; got=%q", expected, azErr.RequestID) } _ = azErr.Error() // the error body should still be there defer r.Body.Close() b, err := ioutil.ReadAll(r.Body) if err != nil { t.Fatal(err) } if string(b) != j { t.Fatalf("response body is wrong. got=%q expected=%q", string(b), j) } } func TestWithErrorUnlessStatusCode_UnwrappedError(t *testing.T) { j := `{ "code": "InternalError", "message": "Azure is having trouble right now.", "target": "target1", "details": [{"code": "conflict1", "message":"error message1"}, {"code": "conflict2", "message":"error message2"}], "innererror": { "customKey": "customValue" }, "additionalInfo": [{"type": "someErrorType", "info": {"someProperty": "someValue"}}] }` uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6" r := mocks.NewResponseWithContent(j) mocks.SetResponseHeader(r, HeaderRequestID, uuid) r.Request = mocks.NewRequest() r.StatusCode = http.StatusInternalServerError r.Status = http.StatusText(r.StatusCode) err := autorest.Respond(r, WithErrorUnlessStatusCode(http.StatusOK), autorest.ByClosing()) if err == nil { t.Fatal("azure: returned nil error for proper error response") } azErr, ok := err.(*RequestError) if !ok { t.Fatalf("returned error is not azure.RequestError: %T", err) } if expected := http.StatusInternalServerError; azErr.StatusCode != expected { t.Logf("Incorrect StatusCode got: %v want: %d", azErr.StatusCode, expected) t.Fail() } if expected := "Azure is having trouble right now."; azErr.Message != expected { t.Logf("Incorrect Message\n\tgot: %q\n\twant: %q", azErr.Message, expected) t.Fail() } if expected := uuid; azErr.RequestID != expected { t.Logf("Incorrect request ID\n\tgot: %q\n\twant: %q", azErr.RequestID, expected) t.Fail() } if azErr.ServiceError == nil { t.Logf("`ServiceError` was nil when it shouldn't have been.") t.Fail() } if expected := "target1"; *azErr.ServiceError.Target != expected { t.Logf("Incorrect Target\n\tgot: %q\n\twant: %q", *azErr.ServiceError.Target, expected) t.Fail() } expectedServiceErrorDetails := `[{"code":"conflict1","message":"error message1"},{"code":"conflict2","message":"error message2"}]` if azErr.ServiceError.Details == nil { t.Logf("`ServiceError.Details` was nil when it should have been %q", expectedServiceErrorDetails) t.Fail() } else if details, _ := json.Marshal(azErr.ServiceError.Details); expectedServiceErrorDetails != string(details) { t.Logf("Error details was not unmarshaled properly.\n\tgot: %q\n\twant: %q", string(details), expectedServiceErrorDetails) t.Fail() } expectedServiceErrorInnerError := `{"customKey":"customValue"}` if azErr.ServiceError.InnerError == nil { t.Logf("`ServiceError.InnerError` was nil when it should have been %q", expectedServiceErrorInnerError) t.Fail() } else if innerError, _ := json.Marshal(azErr.ServiceError.InnerError); expectedServiceErrorInnerError != string(innerError) { t.Logf("Inner error was not unmarshaled properly.\n\tgot: %q\n\twant: %q", string(innerError), expectedServiceErrorInnerError) t.Fail() } expectedServiceErrorAdditionalInfo := `[{"info":{"someProperty":"someValue"},"type":"someErrorType"}]` if azErr.ServiceError.AdditionalInfo == nil { t.Logf("`ServiceError.AdditionalInfo` was nil when it should have been %q", expectedServiceErrorAdditionalInfo) t.Fail() } else if additionalInfo, _ := json.Marshal(azErr.ServiceError.AdditionalInfo); expectedServiceErrorAdditionalInfo != string(additionalInfo) { t.Logf("Additional info was not unmarshaled properly.\n\tgot: %q\n\twant: %q", string(additionalInfo), expectedServiceErrorAdditionalInfo) t.Fail() } // the error body should still be there defer r.Body.Close() b, err := ioutil.ReadAll(r.Body) if err != nil { t.Error(err) } if string(b) != j { t.Fatalf("response body is wrong. got=%q expected=%q", string(b), j) } } func TestRequestErrorString_WithError(t *testing.T) { j := `{ "error": { "code": "InternalError", "message": "Conflict", "target": "target1", "details": [{"code": "conflict1", "message":"error message1"}], "innererror": { "customKey": "customValue" }, "additionalInfo": [{"type": "someErrorType", "info": {"someProperty": "someValue"}}] } }` uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6" r := mocks.NewResponseWithContent(j) mocks.SetResponseHeader(r, HeaderRequestID, uuid) r.Request = mocks.NewRequest() r.StatusCode = http.StatusInternalServerError r.Status = http.StatusText(r.StatusCode) err := autorest.Respond(r, WithErrorUnlessStatusCode(http.StatusOK), autorest.ByClosing()) if err == nil { t.Fatalf("azure: returned nil error for proper error response") } azErr, _ := err.(*RequestError) expected := "autorest/azure: Service returned an error. Status=500 Code=\"InternalError\" Message=\"Conflict\" Target=\"target1\" Details=[{\"code\":\"conflict1\",\"message\":\"error message1\"}] InnerError={\"customKey\":\"customValue\"} AdditionalInfo=[{\"info\":{\"someProperty\":\"someValue\"},\"type\":\"someErrorType\"}]" if expected != azErr.Error() { t.Fatalf("azure: send wrong RequestError.\nexpected=%v\ngot=%v", expected, azErr.Error()) } } func TestRequestErrorString_WithErrorNonConforming(t *testing.T) { j := `{ "error": { "code": "InternalError", "message": "Conflict", "details": {"code": "conflict1", "message":"error message1"} } }` uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6" r := mocks.NewResponseWithContent(j) mocks.SetResponseHeader(r, HeaderRequestID, uuid) r.Request = mocks.NewRequest() r.StatusCode = http.StatusInternalServerError r.Status = http.StatusText(r.StatusCode) err := autorest.Respond(r, WithErrorUnlessStatusCode(http.StatusOK), autorest.ByClosing()) if err == nil { t.Fatalf("azure: returned nil error for proper error response") } azErr, _ := err.(*RequestError) expected := "autorest/azure: Service returned an error. Status=500 Code=\"InternalError\" Message=\"Conflict\" Details=[{\"code\":\"conflict1\",\"message\":\"error message1\"}]" if expected != azErr.Error() { t.Fatalf("azure: send wrong RequestError.\nexpected=%v\ngot=%v", expected, azErr.Error()) } } func TestParseResourceID_WithValidBasicResourceID(t *testing.T) { basicResourceID := "/subscriptions/subid-3-3-4/resourceGroups/regGroupVladdb/providers/Microsoft.Network/LoadBalancer/testResourceName" want := Resource{ SubscriptionID: "subid-3-3-4", ResourceGroup: "regGroupVladdb", Provider: "Microsoft.Network", ResourceType: "LoadBalancer", ResourceName: "testResourceName", } got, err := ParseResourceID(basicResourceID) if err != nil { t.Fatalf("azure: error returned while parsing valid resourceId") } if got != want { t.Logf("got: %+v\nwant: %+v", got, want) t.Fail() } } func TestParseResourceID_WithValidSubResourceID(t *testing.T) { subresourceID := "/subscriptions/subid-3-3-4/resourceGroups/regGroupVladdb/providers/Microsoft.Network/LoadBalancer/resource/is/a/subresource/actualresourceName" want := Resource{ SubscriptionID: "subid-3-3-4", ResourceGroup: "regGroupVladdb", Provider: "Microsoft.Network", ResourceType: "LoadBalancer", ResourceName: "actualresourceName", } got, err := ParseResourceID(subresourceID) if err != nil { t.Fatalf("azure: error returned while parsing valid resourceId") } if got != want { t.Logf("got: %+v\nwant: %+v", got, want) t.Fail() } } func TestParseResourceID_WithIncompleteResourceID(t *testing.T) { basicResourceID := "/subscriptions/subid-3-3-4/resourceGroups/regGroupVladdb/providers/Microsoft.Network/" want := Resource{} got, err := ParseResourceID(basicResourceID) if err == nil { t.Fatalf("azure: no error returned on incomplete resource id") } if got != want { t.Logf("got: %+v\nwant: %+v", got, want) t.Fail() } } func TestParseResourceID_WithMalformedResourceID(t *testing.T) { malformedResourceID := "/providers/subid-3-3-4/resourceGroups/regGroupVladdb/subscriptions/Microsoft.Network/LoadBalancer/testResourceName" want := Resource{} got, err := ParseResourceID(malformedResourceID) if err == nil { t.Fatalf("azure: error returned while parsing malformed resourceID") } if got != want { t.Logf("got: %+v\nwant: %+v", got, want) t.Fail() } } func TestRequestErrorString_WithXMLError(t *testing.T) { j := ` InternalError Internal service error. ` uuid := "71FDB9F4-5E49-4C12-B266-DE7B4FD999A6" r := mocks.NewResponseWithContent(j) mocks.SetResponseHeader(r, HeaderRequestID, uuid) r.Request = mocks.NewRequest() r.StatusCode = http.StatusInternalServerError r.Status = http.StatusText(r.StatusCode) r.Header.Add("Content-Type", "text/xml") err := autorest.Respond(r, WithErrorUnlessStatusCode(http.StatusOK), autorest.ByClosing()) if err == nil { t.Fatalf("azure: returned nil error for proper error response") } azErr, _ := err.(*RequestError) const expected = `autorest/azure: Service returned an error. Status=500 Code="InternalError" Message="Internal service error."` if got := azErr.Error(); expected != got { fmt.Println(got) t.Fatalf("azure: send wrong RequestError.\nexpected=%v\ngot=%v", expected, got) } } func withErrorPrepareDecorator(e *error) autorest.PrepareDecorator { return func(p autorest.Preparer) autorest.Preparer { return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) { *e = fmt.Errorf("azure: Faux Prepare Error") return r, *e }) } } func withAsyncResponseDecorator(n int) autorest.SendDecorator { i := 0 return func(s autorest.Sender) autorest.Sender { return autorest.SenderFunc(func(r *http.Request) (*http.Response, error) { resp, err := s.Do(r) if err == nil { if i < n { resp.StatusCode = http.StatusCreated resp.Header = http.Header{} resp.Header.Add(http.CanonicalHeaderKey(headerAsyncOperation), mocks.TestURL) i++ } else { resp.StatusCode = http.StatusOK resp.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation)) } } return resp, err }) } } type mockAuthorizer struct{} func (ma mockAuthorizer) WithAuthorization() autorest.PrepareDecorator { return autorest.WithHeader(headerAuthorization, mocks.TestAuthorizationHeader) } type mockFailingAuthorizer struct{} func (mfa mockFailingAuthorizer) WithAuthorization() autorest.PrepareDecorator { return func(p autorest.Preparer) autorest.Preparer { return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) { return r, fmt.Errorf("ERROR: mockFailingAuthorizer returned expected error") }) } } type mockInspector struct { wasInvoked bool } func (mi *mockInspector) WithInspection() autorest.PrepareDecorator { return func(p autorest.Preparer) autorest.Preparer { return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) { mi.wasInvoked = true return p.Prepare(r) }) } } func (mi *mockInspector) ByInspecting() autorest.RespondDecorator { return func(r autorest.Responder) autorest.Responder { return autorest.ResponderFunc(func(resp *http.Response) error { mi.wasInvoked = true return r.Respond(resp) }) } } golang-github-azure-go-autorest-14.1.1/autorest/azure/cli/000077500000000000000000000000001367372352400234775ustar00rootroot00000000000000golang-github-azure-go-autorest-14.1.1/autorest/azure/cli/go.mod000066400000000000000000000004561367372352400246120ustar00rootroot00000000000000module github.com/Azure/go-autorest/autorest/azure/cli go 1.12 require ( github.com/Azure/go-autorest/autorest v0.9.0 github.com/Azure/go-autorest/autorest/adal v0.8.0 github.com/Azure/go-autorest/autorest/date v0.2.0 github.com/dimchansky/utfbom v1.1.0 github.com/mitchellh/go-homedir v1.1.0 ) golang-github-azure-go-autorest-14.1.1/autorest/azure/cli/go.sum000066400000000000000000000054701367372352400246400ustar00rootroot00000000000000github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.8.0 h1:CxTzQrySOxDnKpLjFJeZAS5Qrv/qFPkgLjx5bOAi//I= github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSWlm5Ew6bxipnr/tbM= github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= github.com/Azure/go-autorest/autorest/mocks v0.1.0 h1:Kx+AUU2Te+A3JIyYn6Dfs+cFgx5XorQKuIXrZGoq/SI= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0 h1:Ww5g4zThfD/6cLb4z6xxgeyDa7QDkizMkJKe0ysZXp0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.3.0 h1:qJumjCaCudz+OcqE9/XtEPfvtOjOmKaui4EOpFI6zZc= github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang-github-azure-go-autorest-14.1.1/autorest/azure/cli/go_mod_tidy_hack.go000066400000000000000000000017151367372352400273150ustar00rootroot00000000000000// +build modhack package cli // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // This file, and the github.com/Azure/go-autorest/autorest import, won't actually become part of // the resultant binary. // Necessary for safely adding multi-module repo. // See: https://github.com/golang/go/wiki/Modules#is-it-possible-to-add-a-module-to-a-multi-module-repository import _ "github.com/Azure/go-autorest/autorest" golang-github-azure-go-autorest-14.1.1/autorest/azure/cli/profile.go000066400000000000000000000045471367372352400255000ustar00rootroot00000000000000package cli // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "bytes" "encoding/json" "fmt" "io/ioutil" "os" "path/filepath" "github.com/dimchansky/utfbom" "github.com/mitchellh/go-homedir" ) // Profile represents a Profile from the Azure CLI type Profile struct { InstallationID string `json:"installationId"` Subscriptions []Subscription `json:"subscriptions"` } // Subscription represents a Subscription from the Azure CLI type Subscription struct { EnvironmentName string `json:"environmentName"` ID string `json:"id"` IsDefault bool `json:"isDefault"` Name string `json:"name"` State string `json:"state"` TenantID string `json:"tenantId"` User *User `json:"user"` } // User represents a User from the Azure CLI type User struct { Name string `json:"name"` Type string `json:"type"` } const azureProfileJSON = "azureProfile.json" func configDir() string { return os.Getenv("AZURE_CONFIG_DIR") } // ProfilePath returns the path where the Azure Profile is stored from the Azure CLI func ProfilePath() (string, error) { if cfgDir := configDir(); cfgDir != "" { return filepath.Join(cfgDir, azureProfileJSON), nil } return homedir.Expand("~/.azure/" + azureProfileJSON) } // LoadProfile restores a Profile object from a file located at 'path'. func LoadProfile(path string) (result Profile, err error) { var contents []byte contents, err = ioutil.ReadFile(path) if err != nil { err = fmt.Errorf("failed to open file (%s) while loading token: %v", path, err) return } reader := utfbom.SkipOnly(bytes.NewReader(contents)) dec := json.NewDecoder(reader) if err = dec.Decode(&result); err != nil { err = fmt.Errorf("failed to decode contents of file (%s) into a Profile representation: %v", path, err) return } return } golang-github-azure-go-autorest-14.1.1/autorest/azure/cli/token.go000066400000000000000000000141421367372352400251500ustar00rootroot00000000000000package cli // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "bytes" "encoding/json" "fmt" "os" "os/exec" "path/filepath" "regexp" "runtime" "strconv" "time" "github.com/Azure/go-autorest/autorest/adal" "github.com/Azure/go-autorest/autorest/date" "github.com/mitchellh/go-homedir" ) // Token represents an AccessToken from the Azure CLI type Token struct { AccessToken string `json:"accessToken"` Authority string `json:"_authority"` ClientID string `json:"_clientId"` ExpiresOn string `json:"expiresOn"` IdentityProvider string `json:"identityProvider"` IsMRRT bool `json:"isMRRT"` RefreshToken string `json:"refreshToken"` Resource string `json:"resource"` TokenType string `json:"tokenType"` UserID string `json:"userId"` } const accessTokensJSON = "accessTokens.json" // ToADALToken converts an Azure CLI `Token`` to an `adal.Token`` func (t Token) ToADALToken() (converted adal.Token, err error) { tokenExpirationDate, err := ParseExpirationDate(t.ExpiresOn) if err != nil { err = fmt.Errorf("Error parsing Token Expiration Date %q: %+v", t.ExpiresOn, err) return } difference := tokenExpirationDate.Sub(date.UnixEpoch()) converted = adal.Token{ AccessToken: t.AccessToken, Type: t.TokenType, ExpiresIn: "3600", ExpiresOn: json.Number(strconv.Itoa(int(difference.Seconds()))), RefreshToken: t.RefreshToken, Resource: t.Resource, } return } // AccessTokensPath returns the path where access tokens are stored from the Azure CLI // TODO(#199): add unit test. func AccessTokensPath() (string, error) { // Azure-CLI allows user to customize the path of access tokens through environment variable. if accessTokenPath := os.Getenv("AZURE_ACCESS_TOKEN_FILE"); accessTokenPath != "" { return accessTokenPath, nil } // Azure-CLI allows user to customize the path to Azure config directory through environment variable. if cfgDir := configDir(); cfgDir != "" { return filepath.Join(cfgDir, accessTokensJSON), nil } // Fallback logic to default path on non-cloud-shell environment. // TODO(#200): remove the dependency on hard-coding path. return homedir.Expand("~/.azure/" + accessTokensJSON) } // ParseExpirationDate parses either a Azure CLI or CloudShell date into a time object func ParseExpirationDate(input string) (*time.Time, error) { // CloudShell (and potentially the Azure CLI in future) expirationDate, cloudShellErr := time.Parse(time.RFC3339, input) if cloudShellErr != nil { // Azure CLI (Python) e.g. 2017-08-31 19:48:57.998857 (plus the local timezone) const cliFormat = "2006-01-02 15:04:05.999999" expirationDate, cliErr := time.ParseInLocation(cliFormat, input, time.Local) if cliErr == nil { return &expirationDate, nil } return nil, fmt.Errorf("Error parsing expiration date %q.\n\nCloudShell Error: \n%+v\n\nCLI Error:\n%+v", input, cloudShellErr, cliErr) } return &expirationDate, nil } // LoadTokens restores a set of Token objects from a file located at 'path'. func LoadTokens(path string) ([]Token, error) { file, err := os.Open(path) if err != nil { return nil, fmt.Errorf("failed to open file (%s) while loading token: %v", path, err) } defer file.Close() var tokens []Token dec := json.NewDecoder(file) if err = dec.Decode(&tokens); err != nil { return nil, fmt.Errorf("failed to decode contents of file (%s) into a `cli.Token` representation: %v", path, err) } return tokens, nil } // GetTokenFromCLI gets a token using Azure CLI 2.0 for local development scenarios. func GetTokenFromCLI(resource string) (*Token, error) { // This is the path that a developer can set to tell this class what the install path for Azure CLI is. const azureCLIPath = "AzureCLIPath" // The default install paths are used to find Azure CLI. This is for security, so that any path in the calling program's Path environment is not used to execute Azure CLI. azureCLIDefaultPathWindows := fmt.Sprintf("%s\\Microsoft SDKs\\Azure\\CLI2\\wbin; %s\\Microsoft SDKs\\Azure\\CLI2\\wbin", os.Getenv("ProgramFiles(x86)"), os.Getenv("ProgramFiles")) // Default path for non-Windows. const azureCLIDefaultPath = "/bin:/sbin:/usr/bin:/usr/local/bin" // Validate resource, since it gets sent as a command line argument to Azure CLI const invalidResourceErrorTemplate = "Resource %s is not in expected format. Only alphanumeric characters, [dot], [colon], [hyphen], and [forward slash] are allowed." match, err := regexp.MatchString("^[0-9a-zA-Z-.:/]+$", resource) if err != nil { return nil, err } if !match { return nil, fmt.Errorf(invalidResourceErrorTemplate, resource) } // Execute Azure CLI to get token var cliCmd *exec.Cmd if runtime.GOOS == "windows" { cliCmd = exec.Command(fmt.Sprintf("%s\\system32\\cmd.exe", os.Getenv("windir"))) cliCmd.Env = os.Environ() cliCmd.Env = append(cliCmd.Env, fmt.Sprintf("PATH=%s;%s", os.Getenv(azureCLIPath), azureCLIDefaultPathWindows)) cliCmd.Args = append(cliCmd.Args, "/c", "az") } else { cliCmd = exec.Command("az") cliCmd.Env = os.Environ() cliCmd.Env = append(cliCmd.Env, fmt.Sprintf("PATH=%s:%s", os.Getenv(azureCLIPath), azureCLIDefaultPath)) } cliCmd.Args = append(cliCmd.Args, "account", "get-access-token", "-o", "json", "--resource", resource) var stderr bytes.Buffer cliCmd.Stderr = &stderr output, err := cliCmd.Output() if err != nil { return nil, fmt.Errorf("Invoking Azure CLI failed with the following error: %s", stderr.String()) } tokenResponse := Token{} err = json.Unmarshal(output, &tokenResponse) if err != nil { return nil, err } return &tokenResponse, err } golang-github-azure-go-autorest-14.1.1/autorest/azure/environments.go000066400000000000000000000273701367372352400260170ustar00rootroot00000000000000package azure // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "encoding/json" "fmt" "io/ioutil" "os" "strings" ) const ( // EnvironmentFilepathName captures the name of the environment variable containing the path to the file // to be used while populating the Azure Environment. EnvironmentFilepathName = "AZURE_ENVIRONMENT_FILEPATH" // NotAvailable is used for endpoints and resource IDs that are not available for a given cloud. NotAvailable = "N/A" ) var environments = map[string]Environment{ "AZURECHINACLOUD": ChinaCloud, "AZUREGERMANCLOUD": GermanCloud, "AZUREPUBLICCLOUD": PublicCloud, "AZUREUSGOVERNMENTCLOUD": USGovernmentCloud, } // ResourceIdentifier contains a set of Azure resource IDs. type ResourceIdentifier struct { Graph string `json:"graph"` KeyVault string `json:"keyVault"` Datalake string `json:"datalake"` Batch string `json:"batch"` OperationalInsights string `json:"operationalInsights"` Storage string `json:"storage"` } // Environment represents a set of endpoints for each of Azure's Clouds. type Environment struct { Name string `json:"name"` ManagementPortalURL string `json:"managementPortalURL"` PublishSettingsURL string `json:"publishSettingsURL"` ServiceManagementEndpoint string `json:"serviceManagementEndpoint"` ResourceManagerEndpoint string `json:"resourceManagerEndpoint"` ActiveDirectoryEndpoint string `json:"activeDirectoryEndpoint"` GalleryEndpoint string `json:"galleryEndpoint"` KeyVaultEndpoint string `json:"keyVaultEndpoint"` GraphEndpoint string `json:"graphEndpoint"` ServiceBusEndpoint string `json:"serviceBusEndpoint"` BatchManagementEndpoint string `json:"batchManagementEndpoint"` StorageEndpointSuffix string `json:"storageEndpointSuffix"` SQLDatabaseDNSSuffix string `json:"sqlDatabaseDNSSuffix"` TrafficManagerDNSSuffix string `json:"trafficManagerDNSSuffix"` KeyVaultDNSSuffix string `json:"keyVaultDNSSuffix"` ServiceBusEndpointSuffix string `json:"serviceBusEndpointSuffix"` ServiceManagementVMDNSSuffix string `json:"serviceManagementVMDNSSuffix"` ResourceManagerVMDNSSuffix string `json:"resourceManagerVMDNSSuffix"` ContainerRegistryDNSSuffix string `json:"containerRegistryDNSSuffix"` CosmosDBDNSSuffix string `json:"cosmosDBDNSSuffix"` TokenAudience string `json:"tokenAudience"` ResourceIdentifiers ResourceIdentifier `json:"resourceIdentifiers"` } var ( // PublicCloud is the default public Azure cloud environment PublicCloud = Environment{ Name: "AzurePublicCloud", ManagementPortalURL: "https://manage.windowsazure.com/", PublishSettingsURL: "https://manage.windowsazure.com/publishsettings/index", ServiceManagementEndpoint: "https://management.core.windows.net/", ResourceManagerEndpoint: "https://management.azure.com/", ActiveDirectoryEndpoint: "https://login.microsoftonline.com/", GalleryEndpoint: "https://gallery.azure.com/", KeyVaultEndpoint: "https://vault.azure.net/", GraphEndpoint: "https://graph.windows.net/", ServiceBusEndpoint: "https://servicebus.windows.net/", BatchManagementEndpoint: "https://batch.core.windows.net/", StorageEndpointSuffix: "core.windows.net", SQLDatabaseDNSSuffix: "database.windows.net", TrafficManagerDNSSuffix: "trafficmanager.net", KeyVaultDNSSuffix: "vault.azure.net", ServiceBusEndpointSuffix: "servicebus.windows.net", ServiceManagementVMDNSSuffix: "cloudapp.net", ResourceManagerVMDNSSuffix: "cloudapp.azure.com", ContainerRegistryDNSSuffix: "azurecr.io", CosmosDBDNSSuffix: "documents.azure.com", TokenAudience: "https://management.azure.com/", ResourceIdentifiers: ResourceIdentifier{ Graph: "https://graph.windows.net/", KeyVault: "https://vault.azure.net", Datalake: "https://datalake.azure.net/", Batch: "https://batch.core.windows.net/", OperationalInsights: "https://api.loganalytics.io", Storage: "https://storage.azure.com/", }, } // USGovernmentCloud is the cloud environment for the US Government USGovernmentCloud = Environment{ Name: "AzureUSGovernmentCloud", ManagementPortalURL: "https://manage.windowsazure.us/", PublishSettingsURL: "https://manage.windowsazure.us/publishsettings/index", ServiceManagementEndpoint: "https://management.core.usgovcloudapi.net/", ResourceManagerEndpoint: "https://management.usgovcloudapi.net/", ActiveDirectoryEndpoint: "https://login.microsoftonline.us/", GalleryEndpoint: "https://gallery.usgovcloudapi.net/", KeyVaultEndpoint: "https://vault.usgovcloudapi.net/", GraphEndpoint: "https://graph.windows.net/", ServiceBusEndpoint: "https://servicebus.usgovcloudapi.net/", BatchManagementEndpoint: "https://batch.core.usgovcloudapi.net/", StorageEndpointSuffix: "core.usgovcloudapi.net", SQLDatabaseDNSSuffix: "database.usgovcloudapi.net", TrafficManagerDNSSuffix: "usgovtrafficmanager.net", KeyVaultDNSSuffix: "vault.usgovcloudapi.net", ServiceBusEndpointSuffix: "servicebus.usgovcloudapi.net", ServiceManagementVMDNSSuffix: "usgovcloudapp.net", ResourceManagerVMDNSSuffix: "cloudapp.windowsazure.us", ContainerRegistryDNSSuffix: "azurecr.us", CosmosDBDNSSuffix: "documents.azure.us", TokenAudience: "https://management.usgovcloudapi.net/", ResourceIdentifiers: ResourceIdentifier{ Graph: "https://graph.windows.net/", KeyVault: "https://vault.usgovcloudapi.net", Datalake: NotAvailable, Batch: "https://batch.core.usgovcloudapi.net/", OperationalInsights: "https://api.loganalytics.us", Storage: "https://storage.azure.com/", }, } // ChinaCloud is the cloud environment operated in China ChinaCloud = Environment{ Name: "AzureChinaCloud", ManagementPortalURL: "https://manage.chinacloudapi.com/", PublishSettingsURL: "https://manage.chinacloudapi.com/publishsettings/index", ServiceManagementEndpoint: "https://management.core.chinacloudapi.cn/", ResourceManagerEndpoint: "https://management.chinacloudapi.cn/", ActiveDirectoryEndpoint: "https://login.chinacloudapi.cn/", GalleryEndpoint: "https://gallery.chinacloudapi.cn/", KeyVaultEndpoint: "https://vault.azure.cn/", GraphEndpoint: "https://graph.chinacloudapi.cn/", ServiceBusEndpoint: "https://servicebus.chinacloudapi.cn/", BatchManagementEndpoint: "https://batch.chinacloudapi.cn/", StorageEndpointSuffix: "core.chinacloudapi.cn", SQLDatabaseDNSSuffix: "database.chinacloudapi.cn", TrafficManagerDNSSuffix: "trafficmanager.cn", KeyVaultDNSSuffix: "vault.azure.cn", ServiceBusEndpointSuffix: "servicebus.chinacloudapi.cn", ServiceManagementVMDNSSuffix: "chinacloudapp.cn", ResourceManagerVMDNSSuffix: "cloudapp.azure.cn", ContainerRegistryDNSSuffix: "azurecr.cn", CosmosDBDNSSuffix: "documents.azure.cn", TokenAudience: "https://management.chinacloudapi.cn/", ResourceIdentifiers: ResourceIdentifier{ Graph: "https://graph.chinacloudapi.cn/", KeyVault: "https://vault.azure.cn", Datalake: NotAvailable, Batch: "https://batch.chinacloudapi.cn/", OperationalInsights: NotAvailable, Storage: "https://storage.azure.com/", }, } // GermanCloud is the cloud environment operated in Germany GermanCloud = Environment{ Name: "AzureGermanCloud", ManagementPortalURL: "http://portal.microsoftazure.de/", PublishSettingsURL: "https://manage.microsoftazure.de/publishsettings/index", ServiceManagementEndpoint: "https://management.core.cloudapi.de/", ResourceManagerEndpoint: "https://management.microsoftazure.de/", ActiveDirectoryEndpoint: "https://login.microsoftonline.de/", GalleryEndpoint: "https://gallery.cloudapi.de/", KeyVaultEndpoint: "https://vault.microsoftazure.de/", GraphEndpoint: "https://graph.cloudapi.de/", ServiceBusEndpoint: "https://servicebus.cloudapi.de/", BatchManagementEndpoint: "https://batch.cloudapi.de/", StorageEndpointSuffix: "core.cloudapi.de", SQLDatabaseDNSSuffix: "database.cloudapi.de", TrafficManagerDNSSuffix: "azuretrafficmanager.de", KeyVaultDNSSuffix: "vault.microsoftazure.de", ServiceBusEndpointSuffix: "servicebus.cloudapi.de", ServiceManagementVMDNSSuffix: "azurecloudapp.de", ResourceManagerVMDNSSuffix: "cloudapp.microsoftazure.de", ContainerRegistryDNSSuffix: NotAvailable, CosmosDBDNSSuffix: "documents.microsoftazure.de", TokenAudience: "https://management.microsoftazure.de/", ResourceIdentifiers: ResourceIdentifier{ Graph: "https://graph.cloudapi.de/", KeyVault: "https://vault.microsoftazure.de", Datalake: NotAvailable, Batch: "https://batch.cloudapi.de/", OperationalInsights: NotAvailable, Storage: "https://storage.azure.com/", }, } ) // EnvironmentFromName returns an Environment based on the common name specified. func EnvironmentFromName(name string) (Environment, error) { // IMPORTANT // As per @radhikagupta5: // This is technical debt, fundamentally here because Kubernetes is not currently accepting // contributions to the providers. Once that is an option, the provider should be updated to // directly call `EnvironmentFromFile`. Until then, we rely on dispatching Azure Stack environment creation // from this method based on the name that is provided to us. if strings.EqualFold(name, "AZURESTACKCLOUD") { return EnvironmentFromFile(os.Getenv(EnvironmentFilepathName)) } name = strings.ToUpper(name) env, ok := environments[name] if !ok { return env, fmt.Errorf("autorest/azure: There is no cloud environment matching the name %q", name) } return env, nil } // EnvironmentFromFile loads an Environment from a configuration file available on disk. // This function is particularly useful in the Hybrid Cloud model, where one must define their own // endpoints. func EnvironmentFromFile(location string) (unmarshaled Environment, err error) { fileContents, err := ioutil.ReadFile(location) if err != nil { return } err = json.Unmarshal(fileContents, &unmarshaled) return } // SetEnvironment updates the environment map with the specified values. func SetEnvironment(name string, env Environment) { environments[strings.ToUpper(name)] = env } golang-github-azure-go-autorest-14.1.1/autorest/azure/environments_test.go000066400000000000000000000446541367372352400270620ustar00rootroot00000000000000// test package azure // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "encoding/json" "io/ioutil" "net/http" "net/http/httptest" "os" "path" "path/filepath" "runtime" "testing" ) const ( batchResourceID = "--batch-resource-id--" datalakeResourceID = "--datalake-resource-id--" graphResourceID = "--graph-resource-id--" keyvaultResourceID = "--keyvault-resource-id--" opInsightsResourceID = "--operational-insights-resource-id--" ) // This correlates to the expected contents of ./testdata/test_environment_1.json var testEnvironment1 = Environment{ Name: "--unit-test--", ManagementPortalURL: "--management-portal-url", PublishSettingsURL: "--publish-settings-url--", ServiceManagementEndpoint: "--service-management-endpoint--", ResourceManagerEndpoint: "--resource-management-endpoint--", ActiveDirectoryEndpoint: "--active-directory-endpoint--", GalleryEndpoint: "--gallery-endpoint--", KeyVaultEndpoint: "--key-vault--endpoint--", GraphEndpoint: "--graph-endpoint--", StorageEndpointSuffix: "--storage-endpoint-suffix--", SQLDatabaseDNSSuffix: "--sql-database-dns-suffix--", TrafficManagerDNSSuffix: "--traffic-manager-dns-suffix--", KeyVaultDNSSuffix: "--key-vault-dns-suffix--", ServiceBusEndpointSuffix: "--service-bus-endpoint-suffix--", ServiceManagementVMDNSSuffix: "--asm-vm-dns-suffix--", ResourceManagerVMDNSSuffix: "--arm-vm-dns-suffix--", ContainerRegistryDNSSuffix: "--container-registry-dns-suffix--", TokenAudience: "--token-audience", ResourceIdentifiers: ResourceIdentifier{ Batch: batchResourceID, Datalake: datalakeResourceID, Graph: graphResourceID, KeyVault: keyvaultResourceID, OperationalInsights: opInsightsResourceID, }, } func TestEnvironment_EnvironmentFromURL_NoOverride_Success(t *testing.T) { fileContents, _ := ioutil.ReadFile(filepath.Join("testdata", "test_metadata_environment_1.json")) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(fileContents)) })) defer ts.Close() got, err := EnvironmentFromURL(ts.URL) if err != nil { t.Error(err) } if got.Name != "HybridEnvironment" { t.Logf("got: %v want: HybridEnvironment", got.Name) t.Fail() } } func TestEnvironment_EnvironmentFromURL_OverrideStorageSuffix_Success(t *testing.T) { fileContents, _ := ioutil.ReadFile(filepath.Join("testdata", "test_metadata_environment_1.json")) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(fileContents)) })) defer ts.Close() overrideProperty := OverrideProperty{ Key: EnvironmentStorageEndpointSuffix, Value: "fakeStorageSuffix", } got, err := EnvironmentFromURL(ts.URL, overrideProperty) if err != nil { t.Error(err) } if got.StorageEndpointSuffix != "fakeStorageSuffix" { t.Logf("got: %v want: fakeStorageSuffix", got.StorageEndpointSuffix) t.Fail() } } func TestEnvironment_EnvironmentFromURL_EmptyEndpoint_Failure(t *testing.T) { _, err := EnvironmentFromURL("") if err == nil { t.Fail() } if err.Error() != "Metadata resource manager endpoint is empty" { t.Fail() } } func TestEnvironment_EnvironmentFromFile(t *testing.T) { got, err := EnvironmentFromFile(filepath.Join("testdata", "test_environment_1.json")) if err != nil { t.Error(err) } if got != testEnvironment1 { t.Logf("got: %v want: %v", got, testEnvironment1) t.Fail() } } func TestEnvironment_EnvironmentFromName_Stack(t *testing.T) { _, currentFile, _, _ := runtime.Caller(0) prevEnvFilepathValue := os.Getenv(EnvironmentFilepathName) os.Setenv(EnvironmentFilepathName, filepath.Join(path.Dir(currentFile), "testdata", "test_environment_1.json")) defer os.Setenv(EnvironmentFilepathName, prevEnvFilepathValue) got, err := EnvironmentFromName("AZURESTACKCLOUD") if err != nil { t.Error(err) } if got != testEnvironment1 { t.Logf("got: %v want: %v", got, testEnvironment1) t.Fail() } } func TestEnvironmentFromName(t *testing.T) { name := "azurechinacloud" if env, _ := EnvironmentFromName(name); env != ChinaCloud { t.Errorf("Expected to get ChinaCloud for %q", name) } name = "AzureChinaCloud" if env, _ := EnvironmentFromName(name); env != ChinaCloud { t.Errorf("Expected to get ChinaCloud for %q", name) } name = "azuregermancloud" if env, _ := EnvironmentFromName(name); env != GermanCloud { t.Errorf("Expected to get GermanCloud for %q", name) } name = "AzureGermanCloud" if env, _ := EnvironmentFromName(name); env != GermanCloud { t.Errorf("Expected to get GermanCloud for %q", name) } name = "azurepubliccloud" if env, _ := EnvironmentFromName(name); env != PublicCloud { t.Errorf("Expected to get PublicCloud for %q", name) } name = "AzurePublicCloud" if env, _ := EnvironmentFromName(name); env != PublicCloud { t.Errorf("Expected to get PublicCloud for %q", name) } name = "azureusgovernmentcloud" if env, _ := EnvironmentFromName(name); env != USGovernmentCloud { t.Errorf("Expected to get USGovernmentCloud for %q", name) } name = "AzureUSGovernmentCloud" if env, _ := EnvironmentFromName(name); env != USGovernmentCloud { t.Errorf("Expected to get USGovernmentCloud for %q", name) } name = "thisisnotarealcloudenv" if _, err := EnvironmentFromName(name); err == nil { t.Errorf("Expected to get an error for %q", name) } } func TestDeserializeEnvironment(t *testing.T) { env := `{ "name": "--name--", "ActiveDirectoryEndpoint": "--active-directory-endpoint--", "galleryEndpoint": "--gallery-endpoint--", "graphEndpoint": "--graph-endpoint--", "serviceBusEndpoint": "--service-bus-endpoint--", "keyVaultDNSSuffix": "--key-vault-dns-suffix--", "keyVaultEndpoint": "--key-vault-endpoint--", "managementPortalURL": "--management-portal-url--", "publishSettingsURL": "--publish-settings-url--", "resourceManagerEndpoint": "--resource-manager-endpoint--", "serviceBusEndpointSuffix": "--service-bus-endpoint-suffix--", "serviceManagementEndpoint": "--service-management-endpoint--", "sqlDatabaseDNSSuffix": "--sql-database-dns-suffix--", "storageEndpointSuffix": "--storage-endpoint-suffix--", "trafficManagerDNSSuffix": "--traffic-manager-dns-suffix--", "serviceManagementVMDNSSuffix": "--asm-vm-dns-suffix--", "resourceManagerVMDNSSuffix": "--arm-vm-dns-suffix--", "containerRegistryDNSSuffix": "--container-registry-dns-suffix--", "resourceIdentifiers": { "batch": "` + batchResourceID + `", "datalake": "` + datalakeResourceID + `", "graph": "` + graphResourceID + `", "keyVault": "` + keyvaultResourceID + `", "operationalInsights": "` + opInsightsResourceID + `" } }` testSubject := Environment{} err := json.Unmarshal([]byte(env), &testSubject) if err != nil { t.Fatalf("failed to unmarshal: %s", err) } if "--name--" != testSubject.Name { t.Errorf("Expected Name to be \"--name--\", but got %q", testSubject.Name) } if "--management-portal-url--" != testSubject.ManagementPortalURL { t.Errorf("Expected ManagementPortalURL to be \"--management-portal-url--\", but got %q", testSubject.ManagementPortalURL) } if "--publish-settings-url--" != testSubject.PublishSettingsURL { t.Errorf("Expected PublishSettingsURL to be \"--publish-settings-url--\", but got %q", testSubject.PublishSettingsURL) } if "--service-management-endpoint--" != testSubject.ServiceManagementEndpoint { t.Errorf("Expected ServiceManagementEndpoint to be \"--service-management-endpoint--\", but got %q", testSubject.ServiceManagementEndpoint) } if "--resource-manager-endpoint--" != testSubject.ResourceManagerEndpoint { t.Errorf("Expected ResourceManagerEndpoint to be \"--resource-manager-endpoint--\", but got %q", testSubject.ResourceManagerEndpoint) } if "--active-directory-endpoint--" != testSubject.ActiveDirectoryEndpoint { t.Errorf("Expected ActiveDirectoryEndpoint to be \"--active-directory-endpoint--\", but got %q", testSubject.ActiveDirectoryEndpoint) } if "--gallery-endpoint--" != testSubject.GalleryEndpoint { t.Errorf("Expected GalleryEndpoint to be \"--gallery-endpoint--\", but got %q", testSubject.GalleryEndpoint) } if "--key-vault-endpoint--" != testSubject.KeyVaultEndpoint { t.Errorf("Expected KeyVaultEndpoint to be \"--key-vault-endpoint--\", but got %q", testSubject.KeyVaultEndpoint) } if "--service-bus-endpoint--" != testSubject.ServiceBusEndpoint { t.Errorf("Expected ServiceBusEndpoint to be \"--service-bus-endpoint--\", but goet %q", testSubject.ServiceBusEndpoint) } if "--graph-endpoint--" != testSubject.GraphEndpoint { t.Errorf("Expected GraphEndpoint to be \"--graph-endpoint--\", but got %q", testSubject.GraphEndpoint) } if "--storage-endpoint-suffix--" != testSubject.StorageEndpointSuffix { t.Errorf("Expected StorageEndpointSuffix to be \"--storage-endpoint-suffix--\", but got %q", testSubject.StorageEndpointSuffix) } if "--sql-database-dns-suffix--" != testSubject.SQLDatabaseDNSSuffix { t.Errorf("Expected sql-database-dns-suffix to be \"--sql-database-dns-suffix--\", but got %q", testSubject.SQLDatabaseDNSSuffix) } if "--key-vault-dns-suffix--" != testSubject.KeyVaultDNSSuffix { t.Errorf("Expected StorageEndpointSuffix to be \"--key-vault-dns-suffix--\", but got %q", testSubject.KeyVaultDNSSuffix) } if "--service-bus-endpoint-suffix--" != testSubject.ServiceBusEndpointSuffix { t.Errorf("Expected StorageEndpointSuffix to be \"--service-bus-endpoint-suffix--\", but got %q", testSubject.ServiceBusEndpointSuffix) } if "--asm-vm-dns-suffix--" != testSubject.ServiceManagementVMDNSSuffix { t.Errorf("Expected ServiceManagementVMDNSSuffix to be \"--asm-vm-dns-suffix--\", but got %q", testSubject.ServiceManagementVMDNSSuffix) } if "--arm-vm-dns-suffix--" != testSubject.ResourceManagerVMDNSSuffix { t.Errorf("Expected ResourceManagerVMDNSSuffix to be \"--arm-vm-dns-suffix--\", but got %q", testSubject.ResourceManagerVMDNSSuffix) } if "--container-registry-dns-suffix--" != testSubject.ContainerRegistryDNSSuffix { t.Errorf("Expected ContainerRegistryDNSSuffix to be \"--container-registry-dns-suffix--\", but got %q", testSubject.ContainerRegistryDNSSuffix) } if batchResourceID != testSubject.ResourceIdentifiers.Batch { t.Errorf("Expected ResourceIdentifiers.Batch to be "+batchResourceID+", but got %q", testSubject.ResourceIdentifiers.Batch) } if datalakeResourceID != testSubject.ResourceIdentifiers.Datalake { t.Errorf("Expected ResourceIdentifiers.Datalake to be "+datalakeResourceID+", but got %q", testSubject.ResourceIdentifiers.Datalake) } if graphResourceID != testSubject.ResourceIdentifiers.Graph { t.Errorf("Expected ResourceIdentifiers.Graph to be "+graphResourceID+", but got %q", testSubject.ResourceIdentifiers.Graph) } if keyvaultResourceID != testSubject.ResourceIdentifiers.KeyVault { t.Errorf("Expected ResourceIdentifiers.KeyVault to be "+keyvaultResourceID+", but got %q", testSubject.ResourceIdentifiers.KeyVault) } if opInsightsResourceID != testSubject.ResourceIdentifiers.OperationalInsights { t.Errorf("Expected ResourceIdentifiers.OperationalInsights to be "+opInsightsResourceID+", but got %q", testSubject.ResourceIdentifiers.OperationalInsights) } } func TestRoundTripSerialization(t *testing.T) { env := Environment{ Name: "--unit-test--", ManagementPortalURL: "--management-portal-url", PublishSettingsURL: "--publish-settings-url--", ServiceManagementEndpoint: "--service-management-endpoint--", ResourceManagerEndpoint: "--resource-management-endpoint--", ActiveDirectoryEndpoint: "--active-directory-endpoint--", GalleryEndpoint: "--gallery-endpoint--", KeyVaultEndpoint: "--key-vault--endpoint--", GraphEndpoint: "--graph-endpoint--", ServiceBusEndpoint: "--service-bus-endpoint--", StorageEndpointSuffix: "--storage-endpoint-suffix--", SQLDatabaseDNSSuffix: "--sql-database-dns-suffix--", TrafficManagerDNSSuffix: "--traffic-manager-dns-suffix--", KeyVaultDNSSuffix: "--key-vault-dns-suffix--", ServiceBusEndpointSuffix: "--service-bus-endpoint-suffix--", ServiceManagementVMDNSSuffix: "--asm-vm-dns-suffix--", ResourceManagerVMDNSSuffix: "--arm-vm-dns-suffix--", ContainerRegistryDNSSuffix: "--container-registry-dns-suffix--", ResourceIdentifiers: ResourceIdentifier{ Batch: batchResourceID, Datalake: datalakeResourceID, Graph: graphResourceID, KeyVault: keyvaultResourceID, OperationalInsights: opInsightsResourceID, }, } bytes, err := json.Marshal(env) if err != nil { t.Fatalf("failed to marshal: %s", err) } testSubject := Environment{} err = json.Unmarshal(bytes, &testSubject) if err != nil { t.Fatalf("failed to unmarshal: %s", err) } if env.Name != testSubject.Name { t.Errorf("Expected Name to be %q, but got %q", env.Name, testSubject.Name) } if env.ManagementPortalURL != testSubject.ManagementPortalURL { t.Errorf("Expected ManagementPortalURL to be %q, but got %q", env.ManagementPortalURL, testSubject.ManagementPortalURL) } if env.PublishSettingsURL != testSubject.PublishSettingsURL { t.Errorf("Expected PublishSettingsURL to be %q, but got %q", env.PublishSettingsURL, testSubject.PublishSettingsURL) } if env.ServiceManagementEndpoint != testSubject.ServiceManagementEndpoint { t.Errorf("Expected ServiceManagementEndpoint to be %q, but got %q", env.ServiceManagementEndpoint, testSubject.ServiceManagementEndpoint) } if env.ResourceManagerEndpoint != testSubject.ResourceManagerEndpoint { t.Errorf("Expected ResourceManagerEndpoint to be %q, but got %q", env.ResourceManagerEndpoint, testSubject.ResourceManagerEndpoint) } if env.ActiveDirectoryEndpoint != testSubject.ActiveDirectoryEndpoint { t.Errorf("Expected ActiveDirectoryEndpoint to be %q, but got %q", env.ActiveDirectoryEndpoint, testSubject.ActiveDirectoryEndpoint) } if env.GalleryEndpoint != testSubject.GalleryEndpoint { t.Errorf("Expected GalleryEndpoint to be %q, but got %q", env.GalleryEndpoint, testSubject.GalleryEndpoint) } if env.ServiceBusEndpoint != testSubject.ServiceBusEndpoint { t.Errorf("Expected ServiceBusEnpoint to be %q, but got %q", env.ServiceBusEndpoint, testSubject.ServiceBusEndpoint) } if env.KeyVaultEndpoint != testSubject.KeyVaultEndpoint { t.Errorf("Expected KeyVaultEndpoint to be %q, but got %q", env.KeyVaultEndpoint, testSubject.KeyVaultEndpoint) } if env.GraphEndpoint != testSubject.GraphEndpoint { t.Errorf("Expected GraphEndpoint to be %q, but got %q", env.GraphEndpoint, testSubject.GraphEndpoint) } if env.StorageEndpointSuffix != testSubject.StorageEndpointSuffix { t.Errorf("Expected StorageEndpointSuffix to be %q, but got %q", env.StorageEndpointSuffix, testSubject.StorageEndpointSuffix) } if env.SQLDatabaseDNSSuffix != testSubject.SQLDatabaseDNSSuffix { t.Errorf("Expected SQLDatabaseDNSSuffix to be %q, but got %q", env.SQLDatabaseDNSSuffix, testSubject.SQLDatabaseDNSSuffix) } if env.TrafficManagerDNSSuffix != testSubject.TrafficManagerDNSSuffix { t.Errorf("Expected TrafficManagerDNSSuffix to be %q, but got %q", env.TrafficManagerDNSSuffix, testSubject.TrafficManagerDNSSuffix) } if env.KeyVaultDNSSuffix != testSubject.KeyVaultDNSSuffix { t.Errorf("Expected KeyVaultDNSSuffix to be %q, but got %q", env.KeyVaultDNSSuffix, testSubject.KeyVaultDNSSuffix) } if env.ServiceBusEndpointSuffix != testSubject.ServiceBusEndpointSuffix { t.Errorf("Expected ServiceBusEndpointSuffix to be %q, but got %q", env.ServiceBusEndpointSuffix, testSubject.ServiceBusEndpointSuffix) } if env.ServiceManagementVMDNSSuffix != testSubject.ServiceManagementVMDNSSuffix { t.Errorf("Expected ServiceManagementVMDNSSuffix to be %q, but got %q", env.ServiceManagementVMDNSSuffix, testSubject.ServiceManagementVMDNSSuffix) } if env.ResourceManagerVMDNSSuffix != testSubject.ResourceManagerVMDNSSuffix { t.Errorf("Expected ResourceManagerVMDNSSuffix to be %q, but got %q", env.ResourceManagerVMDNSSuffix, testSubject.ResourceManagerVMDNSSuffix) } if env.ContainerRegistryDNSSuffix != testSubject.ContainerRegistryDNSSuffix { t.Errorf("Expected ContainerRegistryDNSSuffix to be %q, but got %q", env.ContainerRegistryDNSSuffix, testSubject.ContainerRegistryDNSSuffix) } if env.ResourceIdentifiers.Batch != testSubject.ResourceIdentifiers.Batch { t.Errorf("Expected ResourceIdentifiers.Batch to be %q, but got %q", env.ResourceIdentifiers.Batch, testSubject.ResourceIdentifiers.Batch) } if env.ResourceIdentifiers.Datalake != testSubject.ResourceIdentifiers.Datalake { t.Errorf("Expected ResourceIdentifiers.Datalake to be %q, but got %q", env.ResourceIdentifiers.Datalake, testSubject.ResourceIdentifiers.Datalake) } if env.ResourceIdentifiers.Graph != testSubject.ResourceIdentifiers.Graph { t.Errorf("Expected ResourceIdentifiers.Graph to be %q, but got %q", env.ResourceIdentifiers.Graph, testSubject.ResourceIdentifiers.Graph) } if env.ResourceIdentifiers.KeyVault != testSubject.ResourceIdentifiers.KeyVault { t.Errorf("Expected ResourceIdentifiers.KeyVault to be %q, but got %q", env.ResourceIdentifiers.KeyVault, testSubject.ResourceIdentifiers.KeyVault) } if env.ResourceIdentifiers.OperationalInsights != testSubject.ResourceIdentifiers.OperationalInsights { t.Errorf("Expected ResourceIdentifiers.OperationalInsights to be %q, but got %q", env.ResourceIdentifiers.OperationalInsights, testSubject.ResourceIdentifiers.OperationalInsights) } } func TestSetEnvironment(t *testing.T) { const testEnvName = "testenvironment" if _, err := EnvironmentFromName(testEnvName); err == nil { t.Fatal("expected non-nil error") } testEnv := Environment{Name: testEnvName} SetEnvironment(testEnvName, testEnv) result, err := EnvironmentFromName(testEnvName) if err != nil { t.Fatalf("failed to get custom environment: %v", err) } if testEnv != result { t.Fatalf("expected %v, got %v", testEnv, result) } } golang-github-azure-go-autorest-14.1.1/autorest/azure/example/000077500000000000000000000000001367372352400243635ustar00rootroot00000000000000golang-github-azure-go-autorest-14.1.1/autorest/azure/example/README.md000066400000000000000000000073331367372352400256500ustar00rootroot00000000000000# autorest azure example ## Usage (device mode) This shows how to use the example for device auth. 1. Execute this. It will save your token to /tmp/azure-example-token: ``` ./example -tenantId "13de0a15-b5db-44b9-b682-b4ba82afbd29" -subscriptionId "aff271ee-e9be-4441-b9bb-42f5af4cbaeb" -mode "device" -tokenCachePath "/tmp/azure-example-token" ``` 2. Execute it again, it will load the token from cache and not prompt for auth again. ## Usage (certificate mode) This example covers how to make an authenticated call to the Azure Resource Manager APIs, using certificate-based authentication. 0. Export some required variables ``` export SUBSCRIPTION_ID="aff271ee-e9be-4441-b9bb-42f5af4cbaeb" export TENANT_ID="13de0a15-b5db-44b9-b682-b4ba82afbd29" export RESOURCE_GROUP="someresourcegroup" ``` * replace both values with your own 1. Create a private key ``` openssl genrsa -out "example.key" 2048 ``` 2. Create the certificate ``` openssl req -new -key "example.key" -subj "/CN=example" -out "example.csr" openssl x509 -req -in "example.csr" -signkey "example.key" -out "example.crt" -days 10000 ``` 3. Create the PKCS12 version of the certificate (with no password) ``` openssl pkcs12 -export -out "example.pfx" -inkey "example.key" -in "example.crt" -passout pass: ``` 4. Register a new Azure AD Application with the certificate contents ``` certificateContents="$(tail -n+2 "example.key" | head -n-1)" azure ad app create \ --name "example-azuread-app" \ --home-page="http://example-azuread-app/home" \ --identifier-uris "http://example-azuread-app/app" \ --key-usage "Verify" \ --end-date "2020-01-01" \ --key-value "${certificateContents}" ``` 5. Create a new service principal using the "Application Id" from the previous step ``` azure ad sp create "APPLICATION_ID" ``` * Replace APPLICATION_ID with the "Application Id" returned in step 4 6. Grant your service principal necessary permissions ``` azure role assignment create \ --resource-group "${RESOURCE_GROUP}" \ --roleName "Contributor" \ --subscription "${SUBSCRIPTION_ID}" \ --spn "http://example-azuread-app/app" ``` * Replace SUBSCRIPTION_ID with your subscription id * Replace RESOURCE_GROUP with the resource group for the assignment * Ensure that the `spn` parameter matches an `identifier-url` from Step 4 7. Run this example app to see your resource groups ``` go run main.go \ --tenantId="${TENANT_ID}" \ --subscriptionId="${SUBSCRIPTION_ID}" \ --applicationId="http://example-azuread-app/app" \ --certificatePath="certificate.pfx" ``` You should see something like this as output: ``` 2015/11/08 18:28:39 Using these settings: 2015/11/08 18:28:39 * certificatePath: certificate.pfx 2015/11/08 18:28:39 * applicationID: http://example-azuread-app/app 2015/11/08 18:28:39 * tenantID: 13de0a15-b5db-44b9-b682-b4ba82afbd29 2015/11/08 18:28:39 * subscriptionID: aff271ee-e9be-4441-b9bb-42f5af4cbaeb 2015/11/08 18:28:39 loading certificate... 2015/11/08 18:28:39 retrieve oauth token... 2015/11/08 18:28:39 querying the list of resource groups... 2015/11/08 18:28:50 2015/11/08 18:28:50 Groups: {"value":[{"id":"/subscriptions/aff271ee-e9be-4441-b9bb-42f5af4cbaeb/resourceGroups/kube-66f30810","name":"kube-66f30810","location":"westus","tags":{},"properties":{"provisioningState":"Succeeded"}}]} ``` ## Notes You may need to wait sometime between executing step 4, step 5 and step 6. If you issue those requests too quickly, you might hit an AD server that is not consistent with the server where the resource was created. golang-github-azure-go-autorest-14.1.1/autorest/azure/example/main.go000066400000000000000000000173361367372352400256500ustar00rootroot00000000000000package main // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "crypto/rsa" "crypto/x509" "encoding/json" "flag" "fmt" "io/ioutil" "log" "net/http" "strings" "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/adal" "github.com/Azure/go-autorest/autorest/azure" "golang.org/x/crypto/pkcs12" ) const ( resourceGroupURLTemplate = "https://management.azure.com" apiVersion = "2015-01-01" nativeAppClientID = "a87032a7-203c-4bf7-913c-44c50d23409a" resource = "https://management.core.windows.net/" ) var ( mode string tenantID string subscriptionID string applicationID string tokenCachePath string forceRefresh bool impatient bool certificatePath string ) func init() { flag.StringVar(&mode, "mode", "device", "mode of operation for SPT creation") flag.StringVar(&certificatePath, "certificatePath", "", "path to pk12/pfx certificate") flag.StringVar(&applicationID, "applicationId", "", "application id") flag.StringVar(&tenantID, "tenantId", "", "tenant id") flag.StringVar(&subscriptionID, "subscriptionId", "", "subscription id") flag.StringVar(&tokenCachePath, "tokenCachePath", "", "location of oauth token cache") flag.BoolVar(&forceRefresh, "forceRefresh", false, "pass true to force a token refresh") flag.Parse() log.Printf("mode(%s) certPath(%s) appID(%s) tenantID(%s), subID(%s)\n", mode, certificatePath, applicationID, tenantID, subscriptionID) if mode == "certificate" && (strings.TrimSpace(tenantID) == "" || strings.TrimSpace(subscriptionID) == "") { log.Fatalln("Bad usage. Using certificate mode. Please specify tenantID, subscriptionID") } if mode != "certificate" && mode != "device" { log.Fatalln("Bad usage. Mode must be one of 'certificate' or 'device'.") } if mode == "device" && strings.TrimSpace(applicationID) == "" { log.Println("Using device mode auth. Will use `azkube` clientID since none was specified on the comand line.") applicationID = nativeAppClientID } if mode == "certificate" && strings.TrimSpace(certificatePath) == "" { log.Fatalln("Bad usage. Mode 'certificate' requires the 'certificatePath' argument.") } if strings.TrimSpace(tenantID) == "" || strings.TrimSpace(subscriptionID) == "" || strings.TrimSpace(applicationID) == "" { log.Fatalln("Bad usage. Must specify the 'tenantId' and 'subscriptionId'") } } func getSptFromCachedToken(oauthConfig adal.OAuthConfig, clientID, resource string, callbacks ...adal.TokenRefreshCallback) (*adal.ServicePrincipalToken, error) { token, err := adal.LoadToken(tokenCachePath) if err != nil { return nil, fmt.Errorf("failed to load token from cache: %v", err) } spt, _ := adal.NewServicePrincipalTokenFromManualToken( oauthConfig, clientID, resource, *token, callbacks...) return spt, nil } func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.PrivateKey, error) { privateKey, certificate, err := pkcs12.Decode(pkcs, password) if err != nil { return nil, nil, err } rsaPrivateKey, isRsaKey := privateKey.(*rsa.PrivateKey) if !isRsaKey { return nil, nil, fmt.Errorf("PKCS#12 certificate must contain an RSA private key") } return certificate, rsaPrivateKey, nil } func getSptFromCertificate(oauthConfig adal.OAuthConfig, clientID, resource, certicatePath string, callbacks ...adal.TokenRefreshCallback) (*adal.ServicePrincipalToken, error) { certData, err := ioutil.ReadFile(certificatePath) if err != nil { return nil, fmt.Errorf("failed to read the certificate file (%s): %v", certificatePath, err) } certificate, rsaPrivateKey, err := decodePkcs12(certData, "") if err != nil { return nil, fmt.Errorf("failed to decode pkcs12 certificate while creating spt: %v", err) } spt, _ := adal.NewServicePrincipalTokenFromCertificate( oauthConfig, clientID, certificate, rsaPrivateKey, resource, callbacks...) return spt, nil } func getSptFromDeviceFlow(oauthConfig adal.OAuthConfig, clientID, resource string, callbacks ...adal.TokenRefreshCallback) (*adal.ServicePrincipalToken, error) { oauthClient := &autorest.Client{} deviceCode, err := adal.InitiateDeviceAuth(oauthClient, oauthConfig, clientID, resource) if err != nil { return nil, fmt.Errorf("failed to start device auth flow: %s", err) } fmt.Println(*deviceCode.Message) token, err := adal.WaitForUserCompletion(oauthClient, deviceCode) if err != nil { return nil, fmt.Errorf("failed to finish device auth flow: %s", err) } spt, err := adal.NewServicePrincipalTokenFromManualToken( oauthConfig, clientID, resource, *token, callbacks...) if err != nil { return nil, fmt.Errorf("failed to get oauth token from device flow: %v", err) } return spt, nil } func printResourceGroups(client *autorest.Client) error { p := map[string]interface{}{"subscription-id": subscriptionID} q := map[string]interface{}{"api-version": apiVersion} req, _ := autorest.Prepare(&http.Request{}, autorest.AsGet(), autorest.WithBaseURL(resourceGroupURLTemplate), autorest.WithPathParameters("/subscriptions/{subscription-id}/resourcegroups", p), autorest.WithQueryParameters(q)) resp, err := autorest.SendWithSender(client, req) if err != nil { return err } value := struct { ResourceGroups []struct { Name string `json:"name"` } `json:"value"` }{} defer resp.Body.Close() dec := json.NewDecoder(resp.Body) err = dec.Decode(&value) if err != nil { return err } var groupNames = make([]string, len(value.ResourceGroups)) for i, name := range value.ResourceGroups { groupNames[i] = name.Name } log.Println("Groups:", strings.Join(groupNames, ", ")) return err } func saveToken(spt adal.Token) { if tokenCachePath != "" { err := adal.SaveToken(tokenCachePath, 0600, spt) if err != nil { log.Println("error saving token", err) } else { log.Println("saved token to", tokenCachePath) } } } func main() { var spt *adal.ServicePrincipalToken var err error callback := func(t adal.Token) error { log.Println("refresh callback was called") saveToken(t) return nil } oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, tenantID) if err != nil { panic(err) } if tokenCachePath != "" { log.Println("tokenCachePath specified; attempting to load from", tokenCachePath) spt, err = getSptFromCachedToken(*oauthConfig, applicationID, resource, callback) if err != nil { spt = nil // just in case, this is the condition below log.Println("loading from cache failed:", err) } } if spt == nil { log.Println("authenticating via 'mode'", mode) switch mode { case "device": spt, err = getSptFromDeviceFlow(*oauthConfig, applicationID, resource, callback) case "certificate": spt, err = getSptFromCertificate(*oauthConfig, applicationID, resource, certificatePath, callback) } if err != nil { log.Fatalln("failed to retrieve token:", err) } // should save it as soon as you get it since Refresh won't be called for some time if tokenCachePath != "" { saveToken(spt.Token()) } } client := &autorest.Client{} client.Authorizer = autorest.NewBearerAuthorizer(spt) printResourceGroups(client) if forceRefresh { err = spt.Refresh() if err != nil { panic(err) } printResourceGroups(client) } } golang-github-azure-go-autorest-14.1.1/autorest/azure/metadata_environment.go000066400000000000000000000205471367372352400274730ustar00rootroot00000000000000package azure import ( "encoding/json" "fmt" "io/ioutil" "net/http" "strings" "github.com/Azure/go-autorest/autorest" ) // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. type audience []string type authentication struct { LoginEndpoint string `json:"loginEndpoint"` Audiences audience `json:"audiences"` } type environmentMetadataInfo struct { GalleryEndpoint string `json:"galleryEndpoint"` GraphEndpoint string `json:"graphEndpoint"` PortalEndpoint string `json:"portalEndpoint"` Authentication authentication `json:"authentication"` } // EnvironmentProperty represent property names that clients can override type EnvironmentProperty string const ( // EnvironmentName ... EnvironmentName EnvironmentProperty = "name" // EnvironmentManagementPortalURL .. EnvironmentManagementPortalURL EnvironmentProperty = "managementPortalURL" // EnvironmentPublishSettingsURL ... EnvironmentPublishSettingsURL EnvironmentProperty = "publishSettingsURL" // EnvironmentServiceManagementEndpoint ... EnvironmentServiceManagementEndpoint EnvironmentProperty = "serviceManagementEndpoint" // EnvironmentResourceManagerEndpoint ... EnvironmentResourceManagerEndpoint EnvironmentProperty = "resourceManagerEndpoint" // EnvironmentActiveDirectoryEndpoint ... EnvironmentActiveDirectoryEndpoint EnvironmentProperty = "activeDirectoryEndpoint" // EnvironmentGalleryEndpoint ... EnvironmentGalleryEndpoint EnvironmentProperty = "galleryEndpoint" // EnvironmentKeyVaultEndpoint ... EnvironmentKeyVaultEndpoint EnvironmentProperty = "keyVaultEndpoint" // EnvironmentGraphEndpoint ... EnvironmentGraphEndpoint EnvironmentProperty = "graphEndpoint" // EnvironmentServiceBusEndpoint ... EnvironmentServiceBusEndpoint EnvironmentProperty = "serviceBusEndpoint" // EnvironmentBatchManagementEndpoint ... EnvironmentBatchManagementEndpoint EnvironmentProperty = "batchManagementEndpoint" // EnvironmentStorageEndpointSuffix ... EnvironmentStorageEndpointSuffix EnvironmentProperty = "storageEndpointSuffix" // EnvironmentSQLDatabaseDNSSuffix ... EnvironmentSQLDatabaseDNSSuffix EnvironmentProperty = "sqlDatabaseDNSSuffix" // EnvironmentTrafficManagerDNSSuffix ... EnvironmentTrafficManagerDNSSuffix EnvironmentProperty = "trafficManagerDNSSuffix" // EnvironmentKeyVaultDNSSuffix ... EnvironmentKeyVaultDNSSuffix EnvironmentProperty = "keyVaultDNSSuffix" // EnvironmentServiceBusEndpointSuffix ... EnvironmentServiceBusEndpointSuffix EnvironmentProperty = "serviceBusEndpointSuffix" // EnvironmentServiceManagementVMDNSSuffix ... EnvironmentServiceManagementVMDNSSuffix EnvironmentProperty = "serviceManagementVMDNSSuffix" // EnvironmentResourceManagerVMDNSSuffix ... EnvironmentResourceManagerVMDNSSuffix EnvironmentProperty = "resourceManagerVMDNSSuffix" // EnvironmentContainerRegistryDNSSuffix ... EnvironmentContainerRegistryDNSSuffix EnvironmentProperty = "containerRegistryDNSSuffix" // EnvironmentTokenAudience ... EnvironmentTokenAudience EnvironmentProperty = "tokenAudience" ) // OverrideProperty represents property name and value that clients can override type OverrideProperty struct { Key EnvironmentProperty Value string } // EnvironmentFromURL loads an Environment from a URL // This function is particularly useful in the Hybrid Cloud model, where one may define their own // endpoints. func EnvironmentFromURL(resourceManagerEndpoint string, properties ...OverrideProperty) (environment Environment, err error) { var metadataEnvProperties environmentMetadataInfo if resourceManagerEndpoint == "" { return environment, fmt.Errorf("Metadata resource manager endpoint is empty") } if metadataEnvProperties, err = retrieveMetadataEnvironment(resourceManagerEndpoint); err != nil { return environment, err } // Give priority to user's override values overrideProperties(&environment, properties) if environment.Name == "" { environment.Name = "HybridEnvironment" } stampDNSSuffix := environment.StorageEndpointSuffix if stampDNSSuffix == "" { stampDNSSuffix = strings.TrimSuffix(strings.TrimPrefix(strings.Replace(resourceManagerEndpoint, strings.Split(resourceManagerEndpoint, ".")[0], "", 1), "."), "/") environment.StorageEndpointSuffix = stampDNSSuffix } if environment.KeyVaultDNSSuffix == "" { environment.KeyVaultDNSSuffix = fmt.Sprintf("%s.%s", "vault", stampDNSSuffix) } if environment.KeyVaultEndpoint == "" { environment.KeyVaultEndpoint = fmt.Sprintf("%s%s", "https://", environment.KeyVaultDNSSuffix) } if environment.TokenAudience == "" { environment.TokenAudience = metadataEnvProperties.Authentication.Audiences[0] } if environment.ActiveDirectoryEndpoint == "" { environment.ActiveDirectoryEndpoint = metadataEnvProperties.Authentication.LoginEndpoint } if environment.ResourceManagerEndpoint == "" { environment.ResourceManagerEndpoint = resourceManagerEndpoint } if environment.GalleryEndpoint == "" { environment.GalleryEndpoint = metadataEnvProperties.GalleryEndpoint } if environment.GraphEndpoint == "" { environment.GraphEndpoint = metadataEnvProperties.GraphEndpoint } return environment, nil } func overrideProperties(environment *Environment, properties []OverrideProperty) { for _, property := range properties { switch property.Key { case EnvironmentName: { environment.Name = property.Value } case EnvironmentManagementPortalURL: { environment.ManagementPortalURL = property.Value } case EnvironmentPublishSettingsURL: { environment.PublishSettingsURL = property.Value } case EnvironmentServiceManagementEndpoint: { environment.ServiceManagementEndpoint = property.Value } case EnvironmentResourceManagerEndpoint: { environment.ResourceManagerEndpoint = property.Value } case EnvironmentActiveDirectoryEndpoint: { environment.ActiveDirectoryEndpoint = property.Value } case EnvironmentGalleryEndpoint: { environment.GalleryEndpoint = property.Value } case EnvironmentKeyVaultEndpoint: { environment.KeyVaultEndpoint = property.Value } case EnvironmentGraphEndpoint: { environment.GraphEndpoint = property.Value } case EnvironmentServiceBusEndpoint: { environment.ServiceBusEndpoint = property.Value } case EnvironmentBatchManagementEndpoint: { environment.BatchManagementEndpoint = property.Value } case EnvironmentStorageEndpointSuffix: { environment.StorageEndpointSuffix = property.Value } case EnvironmentSQLDatabaseDNSSuffix: { environment.SQLDatabaseDNSSuffix = property.Value } case EnvironmentTrafficManagerDNSSuffix: { environment.TrafficManagerDNSSuffix = property.Value } case EnvironmentKeyVaultDNSSuffix: { environment.KeyVaultDNSSuffix = property.Value } case EnvironmentServiceBusEndpointSuffix: { environment.ServiceBusEndpointSuffix = property.Value } case EnvironmentServiceManagementVMDNSSuffix: { environment.ServiceManagementVMDNSSuffix = property.Value } case EnvironmentResourceManagerVMDNSSuffix: { environment.ResourceManagerVMDNSSuffix = property.Value } case EnvironmentContainerRegistryDNSSuffix: { environment.ContainerRegistryDNSSuffix = property.Value } case EnvironmentTokenAudience: { environment.TokenAudience = property.Value } } } } func retrieveMetadataEnvironment(endpoint string) (environment environmentMetadataInfo, err error) { client := autorest.NewClientWithUserAgent("") managementEndpoint := fmt.Sprintf("%s%s", strings.TrimSuffix(endpoint, "/"), "/metadata/endpoints?api-version=1.0") req, _ := http.NewRequest("GET", managementEndpoint, nil) response, err := client.Do(req) if err != nil { return environment, err } defer response.Body.Close() jsonResponse, err := ioutil.ReadAll(response.Body) if err != nil { return environment, err } err = json.Unmarshal(jsonResponse, &environment) return environment, err } golang-github-azure-go-autorest-14.1.1/autorest/azure/rp.go000066400000000000000000000145261367372352400237100ustar00rootroot00000000000000// Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package azure import ( "errors" "fmt" "net/http" "net/url" "strings" "time" "github.com/Azure/go-autorest/autorest" ) // DoRetryWithRegistration tries to register the resource provider in case it is unregistered. // It also handles request retries func DoRetryWithRegistration(client autorest.Client) autorest.SendDecorator { return func(s autorest.Sender) autorest.Sender { return autorest.SenderFunc(func(r *http.Request) (resp *http.Response, err error) { rr := autorest.NewRetriableRequest(r) for currentAttempt := 0; currentAttempt < client.RetryAttempts; currentAttempt++ { err = rr.Prepare() if err != nil { return resp, err } resp, err = autorest.SendWithSender(s, rr.Request(), autorest.DoRetryForStatusCodes(client.RetryAttempts, client.RetryDuration, autorest.StatusCodesForRetry...), ) if err != nil { return resp, err } if resp.StatusCode != http.StatusConflict || client.SkipResourceProviderRegistration { return resp, err } var re RequestError if strings.Contains(r.Header.Get("Content-Type"), "xml") { // XML errors (e.g. Storage Data Plane) only return the inner object err = autorest.Respond(resp, autorest.ByUnmarshallingXML(&re.ServiceError)) } else { err = autorest.Respond(resp, autorest.ByUnmarshallingJSON(&re)) } if err != nil { return resp, err } err = re if re.ServiceError != nil && re.ServiceError.Code == "MissingSubscriptionRegistration" { regErr := register(client, r, re) if regErr != nil { return resp, fmt.Errorf("failed auto registering Resource Provider: %s. Original error: %s", regErr, err) } } } return resp, err }) } } func getProvider(re RequestError) (string, error) { if re.ServiceError != nil && len(re.ServiceError.Details) > 0 { return re.ServiceError.Details[0]["target"].(string), nil } return "", errors.New("provider was not found in the response") } func register(client autorest.Client, originalReq *http.Request, re RequestError) error { subID := getSubscription(originalReq.URL.Path) if subID == "" { return errors.New("missing parameter subscriptionID to register resource provider") } providerName, err := getProvider(re) if err != nil { return fmt.Errorf("missing parameter provider to register resource provider: %s", err) } newURL := url.URL{ Scheme: originalReq.URL.Scheme, Host: originalReq.URL.Host, } // taken from the resources SDK // with almost identical code, this sections are easier to mantain // It is also not a good idea to import the SDK here // https://github.com/Azure/azure-sdk-for-go/blob/9f366792afa3e0ddaecdc860e793ba9d75e76c27/arm/resources/resources/providers.go#L252 pathParameters := map[string]interface{}{ "resourceProviderNamespace": autorest.Encode("path", providerName), "subscriptionId": autorest.Encode("path", subID), } const APIVersion = "2016-09-01" queryParameters := map[string]interface{}{ "api-version": APIVersion, } preparer := autorest.CreatePreparer( autorest.AsPost(), autorest.WithBaseURL(newURL.String()), autorest.WithPathParameters("/subscriptions/{subscriptionId}/providers/{resourceProviderNamespace}/register", pathParameters), autorest.WithQueryParameters(queryParameters), ) req, err := preparer.Prepare(&http.Request{}) if err != nil { return err } req = req.WithContext(originalReq.Context()) resp, err := autorest.SendWithSender(client, req, autorest.DoRetryForStatusCodes(client.RetryAttempts, client.RetryDuration, autorest.StatusCodesForRetry...), ) if err != nil { return err } type Provider struct { RegistrationState *string `json:"registrationState,omitempty"` } var provider Provider err = autorest.Respond( resp, WithErrorUnlessStatusCode(http.StatusOK), autorest.ByUnmarshallingJSON(&provider), autorest.ByClosing(), ) if err != nil { return err } // poll for registered provisioning state registrationStartTime := time.Now() for err == nil && (client.PollingDuration == 0 || (client.PollingDuration != 0 && time.Since(registrationStartTime) < client.PollingDuration)) { // taken from the resources SDK // https://github.com/Azure/azure-sdk-for-go/blob/9f366792afa3e0ddaecdc860e793ba9d75e76c27/arm/resources/resources/providers.go#L45 preparer := autorest.CreatePreparer( autorest.AsGet(), autorest.WithBaseURL(newURL.String()), autorest.WithPathParameters("/subscriptions/{subscriptionId}/providers/{resourceProviderNamespace}", pathParameters), autorest.WithQueryParameters(queryParameters), ) req, err = preparer.Prepare(&http.Request{}) if err != nil { return err } req = req.WithContext(originalReq.Context()) resp, err := autorest.SendWithSender(client, req, autorest.DoRetryForStatusCodes(client.RetryAttempts, client.RetryDuration, autorest.StatusCodesForRetry...), ) if err != nil { return err } err = autorest.Respond( resp, WithErrorUnlessStatusCode(http.StatusOK), autorest.ByUnmarshallingJSON(&provider), autorest.ByClosing(), ) if err != nil { return err } if provider.RegistrationState != nil && *provider.RegistrationState == "Registered" { break } delayed := autorest.DelayWithRetryAfter(resp, originalReq.Context().Done()) if !delayed && !autorest.DelayForBackoff(client.PollingDelay, 0, originalReq.Context().Done()) { return originalReq.Context().Err() } } if client.PollingDuration != 0 && !(time.Since(registrationStartTime) < client.PollingDuration) { return errors.New("polling for resource provider registration has exceeded the polling duration") } return err } func getSubscription(path string) string { parts := strings.Split(path, "/") for i, v := range parts { if v == "subscriptions" && (i+1) < len(parts) { return parts[i+1] } } return "" } golang-github-azure-go-autorest-14.1.1/autorest/azure/rp_test.go000066400000000000000000000136101367372352400247400ustar00rootroot00000000000000// Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package azure import ( "context" "net/http" "sync" "testing" "time" "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/mocks" ) func TestDoRetryWithRegistration(t *testing.T) { client := mocks.NewSender() // first response, should retry because it is a transient error client.AppendResponse(mocks.NewResponseWithStatus("Internal server error", http.StatusInternalServerError)) // response indicates the resource provider has not been registered client.AppendResponse(mocks.NewResponseWithBodyAndStatus(mocks.NewBody(`{ "error":{ "code":"MissingSubscriptionRegistration", "message":"The subscription registration is in 'Unregistered' state. The subscription must be registered to use namespace 'Microsoft.EventGrid'. See https://aka.ms/rps-not-found for how to register subscriptions.", "details":[ { "code":"MissingSubscriptionRegistration", "target":"Microsoft.EventGrid", "message":"The subscription registration is in 'Unregistered' state. The subscription must be registered to use namespace 'Microsoft.EventGrid'. See https://aka.ms/rps-not-found for how to register subscriptions." } ] } } `), http.StatusConflict, "MissingSubscriptionRegistration")) // first poll response, still not ready client.AppendResponse(mocks.NewResponseWithBodyAndStatus(mocks.NewBody(`{ "registrationState": "Registering" } `), http.StatusOK, "200 OK")) // last poll response, respurce provider has been registered client.AppendResponse(mocks.NewResponseWithBodyAndStatus(mocks.NewBody(`{ "registrationState": "Registered" } `), http.StatusOK, "200 OK")) // retry original request, response is successful client.AppendResponse(mocks.NewResponseWithStatus("200 OK", http.StatusOK)) req := mocks.NewRequestForURL("https://lol/subscriptions/rofl") req.Body = mocks.NewBody("lolol") r, err := autorest.SendWithSender(client, req, DoRetryWithRegistration(autorest.Client{ PollingDelay: time.Second, PollingDuration: time.Second * 10, RetryAttempts: 5, RetryDuration: time.Second, Sender: client, }), ) if err != nil { t.Fatalf("got error: %v", err) } autorest.Respond(r, autorest.ByDiscardingBody(), autorest.ByClosing(), ) if r.StatusCode != http.StatusOK { t.Fatalf("azure: Sender#DoRetryWithRegistration -- Got: StatusCode %v; Want: StatusCode 200 OK", r.StatusCode) } } func TestDoRetrySkipRegistration(t *testing.T) { client := mocks.NewSender() // first response, should retry because it is a transient error client.AppendResponse(mocks.NewResponseWithStatus("Internal server error", http.StatusInternalServerError)) // response indicates the resource provider has not been registered client.AppendResponse(mocks.NewResponseWithBodyAndStatus(mocks.NewBody(`{ "error":{ "code":"MissingSubscriptionRegistration", "message":"The subscription registration is in 'Unregistered' state. The subscription must be registered to use namespace 'Microsoft.EventGrid'. See https://aka.ms/rps-not-found for how to register subscriptions.", "details":[ { "code":"MissingSubscriptionRegistration", "target":"Microsoft.EventGrid", "message":"The subscription registration is in 'Unregistered' state. The subscription must be registered to use namespace 'Microsoft.EventGrid'. See https://aka.ms/rps-not-found for how to register subscriptions." } ] } }`), http.StatusConflict, "MissingSubscriptionRegistration")) req := mocks.NewRequestForURL("https://lol/subscriptions/rofl") req.Body = mocks.NewBody("lolol") r, err := autorest.SendWithSender(client, req, DoRetryWithRegistration(autorest.Client{ PollingDelay: time.Second, PollingDuration: time.Second * 10, RetryAttempts: 5, RetryDuration: time.Second, Sender: client, SkipResourceProviderRegistration: true, }), ) if err != nil { t.Fatalf("got error: %v", err) } autorest.Respond(r, autorest.ByDiscardingBody(), autorest.ByClosing(), ) if r.StatusCode != http.StatusConflict { t.Fatalf("azure: Sender#DoRetryWithRegistration -- Got: StatusCode %v; Want: StatusCode 409 Conflict", r.StatusCode) } } func TestDoRetryWithRegistration_CanBeCancelled(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) delay := 5 * time.Second client := mocks.NewSender() client.AppendAndRepeatResponse(mocks.NewResponseWithStatus("Internal server error", http.StatusInternalServerError), 5) var wg sync.WaitGroup wg.Add(1) start := time.Now() end := time.Now() var err error go func() { req := mocks.NewRequestForURL("https://lol/subscriptions/rofl") req = req.WithContext(ctx) req.Body = mocks.NewBody("lolol") _, err = autorest.SendWithSender(client, req, DoRetryWithRegistration(autorest.Client{ PollingDelay: time.Second, PollingDuration: delay, RetryAttempts: 5, RetryDuration: time.Second, Sender: client, SkipResourceProviderRegistration: true, }), ) end = time.Now() wg.Done() }() cancel() wg.Wait() time.Sleep(5 * time.Millisecond) if err == nil { t.Fatalf("azure: DoRetryWithRegistration didn't cancel") } if end.Sub(start) >= delay { t.Fatalf("azure: DoRetryWithRegistration failed to cancel") } } golang-github-azure-go-autorest-14.1.1/autorest/azure/testdata/000077500000000000000000000000001367372352400245415ustar00rootroot00000000000000golang-github-azure-go-autorest-14.1.1/autorest/azure/testdata/test_environment_1.json000066400000000000000000000026541367372352400312660ustar00rootroot00000000000000{ "name": "--unit-test--", "managementPortalURL": "--management-portal-url", "publishSettingsURL": "--publish-settings-url--", "serviceManagementEndpoint": "--service-management-endpoint--", "resourceManagerEndpoint": "--resource-management-endpoint--", "activeDirectoryEndpoint": "--active-directory-endpoint--", "galleryEndpoint": "--gallery-endpoint--", "keyVaultEndpoint": "--key-vault--endpoint--", "graphEndpoint": "--graph-endpoint--", "storageEndpointSuffix": "--storage-endpoint-suffix--", "sqlDatabaseDNSSuffix": "--sql-database-dns-suffix--", "trafficManagerDNSSuffix": "--traffic-manager-dns-suffix--", "keyVaultDNSSuffix": "--key-vault-dns-suffix--", "serviceBusEndpointSuffix": "--service-bus-endpoint-suffix--", "serviceManagementVMDNSSuffix": "--asm-vm-dns-suffix--", "resourceManagerVMDNSSuffix": "--arm-vm-dns-suffix--", "containerRegistryDNSSuffix": "--container-registry-dns-suffix--", "tokenAudience": "--token-audience", "resourceIdentifiers": { "batch": "--batch-resource-id--", "datalake": "--datalake-resource-id--", "graph": "--graph-resource-id--", "keyVault": "--keyvault-resource-id--", "operationalInsights": "--operational-insights-resource-id--" } } golang-github-azure-go-autorest-14.1.1/autorest/azure/testdata/test_metadata_environment_1.json000066400000000000000000000006311367372352400331170ustar00rootroot00000000000000{ "galleryEndpoint":"https://portal.local.azurestack.external:30015/", "graphEndpoint":"https://graph.windows.net/", "portalEndpoint":"https://portal.local.azurestack.external/", "authentication":{ "loginEndpoint":"https://login.windows.net/", "audiences":[ "https://management.azurestackci04.onmicrosoft.com/3ee899c8-c137-4ce4-b230-619961a09a73" ] } } golang-github-azure-go-autorest-14.1.1/autorest/client.go000066400000000000000000000261401367372352400234120ustar00rootroot00000000000000package autorest // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "bytes" "crypto/tls" "fmt" "io" "io/ioutil" "log" "net/http" "strings" "time" "github.com/Azure/go-autorest/logger" ) const ( // DefaultPollingDelay is a reasonable delay between polling requests. DefaultPollingDelay = 60 * time.Second // DefaultPollingDuration is a reasonable total polling duration. DefaultPollingDuration = 15 * time.Minute // DefaultRetryAttempts is number of attempts for retry status codes (5xx). DefaultRetryAttempts = 3 // DefaultRetryDuration is the duration to wait between retries. DefaultRetryDuration = 30 * time.Second ) var ( // StatusCodesForRetry are a defined group of status code for which the client will retry StatusCodesForRetry = []int{ http.StatusRequestTimeout, // 408 http.StatusTooManyRequests, // 429 http.StatusInternalServerError, // 500 http.StatusBadGateway, // 502 http.StatusServiceUnavailable, // 503 http.StatusGatewayTimeout, // 504 } ) const ( requestFormat = `HTTP Request Begin =================================================== %s ===================================================== HTTP Request End ` responseFormat = `HTTP Response Begin =================================================== %s ===================================================== HTTP Response End ` ) // Response serves as the base for all responses from generated clients. It provides access to the // last http.Response. type Response struct { *http.Response `json:"-"` } // IsHTTPStatus returns true if the returned HTTP status code matches the provided status code. // If there was no response (i.e. the underlying http.Response is nil) the return value is false. func (r Response) IsHTTPStatus(statusCode int) bool { if r.Response == nil { return false } return r.Response.StatusCode == statusCode } // HasHTTPStatus returns true if the returned HTTP status code matches one of the provided status codes. // If there was no response (i.e. the underlying http.Response is nil) or not status codes are provided // the return value is false. func (r Response) HasHTTPStatus(statusCodes ...int) bool { return ResponseHasStatusCode(r.Response, statusCodes...) } // LoggingInspector implements request and response inspectors that log the full request and // response to a supplied log. type LoggingInspector struct { Logger *log.Logger } // WithInspection returns a PrepareDecorator that emits the http.Request to the supplied logger. The // body is restored after being emitted. // // Note: Since it reads the entire Body, this decorator should not be used where body streaming is // important. It is best used to trace JSON or similar body values. func (li LoggingInspector) WithInspection() PrepareDecorator { return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { var body, b bytes.Buffer defer r.Body.Close() r.Body = ioutil.NopCloser(io.TeeReader(r.Body, &body)) if err := r.Write(&b); err != nil { return nil, fmt.Errorf("Failed to write response: %v", err) } li.Logger.Printf(requestFormat, b.String()) r.Body = ioutil.NopCloser(&body) return p.Prepare(r) }) } } // ByInspecting returns a RespondDecorator that emits the http.Response to the supplied logger. The // body is restored after being emitted. // // Note: Since it reads the entire Body, this decorator should not be used where body streaming is // important. It is best used to trace JSON or similar body values. func (li LoggingInspector) ByInspecting() RespondDecorator { return func(r Responder) Responder { return ResponderFunc(func(resp *http.Response) error { var body, b bytes.Buffer defer resp.Body.Close() resp.Body = ioutil.NopCloser(io.TeeReader(resp.Body, &body)) if err := resp.Write(&b); err != nil { return fmt.Errorf("Failed to write response: %v", err) } li.Logger.Printf(responseFormat, b.String()) resp.Body = ioutil.NopCloser(&body) return r.Respond(resp) }) } } // Client is the base for autorest generated clients. It provides default, "do nothing" // implementations of an Authorizer, RequestInspector, and ResponseInspector. It also returns the // standard, undecorated http.Client as a default Sender. // // Generated clients should also use Error (see NewError and NewErrorWithError) for errors and // return responses that compose with Response. // // Most customization of generated clients is best achieved by supplying a custom Authorizer, custom // RequestInspector, and / or custom ResponseInspector. Users may log requests, implement circuit // breakers (see https://msdn.microsoft.com/en-us/library/dn589784.aspx) or otherwise influence // sending the request by providing a decorated Sender. type Client struct { Authorizer Authorizer Sender Sender RequestInspector PrepareDecorator ResponseInspector RespondDecorator // PollingDelay sets the polling frequency used in absence of a Retry-After HTTP header PollingDelay time.Duration // PollingDuration sets the maximum polling time after which an error is returned. // Setting this to zero will use the provided context to control the duration. PollingDuration time.Duration // RetryAttempts sets the default number of retry attempts for client. RetryAttempts int // RetryDuration sets the delay duration for retries. RetryDuration time.Duration // UserAgent, if not empty, will be set as the HTTP User-Agent header on all requests sent // through the Do method. UserAgent string Jar http.CookieJar // Set to true to skip attempted registration of resource providers (false by default). SkipResourceProviderRegistration bool // SendDecorators can be used to override the default chain of SendDecorators. // This can be used to specify things like a custom retry SendDecorator. // Set this to an empty slice to use no SendDecorators. SendDecorators []SendDecorator } // NewClientWithUserAgent returns an instance of a Client with the UserAgent set to the passed // string. func NewClientWithUserAgent(ua string) Client { return newClient(ua, tls.RenegotiateNever) } // ClientOptions contains various Client configuration options. type ClientOptions struct { // UserAgent is an optional user-agent string to append to the default user agent. UserAgent string // Renegotiation is an optional setting to control client-side TLS renegotiation. Renegotiation tls.RenegotiationSupport } // NewClientWithOptions returns an instance of a Client with the specified values. func NewClientWithOptions(options ClientOptions) Client { return newClient(options.UserAgent, options.Renegotiation) } func newClient(ua string, renegotiation tls.RenegotiationSupport) Client { c := Client{ PollingDelay: DefaultPollingDelay, PollingDuration: DefaultPollingDuration, RetryAttempts: DefaultRetryAttempts, RetryDuration: DefaultRetryDuration, UserAgent: UserAgent(), } c.Sender = c.sender(renegotiation) c.AddToUserAgent(ua) return c } // AddToUserAgent adds an extension to the current user agent func (c *Client) AddToUserAgent(extension string) error { if extension != "" { c.UserAgent = fmt.Sprintf("%s %s", c.UserAgent, extension) return nil } return fmt.Errorf("Extension was empty, User Agent stayed as %s", c.UserAgent) } // Do implements the Sender interface by invoking the active Sender after applying authorization. // If Sender is not set, it uses a new instance of http.Client. In both cases it will, if UserAgent // is set, apply set the User-Agent header. func (c Client) Do(r *http.Request) (*http.Response, error) { if r.UserAgent() == "" { r, _ = Prepare(r, WithUserAgent(c.UserAgent)) } // NOTE: c.WithInspection() must be last in the list so that it can inspect all preceding operations r, err := Prepare(r, c.WithAuthorization(), c.WithInspection()) if err != nil { var resp *http.Response if detErr, ok := err.(DetailedError); ok { // if the authorization failed (e.g. invalid credentials) there will // be a response associated with the error, be sure to return it. resp = detErr.Response } return resp, NewErrorWithError(err, "autorest/Client", "Do", nil, "Preparing request failed") } logger.Instance.WriteRequest(r, logger.Filter{ Header: func(k string, v []string) (bool, []string) { // remove the auth token from the log if strings.EqualFold(k, "Authorization") || strings.EqualFold(k, "Ocp-Apim-Subscription-Key") { v = []string{"**REDACTED**"} } return true, v }, }) resp, err := SendWithSender(c.sender(tls.RenegotiateNever), r) logger.Instance.WriteResponse(resp, logger.Filter{}) Respond(resp, c.ByInspecting()) return resp, err } // sender returns the Sender to which to send requests. func (c Client) sender(renengotiation tls.RenegotiationSupport) Sender { if c.Sender == nil { return sender(renengotiation) } return c.Sender } // WithAuthorization is a convenience method that returns the WithAuthorization PrepareDecorator // from the current Authorizer. If not Authorizer is set, it uses the NullAuthorizer. func (c Client) WithAuthorization() PrepareDecorator { return c.authorizer().WithAuthorization() } // authorizer returns the Authorizer to use. func (c Client) authorizer() Authorizer { if c.Authorizer == nil { return NullAuthorizer{} } return c.Authorizer } // WithInspection is a convenience method that passes the request to the supplied RequestInspector, // if present, or returns the WithNothing PrepareDecorator otherwise. func (c Client) WithInspection() PrepareDecorator { if c.RequestInspector == nil { return WithNothing() } return c.RequestInspector } // ByInspecting is a convenience method that passes the response to the supplied ResponseInspector, // if present, or returns the ByIgnoring RespondDecorator otherwise. func (c Client) ByInspecting() RespondDecorator { if c.ResponseInspector == nil { return ByIgnoring() } return c.ResponseInspector } // Send sends the provided http.Request using the client's Sender or the default sender. // It returns the http.Response and possible error. It also accepts a, possibly empty, // default set of SendDecorators used when sending the request. // SendDecorators have the following precedence: // 1. In a request's context via WithSendDecorators() // 2. Specified on the client in SendDecorators // 3. The default values specified in this method func (c Client) Send(req *http.Request, decorators ...SendDecorator) (*http.Response, error) { if c.SendDecorators != nil { decorators = c.SendDecorators } inCtx := req.Context().Value(ctxSendDecorators{}) if sd, ok := inCtx.([]SendDecorator); ok { decorators = sd } return SendWithSender(c, req, decorators...) } golang-github-azure-go-autorest-14.1.1/autorest/client_test.go000066400000000000000000000364671367372352400244660ustar00rootroot00000000000000package autorest // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "bytes" "context" "crypto/tls" "fmt" "io/ioutil" "log" "math/rand" "net/http" "net/http/httptest" "reflect" "testing" "time" "github.com/Azure/go-autorest/autorest/mocks" ) func TestLoggingInspectorWithInspection(t *testing.T) { b := bytes.Buffer{} c := Client{} li := LoggingInspector{Logger: log.New(&b, "", 0)} c.RequestInspector = li.WithInspection() Prepare(mocks.NewRequestWithContent("Content"), c.WithInspection()) if len(b.String()) <= 0 { t.Fatal("autorest: LoggingInspector#WithInspection did not record Request to the log") } } func TestLoggingInspectorWithInspectionEmitsErrors(t *testing.T) { b := bytes.Buffer{} c := Client{} r := mocks.NewRequestWithContent("Content") li := LoggingInspector{Logger: log.New(&b, "", 0)} c.RequestInspector = li.WithInspection() if _, err := Prepare(r, c.WithInspection()); err != nil { t.Error(err) } if len(b.String()) <= 0 { t.Fatal("autorest: LoggingInspector#WithInspection did not record Request to the log") } } func TestLoggingInspectorWithInspectionRestoresBody(t *testing.T) { b := bytes.Buffer{} c := Client{} r := mocks.NewRequestWithContent("Content") li := LoggingInspector{Logger: log.New(&b, "", 0)} c.RequestInspector = li.WithInspection() Prepare(r, c.WithInspection()) s, _ := ioutil.ReadAll(r.Body) if len(s) <= 0 { t.Fatal("autorest: LoggingInspector#WithInspection did not restore the Request body") } } func TestLoggingInspectorByInspecting(t *testing.T) { b := bytes.Buffer{} c := Client{} li := LoggingInspector{Logger: log.New(&b, "", 0)} c.ResponseInspector = li.ByInspecting() Respond(mocks.NewResponseWithContent("Content"), c.ByInspecting()) if len(b.String()) <= 0 { t.Fatal("autorest: LoggingInspector#ByInspection did not record Response to the log") } } func TestLoggingInspectorByInspectingEmitsErrors(t *testing.T) { b := bytes.Buffer{} c := Client{} r := mocks.NewResponseWithContent("Content") li := LoggingInspector{Logger: log.New(&b, "", 0)} c.ResponseInspector = li.ByInspecting() if err := Respond(r, c.ByInspecting()); err != nil { t.Fatal(err) } if len(b.String()) <= 0 { t.Fatal("autorest: LoggingInspector#ByInspection did not record Response to the log") } } func TestLoggingInspectorByInspectingRestoresBody(t *testing.T) { b := bytes.Buffer{} c := Client{} r := mocks.NewResponseWithContent("Content") li := LoggingInspector{Logger: log.New(&b, "", 0)} c.ResponseInspector = li.ByInspecting() Respond(r, c.ByInspecting()) s, _ := ioutil.ReadAll(r.Body) if len(s) <= 0 { t.Fatal("autorest: LoggingInspector#ByInspecting did not restore the Response body") } } func TestNewClientWithUserAgent(t *testing.T) { ua := "UserAgent" c := NewClientWithUserAgent(ua) completeUA := fmt.Sprintf("%s %s", UserAgent(), ua) if c.UserAgent != completeUA { t.Fatalf("autorest: NewClientWithUserAgent failed to set the UserAgent -- expected %s, received %s", completeUA, c.UserAgent) } r := c.Sender.(*http.Client).Transport.(*http.Transport).TLSClientConfig.Renegotiation if r != tls.RenegotiateNever { t.Fatal("autorest: TestNewClientWithUserAgentTLSRenegotiation expected RenegotiateNever") } } func TestNewClientWithOptions(t *testing.T) { const ua = "UserAgent" c1 := NewClientWithOptions(ClientOptions{ UserAgent: ua, Renegotiation: tls.RenegotiateFreelyAsClient, }) r1 := c1.Sender.(*http.Client).Transport.(*http.Transport).TLSClientConfig.Renegotiation if r1 != tls.RenegotiateFreelyAsClient { t.Fatal("autorest: TestNewClientWithUserAgentTLSRenegotiation expected RenegotiateFreelyAsClient") } // ensure default value doesn't stomp over previous value c2 := NewClientWithUserAgent(ua) r2 := c2.Sender.(*http.Client).Transport.(*http.Transport).TLSClientConfig.Renegotiation if r2 != tls.RenegotiateNever { t.Fatal("autorest: TestNewClientWithUserAgentTLSRenegotiation expected RenegotiateNever") } r1 = c1.Sender.(*http.Client).Transport.(*http.Transport).TLSClientConfig.Renegotiation if r1 != tls.RenegotiateFreelyAsClient { t.Fatal("autorest: TestNewClientWithUserAgentTLSRenegotiation expected RenegotiateFreelyAsClient (overwritten)") } r2 = c2.Sender.(*http.Client).Transport.(*http.Transport).TLSClientConfig.Renegotiation if r2 != tls.RenegotiateNever { t.Fatal("autorest: TestNewClientWithUserAgentTLSRenegotiation expected RenegotiateNever (overwritten)") } } func TestAddToUserAgent(t *testing.T) { ua := "UserAgent" c := NewClientWithUserAgent(ua) ext := "extension" err := c.AddToUserAgent(ext) if err != nil { t.Fatalf("autorest: AddToUserAgent returned error -- expected nil, received %s", err) } completeUA := fmt.Sprintf("%s %s %s", UserAgent(), ua, ext) if c.UserAgent != completeUA { t.Fatalf("autorest: AddToUserAgent failed to add an extension to the UserAgent -- expected %s, received %s", completeUA, c.UserAgent) } err = c.AddToUserAgent("") if err == nil { t.Fatalf("autorest: AddToUserAgent didn't return error -- expected %s, received nil", fmt.Errorf("Extension was empty, User Agent stayed as %s", c.UserAgent)) } if c.UserAgent != completeUA { t.Fatalf("autorest: AddToUserAgent failed to not add an empty extension to the UserAgent -- expected %s, received %s", completeUA, c.UserAgent) } } func TestClientSenderReturnsHttpClientByDefault(t *testing.T) { c := Client{} if fmt.Sprintf("%T", c.sender(tls.RenegotiateNever)) != "*http.Client" { t.Fatal("autorest: Client#sender failed to return http.Client by default") } } func TestClientSenderReturnsSetSender(t *testing.T) { c := Client{} s := mocks.NewSender() c.Sender = s if c.sender(tls.RenegotiateNever) != s { t.Fatal("autorest: Client#sender failed to return set Sender") } } func TestClientDoInvokesSender(t *testing.T) { c := Client{} s := mocks.NewSender() c.Sender = s c.Do(&http.Request{}) if s.Attempts() != 1 { t.Fatal("autorest: Client#Do failed to invoke the Sender") } } func TestClientDoSetsUserAgent(t *testing.T) { ua := "UserAgent" c := Client{UserAgent: ua} r := mocks.NewRequest() s := mocks.NewSender() c.Sender = s c.Do(r) if r.UserAgent() != ua { t.Fatalf("autorest: Client#Do failed to correctly set User-Agent header: %s=%s", http.CanonicalHeaderKey(headerUserAgent), r.UserAgent()) } } func TestClientDoSetsAuthorization(t *testing.T) { r := mocks.NewRequest() s := mocks.NewSender() c := Client{Authorizer: mockAuthorizer{}, Sender: s} c.Do(r) if len(r.Header.Get(http.CanonicalHeaderKey(headerAuthorization))) <= 0 { t.Fatalf("autorest: Client#Send failed to set Authorization header -- %s=%s", http.CanonicalHeaderKey(headerAuthorization), r.Header.Get(http.CanonicalHeaderKey(headerAuthorization))) } } func TestClientDoInvokesRequestInspector(t *testing.T) { r := mocks.NewRequest() s := mocks.NewSender() i := &mockInspector{} c := Client{RequestInspector: i.WithInspection(), Sender: s} c.Do(r) if !i.wasInvoked { t.Fatal("autorest: Client#Send failed to invoke the RequestInspector") } } func TestClientDoInvokesResponseInspector(t *testing.T) { r := mocks.NewRequest() s := mocks.NewSender() i := &mockInspector{} c := Client{ResponseInspector: i.ByInspecting(), Sender: s} c.Do(r) if !i.wasInvoked { t.Fatal("autorest: Client#Send failed to invoke the ResponseInspector") } } func TestClientDoReturnsErrorIfPrepareFails(t *testing.T) { c := Client{} s := mocks.NewSender() c.Authorizer = mockFailingAuthorizer{} c.Sender = s _, err := c.Do(&http.Request{}) if err == nil { t.Fatalf("autorest: Client#Do failed to return an error when Prepare failed") } } func TestClientDoDoesNotSendIfPrepareFails(t *testing.T) { c := Client{} s := mocks.NewSender() c.Authorizer = mockFailingAuthorizer{} c.Sender = s c.Do(&http.Request{}) if s.Attempts() > 0 { t.Fatal("autorest: Client#Do failed to invoke the Sender") } } func TestClientAuthorizerReturnsNullAuthorizerByDefault(t *testing.T) { c := Client{} if fmt.Sprintf("%T", c.authorizer()) != "autorest.NullAuthorizer" { t.Fatal("autorest: Client#authorizer failed to return the NullAuthorizer by default") } } func TestClientAuthorizerReturnsSetAuthorizer(t *testing.T) { c := Client{} c.Authorizer = mockAuthorizer{} if fmt.Sprintf("%T", c.authorizer()) != "autorest.mockAuthorizer" { t.Fatal("autorest: Client#authorizer failed to return the set Authorizer") } } func TestClientWithAuthorizer(t *testing.T) { c := Client{} c.Authorizer = mockAuthorizer{} req, _ := Prepare(&http.Request{}, c.WithAuthorization()) if req.Header.Get(headerAuthorization) == "" { t.Fatal("autorest: Client#WithAuthorizer failed to return the WithAuthorizer from the active Authorizer") } } func TestClientWithInspection(t *testing.T) { c := Client{} r := &mockInspector{} c.RequestInspector = r.WithInspection() Prepare(&http.Request{}, c.WithInspection()) if !r.wasInvoked { t.Fatal("autorest: Client#WithInspection failed to invoke RequestInspector") } } func TestClientWithInspectionSetsDefault(t *testing.T) { c := Client{} r1 := &http.Request{} r2, _ := Prepare(r1, c.WithInspection()) if !reflect.DeepEqual(r1, r2) { t.Fatal("autorest: Client#WithInspection failed to provide a default RequestInspector") } } func TestClientByInspecting(t *testing.T) { c := Client{} r := &mockInspector{} c.ResponseInspector = r.ByInspecting() Respond(&http.Response{}, c.ByInspecting()) if !r.wasInvoked { t.Fatal("autorest: Client#ByInspecting failed to invoke ResponseInspector") } } func TestClientByInspectingSetsDefault(t *testing.T) { c := Client{} r := &http.Response{} Respond(r, c.ByInspecting()) if !reflect.DeepEqual(r, &http.Response{}) { t.Fatal("autorest: Client#ByInspecting failed to provide a default ResponseInspector") } } func TestCookies(t *testing.T) { second := "second" expected := http.Cookie{ Name: "tastes", Value: "delicious", } server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.SetCookie(w, &expected) b, err := ioutil.ReadAll(r.Body) if err != nil { t.Fatalf("autorest: ioutil.ReadAll failed reading request body: %s", err) } if string(b) == second { cookie, err := r.Cookie(expected.Name) if err != nil { t.Fatalf("autorest: r.Cookie could not get request cookie: %s", err) } if cookie == nil { t.Fatalf("autorest: got nil cookie, expecting %v", expected) } if cookie.Value != expected.Value { t.Fatalf("autorest: got cookie value '%s', expecting '%s'", cookie.Value, expected.Name) } } })) defer server.Close() client := NewClientWithUserAgent("") _, err := SendWithSender(client, mocks.NewRequestForURL(server.URL)) if err != nil { t.Fatalf("autorest: first request failed: %s", err) } r2, err := http.NewRequest(http.MethodGet, server.URL, mocks.NewBody(second)) if err != nil { t.Fatalf("autorest: failed creating second request: %s", err) } _, err = SendWithSender(client, r2) if err != nil { t.Fatalf("autorest: second request failed: %s", err) } } func TestResponseIsHTTPStatus(t *testing.T) { r := Response{} if r.IsHTTPStatus(http.StatusBadRequest) { t.Fatal("autorest: expected false for nil response") } r.Response = &http.Response{StatusCode: http.StatusOK} if r.IsHTTPStatus(http.StatusBadRequest) { t.Fatal("autorest: expected false") } if !r.IsHTTPStatus(http.StatusOK) { t.Fatal("autorest: expected true") } } func TestResponseHasHTTPStatus(t *testing.T) { r := Response{} if r.HasHTTPStatus(http.StatusBadRequest, http.StatusInternalServerError) { t.Fatal("autorest: expected false for nil response") } r.Response = &http.Response{StatusCode: http.StatusAccepted} if r.HasHTTPStatus(http.StatusBadRequest, http.StatusInternalServerError) { t.Fatal("autorest: expected false") } if !r.HasHTTPStatus(http.StatusOK, http.StatusCreated, http.StatusAccepted) { t.Fatal("autorest: expected true") } if r.HasHTTPStatus() { t.Fatal("autorest: expected false for no status codes") } } func randomString(n int) string { const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" r := rand.New(rand.NewSource(time.Now().UTC().UnixNano())) s := make([]byte, n) for i := range s { s[i] = chars[r.Intn(len(chars))] } return string(s) } func TestClientSendMethod(t *testing.T) { sender := mocks.NewSender() sender.AppendResponse(newAcceptedResponse()) client := Client{ Sender: sender, } req, err := http.NewRequest(http.MethodGet, mocks.TestURL, nil) req = req.WithContext(context.Background()) if err != nil { t.Fatal(err) } // no SendDecorators resp, err := client.Send(req) if err != nil { t.Fatal(err) } if resp.StatusCode != http.StatusAccepted { t.Fatalf("expected status code %d, got %d", http.StatusAccepted, resp.StatusCode) } // default SendDecorators sender.AppendResponse(newAcceptedResponse()) resp, err = client.Send(req, DefaultSendDecorator()) if err != nil { t.Fatal(err) } if v := resp.Header.Get("default-decorator"); v != "true" { t.Fatal("didn't find default-decorator header in response") } // using client SendDecorators sender.AppendResponse(newAcceptedResponse()) client.SendDecorators = []SendDecorator{ClientSendDecorator()} resp, err = client.Send(req, DefaultSendDecorator()) if err != nil { t.Fatal(err) } if v := resp.Header.Get("client-decorator"); v != "true" { t.Fatal("didn't find client-decorator header in response") } if v := resp.Header.Get("default-decorator"); v == "true" { t.Fatal("unexpected default-decorator header in response") } // using context SendDecorators sender.AppendResponse(newAcceptedResponse()) req = req.WithContext(WithSendDecorators(req.Context(), []SendDecorator{ContextSendDecorator()})) resp, err = client.Send(req, DefaultSendDecorator()) if err != nil { t.Fatal(err) } if v := resp.Header.Get("context-decorator"); v != "true" { t.Fatal("didn't find context-decorator header in response") } if v := resp.Header.Get("client-decorator"); v == "true" { t.Fatal("unexpected client-decorator header in response") } if v := resp.Header.Get("default-decorator"); v == "true" { t.Fatal("unexpected default-decorator header in response") } } func DefaultSendDecorator() SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { resp, err := s.Do(r) resp.Header.Set("default-decorator", "true") return resp, err }) } } func ClientSendDecorator() SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { resp, err := s.Do(r) resp.Header.Set("client-decorator", "true") return resp, err }) } } func ContextSendDecorator() SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { resp, err := s.Do(r) resp.Header.Set("context-decorator", "true") return resp, err }) } } golang-github-azure-go-autorest-14.1.1/autorest/date/000077500000000000000000000000001367372352400225175ustar00rootroot00000000000000golang-github-azure-go-autorest-14.1.1/autorest/date/date.go000066400000000000000000000060061367372352400237650ustar00rootroot00000000000000/* Package date provides time.Time derivatives that conform to the Swagger.io (https://swagger.io/) defined date formats: Date and DateTime. Both types may, in most cases, be used in lieu of time.Time types. And both convert to time.Time through a ToTime method. */ package date // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "fmt" "time" ) const ( fullDate = "2006-01-02" fullDateJSON = `"2006-01-02"` dateFormat = "%04d-%02d-%02d" jsonFormat = `"%04d-%02d-%02d"` ) // Date defines a type similar to time.Time but assumes a layout of RFC3339 full-date (i.e., // 2006-01-02). type Date struct { time.Time } // ParseDate create a new Date from the passed string. func ParseDate(date string) (d Date, err error) { return parseDate(date, fullDate) } func parseDate(date string, format string) (Date, error) { d, err := time.Parse(format, date) return Date{Time: d}, err } // MarshalBinary preserves the Date as a byte array conforming to RFC3339 full-date (i.e., // 2006-01-02). func (d Date) MarshalBinary() ([]byte, error) { return d.MarshalText() } // UnmarshalBinary reconstitutes a Date saved as a byte array conforming to RFC3339 full-date (i.e., // 2006-01-02). func (d *Date) UnmarshalBinary(data []byte) error { return d.UnmarshalText(data) } // MarshalJSON preserves the Date as a JSON string conforming to RFC3339 full-date (i.e., // 2006-01-02). func (d Date) MarshalJSON() (json []byte, err error) { return []byte(fmt.Sprintf(jsonFormat, d.Year(), d.Month(), d.Day())), nil } // UnmarshalJSON reconstitutes the Date from a JSON string conforming to RFC3339 full-date (i.e., // 2006-01-02). func (d *Date) UnmarshalJSON(data []byte) (err error) { d.Time, err = time.Parse(fullDateJSON, string(data)) return err } // MarshalText preserves the Date as a byte array conforming to RFC3339 full-date (i.e., // 2006-01-02). func (d Date) MarshalText() (text []byte, err error) { return []byte(fmt.Sprintf(dateFormat, d.Year(), d.Month(), d.Day())), nil } // UnmarshalText reconstitutes a Date saved as a byte array conforming to RFC3339 full-date (i.e., // 2006-01-02). func (d *Date) UnmarshalText(data []byte) (err error) { d.Time, err = time.Parse(fullDate, string(data)) return err } // String returns the Date formatted as an RFC3339 full-date string (i.e., 2006-01-02). func (d Date) String() string { return fmt.Sprintf(dateFormat, d.Year(), d.Month(), d.Day()) } // ToTime returns a Date as a time.Time func (d Date) ToTime() time.Time { return d.Time } golang-github-azure-go-autorest-14.1.1/autorest/date/date_test.go000066400000000000000000000115311367372352400250230ustar00rootroot00000000000000package date // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "encoding/json" "fmt" "reflect" "testing" "time" ) func ExampleParseDate() { d, err := ParseDate("2001-02-03") if err != nil { fmt.Println(err) } fmt.Println(d) // Output: 2001-02-03 } func ExampleDate() { d, err := ParseDate("2001-02-03") if err != nil { fmt.Println(err) } t, err := time.Parse(time.RFC3339, "2001-02-04T00:00:00Z") if err != nil { fmt.Println(err) } // Date acts as time.Time when the receiver if d.Before(t) { fmt.Printf("Before ") } else { fmt.Printf("After ") } // Convert Date when needing a time.Time if t.After(d.ToTime()) { fmt.Printf("After") } else { fmt.Printf("Before") } // Output: Before After } func ExampleDate_MarshalBinary() { d, err := ParseDate("2001-02-03") if err != nil { fmt.Println(err) } t, err := d.MarshalBinary() if err != nil { fmt.Println(err) } fmt.Println(string(t)) // Output: 2001-02-03 } func ExampleDate_UnmarshalBinary() { d := Date{} t := "2001-02-03" if err := d.UnmarshalBinary([]byte(t)); err != nil { fmt.Println(err) } fmt.Println(d) // Output: 2001-02-03 } func ExampleDate_MarshalJSON() { d, err := ParseDate("2001-02-03") if err != nil { fmt.Println(err) } j, err := json.Marshal(d) if err != nil { fmt.Println(err) } fmt.Println(string(j)) // Output: "2001-02-03" } func ExampleDate_UnmarshalJSON() { var d struct { Date Date `json:"date"` } j := `{"date" : "2001-02-03"}` if err := json.Unmarshal([]byte(j), &d); err != nil { fmt.Println(err) } fmt.Println(d.Date) // Output: 2001-02-03 } func ExampleDate_MarshalText() { d, err := ParseDate("2001-02-03") if err != nil { fmt.Println(err) } t, err := d.MarshalText() if err != nil { fmt.Println(err) } fmt.Println(string(t)) // Output: 2001-02-03 } func ExampleDate_UnmarshalText() { d := Date{} t := "2001-02-03" if err := d.UnmarshalText([]byte(t)); err != nil { fmt.Println(err) } fmt.Println(d) // Output: 2001-02-03 } func TestDateString(t *testing.T) { d, err := ParseDate("2001-02-03") if err != nil { t.Fatalf("date: String failed (%v)", err) } if d.String() != "2001-02-03" { t.Fatalf("date: String failed (%v)", d.String()) } } func TestDateBinaryRoundTrip(t *testing.T) { d1, err := ParseDate("2001-02-03") if err != nil { t.Fatalf("date: ParseDate failed (%v)", err) } t1, err := d1.MarshalBinary() if err != nil { t.Fatalf("date: MarshalBinary failed (%v)", err) } d2 := Date{} if err = d2.UnmarshalBinary(t1); err != nil { t.Fatalf("date: UnmarshalBinary failed (%v)", err) } if !reflect.DeepEqual(d1, d2) { t.Fatalf("date: Round-trip Binary failed (%v, %v)", d1, d2) } } func TestDateJSONRoundTrip(t *testing.T) { type s struct { Date Date `json:"date"` } var err error d1 := s{} d1.Date, err = ParseDate("2001-02-03") if err != nil { t.Fatalf("date: ParseDate failed (%v)", err) } j, err := json.Marshal(d1) if err != nil { t.Fatalf("date: MarshalJSON failed (%v)", err) } d2 := s{} if err = json.Unmarshal(j, &d2); err != nil { t.Fatalf("date: UnmarshalJSON failed (%v)", err) } if !reflect.DeepEqual(d1, d2) { t.Fatalf("date: Round-trip JSON failed (%v, %v)", d1, d2) } } func TestDateTextRoundTrip(t *testing.T) { d1, err := ParseDate("2001-02-03") if err != nil { t.Fatalf("date: ParseDate failed (%v)", err) } t1, err := d1.MarshalText() if err != nil { t.Fatalf("date: MarshalText failed (%v)", err) } d2 := Date{} if err = d2.UnmarshalText(t1); err != nil { t.Fatalf("date: UnmarshalText failed (%v)", err) } if !reflect.DeepEqual(d1, d2) { t.Fatalf("date: Round-trip Text failed (%v, %v)", d1, d2) } } func TestDateToTime(t *testing.T) { var d Date d, err := ParseDate("2001-02-03") if err != nil { t.Fatalf("date: ParseDate failed (%v)", err) } var _ time.Time = d.ToTime() } func TestDateUnmarshalJSONReturnsError(t *testing.T) { var d struct { Date Date `json:"date"` } j := `{"date" : "February 3, 2001"}` if err := json.Unmarshal([]byte(j), &d); err == nil { t.Fatal("date: Date failed to return error for malformed JSON date") } } func TestDateUnmarshalTextReturnsError(t *testing.T) { d := Date{} txt := "February 3, 2001" if err := d.UnmarshalText([]byte(txt)); err == nil { t.Fatal("date: Date failed to return error for malformed Text date") } } golang-github-azure-go-autorest-14.1.1/autorest/date/go.mod000066400000000000000000000001611367372352400236230ustar00rootroot00000000000000module github.com/Azure/go-autorest/autorest/date go 1.12 require github.com/Azure/go-autorest/autorest v0.9.0 golang-github-azure-go-autorest-14.1.1/autorest/date/go.sum000066400000000000000000000031101367372352400236450ustar00rootroot00000000000000github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0 h1:Ww5g4zThfD/6cLb4z6xxgeyDa7QDkizMkJKe0ysZXp0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang-github-azure-go-autorest-14.1.1/autorest/date/go_mod_tidy_hack.go000066400000000000000000000017161367372352400263360ustar00rootroot00000000000000// +build modhack package date // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // This file, and the github.com/Azure/go-autorest/autorest import, won't actually become part of // the resultant binary. // Necessary for safely adding multi-module repo. // See: https://github.com/golang/go/wiki/Modules#is-it-possible-to-add-a-module-to-a-multi-module-repository import _ "github.com/Azure/go-autorest/autorest" golang-github-azure-go-autorest-14.1.1/autorest/date/time.go000066400000000000000000000061571367372352400240150ustar00rootroot00000000000000package date // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "regexp" "time" ) // Azure reports time in UTC but it doesn't include the 'Z' time zone suffix in some cases. const ( azureUtcFormatJSON = `"2006-01-02T15:04:05.999999999"` azureUtcFormat = "2006-01-02T15:04:05.999999999" rfc3339JSON = `"` + time.RFC3339Nano + `"` rfc3339 = time.RFC3339Nano tzOffsetRegex = `(Z|z|\+|-)(\d+:\d+)*"*$` ) // Time defines a type similar to time.Time but assumes a layout of RFC3339 date-time (i.e., // 2006-01-02T15:04:05Z). type Time struct { time.Time } // MarshalBinary preserves the Time as a byte array conforming to RFC3339 date-time (i.e., // 2006-01-02T15:04:05Z). func (t Time) MarshalBinary() ([]byte, error) { return t.Time.MarshalText() } // UnmarshalBinary reconstitutes a Time saved as a byte array conforming to RFC3339 date-time // (i.e., 2006-01-02T15:04:05Z). func (t *Time) UnmarshalBinary(data []byte) error { return t.UnmarshalText(data) } // MarshalJSON preserves the Time as a JSON string conforming to RFC3339 date-time (i.e., // 2006-01-02T15:04:05Z). func (t Time) MarshalJSON() (json []byte, err error) { return t.Time.MarshalJSON() } // UnmarshalJSON reconstitutes the Time from a JSON string conforming to RFC3339 date-time // (i.e., 2006-01-02T15:04:05Z). func (t *Time) UnmarshalJSON(data []byte) (err error) { timeFormat := azureUtcFormatJSON match, err := regexp.Match(tzOffsetRegex, data) if err != nil { return err } else if match { timeFormat = rfc3339JSON } t.Time, err = ParseTime(timeFormat, string(data)) return err } // MarshalText preserves the Time as a byte array conforming to RFC3339 date-time (i.e., // 2006-01-02T15:04:05Z). func (t Time) MarshalText() (text []byte, err error) { return t.Time.MarshalText() } // UnmarshalText reconstitutes a Time saved as a byte array conforming to RFC3339 date-time // (i.e., 2006-01-02T15:04:05Z). func (t *Time) UnmarshalText(data []byte) (err error) { timeFormat := azureUtcFormat match, err := regexp.Match(tzOffsetRegex, data) if err != nil { return err } else if match { timeFormat = rfc3339 } t.Time, err = ParseTime(timeFormat, string(data)) return err } // String returns the Time formatted as an RFC3339 date-time string (i.e., // 2006-01-02T15:04:05Z). func (t Time) String() string { // Note: time.Time.String does not return an RFC3339 compliant string, time.Time.MarshalText does. b, err := t.MarshalText() if err != nil { return "" } return string(b) } // ToTime returns a Time as a time.Time func (t Time) ToTime() time.Time { return t.Time } golang-github-azure-go-autorest-14.1.1/autorest/date/time_test.go000066400000000000000000000144601367372352400250500ustar00rootroot00000000000000package date // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "encoding/json" "fmt" "reflect" "testing" "time" ) func ExampleParseTime() { d, _ := ParseTime(rfc3339, "2001-02-03T04:05:06Z") fmt.Println(d) // Output: 2001-02-03 04:05:06 +0000 UTC } func ExampleTime_MarshalBinary() { ti, err := ParseTime(rfc3339, "2001-02-03T04:05:06Z") if err != nil { fmt.Println(err) } d := Time{ti} t, err := d.MarshalBinary() if err != nil { fmt.Println(err) } fmt.Println(string(t)) // Output: 2001-02-03T04:05:06Z } func ExampleTime_UnmarshalBinary() { d := Time{} t := "2001-02-03T04:05:06Z" if err := d.UnmarshalBinary([]byte(t)); err != nil { fmt.Println(err) } fmt.Println(d) // Output: 2001-02-03T04:05:06Z } func ExampleTime_MarshalJSON() { d, err := ParseTime(rfc3339, "2001-02-03T04:05:06Z") if err != nil { fmt.Println(err) } j, err := json.Marshal(d) if err != nil { fmt.Println(err) } fmt.Println(string(j)) // Output: "2001-02-03T04:05:06Z" } func ExampleTime_UnmarshalJSON() { var d struct { Time Time `json:"datetime"` } j := `{"datetime" : "2001-02-03T04:05:06Z"}` if err := json.Unmarshal([]byte(j), &d); err != nil { fmt.Println(err) } fmt.Println(d.Time) // Output: 2001-02-03T04:05:06Z } func ExampleTime_MarshalText() { d, err := ParseTime(rfc3339, "2001-02-03T04:05:06Z") if err != nil { fmt.Println(err) } t, err := d.MarshalText() if err != nil { fmt.Println(err) } fmt.Println(string(t)) // Output: 2001-02-03T04:05:06Z } func ExampleTime_UnmarshalText() { d := Time{} t := "2001-02-03T04:05:06Z" if err := d.UnmarshalText([]byte(t)); err != nil { fmt.Println(err) } fmt.Println(d) // Output: 2001-02-03T04:05:06Z } func TestUnmarshalTextforInvalidDate(t *testing.T) { d := Time{} dt := "2001-02-03T04:05:06AAA" if err := d.UnmarshalText([]byte(dt)); err == nil { t.Fatalf("date: Time#Unmarshal was expecting error for invalid date") } } func TestUnmarshalJSONforInvalidDate(t *testing.T) { d := Time{} dt := `"2001-02-03T04:05:06AAA"` if err := d.UnmarshalJSON([]byte(dt)); err == nil { t.Fatalf("date: Time#Unmarshal was expecting error for invalid date") } } func TestTimeString(t *testing.T) { ti, err := ParseTime(rfc3339, "2001-02-03T04:05:06Z") if err != nil { fmt.Println(err) } d := Time{ti} if d.String() != "2001-02-03T04:05:06Z" { t.Fatalf("date: Time#String failed (%v)", d.String()) } } func TestTimeStringReturnsEmptyStringForError(t *testing.T) { d := Time{Time: time.Date(20000, 01, 01, 01, 01, 01, 01, time.UTC)} if d.String() != "" { t.Fatalf("date: Time#String failed empty string for an error") } } func TestTimeBinaryRoundTrip(t *testing.T) { ti, err := ParseTime(rfc3339, "2001-02-03T04:05:06Z") if err != nil { t.Fatalf("date: Time#ParseTime failed (%v)", err) } d1 := Time{ti} t1, err := d1.MarshalBinary() if err != nil { t.Fatalf("date: Time#MarshalBinary failed (%v)", err) } d2 := Time{} if err = d2.UnmarshalBinary(t1); err != nil { t.Fatalf("date: Time#UnmarshalBinary failed (%v)", err) } if !reflect.DeepEqual(d1, d2) { t.Fatalf("date:Round-trip Binary failed (%v, %v)", d1, d2) } } func TestTimeJSONRoundTrip(t *testing.T) { type s struct { Time Time `json:"datetime"` } ti, err := ParseTime(rfc3339, "2001-02-03T04:05:06Z") if err != nil { t.Fatalf("date: Time#ParseTime failed (%v)", err) } d1 := s{Time: Time{ti}} j, err := json.Marshal(d1) if err != nil { t.Fatalf("date: Time#MarshalJSON failed (%v)", err) } d2 := s{} if err = json.Unmarshal(j, &d2); err != nil { t.Fatalf("date: Time#UnmarshalJSON failed (%v)", err) } if !reflect.DeepEqual(d1, d2) { t.Fatalf("date: Round-trip JSON failed (%v, %v)", d1, d2) } } func TestTimeTextRoundTrip(t *testing.T) { ti, err := ParseTime(rfc3339, "2001-02-03T04:05:06Z") if err != nil { t.Fatalf("date: Time#ParseTime failed (%v)", err) } d1 := Time{Time: ti} t1, err := d1.MarshalText() if err != nil { t.Fatalf("date: Time#MarshalText failed (%v)", err) } d2 := Time{} if err = d2.UnmarshalText(t1); err != nil { t.Fatalf("date: Time#UnmarshalText failed (%v)", err) } if !reflect.DeepEqual(d1, d2) { t.Fatalf("date: Round-trip Text failed (%v, %v)", d1, d2) } } func TestTimeToTime(t *testing.T) { ti, err := ParseTime(rfc3339, "2001-02-03T04:05:06Z") d := Time{ti} if err != nil { t.Fatalf("date: Time#ParseTime failed (%v)", err) } var _ time.Time = d.ToTime() } func TestUnmarshalJSONNoOffset(t *testing.T) { var d struct { Time Time `json:"datetime"` } j := `{"datetime" : "2001-02-03T04:05:06.789"}` if err := json.Unmarshal([]byte(j), &d); err != nil { t.Fatalf("date: Time#Unmarshal failed (%v)", err) } } func TestUnmarshalJSONPosOffset(t *testing.T) { var d struct { Time Time `json:"datetime"` } j := `{"datetime" : "1980-01-02T00:11:35.01+01:00"}` if err := json.Unmarshal([]byte(j), &d); err != nil { t.Fatalf("date: Time#Unmarshal failed (%v)", err) } } func TestUnmarshalJSONNegOffset(t *testing.T) { var d struct { Time Time `json:"datetime"` } j := `{"datetime" : "1492-10-12T10:15:01.789-08:00"}` if err := json.Unmarshal([]byte(j), &d); err != nil { t.Fatalf("date: Time#Unmarshal failed (%v)", err) } } func TestUnmarshalTextNoOffset(t *testing.T) { d := Time{} t1 := "2001-02-03T04:05:06" if err := d.UnmarshalText([]byte(t1)); err != nil { t.Fatalf("date: Time#UnmarshalText failed (%v)", err) } } func TestUnmarshalTextPosOffset(t *testing.T) { d := Time{} t1 := "2001-02-03T04:05:06+00:30" if err := d.UnmarshalText([]byte(t1)); err != nil { t.Fatalf("date: Time#UnmarshalText failed (%v)", err) } } func TestUnmarshalTextNegOffset(t *testing.T) { d := Time{} t1 := "2001-02-03T04:05:06-11:00" if err := d.UnmarshalText([]byte(t1)); err != nil { t.Fatalf("date: Time#UnmarshalText failed (%v)", err) } } golang-github-azure-go-autorest-14.1.1/autorest/date/timerfc1123.go000066400000000000000000000057651367372352400250230ustar00rootroot00000000000000package date // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "errors" "time" ) const ( rfc1123JSON = `"` + time.RFC1123 + `"` rfc1123 = time.RFC1123 ) // TimeRFC1123 defines a type similar to time.Time but assumes a layout of RFC1123 date-time (i.e., // Mon, 02 Jan 2006 15:04:05 MST). type TimeRFC1123 struct { time.Time } // UnmarshalJSON reconstitutes the Time from a JSON string conforming to RFC1123 date-time // (i.e., Mon, 02 Jan 2006 15:04:05 MST). func (t *TimeRFC1123) UnmarshalJSON(data []byte) (err error) { t.Time, err = ParseTime(rfc1123JSON, string(data)) if err != nil { return err } return nil } // MarshalJSON preserves the Time as a JSON string conforming to RFC1123 date-time (i.e., // Mon, 02 Jan 2006 15:04:05 MST). func (t TimeRFC1123) MarshalJSON() ([]byte, error) { if y := t.Year(); y < 0 || y >= 10000 { return nil, errors.New("Time.MarshalJSON: year outside of range [0,9999]") } b := []byte(t.Format(rfc1123JSON)) return b, nil } // MarshalText preserves the Time as a byte array conforming to RFC1123 date-time (i.e., // Mon, 02 Jan 2006 15:04:05 MST). func (t TimeRFC1123) MarshalText() ([]byte, error) { if y := t.Year(); y < 0 || y >= 10000 { return nil, errors.New("Time.MarshalText: year outside of range [0,9999]") } b := []byte(t.Format(rfc1123)) return b, nil } // UnmarshalText reconstitutes a Time saved as a byte array conforming to RFC1123 date-time // (i.e., Mon, 02 Jan 2006 15:04:05 MST). func (t *TimeRFC1123) UnmarshalText(data []byte) (err error) { t.Time, err = ParseTime(rfc1123, string(data)) if err != nil { return err } return nil } // MarshalBinary preserves the Time as a byte array conforming to RFC1123 date-time (i.e., // Mon, 02 Jan 2006 15:04:05 MST). func (t TimeRFC1123) MarshalBinary() ([]byte, error) { return t.MarshalText() } // UnmarshalBinary reconstitutes a Time saved as a byte array conforming to RFC1123 date-time // (i.e., Mon, 02 Jan 2006 15:04:05 MST). func (t *TimeRFC1123) UnmarshalBinary(data []byte) error { return t.UnmarshalText(data) } // ToTime returns a Time as a time.Time func (t TimeRFC1123) ToTime() time.Time { return t.Time } // String returns the Time formatted as an RFC1123 date-time string (i.e., // Mon, 02 Jan 2006 15:04:05 MST). func (t TimeRFC1123) String() string { // Note: time.Time.String does not return an RFC1123 compliant string, time.Time.MarshalText does. b, err := t.MarshalText() if err != nil { return "" } return string(b) } golang-github-azure-go-autorest-14.1.1/autorest/date/timerfc1123_test.go000066400000000000000000000132741367372352400260540ustar00rootroot00000000000000package date // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "encoding/json" "fmt" "reflect" "testing" "time" ) func ExampleTimeRFC1123() { d, err := ParseTime(rfc1123, "Mon, 02 Jan 2006 15:04:05 MST") if err != nil { fmt.Println(err) } fmt.Println(d) // Output: 2006-01-02 15:04:05 +0000 MST } func ExampleTimeRFC1123_MarshalBinary() { ti, err := ParseTime(rfc1123, "Mon, 02 Jan 2006 15:04:05 MST") if err != nil { fmt.Println(err) } d := TimeRFC1123{ti} b, err := d.MarshalBinary() if err != nil { fmt.Println(err) } fmt.Println(string(b)) // Output: Mon, 02 Jan 2006 15:04:05 MST } func ExampleTimeRFC1123_UnmarshalBinary() { d := TimeRFC1123{} t := "Mon, 02 Jan 2006 15:04:05 MST" if err := d.UnmarshalBinary([]byte(t)); err != nil { fmt.Println(err) } fmt.Println(d) // Output: Mon, 02 Jan 2006 15:04:05 MST } func ExampleTimeRFC1123_MarshalJSON() { ti, err := ParseTime(rfc1123, "Mon, 02 Jan 2006 15:04:05 MST") if err != nil { fmt.Println(err) } d := TimeRFC1123{ti} j, err := json.Marshal(d) if err != nil { fmt.Println(err) } fmt.Println(string(j)) // Output: "Mon, 02 Jan 2006 15:04:05 MST" } func TestTimeRFC1123MarshalJSONInvalid(t *testing.T) { ti := time.Date(20000, 01, 01, 00, 00, 00, 00, time.UTC) d := TimeRFC1123{ti} if _, err := json.Marshal(d); err == nil { t.Fatalf("date: TimeRFC1123#Marshal failed for invalid date") } } func ExampleTimeRFC1123_UnmarshalJSON() { var d struct { Time TimeRFC1123 `json:"datetime"` } j := `{"datetime" : "Mon, 02 Jan 2006 15:04:05 MST"}` if err := json.Unmarshal([]byte(j), &d); err != nil { fmt.Println(err) } fmt.Println(d.Time) // Output: Mon, 02 Jan 2006 15:04:05 MST } func ExampleTimeRFC1123_MarshalText() { ti, err := ParseTime(rfc3339, "2001-02-03T04:05:06Z") if err != nil { fmt.Println(err) } d := TimeRFC1123{ti} t, err := d.MarshalText() if err != nil { fmt.Println(err) } fmt.Println(string(t)) // Output: Sat, 03 Feb 2001 04:05:06 UTC } func ExampleTimeRFC1123_UnmarshalText() { d := TimeRFC1123{} t := "Sat, 03 Feb 2001 04:05:06 UTC" if err := d.UnmarshalText([]byte(t)); err != nil { fmt.Println(err) } fmt.Println(d) // Output: Sat, 03 Feb 2001 04:05:06 UTC } func TestUnmarshalJSONforInvalidDateRfc1123(t *testing.T) { dt := `"Mon, 02 Jan 2000000 15:05 MST"` d := TimeRFC1123{} if err := d.UnmarshalJSON([]byte(dt)); err == nil { t.Fatalf("date: TimeRFC1123#Unmarshal failed for invalid date") } } func TestUnmarshalTextforInvalidDateRfc1123(t *testing.T) { dt := "Mon, 02 Jan 2000000 15:05 MST" d := TimeRFC1123{} if err := d.UnmarshalText([]byte(dt)); err == nil { t.Fatalf("date: TimeRFC1123#Unmarshal failed for invalid date") } } func TestTimeStringRfc1123(t *testing.T) { ti, err := ParseTime(rfc1123, "Mon, 02 Jan 2006 15:04:05 MST") if err != nil { fmt.Println(err) } d := TimeRFC1123{ti} if d.String() != "Mon, 02 Jan 2006 15:04:05 MST" { t.Fatalf("date: TimeRFC1123#String failed (%v)", d.String()) } } func TestTimeStringReturnsEmptyStringForErrorRfc1123(t *testing.T) { d := TimeRFC1123{Time: time.Date(20000, 01, 01, 01, 01, 01, 01, time.UTC)} if d.String() != "" { t.Fatalf("date: TimeRFC1123#String failed empty string for an error") } } func TestTimeBinaryRoundTripRfc1123(t *testing.T) { ti, err := ParseTime(rfc3339, "2001-02-03T04:05:06Z") if err != nil { t.Fatalf("date: TimeRFC1123#ParseTime failed (%v)", err) } d1 := TimeRFC1123{ti} t1, err := d1.MarshalBinary() if err != nil { t.Fatalf("date: TimeRFC1123#MarshalBinary failed (%v)", err) } d2 := TimeRFC1123{} if err = d2.UnmarshalBinary(t1); err != nil { t.Fatalf("date: TimeRFC1123#UnmarshalBinary failed (%v)", err) } if !reflect.DeepEqual(d1, d2) { t.Fatalf("date: Round-trip Binary failed (%v, %v)", d1, d2) } } func TestTimeJSONRoundTripRfc1123(t *testing.T) { type s struct { Time TimeRFC1123 `json:"datetime"` } var err error ti, err := ParseTime(rfc1123, "Mon, 02 Jan 2006 15:04:05 MST") if err != nil { t.Fatalf("date: TimeRFC1123#ParseTime failed (%v)", err) } d1 := s{Time: TimeRFC1123{ti}} j, err := json.Marshal(d1) if err != nil { t.Fatalf("date: TimeRFC1123#MarshalJSON failed (%v)", err) } d2 := s{} if err = json.Unmarshal(j, &d2); err != nil { t.Fatalf("date: TimeRFC1123#UnmarshalJSON failed (%v)", err) } if !reflect.DeepEqual(d1, d2) { t.Fatalf("date: Round-trip JSON failed (%v, %v)", d1, d2) } } func TestTimeTextRoundTripRfc1123(t *testing.T) { ti, err := ParseTime(rfc1123, "Mon, 02 Jan 2006 15:04:05 MST") if err != nil { t.Fatalf("date: TimeRFC1123#ParseTime failed (%v)", err) } d1 := TimeRFC1123{Time: ti} t1, err := d1.MarshalText() if err != nil { t.Fatalf("date: TimeRFC1123#MarshalText failed (%v)", err) } d2 := TimeRFC1123{} if err = d2.UnmarshalText(t1); err != nil { t.Fatalf("date: TimeRFC1123#UnmarshalText failed (%v)", err) } if !reflect.DeepEqual(d1, d2) { t.Fatalf("date: Round-trip Text failed (%v, %v)", d1, d2) } } func TestTimeToTimeRFC1123(t *testing.T) { ti, err := ParseTime(rfc1123, "Mon, 02 Jan 2006 15:04:05 MST") d := TimeRFC1123{ti} if err != nil { t.Fatalf("date: TimeRFC1123#ParseTime failed (%v)", err) } var _ time.Time = d.ToTime() } golang-github-azure-go-autorest-14.1.1/autorest/date/unixtime.go000066400000000000000000000076471367372352400247260ustar00rootroot00000000000000package date // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "bytes" "encoding/binary" "encoding/json" "time" ) // unixEpoch is the moment in time that should be treated as timestamp 0. var unixEpoch = time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC) // UnixTime marshals and unmarshals a time that is represented as the number // of seconds (ignoring skip-seconds) since the Unix Epoch. type UnixTime time.Time // Duration returns the time as a Duration since the UnixEpoch. func (t UnixTime) Duration() time.Duration { return time.Time(t).Sub(unixEpoch) } // NewUnixTimeFromSeconds creates a UnixTime as a number of seconds from the UnixEpoch. func NewUnixTimeFromSeconds(seconds float64) UnixTime { return NewUnixTimeFromDuration(time.Duration(seconds * float64(time.Second))) } // NewUnixTimeFromNanoseconds creates a UnixTime as a number of nanoseconds from the UnixEpoch. func NewUnixTimeFromNanoseconds(nanoseconds int64) UnixTime { return NewUnixTimeFromDuration(time.Duration(nanoseconds)) } // NewUnixTimeFromDuration creates a UnixTime as a duration of time since the UnixEpoch. func NewUnixTimeFromDuration(dur time.Duration) UnixTime { return UnixTime(unixEpoch.Add(dur)) } // UnixEpoch retreives the moment considered the Unix Epoch. I.e. The time represented by '0' func UnixEpoch() time.Time { return unixEpoch } // MarshalJSON preserves the UnixTime as a JSON number conforming to Unix Timestamp requirements. // (i.e. the number of seconds since midnight January 1st, 1970 not considering leap seconds.) func (t UnixTime) MarshalJSON() ([]byte, error) { buffer := &bytes.Buffer{} enc := json.NewEncoder(buffer) err := enc.Encode(float64(time.Time(t).UnixNano()) / 1e9) if err != nil { return nil, err } return buffer.Bytes(), nil } // UnmarshalJSON reconstitures a UnixTime saved as a JSON number of the number of seconds since // midnight January 1st, 1970. func (t *UnixTime) UnmarshalJSON(text []byte) error { dec := json.NewDecoder(bytes.NewReader(text)) var secondsSinceEpoch float64 if err := dec.Decode(&secondsSinceEpoch); err != nil { return err } *t = NewUnixTimeFromSeconds(secondsSinceEpoch) return nil } // MarshalText stores the number of seconds since the Unix Epoch as a textual floating point number. func (t UnixTime) MarshalText() ([]byte, error) { cast := time.Time(t) return cast.MarshalText() } // UnmarshalText populates a UnixTime with a value stored textually as a floating point number of seconds since the Unix Epoch. func (t *UnixTime) UnmarshalText(raw []byte) error { var unmarshaled time.Time if err := unmarshaled.UnmarshalText(raw); err != nil { return err } *t = UnixTime(unmarshaled) return nil } // MarshalBinary converts a UnixTime into a binary.LittleEndian float64 of nanoseconds since the epoch. func (t UnixTime) MarshalBinary() ([]byte, error) { buf := &bytes.Buffer{} payload := int64(t.Duration()) if err := binary.Write(buf, binary.LittleEndian, &payload); err != nil { return nil, err } return buf.Bytes(), nil } // UnmarshalBinary converts a from a binary.LittleEndian float64 of nanoseconds since the epoch into a UnixTime. func (t *UnixTime) UnmarshalBinary(raw []byte) error { var nanosecondsSinceEpoch int64 if err := binary.Read(bytes.NewReader(raw), binary.LittleEndian, &nanosecondsSinceEpoch); err != nil { return err } *t = NewUnixTimeFromNanoseconds(nanosecondsSinceEpoch) return nil } golang-github-azure-go-autorest-14.1.1/autorest/date/unixtime_test.go000066400000000000000000000162311367372352400257520ustar00rootroot00000000000000// +build go1.7 package date // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "bytes" "encoding/binary" "encoding/json" "fmt" "math" "testing" "time" ) func ExampleUnixTime_MarshalJSON() { epoch := UnixTime(UnixEpoch()) text, _ := json.Marshal(epoch) fmt.Print(string(text)) // Output: 0 } func ExampleUnixTime_UnmarshalJSON() { var myTime UnixTime json.Unmarshal([]byte("1.3e2"), &myTime) fmt.Printf("%v", time.Time(myTime)) // Output: 1970-01-01 00:02:10 +0000 UTC } func TestUnixTime_MarshalJSON(t *testing.T) { testCases := []time.Time{ UnixEpoch().Add(-1 * time.Second), // One second befote the Unix Epoch time.Date(2017, time.April, 14, 20, 27, 47, 0, time.UTC), // The time this test was written UnixEpoch(), time.Date(1800, 01, 01, 0, 0, 0, 0, time.UTC), time.Date(2200, 12, 29, 00, 01, 37, 82, time.UTC), } for _, tc := range testCases { t.Run(tc.String(), func(subT *testing.T) { var actual, expected float64 var marshaled []byte target := UnixTime(tc) expected = float64(target.Duration().Nanoseconds()) / 1e9 if temp, err := json.Marshal(target); err == nil { marshaled = temp } else { subT.Error(err) return } dec := json.NewDecoder(bytes.NewReader(marshaled)) if err := dec.Decode(&actual); err != nil { subT.Error(err) return } diff := math.Abs(actual - expected) subT.Logf("\ngot :\t%g\nwant:\t%g\ndiff:\t%g", actual, expected, diff) if diff > 1e-9 { //Must be within 1 nanosecond of one another subT.Fail() } }) } } func TestUnixTime_UnmarshalJSON(t *testing.T) { testCases := []struct { text string expected time.Time }{ {"1", UnixEpoch().Add(time.Second)}, {"0", UnixEpoch()}, {"1492203742", time.Date(2017, time.April, 14, 21, 02, 22, 0, time.UTC)}, // The time this test was written {"-1", time.Date(1969, time.December, 31, 23, 59, 59, 0, time.UTC)}, {"1.5", UnixEpoch().Add(1500 * time.Millisecond)}, {"0e1", UnixEpoch()}, // See http://json.org for 'number' format definition. {"1.3e+2", UnixEpoch().Add(130 * time.Second)}, {"1.6E-10", UnixEpoch()}, // This is so small, it should get truncated into the UnixEpoch {"2E-6", UnixEpoch().Add(2 * time.Microsecond)}, {"1.289345e9", UnixEpoch().Add(1289345000 * time.Second)}, {"1e-9", UnixEpoch().Add(time.Nanosecond)}, } for _, tc := range testCases { t.Run(tc.text, func(subT *testing.T) { var rehydrated UnixTime if err := json.Unmarshal([]byte(tc.text), &rehydrated); err != nil { subT.Error(err) return } if time.Time(rehydrated) != tc.expected { subT.Logf("\ngot: \t%v\nwant:\t%v\ndiff:\t%v", time.Time(rehydrated), tc.expected, time.Time(rehydrated).Sub(tc.expected)) subT.Fail() } }) } } func TestUnixTime_JSONRoundTrip(t *testing.T) { testCases := []time.Time{ UnixEpoch(), time.Date(2005, time.November, 5, 0, 0, 0, 0, time.UTC), // The day V for Vendetta (film) was released. UnixEpoch().Add(-6 * time.Second), UnixEpoch().Add(800 * time.Hour), UnixEpoch().Add(time.Nanosecond), time.Date(2015, time.September, 05, 4, 30, 12, 9992, time.UTC), } for _, tc := range testCases { t.Run(tc.String(), func(subT *testing.T) { subject := UnixTime(tc) var marshaled []byte if temp, err := json.Marshal(subject); err == nil { marshaled = temp } else { subT.Error(err) return } var unmarshaled UnixTime if err := json.Unmarshal(marshaled, &unmarshaled); err != nil { subT.Error(err) } actual := time.Time(unmarshaled) diff := actual.Sub(tc) subT.Logf("\ngot :\t%s\nwant:\t%s\ndiff:\t%s", actual.String(), tc.String(), diff.String()) if diff > time.Duration(100) { // We lose some precision be working in floats. We shouldn't lose more than 100 nanoseconds. subT.Fail() } }) } } func TestUnixTime_MarshalBinary(t *testing.T) { testCases := []struct { expected int64 subject time.Time }{ {0, UnixEpoch()}, {-15 * int64(time.Second), UnixEpoch().Add(-15 * time.Second)}, {54, UnixEpoch().Add(54 * time.Nanosecond)}, } for _, tc := range testCases { t.Run("", func(subT *testing.T) { var marshaled []byte if temp, err := UnixTime(tc.subject).MarshalBinary(); err == nil { marshaled = temp } else { subT.Error(err) return } var unmarshaled int64 if err := binary.Read(bytes.NewReader(marshaled), binary.LittleEndian, &unmarshaled); err != nil { subT.Error(err) return } if unmarshaled != tc.expected { subT.Logf("\ngot: \t%d\nwant:\t%d", unmarshaled, tc.expected) subT.Fail() } }) } } func TestUnixTime_BinaryRoundTrip(t *testing.T) { testCases := []time.Time{ UnixEpoch(), UnixEpoch().Add(800 * time.Minute), UnixEpoch().Add(7 * time.Hour), UnixEpoch().Add(-1 * time.Nanosecond), } for _, tc := range testCases { t.Run(tc.String(), func(subT *testing.T) { original := UnixTime(tc) var marshaled []byte if temp, err := original.MarshalBinary(); err == nil { marshaled = temp } else { subT.Error(err) return } var traveled UnixTime if err := traveled.UnmarshalBinary(marshaled); err != nil { subT.Error(err) return } if traveled != original { subT.Logf("\ngot: \t%s\nwant:\t%s", time.Time(original).String(), time.Time(traveled).String()) subT.Fail() } }) } } func TestUnixTime_MarshalText(t *testing.T) { testCases := []time.Time{ UnixEpoch(), UnixEpoch().Add(45 * time.Second), UnixEpoch().Add(time.Nanosecond), UnixEpoch().Add(-100000 * time.Second), } for _, tc := range testCases { expected, _ := tc.MarshalText() t.Run("", func(subT *testing.T) { var marshaled []byte if temp, err := UnixTime(tc).MarshalText(); err == nil { marshaled = temp } else { subT.Error(err) return } if string(marshaled) != string(expected) { subT.Logf("\ngot: \t%s\nwant:\t%s", string(marshaled), string(expected)) subT.Fail() } }) } } func TestUnixTime_TextRoundTrip(t *testing.T) { testCases := []time.Time{ UnixEpoch(), UnixEpoch().Add(-1 * time.Nanosecond), UnixEpoch().Add(1 * time.Nanosecond), time.Date(2017, time.April, 17, 21, 00, 00, 00, time.UTC), } for _, tc := range testCases { t.Run(tc.String(), func(subT *testing.T) { unixTC := UnixTime(tc) var marshaled []byte if temp, err := unixTC.MarshalText(); err == nil { marshaled = temp } else { subT.Error(err) return } var unmarshaled UnixTime if err := unmarshaled.UnmarshalText(marshaled); err != nil { subT.Error(err) return } if unmarshaled != unixTC { t.Logf("\ngot: \t%s\nwant:\t%s", time.Time(unmarshaled).String(), tc.String()) t.Fail() } }) } } golang-github-azure-go-autorest-14.1.1/autorest/date/utility.go000066400000000000000000000014701367372352400245530ustar00rootroot00000000000000package date // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "strings" "time" ) // ParseTime to parse Time string to specified format. func ParseTime(format string, t string) (d time.Time, err error) { return time.Parse(format, strings.ToUpper(t)) } golang-github-azure-go-autorest-14.1.1/autorest/error.go000066400000000000000000000071461367372352400232720ustar00rootroot00000000000000package autorest // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "fmt" "net/http" ) const ( // UndefinedStatusCode is used when HTTP status code is not available for an error. UndefinedStatusCode = 0 ) // DetailedError encloses a error with details of the package, method, and associated HTTP // status code (if any). type DetailedError struct { Original error // PackageType is the package type of the object emitting the error. For types, the value // matches that produced the the '%T' format specifier of the fmt package. For other elements, // such as functions, it is just the package name (e.g., "autorest"). PackageType string // Method is the name of the method raising the error. Method string // StatusCode is the HTTP Response StatusCode (if non-zero) that led to the error. StatusCode interface{} // Message is the error message. Message string // Service Error is the response body of failed API in bytes ServiceError []byte // Response is the response object that was returned during failure if applicable. Response *http.Response } // NewError creates a new Error conforming object from the passed packageType, method, and // message. message is treated as a format string to which the optional args apply. func NewError(packageType string, method string, message string, args ...interface{}) DetailedError { return NewErrorWithError(nil, packageType, method, nil, message, args...) } // NewErrorWithResponse creates a new Error conforming object from the passed // packageType, method, statusCode of the given resp (UndefinedStatusCode if // resp is nil), and message. message is treated as a format string to which the // optional args apply. func NewErrorWithResponse(packageType string, method string, resp *http.Response, message string, args ...interface{}) DetailedError { return NewErrorWithError(nil, packageType, method, resp, message, args...) } // NewErrorWithError creates a new Error conforming object from the // passed packageType, method, statusCode of the given resp (UndefinedStatusCode // if resp is nil), message, and original error. message is treated as a format // string to which the optional args apply. func NewErrorWithError(original error, packageType string, method string, resp *http.Response, message string, args ...interface{}) DetailedError { if v, ok := original.(DetailedError); ok { return v } statusCode := UndefinedStatusCode if resp != nil { statusCode = resp.StatusCode } return DetailedError{ Original: original, PackageType: packageType, Method: method, StatusCode: statusCode, Message: fmt.Sprintf(message, args...), Response: resp, } } // Error returns a formatted containing all available details (i.e., PackageType, Method, // StatusCode, Message, and original error (if any)). func (e DetailedError) Error() string { if e.Original == nil { return fmt.Sprintf("%s#%s: %s: StatusCode=%d", e.PackageType, e.Method, e.Message, e.StatusCode) } return fmt.Sprintf("%s#%s: %s: StatusCode=%d -- Original Error: %v", e.PackageType, e.Method, e.Message, e.StatusCode, e.Original) } golang-github-azure-go-autorest-14.1.1/autorest/error_test.go000066400000000000000000000165421367372352400243310ustar00rootroot00000000000000package autorest // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "fmt" "net/http" "reflect" "regexp" "testing" ) func TestNewErrorWithError_AssignsPackageType(t *testing.T) { e := NewErrorWithError(fmt.Errorf("original"), "packageType", "method", nil, "message") if e.PackageType != "packageType" { t.Fatalf("autorest: Error failed to set package type -- expected %v, received %v", "packageType", e.PackageType) } } func TestNewErrorWithError_AssignsMethod(t *testing.T) { e := NewErrorWithError(fmt.Errorf("original"), "packageType", "method", nil, "message") if e.Method != "method" { t.Fatalf("autorest: Error failed to set method -- expected %v, received %v", "method", e.Method) } } func TestNewErrorWithError_AssignsMessage(t *testing.T) { e := NewErrorWithError(fmt.Errorf("original"), "packageType", "method", nil, "message") if e.Message != "message" { t.Fatalf("autorest: Error failed to set message -- expected %v, received %v", "message", e.Message) } } func TestNewErrorWithError_AssignsUndefinedStatusCodeIfRespNil(t *testing.T) { e := NewErrorWithError(nil, "packageType", "method", nil, "message") if e.StatusCode != UndefinedStatusCode { t.Fatalf("autorest: Error failed to set status code -- expected %v, received %v", UndefinedStatusCode, e.StatusCode) } } func TestNewErrorWithError_AssignsStatusCode(t *testing.T) { e := NewErrorWithError(fmt.Errorf("original"), "packageType", "method", &http.Response{ StatusCode: http.StatusBadRequest, Status: http.StatusText(http.StatusBadRequest)}, "message") if e.StatusCode != http.StatusBadRequest { t.Fatalf("autorest: Error failed to set status code -- expected %v, received %v", http.StatusBadRequest, e.StatusCode) } } func TestNewErrorWithError_AcceptsArgs(t *testing.T) { e := NewErrorWithError(fmt.Errorf("original"), "packageType", "method", nil, "message %s", "arg") if matched, _ := regexp.MatchString(`.*arg.*`, e.Message); !matched { t.Fatalf("autorest: Error failed to apply message arguments -- expected %v, received %v", `.*arg.*`, e.Message) } } func TestNewErrorWithError_AssignsError(t *testing.T) { err := fmt.Errorf("original") e := NewErrorWithError(err, "packageType", "method", nil, "message") if e.Original != err { t.Fatalf("autorest: Error failed to set error -- expected %v, received %v", err, e.Original) } } func TestNewErrorWithResponse_ContainsStatusCode(t *testing.T) { e := NewErrorWithResponse("packageType", "method", &http.Response{ StatusCode: http.StatusBadRequest, Status: http.StatusText(http.StatusBadRequest)}, "message") if e.StatusCode != http.StatusBadRequest { t.Fatalf("autorest: Error failed to set status code -- expected %v, received %v", http.StatusBadRequest, e.StatusCode) } } func TestNewErrorWithResponse_nilResponse_ReportsUndefinedStatusCode(t *testing.T) { e := NewErrorWithResponse("packageType", "method", nil, "message") if e.StatusCode != UndefinedStatusCode { t.Fatalf("autorest: Error failed to set status code -- expected %v, received %v", UndefinedStatusCode, e.StatusCode) } } func TestNewErrorWithResponse_Forwards(t *testing.T) { e1 := NewError("packageType", "method", "message %s", "arg") e2 := NewErrorWithResponse("packageType", "method", nil, "message %s", "arg") if !reflect.DeepEqual(e1, e2) { t.Fatal("autorest: NewError did not return an error equivalent to NewErrorWithError") } } func TestNewErrorWithError_Forwards(t *testing.T) { e1 := NewError("packageType", "method", "message %s", "arg") e2 := NewErrorWithError(nil, "packageType", "method", nil, "message %s", "arg") if !reflect.DeepEqual(e1, e2) { t.Fatal("autorest: NewError did not return an error equivalent to NewErrorWithError") } } func TestNewErrorWithError_DoesNotWrapADetailedError(t *testing.T) { e1 := NewError("packageType1", "method1", "message1 %s", "arg1") e2 := NewErrorWithError(e1, "packageType2", "method2", nil, "message2 %s", "arg2") if !reflect.DeepEqual(e1, e2) { t.Fatalf("autorest: NewErrorWithError incorrectly wrapped a DetailedError -- expected %v, received %v", e1, e2) } } func TestNewErrorWithError_WrapsAnError(t *testing.T) { e1 := fmt.Errorf("Inner Error") var e2 interface{} = NewErrorWithError(e1, "packageType", "method", nil, "message") if _, ok := e2.(DetailedError); !ok { t.Fatalf("autorest: NewErrorWithError failed to wrap a standard error -- received %T", e2) } } func TestDetailedError(t *testing.T) { err := fmt.Errorf("original") e := NewErrorWithError(err, "packageType", "method", nil, "message") if matched, _ := regexp.MatchString(`.*original.*`, e.Error()); !matched { t.Fatalf("autorest: Error#Error failed to return original error message -- expected %v, received %v", `.*original.*`, e.Error()) } } func TestDetailedErrorConstainsPackageType(t *testing.T) { e := NewErrorWithError(fmt.Errorf("original"), "packageType", "method", nil, "message") if matched, _ := regexp.MatchString(`.*packageType.*`, e.Error()); !matched { t.Fatalf("autorest: Error#String failed to include PackageType -- expected %v, received %v", `.*packageType.*`, e.Error()) } } func TestDetailedErrorConstainsMethod(t *testing.T) { e := NewErrorWithError(fmt.Errorf("original"), "packageType", "method", nil, "message") if matched, _ := regexp.MatchString(`.*method.*`, e.Error()); !matched { t.Fatalf("autorest: Error#String failed to include Method -- expected %v, received %v", `.*method.*`, e.Error()) } } func TestDetailedErrorConstainsMessage(t *testing.T) { e := NewErrorWithError(fmt.Errorf("original"), "packageType", "method", nil, "message") if matched, _ := regexp.MatchString(`.*message.*`, e.Error()); !matched { t.Fatalf("autorest: Error#String failed to include Message -- expected %v, received %v", `.*message.*`, e.Error()) } } func TestDetailedErrorConstainsStatusCode(t *testing.T) { e := NewErrorWithError(fmt.Errorf("original"), "packageType", "method", &http.Response{ StatusCode: http.StatusBadRequest, Status: http.StatusText(http.StatusBadRequest)}, "message") if matched, _ := regexp.MatchString(`.*400.*`, e.Error()); !matched { t.Fatalf("autorest: Error#String failed to include Status Code -- expected %v, received %v", `.*400.*`, e.Error()) } } func TestDetailedErrorConstainsOriginal(t *testing.T) { e := NewErrorWithError(fmt.Errorf("original"), "packageType", "method", nil, "message") if matched, _ := regexp.MatchString(`.*original.*`, e.Error()); !matched { t.Fatalf("autorest: Error#String failed to include Original error -- expected %v, received %v", `.*original.*`, e.Error()) } } func TestDetailedErrorSkipsOriginal(t *testing.T) { e := NewError("packageType", "method", "message") if matched, _ := regexp.MatchString(`.*Original.*`, e.Error()); matched { t.Fatalf("autorest: Error#String included missing Original error -- unexpected %v, received %v", `.*Original.*`, e.Error()) } } golang-github-azure-go-autorest-14.1.1/autorest/go.mod000066400000000000000000000004731367372352400227140ustar00rootroot00000000000000module github.com/Azure/go-autorest/autorest go 1.12 require ( github.com/Azure/go-autorest/autorest/adal v0.8.2 github.com/Azure/go-autorest/autorest/mocks v0.3.0 github.com/Azure/go-autorest/logger v0.1.0 github.com/Azure/go-autorest/tracing v0.5.0 golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 ) golang-github-azure-go-autorest-14.1.1/autorest/go.sum000066400000000000000000000057311367372352400227430ustar00rootroot00000000000000github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.8.2 h1:O1X4oexUxnZCaEUGsvMnr8ZGj8HI37tNezwY4npRqA0= github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSWlm5Ew6bxipnr/tbM= github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= github.com/Azure/go-autorest/autorest/mocks v0.1.0 h1:Kx+AUU2Te+A3JIyYn6Dfs+cFgx5XorQKuIXrZGoq/SI= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0 h1:Ww5g4zThfD/6cLb4z6xxgeyDa7QDkizMkJKe0ysZXp0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.3.0 h1:qJumjCaCudz+OcqE9/XtEPfvtOjOmKaui4EOpFI6zZc= github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang-github-azure-go-autorest-14.1.1/autorest/mocks/000077500000000000000000000000001367372352400227165ustar00rootroot00000000000000golang-github-azure-go-autorest-14.1.1/autorest/mocks/go.mod000066400000000000000000000001621367372352400240230ustar00rootroot00000000000000module github.com/Azure/go-autorest/autorest/mocks go 1.12 require github.com/Azure/go-autorest/autorest v0.9.0 golang-github-azure-go-autorest-14.1.1/autorest/mocks/go.sum000066400000000000000000000031071367372352400240520ustar00rootroot00000000000000github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang-github-azure-go-autorest-14.1.1/autorest/mocks/go_mod_tidy_hack.go000066400000000000000000000017171367372352400265360ustar00rootroot00000000000000// +build modhack package mocks // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // This file, and the github.com/Azure/go-autorest/autorest import, won't actually become part of // the resultant binary. // Necessary for safely adding multi-module repo. // See: https://github.com/golang/go/wiki/Modules#is-it-possible-to-add-a-module-to-a-multi-module-repository import _ "github.com/Azure/go-autorest/autorest" golang-github-azure-go-autorest-14.1.1/autorest/mocks/helpers.go000066400000000000000000000121171367372352400247110ustar00rootroot00000000000000package mocks // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "fmt" "io" "net/http" "time" ) const ( // TestAuthorizationHeader is a faux HTTP Authorization header value TestAuthorizationHeader = "BEARER SECRETTOKEN" // TestBadURL is a malformed URL TestBadURL = " " // TestDelay is the Retry-After delay used in tests. TestDelay = 0 * time.Second // TestHeader is the header used in tests. TestHeader = "x-test-header" // TestURL is the URL used in tests. TestURL = "https://microsoft.com/a/b/c/" // TestAzureAsyncURL is a URL used in Azure asynchronous tests TestAzureAsyncURL = "https://microsoft.com/a/b/c/async" // TestLocationURL is a URL used in Azure asynchronous tests TestLocationURL = "https://microsoft.com/a/b/c/location" ) const ( headerLocation = "Location" headerRetryAfter = "Retry-After" ) // NewRequest instantiates a new request. func NewRequest() *http.Request { return NewRequestWithContent("") } // NewRequestWithContent instantiates a new request using the passed string for the body content. func NewRequestWithContent(c string) *http.Request { r, _ := http.NewRequest("GET", "https://microsoft.com/a/b/c/", NewBody(c)) return r } // NewRequestWithCloseBody instantiates a new request. func NewRequestWithCloseBody() *http.Request { return NewRequestWithCloseBodyContent("request body") } // NewRequestWithCloseBodyContent instantiates a new request using the passed string for the body content. func NewRequestWithCloseBodyContent(c string) *http.Request { r, _ := http.NewRequest("GET", "https://microsoft.com/a/b/c/", NewBodyClose(c)) return r } // NewRequestForURL instantiates a new request using the passed URL. func NewRequestForURL(u string) *http.Request { return NewRequestWithParams("GET", u, NewBody("")) } // NewRequestWithParams instantiates a new request using the provided parameters. func NewRequestWithParams(method, u string, body io.Reader) *http.Request { r, err := http.NewRequest(method, u, body) if err != nil { panic(fmt.Sprintf("mocks: ERROR (%v) parsing testing URL %s", err, u)) } return r } // NewResponse instantiates a new response. func NewResponse() *http.Response { return NewResponseWithContent("") } // NewResponseWithBytes instantiates a new response with the passed bytes as the body content. func NewResponseWithBytes(input []byte) *http.Response { return &http.Response{ Status: "200 OK", StatusCode: 200, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Body: NewBodyWithBytes(input), Request: NewRequest(), } } // NewResponseWithContent instantiates a new response with the passed string as the body content. func NewResponseWithContent(c string) *http.Response { return &http.Response{ Status: "200 OK", StatusCode: 200, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Body: NewBody(c), Request: NewRequest(), } } // NewResponseWithStatus instantiates a new response using the passed string and integer as the // status and status code. func NewResponseWithStatus(s string, c int) *http.Response { resp := NewResponse() resp.Status = s resp.StatusCode = c return resp } // NewResponseWithBodyAndStatus instantiates a new response using the specified mock body, // status and status code func NewResponseWithBodyAndStatus(body *Body, c int, s string) *http.Response { resp := NewResponse() resp.Body = body resp.ContentLength = body.Length() resp.Status = s resp.StatusCode = c return resp } // SetResponseHeader adds a header to the passed response. func SetResponseHeader(resp *http.Response, h string, v string) { if resp.Header == nil { resp.Header = make(http.Header) } resp.Header.Set(h, v) } // SetResponseHeaderValues adds a header containing all the passed string values. func SetResponseHeaderValues(resp *http.Response, h string, values []string) { if resp.Header == nil { resp.Header = make(http.Header) } for _, v := range values { resp.Header.Add(h, v) } } // SetAcceptedHeaders adds the headers usually associated with a 202 Accepted response. func SetAcceptedHeaders(resp *http.Response) { SetLocationHeader(resp, TestURL) SetRetryHeader(resp, TestDelay) } // SetLocationHeader adds the Location header. func SetLocationHeader(resp *http.Response, location string) { SetResponseHeader(resp, http.CanonicalHeaderKey(headerLocation), location) } // SetRetryHeader adds the Retry-After header. func SetRetryHeader(resp *http.Response, delay time.Duration) { SetResponseHeader(resp, http.CanonicalHeaderKey(headerRetryAfter), fmt.Sprintf("%v", delay.Seconds())) } golang-github-azure-go-autorest-14.1.1/autorest/mocks/mocks.go000066400000000000000000000137271367372352400243730ustar00rootroot00000000000000/* Package mocks provides mocks and helpers used in testing. */ package mocks // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "fmt" "io" "net/http" "time" ) // Body implements acceptable body over a string. type Body struct { s string b []byte isOpen bool closeAttempts int } // NewBody creates a new instance of Body. func NewBody(s string) *Body { return (&Body{s: s}).reset() } // NewBodyWithBytes creates a new instance of Body. func NewBodyWithBytes(b []byte) *Body { return &Body{ b: b, isOpen: true, } } // NewBodyClose creates a new instance of Body. func NewBodyClose(s string) *Body { return &Body{s: s} } // Read reads into the passed byte slice and returns the bytes read. func (body *Body) Read(b []byte) (n int, err error) { if !body.IsOpen() { return 0, fmt.Errorf("ERROR: Body has been closed") } if len(body.b) == 0 { return 0, io.EOF } n = copy(b, body.b) body.b = body.b[n:] return n, nil } // Close closes the body. func (body *Body) Close() error { if body.isOpen { body.isOpen = false body.closeAttempts++ } return nil } // CloseAttempts returns the number of times Close was called. func (body *Body) CloseAttempts() int { return body.closeAttempts } // IsOpen returns true if the Body has not been closed, false otherwise. func (body *Body) IsOpen() bool { return body.isOpen } func (body *Body) reset() *Body { body.isOpen = true body.b = []byte(body.s) return body } // Length returns the number of bytes in the body. func (body *Body) Length() int64 { if body == nil { return 0 } return int64(len(body.b)) } type response struct { r *http.Response e error d time.Duration } // Sender implements a simple null sender. type Sender struct { attempts int responses []response numResponses int repeatResponse []int err error repeatError int emitErrorAfter int } // NewSender creates a new instance of Sender. func NewSender() *Sender { return &Sender{} } // Do accepts the passed request and, based on settings, emits a response and possible error. func (c *Sender) Do(r *http.Request) (resp *http.Response, err error) { c.attempts++ if len(c.responses) > 0 { resp = c.responses[0].r if resp != nil { if b, ok := resp.Body.(*Body); ok { b.reset() } } else { err = c.responses[0].e } time.Sleep(c.responses[0].d) c.repeatResponse[0]-- if c.repeatResponse[0] == 0 { c.responses = c.responses[1:] c.repeatResponse = c.repeatResponse[1:] } } else { resp = NewResponse() } if resp != nil { resp.Request = r } if c.emitErrorAfter > 0 { c.emitErrorAfter-- } else if c.err != nil { err = c.err c.repeatError-- if c.repeatError == 0 { c.err = nil } } return } // AppendResponse adds the passed http.Response to the response stack. func (c *Sender) AppendResponse(resp *http.Response) { c.AppendAndRepeatResponse(resp, 1) } // AppendResponseWithDelay adds the passed http.Response to the response stack with the specified delay. func (c *Sender) AppendResponseWithDelay(resp *http.Response, delay time.Duration) { c.AppendAndRepeatResponseWithDelay(resp, delay, 1) } // AppendAndRepeatResponse adds the passed http.Response to the response stack along with a // repeat count. A negative repeat count will return the response for all remaining calls to Do. func (c *Sender) AppendAndRepeatResponse(resp *http.Response, repeat int) { c.appendAndRepeat(response{r: resp}, repeat) } // AppendAndRepeatResponseWithDelay adds the passed http.Response to the response stack with the specified // delay along with a repeat count. A negative repeat count will return the response for all remaining calls to Do. func (c *Sender) AppendAndRepeatResponseWithDelay(resp *http.Response, delay time.Duration, repeat int) { c.appendAndRepeat(response{r: resp, d: delay}, repeat) } // AppendError adds the passed error to the response stack. func (c *Sender) AppendError(err error) { c.AppendAndRepeatError(err, 1) } // AppendAndRepeatError adds the passed error to the response stack along with a repeat // count. A negative repeat count will return the response for all remaining calls to Do. func (c *Sender) AppendAndRepeatError(err error, repeat int) { c.appendAndRepeat(response{e: err}, repeat) } func (c *Sender) appendAndRepeat(resp response, repeat int) { if c.responses == nil { c.responses = []response{resp} c.repeatResponse = []int{repeat} } else { c.responses = append(c.responses, resp) c.repeatResponse = append(c.repeatResponse, repeat) } c.numResponses++ } // Attempts returns the number of times Do was called. func (c *Sender) Attempts() int { return c.attempts } // SetError sets the error Do should return. func (c *Sender) SetError(err error) { c.SetAndRepeatError(err, 1) } // SetAndRepeatError sets the error Do should return and how many calls to Do will return the error. // A negative repeat value will return the error for all remaining calls to Do. func (c *Sender) SetAndRepeatError(err error, repeat int) { c.err = err c.repeatError = repeat } // SetEmitErrorAfter sets the number of attempts to be made before errors are emitted. func (c *Sender) SetEmitErrorAfter(ea int) { c.emitErrorAfter = ea } // NumResponses returns the number of responses that have been added to the sender. func (c *Sender) NumResponses() int { return c.numResponses } // T is a simple testing struct. type T struct { Name string `json:"name" xml:"Name"` Age int `json:"age" xml:"Age"` } golang-github-azure-go-autorest-14.1.1/autorest/preparer.go000066400000000000000000000442221367372352400237550ustar00rootroot00000000000000package autorest // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "bytes" "context" "encoding/json" "encoding/xml" "fmt" "io" "io/ioutil" "mime/multipart" "net/http" "net/url" "strings" ) const ( mimeTypeJSON = "application/json" mimeTypeOctetStream = "application/octet-stream" mimeTypeFormPost = "application/x-www-form-urlencoded" headerAuthorization = "Authorization" headerAuxAuthorization = "x-ms-authorization-auxiliary" headerContentType = "Content-Type" headerUserAgent = "User-Agent" ) // used as a key type in context.WithValue() type ctxPrepareDecorators struct{} // WithPrepareDecorators adds the specified PrepareDecorators to the provided context. // If no PrepareDecorators are provided the context is unchanged. func WithPrepareDecorators(ctx context.Context, prepareDecorator []PrepareDecorator) context.Context { if len(prepareDecorator) == 0 { return ctx } return context.WithValue(ctx, ctxPrepareDecorators{}, prepareDecorator) } // GetPrepareDecorators returns the PrepareDecorators in the provided context or the provided default PrepareDecorators. func GetPrepareDecorators(ctx context.Context, defaultPrepareDecorators ...PrepareDecorator) []PrepareDecorator { inCtx := ctx.Value(ctxPrepareDecorators{}) if pd, ok := inCtx.([]PrepareDecorator); ok { return pd } return defaultPrepareDecorators } // Preparer is the interface that wraps the Prepare method. // // Prepare accepts and possibly modifies an http.Request (e.g., adding Headers). Implementations // must ensure to not share or hold per-invocation state since Preparers may be shared and re-used. type Preparer interface { Prepare(*http.Request) (*http.Request, error) } // PreparerFunc is a method that implements the Preparer interface. type PreparerFunc func(*http.Request) (*http.Request, error) // Prepare implements the Preparer interface on PreparerFunc. func (pf PreparerFunc) Prepare(r *http.Request) (*http.Request, error) { return pf(r) } // PrepareDecorator takes and possibly decorates, by wrapping, a Preparer. Decorators may affect the // http.Request and pass it along or, first, pass the http.Request along then affect the result. type PrepareDecorator func(Preparer) Preparer // CreatePreparer creates, decorates, and returns a Preparer. // Without decorators, the returned Preparer returns the passed http.Request unmodified. // Preparers are safe to share and re-use. func CreatePreparer(decorators ...PrepareDecorator) Preparer { return DecoratePreparer( Preparer(PreparerFunc(func(r *http.Request) (*http.Request, error) { return r, nil })), decorators...) } // DecoratePreparer accepts a Preparer and a, possibly empty, set of PrepareDecorators, which it // applies to the Preparer. Decorators are applied in the order received, but their affect upon the // request depends on whether they are a pre-decorator (change the http.Request and then pass it // along) or a post-decorator (pass the http.Request along and alter it on return). func DecoratePreparer(p Preparer, decorators ...PrepareDecorator) Preparer { for _, decorate := range decorators { p = decorate(p) } return p } // Prepare accepts an http.Request and a, possibly empty, set of PrepareDecorators. // It creates a Preparer from the decorators which it then applies to the passed http.Request. func Prepare(r *http.Request, decorators ...PrepareDecorator) (*http.Request, error) { if r == nil { return nil, NewError("autorest", "Prepare", "Invoked without an http.Request") } return CreatePreparer(decorators...).Prepare(r) } // WithNothing returns a "do nothing" PrepareDecorator that makes no changes to the passed // http.Request. func WithNothing() PrepareDecorator { return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { return p.Prepare(r) }) } } // WithHeader returns a PrepareDecorator that sets the specified HTTP header of the http.Request to // the passed value. It canonicalizes the passed header name (via http.CanonicalHeaderKey) before // adding the header. func WithHeader(header string, value string) PrepareDecorator { return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { r, err := p.Prepare(r) if err == nil { if r.Header == nil { r.Header = make(http.Header) } r.Header.Set(http.CanonicalHeaderKey(header), value) } return r, err }) } } // WithHeaders returns a PrepareDecorator that sets the specified HTTP headers of the http.Request to // the passed value. It canonicalizes the passed headers name (via http.CanonicalHeaderKey) before // adding them. func WithHeaders(headers map[string]interface{}) PrepareDecorator { h := ensureValueStrings(headers) return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { r, err := p.Prepare(r) if err == nil { if r.Header == nil { r.Header = make(http.Header) } for name, value := range h { r.Header.Set(http.CanonicalHeaderKey(name), value) } } return r, err }) } } // WithBearerAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose // value is "Bearer " followed by the supplied token. func WithBearerAuthorization(token string) PrepareDecorator { return WithHeader(headerAuthorization, fmt.Sprintf("Bearer %s", token)) } // AsContentType returns a PrepareDecorator that adds an HTTP Content-Type header whose value // is the passed contentType. func AsContentType(contentType string) PrepareDecorator { return WithHeader(headerContentType, contentType) } // WithUserAgent returns a PrepareDecorator that adds an HTTP User-Agent header whose value is the // passed string. func WithUserAgent(ua string) PrepareDecorator { return WithHeader(headerUserAgent, ua) } // AsFormURLEncoded returns a PrepareDecorator that adds an HTTP Content-Type header whose value is // "application/x-www-form-urlencoded". func AsFormURLEncoded() PrepareDecorator { return AsContentType(mimeTypeFormPost) } // AsJSON returns a PrepareDecorator that adds an HTTP Content-Type header whose value is // "application/json". func AsJSON() PrepareDecorator { return AsContentType(mimeTypeJSON) } // AsOctetStream returns a PrepareDecorator that adds the "application/octet-stream" Content-Type header. func AsOctetStream() PrepareDecorator { return AsContentType(mimeTypeOctetStream) } // WithMethod returns a PrepareDecorator that sets the HTTP method of the passed request. The // decorator does not validate that the passed method string is a known HTTP method. func WithMethod(method string) PrepareDecorator { return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { r.Method = method return p.Prepare(r) }) } } // AsDelete returns a PrepareDecorator that sets the HTTP method to DELETE. func AsDelete() PrepareDecorator { return WithMethod("DELETE") } // AsGet returns a PrepareDecorator that sets the HTTP method to GET. func AsGet() PrepareDecorator { return WithMethod("GET") } // AsHead returns a PrepareDecorator that sets the HTTP method to HEAD. func AsHead() PrepareDecorator { return WithMethod("HEAD") } // AsMerge returns a PrepareDecorator that sets the HTTP method to MERGE. func AsMerge() PrepareDecorator { return WithMethod("MERGE") } // AsOptions returns a PrepareDecorator that sets the HTTP method to OPTIONS. func AsOptions() PrepareDecorator { return WithMethod("OPTIONS") } // AsPatch returns a PrepareDecorator that sets the HTTP method to PATCH. func AsPatch() PrepareDecorator { return WithMethod("PATCH") } // AsPost returns a PrepareDecorator that sets the HTTP method to POST. func AsPost() PrepareDecorator { return WithMethod("POST") } // AsPut returns a PrepareDecorator that sets the HTTP method to PUT. func AsPut() PrepareDecorator { return WithMethod("PUT") } // WithBaseURL returns a PrepareDecorator that populates the http.Request with a url.URL constructed // from the supplied baseUrl. func WithBaseURL(baseURL string) PrepareDecorator { return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { r, err := p.Prepare(r) if err == nil { var u *url.URL if u, err = url.Parse(baseURL); err != nil { return r, err } if u.Scheme == "" { err = fmt.Errorf("autorest: No scheme detected in URL %s", baseURL) } if err == nil { r.URL = u } } return r, err }) } } // WithBytes returns a PrepareDecorator that takes a list of bytes // which passes the bytes directly to the body func WithBytes(input *[]byte) PrepareDecorator { return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { r, err := p.Prepare(r) if err == nil { if input == nil { return r, fmt.Errorf("Input Bytes was nil") } r.ContentLength = int64(len(*input)) r.Body = ioutil.NopCloser(bytes.NewReader(*input)) } return r, err }) } } // WithCustomBaseURL returns a PrepareDecorator that replaces brace-enclosed keys within the // request base URL (i.e., http.Request.URL) with the corresponding values from the passed map. func WithCustomBaseURL(baseURL string, urlParameters map[string]interface{}) PrepareDecorator { parameters := ensureValueStrings(urlParameters) for key, value := range parameters { baseURL = strings.Replace(baseURL, "{"+key+"}", value, -1) } return WithBaseURL(baseURL) } // WithFormData returns a PrepareDecoratore that "URL encodes" (e.g., bar=baz&foo=quux) into the // http.Request body. func WithFormData(v url.Values) PrepareDecorator { return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { r, err := p.Prepare(r) if err == nil { s := v.Encode() if r.Header == nil { r.Header = make(http.Header) } r.Header.Set(http.CanonicalHeaderKey(headerContentType), mimeTypeFormPost) r.ContentLength = int64(len(s)) r.Body = ioutil.NopCloser(strings.NewReader(s)) } return r, err }) } } // WithMultiPartFormData returns a PrepareDecoratore that "URL encodes" (e.g., bar=baz&foo=quux) form parameters // into the http.Request body. func WithMultiPartFormData(formDataParameters map[string]interface{}) PrepareDecorator { return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { r, err := p.Prepare(r) if err == nil { var body bytes.Buffer writer := multipart.NewWriter(&body) for key, value := range formDataParameters { if rc, ok := value.(io.ReadCloser); ok { var fd io.Writer if fd, err = writer.CreateFormFile(key, key); err != nil { return r, err } if _, err = io.Copy(fd, rc); err != nil { return r, err } } else { if err = writer.WriteField(key, ensureValueString(value)); err != nil { return r, err } } } if err = writer.Close(); err != nil { return r, err } if r.Header == nil { r.Header = make(http.Header) } r.Header.Set(http.CanonicalHeaderKey(headerContentType), writer.FormDataContentType()) r.Body = ioutil.NopCloser(bytes.NewReader(body.Bytes())) r.ContentLength = int64(body.Len()) return r, err } return r, err }) } } // WithFile returns a PrepareDecorator that sends file in request body. func WithFile(f io.ReadCloser) PrepareDecorator { return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { r, err := p.Prepare(r) if err == nil { b, err := ioutil.ReadAll(f) if err != nil { return r, err } r.Body = ioutil.NopCloser(bytes.NewReader(b)) r.ContentLength = int64(len(b)) } return r, err }) } } // WithBool returns a PrepareDecorator that encodes the passed bool into the body of the request // and sets the Content-Length header. func WithBool(v bool) PrepareDecorator { return WithString(fmt.Sprintf("%v", v)) } // WithFloat32 returns a PrepareDecorator that encodes the passed float32 into the body of the // request and sets the Content-Length header. func WithFloat32(v float32) PrepareDecorator { return WithString(fmt.Sprintf("%v", v)) } // WithFloat64 returns a PrepareDecorator that encodes the passed float64 into the body of the // request and sets the Content-Length header. func WithFloat64(v float64) PrepareDecorator { return WithString(fmt.Sprintf("%v", v)) } // WithInt32 returns a PrepareDecorator that encodes the passed int32 into the body of the request // and sets the Content-Length header. func WithInt32(v int32) PrepareDecorator { return WithString(fmt.Sprintf("%v", v)) } // WithInt64 returns a PrepareDecorator that encodes the passed int64 into the body of the request // and sets the Content-Length header. func WithInt64(v int64) PrepareDecorator { return WithString(fmt.Sprintf("%v", v)) } // WithString returns a PrepareDecorator that encodes the passed string into the body of the request // and sets the Content-Length header. func WithString(v string) PrepareDecorator { return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { r, err := p.Prepare(r) if err == nil { r.ContentLength = int64(len(v)) r.Body = ioutil.NopCloser(strings.NewReader(v)) } return r, err }) } } // WithJSON returns a PrepareDecorator that encodes the data passed as JSON into the body of the // request and sets the Content-Length header. func WithJSON(v interface{}) PrepareDecorator { return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { r, err := p.Prepare(r) if err == nil { b, err := json.Marshal(v) if err == nil { r.ContentLength = int64(len(b)) r.Body = ioutil.NopCloser(bytes.NewReader(b)) } } return r, err }) } } // WithXML returns a PrepareDecorator that encodes the data passed as XML into the body of the // request and sets the Content-Length header. func WithXML(v interface{}) PrepareDecorator { return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { r, err := p.Prepare(r) if err == nil { b, err := xml.Marshal(v) if err == nil { // we have to tack on an XML header withHeader := xml.Header + string(b) bytesWithHeader := []byte(withHeader) r.ContentLength = int64(len(bytesWithHeader)) r.Body = ioutil.NopCloser(bytes.NewReader(bytesWithHeader)) } } return r, err }) } } // WithPath returns a PrepareDecorator that adds the supplied path to the request URL. If the path // is absolute (that is, it begins with a "/"), it replaces the existing path. func WithPath(path string) PrepareDecorator { return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { r, err := p.Prepare(r) if err == nil { if r.URL == nil { return r, NewError("autorest", "WithPath", "Invoked with a nil URL") } if r.URL, err = parseURL(r.URL, path); err != nil { return r, err } } return r, err }) } } // WithEscapedPathParameters returns a PrepareDecorator that replaces brace-enclosed keys within the // request path (i.e., http.Request.URL.Path) with the corresponding values from the passed map. The // values will be escaped (aka URL encoded) before insertion into the path. func WithEscapedPathParameters(path string, pathParameters map[string]interface{}) PrepareDecorator { parameters := escapeValueStrings(ensureValueStrings(pathParameters)) return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { r, err := p.Prepare(r) if err == nil { if r.URL == nil { return r, NewError("autorest", "WithEscapedPathParameters", "Invoked with a nil URL") } for key, value := range parameters { path = strings.Replace(path, "{"+key+"}", value, -1) } if r.URL, err = parseURL(r.URL, path); err != nil { return r, err } } return r, err }) } } // WithPathParameters returns a PrepareDecorator that replaces brace-enclosed keys within the // request path (i.e., http.Request.URL.Path) with the corresponding values from the passed map. func WithPathParameters(path string, pathParameters map[string]interface{}) PrepareDecorator { parameters := ensureValueStrings(pathParameters) return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { r, err := p.Prepare(r) if err == nil { if r.URL == nil { return r, NewError("autorest", "WithPathParameters", "Invoked with a nil URL") } for key, value := range parameters { path = strings.Replace(path, "{"+key+"}", value, -1) } if r.URL, err = parseURL(r.URL, path); err != nil { return r, err } } return r, err }) } } func parseURL(u *url.URL, path string) (*url.URL, error) { p := strings.TrimRight(u.String(), "/") if !strings.HasPrefix(path, "/") { path = "/" + path } return url.Parse(p + path) } // WithQueryParameters returns a PrepareDecorators that encodes and applies the query parameters // given in the supplied map (i.e., key=value). func WithQueryParameters(queryParameters map[string]interface{}) PrepareDecorator { parameters := MapToValues(queryParameters) return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { r, err := p.Prepare(r) if err == nil { if r.URL == nil { return r, NewError("autorest", "WithQueryParameters", "Invoked with a nil URL") } v := r.URL.Query() for key, value := range parameters { for i := range value { d, err := url.QueryUnescape(value[i]) if err != nil { return r, err } value[i] = d } v[key] = value } r.URL.RawQuery = v.Encode() } return r, err }) } } golang-github-azure-go-autorest-14.1.1/autorest/preparer_test.go000066400000000000000000000610041367372352400250110ustar00rootroot00000000000000package autorest // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "context" "fmt" "io/ioutil" "net/http" "net/url" "reflect" "strconv" "strings" "testing" "github.com/Azure/go-autorest/autorest/mocks" ) // PrepareDecorators wrap and invoke a Preparer. Most often, the decorator invokes the passed // Preparer and decorates the response. func ExamplePrepareDecorator() { path := "a/b/c/" pd := func() PrepareDecorator { return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { r, err := p.Prepare(r) if err == nil { if r.URL == nil { return r, fmt.Errorf("ERROR: URL is not set") } r.URL.Path += path } return r, err }) } } r, _ := Prepare(&http.Request{}, WithBaseURL("https://microsoft.com/"), pd()) fmt.Printf("Path is %s\n", r.URL) // Output: Path is https://microsoft.com/a/b/c/ } // PrepareDecorators may also modify and then invoke the Preparer. func ExamplePrepareDecorator_pre() { pd := func() PrepareDecorator { return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { r.Header.Add(http.CanonicalHeaderKey("ContentType"), "application/json") return p.Prepare(r) }) } } r, _ := Prepare(&http.Request{Header: http.Header{}}, pd()) fmt.Printf("ContentType is %s\n", r.Header.Get("ContentType")) // Output: ContentType is application/json } // Create a sequence of three Preparers that build up the URL path. func ExampleCreatePreparer() { p := CreatePreparer( WithBaseURL("https://microsoft.com/"), WithPath("a"), WithPath("b"), WithPath("c")) r, err := p.Prepare(&http.Request{}) if err != nil { fmt.Printf("ERROR: %v\n", err) } else { fmt.Println(r.URL) } // Output: https://microsoft.com/a/b/c } // Create and apply separate Preparers func ExampleCreatePreparer_multiple() { params := map[string]interface{}{ "param1": "a", "param2": "c", } p1 := CreatePreparer(WithBaseURL("https://microsoft.com/")) p2 := CreatePreparer(WithPathParameters("/{param1}/b/{param2}/", params)) r, err := p1.Prepare(&http.Request{}) if err != nil { fmt.Printf("ERROR: %v\n", err) } r, err = p2.Prepare(r) if err != nil { fmt.Printf("ERROR: %v\n", err) } else { fmt.Println(r.URL) } // Output: https://microsoft.com/a/b/c/ } // Create and chain separate Preparers func ExampleCreatePreparer_chain() { params := map[string]interface{}{ "param1": "a", "param2": "c", } p := CreatePreparer(WithBaseURL("https://microsoft.com/")) p = DecoratePreparer(p, WithPathParameters("/{param1}/b/{param2}/", params)) r, err := p.Prepare(&http.Request{}) if err != nil { fmt.Printf("ERROR: %v\n", err) } else { fmt.Println(r.URL) } // Output: https://microsoft.com/a/b/c/ } // Create and prepare an http.Request in one call func ExamplePrepare() { r, err := Prepare(&http.Request{}, AsGet(), WithBaseURL("https://microsoft.com/"), WithPath("a/b/c/")) if err != nil { fmt.Printf("ERROR: %v\n", err) } else { fmt.Printf("%s %s", r.Method, r.URL) } // Output: GET https://microsoft.com/a/b/c/ } // Create a request for a supplied base URL and path func ExampleWithBaseURL() { r, err := Prepare(&http.Request{}, WithBaseURL("https://microsoft.com/a/b/c/")) if err != nil { fmt.Printf("ERROR: %v\n", err) } else { fmt.Println(r.URL) } // Output: https://microsoft.com/a/b/c/ } func TestWithBaseURL_second(t *testing.T) { _, err := Prepare(&http.Request{}, WithBaseURL(":")) if err == nil { t.Fatal("unexpected nil error") } } // Create a request whose Body is a byte array func TestWithBytes(t *testing.T) { input := []byte{41, 82, 109} r, err := Prepare(&http.Request{}, WithBytes(&input)) if err != nil { t.Fatalf("ERROR: %v\n", err) } b, err := ioutil.ReadAll(r.Body) if err != nil { t.Fatalf("ERROR: %v\n", err) } if len(b) != len(input) { t.Fatalf("Expected the Body to contain %d bytes but got %d", len(input), len(b)) } if !reflect.DeepEqual(b, input) { t.Fatalf("Body doesn't contain the same bytes: %s (Expected %s)", b, input) } } func ExampleWithCustomBaseURL() { r, err := Prepare(&http.Request{}, WithCustomBaseURL("https://{account}.{service}.core.windows.net/", map[string]interface{}{ "account": "myaccount", "service": "blob", })) if err != nil { fmt.Printf("ERROR: %v\n", err) } else { fmt.Println(r.URL) } // Output: https://myaccount.blob.core.windows.net/ } func TestWithCustomBaseURL_second(t *testing.T) { _, err := Prepare(&http.Request{}, WithCustomBaseURL(":", map[string]interface{}{})) if err == nil { t.Fatal("unexpected nil error") } } // Create a request with a custom HTTP header func ExampleWithHeader() { r, err := Prepare(&http.Request{}, WithBaseURL("https://microsoft.com/a/b/c/"), WithHeader("x-foo", "bar")) if err != nil { fmt.Printf("ERROR: %v\n", err) } else { fmt.Printf("Header %s=%s\n", "x-foo", r.Header.Get("x-foo")) } // Output: Header x-foo=bar } // Create a request whose Body is the JSON encoding of a structure func ExampleWithFormData() { v := url.Values{} v.Add("name", "Rob Pike") v.Add("age", "42") r, err := Prepare(&http.Request{}, WithFormData(v)) if err != nil { fmt.Printf("ERROR: %v\n", err) } b, err := ioutil.ReadAll(r.Body) if err != nil { fmt.Printf("ERROR: %v\n", err) } else { fmt.Printf("Request Body contains %s\n", string(b)) } // Output: Request Body contains age=42&name=Rob+Pike } // Create a request whose Body is the JSON encoding of a structure func ExampleWithJSON() { t := mocks.T{Name: "Rob Pike", Age: 42} r, err := Prepare(&http.Request{}, WithJSON(&t)) if err != nil { fmt.Printf("ERROR: %v\n", err) } b, err := ioutil.ReadAll(r.Body) if err != nil { fmt.Printf("ERROR: %v\n", err) } else { fmt.Printf("Request Body contains %s\n", string(b)) } // Output: Request Body contains {"name":"Rob Pike","age":42} } // Create a request whose Body is the XML encoding of a structure func ExampleWithXML() { t := mocks.T{Name: "Rob Pike", Age: 42} r, err := Prepare(&http.Request{}, WithXML(&t)) if err != nil { fmt.Printf("ERROR: %v\n", err) } b, err := ioutil.ReadAll(r.Body) if err != nil { fmt.Printf("ERROR: %v\n", err) } else { fmt.Printf("Request Body contains %s\n", string(b)) } // Output: Request Body contains // Rob Pike42 } // Create a request from a path with escaped parameters func ExampleWithEscapedPathParameters() { params := map[string]interface{}{ "param1": "a b c", "param2": "d e f", } r, err := Prepare(&http.Request{}, WithBaseURL("https://microsoft.com/"), WithEscapedPathParameters("/{param1}/b/{param2}/", params)) if err != nil { fmt.Printf("ERROR: %v\n", err) } else { fmt.Println(r.URL) } // Output: https://microsoft.com/a+b+c/b/d+e+f/ } // Create a request from a path with parameters func ExampleWithPathParameters() { params := map[string]interface{}{ "param1": "a", "param2": "c", } r, err := Prepare(&http.Request{}, WithBaseURL("https://microsoft.com/"), WithPathParameters("/{param1}/b/{param2}/", params)) if err != nil { fmt.Printf("ERROR: %v\n", err) } else { fmt.Println(r.URL) } // Output: https://microsoft.com/a/b/c/ } // Create a request with query parameters func ExampleWithQueryParameters() { params := map[string]interface{}{ "q1": []string{"value1"}, "q2": []string{"value2"}, } r, err := Prepare(&http.Request{}, WithBaseURL("https://microsoft.com/"), WithPath("/a/b/c/"), WithQueryParameters(params)) if err != nil { fmt.Printf("ERROR: %v\n", err) } else { fmt.Println(r.URL) } // Output: https://microsoft.com/a/b/c/?q1=value1&q2=value2 } func TestWithCustomBaseURL(t *testing.T) { r, err := Prepare(&http.Request{}, WithCustomBaseURL("https://{account}.{service}.core.windows.net/", map[string]interface{}{ "account": "myaccount", "service": "blob", })) if err != nil { t.Fatalf("autorest: WithCustomBaseURL should not fail") } if r.URL.String() != "https://myaccount.blob.core.windows.net/" { t.Fatalf("autorest: WithCustomBaseURL expected https://myaccount.blob.core.windows.net/, got %s", r.URL) } } func TestWithCustomBaseURLwithInvalidURL(t *testing.T) { _, err := Prepare(&http.Request{}, WithCustomBaseURL("hello/{account}.{service}.core.windows.net/", map[string]interface{}{ "account": "myaccount", "service": "blob", })) if err == nil { t.Fatalf("autorest: WithCustomBaseURL should fail fo URL parse error") } } func TestWithPathWithInvalidPath(t *testing.T) { p := "path%2*end" if _, err := Prepare(&http.Request{}, WithBaseURL("https://microsoft.com/"), WithPath(p)); err == nil { t.Fatalf("autorest: WithPath should fail for invalid URL escape error for path '%v' ", p) } } func TestWithPathParametersWithInvalidPath(t *testing.T) { p := "path%2*end" m := map[string]interface{}{ "path1": p, } if _, err := Prepare(&http.Request{}, WithBaseURL("https://microsoft.com/"), WithPathParameters("/{path1}/", m)); err == nil { t.Fatalf("autorest: WithPath should fail for invalid URL escape for path '%v' ", p) } } func TestCreatePreparerDoesNotModify(t *testing.T) { r1 := &http.Request{} p := CreatePreparer() r2, err := p.Prepare(r1) if err != nil { t.Fatalf("autorest: CreatePreparer failed (%v)", err) } if !reflect.DeepEqual(r1, r2) { t.Fatalf("autorest: CreatePreparer without decorators modified the request") } } func TestCreatePreparerRunsDecoratorsInOrder(t *testing.T) { p := CreatePreparer(WithBaseURL("https://microsoft.com/"), WithPath("1"), WithPath("2"), WithPath("3")) r, err := p.Prepare(&http.Request{}) if err != nil { t.Fatalf("autorest: CreatePreparer failed (%v)", err) } if r.URL.String() != "https:/1/2/3" && r.URL.Host != "microsoft.com" { t.Fatalf("autorest: CreatePreparer failed to run decorators in order") } } func TestAsContentType(t *testing.T) { r, err := Prepare(mocks.NewRequest(), AsContentType("application/text")) if err != nil { fmt.Printf("ERROR: %v", err) } if r.Header.Get(headerContentType) != "application/text" { t.Fatalf("autorest: AsContentType failed to add header (%s=%s)", headerContentType, r.Header.Get(headerContentType)) } } func TestAsFormURLEncoded(t *testing.T) { r, err := Prepare(mocks.NewRequest(), AsFormURLEncoded()) if err != nil { fmt.Printf("ERROR: %v", err) } if r.Header.Get(headerContentType) != mimeTypeFormPost { t.Fatalf("autorest: AsFormURLEncoded failed to add header (%s=%s)", headerContentType, r.Header.Get(headerContentType)) } } func TestAsJSON(t *testing.T) { r, err := Prepare(mocks.NewRequest(), AsJSON()) if err != nil { fmt.Printf("ERROR: %v", err) } if r.Header.Get(headerContentType) != mimeTypeJSON { t.Fatalf("autorest: AsJSON failed to add header (%s=%s)", headerContentType, r.Header.Get(headerContentType)) } } func TestWithNothing(t *testing.T) { r1 := mocks.NewRequest() r2, err := Prepare(r1, WithNothing()) if err != nil { t.Fatalf("autorest: WithNothing returned an unexpected error (%v)", err) } if !reflect.DeepEqual(r1, r2) { t.Fatal("azure: WithNothing modified the passed HTTP Request") } } func TestWithBearerAuthorization(t *testing.T) { r, err := Prepare(mocks.NewRequest(), WithBearerAuthorization("SOME-TOKEN")) if err != nil { fmt.Printf("ERROR: %v", err) } if r.Header.Get(headerAuthorization) != "Bearer SOME-TOKEN" { t.Fatalf("autorest: WithBearerAuthorization failed to add header (%s=%s)", headerAuthorization, r.Header.Get(headerAuthorization)) } } func TestWithUserAgent(t *testing.T) { ua := "User Agent Go" r, err := Prepare(mocks.NewRequest(), WithUserAgent(ua)) if err != nil { fmt.Printf("ERROR: %v", err) } if r.UserAgent() != ua || r.Header.Get(headerUserAgent) != ua { t.Fatalf("autorest: WithUserAgent failed to add header (%s=%s)", headerUserAgent, r.Header.Get(headerUserAgent)) } } func TestWithMethod(t *testing.T) { r, _ := Prepare(mocks.NewRequest(), WithMethod("HEAD")) if r.Method != "HEAD" { t.Fatal("autorest: WithMethod failed to set HTTP method header") } } func TestAsDelete(t *testing.T) { r, _ := Prepare(mocks.NewRequest(), AsDelete()) if r.Method != "DELETE" { t.Fatal("autorest: AsDelete failed to set HTTP method header to DELETE") } } func TestAsGet(t *testing.T) { r, _ := Prepare(mocks.NewRequest(), AsGet()) if r.Method != "GET" { t.Fatal("autorest: AsGet failed to set HTTP method header to GET") } } func TestAsHead(t *testing.T) { r, _ := Prepare(mocks.NewRequest(), AsHead()) if r.Method != "HEAD" { t.Fatal("autorest: AsHead failed to set HTTP method header to HEAD") } } func TestAsMerge(t *testing.T) { r, _ := Prepare(mocks.NewRequest(), AsMerge()) if r.Method != "MERGE" { t.Fatal("autorest: AsMerge failed to set HTTP method header to MERGE") } } func TestAsOptions(t *testing.T) { r, _ := Prepare(mocks.NewRequest(), AsOptions()) if r.Method != "OPTIONS" { t.Fatal("autorest: AsOptions failed to set HTTP method header to OPTIONS") } } func TestAsPatch(t *testing.T) { r, _ := Prepare(mocks.NewRequest(), AsPatch()) if r.Method != "PATCH" { t.Fatal("autorest: AsPatch failed to set HTTP method header to PATCH") } } func TestAsPost(t *testing.T) { r, _ := Prepare(mocks.NewRequest(), AsPost()) if r.Method != "POST" { t.Fatal("autorest: AsPost failed to set HTTP method header to POST") } } func TestAsPut(t *testing.T) { r, _ := Prepare(mocks.NewRequest(), AsPut()) if r.Method != "PUT" { t.Fatal("autorest: AsPut failed to set HTTP method header to PUT") } } func TestPrepareWithNullRequest(t *testing.T) { _, err := Prepare(nil) if err == nil { t.Fatal("autorest: Prepare failed to return an error when given a null http.Request") } } func TestWithFormData(t *testing.T) { v := url.Values{} v.Add("name", "Rob Pike") v.Add("age", "42") r, err := Prepare(&http.Request{}, WithFormData(v)) if err != nil { t.Fatalf("autorest: WithFormData failed with error (%v)", err) } b, err := ioutil.ReadAll(r.Body) if err != nil { t.Fatalf("autorest: WithFormData failed with error (%v)", err) } expected := "name=Rob+Pike&age=42" if !(string(b) == "name=Rob+Pike&age=42" || string(b) == "age=42&name=Rob+Pike") { t.Fatalf("autorest:WithFormData failed to return correct string got (%v), expected (%v)", string(b), expected) } if r.ContentLength != int64(len(b)) { t.Fatalf("autorest:WithFormData set Content-Length to %v, expected %v", r.ContentLength, len(b)) } if expected, got := r.Header.Get(http.CanonicalHeaderKey(headerContentType)), mimeTypeFormPost; expected != got { t.Fatalf("autorest:WithFormData Content Type not set or set to wrong value. Expected %v and got %v", expected, got) } } func TestWithMultiPartFormDataSetsContentLength(t *testing.T) { v := map[string]interface{}{ "file": ioutil.NopCloser(strings.NewReader("Hello Gopher")), "age": "42", } r, err := Prepare(&http.Request{}, WithMultiPartFormData(v)) if err != nil { t.Fatalf("autorest: WithMultiPartFormData failed with error (%v)", err) } b, err := ioutil.ReadAll(r.Body) if err != nil { t.Fatalf("autorest: WithMultiPartFormData failed with error (%v)", err) } if r.ContentLength != int64(len(b)) { t.Fatalf("autorest:WithMultiPartFormData set Content-Length to %v, expected %v", r.ContentLength, len(b)) } } func TestWithMultiPartFormDataWithNoFile(t *testing.T) { v := map[string]interface{}{ "file": "no file", "age": "42", } r, err := Prepare(&http.Request{}, WithMultiPartFormData(v)) if err != nil { t.Fatalf("autorest: WithMultiPartFormData failed with error (%v)", err) } b, err := ioutil.ReadAll(r.Body) if err != nil { t.Fatalf("autorest: WithMultiPartFormData failed with error (%v)", err) } if r.ContentLength != int64(len(b)) { t.Fatalf("autorest:WithMultiPartFormData set Content-Length to %v, expected %v", r.ContentLength, len(b)) } } func TestWithFile(t *testing.T) { r, err := Prepare(&http.Request{}, WithFile(ioutil.NopCloser(strings.NewReader("Hello Gopher")))) if err != nil { t.Fatalf("autorest: WithFile failed with error (%v)", err) } b, err := ioutil.ReadAll(r.Body) if err != nil { t.Fatalf("autorest: WithFile failed with error (%v)", err) } if r.ContentLength != int64(len(b)) { t.Fatalf("autorest:WithFile set Content-Length to %v, expected %v", r.ContentLength, len(b)) } } func TestWithBool_SetsTheBody(t *testing.T) { r, err := Prepare(&http.Request{}, WithBool(false)) if err != nil { t.Fatalf("autorest: WithBool failed with error (%v)", err) } s, err := ioutil.ReadAll(r.Body) if err != nil { t.Fatalf("autorest: WithBool failed with error (%v)", err) } if r.ContentLength != int64(len(fmt.Sprintf("%v", false))) { t.Fatalf("autorest: WithBool set Content-Length to %v, expected %v", r.ContentLength, int64(len(fmt.Sprintf("%v", false)))) } v, err := strconv.ParseBool(string(s)) if err != nil || v { t.Fatalf("autorest: WithBool incorrectly encoded the boolean as %v", s) } } func TestWithFloat32_SetsTheBody(t *testing.T) { r, err := Prepare(&http.Request{}, WithFloat32(42.0)) if err != nil { t.Fatalf("autorest: WithFloat32 failed with error (%v)", err) } s, err := ioutil.ReadAll(r.Body) if err != nil { t.Fatalf("autorest: WithFloat32 failed with error (%v)", err) } if r.ContentLength != int64(len(fmt.Sprintf("%v", 42.0))) { t.Fatalf("autorest: WithFloat32 set Content-Length to %v, expected %v", r.ContentLength, int64(len(fmt.Sprintf("%v", 42.0)))) } v, err := strconv.ParseFloat(string(s), 32) if err != nil || float32(v) != float32(42.0) { t.Fatalf("autorest: WithFloat32 incorrectly encoded the boolean as %v", s) } } func TestWithFloat64_SetsTheBody(t *testing.T) { r, err := Prepare(&http.Request{}, WithFloat64(42.0)) if err != nil { t.Fatalf("autorest: WithFloat64 failed with error (%v)", err) } s, err := ioutil.ReadAll(r.Body) if err != nil { t.Fatalf("autorest: WithFloat64 failed with error (%v)", err) } if r.ContentLength != int64(len(fmt.Sprintf("%v", 42.0))) { t.Fatalf("autorest: WithFloat64 set Content-Length to %v, expected %v", r.ContentLength, int64(len(fmt.Sprintf("%v", 42.0)))) } v, err := strconv.ParseFloat(string(s), 64) if err != nil || v != float64(42.0) { t.Fatalf("autorest: WithFloat64 incorrectly encoded the boolean as %v", s) } } func TestWithInt32_SetsTheBody(t *testing.T) { r, err := Prepare(&http.Request{}, WithInt32(42)) if err != nil { t.Fatalf("autorest: WithInt32 failed with error (%v)", err) } s, err := ioutil.ReadAll(r.Body) if err != nil { t.Fatalf("autorest: WithInt32 failed with error (%v)", err) } if r.ContentLength != int64(len(fmt.Sprintf("%v", 42))) { t.Fatalf("autorest: WithInt32 set Content-Length to %v, expected %v", r.ContentLength, int64(len(fmt.Sprintf("%v", 42)))) } v, err := strconv.ParseInt(string(s), 10, 32) if err != nil || int32(v) != int32(42) { t.Fatalf("autorest: WithInt32 incorrectly encoded the boolean as %v", s) } } func TestWithInt64_SetsTheBody(t *testing.T) { r, err := Prepare(&http.Request{}, WithInt64(42)) if err != nil { t.Fatalf("autorest: WithInt64 failed with error (%v)", err) } s, err := ioutil.ReadAll(r.Body) if err != nil { t.Fatalf("autorest: WithInt64 failed with error (%v)", err) } if r.ContentLength != int64(len(fmt.Sprintf("%v", 42))) { t.Fatalf("autorest: WithInt64 set Content-Length to %v, expected %v", r.ContentLength, int64(len(fmt.Sprintf("%v", 42)))) } v, err := strconv.ParseInt(string(s), 10, 64) if err != nil || v != int64(42) { t.Fatalf("autorest: WithInt64 incorrectly encoded the boolean as %v", s) } } func TestWithString_SetsTheBody(t *testing.T) { r, err := Prepare(&http.Request{}, WithString("value")) if err != nil { t.Fatalf("autorest: WithString failed with error (%v)", err) } s, err := ioutil.ReadAll(r.Body) if err != nil { t.Fatalf("autorest: WithString failed with error (%v)", err) } if r.ContentLength != int64(len("value")) { t.Fatalf("autorest: WithString set Content-Length to %v, expected %v", r.ContentLength, int64(len("value"))) } if string(s) != "value" { t.Fatalf("autorest: WithString incorrectly encoded the string as %v", s) } } func TestWithJSONSetsContentLength(t *testing.T) { r, err := Prepare(&http.Request{}, WithJSON(&mocks.T{Name: "Rob Pike", Age: 42})) if err != nil { t.Fatalf("autorest: WithJSON failed with error (%v)", err) } b, err := ioutil.ReadAll(r.Body) if err != nil { t.Fatalf("autorest: WithJSON failed with error (%v)", err) } if r.ContentLength != int64(len(b)) { t.Fatalf("autorest:WithJSON set Content-Length to %v, expected %v", r.ContentLength, len(b)) } } func TestWithHeaderAllocatesHeaders(t *testing.T) { r, err := Prepare(mocks.NewRequest(), WithHeader("x-foo", "bar")) if err != nil { t.Fatalf("autorest: WithHeader failed (%v)", err) } if r.Header.Get("x-foo") != "bar" { t.Fatalf("autorest: WithHeader failed to add header (%s=%s)", "x-foo", r.Header.Get("x-foo")) } } func TestWithPathCatchesNilURL(t *testing.T) { _, err := Prepare(&http.Request{}, WithPath("a")) if err == nil { t.Fatalf("autorest: WithPath failed to catch a nil URL") } } func TestWithEscapedPathParametersCatchesNilURL(t *testing.T) { _, err := Prepare(&http.Request{}, WithEscapedPathParameters("", map[string]interface{}{"foo": "bar"})) if err == nil { t.Fatalf("autorest: WithEscapedPathParameters failed to catch a nil URL") } } func TestWithPathParametersCatchesNilURL(t *testing.T) { _, err := Prepare(&http.Request{}, WithPathParameters("", map[string]interface{}{"foo": "bar"})) if err == nil { t.Fatalf("autorest: WithPathParameters failed to catch a nil URL") } } func TestWithQueryParametersCatchesNilURL(t *testing.T) { _, err := Prepare(&http.Request{}, WithQueryParameters(map[string]interface{}{"foo": "bar"})) if err == nil { t.Fatalf("autorest: WithQueryParameters failed to catch a nil URL") } } func TestModifyingExistingRequest(t *testing.T) { r, err := Prepare(mocks.NewRequestForURL("https://bing.com"), WithPath("search"), WithQueryParameters(map[string]interface{}{"q": "golang"})) if err != nil { t.Fatalf("autorest: Preparing an existing request returned an error (%v)", err) } if r.URL.Host != "bing.com" { t.Fatalf("autorest: Preparing an existing request failed when setting the host (%s)", r.URL) } if r.URL.Path != "/search" { t.Fatalf("autorest: Preparing an existing request failed when setting the path (%s)", r.URL.Path) } if r.URL.RawQuery != "q=golang" { t.Fatalf("autorest: Preparing an existing request failed when setting the query parameters (%s)", r.URL.RawQuery) } } func TestModifyingRequestWithExistingQueryParameters(t *testing.T) { r, err := Prepare( mocks.NewRequestForURL("https://bing.com"), WithPath("search"), WithQueryParameters(map[string]interface{}{"q": "golang the best"}), WithQueryParameters(map[string]interface{}{"pq": "golang+encoded"}), WithQueryParameters(map[string]interface{}{"zq": []string{"one", "two"}}), ) if err != nil { t.Fatalf("autorest: Preparing an existing request returned an error (%v)", err) } if r.URL.Host != "bing.com" { t.Fatalf("autorest: Preparing an existing request failed when setting the host (%s)", r.URL) } if r.URL.Path != "/search" { t.Fatalf("autorest: Preparing an existing request failed when setting the path (%s)", r.URL.Path) } if r.URL.RawQuery != "pq=golang+encoded&q=golang+the+best&zq=one&zq=two" { t.Fatalf("autorest: Preparing an existing request failed when setting the query parameters (%s)", r.URL.RawQuery) } } func TestGetPrepareDecorators(t *testing.T) { pd := GetPrepareDecorators(context.Background()) if l := len(pd); l != 0 { t.Fatalf("expected zero length but got %d", l) } pd = GetPrepareDecorators(context.Background(), WithNothing(), AsFormURLEncoded()) if l := len(pd); l != 2 { t.Fatalf("expected length of two but got %d", l) } } func TestWithPrepareDecorators(t *testing.T) { ctx := WithPrepareDecorators(context.Background(), []PrepareDecorator{WithUserAgent("somestring")}) pd := GetPrepareDecorators(ctx) if l := len(pd); l != 1 { t.Fatalf("expected length of one but got %d", l) } pd = GetPrepareDecorators(ctx, WithNothing(), WithNothing()) if l := len(pd); l != 1 { t.Fatalf("expected length of one but got %d", l) } } golang-github-azure-go-autorest-14.1.1/autorest/responder.go000066400000000000000000000226671367372352400241470ustar00rootroot00000000000000package autorest // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "bytes" "encoding/json" "encoding/xml" "fmt" "io" "io/ioutil" "net/http" "strings" ) // Responder is the interface that wraps the Respond method. // // Respond accepts and reacts to an http.Response. Implementations must ensure to not share or hold // state since Responders may be shared and re-used. type Responder interface { Respond(*http.Response) error } // ResponderFunc is a method that implements the Responder interface. type ResponderFunc func(*http.Response) error // Respond implements the Responder interface on ResponderFunc. func (rf ResponderFunc) Respond(r *http.Response) error { return rf(r) } // RespondDecorator takes and possibly decorates, by wrapping, a Responder. Decorators may react to // the http.Response and pass it along or, first, pass the http.Response along then react. type RespondDecorator func(Responder) Responder // CreateResponder creates, decorates, and returns a Responder. Without decorators, the returned // Responder returns the passed http.Response unmodified. Responders may or may not be safe to share // and re-used: It depends on the applied decorators. For example, a standard decorator that closes // the response body is fine to share whereas a decorator that reads the body into a passed struct // is not. // // To prevent memory leaks, ensure that at least one Responder closes the response body. func CreateResponder(decorators ...RespondDecorator) Responder { return DecorateResponder( Responder(ResponderFunc(func(r *http.Response) error { return nil })), decorators...) } // DecorateResponder accepts a Responder and a, possibly empty, set of RespondDecorators, which it // applies to the Responder. Decorators are applied in the order received, but their affect upon the // request depends on whether they are a pre-decorator (react to the http.Response and then pass it // along) or a post-decorator (pass the http.Response along and then react). func DecorateResponder(r Responder, decorators ...RespondDecorator) Responder { for _, decorate := range decorators { r = decorate(r) } return r } // Respond accepts an http.Response and a, possibly empty, set of RespondDecorators. // It creates a Responder from the decorators it then applies to the passed http.Response. func Respond(r *http.Response, decorators ...RespondDecorator) error { if r == nil { return nil } return CreateResponder(decorators...).Respond(r) } // ByIgnoring returns a RespondDecorator that ignores the passed http.Response passing it unexamined // to the next RespondDecorator. func ByIgnoring() RespondDecorator { return func(r Responder) Responder { return ResponderFunc(func(resp *http.Response) error { return r.Respond(resp) }) } } // ByCopying copies the contents of the http.Response Body into the passed bytes.Buffer as // the Body is read. func ByCopying(b *bytes.Buffer) RespondDecorator { return func(r Responder) Responder { return ResponderFunc(func(resp *http.Response) error { err := r.Respond(resp) if err == nil && resp != nil && resp.Body != nil { resp.Body = TeeReadCloser(resp.Body, b) } return err }) } } // ByDiscardingBody returns a RespondDecorator that first invokes the passed Responder after which // it copies the remaining bytes (if any) in the response body to ioutil.Discard. Since the passed // Responder is invoked prior to discarding the response body, the decorator may occur anywhere // within the set. func ByDiscardingBody() RespondDecorator { return func(r Responder) Responder { return ResponderFunc(func(resp *http.Response) error { err := r.Respond(resp) if err == nil && resp != nil && resp.Body != nil { if _, err := io.Copy(ioutil.Discard, resp.Body); err != nil { return fmt.Errorf("Error discarding the response body: %v", err) } } return err }) } } // ByClosing returns a RespondDecorator that first invokes the passed Responder after which it // closes the response body. Since the passed Responder is invoked prior to closing the response // body, the decorator may occur anywhere within the set. func ByClosing() RespondDecorator { return func(r Responder) Responder { return ResponderFunc(func(resp *http.Response) error { err := r.Respond(resp) if resp != nil && resp.Body != nil { if err := resp.Body.Close(); err != nil { return fmt.Errorf("Error closing the response body: %v", err) } } return err }) } } // ByClosingIfError returns a RespondDecorator that first invokes the passed Responder after which // it closes the response if the passed Responder returns an error and the response body exists. func ByClosingIfError() RespondDecorator { return func(r Responder) Responder { return ResponderFunc(func(resp *http.Response) error { err := r.Respond(resp) if err != nil && resp != nil && resp.Body != nil { if err := resp.Body.Close(); err != nil { return fmt.Errorf("Error closing the response body: %v", err) } } return err }) } } // ByUnmarshallingBytes returns a RespondDecorator that copies the Bytes returned in the // response Body into the value pointed to by v. func ByUnmarshallingBytes(v *[]byte) RespondDecorator { return func(r Responder) Responder { return ResponderFunc(func(resp *http.Response) error { err := r.Respond(resp) if err == nil { bytes, errInner := ioutil.ReadAll(resp.Body) if errInner != nil { err = fmt.Errorf("Error occurred reading http.Response#Body - Error = '%v'", errInner) } else { *v = bytes } } return err }) } } // ByUnmarshallingJSON returns a RespondDecorator that decodes a JSON document returned in the // response Body into the value pointed to by v. func ByUnmarshallingJSON(v interface{}) RespondDecorator { return func(r Responder) Responder { return ResponderFunc(func(resp *http.Response) error { err := r.Respond(resp) if err == nil { b, errInner := ioutil.ReadAll(resp.Body) // Some responses might include a BOM, remove for successful unmarshalling b = bytes.TrimPrefix(b, []byte("\xef\xbb\xbf")) if errInner != nil { err = fmt.Errorf("Error occurred reading http.Response#Body - Error = '%v'", errInner) } else if len(strings.Trim(string(b), " ")) > 0 { errInner = json.Unmarshal(b, v) if errInner != nil { err = fmt.Errorf("Error occurred unmarshalling JSON - Error = '%v' JSON = '%s'", errInner, string(b)) } } } return err }) } } // ByUnmarshallingXML returns a RespondDecorator that decodes a XML document returned in the // response Body into the value pointed to by v. func ByUnmarshallingXML(v interface{}) RespondDecorator { return func(r Responder) Responder { return ResponderFunc(func(resp *http.Response) error { err := r.Respond(resp) if err == nil { b, errInner := ioutil.ReadAll(resp.Body) if errInner != nil { err = fmt.Errorf("Error occurred reading http.Response#Body - Error = '%v'", errInner) } else { errInner = xml.Unmarshal(b, v) if errInner != nil { err = fmt.Errorf("Error occurred unmarshalling Xml - Error = '%v' Xml = '%s'", errInner, string(b)) } } } return err }) } } // WithErrorUnlessStatusCode returns a RespondDecorator that emits an error unless the response // StatusCode is among the set passed. On error, response body is fully read into a buffer and // presented in the returned error, as well as in the response body. func WithErrorUnlessStatusCode(codes ...int) RespondDecorator { return func(r Responder) Responder { return ResponderFunc(func(resp *http.Response) error { err := r.Respond(resp) if err == nil && !ResponseHasStatusCode(resp, codes...) { derr := NewErrorWithResponse("autorest", "WithErrorUnlessStatusCode", resp, "%v %v failed with %s", resp.Request.Method, resp.Request.URL, resp.Status) if resp.Body != nil { defer resp.Body.Close() b, _ := ioutil.ReadAll(resp.Body) derr.ServiceError = b resp.Body = ioutil.NopCloser(bytes.NewReader(b)) } err = derr } return err }) } } // WithErrorUnlessOK returns a RespondDecorator that emits an error if the response StatusCode is // anything other than HTTP 200. func WithErrorUnlessOK() RespondDecorator { return WithErrorUnlessStatusCode(http.StatusOK) } // ExtractHeader extracts all values of the specified header from the http.Response. It returns an // empty string slice if the passed http.Response is nil or the header does not exist. func ExtractHeader(header string, resp *http.Response) []string { if resp != nil && resp.Header != nil { return resp.Header[http.CanonicalHeaderKey(header)] } return nil } // ExtractHeaderValue extracts the first value of the specified header from the http.Response. It // returns an empty string if the passed http.Response is nil or the header does not exist. func ExtractHeaderValue(header string, resp *http.Response) string { h := ExtractHeader(header, resp) if len(h) > 0 { return h[0] } return "" } golang-github-azure-go-autorest-14.1.1/autorest/responder_test.go000066400000000000000000000416641367372352400252040ustar00rootroot00000000000000package autorest // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "reflect" "strings" "testing" "github.com/Azure/go-autorest/autorest/mocks" ) func ExampleWithErrorUnlessOK() { r := mocks.NewResponse() r.Request = mocks.NewRequest() // Respond and leave the response body open (for a subsequent responder to close) err := Respond(r, WithErrorUnlessOK(), ByDiscardingBody(), ByClosingIfError()) if err == nil { fmt.Printf("%s of %s returned HTTP 200", r.Request.Method, r.Request.URL) // Complete handling the response and close the body Respond(r, ByDiscardingBody(), ByClosing()) } // Output: GET of https://microsoft.com/a/b/c/ returned HTTP 200 } func TestByUnmarshallingBytes(t *testing.T) { expected := []byte("Lorem Ipsum Dolor") // we'll create a fixed-sized array here, since that's the expectation bytes := make([]byte, len(expected)) Respond(mocks.NewResponseWithBytes(expected), ByUnmarshallingBytes(&bytes), ByClosing()) if len(bytes) != len(expected) { t.Fatalf("Expected Response to be %d bytes but got %d bytes", len(expected), len(bytes)) } if !reflect.DeepEqual(expected, bytes) { t.Fatalf("Expected Response to be %s but got %s", expected, bytes) } } func ExampleByUnmarshallingJSON() { c := ` { "name" : "Rob Pike", "age" : 42 } ` type V struct { Name string `json:"name"` Age int `json:"age"` } v := &V{} Respond(mocks.NewResponseWithContent(c), ByUnmarshallingJSON(v), ByClosing()) fmt.Printf("%s is %d years old\n", v.Name, v.Age) // Output: Rob Pike is 42 years old } func ExampleByUnmarshallingXML() { c := ` Rob Pike 42 ` type V struct { Name string `xml:"Name"` Age int `xml:"Age"` } v := &V{} Respond(mocks.NewResponseWithContent(c), ByUnmarshallingXML(v), ByClosing()) fmt.Printf("%s is %d years old\n", v.Name, v.Age) // Output: Rob Pike is 42 years old } func TestCreateResponderDoesNotModify(t *testing.T) { r1 := mocks.NewResponse() r2 := mocks.NewResponse() p := CreateResponder() err := p.Respond(r1) if err != nil { t.Fatalf("autorest: CreateResponder failed (%v)", err) } if !reflect.DeepEqual(r1, r2) { t.Fatalf("autorest: CreateResponder without decorators modified the response") } } func TestCreateResponderRunsDecoratorsInOrder(t *testing.T) { s := "" d := func(n int) RespondDecorator { return func(r Responder) Responder { return ResponderFunc(func(resp *http.Response) error { err := r.Respond(resp) if err == nil { s += fmt.Sprintf("%d", n) } return err }) } } p := CreateResponder(d(1), d(2), d(3)) err := p.Respond(&http.Response{}) if err != nil { t.Fatalf("autorest: Respond failed (%v)", err) } if s != "123" { t.Fatalf("autorest: CreateResponder invoked decorators in an incorrect order; expected '123', received '%s'", s) } } func TestByIgnoring(t *testing.T) { r := mocks.NewResponse() Respond(r, (func() RespondDecorator { return func(r Responder) Responder { return ResponderFunc(func(r2 *http.Response) error { r1 := mocks.NewResponse() if !reflect.DeepEqual(r1, r2) { t.Fatalf("autorest: ByIgnoring modified the HTTP Response -- received %v, expected %v", r2, r1) } return nil }) } })(), ByIgnoring(), ByClosing()) } func TestByCopying_Copies(t *testing.T) { r := mocks.NewResponseWithContent(jsonT) b := &bytes.Buffer{} err := Respond(r, ByCopying(b), ByUnmarshallingJSON(&mocks.T{}), ByClosing()) if err != nil { t.Fatalf("autorest: ByCopying returned an unexpected error -- %v", err) } if b.String() != jsonT { t.Fatalf("autorest: ByCopying failed to copy the bytes read") } } func TestByCopying_ReturnsNestedErrors(t *testing.T) { r := mocks.NewResponseWithContent(jsonT) r.Body.Close() err := Respond(r, ByCopying(&bytes.Buffer{}), ByUnmarshallingJSON(&mocks.T{}), ByClosing()) if err == nil { t.Fatalf("autorest: ByCopying failed to return the expected error") } } func TestByCopying_AcceptsNilReponse(t *testing.T) { r := mocks.NewResponse() Respond(r, (func() RespondDecorator { return func(r Responder) Responder { return ResponderFunc(func(resp *http.Response) error { resp.Body.Close() r.Respond(nil) return nil }) } })(), ByCopying(&bytes.Buffer{})) } func TestByCopying_AcceptsNilBody(t *testing.T) { r := mocks.NewResponse() Respond(r, (func() RespondDecorator { return func(r Responder) Responder { return ResponderFunc(func(resp *http.Response) error { resp.Body.Close() resp.Body = nil r.Respond(resp) return nil }) } })(), ByCopying(&bytes.Buffer{})) } func TestByClosing(t *testing.T) { r := mocks.NewResponse() err := Respond(r, ByClosing()) if err != nil { t.Fatalf("autorest: ByClosing failed (%v)", err) } if r.Body.(*mocks.Body).IsOpen() { t.Fatalf("autorest: ByClosing did not close the response body") } } func TestByClosingAcceptsNilResponse(t *testing.T) { r := mocks.NewResponse() Respond(r, (func() RespondDecorator { return func(r Responder) Responder { return ResponderFunc(func(resp *http.Response) error { resp.Body.Close() r.Respond(nil) return nil }) } })(), ByClosing()) } func TestByClosingAcceptsNilBody(t *testing.T) { r := mocks.NewResponse() Respond(r, (func() RespondDecorator { return func(r Responder) Responder { return ResponderFunc(func(resp *http.Response) error { resp.Body.Close() resp.Body = nil r.Respond(resp) return nil }) } })(), ByClosing()) } func TestByClosingClosesEvenAfterErrors(t *testing.T) { var e error r := mocks.NewResponse() Respond(r, withErrorRespondDecorator(&e), ByClosing()) if r.Body.(*mocks.Body).IsOpen() { t.Fatalf("autorest: ByClosing did not close the response body after an error occurred") } } func TestByClosingClosesReturnsNestedErrors(t *testing.T) { var e error r := mocks.NewResponse() err := Respond(r, withErrorRespondDecorator(&e), ByClosing()) if err == nil || !reflect.DeepEqual(e, err) { t.Fatalf("autorest: ByClosing failed to return a nested error") } } func TestByClosingIfErrorAcceptsNilResponse(t *testing.T) { var e error r := mocks.NewResponse() Respond(r, withErrorRespondDecorator(&e), (func() RespondDecorator { return func(r Responder) Responder { return ResponderFunc(func(resp *http.Response) error { resp.Body.Close() r.Respond(nil) return nil }) } })(), ByClosingIfError()) } func TestByClosingIfErrorAcceptsNilBody(t *testing.T) { var e error r := mocks.NewResponse() Respond(r, withErrorRespondDecorator(&e), (func() RespondDecorator { return func(r Responder) Responder { return ResponderFunc(func(resp *http.Response) error { resp.Body.Close() resp.Body = nil r.Respond(resp) return nil }) } })(), ByClosingIfError()) } func TestByClosingIfErrorClosesIfAnErrorOccurs(t *testing.T) { var e error r := mocks.NewResponse() Respond(r, withErrorRespondDecorator(&e), ByClosingIfError()) if r.Body.(*mocks.Body).IsOpen() { t.Fatalf("autorest: ByClosingIfError did not close the response body after an error occurred") } } func TestByClosingIfErrorDoesNotClosesIfNoErrorOccurs(t *testing.T) { r := mocks.NewResponse() Respond(r, ByClosingIfError()) if !r.Body.(*mocks.Body).IsOpen() { t.Fatalf("autorest: ByClosingIfError closed the response body even though no error occurred") } } func TestByDiscardingBody(t *testing.T) { r := mocks.NewResponse() err := Respond(r, ByDiscardingBody()) if err != nil { t.Fatalf("autorest: ByDiscardingBody failed (%v)", err) } buf, err := ioutil.ReadAll(r.Body) if err != nil { t.Fatalf("autorest: Reading result of ByDiscardingBody failed (%v)", err) } if len(buf) != 0 { t.Logf("autorest: Body was not empty after calling ByDiscardingBody.") t.Fail() } } func TestByDiscardingBodyAcceptsNilResponse(t *testing.T) { var e error r := mocks.NewResponse() Respond(r, withErrorRespondDecorator(&e), (func() RespondDecorator { return func(r Responder) Responder { return ResponderFunc(func(resp *http.Response) error { resp.Body.Close() r.Respond(nil) return nil }) } })(), ByDiscardingBody()) } func TestByDiscardingBodyAcceptsNilBody(t *testing.T) { var e error r := mocks.NewResponse() Respond(r, withErrorRespondDecorator(&e), (func() RespondDecorator { return func(r Responder) Responder { return ResponderFunc(func(resp *http.Response) error { resp.Body.Close() resp.Body = nil r.Respond(resp) return nil }) } })(), ByDiscardingBody()) } func TestByUnmarshallingJSON(t *testing.T) { v := &mocks.T{} r := mocks.NewResponseWithContent(jsonT) err := Respond(r, ByUnmarshallingJSON(v), ByClosing()) if err != nil { t.Fatalf("autorest: ByUnmarshallingJSON failed (%v)", err) } if v.Name != "Rob Pike" || v.Age != 42 { t.Fatalf("autorest: ByUnmarshallingJSON failed to properly unmarshal") } } func TestByUnmarshallingJSON_HandlesReadErrors(t *testing.T) { v := &mocks.T{} r := mocks.NewResponseWithContent(jsonT) r.Body.(*mocks.Body).Close() err := Respond(r, ByUnmarshallingJSON(v), ByClosing()) if err == nil { t.Fatalf("autorest: ByUnmarshallingJSON failed to receive / respond to read error") } } func TestByUnmarshallingJSONIncludesJSONInErrors(t *testing.T) { v := &mocks.T{} j := jsonT[0 : len(jsonT)-2] r := mocks.NewResponseWithContent(j) err := Respond(r, ByUnmarshallingJSON(v), ByClosing()) if err == nil || !strings.Contains(err.Error(), j) { t.Fatalf("autorest: ByUnmarshallingJSON failed to return JSON in error (%v)", err) } } func TestByUnmarshallingJSONEmptyInput(t *testing.T) { v := &mocks.T{} r := mocks.NewResponseWithContent(``) err := Respond(r, ByUnmarshallingJSON(v), ByClosing()) if err != nil { t.Fatalf("autorest: ByUnmarshallingJSON failed to return nil in case of empty JSON (%v)", err) } } func TestByUnmarshallingXML(t *testing.T) { v := &mocks.T{} r := mocks.NewResponseWithContent(xmlT) err := Respond(r, ByUnmarshallingXML(v), ByClosing()) if err != nil { t.Fatalf("autorest: ByUnmarshallingXML failed (%v)", err) } if v.Name != "Rob Pike" || v.Age != 42 { t.Fatalf("autorest: ByUnmarshallingXML failed to properly unmarshal") } } func TestByUnmarshallingXML_HandlesReadErrors(t *testing.T) { v := &mocks.T{} r := mocks.NewResponseWithContent(xmlT) r.Body.(*mocks.Body).Close() err := Respond(r, ByUnmarshallingXML(v), ByClosing()) if err == nil { t.Fatalf("autorest: ByUnmarshallingXML failed to receive / respond to read error") } } func TestByUnmarshallingXMLIncludesXMLInErrors(t *testing.T) { v := &mocks.T{} x := xmlT[0 : len(xmlT)-2] r := mocks.NewResponseWithContent(x) err := Respond(r, ByUnmarshallingXML(v), ByClosing()) if err == nil || !strings.Contains(err.Error(), x) { t.Fatalf("autorest: ByUnmarshallingXML failed to return XML in error (%v)", err) } } func TestRespondAcceptsNullResponse(t *testing.T) { err := Respond(nil) if err != nil { t.Fatalf("autorest: Respond returned an unexpected error when given a null Response (%v)", err) } } func TestWithErrorUnlessStatusCodeOKResponse(t *testing.T) { v := &mocks.T{} r := mocks.NewResponseWithContent(jsonT) err := Respond(r, WithErrorUnlessStatusCode(http.StatusOK), ByUnmarshallingJSON(v), ByClosing()) if err != nil { t.Fatalf("autorest: WithErrorUnlessStatusCode(http.StatusOK) failed on okay response. (%v)", err) } if v.Name != "Rob Pike" || v.Age != 42 { t.Fatalf("autorest: WithErrorUnlessStatusCode(http.StatusOK) corrupted the response body of okay response.") } } func TesWithErrorUnlessStatusCodeErrorResponse(t *testing.T) { v := &mocks.T{} e := &mocks.T{} r := mocks.NewResponseWithContent(jsonT) r.Status = "400 BadRequest" r.StatusCode = http.StatusBadRequest err := Respond(r, WithErrorUnlessStatusCode(http.StatusOK), ByUnmarshallingJSON(v), ByClosing()) if err == nil { t.Fatal("autorest: WithErrorUnlessStatusCode(http.StatusOK) did not return error, on a response to a bad request.") } var errorRespBody []byte if derr, ok := err.(DetailedError); !ok { t.Fatalf("autorest: WithErrorUnlessStatusCode(http.StatusOK) got wrong error type : %T, expected: DetailedError, on a response to a bad request.", err) } else { errorRespBody = derr.ServiceError } if errorRespBody == nil { t.Fatalf("autorest: WithErrorUnlessStatusCode(http.StatusOK) ServiceError not returned in DetailedError on a response to a bad request.") } err = json.Unmarshal(errorRespBody, e) if err != nil { t.Fatalf("autorest: WithErrorUnlessStatusCode(http.StatusOK) cannot parse error returned in ServiceError into json. %v", err) } expected := &mocks.T{Name: "Rob Pike", Age: 42} if e != expected { t.Fatalf("autorest: WithErrorUnlessStatusCode(http.StatusOK wrong value from parsed ServiceError: got=%#v expected=%#v", e, expected) } } func TestWithErrorUnlessStatusCode(t *testing.T) { r := mocks.NewResponse() r.Request = mocks.NewRequest() r.Status = "400 BadRequest" r.StatusCode = http.StatusBadRequest err := Respond(r, WithErrorUnlessStatusCode(http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError), ByClosingIfError()) if err != nil { t.Fatalf("autorest: WithErrorUnlessStatusCode returned an error (%v) for an acceptable status code (%s)", err, r.Status) } } func TestWithErrorUnlessStatusCodeEmitsErrorForUnacceptableStatusCode(t *testing.T) { r := mocks.NewResponse() r.Request = mocks.NewRequest() r.Status = "400 BadRequest" r.StatusCode = http.StatusBadRequest err := Respond(r, WithErrorUnlessStatusCode(http.StatusOK, http.StatusUnauthorized, http.StatusInternalServerError), ByClosingIfError()) if err == nil { t.Fatalf("autorest: WithErrorUnlessStatusCode failed to return an error for an unacceptable status code (%s)", r.Status) } } func TestWithErrorUnlessOK(t *testing.T) { r := mocks.NewResponse() r.Request = mocks.NewRequest() err := Respond(r, WithErrorUnlessOK(), ByClosingIfError()) if err != nil { t.Fatalf("autorest: WithErrorUnlessOK returned an error for OK status code (%v)", err) } } func TestWithErrorUnlessOKEmitsErrorIfNotOK(t *testing.T) { r := mocks.NewResponse() r.Request = mocks.NewRequest() r.Status = "400 BadRequest" r.StatusCode = http.StatusBadRequest err := Respond(r, WithErrorUnlessOK(), ByClosingIfError()) if err == nil { t.Fatalf("autorest: WithErrorUnlessOK failed to return an error for a non-OK status code (%v)", err) } } func TestExtractHeader(t *testing.T) { r := mocks.NewResponse() v := []string{"v1", "v2", "v3"} mocks.SetResponseHeaderValues(r, mocks.TestHeader, v) if !reflect.DeepEqual(ExtractHeader(mocks.TestHeader, r), v) { t.Fatalf("autorest: ExtractHeader failed to retrieve the expected header -- expected [%s]%v, received [%s]%v", mocks.TestHeader, v, mocks.TestHeader, ExtractHeader(mocks.TestHeader, r)) } } func TestExtractHeaderHandlesMissingHeader(t *testing.T) { var v []string r := mocks.NewResponse() if !reflect.DeepEqual(ExtractHeader(mocks.TestHeader, r), v) { t.Fatalf("autorest: ExtractHeader failed to handle a missing header -- expected %v, received %v", v, ExtractHeader(mocks.TestHeader, r)) } } func TestExtractHeaderValue(t *testing.T) { r := mocks.NewResponse() v := "v1" mocks.SetResponseHeader(r, mocks.TestHeader, v) if ExtractHeaderValue(mocks.TestHeader, r) != v { t.Fatalf("autorest: ExtractHeader failed to retrieve the expected header -- expected [%s]%v, received [%s]%v", mocks.TestHeader, v, mocks.TestHeader, ExtractHeaderValue(mocks.TestHeader, r)) } } func TestExtractHeaderValueHandlesMissingHeader(t *testing.T) { r := mocks.NewResponse() v := "" if ExtractHeaderValue(mocks.TestHeader, r) != v { t.Fatalf("autorest: ExtractHeader failed to retrieve the expected header -- expected [%s]%v, received [%s]%v", mocks.TestHeader, v, mocks.TestHeader, ExtractHeaderValue(mocks.TestHeader, r)) } } func TestExtractHeaderValueRetrievesFirstValue(t *testing.T) { r := mocks.NewResponse() v := []string{"v1", "v2", "v3"} mocks.SetResponseHeaderValues(r, mocks.TestHeader, v) if ExtractHeaderValue(mocks.TestHeader, r) != v[0] { t.Fatalf("autorest: ExtractHeader failed to retrieve the expected header -- expected [%s]%v, received [%s]%v", mocks.TestHeader, v[0], mocks.TestHeader, ExtractHeaderValue(mocks.TestHeader, r)) } } golang-github-azure-go-autorest-14.1.1/autorest/retriablerequest.go000066400000000000000000000026341367372352400255200ustar00rootroot00000000000000package autorest // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "bytes" "io" "io/ioutil" "net/http" ) // NewRetriableRequest returns a wrapper around an HTTP request that support retry logic. func NewRetriableRequest(req *http.Request) *RetriableRequest { return &RetriableRequest{req: req} } // Request returns the wrapped HTTP request. func (rr *RetriableRequest) Request() *http.Request { return rr.req } func (rr *RetriableRequest) prepareFromByteReader() (err error) { // fall back to making a copy (only do this once) b := []byte{} if rr.req.ContentLength > 0 { b = make([]byte, rr.req.ContentLength) _, err = io.ReadFull(rr.req.Body, b) if err != nil { return err } } else { b, err = ioutil.ReadAll(rr.req.Body) if err != nil { return err } } rr.br = bytes.NewReader(b) rr.req.Body = ioutil.NopCloser(rr.br) return err } golang-github-azure-go-autorest-14.1.1/autorest/retriablerequest_1.7.go000066400000000000000000000026621367372352400261060ustar00rootroot00000000000000// +build !go1.8 // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package autorest import ( "bytes" "io/ioutil" "net/http" ) // RetriableRequest provides facilities for retrying an HTTP request. type RetriableRequest struct { req *http.Request br *bytes.Reader } // Prepare signals that the request is about to be sent. func (rr *RetriableRequest) Prepare() (err error) { // preserve the request body; this is to support retry logic as // the underlying transport will always close the reqeust body if rr.req.Body != nil { if rr.br != nil { _, err = rr.br.Seek(0, 0 /*io.SeekStart*/) rr.req.Body = ioutil.NopCloser(rr.br) } if err != nil { return err } if rr.br == nil { // fall back to making a copy (only do this once) err = rr.prepareFromByteReader() } } return err } func removeRequestBody(req *http.Request) { req.Body = nil req.ContentLength = 0 } golang-github-azure-go-autorest-14.1.1/autorest/retriablerequest_1.8.go000066400000000000000000000033631367372352400261060ustar00rootroot00000000000000// +build go1.8 // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package autorest import ( "bytes" "io" "io/ioutil" "net/http" ) // RetriableRequest provides facilities for retrying an HTTP request. type RetriableRequest struct { req *http.Request rc io.ReadCloser br *bytes.Reader } // Prepare signals that the request is about to be sent. func (rr *RetriableRequest) Prepare() (err error) { // preserve the request body; this is to support retry logic as // the underlying transport will always close the reqeust body if rr.req.Body != nil { if rr.rc != nil { rr.req.Body = rr.rc } else if rr.br != nil { _, err = rr.br.Seek(0, io.SeekStart) rr.req.Body = ioutil.NopCloser(rr.br) } if err != nil { return err } if rr.req.GetBody != nil { // this will allow us to preserve the body without having to // make a copy. note we need to do this on each iteration rr.rc, err = rr.req.GetBody() if err != nil { return err } } else if rr.br == nil { // fall back to making a copy (only do this once) err = rr.prepareFromByteReader() } } return err } func removeRequestBody(req *http.Request) { req.Body = nil req.GetBody = nil req.ContentLength = 0 } golang-github-azure-go-autorest-14.1.1/autorest/sender.go000066400000000000000000000404121367372352400234120ustar00rootroot00000000000000package autorest // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "context" "crypto/tls" "fmt" "log" "math" "net/http" "net/http/cookiejar" "strconv" "time" "github.com/Azure/go-autorest/tracing" ) // used as a key type in context.WithValue() type ctxSendDecorators struct{} // WithSendDecorators adds the specified SendDecorators to the provided context. // If no SendDecorators are provided the context is unchanged. func WithSendDecorators(ctx context.Context, sendDecorator []SendDecorator) context.Context { if len(sendDecorator) == 0 { return ctx } return context.WithValue(ctx, ctxSendDecorators{}, sendDecorator) } // GetSendDecorators returns the SendDecorators in the provided context or the provided default SendDecorators. func GetSendDecorators(ctx context.Context, defaultSendDecorators ...SendDecorator) []SendDecorator { inCtx := ctx.Value(ctxSendDecorators{}) if sd, ok := inCtx.([]SendDecorator); ok { return sd } return defaultSendDecorators } // Sender is the interface that wraps the Do method to send HTTP requests. // // The standard http.Client conforms to this interface. type Sender interface { Do(*http.Request) (*http.Response, error) } // SenderFunc is a method that implements the Sender interface. type SenderFunc func(*http.Request) (*http.Response, error) // Do implements the Sender interface on SenderFunc. func (sf SenderFunc) Do(r *http.Request) (*http.Response, error) { return sf(r) } // SendDecorator takes and possibly decorates, by wrapping, a Sender. Decorators may affect the // http.Request and pass it along or, first, pass the http.Request along then react to the // http.Response result. type SendDecorator func(Sender) Sender // CreateSender creates, decorates, and returns, as a Sender, the default http.Client. func CreateSender(decorators ...SendDecorator) Sender { return DecorateSender(sender(tls.RenegotiateNever), decorators...) } // DecorateSender accepts a Sender and a, possibly empty, set of SendDecorators, which is applies to // the Sender. Decorators are applied in the order received, but their affect upon the request // depends on whether they are a pre-decorator (change the http.Request and then pass it along) or a // post-decorator (pass the http.Request along and react to the results in http.Response). func DecorateSender(s Sender, decorators ...SendDecorator) Sender { for _, decorate := range decorators { s = decorate(s) } return s } // Send sends, by means of the default http.Client, the passed http.Request, returning the // http.Response and possible error. It also accepts a, possibly empty, set of SendDecorators which // it will apply the http.Client before invoking the Do method. // // Send is a convenience method and not recommended for production. Advanced users should use // SendWithSender, passing and sharing their own Sender (e.g., instance of http.Client). // // Send will not poll or retry requests. func Send(r *http.Request, decorators ...SendDecorator) (*http.Response, error) { return SendWithSender(sender(tls.RenegotiateNever), r, decorators...) } // SendWithSender sends the passed http.Request, through the provided Sender, returning the // http.Response and possible error. It also accepts a, possibly empty, set of SendDecorators which // it will apply the http.Client before invoking the Do method. // // SendWithSender will not poll or retry requests. func SendWithSender(s Sender, r *http.Request, decorators ...SendDecorator) (*http.Response, error) { return DecorateSender(s, decorators...).Do(r) } func sender(renengotiation tls.RenegotiationSupport) Sender { // Use behaviour compatible with DefaultTransport, but require TLS minimum version. defaultTransport := http.DefaultTransport.(*http.Transport) transport := &http.Transport{ Proxy: defaultTransport.Proxy, DialContext: defaultTransport.DialContext, MaxIdleConns: defaultTransport.MaxIdleConns, IdleConnTimeout: defaultTransport.IdleConnTimeout, TLSHandshakeTimeout: defaultTransport.TLSHandshakeTimeout, ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout, TLSClientConfig: &tls.Config{ MinVersion: tls.VersionTLS12, Renegotiation: renengotiation, }, } var roundTripper http.RoundTripper = transport if tracing.IsEnabled() { roundTripper = tracing.NewTransport(transport) } j, _ := cookiejar.New(nil) return &http.Client{Jar: j, Transport: roundTripper} } // AfterDelay returns a SendDecorator that delays for the passed time.Duration before // invoking the Sender. The delay may be terminated by closing the optional channel on the // http.Request. If canceled, no further Senders are invoked. func AfterDelay(d time.Duration) SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { if !DelayForBackoff(d, 0, r.Context().Done()) { return nil, fmt.Errorf("autorest: AfterDelay canceled before full delay") } return s.Do(r) }) } } // AsIs returns a SendDecorator that invokes the passed Sender without modifying the http.Request. func AsIs() SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { return s.Do(r) }) } } // DoCloseIfError returns a SendDecorator that first invokes the passed Sender after which // it closes the response if the passed Sender returns an error and the response body exists. func DoCloseIfError() SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { resp, err := s.Do(r) if err != nil { Respond(resp, ByDiscardingBody(), ByClosing()) } return resp, err }) } } // DoErrorIfStatusCode returns a SendDecorator that emits an error if the response StatusCode is // among the set passed. Since these are artificial errors, the response body may still require // closing. func DoErrorIfStatusCode(codes ...int) SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { resp, err := s.Do(r) if err == nil && ResponseHasStatusCode(resp, codes...) { err = NewErrorWithResponse("autorest", "DoErrorIfStatusCode", resp, "%v %v failed with %s", resp.Request.Method, resp.Request.URL, resp.Status) } return resp, err }) } } // DoErrorUnlessStatusCode returns a SendDecorator that emits an error unless the response // StatusCode is among the set passed. Since these are artificial errors, the response body // may still require closing. func DoErrorUnlessStatusCode(codes ...int) SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { resp, err := s.Do(r) if err == nil && !ResponseHasStatusCode(resp, codes...) { err = NewErrorWithResponse("autorest", "DoErrorUnlessStatusCode", resp, "%v %v failed with %s", resp.Request.Method, resp.Request.URL, resp.Status) } return resp, err }) } } // DoPollForStatusCodes returns a SendDecorator that polls if the http.Response contains one of the // passed status codes. It expects the http.Response to contain a Location header providing the // URL at which to poll (using GET) and will poll until the time passed is equal to or greater than // the supplied duration. It will delay between requests for the duration specified in the // RetryAfter header or, if the header is absent, the passed delay. Polling may be canceled by // closing the optional channel on the http.Request. func DoPollForStatusCodes(duration time.Duration, delay time.Duration, codes ...int) SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (resp *http.Response, err error) { resp, err = s.Do(r) if err == nil && ResponseHasStatusCode(resp, codes...) { r, err = NewPollingRequestWithContext(r.Context(), resp) for err == nil && ResponseHasStatusCode(resp, codes...) { Respond(resp, ByDiscardingBody(), ByClosing()) resp, err = SendWithSender(s, r, AfterDelay(GetRetryAfter(resp, delay))) } } return resp, err }) } } // DoRetryForAttempts returns a SendDecorator that retries a failed request for up to the specified // number of attempts, exponentially backing off between requests using the supplied backoff // time.Duration (which may be zero). Retrying may be canceled by closing the optional channel on // the http.Request. func DoRetryForAttempts(attempts int, backoff time.Duration) SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (resp *http.Response, err error) { rr := NewRetriableRequest(r) for attempt := 0; attempt < attempts; attempt++ { err = rr.Prepare() if err != nil { return resp, err } DrainResponseBody(resp) resp, err = s.Do(rr.Request()) if err == nil { return resp, err } if !DelayForBackoff(backoff, attempt, r.Context().Done()) { return nil, r.Context().Err() } } return resp, err }) } } // Count429AsRetry indicates that a 429 response should be included as a retry attempt. var Count429AsRetry = true // Max429Delay is the maximum duration to wait between retries on a 429 if no Retry-After header was received. var Max429Delay time.Duration // DoRetryForStatusCodes returns a SendDecorator that retries for specified statusCodes for up to the specified // number of attempts, exponentially backing off between requests using the supplied backoff // time.Duration (which may be zero). Retrying may be canceled by cancelling the context on the http.Request. // NOTE: Code http.StatusTooManyRequests (429) will *not* be counted against the number of attempts. func DoRetryForStatusCodes(attempts int, backoff time.Duration, codes ...int) SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { return doRetryForStatusCodesImpl(s, r, Count429AsRetry, attempts, backoff, 0, codes...) }) } } // DoRetryForStatusCodesWithCap returns a SendDecorator that retries for specified statusCodes for up to the // specified number of attempts, exponentially backing off between requests using the supplied backoff // time.Duration (which may be zero). To cap the maximum possible delay between iterations specify a value greater // than zero for cap. Retrying may be canceled by cancelling the context on the http.Request. func DoRetryForStatusCodesWithCap(attempts int, backoff, cap time.Duration, codes ...int) SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { return doRetryForStatusCodesImpl(s, r, Count429AsRetry, attempts, backoff, cap, codes...) }) } } func doRetryForStatusCodesImpl(s Sender, r *http.Request, count429 bool, attempts int, backoff, cap time.Duration, codes ...int) (resp *http.Response, err error) { rr := NewRetriableRequest(r) // Increment to add the first call (attempts denotes number of retries) for attempt, delayCount := 0, 0; attempt < attempts+1; { err = rr.Prepare() if err != nil { return } DrainResponseBody(resp) resp, err = s.Do(rr.Request()) // we want to retry if err is not nil (e.g. transient network failure). note that for failed authentication // resp and err will both have a value, so in this case we don't want to retry as it will never succeed. if err == nil && !ResponseHasStatusCode(resp, codes...) || IsTokenRefreshError(err) { return resp, err } delayed := DelayWithRetryAfter(resp, r.Context().Done()) // if this was a 429 set the delay cap as specified. // applicable only in the absence of a retry-after header. if resp != nil && resp.StatusCode == http.StatusTooManyRequests { cap = Max429Delay } if !delayed && !DelayForBackoffWithCap(backoff, cap, delayCount, r.Context().Done()) { return resp, r.Context().Err() } // when count429 == false don't count a 429 against the number // of attempts so that we continue to retry until it succeeds if count429 || (resp == nil || resp.StatusCode != http.StatusTooManyRequests) { attempt++ } // delay count is tracked separately from attempts to // ensure that 429 participates in exponential back-off delayCount++ } return resp, err } // DelayWithRetryAfter invokes time.After for the duration specified in the "Retry-After" header. // The value of Retry-After can be either the number of seconds or a date in RFC1123 format. // The function returns true after successfully waiting for the specified duration. If there is // no Retry-After header or the wait is cancelled the return value is false. func DelayWithRetryAfter(resp *http.Response, cancel <-chan struct{}) bool { if resp == nil { return false } var dur time.Duration ra := resp.Header.Get("Retry-After") if retryAfter, _ := strconv.Atoi(ra); retryAfter > 0 { dur = time.Duration(retryAfter) * time.Second } else if t, err := time.Parse(time.RFC1123, ra); err == nil { dur = t.Sub(time.Now()) } if dur > 0 { select { case <-time.After(dur): return true case <-cancel: return false } } return false } // DoRetryForDuration returns a SendDecorator that retries the request until the total time is equal // to or greater than the specified duration, exponentially backing off between requests using the // supplied backoff time.Duration (which may be zero). Retrying may be canceled by closing the // optional channel on the http.Request. func DoRetryForDuration(d time.Duration, backoff time.Duration) SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (resp *http.Response, err error) { rr := NewRetriableRequest(r) end := time.Now().Add(d) for attempt := 0; time.Now().Before(end); attempt++ { err = rr.Prepare() if err != nil { return resp, err } DrainResponseBody(resp) resp, err = s.Do(rr.Request()) if err == nil { return resp, err } if !DelayForBackoff(backoff, attempt, r.Context().Done()) { return nil, r.Context().Err() } } return resp, err }) } } // WithLogging returns a SendDecorator that implements simple before and after logging of the // request. func WithLogging(logger *log.Logger) SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { logger.Printf("Sending %s %s", r.Method, r.URL) resp, err := s.Do(r) if err != nil { logger.Printf("%s %s received error '%v'", r.Method, r.URL, err) } else { logger.Printf("%s %s received %s", r.Method, r.URL, resp.Status) } return resp, err }) } } // DelayForBackoff invokes time.After for the supplied backoff duration raised to the power of // passed attempt (i.e., an exponential backoff delay). Backoff duration is in seconds and can set // to zero for no delay. The delay may be canceled by closing the passed channel. If terminated early, // returns false. // Note: Passing attempt 1 will result in doubling "backoff" duration. Treat this as a zero-based attempt // count. func DelayForBackoff(backoff time.Duration, attempt int, cancel <-chan struct{}) bool { return DelayForBackoffWithCap(backoff, 0, attempt, cancel) } // DelayForBackoffWithCap invokes time.After for the supplied backoff duration raised to the power of // passed attempt (i.e., an exponential backoff delay). Backoff duration is in seconds and can set // to zero for no delay. To cap the maximum possible delay specify a value greater than zero for cap. // The delay may be canceled by closing the passed channel. If terminated early, returns false. // Note: Passing attempt 1 will result in doubling "backoff" duration. Treat this as a zero-based attempt // count. func DelayForBackoffWithCap(backoff, cap time.Duration, attempt int, cancel <-chan struct{}) bool { d := time.Duration(backoff.Seconds()*math.Pow(2, float64(attempt))) * time.Second if cap > 0 && d > cap { d = cap } select { case <-time.After(d): return true case <-cancel: return false } } golang-github-azure-go-autorest-14.1.1/autorest/sender_test.go000066400000000000000000000705651367372352400244650ustar00rootroot00000000000000package autorest // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "bytes" "context" "fmt" "log" "net/http" "net/http/httptest" "os" "reflect" "sync" "testing" "time" "github.com/Azure/go-autorest/autorest/mocks" ) func ExampleSendWithSender() { r := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted) mocks.SetAcceptedHeaders(r) client := mocks.NewSender() client.AppendAndRepeatResponse(r, 10) logger := log.New(os.Stdout, "autorest: ", 0) na := NullAuthorizer{} req, _ := Prepare(&http.Request{}, AsGet(), WithBaseURL("https://microsoft.com/a/b/c/"), na.WithAuthorization()) r, _ = SendWithSender(client, req, WithLogging(logger), DoErrorIfStatusCode(http.StatusAccepted), DoCloseIfError(), DoRetryForAttempts(5, time.Duration(0))) Respond(r, ByDiscardingBody(), ByClosing()) // Output: // autorest: Sending GET https://microsoft.com/a/b/c/ // autorest: GET https://microsoft.com/a/b/c/ received 202 Accepted // autorest: Sending GET https://microsoft.com/a/b/c/ // autorest: GET https://microsoft.com/a/b/c/ received 202 Accepted // autorest: Sending GET https://microsoft.com/a/b/c/ // autorest: GET https://microsoft.com/a/b/c/ received 202 Accepted // autorest: Sending GET https://microsoft.com/a/b/c/ // autorest: GET https://microsoft.com/a/b/c/ received 202 Accepted // autorest: Sending GET https://microsoft.com/a/b/c/ // autorest: GET https://microsoft.com/a/b/c/ received 202 Accepted } func ExampleDoRetryForAttempts() { client := mocks.NewSender() client.SetAndRepeatError(fmt.Errorf("Faux Error"), 10) // Retry with backoff -- ensure returned Bodies are closed r, _ := SendWithSender(client, mocks.NewRequest(), DoCloseIfError(), DoRetryForAttempts(5, time.Duration(0))) Respond(r, ByDiscardingBody(), ByClosing()) fmt.Printf("Retry stopped after %d attempts", client.Attempts()) // Output: Retry stopped after 5 attempts } func ExampleDoErrorIfStatusCode() { client := mocks.NewSender() client.AppendAndRepeatResponse(mocks.NewResponseWithStatus("204 NoContent", http.StatusNoContent), 10) // Chain decorators to retry the request, up to five times, if the status code is 204 r, _ := SendWithSender(client, mocks.NewRequest(), DoErrorIfStatusCode(http.StatusNoContent), DoCloseIfError(), DoRetryForAttempts(5, time.Duration(0))) Respond(r, ByDiscardingBody(), ByClosing()) fmt.Printf("Retry stopped after %d attempts with code %s", client.Attempts(), r.Status) // Output: Retry stopped after 5 attempts with code 204 NoContent } func TestSendWithSenderRunsDecoratorsInOrder(t *testing.T) { client := mocks.NewSender() s := "" r, err := SendWithSender(client, mocks.NewRequest(), withMessage(&s, "a"), withMessage(&s, "b"), withMessage(&s, "c")) if err != nil { t.Fatalf("autorest: SendWithSender returned an error (%v)", err) } Respond(r, ByDiscardingBody(), ByClosing()) if s != "abc" { t.Fatalf("autorest: SendWithSender invoke decorators out of order; expected 'abc', received '%s'", s) } } func TestCreateSender(t *testing.T) { f := false s := CreateSender( (func() SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { f = true return nil, nil }) } })()) s.Do(&http.Request{}) if !f { t.Fatal("autorest: CreateSender failed to apply supplied decorator") } } func TestSend(t *testing.T) { f := false Send(&http.Request{}, (func() SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { f = true return nil, nil }) } })()) if !f { t.Fatal("autorest: Send failed to apply supplied decorator") } } func TestAfterDelayWaits(t *testing.T) { client := mocks.NewSender() d := 2 * time.Second tt := time.Now() r, _ := SendWithSender(client, mocks.NewRequest(), AfterDelay(d)) s := time.Since(tt) if s < d { t.Fatal("autorest: AfterDelay failed to wait for at least the specified duration") } Respond(r, ByDiscardingBody(), ByClosing()) } func TestAfterDelay_Cancels(t *testing.T) { client := mocks.NewSender() ctx, cancel := context.WithCancel(context.Background()) delay := 5 * time.Second var wg sync.WaitGroup wg.Add(1) start := time.Now() end := time.Now() var err error go func() { req := mocks.NewRequest() req = req.WithContext(ctx) _, err = SendWithSender(client, req, AfterDelay(delay)) end = time.Now() wg.Done() }() cancel() wg.Wait() time.Sleep(5 * time.Millisecond) if end.Sub(start) >= delay { t.Fatal("autorest: AfterDelay elapsed") } if err == nil { t.Fatal("autorest: AfterDelay didn't cancel") } } func TestAfterDelayDoesNotWaitTooLong(t *testing.T) { client := mocks.NewSender() d := 5 * time.Millisecond start := time.Now() r, _ := SendWithSender(client, mocks.NewRequest(), AfterDelay(d)) if time.Since(start) > (5 * d) { t.Fatal("autorest: AfterDelay waited too long (exceeded 5 times specified duration)") } Respond(r, ByDiscardingBody(), ByClosing()) } func TestAsIs(t *testing.T) { client := mocks.NewSender() r1 := mocks.NewResponse() client.AppendResponse(r1) r2, err := SendWithSender(client, mocks.NewRequest(), AsIs()) if err != nil { t.Fatalf("autorest: AsIs returned an unexpected error (%v)", err) } else if !reflect.DeepEqual(r1, r2) { t.Fatalf("autorest: AsIs modified the response -- received %v, expected %v", r2, r1) } Respond(r1, ByDiscardingBody(), ByClosing()) Respond(r2, ByDiscardingBody(), ByClosing()) } func TestDoCloseIfError(t *testing.T) { client := mocks.NewSender() client.AppendResponse(mocks.NewResponseWithStatus("400 BadRequest", http.StatusBadRequest)) r, _ := SendWithSender(client, mocks.NewRequest(), DoErrorIfStatusCode(http.StatusBadRequest), DoCloseIfError()) if r.Body.(*mocks.Body).IsOpen() { t.Fatal("autorest: Expected DoCloseIfError to close response body -- it was left open") } Respond(r, ByDiscardingBody(), ByClosing()) } func TestDoCloseIfErrorAcceptsNilResponse(t *testing.T) { client := mocks.NewSender() SendWithSender(client, mocks.NewRequest(), (func() SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { resp, err := s.Do(r) if err != nil { resp.Body.Close() } return nil, fmt.Errorf("Faux Error") }) } })(), DoCloseIfError()) } func TestDoCloseIfErrorAcceptsNilBody(t *testing.T) { client := mocks.NewSender() SendWithSender(client, mocks.NewRequest(), (func() SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { resp, err := s.Do(r) if err != nil { resp.Body.Close() } resp.Body = nil return resp, fmt.Errorf("Faux Error") }) } })(), DoCloseIfError()) } func TestDoErrorIfStatusCode(t *testing.T) { client := mocks.NewSender() client.AppendResponse(mocks.NewResponseWithStatus("400 BadRequest", http.StatusBadRequest)) r, err := SendWithSender(client, mocks.NewRequest(), DoErrorIfStatusCode(http.StatusBadRequest), DoCloseIfError()) if err == nil { t.Fatal("autorest: DoErrorIfStatusCode failed to emit an error for passed code") } Respond(r, ByDiscardingBody(), ByClosing()) } func TestDoErrorIfStatusCodeIgnoresStatusCodes(t *testing.T) { client := mocks.NewSender() client.AppendResponse(newAcceptedResponse()) r, err := SendWithSender(client, mocks.NewRequest(), DoErrorIfStatusCode(http.StatusBadRequest), DoCloseIfError()) if err != nil { t.Fatal("autorest: DoErrorIfStatusCode failed to ignore a status code") } Respond(r, ByDiscardingBody(), ByClosing()) } func TestDoErrorUnlessStatusCode(t *testing.T) { client := mocks.NewSender() client.AppendResponse(mocks.NewResponseWithStatus("400 BadRequest", http.StatusBadRequest)) r, err := SendWithSender(client, mocks.NewRequest(), DoErrorUnlessStatusCode(http.StatusAccepted), DoCloseIfError()) if err == nil { t.Fatal("autorest: DoErrorUnlessStatusCode failed to emit an error for an unknown status code") } Respond(r, ByDiscardingBody(), ByClosing()) } func TestDoErrorUnlessStatusCodeIgnoresStatusCodes(t *testing.T) { client := mocks.NewSender() client.AppendResponse(newAcceptedResponse()) r, err := SendWithSender(client, mocks.NewRequest(), DoErrorUnlessStatusCode(http.StatusAccepted), DoCloseIfError()) if err != nil { t.Fatal("autorest: DoErrorUnlessStatusCode emitted an error for a knonwn status code") } Respond(r, ByDiscardingBody(), ByClosing()) } func TestDoRetryForAttemptsStopsAfterSuccess(t *testing.T) { client := mocks.NewSender() r, err := SendWithSender(client, mocks.NewRequest(), DoRetryForAttempts(5, time.Duration(0))) if client.Attempts() != 1 { t.Fatalf("autorest: DoRetryForAttempts failed to stop after success -- expected attempts %v, actual %v", 1, client.Attempts()) } if err != nil { t.Fatalf("autorest: DoRetryForAttempts returned an unexpected error (%v)", err) } Respond(r, ByDiscardingBody(), ByClosing()) } func TestDoRetryForAttemptsStopsAfterAttempts(t *testing.T) { client := mocks.NewSender() client.SetAndRepeatError(fmt.Errorf("Faux Error"), 10) r, err := SendWithSender(client, mocks.NewRequest(), DoRetryForAttempts(5, time.Duration(0)), DoCloseIfError()) if err == nil { t.Fatal("autorest: Mock client failed to emit errors") } Respond(r, ByDiscardingBody(), ByClosing()) if client.Attempts() != 5 { t.Fatal("autorest: DoRetryForAttempts failed to stop after specified number of attempts") } } func TestDoRetryForAttemptsReturnsResponse(t *testing.T) { client := mocks.NewSender() client.SetError(fmt.Errorf("Faux Error")) r, err := SendWithSender(client, mocks.NewRequest(), DoRetryForAttempts(1, time.Duration(0))) if err == nil { t.Fatal("autorest: Mock client failed to emit errors") } if r == nil { t.Fatal("autorest: DoRetryForAttempts failed to return the underlying response") } Respond(r, ByDiscardingBody(), ByClosing()) } func TestDoRetryForDurationStopsAfterSuccess(t *testing.T) { client := mocks.NewSender() r, err := SendWithSender(client, mocks.NewRequest(), DoRetryForDuration(10*time.Millisecond, time.Duration(0))) if client.Attempts() != 1 { t.Fatalf("autorest: DoRetryForDuration failed to stop after success -- expected attempts %v, actual %v", 1, client.Attempts()) } if err != nil { t.Fatalf("autorest: DoRetryForDuration returned an unexpected error (%v)", err) } Respond(r, ByDiscardingBody(), ByClosing()) } func TestDoRetryForDurationStopsAfterDuration(t *testing.T) { client := mocks.NewSender() client.SetAndRepeatError(fmt.Errorf("Faux Error"), -1) d := 5 * time.Millisecond start := time.Now() r, err := SendWithSender(client, mocks.NewRequest(), DoRetryForDuration(d, time.Duration(0)), DoCloseIfError()) if err == nil { t.Fatal("autorest: Mock client failed to emit errors") } if time.Since(start) < d { t.Fatal("autorest: DoRetryForDuration failed stopped too soon") } Respond(r, ByDiscardingBody(), ByClosing()) } func TestDoRetryForDurationStopsWithinReason(t *testing.T) { client := mocks.NewSender() client.SetAndRepeatError(fmt.Errorf("Faux Error"), -1) d := 5 * time.Second start := time.Now() r, err := SendWithSender(client, mocks.NewRequest(), DoRetryForDuration(d, time.Duration(0)), DoCloseIfError()) if err == nil { t.Fatal("autorest: Mock client failed to emit errors") } if time.Since(start) > (5 * d) { t.Fatal("autorest: DoRetryForDuration failed stopped soon enough (exceeded 5 times specified duration)") } Respond(r, ByDiscardingBody(), ByClosing()) } func TestDoRetryForDurationReturnsResponse(t *testing.T) { client := mocks.NewSender() client.SetAndRepeatError(fmt.Errorf("Faux Error"), -1) r, err := SendWithSender(client, mocks.NewRequest(), DoRetryForDuration(10*time.Millisecond, time.Duration(0)), DoCloseIfError()) if err == nil { t.Fatal("autorest: Mock client failed to emit errors") } if r == nil { t.Fatal("autorest: DoRetryForDuration failed to return the underlying response") } Respond(r, ByDiscardingBody(), ByClosing()) } func TestDelayForBackoff(t *testing.T) { d := 2 * time.Second start := time.Now() DelayForBackoff(d, 0, nil) if time.Since(start) < d { t.Fatal("autorest: DelayForBackoff did not delay as long as expected") } } func TestDelayForBackoffWithCap(t *testing.T) { d := 2 * time.Second start := time.Now() DelayForBackoffWithCap(d, 1*time.Second, 0, nil) if time.Since(start) >= d { t.Fatal("autorest: DelayForBackoffWithCap delayed for too long") } } func TestDelayForBackoff_Cancels(t *testing.T) { cancel := make(chan struct{}) delay := 5 * time.Second var wg sync.WaitGroup wg.Add(1) start := time.Now() go func() { wg.Done() DelayForBackoff(delay, 0, cancel) }() wg.Wait() close(cancel) time.Sleep(5 * time.Millisecond) if time.Since(start) >= delay { t.Fatal("autorest: DelayForBackoff failed to cancel") } } func TestDelayForBackoffWithinReason(t *testing.T) { d := 5 * time.Second maxCoefficient := 2 start := time.Now() DelayForBackoff(d, 0, nil) if time.Since(start) > (time.Duration(maxCoefficient) * d) { t.Fatalf("autorest: DelayForBackoff delayed too long (exceeded %d times the specified duration)", maxCoefficient) } } func TestDoPollForStatusCodes_IgnoresUnspecifiedStatusCodes(t *testing.T) { client := mocks.NewSender() r, _ := SendWithSender(client, mocks.NewRequest(), DoPollForStatusCodes(time.Duration(0), time.Duration(0))) if client.Attempts() != 1 { t.Fatalf("autorest: Sender#DoPollForStatusCodes polled for unspecified status code") } Respond(r, ByDiscardingBody(), ByClosing()) } func TestDoPollForStatusCodes_PollsForSpecifiedStatusCodes(t *testing.T) { client := mocks.NewSender() client.AppendResponse(newAcceptedResponse()) r, _ := SendWithSender(client, mocks.NewRequest(), DoPollForStatusCodes(time.Millisecond, time.Millisecond, http.StatusAccepted)) if client.Attempts() != 2 { t.Fatalf("autorest: Sender#DoPollForStatusCodes failed to poll for specified status code") } Respond(r, ByDiscardingBody(), ByClosing()) } func TestDoPollForStatusCodes_CanBeCanceled(t *testing.T) { cancel := make(chan struct{}) delay := 5 * time.Second r := mocks.NewResponse() mocks.SetAcceptedHeaders(r) client := mocks.NewSender() client.AppendAndRepeatResponse(r, 100) var wg sync.WaitGroup wg.Add(1) start := time.Now() go func() { wg.Done() r, _ := SendWithSender(client, mocks.NewRequest(), DoPollForStatusCodes(time.Millisecond, time.Millisecond, http.StatusAccepted)) Respond(r, ByDiscardingBody(), ByClosing()) }() wg.Wait() close(cancel) time.Sleep(5 * time.Millisecond) if time.Since(start) >= delay { t.Fatalf("autorest: Sender#DoPollForStatusCodes failed to cancel") } } func TestDoPollForStatusCodes_ClosesAllNonreturnedResponseBodiesWhenPolling(t *testing.T) { resp := newAcceptedResponse() client := mocks.NewSender() client.AppendAndRepeatResponse(resp, 2) r, _ := SendWithSender(client, mocks.NewRequest(), DoPollForStatusCodes(time.Millisecond, time.Millisecond, http.StatusAccepted)) if resp.Body.(*mocks.Body).IsOpen() || resp.Body.(*mocks.Body).CloseAttempts() < 2 { t.Fatalf("autorest: Sender#DoPollForStatusCodes did not close unreturned response bodies") } Respond(r, ByDiscardingBody(), ByClosing()) } func TestDoPollForStatusCodes_LeavesLastResponseBodyOpen(t *testing.T) { client := mocks.NewSender() client.AppendResponse(newAcceptedResponse()) r, _ := SendWithSender(client, mocks.NewRequest(), DoPollForStatusCodes(time.Millisecond, time.Millisecond, http.StatusAccepted)) if !r.Body.(*mocks.Body).IsOpen() { t.Fatalf("autorest: Sender#DoPollForStatusCodes did not leave open the body of the last response") } Respond(r, ByDiscardingBody(), ByClosing()) } func TestDoPollForStatusCodes_StopsPollingAfterAnError(t *testing.T) { client := mocks.NewSender() client.AppendAndRepeatResponse(newAcceptedResponse(), 5) client.SetError(fmt.Errorf("Faux Error")) client.SetEmitErrorAfter(1) r, _ := SendWithSender(client, mocks.NewRequest(), DoPollForStatusCodes(time.Millisecond, time.Millisecond, http.StatusAccepted)) if client.Attempts() > 2 { t.Fatalf("autorest: Sender#DoPollForStatusCodes failed to stop polling after receiving an error") } Respond(r, ByDiscardingBody(), ByClosing()) } func TestDoPollForStatusCodes_ReturnsPollingError(t *testing.T) { client := mocks.NewSender() client.AppendAndRepeatResponse(newAcceptedResponse(), 5) client.SetError(fmt.Errorf("Faux Error")) client.SetEmitErrorAfter(1) r, err := SendWithSender(client, mocks.NewRequest(), DoPollForStatusCodes(time.Millisecond, time.Millisecond, http.StatusAccepted)) if err == nil { t.Fatalf("autorest: Sender#DoPollForStatusCodes failed to return error from polling") } Respond(r, ByDiscardingBody(), ByClosing()) } func TestWithLogging_Logs(t *testing.T) { buf := &bytes.Buffer{} logger := log.New(buf, "autorest: ", 0) client := mocks.NewSender() r, _ := SendWithSender(client, &http.Request{}, WithLogging(logger)) if buf.String() == "" { t.Fatal("autorest: Sender#WithLogging failed to log the request") } Respond(r, ByDiscardingBody(), ByClosing()) } func TestWithLogging_HandlesMissingResponse(t *testing.T) { buf := &bytes.Buffer{} logger := log.New(buf, "autorest: ", 0) client := mocks.NewSender() client.AppendResponse(nil) client.SetError(fmt.Errorf("Faux Error")) r, err := SendWithSender(client, &http.Request{}, WithLogging(logger)) if r != nil || err == nil { t.Fatal("autorest: Sender#WithLogging returned a valid response -- expecting nil") } if buf.String() == "" { t.Fatal("autorest: Sender#WithLogging failed to log the request for a nil response") } Respond(r, ByDiscardingBody(), ByClosing()) } func TestDoRetryForStatusCodesWithSuccess(t *testing.T) { client := mocks.NewSender() client.AppendAndRepeatResponse(mocks.NewResponseWithStatus("408 Request Timeout", http.StatusRequestTimeout), 2) client.AppendResponse(mocks.NewResponseWithStatus("200 OK", http.StatusOK)) r, _ := SendWithSender(client, mocks.NewRequest(), DoRetryForStatusCodes(5, time.Duration(2*time.Second), http.StatusRequestTimeout), ) Respond(r, ByDiscardingBody(), ByClosing()) if client.Attempts() != 3 { t.Fatalf("autorest: Sender#DoRetryForStatusCodes -- Got: StatusCode %v in %v attempts; Want: StatusCode 200 OK in 2 attempts -- ", r.Status, client.Attempts()-1) } } func TestDoRetryForStatusCodesWithNoSuccess(t *testing.T) { client := mocks.NewSender() client.AppendAndRepeatResponse(mocks.NewResponseWithStatus("504 Gateway Timeout", http.StatusGatewayTimeout), 5) r, _ := SendWithSender(client, mocks.NewRequest(), DoRetryForStatusCodes(2, time.Duration(2*time.Second), http.StatusGatewayTimeout), ) Respond(r, ByDiscardingBody(), ByClosing()) if client.Attempts() != 3 { t.Fatalf("autorest: Sender#DoRetryForStatusCodes -- Got: failed stop after %v retry attempts; Want: Stop after 2 retry attempts", client.Attempts()-1) } } func TestDoRetryForStatusCodes_CodeNotInRetryList(t *testing.T) { client := mocks.NewSender() client.AppendAndRepeatResponse(mocks.NewResponseWithStatus("204 No Content", http.StatusNoContent), 1) r, _ := SendWithSender(client, mocks.NewRequest(), DoRetryForStatusCodes(6, time.Duration(2*time.Second), http.StatusGatewayTimeout), ) Respond(r, ByDiscardingBody(), ByClosing()) if client.Attempts() != 1 || r.Status != "204 No Content" { t.Fatalf("autorest: Sender#DoRetryForStatusCodes -- Got: Retry attempts %v for StatusCode %v; Want: 0 attempts for StatusCode 204", client.Attempts(), r.Status) } } func TestDoRetryForStatusCodes_RequestBodyReadError(t *testing.T) { client := mocks.NewSender() client.AppendAndRepeatResponse(mocks.NewResponseWithStatus("204 No Content", http.StatusNoContent), 2) r, err := SendWithSender(client, mocks.NewRequestWithCloseBody(), DoRetryForStatusCodes(6, time.Duration(2*time.Second), http.StatusGatewayTimeout), ) Respond(r, ByDiscardingBody(), ByClosing()) if err == nil || client.Attempts() != 0 { t.Fatalf("autorest: Sender#DoRetryForStatusCodes -- Got: Not failed for request body read error; Want: Failed for body read error - %v", err) } } func newAcceptedResponse() *http.Response { resp := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted) mocks.SetAcceptedHeaders(resp) return resp } func TestDelayWithRetryAfterWithSuccess(t *testing.T) { Count429AsRetry = false defer func() { Count429AsRetry = true }() after, retries := 2, 2 totalSecs := after * retries client := mocks.NewSender() resp := mocks.NewResponseWithStatus("429 Too many requests", http.StatusTooManyRequests) mocks.SetResponseHeader(resp, "Retry-After", fmt.Sprintf("%v", after)) client.AppendAndRepeatResponse(resp, retries) client.AppendResponse(mocks.NewResponseWithStatus("200 OK", http.StatusOK)) d := time.Second * time.Duration(totalSecs) start := time.Now() r, _ := SendWithSender(client, mocks.NewRequest(), DoRetryForStatusCodes(1, time.Duration(time.Second), http.StatusTooManyRequests), ) if time.Since(start) < d { t.Fatal("autorest: DelayWithRetryAfter failed stopped too soon") } Respond(r, ByDiscardingBody(), ByClosing()) if r.StatusCode != http.StatusOK { t.Fatalf("autorest: Sender#DelayWithRetryAfterWithSuccess -- got status code %d, wanted 200", r.StatusCode) } if client.Attempts() != 3 { t.Fatalf("autorest: Sender#DelayWithRetryAfterWithSuccess -- Got: StatusCode %v in %v attempts; Want: StatusCode 200 OK in 2 attempts -- ", r.Status, client.Attempts()-1) } } func TestDelayWithRetryAfterWithFail(t *testing.T) { after, retries := 2, 2 totalSecs := after * retries client := mocks.NewSender() resp := mocks.NewResponseWithStatus("429 Too many requests", http.StatusTooManyRequests) mocks.SetResponseHeader(resp, "Retry-After", fmt.Sprintf("%v", after)) client.AppendAndRepeatResponse(resp, retries) client.AppendResponse(mocks.NewResponseWithStatus("200 OK", http.StatusOK)) d := time.Second * time.Duration(totalSecs) start := time.Now() r, _ := SendWithSender(client, mocks.NewRequest(), DoRetryForStatusCodes(1, time.Duration(time.Second), http.StatusTooManyRequests), ) if time.Since(start) < d { t.Fatal("autorest: DelayWithRetryAfter failed stopped too soon") } Respond(r, ByDiscardingBody(), ByClosing()) if r.StatusCode != http.StatusTooManyRequests { t.Fatalf("autorest: Sender#DelayWithRetryAfterWithFail -- got status code %d, wanted 429", r.StatusCode) } if client.Attempts() != 2 { t.Fatalf("autorest: Sender#DelayWithRetryAfterWithFail -- Got: StatusCode %v in %v attempts; Want: StatusCode 429 OK in 1 attempt -- ", r.Status, client.Attempts()-1) } } func TestDelayWithRetryAfterWithSuccessDateTime(t *testing.T) { resumeAt := time.Now().Add(2 * time.Second).Round(time.Second) client := mocks.NewSender() resp := mocks.NewResponseWithStatus("503 Service temporarily unavailable", http.StatusServiceUnavailable) mocks.SetResponseHeader(resp, "Retry-After", resumeAt.Format(time.RFC1123)) client.AppendResponse(resp) client.AppendResponse(mocks.NewResponseWithStatus("200 OK", http.StatusOK)) r, _ := SendWithSender(client, mocks.NewRequest(), DoRetryForStatusCodes(1, time.Duration(time.Second), http.StatusServiceUnavailable), ) if time.Now().Before(resumeAt) { t.Fatal("autorest: DelayWithRetryAfter failed stopped too soon") } Respond(r, ByDiscardingBody(), ByClosing()) if client.Attempts() != 2 { t.Fatalf("autorest: Sender#DelayWithRetryAfter -- Got: StatusCode %v in %v attempts; Want: StatusCode 200 OK in 2 attempts -- ", r.Status, client.Attempts()-1) } } type temporaryError struct { message string } func (te temporaryError) Error() string { return te.message } func (te temporaryError) Timeout() bool { return true } func (te temporaryError) Temporary() bool { return true } func TestDoRetryForStatusCodes_NilResponseTemporaryError(t *testing.T) { client := mocks.NewSender() client.AppendResponse(nil) client.SetError(temporaryError{message: "faux error"}) r, err := SendWithSender(client, mocks.NewRequest(), DoRetryForStatusCodes(3, time.Duration(1*time.Second), StatusCodesForRetry...), ) Respond(r, ByDiscardingBody(), ByClosing()) if err != nil || client.Attempts() != 2 { t.Fatalf("autorest: Sender#TestDoRetryForStatusCodes_NilResponseTemporaryError -- Got: non-nil error or wrong number of attempts - %v", err) } } func TestDoRetryForStatusCodes_NilResponseTemporaryError2(t *testing.T) { client := mocks.NewSender() client.AppendResponse(nil) client.SetError(fmt.Errorf("faux error")) r, err := SendWithSender(client, mocks.NewRequest(), DoRetryForStatusCodes(3, time.Duration(1*time.Second), StatusCodesForRetry...), ) Respond(r, ByDiscardingBody(), ByClosing()) if err != nil || client.Attempts() != 2 { t.Fatalf("autorest: Sender#TestDoRetryForStatusCodes_NilResponseTemporaryError2 -- Got: nil error or wrong number of attempts - %v", err) } } type fatalError struct { message string } func (fe fatalError) Error() string { return fe.message } func (fe fatalError) Timeout() bool { return false } func (fe fatalError) Temporary() bool { return false } func TestDoRetryForStatusCodes_NilResponseFatalError(t *testing.T) { const retryAttempts = 3 client := mocks.NewSender() client.AppendAndRepeatResponse(nil, retryAttempts+1) client.SetAndRepeatError(fatalError{"fatal error"}, retryAttempts+1) r, err := SendWithSender(client, mocks.NewRequest(), DoRetryForStatusCodes(retryAttempts, time.Duration(1*time.Second), StatusCodesForRetry...), ) Respond(r, ByDiscardingBody(), ByClosing()) if err == nil || client.Attempts() < retryAttempts+1 { t.Fatalf("autorest: Sender#TestDoRetryForStatusCodes_NilResponseFatalError -- Got: nil error or wrong number of attempts - %v", err) } } func TestDoRetryForStatusCodes_Cancel429(t *testing.T) { Count429AsRetry = false defer func() { Count429AsRetry = true }() retries := 6 client := mocks.NewSender() resp := mocks.NewResponseWithStatus("429 Too many requests", http.StatusTooManyRequests) client.AppendAndRepeatResponse(resp, retries) ctx, cancel := context.WithTimeout(context.Background(), time.Duration(retries/2)*time.Second) defer cancel() req := mocks.NewRequest().WithContext(ctx) r, err := SendWithSender(client, req, DoRetryForStatusCodes(1, time.Duration(time.Second), http.StatusTooManyRequests), ) if err == nil { t.Fatal("unexpected nil-error") } if r == nil { t.Fatal("unexpected nil response") } if r.StatusCode != http.StatusTooManyRequests { t.Fatalf("expected status code 429, got: %d", r.StatusCode) } if client.Attempts() >= retries { t.Fatalf("too many attempts: %d", client.Attempts()) } } func TestDoRetryForStatusCodes_Race(t *testing.T) { // cannot use the mock sender as it's not safe for concurrent use s := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})) defer s.Close() sender := DecorateSender(s.Client(), DoRetryForStatusCodes(0, 0, http.StatusRequestTimeout)) var wg sync.WaitGroup for i := 0; i < 2; i++ { wg.Add(1) go func() { defer wg.Done() req, _ := http.NewRequest(http.MethodGet, s.URL, nil) if _, err := sender.Do(req); err != nil { t.Fatal(err) } }() } wg.Wait() } func TestGetSendDecorators(t *testing.T) { sd := GetSendDecorators(context.Background()) if l := len(sd); l != 0 { t.Fatalf("expected zero length but got %d", l) } sd = GetSendDecorators(context.Background(), DoCloseIfError(), DoErrorIfStatusCode()) if l := len(sd); l != 2 { t.Fatalf("expected length of two but got %d", l) } } func TestWithSendDecorators(t *testing.T) { ctx := WithSendDecorators(context.Background(), []SendDecorator{DoRetryForAttempts(5, 5*time.Second)}) sd := GetSendDecorators(ctx) if l := len(sd); l != 1 { t.Fatalf("expected length of one but got %d", l) } sd = GetSendDecorators(ctx, DoCloseIfError(), DoErrorIfStatusCode()) if l := len(sd); l != 1 { t.Fatalf("expected length of one but got %d", l) } } golang-github-azure-go-autorest-14.1.1/autorest/to/000077500000000000000000000000001367372352400222245ustar00rootroot00000000000000golang-github-azure-go-autorest-14.1.1/autorest/to/convert.go000066400000000000000000000071171367372352400242410ustar00rootroot00000000000000/* Package to provides helpers to ease working with pointer values of marshalled structures. */ package to // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // String returns a string value for the passed string pointer. It returns the empty string if the // pointer is nil. func String(s *string) string { if s != nil { return *s } return "" } // StringPtr returns a pointer to the passed string. func StringPtr(s string) *string { return &s } // StringSlice returns a string slice value for the passed string slice pointer. It returns a nil // slice if the pointer is nil. func StringSlice(s *[]string) []string { if s != nil { return *s } return nil } // StringSlicePtr returns a pointer to the passed string slice. func StringSlicePtr(s []string) *[]string { return &s } // StringMap returns a map of strings built from the map of string pointers. The empty string is // used for nil pointers. func StringMap(msp map[string]*string) map[string]string { ms := make(map[string]string, len(msp)) for k, sp := range msp { if sp != nil { ms[k] = *sp } else { ms[k] = "" } } return ms } // StringMapPtr returns a pointer to a map of string pointers built from the passed map of strings. func StringMapPtr(ms map[string]string) *map[string]*string { msp := make(map[string]*string, len(ms)) for k, s := range ms { msp[k] = StringPtr(s) } return &msp } // Bool returns a bool value for the passed bool pointer. It returns false if the pointer is nil. func Bool(b *bool) bool { if b != nil { return *b } return false } // BoolPtr returns a pointer to the passed bool. func BoolPtr(b bool) *bool { return &b } // Int returns an int value for the passed int pointer. It returns 0 if the pointer is nil. func Int(i *int) int { if i != nil { return *i } return 0 } // IntPtr returns a pointer to the passed int. func IntPtr(i int) *int { return &i } // Int32 returns an int value for the passed int pointer. It returns 0 if the pointer is nil. func Int32(i *int32) int32 { if i != nil { return *i } return 0 } // Int32Ptr returns a pointer to the passed int32. func Int32Ptr(i int32) *int32 { return &i } // Int64 returns an int value for the passed int pointer. It returns 0 if the pointer is nil. func Int64(i *int64) int64 { if i != nil { return *i } return 0 } // Int64Ptr returns a pointer to the passed int64. func Int64Ptr(i int64) *int64 { return &i } // Float32 returns an int value for the passed int pointer. It returns 0.0 if the pointer is nil. func Float32(i *float32) float32 { if i != nil { return *i } return 0.0 } // Float32Ptr returns a pointer to the passed float32. func Float32Ptr(i float32) *float32 { return &i } // Float64 returns an int value for the passed int pointer. It returns 0.0 if the pointer is nil. func Float64(i *float64) float64 { if i != nil { return *i } return 0.0 } // Float64Ptr returns a pointer to the passed float64. func Float64Ptr(i float64) *float64 { return &i } // ByteSlicePtr returns a pointer to the passed byte slice. func ByteSlicePtr(b []byte) *[]byte { return &b } golang-github-azure-go-autorest-14.1.1/autorest/to/convert_test.go000066400000000000000000000142421367372352400252750ustar00rootroot00000000000000package to // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "reflect" "testing" ) func TestString(t *testing.T) { v := "" if String(&v) != v { t.Fatalf("to: String failed to return the correct string -- expected %v, received %v", v, String(&v)) } } func TestStringHandlesNil(t *testing.T) { if String(nil) != "" { t.Fatalf("to: String failed to correctly convert nil -- expected %v, received %v", "", String(nil)) } } func TestStringPtr(t *testing.T) { v := "" if *StringPtr(v) != v { t.Fatalf("to: StringPtr failed to return the correct string -- expected %v, received %v", v, *StringPtr(v)) } } func TestStringSlice(t *testing.T) { v := []string{} if out := StringSlice(&v); !reflect.DeepEqual(out, v) { t.Fatalf("to: StringSlice failed to return the correct slice -- expected %v, received %v", v, out) } } func TestStringSliceHandlesNil(t *testing.T) { if out := StringSlice(nil); out != nil { t.Fatalf("to: StringSlice failed to correctly convert nil -- expected %v, received %v", nil, out) } } func TestStringSlicePtr(t *testing.T) { v := []string{"a", "b"} if out := StringSlicePtr(v); !reflect.DeepEqual(*out, v) { t.Fatalf("to: StringSlicePtr failed to return the correct slice -- expected %v, received %v", v, *out) } } func TestStringMap(t *testing.T) { msp := map[string]*string{"foo": StringPtr("foo"), "bar": StringPtr("bar"), "baz": StringPtr("baz")} for k, v := range StringMap(msp) { if *msp[k] != v { t.Fatalf("to: StringMap incorrectly converted an entry -- expected [%s]%v, received[%s]%v", k, v, k, *msp[k]) } } } func TestStringMapHandlesNil(t *testing.T) { msp := map[string]*string{"foo": StringPtr("foo"), "bar": nil, "baz": StringPtr("baz")} for k, v := range StringMap(msp) { if msp[k] == nil && v != "" { t.Fatalf("to: StringMap incorrectly converted a nil entry -- expected [%s]%v, received[%s]%v", k, v, k, *msp[k]) } } } func TestStringMapPtr(t *testing.T) { ms := map[string]string{"foo": "foo", "bar": "bar", "baz": "baz"} for k, msp := range *StringMapPtr(ms) { if ms[k] != *msp { t.Fatalf("to: StringMapPtr incorrectly converted an entry -- expected [%s]%v, received[%s]%v", k, ms[k], k, *msp) } } } func TestBool(t *testing.T) { v := false if Bool(&v) != v { t.Fatalf("to: Bool failed to return the correct string -- expected %v, received %v", v, Bool(&v)) } } func TestBoolHandlesNil(t *testing.T) { if Bool(nil) != false { t.Fatalf("to: Bool failed to correctly convert nil -- expected %v, received %v", false, Bool(nil)) } } func TestBoolPtr(t *testing.T) { v := false if *BoolPtr(v) != v { t.Fatalf("to: BoolPtr failed to return the correct string -- expected %v, received %v", v, *BoolPtr(v)) } } func TestInt(t *testing.T) { v := 0 if Int(&v) != v { t.Fatalf("to: Int failed to return the correct string -- expected %v, received %v", v, Int(&v)) } } func TestIntHandlesNil(t *testing.T) { if Int(nil) != 0 { t.Fatalf("to: Int failed to correctly convert nil -- expected %v, received %v", 0, Int(nil)) } } func TestIntPtr(t *testing.T) { v := 0 if *IntPtr(v) != v { t.Fatalf("to: IntPtr failed to return the correct string -- expected %v, received %v", v, *IntPtr(v)) } } func TestInt32(t *testing.T) { v := int32(0) if Int32(&v) != v { t.Fatalf("to: Int32 failed to return the correct string -- expected %v, received %v", v, Int32(&v)) } } func TestInt32HandlesNil(t *testing.T) { if Int32(nil) != int32(0) { t.Fatalf("to: Int32 failed to correctly convert nil -- expected %v, received %v", 0, Int32(nil)) } } func TestInt32Ptr(t *testing.T) { v := int32(0) if *Int32Ptr(v) != v { t.Fatalf("to: Int32Ptr failed to return the correct string -- expected %v, received %v", v, *Int32Ptr(v)) } } func TestInt64(t *testing.T) { v := int64(0) if Int64(&v) != v { t.Fatalf("to: Int64 failed to return the correct string -- expected %v, received %v", v, Int64(&v)) } } func TestInt64HandlesNil(t *testing.T) { if Int64(nil) != int64(0) { t.Fatalf("to: Int64 failed to correctly convert nil -- expected %v, received %v", 0, Int64(nil)) } } func TestInt64Ptr(t *testing.T) { v := int64(0) if *Int64Ptr(v) != v { t.Fatalf("to: Int64Ptr failed to return the correct string -- expected %v, received %v", v, *Int64Ptr(v)) } } func TestFloat32(t *testing.T) { v := float32(0) if Float32(&v) != v { t.Fatalf("to: Float32 failed to return the correct string -- expected %v, received %v", v, Float32(&v)) } } func TestFloat32HandlesNil(t *testing.T) { if Float32(nil) != float32(0) { t.Fatalf("to: Float32 failed to correctly convert nil -- expected %v, received %v", 0, Float32(nil)) } } func TestFloat32Ptr(t *testing.T) { v := float32(0) if *Float32Ptr(v) != v { t.Fatalf("to: Float32Ptr failed to return the correct string -- expected %v, received %v", v, *Float32Ptr(v)) } } func TestFloat64(t *testing.T) { v := float64(0) if Float64(&v) != v { t.Fatalf("to: Float64 failed to return the correct string -- expected %v, received %v", v, Float64(&v)) } } func TestFloat64HandlesNil(t *testing.T) { if Float64(nil) != float64(0) { t.Fatalf("to: Float64 failed to correctly convert nil -- expected %v, received %v", 0, Float64(nil)) } } func TestFloat64Ptr(t *testing.T) { v := float64(0) if *Float64Ptr(v) != v { t.Fatalf("to: Float64Ptr failed to return the correct string -- expected %v, received %v", v, *Float64Ptr(v)) } } func TestByteSlicePtr(t *testing.T) { v := []byte("bytes") if out := ByteSlicePtr(v); !reflect.DeepEqual(*out, v) { t.Fatalf("to: ByteSlicePtr failed to return the correct slice -- expected %v, received %v", v, *out) } } golang-github-azure-go-autorest-14.1.1/autorest/to/go.mod000066400000000000000000000001571367372352400233350ustar00rootroot00000000000000module github.com/Azure/go-autorest/autorest/to go 1.12 require github.com/Azure/go-autorest/autorest v0.9.0 golang-github-azure-go-autorest-14.1.1/autorest/to/go.sum000066400000000000000000000032521367372352400233610ustar00rootroot00000000000000github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0 h1:Ww5g4zThfD/6cLb4z6xxgeyDa7QDkizMkJKe0ysZXp0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang-github-azure-go-autorest-14.1.1/autorest/to/go_mod_tidy_hack.go000066400000000000000000000017141367372352400260410ustar00rootroot00000000000000// +build modhack package to // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // This file, and the github.com/Azure/go-autorest/autorest import, won't actually become part of // the resultant binary. // Necessary for safely adding multi-module repo. // See: https://github.com/golang/go/wiki/Modules#is-it-possible-to-add-a-module-to-a-multi-module-repository import _ "github.com/Azure/go-autorest/autorest" golang-github-azure-go-autorest-14.1.1/autorest/utility.go000066400000000000000000000152161367372352400236410ustar00rootroot00000000000000package autorest // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "bytes" "encoding/json" "encoding/xml" "fmt" "io" "io/ioutil" "net" "net/http" "net/url" "reflect" "strings" "github.com/Azure/go-autorest/autorest/adal" ) // EncodedAs is a series of constants specifying various data encodings type EncodedAs string const ( // EncodedAsJSON states that data is encoded as JSON EncodedAsJSON EncodedAs = "JSON" // EncodedAsXML states that data is encoded as Xml EncodedAsXML EncodedAs = "XML" ) // Decoder defines the decoding method json.Decoder and xml.Decoder share type Decoder interface { Decode(v interface{}) error } // NewDecoder creates a new decoder appropriate to the passed encoding. // encodedAs specifies the type of encoding and r supplies the io.Reader containing the // encoded data. func NewDecoder(encodedAs EncodedAs, r io.Reader) Decoder { if encodedAs == EncodedAsJSON { return json.NewDecoder(r) } else if encodedAs == EncodedAsXML { return xml.NewDecoder(r) } return nil } // CopyAndDecode decodes the data from the passed io.Reader while making a copy. Having a copy // is especially useful if there is a chance the data will fail to decode. // encodedAs specifies the expected encoding, r provides the io.Reader to the data, and v // is the decoding destination. func CopyAndDecode(encodedAs EncodedAs, r io.Reader, v interface{}) (bytes.Buffer, error) { b := bytes.Buffer{} return b, NewDecoder(encodedAs, io.TeeReader(r, &b)).Decode(v) } // TeeReadCloser returns a ReadCloser that writes to w what it reads from rc. // It utilizes io.TeeReader to copy the data read and has the same behavior when reading. // Further, when it is closed, it ensures that rc is closed as well. func TeeReadCloser(rc io.ReadCloser, w io.Writer) io.ReadCloser { return &teeReadCloser{rc, io.TeeReader(rc, w)} } type teeReadCloser struct { rc io.ReadCloser r io.Reader } func (t *teeReadCloser) Read(p []byte) (int, error) { return t.r.Read(p) } func (t *teeReadCloser) Close() error { return t.rc.Close() } func containsInt(ints []int, n int) bool { for _, i := range ints { if i == n { return true } } return false } func escapeValueStrings(m map[string]string) map[string]string { for key, value := range m { m[key] = url.QueryEscape(value) } return m } func ensureValueStrings(mapOfInterface map[string]interface{}) map[string]string { mapOfStrings := make(map[string]string) for key, value := range mapOfInterface { mapOfStrings[key] = ensureValueString(value) } return mapOfStrings } func ensureValueString(value interface{}) string { if value == nil { return "" } switch v := value.(type) { case string: return v case []byte: return string(v) default: return fmt.Sprintf("%v", v) } } // MapToValues method converts map[string]interface{} to url.Values. func MapToValues(m map[string]interface{}) url.Values { v := url.Values{} for key, value := range m { x := reflect.ValueOf(value) if x.Kind() == reflect.Array || x.Kind() == reflect.Slice { for i := 0; i < x.Len(); i++ { v.Add(key, ensureValueString(x.Index(i))) } } else { v.Add(key, ensureValueString(value)) } } return v } // AsStringSlice method converts interface{} to []string. // s must be of type slice or array or an error is returned. // Each element of s will be converted to its string representation. func AsStringSlice(s interface{}) ([]string, error) { v := reflect.ValueOf(s) if v.Kind() != reflect.Slice && v.Kind() != reflect.Array { return nil, NewError("autorest", "AsStringSlice", "the value's type is not a slice or array.") } stringSlice := make([]string, 0, v.Len()) for i := 0; i < v.Len(); i++ { stringSlice = append(stringSlice, fmt.Sprintf("%v", v.Index(i))) } return stringSlice, nil } // String method converts interface v to string. If interface is a list, it // joins list elements using the separator. Note that only sep[0] will be used for // joining if any separator is specified. func String(v interface{}, sep ...string) string { if len(sep) == 0 { return ensureValueString(v) } stringSlice, ok := v.([]string) if ok == false { var err error stringSlice, err = AsStringSlice(v) if err != nil { panic(fmt.Sprintf("autorest: Couldn't convert value to a string %s.", err)) } } return ensureValueString(strings.Join(stringSlice, sep[0])) } // Encode method encodes url path and query parameters. func Encode(location string, v interface{}, sep ...string) string { s := String(v, sep...) switch strings.ToLower(location) { case "path": return pathEscape(s) case "query": return queryEscape(s) default: return s } } func pathEscape(s string) string { return strings.Replace(url.QueryEscape(s), "+", "%20", -1) } func queryEscape(s string) string { return url.QueryEscape(s) } // ChangeToGet turns the specified http.Request into a GET (it assumes it wasn't). // This is mainly useful for long-running operations that use the Azure-AsyncOperation // header, so we change the initial PUT into a GET to retrieve the final result. func ChangeToGet(req *http.Request) *http.Request { req.Method = "GET" req.Body = nil req.ContentLength = 0 req.Header.Del("Content-Length") return req } // IsTokenRefreshError returns true if the specified error implements the TokenRefreshError // interface. If err is a DetailedError it will walk the chain of Original errors. func IsTokenRefreshError(err error) bool { if _, ok := err.(adal.TokenRefreshError); ok { return true } if de, ok := err.(DetailedError); ok { return IsTokenRefreshError(de.Original) } return false } // IsTemporaryNetworkError returns true if the specified error is a temporary network error or false // if it's not. If the error doesn't implement the net.Error interface the return value is true. func IsTemporaryNetworkError(err error) bool { if netErr, ok := err.(net.Error); !ok || (ok && netErr.Temporary()) { return true } return false } // DrainResponseBody reads the response body then closes it. func DrainResponseBody(resp *http.Response) error { if resp != nil && resp.Body != nil { _, err := io.Copy(ioutil.Discard, resp.Body) resp.Body.Close() return err } return nil } golang-github-azure-go-autorest-14.1.1/autorest/utility_test.go000066400000000000000000000300751367372352400247000ustar00rootroot00000000000000package autorest // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "bytes" "encoding/json" "encoding/xml" "errors" "fmt" "io" "net/http" "net/url" "reflect" "sort" "strings" "testing" "github.com/Azure/go-autorest/autorest/mocks" ) const ( jsonT = ` { "name":"Rob Pike", "age":42 }` xmlT = ` Rob Pike 42 ` ) func TestNewDecoderCreatesJSONDecoder(t *testing.T) { d := NewDecoder(EncodedAsJSON, strings.NewReader(jsonT)) _, ok := d.(*json.Decoder) if d == nil || !ok { t.Fatal("autorest: NewDecoder failed to create a JSON decoder when requested") } } func TestNewDecoderCreatesXMLDecoder(t *testing.T) { d := NewDecoder(EncodedAsXML, strings.NewReader(xmlT)) _, ok := d.(*xml.Decoder) if d == nil || !ok { t.Fatal("autorest: NewDecoder failed to create an XML decoder when requested") } } func TestNewDecoderReturnsNilForUnknownEncoding(t *testing.T) { d := NewDecoder("unknown", strings.NewReader(xmlT)) if d != nil { t.Fatal("autorest: NewDecoder created a decoder for an unknown encoding") } } func TestCopyAndDecodeDecodesJSON(t *testing.T) { _, err := CopyAndDecode(EncodedAsJSON, strings.NewReader(jsonT), &mocks.T{}) if err != nil { t.Fatalf("autorest: CopyAndDecode returned an error with valid JSON - %v", err) } } func TestCopyAndDecodeDecodesXML(t *testing.T) { _, err := CopyAndDecode(EncodedAsXML, strings.NewReader(xmlT), &mocks.T{}) if err != nil { t.Fatalf("autorest: CopyAndDecode returned an error with valid XML - %v", err) } } func TestCopyAndDecodeReturnsJSONDecodingErrors(t *testing.T) { _, err := CopyAndDecode(EncodedAsJSON, strings.NewReader(jsonT[0:len(jsonT)-2]), &mocks.T{}) if err == nil { t.Fatalf("autorest: CopyAndDecode failed to return an error with invalid JSON") } } func TestCopyAndDecodeReturnsXMLDecodingErrors(t *testing.T) { _, err := CopyAndDecode(EncodedAsXML, strings.NewReader(xmlT[0:len(xmlT)-2]), &mocks.T{}) if err == nil { t.Fatalf("autorest: CopyAndDecode failed to return an error with invalid XML") } } func TestCopyAndDecodeAlwaysReturnsACopy(t *testing.T) { b, _ := CopyAndDecode(EncodedAsJSON, strings.NewReader(jsonT), &mocks.T{}) if b.String() != jsonT { t.Fatalf("autorest: CopyAndDecode failed to return a valid copy of the data - %v", b.String()) } } func TestTeeReadCloser_Copies(t *testing.T) { v := &mocks.T{} r := mocks.NewResponseWithContent(jsonT) b := &bytes.Buffer{} r.Body = TeeReadCloser(r.Body, b) err := Respond(r, ByUnmarshallingJSON(v), ByClosing()) if err != nil { t.Fatalf("autorest: TeeReadCloser returned an unexpected error -- %v", err) } if b.String() != jsonT { t.Fatalf("autorest: TeeReadCloser failed to copy the bytes read") } } func TestTeeReadCloser_PassesReadErrors(t *testing.T) { v := &mocks.T{} r := mocks.NewResponseWithContent(jsonT) r.Body.(*mocks.Body).Close() r.Body = TeeReadCloser(r.Body, &bytes.Buffer{}) err := Respond(r, ByUnmarshallingJSON(v), ByClosing()) if err == nil { t.Fatalf("autorest: TeeReadCloser failed to return the expected error") } } func TestTeeReadCloser_ClosesWrappedReader(t *testing.T) { v := &mocks.T{} r := mocks.NewResponseWithContent(jsonT) b := r.Body.(*mocks.Body) r.Body = TeeReadCloser(r.Body, &bytes.Buffer{}) err := Respond(r, ByUnmarshallingJSON(v), ByClosing()) if err != nil { t.Fatalf("autorest: TeeReadCloser returned an unexpected error -- %v", err) } if b.IsOpen() { t.Fatalf("autorest: TeeReadCloser failed to close the nested io.ReadCloser") } } func TestContainsIntFindsValue(t *testing.T) { ints := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} v := 5 if !containsInt(ints, v) { t.Fatalf("autorest: containsInt failed to find %v in %v", v, ints) } } func TestContainsIntDoesNotFindValue(t *testing.T) { ints := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} v := 42 if containsInt(ints, v) { t.Fatalf("autorest: containsInt unexpectedly found %v in %v", v, ints) } } func TestContainsIntAcceptsEmptyList(t *testing.T) { ints := make([]int, 10) if containsInt(ints, 42) { t.Fatalf("autorest: containsInt failed to handle an empty list") } } func TestContainsIntAcceptsNilList(t *testing.T) { var ints []int if containsInt(ints, 42) { t.Fatalf("autorest: containsInt failed to handle an nil list") } } func TestEscapeStrings(t *testing.T) { m := map[string]string{ "string": "a long string with = odd characters", "int": "42", "nil": "", } r := map[string]string{ "string": "a+long+string+with+%3D+odd+characters", "int": "42", "nil": "", } v := escapeValueStrings(m) if !reflect.DeepEqual(v, r) { t.Fatalf("autorest: ensureValueStrings returned %v\n", v) } } func TestEnsureStrings(t *testing.T) { m := map[string]interface{}{ "string": "string", "int": 42, "nil": nil, "bytes": []byte{255, 254, 253}, } r := map[string]string{ "string": "string", "int": "42", "nil": "", "bytes": string([]byte{255, 254, 253}), } v := ensureValueStrings(m) if !reflect.DeepEqual(v, r) { t.Fatalf("autorest: ensureValueStrings returned %v\n", v) } } func ExampleString() { m := []string{ "string1", "string2", "string3", } fmt.Println(String(m, ",")) // Output: string1,string2,string3 } func TestStringWithValidString(t *testing.T) { i := 123 if got, want := String(i), "123"; got != want { t.Logf("got: %q\nwant: %q", got, want) t.Fail() } } func TestStringWithStringSlice(t *testing.T) { s := []string{"string1", "string2"} if got, want := String(s, ","), "string1,string2"; got != want { t.Logf("got: %q\nwant: %q", got, want) t.Fail() } } func TestStringWithEnum(t *testing.T) { type TestEnumType string s := TestEnumType("string1") if got, want := String(s), "string1"; got != want { t.Logf("got: %q\nwant: %q", got, want) t.Fail() } } func TestStringWithEnumSlice(t *testing.T) { type TestEnumType string s := []TestEnumType{"string1", "string2"} if got, want := String(s, ","), "string1,string2"; got != want { t.Logf("got: %q\nwant: %q", got, want) t.Fail() } } func TestStringWithIntegerSlice(t *testing.T) { type TestEnumType int32 s := []TestEnumType{1, 2} if got, want := String(s, ","), "1,2"; got != want { t.Logf("got: %q\nwant: %q", got, want) t.Fail() } } func ExampleAsStringSlice() { type TestEnumType string a := []TestEnumType{"value1", "value2"} b, _ := AsStringSlice(a) for _, c := range b { fmt.Println(c) } // Output: // value1 // value2 } func TestEncodeWithValidPath(t *testing.T) { s := Encode("Path", "Hello Gopher") if s != "Hello%20Gopher" { t.Fatalf("autorest: Encode method failed for valid path encoding. Got: %v; Want: %v", s, "Hello%20Gopher") } } func TestEncodeWithValidQuery(t *testing.T) { s := Encode("Query", "Hello Gopher") if s != "Hello+Gopher" { t.Fatalf("autorest: Encode method failed for valid query encoding. Got: '%v'; Want: 'Hello+Gopher'", s) } } func TestEncodeWithValidNotPathQuery(t *testing.T) { s := Encode("Host", "Hello Gopher") if s != "Hello Gopher" { t.Fatalf("autorest: Encode method failed for parameter not query or path. Got: '%v'; Want: 'Hello Gopher'", s) } } func TestMapToValues(t *testing.T) { m := map[string]interface{}{ "a": "a", "b": 2, } v := url.Values{} v.Add("a", "a") v.Add("b", "2") if !isEqual(v, MapToValues(m)) { t.Fatalf("autorest: MapToValues method failed to return correct values - expected(%v) got(%v)", v, MapToValues(m)) } } func TestMapToValuesWithArrayValues(t *testing.T) { m := map[string]interface{}{ "a": []string{"a", "b"}, "b": 2, "c": []int{3, 4}, } v := url.Values{} v.Add("a", "a") v.Add("a", "b") v.Add("b", "2") v.Add("c", "3") v.Add("c", "4") if !isEqual(v, MapToValues(m)) { t.Fatalf("autorest: MapToValues method failed to return correct values - expected(%v) got(%v)", v, MapToValues(m)) } } type someTempError struct{} func (s someTempError) Error() string { return "temporary error" } func (s someTempError) Timeout() bool { return true } func (s someTempError) Temporary() bool { return true } func TestIsTemporaryNetworkErrorTrue(t *testing.T) { if !IsTemporaryNetworkError(someTempError{}) { t.Fatal("expected someTempError to be a temporary network error") } if !IsTemporaryNetworkError(errors.New("non-temporary network error")) { t.Fatal("expected random error to be a temporary network error") } } type someFatalError struct{} func (s someFatalError) Error() string { return "fatal error" } func (s someFatalError) Timeout() bool { return false } func (s someFatalError) Temporary() bool { return false } func TestIsTemporaryNetworkErrorFalse(t *testing.T) { if IsTemporaryNetworkError(someFatalError{}) { t.Fatal("expected someFatalError to be a fatal network error") } } func isEqual(v, u url.Values) bool { for key, value := range v { if len(u[key]) == 0 { return false } sort.Strings(value) sort.Strings(u[key]) for i := range value { if value[i] != u[key][i] { return false } } u.Del(key) } if len(u) > 0 { return false } return true } func doEnsureBodyClosed(t *testing.T) SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { resp, err := s.Do(r) if resp != nil && resp.Body != nil && resp.Body.(*mocks.Body).IsOpen() { t.Fatal("autorest: Expected Body to be closed -- it was left open") } return resp, err }) } } type mockAuthorizer struct{} func (ma mockAuthorizer) WithAuthorization() PrepareDecorator { return WithHeader(headerAuthorization, mocks.TestAuthorizationHeader) } type mockFailingAuthorizer struct{} func (mfa mockFailingAuthorizer) WithAuthorization() PrepareDecorator { return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { return r, fmt.Errorf("ERROR: mockFailingAuthorizer returned expected error") }) } } type mockInspector struct { wasInvoked bool } func (mi *mockInspector) WithInspection() PrepareDecorator { return func(p Preparer) Preparer { return PreparerFunc(func(r *http.Request) (*http.Request, error) { mi.wasInvoked = true return p.Prepare(r) }) } } func (mi *mockInspector) ByInspecting() RespondDecorator { return func(r Responder) Responder { return ResponderFunc(func(resp *http.Response) error { mi.wasInvoked = true return r.Respond(resp) }) } } func withMessage(output *string, msg string) SendDecorator { return func(s Sender) Sender { return SenderFunc(func(r *http.Request) (*http.Response, error) { resp, err := s.Do(r) if err == nil { *output += msg } return resp, err }) } } func withErrorRespondDecorator(e *error) RespondDecorator { return func(r Responder) Responder { return ResponderFunc(func(resp *http.Response) error { err := r.Respond(resp) if err != nil { return err } *e = fmt.Errorf("autorest: Faux Respond Error") return *e }) } } type mockDrain struct { read bool closed bool } func (md *mockDrain) Read(b []byte) (int, error) { md.read = true b = append(b, 0xff) return 1, io.EOF } func (md *mockDrain) Close() error { md.closed = true return nil } func TestDrainResponseBody(t *testing.T) { err := DrainResponseBody(nil) if err != nil { t.Fatalf("expected nil error, got %v", err) } err = DrainResponseBody(&http.Response{}) if err != nil { t.Fatalf("expected nil error, got %v", err) } md := &mockDrain{} err = DrainResponseBody(&http.Response{Body: md}) if err != nil { t.Fatalf("expected nil error, got %v", err) } if !md.closed { t.Fatal("mockDrain wasn't closed") } if !md.read { t.Fatal("mockDrain wasn't read") } } golang-github-azure-go-autorest-14.1.1/autorest/validation/000077500000000000000000000000001367372352400237345ustar00rootroot00000000000000golang-github-azure-go-autorest-14.1.1/autorest/validation/error.go000066400000000000000000000032671367372352400254240ustar00rootroot00000000000000package validation // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "fmt" ) // Error is the type that's returned when the validation of an APIs arguments constraints fails. type Error struct { // PackageType is the package type of the object emitting the error. For types, the value // matches that produced the the '%T' format specifier of the fmt package. For other elements, // such as functions, it is just the package name (e.g., "autorest"). PackageType string // Method is the name of the method raising the error. Method string // Message is the error message. Message string } // Error returns a string containing the details of the validation failure. func (e Error) Error() string { return fmt.Sprintf("%s#%s: Invalid input: %s", e.PackageType, e.Method, e.Message) } // NewError creates a new Error object with the specified parameters. // message is treated as a format string to which the optional args apply. func NewError(packageType string, method string, message string, args ...interface{}) Error { return Error{ PackageType: packageType, Method: method, Message: fmt.Sprintf(message, args...), } } golang-github-azure-go-autorest-14.1.1/autorest/validation/go.mod000066400000000000000000000002401367372352400250360ustar00rootroot00000000000000module github.com/Azure/go-autorest/autorest/validation go 1.12 require ( github.com/Azure/go-autorest/autorest v0.9.0 github.com/stretchr/testify v1.3.0 ) golang-github-azure-go-autorest-14.1.1/autorest/validation/go.sum000066400000000000000000000044121367372352400250700ustar00rootroot00000000000000github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0 h1:Ww5g4zThfD/6cLb4z6xxgeyDa7QDkizMkJKe0ysZXp0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 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/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang-github-azure-go-autorest-14.1.1/autorest/validation/go_mod_tidy_hack.go000066400000000000000000000017241367372352400275520ustar00rootroot00000000000000// +build modhack package validation // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // This file, and the github.com/Azure/go-autorest/autorest import, won't actually become part of // the resultant binary. // Necessary for safely adding multi-module repo. // See: https://github.com/golang/go/wiki/Modules#is-it-possible-to-add-a-module-to-a-multi-module-repository import _ "github.com/Azure/go-autorest/autorest" golang-github-azure-go-autorest-14.1.1/autorest/validation/validation.go000066400000000000000000000253571367372352400264310ustar00rootroot00000000000000/* Package validation provides methods for validating parameter value using reflection. */ package validation // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "fmt" "reflect" "regexp" "strings" ) // Constraint stores constraint name, target field name // Rule and chain validations. type Constraint struct { // Target field name for validation. Target string // Constraint name e.g. minLength, MaxLength, Pattern, etc. Name string // Rule for constraint e.g. greater than 10, less than 5 etc. Rule interface{} // Chain Validations for struct type Chain []Constraint } // Validation stores parameter-wise validation. type Validation struct { TargetValue interface{} Constraints []Constraint } // Constraint list const ( Empty = "Empty" Null = "Null" ReadOnly = "ReadOnly" Pattern = "Pattern" MaxLength = "MaxLength" MinLength = "MinLength" MaxItems = "MaxItems" MinItems = "MinItems" MultipleOf = "MultipleOf" UniqueItems = "UniqueItems" InclusiveMaximum = "InclusiveMaximum" ExclusiveMaximum = "ExclusiveMaximum" ExclusiveMinimum = "ExclusiveMinimum" InclusiveMinimum = "InclusiveMinimum" ) // Validate method validates constraints on parameter // passed in validation array. func Validate(m []Validation) error { for _, item := range m { v := reflect.ValueOf(item.TargetValue) for _, constraint := range item.Constraints { var err error switch v.Kind() { case reflect.Ptr: err = validatePtr(v, constraint) case reflect.String: err = validateString(v, constraint) case reflect.Struct: err = validateStruct(v, constraint) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: err = validateInt(v, constraint) case reflect.Float32, reflect.Float64: err = validateFloat(v, constraint) case reflect.Array, reflect.Slice, reflect.Map: err = validateArrayMap(v, constraint) default: err = createError(v, constraint, fmt.Sprintf("unknown type %v", v.Kind())) } if err != nil { return err } } } return nil } func validateStruct(x reflect.Value, v Constraint, name ...string) error { //Get field name from target name which is in format a.b.c s := strings.Split(v.Target, ".") f := x.FieldByName(s[len(s)-1]) if isZero(f) { return createError(x, v, fmt.Sprintf("field %q doesn't exist", v.Target)) } return Validate([]Validation{ { TargetValue: getInterfaceValue(f), Constraints: []Constraint{v}, }, }) } func validatePtr(x reflect.Value, v Constraint) error { if v.Name == ReadOnly { if !x.IsNil() { return createError(x.Elem(), v, "readonly parameter; must send as nil or empty in request") } return nil } if x.IsNil() { return checkNil(x, v) } if v.Chain != nil { return Validate([]Validation{ { TargetValue: getInterfaceValue(x.Elem()), Constraints: v.Chain, }, }) } return nil } func validateInt(x reflect.Value, v Constraint) error { i := x.Int() r, ok := toInt64(v.Rule) if !ok { return createError(x, v, fmt.Sprintf("rule must be integer value for %v constraint; got: %v", v.Name, v.Rule)) } switch v.Name { case MultipleOf: if i%r != 0 { return createError(x, v, fmt.Sprintf("value must be a multiple of %v", r)) } case ExclusiveMinimum: if i <= r { return createError(x, v, fmt.Sprintf("value must be greater than %v", r)) } case ExclusiveMaximum: if i >= r { return createError(x, v, fmt.Sprintf("value must be less than %v", r)) } case InclusiveMinimum: if i < r { return createError(x, v, fmt.Sprintf("value must be greater than or equal to %v", r)) } case InclusiveMaximum: if i > r { return createError(x, v, fmt.Sprintf("value must be less than or equal to %v", r)) } default: return createError(x, v, fmt.Sprintf("constraint %v is not applicable for type integer", v.Name)) } return nil } func validateFloat(x reflect.Value, v Constraint) error { f := x.Float() r, ok := v.Rule.(float64) if !ok { return createError(x, v, fmt.Sprintf("rule must be float value for %v constraint; got: %v", v.Name, v.Rule)) } switch v.Name { case ExclusiveMinimum: if f <= r { return createError(x, v, fmt.Sprintf("value must be greater than %v", r)) } case ExclusiveMaximum: if f >= r { return createError(x, v, fmt.Sprintf("value must be less than %v", r)) } case InclusiveMinimum: if f < r { return createError(x, v, fmt.Sprintf("value must be greater than or equal to %v", r)) } case InclusiveMaximum: if f > r { return createError(x, v, fmt.Sprintf("value must be less than or equal to %v", r)) } default: return createError(x, v, fmt.Sprintf("constraint %s is not applicable for type float", v.Name)) } return nil } func validateString(x reflect.Value, v Constraint) error { s := x.String() switch v.Name { case Empty: if len(s) == 0 { return checkEmpty(x, v) } case Pattern: reg, err := regexp.Compile(v.Rule.(string)) if err != nil { return createError(x, v, err.Error()) } if !reg.MatchString(s) { return createError(x, v, fmt.Sprintf("value doesn't match pattern %v", v.Rule)) } case MaxLength: if _, ok := v.Rule.(int); !ok { return createError(x, v, fmt.Sprintf("rule must be integer value for %v constraint; got: %v", v.Name, v.Rule)) } if len(s) > v.Rule.(int) { return createError(x, v, fmt.Sprintf("value length must be less than or equal to %v", v.Rule)) } case MinLength: if _, ok := v.Rule.(int); !ok { return createError(x, v, fmt.Sprintf("rule must be integer value for %v constraint; got: %v", v.Name, v.Rule)) } if len(s) < v.Rule.(int) { return createError(x, v, fmt.Sprintf("value length must be greater than or equal to %v", v.Rule)) } case ReadOnly: if len(s) > 0 { return createError(reflect.ValueOf(s), v, "readonly parameter; must send as nil or empty in request") } default: return createError(x, v, fmt.Sprintf("constraint %s is not applicable to string type", v.Name)) } if v.Chain != nil { return Validate([]Validation{ { TargetValue: getInterfaceValue(x), Constraints: v.Chain, }, }) } return nil } func validateArrayMap(x reflect.Value, v Constraint) error { switch v.Name { case Null: if x.IsNil() { return checkNil(x, v) } case Empty: if x.IsNil() || x.Len() == 0 { return checkEmpty(x, v) } case MaxItems: if _, ok := v.Rule.(int); !ok { return createError(x, v, fmt.Sprintf("rule must be integer for %v constraint; got: %v", v.Name, v.Rule)) } if x.Len() > v.Rule.(int) { return createError(x, v, fmt.Sprintf("maximum item limit is %v; got: %v", v.Rule, x.Len())) } case MinItems: if _, ok := v.Rule.(int); !ok { return createError(x, v, fmt.Sprintf("rule must be integer for %v constraint; got: %v", v.Name, v.Rule)) } if x.Len() < v.Rule.(int) { return createError(x, v, fmt.Sprintf("minimum item limit is %v; got: %v", v.Rule, x.Len())) } case UniqueItems: if x.Kind() == reflect.Array || x.Kind() == reflect.Slice { if !checkForUniqueInArray(x) { return createError(x, v, fmt.Sprintf("all items in parameter %q must be unique; got:%v", v.Target, x)) } } else if x.Kind() == reflect.Map { if !checkForUniqueInMap(x) { return createError(x, v, fmt.Sprintf("all items in parameter %q must be unique; got:%v", v.Target, x)) } } else { return createError(x, v, fmt.Sprintf("type must be array, slice or map for constraint %v; got: %v", v.Name, x.Kind())) } case ReadOnly: if x.Len() != 0 { return createError(x, v, "readonly parameter; must send as nil or empty in request") } case Pattern: reg, err := regexp.Compile(v.Rule.(string)) if err != nil { return createError(x, v, err.Error()) } keys := x.MapKeys() for _, k := range keys { if !reg.MatchString(k.String()) { return createError(k, v, fmt.Sprintf("map key doesn't match pattern %v", v.Rule)) } } default: return createError(x, v, fmt.Sprintf("constraint %v is not applicable to array, slice and map type", v.Name)) } if v.Chain != nil { return Validate([]Validation{ { TargetValue: getInterfaceValue(x), Constraints: v.Chain, }, }) } return nil } func checkNil(x reflect.Value, v Constraint) error { if _, ok := v.Rule.(bool); !ok { return createError(x, v, fmt.Sprintf("rule must be bool value for %v constraint; got: %v", v.Name, v.Rule)) } if v.Rule.(bool) { return createError(x, v, "value can not be null; required parameter") } return nil } func checkEmpty(x reflect.Value, v Constraint) error { if _, ok := v.Rule.(bool); !ok { return createError(x, v, fmt.Sprintf("rule must be bool value for %v constraint; got: %v", v.Name, v.Rule)) } if v.Rule.(bool) { return createError(x, v, "value can not be null or empty; required parameter") } return nil } func checkForUniqueInArray(x reflect.Value) bool { if x == reflect.Zero(reflect.TypeOf(x)) || x.Len() == 0 { return false } arrOfInterface := make([]interface{}, x.Len()) for i := 0; i < x.Len(); i++ { arrOfInterface[i] = x.Index(i).Interface() } m := make(map[interface{}]bool) for _, val := range arrOfInterface { if m[val] { return false } m[val] = true } return true } func checkForUniqueInMap(x reflect.Value) bool { if x == reflect.Zero(reflect.TypeOf(x)) || x.Len() == 0 { return false } mapOfInterface := make(map[interface{}]interface{}, x.Len()) keys := x.MapKeys() for _, k := range keys { mapOfInterface[k.Interface()] = x.MapIndex(k).Interface() } m := make(map[interface{}]bool) for _, val := range mapOfInterface { if m[val] { return false } m[val] = true } return true } func getInterfaceValue(x reflect.Value) interface{} { if x.Kind() == reflect.Invalid { return nil } return x.Interface() } func isZero(x interface{}) bool { return x == reflect.Zero(reflect.TypeOf(x)).Interface() } func createError(x reflect.Value, v Constraint, err string) error { return fmt.Errorf("autorest/validation: validation failed: parameter=%s constraint=%s value=%#v details: %s", v.Target, v.Name, getInterfaceValue(x), err) } func toInt64(v interface{}) (int64, bool) { if i64, ok := v.(int64); ok { return i64, true } // older generators emit max constants as int, so if int64 fails fall back to int if i32, ok := v.(int); ok { return int64(i32), true } return 0, false } golang-github-azure-go-autorest-14.1.1/autorest/validation/validation_test.go000066400000000000000000001662301367372352400274640ustar00rootroot00000000000000package validation // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "fmt" "reflect" "strings" "testing" "github.com/stretchr/testify/require" ) func TestCheckForUniqueInArrayTrue(t *testing.T) { require.Equal(t, checkForUniqueInArray(reflect.ValueOf([]int{1, 2, 3})), true) } func TestCheckForUniqueInArrayFalse(t *testing.T) { require.Equal(t, checkForUniqueInArray(reflect.ValueOf([]int{1, 2, 3, 3})), false) } func TestCheckForUniqueInArrayEmpty(t *testing.T) { require.Equal(t, checkForUniqueInArray(reflect.ValueOf([]int{})), false) } func TestCheckForUniqueInMapTrue(t *testing.T) { require.Equal(t, checkForUniqueInMap(reflect.ValueOf(map[string]int{"one": 1, "two": 2})), true) } func TestCheckForUniqueInMapFalse(t *testing.T) { require.Equal(t, checkForUniqueInMap(reflect.ValueOf(map[int]string{1: "one", 2: "one"})), false) } func TestCheckForUniqueInMapEmpty(t *testing.T) { require.Equal(t, checkForUniqueInMap(reflect.ValueOf(map[int]string{})), false) } func TestCheckEmpty_WithValueEmptyRuleTrue(t *testing.T) { var x interface{} v := Constraint{ Target: "str", Name: Empty, Rule: true, Chain: nil, } expected := createError(reflect.ValueOf(x), v, "value can not be null or empty; required parameter") require.Equal(t, checkEmpty(reflect.ValueOf(x), v).Error(), expected.Error()) } func TestCheckEmpty_WithEmptyStringRuleFalse(t *testing.T) { var x interface{} v := Constraint{ Target: "str", Name: Empty, Rule: false, Chain: nil, } require.Nil(t, checkEmpty(reflect.ValueOf(x), v)) } func TestCheckEmpty_IncorrectRule(t *testing.T) { var x interface{} v := Constraint{ Target: "str", Name: Empty, Rule: 10, Chain: nil, } expected := createError(reflect.ValueOf(x), v, fmt.Sprintf("rule must be bool value for %v constraint; got: %v", v.Name, v.Rule)) require.Equal(t, checkEmpty(reflect.ValueOf(x), v).Error(), expected.Error()) } func TestCheckEmpty_WithErrorArray(t *testing.T) { var x interface{} = []string{} v := Constraint{ Target: "str", Name: Empty, Rule: true, Chain: nil, } expected := createError(reflect.ValueOf(x), v, "value can not be null or empty; required parameter") require.Equal(t, checkEmpty(reflect.ValueOf(x), v).Error(), expected.Error()) } func TestCheckNil_WithNilValueRuleTrue(t *testing.T) { var x interface{} v := Constraint{ Target: "x", Name: Null, Rule: true, Chain: []Constraint{ {"x", MaxItems, 4, nil}, }, } expected := createError(reflect.ValueOf(x), v, "value can not be null; required parameter") require.Equal(t, checkNil(reflect.ValueOf(x), v).Error(), expected.Error()) } func TestCheckNil_WithNilValueRuleFalse(t *testing.T) { var x interface{} v := Constraint{ Target: "x", Name: Null, Rule: false, Chain: []Constraint{ {"x", MaxItems, 4, nil}, }, } require.Nil(t, checkNil(reflect.ValueOf(x), v)) } func TestCheckNil_IncorrectRule(t *testing.T) { var x interface{} c := Constraint{ Target: "str", Name: Null, Rule: 10, Chain: nil, } expected := createError(reflect.ValueOf(x), c, fmt.Sprintf("rule must be bool value for %v constraint; got: %v", c.Name, c.Rule)) require.Equal(t, checkNil(reflect.ValueOf(x), c).Error(), expected.Error()) } func TestValidateArrayMap_WithNilValueRuleTrue(t *testing.T) { var a []string var x interface{} = a c := Constraint{ Target: "arr", Name: Null, Rule: true, Chain: nil, } expected := createError(reflect.ValueOf(x), c, "value can not be null; required parameter") require.Equal(t, validateArrayMap(reflect.ValueOf(x), c), expected) } func TestValidateArrayMap_WithNilValueRuleFalse(t *testing.T) { var x interface{} = []string{} c := Constraint{ Target: "arr", Name: Null, Rule: false, Chain: nil, } require.Nil(t, validateArrayMap(reflect.ValueOf(x), c)) } func TestValidateArrayMap_WithValueRuleNullTrue(t *testing.T) { var x interface{} = []string{"1", "2"} c := Constraint{ Target: "arr", Name: Null, Rule: false, Chain: nil, } require.Nil(t, validateArrayMap(reflect.ValueOf(x), c)) } func TestValidateArrayMap_WithEmptyValueRuleTrue(t *testing.T) { var x interface{} = []string{} c := Constraint{ Target: "arr", Name: Empty, Rule: true, Chain: nil, } expected := createError(reflect.ValueOf(x), c, "value can not be null or empty; required parameter") require.Equal(t, validateArrayMap(reflect.ValueOf(x), c), expected) } func TestValidateArrayMap_WithEmptyValueRuleFalse(t *testing.T) { var x interface{} = []string{} c := Constraint{ Target: "arr", Name: Empty, Rule: false, Chain: nil, } require.Nil(t, validateArrayMap(reflect.ValueOf(x), c)) } func TestValidateArrayMap_WithEmptyRuleEmptyTrue(t *testing.T) { var x interface{} = []string{"1", "2"} c := Constraint{ Target: "arr", Name: Empty, Rule: false, Chain: nil, } require.Nil(t, validateArrayMap(reflect.ValueOf(x), c)) } func TestValidateArrayMap_MaxItemsIncorrectRule(t *testing.T) { var x interface{} = []string{"1", "2"} c := Constraint{ Target: "arr", Name: MaxItems, Rule: false, Chain: nil, } expected := createError(reflect.ValueOf(x), c, fmt.Sprintf("rule must be integer for %v constraint; got: %v", c.Name, c.Rule)) require.Equal(t, validateArrayMap(reflect.ValueOf(x), c).Error(), expected.Error()) } func TestValidateArrayMap_MaxItemsNoError(t *testing.T) { var x interface{} = []string{"1", "2"} c := Constraint{ Target: "arr", Name: MaxItems, Rule: 2, Chain: nil, } require.Nil(t, validateArrayMap(reflect.ValueOf(x), c)) } func TestValidateArrayMap_MaxItemsWithError(t *testing.T) { var x interface{} = []string{"1", "2", "3"} c := Constraint{ Target: "arr", Name: MaxItems, Rule: 2, Chain: nil, } expected := createError(reflect.ValueOf(x), c, fmt.Sprintf("maximum item limit is %v; got: 3", c.Rule)) require.Equal(t, validateArrayMap(reflect.ValueOf(x), c).Error(), expected.Error()) } func TestValidateArrayMap_MaxItemsWithEmpty(t *testing.T) { var x interface{} = []string{} c := Constraint{ Target: "arr", Name: MaxItems, Rule: 2, Chain: nil, } require.Nil(t, validateArrayMap(reflect.ValueOf(x), c)) } func TestValidateArrayMap_MinItemsIncorrectRule(t *testing.T) { var x interface{} = []int{1, 2} c := Constraint{ Target: "arr", Name: MinItems, Rule: false, Chain: nil, } expected := createError(reflect.ValueOf(x), c, fmt.Sprintf("rule must be integer for %v constraint; got: %v", c.Name, c.Rule)) require.Equal(t, validateArrayMap(reflect.ValueOf(x), c).Error(), expected.Error()) } func TestValidateArrayMap_MinItemsNoError1(t *testing.T) { c := Constraint{ Target: "arr", Name: MinItems, Rule: 2, Chain: nil, } require.Nil(t, validateArrayMap(reflect.ValueOf([]int{1, 2}), c)) } func TestValidateArrayMap_MinItemsNoError2(t *testing.T) { c := Constraint{ Target: "arr", Name: MinItems, Rule: 2, Chain: nil, } require.Nil(t, validateArrayMap(reflect.ValueOf([]int{1, 2, 3}), c)) } func TestValidateArrayMap_MinItemsWithError(t *testing.T) { var x interface{} = []int{1} c := Constraint{ Target: "arr", Name: MinItems, Rule: 2, Chain: nil, } expected := createError(reflect.ValueOf(x), c, fmt.Sprintf("minimum item limit is %v; got: 1", c.Rule)) require.Equal(t, validateArrayMap(reflect.ValueOf(x), c).Error(), expected.Error()) } func TestValidateArrayMap_MinItemsWithEmpty(t *testing.T) { var x interface{} = []int{} c := Constraint{ Target: "arr", Name: MinItems, Rule: 2, Chain: nil, } expected := createError(reflect.ValueOf(x), c, fmt.Sprintf("minimum item limit is %v; got: 0", c.Rule)) require.Equal(t, validateArrayMap(reflect.ValueOf(x), c).Error(), expected.Error()) } func TestValidateArrayMap_Map_MaxItemsIncorrectRule(t *testing.T) { var x interface{} = map[int]string{1: "1", 2: "2"} c := Constraint{ Target: "arr", Name: MaxItems, Rule: false, Chain: nil, } require.Equal(t, strings.Contains(validateArrayMap(reflect.ValueOf(x), c).Error(), fmt.Sprintf("rule must be integer for %v constraint; got: %v", c.Name, c.Rule)), true) } func TestValidateArrayMap_Map_MaxItemsNoError(t *testing.T) { var x interface{} = map[int]string{1: "1", 2: "2"} c := Constraint{ Target: "arr", Name: MaxItems, Rule: 2, Chain: nil, } require.Nil(t, validateArrayMap(reflect.ValueOf(x), c)) } func TestValidateArrayMap_Map_MaxItemsWithError(t *testing.T) { a := map[int]string{1: "1", 2: "2", 3: "3"} var x interface{} = a c := Constraint{ Target: "arr", Name: MaxItems, Rule: 2, Chain: nil, } require.Equal(t, strings.Contains(validateArrayMap(reflect.ValueOf(x), c).Error(), fmt.Sprintf("maximum item limit is %v; got: %v", c.Rule, len(a))), true) } func TestValidateArrayMap_Map_MaxItemsWithEmpty(t *testing.T) { a := map[int]string{} var x interface{} = a c := Constraint{ Target: "arr", Name: MaxItems, Rule: 2, Chain: nil, } require.Nil(t, validateArrayMap(reflect.ValueOf(x), c)) } func TestValidateArrayMap_Map_MinItemsIncorrectRule(t *testing.T) { var x interface{} = map[int]string{1: "1", 2: "2"} c := Constraint{ Target: "arr", Name: MinItems, Rule: false, Chain: nil, } require.Equal(t, strings.Contains(validateArrayMap(reflect.ValueOf(x), c).Error(), fmt.Sprintf("rule must be integer for %v constraint; got: %v", c.Name, c.Rule)), true) } func TestValidateArrayMap_Map_MinItemsNoError1(t *testing.T) { var x interface{} = map[int]string{1: "1", 2: "2"} require.Nil(t, validateArrayMap(reflect.ValueOf(x), Constraint{ Target: "arr", Name: MinItems, Rule: 2, Chain: nil, })) } func TestValidateArrayMap_Map_MinItemsNoError2(t *testing.T) { var x interface{} = map[int]string{1: "1", 2: "2", 3: "3"} c := Constraint{ Target: "arr", Name: MinItems, Rule: 2, Chain: nil, } require.Nil(t, validateArrayMap(reflect.ValueOf(x), c)) } func TestValidateArrayMap_Map_MinItemsWithError(t *testing.T) { a := map[int]string{1: "1"} var x interface{} = a c := Constraint{ Target: "arr", Name: MinItems, Rule: 2, Chain: nil, } expected := createError(reflect.ValueOf(x), c, fmt.Sprintf("minimum item limit is %v; got: %v", c.Rule, len(a))) require.Equal(t, validateArrayMap(reflect.ValueOf(x), c).Error(), expected.Error()) } func TestValidateArrayMap_Map_MinItemsWithEmpty(t *testing.T) { a := map[int]string{} var x interface{} = a c := Constraint{ Target: "arr", Name: MinItems, Rule: 2, Chain: nil, } expected := createError(reflect.ValueOf(x), c, fmt.Sprintf("minimum item limit is %v; got: %v", c.Rule, len(a))) require.Equal(t, validateArrayMap(reflect.ValueOf(x), c).Error(), expected.Error()) } // func TestValidateArrayMap_Map_MinItemsNil(t *testing.T) { // var a map[int]float64 // var x interface{} = a // c := Constraint{ // Target: "str", // Name: MinItems, // Rule: true, // Chain: nil, // } // expected := createError(reflect.Value(x), c, fmt.Sprintf("all items in parameter %v must be unique; got:%v", c.Target, x)) // if z := validateArrayMap(reflect.ValueOf(x), c); strings.Contains(z.Error(), "all items in parameter str must be unique;") { // t.Fatalf("autorest/validation: valiateArrayMap failed to return error \nexpect: %v;\ngot: %v", expected, z) // } // } func TestValidateArrayMap_Map_UniqueItemsTrue(t *testing.T) { var x interface{} = map[float64]int{1.2: 1, 1.4: 2} c := Constraint{ Target: "str", Name: UniqueItems, Rule: true, Chain: nil, } require.Nil(t, validateArrayMap(reflect.ValueOf(x), c)) } func TestValidateArrayMap_Map_UniqueItemsFalse(t *testing.T) { var x interface{} = map[string]string{"1": "1", "2": "2", "3": "1"} c := Constraint{ Target: "str", Name: UniqueItems, Rule: true, Chain: nil, } z := validateArrayMap(reflect.ValueOf(x), c) require.Equal(t, strings.Contains(z.Error(), fmt.Sprintf("all items in parameter %q must be unique", c.Target)), true) } func TestValidateArrayMap_Map_UniqueItemsEmpty(t *testing.T) { // Consider Empty map as not unique returns false var x interface{} = map[int]float64{} c := Constraint{ Target: "str", Name: UniqueItems, Rule: true, Chain: nil, } z := validateArrayMap(reflect.ValueOf(x), c) require.Equal(t, strings.Contains(z.Error(), fmt.Sprintf("all items in parameter %q must be unique", c.Target)), true) } func TestValidateArrayMap_Map_UniqueItemsNil(t *testing.T) { var a map[int]float64 var x interface{} = a c := Constraint{ Target: "str", Name: UniqueItems, Rule: true, Chain: nil, } z := validateArrayMap(reflect.ValueOf(x), c) require.Equal(t, strings.Contains(z.Error(), fmt.Sprintf("all items in parameter %q must be unique; got:%v", c.Target, x)), true) } func TestValidateArrayMap_Array_UniqueItemsTrue(t *testing.T) { var x interface{} = []int{1, 2} c := Constraint{ Target: "str", Name: UniqueItems, Rule: true, Chain: nil, } require.Nil(t, validateArrayMap(reflect.ValueOf(x), c)) } func TestValidateArrayMap_Array_UniqueItemsFalse(t *testing.T) { var x interface{} = []string{"1", "2", "1"} c := Constraint{ Target: "str", Name: UniqueItems, Rule: true, Chain: nil, } z := validateArrayMap(reflect.ValueOf(x), c) require.Equal(t, strings.Contains(z.Error(), fmt.Sprintf("all items in parameter %q must be unique; got:%v", c.Target, x)), true) } func TestValidateArrayMap_Array_UniqueItemsEmpty(t *testing.T) { // Consider Empty array as not unique returns false var x interface{} = []float64{} c := Constraint{ Target: "str", Name: UniqueItems, Rule: true, Chain: nil, } z := validateArrayMap(reflect.ValueOf(x), c) require.Equal(t, strings.Contains(z.Error(), fmt.Sprintf("all items in parameter %q must be unique; got:%v", c.Target, x)), true) } func TestValidateArrayMap_Array_UniqueItemsNil(t *testing.T) { // Consider nil array as not unique returns false var a []float64 var x interface{} = a c := Constraint{ Target: "str", Name: UniqueItems, Rule: true, Chain: nil, } z := validateArrayMap(reflect.ValueOf(x), c) require.Equal(t, strings.Contains(z.Error(), fmt.Sprintf("all items in parameter %q must be unique; got:%v", c.Target, x)), true) } func TestValidateArrayMap_Array_UniqueItemsInvalidType(t *testing.T) { var x interface{} = "hello" c := Constraint{ Target: "str", Name: UniqueItems, Rule: true, Chain: nil, } z := validateArrayMap(reflect.ValueOf(x), c) require.Equal(t, strings.Contains(z.Error(), fmt.Sprintf("type must be array, slice or map for constraint %v; got: %v", c.Name, reflect.ValueOf(x).Kind())), true) } func TestValidateArrayMap_Array_UniqueItemsInvalidConstraint(t *testing.T) { var x interface{} = "hello" c := Constraint{ Target: "str", Name: "sdad", Rule: true, Chain: nil, } z := validateArrayMap(reflect.ValueOf(x), c) require.Equal(t, strings.Contains(z.Error(), fmt.Sprintf("constraint %v is not applicable to array, slice and map type", c.Name)), true) } func TestValidateArrayMap_ValidateChainConstraint1(t *testing.T) { a := []int{1, 2, 3, 4} var x interface{} = a c := Constraint{ Target: "str", Name: Null, Rule: true, Chain: []Constraint{ {"str", MaxItems, 3, nil}, }, } z := validateArrayMap(reflect.ValueOf(x), c) require.Equal(t, strings.Contains(z.Error(), fmt.Sprintf("maximum item limit is %v; got: %v", (c.Chain)[0].Rule, len(a))), true) } func TestValidateArrayMap_ValidateChainConstraint2(t *testing.T) { a := []int{1, 2, 3, 4} var x interface{} = a c := Constraint{ Target: "str", Name: Empty, Rule: true, Chain: []Constraint{ {"str", MaxItems, 3, nil}, }, } z := validateArrayMap(reflect.ValueOf(x), c) require.Equal(t, strings.Contains(z.Error(), fmt.Sprintf("maximum item limit is %v; got: %v", (c.Chain)[0].Rule, len(a))), true) } func TestValidateArrayMap_ValidateChainConstraint3(t *testing.T) { var a []string var x interface{} = a c := Constraint{ Target: "str", Name: Null, Rule: true, Chain: []Constraint{ {"str", MaxItems, 3, nil}, }, } z := validateArrayMap(reflect.ValueOf(x), c) require.Equal(t, strings.Contains(z.Error(), fmt.Sprintf("value can not be null; required parameter")), true) } func TestValidateArrayMap_ValidateChainConstraint4(t *testing.T) { var x interface{} = []int{} c := Constraint{ Target: "str", Name: Empty, Rule: true, Chain: []Constraint{ {"str", MaxItems, 3, nil}, }, } z := validateArrayMap(reflect.ValueOf(x), c) require.Equal(t, strings.Contains(z.Error(), fmt.Sprintf("value can not be null or empty; required parameter")), true) } func TestValidateArrayMap_ValidateChainConstraintNilNotRequired(t *testing.T) { var a []int var x interface{} = a c := Constraint{ Target: "str", Name: Null, Rule: false, Chain: []Constraint{ {"str", MaxItems, 3, nil}, }, } require.Nil(t, validateArrayMap(reflect.ValueOf(x), c)) } func TestValidateArrayMap_ValidateChainConstraintEmptyNotRequired(t *testing.T) { var x interface{} = map[string]int{} c := Constraint{ Target: "str", Name: Empty, Rule: false, Chain: []Constraint{ {"str", MaxItems, 3, nil}, }, } require.Nil(t, validateArrayMap(reflect.ValueOf(x), c)) } func TestValidateArrayMap_ReadOnlyWithError(t *testing.T) { var x interface{} = []int{1, 2} c := Constraint{ Target: "str", Name: ReadOnly, Rule: true, Chain: []Constraint{ {"str", MaxItems, 3, nil}, }, } z := validateArrayMap(reflect.ValueOf(x), c) require.Equal(t, strings.Contains(z.Error(), fmt.Sprintf("readonly parameter; must send as nil or empty in request")), true) } func TestValidateArrayMap_ReadOnlyWithoutError(t *testing.T) { var x interface{} = []int{} c := Constraint{ Target: "str", Name: ReadOnly, Rule: true, Chain: nil, } require.Nil(t, validateArrayMap(reflect.ValueOf(x), c)) } func TestValidateString_ReadOnly(t *testing.T) { var x interface{} = "Hello Gopher" c := Constraint{ Target: "str", Name: ReadOnly, Rule: true, Chain: nil, } require.Equal(t, strings.Contains(validateString(reflect.ValueOf(x), c).Error(), fmt.Sprintf("readonly parameter; must send as nil or empty in request")), true) } func TestValidateString_EmptyTrue(t *testing.T) { // Empty true means parameter is required but Empty returns error c := Constraint{ Target: "str", Name: Empty, Rule: true, Chain: nil, } require.Equal(t, strings.Contains(validateString(reflect.ValueOf(""), c).Error(), fmt.Sprintf("value can not be null or empty; required parameter")), true) } func TestValidateString_EmptyFalse(t *testing.T) { // Empty false means parameter is not required and Empty return nil var x interface{} c := Constraint{ Target: "str", Name: Empty, Rule: false, Chain: nil, } require.Nil(t, validateString(reflect.ValueOf(x), c)) } func TestValidateString_MaxLengthInvalid(t *testing.T) { // Empty true means parameter is required but Empty returns error var x interface{} = "Hello" c := Constraint{ Target: "str", Name: MaxLength, Rule: 4, Chain: nil, } require.Equal(t, strings.Contains(validateString(reflect.ValueOf(x), c).Error(), fmt.Sprintf("value length must be less than or equal to %v", c.Rule)), true) } func TestValidateString_MaxLengthValid(t *testing.T) { // Empty false means parameter is not required and Empty return nil c := Constraint{ Target: "str", Name: MaxLength, Rule: 7, Chain: nil, } require.Nil(t, validateString(reflect.ValueOf("Hello"), c)) } func TestValidateString_MaxLengthRuleInvalid(t *testing.T) { var x interface{} = "Hello" c := Constraint{ Target: "str", Name: MaxLength, Rule: true, // must be int for maxLength Chain: nil, } require.Equal(t, strings.Contains(validateString(reflect.ValueOf(x), c).Error(), fmt.Sprintf("rule must be integer value for %v constraint; got: %v", c.Name, c.Rule)), true) } func TestValidateString_MinLengthInvalid(t *testing.T) { var x interface{} = "Hello" c := Constraint{ Target: "str", Name: MinLength, Rule: 10, Chain: nil, } require.Equal(t, strings.Contains(validateString(reflect.ValueOf(x), c).Error(), fmt.Sprintf("value length must be greater than or equal to %v", c.Rule)), true) } func TestValidateString_MinLengthValid(t *testing.T) { c := Constraint{ Target: "str", Name: MinLength, Rule: 2, Chain: nil, } require.Nil(t, validateString(reflect.ValueOf("Hello"), c)) } func TestValidateString_MinLengthRuleInvalid(t *testing.T) { var x interface{} = "Hello" c := Constraint{ Target: "str", Name: MinLength, Rule: true, // must be int for minLength Chain: nil, } require.Equal(t, strings.Contains(validateString(reflect.ValueOf(x), c).Error(), fmt.Sprintf("rule must be integer value for %v constraint; got: %v", c.Name, c.Rule)), true) } func TestValidateString_PatternInvalidPattern(t *testing.T) { var x interface{} = "Hello" c := Constraint{ Target: "str", Name: Pattern, Rule: `^[[:alnum:$`, Chain: nil, } require.Equal(t, strings.Contains(validateString(reflect.ValueOf(x), c).Error(), "error parsing regexp: missing closing ]"), true) } func TestValidateString_PatternMatch1(t *testing.T) { c := Constraint{ Target: "str", Name: Pattern, Rule: `^http://\w+$`, Chain: nil, } require.Nil(t, validateString(reflect.ValueOf("http://masd"), c)) } func TestValidateString_PatternMatch2(t *testing.T) { c := Constraint{ Target: "str", Name: Pattern, Rule: `^[a-zA-Z0-9]+$`, Chain: nil, } require.Nil(t, validateString(reflect.ValueOf("asdadad2323sad"), c)) } func TestValidateString_PatternNotMatch(t *testing.T) { var x interface{} = "asdad@@ad2323sad" c := Constraint{ Target: "str", Name: Pattern, Rule: `^[a-zA-Z0-9]+$`, Chain: nil, } require.Equal(t, strings.Contains(validateString(reflect.ValueOf(x), c).Error(), fmt.Sprintf("value doesn't match pattern %v", c.Rule)), true) } func TestValidateString_InvalidConstraint(t *testing.T) { var x interface{} = "asdad@@ad2323sad" c := Constraint{ Target: "str", Name: UniqueItems, Rule: "^[a-zA-Z0-9]+$", Chain: nil, } require.Equal(t, strings.Contains(validateString(reflect.ValueOf(x), c).Error(), fmt.Sprintf("constraint %s is not applicable to string type", c.Name)), true) } func TestValidateFloat_InvalidConstraint(t *testing.T) { var x interface{} = 1.4 c := Constraint{ Target: "str", Name: UniqueItems, Rule: 3.0, Chain: nil, } require.Equal(t, strings.Contains(validateFloat(reflect.ValueOf(x), c).Error(), fmt.Sprintf("constraint %v is not applicable for type float", c.Name)), true) } func TestValidateFloat_InvalidRuleValue(t *testing.T) { var x interface{} = 1.4 c := Constraint{ Target: "str", Name: ExclusiveMinimum, Rule: 3, Chain: nil, } require.Equal(t, strings.Contains(validateFloat(reflect.ValueOf(x), c).Error(), fmt.Sprintf("rule must be float value for %v constraint; got: %v", c.Name, c.Rule)), true) } func TestValidateFloat_ExclusiveMinimumConstraintValid(t *testing.T) { c := Constraint{ Target: "str", Name: ExclusiveMinimum, Rule: 1.0, Chain: nil, } require.Nil(t, validateFloat(reflect.ValueOf(1.42), c)) } func TestValidateFloat_ExclusiveMinimumConstraintInvalid(t *testing.T) { var x interface{} = 1.4 c := Constraint{ Target: "str", Name: ExclusiveMinimum, Rule: 1.5, Chain: nil, } require.Equal(t, strings.Contains(validateFloat(reflect.ValueOf(x), c).Error(), fmt.Sprintf("value must be greater than %v", c.Rule)), true) } func TestValidateFloat_ExclusiveMinimumConstraintBoundary(t *testing.T) { var x interface{} = 1.42 c := Constraint{ Target: "str", Name: ExclusiveMinimum, Rule: 1.42, Chain: nil, } require.Equal(t, strings.Contains(validateFloat(reflect.ValueOf(x), c).Error(), fmt.Sprintf("value must be greater than %v", c.Rule)), true) } func TestValidateFloat_exclusiveMaximumConstraintValid(t *testing.T) { c := Constraint{ Target: "str", Name: ExclusiveMaximum, Rule: 2.0, Chain: nil, } require.Nil(t, validateFloat(reflect.ValueOf(1.42), c)) } func TestValidateFloat_exclusiveMaximumConstraintInvalid(t *testing.T) { var x interface{} = 1.42 c := Constraint{ Target: "str", Name: ExclusiveMaximum, Rule: 1.2, Chain: nil, } require.Equal(t, strings.Contains(validateFloat(reflect.ValueOf(x), c).Error(), fmt.Sprintf("value must be less than %v", c.Rule)), true) } func TestValidateFloat_exclusiveMaximumConstraintBoundary(t *testing.T) { var x interface{} = 1.42 c := Constraint{ Target: "str", Name: ExclusiveMaximum, Rule: 1.42, Chain: nil, } require.Equal(t, strings.Contains(validateFloat(reflect.ValueOf(x), c).Error(), fmt.Sprintf("value must be less than %v", c.Rule)), true) } func TestValidateFloat_inclusiveMaximumConstraintValid(t *testing.T) { c := Constraint{ Target: "str", Name: InclusiveMaximum, Rule: 2.0, Chain: nil, } require.Nil(t, validateFloat(reflect.ValueOf(1.42), c)) } func TestValidateFloat_inclusiveMaximumConstraintInvalid(t *testing.T) { var x interface{} = 1.42 c := Constraint{ Target: "str", Name: InclusiveMaximum, Rule: 1.2, Chain: nil, } require.Equal(t, strings.Contains(validateFloat(reflect.ValueOf(x), c).Error(), fmt.Sprintf("value must be less than or equal to %v", c.Rule)), true) } func TestValidateFloat_inclusiveMaximumConstraintBoundary(t *testing.T) { c := Constraint{ Target: "str", Name: InclusiveMaximum, Rule: 1.42, Chain: nil, } require.Nil(t, validateFloat(reflect.ValueOf(1.42), c)) } func TestValidateFloat_InclusiveMinimumConstraintValid(t *testing.T) { c := Constraint{ Target: "str", Name: InclusiveMinimum, Rule: 1.0, Chain: nil, } require.Nil(t, validateFloat(reflect.ValueOf(1.42), c)) } func TestValidateFloat_InclusiveMinimumConstraintInvalid(t *testing.T) { var x interface{} = 1.42 c := Constraint{ Target: "str", Name: InclusiveMinimum, Rule: 1.5, Chain: nil, } require.Equal(t, strings.Contains(validateFloat(reflect.ValueOf(x), c).Error(), fmt.Sprintf("value must be greater than or equal to %v", c.Rule)), true) } func TestValidateFloat_InclusiveMinimumConstraintBoundary(t *testing.T) { c := Constraint{ Target: "str", Name: InclusiveMinimum, Rule: 1.42, Chain: nil, } require.Nil(t, validateFloat(reflect.ValueOf(1.42), c)) } func TestValidateInt_InvalidConstraint(t *testing.T) { var x interface{} = 1 c := Constraint{ Target: "str", Name: UniqueItems, Rule: 3, Chain: nil, } require.Equal(t, strings.Contains(validateInt(reflect.ValueOf(x), c).Error(), fmt.Sprintf("constraint %s is not applicable for type integer", c.Name)), true) } func TestValidateInt_InvalidRuleValue(t *testing.T) { var x interface{} = 1 c := Constraint{ Target: "str", Name: ExclusiveMinimum, Rule: 3.4, Chain: nil, } require.Equal(t, strings.Contains(validateInt(reflect.ValueOf(x), c).Error(), fmt.Sprintf("rule must be integer value for %v constraint; got: %v", c.Name, c.Rule)), true) } func TestValidateInt_ExclusiveMinimumConstraintValid(t *testing.T) { c := Constraint{ Target: "str", Name: ExclusiveMinimum, Rule: 1, Chain: nil, } require.Nil(t, validateInt(reflect.ValueOf(3), c)) } func TestValidateInt_ExclusiveMinimumConstraintInvalid(t *testing.T) { var x interface{} = 1 c := Constraint{ Target: "str", Name: ExclusiveMinimum, Rule: 3, Chain: nil, } require.Equal(t, validateInt(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf(x), c, fmt.Sprintf("value must be greater than %v", c.Rule)).Error()) } func TestValidateInt_ExclusiveMinimumConstraintBoundary(t *testing.T) { var x interface{} = 1 c := Constraint{ Target: "str", Name: ExclusiveMinimum, Rule: 1, Chain: nil, } require.Equal(t, validateInt(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf(x), c, fmt.Sprintf("value must be greater than %v", c.Rule)).Error()) } func TestValidateInt_exclusiveMaximumConstraintValid(t *testing.T) { c := Constraint{ Target: "str", Name: ExclusiveMaximum, Rule: 2, Chain: nil, } require.Nil(t, validateInt(reflect.ValueOf(1), c)) } func TestValidateInt_exclusiveMaximumConstraintInvalid(t *testing.T) { var x interface{} = 2 c := Constraint{ Target: "str", Name: ExclusiveMaximum, Rule: 1, Chain: nil, } require.Equal(t, validateInt(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf(x), c, fmt.Sprintf("value must be less than %v", c.Rule)).Error()) } func TestValidateInt_exclusiveMaximumConstraintBoundary(t *testing.T) { var x interface{} = 1 c := Constraint{ Target: "str", Name: ExclusiveMaximum, Rule: 1, Chain: nil, } require.Equal(t, validateInt(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf(x), c, fmt.Sprintf("value must be less than %v", c.Rule)).Error()) } func TestValidateInt_inclusiveMaximumConstraintValid(t *testing.T) { c := Constraint{ Target: "str", Name: InclusiveMaximum, Rule: int64(2), Chain: nil, } require.Nil(t, validateInt(reflect.ValueOf(1), c)) } func TestValidateInt_inclusiveMaximumConstraintInvalid(t *testing.T) { var x interface{} = 2 c := Constraint{ Target: "str", Name: InclusiveMaximum, Rule: int64(1), Chain: nil, } require.Equal(t, validateInt(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf(x), c, fmt.Sprintf("value must be less than or equal to %v", c.Rule)).Error()) } func TestValidateInt_inclusiveMaximumConstraintBoundary(t *testing.T) { c := Constraint{ Target: "str", Name: InclusiveMaximum, Rule: int64(1), Chain: nil, } require.Nil(t, validateInt(reflect.ValueOf(1), c)) } func TestValidateInt_InclusiveMinimumConstraintValid(t *testing.T) { c := Constraint{ Target: "str", Name: InclusiveMinimum, Rule: 1, Chain: nil, } require.Nil(t, validateInt(reflect.ValueOf(1), c)) } func TestValidateInt_InclusiveMinimumConstraintInvalid(t *testing.T) { var x interface{} = 1 c := Constraint{ Target: "str", Name: InclusiveMinimum, Rule: 2, Chain: nil, } require.Equal(t, validateInt(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf(x), c, fmt.Sprintf("value must be greater than or equal to %v", c.Rule)).Error()) } func TestValidateInt_InclusiveMinimumConstraintBoundary(t *testing.T) { c := Constraint{ Target: "str", Name: InclusiveMinimum, Rule: 1, Chain: nil, } require.Nil(t, validateInt(reflect.ValueOf(1), c)) } func TestValidateInt_MultipleOfWithoutError(t *testing.T) { c := Constraint{ Target: "str", Name: MultipleOf, Rule: 10, Chain: nil, } require.Nil(t, validateInt(reflect.ValueOf(2300), c)) } func TestValidateInt_MultipleOfWithError(t *testing.T) { c := Constraint{ Target: "str", Name: MultipleOf, Rule: 11, Chain: nil, } require.Equal(t, validateInt(reflect.ValueOf(2300), c).Error(), createError(reflect.ValueOf(2300), c, fmt.Sprintf("value must be a multiple of %v", c.Rule)).Error()) } func TestValidatePointer_NilTrue(t *testing.T) { var z *int var x interface{} = z c := Constraint{ Target: "ptr", Name: Null, Rule: true, // Required property Chain: nil, } require.Equal(t, validatePtr(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf(x), c, "value can not be null; required parameter").Error()) } func TestValidatePointer_NilFalse(t *testing.T) { var z *int var x interface{} = z c := Constraint{ Target: "ptr", Name: Null, Rule: false, // not required property Chain: nil, } require.Nil(t, validatePtr(reflect.ValueOf(x), c)) } func TestValidatePointer_NilReadonlyValid(t *testing.T) { var z *int var x interface{} = z c := Constraint{ Target: "ptr", Name: ReadOnly, Rule: true, Chain: nil, } require.Nil(t, validatePtr(reflect.ValueOf(x), c)) } func TestValidatePointer_NilReadonlyInvalid(t *testing.T) { z := 10 var x interface{} = &z c := Constraint{ Target: "ptr", Name: ReadOnly, Rule: true, Chain: nil, } require.Equal(t, validatePtr(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf(z), c, "readonly parameter; must send as nil or empty in request").Error()) } func TestValidatePointer_IntValid(t *testing.T) { z := 10 var x interface{} = &z c := Constraint{ Target: "ptr", Name: InclusiveMinimum, Rule: 3, Chain: nil, } require.Nil(t, validatePtr(reflect.ValueOf(x), c)) } func TestValidatePointer_IntInvalid(t *testing.T) { z := 10 var x interface{} = &z c := Constraint{ Target: "ptr", Name: Null, Rule: true, Chain: []Constraint{ { Target: "ptr", Name: InclusiveMinimum, Rule: 11, Chain: nil, }, }, } require.Equal(t, validatePtr(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf(10), c.Chain[0], "value must be greater than or equal to 11").Error()) } func TestValidatePointer_IntInvalidConstraint(t *testing.T) { z := 10 var x interface{} = &z c := Constraint{ Target: "ptr", Name: Null, Rule: true, Chain: []Constraint{ { Target: "ptr", Name: MaxItems, Rule: 3, Chain: nil, }, }, } require.Equal(t, validatePtr(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf(10), c.Chain[0], fmt.Sprintf("constraint %v is not applicable for type integer", MaxItems)).Error()) } func TestValidatePointer_ValidInt64(t *testing.T) { z := int64(10) var x interface{} = &z c := Constraint{ Target: "ptr", Name: Null, Rule: true, Chain: []Constraint{ { Target: "ptr", Name: InclusiveMinimum, Rule: 3, Chain: nil, }, }} require.Nil(t, validatePtr(reflect.ValueOf(x), c)) } func TestValidatePointer_InvalidConstraintInt64(t *testing.T) { z := int64(10) var x interface{} = &z c := Constraint{ Target: "ptr", Name: Null, Rule: true, Chain: []Constraint{ { Target: "ptr", Name: MaxItems, Rule: 3, Chain: nil, }, }, } require.Equal(t, validatePtr(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf(10), c.Chain[0], fmt.Sprintf("constraint %v is not applicable for type integer", MaxItems)).Error()) } func TestValidatePointer_ValidFloat(t *testing.T) { z := 10.1 var x interface{} = &z c := Constraint{ Target: "ptr", Name: Null, Rule: true, Chain: []Constraint{ { Target: "ptr", Name: InclusiveMinimum, Rule: 3.0, Chain: nil, }}} require.Nil(t, validatePtr(reflect.ValueOf(x), c)) } func TestValidatePointer_InvalidFloat(t *testing.T) { z := 10.1 var x interface{} = &z c := Constraint{ Target: "ptr", Name: Null, Rule: true, Chain: []Constraint{ { Target: "ptr", Name: InclusiveMinimum, Rule: 12.0, Chain: nil, }}, } require.Equal(t, validatePtr(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf(10.1), c.Chain[0], "value must be greater than or equal to 12").Error()) } func TestValidatePointer_InvalidConstraintFloat(t *testing.T) { z := 10.1 var x interface{} = &z c := Constraint{ Target: "ptr", Name: Null, Rule: true, Chain: []Constraint{ { Target: "ptr", Name: MaxItems, Rule: 3.0, Chain: nil, }}, } require.Equal(t, validatePtr(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf(10.1), c.Chain[0], fmt.Sprintf("constraint %v is not applicable for type float", MaxItems)).Error()) } func TestValidatePointer_StringValid(t *testing.T) { z := "hello" var x interface{} = &z c := Constraint{ Target: "ptr", Name: Null, Rule: true, Chain: []Constraint{ { Target: "ptr", Name: Pattern, Rule: "^[a-z]+$", Chain: nil, }}} require.Nil(t, validatePtr(reflect.ValueOf(x), c)) } func TestValidatePointer_StringInvalid(t *testing.T) { z := "hello" var x interface{} = &z c := Constraint{ Target: "ptr", Name: Null, Rule: true, Chain: []Constraint{ { Target: "ptr", Name: MaxLength, Rule: 2, Chain: nil, }}} require.Equal(t, validatePtr(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf("hello"), c.Chain[0], "value length must be less than or equal to 2").Error()) } func TestValidatePointer_ArrayValid(t *testing.T) { c := Constraint{ Target: "ptr", Name: Null, Rule: true, Chain: []Constraint{ { Target: "ptr", Name: UniqueItems, Rule: "true", Chain: nil, }}} require.Nil(t, validatePtr(reflect.ValueOf(&[]string{"1", "2"}), c)) } func TestValidatePointer_ArrayInvalid(t *testing.T) { z := []string{"1", "2", "2"} var x interface{} = &z c := Constraint{ Target: "ptr", Name: Null, Rule: true, Chain: []Constraint{{ Target: "ptr", Name: UniqueItems, Rule: true, Chain: nil, }}, } require.Equal(t, validatePtr(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf(z), c.Chain[0], fmt.Sprintf("all items in parameter %q must be unique; got:%v", c.Target, z)).Error()) } func TestValidatePointer_MapValid(t *testing.T) { c := Constraint{ Target: "ptr", Name: Null, Rule: true, Chain: []Constraint{ { Target: "ptr", Name: UniqueItems, Rule: true, Chain: nil, }}} require.Nil(t, validatePtr(reflect.ValueOf(&map[interface{}]string{1: "1", "1": "2"}), c)) } func TestValidatePointer_MapInvalid(t *testing.T) { z := map[interface{}]string{1: "1", "1": "2", 1.3: "2"} var x interface{} = &z c := Constraint{ Target: "ptr", Name: Null, Rule: true, Chain: []Constraint{{ Target: "ptr", Name: UniqueItems, Rule: true, Chain: nil, }}, } require.Equal(t, strings.Contains(validatePtr(reflect.ValueOf(x), c).Error(), fmt.Sprintf("all items in parameter %q must be unique;", c.Target)), true) } type Child struct { I string } type Product struct { C *Child Str *string Name string Arr *[]string M *map[string]string Num *int32 } type Sample struct { M *map[string]*string Name string } func TestValidatePointer_StructWithError(t *testing.T) { s := "hello" var x interface{} = &Product{ C: &Child{"100"}, Str: &s, Name: "Gopher", } c := Constraint{ "p", Null, "True", []Constraint{ {"C", Null, true, []Constraint{ {"I", MaxLength, 2, nil}, }}, {"Str", MaxLength, 2, nil}, {"Name", MaxLength, 5, nil}, }, } require.Equal(t, validatePtr(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf("100"), c.Chain[0].Chain[0], "value length must be less than or equal to 2").Error()) } func TestValidatePointer_WithNilStruct(t *testing.T) { var p *Product var x interface{} = p c := Constraint{ "p", Null, true, []Constraint{ {"C", Null, true, []Constraint{ {"I", Empty, true, []Constraint{ {"I", MaxLength, 5, nil}, }}, }}, {"Str", MaxLength, 2, nil}, {"Name", MaxLength, 5, nil}, }, } require.Equal(t, validatePtr(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf(x), c, fmt.Sprintf("value can not be null; required parameter")).Error()) } func TestValidatePointer_StructWithNoError(t *testing.T) { s := "hello" var x interface{} = &Product{ C: &Child{"100"}, Str: &s, Name: "Gopher", } c := Constraint{ "p", Null, true, []Constraint{ {"C", Null, true, []Constraint{ {"I", Empty, true, []Constraint{ {"I", MaxLength, 5, nil}, }}, }}, }, } require.Nil(t, validatePtr(reflect.ValueOf(x), c)) } func TestValidateStruct_FieldNotExist(t *testing.T) { s := "hello" var x interface{} = Product{ C: &Child{"100"}, Str: &s, Name: "Gopher", } c := Constraint{ "C", Null, true, []Constraint{ {"Name", Empty, true, nil}, }, } s = "Name" require.Equal(t, validateStruct(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf(Child{"100"}), c.Chain[0], fmt.Sprintf("field %q doesn't exist", s)).Error()) } func TestValidateStruct_WithChainConstraint(t *testing.T) { s := "hello" var x interface{} = Product{ C: &Child{"100"}, Str: &s, Name: "Gopher", } c := Constraint{ "C", Null, true, []Constraint{ {"I", Empty, true, []Constraint{ {"I", MaxLength, 2, nil}, }}, }, } require.Equal(t, validateStruct(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf("100"), c.Chain[0].Chain[0], "value length must be less than or equal to 2").Error()) } func TestValidateStruct_WithoutChainConstraint(t *testing.T) { s := "hello" var x interface{} = Product{ C: &Child{""}, Str: &s, Name: "Gopher", } c := Constraint{"C", Null, true, []Constraint{ {"I", Empty, true, nil}, // throw error for Empty }} require.Equal(t, validateStruct(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf(""), c.Chain[0], "value can not be null or empty; required parameter").Error()) } func TestValidateStruct_WithArrayNull(t *testing.T) { s := "hello" var x interface{} = Product{ C: &Child{""}, Str: &s, Name: "Gopher", Arr: nil, } c := Constraint{"Arr", Null, true, []Constraint{ {"Arr", MaxItems, 4, nil}, {"Arr", MinItems, 2, nil}, }, } require.Equal(t, validateStruct(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf(x.(Product).Arr), c, "value can not be null; required parameter").Error()) } func TestValidateStruct_WithArrayEmptyError(t *testing.T) { // arr := []string{} var x interface{} = Product{ Arr: &[]string{}, } c := Constraint{ "Arr", Null, true, []Constraint{ {"Arr", Empty, true, nil}, {"Arr", MaxItems, 4, nil}, {"Arr", MinItems, 2, nil}, }} require.Equal(t, validateStruct(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf(*(x.(Product).Arr)), c.Chain[0], fmt.Sprintf("value can not be null or empty; required parameter")).Error()) } func TestValidateStruct_WithArrayEmptyWithoutError(t *testing.T) { var x interface{} = Product{ Arr: &[]string{}, } c := Constraint{ "Arr", Null, true, []Constraint{ {"Arr", Empty, false, nil}, {"Arr", MaxItems, 4, nil}, }, } require.Nil(t, validateStruct(reflect.ValueOf(x), c)) } func TestValidateStruct_ArrayWithError(t *testing.T) { arr := []string{"1", "1"} var x interface{} = Product{ Arr: &arr, } c := Constraint{ "Arr", Null, true, []Constraint{ {"Arr", Empty, true, nil}, {"Arr", MaxItems, 4, nil}, {"Arr", UniqueItems, true, nil}, }, } s := "Arr" require.Equal(t, validateStruct(reflect.ValueOf(x), c).Error(), createError(reflect.ValueOf(*(x.(Product).Arr)), c.Chain[2], fmt.Sprintf("all items in parameter %q must be unique; got:%v", s, *(x.(Product).Arr))).Error()) } func TestValidateStruct_MapWithError(t *testing.T) { m := map[string]string{ "a": "hello", "b": "hello", } var x interface{} = Product{ M: &m, } c := Constraint{ "M", Null, true, []Constraint{ {"M", Empty, true, nil}, {"M", MaxItems, 4, nil}, {"M", UniqueItems, true, nil}, }, } s := "M" require.Equal(t, strings.Contains(validateStruct(reflect.ValueOf(x), c).Error(), fmt.Sprintf("all items in parameter %q must be unique;", s)), true) } func TestValidateStruct_MapWithNoError(t *testing.T) { m := map[string]string{} var x interface{} = Product{ M: &m, } c := Constraint{ "M", Null, true, []Constraint{ {"M", Empty, false, nil}, {"M", MaxItems, 4, nil}, }, } require.Nil(t, validateStruct(reflect.ValueOf(x), c)) } func TestValidateStruct_MapNilNoError(t *testing.T) { var m map[string]string var x interface{} = Product{ M: &m, } c := Constraint{ "M", Null, false, []Constraint{ {"M", Empty, false, nil}, {"M", MaxItems, 4, nil}, }, } require.Nil(t, validateStruct(reflect.ValueOf(x), c)) } func TestValidate_MapValidationWithError(t *testing.T) { var x1 interface{} = &Product{ Arr: &[]string{"1", "2"}, M: &map[string]string{"a": "hello"}, } s := "hello" var x2 interface{} = &Sample{ M: &map[string]*string{"a": &s}, } v := []Validation{ {x1, []Constraint{{"x1", Null, true, []Constraint{ {"Arr", Null, true, []Constraint{ {"Arr", Empty, true, nil}, {"Arr", MaxItems, 4, nil}, {"Arr", UniqueItems, true, nil}, }, }, {"M", Null, false, []Constraint{ {"M", Empty, false, nil}, {"M", MinItems, 1, nil}, {"M", UniqueItems, true, nil}, }, }, }, }}}, {x2, []Constraint{ {"x2", Null, true, []Constraint{ {"M", Null, false, []Constraint{ {"M", Empty, false, nil}, {"M", MinItems, 2, nil}, {"M", UniqueItems, true, nil}, }, }, }, }, {"Name", Empty, true, nil}, }}, } z := Validate(v).Error() require.Equal(t, strings.Contains(z, "minimum item limit is 2; got: 1"), true) require.Equal(t, strings.Contains(z, "MinItems"), true) } func TestValidate_MapValidationWithoutError(t *testing.T) { var x1 interface{} = &Product{ Arr: &[]string{"1", "2"}, M: &map[string]string{"a": "hello"}, } s := "hello" var x2 interface{} = &Sample{ M: &map[string]*string{"a": &s}, } v := []Validation{ {x1, []Constraint{{"x1", Null, true, []Constraint{ {"Arr", Null, true, []Constraint{ {"Arr", Empty, true, nil}, {"Arr", MaxItems, 4, nil}, {"Arr", UniqueItems, true, nil}, }, }, {"M", Null, false, []Constraint{ {"M", Empty, false, nil}, {"M", MinItems, 1, nil}, {"M", UniqueItems, true, nil}, {"M", Pattern, "^[a-z]+$", nil}, }, }, }, }}}, {x2, []Constraint{ {"x2", Null, true, []Constraint{ {"M", Null, false, []Constraint{ {"M", Empty, false, nil}, {"M", MinItems, 1, nil}, {"M", UniqueItems, true, nil}, {"M", Pattern, "^[a-z]+$", nil}, }, }, }, }, {"Name", Empty, true, nil}, }}, } require.Nil(t, Validate(v)) } func TestValidate_UnknownType(t *testing.T) { var c chan int v := []Validation{ {c, []Constraint{{"c", Null, true, nil}}}, } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf(c), v[0].Constraints[0], fmt.Sprintf("unknown type %v", reflect.ValueOf(c).Kind())).Error()) } func TestValidate_example1(t *testing.T) { var x1 interface{} = Product{ Arr: &[]string{"1", "1"}, M: &map[string]string{"a": "hello"}, } s := "hello" var x2 interface{} = Sample{ M: &map[string]*string{"a": &s}, } v := []Validation{ {x1, []Constraint{{"Arr", Null, true, []Constraint{ {"Arr", Empty, true, nil}, {"Arr", MaxItems, 4, nil}, {"Arr", UniqueItems, true, nil}, }}, {"M", Null, false, []Constraint{ {"M", Empty, false, nil}, {"M", MinItems, 1, nil}, {"M", UniqueItems, true, nil}, }, }, }}, {x2, []Constraint{ {"M", Null, false, []Constraint{ {"M", Empty, false, nil}, {"M", MinItems, 1, nil}, {"M", UniqueItems, true, nil}, }, }, {"Name", Empty, true, nil}, }}, } s = "Arr" require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf([]string{"1", "1"}), v[0].Constraints[0].Chain[2], fmt.Sprintf("all items in parameter %q must be unique; got:%v", s, []string{"1", "1"})).Error()) } func TestValidate_Int(t *testing.T) { n := int32(100) v := []Validation{ {n, []Constraint{ {"n", MultipleOf, 10, nil}, {"n", ExclusiveMinimum, 100, nil}, }, }, } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf(n), v[0].Constraints[1], "value must be greater than 100").Error()) } func TestValidate_IntPointer(t *testing.T) { n := int32(100) p := &n v := []Validation{ {p, []Constraint{ {"p", Null, true, []Constraint{ {"p", ExclusiveMinimum, 100, nil}, }}, }, }, } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf(n), v[0].Constraints[0].Chain[0], "value must be greater than 100").Error()) // required parameter p = nil v = []Validation{ {p, []Constraint{ {"p", Null, true, []Constraint{ {"p", ExclusiveMinimum, 100, nil}, }}, }, }, } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf(v[0].TargetValue), v[0].Constraints[0], "value can not be null; required parameter").Error()) // Not required p = nil v = []Validation{ {p, []Constraint{ {"p", Null, false, []Constraint{ {"p", ExclusiveMinimum, 100, nil}, }}, }, }, } require.Nil(t, Validate(v)) } func TestValidate_IntStruct(t *testing.T) { n := int32(100) p := &Product{ Num: &n, } v := []Validation{ {p, []Constraint{{"p", Null, true, []Constraint{ {"Num", Null, true, []Constraint{ {"Num", ExclusiveMinimum, 100, nil}, }}, }, }}}, } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf(n), v[0].Constraints[0].Chain[0].Chain[0], "value must be greater than 100").Error()) // required parameter p = &Product{} v = []Validation{ {p, []Constraint{{"p", Null, true, []Constraint{ {"p.Num", Null, true, []Constraint{ {"p.Num", ExclusiveMinimum, 100, nil}, }}, }, }}}, } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf(p.Num), v[0].Constraints[0].Chain[0], "value can not be null; required parameter").Error()) // Not required p = &Product{} v = []Validation{ {p, []Constraint{{"p", Null, true, []Constraint{ {"Num", Null, false, []Constraint{ {"Num", ExclusiveMinimum, 100, nil}, }}, }, }}}, } require.Nil(t, Validate(v)) // Parent not required p = nil v = []Validation{ {p, []Constraint{{"p", Null, false, []Constraint{ {"Num", Null, false, []Constraint{ {"Num", ExclusiveMinimum, 100, nil}, }}, }, }}}, } require.Nil(t, Validate(v)) } func TestValidate_String(t *testing.T) { s := "hello" v := []Validation{ {s, []Constraint{ {"s", Empty, true, nil}, {"s", Empty, true, []Constraint{{"s", MaxLength, 3, nil}}}, }, }, } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf(s), v[0].Constraints[1].Chain[0], "value length must be less than or equal to 3").Error()) // required parameter s = "" v = []Validation{ {s, []Constraint{ {"s", Empty, true, nil}, {"s", Empty, true, []Constraint{{"s", MaxLength, 3, nil}}}, }, }, } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf(s), v[0].Constraints[1], "value can not be null or empty; required parameter").Error()) // not required parameter s = "" v = []Validation{ {s, []Constraint{ {"s", Empty, false, nil}, {"s", Empty, false, []Constraint{{"s", MaxLength, 3, nil}}}, }, }, } require.Nil(t, Validate(v)) } func TestValidate_StringStruct(t *testing.T) { s := "hello" p := &Product{ Str: &s, } v := []Validation{ {p, []Constraint{{"p", Null, true, []Constraint{ {"p.Str", Null, true, []Constraint{ {"p.Str", Empty, true, nil}, {"p.Str", MaxLength, 3, nil}, }}, }, }}}, } // e := ValidationError{ // Constraint: MaxLength, // Target: "Str", // TargetValue: s, // Details: fmt.Sprintf("value length must be less than 3", s), // } // if z := Validate(v); !reflect.DeepEqual(e, z) { // t.Fatalf("autorest/validation: Validate failed to return error \nexpect: %v\ngot: %v", e, z) // } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf(s), v[0].Constraints[0].Chain[0].Chain[1], "value length must be less than or equal to 3").Error()) // required parameter - can't be Empty s = "" p = &Product{ Str: &s, } v = []Validation{ {p, []Constraint{{"p", Null, true, []Constraint{ {"Str", Null, true, []Constraint{ {"Str", Empty, true, nil}, {"Str", MaxLength, 3, nil}, }}, }, }}}, } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf(s), v[0].Constraints[0].Chain[0].Chain[0], "value can not be null or empty; required parameter").Error()) // required parameter - can't be null p = &Product{} v = []Validation{ {p, []Constraint{{"p", Null, true, []Constraint{ {"p.Str", Null, true, []Constraint{ {"p.Str", Empty, true, nil}, {"p.Str", MaxLength, 3, nil}, }}, }, }}}, } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf(p.Str), v[0].Constraints[0].Chain[0], "value can not be null; required parameter").Error()) // Not required p = &Product{} v = []Validation{ {p, []Constraint{{"p", Null, true, []Constraint{ {"Str", Null, false, []Constraint{ {"Str", Empty, true, nil}, {"Str", MaxLength, 3, nil}, }}, }, }}}, } require.Nil(t, Validate(v)) // Parent not required p = nil v = []Validation{ {p, []Constraint{{"p", Null, false, []Constraint{ {"Str", Null, true, []Constraint{ {"Str", Empty, true, nil}, {"Str", MaxLength, 3, nil}, }}, }, }}}, } require.Nil(t, Validate(v)) } func TestValidate_Array(t *testing.T) { s := []string{"hello"} v := []Validation{ {s, []Constraint{ {"s", Null, true, []Constraint{ {"s", Empty, true, nil}, {"s", MinItems, 2, nil}, }}, }, }, } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf(s), v[0].Constraints[0].Chain[1], fmt.Sprintf("minimum item limit is 2; got: %v", len(s))).Error()) // Empty array v = []Validation{ {[]string{}, []Constraint{ {"s", Null, true, []Constraint{ {"s", Empty, true, nil}, {"s", MinItems, 2, nil}}}, }, }, } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf([]string{}), v[0].Constraints[0].Chain[0], "value can not be null or empty; required parameter").Error()) // null array var s1 []string v = []Validation{ {s1, []Constraint{ {"s1", Null, true, []Constraint{ {"s1", Empty, true, nil}, {"s1", MinItems, 2, nil}}}, }, }, } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf(s1), v[0].Constraints[0], "value can not be null; required parameter").Error()) // not required parameter v = []Validation{ {s1, []Constraint{ {"s1", Null, false, []Constraint{ {"s1", Empty, true, nil}, {"s1", MinItems, 2, nil}}}, }, }, } require.Nil(t, Validate(v)) } func TestValidate_ArrayPointer(t *testing.T) { s := []string{"hello"} v := []Validation{ {&s, []Constraint{ {"s", Null, true, []Constraint{ {"s", Empty, true, nil}, {"s", MinItems, 2, nil}, }}, }, }, } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf(s), v[0].Constraints[0].Chain[1], fmt.Sprintf("minimum item limit is 2; got: %v", len(s))).Error()) // Empty array v = []Validation{ {&[]string{}, []Constraint{ {"s", Null, true, []Constraint{ {"s", Empty, true, nil}, {"s", MinItems, 2, nil}}}, }, }, } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf([]string{}), v[0].Constraints[0].Chain[0], "value can not be null or empty; required parameter").Error()) // null array var s1 *[]string v = []Validation{ {s1, []Constraint{ {"s1", Null, true, []Constraint{ {"s1", Empty, true, nil}, {"s1", MinItems, 2, nil}}}, }, }, } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf(s1), v[0].Constraints[0], "value can not be null; required parameter").Error()) // not required parameter v = []Validation{ {s1, []Constraint{ {"s1", Null, false, []Constraint{ {"s1", Empty, true, nil}, {"s1", MinItems, 2, nil}}}, }, }, } require.Nil(t, Validate(v)) } func TestValidate_ArrayInStruct(t *testing.T) { s := []string{"hello"} p := &Product{ Arr: &s, } v := []Validation{ {p, []Constraint{{"p", Null, true, []Constraint{ {"p.Arr", Null, true, []Constraint{ {"p.Arr", Empty, true, nil}, {"p.Arr", MinItems, 2, nil}, }}, }, }}}, } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf(s), v[0].Constraints[0].Chain[0].Chain[1], fmt.Sprintf("minimum item limit is 2; got: %v", len(s))).Error()) // required parameter - can't be Empty p = &Product{ Arr: &[]string{}, } v = []Validation{ {p, []Constraint{{"p", Null, true, []Constraint{ {"p.Arr", Null, true, []Constraint{ {"p.Arr", Empty, true, nil}, {"p.Arr", MinItems, 2, nil}, }}, }, }}}, } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf([]string{}), v[0].Constraints[0].Chain[0].Chain[0], "value can not be null or empty; required parameter").Error()) // required parameter - can't be null p = &Product{} v = []Validation{ {p, []Constraint{{"p", Null, true, []Constraint{ {"p.Arr", Null, true, []Constraint{ {"p.Arr", Empty, true, nil}, {"p.Arr", MinItems, 2, nil}, }}, }, }}}, } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf(p.Arr), v[0].Constraints[0].Chain[0], "value can not be null; required parameter").Error()) // Not required v = []Validation{ {&Product{}, []Constraint{{"p", Null, true, []Constraint{ {"Arr", Null, false, []Constraint{ {"Arr", Empty, true, nil}, {"Arr", MinItems, 2, nil}, }}, }, }}}, } require.Nil(t, Validate(v)) // Parent not required p = nil v = []Validation{ {p, []Constraint{{"p", Null, false, []Constraint{ {"Arr", Null, true, []Constraint{ {"Arr", Empty, true, nil}, {"Arr", MinItems, 2, nil}, }}, }, }}}, } require.Nil(t, Validate(v)) } func TestValidate_StructInStruct(t *testing.T) { p := &Product{ C: &Child{I: "hello"}, } v := []Validation{ {p, []Constraint{{"p", Null, true, []Constraint{{"C", Null, true, []Constraint{{"I", MinLength, 7, nil}}}, }, }}}, } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf(p.C.I), v[0].Constraints[0].Chain[0].Chain[0], "value length must be greater than or equal to 7").Error()) // required parameter - can't be Empty p = &Product{ C: &Child{I: ""}, } v = []Validation{ {p, []Constraint{{"p", Null, true, []Constraint{{"C", Null, true, []Constraint{{"I", Empty, true, nil}}}, }, }}}, } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf(p.C.I), v[0].Constraints[0].Chain[0].Chain[0], "value can not be null or empty; required parameter").Error()) // required parameter - can't be null p = &Product{} v = []Validation{ {p, []Constraint{{"p", Null, true, []Constraint{{"C", Null, true, []Constraint{{"I", Empty, true, nil}}}, }, }}}, } require.Equal(t, Validate(v).Error(), createError(reflect.ValueOf(p.C), v[0].Constraints[0].Chain[0], "value can not be null; required parameter").Error()) // Not required v = []Validation{ {&Product{}, []Constraint{{"p", Null, true, []Constraint{{"p.C", Null, false, []Constraint{{"p.C.I", Empty, true, nil}}}, }, }}}, } require.Nil(t, Validate(v)) // Parent not required p = nil v = []Validation{ {p, []Constraint{{"p", Null, false, []Constraint{{"p.C", Null, false, []Constraint{{"p.C.I", Empty, true, nil}}}, }, }}}, } require.Nil(t, Validate(v)) } func TestNewError(t *testing.T) { p := &Product{} v := []Validation{ {p, []Constraint{{"p", Null, true, []Constraint{{"p.C", Null, true, []Constraint{{"p.C.I", Empty, true, nil}}}, }, }}}, } err := createError(reflect.ValueOf(p.C), v[0].Constraints[0].Chain[0], "value can not be null; required parameter") z := fmt.Sprintf("batch.AccountClient#Create: Invalid input: %s", err.Error()) require.Equal(t, NewError("batch.AccountClient", "Create", err.Error()).Error(), z) } golang-github-azure-go-autorest-14.1.1/autorest/version.go000066400000000000000000000020731367372352400236200ustar00rootroot00000000000000package autorest // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "fmt" "runtime" ) const number = "v14.1.1" var ( userAgent = fmt.Sprintf("Go/%s (%s-%s) go-autorest/%s", runtime.Version(), runtime.GOARCH, runtime.GOOS, number, ) ) // UserAgent returns a string containing the Go version, system architecture and OS, and the go-autorest version. func UserAgent() string { return userAgent } // Version returns the semantic version (see http://semver.org). func Version() string { return number } golang-github-azure-go-autorest-14.1.1/azure-pipelines.yml000066400000000000000000000063321367372352400235770ustar00rootroot00000000000000variables: GOPATH: '$(system.defaultWorkingDirectory)/work' sdkPath: '$(GOPATH)/src/github.com/$(build.repository.name)' jobs: - job: 'goautorest' displayName: 'Run go-autorest CI Checks' strategy: matrix: Linux_Go113: vm.image: 'ubuntu-18.04' go.version: '1.13' Linux_Go114: vm.image: 'ubuntu-18.04' go.version: '1.14' pool: vmImage: '$(vm.image)' steps: - task: GoTool@0 inputs: version: '$(go.version)' displayName: "Select Go Version" - script: | set -e mkdir -p '$(GOPATH)/bin' mkdir -p '$(sdkPath)' shopt -s extglob mv !(work) '$(sdkPath)' echo '##vso[task.prependpath]$(GOPATH)/bin' displayName: 'Create Go Workspace' - script: | set -e curl -sSL https://raw.githubusercontent.com/golang/dep/master/install.sh | sh dep ensure -v go install ./vendor/golang.org/x/lint/golint go get github.com/jstemmer/go-junit-report go get github.com/axw/gocov/gocov go get github.com/AlekSi/gocov-xml go get -u github.com/matm/gocov-html workingDirectory: '$(sdkPath)' displayName: 'Install Dependencies' - script: | go vet ./autorest/... go vet ./logger/... go vet ./tracing/... workingDirectory: '$(sdkPath)' displayName: 'Vet' - script: | go build -v ./autorest/... go build -v ./logger/... go build -v ./tracing/... workingDirectory: '$(sdkPath)' displayName: 'Build' - script: | set -e go test -race -v -coverprofile=coverage.txt -covermode atomic ./autorest/... ./logger/... ./tracing/... 2>&1 | go-junit-report > report.xml gocov convert coverage.txt > coverage.json gocov-xml < coverage.json > coverage.xml gocov-html < coverage.json > coverage.html workingDirectory: '$(sdkPath)' displayName: 'Run Tests' - script: grep -L -r --include *.go --exclude-dir vendor -P "Copyright (\d{4}|\(c\)) Microsoft" ./ | tee >&2 workingDirectory: '$(sdkPath)' displayName: 'Copyright Header Check' failOnStderr: true condition: succeededOrFailed() - script: | gofmt -s -l -w ./autorest/. >&2 gofmt -s -l -w ./logger/. >&2 gofmt -s -l -w ./tracing/. >&2 workingDirectory: '$(sdkPath)' displayName: 'Format Check' failOnStderr: true condition: succeededOrFailed() - script: | golint ./autorest/... >&2 golint ./logger/... >&2 golint ./tracing/... >&2 workingDirectory: '$(sdkPath)' displayName: 'Linter Check' failOnStderr: true condition: succeededOrFailed() - task: PublishTestResults@2 inputs: testRunner: JUnit testResultsFiles: $(sdkPath)/report.xml failTaskOnFailedTests: true - task: PublishCodeCoverageResults@1 inputs: codeCoverageTool: Cobertura summaryFileLocation: $(sdkPath)/coverage.xml additionalCodeCoverageFiles: $(sdkPath)/coverage.html golang-github-azure-go-autorest-14.1.1/logger/000077500000000000000000000000001367372352400212135ustar00rootroot00000000000000golang-github-azure-go-autorest-14.1.1/logger/go.mod000066400000000000000000000000641367372352400223210ustar00rootroot00000000000000module github.com/Azure/go-autorest/logger go 1.12 golang-github-azure-go-autorest-14.1.1/logger/logger.go000066400000000000000000000225411367372352400230250ustar00rootroot00000000000000package logger // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "bytes" "fmt" "io" "io/ioutil" "net/http" "net/url" "os" "strings" "sync" "time" ) // LevelType tells a logger the minimum level to log. When code reports a log entry, // the LogLevel indicates the level of the log entry. The logger only records entries // whose level is at least the level it was told to log. See the Log* constants. // For example, if a logger is configured with LogError, then LogError, LogPanic, // and LogFatal entries will be logged; lower level entries are ignored. type LevelType uint32 const ( // LogNone tells a logger not to log any entries passed to it. LogNone LevelType = iota // LogFatal tells a logger to log all LogFatal entries passed to it. LogFatal // LogPanic tells a logger to log all LogPanic and LogFatal entries passed to it. LogPanic // LogError tells a logger to log all LogError, LogPanic and LogFatal entries passed to it. LogError // LogWarning tells a logger to log all LogWarning, LogError, LogPanic and LogFatal entries passed to it. LogWarning // LogInfo tells a logger to log all LogInfo, LogWarning, LogError, LogPanic and LogFatal entries passed to it. LogInfo // LogDebug tells a logger to log all LogDebug, LogInfo, LogWarning, LogError, LogPanic and LogFatal entries passed to it. LogDebug ) const ( logNone = "NONE" logFatal = "FATAL" logPanic = "PANIC" logError = "ERROR" logWarning = "WARNING" logInfo = "INFO" logDebug = "DEBUG" logUnknown = "UNKNOWN" ) // ParseLevel converts the specified string into the corresponding LevelType. func ParseLevel(s string) (lt LevelType, err error) { switch strings.ToUpper(s) { case logFatal: lt = LogFatal case logPanic: lt = LogPanic case logError: lt = LogError case logWarning: lt = LogWarning case logInfo: lt = LogInfo case logDebug: lt = LogDebug default: err = fmt.Errorf("bad log level '%s'", s) } return } // String implements the stringer interface for LevelType. func (lt LevelType) String() string { switch lt { case LogNone: return logNone case LogFatal: return logFatal case LogPanic: return logPanic case LogError: return logError case LogWarning: return logWarning case LogInfo: return logInfo case LogDebug: return logDebug default: return logUnknown } } // Filter defines functions for filtering HTTP request/response content. type Filter struct { // URL returns a potentially modified string representation of a request URL. URL func(u *url.URL) string // Header returns a potentially modified set of values for the specified key. // To completely exclude the header key/values return false. Header func(key string, val []string) (bool, []string) // Body returns a potentially modified request/response body. Body func(b []byte) []byte } func (f Filter) processURL(u *url.URL) string { if f.URL == nil { return u.String() } return f.URL(u) } func (f Filter) processHeader(k string, val []string) (bool, []string) { if f.Header == nil { return true, val } return f.Header(k, val) } func (f Filter) processBody(b []byte) []byte { if f.Body == nil { return b } return f.Body(b) } // Writer defines methods for writing to a logging facility. type Writer interface { // Writeln writes the specified message with the standard log entry header and new-line character. Writeln(level LevelType, message string) // Writef writes the specified format specifier with the standard log entry header and no new-line character. Writef(level LevelType, format string, a ...interface{}) // WriteRequest writes the specified HTTP request to the logger if the log level is greater than // or equal to LogInfo. The request body, if set, is logged at level LogDebug or higher. // Custom filters can be specified to exclude URL, header, and/or body content from the log. // By default no request content is excluded. WriteRequest(req *http.Request, filter Filter) // WriteResponse writes the specified HTTP response to the logger if the log level is greater than // or equal to LogInfo. The response body, if set, is logged at level LogDebug or higher. // Custom filters can be specified to exclude URL, header, and/or body content from the log. // By default no response content is excluded. WriteResponse(resp *http.Response, filter Filter) } // Instance is the default log writer initialized during package init. // This can be replaced with a custom implementation as required. var Instance Writer // default log level var logLevel = LogNone // Level returns the value specified in AZURE_GO_AUTOREST_LOG_LEVEL. // If no value was specified the default value is LogNone. // Custom loggers can call this to retrieve the configured log level. func Level() LevelType { return logLevel } func init() { // separated for testing purposes initDefaultLogger() } func initDefaultLogger() { // init with nilLogger so callers don't have to do a nil check on Default Instance = nilLogger{} llStr := strings.ToLower(os.Getenv("AZURE_GO_SDK_LOG_LEVEL")) if llStr == "" { return } var err error logLevel, err = ParseLevel(llStr) if err != nil { fmt.Fprintf(os.Stderr, "go-autorest: failed to parse log level: %s\n", err.Error()) return } if logLevel == LogNone { return } // default to stderr dest := os.Stderr lfStr := os.Getenv("AZURE_GO_SDK_LOG_FILE") if strings.EqualFold(lfStr, "stdout") { dest = os.Stdout } else if lfStr != "" { lf, err := os.Create(lfStr) if err == nil { dest = lf } else { fmt.Fprintf(os.Stderr, "go-autorest: failed to create log file, using stderr: %s\n", err.Error()) } } Instance = fileLogger{ logLevel: logLevel, mu: &sync.Mutex{}, logFile: dest, } } // the nil logger does nothing type nilLogger struct{} func (nilLogger) Writeln(LevelType, string) {} func (nilLogger) Writef(LevelType, string, ...interface{}) {} func (nilLogger) WriteRequest(*http.Request, Filter) {} func (nilLogger) WriteResponse(*http.Response, Filter) {} // A File is used instead of a Logger so the stream can be flushed after every write. type fileLogger struct { logLevel LevelType mu *sync.Mutex // for synchronizing writes to logFile logFile *os.File } func (fl fileLogger) Writeln(level LevelType, message string) { fl.Writef(level, "%s\n", message) } func (fl fileLogger) Writef(level LevelType, format string, a ...interface{}) { if fl.logLevel >= level { fl.mu.Lock() defer fl.mu.Unlock() fmt.Fprintf(fl.logFile, "%s %s", entryHeader(level), fmt.Sprintf(format, a...)) fl.logFile.Sync() } } func (fl fileLogger) WriteRequest(req *http.Request, filter Filter) { if req == nil || fl.logLevel < LogInfo { return } b := &bytes.Buffer{} fmt.Fprintf(b, "%s REQUEST: %s %s\n", entryHeader(LogInfo), req.Method, filter.processURL(req.URL)) // dump headers for k, v := range req.Header { if ok, mv := filter.processHeader(k, v); ok { fmt.Fprintf(b, "%s: %s\n", k, strings.Join(mv, ",")) } } if fl.shouldLogBody(req.Header, req.Body) { // dump body body, err := ioutil.ReadAll(req.Body) if err == nil { fmt.Fprintln(b, string(filter.processBody(body))) if nc, ok := req.Body.(io.Seeker); ok { // rewind to the beginning nc.Seek(0, io.SeekStart) } else { // recreate the body req.Body = ioutil.NopCloser(bytes.NewReader(body)) } } else { fmt.Fprintf(b, "failed to read body: %v\n", err) } } fl.mu.Lock() defer fl.mu.Unlock() fmt.Fprint(fl.logFile, b.String()) fl.logFile.Sync() } func (fl fileLogger) WriteResponse(resp *http.Response, filter Filter) { if resp == nil || fl.logLevel < LogInfo { return } b := &bytes.Buffer{} fmt.Fprintf(b, "%s RESPONSE: %d %s\n", entryHeader(LogInfo), resp.StatusCode, filter.processURL(resp.Request.URL)) // dump headers for k, v := range resp.Header { if ok, mv := filter.processHeader(k, v); ok { fmt.Fprintf(b, "%s: %s\n", k, strings.Join(mv, ",")) } } if fl.shouldLogBody(resp.Header, resp.Body) { // dump body defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err == nil { fmt.Fprintln(b, string(filter.processBody(body))) resp.Body = ioutil.NopCloser(bytes.NewReader(body)) } else { fmt.Fprintf(b, "failed to read body: %v\n", err) } } fl.mu.Lock() defer fl.mu.Unlock() fmt.Fprint(fl.logFile, b.String()) fl.logFile.Sync() } // returns true if the provided body should be included in the log func (fl fileLogger) shouldLogBody(header http.Header, body io.ReadCloser) bool { ct := header.Get("Content-Type") return fl.logLevel >= LogDebug && body != nil && !strings.Contains(ct, "application/octet-stream") } // creates standard header for log entries, it contains a timestamp and the log level func entryHeader(level LevelType) string { // this format provides a fixed number of digits so the size of the timestamp is constant return fmt.Sprintf("(%s) %s:", time.Now().Format("2006-01-02T15:04:05.0000000Z07:00"), level.String()) } golang-github-azure-go-autorest-14.1.1/logger/logger_test.go000066400000000000000000000134651367372352400240710ustar00rootroot00000000000000package logger // Copyright 2017 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "fmt" "io/ioutil" "net/http" "os" "path" "regexp" "strings" "testing" ) func TestNilLogger(t *testing.T) { // verify no crash with no logging Instance.WriteRequest(nil, Filter{}) } const ( reqURL = "https://fakething/dot/com" reqHeaderKey = "x-header" reqHeaderVal = "value" reqBody = "the request body" respHeaderKey = "response-header" respHeaderVal = "something" respBody = "the response body" logFileTimeStampRegex = `\(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{7}(-\d{2}:\d{2}|Z)\)` ) func TestLogReqRespNoBody(t *testing.T) { err := os.Setenv("AZURE_GO_SDK_LOG_LEVEL", "info") if err != nil { t.Fatalf("failed to set log level: %v", err) } lf := path.Join(os.TempDir(), "testloggingbasic.log") err = os.Setenv("AZURE_GO_SDK_LOG_FILE", lf) if err != nil { t.Fatalf("failed to set log file: %v", err) } initDefaultLogger() if Level() != LogInfo { t.Fatalf("wrong log level: %d", Level()) } // create mock request and response for logging req, err := http.NewRequest(http.MethodGet, reqURL, nil) if err != nil { t.Fatalf("failed to create mock request: %v", err) } req.Header.Add(reqHeaderKey, reqHeaderVal) Instance.WriteRequest(req, Filter{}) resp := &http.Response{ Status: "200 OK", StatusCode: 200, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Request: req, Header: http.Header{}, } resp.Header.Add(respHeaderKey, respHeaderVal) Instance.WriteResponse(resp, Filter{}) if fl, ok := Instance.(fileLogger); ok { fl.logFile.Close() } else { t.Fatal("expected Instance to be fileLogger") } // parse log file to ensure contents match b, err := ioutil.ReadFile(lf) if err != nil { t.Fatalf("failed to read log file: %v", err) } parts := strings.Split(string(b), "\n") reqMatch := fmt.Sprintf("%s INFO: REQUEST: %s %s", logFileTimeStampRegex, req.Method, req.URL.String()) respMatch := fmt.Sprintf("%s INFO: RESPONSE: %d %s", logFileTimeStampRegex, resp.StatusCode, resp.Request.URL.String()) if !matchRegex(t, reqMatch, parts[0]) { t.Fatalf("request header doesn't match: %s", parts[0]) } if !matchRegex(t, fmt.Sprintf("(?i)%s: %s", reqHeaderKey, reqHeaderVal), parts[1]) { t.Fatalf("request header entry doesn't match: %s", parts[1]) } if !matchRegex(t, respMatch, parts[2]) { t.Fatalf("response header doesn't match: %s", parts[2]) } if !matchRegex(t, fmt.Sprintf("(?i)%s: %s", respHeaderKey, respHeaderVal), parts[3]) { t.Fatalf("response header value doesn't match: %s", parts[3]) } // disable logging err = os.Setenv("AZURE_GO_SDK_LOG_LEVEL", "") if err != nil { t.Fatalf("failed to clear log level: %v", err) } } func TestLogReqRespWithBody(t *testing.T) { err := os.Setenv("AZURE_GO_SDK_LOG_LEVEL", "debug") if err != nil { t.Fatalf("failed to set log level: %v", err) } lf := path.Join(os.TempDir(), "testloggingfull.log") err = os.Setenv("AZURE_GO_SDK_LOG_FILE", lf) if err != nil { t.Fatalf("failed to set log file: %v", err) } initDefaultLogger() if Level() != LogDebug { t.Fatalf("wrong log level: %d", Level()) } // create mock request and response for logging req, err := http.NewRequest(http.MethodGet, reqURL, strings.NewReader(reqBody)) if err != nil { t.Fatalf("failed to create mock request: %v", err) } req.Header.Add(reqHeaderKey, reqHeaderVal) Instance.WriteRequest(req, Filter{}) resp := &http.Response{ Status: "200 OK", StatusCode: 200, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Request: req, Header: http.Header{}, Body: ioutil.NopCloser(strings.NewReader(respBody)), } resp.Header.Add(respHeaderKey, respHeaderVal) Instance.WriteResponse(resp, Filter{}) if fl, ok := Instance.(fileLogger); ok { fl.logFile.Close() } else { t.Fatal("expected Instance to be fileLogger") } // parse log file to ensure contents match b, err := ioutil.ReadFile(lf) if err != nil { t.Fatalf("failed to read log file: %v", err) } parts := strings.Split(string(b), "\n") reqMatch := fmt.Sprintf("%s INFO: REQUEST: %s %s", logFileTimeStampRegex, req.Method, req.URL.String()) respMatch := fmt.Sprintf("%s INFO: RESPONSE: %d %s", logFileTimeStampRegex, resp.StatusCode, resp.Request.URL.String()) if !matchRegex(t, reqMatch, parts[0]) { t.Fatalf("request header doesn't match: %s", parts[0]) } if !matchRegex(t, fmt.Sprintf("(?i)%s: %s", reqHeaderKey, reqHeaderVal), parts[1]) { t.Fatalf("request header value doesn't match: %s", parts[1]) } if !matchRegex(t, reqBody, parts[2]) { t.Fatalf("request body doesn't match: %s", parts[2]) } if !matchRegex(t, respMatch, parts[3]) { t.Fatalf("response header doesn't match: %s", parts[3]) } if !matchRegex(t, fmt.Sprintf("(?i)%s: %s", respHeaderKey, respHeaderVal), parts[4]) { t.Fatalf("response header value doesn't match: %s", parts[4]) } if !matchRegex(t, respBody, parts[5]) { t.Fatalf("response body doesn't match: %s", parts[5]) } // disable logging err = os.Setenv("AZURE_GO_SDK_LOG_LEVEL", "") if err != nil { t.Fatalf("failed to clear log level: %v", err) } } func matchRegex(t *testing.T, pattern, s string) bool { match, err := regexp.MatchString(pattern, s) if err != nil { t.Fatalf("regexp failure: %v", err) } return match } golang-github-azure-go-autorest-14.1.1/tracing/000077500000000000000000000000001367372352400213635ustar00rootroot00000000000000golang-github-azure-go-autorest-14.1.1/tracing/go.mod000066400000000000000000000000651367372352400224720ustar00rootroot00000000000000module github.com/Azure/go-autorest/tracing go 1.12 golang-github-azure-go-autorest-14.1.1/tracing/opencensus/000077500000000000000000000000001367372352400235455ustar00rootroot00000000000000golang-github-azure-go-autorest-14.1.1/tracing/opencensus/go.mod000066400000000000000000000003061367372352400246520ustar00rootroot00000000000000module github.com/Azure/go-autorest/tracing/opencensus go 1.12 require ( contrib.go.opencensus.io/exporter/ocagent v0.6.0 github.com/Azure/go-autorest/tracing v0.5.0 go.opencensus.io v0.22.0 ) golang-github-azure-go-autorest-14.1.1/tracing/opencensus/go.sum000066400000000000000000000251721367372352400247070ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= contrib.go.opencensus.io/exporter/ocagent v0.6.0 h1:Z1n6UAyr0QwM284yUuh5Zd8JlvxUGAhFZcgMJkMPrGM= contrib.go.opencensus.io/exporter/ocagent v0.6.0/go.mod h1:zmKjrJcdo0aYcVS7bmEeSEBLPA9YJp5bjrofdU3pIXs= github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/grpc-ecosystem/grpc-gateway v1.9.4 h1:5xLhQjsk4zqPf9EHCrja2qFZMx+yBqkO3XgJ14bNnU0= github.com/grpc-ecosystem/grpc-gateway v1.9.4/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI= golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610 h1:Ygq9/SRJX9+dU0WCIICM8RkWvDw03lvB77hrhJnpxfU= google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw= google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= golang-github-azure-go-autorest-14.1.1/tracing/opencensus/opencensus.go000066400000000000000000000122531367372352400262610ustar00rootroot00000000000000package opencensus // Copyright 2018 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "context" "fmt" "net/http" "os" "contrib.go.opencensus.io/exporter/ocagent" "github.com/Azure/go-autorest/tracing" "go.opencensus.io/plugin/ochttp" "go.opencensus.io/plugin/ochttp/propagation/tracecontext" "go.opencensus.io/stats/view" "go.opencensus.io/trace" ) func init() { enableFromEnv() } // split out for testing purposes func enableFromEnv() { if _, ok := os.LookupEnv("AZURE_SDK_TRACING_ENABLED"); ok { agentEndpoint, ok := os.LookupEnv("OCAGENT_TRACE_EXPORTER_ENDPOINT") if ok { EnableWithAIForwarding(agentEndpoint) } else { Enable() } } } var defaultTracer = newocTracer() type ocTracer struct { // Sampler is the tracing sampler. If tracing is disabled it will never sample. Otherwise // it will be using the parent sampler or the default. sampler trace.Sampler // Views for metric instrumentation. views map[string]*view.View // the trace exporter traceExporter trace.Exporter } func newocTracer() *ocTracer { return &ocTracer{ sampler: trace.NeverSample(), views: map[string]*view.View{}, } } // NewTransport returns a new instance of a tracing-aware RoundTripper. func (oct ocTracer) NewTransport(base *http.Transport) http.RoundTripper { return &ochttp.Transport{ Base: base, Propagation: &tracecontext.HTTPFormat{}, GetStartOptions: func(*http.Request) trace.StartOptions { return trace.StartOptions{ Sampler: oct.sampler, } }, } } // StartSpan starts a trace span func (oct ocTracer) StartSpan(ctx context.Context, name string) context.Context { ctx, _ = trace.StartSpan(ctx, name, trace.WithSampler(oct.sampler)) return ctx } // EndSpan ends a previously started span stored in the context func (oct ocTracer) EndSpan(ctx context.Context, httpStatusCode int, err error) { span := trace.FromContext(ctx) if span == nil { return } if err != nil { span.SetStatus(trace.Status{Message: err.Error(), Code: toTraceStatusCode(httpStatusCode)}) } span.End() } // Enable will start instrumentation for metrics and traces. func Enable() error { defaultTracer.sampler = nil // register the views for HTTP metrics clientViews := []*view.View{ ochttp.ClientCompletedCount, ochttp.ClientRoundtripLatencyDistribution, ochttp.ClientReceivedBytesDistribution, ochttp.ClientSentBytesDistribution, } for _, cv := range clientViews { vn := fmt.Sprintf("Azure/go-autorest/tracing/opencensus-%s", cv.Name) defaultTracer.views[vn] = cv.WithName(vn) err := view.Register(defaultTracer.views[vn]) if err != nil { return err } } tracing.Register(defaultTracer) return nil } // Disable will disable instrumentation for metrics and traces. func Disable() { // unregister any previously registered metrics for _, v := range defaultTracer.views { view.Unregister(v) } defaultTracer.sampler = trace.NeverSample() if defaultTracer.traceExporter != nil { trace.UnregisterExporter(defaultTracer.traceExporter) } tracing.Register(nil) } // EnableWithAIForwarding will start instrumentation and will connect to app insights forwarder // exporter making the metrics and traces available in app insights. func EnableWithAIForwarding(agentEndpoint string) error { err := Enable() if err != nil { return err } defaultTracer.traceExporter, err = ocagent.NewExporter(ocagent.WithInsecure(), ocagent.WithAddress(agentEndpoint)) if err != nil { return err } trace.RegisterExporter(defaultTracer.traceExporter) return nil } // toTraceStatusCode converts HTTP Codes to OpenCensus codes as defined // at https://github.com/census-instrumentation/opencensus-specs/blob/master/trace/HTTP.md#status func toTraceStatusCode(httpStatusCode int) int32 { switch { case http.StatusOK <= httpStatusCode && httpStatusCode < http.StatusBadRequest: return trace.StatusCodeOK case httpStatusCode == http.StatusBadRequest: return trace.StatusCodeInvalidArgument case httpStatusCode == http.StatusUnauthorized: // 401 is actually unauthenticated. return trace.StatusCodeUnauthenticated case httpStatusCode == http.StatusForbidden: return trace.StatusCodePermissionDenied case httpStatusCode == http.StatusNotFound: return trace.StatusCodeNotFound case httpStatusCode == http.StatusTooManyRequests: return trace.StatusCodeResourceExhausted case httpStatusCode == 499: return trace.StatusCodeCancelled case httpStatusCode == http.StatusNotImplemented: return trace.StatusCodeUnimplemented case httpStatusCode == http.StatusServiceUnavailable: return trace.StatusCodeUnavailable case httpStatusCode == http.StatusGatewayTimeout: return trace.StatusCodeDeadlineExceeded default: return trace.StatusCodeUnknown } } golang-github-azure-go-autorest-14.1.1/tracing/opencensus/opencensus_test.go000066400000000000000000000112701367372352400273160ustar00rootroot00000000000000package opencensus // Copyright 2018 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "context" "fmt" "net/http" "os" "reflect" "testing" "contrib.go.opencensus.io/exporter/ocagent" "github.com/Azure/go-autorest/tracing" "go.opencensus.io/plugin/ochttp" "go.opencensus.io/stats/view" "go.opencensus.io/trace" ) func TestNoTracingByDefault(t *testing.T) { if expected, got := false, tracing.IsEnabled(); expected != got { t.Fatalf("By default expected %t, got %t", expected, got) } if defaultTracer == nil { t.Fatal("unexpected nil defaultTracer") } if defaultTracer.sampler == nil { t.Fatal("By default expected non nil sampler") } if tr := defaultTracer.NewTransport(nil); tr.(*ochttp.Transport).GetStartOptions(&http.Request{}).Sampler == nil { t.Fatalf("By default expected configured Sampler to be non-nil") } for n := range defaultTracer.views { v := view.Find(n) if v != nil { t.Fatalf("By default expected no registered views, found %s", v.Name) } } } func TestEnableTracing(t *testing.T) { err := Enable() if err != nil { t.Fatalf("Enable failed, got error %v", err) } if e := tracing.IsEnabled(); !e { t.Fatalf("Enable failed, IsEnabled() is %t", e) } if defaultTracer.sampler != nil { t.Fatalf("Enable failed, expected nil sampler, got %v", defaultTracer.sampler) } if tr := tracing.NewTransport(nil); tr.(*ochttp.Transport).GetStartOptions(&http.Request{}).Sampler != nil { t.Fatalf("Enable failed, expected Transport.GetStartOptions.Sampler to be nil") } for n, v := range defaultTracer.views { fv := view.Find(n) if fv == nil || !reflect.DeepEqual(v, fv) { t.Fatalf("Enable failed, view %s was not registered", n) } } } func TestTracingByEnv(t *testing.T) { os.Setenv("AZURE_SDK_TRACING_ENABLED", "") enableFromEnv() if e := tracing.IsEnabled(); !e { t.Fatalf("Enable failed, IsEnabled() is %t", e) } if defaultTracer.sampler != nil { t.Fatalf("Enable failed, expected nil sampler, got %v", defaultTracer.sampler) } if tr := tracing.NewTransport(nil); tr.(*ochttp.Transport).GetStartOptions(&http.Request{}).Sampler != nil { t.Fatalf("Enable failed, expected Transport.GetStartOptions.Sampler to be nil") } for n, v := range defaultTracer.views { fv := view.Find(n) if fv == nil || !reflect.DeepEqual(v, fv) { t.Fatalf("Enable failed, view %s was not registered", n) } } } func TestEnableTracingWithAIError(t *testing.T) { agentEndpoint := fmt.Sprintf("%s:%d", ocagent.DefaultAgentHost, ocagent.DefaultAgentPort) if err := EnableWithAIForwarding(agentEndpoint); err != nil { // note that even though the agent isn't running no error is // returned, the exporter's state is simply set to disconnected. t.Fatalf("EnableWithAIForwarding failed: %v", err) } if e := tracing.IsEnabled(); !e { t.Fatalf("Enable failed, IsEnabled() is %t", e) } if defaultTracer.sampler != nil { t.Fatalf("Enable failed, expected nil sampler, got %v", defaultTracer.sampler) } if tr := tracing.NewTransport(nil); tr.(*ochttp.Transport).GetStartOptions(&http.Request{}).Sampler != nil { t.Fatalf("Enable failed, expected Transport.GetStartOptions.Sampler to be nil") } for n, v := range defaultTracer.views { fv := view.Find(n) if fv == nil || !reflect.DeepEqual(v, fv) { t.Fatalf("Enable failed, view %s was not registered", n) } } } func TestDisableTracing(t *testing.T) { Enable() Disable() if expected, got := false, tracing.IsEnabled(); expected != got { t.Fatalf("By default expected %t, got %t", expected, got) } if defaultTracer.sampler == nil { t.Fatal("By default expected non nil sampler") } if tr := defaultTracer.NewTransport(nil); tr.(*ochttp.Transport).GetStartOptions(&http.Request{}).Sampler == nil { t.Fatalf("By default expected configured Sampler to be non-nil") } for n := range defaultTracer.views { v := view.Find(n) if v != nil { t.Fatalf("By default expected no registered views, found %s", v.Name) } } } func TestStartSpan(t *testing.T) { ctx := defaultTracer.StartSpan(context.Background(), "testSpan") defer defaultTracer.EndSpan(ctx, 200, nil) span := trace.FromContext(ctx) if span == nil { t.Fatal("StartSpan failed, expected non-nil span") } } golang-github-azure-go-autorest-14.1.1/tracing/tracing.go000066400000000000000000000037061367372352400233470ustar00rootroot00000000000000package tracing // Copyright 2018 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "context" "net/http" ) // Tracer represents an HTTP tracing facility. type Tracer interface { NewTransport(base *http.Transport) http.RoundTripper StartSpan(ctx context.Context, name string) context.Context EndSpan(ctx context.Context, httpStatusCode int, err error) } var ( tracer Tracer ) // Register will register the provided Tracer. Pass nil to unregister a Tracer. func Register(t Tracer) { tracer = t } // IsEnabled returns true if a Tracer has been registered. func IsEnabled() bool { return tracer != nil } // NewTransport creates a new instrumenting http.RoundTripper for the // registered Tracer. If no Tracer has been registered it returns nil. func NewTransport(base *http.Transport) http.RoundTripper { if tracer != nil { return tracer.NewTransport(base) } return nil } // StartSpan starts a trace span with the specified name, associating it with the // provided context. Has no effect if a Tracer has not been registered. func StartSpan(ctx context.Context, name string) context.Context { if tracer != nil { return tracer.StartSpan(ctx, name) } return ctx } // EndSpan ends a previously started span stored in the context. // Has no effect if a Tracer has not been registered. func EndSpan(ctx context.Context, httpStatusCode int, err error) { if tracer != nil { tracer.EndSpan(ctx, httpStatusCode, err) } } golang-github-azure-go-autorest-14.1.1/tracing/tracing_test.go000066400000000000000000000036341367372352400244060ustar00rootroot00000000000000package tracing // Copyright 2018 Microsoft Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import ( "context" "net/http" "testing" ) func TestDisabled(t *testing.T) { if IsEnabled() { t.Fatal("unexpected enabled tracing") } if tr := NewTransport(&http.Transport{}); tr != nil { t.Fatal("unexpected non-nil transport") } if ctx := StartSpan(context.Background(), "foo"); ctx != context.Background() { t.Fatal("contexts don't match") } } func TestEnabled(t *testing.T) { mt := mockTracer{} Register(&mt) if !IsEnabled() { t.Fatal("unexpected disabled tracing") } if tr := NewTransport(&http.Transport{}); tr != http.DefaultTransport { t.Fatal("didn't receive expected transport") } ctx := StartSpan(context.Background(), "foo") v := ctx.Value(mockTracer{}) if val, ok := v.(string); !ok { t.Fatal("unexpected value type") } else if val != "foo" { t.Fatal("unexpected value") } EndSpan(ctx, http.StatusOK, nil) if !mt.ended { t.Fatal("EndSpan didn't forward call to registered tracer") } } type mockTracer struct { ended bool } func (m mockTracer) NewTransport(base *http.Transport) http.RoundTripper { return http.DefaultTransport } func (m mockTracer) StartSpan(ctx context.Context, name string) context.Context { return context.WithValue(ctx, mockTracer{}, name) } func (m *mockTracer) EndSpan(ctx context.Context, httpStatusCode int, err error) { m.ended = true }