github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/apiserver/registration_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package apiserver_test
     5  
     6  import (
     7  	"encoding/base64"
     8  	"encoding/json"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"net/http"
    12  	"strings"
    13  
    14  	jc "github.com/juju/testing/checkers"
    15  	"github.com/juju/testing/httptesting"
    16  	"github.com/juju/utils"
    17  	"golang.org/x/crypto/nacl/secretbox"
    18  	gc "gopkg.in/check.v1"
    19  
    20  	"github.com/juju/juju/apiserver/params"
    21  	"github.com/juju/juju/state"
    22  )
    23  
    24  type registrationSuite struct {
    25  	authHTTPSuite
    26  	bob *state.User
    27  }
    28  
    29  var _ = gc.Suite(&registrationSuite{})
    30  
    31  func (s *registrationSuite) SetUpTest(c *gc.C) {
    32  	s.authHTTPSuite.SetUpTest(c)
    33  	bob, err := s.BackingState.AddUserWithSecretKey("bob", "", "admin")
    34  	c.Assert(err, jc.ErrorIsNil)
    35  	s.bob = bob
    36  }
    37  
    38  func (s *registrationSuite) assertErrorResponse(c *gc.C, resp *http.Response, expCode int, expError string) {
    39  	body := assertResponse(c, resp, expCode, params.ContentTypeJSON)
    40  	var result params.ErrorResult
    41  	s.unmarshal(c, body, &result)
    42  	c.Assert(result.Error, gc.NotNil)
    43  	c.Assert(result.Error, gc.Matches, expError)
    44  }
    45  
    46  func (s *registrationSuite) assertResponse(c *gc.C, resp *http.Response) params.SecretKeyLoginResponse {
    47  	body := assertResponse(c, resp, http.StatusOK, params.ContentTypeJSON)
    48  	var response params.SecretKeyLoginResponse
    49  	s.unmarshal(c, body, &response)
    50  	return response
    51  }
    52  
    53  func (*registrationSuite) unmarshal(c *gc.C, body []byte, out interface{}) {
    54  	err := json.Unmarshal(body, out)
    55  	c.Assert(err, jc.ErrorIsNil, gc.Commentf("body: %s", body))
    56  }
    57  
    58  func (s *registrationSuite) registrationURL(c *gc.C) string {
    59  	url := s.baseURL(c)
    60  	url.Path = "/register"
    61  	return url.String()
    62  }
    63  
    64  func (s *registrationSuite) TestRegister(c *gc.C) {
    65  	// Ensure we cannot log in with the password yet.
    66  	const password = "hunter2"
    67  	c.Assert(s.bob.PasswordValid(password), jc.IsFalse)
    68  
    69  	validNonce := []byte(strings.Repeat("X", 24))
    70  	secretKey := s.bob.SecretKey()
    71  	ciphertext := s.sealBox(
    72  		c, validNonce, secretKey, fmt.Sprintf(`{"password": "%s"}`, password),
    73  	)
    74  	resp := httptesting.Do(c, httptesting.DoRequestParams{
    75  		Do:     utils.GetNonValidatingHTTPClient().Do,
    76  		URL:    s.registrationURL(c),
    77  		Method: "POST",
    78  		JSONBody: &params.SecretKeyLoginRequest{
    79  			User:              "user-bob",
    80  			Nonce:             validNonce,
    81  			PayloadCiphertext: ciphertext,
    82  		},
    83  	})
    84  	c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)
    85  	defer resp.Body.Close()
    86  
    87  	// It should be possible to log in as bob with the
    88  	// password "hunter2" now, and there should be no
    89  	// secret key any longer.
    90  	err := s.bob.Refresh()
    91  	c.Assert(err, jc.ErrorIsNil)
    92  	c.Assert(s.bob.PasswordValid(password), jc.IsTrue)
    93  	c.Assert(s.bob.SecretKey(), gc.IsNil)
    94  
    95  	var response params.SecretKeyLoginResponse
    96  	bodyData, err := ioutil.ReadAll(resp.Body)
    97  	c.Assert(err, jc.ErrorIsNil)
    98  	err = json.Unmarshal(bodyData, &response)
    99  	c.Assert(err, jc.ErrorIsNil)
   100  	c.Assert(response.Nonce, gc.HasLen, len(validNonce))
   101  	plaintext := s.openBox(c, response.PayloadCiphertext, response.Nonce, secretKey)
   102  
   103  	var responsePayload params.SecretKeyLoginResponsePayload
   104  	err = json.Unmarshal(plaintext, &responsePayload)
   105  	c.Assert(err, jc.ErrorIsNil)
   106  	c.Assert(responsePayload.CACert, gc.Equals, s.BackingState.CACert())
   107  	model, err := s.BackingState.Model()
   108  	c.Assert(err, jc.ErrorIsNil)
   109  	c.Assert(responsePayload.ControllerUUID, gc.Equals, model.ControllerUUID())
   110  }
   111  
   112  func (s *registrationSuite) TestRegisterInvalidMethod(c *gc.C) {
   113  	httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
   114  		Do:           utils.GetNonValidatingHTTPClient().Do,
   115  		URL:          s.registrationURL(c),
   116  		Method:       "GET",
   117  		ExpectStatus: http.StatusMethodNotAllowed,
   118  		ExpectBody: &params.ErrorResult{
   119  			Error: &params.Error{
   120  				Message: `unsupported method: "GET"`,
   121  				Code:    params.CodeMethodNotAllowed,
   122  			},
   123  		},
   124  	})
   125  }
   126  
   127  func (s *registrationSuite) TestRegisterInvalidFormat(c *gc.C) {
   128  	s.testInvalidRequest(
   129  		c, "[]", "json: cannot unmarshal array into Go value of type params.SecretKeyLoginRequest", "",
   130  		http.StatusInternalServerError,
   131  	)
   132  }
   133  
   134  func (s *registrationSuite) TestRegisterInvalidUserTag(c *gc.C) {
   135  	s.testInvalidRequest(
   136  		c, `{"user": "application-bob"}`, `"application-bob" is not a valid user tag`, "",
   137  		http.StatusInternalServerError,
   138  	)
   139  }
   140  
   141  func (s *registrationSuite) TestRegisterInvalidNonce(c *gc.C) {
   142  	s.testInvalidRequest(
   143  		c, `{"user": "user-bob", "nonce": ""}`, `nonce not valid`, "",
   144  		http.StatusInternalServerError,
   145  	)
   146  }
   147  
   148  func (s *registrationSuite) TestRegisterInvalidCiphertext(c *gc.C) {
   149  	validNonce := []byte(strings.Repeat("X", 24))
   150  	s.testInvalidRequest(c,
   151  		fmt.Sprintf(
   152  			`{"user": "user-bob", "nonce": "%s"}`,
   153  			base64.StdEncoding.EncodeToString(validNonce),
   154  		), `secret key not valid`, "",
   155  		http.StatusInternalServerError,
   156  	)
   157  }
   158  
   159  func (s *registrationSuite) TestRegisterNoSecretKey(c *gc.C) {
   160  	err := s.bob.SetPassword("anything")
   161  	c.Assert(err, jc.ErrorIsNil)
   162  	validNonce := []byte(strings.Repeat("X", 24))
   163  	s.testInvalidRequest(c,
   164  		fmt.Sprintf(
   165  			`{"user": "user-bob", "nonce": "%s"}`,
   166  			base64.StdEncoding.EncodeToString(validNonce),
   167  		), `secret key for user "bob" not found`, params.CodeNotFound,
   168  		http.StatusNotFound,
   169  	)
   170  }
   171  
   172  func (s *registrationSuite) TestRegisterInvalidRequestPayload(c *gc.C) {
   173  	validNonce := []byte(strings.Repeat("X", 24))
   174  	ciphertext := s.sealBox(c, validNonce, s.bob.SecretKey(), "[]")
   175  	s.testInvalidRequest(c,
   176  		fmt.Sprintf(
   177  			`{"user": "user-bob", "nonce": "%s", "cipher-text": "%s"}`,
   178  			base64.StdEncoding.EncodeToString(validNonce),
   179  			base64.StdEncoding.EncodeToString(ciphertext),
   180  		),
   181  		`cannot unmarshal payload: json: cannot unmarshal array into Go value of type params.SecretKeyLoginRequestPayload`, "",
   182  		http.StatusInternalServerError,
   183  	)
   184  }
   185  
   186  func (s *registrationSuite) testInvalidRequest(c *gc.C, requestBody, errorMessage, errorCode string, statusCode int) {
   187  	httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
   188  		Do:           utils.GetNonValidatingHTTPClient().Do,
   189  		URL:          s.registrationURL(c),
   190  		Method:       "POST",
   191  		Body:         strings.NewReader(requestBody),
   192  		ExpectStatus: statusCode,
   193  		ExpectBody: &params.ErrorResult{
   194  			Error: &params.Error{Message: errorMessage, Code: errorCode},
   195  		},
   196  	})
   197  }
   198  
   199  func (s *registrationSuite) sealBox(c *gc.C, nonce, key []byte, message string) []byte {
   200  	var nonceArray [24]byte
   201  	var keyArray [32]byte
   202  	c.Assert(copy(nonceArray[:], nonce), gc.Equals, len(nonceArray))
   203  	c.Assert(copy(keyArray[:], key), gc.Equals, len(keyArray))
   204  	return secretbox.Seal(nil, []byte(message), &nonceArray, &keyArray)
   205  }
   206  
   207  func (s *registrationSuite) openBox(c *gc.C, ciphertext, nonce, key []byte) []byte {
   208  	var nonceArray [24]byte
   209  	var keyArray [32]byte
   210  	c.Assert(copy(nonceArray[:], nonce), gc.Equals, len(nonceArray))
   211  	c.Assert(copy(keyArray[:], key), gc.Equals, len(keyArray))
   212  	message, ok := secretbox.Open(nil, ciphertext, &nonceArray, &keyArray)
   213  	c.Assert(ok, jc.IsTrue)
   214  	return message
   215  }