3
0

Merge branch '2021.9.x-feature-wf-jwt' into 2021.9.x

This commit is contained in:
Peter Grlica
2021-12-08 10:22:15 +01:00
11 changed files with 482 additions and 3 deletions
+184
View File
@@ -0,0 +1,184 @@
package automation
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// automation/automation/jwt_handler.yaml
import (
"context"
atypes "github.com/cortezaproject/corteza-server/automation/types"
"github.com/cortezaproject/corteza-server/pkg/expr"
"github.com/cortezaproject/corteza-server/pkg/wfexec"
"io"
)
var _ wfexec.ExecResponse
type (
jwtHandlerRegistry interface {
AddFunctions(ff ...*atypes.Function)
Type(ref string) expr.Type
}
)
func (h jwtHandler) register() {
h.reg.AddFunctions(
h.Generate(),
)
}
type (
jwtGenerateArgs struct {
hasScope bool
Scope string
hasHeader bool
Header interface{}
headerVars map[string]expr.TypedValue
headerString string
hasPayload bool
Payload interface{}
payloadVars map[string]expr.TypedValue
payloadString string
hasSecret bool
Secret interface{}
secretString string
secretStream io.Reader
}
jwtGenerateResults struct {
Token string
}
)
func (a jwtGenerateArgs) GetHeader() (bool, map[string]expr.TypedValue, string) {
return a.hasHeader, a.headerVars, a.headerString
}
func (a jwtGenerateArgs) GetPayload() (bool, map[string]expr.TypedValue, string) {
return a.hasPayload, a.payloadVars, a.payloadString
}
func (a jwtGenerateArgs) GetSecret() (bool, string, io.Reader) {
return a.hasSecret, a.secretString, a.secretStream
}
// Generate function Generate JWT
//
// expects implementation of generate function:
// func (h jwtHandler) generate(ctx context.Context, args *jwtGenerateArgs) (results *jwtGenerateResults, err error) {
// return
// }
func (h jwtHandler) Generate() *atypes.Function {
return &atypes.Function{
Ref: "jwtGenerate",
Kind: "function",
Labels: map[string]string(nil),
Meta: &atypes.FunctionMeta{
Short: "Generate JWT",
},
Parameters: []*atypes.Param{
{
Name: "scope",
Types: []string{"String"},
},
{
Name: "header",
Types: []string{"Vars", "String"}, Required: true,
},
{
Name: "payload",
Types: []string{"Vars", "String"}, Required: true,
},
{
Name: "secret",
Types: []string{"String", "Reader"}, Required: true,
},
},
Results: []*atypes.Param{
{
Name: "token",
Types: []string{"String"},
},
},
Handler: func(ctx context.Context, in *expr.Vars) (out *expr.Vars, err error) {
var (
args = &jwtGenerateArgs{
hasScope: in.Has("scope"),
hasHeader: in.Has("header"),
hasPayload: in.Has("payload"),
hasSecret: in.Has("secret"),
}
)
if err = in.Decode(args); err != nil {
return
}
// Converting Header argument
if args.hasHeader {
aux := expr.Must(expr.Select(in, "header"))
switch aux.Type() {
case h.reg.Type("Vars").Type():
args.headerVars = aux.Get().(map[string]expr.TypedValue)
case h.reg.Type("String").Type():
args.headerString = aux.Get().(string)
}
}
// Converting Payload argument
if args.hasPayload {
aux := expr.Must(expr.Select(in, "payload"))
switch aux.Type() {
case h.reg.Type("Vars").Type():
args.payloadVars = aux.Get().(map[string]expr.TypedValue)
case h.reg.Type("String").Type():
args.payloadString = aux.Get().(string)
}
}
// Converting Secret argument
if args.hasSecret {
aux := expr.Must(expr.Select(in, "secret"))
switch aux.Type() {
case h.reg.Type("String").Type():
args.secretString = aux.Get().(string)
case h.reg.Type("Reader").Type():
args.secretStream = aux.Get().(io.Reader)
}
}
var results *jwtGenerateResults
if results, err = h.generate(ctx, args); err != nil {
return
}
out = &expr.Vars{}
{
// converting results.Token (string) to String
var (
tval expr.TypedValue
)
if tval, err = h.reg.Type("String").Cast(results.Token); err != nil {
return
} else if err = expr.Assign(out, "token", tval); err != nil {
return
}
}
return
},
}
}
+96
View File
@@ -0,0 +1,96 @@
package automation
import (
"context"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"strings"
"github.com/dgrijalva/jwt-go"
)
type (
jwtHandler struct {
reg jwtHandlerRegistry
}
)
func JwtHandler(reg jwtHandlerRegistry) *jwtHandler {
h := &jwtHandler{
reg: reg,
}
h.register()
return h
}
func (h jwtHandler) generate(ctx context.Context, args *jwtGenerateArgs) (res *jwtGenerateResults, err error) {
var (
secret interface{}
auxp = make(map[string]interface{})
auxh = make(map[string]interface{})
)
if !args.hasPayload {
err = fmt.Errorf("could not generate JWT, payload missing")
return
}
if !args.hasSecret {
err = fmt.Errorf("could not generate JWT, secret or cert missing")
return
}
for k, v := range args.headerVars {
auxp[k] = v.Get()
}
for k, v := range args.payloadVars {
auxp[k] = v.Get()
}
if args.payloadString != "" {
if err = json.Unmarshal([]byte(args.payloadString), &auxp); err != nil {
return
}
}
if args.headerString != "" {
if err = json.Unmarshal([]byte(args.headerString), &auxh); err != nil {
return
}
}
// check for delimiters
auxp["scope"] = strings.FieldsFunc(args.Scope, func(r rune) bool {
return r == ' ' || r == ','
})
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims(auxp))
// merge header with user-provided header
for k, v := range auxh {
token.Header[k] = v
}
// check if we use cert
{
pemBlock, _ := pem.Decode([]byte(args.secretString))
if pemBlock != nil {
if secret, err = x509.ParsePKCS8PrivateKey(pemBlock.Bytes); err != nil {
return
}
} else {
secret = args.secretString
}
}
res = &jwtGenerateResults{}
res.Token, err = token.SignedString(secret)
return
}
+30
View File
@@ -0,0 +1,30 @@
imports:
- io
functions:
generate:
meta:
short: Generate JWT
params:
scope:
require: false
types:
- { wf: String }
header:
required: true
types:
- { wf: Vars, suffix: Vars, go: 'map[string]expr.TypedValue' }
- { wf: String, suffix: String }
payload:
required: true
types:
- { wf: Vars, suffix: Vars, go: 'map[string]expr.TypedValue' }
- { wf: String, suffix: String }
secret:
required: true
types:
- { wf: String, suffix: String }
- { wf: Reader, suffix: Stream }
results:
token:
wf: String
+1
View File
@@ -125,6 +125,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, ws websock
automation.LoopHandler(Registry(), DefaultWorkflow.parser)
automation.CorredorHandler(Registry(), corredor.Service())
automation.EmailHandler(Registry())
automation.JwtHandler(Registry())
return
}
+9 -2
View File
@@ -1,10 +1,12 @@
package expr
import (
"github.com/PaesslerAG/gval"
"github.com/pkg/errors"
"math"
"math/rand"
"github.com/PaesslerAG/gval"
"github.com/pkg/errors"
"github.com/spf13/cast"
)
func NumericFunctions() []gval.Language {
@@ -21,6 +23,7 @@ func NumericFunctions() []gval.Language {
gval.Function("sum", sum),
gval.Function("average", average),
gval.Function("random", random),
gval.Function("int", toInt64),
}
}
@@ -141,3 +144,7 @@ func random(v ...float64) (out float64, err error) {
out = from + rand.Float64()*(to-from)
return
}
func toInt64(aa interface{}) (i int64) {
return cast.ToInt64(aa)
}
+21
View File
@@ -90,3 +90,24 @@ func Example_randomWithTwoInput() {
// output:
// 1
}
func Example_castStringToInt() {
eval(`int("abc")`, nil)
// output:
// 0
}
func Example_castStringNumberToInt() {
eval(`int("42690")`, nil)
// output:
// 42690
}
func Example_castFloatToInt() {
eval(`int("42.690")`, nil)
// output:
// 0
}
+6 -1
View File
@@ -190,10 +190,15 @@ func prepMod(base interface{}, mod interface{}) (*time.Time, int, error) {
// https://github.com/lestrrat-go/strftime#supported-conversion-specifications
func strfTime(base interface{}, f string) (string, error) {
t, _, err := prepMod(base, 0)
if err != nil {
return "", err
}
o, _ := strftime.Format(f, *t, strftime.WithMilliseconds('b'))
o, _ := strftime.Format(f, *t,
strftime.WithMilliseconds('b'),
strftime.WithUnixSeconds('L'))
return o, nil
}
+7
View File
@@ -134,3 +134,10 @@ func Example_subErrorCheck() {
// output:
// error: can not evaluate sub(ghd, hgp): expecting 2nd input to be less than 1st input
}
func Example_unix() {
eval(`strftime(ghd, "%L")`, exampleTimeParams)
// output:
// 728650800
}
+34
View File
@@ -0,0 +1,34 @@
package workflows
import (
"context"
_ "embed"
"strings"
"testing"
"github.com/cortezaproject/corteza-server/automation/types"
"github.com/stretchr/testify/require"
)
//go:embed testdata/jwt_generate/match_token
var tkn string
func Test_jwt_generate(t *testing.T) {
var (
ctx = bypassRBAC(context.Background())
req = require.New(t)
aux = struct {
Token string
}{}
)
req.NoError(defStore.TruncateAttachments(ctx))
loadNewScenario(ctx, t)
vars, _ := mustExecWorkflow(ctx, t, "jwt_generate", types.WorkflowExecParams{})
req.NoError(vars.Decode(&aux))
req.Equal(strings.TrimSuffix(tkn, "\n"), aux.Token)
}
+1
View File
@@ -0,0 +1 @@
eyJhbGciOiJSUzI1NiIsImZvbyI6ImJhciIsInR5cCI6IkpXVCJ9.eyJleHAiOjcyODY1NDQwMCwiaWF0Ijo3Mjg2NTA4MDAsInNjb3BlIjpbXSwic3ViIjoiMTIzNDU2Nzg5MCJ9.Tk19aAqkcSuVd54OHSSX6VOjLk4MFhxoeufiEDGo8U7wtsW6EL3YGFCiWtcMpjrK03aNVUqQwue2BjB8ysqq3NsNvFVWiJFDUZR-4jaRHrz4FALZpb43do95YQFhJHliAnzmAEgbFQY704FTd9PYP9IGuw0EUwq_WOAtIiaKNHG20rPIRvltr_p1t54rDG0Xx_9iGCU-J-GJSWuYV7-XNHsdz0xJqxRAUeaH4Ly5BzjHBX8HSB0VezopZly2sJDlqfZCUBkL80eMvECJWc7g4znN9dPHKDcKclqvFI5sPSdQIQ7zZVeSbTizqXe-KXz9TykVTDOudnY4iRXzNerZ0_rzq7qT253qGoypkM0_N3oidFU0UkSnLgWIyeuw09bFraDyWuYEhYbsDif7qT8tP-w6YaKv0mFZ8BwRKl6yZafR8Ji7UNWhVQ3Qz8rJjAclX4elEFGq0deK7B2Kiwo6U33tNyx2Xi56QRMs6r26S9QoA3fNbA7C2HrFwy3coZxyJAKVCAHcfYOue-u1oersq0vAk_HGXytBrdiz6xL6wbRjNqfrT--I-uq2MfUEsZSh2Xy0SCEUt8IJeX6vt7nGeKqu6AUuKbCpA6CrXRI7K9jShuAsFeyTdKKjJsKCXwZRQp9ZyTmWYXcyXlDHf63I8JXNP2wzPM_3p0CCQ313S_Q
+93
View File
@@ -0,0 +1,93 @@
workflows:
jwt_generate:
enabled: true
trace: true
triggers:
- enabled: true
stepID: 5
steps:
- stepID: 5
kind: function
ref: jwtGenerate
arguments:
- target: scope
type: String
- target: header
value: |-
{
"foo": "bar"
}
type: String
- target: payload
expr: |-
{
"sub": "1234567890",
"iat": int(strftime(date("1993-02-02T06:00:00-05:00"), "%L")),
"exp": int(strftime(modTime(date("1993-02-02T06:00:00-05:00"), "+1h"), "%L"))
}
type: Vars
- target: secret
value: |-
-----BEGIN PRIVATE KEY-----
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
C/p2c759krBsxv/zOxJJsWDEF1AolQI=
-----END PRIVATE KEY-----
type: String
results:
- target: token
expr: token