mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-16 12:58:00 +00:00
5823fdbef9
* project quota added
* project quota removed
* add periods table
* make log record generic
* accumulate usage
* query usage
* count action run seconds
* fix filter in ReportQuotaUsage
* fix existing tests
* fix logstore tests
* fix typo
* fix: add quota unit tests command side
* fix: add quota unit tests command side
* fix: add quota unit tests command side
* move notifications into debouncer and improve limit querying
* cleanup
* comment
* fix: add quota unit tests command side
* fix remaining quota usage query
* implement InmemLogStorage
* cleanup and linting
* improve test
* fix: add quota unit tests command side
* fix: add quota unit tests command side
* fix: add quota unit tests command side
* fix: add quota unit tests command side
* action notifications and fixes for notifications query
* revert console prefix
* fix: add quota unit tests command side
* fix: add quota integration tests
* improve accountable requests
* improve accountable requests
* fix: add quota integration tests
* fix: add quota integration tests
* fix: add quota integration tests
* comment
* remove ability to store logs in db and other changes requested from review
* changes requested from review
* changes requested from review
* Update internal/api/http/middleware/access_interceptor.go
Co-authored-by: Silvan <silvan.reusser@gmail.com>
* tests: fix quotas integration tests
* improve incrementUsageStatement
* linting
* fix: delete e2e tests as intergation tests cover functionality
* Update internal/api/http/middleware/access_interceptor.go
Co-authored-by: Silvan <silvan.reusser@gmail.com>
* backup
* fix conflict
* create rc
* create prerelease
* remove issue release labeling
* fix tracing
---------
Co-authored-by: Livio Spring <livio.a@gmail.com>
Co-authored-by: Stefan Benz <stefan@caos.ch>
Co-authored-by: adlerhurst <silvan.reusser@gmail.com>
(cherry picked from commit 1a49b7d298
)
438 lines
8.9 KiB
Go
438 lines
8.9 KiB
Go
package actions
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/dop251/goja"
|
|
|
|
"github.com/zitadel/zitadel/internal/errors"
|
|
"github.com/zitadel/zitadel/internal/logstore"
|
|
"github.com/zitadel/zitadel/internal/logstore/record"
|
|
)
|
|
|
|
func Test_isHostBlocked(t *testing.T) {
|
|
SetLogstoreService(logstore.New[*record.ExecutionLog](nil, nil))
|
|
var denyList = []AddressChecker{
|
|
mustNewIPChecker(t, "192.168.5.0/24"),
|
|
mustNewIPChecker(t, "127.0.0.1"),
|
|
&DomainChecker{Domain: "test.com"},
|
|
}
|
|
type args struct {
|
|
address *url.URL
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want bool
|
|
}{
|
|
{
|
|
name: "in range",
|
|
args: args{
|
|
address: mustNewURL(t, "https://192.168.5.4/hodor"),
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "exact ip",
|
|
args: args{
|
|
address: mustNewURL(t, "http://127.0.0.1:8080/hodor"),
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "address match",
|
|
args: args{
|
|
address: mustNewURL(t, "https://test.com:42/hodor"),
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "address not match",
|
|
args: args{
|
|
address: mustNewURL(t, "https://test2.com/hodor"),
|
|
},
|
|
want: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := isHostBlocked(denyList, tt.args.address); got != tt.want {
|
|
t.Errorf("isHostBlocked() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func mustNewIPChecker(t *testing.T, ip string) AddressChecker {
|
|
t.Helper()
|
|
checker, err := NewIPChecker(ip)
|
|
if err != nil {
|
|
t.Errorf("unable to parse cidr of %q because: %v", ip, err)
|
|
t.FailNow()
|
|
}
|
|
return checker
|
|
}
|
|
|
|
func mustNewURL(t *testing.T, raw string) *url.URL {
|
|
u, err := url.Parse(raw)
|
|
if err != nil {
|
|
t.Errorf("unable to parse address of %q because: %v", raw, err)
|
|
t.FailNow()
|
|
}
|
|
return u
|
|
}
|
|
|
|
func TestHTTP_fetchConfigFromArg(t *testing.T) {
|
|
runtime := goja.New()
|
|
runtime.SetFieldNameMapper(goja.UncapFieldNameMapper())
|
|
|
|
type args struct {
|
|
arg *goja.Object
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
wantConfig fetchConfig
|
|
wantErr func(error) bool
|
|
}{
|
|
{
|
|
name: "no fetch option provided",
|
|
args: args{
|
|
arg: runtime.ToValue(
|
|
struct{}{},
|
|
).ToObject(runtime),
|
|
},
|
|
wantConfig: fetchConfig{},
|
|
wantErr: func(err error) bool {
|
|
return err == nil
|
|
},
|
|
},
|
|
{
|
|
name: "header set as string",
|
|
args: args{
|
|
arg: runtime.ToValue(
|
|
&struct {
|
|
Headers map[string]string
|
|
}{
|
|
Headers: map[string]string{
|
|
"Authorization": "Bearer token",
|
|
},
|
|
},
|
|
).ToObject(runtime),
|
|
},
|
|
wantConfig: fetchConfig{
|
|
Headers: http.Header{
|
|
"Authorization": {"Bearer token"},
|
|
},
|
|
},
|
|
wantErr: func(err error) bool {
|
|
return err == nil
|
|
},
|
|
},
|
|
{
|
|
name: "header set as list",
|
|
args: args{
|
|
arg: runtime.ToValue(
|
|
&struct {
|
|
Headers map[string][]any
|
|
}{
|
|
Headers: map[string][]any{
|
|
"Authorization": {"Bearer token"},
|
|
},
|
|
},
|
|
).ToObject(runtime),
|
|
},
|
|
wantConfig: fetchConfig{
|
|
Headers: http.Header{
|
|
"Authorization": {"Bearer token"},
|
|
},
|
|
},
|
|
wantErr: func(err error) bool {
|
|
return err == nil
|
|
},
|
|
},
|
|
{
|
|
name: "method set",
|
|
args: args{
|
|
arg: runtime.ToValue(
|
|
&struct {
|
|
Method string
|
|
}{
|
|
Method: http.MethodPost,
|
|
},
|
|
).ToObject(runtime),
|
|
},
|
|
wantConfig: fetchConfig{
|
|
Method: http.MethodPost,
|
|
},
|
|
wantErr: func(err error) bool {
|
|
return err == nil
|
|
},
|
|
},
|
|
{
|
|
name: "body set",
|
|
args: args{
|
|
arg: runtime.ToValue(
|
|
&struct {
|
|
Body struct{ Id string }
|
|
}{
|
|
Body: struct{ Id string }{
|
|
Id: "asdf123",
|
|
},
|
|
},
|
|
).ToObject(runtime),
|
|
},
|
|
wantConfig: fetchConfig{
|
|
Body: bytes.NewReader([]byte(`{"id":"asdf123"}`)),
|
|
},
|
|
wantErr: func(err error) bool {
|
|
return err == nil
|
|
},
|
|
},
|
|
{
|
|
name: "invalid header",
|
|
args: args{
|
|
arg: runtime.ToValue(
|
|
&struct {
|
|
NotExists struct{}
|
|
}{
|
|
NotExists: struct{}{},
|
|
},
|
|
).ToObject(runtime),
|
|
},
|
|
wantConfig: fetchConfig{},
|
|
wantErr: func(err error) bool {
|
|
return errors.IsErrorInvalidArgument(err)
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
c := &HTTP{
|
|
runtime: runtime,
|
|
client: http.DefaultClient,
|
|
}
|
|
gotConfig := new(fetchConfig)
|
|
|
|
err := c.fetchConfigFromArg(tt.args.arg, gotConfig)
|
|
if !tt.wantErr(err) {
|
|
t.Errorf("HTTP.fetchConfigFromArg() error = %v", err)
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(gotConfig.Headers, tt.wantConfig.Headers) {
|
|
t.Errorf("config.Headers got = %#v, want %#v", gotConfig.Headers, tt.wantConfig.Headers)
|
|
}
|
|
if gotConfig.Method != tt.wantConfig.Method {
|
|
t.Errorf("config.Method got = %#v, want %#v", gotConfig.Method, tt.wantConfig.Method)
|
|
}
|
|
|
|
if tt.wantConfig.Body == nil {
|
|
if gotConfig.Body != nil {
|
|
t.Errorf("didn't expect a body")
|
|
}
|
|
return
|
|
}
|
|
|
|
gotBody, _ := io.ReadAll(gotConfig.Body)
|
|
wantBody, _ := io.ReadAll(tt.wantConfig.Body)
|
|
|
|
if !reflect.DeepEqual(gotBody, wantBody) {
|
|
t.Errorf("config.Body got = %s, want %s", gotBody, wantBody)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHTTP_buildHTTPRequest(t *testing.T) {
|
|
runtime := goja.New()
|
|
runtime.SetFieldNameMapper(goja.UncapFieldNameMapper())
|
|
|
|
type args struct {
|
|
args []goja.Value
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
wantReq *http.Request
|
|
shouldPanic bool
|
|
}{
|
|
{
|
|
name: "only url",
|
|
args: args{
|
|
args: []goja.Value{
|
|
runtime.ToValue("http://my-url.ch"),
|
|
},
|
|
},
|
|
wantReq: &http.Request{
|
|
Method: http.MethodGet,
|
|
URL: mustNewURL(t, "http://my-url.ch"),
|
|
Header: defaultFetchConfig.Headers,
|
|
Body: nil,
|
|
},
|
|
},
|
|
{
|
|
name: "no params",
|
|
args: args{
|
|
args: []goja.Value{
|
|
runtime.ToValue("http://my-url.ch"),
|
|
runtime.ToValue(&struct{}{}),
|
|
},
|
|
},
|
|
wantReq: &http.Request{
|
|
Method: http.MethodGet,
|
|
URL: mustNewURL(t, "http://my-url.ch"),
|
|
Header: defaultFetchConfig.Headers,
|
|
Body: nil,
|
|
},
|
|
},
|
|
{
|
|
name: "overwrite headers",
|
|
args: args{
|
|
args: []goja.Value{
|
|
runtime.ToValue("http://my-url.ch"),
|
|
runtime.ToValue(struct {
|
|
Headers map[string][]interface{}
|
|
}{
|
|
Headers: map[string][]interface{}{"Authorization": {"some token"}},
|
|
}),
|
|
},
|
|
},
|
|
wantReq: &http.Request{
|
|
Method: http.MethodGet,
|
|
URL: mustNewURL(t, "http://my-url.ch"),
|
|
Header: http.Header{
|
|
"Authorization": []string{"some token"},
|
|
},
|
|
Body: nil,
|
|
},
|
|
},
|
|
{
|
|
name: "post with body",
|
|
args: args{
|
|
args: []goja.Value{
|
|
runtime.ToValue("http://my-url.ch"),
|
|
runtime.ToValue(struct {
|
|
Body struct{ MyData string }
|
|
}{
|
|
Body: struct{ MyData string }{MyData: "hello world"},
|
|
}),
|
|
},
|
|
},
|
|
wantReq: &http.Request{
|
|
Method: http.MethodGet,
|
|
URL: mustNewURL(t, "http://my-url.ch"),
|
|
Header: defaultFetchConfig.Headers,
|
|
Body: io.NopCloser(bytes.NewReader([]byte(`{"myData":"hello world"}`))),
|
|
},
|
|
},
|
|
{
|
|
name: "too many args",
|
|
args: args{
|
|
args: []goja.Value{
|
|
runtime.ToValue("http://my-url.ch"),
|
|
runtime.ToValue("http://my-url.ch"),
|
|
runtime.ToValue("http://my-url.ch"),
|
|
},
|
|
},
|
|
wantReq: nil,
|
|
shouldPanic: true,
|
|
},
|
|
{
|
|
name: "no args",
|
|
args: args{
|
|
args: []goja.Value{},
|
|
},
|
|
wantReq: nil,
|
|
shouldPanic: true,
|
|
},
|
|
{
|
|
name: "invalid config",
|
|
args: args{
|
|
args: []goja.Value{
|
|
runtime.ToValue("http://my-url.ch"),
|
|
runtime.ToValue(struct {
|
|
Invalid bool
|
|
}{
|
|
Invalid: true,
|
|
}),
|
|
},
|
|
},
|
|
wantReq: nil,
|
|
shouldPanic: true,
|
|
},
|
|
{
|
|
name: "invalid method",
|
|
args: args{
|
|
args: []goja.Value{
|
|
runtime.ToValue("http://my-url.ch"),
|
|
runtime.ToValue(struct {
|
|
Method string
|
|
}{
|
|
Method: " asdf asdf",
|
|
}),
|
|
},
|
|
},
|
|
wantReq: nil,
|
|
shouldPanic: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
panicked := false
|
|
if tt.shouldPanic {
|
|
defer func() {
|
|
if panicked != tt.shouldPanic {
|
|
t.Errorf("wanted panic: %v got %v", tt.shouldPanic, panicked)
|
|
}
|
|
}()
|
|
defer func() {
|
|
recover()
|
|
panicked = true
|
|
}()
|
|
}
|
|
|
|
c := &HTTP{
|
|
runtime: runtime,
|
|
}
|
|
|
|
gotReq := c.buildHTTPRequest(context.Background(), tt.args.args)
|
|
|
|
if tt.shouldPanic {
|
|
return
|
|
}
|
|
|
|
if gotReq.URL.String() != tt.wantReq.URL.String() {
|
|
t.Errorf("url = %s, want %s", gotReq.URL, tt.wantReq.URL)
|
|
}
|
|
|
|
if !reflect.DeepEqual(gotReq.Header, tt.wantReq.Header) {
|
|
t.Errorf("headers = %v, want %v", gotReq.Header, tt.wantReq.Header)
|
|
}
|
|
|
|
if gotReq.Method != tt.wantReq.Method {
|
|
t.Errorf("method = %s, want %s", gotReq.Method, tt.wantReq.Method)
|
|
}
|
|
|
|
if tt.wantReq.Body == nil {
|
|
if gotReq.Body != nil {
|
|
t.Errorf("didn't expect a body")
|
|
}
|
|
return
|
|
}
|
|
|
|
gotBody, _ := io.ReadAll(gotReq.Body)
|
|
wantBody, _ := io.ReadAll(tt.wantReq.Body)
|
|
|
|
if !reflect.DeepEqual(gotBody, wantBody) {
|
|
t.Errorf("config.Body got = %s, want %s", gotBody, wantBody)
|
|
}
|
|
})
|
|
}
|
|
}
|