|
@@ -21,10 +21,12 @@ import (
|
|
|
"errors"
|
|
|
"io/ioutil"
|
|
|
"net/http"
|
|
|
+ "net/http/httptest"
|
|
|
"net/url"
|
|
|
"os"
|
|
|
"path/filepath"
|
|
|
"reflect"
|
|
|
+ "sync/atomic"
|
|
|
"testing"
|
|
|
"time"
|
|
|
|
|
@@ -36,7 +38,7 @@ import (
|
|
|
"golang.org/x/oauth2"
|
|
|
)
|
|
|
|
|
|
-func testLoginService(t *testing.T, m *MockAzureHelper) (*AzureLoginService, error) {
|
|
|
+func testLoginService(t *testing.T, apiHelperMock *MockAzureHelper, cloudEnvironmentSvc CloudEnvironmentService) (*azureLoginService, error) {
|
|
|
dir, err := ioutil.TempDir("", "test_store")
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
@@ -44,20 +46,45 @@ func testLoginService(t *testing.T, m *MockAzureHelper) (*AzureLoginService, err
|
|
|
t.Cleanup(func() {
|
|
|
_ = os.RemoveAll(dir)
|
|
|
})
|
|
|
- return newAzureLoginServiceFromPath(filepath.Join(dir, tokenStoreFilename), m)
|
|
|
+
|
|
|
+ ces := CloudEnvironments
|
|
|
+ if cloudEnvironmentSvc != nil {
|
|
|
+ ces = cloudEnvironmentSvc
|
|
|
+ }
|
|
|
+ return newAzureLoginServiceFromPath(filepath.Join(dir, tokenStoreFilename), apiHelperMock, ces)
|
|
|
}
|
|
|
|
|
|
func TestRefreshInValidToken(t *testing.T) {
|
|
|
- data := refreshTokenData("refreshToken")
|
|
|
- m := &MockAzureHelper{}
|
|
|
- m.On("queryToken", data, "123456").Return(azureToken{
|
|
|
+ data := url.Values{
|
|
|
+ "grant_type": []string{"refresh_token"},
|
|
|
+ "client_id": []string{clientID},
|
|
|
+ "scope": []string{"offline_access https://management.docker.com/.default"},
|
|
|
+ "refresh_token": []string{"refreshToken"},
|
|
|
+ }
|
|
|
+ helperMock := &MockAzureHelper{}
|
|
|
+ helperMock.On("queryToken", mock.AnythingOfType("login.CloudEnvironment"), data, "123456").Return(azureToken{
|
|
|
RefreshToken: "newRefreshToken",
|
|
|
AccessToken: "newAccessToken",
|
|
|
ExpiresIn: 3600,
|
|
|
Foci: "1",
|
|
|
}, nil)
|
|
|
|
|
|
- azureLogin, err := testLoginService(t, m)
|
|
|
+ cloudEnvironmentSvcMock := &MockCloudEnvironmentService{}
|
|
|
+ cloudEnvironmentSvcMock.On("Get", "AzureDockerCloud").Return(CloudEnvironment{
|
|
|
+ Name: "AzureDockerCloud",
|
|
|
+ Authentication: CloudEnvironmentAuthentication{
|
|
|
+ LoginEndpoint: "https://login.docker.com",
|
|
|
+ Audiences: []string{
|
|
|
+ "https://management.docker.com",
|
|
|
+ "https://management-ext.docker.com",
|
|
|
+ },
|
|
|
+ Tenant: "common",
|
|
|
+ },
|
|
|
+ ResourceManagerURL: "https://management.docker.com",
|
|
|
+ Suffixes: map[string]string{},
|
|
|
+ }, nil)
|
|
|
+
|
|
|
+ azureLogin, err := testLoginService(t, helperMock, cloudEnvironmentSvcMock)
|
|
|
assert.NilError(t, err)
|
|
|
err = azureLogin.tokenStore.writeLoginInfo(TokenInfo{
|
|
|
TenantID: "123456",
|
|
@@ -67,33 +94,51 @@ func TestRefreshInValidToken(t *testing.T) {
|
|
|
Expiry: time.Now().Add(-1 * time.Hour),
|
|
|
TokenType: "Bearer",
|
|
|
},
|
|
|
+ CloudEnvironment: "AzureDockerCloud",
|
|
|
})
|
|
|
assert.NilError(t, err)
|
|
|
|
|
|
- token, _ := azureLogin.GetValidToken()
|
|
|
+ token, tenantID, err := azureLogin.GetValidToken()
|
|
|
+ assert.NilError(t, err)
|
|
|
+ assert.Equal(t, tenantID, "123456")
|
|
|
|
|
|
assert.Equal(t, token.AccessToken, "newAccessToken")
|
|
|
assert.Assert(t, time.Now().Add(3500*time.Second).Before(token.Expiry))
|
|
|
|
|
|
- storedToken, _ := azureLogin.tokenStore.readToken()
|
|
|
+ storedToken, err := azureLogin.tokenStore.readToken()
|
|
|
+ assert.NilError(t, err)
|
|
|
assert.Equal(t, storedToken.Token.AccessToken, "newAccessToken")
|
|
|
assert.Equal(t, storedToken.Token.RefreshToken, "newRefreshToken")
|
|
|
assert.Assert(t, time.Now().Add(3500*time.Second).Before(storedToken.Token.Expiry))
|
|
|
+
|
|
|
+ assert.Equal(t, storedToken.CloudEnvironment, "AzureDockerCloud")
|
|
|
}
|
|
|
|
|
|
-func TestClearErrorMessageIfNotAlreadyLoggedIn(t *testing.T) {
|
|
|
- dir, err := ioutil.TempDir("", "test_store")
|
|
|
+func TestDoesNotRefreshValidToken(t *testing.T) {
|
|
|
+ expiryDate := time.Now().Add(1 * time.Hour)
|
|
|
+ azureLogin, err := testLoginService(t, nil, nil)
|
|
|
assert.NilError(t, err)
|
|
|
- t.Cleanup(func() {
|
|
|
- _ = os.RemoveAll(dir)
|
|
|
+ err = azureLogin.tokenStore.writeLoginInfo(TokenInfo{
|
|
|
+ TenantID: "123456",
|
|
|
+ Token: oauth2.Token{
|
|
|
+ AccessToken: "accessToken",
|
|
|
+ RefreshToken: "refreshToken",
|
|
|
+ Expiry: expiryDate,
|
|
|
+ TokenType: "Bearer",
|
|
|
+ },
|
|
|
+ CloudEnvironment: AzurePublicCloudName,
|
|
|
})
|
|
|
- _, err = newAuthorizerFromLoginStorePath(filepath.Join(dir, tokenStoreFilename))
|
|
|
- assert.ErrorContains(t, err, "not logged in to azure, you need to run \"docker login azure\" first")
|
|
|
+ assert.NilError(t, err)
|
|
|
+
|
|
|
+ token, tenantID, err := azureLogin.GetValidToken()
|
|
|
+ assert.NilError(t, err)
|
|
|
+ assert.Equal(t, token.AccessToken, "accessToken")
|
|
|
+ assert.Equal(t, tenantID, "123456")
|
|
|
}
|
|
|
|
|
|
-func TestDoesNotRefreshValidToken(t *testing.T) {
|
|
|
+func TestTokenStoreAssumesAzurePublicCloud(t *testing.T) {
|
|
|
expiryDate := time.Now().Add(1 * time.Hour)
|
|
|
- azureLogin, err := testLoginService(t, nil)
|
|
|
+ azureLogin, err := testLoginService(t, nil, nil)
|
|
|
assert.NilError(t, err)
|
|
|
err = azureLogin.tokenStore.writeLoginInfo(TokenInfo{
|
|
|
TenantID: "123456",
|
|
@@ -103,25 +148,33 @@ func TestDoesNotRefreshValidToken(t *testing.T) {
|
|
|
Expiry: expiryDate,
|
|
|
TokenType: "Bearer",
|
|
|
},
|
|
|
+ // Simulates upgrade from older version of Docker CLI that did not have cloud environment concept
|
|
|
+ CloudEnvironment: "",
|
|
|
})
|
|
|
assert.NilError(t, err)
|
|
|
|
|
|
- token, _ := azureLogin.GetValidToken()
|
|
|
+ token, tenantID, err := azureLogin.GetValidToken()
|
|
|
+ assert.NilError(t, err)
|
|
|
+ assert.Equal(t, tenantID, "123456")
|
|
|
assert.Equal(t, token.AccessToken, "accessToken")
|
|
|
+
|
|
|
+ ce, err := azureLogin.GetCloudEnvironment()
|
|
|
+ assert.NilError(t, err)
|
|
|
+ assert.Equal(t, ce.Name, AzurePublicCloudName)
|
|
|
}
|
|
|
|
|
|
func TestInvalidLogin(t *testing.T) {
|
|
|
m := &MockAzureHelper{}
|
|
|
- m.On("openAzureLoginPage", mock.AnythingOfType("string")).Run(func(args mock.Arguments) {
|
|
|
+ m.On("openAzureLoginPage", mock.AnythingOfType("string"), mock.AnythingOfType("CloudEnvironment")).Run(func(args mock.Arguments) {
|
|
|
redirectURL := args.Get(0).(string)
|
|
|
err := queryKeyValue(redirectURL, "error", "access denied: login failed")
|
|
|
assert.NilError(t, err)
|
|
|
}).Return(nil)
|
|
|
|
|
|
- azureLogin, err := testLoginService(t, m)
|
|
|
+ azureLogin, err := testLoginService(t, m, nil)
|
|
|
assert.NilError(t, err)
|
|
|
|
|
|
- err = azureLogin.Login(context.TODO(), "")
|
|
|
+ err = azureLogin.Login(context.TODO(), "", AzurePublicCloudName)
|
|
|
assert.Error(t, err, "no login code: login failed")
|
|
|
}
|
|
|
|
|
@@ -129,19 +182,22 @@ func TestValidLogin(t *testing.T) {
|
|
|
var redirectURL string
|
|
|
ctx := context.TODO()
|
|
|
m := &MockAzureHelper{}
|
|
|
- m.On("openAzureLoginPage", mock.AnythingOfType("string")).Run(func(args mock.Arguments) {
|
|
|
+ ce, err := CloudEnvironments.Get(AzurePublicCloudName)
|
|
|
+ assert.NilError(t, err)
|
|
|
+
|
|
|
+ m.On("openAzureLoginPage", mock.AnythingOfType("string"), mock.AnythingOfType("CloudEnvironment")).Run(func(args mock.Arguments) {
|
|
|
redirectURL = args.Get(0).(string)
|
|
|
err := queryKeyValue(redirectURL, "code", "123456879")
|
|
|
assert.NilError(t, err)
|
|
|
}).Return(nil)
|
|
|
|
|
|
- m.On("queryToken", mock.MatchedBy(func(data url.Values) bool {
|
|
|
+ m.On("queryToken", mock.AnythingOfType("login.CloudEnvironment"), mock.MatchedBy(func(data url.Values) bool {
|
|
|
//Need a matcher here because the value of redirectUrl is not known until executing openAzureLoginPage
|
|
|
return reflect.DeepEqual(data, url.Values{
|
|
|
"grant_type": []string{"authorization_code"},
|
|
|
"client_id": []string{clientID},
|
|
|
"code": []string{"123456879"},
|
|
|
- "scope": []string{scopes},
|
|
|
+ "scope": []string{ce.GetTokenScope()},
|
|
|
"redirect_uri": []string{redirectURL},
|
|
|
})
|
|
|
}), "organizations").Return(azureToken{
|
|
@@ -153,18 +209,18 @@ func TestValidLogin(t *testing.T) {
|
|
|
|
|
|
authBody := `{"value":[{"id":"/tenants/12345a7c-c56d-43e8-9549-dd230ce8a038","tenantId":"12345a7c-c56d-43e8-9549-dd230ce8a038"}]}`
|
|
|
|
|
|
- m.On("queryAPIWithHeader", ctx, getTenantURL, "Bearer firstAccessToken").Return([]byte(authBody), 200, nil)
|
|
|
- data := refreshTokenData("firstRefreshToken")
|
|
|
- m.On("queryToken", data, "12345a7c-c56d-43e8-9549-dd230ce8a038").Return(azureToken{
|
|
|
+ m.On("queryAPIWithHeader", ctx, ce.GetTenantQueryURL(), "Bearer firstAccessToken").Return([]byte(authBody), 200, nil)
|
|
|
+ data := refreshTokenData("firstRefreshToken", ce)
|
|
|
+ m.On("queryToken", mock.AnythingOfType("login.CloudEnvironment"), data, "12345a7c-c56d-43e8-9549-dd230ce8a038").Return(azureToken{
|
|
|
RefreshToken: "newRefreshToken",
|
|
|
AccessToken: "newAccessToken",
|
|
|
ExpiresIn: 3600,
|
|
|
Foci: "1",
|
|
|
}, nil)
|
|
|
- azureLogin, err := testLoginService(t, m)
|
|
|
+ azureLogin, err := testLoginService(t, m, nil)
|
|
|
assert.NilError(t, err)
|
|
|
|
|
|
- err = azureLogin.Login(ctx, "")
|
|
|
+ err = azureLogin.Login(ctx, "", AzurePublicCloudName)
|
|
|
assert.NilError(t, err)
|
|
|
|
|
|
loginToken, err := azureLogin.tokenStore.readToken()
|
|
@@ -174,24 +230,28 @@ func TestValidLogin(t *testing.T) {
|
|
|
assert.Assert(t, time.Now().Add(3500*time.Second).Before(loginToken.Token.Expiry))
|
|
|
assert.Equal(t, loginToken.TenantID, "12345a7c-c56d-43e8-9549-dd230ce8a038")
|
|
|
assert.Equal(t, loginToken.Token.Type(), "Bearer")
|
|
|
+ assert.Equal(t, loginToken.CloudEnvironment, "AzureCloud")
|
|
|
}
|
|
|
|
|
|
func TestValidLoginRequestedTenant(t *testing.T) {
|
|
|
var redirectURL string
|
|
|
m := &MockAzureHelper{}
|
|
|
- m.On("openAzureLoginPage", mock.AnythingOfType("string")).Run(func(args mock.Arguments) {
|
|
|
+ ce, err := CloudEnvironments.Get(AzurePublicCloudName)
|
|
|
+ assert.NilError(t, err)
|
|
|
+
|
|
|
+ m.On("openAzureLoginPage", mock.AnythingOfType("string"), mock.AnythingOfType("CloudEnvironment")).Run(func(args mock.Arguments) {
|
|
|
redirectURL = args.Get(0).(string)
|
|
|
err := queryKeyValue(redirectURL, "code", "123456879")
|
|
|
assert.NilError(t, err)
|
|
|
}).Return(nil)
|
|
|
|
|
|
- m.On("queryToken", mock.MatchedBy(func(data url.Values) bool {
|
|
|
+ m.On("queryToken", mock.AnythingOfType("login.CloudEnvironment"), mock.MatchedBy(func(data url.Values) bool {
|
|
|
//Need a matcher here because the value of redirectUrl is not known until executing openAzureLoginPage
|
|
|
return reflect.DeepEqual(data, url.Values{
|
|
|
"grant_type": []string{"authorization_code"},
|
|
|
"client_id": []string{clientID},
|
|
|
"code": []string{"123456879"},
|
|
|
- "scope": []string{scopes},
|
|
|
+ "scope": []string{ce.GetTokenScope()},
|
|
|
"redirect_uri": []string{redirectURL},
|
|
|
})
|
|
|
}), "organizations").Return(azureToken{
|
|
@@ -205,18 +265,18 @@ func TestValidLoginRequestedTenant(t *testing.T) {
|
|
|
{"id":"/tenants/12345a7c-c56d-43e8-9549-dd230ce8a038","tenantId":"12345a7c-c56d-43e8-9549-dd230ce8a038"}]}`
|
|
|
|
|
|
ctx := context.TODO()
|
|
|
- m.On("queryAPIWithHeader", ctx, getTenantURL, "Bearer firstAccessToken").Return([]byte(authBody), 200, nil)
|
|
|
- data := refreshTokenData("firstRefreshToken")
|
|
|
- m.On("queryToken", data, "12345a7c-c56d-43e8-9549-dd230ce8a038").Return(azureToken{
|
|
|
+ m.On("queryAPIWithHeader", ctx, ce.GetTenantQueryURL(), "Bearer firstAccessToken").Return([]byte(authBody), 200, nil)
|
|
|
+ data := refreshTokenData("firstRefreshToken", ce)
|
|
|
+ m.On("queryToken", mock.AnythingOfType("login.CloudEnvironment"), data, "12345a7c-c56d-43e8-9549-dd230ce8a038").Return(azureToken{
|
|
|
RefreshToken: "newRefreshToken",
|
|
|
AccessToken: "newAccessToken",
|
|
|
ExpiresIn: 3600,
|
|
|
Foci: "1",
|
|
|
}, nil)
|
|
|
- azureLogin, err := testLoginService(t, m)
|
|
|
+ azureLogin, err := testLoginService(t, m, nil)
|
|
|
assert.NilError(t, err)
|
|
|
|
|
|
- err = azureLogin.Login(ctx, "12345a7c-c56d-43e8-9549-dd230ce8a038")
|
|
|
+ err = azureLogin.Login(ctx, "12345a7c-c56d-43e8-9549-dd230ce8a038", AzurePublicCloudName)
|
|
|
assert.NilError(t, err)
|
|
|
|
|
|
loginToken, err := azureLogin.tokenStore.readToken()
|
|
@@ -226,24 +286,28 @@ func TestValidLoginRequestedTenant(t *testing.T) {
|
|
|
assert.Assert(t, time.Now().Add(3500*time.Second).Before(loginToken.Token.Expiry))
|
|
|
assert.Equal(t, loginToken.TenantID, "12345a7c-c56d-43e8-9549-dd230ce8a038")
|
|
|
assert.Equal(t, loginToken.Token.Type(), "Bearer")
|
|
|
+ assert.Equal(t, loginToken.CloudEnvironment, "AzureCloud")
|
|
|
}
|
|
|
|
|
|
func TestLoginNoTenant(t *testing.T) {
|
|
|
var redirectURL string
|
|
|
m := &MockAzureHelper{}
|
|
|
- m.On("openAzureLoginPage", mock.AnythingOfType("string")).Run(func(args mock.Arguments) {
|
|
|
+ ce, err := CloudEnvironments.Get(AzurePublicCloudName)
|
|
|
+ assert.NilError(t, err)
|
|
|
+
|
|
|
+ m.On("openAzureLoginPage", mock.AnythingOfType("string"), mock.AnythingOfType("CloudEnvironment")).Run(func(args mock.Arguments) {
|
|
|
redirectURL = args.Get(0).(string)
|
|
|
err := queryKeyValue(redirectURL, "code", "123456879")
|
|
|
assert.NilError(t, err)
|
|
|
}).Return(nil)
|
|
|
|
|
|
- m.On("queryToken", mock.MatchedBy(func(data url.Values) bool {
|
|
|
+ m.On("queryToken", mock.AnythingOfType("login.CloudEnvironment"), mock.MatchedBy(func(data url.Values) bool {
|
|
|
//Need a matcher here because the value of redirectUrl is not known until executing openAzureLoginPage
|
|
|
return reflect.DeepEqual(data, url.Values{
|
|
|
"grant_type": []string{"authorization_code"},
|
|
|
"client_id": []string{clientID},
|
|
|
"code": []string{"123456879"},
|
|
|
- "scope": []string{scopes},
|
|
|
+ "scope": []string{ce.GetTokenScope()},
|
|
|
"redirect_uri": []string{redirectURL},
|
|
|
})
|
|
|
}), "organizations").Return(azureToken{
|
|
@@ -255,31 +319,34 @@ func TestLoginNoTenant(t *testing.T) {
|
|
|
|
|
|
ctx := context.TODO()
|
|
|
authBody := `{"value":[{"id":"/tenants/12345a7c-c56d-43e8-9549-dd230ce8a038","tenantId":"12345a7c-c56d-43e8-9549-dd230ce8a038"}]}`
|
|
|
- m.On("queryAPIWithHeader", ctx, getTenantURL, "Bearer firstAccessToken").Return([]byte(authBody), 200, nil)
|
|
|
+ m.On("queryAPIWithHeader", ctx, ce.GetTenantQueryURL(), "Bearer firstAccessToken").Return([]byte(authBody), 200, nil)
|
|
|
|
|
|
- azureLogin, err := testLoginService(t, m)
|
|
|
+ azureLogin, err := testLoginService(t, m, nil)
|
|
|
assert.NilError(t, err)
|
|
|
|
|
|
- err = azureLogin.Login(ctx, "00000000-c56d-43e8-9549-dd230ce8a038")
|
|
|
+ err = azureLogin.Login(ctx, "00000000-c56d-43e8-9549-dd230ce8a038", AzurePublicCloudName)
|
|
|
assert.Error(t, err, "could not find requested azure tenant 00000000-c56d-43e8-9549-dd230ce8a038: login failed")
|
|
|
}
|
|
|
|
|
|
func TestLoginRequestedTenantNotFound(t *testing.T) {
|
|
|
var redirectURL string
|
|
|
m := &MockAzureHelper{}
|
|
|
- m.On("openAzureLoginPage", mock.AnythingOfType("string")).Run(func(args mock.Arguments) {
|
|
|
+ ce, err := CloudEnvironments.Get(AzurePublicCloudName)
|
|
|
+ assert.NilError(t, err)
|
|
|
+
|
|
|
+ m.On("openAzureLoginPage", mock.AnythingOfType("string"), mock.AnythingOfType("CloudEnvironment")).Run(func(args mock.Arguments) {
|
|
|
redirectURL = args.Get(0).(string)
|
|
|
err := queryKeyValue(redirectURL, "code", "123456879")
|
|
|
assert.NilError(t, err)
|
|
|
}).Return(nil)
|
|
|
|
|
|
- m.On("queryToken", mock.MatchedBy(func(data url.Values) bool {
|
|
|
+ m.On("queryToken", mock.AnythingOfType("login.CloudEnvironment"), mock.MatchedBy(func(data url.Values) bool {
|
|
|
//Need a matcher here because the value of redirectUrl is not known until executing openAzureLoginPage
|
|
|
return reflect.DeepEqual(data, url.Values{
|
|
|
"grant_type": []string{"authorization_code"},
|
|
|
"client_id": []string{clientID},
|
|
|
"code": []string{"123456879"},
|
|
|
- "scope": []string{scopes},
|
|
|
+ "scope": []string{ce.GetTokenScope()},
|
|
|
"redirect_uri": []string{redirectURL},
|
|
|
})
|
|
|
}), "organizations").Return(azureToken{
|
|
@@ -291,31 +358,34 @@ func TestLoginRequestedTenantNotFound(t *testing.T) {
|
|
|
|
|
|
ctx := context.TODO()
|
|
|
authBody := `{"value":[]}`
|
|
|
- m.On("queryAPIWithHeader", ctx, getTenantURL, "Bearer firstAccessToken").Return([]byte(authBody), 200, nil)
|
|
|
+ m.On("queryAPIWithHeader", ctx, ce.GetTenantQueryURL(), "Bearer firstAccessToken").Return([]byte(authBody), 200, nil)
|
|
|
|
|
|
- azureLogin, err := testLoginService(t, m)
|
|
|
+ azureLogin, err := testLoginService(t, m, nil)
|
|
|
assert.NilError(t, err)
|
|
|
|
|
|
- err = azureLogin.Login(ctx, "")
|
|
|
+ err = azureLogin.Login(ctx, "", AzurePublicCloudName)
|
|
|
assert.Error(t, err, "could not find azure tenant: login failed")
|
|
|
}
|
|
|
|
|
|
func TestLoginAuthorizationFailed(t *testing.T) {
|
|
|
var redirectURL string
|
|
|
m := &MockAzureHelper{}
|
|
|
- m.On("openAzureLoginPage", mock.AnythingOfType("string")).Run(func(args mock.Arguments) {
|
|
|
+ ce, err := CloudEnvironments.Get(AzurePublicCloudName)
|
|
|
+ assert.NilError(t, err)
|
|
|
+
|
|
|
+ m.On("openAzureLoginPage", mock.AnythingOfType("string"), mock.AnythingOfType("CloudEnvironment")).Run(func(args mock.Arguments) {
|
|
|
redirectURL = args.Get(0).(string)
|
|
|
err := queryKeyValue(redirectURL, "code", "123456879")
|
|
|
assert.NilError(t, err)
|
|
|
}).Return(nil)
|
|
|
|
|
|
- m.On("queryToken", mock.MatchedBy(func(data url.Values) bool {
|
|
|
+ m.On("queryToken", mock.AnythingOfType("login.CloudEnvironment"), mock.MatchedBy(func(data url.Values) bool {
|
|
|
//Need a matcher here because the value of redirectUrl is not known until executing openAzureLoginPage
|
|
|
return reflect.DeepEqual(data, url.Values{
|
|
|
"grant_type": []string{"authorization_code"},
|
|
|
"client_id": []string{clientID},
|
|
|
"code": []string{"123456879"},
|
|
|
- "scope": []string{scopes},
|
|
|
+ "scope": []string{ce.GetTokenScope()},
|
|
|
"redirect_uri": []string{redirectURL},
|
|
|
})
|
|
|
}), "organizations").Return(azureToken{
|
|
@@ -328,35 +398,38 @@ func TestLoginAuthorizationFailed(t *testing.T) {
|
|
|
authBody := `[access denied]`
|
|
|
|
|
|
ctx := context.TODO()
|
|
|
- m.On("queryAPIWithHeader", ctx, getTenantURL, "Bearer firstAccessToken").Return([]byte(authBody), 400, nil)
|
|
|
+ m.On("queryAPIWithHeader", ctx, ce.GetTenantQueryURL(), "Bearer firstAccessToken").Return([]byte(authBody), 400, nil)
|
|
|
|
|
|
- azureLogin, err := testLoginService(t, m)
|
|
|
+ azureLogin, err := testLoginService(t, m, nil)
|
|
|
assert.NilError(t, err)
|
|
|
|
|
|
- err = azureLogin.Login(ctx, "")
|
|
|
+ err = azureLogin.Login(ctx, "", AzurePublicCloudName)
|
|
|
assert.Error(t, err, "unable to login status code 400: [access denied]: login failed")
|
|
|
}
|
|
|
|
|
|
func TestValidThroughDeviceCodeFlow(t *testing.T) {
|
|
|
m := &MockAzureHelper{}
|
|
|
- m.On("openAzureLoginPage", mock.AnythingOfType("string")).Return(errors.New("Could not open browser"))
|
|
|
- m.On("getDeviceCodeFlowToken").Return(adal.Token{AccessToken: "firstAccessToken", RefreshToken: "firstRefreshToken"}, nil)
|
|
|
+ ce, err := CloudEnvironments.Get(AzurePublicCloudName)
|
|
|
+ assert.NilError(t, err)
|
|
|
+
|
|
|
+ m.On("openAzureLoginPage", mock.AnythingOfType("string"), mock.AnythingOfType("CloudEnvironment")).Return(errors.New("Could not open browser"))
|
|
|
+ m.On("getDeviceCodeFlowToken", mock.AnythingOfType("CloudEnvironment")).Return(adal.Token{AccessToken: "firstAccessToken", RefreshToken: "firstRefreshToken"}, nil)
|
|
|
|
|
|
authBody := `{"value":[{"id":"/tenants/12345a7c-c56d-43e8-9549-dd230ce8a038","tenantId":"12345a7c-c56d-43e8-9549-dd230ce8a038"}]}`
|
|
|
|
|
|
ctx := context.TODO()
|
|
|
- m.On("queryAPIWithHeader", ctx, getTenantURL, "Bearer firstAccessToken").Return([]byte(authBody), 200, nil)
|
|
|
- data := refreshTokenData("firstRefreshToken")
|
|
|
- m.On("queryToken", data, "12345a7c-c56d-43e8-9549-dd230ce8a038").Return(azureToken{
|
|
|
+ m.On("queryAPIWithHeader", ctx, ce.GetTenantQueryURL(), "Bearer firstAccessToken").Return([]byte(authBody), 200, nil)
|
|
|
+ data := refreshTokenData("firstRefreshToken", ce)
|
|
|
+ m.On("queryToken", mock.AnythingOfType("login.CloudEnvironment"), data, "12345a7c-c56d-43e8-9549-dd230ce8a038").Return(azureToken{
|
|
|
RefreshToken: "newRefreshToken",
|
|
|
AccessToken: "newAccessToken",
|
|
|
ExpiresIn: 3600,
|
|
|
Foci: "1",
|
|
|
}, nil)
|
|
|
- azureLogin, err := testLoginService(t, m)
|
|
|
+ azureLogin, err := testLoginService(t, m, nil)
|
|
|
assert.NilError(t, err)
|
|
|
|
|
|
- err = azureLogin.Login(ctx, "")
|
|
|
+ err = azureLogin.Login(ctx, "", AzurePublicCloudName)
|
|
|
assert.NilError(t, err)
|
|
|
|
|
|
loginToken, err := azureLogin.tokenStore.readToken()
|
|
@@ -366,13 +439,110 @@ func TestValidThroughDeviceCodeFlow(t *testing.T) {
|
|
|
assert.Assert(t, time.Now().Add(3500*time.Second).Before(loginToken.Token.Expiry))
|
|
|
assert.Equal(t, loginToken.TenantID, "12345a7c-c56d-43e8-9549-dd230ce8a038")
|
|
|
assert.Equal(t, loginToken.Token.Type(), "Bearer")
|
|
|
+ assert.Equal(t, loginToken.CloudEnvironment, "AzureCloud")
|
|
|
}
|
|
|
|
|
|
-func refreshTokenData(refreshToken string) url.Values {
|
|
|
+func TestNonstandardCloudEnvironment(t *testing.T) {
|
|
|
+ dockerCloudMetadata := []byte(`
|
|
|
+ [{
|
|
|
+ "authentication": {
|
|
|
+ "loginEndpoint": "https://login.docker.com/",
|
|
|
+ "audiences": [
|
|
|
+ "https://management.docker.com/",
|
|
|
+ "https://management.cli.docker.com/"
|
|
|
+ ],
|
|
|
+ "tenant": "F5773994-FE88-482E-9E33-6E799D250416"
|
|
|
+ },
|
|
|
+ "name": "AzureDockerCloud",
|
|
|
+ "suffixes": {
|
|
|
+ "acrLoginServer": "azurecr.docker.io"
|
|
|
+ },
|
|
|
+ "resourceManager": "https://management.docker.com/"
|
|
|
+ }]`)
|
|
|
+ var metadataReqCount int32 = 0
|
|
|
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
+ _, err := w.Write(dockerCloudMetadata)
|
|
|
+ assert.NilError(t, err)
|
|
|
+ atomic.AddInt32(&metadataReqCount, 1)
|
|
|
+ }))
|
|
|
+ defer srv.Close()
|
|
|
+
|
|
|
+ cloudMetadataURL, cloudMetadataURLSet := os.LookupEnv(CloudMetadataURLVar)
|
|
|
+ if cloudMetadataURLSet {
|
|
|
+ defer func() {
|
|
|
+ err := os.Setenv(CloudMetadataURLVar, cloudMetadataURL)
|
|
|
+ assert.NilError(t, err)
|
|
|
+ }()
|
|
|
+ }
|
|
|
+ err := os.Setenv(CloudMetadataURLVar, srv.URL)
|
|
|
+ assert.NilError(t, err)
|
|
|
+
|
|
|
+ ctx := context.TODO()
|
|
|
+
|
|
|
+ ces := newCloudEnvironmentService()
|
|
|
+ ces.cloudMetadataURL = srv.URL
|
|
|
+ dockerCloudEnv, err := ces.Get("AzureDockerCloud")
|
|
|
+ assert.NilError(t, err)
|
|
|
+
|
|
|
+ helperMock := &MockAzureHelper{}
|
|
|
+ var redirectURL string
|
|
|
+ helperMock.On("openAzureLoginPage", mock.AnythingOfType("string"), mock.AnythingOfType("CloudEnvironment")).Run(func(args mock.Arguments) {
|
|
|
+ redirectURL = args.Get(0).(string)
|
|
|
+ err := queryKeyValue(redirectURL, "code", "123456879")
|
|
|
+ assert.NilError(t, err)
|
|
|
+ }).Return(nil)
|
|
|
+
|
|
|
+ helperMock.On("queryToken", mock.AnythingOfType("login.CloudEnvironment"), mock.MatchedBy(func(data url.Values) bool {
|
|
|
+ //Need a matcher here because the value of redirectUrl is not known until executing openAzureLoginPage
|
|
|
+ return reflect.DeepEqual(data, url.Values{
|
|
|
+ "grant_type": []string{"authorization_code"},
|
|
|
+ "client_id": []string{clientID},
|
|
|
+ "code": []string{"123456879"},
|
|
|
+ "scope": []string{dockerCloudEnv.GetTokenScope()},
|
|
|
+ "redirect_uri": []string{redirectURL},
|
|
|
+ })
|
|
|
+ }), "organizations").Return(azureToken{
|
|
|
+ RefreshToken: "firstRefreshToken",
|
|
|
+ AccessToken: "firstAccessToken",
|
|
|
+ ExpiresIn: 3600,
|
|
|
+ Foci: "1",
|
|
|
+ }, nil)
|
|
|
+
|
|
|
+ authBody := `{"value":[{"id":"/tenants/F5773994-FE88-482E-9E33-6E799D250416","tenantId":"F5773994-FE88-482E-9E33-6E799D250416"}]}`
|
|
|
+
|
|
|
+ helperMock.On("queryAPIWithHeader", ctx, dockerCloudEnv.GetTenantQueryURL(), "Bearer firstAccessToken").Return([]byte(authBody), 200, nil)
|
|
|
+ data := refreshTokenData("firstRefreshToken", dockerCloudEnv)
|
|
|
+ helperMock.On("queryToken", mock.AnythingOfType("login.CloudEnvironment"), data, "F5773994-FE88-482E-9E33-6E799D250416").Return(azureToken{
|
|
|
+ RefreshToken: "newRefreshToken",
|
|
|
+ AccessToken: "newAccessToken",
|
|
|
+ ExpiresIn: 3600,
|
|
|
+ Foci: "1",
|
|
|
+ }, nil)
|
|
|
+
|
|
|
+ azureLogin, err := testLoginService(t, helperMock, ces)
|
|
|
+ assert.NilError(t, err)
|
|
|
+
|
|
|
+ err = azureLogin.Login(ctx, "", "AzureDockerCloud")
|
|
|
+ assert.NilError(t, err)
|
|
|
+
|
|
|
+ loginToken, err := azureLogin.tokenStore.readToken()
|
|
|
+ assert.NilError(t, err)
|
|
|
+ assert.Equal(t, loginToken.Token.AccessToken, "newAccessToken")
|
|
|
+ assert.Equal(t, loginToken.Token.RefreshToken, "newRefreshToken")
|
|
|
+ assert.Assert(t, time.Now().Add(3500*time.Second).Before(loginToken.Token.Expiry))
|
|
|
+ assert.Equal(t, loginToken.TenantID, "F5773994-FE88-482E-9E33-6E799D250416")
|
|
|
+ assert.Equal(t, loginToken.Token.Type(), "Bearer")
|
|
|
+ assert.Equal(t, loginToken.CloudEnvironment, "AzureDockerCloud")
|
|
|
+ assert.Equal(t, metadataReqCount, int32(1))
|
|
|
+}
|
|
|
+
|
|
|
+// Don't warn about refreshToken parameter taking the same value for all invocations
|
|
|
+// nolint:unparam
|
|
|
+func refreshTokenData(refreshToken string, ce CloudEnvironment) url.Values {
|
|
|
return url.Values{
|
|
|
"grant_type": []string{"refresh_token"},
|
|
|
"client_id": []string{clientID},
|
|
|
- "scope": []string{scopes},
|
|
|
+ "scope": []string{ce.GetTokenScope()},
|
|
|
"refresh_token": []string{refreshToken},
|
|
|
}
|
|
|
}
|
|
@@ -394,13 +564,13 @@ type MockAzureHelper struct {
|
|
|
mock.Mock
|
|
|
}
|
|
|
|
|
|
-func (s *MockAzureHelper) getDeviceCodeFlowToken() (adal.Token, error) {
|
|
|
- args := s.Called()
|
|
|
+func (s *MockAzureHelper) getDeviceCodeFlowToken(ce CloudEnvironment) (adal.Token, error) {
|
|
|
+ args := s.Called(ce)
|
|
|
return args.Get(0).(adal.Token), args.Error(1)
|
|
|
}
|
|
|
|
|
|
-func (s *MockAzureHelper) queryToken(data url.Values, tenantID string) (token azureToken, err error) {
|
|
|
- args := s.Called(data, tenantID)
|
|
|
+func (s *MockAzureHelper) queryToken(ce CloudEnvironment, data url.Values, tenantID string) (token azureToken, err error) {
|
|
|
+ args := s.Called(ce, data, tenantID)
|
|
|
return args.Get(0).(azureToken), args.Error(1)
|
|
|
}
|
|
|
|
|
@@ -409,7 +579,16 @@ func (s *MockAzureHelper) queryAPIWithHeader(ctx context.Context, authorizationU
|
|
|
return args.Get(0).([]byte), args.Int(1), args.Error(2)
|
|
|
}
|
|
|
|
|
|
-func (s *MockAzureHelper) openAzureLoginPage(redirectURL string) error {
|
|
|
- args := s.Called(redirectURL)
|
|
|
+func (s *MockAzureHelper) openAzureLoginPage(redirectURL string, ce CloudEnvironment) error {
|
|
|
+ args := s.Called(redirectURL, ce)
|
|
|
return args.Error(0)
|
|
|
}
|
|
|
+
|
|
|
+type MockCloudEnvironmentService struct {
|
|
|
+ mock.Mock
|
|
|
+}
|
|
|
+
|
|
|
+func (s *MockCloudEnvironmentService) Get(name string) (CloudEnvironment, error) {
|
|
|
+ args := s.Called(name)
|
|
|
+ return args.Get(0).(CloudEnvironment), args.Error(1)
|
|
|
+}
|