github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/juju/controller/register_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package controller_test
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/asn1"
     9  	"encoding/base64"
    10  	"encoding/json"
    11  	"io"
    12  	"io/ioutil"
    13  	"net/http"
    14  	"net/http/httptest"
    15  	"net/url"
    16  	"strings"
    17  
    18  	"github.com/juju/cmd"
    19  	"github.com/juju/errors"
    20  	jc "github.com/juju/testing/checkers"
    21  	"golang.org/x/crypto/nacl/secretbox"
    22  	gc "gopkg.in/check.v1"
    23  	"gopkg.in/macaroon.v1"
    24  
    25  	"github.com/juju/juju/api"
    26  	"github.com/juju/juju/apiserver/params"
    27  	"github.com/juju/juju/cmd/juju/controller"
    28  	"github.com/juju/juju/jujuclient"
    29  	"github.com/juju/juju/jujuclient/jujuclienttesting"
    30  	"github.com/juju/juju/testing"
    31  )
    32  
    33  type RegisterSuite struct {
    34  	testing.FakeJujuXDGDataHomeSuite
    35  	apiConnection               *mockAPIConnection
    36  	store                       *jujuclienttesting.MemStore
    37  	apiOpenError                error
    38  	refreshModels               func(jujuclient.ClientStore, string, string) error
    39  	refreshModelsControllerName string
    40  	refreshModelsAccountName    string
    41  	server                      *httptest.Server
    42  	httpHandler                 http.Handler
    43  }
    44  
    45  var _ = gc.Suite(&RegisterSuite{})
    46  
    47  func (s *RegisterSuite) SetUpTest(c *gc.C) {
    48  	s.FakeJujuXDGDataHomeSuite.SetUpTest(c)
    49  
    50  	s.apiOpenError = nil
    51  	s.httpHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
    52  	s.server = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    53  		s.httpHandler.ServeHTTP(w, r)
    54  	}))
    55  
    56  	serverURL, err := url.Parse(s.server.URL)
    57  	c.Assert(err, jc.ErrorIsNil)
    58  	s.apiConnection = &mockAPIConnection{
    59  		controllerTag: testing.ModelTag,
    60  		addr:          serverURL.Host,
    61  	}
    62  	s.refreshModelsControllerName = ""
    63  	s.refreshModelsAccountName = ""
    64  	s.refreshModels = func(store jujuclient.ClientStore, controllerName, accountName string) error {
    65  		s.refreshModelsControllerName = controllerName
    66  		s.refreshModelsAccountName = accountName
    67  		return nil
    68  	}
    69  
    70  	s.store = jujuclienttesting.NewMemStore()
    71  }
    72  
    73  func (s *RegisterSuite) TearDownTest(c *gc.C) {
    74  	s.server.Close()
    75  	s.FakeJujuXDGDataHomeSuite.TearDownTest(c)
    76  }
    77  
    78  func (s *RegisterSuite) apiOpen(info *api.Info, opts api.DialOpts) (api.Connection, error) {
    79  	if s.apiOpenError != nil {
    80  		return nil, s.apiOpenError
    81  	}
    82  	s.apiConnection.info = info
    83  	s.apiConnection.opts = opts
    84  	return s.apiConnection, nil
    85  }
    86  
    87  func (s *RegisterSuite) run(c *gc.C, stdin io.Reader, args ...string) (*cmd.Context, error) {
    88  	command := controller.NewRegisterCommandForTest(s.apiOpen, s.refreshModels, s.store)
    89  	err := testing.InitCommand(command, args)
    90  	c.Assert(err, jc.ErrorIsNil)
    91  	ctx := testing.Context(c)
    92  	ctx.Stdin = stdin
    93  	return ctx, command.Run(ctx)
    94  }
    95  
    96  func (s *RegisterSuite) encodeRegistrationData(c *gc.C, user string, secretKey []byte) string {
    97  	data, err := asn1.Marshal(jujuclient.RegistrationInfo{
    98  		User:      user,
    99  		Addrs:     []string{s.apiConnection.addr},
   100  		SecretKey: secretKey,
   101  	})
   102  	c.Assert(err, jc.ErrorIsNil)
   103  	// Append some junk to the end of the encoded data to
   104  	// ensure that, if we have to pad the data in add-user,
   105  	// register can still decode it.
   106  	data = append(data, 0, 0, 0)
   107  	return base64.URLEncoding.EncodeToString(data)
   108  }
   109  
   110  func (s *RegisterSuite) seal(c *gc.C, message, key, nonce []byte) []byte {
   111  	var keyArray [32]byte
   112  	var nonceArray [24]byte
   113  	c.Assert(copy(keyArray[:], key), gc.Equals, len(keyArray))
   114  	c.Assert(copy(nonceArray[:], nonce), gc.Equals, len(nonceArray))
   115  	return secretbox.Seal(nil, message, &nonceArray, &keyArray)
   116  }
   117  
   118  func (s *RegisterSuite) TestInit(c *gc.C) {
   119  	registerCommand := controller.NewRegisterCommandForTest(nil, nil, nil)
   120  
   121  	err := testing.InitCommand(registerCommand, []string{})
   122  	c.Assert(err, gc.ErrorMatches, "registration data missing")
   123  
   124  	err = testing.InitCommand(registerCommand, []string{"foo"})
   125  	c.Assert(err, jc.ErrorIsNil)
   126  	c.Assert(registerCommand.EncodedData, gc.Equals, "foo")
   127  
   128  	err = testing.InitCommand(registerCommand, []string{"foo", "bar"})
   129  	c.Assert(err, gc.ErrorMatches, `unrecognized args: \["bar"\]`)
   130  }
   131  
   132  func (s *RegisterSuite) TestRegister(c *gc.C) {
   133  	ctx := s.testRegister(c)
   134  	c.Assert(s.refreshModelsControllerName, gc.Equals, "controller-name")
   135  	c.Assert(s.refreshModelsAccountName, gc.Equals, "bob@local")
   136  	stderr := testing.Stderr(ctx)
   137  	c.Assert(stderr, gc.Equals, `
   138  Please set a name for this controller: 
   139  Enter password: 
   140  Confirm password: 
   141  
   142  Welcome, bob. You are now logged into "controller-name".
   143  
   144  There are no models available. You can create models with
   145  "juju create-model", or you can ask an administrator or owner
   146  of a model to grant access to that model with "juju grant".
   147  
   148  `[1:])
   149  }
   150  
   151  func (s *RegisterSuite) TestRegisterOneModel(c *gc.C) {
   152  	s.refreshModels = func(store jujuclient.ClientStore, controller, account string) error {
   153  		err := store.UpdateModel(controller, account, "theoneandonly", jujuclient.ModelDetails{
   154  			ModelUUID: "df136476-12e9-11e4-8a70-b2227cce2b54",
   155  		})
   156  		c.Assert(err, jc.ErrorIsNil)
   157  		return nil
   158  	}
   159  	s.testRegister(c)
   160  	c.Assert(
   161  		s.store.Models["controller-name"].AccountModels["bob@local"].CurrentModel,
   162  		gc.Equals, "theoneandonly",
   163  	)
   164  }
   165  
   166  func (s *RegisterSuite) TestRegisterMultipleModels(c *gc.C) {
   167  	s.refreshModels = func(store jujuclient.ClientStore, controller, account string) error {
   168  		for _, name := range [...]string{"model1", "model2"} {
   169  			err := store.UpdateModel(controller, account, name, jujuclient.ModelDetails{
   170  				ModelUUID: "df136476-12e9-11e4-8a70-b2227cce2b54",
   171  			})
   172  			c.Assert(err, jc.ErrorIsNil)
   173  		}
   174  		return nil
   175  	}
   176  	ctx := s.testRegister(c)
   177  
   178  	// When there are multiple models, no current model will be set.
   179  	// Instead, the command will output the list of models and inform
   180  	// the user how to set the current model.
   181  	_, err := s.store.CurrentModel("controller-name", "bob@local")
   182  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   183  
   184  	stderr := testing.Stderr(ctx)
   185  	c.Assert(stderr, gc.Equals, `
   186  Please set a name for this controller: 
   187  Enter password: 
   188  Confirm password: 
   189  
   190  Welcome, bob. You are now logged into "controller-name".
   191  
   192  There are 2 models available. Use "juju switch" to select
   193  one of them:
   194    - juju switch model1
   195    - juju switch model2
   196  
   197  `[1:])
   198  }
   199  
   200  func (s *RegisterSuite) testRegister(c *gc.C) *cmd.Context {
   201  	secretKey := []byte(strings.Repeat("X", 32))
   202  	respNonce := []byte(strings.Repeat("X", 24))
   203  
   204  	macaroon, err := macaroon.New(nil, "mymacaroon", "tone")
   205  	c.Assert(err, jc.ErrorIsNil)
   206  	macaroonJSON, err := macaroon.MarshalJSON()
   207  	c.Assert(err, jc.ErrorIsNil)
   208  
   209  	var requests []*http.Request
   210  	var requestBodies [][]byte
   211  	const controllerUUID = "df136476-12e9-11e4-8a70-b2227cce2b54"
   212  	responsePayloadPlaintext, err := json.Marshal(params.SecretKeyLoginResponsePayload{
   213  		CACert:         testing.CACert,
   214  		ControllerUUID: controllerUUID,
   215  		Macaroon:       macaroon,
   216  	})
   217  	c.Assert(err, jc.ErrorIsNil)
   218  	response, err := json.Marshal(params.SecretKeyLoginResponse{
   219  		Nonce:             respNonce,
   220  		PayloadCiphertext: s.seal(c, responsePayloadPlaintext, secretKey, respNonce),
   221  	})
   222  	c.Assert(err, jc.ErrorIsNil)
   223  	s.httpHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   224  		requests = append(requests, r)
   225  		requestBody, err := ioutil.ReadAll(requests[0].Body)
   226  		c.Check(err, jc.ErrorIsNil)
   227  		requestBodies = append(requestBodies, requestBody)
   228  		_, err = w.Write(response)
   229  		c.Check(err, jc.ErrorIsNil)
   230  	})
   231  
   232  	registrationData := s.encodeRegistrationData(c, "bob", secretKey)
   233  	stdin := strings.NewReader("controller-name\nhunter2\nhunter2\n")
   234  	ctx, err := s.run(c, stdin, registrationData)
   235  	c.Assert(err, jc.ErrorIsNil)
   236  
   237  	// There should have been one POST command to "/register".
   238  	c.Assert(requests, gc.HasLen, 1)
   239  	c.Assert(requests[0].Method, gc.Equals, "POST")
   240  	c.Assert(requests[0].URL.Path, gc.Equals, "/register")
   241  	var request params.SecretKeyLoginRequest
   242  	err = json.Unmarshal(requestBodies[0], &request)
   243  	c.Assert(err, jc.ErrorIsNil)
   244  	c.Assert(request.User, jc.DeepEquals, "user-bob")
   245  	c.Assert(request.Nonce, gc.HasLen, 24)
   246  	requestPayloadPlaintext, err := json.Marshal(params.SecretKeyLoginRequestPayload{
   247  		"hunter2",
   248  	})
   249  	c.Assert(err, jc.ErrorIsNil)
   250  	expectedCiphertext := s.seal(c, requestPayloadPlaintext, secretKey, request.Nonce)
   251  	c.Assert(request.PayloadCiphertext, jc.DeepEquals, expectedCiphertext)
   252  
   253  	// The controller and account details should be recorded with
   254  	// the specified controller name ("controller-name") and user
   255  	// name from the registration string.
   256  
   257  	controller, err := s.store.ControllerByName("controller-name")
   258  	c.Assert(err, jc.ErrorIsNil)
   259  	c.Assert(controller, jc.DeepEquals, &jujuclient.ControllerDetails{
   260  		ControllerUUID: controllerUUID,
   261  		APIEndpoints:   []string{s.apiConnection.addr},
   262  		CACert:         testing.CACert,
   263  	})
   264  	account, err := s.store.AccountByName("controller-name", "bob@local")
   265  	c.Assert(err, jc.ErrorIsNil)
   266  	c.Assert(account, jc.DeepEquals, &jujuclient.AccountDetails{
   267  		User:     "bob@local",
   268  		Macaroon: string(macaroonJSON),
   269  	})
   270  	return ctx
   271  }
   272  
   273  func (s *RegisterSuite) TestRegisterInvalidRegistrationData(c *gc.C) {
   274  	_, err := s.run(c, bytes.NewReader(nil), "not base64")
   275  	c.Assert(err, gc.ErrorMatches, "illegal base64 data at input byte 3")
   276  
   277  	_, err = s.run(c, bytes.NewReader(nil), "YXNuLjEK")
   278  	c.Assert(err, gc.ErrorMatches, "asn1: structure error: .*")
   279  }
   280  
   281  func (s *RegisterSuite) TestRegisterEmptyControllerName(c *gc.C) {
   282  	secretKey := []byte(strings.Repeat("X", 32))
   283  	registrationData := s.encodeRegistrationData(c, "bob", secretKey)
   284  	stdin := strings.NewReader("\n")
   285  	_, err := s.run(c, stdin, registrationData)
   286  	c.Assert(err, gc.ErrorMatches, "you must specify a non-empty controller name")
   287  }
   288  
   289  func (s *RegisterSuite) TestRegisterControllerNameExists(c *gc.C) {
   290  	err := s.store.UpdateController("controller-name", jujuclient.ControllerDetails{
   291  		ControllerUUID: "df136476-12e9-11e4-8a70-b2227cce2b54",
   292  		CACert:         testing.CACert,
   293  	})
   294  
   295  	secretKey := []byte(strings.Repeat("X", 32))
   296  	registrationData := s.encodeRegistrationData(c, "bob", secretKey)
   297  	stdin := strings.NewReader("controller-name\nhunter2\nhunter2\n")
   298  	_, err = s.run(c, stdin, registrationData)
   299  	c.Assert(err, gc.ErrorMatches, `controller "controller-name" already exists`)
   300  }
   301  
   302  func (s *RegisterSuite) TestRegisterEmptyPassword(c *gc.C) {
   303  	secretKey := []byte(strings.Repeat("X", 32))
   304  	registrationData := s.encodeRegistrationData(c, "bob", secretKey)
   305  	stdin := strings.NewReader("controller-name\n\n")
   306  	_, err := s.run(c, stdin, registrationData)
   307  	c.Assert(err, gc.ErrorMatches, "you must specify a non-empty password")
   308  }
   309  
   310  func (s *RegisterSuite) TestRegisterPasswordMismatch(c *gc.C) {
   311  	secretKey := []byte(strings.Repeat("X", 32))
   312  	registrationData := s.encodeRegistrationData(c, "bob", secretKey)
   313  	stdin := strings.NewReader("controller-name\nhunter2\nhunter3\n")
   314  	_, err := s.run(c, stdin, registrationData)
   315  	c.Assert(err, gc.ErrorMatches, "passwords do not match")
   316  }
   317  
   318  func (s *RegisterSuite) TestAPIOpenError(c *gc.C) {
   319  	secretKey := []byte(strings.Repeat("X", 32))
   320  	registrationData := s.encodeRegistrationData(c, "bob", secretKey)
   321  	stdin := strings.NewReader("controller-name\nhunter2\nhunter2\n")
   322  	s.apiOpenError = errors.New("open failed")
   323  	_, err := s.run(c, stdin, registrationData)
   324  	c.Assert(err, gc.ErrorMatches, `open failed`)
   325  }
   326  
   327  func (s *RegisterSuite) TestRegisterServerError(c *gc.C) {
   328  	secretKey := []byte(strings.Repeat("X", 32))
   329  	response, err := json.Marshal(params.ErrorResult{
   330  		Error: &params.Error{Message: "xyz", Code: "123"},
   331  	})
   332  
   333  	s.httpHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   334  		w.WriteHeader(http.StatusInternalServerError)
   335  		_, err = w.Write(response)
   336  		c.Check(err, jc.ErrorIsNil)
   337  	})
   338  
   339  	registrationData := s.encodeRegistrationData(c, "bob", secretKey)
   340  	stdin := strings.NewReader("controller-name\nhunter2\nhunter2\n")
   341  	_, err = s.run(c, stdin, registrationData)
   342  	c.Assert(err, gc.ErrorMatches, "xyz")
   343  
   344  	_, err = s.store.ControllerByName("controller-name")
   345  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   346  }