Skip to content

Commit

Permalink
Merge pull request #102 from NerdJeremia/fix/no-https-for-localhost
Browse files Browse the repository at this point in the history
dont require https for localhost
  • Loading branch information
baywet committed Oct 13, 2023
2 parents b23fe50 + 2bdea6d commit 158857c
Show file tree
Hide file tree
Showing 3 changed files with 241 additions and 206 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

## [1.0.1] - 2023-10-13

### Changed

- Allow http on localhost.

## [1.0.0] - 2023-05-04

### Changed
Expand Down
271 changes: 144 additions & 127 deletions azure_identity_access_token_provider.go
Original file line number Diff line number Diff line change
@@ -1,127 +1,144 @@
package microsoft_kiota_authentication_azure

import (
"context"
"encoding/base64"
"errors"
"strings"

u "net/url"

azcore "github.com/Azure/azure-sdk-for-go/sdk/azcore"
azpolicy "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
absauth "github.com/microsoft/kiota-abstractions-go/authentication"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
)

// AzureIdentityAccessTokenProvider implementation of AccessTokenProvider that supports implementations of TokenCredential from Azure.Identity.
type AzureIdentityAccessTokenProvider struct {
scopes []string
credential azcore.TokenCredential
allowedHostsValidator *absauth.AllowedHostsValidator
// The observation options for the request adapter.
observabilityOptions ObservabilityOptions
}

// ObservabilityOptions holds the tracing, metrics and logging configuration for the request adapter
type ObservabilityOptions struct {
}

func (o ObservabilityOptions) GetTracerInstrumentationName() string {
return "github.com/microsoft/kiota-authentication-azure-go"
}

// NewAzureIdentityAccessTokenProvider creates a new instance of the AzureIdentityAccessTokenProvider using "<scheme>://<host>/.default" as the default scope.
func NewAzureIdentityAccessTokenProvider(credential azcore.TokenCredential) (*AzureIdentityAccessTokenProvider, error) {
return NewAzureIdentityAccessTokenProviderWithScopes(credential, nil)
}

// NewAzureIdentityAccessTokenProviderWithScopes creates a new instance of the AzureIdentityAccessTokenProvider.
func NewAzureIdentityAccessTokenProviderWithScopes(credential azcore.TokenCredential, scopes []string) (*AzureIdentityAccessTokenProvider, error) {
return NewAzureIdentityAccessTokenProviderWithScopesAndValidHosts(credential, scopes, nil)
}

// NewAzureIdentityAccessTokenProviderWithScopesAndValidHosts creates a new instance of the AzureIdentityAccessTokenProvider.
func NewAzureIdentityAccessTokenProviderWithScopesAndValidHosts(credential azcore.TokenCredential, scopes []string, validHosts []string) (*AzureIdentityAccessTokenProvider, error) {
return NewAzureIdentityAccessTokenProviderWithScopesAndValidHostsAndObservabilityOptions(credential, scopes, validHosts, ObservabilityOptions{})
}

// NewAzureIdentityAccessTokenProviderWithScopesAndValidHosts creates a new instance of the AzureIdentityAccessTokenProvider.
func NewAzureIdentityAccessTokenProviderWithScopesAndValidHostsAndObservabilityOptions(credential azcore.TokenCredential, scopes []string, validHosts []string, observabilityOptions ObservabilityOptions) (*AzureIdentityAccessTokenProvider, error) {
if credential == nil {
return nil, errors.New("credential cannot be nil")
}
scopesLen := len(scopes)
finalScopes := make([]string, scopesLen)
if scopesLen > 0 {
copy(finalScopes, scopes)
}
validator := absauth.NewAllowedHostsValidator(validHosts)
result := &AzureIdentityAccessTokenProvider{
credential: credential,
scopes: finalScopes,
allowedHostsValidator: &validator,
observabilityOptions: observabilityOptions,
}

return result, nil
}

const claimsKey = "claims"

// GetAuthorizationToken returns the access token for the provided url.
func (p *AzureIdentityAccessTokenProvider) GetAuthorizationToken(ctx context.Context, url *u.URL, additionalAuthenticationContext map[string]interface{}) (string, error) {
ctx, span := otel.GetTracerProvider().Tracer(p.observabilityOptions.GetTracerInstrumentationName()).Start(ctx, "GetAuthorizationToken")
defer span.End()
if !(*(p.allowedHostsValidator)).IsUrlHostValid(url) {
span.SetAttributes(attribute.Bool("com.microsoft.kiota.authentication.is_url_valid", false))
return "", nil
}
if !strings.EqualFold(url.Scheme, "https") {
span.SetAttributes(attribute.Bool("com.microsoft.kiota.authentication.is_url_valid", false))
err := errors.New("url scheme must be https")
span.RecordError(err)
return "", err
}
span.SetAttributes(attribute.Bool("com.microsoft.kiota.authentication.is_url_valid", true))

claims := ""

if additionalAuthenticationContext != nil &&
additionalAuthenticationContext[claimsKey] != nil {
if rawClaims, ok := additionalAuthenticationContext[claimsKey].(string); ok {
decodedClaims, err := base64.StdEncoding.DecodeString(rawClaims)
if err != nil {
span.RecordError(err)
return "", err
}
claims = string(decodedClaims)
err = errors.New("received a claim for CAE but azure identity doesn't support claims: " + claims + " https://github.com/Azure/azure-sdk-for-go/issues/14284")
span.RecordError(err)
return "", err
}
}
span.SetAttributes(attribute.Bool("com.microsoft.kiota.authentication.additional_claims_provided", claims != ""))

if len(p.scopes) == 0 {
p.scopes = append(p.scopes, url.Scheme+"://"+url.Host+"/.default")
}

options := azpolicy.TokenRequestOptions{
Scopes: p.scopes,
//TODO pass the claims once the API is updated to support it https://github.com/Azure/azure-sdk-for-go/issues/14284
}
span.SetAttributes(attribute.String("com.microsoft.kiota.authentication.scopes", strings.Join(p.scopes, ",")))
token, err := p.credential.GetToken(ctx, options)
if err != nil {
span.RecordError(err)
return "", err
}
return token.Token, nil
}

// GetAllowedHostsValidator returns the hosts validator.
func (p *AzureIdentityAccessTokenProvider) GetAllowedHostsValidator() *absauth.AllowedHostsValidator {
return p.allowedHostsValidator
}
package microsoft_kiota_authentication_azure

import (
"context"
"encoding/base64"
"errors"
"strings"

u "net/url"

azcore "github.com/Azure/azure-sdk-for-go/sdk/azcore"
azpolicy "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
absauth "github.com/microsoft/kiota-abstractions-go/authentication"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
)

// AzureIdentityAccessTokenProvider implementation of AccessTokenProvider that supports implementations of TokenCredential from Azure.Identity.
type AzureIdentityAccessTokenProvider struct {
scopes []string
credential azcore.TokenCredential
allowedHostsValidator *absauth.AllowedHostsValidator
// The observation options for the request adapter.
observabilityOptions ObservabilityOptions
}

// ObservabilityOptions holds the tracing, metrics and logging configuration for the request adapter
type ObservabilityOptions struct {
}

var LocalhostStrings = [4]string{"localhost", "[::1]", "::1", "127.0.0.1"}

func (o ObservabilityOptions) GetTracerInstrumentationName() string {
return "github.com/microsoft/kiota-authentication-azure-go"
}

// NewAzureIdentityAccessTokenProvider creates a new instance of the AzureIdentityAccessTokenProvider using "<scheme>://<host>/.default" as the default scope.
func NewAzureIdentityAccessTokenProvider(credential azcore.TokenCredential) (*AzureIdentityAccessTokenProvider, error) {
return NewAzureIdentityAccessTokenProviderWithScopes(credential, nil)
}

// NewAzureIdentityAccessTokenProviderWithScopes creates a new instance of the AzureIdentityAccessTokenProvider.
func NewAzureIdentityAccessTokenProviderWithScopes(credential azcore.TokenCredential, scopes []string) (*AzureIdentityAccessTokenProvider, error) {
return NewAzureIdentityAccessTokenProviderWithScopesAndValidHosts(credential, scopes, nil)
}

// NewAzureIdentityAccessTokenProviderWithScopesAndValidHosts creates a new instance of the AzureIdentityAccessTokenProvider.
func NewAzureIdentityAccessTokenProviderWithScopesAndValidHosts(credential azcore.TokenCredential, scopes []string, validHosts []string) (*AzureIdentityAccessTokenProvider, error) {
return NewAzureIdentityAccessTokenProviderWithScopesAndValidHostsAndObservabilityOptions(credential, scopes, validHosts, ObservabilityOptions{})
}

// NewAzureIdentityAccessTokenProviderWithScopesAndValidHosts creates a new instance of the AzureIdentityAccessTokenProvider.
func NewAzureIdentityAccessTokenProviderWithScopesAndValidHostsAndObservabilityOptions(credential azcore.TokenCredential, scopes []string, validHosts []string, observabilityOptions ObservabilityOptions) (*AzureIdentityAccessTokenProvider, error) {
if credential == nil {
return nil, errors.New("credential cannot be nil")
}
scopesLen := len(scopes)
finalScopes := make([]string, scopesLen)
if scopesLen > 0 {
copy(finalScopes, scopes)
}
validator := absauth.NewAllowedHostsValidator(validHosts)
result := &AzureIdentityAccessTokenProvider{
credential: credential,
scopes: finalScopes,
allowedHostsValidator: &validator,
observabilityOptions: observabilityOptions,
}

return result, nil
}

const claimsKey = "claims"

// GetAuthorizationToken returns the access token for the provided url.
func (p *AzureIdentityAccessTokenProvider) GetAuthorizationToken(ctx context.Context, url *u.URL, additionalAuthenticationContext map[string]interface{}) (string, error) {
ctx, span := otel.GetTracerProvider().Tracer(p.observabilityOptions.GetTracerInstrumentationName()).Start(ctx, "GetAuthorizationToken")
defer span.End()
if !(*(p.allowedHostsValidator)).IsUrlHostValid(url) {
span.SetAttributes(attribute.Bool("com.microsoft.kiota.authentication.is_url_valid", false))
return "", nil
}
if !strings.EqualFold(url.Scheme, "https") && !isLocalhost(url.Host) {
span.SetAttributes(attribute.Bool("com.microsoft.kiota.authentication.is_url_valid", false))
err := errors.New("url scheme must be https")
span.RecordError(err)
return "", err
}
span.SetAttributes(attribute.Bool("com.microsoft.kiota.authentication.is_url_valid", true))

claims := ""

if additionalAuthenticationContext != nil &&
additionalAuthenticationContext[claimsKey] != nil {
if rawClaims, ok := additionalAuthenticationContext[claimsKey].(string); ok {
decodedClaims, err := base64.StdEncoding.DecodeString(rawClaims)
if err != nil {
span.RecordError(err)
return "", err
}
claims = string(decodedClaims)
err = errors.New("received a claim for CAE but azure identity doesn't support claims: " + claims + " https://github.com/Azure/azure-sdk-for-go/issues/14284")
span.RecordError(err)
return "", err
}
}
span.SetAttributes(attribute.Bool("com.microsoft.kiota.authentication.additional_claims_provided", claims != ""))

if len(p.scopes) == 0 {
p.scopes = append(p.scopes, url.Scheme+"://"+url.Host+"/.default")
}

options := azpolicy.TokenRequestOptions{
Scopes: p.scopes,
//TODO pass the claims once the API is updated to support it https://github.com/Azure/azure-sdk-for-go/issues/14284
}
span.SetAttributes(attribute.String("com.microsoft.kiota.authentication.scopes", strings.Join(p.scopes, ",")))
token, err := p.credential.GetToken(ctx, options)
if err != nil {
span.RecordError(err)
return "", err
}
return token.Token, nil
}

// GetAllowedHostsValidator returns the hosts validator.
func (p *AzureIdentityAccessTokenProvider) GetAllowedHostsValidator() *absauth.AllowedHostsValidator {
return p.allowedHostsValidator
}

func isLocalhost(host string) bool {
normalizedHost := strings.ToLower(host)
for _, localhostString := range LocalhostStrings {
if strings.HasPrefix(normalizedHost, localhostString) {
return isValidRemainder(strings.TrimPrefix(normalizedHost, localhostString))
}
}

return false
}

func isValidRemainder(remainder string) bool {
return remainder == "" || strings.HasPrefix(remainder, ":")
}
Loading

0 comments on commit 158857c

Please sign in to comment.