github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/provider/azure/internal/azureauth/interactive_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package azureauth_test
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"net/http"
    12  	"time"
    13  
    14  	"github.com/Azure/azure-sdk-for-go/arm/authorization"
    15  	"github.com/Azure/go-autorest/autorest"
    16  	"github.com/Azure/go-autorest/autorest/azure"
    17  	"github.com/Azure/go-autorest/autorest/mocks"
    18  	"github.com/Azure/go-autorest/autorest/to"
    19  	"github.com/juju/testing"
    20  	jc "github.com/juju/testing/checkers"
    21  	"github.com/juju/utils"
    22  	gc "gopkg.in/check.v1"
    23  
    24  	"github.com/juju/juju/provider/azure/internal/ad"
    25  	"github.com/juju/juju/provider/azure/internal/azureauth"
    26  	"github.com/juju/juju/provider/azure/internal/azuretesting"
    27  )
    28  
    29  func clockStartTime() time.Time {
    30  	t, _ := time.Parse("2006-Jan-02 3:04am", "2016-Sep-19 9:47am")
    31  	return t
    32  }
    33  
    34  type InteractiveSuite struct {
    35  	testing.IsolationSuite
    36  	clock   *testing.Clock
    37  	newUUID func() (utils.UUID, error)
    38  }
    39  
    40  var _ = gc.Suite(&InteractiveSuite{})
    41  
    42  func deviceCodeSender() autorest.Sender {
    43  	return azuretesting.NewSenderWithValue(azure.DeviceCode{
    44  		DeviceCode: to.StringPtr("device-code"),
    45  		Interval:   to.Int64Ptr(1), // 1 second between polls
    46  		Message:    to.StringPtr("open your browser, etc."),
    47  	})
    48  }
    49  
    50  func tokenSender() autorest.Sender {
    51  	return azuretesting.NewSenderWithValue(azure.Token{
    52  		RefreshToken: "refresh-token",
    53  		ExpiresOn:    fmt.Sprint(time.Now().Add(time.Hour).Unix()),
    54  	})
    55  }
    56  
    57  func passwordCredentialsListSender() autorest.Sender {
    58  	return azuretesting.NewSenderWithValue(ad.PasswordCredentialsListResult{
    59  		Value: []ad.PasswordCredential{{
    60  			KeyId: "password-credential-key-id",
    61  		}},
    62  	})
    63  }
    64  
    65  func updatePasswordCredentialsSender() autorest.Sender {
    66  	sender := mocks.NewSender()
    67  	sender.AppendResponse(mocks.NewResponseWithStatus("", http.StatusNoContent))
    68  	return sender
    69  }
    70  
    71  func currentUserSender() autorest.Sender {
    72  	return azuretesting.NewSenderWithValue(ad.AADObject{
    73  		DisplayName: "Foo Bar",
    74  	})
    75  }
    76  
    77  func createServicePrincipalSender() autorest.Sender {
    78  	return azuretesting.NewSenderWithValue(ad.ServicePrincipal{
    79  		ApplicationID: "cbb548f1-5039-4836-af0b-727e8571f6a9",
    80  		ObjectID:      "sp-object-id",
    81  	})
    82  }
    83  
    84  func createServicePrincipalAlreadyExistsSender() autorest.Sender {
    85  	sender := mocks.NewSender()
    86  	body := mocks.NewBody(`{"odata.error":{"code":"Request_MultipleObjectsWithSameKeyValue"}}`)
    87  	sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusConflict, ""))
    88  	return sender
    89  }
    90  
    91  func servicePrincipalListSender() autorest.Sender {
    92  	return azuretesting.NewSenderWithValue(ad.ServicePrincipalListResult{
    93  		Value: []ad.ServicePrincipal{{
    94  			ApplicationID: "cbb548f1-5039-4836-af0b-727e8571f6a9",
    95  			ObjectID:      "sp-object-id",
    96  		}},
    97  	})
    98  }
    99  
   100  func roleDefinitionListSender() autorest.Sender {
   101  	roleDefinitions := []authorization.RoleDefinition{{
   102  		ID:   to.StringPtr("owner-role-id"),
   103  		Name: to.StringPtr("Owner"),
   104  	}}
   105  	return azuretesting.NewSenderWithValue(authorization.RoleDefinitionListResult{
   106  		Value: &roleDefinitions,
   107  	})
   108  }
   109  
   110  func roleAssignmentSender() autorest.Sender {
   111  	return azuretesting.NewSenderWithValue(authorization.RoleAssignment{})
   112  }
   113  
   114  func roleAssignmentAlreadyExistsSender() autorest.Sender {
   115  	sender := mocks.NewSender()
   116  	body := mocks.NewBody(`{"error":{"code":"RoleAssignmentExists"}}`)
   117  	sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusConflict, ""))
   118  	return sender
   119  }
   120  
   121  func (s *InteractiveSuite) SetUpTest(c *gc.C) {
   122  	s.IsolationSuite.SetUpTest(c)
   123  	uuids := []string{
   124  		"33333333-3333-3333-3333-333333333333", // password
   125  		"44444444-4444-4444-4444-444444444444", // password key ID
   126  		"55555555-5555-5555-5555-555555555555", // role assignment ID
   127  	}
   128  	s.newUUID = func() (utils.UUID, error) {
   129  		uuid, err := utils.UUIDFromString(uuids[0])
   130  		if err != nil {
   131  			return utils.UUID{}, err
   132  		}
   133  		uuids = uuids[1:]
   134  		return uuid, nil
   135  	}
   136  	s.clock = testing.NewClock(clockStartTime())
   137  }
   138  
   139  func (s *InteractiveSuite) TestInteractive(c *gc.C) {
   140  
   141  	var requests []*http.Request
   142  	senders := azuretesting.Senders{
   143  		oauthConfigSender(),
   144  		deviceCodeSender(),
   145  		tokenSender(), // CheckForUserCompletion returns a token.
   146  
   147  		// Token.Refresh returns a token. We do this
   148  		// twice: once for ARM, and once for AAD.
   149  		tokenSender(),
   150  		tokenSender(),
   151  
   152  		currentUserSender(),
   153  		createServicePrincipalSender(),
   154  		roleDefinitionListSender(),
   155  		roleAssignmentSender(),
   156  	}
   157  
   158  	var stderr bytes.Buffer
   159  	subscriptionId := "22222222-2222-2222-2222-222222222222"
   160  	appId, password, err := azureauth.InteractiveCreateServicePrincipal(
   161  		&stderr,
   162  		&senders,
   163  		azuretesting.RequestRecorder(&requests),
   164  		"https://arm.invalid",
   165  		"https://graph.invalid",
   166  		subscriptionId,
   167  		s.clock,
   168  		s.newUUID,
   169  	)
   170  	c.Assert(err, jc.ErrorIsNil)
   171  	c.Assert(appId, gc.Equals, "cbb548f1-5039-4836-af0b-727e8571f6a9")
   172  	c.Assert(password, gc.Equals, "33333333-3333-3333-3333-333333333333")
   173  	c.Assert(stderr.String(), gc.Equals, `
   174  Initiating interactive authentication.
   175  
   176  open your browser, etc.
   177  
   178  Authenticated as "Foo Bar".
   179  Creating/updating service principal.
   180  Assigning Owner role to service principal.
   181  `[1:])
   182  
   183  	// Token refreshes don't go through the inspectors.
   184  	c.Assert(requests, gc.HasLen, 7)
   185  	c.Check(requests[0].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222")
   186  	c.Check(requests[1].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/devicecode")
   187  	c.Check(requests[2].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token")
   188  	c.Check(requests[3].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/me")
   189  	c.Check(requests[4].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals")
   190  	c.Check(requests[5].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleDefinitions")
   191  	c.Check(requests[6].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleAssignments/55555555-5555-5555-5555-555555555555")
   192  
   193  	// The service principal creation includes the password. Check that the
   194  	// password returned from the function is the same as the one set in the
   195  	// request.
   196  	var params ad.ServicePrincipalCreateParameters
   197  	err = json.NewDecoder(requests[4].Body).Decode(&params)
   198  	c.Assert(err, jc.ErrorIsNil)
   199  	c.Assert(params.PasswordCredentials, gc.HasLen, 1)
   200  	assertPasswordCredential(c, params.PasswordCredentials[0])
   201  }
   202  
   203  func assertPasswordCredential(c *gc.C, cred ad.PasswordCredential) {
   204  	startDate := cred.StartDate
   205  	endDate := cred.EndDate
   206  	c.Assert(startDate, gc.Equals, clockStartTime())
   207  	c.Assert(endDate.Sub(startDate), gc.Equals, 365*24*time.Hour)
   208  
   209  	cred.StartDate = time.Time{}
   210  	cred.EndDate = time.Time{}
   211  	c.Assert(cred, jc.DeepEquals, ad.PasswordCredential{
   212  		CustomKeyIdentifier: []byte("juju-20160919"),
   213  		KeyId:               "44444444-4444-4444-4444-444444444444",
   214  		Value:               "33333333-3333-3333-3333-333333333333",
   215  	})
   216  }
   217  
   218  func (s *InteractiveSuite) TestInteractiveRoleAssignmentAlreadyExists(c *gc.C) {
   219  	var requests []*http.Request
   220  	senders := azuretesting.Senders{
   221  		oauthConfigSender(),
   222  		deviceCodeSender(),
   223  		tokenSender(),
   224  		tokenSender(),
   225  		tokenSender(),
   226  		currentUserSender(),
   227  		createServicePrincipalSender(),
   228  		roleDefinitionListSender(),
   229  		roleAssignmentAlreadyExistsSender(),
   230  	}
   231  	_, _, err := azureauth.InteractiveCreateServicePrincipal(
   232  		ioutil.Discard,
   233  		&senders,
   234  		azuretesting.RequestRecorder(&requests),
   235  		"https://arm.invalid",
   236  		"https://graph.invalid",
   237  		"22222222-2222-2222-2222-222222222222",
   238  		s.clock,
   239  		s.newUUID,
   240  	)
   241  	c.Assert(err, jc.ErrorIsNil)
   242  }
   243  
   244  func (s *InteractiveSuite) TestInteractiveServicePrincipalAlreadyExists(c *gc.C) {
   245  	var requests []*http.Request
   246  	senders := azuretesting.Senders{
   247  		oauthConfigSender(),
   248  		deviceCodeSender(),
   249  		tokenSender(),
   250  		tokenSender(),
   251  		tokenSender(),
   252  		currentUserSender(),
   253  		createServicePrincipalAlreadyExistsSender(),
   254  		servicePrincipalListSender(),
   255  		passwordCredentialsListSender(),
   256  		updatePasswordCredentialsSender(),
   257  		roleDefinitionListSender(),
   258  		roleAssignmentAlreadyExistsSender(),
   259  	}
   260  	_, password, err := azureauth.InteractiveCreateServicePrincipal(
   261  		ioutil.Discard,
   262  		&senders,
   263  		azuretesting.RequestRecorder(&requests),
   264  		"https://arm.invalid",
   265  		"https://graph.invalid",
   266  		"22222222-2222-2222-2222-222222222222",
   267  		s.clock,
   268  		s.newUUID,
   269  	)
   270  	c.Assert(err, jc.ErrorIsNil)
   271  	c.Assert(password, gc.Equals, "33333333-3333-3333-3333-333333333333")
   272  
   273  	c.Assert(requests, gc.HasLen, 10)
   274  	c.Check(requests[0].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222")
   275  	c.Check(requests[1].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/devicecode")
   276  	c.Check(requests[2].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token")
   277  	c.Check(requests[3].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/me")
   278  	c.Check(requests[4].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals")                                  // create
   279  	c.Check(requests[5].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals")                                  // list
   280  	c.Check(requests[6].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals/sp-object-id/passwordCredentials") // list
   281  	c.Check(requests[7].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals/sp-object-id/passwordCredentials") // update
   282  	c.Check(requests[8].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleDefinitions")
   283  	c.Check(requests[9].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleAssignments/55555555-5555-5555-5555-555555555555")
   284  
   285  	// Make sure that we don't wipe existing password credentials, and that
   286  	// the new password credential matches the one returned from the
   287  	// function.
   288  	var params ad.PasswordCredentialsUpdateParameters
   289  	err = json.NewDecoder(requests[7].Body).Decode(&params)
   290  	c.Assert(err, jc.ErrorIsNil)
   291  	c.Assert(params.Value, gc.HasLen, 2)
   292  	c.Assert(params.Value[0], jc.DeepEquals, ad.PasswordCredential{
   293  		KeyId:     "password-credential-key-id",
   294  		StartDate: time.Time{}.UTC(),
   295  		EndDate:   time.Time{}.UTC(),
   296  	})
   297  	assertPasswordCredential(c, params.Value[1])
   298  }