Signature for /sink requests

This commit is contained in:
Denis Arh
2019-09-03 20:23:10 +02:00
parent 8c2953a7fc
commit 14acd129d0
6 changed files with 145 additions and 20 deletions
+89
View File
@@ -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
}
+2 -2
View File
@@ -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
+1 -1
View File
@@ -30,10 +30,10 @@ const (
func Sink() *sink {
return &sink{
logger: DefaultLogger,
proc: map[string]sinkContentProc{
SinkContentTypeMail: Mailproc(),
},
logger: DefaultLogger,
}
}
+7 -6
View File
@@ -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
View File
@@ -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)
+3
View File
@@ -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{