Signature for /sink requests
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/internal/auth"
|
||||
"github.com/cortezaproject/corteza-server/pkg/cli"
|
||||
)
|
||||
|
||||
// Will perform OpenID connect auto-configuration
|
||||
func Sink(ctx context.Context, c *cli.Config) *cobra.Command {
|
||||
var (
|
||||
expires string
|
||||
origin string
|
||||
contentType string
|
||||
method string
|
||||
)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "sink",
|
||||
Short: "Sink",
|
||||
}
|
||||
|
||||
signatureCmd := &cobra.Command{
|
||||
Use: "signature",
|
||||
Short: "Creates signature for sink HTTP endpoint",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
c.InitServices(ctx, c)
|
||||
|
||||
method = strings.ToUpper(method)
|
||||
|
||||
if expires != "" {
|
||||
// validate expiration date if set
|
||||
if _, err := time.Parse("2006-01-02", expires); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("sign", auth.DefaultSigner.Sign(0, method, "/sink", contentType, origin, expires))
|
||||
v.Set("expires", expires)
|
||||
v.Set("content-type", contentType)
|
||||
v.Set("origin", origin)
|
||||
v.Set("method", method)
|
||||
|
||||
// @todo add host & schema
|
||||
cmd.Println((&url.URL{
|
||||
Path: "/sink",
|
||||
RawQuery: v.Encode()}).String())
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
signatureCmd.Flags().StringVar(
|
||||
&origin,
|
||||
"origin",
|
||||
"",
|
||||
"Origin of the request (arbitrary string, optional)")
|
||||
|
||||
signatureCmd.Flags().StringVar(
|
||||
&contentType,
|
||||
"content-type",
|
||||
"",
|
||||
"Content type (optional)")
|
||||
|
||||
signatureCmd.Flags().StringVar(
|
||||
&expires,
|
||||
"expires",
|
||||
"",
|
||||
"Date of expiration (YYYY-MM-DD, optional)")
|
||||
|
||||
signatureCmd.Flags().StringVar(
|
||||
&method,
|
||||
"method",
|
||||
"GET",
|
||||
"HTTP method that will be used")
|
||||
|
||||
cmd.AddCommand(
|
||||
signatureCmd,
|
||||
)
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -80,8 +80,8 @@ func (svc automationRunner) findMailScripts(headers types.MailMessageHeader) aut
|
||||
|
||||
ss, _ := svc.scriptFinder.FindRunnableScripts("system:mail", "onReceive", mailTrigger.MakeChecker(headers, uev)).
|
||||
Filter(func(script *automation.Script) (bool, error) {
|
||||
// Filter out user-agent scripts
|
||||
return !script.RunInUA, nil
|
||||
// Filter out user-agent scripts && scripts w/o defined runner.
|
||||
return !script.RunInUA && script.RunAsDefined(), nil
|
||||
})
|
||||
|
||||
return ss
|
||||
|
||||
@@ -30,10 +30,10 @@ const (
|
||||
|
||||
func Sink() *sink {
|
||||
return &sink{
|
||||
logger: DefaultLogger,
|
||||
proc: map[string]sinkContentProc{
|
||||
SinkContentTypeMail: Mailproc(),
|
||||
},
|
||||
logger: DefaultLogger,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,18 +14,19 @@ func MountRoutes(r chi.Router) {
|
||||
r.Group(func(r chi.Router) {
|
||||
handlers.NewAuth((Auth{}).New()).MountRoutes(r)
|
||||
handlers.NewAuthInternal((AuthInternal{}).New()).MountRoutes(r)
|
||||
|
||||
// A special case that, we do not add this through standard request, handlers & controllers
|
||||
// combo but directly -- we need access to r.Body
|
||||
r.Handle("/sink", &Sink{
|
||||
svc: service.DefaultSink,
|
||||
sign: auth.DefaultSigner,
|
||||
})
|
||||
})
|
||||
|
||||
// Protect all _private_ routes
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(auth.MiddlewareValidOnly)
|
||||
|
||||
// A special case that, we do not add this through standard request, handlers & controllers
|
||||
// combo but directly -- we need access to r.Body
|
||||
r.Handle("/sink", &Sink{
|
||||
svc: service.DefaultSink,
|
||||
})
|
||||
|
||||
handlers.NewUser(User{}.New()).MountRoutes(r)
|
||||
handlers.NewRole(Role{}.New()).MountRoutes(r)
|
||||
handlers.NewOrganisation(Organisation{}.New()).MountRoutes(r)
|
||||
|
||||
+43
-11
@@ -5,9 +5,11 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/internal/auth"
|
||||
"github.com/cortezaproject/corteza-server/system/internal/service"
|
||||
)
|
||||
|
||||
@@ -18,36 +20,66 @@ type Sink struct {
|
||||
svc interface {
|
||||
Process(context.Context, string, io.Reader) error
|
||||
}
|
||||
|
||||
sign auth.Signer
|
||||
}
|
||||
|
||||
func (ctrl *Sink) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
cType = r.URL.Query().Get("content-type")
|
||||
ctx = r.Context()
|
||||
|
||||
// What are we getting + part of the signature
|
||||
contentType = r.URL.Query().Get("content-type")
|
||||
|
||||
//
|
||||
sign = r.URL.Query().Get("sign")
|
||||
origin = r.URL.Query().Get("origin")
|
||||
expires = r.URL.Query().Get("expires")
|
||||
method = strings.ToUpper(r.Method)
|
||||
|
||||
unsupported = func() {
|
||||
http.Error(w, "unsupported content-type", http.StatusBadRequest)
|
||||
}
|
||||
)
|
||||
|
||||
if cType == "" {
|
||||
// If content-type not explicitly set (via QS),
|
||||
// try to get it from the headers
|
||||
cType = r.Header.Get("content-type")
|
||||
if i := strings.Index(cType, ";"); i > 0 {
|
||||
// intentionally > 0
|
||||
cType = cType[0 : i-1]
|
||||
if sign == "" {
|
||||
http.Error(w, "signature missing", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if ctrl.sign.Verify(sign, 0, method, "/sink", contentType, origin, expires) {
|
||||
http.Error(w, "invalid signature", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if expires != "" {
|
||||
if exp, err := time.Parse("2006-01-02", expires); err != nil {
|
||||
http.Error(w, "could not process expiration date", http.StatusInternalServerError)
|
||||
return
|
||||
} else if exp.Before(time.Now()) {
|
||||
http.Error(w, "signature expired", http.StatusGone)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if cType == "" {
|
||||
if contentType == "" {
|
||||
// If content-type not explicitly set (via QS),
|
||||
// try to get it from the headers
|
||||
contentType = r.Header.Get("content-type")
|
||||
if i := strings.Index(contentType, ";"); i > 0 {
|
||||
// intentionally > 0
|
||||
contentType = contentType[0 : i-1]
|
||||
}
|
||||
}
|
||||
|
||||
if contentType == "" {
|
||||
unsupported()
|
||||
return
|
||||
}
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
switch ctrl.svc.Process(ctx, cType, r.Body) {
|
||||
switch ctrl.svc.Process(ctx, contentType, r.Body) {
|
||||
case service.ErrSinkContentProcessingFailed:
|
||||
http.Error(w, "sink processing failed", http.StatusInternalServerError)
|
||||
|
||||
|
||||
@@ -129,6 +129,9 @@ func Configure() *cli.Config {
|
||||
func(ctx context.Context, c *cli.Config) *cobra.Command {
|
||||
return commands.Roles(ctx, c)
|
||||
},
|
||||
func(ctx context.Context, c *cli.Config) *cobra.Command {
|
||||
return commands.Sink(ctx, c)
|
||||
},
|
||||
},
|
||||
|
||||
ProvisionMigrateDatabase: cli.Runners{
|
||||
|
||||
Reference in New Issue
Block a user