Skip to content

Commit

Permalink
Merge branch 'master' into expand-resource-labels
Browse files Browse the repository at this point in the history
  • Loading branch information
Forfold committed Jul 3, 2024
2 parents 8af00f0 + 1c7f83b commit 5737a0b
Show file tree
Hide file tree
Showing 210 changed files with 17,873 additions and 14,812 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '16'
node-version: '22'
- run: make yarn
- run: 'echo "nodeLinker: node-modules" >>.yarnrc.yml'
- run: yarn install
Expand Down
63 changes: 56 additions & 7 deletions .storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,61 @@
import type { Preview } from '@storybook/react'
import DefaultDecorator from '../web/src/app/storybook/decorators'
import { initialize, mswLoader } from 'msw-storybook-addon'
import {
handleDefaultConfig,
handleExpFlags,
} from '../web/src/app/storybook/graphql'
import { defaultConfig } from '../web/src/app/storybook/graphql'
import { graphql, HttpResponse } from 'msw'

initialize({
onUnhandledRequest: 'bypass',
})

export type GraphQLRequestHandler = (variables: unknown) => object
export type GraphQLParams = Record<string, GraphQLRequestHandler>

const componentConfig: Record<string, GraphQLParams> = {}

export const mswHandler = graphql.operation((params) => {
const url = new URL(params.request.url)
const gql = componentConfig[url.pathname]
const handler = gql[params.operationName]
if (!handler) {
return HttpResponse.json({
errors: [
{ message: `No mock defined for operation '${params.operationName}'.` },
],
})
}

if (typeof handler === 'function') {
return HttpResponse.json(handler(params.variables))
}

return HttpResponse.json(handler)
})

interface LoaderArg {
id: string
parameters: {
graphql?: GraphQLParams
}
}

/**
* graphqlLoader is a loader function that sets up the GraphQL mocks for the
* component. It stores them in a global object, componentConfig, which is used
* by the mswHandler to resolve the mocks.
*
* We need to do this because the browser will render all components at once,
* and so we need to handle all GraphQL mocks at once.
*
* The way this works is that each component get's a unique URL for GraphQL
* (see decorators.tsx). This URL is used to store the GraphQL mocks for that
* component in componentConfig.
*/
export function graphQLLoader(arg: LoaderArg): void {
const path = '/' + encodeURIComponent(arg.id) + '/api/graphql'
componentConfig[path] = arg.parameters.graphql || {}
}

const preview: Preview = {
parameters: {
controls: {
Expand All @@ -18,12 +64,15 @@ const preview: Preview = {
date: /Date$/i,
},
},
msw: {
handlers: [handleDefaultConfig, handleExpFlags()],
graphql: {
// default GraphQL handlers
RequireConfig: { data: defaultConfig },
useExpFlag: { data: { experimentalFlags: [] } },
},
msw: { handlers: [mswHandler] },
},
decorators: [DefaultDecorator],
loaders: [mswLoader],
loaders: [graphQLLoader, mswLoader],
}

export default preview
874 changes: 0 additions & 874 deletions .yarn/releases/yarn-3.6.3.cjs

This file was deleted.

894 changes: 894 additions & 0 deletions .yarn/releases/yarn-4.3.1.cjs

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
compressionLevel: mixed

defaultSemverRangePrefix: ''

enableGlobalCache: false

packageExtensions:
jest-playwright-preset@*:
dependencies:
Expand All @@ -10,4 +14,4 @@ packageExtensions:

pnpEnableEsmLoader: true

yarnPath: .yarn/releases/yarn-3.6.3.cjs
yarnPath: .yarn/releases/yarn-4.3.1.cjs
7 changes: 2 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ SWO_DB_URL_NEXT = $(shell go run ./devtools/scripts/db-url "$(DB_URL)" "$(SWO_DB

LOG_DIR=
GOPATH:=$(shell go env GOPATH)
YARN_VERSION=3.6.3
YARN_VERSION=4.3.1
PG_VERSION=13

# add all files except those under web/src/build and web/src/cypress
Expand Down Expand Up @@ -351,10 +351,7 @@ new-migration:
vscode: $(NODE_DEPS)
yarn dlx @yarnpkg/sdks vscode

.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs: $(NODE_DEPS)
yarn plugin import interactive-tools

upgrade-js: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs ## Interactively upgrade javascript packages
upgrade-js: ## Interactively upgrade javascript packages
yarn upgrade-interactive

test/coverage/total.out: test/coverage/integration/*/* test/coverage/*/* Makefile
Expand Down
7 changes: 7 additions & 0 deletions apikey/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ package apikey
import "github.com/target/goalert/permission"

// GQLPolicy is a GraphQL API key policy.
//
// Any changes to existing fields MUST require the version to be incremented.
//
// If new fields are added, they MUST be set to `omitempty`
// to ensure existing keys don't break.
//
// It MUST be possible to unmarshal & re-marshal the policy without changing the data for existing keys.
type GQLPolicy struct {
Version int
Query string
Expand Down
27 changes: 21 additions & 6 deletions apikey/policyinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,25 @@ type policyInfo struct {
Policy GQLPolicy
}

func parsePolicyInfo(data []byte) (*policyInfo, error) {
var info policyInfo
err := json.Unmarshal(data, &info.Policy)
if err != nil {
return nil, err
}

// re-encode policy to get a consistent hash
data, err = json.Marshal(info.Policy)
if err != nil {
return nil, err
}

h := sha256.Sum256(data)
info.Hash = h[:]

return &info, nil
}

// _fetchPolicyInfo will fetch the policyInfo for the given key.
func (s *Store) _fetchPolicyInfo(ctx context.Context, id uuid.UUID) (*policyInfo, bool, error) {
polData, err := gadb.New(s.db).APIKeyAuthPolicy(ctx, id)
Expand All @@ -26,14 +45,10 @@ func (s *Store) _fetchPolicyInfo(ctx context.Context, id uuid.UUID) (*policyInfo
return nil, false, err
}

var info policyInfo
err = json.Unmarshal(polData, &info.Policy)
info, err := parsePolicyInfo(polData)
if err != nil {
return nil, false, err
}

h := sha256.Sum256(polData)
info.Hash = h[:]

return &info, true, nil
return info, true, nil
}
38 changes: 38 additions & 0 deletions apikey/policyinfo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package apikey

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestParsePolicyInfo(t *testing.T) {
expected := &policyInfo{
// Never change this hash -- it is used to verify future changes wont break existing keys.
Hash: []byte{0x2d, 0x6d, 0xef, 0x3b, 0xfa, 0xd8, 0xef, 0x9d, 0x7e, 0x6c, 0xf4, 0xed, 0xd1, 0x81, 0x16, 0xf4, 0x23, 0xaa, 0x4a, 0xaf, 0x70, 0x1b, 0xfd, 0x1b, 0x26, 0x1d, 0xb3, 0x1, 0x4f, 0xdc, 0xa1, 0x61},
Policy: GQLPolicy{
Version: 1,
Query: "query",
Role: "admin",
},
}

info, err := parsePolicyInfo([]byte(`{"Version":1,"Query":"query","Role":"admin"}`))
assert.NoError(t, err)
assert.Equal(t, expected, info)

// add spaces and re-order keys
info, err = parsePolicyInfo([]byte(`{ "Role":"admin", "Query":"query","Version":1}`))
assert.NoError(t, err)
assert.Equal(t, expected, info)

// add extra field
info, err = parsePolicyInfo([]byte(`{"Version":1,"Query":"query","Role":"admin","Extra":true}`))
assert.NoError(t, err)
assert.Equal(t, expected, info)

// changed query
info, err = parsePolicyInfo([]byte(`{"Version":1,"Query":"query2","Role":"admin"}`))
assert.NoError(t, err)
assert.NotEqual(t, expected.Hash, info.Hash) // NOT equal
}
2 changes: 2 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/target/goalert/graphql2/graphqlapp"
"github.com/target/goalert/heartbeat"
"github.com/target/goalert/integrationkey"
"github.com/target/goalert/integrationkey/uik"
"github.com/target/goalert/keyring"
"github.com/target/goalert/label"
"github.com/target/goalert/limit"
Expand Down Expand Up @@ -99,6 +100,7 @@ type App struct {
ServiceStore *service.Store
EscalationStore *escalation.Store
IntegrationKeyStore *integrationkey.Store
UIKHandler *uik.Handler
ScheduleRuleStore *rule.Store
NotificationStore *notification.Store
ScheduleStore *schedule.Store
Expand Down
4 changes: 4 additions & 0 deletions app/inithttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/target/goalert/config"
"github.com/target/goalert/expflag"
"github.com/target/goalert/genericapi"
"github.com/target/goalert/grafana"
"github.com/target/goalert/mailgun"
Expand Down Expand Up @@ -142,6 +143,9 @@ func (app *App) initHTTP(ctx context.Context) error {
mux.HandleFunc("/api/v2/identity/providers/oidc", oidcAuth)
mux.HandleFunc("/api/v2/identity/providers/oidc/callback", oidcAuth)

if expflag.ContextHas(ctx, expflag.UnivKeys) {
mux.HandleFunc("POST /api/v2/uik", app.UIKHandler.ServeHTTP)
}
mux.HandleFunc("/api/v2/mailgun/incoming", mailgun.IngressWebhooks(app.AlertStore, app.IntegrationKeyStore))
mux.HandleFunc("/api/v2/grafana/incoming", grafana.GrafanaToEventsAPI(app.AlertStore, app.IntegrationKeyStore))
mux.HandleFunc("/api/v2/site24x7/incoming", site24x7.Site24x7ToEventsAPI(app.AlertStore, app.IntegrationKeyStore))
Expand Down
5 changes: 4 additions & 1 deletion app/initstores.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/target/goalert/escalation"
"github.com/target/goalert/heartbeat"
"github.com/target/goalert/integrationkey"
"github.com/target/goalert/integrationkey/uik"
"github.com/target/goalert/keyring"
"github.com/target/goalert/label"
"github.com/target/goalert/limit"
Expand Down Expand Up @@ -217,7 +218,7 @@ func (app *App) initStores(ctx context.Context) error {
}

if app.IntegrationKeyStore == nil {
app.IntegrationKeyStore = integrationkey.NewStore(ctx, app.db)
app.IntegrationKeyStore = integrationkey.NewStore(ctx, app.db, app.APIKeyring)
}

if app.ScheduleRuleStore == nil {
Expand Down Expand Up @@ -299,5 +300,7 @@ func (app *App) initStores(ctx context.Context) error {
return errors.Wrap(err, "init API key store")
}

app.UIKHandler = uik.NewHandler(app.db, app.IntegrationKeyStore, app.AlertStore)

return nil
}
6 changes: 6 additions & 0 deletions auth/authtoken/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ func (t Token) Encode(signFn SignFunc) (string, error) {
b[2] = byte(t.Type)
copy(b[3:], t.ID[:])
binary.BigEndian.PutUint64(b[19:], uint64(t.CreatedAt.Unix()))
case 3:
b = make([]byte, 19)
b[0] = 'V' // versioned header format
b[1] = 3
b[2] = byte(t.Type)
copy(b[3:], t.ID[:])
default:
return "", validation.NewFieldError("Type", "unsupported version")
}
Expand Down
14 changes: 14 additions & 0 deletions auth/authtoken/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,20 @@ func Parse(s string, verifyFn VerifyFunc) (*Token, bool, error) {
}

switch data[1] {
case 3:
if len(data) < 19 {
return nil, false, validation.NewGenericError("invalid length")
}
t := &Token{
Version: 3,
Type: Type(data[2]),
}
valid, isOldKey := verifyFn(t.Type, data[:19], data[19:])
if !valid {
return nil, false, validation.NewGenericError("invalid signature")
}
copy(t.ID[:], data[3:])
return t, isOldKey, nil
case 2:
if len(data) < 27 {
return nil, false, validation.NewGenericError("invalid length")
Expand Down
1 change: 1 addition & 0 deletions auth/authtoken/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ const (
TypeUnknown Type = iota // always make the zero-value Unknown
TypeSession
TypeCalSub
TypeUIK
)
9 changes: 9 additions & 0 deletions auth/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,15 @@ func (h *Handler) authWithToken(w http.ResponseWriter, req *http.Request, next h
next.ServeHTTP(w, req.WithContext(ctx))
return true
}
if req.URL.Path == "/api/v2/uik" && strings.HasPrefix(tokStr, "ey") {
ctx, err = h.cfg.IntKeyStore.AuthorizeUIK(ctx, tokStr)
if errutil.HTTPError(req.Context(), w, err) {
return true
}

next.ServeHTTP(w, req.WithContext(ctx))
return true
}

tok, _, err := authtoken.Parse(tokStr, func(t authtoken.Type, p, sig []byte) (bool, bool) {
if t == authtoken.TypeSession {
Expand Down
2 changes: 1 addition & 1 deletion cmd/goalert-slack-email-sync/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func main() {
creds = credentials.NewTLS(cfg)
}

conn, err := grpc.Dial(*api, grpc.WithTransportCredentials(creds))
conn, err := grpc.NewClient(*api, grpc.WithTransportCredentials(creds))
if err != nil {
log.Fatal("connect to GoAlert:", err)
}
Expand Down
3 changes: 3 additions & 0 deletions devtools/gqltsgen/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ func main() {
if def.Name == "WeekdayFilter" {
typeName = "[boolean, boolean, boolean, boolean, boolean, boolean, boolean]"
}
if strings.HasSuffix(def.Name, "Map") {
typeName = "Record<string, string>"
}
fmt.Fprintf(w, "export type %s = %s\n\n", def.Name, typeName)
case ast.Union:
sort.Strings(def.Types)
Expand Down
2 changes: 1 addition & 1 deletion devtools/pgdump-lite/pgd/db.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions devtools/pgdump-lite/pgd/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion devtools/pgdump-lite/pgd/queries.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 5737a0b

Please sign in to comment.