mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-16 07:44:31 +08:00
671 lines
17 KiB
Go
671 lines
17 KiB
Go
/*
|
|
Copyright 2015 Gravitational, Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package web
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/cookiejar"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"os/user"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gravitational/teleport"
|
|
"github.com/gravitational/teleport/lib/auth"
|
|
authority "github.com/gravitational/teleport/lib/auth/testauthority"
|
|
"github.com/gravitational/teleport/lib/backend"
|
|
"github.com/gravitational/teleport/lib/backend/boltbk"
|
|
"github.com/gravitational/teleport/lib/backend/encryptedbk"
|
|
"github.com/gravitational/teleport/lib/backend/encryptedbk/encryptor"
|
|
"github.com/gravitational/teleport/lib/events"
|
|
"github.com/gravitational/teleport/lib/events/boltlog"
|
|
"github.com/gravitational/teleport/lib/recorder/boltrec"
|
|
"github.com/gravitational/teleport/lib/reversetunnel"
|
|
"github.com/gravitational/teleport/lib/services"
|
|
sess "github.com/gravitational/teleport/lib/session"
|
|
"github.com/gravitational/teleport/lib/srv"
|
|
"github.com/gravitational/teleport/lib/sshutils"
|
|
"github.com/gravitational/teleport/lib/utils"
|
|
|
|
"github.com/codahale/lunk"
|
|
"github.com/gokyle/hotp"
|
|
"github.com/gravitational/roundtrip"
|
|
"golang.org/x/crypto/ssh"
|
|
"golang.org/x/net/websocket"
|
|
. "gopkg.in/check.v1"
|
|
)
|
|
|
|
func TestWeb(t *testing.T) { TestingT(t) }
|
|
|
|
type WebSuite struct {
|
|
node *srv.Server
|
|
srvAddress string
|
|
srvHostPort string
|
|
bk *encryptedbk.ReplicatedBackend
|
|
roleAuth *auth.AuthWithRoles
|
|
dir string
|
|
user string
|
|
domainName string
|
|
signer ssh.Signer
|
|
tunServer *auth.TunServer
|
|
webServer *httptest.Server
|
|
freePorts []string
|
|
}
|
|
|
|
var _ = Suite(&WebSuite{})
|
|
|
|
func (s *WebSuite) SetUpSuite(c *C) {
|
|
utils.InitLoggerCLI()
|
|
}
|
|
|
|
func (s *WebSuite) SetUpTest(c *C) {
|
|
s.dir = c.MkDir()
|
|
|
|
u, err := user.Current()
|
|
c.Assert(err, IsNil)
|
|
s.user = u.Username
|
|
|
|
s.freePorts, err = utils.GetFreeTCPPorts(3)
|
|
c.Assert(err, IsNil)
|
|
|
|
baseBk, err := boltbk.New(filepath.Join(s.dir, "db"))
|
|
c.Assert(err, IsNil)
|
|
s.bk, err = encryptedbk.NewReplicatedBackend(baseBk,
|
|
filepath.Join(s.dir, "keys"), nil,
|
|
encryptor.GetTestKey)
|
|
c.Assert(err, IsNil)
|
|
|
|
s.domainName = "localhost"
|
|
authServer := auth.NewAuthServer(s.bk, authority.New(), s.domainName)
|
|
|
|
eventsLog, err := boltlog.New(filepath.Join(s.dir, "boltlog"))
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Assert(authServer.UpsertCertAuthority(
|
|
*services.NewTestCA(services.UserCA, s.domainName), backend.Forever), IsNil)
|
|
c.Assert(authServer.UpsertCertAuthority(
|
|
*services.NewTestCA(services.HostCA, s.domainName), backend.Forever), IsNil)
|
|
|
|
recorder, err := boltrec.New(s.dir)
|
|
c.Assert(err, IsNil)
|
|
|
|
sessionServer := sess.New(baseBk)
|
|
s.roleAuth = auth.NewAuthWithRoles(authServer,
|
|
auth.NewStandardPermissions(),
|
|
eventsLog,
|
|
sessionServer,
|
|
teleport.RoleAdmin,
|
|
recorder)
|
|
|
|
// set up host private key and certificate
|
|
hpriv, hpub, err := authServer.GenerateKeyPair("")
|
|
c.Assert(err, IsNil)
|
|
hcert, err := authServer.GenerateHostCert(
|
|
hpub, s.domainName, s.domainName, teleport.RoleAdmin, 0)
|
|
c.Assert(err, IsNil)
|
|
|
|
// set up user CA and set up a user that has access to the server
|
|
s.signer, err = sshutils.NewSigner(hpriv, hcert)
|
|
c.Assert(err, IsNil)
|
|
|
|
// start node
|
|
nodePort := s.freePorts[len(s.freePorts)-1]
|
|
s.freePorts = s.freePorts[:len(s.freePorts)-1]
|
|
|
|
s.srvAddress = fmt.Sprintf("127.0.0.1:%v", nodePort)
|
|
node, err := srv.New(
|
|
utils.NetAddr{AddrNetwork: "tcp", Addr: s.srvAddress},
|
|
s.domainName,
|
|
[]ssh.Signer{s.signer},
|
|
s.roleAuth,
|
|
s.dir,
|
|
srv.SetShell("/bin/sh"),
|
|
srv.SetSessionServer(sessionServer),
|
|
srv.SetRecorder(recorder),
|
|
srv.SetEventLogger(eventsLog),
|
|
)
|
|
c.Assert(err, IsNil)
|
|
s.node = node
|
|
|
|
c.Assert(s.node.Start(), IsNil)
|
|
|
|
revTunServer, err := reversetunnel.NewServer(
|
|
utils.NetAddr{
|
|
AddrNetwork: "tcp",
|
|
Addr: fmt.Sprintf("%v:0", s.domainName),
|
|
},
|
|
[]ssh.Signer{s.signer},
|
|
s.roleAuth,
|
|
reversetunnel.ServerTimeout(200*time.Millisecond),
|
|
reversetunnel.DirectSite(s.domainName, s.roleAuth),
|
|
)
|
|
c.Assert(err, IsNil)
|
|
|
|
apiPort := s.freePorts[len(s.freePorts)-1]
|
|
s.freePorts = s.freePorts[:len(s.freePorts)-1]
|
|
|
|
apiServer := auth.NewAPIWithRoles(authServer, eventsLog, sessionServer, recorder,
|
|
auth.NewAllowAllPermissions(),
|
|
auth.StandardRoles,
|
|
)
|
|
go apiServer.Serve()
|
|
|
|
tunAddr := utils.NetAddr{
|
|
AddrNetwork: "tcp", Addr: fmt.Sprintf("127.0.0.1:%v", apiPort),
|
|
}
|
|
|
|
s.tunServer, err = auth.NewTunServer(
|
|
tunAddr,
|
|
[]ssh.Signer{s.signer},
|
|
apiServer, authServer)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(s.tunServer.Start(), IsNil)
|
|
|
|
// start handler
|
|
handler, err := NewHandler(Config{
|
|
InsecureHTTPMode: true,
|
|
Proxy: revTunServer,
|
|
AssetsDir: "assets/web",
|
|
AuthServers: tunAddr,
|
|
DomainName: s.domainName,
|
|
}, SetSessionStreamPollPeriod(200*time.Millisecond))
|
|
|
|
s.webServer = httptest.NewServer(handler)
|
|
}
|
|
|
|
func (s *WebSuite) url() *url.URL {
|
|
u, err := url.Parse("http://" + s.webServer.Listener.Addr().String())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return u
|
|
}
|
|
|
|
func (s *WebSuite) client(opts ...roundtrip.ClientParam) *webClient {
|
|
clt, err := newWebClient(s.url().String(), opts...)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return clt
|
|
}
|
|
|
|
func (s *WebSuite) TearDownTest(c *C) {
|
|
c.Assert(s.node.Close(), IsNil)
|
|
c.Assert(s.tunServer.Close(), IsNil)
|
|
s.webServer.Close()
|
|
}
|
|
|
|
func (s *WebSuite) TestNewUser(c *C) {
|
|
token, err := s.roleAuth.CreateSignupToken("bob", []string{s.user})
|
|
c.Assert(err, IsNil)
|
|
|
|
clt := s.client()
|
|
re, err := clt.Get(clt.Endpoint("webapi", "users", "invites", token), url.Values{})
|
|
c.Assert(err, IsNil)
|
|
|
|
var out *renderUserInviteResponse
|
|
c.Assert(json.Unmarshal(re.Bytes(), &out), IsNil)
|
|
c.Assert(out.User, Equals, "bob")
|
|
c.Assert(out.InviteToken, Equals, token)
|
|
|
|
_, _, hotpValues, err := s.roleAuth.GetSignupTokenData(token)
|
|
c.Assert(err, IsNil)
|
|
|
|
tempPass := "abc123"
|
|
|
|
re, err = clt.PostJSON(clt.Endpoint("webapi", "users"), createNewUserReq{
|
|
InviteToken: token,
|
|
Pass: tempPass,
|
|
SecondFactorToken: hotpValues[0],
|
|
})
|
|
c.Assert(err, IsNil)
|
|
|
|
var sess *createSessionResponse
|
|
c.Assert(json.Unmarshal(re.Bytes(), &sess), IsNil)
|
|
cookies := re.Cookies()
|
|
c.Assert(len(cookies), Equals, 1)
|
|
|
|
// now make sure we are logged in by calling authenticated method
|
|
// we need to supply both session cookie and bearer token for
|
|
// request to succeed
|
|
jar, err := cookiejar.New(nil)
|
|
c.Assert(err, IsNil)
|
|
|
|
clt = s.client(roundtrip.BearerAuth(sess.Token), roundtrip.CookieJar(jar))
|
|
jar.SetCookies(s.url(), re.Cookies())
|
|
|
|
re, err = clt.Get(clt.Endpoint("webapi", "sites"), url.Values{})
|
|
c.Assert(err, IsNil)
|
|
|
|
var sites *getSitesResponse
|
|
c.Assert(json.Unmarshal(re.Bytes(), &sites), IsNil)
|
|
|
|
// in absense of session cookie or bearer auth the same request fill fail
|
|
|
|
// no session cookie:
|
|
clt = s.client(roundtrip.BearerAuth(sess.Token))
|
|
re, err = clt.Get(clt.Endpoint("webapi", "sites"), url.Values{})
|
|
c.Assert(err, NotNil)
|
|
c.Assert(teleport.IsAccessDenied(err), Equals, true)
|
|
|
|
// no bearer token:
|
|
clt = s.client(roundtrip.CookieJar(jar))
|
|
re, err = clt.Get(clt.Endpoint("webapi", "sites"), url.Values{})
|
|
c.Assert(err, NotNil)
|
|
c.Assert(teleport.IsAccessDenied(err), Equals, true)
|
|
}
|
|
|
|
type authPack struct {
|
|
user string
|
|
otp *hotp.HOTP
|
|
session *createSessionResponse
|
|
clt *webClient
|
|
cookies []*http.Cookie
|
|
}
|
|
|
|
func (s *WebSuite) authPackFromResponse(c *C, re *roundtrip.Response) *authPack {
|
|
var sess *createSessionResponse
|
|
c.Assert(json.Unmarshal(re.Bytes(), &sess), IsNil)
|
|
|
|
jar, err := cookiejar.New(nil)
|
|
c.Assert(err, IsNil)
|
|
|
|
clt := s.client(roundtrip.BearerAuth(sess.Token), roundtrip.CookieJar(jar))
|
|
jar.SetCookies(s.url(), re.Cookies())
|
|
|
|
return &authPack{
|
|
user: sess.User.Name,
|
|
session: sess,
|
|
clt: clt,
|
|
cookies: re.Cookies(),
|
|
}
|
|
}
|
|
|
|
// authPack returns new authenticated package consisting
|
|
// of created valid user, hotp token, created web session and
|
|
// authenticated client
|
|
func (s *WebSuite) authPack(c *C) *authPack {
|
|
user := "bob"
|
|
pass := "abc123"
|
|
|
|
hotpURL, _, err := s.roleAuth.UpsertPassword(user, []byte(pass))
|
|
c.Assert(err, IsNil)
|
|
otp, _, err := hotp.FromURL(hotpURL)
|
|
c.Assert(err, IsNil)
|
|
otp.Increment()
|
|
|
|
err = s.roleAuth.UpsertUser(
|
|
services.User{Name: user, AllowedLogins: []string{s.user}})
|
|
c.Assert(err, IsNil)
|
|
|
|
clt := s.client()
|
|
|
|
re, err := clt.PostJSON(clt.Endpoint("webapi", "sessions"), createSessionReq{
|
|
User: user,
|
|
Pass: pass,
|
|
SecondFactorToken: otp.OTP(),
|
|
})
|
|
c.Assert(err, IsNil)
|
|
|
|
var sess *createSessionResponse
|
|
c.Assert(json.Unmarshal(re.Bytes(), &sess), IsNil)
|
|
|
|
jar, err := cookiejar.New(nil)
|
|
c.Assert(err, IsNil)
|
|
|
|
clt = s.client(roundtrip.BearerAuth(sess.Token), roundtrip.CookieJar(jar))
|
|
jar.SetCookies(s.url(), re.Cookies())
|
|
|
|
return &authPack{
|
|
user: user,
|
|
session: sess,
|
|
clt: clt,
|
|
cookies: re.Cookies(),
|
|
}
|
|
}
|
|
|
|
func (s *WebSuite) TestWebSessionsCRUD(c *C) {
|
|
pack := s.authPack(c)
|
|
|
|
// make sure we can use client to make authenticated requests
|
|
re, err := pack.clt.Get(pack.clt.Endpoint("webapi", "sites"), url.Values{})
|
|
c.Assert(err, IsNil)
|
|
|
|
var sites *getSitesResponse
|
|
c.Assert(json.Unmarshal(re.Bytes(), &sites), IsNil)
|
|
|
|
// now delete session
|
|
_, err = pack.clt.Delete(
|
|
pack.clt.Endpoint("webapi", "sessions", pack.session.Token))
|
|
c.Assert(err, IsNil)
|
|
|
|
// subsequent requests trying to use this session will fail
|
|
re, err = pack.clt.Get(pack.clt.Endpoint("webapi", "sites"), url.Values{})
|
|
c.Assert(err, NotNil)
|
|
c.Assert(teleport.IsAccessDenied(err), Equals, true)
|
|
}
|
|
|
|
func (s *WebSuite) TestWebSessionsRenew(c *C) {
|
|
pack := s.authPack(c)
|
|
|
|
// make sure we can use client to make authenticated requests
|
|
// before we issue this request, we will recover session id and bearer token
|
|
//
|
|
prevSessionCookie := *pack.cookies[0]
|
|
prevBearerToken := pack.session.Token
|
|
re, err := pack.clt.PostJSON(pack.clt.Endpoint("webapi", "sessions", "renew"), nil)
|
|
c.Assert(err, IsNil)
|
|
|
|
newPack := s.authPackFromResponse(c, re)
|
|
|
|
// new session is functioning
|
|
re, err = newPack.clt.Get(pack.clt.Endpoint("webapi", "sites"), url.Values{})
|
|
c.Assert(err, IsNil)
|
|
|
|
// old session is stil valid too (until it expires)
|
|
jar, err := cookiejar.New(nil)
|
|
c.Assert(err, IsNil)
|
|
oldClt := s.client(roundtrip.BearerAuth(prevBearerToken), roundtrip.CookieJar(jar))
|
|
jar.SetCookies(s.url(), []*http.Cookie{&prevSessionCookie})
|
|
re, err = oldClt.Get(pack.clt.Endpoint("webapi", "sites"), url.Values{})
|
|
c.Assert(err, IsNil)
|
|
|
|
// now delete session
|
|
_, err = newPack.clt.Delete(
|
|
pack.clt.Endpoint("webapi", "sessions", newPack.session.Token))
|
|
c.Assert(err, IsNil)
|
|
|
|
// subsequent requests trying to use this session will fail
|
|
re, err = newPack.clt.Get(pack.clt.Endpoint("webapi", "sites"), url.Values{})
|
|
c.Assert(err, NotNil)
|
|
c.Assert(teleport.IsAccessDenied(err), Equals, true)
|
|
}
|
|
|
|
func (s *WebSuite) TestWebSessionsBadInput(c *C) {
|
|
user := "bob"
|
|
pass := "abc123"
|
|
|
|
hotpURL, _, err := s.roleAuth.UpsertPassword(user, []byte(pass))
|
|
c.Assert(err, IsNil)
|
|
otp, _, err := hotp.FromURL(hotpURL)
|
|
c.Assert(err, IsNil)
|
|
otp.Increment()
|
|
|
|
clt := s.client()
|
|
|
|
token := otp.OTP()
|
|
|
|
reqs := []createSessionReq{
|
|
// emtpy request
|
|
{},
|
|
// missing user
|
|
{
|
|
Pass: pass,
|
|
SecondFactorToken: token,
|
|
},
|
|
// missing pass
|
|
{
|
|
User: user,
|
|
SecondFactorToken: token,
|
|
},
|
|
// bad pass
|
|
{
|
|
User: user,
|
|
Pass: "bla bla",
|
|
SecondFactorToken: token,
|
|
},
|
|
// bad hotp token
|
|
{
|
|
User: user,
|
|
Pass: pass,
|
|
SecondFactorToken: "bad token",
|
|
},
|
|
// missing hotp token
|
|
{
|
|
User: user,
|
|
Pass: pass,
|
|
},
|
|
}
|
|
for i, req := range reqs {
|
|
_, err = clt.PostJSON(clt.Endpoint("webapi", "sessions"), req)
|
|
c.Assert(err, NotNil, Commentf("tc %v", i))
|
|
c.Assert(teleport.IsAccessDenied(err), Equals, true, Commentf("tc %v %T is not access denied", i, err))
|
|
}
|
|
}
|
|
|
|
func (s *WebSuite) TestGetSiteNodes(c *C) {
|
|
pack := s.authPack(c)
|
|
|
|
// get site nodes
|
|
re, err := pack.clt.Get(pack.clt.Endpoint("webapi", "sites", s.domainName, "nodes"), url.Values{})
|
|
c.Assert(err, IsNil)
|
|
|
|
var nodes *getSiteNodesResponse
|
|
c.Assert(json.Unmarshal(re.Bytes(), &nodes), IsNil)
|
|
c.Assert(len(nodes.Nodes), Equals, 1)
|
|
|
|
// get site nodes using shortcut
|
|
re, err = pack.clt.Get(pack.clt.Endpoint("webapi", "sites", currentSiteShortcut, "nodes"), url.Values{})
|
|
c.Assert(err, IsNil)
|
|
|
|
var nodes2 *getSiteNodesResponse
|
|
c.Assert(json.Unmarshal(re.Bytes(), &nodes2), IsNil)
|
|
c.Assert(len(nodes.Nodes), Equals, 1)
|
|
|
|
c.Assert(nodes2, DeepEquals, nodes)
|
|
}
|
|
|
|
func (s *WebSuite) connect(c *C, pack *authPack, opts ...string) *websocket.Conn {
|
|
var sessionID string
|
|
if len(opts) != 0 {
|
|
sessionID = opts[0]
|
|
}
|
|
u := url.URL{Host: s.url().Host, Scheme: "ws", Path: fmt.Sprintf("/v1/webapi/sites/%v/connect", currentSiteShortcut)}
|
|
data, err := json.Marshal(connectReq{
|
|
Addr: s.srvAddress,
|
|
Login: s.user,
|
|
Term: connectTerm{W: 100, H: 100},
|
|
SessionID: sessionID,
|
|
})
|
|
c.Assert(err, IsNil)
|
|
|
|
q := u.Query()
|
|
q.Set("params", string(data))
|
|
q.Set(roundtrip.AccessTokenQueryParam, pack.session.Token)
|
|
u.RawQuery = q.Encode()
|
|
|
|
wscfg, err := websocket.NewConfig(u.String(), "http://localhost")
|
|
c.Assert(err, IsNil)
|
|
for _, cookie := range pack.cookies {
|
|
wscfg.Header.Add("Cookie", cookie.String())
|
|
}
|
|
clt, err := websocket.DialConfig(wscfg)
|
|
c.Assert(err, IsNil)
|
|
|
|
return clt
|
|
}
|
|
|
|
func (s *WebSuite) sessionStream(c *C, pack *authPack, sessionID string, opts ...string) *websocket.Conn {
|
|
u := url.URL{
|
|
Host: s.url().Host,
|
|
Scheme: "ws",
|
|
Path: fmt.Sprintf(
|
|
"/v1/webapi/sites/%v/sessions/%v/events/stream",
|
|
currentSiteShortcut,
|
|
sessionID),
|
|
}
|
|
q := u.Query()
|
|
q.Set(roundtrip.AccessTokenQueryParam, pack.session.Token)
|
|
u.RawQuery = q.Encode()
|
|
wscfg, err := websocket.NewConfig(u.String(), "http://localhost")
|
|
c.Assert(err, IsNil)
|
|
for _, cookie := range pack.cookies {
|
|
wscfg.Header.Add("Cookie", cookie.String())
|
|
}
|
|
clt, err := websocket.DialConfig(wscfg)
|
|
c.Assert(err, IsNil)
|
|
|
|
return clt
|
|
}
|
|
|
|
func (s *WebSuite) TestConnect(c *C) {
|
|
clt := s.connect(c, s.authPack(c))
|
|
defer clt.Close()
|
|
|
|
doneC := make(chan error, 2)
|
|
go func() {
|
|
_, err := io.WriteString(clt, "expr 137 + 39\r\nexit\r\n")
|
|
doneC <- err
|
|
}()
|
|
|
|
output := &bytes.Buffer{}
|
|
go func() {
|
|
_, err := io.Copy(output, clt)
|
|
doneC <- err
|
|
}()
|
|
|
|
timeoutC := time.After(time.Second)
|
|
for i := 0; i < 2; i++ {
|
|
select {
|
|
case <-doneC:
|
|
break
|
|
case <-timeoutC:
|
|
c.Fatalf("timeout!")
|
|
}
|
|
}
|
|
|
|
c.Assert(removeSpace(output.String()), Matches, ".*176.*")
|
|
}
|
|
|
|
func (s *WebSuite) TestNodesWithSessions(c *C) {
|
|
sid := "testsession"
|
|
pack := s.authPack(c)
|
|
clt := s.connect(c, pack, sid)
|
|
defer clt.Close()
|
|
|
|
// to make sure we have a session
|
|
_, err := io.WriteString(clt, "expr 137 + 39\r\n")
|
|
c.Assert(err, IsNil)
|
|
|
|
// make sure server has replied
|
|
out := make([]byte, 100)
|
|
clt.Read(out)
|
|
fmt.Printf("%v", string(out))
|
|
|
|
var nodes *getSiteNodesResponse
|
|
for i := 0; i < 3; i++ {
|
|
// get site nodes and make sure the node has our active party
|
|
re, err := pack.clt.Get(pack.clt.Endpoint("webapi", "sites", s.domainName, "nodes"), url.Values{})
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Assert(json.Unmarshal(re.Bytes(), &nodes), IsNil)
|
|
c.Assert(len(nodes.Nodes), Equals, 1)
|
|
|
|
if len(nodes.Nodes[0].Sessions) == 1 {
|
|
break
|
|
}
|
|
// sessions do not appear momentarily as there's async heartbeat
|
|
// procedure
|
|
time.Sleep(20 * time.Millisecond)
|
|
}
|
|
|
|
c.Assert(len(nodes.Nodes[0].Sessions), Equals, 1)
|
|
c.Assert(nodes.Nodes[0].Sessions[0].ID, Equals, sid)
|
|
|
|
// connect to session stream and receive events
|
|
stream := s.sessionStream(c, pack, sid)
|
|
defer stream.Close()
|
|
var event *sessionStreamEvent
|
|
c.Assert(websocket.JSON.Receive(stream, &event), IsNil)
|
|
c.Assert(event, NotNil)
|
|
c.Assert(getEvent(events.SessionEvent, event.Events), NotNil)
|
|
|
|
// one more party joins the session
|
|
clt2 := s.connect(c, pack, sid)
|
|
defer clt2.Close()
|
|
|
|
// to make sure we have a session
|
|
_, err = io.WriteString(clt, "expr 147 + 29\r\n")
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Assert(websocket.JSON.Receive(stream, &event), IsNil)
|
|
c.Assert(len(event.Session.Parties), Equals, 2)
|
|
}
|
|
|
|
func (s *WebSuite) TestCloseConnectionsOnLogout(c *C) {
|
|
sid := "testsession"
|
|
pack := s.authPack(c)
|
|
clt := s.connect(c, pack, sid)
|
|
defer clt.Close()
|
|
|
|
// to make sure we have a session
|
|
_, err := io.WriteString(clt, "expr 137 + 39\r\n")
|
|
c.Assert(err, IsNil)
|
|
|
|
// make sure server has replied
|
|
out := make([]byte, 100)
|
|
clt.Read(out)
|
|
|
|
_, err = pack.clt.Delete(
|
|
pack.clt.Endpoint("webapi", "sessions", pack.session.Token))
|
|
c.Assert(err, IsNil)
|
|
|
|
// wait until we timeout or detect that connection has been closed
|
|
after := time.After(time.Second)
|
|
errC := make(chan error)
|
|
go func() {
|
|
for {
|
|
_, err := clt.Read(out)
|
|
if err != nil {
|
|
errC <- err
|
|
}
|
|
}
|
|
}()
|
|
|
|
select {
|
|
case <-after:
|
|
c.Fatalf("timeout")
|
|
case err := <-errC:
|
|
c.Assert(err, Equals, io.EOF)
|
|
}
|
|
|
|
}
|
|
|
|
func getEvent(schema string, events []lunk.Entry) *lunk.Entry {
|
|
for i := range events {
|
|
e := events[i]
|
|
if e.Schema == schema {
|
|
return &e
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func removeSpace(in string) string {
|
|
for _, c := range []string{"\n", "\r", "\t"} {
|
|
in = strings.Replace(in, c, " ", -1)
|
|
}
|
|
return strings.TrimSpace(in)
|
|
}
|