diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c6c85a69..751a03937 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Unreleased changes are available as `avenga/couper:edge` container. * **Changed** * Use nested `jwt_signing_profile` block in [`oauth2` block](https://docs.couper.io/configuration/block/oauth2) for `grant_type` `"urn:ietf:params:oauth:grant-type:jwt-bearer"` in absence of `assertion` attribute ([#619](https://github.com/avenga/couper/pull/619)) * Improved the way an SPA `bootstrap_file` gets cached and served in combination with `bootstrap_data` ([#656](https://github.com/avenga/couper/pull/656)) + * Harmonized and improved logged error information for references to undefined blocks ([#651](https://github.com/avenga/couper/pull/651)) --- diff --git a/config/configload/endpoint.go b/config/configload/endpoint.go index f77c153ae..5e9cf9b5d 100644 --- a/config/configload/endpoint.go +++ b/config/configload/endpoint.go @@ -25,7 +25,7 @@ func newCatchAllEndpoint() *config.Endpoint { } } -func refineEndpoints(helper *helper, endpoints config.Endpoints, checkPathPattern bool) error { +func refineEndpoints(helper *helper, endpoints config.Endpoints, checkPathPattern bool, definedACs map[string]struct{}) error { var err error for _, ep := range endpoints { @@ -38,6 +38,12 @@ func refineEndpoints(helper *helper, endpoints config.Endpoints, checkPathPatter } endpointBody := ep.HCLBody() + if definedACs != nil { + if err := checkReferencedAccessControls(endpointBody, ep.AccessControl, ep.DisableAccessControl, definedACs); err != nil { + return err + } + } + rp := endpointBody.Attributes["beta_required_permission"] if rp != nil { ep.RequiredPermission = rp.Expr diff --git a/config/configload/endpoint_test.go b/config/configload/endpoint_test.go index 6ea10d7c6..23cbb2bd2 100644 --- a/config/configload/endpoint_test.go +++ b/config/configload/endpoint_test.go @@ -8,7 +8,7 @@ import ( ) func Test_refineEndpoints_noPattern(t *testing.T) { - err := refineEndpoints(nil, config.Endpoints{{Pattern: ""}}, true) + err := refineEndpoints(nil, config.Endpoints{{Pattern: ""}}, true, nil) if err == nil || !strings.HasSuffix(err.Error(), "endpoint: missing path pattern; ") { t.Errorf("refineEndpoints() error = %v, wantErr: endpoint: missing path pattern ", err) } diff --git a/config/configload/error_handler.go b/config/configload/error_handler.go index f3ff68cc6..18e9f6fec 100644 --- a/config/configload/error_handler.go +++ b/config/configload/error_handler.go @@ -152,7 +152,7 @@ func newErrorHandlerConfig(content kindContent, helper *helper) (*config.ErrorHa Requests: errHandlerConf.Requests, } - if err := refineEndpoints(helper, config.Endpoints{ep}, false); err != nil { + if err := refineEndpoints(helper, config.Endpoints{ep}, false, nil); err != nil { return nil, err } diff --git a/config/configload/load.go b/config/configload/load.go index 3ff945fb8..59ab42c40 100644 --- a/config/configload/load.go +++ b/config/configload/load.go @@ -368,6 +368,23 @@ func LoadConfig(body *hclsyntax.Body) (*config.Couper, error) { WithOAuth2AC(helper.config.Definitions.OAuth2AC). WithSAML(helper.config.Definitions.SAML) + definedACs := make(map[string]struct{}) + for _, ac := range helper.config.Definitions.BasicAuth { + definedACs[ac.Name] = struct{}{} + } + for _, ac := range helper.config.Definitions.JWT { + definedACs[ac.Name] = struct{}{} + } + for _, ac := range helper.config.Definitions.OAuth2AC { + definedACs[ac.Name] = struct{}{} + } + for _, ac := range helper.config.Definitions.OIDC { + definedACs[ac.Name] = struct{}{} + } + for _, ac := range helper.config.Definitions.SAML { + definedACs[ac.Name] = struct{}{} + } + // Read per server block and merge backend settings which results in a final server configuration. for _, serverBlock := range hclbody.BlocksOfType(body, server) { serverConfig := &config.Server{} @@ -380,6 +397,22 @@ func LoadConfig(body *hclsyntax.Body) (*config.Couper, error) { serverConfig.Name = serverBlock.Labels[0] } + if err := checkReferencedAccessControls(serverBlock.Body, serverConfig.AccessControl, serverConfig.DisableAccessControl, definedACs); err != nil { + return nil, err + } + + for _, fileConfig := range serverConfig.Files { + if err := checkReferencedAccessControls(fileConfig.HCLBody(), fileConfig.AccessControl, fileConfig.DisableAccessControl, definedACs); err != nil { + return nil, err + } + } + + for _, spaConfig := range serverConfig.SPAs { + if err := checkReferencedAccessControls(spaConfig.HCLBody(), spaConfig.AccessControl, spaConfig.DisableAccessControl, definedACs); err != nil { + return nil, err + } + } + // Read api blocks and merge backends with server and definitions backends. for _, apiConfig := range serverConfig.APIs { apiBody := apiConfig.HCLBody() @@ -390,12 +423,16 @@ func LoadConfig(body *hclsyntax.Body) (*config.Couper, error) { } } + if err := checkReferencedAccessControls(apiBody, apiConfig.AccessControl, apiConfig.DisableAccessControl, definedACs); err != nil { + return nil, err + } + rp := apiBody.Attributes["beta_required_permission"] if rp != nil { apiConfig.RequiredPermission = rp.Expr } - err = refineEndpoints(helper, apiConfig.Endpoints, true) + err = refineEndpoints(helper, apiConfig.Endpoints, true, definedACs) if err != nil { return nil, err } @@ -414,7 +451,7 @@ func LoadConfig(body *hclsyntax.Body) (*config.Couper, error) { } // standalone endpoints - err = refineEndpoints(helper, serverConfig.Endpoints, true) + err = refineEndpoints(helper, serverConfig.Endpoints, true, definedACs) if err != nil { return nil, err } @@ -439,7 +476,7 @@ func LoadConfig(body *hclsyntax.Body) (*config.Couper, error) { Requests: job.Requests, } - err = refineEndpoints(helper, config.Endpoints{endpointConf}, false) + err = refineEndpoints(helper, config.Endpoints{endpointConf}, false, nil) if err != nil { return nil, err } diff --git a/config/configload/merge.go b/config/configload/merge.go index 319c0599c..a2d720710 100644 --- a/config/configload/merge.go +++ b/config/configload/merge.go @@ -767,7 +767,7 @@ func addProxy(block *hclsyntax.Block, proxies map[string]*hclsyntax.Block) error if !ok { sr := attr.Expr.StartRange() - return newDiagErr(&sr, "proxy reference is not defined") + return newDiagErr(&sr, fmt.Sprintf("referenced proxy %q is not defined", reference)) } delete(block.Body.Attributes, proxy) diff --git a/config/configload/validate.go b/config/configload/validate.go index 3452b9d8c..7ff975373 100644 --- a/config/configload/validate.go +++ b/config/configload/validate.go @@ -330,3 +330,26 @@ func validateBackendTLS(block *hclsyntax.Block) error { } return nil } + +func checkReferencedAccessControls(body *hclsyntax.Body, acs, dacs []string, definedACs map[string]struct{}) error { + for _, ac := range acs { + if ac = strings.TrimSpace(ac); ac == "" { + continue + } + if _, set := definedACs[ac]; !set { + r := body.Attributes["access_control"].Expr.Range() + return newDiagErr(&r, fmt.Sprintf("referenced access control %q is not defined", ac)) + } + } + for _, ac := range dacs { + if ac = strings.TrimSpace(ac); ac == "" { + continue + } + if _, set := definedACs[ac]; !set { + r := body.Attributes["disable_access_control"].Expr.Range() + return newDiagErr(&r, fmt.Sprintf("referenced access control %q is not defined", ac)) + } + } + + return nil +} diff --git a/config/configload/validate_test.go b/config/configload/validate_test.go index 2099a16a4..847eef82d 100644 --- a/config/configload/validate_test.go +++ b/config/configload/validate_test.go @@ -1407,6 +1407,11 @@ func TestPermissionMixed(t *testing.T) { response {} } } +} +definitions { + basic_auth "foo" { + password = "asdf" + } }`, "", }, @@ -1522,3 +1527,125 @@ definitions { }) } } + +func Test_checkReferencedAccessControls(t *testing.T) { + tests := []struct { + name string + hcl string + error string + }{ + { + "missing AC referenced by server access_control", + `server { + access_control = ["undefined"] + }`, + `couper.hcl:2,23-36: referenced access control "undefined" is not defined; `, + }, + { + "missing AC referenced by server disable_access_control", + `server { + disable_access_control = ["undefined"] + }`, + `couper.hcl:2,31-44: referenced access control "undefined" is not defined; `, + }, + { + "missing AC referenced by api access_control", + `server { + api { + access_control = ["undefined"] + } + }`, + `couper.hcl:3,25-38: referenced access control "undefined" is not defined; `, + }, + { + "missing AC referenced by api disable_access_control", + `server { + api { + disable_access_control = ["undefined"] + } + }`, + `couper.hcl:3,33-46: referenced access control "undefined" is not defined; `, + }, + { + "missing AC referenced by files access_control", + `server { + files { + access_control = ["undefined"] + document_root = "htdocs" + } + }`, + `couper.hcl:3,25-38: referenced access control "undefined" is not defined; `, + }, + { + "missing AC referenced by files disable_access_control", + `server { + files { + disable_access_control = ["undefined"] + document_root = "htdocs" + } + }`, + `couper.hcl:3,33-46: referenced access control "undefined" is not defined; `, + }, + { + "missing AC referenced by spa access_control", + `server { + spa { + access_control = ["undefined"] + bootstrap_file = "foo" + paths = ["/**"] + } + }`, + `couper.hcl:3,25-38: referenced access control "undefined" is not defined; `, + }, + { + "missing AC referenced by spa disable_access_control", + `server { + spa { + disable_access_control = ["undefined"] + bootstrap_file = "foo" + paths = ["/**"] + } + }`, + `couper.hcl:3,33-46: referenced access control "undefined" is not defined; `, + }, + { + "missing AC referenced by endpoint access_control", + `server { + endpoint "/" { + access_control = ["undefined"] + response { + body = "OK" + } + } + }`, + `couper.hcl:3,25-38: referenced access control "undefined" is not defined; `, + }, + { + "missing AC referenced by endpoint disable_access_control", + `server { + endpoint "/" { + disable_access_control = ["undefined"] + response { + body = "OK" + } + } + }`, + `couper.hcl:3,33-46: referenced access control "undefined" is not defined; `, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(subT *testing.T) { + _, err := LoadBytes([]byte(tt.hcl), "couper.hcl") + + var errMsg string + if err != nil { + errMsg = err.Error() + } + + if tt.error != errMsg { + subT.Errorf("%q: Unexpected configuration error:\n\tWant: %q\n\tGot: %q", tt.name, tt.error, errMsg) + } + }) + } +} diff --git a/config/files.go b/config/files.go index 87c77bd66..d31b39da9 100644 --- a/config/files.go +++ b/config/files.go @@ -3,6 +3,7 @@ package config import ( "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/gohcl" + "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/avenga/couper/config/meta" ) @@ -23,6 +24,11 @@ type Files struct { Remain hcl.Body `hcl:",remain"` } +// HCLBody implements the interface. +func (f Files) HCLBody() *hclsyntax.Body { + return f.Remain.(*hclsyntax.Body) +} + // Inline implements the interface. func (f Files) Inline() interface{} { type Inline struct { diff --git a/config/runtime/access_control.go b/config/runtime/access_control.go index 2ca05ab11..bbd04a728 100644 --- a/config/runtime/access_control.go +++ b/config/runtime/access_control.go @@ -1,7 +1,6 @@ package runtime import ( - "fmt" "strings" "github.com/avenga/couper/accesscontrol" @@ -22,15 +21,3 @@ func (m ACDefinitions) Add(name string, ac accesscontrol.AccessControl, eh []*co ErrorHandler: eh, } } - -func (m ACDefinitions) MustExist(name string) error { - if m == nil { - panic("no accessControl configuration") - } - - if _, ok := m[name]; !ok { - return fmt.Errorf("accessControl is not defined: " + name) - } - - return nil -} diff --git a/config/runtime/server.go b/config/runtime/server.go index a368b637d..7ee967b09 100644 --- a/config/runtime/server.go +++ b/config/runtime/server.go @@ -533,12 +533,7 @@ func configureAccessControls(conf *config.Couper, confCtx *hcl.EvalContext, log for _, saml := range conf.Definitions.SAML { confErr := errors.Configuration.Label(saml.Name) - metadata, err := reader.ReadFromFile("saml2 idp_metadata_file", saml.IdpMetadataFile) - if err != nil { - return nil, confErr.With(err) - } - - s, err := ac.NewSAML2ACS(metadata, saml.Name, saml.SpAcsURL, saml.SpEntityID, saml.ArrayAttributes) + s, err := ac.NewSAML2ACS(saml.MetadataBytes, saml.Name, saml.SpAcsURL, saml.SpEntityID, saml.ArrayAttributes) if err != nil { return nil, confErr.With(err) } @@ -651,9 +646,6 @@ func configureProtectedHandler(m ACDefinitions, conf *config.Couper, ctx *hcl.Ev opts *protectedOptions, log *logrus.Entry) (http.Handler, error) { var list ac.List for _, acName := range parentAC.Merge(handlerAC).List() { - if e := m.MustExist(acName); e != nil { - return nil, e - } eh, err := newErrorHandler(ctx, conf, opts, log, m, acName) if err != nil { return nil, err diff --git a/eval/lib/jwt.go b/eval/lib/jwt.go index 4c07813d6..127b819d5 100644 --- a/eval/lib/jwt.go +++ b/eval/lib/jwt.go @@ -148,14 +148,10 @@ func NewJwtSignFunction(ctx *hcl.EvalContext, jwtSigningConfigs map[string]*JWTS }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, _ cty.Type) (ret cty.Value, err error) { - if len(jwtSigningConfigs) == 0 { - return cty.StringVal(""), fmt.Errorf("missing jwt_signing_profile or jwt (with signing_ttl) definitions") - } - label := args[0].AsString() - signingConfig := jwtSigningConfigs[label] - if signingConfig == nil { - return cty.StringVal(""), fmt.Errorf("missing jwt_signing_profile or jwt (with signing_ttl) for given label %q", label) + signingConfig, exist := jwtSigningConfigs[label] + if !exist { + return cty.StringVal(""), fmt.Errorf("missing jwt_signing_profile or jwt (with signing_ttl) block with referenced label %q", label) } var claims, argumentClaims, headers map[string]interface{} diff --git a/eval/lib/jwt_test.go b/eval/lib/jwt_test.go index c777bed69..15acdf63a 100644 --- a/eval/lib/jwt_test.go +++ b/eval/lib/jwt_test.go @@ -862,56 +862,40 @@ func TestJwtSignError(t *testing.T) { wantErr string }{ { - "missing jwt_signing_profile definitions", + "missing signing profile definitions", ` - server "test" { - endpoint "/" { - response { - body = jwt_sign() - } - } - } + server {} definitions { jwt "MyToken" { signature_algorithm = "HS256" key = "$3cRe4" - claims = { - iss = to_lower("The_Issuer") - aud = to_upper("The_Audience") - } } } `, "MyToken", `{"sub": "12345"}`, - "missing jwt_signing_profile or jwt (with signing_ttl) definitions", + `missing jwt_signing_profile or jwt (with signing_ttl) block with referenced label "MyToken"`, }, { "No profile for label", ` - server "test" { - } + server {} definitions { jwt_signing_profile "MyToken" { signature_algorithm = "HS256" key = "$3cRe4" ttl = "0" - claims = { - iss = to_lower("The_Issuer") - aud = to_upper("The_Audience") - } } } `, "NoProfileForThisLabel", `{"sub":"12345"}`, - `missing jwt_signing_profile or jwt (with signing_ttl) for given label "NoProfileForThisLabel"`, + `missing jwt_signing_profile or jwt (with signing_ttl) block with referenced label "NoProfileForThisLabel"`, }, { "argument claims no object", ` - server "test" { - } + server {} definitions { jwt_signing_profile "MyToken" { signature_algorithm = "HS256" @@ -927,23 +911,18 @@ func TestJwtSignError(t *testing.T) { { "jwt / No profile for label", ` - server "test" { - } + server {} definitions { jwt "MySelfSignedToken" { signature_algorithm = "HS256" key = "$3cRe4" signing_ttl = "0" - claims = { - iss = to_lower("The_Issuer") - aud = to_upper("The_Audience") - } } } `, "NoProfileForThisLabel", `{"sub": "12345"}`, - `missing jwt_signing_profile or jwt (with signing_ttl) for given label "NoProfileForThisLabel"`, + `missing jwt_signing_profile or jwt (with signing_ttl) block with referenced label "NoProfileForThisLabel"`, }, { "jwt / bad curve for algorithm", @@ -984,8 +963,8 @@ func TestJwtSignError(t *testing.T) { subT.Error("expected an error, got nothing") return } - if !strings.Contains(err.Error(), tt.wantErr) { - subT.Errorf("Want:\t%q\nGot:\t%q", tt.wantErr, err.Error()) + if err.Error() != tt.wantErr { + subT.Errorf("\nWant:\t%q\nGot:\t%q", tt.wantErr, err.Error()) } }) } diff --git a/eval/lib/oauth2.go b/eval/lib/oauth2.go index 013b61733..4acf32a29 100644 --- a/eval/lib/oauth2.go +++ b/eval/lib/oauth2.go @@ -37,7 +37,7 @@ func NewOAuthAuthorizationURLFunction(ctx *hcl.EvalContext, oauth2s map[string]c label := args[0].AsString() oauth2, exist := oauth2s[label] if !exist { - return emptyStringVal, fmt.Errorf("undefined reference: %s", label) + return emptyStringVal, fmt.Errorf("missing oidc or beta_oauth2 block with referenced label %q", label) } authorizationEndpoint, err := oauth2.GetAuthorizationEndpoint() diff --git a/eval/lib/oauth2_test.go b/eval/lib/oauth2_test.go index 0fff843f6..a0ba620bd 100644 --- a/eval/lib/oauth2_test.go +++ b/eval/lib/oauth2_test.go @@ -14,6 +14,7 @@ import ( "github.com/avenga/couper/cache" "github.com/avenga/couper/config" "github.com/avenga/couper/config/configload" + "github.com/avenga/couper/config/request" "github.com/avenga/couper/config/runtime" "github.com/avenga/couper/eval" "github.com/avenga/couper/eval/lib" @@ -419,5 +420,70 @@ definitions { } }) } +} + +func TestOAuthAuthorizationURLError(t *testing.T) { + tests := []struct { + name string + config string + label string + wantErr string + }{ + { + "missing oidc/beta_oauth2 definitions", + ` + server {} + definitions { + } + `, + "MyLabel", + `missing oidc or beta_oauth2 block with referenced label "MyLabel"`, + }, + { + "missing referenced oidc/beta_oauth2", + ` + server {} + definitions { + beta_oauth2 "auth-ref" { + grant_type = "authorization_code" + client_id = "test-id" + client_secret = "test-s3cr3t" + authorization_endpoint = "https://a.s./auth" + token_endpoint = "https://a.s./token" + redirect_uri = "/cb" + verifier_method = "ccm_s256" + verifier_value = "asdf" + } + } + `, + "MyLabel", + `missing oidc or beta_oauth2 block with referenced label "MyLabel"`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(subT *testing.T) { + h := test.New(subT) + couperConf, err := configload.LoadBytes([]byte(tt.config), "test.hcl") + h.Must(err) + + ctx, cancel := context.WithCancel(couperConf.Context) + couperConf.Context = ctx + defer cancel() + evalContext := couperConf.Context.Value(request.ContextType).(*eval.Context) + req, err := http.NewRequest(http.MethodGet, "https://www.example.com/foo", nil) + h.Must(err) + evalContext = evalContext.WithClientRequest(req) + + _, err = evalContext.HCLContext().Functions[lib.FnOAuthAuthorizationURL].Call([]cty.Value{cty.StringVal(tt.label)}) + if err == nil { + subT.Error("expected an error, got nothing") + return + } + if err.Error() != tt.wantErr { + subT.Errorf("\nWant:\t%q\nGot:\t%q", tt.wantErr, err.Error()) + } + }) + } } diff --git a/eval/lib/saml.go b/eval/lib/saml.go index 8752436d5..132ad70ea 100644 --- a/eval/lib/saml.go +++ b/eval/lib/saml.go @@ -48,7 +48,7 @@ func NewSamlSsoURLFunction(configs []*config.SAML, origin *url.URL) function.Fun label := args[0].AsString() ent, exist := samlEntities[label] if !exist { - return cty.StringVal(""), fmt.Errorf("undefined reference: %s", label) + return cty.StringVal(""), fmt.Errorf("missing saml block with referenced label %q", label) } metadata := ent.descriptor diff --git a/eval/lib/saml_test.go b/eval/lib/saml_test.go index 97b543de3..6924e62f9 100644 --- a/eval/lib/saml_test.go +++ b/eval/lib/saml_test.go @@ -3,6 +3,7 @@ package lib_test import ( "bytes" "compress/flate" + "context" "encoding/base64" "encoding/xml" "io" @@ -15,6 +16,7 @@ import ( "github.com/avenga/couper/config/configload" "github.com/avenga/couper/config/request" + "github.com/avenga/couper/errors" "github.com/avenga/couper/eval" "github.com/avenga/couper/eval/lib" "github.com/avenga/couper/internal/test" @@ -25,7 +27,6 @@ func Test_SamlSsoURL(t *testing.T) { name string hcl string samlLabel string - wantErr bool wantPfx string }{ { @@ -43,54 +44,14 @@ func Test_SamlSsoURL(t *testing.T) { } `, "MySAML", - false, "https://idp.example.org/saml/SSOService", }, - { - "metadata not found", - ` - server "test" { - } - definitions { - saml "MySAML" { - idp_metadata_file = "not-there" - sp_entity_id = "the-sp" - sp_acs_url = "https://sp.example.com/saml/acs" - array_attributes = ["memberOf"] - } - } - `, - "MySAML", - true, - "", - }, - { - "label mismatch", - ` - server "test" { - } - definitions { - saml "MySAML" { - idp_metadata_file = "testdata/idp-metadata.xml" - sp_entity_id = "the-sp" - sp_acs_url = "https://sp.example.com/saml/acs" - array_attributes = ["memberOf"] - } - } - `, - "NotThere", - true, - "", - }, } for _, tt := range tests { t.Run(tt.name, func(subT *testing.T) { h := test.New(subT) cf, err := configload.LoadBytes([]byte(tt.hcl), "couper.hcl") if err != nil { - if tt.wantErr { - return - } h.Must(err) } @@ -100,16 +61,7 @@ func Test_SamlSsoURL(t *testing.T) { evalContext = evalContext.WithClientRequest(req) ssoURL, err := evalContext.HCLContext().Functions[lib.FnSamlSsoURL].Call([]cty.Value{cty.StringVal(tt.samlLabel)}) - if err == nil && tt.wantErr { - subT.Fatal("Error expected") - } - if err != nil { - if !tt.wantErr { - h.Must(err) - } else { - return - } - } + h.Must(err) if !strings.HasPrefix(ssoURL.AsString(), tt.wantPfx) { subT.Errorf("Expected to start with %q, got: %#v", tt.wantPfx, ssoURL.AsString()) @@ -136,5 +88,104 @@ func Test_SamlSsoURL(t *testing.T) { h.Must(err) }) } +} +func TestSamlConfigError(t *testing.T) { + tests := []struct { + name string + config string + label string + wantErr string + }{ + { + "missing referenced saml IdP metadata", + ` + server {} + definitions { + saml "MySAML" { + idp_metadata_file = "/not/there" + sp_entity_id = "the-sp" + sp_acs_url = "https://sp.example.com/saml/acs" + } + } + `, + "MyLabel", + "configuration error: MySAML: saml2 idp_metadata_file: read error: open /not/there: no such file or directory", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(subT *testing.T) { + _, err := configload.LoadBytes([]byte(tt.config), "test.hcl") + if err == nil { + subT.Error("expected an error, got nothing") + return + } + gErr := err.(errors.GoError) + if gErr.LogError() != tt.wantErr { + subT.Errorf("\nWant:\t%q\nGot:\t%q", tt.wantErr, gErr.LogError()) + } + }) + } +} + +func TestSamlSsoURLError(t *testing.T) { + tests := []struct { + name string + config string + label string + wantErr string + }{ + { + "missing saml definitions", + ` + server {} + definitions { + } + `, + "MyLabel", + `missing saml block with referenced label "MyLabel"`, + }, + { + "missing referenced saml", + ` + server {} + definitions { + saml "MySAML" { + idp_metadata_file = "testdata/idp-metadata.xml" + sp_entity_id = "the-sp" + sp_acs_url = "https://sp.example.com/saml/acs" + } + } + `, + "MyLabel", + `missing saml block with referenced label "MyLabel"`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(subT *testing.T) { + h := test.New(subT) + couperConf, err := configload.LoadBytes([]byte(tt.config), "test.hcl") + h.Must(err) + + ctx, cancel := context.WithCancel(couperConf.Context) + couperConf.Context = ctx + defer cancel() + + evalContext := couperConf.Context.Value(request.ContextType).(*eval.Context) + req, err := http.NewRequest(http.MethodGet, "https://www.example.com/foo", nil) + h.Must(err) + evalContext = evalContext.WithClientRequest(req) + + _, err = evalContext.HCLContext().Functions[lib.FnSamlSsoURL].Call([]cty.Value{cty.StringVal(tt.label)}) + if err == nil { + subT.Error("expected an error, got nothing") + return + } + if err.Error() != tt.wantErr { + subT.Errorf("\nWant:\t%q\nGot:\t%q", tt.wantErr, err.Error()) + } + }) + } } diff --git a/main_test.go b/main_test.go index 7f04acca0..24649c1a9 100644 --- a/main_test.go +++ b/main_test.go @@ -47,13 +47,12 @@ func Test_realmain(t *testing.T) { {"-f w/o file", []string{"couper", "run", "-f"}, nil, `level=error msg="flag needs an argument: -f" build=dev`, 1}, {"path from env", []string{"couper", "run", "-f", base + "/path_from_env.hcl"}, nil, `level=error msg="configuration error: token: jwt key: read error: open %s/public.pem: no such file or directory" build=dev`, 1}, {"path from env /w missing key", []string{"couper", "run", "-f", "public/couper.hcl", "-f", base + "/no_key_from_env.hcl"}, nil, "", 0}, - {"undefined AC", []string{"couper", "run", "-f", base + "/04_couper.hcl"}, nil, `level=error msg="accessControl is not defined: undefined" build=dev`, 1}, {"empty string in allowed_methods in endpoint", []string{"couper", "run", "-f", base + "/13_couper.hcl"}, nil, `level=error msg="%s/13_couper.hcl:3,5-27: method contains invalid character(s); " build=dev`, 1}, {"invalid method in allowed_methods in endpoint", []string{"couper", "run", "-f", base + "/14_couper.hcl"}, nil, `level=error msg="%s/14_couper.hcl:3,5-35: method contains invalid character(s); " build=dev`, 1}, {"invalid method in allowed_methods in api", []string{"couper", "run", "-f", base + "/15_couper.hcl"}, nil, `level=error msg="%s/15_couper.hcl:3,5-35: method contains invalid character(s); " build=dev`, 1}, {"rate_limit block in anonymous backend", []string{"couper", "run", "-f", base + "/17_couper.hcl"}, nil, `level=error msg="configuration error: anonymous_3_11: anonymous backend 'anonymous_3_11' cannot define 'beta_rate_limit' block(s)" build=dev`, 1}, {"non-string proxy reference", []string{"couper", "run", "-f", base + "/19_couper.hcl"}, nil, `level=error msg="%s/19_couper.hcl:3,13-14: proxy must evaluate to string; " build=dev`, 1}, - {"proxy reference does not exist", []string{"couper", "run", "-f", base + "/20_couper.hcl"}, nil, `level=error msg="%s/20_couper.hcl:3,14-17: proxy reference is not defined; " build=dev`, 1}, + {"proxy reference does not exist", []string{"couper", "run", "-f", base + "/20_couper.hcl"}, nil, `level=error msg="%s/20_couper.hcl:3,14-17: referenced proxy \"foo\" is not defined; " build=dev`, 1}, {"circular backend references", []string{"couper", "run", "-f", base + "/21_couper.hcl"}, nil, `level=error msg="configuration error: : configuration error; circular reference:`, 1}, } for _, tt := range tests {