Example #1
0
/*
CreateDocument - Creates a fresh Binder for a new document, which is subsequently stored, returns an
error if either the document ID is already currently in use, or if there is a problem storing the
new document. May require authentication, if so a userID is supplied.
*/
func (c *Curator) CreateDocument(token string, userID string, doc store.Document) (BinderPortal, error) {
	c.log.Debugf("Creating new document with token %v\n", token)

	if !c.authenticator.AuthoriseCreate(token, userID) {
		c.stats.Incr("curator.create.rejected_client", 1)
		return BinderPortal{}, fmt.Errorf("failed to gain permission to create with token: %v\n", token)
	}
	c.stats.Incr("curator.create.accepted_client", 1)

	// Always generate a fresh ID
	doc.ID = util.GenerateStampedUUID()

	if err := c.store.Create(doc.ID, doc); err != nil {
		c.stats.Incr("curator.create_new.failed", 1)
		c.log.Errorf("Failed to create new document: %v\n", err)
		return BinderPortal{}, err
	}
	binder, err := NewBinder(doc.ID, c.store, c.config.BinderConfig, c.errorChan, c.log, c.stats)
	if err != nil {
		c.stats.Incr("curator.bind_new.failed", 1)
		c.log.Errorf("Failed to bind to new document: %v\n", err)
		return BinderPortal{}, err
	}
	c.binderMutex.Lock()
	c.openBinders[doc.ID] = binder
	c.binderMutex.Unlock()
	c.stats.Incr("curator.open_binders", 1)

	return binder.Subscribe(token), nil
}
Example #2
0
/*
CreateDocument - Creates a fresh Binder for a new document, which is subsequently stored, returns an
error if either the document ID is already currently in use, or if there is a problem storing the
new document. May require authentication, if so a userID is supplied.
*/
func (c *impl) CreateDocument(
	userID, token string, doc store.Document, timeout time.Duration,
) (binder.Portal, error) {
	c.log.Debugf("Creating new document with userID %v token %v\n", userID, token)

	if c.auth.Authenticate(userID, token, "") < acl.CreateAccess {
		c.stats.Incr("curator.create.rejected_client", 1)
		return nil, fmt.Errorf("failed to gain permission to create with token: %v\n", token)
	}
	c.stats.Incr("curator.create.accepted_client", 1)

	// Always generate a fresh ID
	doc.ID = util.GenerateStampedUUID()

	if err := c.store.Create(doc); err != nil {
		c.stats.Incr("curator.create_new.failed", 1)
		c.log.Errorf("Failed to create new document: %v\n", err)
		return nil, err
	}
	openBinder, err := binder.New(
		doc.ID, c.store, c.config.BinderConfig, c.errorChan, c.log, c.stats,
	)
	if err != nil {
		c.stats.Incr("curator.bind_new.failed", 1)
		c.log.Errorf("Failed to bind to new document: %v\n", err)
		return nil, err
	}
	c.binderMutex.Lock()
	c.openBinders[doc.ID] = openBinder
	c.binderMutex.Unlock()
	c.stats.Incr("curator.open_binders", 1)

	return openBinder.Subscribe(userID, timeout)
}
Example #3
0
/*
NewDocument - Create a fresh leap document with a title, description, type and the initial content.
*/
func NewDocument(content string) (*Document, error) {
	doc := &Document{
		ID:      util.GenerateStampedUUID(),
		Content: content,
	}
	return doc, nil
}
Example #4
0
func (h *HTTP) createGenerateTokenHandler(tokens tokensMap) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			http.Error(w, "POST endpoint only", http.StatusMethodNotAllowed)
			return
		}

		bytes, err := ioutil.ReadAll(r.Body)
		if err != nil {
			h.logger.Errorf("Failed to read request body: %v\n", err)
			http.Error(w, "Bad request: could not read body", http.StatusBadRequest)
			return
		}

		var bodyObj struct {
			Key string `json:"key_value"`
		}
		if err = json.Unmarshal(bytes, &bodyObj); err != nil {
			h.logger.Errorf("Failed to parse request body: %v\n", err)
			http.Error(w, "Bad request: could not parse body", http.StatusBadRequest)
			return
		}

		if 0 == len(bodyObj.Key) {
			h.logger.Errorln("User ID not found in request body")
			http.Error(w, "Bad request: no user id found", http.StatusBadRequest)
			return
		}

		token := util.GenerateStampedUUID()

		h.mutex.Lock()

		tokens[token] = tokenMapValue{
			value:   bodyObj.Key,
			expires: time.Now().Add(time.Second * time.Duration(h.config.HTTPConfig.ExpiryPeriod)),
		}
		h.mutex.Unlock()

		resBytes, err := json.Marshal(struct {
			Token string `json:"token"`
		}{
			Token: token,
		})
		if err != nil {
			h.logger.Errorf("Failed to generate JSON response: %v\n", err)
			http.Error(w, "Failed to generate response", http.StatusInternalServerError)
			return
		}

		w.Write(resBytes)
		w.Header().Add("Content-Type", "application/json")

		h.clearExpiredTokens(tokens)
	}
}
Example #5
0
/*
Subscribe - Returns a BinderPortal, which represents a contract between a client and the binder. If
the subscription was unsuccessful the BinderPortal will contain an error.
*/
func (b *Binder) Subscribe(token string) BinderPortal {
	if len(token) == 0 {
		token = util.GenerateStampedUUID()
	}
	retChan := make(chan BinderPortal, 1)
	bundle := BinderSubscribeBundle{
		PortalRcvChan: retChan,
		Token:         token,
	}
	b.subscribeChan <- bundle

	return <-retChan
}
Example #6
0
/*
SubscribeReadOnly - Returns a BinderPortal, which represents a contract between a client and the
binder. If the subscription was unsuccessful the BinderPortal will contain an error. This is a read
only version of a BinderPortal and means transforms will be received but cannot be submitted.
*/
func (b *Binder) SubscribeReadOnly(token string) BinderPortal {
	if len(token) == 0 {
		token = util.GenerateStampedUUID()
	}
	retChan := make(chan BinderPortal, 1)
	bundle := BinderSubscribeBundle{
		PortalRcvChan: retChan,
		Token:         token,
	}
	b.subscribeChan <- bundle

	portal := <-retChan
	portal.TransformSndChan = nil

	return portal
}
Example #7
0
/*
processSubscriber - Processes a prospective client wishing to subscribe to this binder. This
involves flushing the OTBuffer in order to obtain a clean version of the document, if this fails
we return false to flag the binder loop that we should shut down.
*/
func (b *impl) processSubscriber(request subscribeRequest) error {
	transformSndChan := make(chan text.OTransform, 1)
	updateSndChan := make(chan ClientUpdate, 1)

	var err error
	var doc store.Document

	// We need to read the full document here anyway, so might as well flush.
	doc, err = b.flush()
	if err != nil {
		select {
		case request.errChan <- err:
		default:
		}
		return err
	}

	client := binderClient{
		userID:        request.userID,
		sessionID:     util.GenerateStampedUUID(),
		transformChan: transformSndChan,
		updateChan:    updateSndChan,
	}
	portal := portalImpl{
		client:           &client,
		version:          b.otBuffer.GetVersion(),
		document:         doc,
		transformRcvChan: transformSndChan,
		updateRcvChan:    updateSndChan,
		transformSndChan: b.transformChan,
		messageSndChan:   b.messageChan,
		exitChan:         b.exitChan,
	}
	select {
	case request.portalChan <- &portal:
		b.stats.Incr("binder.subscribed_clients", 1)
		b.log.Debugf("Subscribed new client %v\n", request.userID)
		b.clients = append(b.clients, &client)
	case <-time.After(time.Duration(b.config.ClientKickPeriod) * time.Millisecond):
		/* We're not bothered if you suck, you just don't get enrolled, and this isn't
		 * considered an error. Deal with it.
		 */
		b.stats.Incr("binder.rejected_client", 1)
		b.log.Infof("Rejected client request %v\n", request.userID)
	}
	return nil
}
Example #8
0
func TestUpdatesSameUserID(t *testing.T) {
	errChan := make(chan Error)
	doc, _ := store.NewDocument("hello world")
	logger, stats := loggerAndStats()

	binder, err := New(
		doc.ID,
		&testStore{documents: map[string]store.Document{doc.ID: *doc}},
		NewConfig(),
		errChan,
		logger,
		stats,
	)
	if err != nil {
		t.Errorf("error: %v", err)
		return
	}

	go func() {
		for e := range errChan {
			t.Errorf("From error channel: %v", e.Err)
		}
	}()

	userID := util.GenerateStampedUUID()

	portal1, _ := binder.Subscribe(userID, time.Second)
	portal2, _ := binder.Subscribe(userID, time.Second)

	if userID != portal1.UserID() {
		t.Errorf("Binder portal wrong user ID: %v != %v", userID, portal1.UserID())
	}
	if userID != portal2.UserID() {
		t.Errorf("Binder portal wrong user ID: %v != %v", userID, portal2.UserID())
	}

	for i := 0; i < 100; i++ {
		portal1.SendMessage(Message{})

		message := <-portal2.UpdateReadChan()
		if message.ClientInfo.UserID != userID {
			t.Errorf(
				"Received incorrect user ID: %v != %v",
				message.ClientInfo.UserID, portal1.UserID(),
			)
		}
		if message.ClientInfo.SessionID != portal1.SessionID() {
			t.Errorf(
				"Received incorrect session ID: %v != %v",
				message.ClientInfo.SessionID, portal1.SessionID(),
			)
		}

		portal2.SendMessage(Message{})

		message2 := <-portal1.UpdateReadChan()
		if message2.ClientInfo.UserID != userID {
			t.Errorf(
				"Received incorrect token: %v != %v",
				message2.ClientInfo.UserID, portal2.UserID(),
			)
		}
		if message2.ClientInfo.SessionID != portal2.SessionID() {
			t.Errorf(
				"Received incorrect session ID: %v != %v",
				message2.ClientInfo.SessionID, portal2.SessionID(),
			)
		}
	}
}
Example #9
0
func TestClientAdminTasks(t *testing.T) {
	errChan := make(chan Error, 10)

	logger, stats := loggerAndStats()
	doc, _ := store.NewDocument("hello world")

	store := testStore{documents: map[string]store.Document{
		"KILL_ME": *doc,
	}}

	binder, err := New("KILL_ME", &store, NewConfig(), errChan, logger, stats)
	if err != nil {
		t.Errorf("Error: %v", err)
		return
	}

	nClients := 10

	portals := make([]Portal, nClients)
	clientIDs := make([]string, nClients)

	for i := 0; i < nClients; i++ {
		clientIDs[i] = util.GenerateStampedUUID()
		var err error
		portals[i], err = binder.Subscribe(clientIDs[i], time.Second)
		if err != nil {
			t.Errorf("Subscribe error: %v\n", err)
			return
		}
	}

	for i := 0; i < nClients; i++ {
		remainingClients, err := binder.GetUsers(time.Second)
		if err != nil {
			t.Errorf("Get users error: %v\n", err)
			return
		}
		if len(remainingClients) != len(clientIDs) {
			t.Errorf("Wrong number of remaining clients: %v != %v\n", len(remainingClients), len(clientIDs))
			return
		}
		for _, val := range clientIDs {
			found := false
			for _, c := range remainingClients {
				if val == c {
					found = true
					break
				}
			}
			if !found {
				t.Errorf("Client not found in binder: %v\n", val)
				return
			}
		}

		killID := clientIDs[0]
		clientIDs = clientIDs[1:]

		if err := binder.KickUser(killID, time.Second); err != nil {
			t.Errorf("Kick user error: %v\n", err)
			return
		}
	}

	binder.Close()
}
Example #10
0
func TestBadKeys(t *testing.T) {
	config := NewConfig()
	config.AllowCreate = true
	config.HTTPConfig.Path = "/test"
	config.HTTPConfig.ExpiryPeriod = 300

	log, stats := loggerAndStats()

	httpAuth := NewHTTP(config, log, stats)

	for i := 0; i < 1000; i++ {
		uuid := util.GenerateStampedUUID()

		bodyReader := bytes.NewReader([]byte(fmt.Sprintf(`{"key_value":"%v"}`, uuid)))
		req, _ := http.NewRequest("POST", "http://localhost:8001/test/create", bodyReader)

		dWriter := dummyWriter{header: http.Header{}, Token: ""}

		switch i % 3 {
		case 0:
			httpAuth.joinHandler(&dWriter, req)
			if _, ok := httpAuth.tokensJoin[dWriter.Token]; !ok {
				t.Errorf("Token not added: %v", dWriter.Token)
			}
		case 1:
			httpAuth.createHandler(&dWriter, req)
			if _, ok := httpAuth.tokensCreate[dWriter.Token]; !ok {
				t.Errorf("Token not added: %v", dWriter.Token)
			}
		default:
			httpAuth.readOnlyHandler(&dWriter, req)
			if _, ok := httpAuth.tokensReadOnly[dWriter.Token]; !ok {
				t.Errorf("Token not added: %v", dWriter.Token)
			}
		}
	}

	// Check existing tokens with random values
	for token, key := range httpAuth.tokensJoin {
		randomKey := util.GenerateStampedUUID()

		if httpAuth.AuthoriseJoin(token, randomKey) {
			if key.value != randomKey {
				t.Errorf("Authorised join random key: %v %v", token, randomKey)
			}
		}
		if httpAuth.AuthoriseCreate(token, randomKey) {
			if key.value != randomKey {
				t.Errorf("Authorised create random key: %v %v", token, randomKey)
			}
		}
		if httpAuth.AuthoriseReadOnly(token, randomKey) {
			if key.value != randomKey {
				t.Errorf("Authorised create random key: %v %v", token, randomKey)
			}
		}
	}

	// Check random tokens and values
	for i := 0; i < 1000; i++ {
		randomToken := util.GenerateStampedUUID()
		randomKey := util.GenerateStampedUUID()

		if httpAuth.AuthoriseJoin(randomToken, randomKey) {
			t.Errorf("Authorised join random key/token: %v %v", randomToken, randomKey)
		}
		if httpAuth.AuthoriseCreate(randomToken, randomKey) {
			t.Errorf("Authorised create random key/token: %v %v", randomToken, randomKey)
		}
		if httpAuth.AuthoriseReadOnly(randomToken, randomKey) {
			t.Errorf("Authorised create random key/token: %v %v", randomToken, randomKey)
		}
	}
}
Example #11
0
func senderClient(id, url string, feeds <-chan text.OTransform, t *testing.T) {
	origin := "http://127.0.0.1/"

	userID := util.GenerateStampedUUID()

	ws, err := websocket.Dial(url, "", origin)
	if err != nil {
		t.Errorf("client connect error: %v", err)
		return
	}

	if err = findDocument(id, userID, ws); err != nil {
		t.Errorf("%v", err)
		return
	}

	rcvChan := make(chan []text.OTransform, 5)
	crctChan := make(chan bool)
	go func() {
		for {
			var serverMsg leapSocketServerMessage
			if err := websocket.JSON.Receive(ws, &serverMsg); err == nil {
				switch serverMsg.Type {
				case "correction":
					crctChan <- true
				case "transforms":
					rcvChan <- serverMsg.Transforms
				case "update":
				case "error":
					t.Errorf("Server returned error: %v", serverMsg.Error)
				default:
					t.Errorf("unexpected message type from server: %v", serverMsg.Type)
				}
			} else {
				close(rcvChan)
				return
			}
		}
	}()

	for {
		select {
		case feed, open := <-feeds:
			if !open {
				return
			}
			websocket.JSON.Send(ws, leapSocketClientMessage{
				Command:   "submit",
				Transform: &feed,
			})
			websocket.JSON.Send(ws, leapSocketClientMessage{
				Command: "ping",
			})
			select {
			case <-crctChan:
			case <-time.After(1 * time.Second):
				t.Errorf("Timed out waiting for correction")
				return
			}
		case _, open := <-rcvChan:
			if !open {
				return
			}
			t.Errorf("sender client received a transform")
		case <-time.After(8 * time.Second):
			t.Errorf("Sender client timeout occured")
			return
		}
	}
}
Example #12
0
func goodStoryClient(id, url string, bstory *binderStory, wg *sync.WaitGroup, t *testing.T) {
	origin := "http://127.0.0.1/"

	userID := util.GenerateStampedUUID()

	ws, err := websocket.Dial(url, "", origin)
	if err != nil {
		t.Errorf("client connect error: %v", err)
		wg.Done()
		return
	}

	if err = findDocument(id, userID, ws); err != nil {
		t.Errorf("%v", err)
		wg.Done()
		return
	}

	rcvChan := make(chan []text.OTransform, 5)
	go func() {
		for {
			var serverMsg leapSocketServerMessage
			if err := websocket.JSON.Receive(ws, &serverMsg); err == nil {
				switch serverMsg.Type {
				case "correction":
					t.Errorf("listening client received correction")
				case "transforms":
					rcvChan <- serverMsg.Transforms
				case "update":
					// Ignore
				case "error":
					t.Errorf("Server returned error: %v", serverMsg.Error)
				default:
					t.Errorf("unexpected message type from server: %v", serverMsg.Type)
				}
			} else {
				close(rcvChan)
				return
			}
		}
	}()

	tformIndex, lenCorrected := 0, len(bstory.TCorrected)
	for {
		select {
		case ret, open := <-rcvChan:
			if !open {
				t.Errorf("channel was closed before receiving last change")
				wg.Done()
				return
			}
			for _, tform := range ret {
				if tform.Version != bstory.TCorrected[tformIndex].Version ||
					tform.Insert != bstory.TCorrected[tformIndex].Insert ||
					tform.Delete != bstory.TCorrected[tformIndex].Delete ||
					tform.Position != bstory.TCorrected[tformIndex].Position {
					t.Errorf("Transform (%v) not expected, %v != %v",
						tformIndex, tform, bstory.TCorrected[tformIndex])
				}
				tformIndex++
				if tformIndex == lenCorrected {
					wg.Done()
					return
				}
			}
		case <-time.After(8 * time.Second):
			t.Errorf("Timeout occured")
			wg.Done()
			return
		}
	}
}