Skip to content

Commit

Permalink
create SAML and OAuth URL creating functions with client request orig…
Browse files Browse the repository at this point in the history
…in to resolve relative URLs from config against it
  • Loading branch information
Johannes Koch committed Jun 29, 2021
1 parent dda8897 commit 807dd34
Show file tree
Hide file tree
Showing 10 changed files with 112 additions and 18 deletions.
8 changes: 7 additions & 1 deletion accesscontrol/oauth2.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,13 @@ func (oa *OAuth2Callback) Validate(req *http.Request) error {
}

requestConfig.Code = &code
requestConfig.RedirectURI = oa.config.RedirectURI

origin := eval.NewRawOrigin(req.URL)
absRedirectUri, err := lib.MakeUrlAbsolute(*oa.config.RedirectURI, origin)
if err != nil {
return err
}
requestConfig.RedirectURI = &absRedirectUri

tokenResponse, err := oa.oauth2.RequestToken(req.Context(), requestConfig)
if err != nil {
Expand Down
9 changes: 9 additions & 0 deletions accesscontrol/saml2.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (

"github.com/avenga/couper/config/request"
"github.com/avenga/couper/errors"
"github.com/avenga/couper/eval"
"github.com/avenga/couper/eval/lib"
)

type Saml2 struct {
Expand Down Expand Up @@ -80,6 +82,13 @@ func (s *Saml2) Validate(req *http.Request) error {
return err
}

origin := eval.NewRawOrigin(req.URL)
absAcsUrl, err := lib.MakeUrlAbsolute(s.sp.AssertionConsumerServiceURL, origin)
if err != nil {
return err
}
s.sp.AssertionConsumerServiceURL = absAcsUrl

encodedResponse := req.FormValue("SAMLResponse")
req.ContentLength = 0

Expand Down
27 changes: 16 additions & 11 deletions eval/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,6 @@ func (c *Context) WithClientRequest(req *http.Request) *Context {
saml: c.saml[:],
}

ctx.createOAuth2Functions()

if rc := req.Context(); rc != nil {
ctx.inner = context.WithValue(rc, ContextType, ctx)
}
Expand Down Expand Up @@ -128,6 +126,8 @@ func (c *Context) WithClientRequest(req *http.Request) *Context {
}
port, _ := strconv.ParseInt(p, 10, 64)
body, jsonBody := parseReqBody(req)

origin := NewRawOrigin(req.URL)
ctx.eval.Variables[ClientRequest] = cty.ObjectVal(ctxMap.Merge(ContextMap{
FormBody: seetie.ValuesMapToValue(parseForm(req).PostForm),
ID: cty.StringVal(id),
Expand All @@ -138,12 +138,14 @@ func (c *Context) WithClientRequest(req *http.Request) *Context {
PathParam: seetie.MapToValue(pathParams),
Query: seetie.ValuesMapToValue(req.URL.Query()),
URL: cty.StringVal(newRawURL(req.URL).String()),
Origin: cty.StringVal(newRawOrigin(req.URL).String()),
Origin: cty.StringVal(origin.String()),
Protocol: cty.StringVal(req.URL.Scheme),
Host: cty.StringVal(req.URL.Hostname()),
Port: cty.NumberIntVal(port),
}.Merge(newVariable(ctx.inner, req.Cookies(), req.Header))))

ctx.createClientRequestRelatedFunctions(origin)

updateFunctions(ctx)

return ctx
Expand Down Expand Up @@ -210,7 +212,7 @@ func (c *Context) WithJWTProfiles(profiles []*config.JWTSigningProfile) *Context
return c
}

// WithOAuth2 initially setup the lib.FnOAuthAuthorizationUrl function.
// WithOAuth2 initially setup the oauth2 configuration.
func (c *Context) WithOAuth2(o []*config.OAuth2AC) *Context {
c.oauth2 = o
if c.oauth2 == nil {
Expand All @@ -219,31 +221,34 @@ func (c *Context) WithOAuth2(o []*config.OAuth2AC) *Context {
return c
}

// WithSAML initially setup the lib.FnSamlSsoUrl function.
// WithSAML initially setup the saml configuration.
func (c *Context) WithSAML(s []*config.SAML) *Context {
c.saml = s
if c.saml == nil {
c.saml = make([]*config.SAML, 0)
}
samlfn := lib.NewSamlSsoUrlFunction(c.saml)
c.eval.Functions[lib.FnSamlSsoUrl] = samlfn
return c
}

func (c *Context) HCLContext() *hcl.EvalContext {
return c.eval
}

// createOAuth2Functions creates the listed OAuth2 functions for the client request context.
func (c *Context) createOAuth2Functions() {
// createClientRequestRelatedFunctions creates the listed functions for the client request context.
func (c *Context) createClientRequestRelatedFunctions(origin *url.URL) {
if c.oauth2 != nil {
oauth2fn := lib.NewOAuthAuthorizationUrlFunction(c.oauth2, c.getCodeVerifier)
oauth2fn := lib.NewOAuthAuthorizationUrlFunction(c.oauth2, c.getCodeVerifier, origin)
c.eval.Functions[lib.FnOAuthAuthorizationUrl] = oauth2fn
}
c.eval.Functions[lib.FnOAuthCodeVerifier] = lib.NewOAuthCodeVerifierFunction(c.getCodeVerifier)
c.eval.Functions[lib.FnOAuthCsrfToken] = c.eval.Functions[lib.FnOAuthCodeVerifier]
c.eval.Functions[lib.FnOAuthCodeChallenge] = lib.NewOAuthCodeChallengeFunction(c.getCodeVerifier)
c.eval.Functions[lib.FnOAuthHashedCsrfToken] = lib.NewOAuthHashedCsrfTokenFunction(c.getCodeVerifier)

if c.saml != nil {
samlfn := lib.NewSamlSsoUrlFunction(c.saml, origin)
c.eval.Functions[lib.FnSamlSsoUrl] = samlfn
}
}

func (c *Context) getCodeVerifier() (*pkce.CodeVerifier, error) {
Expand Down Expand Up @@ -350,7 +355,7 @@ func newRawURL(u *url.URL) *url.URL {
return &rawURL
}

func newRawOrigin(u *url.URL) *url.URL {
func NewRawOrigin(u *url.URL) *url.URL {
rawOrigin := *newRawURL(u)
rawOrigin.Path = ""
return &rawOrigin
Expand Down
8 changes: 6 additions & 2 deletions eval/lib/oauth2.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const (
CcmS256 = "S256"
)

func NewOAuthAuthorizationUrlFunction(oauth2Configs []*config.OAuth2AC, verifier func() (*pkce.CodeVerifier, error)) function.Function {
func NewOAuthAuthorizationUrlFunction(oauth2Configs []*config.OAuth2AC, verifier func() (*pkce.CodeVerifier, error), origin *url.URL) function.Function {
oauth2s := make(map[string]*config.OAuth2AC)
for _, o := range oauth2Configs {
oauth2s[o.Name] = o
Expand All @@ -47,7 +47,11 @@ func NewOAuthAuthorizationUrlFunction(oauth2Configs []*config.OAuth2AC, verifier
query := oauthAuthorizationUrl.Query()
query.Set("response_type", "code")
query.Set("client_id", oauth2.ClientID)
query.Set("redirect_uri", *oauth2.RedirectURI)
absRedirectUri, err := MakeUrlAbsolute(*oauth2.RedirectURI, origin)
if err != nil {
return cty.StringVal(""), err
}
query.Set("redirect_uri", absRedirectUri)
if oauth2.Scope != nil {
query.Set("scope", *oauth2.Scope)
}
Expand Down
22 changes: 20 additions & 2 deletions eval/lib/saml.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package lib
import (
"encoding/xml"
"fmt"
"net/url"

saml2 "github.com/russellhaering/gosaml2"
"github.com/russellhaering/gosaml2/types"
Expand All @@ -17,7 +18,7 @@ const (
NameIdFormatUnspecified = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
)

func NewSamlSsoUrlFunction(configs []*config.SAML) function.Function {
func NewSamlSsoUrlFunction(configs []*config.SAML, origin *url.URL) function.Function {
type entity struct {
config *config.SAML
descriptor *types.EntityDescriptor
Expand Down Expand Up @@ -68,8 +69,13 @@ func NewSamlSsoUrlFunction(configs []*config.SAML) function.Function {

nameIDFormat := getNameIDFormat(metadata.IDPSSODescriptor.NameIDFormats)

absAcsUrl, err := MakeUrlAbsolute(ent.config.SpAcsUrl, origin)
if err != nil {
return cty.StringVal(""), err
}

sp := &saml2.SAMLServiceProvider{
AssertionConsumerServiceURL: ent.config.SpAcsUrl,
AssertionConsumerServiceURL: absAcsUrl,
IdentityProviderSSOURL: ssoUrl,
ServiceProviderIssuer: ent.config.SpEntityId,
SignAuthnRequests: false,
Expand All @@ -88,6 +94,18 @@ func NewSamlSsoUrlFunction(configs []*config.SAML) function.Function {
})
}

func MakeUrlAbsolute(urlRef string, origin *url.URL) (string, error) {
u, err := url.Parse(urlRef)
if err != nil {
return "", err
}
if !u.IsAbs() {
return origin.ResolveReference(u).String(), nil
} else {
return urlRef, nil
}
}

func getNameIDFormat(supportedNameIDFormats []types.NameIDFormat) string {
nameIDFormat := ""
if isSupportedNameIDFormat(supportedNameIDFormats, NameIdFormatUnspecified) {
Expand Down
8 changes: 6 additions & 2 deletions eval/lib/saml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/base64"
"encoding/xml"
"io/ioutil"
"net/http"
"net/url"
"strings"
"testing"
Expand Down Expand Up @@ -92,9 +93,12 @@ func Test_SamlSsoUrl(t *testing.T) {
h.Must(err)
}

hclContext := cf.Context.Value(eval.ContextType).(*eval.Context).HCLContext()
evalContext := cf.Context.Value(eval.ContextType).(*eval.Context)
req, err := http.NewRequest(http.MethodGet, "https://www.example.com/foo", nil)
h.Must(err)
evalContext = evalContext.WithClientRequest(req)

ssoUrl, err := hclContext.Functions[lib.FnSamlSsoUrl].Call([]cty.Value{cty.StringVal(tt.samlLabel)})
ssoUrl, err := evalContext.HCLContext().Functions[lib.FnSamlSsoUrl].Call([]cty.Value{cty.StringVal(tt.samlLabel)})
if err == nil && tt.wantErr {
st.Fatal("Error expected")
}
Expand Down
6 changes: 6 additions & 0 deletions server/http_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2834,6 +2834,12 @@ func TestOAuthPKCEFunctions(t *testing.T) {
if auq.Get("client_id") != "foo" {
t.Errorf("oauth_authorization_url(): wrong client_id:\nactual:\t\t%s\nexpected:\t%s", auq.Get("client_id"), "foo")
}
au, err = url.Parse(res.Header.Get("x-au-pkce-rel"))
helper.Must(err)
auq = au.Query()
if auq.Get("redirect_uri") != "http://example.com:8080/oidc/callback" {
t.Errorf("oauth_authorization_url(): wrong redirect_uri query param:\nactual:\t\t%s\nexpected:\t%s", auq.Get("redirect_uri"), "http://example.com:8080/oidc/callback")
}

req, err = http.NewRequest(http.MethodGet, "http://example.com:8080/pkce-ok", nil)
helper.Must(err)
Expand Down
1 change: 1 addition & 0 deletions server/http_oauth2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ func TestOAuth2AccessControl(t *testing.T) {
{"code; client_secret_post", "05_couper.hcl", "/cb?code=qeuboub", http.Header{"Cookie": []string{"pkcecv=qerbnr"}}, http.StatusOK, "client_id=foo&client_secret=etbinbp4in&code=qeuboub&code_verifier=qerbnr&grant_type=authorization_code&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fcb", "", ""},
{"code, state param", "06_couper.hcl", "/cb?code=qeuboub&state=" + state, http.Header{"Cookie": []string{"st=" + st}}, http.StatusOK, "code=qeuboub&grant_type=authorization_code&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fcb", "Basic Zm9vOmV0YmluYnA0aW4=", ""},
{"code, nonce param", "07_couper.hcl", "/cb?code=qeuboub-id", http.Header{"Cookie": []string{"nnc=" + st}}, http.StatusOK, "code=qeuboub-id&grant_type=authorization_code&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fcb", "Basic Zm9vOmV0YmluYnA0aW4=", ""},
{"code; client_secret_basic; PKCE; relative redirect_uri", "08_couper.hcl", "/cb?code=qeuboub", http.Header{"Cookie": []string{"pkcecv=qerbnr"}, "X-Forwarded-Proto": []string{"https"}, "X-Forwarded-Host": []string{"www.example.com"}}, http.StatusOK, "code=qeuboub&code_verifier=qerbnr&grant_type=authorization_code&redirect_uri=https%3A%2F%2Fwww.example.com%2Fcb", "Basic Zm9vOmV0YmluYnA0aW4=", ""},
} {
t.Run(tc.path[1:], func(subT *testing.T) {
shutdown, hook := newCouperWithTemplate("testdata/oauth2/"+tc.filename, test.New(t), map[string]interface{}{"asOrigin": oauthOrigin.URL})
Expand Down
14 changes: 14 additions & 0 deletions server/testdata/integration/functions/02_couper.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ server "oauth-functions" {
x-ct-2 = beta_oauth_csrf_token()
x-cht = beta_oauth_hashed_csrf_token()
x-au-pkce = beta_oauth_authorization_url("ac-pkce")
x-au-pkce-rel = beta_oauth_authorization_url("ac-pkce-relative")
x-au-state = beta_oauth_authorization_url("ac-state")
x-au-nonce = beta_oauth_authorization_url("ac-nonce")
}
Expand Down Expand Up @@ -37,6 +38,19 @@ definitions {
code_verifier_value = "not_used_here"
}
}
beta_oauth2 "ac-pkce-relative" {
grant_type = "authorization_code"
authorization_endpoint = "https://authorization.server/oauth/authorize"
scope = "openid profile email"
token_endpoint = "https://authorization.server/oauth/token"
redirect_uri = "/oidc/callback"
client_id = "foo"
client_secret = "5eCr3t"
pkce {
code_challenge_method = "S256"
code_verifier_value = "not_used_here"
}
}
beta_oauth2 "ac-state" {
grant_type = "authorization_code"
authorization_endpoint = "https://authorization.server/oauth/authorize"
Expand Down
27 changes: 27 additions & 0 deletions server/testdata/oauth2/08_couper.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
server "client" {
api {
endpoint "/cb" {
access_control = ["ac"]
response {
json_body = request.context.ac
}
}
}
}
definitions {
beta_oauth2 "ac" {
grant_type = "authorization_code"
redirect_uri = "/cb" # value is not checked
authorization_endpoint = "https://authorization.server/oauth2/authorize"
token_endpoint = "{{.asOrigin}}/token"
client_id = "foo"
client_secret = "etbinbp4in"
pkce {
code_challenge_method = "S256"
code_verifier_value = request.cookies.pkcecv
}
}
}
settings {
accept_forwarded_url = [ "proto", "host" ]
}

0 comments on commit 807dd34

Please sign in to comment.