diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0ebba78..0000000 --- a/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -language: go -go: - - 1.5 - - 1.6 - - 1.7 - - 1.8 - - 1.9 - - "1.10" - - 1.11 - - 1.12 - - tip -install: - - go get github.com/stretchr/testify/assert - - go get github.com/axw/gocov/gocov - - go get github.com/mattn/goveralls - - go get golang.org/x/crypto/nacl/secretbox - - if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi - - $HOME/gopath/bin/goveralls -service=travis-ci -repotoken=$COVERALLS_TOKEN -script: - - go test diff --git a/channel_authentication_test.go b/channel_authentication_test.go deleted file mode 100644 index 2754677..0000000 --- a/channel_authentication_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package pusher - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func setUpAuthClient() Client { - return Client{ - Key: "278d425bdf160c739803", - Secret: "7ad3773142a6692b25b8", - } -} - -func TestPrivateChannelAuthentication(t *testing.T) { - client := setUpAuthClient() - postParams := []byte("channel_name=private-foobar&socket_id=1234.1234") - expected := "{\"auth\":\"278d425bdf160c739803:58df8b0c36d6982b82c3ecf6b4662e34fe8c25bba48f5369f135bf843651c3a4\"}" - result, err := client.AuthenticatePrivateChannel(postParams) - assert.Equal(t, expected, string(result)) - assert.NoError(t, err) -} - -func TestPrivateChannelAuthenticationWrongParams(t *testing.T) { - client := setUpAuthClient() - postParams := []byte("hello=hi&two=3") - _, err := client.AuthenticatePrivateChannel(postParams) - assert.Error(t, err) -} - -func TestPresenceChannelAuthentication(t *testing.T) { - client := setUpAuthClient() - postParams := []byte("channel_name=presence-foobar&socket_id=1234.1234") - presenceData := MemberData{UserID: "10", UserInfo: map[string]string{"name": "Mr. Pusher"}} - expected := "{\"auth\":\"278d425bdf160c739803:48dac51d2d7569e1e9c0f48c227d4b26f238fa68e5c0bb04222c966909c4f7c4\",\"channel_data\":\"{\\\"user_id\\\":\\\"10\\\",\\\"user_info\\\":{\\\"name\\\":\\\"Mr. Pusher\\\"}}\"}" - result, err := client.AuthenticatePresenceChannel(postParams, presenceData) - assert.Equal(t, expected, string(result)) - assert.NoError(t, err) -} - -func TestAuthSocketIDValidation(t *testing.T) { - client := setUpAuthClient() - postParams := []byte("channel_name=private-foobar&socket_id=12341234") - result, err := client.AuthenticatePrivateChannel(postParams) - assert.Nil(t, result) - assert.Error(t, err) -} diff --git a/client.go b/client.go index 8b65713..b1b0264 100644 --- a/client.go +++ b/client.go @@ -1,7 +1,6 @@ package pusher import ( - "encoding/base64" "encoding/json" "errors" "fmt" @@ -51,7 +50,6 @@ type Client struct { Secure bool // true for HTTPS Cluster string HTTPClient *http.Client - EncryptionMasterKey string //for E2E } /* @@ -168,30 +166,16 @@ func (c *Client) TriggerMultiExclusive(channels []string, eventName string, data } func (c *Client) trigger(channels []string, eventName string, data interface{}, socketID *string) error { - hasEncryptedChannel := false - for _, channel := range channels { - if isEncryptedChannel(channel) { - hasEncryptedChannel = true - } - } if len(channels) > maxTriggerableChannels { return fmt.Errorf("You cannot trigger on more than %d channels at once", maxTriggerableChannels) } - if hasEncryptedChannel && len(channels) > 1 { - // For rationale, see limitations of end-to-end encryption in the README - return errors.New("You cannot trigger to multiple channels when using encrypted channels") - - } if !channelsAreValid(channels) { return errors.New("At least one of your channels' names are invalid") } - if hasEncryptedChannel && !validEncryptionKey(c.EncryptionMasterKey) { - return errors.New("Your encryptionMasterKey is not of the correct format") - } if err := validateSocketID(socketID); err != nil { return err } - payload, err := encodeTriggerBody(channels, eventName, data, socketID, c.EncryptionMasterKey) + payload, err := encodeTriggerBody(channels, eventName, data, socketID) if err != nil { return err } @@ -219,11 +203,9 @@ type Event struct { TriggerBatch triggers multiple events on multiple channels in a single call: client.TriggerBatch([]pusher.Event{ { Channel: "donut-1", Name: "ev1", Data: "d1" }, - { Channel: "private-encrypted-secretdonut", Name: "ev2", Data: "d2" }, }) */ func (c *Client) TriggerBatch(batch []Event) error { - hasEncryptedChannel := false // validate every channel name and every sockedID (if present) in batch for _, event := range batch { if !validChannel(event.Channel) { @@ -232,17 +214,9 @@ func (c *Client) TriggerBatch(batch []Event) error { if err := validateSocketID(event.SocketID); err != nil { return err } - if isEncryptedChannel(event.Channel) { - hasEncryptedChannel = true - } } - if hasEncryptedChannel { - // validate EncryptionMasterKey - if !validEncryptionKey(c.EncryptionMasterKey) { - return errors.New("Your encryptionMasterKey is not of the correct format") - } - } - payload, err := encodeTriggerBatchBody(batch, c.EncryptionMasterKey) + + payload, err := encodeTriggerBatchBody(batch) if err != nil { return err } @@ -430,16 +404,7 @@ func (c *Client) authenticateChannel(params []byte, member *MemberData) (respons stringToSign = strings.Join([]string{stringToSign, jsonUserData}, ":") } - var _response map[string]string - - if isEncryptedChannel(channelName) { - sharedSecret := generateSharedSecret(channelName, c.EncryptionMasterKey) - sharedSecretB64 := base64.StdEncoding.EncodeToString(sharedSecret[:]) - _response = createAuthMap(c.Key, c.Secret, stringToSign, sharedSecretB64) - } else { - _response = createAuthMap(c.Key, c.Secret, stringToSign, "") - } - + _response := createAuthMap(c.Key, c.Secret, stringToSign) if member != nil { _response["channel_data"] = jsonUserData } @@ -478,12 +443,7 @@ error will be passed. func (c *Client) Webhook(header http.Header, body []byte) (*Webhook, error) { for _, token := range header["X-Pusher-Key"] { if token == c.Key && checkSignature(header.Get("X-Pusher-Signature"), c.Secret, body) { - unmarshalledWebhooks, err := unmarshalledWebhook(body) - if err != nil { - return unmarshalledWebhooks, err - } - decryptedWebhooks, err := decryptEvents(*unmarshalledWebhooks, c.EncryptionMasterKey) - return decryptedWebhooks, err + return unmarshalledWebhook(body) } } return nil, errors.New("Invalid webhook") diff --git a/client_test.go b/client_test.go deleted file mode 100644 index 426fd93..0000000 --- a/client_test.go +++ /dev/null @@ -1,340 +0,0 @@ -package pusher - -import ( - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "net/url" - "os" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestTriggerSuccessCase(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { - res.WriteHeader(200) - fmt.Fprintf(res, "{}") - assert.Equal(t, "POST", req.Method) - - expectedBody := "{\"name\":\"test\",\"channels\":[\"test_channel\"],\"data\":\"yolo\"}" - actualBody, err := ioutil.ReadAll(req.Body) - assert.Equal(t, expectedBody, string(actualBody)) - - assert.Equal(t, "application/json", req.Header["Content-Type"][0]) - assert.NoError(t, err) - })) - defer server.Close() - - u, _ := url.Parse(server.URL) - client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host} - err := client.Trigger("test_channel", "test", "yolo") - assert.NoError(t, err) -} - -func TestGetChannelsSuccessCase(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { - res.WriteHeader(200) - testJSON := "{\"channels\":{\"presence-session-d41a439c438a100756f5-4bf35003e819bb138249-5cbTiUiPNGI\":{\"user_count\":1},\"presence-session-d41a439c438a100756f5-4bf35003e819bb138249-PbZ5E1pP8uF\":{\"user_count\":1},\"presence-session-d41a439c438a100756f5-4bf35003e819bb138249-oz6iqpSxMwG\":{\"user_count\":1}}}" - - fmt.Fprintf(res, testJSON) - assert.Equal(t, "GET", req.Method) - - })) - defer server.Close() - - u, _ := url.Parse(server.URL) - client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host} - channels, err := client.Channels(nil) - assert.NoError(t, err) - - expected := &ChannelsList{ - Channels: map[string]ChannelListItem{ - "presence-session-d41a439c438a100756f5-4bf35003e819bb138249-5cbTiUiPNGI": ChannelListItem{UserCount: 1}, - "presence-session-d41a439c438a100756f5-4bf35003e819bb138249-PbZ5E1pP8uF": ChannelListItem{UserCount: 1}, - "presence-session-d41a439c438a100756f5-4bf35003e819bb138249-oz6iqpSxMwG": ChannelListItem{UserCount: 1}, - }, - } - assert.Equal(t, channels, expected) -} - -func TestGetChannelSuccess(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { - res.WriteHeader(200) - testJSON := "{\"user_count\":1,\"occupied\":true,\"subscription_count\":1}" - fmt.Fprintf(res, testJSON) - - assert.Equal(t, "GET", req.Method) - })) - defer server.Close() - - u, _ := url.Parse(server.URL) - client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host} - channel, err := client.Channel("test_channel", nil) - assert.NoError(t, err) - - expected := &Channel{ - Name: "test_channel", - Occupied: true, - UserCount: 1, - SubscriptionCount: 1, - } - assert.Equal(t, channel, expected) -} - -func TestGetChannelUserSuccess(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { - res.WriteHeader(200) - testJSON := "{\"users\":[{\"id\":\"red\"},{\"id\":\"blue\"}]}" - fmt.Fprintf(res, testJSON) - - assert.Equal(t, "GET", req.Method) - })) - defer server.Close() - - u, _ := url.Parse(server.URL) - client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host} - users, err := client.GetChannelUsers("test_channel") - assert.NoError(t, err) - - expected := &Users{ - List: []User{User{ID: "red"}, User{ID: "blue"}}, - } - assert.Equal(t, users, expected) -} - -func TestTriggerWithSocketID(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { - res.WriteHeader(200) - expectedBody := "{\"name\":\"test\",\"channels\":[\"test_channel\"],\"data\":\"yolo\",\"socket_id\":\"1234.12\"}" - actualBody, err := ioutil.ReadAll(req.Body) - assert.Equal(t, expectedBody, string(actualBody)) - assert.NoError(t, err) - })) - defer server.Close() - - u, _ := url.Parse(server.URL) - client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host} - client.TriggerExclusive("test_channel", "test", "yolo", "1234.12") -} - -func TestTriggerSocketIDValidation(t *testing.T) { - client := Client{AppID: "id", Key: "key", Secret: "secret"} - err := client.TriggerExclusive("test_channel", "test", "yolo", "1234.12:lalala") - assert.Error(t, err) -} - -func TestTriggerBatchSuccess(t *testing.T) { - expectedBody := `{"batch":[{"channel":"test_channel","name":"test","data":"yolo1"},{"channel":"test_channel","name":"test","data":"yolo2"}]}` - server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { - res.WriteHeader(200) - fmt.Fprintf(res, "{}") - assert.Equal(t, "POST", req.Method) - - actualBody, err := ioutil.ReadAll(req.Body) - assert.Equal(t, expectedBody, string(actualBody)) - assert.Equal(t, "application/json", req.Header["Content-Type"][0]) - assert.Equal(t, "/apps/appid/batch_events", req.URL.Path) - assert.NoError(t, err) - })) - defer server.Close() - - u, _ := url.Parse(server.URL) - client := Client{AppID: "appid", Key: "key", Secret: "secret", Host: u.Host} - err := client.TriggerBatch([]Event{ - {"test_channel", "test", "yolo1", nil}, - {"test_channel", "test", "yolo2", nil}, - }) - - assert.NoError(t, err) -} - -func TestTriggerBatchWithEncryptionMasterKeyNoEncryptedChanSuccess(t *testing.T) { - expectedBody := `{"batch":[{"channel":"test_channel","name":"test","data":"yolo1"},{"channel":"test_channel","name":"test","data":"yolo2"}]}` - server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { - res.WriteHeader(200) - fmt.Fprintf(res, "{}") - assert.Equal(t, "POST", req.Method) - - actualBody, err := ioutil.ReadAll(req.Body) - assert.Equal(t, expectedBody, string(actualBody)) - assert.Equal(t, "application/json", req.Header["Content-Type"][0]) - assert.Equal(t, "/apps/appid/batch_events", req.URL.Path) - assert.NoError(t, err) - })) - defer server.Close() - u, _ := url.Parse(server.URL) - client := Client{AppID: "appid", Key: "key", Secret: "secret", EncryptionMasterKey: "eHPVWHg7nFGYVBsKjOFDXWRribIR2b0b", Host: u.Host} - err := client.TriggerBatch([]Event{ - {"test_channel", "test", "yolo1", nil}, - {"test_channel", "test", "yolo2", nil}, - }) - - assert.NoError(t, err) -} - -func TestTriggerBatchNoEncryptionMasterKeyWithEncryptedChanFailure(t *testing.T) { - expectedBody := `{"batch":[{"channel":"test_channel","name":"test","data":"yolo1"},{"channel":"private-encrypted-test_channel","name":"test","data":"yolo2"}]}` - server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { - res.WriteHeader(200) - fmt.Fprintf(res, "{}") - assert.Equal(t, "POST", req.Method) - - actualBody, err := ioutil.ReadAll(req.Body) - assert.Equal(t, expectedBody, string(actualBody)) - assert.Equal(t, "application/json", req.Header["Content-Type"][0]) - assert.Equal(t, "/apps/appid/batch_events", req.URL.Path) - assert.NoError(t, err) - })) - defer server.Close() - - u, _ := url.Parse(server.URL) - client := Client{AppID: "appid", Key: "key", Secret: "secret", Host: u.Host} - err := client.TriggerBatch([]Event{ - {"test_channel", "test", "yolo1", nil}, - {"private-encrypted-test_channel", "test", "yolo2", nil}, - }) - - assert.Error(t, err) -} - -func TestTriggerBatchWithEncryptedChanSuccess(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { - res.WriteHeader(200) - fmt.Fprintf(res, "{}") - assert.Equal(t, "POST", req.Method) - - _, err := ioutil.ReadAll(req.Body) - assert.Equal(t, "application/json", req.Header["Content-Type"][0]) - assert.Equal(t, "/apps/appid/batch_events", req.URL.Path) - assert.NoError(t, err) - })) - defer server.Close() - - u, _ := url.Parse(server.URL) - client := Client{AppID: "appid", Key: "key", Secret: "secret", EncryptionMasterKey: "eHPVWHg7nFGYVBsKjOFDXWRribIR2b0b", Host: u.Host} - err := client.TriggerBatch([]Event{ - {"test_channel", "test", "yolo1", nil}, - {"private-encrypted-test_channel", "test", "yolo2", nil}, - }) - assert.NoError(t, err) -} - -func TestErrorResponseHandler(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { - res.WriteHeader(400) - fmt.Fprintf(res, "Cannot retrieve the user count unless the channel is a presence channel") - - })) - defer server.Close() - - u, _ := url.Parse(server.URL) - client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host} - channelParams := map[string]string{"info": "user_count,subscription_count"} - channel, err := client.Channel("this_is_not_a_presence_channel", channelParams) - - assert.Error(t, err) - assert.EqualError(t, err, "Status Code: 400 - Cannot retrieve the user count unless the channel is a presence channel") - assert.Nil(t, channel) -} - -func TestRequestTimeouts(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { - time.Sleep(time.Second * 1) - // res.WriteHeader(200) - fmt.Fprintf(res, "{}") - })) - defer server.Close() - - u, _ := url.Parse(server.URL) - client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host, HTTPClient: &http.Client{Timeout: time.Millisecond * 100}} - err := client.Trigger("test_channel", "test", "yolo") - - assert.Error(t, err) -} - -func TestChannelLengthValidation(t *testing.T) { - channels := []string{ - "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", - "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", - "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", - "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", - "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", - "52", "53", "54", "55", "56", "57", "58", "59", "60", "61", - "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", - "72", "73", "74", "75", "76", "77", "78", "79", "80", "81", - "82", "83", "84", "85", "86", "87", "88", "89", "90", "91", - "92", "93", "94", "95", "96", "97", "98", "99", "100", "101", - } - - client := Client{AppID: "id", Key: "key", Secret: "secret"} - err := client.TriggerMulti(channels, "yolo", "woot") - - assert.EqualError(t, err, "You cannot trigger on more than 100 channels at once") -} - -func TestChannelFormatValidation(t *testing.T) { - channel1 := "w000^$$£@@@" - var channel2 string - for i := 0; i <= 202; i++ { - channel2 += "a" - } - client := Client{AppID: "id", Key: "key", Secret: "secret"} - err1 := client.Trigger(channel1, "yolo", "w00t") - - err2 := client.Trigger(channel2, "yolo", "not 19 forever") - - assert.EqualError(t, err1, "At least one of your channels' names are invalid") - - assert.EqualError(t, err2, "At least one of your channels' names are invalid") - -} - -func TestDataSizeValidation(t *testing.T) { - client := Client{AppID: "id", Key: "key", Secret: "secret"} - var data string - for i := 0; i <= 10242; i++ { - data += "a" - } - err := client.Trigger("channel", "event", data) - - assert.EqualError(t, err, "Data must be smaller than 10kb") - -} - -func TestInitialisationFromURL(t *testing.T) { - url := "http://feaf18a411d3cb9216ee:fec81108d90e1898e17a@api.pusherapp.com/apps/104060" - client, _ := ClientFromURL(url) - expectedClient := &Client{Key: "feaf18a411d3cb9216ee", Secret: "fec81108d90e1898e17a", AppID: "104060", Host: "api.pusherapp.com"} - assert.Equal(t, expectedClient, client) -} - -func TestURLInitErrorNoSecret(t *testing.T) { - url := "http://fec81108d90e1898e17a@api.pusherapp.com/apps" - client, err := ClientFromURL(url) - assert.Nil(t, client) - assert.Error(t, err) -} - -func TestURLInitHTTPS(t *testing.T) { - url := "https://key:secret@api.pusherapp.com/apps/104060" - client, _ := ClientFromURL(url) - assert.True(t, client.Secure) -} - -func TestURLInitErrorNoID(t *testing.T) { - url := "http://fec81108d90e1898e17a@api.pusherapp.com/apps" - client, err := ClientFromURL(url) - assert.Nil(t, client) - assert.Error(t, err) -} - -func TestInitialisationFromENV(t *testing.T) { - os.Setenv("PUSHER_URL", "http://feaf18a411d3cb9216ee:fec81108d90e1898e17a@api.pusherapp.com/apps/104060") - client, _ := ClientFromEnv("PUSHER_URL") - expectedClient := &Client{Key: "feaf18a411d3cb9216ee", Secret: "fec81108d90e1898e17a", AppID: "104060", Host: "api.pusherapp.com"} - assert.Equal(t, expectedClient, client) -} diff --git a/crypto.go b/crypto.go index 9cacc9f..91b0f6d 100644 --- a/crypto.go +++ b/crypto.go @@ -3,24 +3,12 @@ package pusher import ( "crypto/hmac" "crypto/md5" - "crypto/rand" "crypto/sha256" - "encoding/base64" "encoding/hex" - "encoding/json" - "errors" - "io" + // "fmt" "strings" - - "golang.org/x/crypto/nacl/secretbox" ) -// EncryptedMessage contains an encrypted message -type EncryptedMessage struct { - Nonce string `json:"nonce"` - Ciphertext string `json:"ciphertext"` -} - func hmacSignature(toSign, secret string) string { return hex.EncodeToString(hmacBytes([]byte(toSign), []byte(secret))) } @@ -34,18 +22,16 @@ func hmacBytes(toSign, secret []byte) []byte { func checkSignature(result, secret string, body []byte) bool { expected := hmacBytes(body, []byte(secret)) resultBytes, err := hex.DecodeString(result) + if err != nil { return false } return hmac.Equal(expected, resultBytes) } -func createAuthMap(key, secret, stringToSign string, sharedSecret string) map[string]string { +func createAuthMap(key, secret, stringToSign string) map[string]string { authSignature := hmacSignature(stringToSign, secret) authString := strings.Join([]string{key, authSignature}, ":") - if sharedSecret != "" { - return map[string]string{"auth": authString, "shared_secret": sharedSecret} - } return map[string]string{"auth": authString} } @@ -54,70 +40,3 @@ func md5Signature(body []byte) string { _bodyMD5.Write([]byte(body)) return hex.EncodeToString(_bodyMD5.Sum(nil)) } - -func encrypt(channel string, data []byte, encryptionKey string) string { - sharedSecret := generateSharedSecret(channel, encryptionKey) - nonce := generateNonce() - nonceB64 := base64.StdEncoding.EncodeToString(nonce[:]) - cipherText := secretbox.Seal([]byte{}, data, &nonce, &sharedSecret) - cipherTextB64 := base64.StdEncoding.EncodeToString(cipherText) - return formatMessage(nonceB64, cipherTextB64) -} - -func formatMessage(nonce string, cipherText string) string { - encryptedMessage := &EncryptedMessage{ - Nonce: nonce, - Ciphertext: cipherText, - } - json, err := json.Marshal(encryptedMessage) - if err != nil { - panic(err) - } - - return string(json) -} - -func generateNonce() [24]byte { - var nonce [24]byte - //Trick ReadFull into thinking nonce is a slice - if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil { - panic(err) - } - return nonce -} - -func generateSharedSecret(channel string, encryptionKey string) [32]byte { - return sha256.Sum256([]byte(channel + encryptionKey)) -} - -func decryptEvents(webhookData Webhook, encryptionKey string) (*Webhook, error) { - decryptedWebhooks := &Webhook{} - decryptedWebhooks.TimeMs = webhookData.TimeMs - for _, event := range webhookData.Events { - if isEncryptedChannel(event.Channel) { - var encryptedMessage EncryptedMessage - json.Unmarshal([]byte(event.Data), &encryptedMessage) - cipherTextBytes, decodePayloadErr := base64.StdEncoding.DecodeString(encryptedMessage.Ciphertext) - if decodePayloadErr != nil { - return decryptedWebhooks, decodePayloadErr - } - nonceBytes, decodeNonceErr := base64.StdEncoding.DecodeString(encryptedMessage.Nonce) - if decodeNonceErr != nil { - return decryptedWebhooks, decodeNonceErr - } - // Convert slice to fixed length array for secretbox - var nonce [24]byte - copy(nonce[:], []byte(nonceBytes[:])) - - sharedSecret := generateSharedSecret(event.Channel, encryptionKey) - box := []byte(cipherTextBytes) - decryptedBox, ok := secretbox.Open([]byte{}, box, &nonce, &sharedSecret) - if !ok { - return decryptedWebhooks, errors.New("Failed to decrypt event, possibly wrong key?") - } - event.Data = string(decryptedBox) - } - decryptedWebhooks.Events = append(decryptedWebhooks.Events, event) - } - return decryptedWebhooks, nil -} diff --git a/crypto_test.go b/crypto_test.go deleted file mode 100644 index 4019d59..0000000 --- a/crypto_test.go +++ /dev/null @@ -1,153 +0,0 @@ -package pusher - -import ( - "encoding/hex" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestHmacSignature(t *testing.T) { - expected := "64e3f44166575febbc5de88c9476325ea7d4b3684752158d9fdb31fce34b980d" - toSign := "Hello!" - secret := "supersecret" - hmac := hmacSignature(toSign, secret) - assert.Equal(t, hmac, expected) -} - -func TestHmacBytes(t *testing.T) { - expectedHex := "64e3f44166575febbc5de88c9476325ea7d4b3684752158d9fdb31fce34b980d" - expectedBytes, _ := hex.DecodeString(expectedHex) - toSign := "Hello!" - secret := "supersecret" - hmacBytes := hmacBytes([]byte(toSign), []byte(secret)) - assert.Equal(t, hmacBytes, expectedBytes) -} - -func TestCheckValidSignature(t *testing.T) { - signature := "64e3f44166575febbc5de88c9476325ea7d4b3684752158d9fdb31fce34b980d" - secret := "supersecret" - body := "Hello!" - validSignature := checkSignature(signature, secret, []byte(body)) - assert.Equal(t, validSignature, true) -} - -func TestCheckInvalidSignature(t *testing.T) { - signature := "no" - secret := "supersecret" - body := "Hello!" - validSignature := checkSignature(signature, secret, []byte(body)) - assert.Equal(t, validSignature, false) -} - -func TestCreateAuthMapNoE2E(t *testing.T) { - signature := "64e3f44166575febbc5de88c9476325ea7d4b3684752158d9fdb31fce34b980d" - key := "key" - secret := "supersecret" - stringToSign := "Hello!" - sharedSecret := "" - authMap := createAuthMap(key, secret, stringToSign, sharedSecret) - // The [4:] here removes the prefix of key: from the string. - assert.Equal(t, authMap["auth"][4:], signature) - assert.Equal(t, authMap["shared_secret"], "") -} - -func TestCreateAuthMapE2E(t *testing.T) { - signature := "64e3f44166575febbc5de88c9476325ea7d4b3684752158d9fdb31fce34b980d" - key := "key" - secret := "supersecret" - stringToSign := "Hello!" - sharedSecret := "This is a string that is 32 chars" - authMap := createAuthMap(key, secret, stringToSign, sharedSecret) - // The [4:] here removes the prefix of key: from the string. - assert.Equal(t, authMap["auth"][4:], signature) - assert.Equal(t, authMap["shared_secret"], sharedSecret) -} - -func TestMD5Signature(t *testing.T) { - expected := "952d2c56d0485958336747bcdd98590d" - actual := md5Signature([]byte("Hello!")) - assert.Equal(t, expected, actual) -} - -func TestEncrypt(t *testing.T) { - channel := "private-encrypted-bla" - body := []byte("Hello!") - encryptionKey := "This is a string that is 32 chars" - cipherText := encrypt(channel, body, encryptionKey) - assert.NotNil(t, cipherText) - assert.NotEqual(t, cipherText, body) -} - -func TestFormatMessage(t *testing.T) { - nonce := "a" - cipherText := "b" - formatted := formatMessage(nonce, cipherText) - assert.Equal(t, formatted, "{\"nonce\":\"a\",\"ciphertext\":\"b\"}") -} - -func TestGenerateSharedSecret(t *testing.T) { - channel := "private-encrypted-bla" - encryptionKey := "This is a string that is 32 chars" - sharedSecret := generateSharedSecret(channel, encryptionKey) - t.Log(hex.EncodeToString(sharedSecret[:])) - expected := "004831f99d2a4e86723e893caded3a2897deeddbed9514fe9497dcddc52bd50b" - assert.Equal(t, hex.EncodeToString(sharedSecret[:]), expected) -} - -func TestDecryptValidKey(t *testing.T) { - channel := "private-encrypted-bla" - plaintext := "Hello!" - cipherText := "{\"nonce\":\"sjklahvpWWQgAjTx5FfYHCCxd2AmaL9T\",\"ciphertext\":\"zoDEe8dA3nDXKsybAWce/hXGW4szJw==\"}" - encryptionKey := "This is a string that is 32 chars" - - encryptedWebhookData := &Webhook{ - TimeMs: 1, - Events: []WebhookEvent{ - WebhookEvent{ - Name: "event", - Channel: channel, - Event: "event", - Data: cipherText, - SocketID: "44610.7511910", - }, - }, - } - - expectedWebhookData := &Webhook{ - TimeMs: 1, - Events: []WebhookEvent{ - WebhookEvent{ - Name: "event", - Channel: channel, - Event: "event", - Data: plaintext, - SocketID: "44610.7511910", - }, - }, - } - decryptedWebhooks, _ := decryptEvents(*encryptedWebhookData, encryptionKey) - assert.Equal(t, expectedWebhookData, decryptedWebhooks) -} - -func TestDecryptInvalidKey(t *testing.T) { - channel := "private-encrypted-bla" - cipherText := "{\"nonce\":\"sjklahvpWWQgAjTx5FfYHCCxd2AmaL9T\",\"ciphertext\":\"zoDEe8dA3nDXKsybAWce/hXGW4szJw==\"}" - encryptionKey := "This is an invalid key 32 chars!!" - - encryptedWebhookData := &Webhook{ - TimeMs: 1, - Events: []WebhookEvent{ - WebhookEvent{ - Name: "event", - Channel: channel, - Event: "event", - Data: cipherText, - SocketID: "44610.7511910", - }, - }, - } - decryptedWebhooks, err := decryptEvents(*encryptedWebhookData, encryptionKey) - assert.Equal(t, decryptedWebhooks.Events, []WebhookEvent(nil)) - assert.EqualError(t, err, "Failed to decrypt event, possibly wrong key?") -} diff --git a/encoder.go b/encoder.go index 3f642d3..d6f4b5c 100644 --- a/encoder.go +++ b/encoder.go @@ -26,17 +26,13 @@ type eventPayload struct { SocketID *string `json:"socket_id,omitempty"` } -func encodeTriggerBody(channels []string, event string, data interface{}, socketID *string, encryptionKey string) ([]byte, error) { +func encodeTriggerBody(channels []string, event string, data interface{}, socketID *string) ([]byte, error) { dataBytes, err := encodeEventData(data) if err != nil { return nil, err } - var payloadData string - if isEncryptedChannel(channels[0]) { - payloadData = encrypt(channels[0], dataBytes, encryptionKey) - } else { - payloadData = string(dataBytes) - } + + payloadData := string(dataBytes) if len(payloadData) > maxEventPayloadSize { return nil, errors.New("Data must be smaller than 10kb") } @@ -48,19 +44,15 @@ func encodeTriggerBody(channels []string, event string, data interface{}, socket }) } -func encodeTriggerBatchBody(batch []Event, encryptionKey string) ([]byte, error) { +func encodeTriggerBatchBody(batch []Event) ([]byte, error) { batchEvents := make([]batchEvent, len(batch)) for idx, e := range batch { - var stringifyedDataBytes string dataBytes, err := encodeEventData(e.Data) if err != nil { return nil, err } - if isEncryptedChannel(e.Channel) { - stringifyedDataBytes = encrypt(e.Channel, dataBytes, encryptionKey) - } else { - stringifyedDataBytes = string(dataBytes) - } + + stringifyedDataBytes := string(dataBytes) if len(stringifyedDataBytes) > maxEventPayloadSize { return nil, fmt.Errorf("Data of the event #%d in batch, must be smaller than 10kb", idx) } diff --git a/request_url_test.go b/request_url_test.go deleted file mode 100644 index 4d57add..0000000 --- a/request_url_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package pusher - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestTriggerRequestUrl(t *testing.T) { - expected := "http://api.pusherapp.com/apps/3/events?auth_key=278d425bdf160c739803&auth_signature=da454824c97ba181a32ccc17a72625ba02771f50b50e1e7430e47a1f3f457e6c&auth_timestamp=1353088179&auth_version=1.0&body_md5=ec365a775a4cd0599faeb73354201b6f" - payload := []byte("{\"name\":\"foo\",\"channels\":[\"project-3\"],\"data\":\"{\\\"some\\\":\\\"data\\\"}\"}") - result, _ := createRequestURL("POST", "", "/apps/3/events", "278d425bdf160c739803", "7ad3773142a6692b25b8", "1353088179", false, payload, nil, "") - assert.Equal(t, expected, result) -} - -func TestBuildClusterTriggerUrl(t *testing.T) { - expected := "http://api-eu.pusher.com/apps/3/events?auth_key=278d425bdf160c739803&auth_signature=da454824c97ba181a32ccc17a72625ba02771f50b50e1e7430e47a1f3f457e6c&auth_timestamp=1353088179&auth_version=1.0&body_md5=ec365a775a4cd0599faeb73354201b6f" - payload := []byte("{\"name\":\"foo\",\"channels\":[\"project-3\"],\"data\":\"{\\\"some\\\":\\\"data\\\"}\"}") - result, _ := createRequestURL("POST", "", "/apps/3/events", "278d425bdf160c739803", "7ad3773142a6692b25b8", "1353088179", false, payload, nil, "eu") - assert.Equal(t, expected, result) -} - -func TestBuildCustomHostTriggerUrl(t *testing.T) { - expected := "http://my.server.com/apps/3/events?auth_key=278d425bdf160c739803&auth_signature=da454824c97ba181a32ccc17a72625ba02771f50b50e1e7430e47a1f3f457e6c&auth_timestamp=1353088179&auth_version=1.0&body_md5=ec365a775a4cd0599faeb73354201b6f" - payload := []byte("{\"name\":\"foo\",\"channels\":[\"project-3\"],\"data\":\"{\\\"some\\\":\\\"data\\\"}\"}") - result, _ := createRequestURL("POST", "my.server.com", "/apps/3/events", "278d425bdf160c739803", "7ad3773142a6692b25b8", "1353088179", false, payload, nil, "") - assert.Equal(t, expected, result) -} - -func TestTriggerSecureRequestUrl(t *testing.T) { - expected := "https://api.pusherapp.com/apps/3/events?auth_key=278d425bdf160c739803&auth_signature=da454824c97ba181a32ccc17a72625ba02771f50b50e1e7430e47a1f3f457e6c&auth_timestamp=1353088179&auth_version=1.0&body_md5=ec365a775a4cd0599faeb73354201b6f" - payload := []byte("{\"name\":\"foo\",\"channels\":[\"project-3\"],\"data\":\"{\\\"some\\\":\\\"data\\\"}\"}") - result, _ := createRequestURL("POST", "", "/apps/3/events", "278d425bdf160c739803", "7ad3773142a6692b25b8", "1353088179", true, payload, nil, "") - assert.Equal(t, expected, result) -} - -func TestGetAllChannelsUrl(t *testing.T) { - expected := "http://api.pusherapp.com/apps/102015/channels?auth_key=d41a439c438a100756f5&auth_signature=4d8a02edcc8a758b0162cd6da690a9a45fb8ae326a276dca1e06a0bc42796c11&auth_timestamp=1427034994&auth_version=1.0&filter_by_prefix=presence-&info=user_count" - additionalQueries := map[string]string{"filter_by_prefix": "presence-", "info": "user_count"} - result, _ := createRequestURL("GET", "", "/apps/102015/channels", "d41a439c438a100756f5", "4bf35003e819bb138249", "1427034994", false, nil, additionalQueries, "") - assert.Equal(t, expected, result) -} - -func TestGetAllChannelsWithOneAdditionalParamUrl(t *testing.T) { - expected := "http://api.pusherapp.com/apps/102015/channels?auth_key=d41a439c438a100756f5&auth_signature=b540383af4582af5fbb5df7be5472d54bd0838c9c2021c7743062568839e6f97&auth_timestamp=1427036577&auth_version=1.0&filter_by_prefix=presence-" - additionalQueries := map[string]string{"filter_by_prefix": "presence-"} - result, _ := createRequestURL("GET", "", "/apps/102015/channels", "d41a439c438a100756f5", "4bf35003e819bb138249", "1427036577", false, nil, additionalQueries, "") - assert.Equal(t, expected, result) -} - -func TestGetAllChannelsWithNoParamsUrl(t *testing.T) { - expected := "http://api.pusherapp.com/apps/102015/channels?auth_key=d41a439c438a100756f5&auth_signature=df89248f87f6e6d028925e0b04d60f316527a865992ace6936afa91281d8bef0&auth_timestamp=1427036787&auth_version=1.0" - additionalQueries := map[string]string{} - result, _ := createRequestURL("GET", "", "/apps/102015/channels", "d41a439c438a100756f5", "4bf35003e819bb138249", "1427036787", false, nil, additionalQueries, "") - assert.Equal(t, expected, result) -} - -func TestGetChannelUrl(t *testing.T) { - expected := "http://api.pusherapp.com/apps/102015/channels/presence-session-d41a439c438a100756f5-4bf35003e819bb138249-ROpCFmgFhXY?auth_key=d41a439c438a100756f5&auth_signature=f93ceb31f396aef336226efe512aaf339bd5e39c7c2c04b81cc8681dc16ee785&auth_timestamp=1427053326&auth_version=1.0&info=user_count,subscription_count" - additionalQueries := map[string]string{"info": "user_count,subscription_count"} - result, _ := createRequestURL("GET", "", "/apps/102015/channels/presence-session-d41a439c438a100756f5-4bf35003e819bb138249-ROpCFmgFhXY", "d41a439c438a100756f5", "4bf35003e819bb138249", "1427053326", false, nil, additionalQueries, "") - assert.Equal(t, expected, result) -} - -func TestGetUsersUrl(t *testing.T) { - expected := "http://api.pusherapp.com/apps/102015/channels/presence-session-d41a439c438a100756f5-4bf35003e819bb138249-nYJLy67qh52/users?auth_key=d41a439c438a100756f5&auth_signature=207feaf4e8efeb24e5f148011704251bf90e2059a5f97a3eb52d06178b11feca&auth_timestamp=1427053709&auth_version=1.0" - result, _ := createRequestURL("GET", "", "/apps/102015/channels/presence-session-d41a439c438a100756f5-4bf35003e819bb138249-nYJLy67qh52/users", "d41a439c438a100756f5", "4bf35003e819bb138249", "1427053709", false, nil, nil, "") - assert.Equal(t, expected, result) -} - -func TestBrokenUrl(t *testing.T) { - result, err := createRequestURL("GET", "", "#%$)(!foo", "d41a439c438a100756f5", "4bf35003e819bb138249", "1427053709", false, nil, nil, "") - assert.Error(t, err) - assert.Equal(t, "", result) -} diff --git a/response_parsing_test.go b/response_parsing_test.go deleted file mode 100644 index 836700a..0000000 --- a/response_parsing_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package pusher - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestParsingChannelsList(t *testing.T) { - testJSON := []byte("{\"channels\":{\"presence-session-d41a439c438a100756f5-4bf35003e819bb138249-5cbTiUiPNGI\":{\"user_count\":1},\"presence-session-d41a439c438a100756f5-4bf35003e819bb138249-PbZ5E1pP8uF\":{\"user_count\":1},\"presence-session-d41a439c438a100756f5-4bf35003e819bb138249-oz6iqpSxMwG\":{\"user_count\":1}}}") - expected := &ChannelsList{ - Channels: map[string]ChannelListItem{ - "presence-session-d41a439c438a100756f5-4bf35003e819bb138249-5cbTiUiPNGI": ChannelListItem{UserCount: 1}, - "presence-session-d41a439c438a100756f5-4bf35003e819bb138249-PbZ5E1pP8uF": ChannelListItem{UserCount: 1}, - "presence-session-d41a439c438a100756f5-4bf35003e819bb138249-oz6iqpSxMwG": ChannelListItem{UserCount: 1}, - }, - } - result, err := unmarshalledChannelsList(testJSON) - assert.Equal(t, expected, result) - assert.NoError(t, err) -} - -func TestParsingChannel(t *testing.T) { - testJSON := []byte("{\"user_count\":1,\"occupied\":true,\"subscription_count\":1}") - channelName := "test" - expected := &Channel{ - Name: channelName, - Occupied: true, - UserCount: 1, - SubscriptionCount: 1, - } - result, err := unmarshalledChannel(testJSON, channelName) - assert.Equal(t, expected, result) - assert.NoError(t, err) - -} - -func TestParsingChannelUsers(t *testing.T) { - testJSON := []byte("{\"users\":[{\"id\":\"red\"},{\"id\":\"blue\"}]}") - expected := &Users{ - List: []User{User{ID: "red"}, User{ID: "blue"}}, - } - result, err := unmarshalledChannelUsers(testJSON) - assert.Equal(t, expected, result) - assert.NoError(t, err) - -} - -func TestParserError(t *testing.T) { - testJSON := []byte("[];;[[p{{}}{{{}[][][]@£$@") - _, err := unmarshalledChannelsList(testJSON) - assert.Error(t, err) -} diff --git a/util.go b/util.go index d1dce46..0420c56 100644 --- a/util.go +++ b/util.go @@ -5,7 +5,6 @@ import ( "net/url" "regexp" "strconv" - "strings" "time" ) @@ -47,17 +46,6 @@ func channelsAreValid(channels []string) bool { return true } -func isEncryptedChannel(channel string) bool { - if strings.HasPrefix(channel, "private-encrypted-") { - return true - } - return false -} - -func validEncryptionKey(encryptionKey string) bool { - return len(encryptionKey) == 32 -} - func validateSocketID(socketID *string) (err error) { if (socketID == nil) || socketIDValidationRegex.MatchString(*socketID) { return diff --git a/util_test.go b/util_test.go deleted file mode 100644 index 7429ed1..0000000 --- a/util_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package pusher - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestParseAuthRequestParamsNoSock(t *testing.T) { - params := "channel_name=hello" - _, _, result := parseAuthRequestParams([]byte(params)) - assert.Error(t, result) - assert.EqualError(t, result, "Socket_id not found") -} - -func TestParseAuthRequestParamsNoChan(t *testing.T) { - params := "socket_id=45.3" - _, _, result := parseAuthRequestParams([]byte(params)) - assert.Error(t, result) - assert.EqualError(t, result, "Channel param not found") -} - -func TestInvalidAuthParams(t *testing.T) { - params := "%$@£$${}$£%|$^%$^|" - _, _, result := parseAuthRequestParams([]byte(params)) - assert.Error(t, result) -} diff --git a/webhook_test.go b/webhook_test.go deleted file mode 100644 index bf87cea..0000000 --- a/webhook_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package pusher - -import ( - "net/http" - "testing" - - "github.com/stretchr/testify/assert" -) - -func setUpClient() Client { - return Client{AppID: "id", Key: "key", Secret: "secret"} -} - -func TestClientWebhookValidation(t *testing.T) { - client := setUpClient() - header := make(http.Header) - header["X-Pusher-Key"] = []string{"key"} - header["X-Pusher-Signature"] = []string{"2677ad3e7c090b2fa2c0fb13020d66d5420879b8316eb356a2d60fb9073bc778"} - body := []byte("{\"hello\":\"world\"}") - webhook, err := client.Webhook(header, body) - assert.NotNil(t, webhook) - assert.Nil(t, err) -} - -func TestWebhookImproperKeyCase(t *testing.T) { - client := setUpClient() - badHeader := make(http.Header) - badHeader["X-Pusher-Key"] = []string{"narr you're going down!"} - badHeader["X-Pusher-Signature"] = []string{"2677ad3e7c090b2fa2c0fb13020d66d5420879b8316eb356a2d60fb9073bc778"} - badBody := []byte("{\"hello\":\"world\"}") - - badWebhook, err := client.Webhook(badHeader, badBody) - assert.Nil(t, badWebhook) - assert.Error(t, err) -} - -func TestWebhookImproperSignatureCase(t *testing.T) { - client := setUpClient() - badHeader := make(http.Header) - badHeader["X-Pusher-Key"] = []string{"key"} - badHeader["X-Pusher-Signature"] = []string{"2677ad3e7c090i'mgonnagetyaeb356a2d60fb9073bc778"} - badBody := []byte("{\"hello\":\"world\"}") - - badWebhook, err := client.Webhook(badHeader, badBody) - assert.Nil(t, badWebhook) - assert.Error(t, err) -} - -func TestWebhookNoSignature(t *testing.T) { - client := setUpClient() - badHeader := make(http.Header) - badHeader["X-Pusher-Key"] = []string{"key"} - badBody := []byte("{\"hello\":\"world\"}") - - badWebhook, err := client.Webhook(badHeader, badBody) - assert.Nil(t, badWebhook) - assert.Error(t, err) -} - -func TestWebhookUnmarshalling(t *testing.T) { - body := []byte("{\"time_ms\":1427233518933,\"events\":[{\"name\":\"client_event\",\"channel\":\"private-channel\",\"event\":\"client-yolo\",\"data\":\"{\\\"yolo\\\":\\\"woot\\\"}\",\"socket_id\":\"44610.7511910\"}]}") - result, err := unmarshalledWebhook(body) - expected := &Webhook{ - TimeMs: 1427233518933, - Events: []WebhookEvent{ - WebhookEvent{ - Name: "client_event", - Channel: "private-channel", - Event: "client-yolo", - Data: "{\"yolo\":\"woot\"}", - SocketID: "44610.7511910", - }, - }, - } - - assert.Equal(t, expected, result) - assert.NoError(t, err) -}