Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 94 additions & 2 deletions sdk/azidentity/azidentity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ package azidentity
import (
"context"
"errors"
"io/ioutil"
"net/http"
"os"
"strings"
"testing"

"github.com/Azure/azure-sdk-for-go/sdk/internal/mock"
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential"
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/public"
"github.com/golang-jwt/jwt/v4"
)

// constants used throughout this package
Expand All @@ -23,10 +28,97 @@ const (

// constants for this file
const (
envHostString = "https://mock.com/"
customHostString = "https://custommock.com/"
envHostString = "https://mock.com/"
customHostString = "https://custommock.com/"
tenantDiscoveryResponse = `{
"token_endpoint": "https://login.microsoftonline.com/3c631bb7-a9f7-4343-a5ba-a6159135f1fc/oauth2/v2.0/token",
"token_endpoint_auth_methods_supported": [
"client_secret_post",
"private_key_jwt",
"client_secret_basic"
],
"jwks_uri": "https://login.microsoftonline.com/3c631bb7-a9f7-4343-a5ba-a6159135f1fc/discovery/v2.0/keys",
"response_modes_supported": [
"query",
"fragment",
"form_post"
],
"subject_types_supported": [
"pairwise"
],
"id_token_signing_alg_values_supported": [
"RS256"
],
"response_types_supported": [
"code",
"id_token",
"code id_token",
"id_token token"
],
"scopes_supported": [
"openid",
"profile",
"email",
"offline_access"
],
"issuer": "https://login.microsoftonline.com/3c631bb7-a9f7-4343-a5ba-a6159135f1fc/v2.0",
"request_uri_parameter_supported": false,
"userinfo_endpoint": "https://graph.microsoft.com/oidc/userinfo",
"authorization_endpoint": "https://login.microsoftonline.com/3c631bb7-a9f7-4343-a5ba-a6159135f1fc/oauth2/v2.0/authorize",
"device_authorization_endpoint": "https://login.microsoftonline.com/3c631bb7-a9f7-4343-a5ba-a6159135f1fc/oauth2/v2.0/devicecode",
"http_logout_supported": true,
"frontchannel_logout_supported": true,
"end_session_endpoint": "https://login.microsoftonline.com/3c631bb7-a9f7-4343-a5ba-a6159135f1fc/oauth2/v2.0/logout",
"claims_supported": [
"sub",
"iss",
"cloud_instance_name",
"cloud_instance_host_name",
"cloud_graph_host_name",
"msgraph_host",
"aud",
"exp",
"iat",
"auth_time",
"acr",
"nonce",
"preferred_username",
"name",
"tid",
"ver",
"at_hash",
"c_hash",
"email"
],
"kerberos_endpoint": "https://login.microsoftonline.com/3c631bb7-a9f7-4343-a5ba-a6159135f1fc/kerberos",
"tenant_region_scope": "NA",
"cloud_instance_name": "microsoftonline.com",
"cloud_graph_host_name": "graph.windows.net",
"msgraph_host": "graph.microsoft.com",
"rbac_url": "https://pas.windows.net"
}`
)

func validateJWTRequestContainsHeader(t *testing.T, headerName string) mock.ResponsePredicate {
return func(req *http.Request) bool {
body, err := ioutil.ReadAll(req.Body)
if err != nil {
t.Fatal("Expected a request with the JWT in the body.")
}
bodystr := string(body)
kvps := strings.Split(bodystr, "&")
assertion := strings.Split(kvps[0], "=")
token, _ := jwt.Parse(assertion[1], nil)
if token == nil {
t.Fatalf("Failed to parse the JWT token: %s.", assertion[1])
}
if _, ok := token.Header[headerName]; !ok {
t.Fatalf("JWT did not contain the %s header", headerName)
}
return true
}
}

// Set environment variables for the duration of a test. Restore their prior values
// after the test completes. Obviated by 1.17's T.Setenv
func setEnvironmentVariables(t *testing.T, vars map[string]string) {
Expand Down
24 changes: 24 additions & 0 deletions sdk/azidentity/client_certificate_credential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"os"
"testing"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/internal/mock"
)
Expand Down Expand Up @@ -82,6 +83,29 @@ func TestClientCertificateCredential_GetTokenSuccess_withCertificateChain(t *tes
}
}

func TestClientCertificateCredential_GetTokenSuccess_withCertificateChain_mock(t *testing.T) {
test := allCertTests[0]
srv, close := mock.NewServer(mock.WithTransformAllRequestsToTestServerUrl())
defer close()
srv.AppendResponse()
srv.AppendResponse(mock.WithBody([]byte(tenantDiscoveryResponse)))
srv.AppendResponse(mock.WithPredicate(validateJWTRequestContainsHeader(t, "x5c")), mock.WithBody([]byte(accessTokenRespSuccess)))
srv.AppendResponse()

options := ClientCertificateCredentialOptions{ClientOptions: azcore.ClientOptions{Transport: srv}, SendCertificateChain: true}
cred, err := NewClientCertificateCredential(fakeTenantID, fakeClientID, test.certs, test.key, &options)
if err != nil {
t.Fatal(err)
}
tk, err := cred.GetToken(context.Background(), policy.TokenRequestOptions{Scopes: []string{liveTestScope}})
if err != nil {
t.Fatal(err)
}
if tk.Token != tokenValue {
t.Fatalf("unexpected token: %s", tk.Token)
}
}

func TestClientCertificateCredential_GetTokenCheckPrivateKeyBlocks(t *testing.T) {
test := allCertTests[0]
cred, err := NewClientCertificateCredential(fakeTenantID, fakeClientID, test.certs, test.key, nil)
Expand Down
6 changes: 6 additions & 0 deletions sdk/azidentity/environment_credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ import (
"errors"
"fmt"
"os"
"strings"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/internal/log"
)

const envVarSendCertChain = "AZURE_CLIENT_SEND_CERTIFICATE_CHAIN"

// EnvironmentCredentialOptions contains optional parameters for EnvironmentCredential
type EnvironmentCredentialOptions struct {
azcore.ClientOptions
Expand Down Expand Up @@ -81,6 +84,9 @@ func NewEnvironmentCredential(options *EnvironmentCredentialOptions) (*Environme
return nil, fmt.Errorf(`failed to load certificate from "%s": %v`, certPath, err)
}
o := &ClientCertificateCredentialOptions{AuthorityHost: options.AuthorityHost, ClientOptions: options.ClientOptions}
if v, ok := os.LookupEnv(envVarSendCertChain); ok {
o.SendCertificateChain = v == "1" || strings.ToLower(v) == "true"
}
cred, err := NewClientCertificateCredential(tenantID, clientID, certs, key, o)
if err != nil {
return nil, err
Expand Down
31 changes: 31 additions & 0 deletions sdk/azidentity/environment_credential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import (
"os"
"testing"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/internal/mock"
)

func initEnvironmentVarsForTest() error {
Expand Down Expand Up @@ -173,6 +175,35 @@ func TestEnvironmentCredential_UsernamePasswordSet(t *testing.T) {
}
}

func TestEnvironmentCredential_SendCertificateChain(t *testing.T) {
resetEnvironmentVarsForTest()
srv, close := mock.NewServer(mock.WithTransformAllRequestsToTestServerUrl())
defer close()
srv.AppendResponse()
srv.AppendResponse(mock.WithBody([]byte(tenantDiscoveryResponse)))
srv.AppendResponse(mock.WithPredicate(validateJWTRequestContainsHeader(t, "x5c")), mock.WithBody([]byte(accessTokenRespSuccess)))
srv.AppendResponse()

vars := map[string]string{
"AZURE_CLIENT_ID": liveSP.clientID,
"AZURE_CLIENT_CERTIFICATE_PATH": liveSP.pfxPath,
"AZURE_TENANT_ID": liveSP.tenantID,
envVarSendCertChain: "true",
}
setEnvironmentVariables(t, vars)
cred, err := NewEnvironmentCredential(&EnvironmentCredentialOptions{ClientOptions: azcore.ClientOptions{Transport: srv}})
if err != nil {
t.Fatal(err)
}
tk, err := cred.GetToken(context.Background(), policy.TokenRequestOptions{Scopes: []string{liveTestScope}})
if err != nil {
t.Fatal(err)
}
if tk.Token != tokenValue {
t.Fatalf("unexpected token: %s", tk.Token)
}
}

func TestEnvironmentCredential_ClientSecretLive(t *testing.T) {
vars := map[string]string{
"AZURE_CLIENT_ID": liveSP.clientID,
Expand Down
3 changes: 3 additions & 0 deletions sdk/azidentity/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3
github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

replace github.com/Azure/azure-sdk-for-go/sdk/internal => ../internal
2 changes: 2 additions & 0 deletions sdk/azidentity/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c=
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
Expand Down
30 changes: 29 additions & 1 deletion sdk/internal/mock/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ package mock
import (
"crypto/tls"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"sync"
"time"
)
Expand All @@ -38,6 +40,9 @@ type Server struct {

// count tracks the number of requests that have been made.
count int

// determines whether all requests will be routed to the httptest Server by changing the Host of each request
routeAllRequestsToMockServer bool
}

func newServer() *Server {
Expand Down Expand Up @@ -123,7 +128,24 @@ func (s *Server) Do(req *http.Request) (*http.Response, error) {
resp := s.getResponse()
return nil, resp.err
}
resp, err := s.srv.Client().Do(req)
var err error
var resp *http.Response
if s.routeAllRequestsToMockServer {
var srvUrl *url.URL
originalURL := req.URL
mockUrl := *req.URL
srvUrl, err = url.Parse(s.srv.URL)
if err != nil {
return nil, fmt.Errorf("Unable to parse the test server URL: %v", err)
}
mockUrl.Host = srvUrl.Host
mockUrl.Scheme = srvUrl.Scheme
req.URL = &mockUrl
resp, err = s.srv.Client().Do(req)
req.URL = originalURL
} else {
resp, err = s.srv.Client().Do(req)
}
if err != nil {
return resp, err
}
Expand Down Expand Up @@ -225,6 +247,12 @@ func (fn fnSrvOpt) apply(s *Server) {
fn(s)
}

func WithTransformAllRequestsToTestServerUrl() ServerOption {
return fnSrvOpt(func(s *Server) {
s.routeAllRequestsToMockServer = true
})
}

// WithTLSConfig sets the given TLS config on server.
func WithTLSConfig(cfg *tls.Config) ServerOption {
return fnSrvOpt(func(s *Server) {
Expand Down