Skip to content

Commit

Permalink
refactor(server): simplify templating and url derivation (#4547)
Browse files Browse the repository at this point in the history
This refactors a few areas of the server templating and related functions.
  • Loading branch information
james-d-elliott committed Dec 17, 2022
1 parent 3de6936 commit d13247c
Show file tree
Hide file tree
Showing 15 changed files with 180 additions and 188 deletions.
7 changes: 7 additions & 0 deletions .yamllint.yml
@@ -1,6 +1,13 @@
---
extends: default

locale: en_US.UTF-8

yaml-files:
- '*.yaml'
- '*.yml'
- '.yamllint'

ignore: |
docs/pnpm-lock.yaml
internal/configuration/test_resources/config_bad_quoting.yml
Expand Down
8 changes: 1 addition & 7 deletions internal/handlers/handler_oidc_authorization.go
Expand Up @@ -52,13 +52,7 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr
return
}

if issuer, err = ctx.IssuerURL(); err != nil {
ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: error occurred determining issuer: %+v", requester.GetID(), clientID, err)

ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrIssuerCouldNotDerive)

return
}
issuer = ctx.RootURL()

userSession := ctx.GetSession()

Expand Down
7 changes: 1 addition & 6 deletions internal/handlers/handler_oidc_consent.go
Expand Up @@ -130,12 +130,7 @@ func OpenIDConnectConsentPOST(ctx *middlewares.AutheliaCtx) {
query url.Values
)

if redirectURI, err = ctx.IssuerURL(); err != nil {
ctx.Logger.Errorf("Failed to parse the consent redirect URL: %+v", err)
ctx.SetJSONError(messageOperationFailed)

return
}
redirectURI = ctx.RootURL()

if query, err = url.ParseQuery(consent.Form); err != nil {
ctx.Logger.Errorf("Failed to parse the consent form values: %+v", err)
Expand Down
16 changes: 2 additions & 14 deletions internal/handlers/handler_oidc_wellknown.go
Expand Up @@ -20,13 +20,7 @@ func OpenIDConnectConfigurationWellKnownGET(ctx *middlewares.AutheliaCtx) {
err error
)

if issuer, err = ctx.IssuerURL(); err != nil {
ctx.Logger.Errorf("Error occurred determining OpenID Connect issuer details: %+v", err)

ctx.ReplyStatusCode(fasthttp.StatusBadRequest)

return
}
issuer = ctx.RootURL()

wellKnown := ctx.Providers.OpenIDConnect.GetOpenIDConnectWellKnownConfiguration(issuer.String())

Expand All @@ -52,13 +46,7 @@ func OAuthAuthorizationServerWellKnownGET(ctx *middlewares.AutheliaCtx) {
err error
)

if issuer, err = ctx.IssuerURL(); err != nil {
ctx.Logger.Errorf("Error occurred determining OpenID Connect issuer details: %+v", err)

ctx.ReplyStatusCode(fasthttp.StatusBadRequest)

return
}
issuer = ctx.RootURL()

wellKnown := ctx.Providers.OpenIDConnect.GetOAuth2WellKnownConfiguration(issuer.String())

Expand Down
12 changes: 2 additions & 10 deletions internal/handlers/response.go
Expand Up @@ -144,11 +144,7 @@ func handleOIDCWorkflowResponseWithTargetURL(ctx *middlewares.AutheliaCtx, targe
return
}

if issuerURL, err = ctx.IssuerURL(); err != nil {
ctx.Error(fmt.Errorf("unable to get issuer for redirection: %w", err), messageAuthenticationFailed)

return
}
issuerURL = ctx.RootURL()

if targetURL.Host != issuerURL.Host {
ctx.Error(fmt.Errorf("unable to redirect to '%s': target host '%s' does not match expected issuer host '%s'", targetURL, targetURL.Host, issuerURL.Host), messageAuthenticationFailed)
Expand Down Expand Up @@ -221,11 +217,7 @@ func handleOIDCWorkflowResponseWithID(ctx *middlewares.AutheliaCtx, id string) {
form url.Values
)

if targetURL, err = ctx.IssuerURL(); err != nil {
ctx.Error(fmt.Errorf("unable to get issuer for redirection: %w", err), messageAuthenticationFailed)

return
}
targetURL = ctx.RootURL()

if form, err = consent.GetForm(); err != nil {
ctx.Error(fmt.Errorf("unable to get authorization form values from consent session with challenge id '%s': %w", consent.ChallengeID, err), messageAuthenticationFailed)
Expand Down
116 changes: 56 additions & 60 deletions internal/middlewares/authelia_context.go
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"net"
"net/url"
"path"
"strings"

"github.com/asaskevich/govalidator"
Expand Down Expand Up @@ -81,7 +80,7 @@ func (ctx *AutheliaCtx) ReplyError(err error, message string) {
ctx.Logger.Error(marshalErr)
}

ctx.SetContentTypeBytes(contentTypeApplicationJSON)
ctx.SetContentTypeApplicationJSON()
ctx.SetBody(b)
ctx.Logger.Debug(err)
}
Expand All @@ -90,7 +89,7 @@ func (ctx *AutheliaCtx) ReplyError(err error, message string) {
func (ctx *AutheliaCtx) ReplyStatusCode(statusCode int) {
ctx.Response.Reset()
ctx.SetStatusCode(statusCode)
ctx.SetContentTypeBytes(contentTypeTextPlain)
ctx.SetContentTypeTextPlain()
ctx.SetBodyString(fmt.Sprintf("%d %s", statusCode, fasthttp.StatusMessage(statusCode)))
}

Expand All @@ -108,7 +107,7 @@ func (ctx *AutheliaCtx) ReplyJSON(data any, statusCode int) (err error) {
ctx.SetStatusCode(statusCode)
}

ctx.SetContentTypeBytes(contentTypeApplicationJSON)
ctx.SetContentTypeApplicationJSON()
ctx.SetBody(body)

return nil
Expand Down Expand Up @@ -145,7 +144,7 @@ func (ctx *AutheliaCtx) XForwardedProto() (proto []byte) {
}

// XForwardedMethod return the content of the X-Forwarded-Method header.
func (ctx *AutheliaCtx) XForwardedMethod() (method []byte) {
func (ctx *AutheliaCtx) XForwardedMethod() []byte {
return ctx.RequestCtx.Request.Header.PeekBytes(headerXForwardedMethod)
}

Expand All @@ -171,79 +170,61 @@ func (ctx *AutheliaCtx) XForwardedURI() (uri []byte) {
return uri
}

// XAutheliaURL return the content of the X-Authelia-URL header.
func (ctx *AutheliaCtx) XAutheliaURL() (autheliaURL []byte) {
// XOriginalURL returns the content of the X-Original-URL header.
func (ctx *AutheliaCtx) XOriginalURL() []byte {
return ctx.RequestCtx.Request.Header.PeekBytes(headerXOriginalURL)
}

// XOriginalMethod return the content of the X-Original-Method header.
func (ctx *AutheliaCtx) XOriginalMethod() []byte {
return ctx.RequestCtx.Request.Header.PeekBytes(headerXOriginalMethod)
}

// XAutheliaURL return the content of the X-Authelia-URL header which is used to communicate the location of the
// portal when using proxies like Envoy.
func (ctx *AutheliaCtx) XAutheliaURL() []byte {
return ctx.RequestCtx.Request.Header.PeekBytes(headerXAutheliaURL)
}

// QueryArgRedirect return the content of the rd query argument.
func (ctx *AutheliaCtx) QueryArgRedirect() (val []byte) {
return ctx.RequestCtx.QueryArgs().PeekBytes(queryArgRedirect)
func (ctx *AutheliaCtx) QueryArgRedirect() []byte {
return ctx.RequestCtx.QueryArgs().PeekBytes(qryArgRedirect)
}

// BasePath returns the base_url as per the path visited by the client.
func (ctx *AutheliaCtx) BasePath() (base string) {
func (ctx *AutheliaCtx) BasePath() string {
if baseURL := ctx.UserValueBytes(UserValueKeyBaseURL); baseURL != nil {
return baseURL.(string)
}

return base
return ""
}

// ExternalRootURL gets the X-Forwarded-Proto, X-Forwarded-Host headers and the BasePath and forms them into a URL.
func (ctx *AutheliaCtx) ExternalRootURL() (string, error) {
protocol := ctx.XForwardedProto()
if protocol == nil {
return "", errMissingXForwardedProto
}

host := ctx.XForwardedHost()
if host == nil {
return "", errMissingXForwardedHost
}

externalRootURL := fmt.Sprintf("%s://%s", protocol, host)

if base := ctx.BasePath(); base != "" {
externalBaseURL, err := url.ParseRequestURI(externalRootURL)
if err != nil {
return "", err
}

externalBaseURL.Path = path.Join(externalBaseURL.Path, base)

return externalBaseURL.String(), nil
// BasePathSlash is the same as BasePath but returns a final slash as well.
func (ctx *AutheliaCtx) BasePathSlash() string {
if baseURL := ctx.UserValueBytes(UserValueKeyBaseURL); baseURL != nil {
return baseURL.(string) + strSlash
}

return externalRootURL, nil
return strSlash
}

// IssuerURL returns the expected Issuer.
func (ctx *AutheliaCtx) IssuerURL() (issuerURL *url.URL, err error) {
issuerURL = &url.URL{
Scheme: "https",
}

if scheme := ctx.XForwardedProto(); scheme != nil {
issuerURL.Scheme = string(scheme)
}

if host := ctx.XForwardedHost(); len(host) != 0 {
issuerURL.Host = string(host)
} else {
return nil, errMissingXForwardedHost
}

if base := ctx.BasePath(); base != "" {
issuerURL.Path = path.Join(issuerURL.Path, base)
// RootURL returns the Root URL.
func (ctx *AutheliaCtx) RootURL() (issuerURL *url.URL) {
return &url.URL{
Scheme: string(ctx.XForwardedProto()),
Host: string(ctx.XForwardedHost()),
Path: ctx.BasePath(),
}

return issuerURL, nil
}

// XOriginalURL return the content of the X-Original-URL header.
func (ctx *AutheliaCtx) XOriginalURL() []byte {
return ctx.RequestCtx.Request.Header.PeekBytes(headerXOriginalURL)
// RootURLSlash is the same as RootURL but includes a final slash as well.
func (ctx *AutheliaCtx) RootURLSlash() (issuerURL *url.URL) {
return &url.URL{
Scheme: string(ctx.XForwardedProto()),
Host: string(ctx.XForwardedHost()),
Path: ctx.BasePathSlash(),
}
}

// GetSession return the user session. Any update will be saved in cache.
Expand All @@ -264,7 +245,7 @@ func (ctx *AutheliaCtx) SaveSession(userSession session.UserSession) error {

// ReplyOK is a helper method to reply ok.
func (ctx *AutheliaCtx) ReplyOK() {
ctx.SetContentTypeBytes(contentTypeApplicationJSON)
ctx.SetContentTypeApplicationJSON()
ctx.SetBody(okMessageBytes)
}

Expand Down Expand Up @@ -377,7 +358,7 @@ func (ctx *AutheliaCtx) SpecialRedirect(uri string, statusCode int) {
statusCode = fasthttp.StatusFound
}

ctx.SetContentTypeBytes(contentTypeTextHTML)
ctx.SetContentTypeTextHTML()
ctx.SetStatusCode(statusCode)

u := fasthttp.AcquireURI()
Expand All @@ -400,3 +381,18 @@ func (ctx *AutheliaCtx) RecordAuthentication(success, regulated bool, method str

ctx.Providers.Metrics.RecordAuthentication(success, regulated, method)
}

// SetContentTypeTextPlain efficiently sets the Content-Type header to 'text/plain; charset=utf-8'.
func (ctx *AutheliaCtx) SetContentTypeTextPlain() {
ctx.SetContentTypeBytes(contentTypeTextPlain)
}

// SetContentTypeTextHTML efficiently sets the Content-Type header to 'text/html; charset=utf-8'.
func (ctx *AutheliaCtx) SetContentTypeTextHTML() {
ctx.SetContentTypeBytes(contentTypeTextHTML)
}

// SetContentTypeApplicationJSON efficiently sets the Content-Type header to 'application/json; charset=utf-8'.
func (ctx *AutheliaCtx) SetContentTypeApplicationJSON() {
ctx.SetContentTypeBytes(contentTypeApplicationJSON)
}
22 changes: 7 additions & 15 deletions internal/middlewares/authelia_context_test.go
Expand Up @@ -21,7 +21,6 @@ func TestIssuerURL(t *testing.T) {
name string
proto, host, base string
expected string
err string
}{
{
name: "Standard",
Expand All @@ -36,7 +35,7 @@ func TestIssuerURL(t *testing.T) {
{
name: "NoHost",
proto: "https", host: "", base: "",
err: "Missing header X-Forwarded-Host",
expected: "https:",
},
}

Expand All @@ -52,21 +51,14 @@ func TestIssuerURL(t *testing.T) {
mock.Ctx.SetUserValue("base_url", tc.base)
}

actual, err := mock.Ctx.IssuerURL()
actual := mock.Ctx.RootURL()

switch tc.err {
case "":
assert.NoError(t, err)
require.NotNil(t, actual)
require.NotNil(t, actual)

assert.Equal(t, tc.expected, actual.String())
assert.Equal(t, tc.proto, actual.Scheme)
assert.Equal(t, tc.host, actual.Host)
assert.Equal(t, tc.base, actual.Path)
default:
assert.EqualError(t, err, tc.err)
assert.Nil(t, actual)
}
assert.Equal(t, tc.expected, actual.String())
assert.Equal(t, tc.proto, actual.Scheme)
assert.Equal(t, tc.host, actual.Host)
assert.Equal(t, tc.base, actual.Path)
})
}
}
Expand Down
7 changes: 6 additions & 1 deletion internal/middlewares/const.go
Expand Up @@ -20,6 +20,7 @@ var (

headerXForwardedURI = []byte("X-Forwarded-URI")
headerXOriginalURL = []byte("X-Original-URL")
headerXOriginalMethod = []byte("X-Original-Method")
headerXForwardedMethod = []byte("X-Forwarded-Method")

headerVary = []byte(fasthttp.HeaderVary)
Expand Down Expand Up @@ -67,13 +68,17 @@ var (
const (
strProtoHTTPS = "https"
strProtoHTTP = "http"
strSlash = "/"

queryArgRedirect = "rd"
queryArgToken = "token"
)

var (
protoHTTPS = []byte(strProtoHTTPS)
protoHTTP = []byte(strProtoHTTP)

queryArgRedirect = []byte("rd")
qryArgRedirect = []byte(queryArgRedirect)

// UserValueKeyBaseURL is the User Value key where we store the Base URL.
UserValueKeyBaseURL = []byte("base_url")
Expand Down

0 comments on commit d13247c

Please sign in to comment.