mirror of
https://github.com/retailcrm/mg-transport-core.git
synced 2024-11-25 14:46:02 +03:00
One-step connection middlewares (#34)
This commit is contained in:
parent
65f0ecfa6a
commit
b0d5488f5a
@ -34,6 +34,7 @@ type InfoInterface interface {
|
|||||||
GetName() string
|
GetName() string
|
||||||
GetCode() string
|
GetCode() string
|
||||||
GetLogoPath() string
|
GetLogoPath() string
|
||||||
|
GetSecret() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config struct.
|
// Config struct.
|
||||||
@ -195,6 +196,11 @@ func (t Info) GetLogoPath() string {
|
|||||||
return t.LogoPath
|
return t.LogoPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSecret returns secret.
|
||||||
|
func (t Info) GetSecret() string {
|
||||||
|
return t.Secret
|
||||||
|
}
|
||||||
|
|
||||||
// IsSSLVerificationEnabled returns SSL verification flag (default is true).
|
// IsSSLVerificationEnabled returns SSL verification flag (default is true).
|
||||||
func (h *HTTPClientConfig) IsSSLVerificationEnabled() bool {
|
func (h *HTTPClientConfig) IsSSLVerificationEnabled() bool {
|
||||||
if h.SSLVerification == nil {
|
if h.SSLVerification == nil {
|
||||||
|
50
core/one_step_connection.go
Normal file
50
core/one_step_connection.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
retailcrm "github.com/retailcrm/api-client-go/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConnectionConfig returns middleware for the one-step connection configuration route.
|
||||||
|
func ConnectionConfig(registerURL string, scopes []string) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
c.AbortWithStatusJSON(http.StatusOK, retailcrm.ConnectionConfigResponse{
|
||||||
|
SuccessfulResponse: retailcrm.SuccessfulResponse{Success: true},
|
||||||
|
Scopes: scopes,
|
||||||
|
RegisterURL: registerURL,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyConnectRequest will verify ConnectRequest and place it into the "request" context field.
|
||||||
|
func VerifyConnectRequest(secret string) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
connectReqData := c.PostForm("register")
|
||||||
|
if connectReqData == "" {
|
||||||
|
c.AbortWithStatusJSON(http.StatusOK, retailcrm.ErrorResponse{ErrorMessage: "No data provided"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var r retailcrm.ConnectRequest
|
||||||
|
err := json.Unmarshal([]byte(connectReqData), &r)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusOK, retailcrm.ErrorResponse{ErrorMessage: "Invalid JSON provided"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.Verify(secret) {
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("request", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGetConnectRequest will extract retailcrm.ConnectRequest from the request context.
|
||||||
|
func MustGetConnectRequest(c *gin.Context) retailcrm.ConnectRequest {
|
||||||
|
return c.MustGet("request").(retailcrm.ConnectRequest)
|
||||||
|
}
|
166
core/one_step_connection_test.go
Normal file
166
core/one_step_connection_test.go
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
retailcrm "github.com/retailcrm/api-client-go/v2"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMustGetConnectRequest_Success(t *testing.T) {
|
||||||
|
assert.Equal(t, retailcrm.ConnectRequest{}, MustGetConnectRequest(&gin.Context{
|
||||||
|
Keys: map[string]interface{}{
|
||||||
|
"request": retailcrm.ConnectRequest{},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMustGetConnectRequest_Failure(t *testing.T) {
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
MustGetConnectRequest(&gin.Context{
|
||||||
|
Keys: map[string]interface{}{},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
MustGetConnectRequest(&gin.Context{})
|
||||||
|
})
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
MustGetConnectRequest(nil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnectionConfig(t *testing.T) {
|
||||||
|
scopes := []string{
|
||||||
|
"integration_read",
|
||||||
|
"integration_write",
|
||||||
|
}
|
||||||
|
registerURL := "https://example.com"
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
g := gin.New()
|
||||||
|
g.GET("/", ConnectionConfig(registerURL, scopes))
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
g.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
var cc retailcrm.ConnectionConfigResponse
|
||||||
|
require.NoError(t, json.NewDecoder(rr.Body).Decode(&cc))
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, rr.Code)
|
||||||
|
assert.Equal(t, retailcrm.ConnectionConfigResponse{
|
||||||
|
SuccessfulResponse: retailcrm.SuccessfulResponse{
|
||||||
|
Success: true,
|
||||||
|
},
|
||||||
|
Scopes: scopes,
|
||||||
|
RegisterURL: registerURL,
|
||||||
|
}, cc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyConnectRequest_NoData(t *testing.T) {
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
g := gin.New()
|
||||||
|
g.POST("/", VerifyConnectRequest("secret"))
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, "/", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
g.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
var resp retailcrm.ErrorResponse
|
||||||
|
require.NoError(t, json.NewDecoder(rr.Body).Decode(&resp))
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, rr.Code)
|
||||||
|
assert.Equal(t, retailcrm.ErrorResponse{ErrorMessage: "No data provided"}, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyConnectRequest_InvalidJSON(t *testing.T) {
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
g := gin.New()
|
||||||
|
g.POST("/", VerifyConnectRequest("secret"))
|
||||||
|
|
||||||
|
req, err := http.NewRequest(
|
||||||
|
http.MethodPost, "/", strings.NewReader(url.Values{"register": {"invalid json"}}.Encode()))
|
||||||
|
require.NoError(t, err)
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
g.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
var resp retailcrm.ErrorResponse
|
||||||
|
require.NoError(t, json.NewDecoder(rr.Body).Decode(&resp))
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, rr.Code)
|
||||||
|
assert.Equal(t, retailcrm.ErrorResponse{ErrorMessage: "Invalid JSON provided"}, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyConnectRequest_InvalidToken(t *testing.T) {
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
g := gin.New()
|
||||||
|
g.POST("/", VerifyConnectRequest("secret"))
|
||||||
|
|
||||||
|
data, err := json.Marshal(retailcrm.ConnectRequest{
|
||||||
|
Token: "token",
|
||||||
|
APIKey: "key",
|
||||||
|
URL: "https://example.com",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(
|
||||||
|
http.MethodPost, "/", strings.NewReader(url.Values{"register": {string(data)}}.Encode()))
|
||||||
|
require.NoError(t, err)
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
g.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusUnauthorized, rr.Code)
|
||||||
|
assert.Equal(t, "{}", rr.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyConnectRequest_OK(t *testing.T) {
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
g := gin.New()
|
||||||
|
g.POST("/", VerifyConnectRequest("secret"), func(c *gin.Context) {
|
||||||
|
_ = MustGetConnectRequest(c)
|
||||||
|
c.AbortWithStatus(http.StatusCreated)
|
||||||
|
})
|
||||||
|
|
||||||
|
data, err := json.Marshal(retailcrm.ConnectRequest{
|
||||||
|
Token: createConnectToken("key", "secret"),
|
||||||
|
APIKey: "key",
|
||||||
|
URL: "https://example.com",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(
|
||||||
|
http.MethodPost, "/", strings.NewReader(url.Values{"register": {string(data)}}.Encode()))
|
||||||
|
require.NoError(t, err)
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
g.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusCreated, rr.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createConnectToken(apiKey, secret string) string {
|
||||||
|
mac := hmac.New(sha256.New, []byte(secret))
|
||||||
|
if _, err := mac.Write([]byte(apiKey)); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return hex.EncodeToString(mac.Sum(nil))
|
||||||
|
}
|
2
go.mod
2
go.mod
@ -27,7 +27,7 @@ require (
|
|||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/retailcrm/api-client-go/v2 v2.0.1
|
github.com/retailcrm/api-client-go/v2 v2.0.3
|
||||||
github.com/retailcrm/mg-transport-api-client-go v1.1.32
|
github.com/retailcrm/mg-transport-api-client-go v1.1.32
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/ugorji/go v1.2.6 // indirect
|
github.com/ugorji/go v1.2.6 // indirect
|
||||||
|
4
go.sum
4
go.sum
@ -251,6 +251,10 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R
|
|||||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||||
github.com/retailcrm/api-client-go/v2 v2.0.1 h1:wyM0F1VTSJPO8PVEXB0u7s6ZEs0xRCnUu7YdQ1E4UZ8=
|
github.com/retailcrm/api-client-go/v2 v2.0.1 h1:wyM0F1VTSJPO8PVEXB0u7s6ZEs0xRCnUu7YdQ1E4UZ8=
|
||||||
github.com/retailcrm/api-client-go/v2 v2.0.1/go.mod h1:1yTZl9+gd3+/k0kAJe7sYvC+mL4fqMwIwtnSgSWZlkQ=
|
github.com/retailcrm/api-client-go/v2 v2.0.1/go.mod h1:1yTZl9+gd3+/k0kAJe7sYvC+mL4fqMwIwtnSgSWZlkQ=
|
||||||
|
github.com/retailcrm/api-client-go/v2 v2.0.2 h1:oFQycGqwcvfgW2JrbeWmPjxH7Wmh9j762c4FRxCDGNs=
|
||||||
|
github.com/retailcrm/api-client-go/v2 v2.0.2/go.mod h1:1yTZl9+gd3+/k0kAJe7sYvC+mL4fqMwIwtnSgSWZlkQ=
|
||||||
|
github.com/retailcrm/api-client-go/v2 v2.0.3 h1:7oKwOZgRLM7eEJUvFNhzfnyIJVomy80ffOEBdYpQRIs=
|
||||||
|
github.com/retailcrm/api-client-go/v2 v2.0.3/go.mod h1:1yTZl9+gd3+/k0kAJe7sYvC+mL4fqMwIwtnSgSWZlkQ=
|
||||||
github.com/retailcrm/mg-transport-api-client-go v1.1.32 h1:IBPltSoD5q2PPZJbNC/prK5F9rEVPXVx/ZzDpi7HKhs=
|
github.com/retailcrm/mg-transport-api-client-go v1.1.32 h1:IBPltSoD5q2PPZJbNC/prK5F9rEVPXVx/ZzDpi7HKhs=
|
||||||
github.com/retailcrm/mg-transport-api-client-go v1.1.32/go.mod h1:AWV6BueE28/6SCoyfKURTo4lF0oXYoOKmHTzehd5vAI=
|
github.com/retailcrm/mg-transport-api-client-go v1.1.32/go.mod h1:AWV6BueE28/6SCoyfKURTo4lF0oXYoOKmHTzehd5vAI=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
Loading…
Reference in New Issue
Block a user